Skip to content

Commit 0b70cd7

Browse files
authored
Feat/add progress (#18)
* bump fsrs-browser to 0.6.2 * update train in client-side * update train in server-side * update dependencies * Feat/add Train Progress * update own train * Fix/timezones react-hydration-error * Fix/not shown progress * Feat/enabled thread pool * set custom timezone * update training.png * value -> selected * Fix/unmatched state
1 parent 89e9f8e commit 0b70cd7

File tree

19 files changed

+409
-76
lines changed

19 files changed

+409
-76
lines changed

images/training.png

-8.87 KB
Loading

next.config.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,22 @@ const nextConfig = {
3434

3535
return config;
3636
},
37+
async headers() {
38+
return [
39+
{
40+
source: "/(.*)",
41+
headers: [
42+
{
43+
key: "Cross-Origin-Embedder-Policy",
44+
value: "require-corp",
45+
},
46+
{
47+
key: "Cross-Origin-Opener-Policy",
48+
value: "same-origin",
49+
},
50+
],
51+
},
52+
];
53+
}
3754
};
3855
module.exports = nextConfig;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"@vercel/speed-insights": "^1.0.2",
2222
"bcrypt": "^5.1.1",
2323
"clsx": "^2.0.0",
24-
"fsrs-browser": "^0.6.1",
24+
"fsrs-browser": "^0.6.2",
2525
"next": "14.1.0",
2626
"next-auth": "^4.24.5",
2727
"next-themes": "^0.2.1",

pnpm-lock.yaml

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

public/fsrs_browser_bg.wasm

57.6 KB
Binary file not shown.

src/app/api/fsrs/train/route.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
import { NextRequest, NextResponse } from "next/server";
22
import { Readable } from "stream";
33
import { getProcessW, loadCsvAndTrain } from "./train";
4+
import { computerMinuteOffset } from "@/lib/date";
45

56
export async function GET(request: NextRequest) {
67
const url = new URL(request.url);
78
const searchParams = new URLSearchParams(url.search);
89

910
const fileURL = searchParams.get("url") ?? `${url.origin}/revlog.csv`;
11+
const timezone = searchParams.get("timezone") ?? "UTC";
12+
const nextDayStart = searchParams.get("nextDayStart") ?? "4";
13+
const offset = computerMinuteOffset(timezone, Number(nextDayStart));
1014
const stream = await getReadStream(fileURL);
1115

1216
const wasmURL = new URL("./fsrs_browser_bg.wasm", url.origin);
13-
const { w, ...others } = await loadCsvAndTrain(wasmURL, stream);
14-
17+
const { w, ...others } = await loadCsvAndTrain(wasmURL, stream, offset);
1518

1619
return NextResponse.json({
1720
data: fileURL,
@@ -30,11 +33,11 @@ export async function POST(request: NextRequest) {
3033
const filename = file.name.replaceAll(" ", "_");
3134
const buffer = Buffer.from(await file.arrayBuffer());
3235
const stream = Readable.from(buffer);
33-
const wasmURL = new URL(
34-
"fsrs_browser_bg.wasm",
35-
new URL(request.url).origin
36-
);
37-
const { w, ...others } = await loadCsvAndTrain(wasmURL, stream);
36+
const wasmURL = new URL("fsrs_browser_bg.wasm", new URL(request.url).origin);
37+
const timezone = formData.get("timezone") ?? "UTC";
38+
const nextDayStart = formData.get("nextDayStart") ?? "4";
39+
const offset = computerMinuteOffset(String(timezone), Number(nextDayStart));
40+
const { w, ...others } = await loadCsvAndTrain(wasmURL, stream, offset);
3841

3942
return NextResponse.json({
4043
fileName: filename,

src/app/api/fsrs/train/train.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ Error.stackTraceLimit = 30;
1010
// self.postMessage(result);
1111
// };
1212

13-
export async function loadCsvAndTrain(wasmURL: URL, file: papa.LocalFile) {
13+
export async function loadCsvAndTrain(
14+
wasmURL: URL,
15+
file: papa.LocalFile,
16+
minute_offset: number
17+
) {
1418
const cids: bigint[] = [];
1519
const eases: number[] = [];
1620
const ids: bigint[] = [];
@@ -28,11 +32,12 @@ export async function loadCsvAndTrain(wasmURL: URL, file: papa.LocalFile) {
2832
eases.push(Number(data.review_rating));
2933
types.push(Number(data.review_state));
3034
},
31-
complete:async function () {
35+
complete: async function () {
3236
const loadEndTime = performance.now();
3337
const trainStartTime = performance.now();
3438
const w = await computeParameters(
3539
wasmURL,
40+
minute_offset,
3641
new BigInt64Array(cids),
3742
new Uint8Array(eases),
3843
new BigInt64Array(ids),
@@ -56,6 +61,7 @@ export async function loadCsvAndTrain(wasmURL: URL, file: papa.LocalFile) {
5661

5762
export async function computeParameters(
5863
wasmURL: URL,
64+
minute_offset: number,
5965
cids: BigInt64Array,
6066
eases: Uint8Array,
6167
ids: BigInt64Array,
@@ -65,14 +71,19 @@ export async function computeParameters(
6571
// await initThreadPool(cpus().length);
6672
let fsrs = new Fsrs();
6773
console.time("full training time");
68-
let parameters = fsrs.computeParametersAnki(cids, eases, ids, types);
74+
let parameters = fsrs.computeParametersAnki(
75+
minute_offset,
76+
cids,
77+
eases,
78+
ids,
79+
types
80+
);
6981
console.timeEnd("full training time");
7082
fsrs.free();
7183
console.log(parameters);
7284
return parameters;
7385
}
7486

75-
7687
export function getProcessW(w: Float32Array) {
7788
const processed_w = [];
7889
for (let i = 0; i < w.length; i++) {

src/app/api/fsrs/train/type.d.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,26 @@ interface TrainResult {
1212
trainTime: string;
1313
totalTime: string;
1414
count: number;
15-
}
15+
}
16+
17+
interface ProgressStart {
18+
tag: "start";
19+
wasmMemoryBuffer: ArrayBuffer;
20+
pointer: number;
21+
}
22+
23+
interface ProgressFinish {
24+
tag: "finish";
25+
parameters: Float32Array;
26+
}
27+
28+
interface ProgressItem {
29+
itemsProcessed: number;
30+
itemsTotal: number;
31+
}
32+
33+
interface Progress extends ProgressItem {
34+
tag: "progress";
35+
}
36+
37+
type ProgressState = Progress | ProgressStart | ProgressFinish;

src/app/train/page.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import TrainDisplay from "@/components/train/display";
22
import FileTrain from "@/components/train/file-train-button";
3+
import NextDayStartAt from "@/components/train/nextDayStartAt";
34
import OwnTrain from "@/components/train/own-train";
4-
import TrainTestButton from "@/components/train/test-button";
5+
import TrainProgress from "@/components/train/progress";
6+
import TimezoneSelector from "@/components/train/timezones";
57
import TrainProvider from "@/context/TrainContext";
68
export default async function Page() {
79
return (
810
<div className="flex justify-center flex-col items-center">
911
<TrainProvider>
12+
<TimezoneSelector />
13+
<NextDayStartAt />
1014
<OwnTrain />
1115
<FileTrain />
1216
<TrainDisplay />
13-
<TrainTestButton/>
17+
<TrainProgress />
1418
</TrainProvider>
1519
</div>
1620
);

src/components/train/file-train-button.tsx

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,32 @@
22

33
import { getProcessW } from "@/app/api/fsrs/train/train";
44
import { useTrainContext } from "@/context/TrainContext";
5+
import { computerMinuteOffset } from "@/lib/date";
56
import { useEffect, useRef } from "react";
67

78
export default function FileTrain() {
89
const workerRef = useRef<Worker>();
9-
const { loading, setLoading, setW, setLoadTime, setTrainTime, setTotalTime } =
10-
useTrainContext();
10+
const timeIdRef = useRef<NodeJS.Timeout>();
11+
const {
12+
loading,
13+
setLoading,
14+
setW,
15+
setLoadTime,
16+
setTrainTime,
17+
setTotalTime,
18+
timezone,
19+
nextDayStart,
20+
handleProgress,
21+
} = useTrainContext();
1122

1223
const handleClick = async (e: React.ChangeEvent<HTMLInputElement>) => {
1324
if (!e.target.files) {
1425
return;
1526
}
1627
setLoading(true);
1728
const file = e.target.files[0];
18-
workerRef.current?.postMessage(file);
29+
const offset = computerMinuteOffset(timezone, nextDayStart);
30+
workerRef.current?.postMessage({ file, offset });
1931
if (e.target.value) {
2032
e.target.value = "";
2133
}
@@ -25,13 +37,32 @@ export default function FileTrain() {
2537
workerRef.current = new Worker(
2638
new URL("./fsrs_worker.ts", import.meta.url)
2739
);
28-
workerRef.current.onmessage = (event: MessageEvent<TrainResult>) => {
29-
console.log(event.data)
30-
setW(getProcessW(event.data.w));
31-
setLoadTime(event.data.loadTime)
32-
setTrainTime(event.data.trainTime);
33-
setTotalTime(event.data.totalTime);
34-
setLoading(false);
40+
workerRef.current.onmessage = (
41+
event: MessageEvent<TrainResult | ProgressState>
42+
) => {
43+
console.log(event.data);
44+
if ("tag" in event.data) {
45+
// process ProgressState
46+
const progressState = event.data as ProgressState;
47+
if (progressState.tag === "start") {
48+
const { wasmMemoryBuffer, pointer } = progressState;
49+
handleProgress(wasmMemoryBuffer, pointer);
50+
timeIdRef.current = setInterval(() => {
51+
handleProgress(wasmMemoryBuffer, pointer);
52+
}, 100);
53+
} else if (progressState.tag === "finish") {
54+
clearInterval(timeIdRef.current);
55+
console.log("finish");
56+
}
57+
} else {
58+
// process TrainResult
59+
const trainResult = event.data as TrainResult;
60+
setW(getProcessW(trainResult.w));
61+
setLoadTime(trainResult.loadTime);
62+
setTrainTime(trainResult.trainTime);
63+
setTotalTime(trainResult.totalTime);
64+
setLoading(false);
65+
}
3566
};
3667
return () => {
3768
workerRef.current?.terminate();

0 commit comments

Comments
 (0)