diff --git a/src/components/task/TaskOutputCard.vue b/src/components/task/TaskOutputCard.vue new file mode 100644 index 0000000..66d8c62 --- /dev/null +++ b/src/components/task/TaskOutputCard.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/src/views/TaskDetail.vue b/src/views/TaskDetail.vue index 939198c..0e0c201 100644 --- a/src/views/TaskDetail.vue +++ b/src/views/TaskDetail.vue @@ -125,23 +125,8 @@ {{ $t('taskDetail.workflowDef') }} {{ task.workflow_def_id ?? '—' }} - - -
- {{ $t('taskDetail.output') }} -
- -
- {{ reviewVerdictLabel }} - {{ parsedOutput.review.summary }} -
    -
  • {{ s }}
  • -
-
- -
-
-
+ +
{{ $t('taskDetail.createdAt') }} {{ formatDate(task.created_at) }} @@ -172,6 +157,7 @@ import { priorityClass, } from '../constants/project' import type { TaskRecord, ProjectRecord, DfDataChangedPayload, WorkflowEventPayload } from '@/api/types' +import TaskOutputCard from '@/components/task/TaskOutputCard.vue' const { t } = useI18n() const route = useRoute() @@ -210,56 +196,8 @@ const { rendered: renderedDesc, ensureLoaded } = useRendered( () => task.value?.description ?? '', ) -// ============================================================ -// F-AiNodeSelfReview: output_json 解析 + AI 产出 Markdown 渲染 -// ------------------------------------------------------------ -// output_json schema(与 df-nodes ai_node.rs 写产出现对齐): -// { text, model?, usage?, review?: { verdict: 'pass'|'fail'|'unknown', summary?, suggestions? } } -// parsedOutput try/catch: JSON.parse 边界必要兜底(LLM 自审输出可能不合规/老数据格式漂移), -// 解析失败返回 null → v-if 守卫整块不渲染(不裸显报错)。 -interface TaskOutputReview { - verdict?: string - summary?: string - suggestions?: unknown -} -interface TaskOutput { - text?: string - model?: string - usage?: unknown - review?: TaskOutputReview -} -const parsedOutput = computed(() => { - const raw = task.value?.output_json - if (!raw) return null - try { - return JSON.parse(raw) as TaskOutput - } catch { - return null - } -}) -// AI 产出正文渲染(复用模块单例渲染器,与 renderedDesc 共享 marked/DOMPurify/缓存) -const { rendered: renderedOutput } = useRendered( - () => parsedOutput.value?.text ?? '', -) -// review.verdict 红绿标:pass→绿(success),fail→红(danger),未知/缺失→灰(dim) -const reviewVerdictClass = computed(() => { - const v = parsedOutput.value?.review?.verdict - if (v === 'pass') return 'review-pass' - if (v === 'fail') return 'review-fail' - return 'review-unknown' -}) -// review.verdict 标签(pass/fail/unknown 走 i18n,异常值兜底 unknown) -const reviewVerdictLabel = computed(() => { - const v = parsedOutput.value?.review?.verdict - if (v === 'pass') return t('taskDetail.review.pass') - if (v === 'fail') return t('taskDetail.review.fail') - return t('taskDetail.review.unknown') -}) -// review.suggestions 列表(数组守卫,非数组时返回空避免 v-for 报错) -const reviewSuggestions = computed(() => { - const s = parsedOutput.value?.review?.suggestions - return Array.isArray(s) ? s.map(String) : [] -}) +// F-AiNodeSelfReview: output_json 解析 + AI 产出/自审渲染已抽至 TaskOutputCard 子组件 +// (components/task/TaskOutputCard.vue),父级只传 output-json prop,零行为变更。 const projectName = computed(() => { const p = projects.value.find(p => p.id === task.value?.project_id) @@ -640,34 +578,7 @@ onBeforeUnmount(() => { /* B-24 漏 white-space 覆盖致 v-html 后 HTML 标签间 \n 被 pre-wrap 渲染为空行间距——移除 pre-wrap,描述由 .ai-md 接管(line-height 1.5 对齐 AiChat li) */ .info-item .value.description { line-height: 1.5; } -/* ===== F-AiNodeSelfReview: output_json 区块(产出 + 自审结论卡) ===== */ -.output-block .output-body { - display: flex; - flex-direction: column; - gap: 10px; -} -/* 自审结论卡:verdict 红绿标(pass 绿 / fail 红 / unknown 灰),复用全局 --df-success/danger token */ -.review-card { - display: flex; - flex-direction: column; - gap: 4px; - padding: 8px 12px; - border-radius: var(--df-radius-sm); - border: 0.5px solid var(--df-border); - font-size: 13px; -} -.review-pass { background: var(--df-success-bg); border-color: rgba(61,219,160,0.3); } -.review-fail { background: var(--df-danger-bg); border-color: rgba(240,101,101,0.3); } -.review-unknown { background: var(--df-bg-card); } -.review-verdict { font-weight: 600; } -.review-pass .review-verdict { color: var(--df-success); } -.review-fail .review-verdict { color: var(--df-danger); } -.review-unknown .review-verdict { color: var(--df-text-dim); } -.review-summary { color: var(--df-text-secondary); line-height: 1.5; } -.review-suggestions { margin: 4px 0 0; padding-left: 20px; color: var(--df-text-secondary); } -.review-suggestions li { line-height: 1.6; } -/* AI 产出正文(继承全局 .ai-md 块级样式,line-height 对齐描述) */ -.output-text { line-height: 1.5; } +/* F-AiNodeSelfReview output_json 区块样式已随子组件移至 TaskOutputCard.vue */ /* ===== 任务描述 Markdown 渲染(B-24,基础样式收敛至全局 ai-md.css) ===== */