新增: 批次工作落地(推进链/评估闭环/事件总线/并发/加固) + 技术债清理 + 文档整理
后端: - 工作流推进链(D-03):advance_task/状态机/闸门走 df-nodes Node trait,conditions 条件引擎扩展 - 想法评估闭环:启发式评分+对抗评估,df-ideas/scoring + df-storage/idea_eval_repo + idea 前端打通 - 全局事件数据总线:df-ai/context+context_helpers+augmentation 跨模块解耦 - AI planner/plan_hint/intent:aichat B 路线并行多轮基础 - patch_file 加固(TD-03/04):读改写整体锁防 lost update,expected_hash 合约闭环 - 压缩超时兜底(F-15 卡死根治) - F-09 多会话并发:LlmConcurrency per-conv + streamingGuard 前端守护 + verify 脚本 - 知识注入 DRY/skills/audit 扩展 清理: - aichat 技术债(误报 allow/死导入/过时注释 30 项) - URGENT.md 删除(11 项加急全解决/迁 todo) - 文档整理(todo/待决策/待审查/ARCHITECTURE/INDEX + 总线/技术债审查新文档)
This commit is contained in:
@@ -66,14 +66,6 @@
|
||||
<h2>{{ $t('projectDetail.infoTitle') }}</h2>
|
||||
</div>
|
||||
<div class="project-info" v-if="currentProject">
|
||||
<div class="info-item">
|
||||
<span class="label">{{ $t('projectDetail.sourceIdea') }}</span>
|
||||
<router-link v-if="currentProject.idea_id" :to="`/ideas/${currentProject.idea_id}`" class="idea-link">
|
||||
{{ $t('projectDetail.viewIdea') }}
|
||||
</router-link>
|
||||
<span v-else class="no-idea">{{ $t('projectDetail.ideaDeleted') }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 代码目录(绑定 + 重定位) -->
|
||||
<div class="info-item">
|
||||
<span class="label">{{ $t('projectDetail.codeDirLabel') }}</span>
|
||||
@@ -124,6 +116,65 @@
|
||||
<span class="label">{{ $t('projectDetail.projectStatus') }}</span>
|
||||
<span class="status-badge" :class="currentProject.status">{{ $t(statusLabel) }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 来源灵感卡片(晋升携带评估结论回溯):仅 idea_id 有值时显示 -->
|
||||
<div v-if="currentProject.idea_id" class="source-idea-card">
|
||||
<div class="source-idea-header">
|
||||
<span class="source-idea-title">{{ $t('ideas.sourceIdea') }}</span>
|
||||
<router-link
|
||||
v-if="sourceIdea"
|
||||
class="idea-link"
|
||||
:to="`/ideas/${currentProject.idea_id}`"
|
||||
>
|
||||
{{ sourceIdea.title }} →
|
||||
</router-link>
|
||||
<router-link
|
||||
v-else
|
||||
class="idea-link idea-link-dim"
|
||||
:to="`/ideas/${currentProject.idea_id}`"
|
||||
>
|
||||
#{{ currentProject.idea_id }} →
|
||||
</router-link>
|
||||
</div>
|
||||
<!-- sourceIdea 未找到(灵感被删/未 load):提示跳转 -->
|
||||
<p v-if="!sourceIdea" class="source-idea-hint">{{ $t('projectDetail.ideaDeleted') }}</p>
|
||||
<!-- 已评估:显示评估结论回溯 -->
|
||||
<template v-else-if="sourceAiAnalysis">
|
||||
<div class="source-idea-body">
|
||||
<span
|
||||
class="assessment-badge"
|
||||
:class="assessmentClass(sourceAiAnalysis.recommendation)"
|
||||
>
|
||||
{{ assessmentLabel(sourceAiAnalysis.recommendation) }}
|
||||
</span>
|
||||
<span class="source-final-score">
|
||||
{{ $t('ideas.finalScore', { score: sourceAiAnalysis.final_score.toFixed(1) }) }}
|
||||
</span>
|
||||
</div>
|
||||
<p v-if="sourceAiAnalysis.summary" class="source-summary">{{ sourceAiAnalysis.summary }}</p>
|
||||
<!-- 多维评分条(借鉴 IdeaDetail parseScores 渲染) -->
|
||||
<div v-if="sourceScores.length" class="source-scores">
|
||||
<div class="radar-row" v-for="dim in sourceScores" :key="dim.name">
|
||||
<span class="radar-label">{{ dim.name }}</span>
|
||||
<div class="radar-bar-track">
|
||||
<div
|
||||
class="radar-bar-fill"
|
||||
:class="dim.score >= 80 ? 'fill-high' : dim.score >= 60 ? 'fill-mid' : 'fill-low'"
|
||||
:style="{ width: dim.score + '%' }"
|
||||
></div>
|
||||
</div>
|
||||
<span class="radar-value">{{ dim.score }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 灵感未评估:鼓励去评估 -->
|
||||
<p v-else class="source-idea-hint">
|
||||
<router-link class="idea-link" :to="`/ideas/${currentProject.idea_id}`">
|
||||
{{ $t('ideas.sourceIdeaNotEvaluated') }} →
|
||||
</router-link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -195,6 +246,7 @@ import { useProjectStore } from '@/stores/project'
|
||||
import { projectApi } from '@/api'
|
||||
import { formatDate } from '@/utils/time'
|
||||
import { parseStack } from '@/utils/project'
|
||||
import { parseScores as parseScoresJson, assessmentClass, assessmentLabel as assessmentLabelI18n } from '@/utils/ideaEval'
|
||||
import { projectStatusLabel, projectStageInfo, taskStatusLabel, taskStatusClass } from '../constants/project'
|
||||
import ConfirmDialog from '@/components/ConfirmDialog.vue'
|
||||
import ApprovalDialog from '@/components/project/ApprovalDialog.vue'
|
||||
@@ -237,6 +289,48 @@ const stageKey = computed(() => currentProject.value?.status ?? 'planning')
|
||||
const statusLabel = computed(() => projectStatusLabel(stageKey.value))
|
||||
const currentStageIndex = computed(() => projectStageInfo(stageKey.value).stepIndex)
|
||||
|
||||
// ── 来源灵感(晋升携带评估结论回溯)──
|
||||
// ProjectRecord.idea_id 存灵感 id(promote_idea 写入)。
|
||||
// sourceIdea: 从 store.ideas 反查;store 未 load 时由 onMounted 守卫补 load。
|
||||
const sourceIdea = computed(() =>
|
||||
store.ideas.find(i => i.id === currentProject.value?.idea_id) ?? null,
|
||||
)
|
||||
|
||||
// ai_analysis / scores 是 JSON 字符串(对齐 IdeaDetail 现有解析模式,try/catch 容错)
|
||||
interface SourceAnalysis {
|
||||
recommendation: string
|
||||
final_score: number
|
||||
summary: string
|
||||
}
|
||||
const sourceAiAnalysis = computed<SourceAnalysis | null>(() => {
|
||||
const idea = sourceIdea.value
|
||||
if (!idea?.ai_analysis) return null
|
||||
try {
|
||||
const parsed = JSON.parse(idea.ai_analysis) as Record<string, unknown>
|
||||
if (typeof parsed !== 'object' || parsed === null) return null
|
||||
const recommendation = typeof parsed.recommendation === 'string' ? parsed.recommendation : ''
|
||||
const final_score = typeof parsed.final_score === 'number' ? parsed.final_score : 0
|
||||
// summary 优先顶层,回退 analyst.summary(对齐 IdeaDetail historySummary 模式)
|
||||
const summary = typeof parsed.summary === 'string' ? parsed.summary
|
||||
: (typeof parsed.analyst === 'object' && parsed.analyst !== null
|
||||
&& typeof (parsed.analyst as Record<string, unknown>).summary === 'string'
|
||||
? ((parsed.analyst as Record<string, unknown>).summary as string)
|
||||
: '')
|
||||
if (!recommendation && !final_score && !summary) return null
|
||||
return { recommendation, final_score, summary }
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
// 项1 DRY:ScoreDimension/parseScores/assessmentClass/assessmentLabel 抽到 @/utils/ideaEval,
|
||||
// 与 IdeaDetail 共用。sourceScores 仅保留 computed(读 sourceIdea.scores → 复用纯函数解析)。
|
||||
const sourceScores = computed(() => parseScoresJson(sourceIdea.value?.scores))
|
||||
|
||||
// assessmentClass 直接复用 ideaEval(模板直调);assessmentLabel 需注入 t,留薄包装消除重复。
|
||||
const assessmentLabel = (recommendation: string): string =>
|
||||
assessmentLabelI18n(t, recommendation)
|
||||
|
||||
// ── 任务 ──
|
||||
const projectTasks = computed(() =>
|
||||
store.tasks.filter(t => t.project_id === projectId.value)
|
||||
@@ -410,6 +504,9 @@ onMounted(async () => {
|
||||
ensureLoaded() // 后台预热 Markdown 渲染器(模块单例,与 AiChat/TaskDetail 共享),不阻塞
|
||||
await store.loadProjects()
|
||||
await store.loadTasks() // 全量加载,详情页用 computed 过滤本项目(避免覆盖全局 tasks 单例)
|
||||
// 来源灵感回溯:项目有 idea_id 但 store.ideas 为空(本页未 load 过)时补拉,
|
||||
// 否则 sourceIdea computed 永远找不到对应灵感记录
|
||||
await store.loadIdeas()
|
||||
unlisten = await store.startEventListener()
|
||||
await checkPath()
|
||||
})
|
||||
@@ -477,6 +574,85 @@ onUnmounted(() => {
|
||||
|
||||
/* ===== 项目描述 Markdown 渲染(B-260615-25,基础样式收敛至全局 ai-md.css) ===== */
|
||||
.description.ai-md { font-size: 14px; color: var(--df-text); line-height: 1.6; }
|
||||
|
||||
/* ===== 来源灵感卡片(晋升携带评估结论回溯)===== */
|
||||
.source-idea-card {
|
||||
margin-top: 8px;
|
||||
padding: 12px 14px;
|
||||
background: var(--df-bg-raised);
|
||||
border: 0.5px solid var(--df-border);
|
||||
border-left: 3px solid var(--df-accent);
|
||||
border-radius: var(--df-radius);
|
||||
}
|
||||
.source-idea-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.source-idea-title { font-size: 13px; font-weight: 500; color: var(--df-text); }
|
||||
.info-item a.idea-link.idea-link-dim,
|
||||
.source-idea-card a.idea-link-dim { color: var(--df-text-dim); }
|
||||
.source-idea-card .source-idea-hint {
|
||||
font-size: 12px;
|
||||
color: var(--df-text-dim);
|
||||
margin: 4px 0 0;
|
||||
}
|
||||
.source-idea-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
margin: 6px 0;
|
||||
}
|
||||
.source-final-score {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: var(--df-text);
|
||||
}
|
||||
.source-summary {
|
||||
font-size: 12px;
|
||||
color: var(--df-text-secondary);
|
||||
line-height: 1.5;
|
||||
margin: 6px 0;
|
||||
}
|
||||
|
||||
/* assessment badge(复用 IdeaDetail 同名类的样式定义) */
|
||||
.assessment-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.assessment-badge.immediate { background: var(--df-success-bg); color: var(--df-success); }
|
||||
.assessment-badge.soon { background: var(--df-info-bg); color: var(--df-info); }
|
||||
.assessment-badge.conditional { background: var(--df-warning-bg); color: var(--df-warning); }
|
||||
.assessment-badge.revised { background: rgba(255, 217, 61, 0.2); color: var(--df-warning); }
|
||||
.assessment-badge.defer { background: var(--df-danger-bg); color: var(--df-danger); }
|
||||
.assessment-badge.cancel { background: var(--df-danger-bg); color: var(--df-danger); }
|
||||
|
||||
/* 多维评分条(复用 IdeaDetail radar-* 样式定义) */
|
||||
.source-scores {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.radar-row { display: flex; align-items: center; gap: 10px; }
|
||||
.radar-label { font-size: 12px; color: var(--df-text-secondary); min-width: 64px; text-align: right; }
|
||||
.radar-bar-track {
|
||||
flex: 1; height: 8px; background: var(--df-border);
|
||||
border-radius: var(--df-radius-xs); overflow: hidden;
|
||||
}
|
||||
.radar-bar-fill { height: 100%; border-radius: var(--df-radius-xs); transition: width 0.4s; }
|
||||
.fill-high { background: var(--df-success); }
|
||||
.fill-mid { background: var(--df-warning); }
|
||||
.fill-low { background: var(--df-danger); }
|
||||
.radar-value { font-size: 12px; font-weight: 500; min-width: 28px; text-align: right; color: var(--df-text); }
|
||||
|
||||
</style>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user