修复: 全栈审查 FR-C1~C5 五项正确性 bug
- 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
This commit is contained in:
@@ -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 };
|
||||
|
||||
@@ -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,供 `/` 联想 + 注入) */
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -28,6 +28,7 @@ export default {
|
||||
netSentiment: '整体倾向: {tone} ({n})',
|
||||
sentimentPositive: '积极',
|
||||
sentimentNegative: '谨慎',
|
||||
sentimentNeutral: '中性',
|
||||
actionTitle: '💡 行动建议',
|
||||
evaluating: '⏳ 评估中…',
|
||||
startEval: '🔍 开始对抗评估',
|
||||
|
||||
@@ -11,7 +11,7 @@ const state = reactive({
|
||||
tasks: [] as TaskRecord[],
|
||||
ideas: [] as IdeaRecord[],
|
||||
workflowExecutions: [] as WorkflowRecord[],
|
||||
liveEvents: [] as WorkflowEventPayload[],
|
||||
liveEvents: [] as Array<WorkflowEventPayload & { _ts: number }>,
|
||||
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') {
|
||||
|
||||
@@ -100,7 +100,7 @@
|
||||
</div>
|
||||
<p class="final-score">{{ $t('ideas.finalScore', { score: adversarialEval.final_score.toFixed(1) }) }}</p>
|
||||
<div class="net-sentiment" :class="sentimentClass(adversarialEval.net_sentiment)">
|
||||
{{ $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) }) }}
|
||||
</div>
|
||||
<p class="summary">{{ adversarialEval.analyst.summary }}</p>
|
||||
</div>
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<LogEntry[]>(() => {
|
||||
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)}`
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user