Skip to content

Commit 80ff35d

Browse files
committed
Feat: basic infer fsrs func
1 parent 8b25545 commit 80ff35d

File tree

4 files changed

+258
-1
lines changed

4 files changed

+258
-1
lines changed

popup.html

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,33 @@ <h4 style="margin: 0; font-size: 0.95em; line-height: 1;">Active Reminder</h4>
518518
<small style="display: block; font-size: 0.8em;">2. Disable to stop all pop-up reminders</small>
519519
</div>
520520
</div>
521+
522+
<!-- FSRS参数优化卡片 -->
523+
<div class="option-card" style="padding: 12px;">
524+
<div style="display: flex; align-items: center; justify-content: space-between;">
525+
<div style="display: flex; align-items: center; gap: 12px;">
526+
<i class="fas fa-cogs" style="font-size: 0.95em; line-height: 1; position: relative; top: 7px;"></i>
527+
<h4 style="margin: 0; font-size: 0.95em; line-height: 1;">FSRS参数优化</h4>
528+
</div>
529+
<div style="display: flex; gap: 8px;">
530+
<button id="exportRevlogsBtn" class="btn btn-sm btn-outline-secondary" style="font-size: 0.8em;">
531+
导出记录
532+
</button>
533+
<button id="optimizeParamsBtn" class="btn btn-sm btn-primary" style="font-size: 0.8em;">
534+
优化参数
535+
</button>
536+
</div>
537+
</div>
538+
539+
<div class="fsrs-info" style="padding: 8px; border-radius: 4px;">
540+
<small style="display: block; margin-bottom: 2px; font-size: 0.8em;">
541+
当前复习记录数: <span id="revlogCount">0</span>
542+
</small>
543+
<small style="display: block; font-size: 0.8em;">
544+
点击按钮优化FSRS算法参数
545+
</small>
546+
</div>
547+
</div>
521548

522549

523550

src/popup/daily-review.js

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {handleAddProblem} from "./script/submission.js"
1212
import Swal from 'sweetalert2';
1313
// 导入 getAllRevlogs 函数
1414
import { getAllRevlogs, exportRevlogsToCSV } from './util/fsrs.js';
15+
import { getRevlogCount, optimizeParameters } from './service/fsrsService.js';
1516

1617
// 在文件开头添加
1718
const LAST_AVERAGE_KEY = 'lastRetrievabilityAverage';
@@ -783,6 +784,94 @@ function initializeAddProblem() {
783784
}
784785
}
785786

787+
// 显示弹窗函数
788+
function showModal(title, content) {
789+
Swal.fire({
790+
title: title,
791+
html: content,
792+
background: '#1d2e3d',
793+
color: '#ffffff',
794+
confirmButtonColor: '#4a9d9c',
795+
width: '600px'
796+
});
797+
}
798+
799+
// 初始化FSRS参数优化卡片
800+
async function initializeFSRSOptimization() {
801+
try {
802+
// 获取并显示复习记录数量
803+
const count = await getRevlogCount();
804+
const revlogCountElement = document.getElementById('revlogCount');
805+
if (revlogCountElement) {
806+
revlogCountElement.textContent = count;
807+
}
808+
809+
// 添加优化按钮点击事件
810+
const optimizeParamsBtn = document.getElementById('optimizeParamsBtn');
811+
if (optimizeParamsBtn) {
812+
optimizeParamsBtn.addEventListener('click', async () => {
813+
// 将originalText变量移到try块之外
814+
const originalText = optimizeParamsBtn.textContent || '优化参数';
815+
816+
try {
817+
// 显示加载中提示
818+
optimizeParamsBtn.disabled = true;
819+
optimizeParamsBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 优化中...';
820+
821+
// 创建进度显示元素
822+
const progressContainer = document.createElement('div');
823+
progressContainer.className = 'progress mt-2';
824+
progressContainer.style.height = '5px';
825+
progressContainer.innerHTML = `
826+
<div class="progress-bar progress-bar-striped progress-bar-animated"
827+
role="progressbar"
828+
style="width: 0%"
829+
aria-valuenow="0"
830+
aria-valuemin="0"
831+
aria-valuemax="100">
832+
</div>
833+
`;
834+
optimizeParamsBtn.parentNode.appendChild(progressContainer);
835+
836+
// 进度回调函数
837+
const onProgress = (progress) => {
838+
console.log('Progress update:', progress);
839+
const percent = Math.round(progress.percent * 100);
840+
const progressBar = progressContainer.querySelector('.progress-bar');
841+
if (progressBar) {
842+
progressBar.style.width = `${percent}%`;
843+
progressBar.setAttribute('aria-valuenow', percent);
844+
progressBar.textContent = `${percent}%`;
845+
}
846+
};
847+
848+
// 调用优化API
849+
const result = await optimizeParameters(onProgress);
850+
851+
// 移除进度显示元素
852+
progressContainer.remove();
853+
854+
// 显示结果弹窗
855+
showModal('FSRS参数优化结果', `
856+
<div style="max-height: 300px; overflow-y: auto;">
857+
<pre style="white-space: pre-wrap; word-break: break-all;">${JSON.stringify(result, null, 2)}</pre>
858+
</div>
859+
`);
860+
} catch (error) {
861+
console.error('Error optimizing FSRS parameters:', error);
862+
showModal('错误', `优化参数时发生错误: ${error.message}`);
863+
} finally {
864+
// 恢复按钮状态
865+
optimizeParamsBtn.disabled = false;
866+
optimizeParamsBtn.textContent = originalText;
867+
}
868+
});
869+
}
870+
} catch (error) {
871+
console.error('Error initializing FSRS optimization:', error);
872+
}
873+
}
874+
786875
// 添加设置相关的初始化函数
787876
async function initializeOptions() {
788877
await loadConfigs();
@@ -818,7 +907,10 @@ async function initializeOptions() {
818907
if (reminderToggle) {
819908
reminderToggle.checked = store.isReminderEnabled || false;
820909
}
821-
910+
911+
// 初始化FSRS参数优化卡片
912+
await initializeFSRSOptimization();
913+
822914
// 修改保存成功提示
823915
optionsForm.addEventListener('submit', async e => {
824916
e.preventDefault();

src/popup/delegate/fsrsDelegate.js

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// FSRS参数优化相关的API请求处理
2+
export const optimizeFSRSParams = async (csvContent, onProgress) => {
3+
try {
4+
const formData = new FormData();
5+
const csvBlob = new Blob([csvContent], { type: 'text/csv' });
6+
formData.append('file', csvBlob, 'revlog.csv');
7+
formData.append('sse', '1');
8+
formData.append('hour_offset', '4');
9+
formData.append('enable_short_term', '0');
10+
formData.append('timezone', 'Asia/Shanghai');
11+
12+
const response = await fetch('https://ishiko732-fsrs-online-training.hf.space/api/train', {
13+
method: 'POST',
14+
body: formData
15+
});
16+
17+
if (!response.ok) {
18+
throw new Error(`HTTP error! status: ${response.status}`);
19+
}
20+
21+
// 手动解析SSE响应
22+
const reader = response.body.getReader();
23+
const decoder = new TextDecoder();
24+
let result = null;
25+
let lastProgress = null;
26+
let doneParams = null;
27+
28+
while (true) {
29+
const { done, value } = await reader.read();
30+
if (done) break;
31+
32+
const chunk = decoder.decode(value, { stream: true });
33+
const lines = chunk.split('\n');
34+
35+
// 处理SSE响应
36+
for (let i = 0; i < lines.length; i++) {
37+
const line = lines[i];
38+
39+
// 处理事件类型
40+
if (line.startsWith('event: ')) {
41+
const eventType = line.substring(7);
42+
console.log('事件类型:', eventType);
43+
44+
// 查找下一个data行
45+
let dataLine = '';
46+
for (let j = i + 1; j < lines.length; j++) {
47+
if (lines[j].startsWith('data: ')) {
48+
dataLine = lines[j];
49+
break;
50+
}
51+
}
52+
53+
if (dataLine) {
54+
try {
55+
const data = JSON.parse(dataLine.substring(6));
56+
57+
// 处理进度信息
58+
if (eventType === 'progress') {
59+
lastProgress = data;
60+
// 如果提供了进度回调函数,则调用它
61+
if (onProgress) {
62+
onProgress(data);
63+
}
64+
}
65+
66+
// 处理完成事件
67+
if (eventType === 'done') {
68+
doneParams = data;
69+
console.log('捕获到done事件中的参数:', doneParams);
70+
}
71+
72+
// 处理训练结果
73+
if (eventType === 'info' && data.type === 'Train') {
74+
result = data;
75+
}
76+
} catch (e) {
77+
console.warn('Error parsing SSE data:', e, dataLine);
78+
}
79+
}
80+
}
81+
}
82+
}
83+
84+
// 优先返回done标签中的参数
85+
if (doneParams) {
86+
return doneParams;
87+
}
88+
89+
// 如果没有获取到done参数,但有进度信息,则返回进度信息
90+
if (!result && lastProgress) {
91+
result = {
92+
type: 'Progress',
93+
progress: lastProgress
94+
};
95+
}
96+
97+
return result || { type: 'Error', message: 'No result received' };
98+
} catch (error) {
99+
console.error('Error optimizing FSRS parameters:', error);
100+
throw error;
101+
}
102+
};

src/popup/service/fsrsService.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { getAllRevlogs, exportRevlogsToCSV } from '../util/fsrs.js';
2+
import { optimizeFSRSParams } from '../delegate/fsrsDelegate.js';
3+
4+
// 获取复习记录数量
5+
export const getRevlogCount = async () => {
6+
try {
7+
const allRevlogs = await getAllRevlogs();
8+
let totalCount = 0;
9+
10+
// 计算所有卡片的复习记录总数
11+
Object.values(allRevlogs).forEach(cardRevlogs => {
12+
totalCount += cardRevlogs.length;
13+
});
14+
15+
return totalCount;
16+
} catch (error) {
17+
console.error('Error getting revlog count:', error);
18+
return 0;
19+
}
20+
};
21+
22+
// 优化FSRS参数
23+
export const optimizeParameters = async (onProgress) => {
24+
try {
25+
// 获取并导出CSV格式的复习记录
26+
const csvContent = await exportRevlogsToCSV();
27+
28+
// 调用API进行参数优化
29+
const result = await optimizeFSRSParams(csvContent, onProgress);
30+
31+
return result;
32+
} catch (error) {
33+
console.error('Error optimizing parameters:', error);
34+
throw error;
35+
}
36+
};

0 commit comments

Comments
 (0)