修复: 全栈审查 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 anyhow::Result;
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
|
|
||||||
/// 当前迁移版本
|
|
||||||
const MIGRATION_VERSION: i32 = 12;
|
|
||||||
|
|
||||||
/// 检测表是否存在指定列(幂等迁移通用辅助)
|
/// 检测表是否存在指定列(幂等迁移通用辅助)
|
||||||
fn column_exists(conn: &Connection, table: &str, col: &str) -> bool {
|
fn column_exists(conn: &Connection, table: &str, col: &str) -> bool {
|
||||||
let Ok(mut stmt) = conn.prepare(&format!("PRAGMA table_info({table})")) else { return false };
|
let Ok(mut stmt) = conn.prepare(&format!("PRAGMA table_info({table})")) else { return false };
|
||||||
|
|||||||
@@ -243,6 +243,8 @@ export interface AiConversationDetail {
|
|||||||
id: string
|
id: string
|
||||||
title: string | null
|
title: string | null
|
||||||
messages: string // JSON string of ChatMessage[]
|
messages: string // JSON string of ChatMessage[]
|
||||||
|
/** 生成中返回 true(后端 ai_conversation_switch 在 generating 时置),前端据此禁编辑(FR-C5) */
|
||||||
|
readonly?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 技能信息(本机 Claude skill,供 `/` 联想 + 注入) */
|
/** 技能信息(本机 Claude skill,供 `/` 联想 + 注入) */
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export default {
|
|||||||
netSentiment: 'Overall sentiment: {tone} ({n})',
|
netSentiment: 'Overall sentiment: {tone} ({n})',
|
||||||
sentimentPositive: 'Positive',
|
sentimentPositive: 'Positive',
|
||||||
sentimentNegative: 'Cautious',
|
sentimentNegative: 'Cautious',
|
||||||
|
sentimentNeutral: 'Neutral',
|
||||||
actionTitle: '💡 Action Items',
|
actionTitle: '💡 Action Items',
|
||||||
evaluating: '⏳ Evaluating…',
|
evaluating: '⏳ Evaluating…',
|
||||||
startEval: '🔍 Start Adversarial Eval',
|
startEval: '🔍 Start Adversarial Eval',
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export default {
|
|||||||
netSentiment: '整体倾向: {tone} ({n})',
|
netSentiment: '整体倾向: {tone} ({n})',
|
||||||
sentimentPositive: '积极',
|
sentimentPositive: '积极',
|
||||||
sentimentNegative: '谨慎',
|
sentimentNegative: '谨慎',
|
||||||
|
sentimentNeutral: '中性',
|
||||||
actionTitle: '💡 行动建议',
|
actionTitle: '💡 行动建议',
|
||||||
evaluating: '⏳ 评估中…',
|
evaluating: '⏳ 评估中…',
|
||||||
startEval: '🔍 开始对抗评估',
|
startEval: '🔍 开始对抗评估',
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const state = reactive({
|
|||||||
tasks: [] as TaskRecord[],
|
tasks: [] as TaskRecord[],
|
||||||
ideas: [] as IdeaRecord[],
|
ideas: [] as IdeaRecord[],
|
||||||
workflowExecutions: [] as WorkflowRecord[],
|
workflowExecutions: [] as WorkflowRecord[],
|
||||||
liveEvents: [] as WorkflowEventPayload[],
|
liveEvents: [] as Array<WorkflowEventPayload & { _ts: number }>,
|
||||||
pendingApproval: null as {
|
pendingApproval: null as {
|
||||||
execution_id: string
|
execution_id: string
|
||||||
node_id: string
|
node_id: string
|
||||||
@@ -209,7 +209,8 @@ function createStore() {
|
|||||||
if (_eventUnlisten) return _eventUnlisten
|
if (_eventUnlisten) return _eventUnlisten
|
||||||
_eventUnlisten = await workflowApi.onEvent((payload) => {
|
_eventUnlisten = await workflowApi.onEvent((payload) => {
|
||||||
try {
|
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)
|
// WorkflowEvent serde tag=type rename_all=snake_case → type='human_approval_request',字段扁平(无 data 包装)(B-03b-R7)
|
||||||
if (payload.event?.type === 'human_approval_request') {
|
if (payload.event?.type === 'human_approval_request') {
|
||||||
|
|||||||
@@ -100,7 +100,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<p class="final-score">{{ $t('ideas.finalScore', { score: adversarialEval.final_score.toFixed(1) }) }}</p>
|
<p class="final-score">{{ $t('ideas.finalScore', { score: adversarialEval.final_score.toFixed(1) }) }}</p>
|
||||||
<div class="net-sentiment" :class="sentimentClass(adversarialEval.net_sentiment)">
|
<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>
|
</div>
|
||||||
<p class="summary">{{ adversarialEval.analyst.summary }}</p>
|
<p class="summary">{{ adversarialEval.analyst.summary }}</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -366,7 +366,10 @@ function assessmentLabel(recommendation: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sentimentClass(sentiment: number) {
|
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) {
|
function scoreClass(score: number | null) {
|
||||||
|
|||||||
@@ -212,7 +212,6 @@ import { formatDate } from '../utils/time'
|
|||||||
import { parseStack } from '../utils/project'
|
import { parseStack } from '../utils/project'
|
||||||
import { projectStatusLabel, projectStageInfo, taskStatusLabel, taskStatusClass } from '../constants/project'
|
import { projectStatusLabel, projectStageInfo, taskStatusLabel, taskStatusClass } from '../constants/project'
|
||||||
import ConfirmDialog from '../components/ConfirmDialog.vue'
|
import ConfirmDialog from '../components/ConfirmDialog.vue'
|
||||||
import type { WorkflowEventPayload } from '../api/types'
|
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -296,12 +295,13 @@ interface LogEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formattedEvents = computed<LogEntry[]>(() => {
|
const formattedEvents = computed<LogEntry[]>(() => {
|
||||||
return store.liveEvents.map((evt: WorkflowEventPayload) => {
|
return store.liveEvents.map((evt) => {
|
||||||
const ev = evt.event
|
const ev = evt.event
|
||||||
const type = ev.type ?? 'unknown'
|
const type = ev.type ?? 'unknown'
|
||||||
const level = type.includes('error') || type.includes('fail') ? 'error'
|
const level = type.includes('error') || type.includes('fail') ? 'error'
|
||||||
: type.includes('warn') ? 'warn' : 'info'
|
: 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
|
const message = ev.label
|
||||||
? `[${ev.label}] ${type}: ${ev.output ?? ev.code ?? ev.message ?? JSON.stringify(ev)}`
|
? `[${ev.label}] ${type}: ${ev.output ?? ev.code ?? ev.message ?? JSON.stringify(ev)}`
|
||||||
: `${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()
|
syncConcurrencyConfig()
|
||||||
})
|
})
|
||||||
|
|
||||||
// 组件卸载清 debounce timer,防 unmount 后旧 timer 仍 fire 发无效 IPC(timer 泄漏)
|
// 组件卸载清全部 debounce timer,防 unmount 后旧 timer 仍 fire 写已销毁 reactive(timer 泄漏 FR-C3)
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (_concurrencyTimer) clearTimeout(_concurrencyTimer)
|
if (_concurrencyTimer) clearTimeout(_concurrencyTimer)
|
||||||
|
if (_knowledgeSaveTimer) clearTimeout(_knowledgeSaveTimer)
|
||||||
|
if (_toastTimer) clearTimeout(_toastTimer)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user