Skip to content

Commit cfa8e44

Browse files
committed
feat: v0.12.0 — 灵感空间重构、智能捕获增强、多模态分析与 Gemini 接入
灵感空间全面重构: - 新增 components/ 组件目录(灵感可视化交互) - 新增 useImmersiveState / useOrbExpand / useRandomSurface Hooks - 新增 useSortPendingReminder 排序提醒 Hook - 新增 lib/ 子模块与 types.ts 类型定义 - 灵感页面与状态管理重构 智能捕获增强: - 新增 SmartCapturePanel 智能捕获面板 - 新增 AnalysisPreview 分析预览组件 - 新增 SubjectFolder 科目文件夹组件 - 新增 VideoRecordPanel 视频录制面板 - 新增 videoRecorder.ts 视频录制器 - captureManager / smartSampler / vadMarker 优化 - videoRecorderRenderer 渲染器 笔记模块增强: - 新增 FreeCanvas / FreeTextBlock 自由画布组件 - 新增 TemplateSelector 模板选择器 - 新增 TodoStats 待办统计 - NoteEditPage / NotesPage / useNoteStore 优化 AI 系统升级: - 新增 sessionAnalyzer 会话分析器 - 新增 errorClassifier.test.ts 测试 - AIPluginLoader / ElectronAIPlugin / RemoteAIPlugin 全面优化 - 全部 AI hooks 更新(Duration/Evaluate/Feynman/Flashcards/Summarize) - LocalFallback / aiServiceFallback / config 优化 后端多模态扩展: - 新增 gemini_provider(Google Gemini 接入) - 新增 multimodal_analyze_chain 多模态分析链 - 新增 video_analyze_chain 视频分析链 - 新增 multimodal 路由 - vision_extract_chain 优化 - 新增 session_analyze / video_analyze Prompt - auth / rate_limit 中间件增强 - 新增 test_e2e_auth / test_timeout_budget 测试 - Dockerfile / requirements.txt 更新 部署与配置: - docker-compose.prod.yml / docker-compose.yml 更新 - Nginx 配置调整 - deploy.sh 更新 - 新增 test_connectivity.ps1 连接测试脚本 其他: - README 更新至 v0.12.0 - .env.example 更新 - 测试环境配置(.env.test) - Tailwind 设计令牌更新 - 类型定义扩展
1 parent b909331 commit cfa8e44

125 files changed

Lines changed: 11350 additions & 1359 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.

‎.env.example‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ DEEPSEEK_API_KEY=sk-your-deepseek-key
3030
# 智谱 GLM(可选,作为备选模型):https://open.bigmodel.cn
3131
GLM_API_KEY=sk-your-glm-api-key
3232

33+
# Gemini(可选,用于视频分析):https://aistudio.google.com/app/apikey
34+
GEMINI_API_KEY=your-gemini-api-key
35+
3336
# 数据库(Docker Compose 默认值,本地开发无需修改)
3437
DATABASE_URL=postgresql://keban:keban_dev@localhost:5432/keban
3538
REDIS_URL=redis://localhost:6379

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<p align="center"><strong>你的 AI 智能学习伙伴 — 让每一分钟学习都高效有价值</strong></p>
44
<p align="center">
55
<img src="https://img.shields.io/badge/status-beta-green.svg" alt="Status" />
6-
<img src="https://img.shields.io/badge/version-v0.10.1-blue.svg" alt="Version" />
6+
<img src="https://img.shields.io/badge/version-v0.12.0-blue.svg" alt="Version" />
77
<img src="https://img.shields.io/badge/license-Apache--2.0-blue.svg" alt="License" />
88
</p>
99
</p>

‎client/electron/ai/handlers/evaluateHandler.ts‎

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import { safeHandle } from '../../ipcUtils.js';
88
import { logger } from '../../logger.js';
9-
import { postJson, type AIFeatureDef } from '../utils.js';
9+
import { postJson, gatewayUrl, type AIFeatureDef } from '../utils.js';
1010

1111
// ================================================================
1212
// IPC Handler
@@ -24,15 +24,20 @@ function register(): void {
2424
concept: string;
2525
explanation: string;
2626
authToken?: string;
27+
userApiKey?: string;
2728
},
2829
) => {
2930
const startMs = Date.now();
30-
logger.info(`[IPC] ai_evaluate start, concept_length=${args.concept.length}`);
31+
logger.info(`[AI] [evaluate] IPC received: concept_length=${args.concept.length}, explanation_length=${args.explanation.length}, hasAuth=${!!args.authToken}`);
32+
logger.debug(`[AI] [evaluate] Concept: ${args.concept.slice(0, 60)}, Explanation preview: ${args.explanation.slice(0, 80)}...`);
33+
3134
const reqBody = {
3235
concept: args.concept,
3336
explanation: args.explanation,
3437
};
3538

39+
logger.info(`[AI] [evaluate] Target: ${gatewayUrl()}/api/v1/ai/evaluate-explanation`);
40+
3641
interface DimensionResp {
3742
dimension: string;
3843
score: number;
@@ -49,29 +54,39 @@ function register(): void {
4954
latency_ms: number;
5055
}
5156

52-
const { data: resp, requestId } = await postJson<typeof reqBody, EvaluateResp>(
53-
'/api/v1/ai/evaluate-explanation',
54-
reqBody,
55-
args.authToken,
56-
);
57+
try {
58+
const { data: resp, requestId } = await postJson<typeof reqBody, EvaluateResp>(
59+
'/api/v1/ai/evaluate-explanation',
60+
reqBody,
61+
args.authToken,
62+
args.userApiKey,
63+
40000,
64+
);
5765

58-
logger.info(`[IPC] Request ID: ${requestId ?? 'N/A'}`);
59-
logger.info(`[IPC] ai_evaluate end, overall_score=${resp.overall_score}`);
60-
return {
61-
overallScore: resp.overall_score,
62-
dimensions: resp.dimensions.map((d) => ({
63-
name: d.dimension, // 后端字段 dimension → 前端 name
64-
score: d.score,
65-
feedback: d.feedback,
66-
})),
67-
strengths: resp.strengths,
68-
improvements: resp.improvements,
69-
encouragement: resp.encouragement,
70-
model: resp.model,
71-
tokensUsed: resp.tokens_used,
72-
latencyMs: resp.latency_ms,
73-
requestId,
74-
};
66+
const elapsed = Date.now() - startMs;
67+
logger.info(`[AI] [evaluate] ✔ Success: overall_score=${resp.overall_score}, dimensions=${resp.dimensions.length}, model=${resp.model}, tokens=${resp.tokens_used}, total=${elapsed}ms, reqId=${requestId ?? 'N/A'}`);
68+
return {
69+
overallScore: resp.overall_score,
70+
dimensions: resp.dimensions.map((d) => ({
71+
name: d.dimension, // 后端字段 dimension → 前端 name
72+
score: d.score,
73+
feedback: d.feedback,
74+
})),
75+
strengths: resp.strengths,
76+
improvements: resp.improvements,
77+
encouragement: resp.encouragement,
78+
model: resp.model,
79+
tokensUsed: resp.tokens_used,
80+
latencyMs: resp.latency_ms,
81+
requestId,
82+
};
83+
} catch (err) {
84+
const elapsed = Date.now() - startMs;
85+
const error = err instanceof Error ? err : new Error(String(err));
86+
logger.error(`[AI] [evaluate] ✖ Failed after ${elapsed}ms: ${error.message}`);
87+
if (error.cause) logger.error(`[AI] [evaluate] Error cause: ${error.cause}`);
88+
throw error;
89+
}
7590
},
7691
);
7792
}

‎client/electron/ai/handlers/feynmanHandler.ts‎

Lines changed: 69 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import { safeHandle } from '../../ipcUtils.js';
99
import { logger } from '../../logger.js';
10-
import { postJson, type AIFeatureDef } from '../utils.js';
10+
import { postJson, gatewayUrl, type AIFeatureDef } from '../utils.js';
1111

1212
// ================================================================
1313
// IPC Handler
@@ -28,40 +28,55 @@ function register(): void {
2828
concept: string;
2929
explanation: string;
3030
authToken?: string;
31+
userApiKey?: string;
3132
},
3233
) => {
3334
const startMs = Date.now();
34-
logger.info(`[IPC] ai_feynman_question start, concept_length=${args.concept.length}`);
35+
logger.info(`[AI] [feynman-q] IPC received: concept_length=${args.concept.length}, explanation_length=${args.explanation.length}, hasAuth=${!!args.authToken}`);
36+
logger.debug(`[AI] [feynman-q] Concept: ${args.concept.slice(0, 60)}`);
37+
3538
const reqBody = {
3639
concept: args.concept,
3740
explanation: args.explanation,
3841
};
3942

43+
logger.info(`[AI] [feynman-q] Target: ${gatewayUrl()}/api/v1/ai/feynman-question`);
44+
4045
interface FeynmanQuestionResp {
4146
questions: Array<{ question: string; focus: string }>;
4247
model: string;
4348
tokens_used: number;
4449
latency_ms: number;
4550
}
4651

47-
const { data: resp, requestId } = await postJson<typeof reqBody, FeynmanQuestionResp>(
48-
'/api/v1/ai/feynman-question',
49-
reqBody,
50-
args.authToken,
51-
);
52-
53-
logger.info(`[IPC] Request ID: ${requestId ?? 'N/A'}`);
54-
logger.info(`[IPC] ai_feynman_question end, question_count=${resp.questions.length}`);
55-
return {
56-
questions: resp.questions.map((q) => ({
57-
question: q.question,
58-
focus: q.focus,
59-
})),
60-
model: resp.model,
61-
tokensUsed: resp.tokens_used,
62-
latencyMs: resp.latency_ms,
63-
requestId,
64-
};
52+
try {
53+
const { data: resp, requestId } = await postJson<typeof reqBody, FeynmanQuestionResp>(
54+
'/api/v1/ai/feynman-question',
55+
reqBody,
56+
args.authToken,
57+
args.userApiKey,
58+
40000,
59+
);
60+
61+
const elapsed = Date.now() - startMs;
62+
logger.info(`[AI] [feynman-q] ✔ Success: questions=${resp.questions.length}, model=${resp.model}, tokens=${resp.tokens_used}, total=${elapsed}ms, reqId=${requestId ?? 'N/A'}`);
63+
return {
64+
questions: resp.questions.map((q) => ({
65+
question: q.question,
66+
focus: q.focus,
67+
})),
68+
model: resp.model,
69+
tokensUsed: resp.tokens_used,
70+
latencyMs: resp.latency_ms,
71+
requestId,
72+
};
73+
} catch (err) {
74+
const elapsed = Date.now() - startMs;
75+
const error = err instanceof Error ? err : new Error(String(err));
76+
logger.error(`[AI] [feynman-q] ✖ Failed after ${elapsed}ms: ${error.message}`);
77+
if (error.cause) logger.error(`[AI] [feynman-q] Error cause: ${error.cause}`);
78+
throw error;
79+
}
6580
},
6681
);
6782

@@ -77,16 +92,21 @@ function register(): void {
7792
questions: string[];
7893
answers: string[];
7994
authToken?: string;
95+
userApiKey?: string;
8096
},
8197
) => {
8298
const startMs = Date.now();
83-
logger.info(`[IPC] ai_feynman_evaluate_answers start, concept_length=${args.concept.length}`);
99+
logger.info(`[AI] [feynman-eval] IPC received: concept_length=${args.concept.length}, questions=${args.questions.length}, answers=${args.answers.length}, hasAuth=${!!args.authToken}`);
100+
logger.debug(`[AI] [feynman-eval] Concept: ${args.concept.slice(0, 60)}, Q count=${args.questions.length}`);
101+
84102
const reqBody = {
85103
concept: args.concept,
86104
questions: args.questions,
87105
answers: args.answers,
88106
};
89107

108+
logger.info(`[AI] [feynman-eval] Target: ${gatewayUrl()}/api/v1/ai/feynman-evaluate-answers`);
109+
90110
interface FeynmanAnswerEvalResp {
91111
understanding_score: number;
92112
feedback: string;
@@ -97,24 +117,34 @@ function register(): void {
97117
latency_ms: number;
98118
}
99119

100-
const { data: resp, requestId } = await postJson<typeof reqBody, FeynmanAnswerEvalResp>(
101-
'/api/v1/ai/feynman-evaluate-answers',
102-
reqBody,
103-
args.authToken,
104-
);
105-
106-
logger.info(`[IPC] Request ID: ${requestId ?? 'N/A'}`);
107-
logger.info(`[IPC] ai_feynman_evaluate_answers end, score=${resp.understanding_score}`);
108-
return {
109-
understandingScore: resp.understanding_score,
110-
feedback: resp.feedback,
111-
strongPoints: resp.strong_points,
112-
weakPoints: resp.weak_points,
113-
model: resp.model,
114-
tokensUsed: resp.tokens_used,
115-
latencyMs: resp.latency_ms,
116-
requestId,
117-
};
120+
try {
121+
const { data: resp, requestId } = await postJson<typeof reqBody, FeynmanAnswerEvalResp>(
122+
'/api/v1/ai/feynman-evaluate-answers',
123+
reqBody,
124+
args.authToken,
125+
args.userApiKey,
126+
40000,
127+
);
128+
129+
const elapsed = Date.now() - startMs;
130+
logger.info(`[AI] [feynman-eval] ✔ Success: score=${resp.understanding_score}, strong=${resp.strong_points.length}, weak=${resp.weak_points.length}, model=${resp.model}, total=${elapsed}ms, reqId=${requestId ?? 'N/A'}`);
131+
return {
132+
understandingScore: resp.understanding_score,
133+
feedback: resp.feedback,
134+
strongPoints: resp.strong_points,
135+
weakPoints: resp.weak_points,
136+
model: resp.model,
137+
tokensUsed: resp.tokens_used,
138+
latencyMs: resp.latency_ms,
139+
requestId,
140+
};
141+
} catch (err) {
142+
const elapsed = Date.now() - startMs;
143+
const error = err instanceof Error ? err : new Error(String(err));
144+
logger.error(`[AI] [feynman-eval] ✖ Failed after ${elapsed}ms: ${error.message}`);
145+
if (error.cause) logger.error(`[AI] [feynman-eval] Error cause: ${error.cause}`);
146+
throw error;
147+
}
118148
},
119149
);
120150
}

‎client/electron/ai/handlers/flashcardHandler.ts‎

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
import { safeHandle } from '../../ipcUtils.js';
88
import { logger } from '../../logger.js';
9-
import { postJson, type AIFeatureDef } from '../utils.js';
9+
import { postJson, gatewayUrl, type AIFeatureDef } from '../utils.js';
1010

1111
// ================================================================
1212
// IPC Handler
@@ -26,19 +26,24 @@ function register(): void {
2626
difficulty?: string;
2727
cardType?: string;
2828
authToken?: string;
29+
userApiKey?: string;
2930
},
3031
) => {
3132
const startMs = Date.now();
32-
logger.info(`[IPC] ai_generate_cards start, note_length=${args.note.length}`);
33+
logger.info(`[AI] [flashcard] IPC received: note_length=${args.note.length}, maxCards=${args.maxCards ?? 'default'}, difficulty=${args.difficulty ?? 'default'}, cardType=${args.cardType ?? 'default'}, hasAuth=${!!args.authToken}`);
34+
logger.debug(`[AI] [flashcard] Note preview: ${args.note.slice(0, 80)}...`);
35+
3336
const reqBody = {
3437
note: args.note,
3538
options: {
36-
...(args.maxCards !== undefined && { max_cards: args.maxCards }),
37-
...(args.difficulty !== undefined && { difficulty: args.difficulty }),
38-
...(args.cardType !== undefined && { card_type: args.cardType }),
39+
...(args.maxCards != null && { max_cards: args.maxCards }),
40+
...(args.difficulty != null && { difficulty: args.difficulty }),
41+
...(args.cardType != null && { card_type: args.cardType }),
3942
},
4043
};
4144

45+
logger.info(`[AI] [flashcard] Target: ${gatewayUrl()}/api/v1/ai/generate-cards`);
46+
4247
interface CardResp {
4348
front: string;
4449
back: string;
@@ -52,26 +57,36 @@ function register(): void {
5257
tokens_used: number;
5358
}
5459

55-
const { data: resp, requestId } = await postJson<typeof reqBody, CardGenResp>(
56-
'/api/v1/ai/generate-cards',
57-
reqBody,
58-
args.authToken,
59-
);
60+
try {
61+
const { data: resp, requestId } = await postJson<typeof reqBody, CardGenResp>(
62+
'/api/v1/ai/generate-cards',
63+
reqBody,
64+
args.authToken,
65+
args.userApiKey,
66+
90000,
67+
);
6068

61-
logger.info(`[IPC] Request ID: ${requestId ?? 'N/A'}`);
62-
logger.info(`[IPC] ai_generate_cards end, cards_count=${resp.cards.length}`);
63-
return {
64-
cards: resp.cards.map((c) => ({
65-
front: c.front,
66-
back: c.back,
67-
type: c.type,
68-
confidence: c.confidence,
69-
})),
70-
totalExtracted: resp.total_extracted,
71-
model: resp.model,
72-
tokensUsed: resp.tokens_used,
73-
requestId,
74-
};
69+
const elapsed = Date.now() - startMs;
70+
logger.info(`[AI] [flashcard] ✔ Success: cards=${resp.cards.length}, total_extracted=${resp.total_extracted}, model=${resp.model}, tokens=${resp.tokens_used}, total=${elapsed}ms, reqId=${requestId ?? 'N/A'}`);
71+
return {
72+
cards: resp.cards.map((c) => ({
73+
front: c.front,
74+
back: c.back,
75+
type: c.type,
76+
confidence: c.confidence,
77+
})),
78+
totalExtracted: resp.total_extracted,
79+
model: resp.model,
80+
tokensUsed: resp.tokens_used,
81+
requestId,
82+
};
83+
} catch (err) {
84+
const elapsed = Date.now() - startMs;
85+
const error = err instanceof Error ? err : new Error(String(err));
86+
logger.error(`[AI] [flashcard] ✖ Failed after ${elapsed}ms: ${error.message}`);
87+
if (error.cause) logger.error(`[AI] [flashcard] Error cause: ${error.cause}`);
88+
throw error;
89+
}
7590
},
7691
);
7792
}

0 commit comments

Comments
 (0)