优化: 子代理体验与纪律注入

- 工具纪律段抽共享同源注入
- 子代理轨迹浮层与完成卡身份
- 分离窗会话切换修复
- 960s 统一出口报告不再蒸发
This commit is contained in:
lxy
2026-10-04 16:27:31 +08:00
parent b2d44f85e9
commit 01d231c39d
12 changed files with 1150 additions and 152 deletions
+33 -3
View File
@@ -10,8 +10,25 @@
import { useI18n } from 'vue-i18n'
import type { AiMessage, ContentPart, MentionSpan } from '../../api/types'
import { useAiStore } from '../../stores/ai'
const { t } = useI18n()
const store = useAiStore()
/**
* 前导 /技能名 解析(问题3 技能调用留痕的渲染侧):
* 落库文本形如 `/skill-name 正文`(后端 chat.rs user_content 统一加前缀),命中本机
* 已安装技能名单才渲染为技能 chip——普通以 / 开头的文本(路径、markdown)不误判。
* 返回 null = 非技能调用,走原纯文本路径零回归。
*/
function leadingSkill(msg: AiMessage): { name: string; rest: string } | null {
if (msg.role !== 'user') return null
const m = /^\/([A-Za-z0-9_-]+)(?:\s([\s\S]*))?$/.exec(msg.content)
if (!m) return null
const known = store.state.skills.some((s) => s.name === m[1])
if (!known) return null
return { name: m[1], rest: m[2] ?? '' }
}
defineProps<{
msg: AiMessage
@@ -89,9 +106,16 @@ function imgAlt(img: ImageContentPart): string {
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
</div>
<div class="ai-msg-bubble ai-msg-bubble--user">
<template v-for="(seg, idx) in segmentUserContent(msg)" :key="msg.id + '-seg-' + idx">
<span v-if="seg.type === 'text'">{{ seg.text }}</span>
<span v-else class="ai-msg-chip" :class="'ai-msg-chip--' + seg.span.kind">{{ seg.span.label }}</span>
<!-- 问题3 技能调用留痕:前导 /技能名 命中已安装技能 → 紫色 chip 标识,替代原文前缀显示 -->
<template v-if="leadingSkill(msg)">
<span class="ai-msg-chip ai-msg-chip--skill" :title="t('aiChat.skillBadgeTip', { name: leadingSkill(msg)!.name })">⚡ /{{ leadingSkill(msg)!.name }}</span>
<template v-if="leadingSkill(msg)!.rest">{{ ' ' + leadingSkill(msg)!.rest }}</template>
</template>
<template v-else>
<template v-for="(seg, idx) in segmentUserContent(msg)" :key="msg.id + '-seg-' + idx">
<span v-if="seg.type === 'text'">{{ seg.text }}</span>
<span v-else class="ai-msg-chip" :class="'ai-msg-chip--' + seg.span.kind">{{ seg.span.label }}</span>
</template>
</template>
<template v-if="msgImageParts(msg).length">
<div class="ai-msg-images">
@@ -153,6 +177,12 @@ function imgAlt(img: ImageContentPart): string {
background: var(--df-accent-bg, rgba(108,99,255,0.1));
color: var(--df-accent, #6c63ff);
}
/* 技能调用徽章(问题3):与 mention chip 同视觉家族,亮一档紫色底 + ⚡ 前缀区分 */
.ai-msg-chip--skill {
background: var(--df-accent-bg, rgba(108,99,255,0.18));
color: var(--df-accent, #6c63ff);
font-weight: 600;
}
/* 图片 */
.ai-msg-images {
display: flex;