diff --git a/package-lock.json b/package-lock.json index 16ab06a..04ceb58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@arco-design/web-vue": "^2.58.0", "@tauri-apps/api": "^2", "@tauri-apps/plugin-dialog": "^2.7.1", - "dompurify": "^3.4.9", + "dompurify": "^3.4.10", "marked": "^18.0.5", "vue": "^3.5.13", "vue-i18n": "9", diff --git a/package.json b/package.json index 8c3290c..449dc3c 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "@arco-design/web-vue": "^2.58.0", "@tauri-apps/api": "^2", "@tauri-apps/plugin-dialog": "^2.7.1", - "dompurify": "^3.4.9", + "dompurify": "^3.4.10", "marked": "^18.0.5", "vue": "^3.5.13", "vue-i18n": "9", diff --git a/src/App.vue b/src/App.vue index 24e6094..ea3b919 100644 --- a/src/App.vue +++ b/src/App.vue @@ -270,8 +270,6 @@ const primaryNav = [ const secondaryNav = [ { path: '/knowledge', label: 'nav.knowledge', svg: '' }, - { path: '/decisions', label: 'nav.decisions', - svg: '' }, ] diff --git a/src/api/task.ts b/src/api/task.ts index 6974a03..1d4ed3b 100644 --- a/src/api/task.ts +++ b/src/api/task.ts @@ -6,6 +6,10 @@ export const taskApi = { return invoke('list_tasks', { projectId: projectId ?? null }) }, + get(id: string): Promise { + return invoke('get_task_by_id', { id }) + }, + create(input: CreateTaskInput): Promise { return invoke('create_task', { input }) }, diff --git a/src/api/types.ts b/src/api/types.ts index 8d9aeb5..32f4699 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -180,6 +180,8 @@ export type AiChatEvent = ({ type: 'AiError'; error: string } | { type: 'AiAgentRound'; round: number +} | { + type: 'AiHeartbeat' }) & { conversation_id?: string } @@ -221,6 +223,9 @@ export interface AiConversationSummary { updated_at: string } +/** 审批选择类型(F-260615-01: single=单选默认,multiple=多选) */ +export type ApprovalSelectType = 'single' | 'multiple' + /** 人工审批请求 */ export interface HumanApprovalRequest { execution_id: string @@ -228,6 +233,8 @@ export interface HumanApprovalRequest { title: string description: string options: string[] + /** F-260615-01: 缺省 single(向后兼容),multiple 时允许多选 */ + select_type?: ApprovalSelectType } /** 人工审批响应 */ @@ -235,6 +242,8 @@ export interface HumanApprovalResponse { execution_id: string node_id: string decision: string + /** F-260615-01: 多选结果(single 时长度=1,multiple 时长度≥1) */ + decisions?: string[] comment?: string } diff --git a/src/components/AiChat.vue b/src/components/AiChat.vue index b36e121..fd984bb 100644 --- a/src/components/AiChat.vue +++ b/src/components/AiChat.vue @@ -197,7 +197,7 @@ class="ai-msg-bubble ai-msg-bubble--ai ai-md" :class="{ 'ai-msg-bubble--error': msg.isError }" > -
+
@@ -308,14 +308,20 @@ + + +
{{ toast.msg }}
+
@@ -797,6 +815,63 @@ onMounted(async () => { margin-bottom: var(--df-gap-page); } +/* ===== 灵感描述 Markdown 渲染(B-260615-25,样式同 TaskDetail B-24 .ai-md 收敛) ===== */ +.detail-desc.ai-md { white-space: normal; color: var(--df-text-secondary); } +.detail-desc.ai-md :deep(p) { margin: 0 0 6px; } +.detail-desc.ai-md :deep(p:last-child) { margin-bottom: 0; } +.detail-desc.ai-md :deep(ul), .detail-desc.ai-md :deep(ol) { margin: 4px 0; padding-left: 20px; } +.detail-desc.ai-md :deep(li) { margin: 2px 0; line-height: 1.5; } +.detail-desc.ai-md :deep(code) { + font-family: var(--df-font-mono); + font-size: 12px; + padding: 1px 5px; + background: rgba(255,255,255,0.06); + border-radius: var(--df-radius-sm); + color: var(--df-accent); +} +.detail-desc.ai-md :deep(pre) { + margin: 8px 0; + padding: 10px 12px; + background: var(--df-bg); + border: 0.5px solid var(--df-border); + border-radius: var(--df-radius); + overflow-x: auto; +} +.detail-desc.ai-md :deep(pre code) { + padding: 0; + background: transparent; + border-radius: 0; + color: var(--df-text-secondary); + font-size: 12px; + line-height: 1.5; +} +.detail-desc.ai-md :deep(blockquote) { + margin: 6px 0; + padding: 4px 12px; + border-left: 2px solid var(--df-accent); + color: var(--df-text-secondary); +} +.detail-desc.ai-md :deep(h1), .detail-desc.ai-md :deep(h2), .detail-desc.ai-md :deep(h3) { + font-weight: 500; + color: var(--df-text); + margin: 8px 0 4px; +} +.detail-desc.ai-md :deep(h1) { font-size: 16px; } +.detail-desc.ai-md :deep(h2) { font-size: 14px; } +.detail-desc.ai-md :deep(h3) { font-size: 13px; } +.detail-desc.ai-md :deep(a) { color: var(--df-accent); text-decoration: none; } +.detail-desc.ai-md :deep(a:hover) { text-decoration: underline; } +.detail-desc.ai-md :deep(strong) { font-weight: 500; color: var(--df-text); } +.detail-desc.ai-md :deep(hr) { border: none; border-top: 0.5px solid var(--df-border); margin: 8px 0; } +.detail-desc.ai-md :deep(table) { + width: 100%; border-collapse: collapse; margin: 6px 0; + font-size: 12px; overflow-x: auto; display: block; +} +.detail-desc.ai-md :deep(th), .detail-desc.ai-md :deep(td) { + padding: 4px 8px; border: 0.5px solid var(--df-border); text-align: left; +} +.detail-desc.ai-md :deep(th) { font-weight: 500; background: var(--df-bg); } + .detail-section { margin-bottom: var(--df-gap-page); } diff --git a/src/views/Knowledge.vue b/src/views/Knowledge.vue index d1edc98..06bd804 100644 --- a/src/views/Knowledge.vue +++ b/src/views/Knowledge.vue @@ -110,7 +110,13 @@
-
{{ detail.knowledge.content }}
+ +
+
@@ -229,11 +235,22 @@ import { ref, computed, onMounted, watch } from 'vue' import { useI18n } from 'vue-i18n' import { useKnowledgeStore, KNOWLEDGE_KINDS, parseTags } from '../stores/knowledge' +import { useMarkdown } from '../composables/useMarkdown' import type { KnowledgeDetailPayload, KnowledgeEventRecord } from '../api/types' const { t } = useI18n() const store = useKnowledgeStore() +// B-260615-25:知识内容 Markdown 渲染(复用 AiChat/TaskDetail 同款渲染器,模块级单例) +const { renderMd, loadMarkdown, mdReady } = useMarkdown() + +// 内容渲染依赖 mdReady 响应式:loadMarkdown 完成前 escapeFallback 纯文本,完成后 mdReady 翻转 +// 触发 computed 重算(B-25 漏响应式致 v-html=renderMd 首次纯文本后不重算,代码块不渲染) +const renderedContent = computed(() => { + void mdReady.value + return renderMd(detail.value?.knowledge.content ?? '') +}) + // 顶层 Tab: library(知识库) | inbox(审核收件箱) const topTab = ref<'library' | 'inbox'>('library') @@ -495,6 +512,7 @@ function relativeTime(millisStr: string): string { } onMounted(() => { + loadMarkdown() // 后台预热 Markdown 渲染器(模块单例,与 AiChat/TaskDetail 共享),不阻塞 store.loadList() store.loadCandidates() }) @@ -592,6 +610,63 @@ onMounted(() => { .detail-field { margin-bottom: 12px; } .detail-field label { display: block; font-size: 12px; color: var(--df-text-secondary); margin-bottom: 4px; } .detail-content { font-size: 13px; color: var(--df-text); line-height: 1.6; white-space: pre-wrap; } + +/* ===== 知识内容 Markdown 渲染(B-260615-25,样式同 TaskDetail B-24 .ai-md 收敛) ===== */ +.detail-content.ai-md { white-space: normal; } +.detail-content.ai-md :deep(p) { margin: 0 0 6px; } +.detail-content.ai-md :deep(p:last-child) { margin-bottom: 0; } +.detail-content.ai-md :deep(ul), .detail-content.ai-md :deep(ol) { margin: 4px 0; padding-left: 20px; } +.detail-content.ai-md :deep(li) { margin: 2px 0; line-height: 1.5; } +.detail-content.ai-md :deep(code) { + font-family: var(--df-font-mono); + font-size: 12px; + padding: 1px 5px; + background: rgba(255,255,255,0.06); + border-radius: var(--df-radius-sm); + color: var(--df-accent); +} +.detail-content.ai-md :deep(pre) { + margin: 8px 0; + padding: 10px 12px; + background: var(--df-bg); + border: 0.5px solid var(--df-border); + border-radius: var(--df-radius); + overflow-x: auto; +} +.detail-content.ai-md :deep(pre code) { + padding: 0; + background: transparent; + border-radius: 0; + color: var(--df-text-secondary); + font-size: 12px; + line-height: 1.5; +} +.detail-content.ai-md :deep(blockquote) { + margin: 6px 0; + padding: 4px 12px; + border-left: 2px solid var(--df-accent); + color: var(--df-text-secondary); +} +.detail-content.ai-md :deep(h1), .detail-content.ai-md :deep(h2), .detail-content.ai-md :deep(h3) { + font-weight: 500; + color: var(--df-text); + margin: 8px 0 4px; +} +.detail-content.ai-md :deep(h1) { font-size: 16px; } +.detail-content.ai-md :deep(h2) { font-size: 14px; } +.detail-content.ai-md :deep(h3) { font-size: 13px; } +.detail-content.ai-md :deep(a) { color: var(--df-accent); text-decoration: none; } +.detail-content.ai-md :deep(a:hover) { text-decoration: underline; } +.detail-content.ai-md :deep(strong) { font-weight: 500; color: var(--df-text); } +.detail-content.ai-md :deep(hr) { border: none; border-top: 0.5px solid var(--df-border); margin: 8px 0; } +.detail-content.ai-md :deep(table) { + width: 100%; border-collapse: collapse; margin: 6px 0; + font-size: 12px; overflow-x: auto; display: block; +} +.detail-content.ai-md :deep(th), .detail-content.ai-md :deep(td) { + padding: 4px 8px; border: 0.5px solid var(--df-border); text-align: left; +} +.detail-content.ai-md :deep(th) { font-weight: 500; background: var(--df-bg); } .detail-tags { display: flex; gap: 6px; flex-wrap: wrap; } .tag { font-size: 11px; padding: 2px 8px; border-radius: var(--df-radius-xs); background: rgba(108,99,255,0.1); color: var(--df-accent); } .muted { color: var(--df-text-dim); font-size: 12px; } diff --git a/src/views/ProjectDetail.vue b/src/views/ProjectDetail.vue index 7819f4d..590e5e9 100644 --- a/src/views/ProjectDetail.vue +++ b/src/views/ProjectDetail.vue @@ -110,9 +110,15 @@ {{ $t('projectDetail.updatedAt') }} {{ formatDate(currentProject.updated_at) }} -
+
{{ $t('projectDetail.description') }} - {{ currentProject.description || '—' }} + + +
{{ $t('projectDetail.projectStatus') }} @@ -153,7 +159,6 @@

{{ $t('projectDetail.workflowLogTitle') }}

-
@@ -176,17 +181,39 @@

{{ store.pendingApproval.description }}

{{ $t('projectDetail.approvalHint') }}

-
+ + +
-
+
{{ task.title }} @@ -101,13 +101,15 @@