Skip to content

Commit a1aa349

Browse files
committed
feat(analytics): track suggestion refresh and acceptance events
- Add a AGENTS.md for faster vibe coding - 数据埋点测试
1 parent beeac3f commit a1aa349

File tree

3 files changed

+136
-6
lines changed

3 files changed

+136
-6
lines changed

desktop/AGENTS.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Repository Guidelines
2+
For vibe-coding models
3+
## Project Structure & Module Organization
4+
- `src/main.js` is the Electron main process entry; preload lives in `src/preload.js` and core modules in `src/core/`.
5+
- Renderer UI is React under `src/renderer/` (pages, components, styles).
6+
- ASR and audio tooling live in `src/asr/` and `src/native/` (native system audio capture).
7+
- Python-related assets and backends live in `python-env/`, `python-bootstrap/`, and `backend/`.
8+
- Build outputs go to `dist/` (renderer) and `release/` (packaged apps).
9+
- Helper scripts are in `scripts/` (dev, build, model download, test utilities).
10+
11+
## Build, Test, and Development Commands
12+
- `pnpm install` installs dependencies and runs `postinstall` (rebuilds `better-sqlite3`).
13+
- `pnpm dev` starts the full desktop dev flow (Electron + Vite).
14+
- `pnpm run prepare:python` prepares the local ASR Python environment (`PREPARE_PYTHON_MODE=bundle` for portable builds).
15+
- `pnpm run build` runs prebuild + Vite build + Electron Builder packaging.
16+
- `pnpm run build:mac` / `pnpm run build:win` build platform-specific installers.
17+
- `pnpm run preview` serves the built renderer for inspection.
18+
19+
## Coding Style & Naming Conventions
20+
- JavaScript/TypeScript uses ESM imports, semicolons, and 2-space indentation; follow existing file style.
21+
- React components are PascalCase (`CharacterModal.jsx`), utilities/modules are kebab or camel case (`asr-cache-env.js`).
22+
- Keep Electron main/preload logic in `src/` and avoid mixing UI concerns into main process modules.
23+
24+
## Testing Guidelines
25+
- Automated tests are mostly script-driven: `pnpm run test:asr`, `pnpm run test:settings-logs`, `pnpm run test:audio`.
26+
- Additional utilities live in `scripts/test-*.{js,py}`; run them directly when validating audio/ASR flows.
27+
- Name new tests with clear `test-` prefixes and document any required hardware or model downloads.
28+
29+
## Commit & Pull Request Guidelines
30+
- Commit messages generally follow Conventional Commits (`feat(scope):`, `fix(scope):`, `refactor(scope):`). Use a short, scoped summary.
31+
- Git history shows these common types/scopes: `feat`, `fix`, `refactor`, `chore`, `security` with scopes like `main`, `ci`, `python-env`, `asr`, `ui`, `docs`, `review`, `prompt`, `tests`.
32+
- Messages may be bilingual (English/中文); keep them concise and consistent with existing history.
33+
- PRs should describe user-facing behavior changes, note platform impact (macOS/Windows), and include screenshots/GIFs for UI updates.
34+
- If the change touches ASR or model caches, mention any new env vars or migration steps.
35+
36+
## Configuration & Security Notes
37+
- Local ASR caches can be redirected via `ASR_CACHE_BASE`; keep sensitive paths out of logs.
38+
- Avoid widening IPC surface area without validation (see `src/core/modules/`).

desktop/src/preload.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
183183
// Memory Service (结构化画像/事件)
184184
memoryQueryProfiles: (payload) => ipcRenderer.invoke('memory-query-profiles', payload),
185185
memoryQueryEvents: (payload) => ipcRenderer.invoke('memory-query-events', payload),
186+
memoryUpsertEvent: (payload) => ipcRenderer.invoke('memory-upsert-event', payload),
186187
onSuggestionStreamStart: (callback) => {
187188
const listener = (event, data) => callback(data);
188189
ipcRenderer.on('llm-suggestion-stream-start', listener);

desktop/src/renderer/hooks/useSuggestions.js

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export const useSuggestions = (sessionInfo) => {
7272
} catch (err) {
7373
console.error('加载建议配置失败:', err);
7474
}
75-
}, []);
75+
}, [sessionInfo]);
7676

7777
const updateSuggestionConfig = useCallback(async (updates = {}) => {
7878
try {
@@ -108,11 +108,11 @@ export const useSuggestions = (sessionInfo) => {
108108
const resetStreamState = useCallback((reason = 'unknown') => {
109109
console.log(`[useSuggestions] resetStreamState called (reason: ${reason}). Clearing active stream:`, activeStreamRef.current);
110110
activeStreamRef.current = { id: null, trigger: null, reason: null };
111-
}, []);
111+
}, [sessionInfo]);
112112

113113
const logStreamCharacters = useCallback(() => {
114114
// Disabled: per-character logging is extremely noisy and can stall the renderer.
115-
}, []);
115+
}, [sessionInfo]);
116116

117117
const startSuggestionStream = useCallback(
118118
({ trigger, reason }) => {
@@ -163,6 +163,50 @@ export const useSuggestions = (sessionInfo) => {
163163
[sessionInfo, suggestionConfig, suggestions]
164164
);
165165

166+
const recordSuggestionSignal = useCallback(
167+
({ trigger, reason }) => {
168+
if (trigger !== 'manual') return;
169+
const api = window.electronAPI;
170+
if (!api?.memoryUpsertEvent) return;
171+
172+
const action = reason === 'refresh' ? 'refresh' : 'manual_generate';
173+
const userIdRaw =
174+
sessionInfo?.userId ??
175+
sessionInfo?.user_id ??
176+
sessionInfo?.conversationId ??
177+
'local-user';
178+
const projectIdRaw =
179+
sessionInfo?.projectId ??
180+
sessionInfo?.project_id ??
181+
sessionInfo?.characterId ??
182+
null;
183+
const payload = {
184+
user_id: String(userIdRaw),
185+
event_tip: action === 'refresh' ? '用户刷新建议' : '用户手动生成建议',
186+
event_tags: ['suggestion', 'user_feedback', action],
187+
profile_delta: {
188+
action,
189+
trigger,
190+
reason,
191+
conversation_id: sessionInfo?.conversationId || null,
192+
character_id: sessionInfo?.characterId || null,
193+
suggestion_count: suggestionConfig?.suggestion_count ?? null,
194+
context_message_limit: suggestionConfig?.context_message_limit ?? null,
195+
existing_suggestions: Array.isArray(suggestions) ? suggestions.length : 0
196+
},
197+
timestamp: Date.now()
198+
};
199+
if (projectIdRaw) {
200+
payload.project_id = String(projectIdRaw);
201+
}
202+
203+
api.memoryUpsertEvent(payload).catch((error) => {
204+
console.warn('[useSuggestions] memoryUpsertEvent failed', error);
205+
});
206+
},
207+
[sessionInfo, suggestionConfig, suggestions]
208+
);
209+
166210
/**
167211
* 生成建议
168212
*/
@@ -183,6 +227,8 @@ export const useSuggestions = (sessionInfo) => {
183227
return;
184228
}
185229

230+
recordSuggestionSignal({ trigger, reason });
231+
186232
if (window.electronAPI?.startSuggestionStream) {
187233
startSuggestionStream({ trigger, reason });
188234
return;
@@ -234,7 +280,14 @@ export const useSuggestions = (sessionInfo) => {
234280
setSuggestionStatus('idle');
235281
}
236282
},
237-
[sessionInfo, suggestionConfig, suggestionStatus, startSuggestionStream, suggestions]
283+
[
284+
sessionInfo,
285+
suggestionConfig,
286+
suggestionStatus,
287+
startSuggestionStream,
288+
suggestions,
289+
recordSuggestionSignal
290+
]
238291
);
239292

240293
/**
@@ -259,7 +312,7 @@ export const useSuggestions = (sessionInfo) => {
259312
} catch (err) {
260313
console.error('复制建议失败:', err);
261314
}
262-
}, []);
315+
}, [sessionInfo]);
263316

264317
/**
265318
* 显式确认“采用了哪个建议”(写入 DB,并在当前 UI 内高亮)
@@ -279,6 +332,44 @@ export const useSuggestions = (sessionInfo) => {
279332
});
280333
if (!ok) return false;
281334

335+
if (selected) {
336+
const api = window.electronAPI;
337+
if (api?.memoryUpsertEvent) {
338+
const userIdRaw =
339+
sessionInfo?.userId ??
340+
sessionInfo?.user_id ??
341+
sessionInfo?.conversationId ??
342+
'local-user';
343+
const projectIdRaw =
344+
sessionInfo?.projectId ??
345+
sessionInfo?.project_id ??
346+
sessionInfo?.characterId ??
347+
null;
348+
const payload = {
349+
user_id: String(userIdRaw),
350+
event_tip: '用户采用建议',
351+
event_tags: ['suggestion', 'user_feedback', 'accept'],
352+
profile_delta: {
353+
action: 'accept',
354+
suggestion_id: suggestion.id,
355+
suggestion_index: suggestion.suggestion_index ?? suggestion.index ?? null,
356+
decision_point_id: suggestion.decision_point_id ?? null,
357+
batch_id: suggestion.batch_id ?? null,
358+
conversation_id: sessionInfo?.conversationId || null,
359+
character_id: sessionInfo?.characterId || null
360+
},
361+
timestamp: Date.now()
362+
};
363+
if (projectIdRaw) {
364+
payload.project_id = String(projectIdRaw);
365+
}
366+
367+
api.memoryUpsertEvent(payload).catch((error) => {
368+
console.warn('[useSuggestions] memoryUpsertEvent failed', error);
369+
});
370+
}
371+
}
372+
282373
// UI 侧按 batch_id(优先)互斥,保持与 DB 一致
283374
const scopeBatchId = suggestion.batch_id || null;
284375
const scopeDecisionPointId = suggestion.decision_point_id || null;
@@ -300,7 +391,7 @@ export const useSuggestions = (sessionInfo) => {
300391
console.error('[useSuggestions] Failed to select suggestion:', err);
301392
return false;
302393
}
303-
}, []);
394+
}, [sessionInfo]);
304395

305396
/**
306397
* 情景判定(冷场/连发统一交由 LLM 评估)

0 commit comments

Comments
 (0)