Skip to content

Commit 73d1d6b

Browse files
committed
feat(notes,assistant,ai-gateway): 修复内测三缺陷并批量落地创新功能增强
内测缺陷修复: - fix(assistant): 学伴消息有概率发不出——chat 限流 20→100、chat 豁免全局 daily_total、getAuthToken 增加 token 过期主动刷新、语音文本流式中回填输入框 - fix(notes): 切换标题(字号)后保留 textAlign,避免重置排版 - fix(notes): 光标在表格内拦截 insertTable,杜绝无限嵌套表格 - test(notes): 新增 EditorToolbar 回归测试 4 例 创新功能增强(与修复文件存在耦合,一并提交): - feat(ai-gateway): 新增 quiz_gen/content_tier/conflict_detect/concept_precheck 四条链路、路由与提示词,并注册限流映射 - feat(notes): 笔记健康度指示器、内容分层弹窗、迷你测试、合书测试、概念冲突检测 - feat(feynman): 概念预检卡片、录音器、知识图谱页 - feat(flashcards): 恢复包面板(RecoveryPack) - feat(pomodoro): 锚点提醒、能量建议条、节奏引擎 - feat(website): FAQ 页面与导航/页脚更新 验证:客户端 vitest 731/731、tsc+vite 构建、oxlint 0 errors、网关 pytest 181 passed
1 parent e7fce42 commit 73d1d6b

128 files changed

Lines changed: 7252 additions & 191 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎client/.gitignore‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ lerna-debug.log*
1616
node_modules
1717
dist
1818
dist-ssr
19+
# Electron 主进程编译产物目录(electron-builder 打包输出,不提交到仓库)
20+
dist-electron/
1921
*.local
2022

2123
# Editor directories and files
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/**
2+
* AI 概念预检 Handler(E1 错误概念先破后立)
3+
*
4+
* 处理 ai_concept_precheck IPC 请求,调用 AI 网关为费曼讲解前
5+
* 生成 1-2 个错误概念探测问题。
6+
*
7+
* @ai-context: 概念预检 IPC handler——AIFeatureDef 注册表模式,经 callWithLocalFallback 支持本地 Ollama 优先/云端网关降级;本地降级同样走 JSON 生成。
8+
*/
9+
10+
import { safeHandle } from '../../ipcUtils.js';
11+
import { logger } from '../../logger.js';
12+
import { callWithLocalFallback, gatewayUrl, type AIFeatureDef } from '../utils.js';
13+
import { generateText } from '../ollama/OllamaProvider.js';
14+
15+
// ================================================================
16+
// IPC Handler
17+
// ================================================================
18+
19+
/**
20+
* ai_concept_precheck — POST /api/v1/ai/concept-precheck
21+
*/
22+
function register(): void {
23+
safeHandle(
24+
'ai_concept_precheck',
25+
async (
26+
_event,
27+
args: {
28+
concept: string;
29+
weakHistory?: string;
30+
authToken?: string;
31+
},
32+
) => {
33+
const startMs = Date.now();
34+
logger.info(`[AI] [conceptPrecheck] IPC received: concept=${args.concept.slice(0, 50)}, weak_len=${(args.weakHistory ?? '').length}, hasAuth=${!!args.authToken}`);
35+
36+
const reqBody = {
37+
concept: args.concept.slice(0, 500),
38+
weakHistory: (args.weakHistory ?? '').slice(0, 2000),
39+
};
40+
41+
logger.info(`[AI] [conceptPrecheck] Target: ${gatewayUrl()}/api/v1/ai/concept-precheck`);
42+
43+
interface PrecheckQuestionResp {
44+
question: string;
45+
intent: string;
46+
}
47+
interface ConceptPrecheckResp {
48+
questions: PrecheckQuestionResp[];
49+
model: string;
50+
tokens_used: number;
51+
}
52+
53+
try {
54+
const localHandler = async (): Promise<ConceptPrecheckResp> => {
55+
const prompt = `针对概念「${reqBody.concept}」设计 1-2 个探测性问题,暴露学习者常见误解(非知识记忆考察),仅返回JSON: {"questions": [{"question": "...", "intent": "..."}]}\n\n【学习者历史薄弱点】\n${reqBody.weakHistory || '(暂无)'}`;
56+
const result = await generateText(prompt, '你是一位善于发现学习误区的热心教练。请仅返回JSON。', { temperature: 0.5, maxTokens: 1000 });
57+
const parsed = JSON.parse(result.content);
58+
return {
59+
questions: parsed.questions ?? [],
60+
model: result.model,
61+
tokens_used: result.tokens_used,
62+
};
63+
};
64+
65+
const { data: resp, source, requestId } = await callWithLocalFallback<typeof reqBody, ConceptPrecheckResp>(
66+
'/api/v1/ai/concept-precheck',
67+
reqBody,
68+
localHandler,
69+
args.authToken,
70+
60000,
71+
);
72+
73+
const elapsed = Date.now() - startMs;
74+
logger.info(`[AI] [conceptPrecheck] ✔ Success (${source}): questions=${resp.questions.length}, model=${resp.model}, total=${elapsed}ms, reqId=${requestId ?? 'N/A'}`);
75+
return {
76+
questions: resp.questions.map((q) => ({
77+
question: q.question,
78+
intent: q.intent ?? '',
79+
})),
80+
model: resp.model,
81+
tokensUsed: resp.tokens_used,
82+
requestId,
83+
source,
84+
};
85+
} catch (err) {
86+
const elapsed = Date.now() - startMs;
87+
const error = err instanceof Error ? err : new Error(String(err));
88+
logger.error(`[AI] [conceptPrecheck] ✖ Failed after ${elapsed}ms: ${error.message}`);
89+
if (error.cause) logger.error(`[AI] [conceptPrecheck] Error cause: ${error.cause}`);
90+
throw error;
91+
}
92+
},
93+
);
94+
}
95+
96+
// ================================================================
97+
// 功能定义导出
98+
// ================================================================
99+
100+
export const feature: AIFeatureDef = {
101+
id: 'ai_concept_precheck',
102+
name: 'AI 概念预检',
103+
version: '1.0.0',
104+
register,
105+
};
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* AI 概念冲突检测 Handler(N6)
3+
*
4+
* 处理 ai_conflict_detect IPC 请求,调用 AI 网关比对新笔记与
5+
* 历史理解(旧笔记/费曼讲解摘录)之间的矛盾冲突。
6+
*
7+
* @ai-context: 概念冲突检测 IPC handler——AIFeatureDef 注册表模式,经 callWithLocalFallback 支持本地 Ollama 优先/云端网关降级;本地降级同样走 JSON 生成。
8+
*/
9+
10+
import { safeHandle } from '../../ipcUtils.js';
11+
import { logger } from '../../logger.js';
12+
import { callWithLocalFallback, gatewayUrl, type AIFeatureDef } from '../utils.js';
13+
import { generateText } from '../ollama/OllamaProvider.js';
14+
15+
// ================================================================
16+
// IPC Handler
17+
// ================================================================
18+
19+
/**
20+
* ai_conflict_detect — POST /api/v1/ai/conflict-detect
21+
*/
22+
function register(): void {
23+
safeHandle(
24+
'ai_conflict_detect',
25+
async (
26+
_event,
27+
args: {
28+
newNoteText: string;
29+
historyText: string;
30+
authToken?: string;
31+
},
32+
) => {
33+
const startMs = Date.now();
34+
logger.info(`[AI] [conflictDetect] IPC received: new_len=${args.newNoteText.length}, history_len=${args.historyText.length}, hasAuth=${!!args.authToken}`);
35+
36+
const reqBody = {
37+
newNoteText: args.newNoteText.slice(0, 3000),
38+
historyText: args.historyText.slice(0, 3000),
39+
};
40+
41+
logger.info(`[AI] [conflictDetect] Target: ${gatewayUrl()}/api/v1/ai/conflict-detect`);
42+
43+
interface ConflictItemResp {
44+
old_claim: string;
45+
new_claim: string;
46+
topic: string;
47+
suggestion: string;
48+
}
49+
interface ConflictDetectResp {
50+
conflicts: ConflictItemResp[];
51+
model: string;
52+
tokens_used: number;
53+
}
54+
55+
try {
56+
const localHandler = async (): Promise<ConflictDetectResp> => {
57+
const prompt = `分析新笔记与历史理解之间的概念冲突(只报告真正的矛盾),仅返回JSON: {"conflicts": [{"old_claim": "...", "new_claim": "...", "topic": "...", "suggestion": "..."}]}\n\n【新笔记】\n${reqBody.newNoteText}\n\n【历史理解】\n${reqBody.historyText}`;
58+
const result = await generateText(prompt, '你是一位概念转变研究专家,擅长识别学习者新旧理解之间的矛盾。请仅返回JSON。', { temperature: 0.3, maxTokens: 2000 });
59+
const parsed = JSON.parse(result.content);
60+
return {
61+
conflicts: parsed.conflicts ?? [],
62+
model: result.model,
63+
tokens_used: result.tokens_used,
64+
};
65+
};
66+
67+
const { data: resp, source, requestId } = await callWithLocalFallback<typeof reqBody, ConflictDetectResp>(
68+
'/api/v1/ai/conflict-detect',
69+
reqBody,
70+
localHandler,
71+
args.authToken,
72+
60000,
73+
);
74+
75+
const elapsed = Date.now() - startMs;
76+
logger.info(`[AI] [conflictDetect] ✔ Success (${source}): conflicts=${resp.conflicts.length}, model=${resp.model}, total=${elapsed}ms, reqId=${requestId ?? 'N/A'}`);
77+
return {
78+
conflicts: resp.conflicts.map((c) => ({
79+
oldClaim: c.old_claim,
80+
newClaim: c.new_claim,
81+
topic: c.topic ?? '',
82+
suggestion: c.suggestion ?? '',
83+
})),
84+
model: resp.model,
85+
tokensUsed: resp.tokens_used,
86+
requestId,
87+
source,
88+
};
89+
} catch (err) {
90+
const elapsed = Date.now() - startMs;
91+
const error = err instanceof Error ? err : new Error(String(err));
92+
logger.error(`[AI] [conflictDetect] ✖ Failed after ${elapsed}ms: ${error.message}`);
93+
if (error.cause) logger.error(`[AI] [conflictDetect] Error cause: ${error.cause}`);
94+
throw error;
95+
}
96+
},
97+
);
98+
}
99+
100+
// ================================================================
101+
// 功能定义导出
102+
// ================================================================
103+
104+
export const feature: AIFeatureDef = {
105+
id: 'ai_conflict_detect',
106+
name: 'AI 概念冲突检测',
107+
version: '1.0.0',
108+
register,
109+
};
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
* AI 内容分层 Handler(N5 策略性遗忘标记)
3+
*
4+
* 处理 ai_content_tier IPC 请求,调用 AI 网关将笔记内容分为
5+
* 核心概念/支撑材料/参考细节三层。
6+
*
7+
* @ai-context: 内容分层 IPC handler——AIFeatureDef 注册表模式,经 callWithLocalFallback 支持本地 Ollama 优先/云端网关降级;本地降级同样走 JSON 生成。
8+
*/
9+
10+
import { safeHandle } from '../../ipcUtils.js';
11+
import { logger } from '../../logger.js';
12+
import { callWithLocalFallback, gatewayUrl, type AIFeatureDef } from '../utils.js';
13+
import { generateText } from '../ollama/OllamaProvider.js';
14+
15+
// ================================================================
16+
// IPC Handler
17+
// ================================================================
18+
19+
/**
20+
* ai_content_tier — POST /api/v1/ai/content-tier
21+
*/
22+
function register(): void {
23+
safeHandle(
24+
'ai_content_tier',
25+
async (
26+
_event,
27+
args: {
28+
notesText: string;
29+
authToken?: string;
30+
},
31+
) => {
32+
const startMs = Date.now();
33+
logger.info(`[AI] [contentTier] IPC received: text_len=${args.notesText.length}, hasAuth=${!!args.authToken}`);
34+
35+
const reqBody = { notesText: args.notesText.slice(0, 6000) };
36+
37+
logger.info(`[AI] [contentTier] Target: ${gatewayUrl()}/api/v1/ai/content-tier`);
38+
39+
interface TierItemResp {
40+
text: string;
41+
reason?: string;
42+
}
43+
interface ContentTierResp {
44+
core: TierItemResp[];
45+
support: TierItemResp[];
46+
detail: TierItemResp[];
47+
model: string;
48+
tokens_used: number;
49+
}
50+
51+
try {
52+
const localHandler = async (): Promise<ContentTierResp> => {
53+
const prompt = `将以下笔记内容分为三层:core(核心概念,含 reason)、support(支撑材料)、detail(参考细节),仅返回JSON: {"core": [{"text": "...", "reason": "..."}], "support": [{"text": "..."}], "detail": [{"text": "..."}]}\n\n${reqBody.notesText}`;
54+
const result = await generateText(prompt, '你是一位认知负荷管理专家,擅长从笔记中提炼核心概念。请仅返回JSON。', { temperature: 0.3, maxTokens: 2500 });
55+
const parsed = JSON.parse(result.content);
56+
return {
57+
core: parsed.core ?? [],
58+
support: parsed.support ?? [],
59+
detail: parsed.detail ?? [],
60+
model: result.model,
61+
tokens_used: result.tokens_used,
62+
};
63+
};
64+
65+
const { data: resp, source, requestId } = await callWithLocalFallback<typeof reqBody, ContentTierResp>(
66+
'/api/v1/ai/content-tier',
67+
reqBody,
68+
localHandler,
69+
args.authToken,
70+
60000,
71+
);
72+
73+
const elapsed = Date.now() - startMs;
74+
logger.info(`[AI] [contentTier] ✔ Success (${source}): core=${resp.core.length}, support=${resp.support.length}, detail=${resp.detail.length}, model=${resp.model}, total=${elapsed}ms, reqId=${requestId ?? 'N/A'}`);
75+
return {
76+
core: resp.core.map((item) => ({ text: item.text, reason: item.reason ?? '' })),
77+
support: resp.support.map((item) => ({ text: item.text, reason: item.reason ?? '' })),
78+
detail: resp.detail.map((item) => ({ text: item.text, reason: item.reason ?? '' })),
79+
model: resp.model,
80+
tokensUsed: resp.tokens_used,
81+
requestId,
82+
source,
83+
};
84+
} catch (err) {
85+
const elapsed = Date.now() - startMs;
86+
const error = err instanceof Error ? err : new Error(String(err));
87+
logger.error(`[AI] [contentTier] ✖ Failed after ${elapsed}ms: ${error.message}`);
88+
if (error.cause) logger.error(`[AI] [contentTier] Error cause: ${error.cause}`);
89+
throw error;
90+
}
91+
},
92+
);
93+
}
94+
95+
// ================================================================
96+
// 功能定义导出
97+
// ================================================================
98+
99+
export const feature: AIFeatureDef = {
100+
id: 'ai_content_tier',
101+
name: 'AI 内容分层',
102+
version: '1.0.0',
103+
register,
104+
};

0 commit comments

Comments
 (0)