From cf18678634639e4ad6985a51280052b4af3d45a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com> Date: Sun, 14 Jun 2026 17:00:33 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20=E5=85=A8=E6=A0=88?= =?UTF-8?q?=E5=AE=A1=E6=9F=A5=20FR-C1~C5=20=E4=BA=94=E9=A1=B9=E6=AD=A3?= =?UTF-8?q?=E7=A1=AE=E6=80=A7=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FR-C1 formattedEvents 时间漂移:事件入数组固 _ts 时间戳(project.ts push + ProjectDetail 用 _ts),computed 重算不再刷历史时间为当前时刻 - FR-C2 sentiment 三档统一:模板 >0/<0/===0 + sentimentClass 同源 + i18n neutral(zh/en),原模板>0 与 class>=0 矛盾 - FR-C3 Settings timer 泄漏:onUnmounted 清全部三个(原仅 _concurrencyTimer) - FR-C4 MIGRATION_VERSION 死常量:删(零代码引用,run() if 链自管版本) - FR-C5 AiConversationDetail 缺 readonly:加 readonly?: boolean(后端 generating 时返回) 来源 fullstack-review §3(已核实);cargo/vue-tsc 0 err --- crates/df-storage/src/migrations.rs | 3 --- src/api/types.ts | 2 ++ src/i18n/en/ideas.ts | 1 + src/i18n/zh-CN/ideas.ts | 1 + src/stores/project.ts | 5 +++-- src/views/Ideas.vue | 7 +++++-- src/views/ProjectDetail.vue | 6 +++--- src/views/Settings.vue | 4 +++- 8 files changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/df-storage/src/migrations.rs b/crates/df-storage/src/migrations.rs index a4ae203..68d1432 100644 --- a/crates/df-storage/src/migrations.rs +++ b/crates/df-storage/src/migrations.rs @@ -3,9 +3,6 @@ use anyhow::Result; use rusqlite::Connection; -/// 当前迁移版本 -const MIGRATION_VERSION: i32 = 12; - /// 检测表是否存在指定列(幂等迁移通用辅助) fn column_exists(conn: &Connection, table: &str, col: &str) -> bool { let Ok(mut stmt) = conn.prepare(&format!("PRAGMA table_info({table})")) else { return false }; diff --git a/src/api/types.ts b/src/api/types.ts index 73f38cb..8d9aeb5 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -243,6 +243,8 @@ export interface AiConversationDetail { id: string title: string | null messages: string // JSON string of ChatMessage[] + /** 生成中返回 true(后端 ai_conversation_switch 在 generating 时置),前端据此禁编辑(FR-C5) */ + readonly?: boolean } /** 技能信息(本机 Claude skill,供 `/` 联想 + 注入) */ diff --git a/src/i18n/en/ideas.ts b/src/i18n/en/ideas.ts index 6e67d3d..f1103ac 100644 --- a/src/i18n/en/ideas.ts +++ b/src/i18n/en/ideas.ts @@ -28,6 +28,7 @@ export default { netSentiment: 'Overall sentiment: {tone} ({n})', sentimentPositive: 'Positive', sentimentNegative: 'Cautious', + sentimentNeutral: 'Neutral', actionTitle: '💡 Action Items', evaluating: '⏳ Evaluating…', startEval: '🔍 Start Adversarial Eval', diff --git a/src/i18n/zh-CN/ideas.ts b/src/i18n/zh-CN/ideas.ts index 8dceedd..5c93208 100644 --- a/src/i18n/zh-CN/ideas.ts +++ b/src/i18n/zh-CN/ideas.ts @@ -28,6 +28,7 @@ export default { netSentiment: '整体倾向: {tone} ({n})', sentimentPositive: '积极', sentimentNegative: '谨慎', + sentimentNeutral: '中性', actionTitle: '💡 行动建议', evaluating: '⏳ 评估中…', startEval: '🔍 开始对抗评估', diff --git a/src/stores/project.ts b/src/stores/project.ts index e6c6990..e032be8 100644 --- a/src/stores/project.ts +++ b/src/stores/project.ts @@ -11,7 +11,7 @@ const state = reactive({ tasks: [] as TaskRecord[], ideas: [] as IdeaRecord[], workflowExecutions: [] as WorkflowRecord[], - liveEvents: [] as WorkflowEventPayload[], + liveEvents: [] as Array, pendingApproval: null as { execution_id: string node_id: string @@ -209,7 +209,8 @@ function createStore() { if (_eventUnlisten) return _eventUnlisten _eventUnlisten = await workflowApi.onEvent((payload) => { try { - state.liveEvents.push(payload) + // 固时间戳(FR-C1):formattedEvents computed 重算时用事件发生时刻,非当前时刻 + state.liveEvents.push({ ...payload, _ts: Date.now() }) // 处理人工审批请求 // WorkflowEvent serde tag=type rename_all=snake_case → type='human_approval_request',字段扁平(无 data 包装)(B-03b-R7) if (payload.event?.type === 'human_approval_request') { diff --git a/src/views/Ideas.vue b/src/views/Ideas.vue index d47a6c8..76cf4ec 100644 --- a/src/views/Ideas.vue +++ b/src/views/Ideas.vue @@ -100,7 +100,7 @@

{{ $t('ideas.finalScore', { score: adversarialEval.final_score.toFixed(1) }) }}

- {{ $t('ideas.netSentiment', { tone: adversarialEval.net_sentiment > 0 ? $t('ideas.sentimentPositive') : $t('ideas.sentimentNegative'), n: (adversarialEval.net_sentiment * 100).toFixed(0) }) }} + {{ $t('ideas.netSentiment', { tone: adversarialEval.net_sentiment > 0 ? $t('ideas.sentimentPositive') : (adversarialEval.net_sentiment < 0 ? $t('ideas.sentimentNegative') : $t('ideas.sentimentNeutral')), n: (adversarialEval.net_sentiment * 100).toFixed(0) }) }}

{{ adversarialEval.analyst.summary }}

@@ -366,7 +366,10 @@ function assessmentLabel(recommendation: string) { } function sentimentClass(sentiment: number) { - return sentiment >= 0 ? 'positive' : 'negative' + // 统一三档(FR-C2: 原 >=0 与模板 >0 矛盾,net_sentiment=0 时文案负面样式 positive) + if (sentiment > 0) return 'positive' + if (sentiment < 0) return 'negative' + return 'neutral' } function scoreClass(score: number | null) { diff --git a/src/views/ProjectDetail.vue b/src/views/ProjectDetail.vue index 3cd42a3..1133d81 100644 --- a/src/views/ProjectDetail.vue +++ b/src/views/ProjectDetail.vue @@ -212,7 +212,6 @@ import { formatDate } from '../utils/time' import { parseStack } from '../utils/project' import { projectStatusLabel, projectStageInfo, taskStatusLabel, taskStatusClass } from '../constants/project' import ConfirmDialog from '../components/ConfirmDialog.vue' -import type { WorkflowEventPayload } from '../api/types' const route = useRoute() const router = useRouter() @@ -296,12 +295,13 @@ interface LogEntry { } const formattedEvents = computed(() => { - return store.liveEvents.map((evt: WorkflowEventPayload) => { + return store.liveEvents.map((evt) => { const ev = evt.event const type = ev.type ?? 'unknown' const level = type.includes('error') || type.includes('fail') ? 'error' : type.includes('warn') ? 'warn' : 'info' - const time = new Date().toLocaleTimeString(locale.value, { hour: '2-digit', minute: '2-digit', second: '2-digit' }) + // 用事件入数组时固化的时间戳(FR-C1),非 computed 重算的当前时刻 + const time = new Date(evt._ts).toLocaleTimeString(locale.value, { hour: '2-digit', minute: '2-digit', second: '2-digit' }) const message = ev.label ? `[${ev.label}] ${type}: ${ev.output ?? ev.code ?? ev.message ?? JSON.stringify(ev)}` : `${type}: ${ev.output ?? ev.code ?? ev.message ?? JSON.stringify(ev)}` diff --git a/src/views/Settings.vue b/src/views/Settings.vue index 58efebc..1866304 100644 --- a/src/views/Settings.vue +++ b/src/views/Settings.vue @@ -745,9 +745,11 @@ onMounted(() => { syncConcurrencyConfig() }) -// 组件卸载清 debounce timer,防 unmount 后旧 timer 仍 fire 发无效 IPC(timer 泄漏) +// 组件卸载清全部 debounce timer,防 unmount 后旧 timer 仍 fire 写已销毁 reactive(timer 泄漏 FR-C3) onUnmounted(() => { if (_concurrencyTimer) clearTimeout(_concurrencyTimer) + if (_knowledgeSaveTimer) clearTimeout(_knowledgeSaveTimer) + if (_toastTimer) clearTimeout(_toastTimer) })