重构: 对话透明化 L1 + ③.2 审批拆分 + ⑥.4 @展开 + DAG 展示

修复 tool_result 压缩零效果(BUG-260628-01):单行/少行/JSON 大字符
串字段逃逸压缩的问题,基于实测数据(53/94 次迭代零节省)调参,
增加字符级保底截断,压缩有效率从 8% 提升至 ~85%+
This commit is contained in:
2026-06-28 01:40:02 +08:00
parent e4ceb0015b
commit 53e1c1da77
19 changed files with 921 additions and 350 deletions

View File

@@ -120,6 +120,25 @@
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
</button>
</div>
<!-- .4 Phase 4: @项目展开摘要(输入区 enrichment 预览) -->
<div v-if="hasEnrichment" class="ai-enrichment-bar">
<button
class="ai-enrichment-toggle"
:class="{ 'ai-enrichment-toggle--expanded': enrichmentExpanded }"
@click="enrichmentExpanded = !enrichmentExpanded"
:title="enrichmentExpanded ? $t('aiChat.enrichmentCollapse') : $t('aiChat.enrichmentExpand')"
:aria-label="enrichmentExpanded ? $t('aiChat.enrichmentCollapse') : $t('aiChat.enrichmentExpand')"
>
<svg class="ai-enrichment-chevron" width="10" height="10" viewBox="0 0 10 10" fill="currentColor">
<path d="M2 3l3 4 3-4" />
</svg>
<span class="ai-enrichment-badge">{{ enrichmentBadgeText }}</span>
</button>
<div v-if="enrichmentExpanded" class="ai-enrichment-panel">
<div class="ai-enrichment-label">{{ $t('aiChat.enrichmentLabel') }}</div>
<pre class="ai-enrichment-body"><code>{{ enrichmentDetailText }}</code></pre>
</div>
</div>
<div class="ai-input-hint">{{ $t('aiChat.inputHint') }}</div>
</div>
</template>
@@ -382,6 +401,90 @@ const mentionStart = ref(-1)
// 后端据 mentionSpans resolve 投影成 Augmentation 注入;MessageList 据此精确切区间渲染 chip。
const pendingMentionSpans = ref<MentionSpan[]>([])
// ── ⑥.4 Phase 4: @项目展开摘要(输入区 enrichment 预览) ──
// 当用户 @[项目:xxx] 后,从 projectsStore 反查项目+关联任务/灵感,
// 在输入区下方显式 badge + 可展开的上下文参考面板。
interface ProjectEnrichment {
id: string
name: string
status: string
path: string | null
taskCount: number
ideaCount: number
inProgressTasks: { title: string; status: string }[]
pendingIdeas: { title: string; status: string }[]
}
const enrichmentExpanded = ref(false)
/** @项目 mentions 经 stores 反查后的 enrichment 摘要列表 */
const projectEnrichments = computed<ProjectEnrichment[]>(() => {
const projectSpans = pendingMentionSpans.value.filter(sp => sp.kind === 'project')
if (projectSpans.length === 0) return []
const projects = (projectsStore.projects || []) as ProjectRecord[]
const tasks = (projectsStore.tasks || []) as TaskRecord[]
const ideas = (projectsStore.ideas || []) as IdeaRecord[]
const results: ProjectEnrichment[] = []
for (const span of projectSpans) {
const project = projects.find(p => p.id === span.refId)
if (!project) continue
const relTasks = tasks.filter(t => t.project_id === project.id)
// ideas linking to this project via promoted_to (project name matching)
const relIdeas = ideas.filter(i => i.promoted_to === project.name || i.promoted_to === project.id)
const running = relTasks.filter(t => t.status === 'in_progress').slice(0, 20)
const pending = relIdeas.filter(i => i.status === 'pending_review').slice(0, 20)
results.push({
id: project.id,
name: project.name,
status: project.status as string,
path: project.path,
taskCount: relTasks.length,
ideaCount: relIdeas.length,
inProgressTasks: running.map(t => ({ title: t.title, status: t.status as string })),
pendingIdeas: pending.map(i => ({ title: i.title, status: i.status as string })),
})
}
return results
})
/** 是否有 enrichment 可展示 */
const hasEnrichment = computed(() => projectEnrichments.value.length > 0)
/** 概要 badge 文本(汇总所有 @项目 entrichment) */
const enrichmentBadgeText = computed(() => {
const list = projectEnrichments.value
if (!list.length) return ''
const totalTasks = list.reduce((s, p) => s + p.taskCount, 0)
const totalIdeas = list.reduce((s, p) => s + p.ideaCount, 0)
const names = list.map(p => p.name).join('、')
return t('aiChat.enrichmentBadge', { project: names, tasks: totalTasks, ideas: totalIdeas })
})
/** 展开后的 enrichment 正文(mimic 后端 build_augmentation_segment 格式) */
const enrichmentDetailText = computed(() => {
const list = projectEnrichments.value
if (!list.length) return ''
const lines: string[] = []
for (const p of list) {
lines.push(`【项目】${p.name}(状态: ${p.status}`)
if (p.path) lines.push(`目录: ${p.path}`)
if (p.inProgressTasks.length > 0) {
lines.push(`进行中任务(${p.taskCount}):`)
for (const t of p.inProgressTasks) {
lines.push(` - ${t.title} (${t.status})`)
}
}
if (p.pendingIdeas.length > 0) {
lines.push(`待评估灵感(${p.ideaCount}):`)
for (const i of p.pendingIdeas) {
lines.push(` - ${i.title} (${i.status})`)
}
}
lines.push('')
}
return lines.join('\n')
})
const mentionGroupLabel = computed(() => ({
project: t('aiChat.mentionGroupProject'),
task: t('aiChat.mentionGroupTask'),
@@ -972,4 +1075,70 @@ defineExpose({
margin-top: 4px;
opacity: 0.6;
}
/* ── ⑥.4 Phase 4: @项目展开摘要(输入区 enrichment 预览) ── */
.ai-enrichment-bar {
margin-top: 6px;
border: 0.5px solid color-mix(in srgb, var(--df-accent) 30%, transparent);
border-radius: var(--df-radius);
overflow: hidden;
}
.ai-enrichment-toggle {
display: flex;
align-items: center;
gap: 6px;
width: 100%;
padding: 4px 8px;
border: none;
background: color-mix(in srgb, var(--df-accent) 10%, transparent);
color: var(--df-text);
cursor: pointer;
font-size: 11px;
text-align: left;
transition: background 0.15s;
}
.ai-enrichment-toggle:hover {
background: color-mix(in srgb, var(--df-accent) 18%, transparent);
}
.ai-enrichment-toggle--expanded {
border-bottom: 0.5px solid color-mix(in srgb, var(--df-accent) 20%, transparent);
}
.ai-enrichment-chevron {
flex-shrink: 0;
color: var(--df-text-dim);
transition: transform 0.15s;
}
.ai-enrichment-toggle--expanded .ai-enrichment-chevron {
transform: rotate(180deg);
}
.ai-enrichment-badge {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
color: var(--df-accent);
font-weight: 500;
}
.ai-enrichment-panel {
padding: 6px 8px;
background: var(--df-bg);
max-height: 200px;
overflow-y: auto;
}
.ai-enrichment-label {
font-size: 10px;
color: var(--df-text-dim);
margin-bottom: 4px;
font-weight: 600;
}
.ai-enrichment-body {
margin: 0;
font-size: 11px;
line-height: 1.6;
color: var(--df-text);
white-space: pre-wrap;
word-break: break-all;
font-family: var(--df-font-mono, ui-monospace, monospace);
}
</style>