优化: 清理无意义注释(走查编号前缀/过时历史标注/死代码注释)
This commit is contained in:
@@ -128,7 +128,7 @@ import type { AiMessage, ContentPart, SkillInfo, ProjectRecord, TaskRecord, Idea
|
||||
import type { MentionItem } from './MentionItem'
|
||||
|
||||
const props = defineProps<{
|
||||
/** UX-09: 编辑末条 user 消息态;父持有,handleSend 据此走 editMessage 而非 sendMessage */
|
||||
/** 编辑末条 user 消息态;父持有,handleSend 据此走 editMessage 而非 sendMessage */
|
||||
editingMsgId: string | null
|
||||
}>()
|
||||
|
||||
@@ -317,7 +317,7 @@ watch(inputText, (v) => {
|
||||
// / 技能联想(仅当输入以 / 开头且光标在首字符区)
|
||||
skillOpen.value = v.startsWith('/')
|
||||
if (skillOpen.value) skillIndex.value = 0
|
||||
// UX-10: @ 实体引用(技能联想与 @ 联想互斥,二者同时仅一开;视觉打架防护)
|
||||
// @ 实体引用(技能联想与 @ 联想互斥,二者同时仅一开;视觉打架防护)
|
||||
if (skillOpen.value) {
|
||||
mentionOpen.value = false
|
||||
mentionStart.value = -1
|
||||
@@ -327,7 +327,7 @@ watch(inputText, (v) => {
|
||||
})
|
||||
|
||||
/**
|
||||
* UX-10: 检测光标前最近的 @ 触发符。
|
||||
* 检测光标前最近的 @ 触发符。
|
||||
* 规则:从光标位置向前找最近的 @,期间无空白/换行则激活(支持 "abc @dev" 但不支持 "a @b c @d" 中第二个被空格阻断)。
|
||||
* 找到 → mentionStart=该 @ 下标, mentionOpen=true; 否则关闭。
|
||||
*/
|
||||
@@ -675,7 +675,7 @@ function selectMention(item: MentionItem) {
|
||||
}
|
||||
|
||||
function handleKeydown(e: KeyboardEvent) {
|
||||
// UX-09:编辑态 Escape 取消编辑(最高优先级)
|
||||
// 编辑态 Escape 取消编辑(最高优先级)
|
||||
if (props.editingMsgId && e.key === 'Escape') {
|
||||
e.preventDefault()
|
||||
emit('cancel-edit')
|
||||
@@ -716,7 +716,7 @@ function handleKeydown(e: KeyboardEvent) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// UX-10: @ 实体引用浮层导航(仅有匹配项时拦截方向/回车;与技能联想互斥)
|
||||
// @ 实体引用浮层导航(仅有匹配项时拦截方向/回车;与技能联想互斥)
|
||||
if (mentionOpen.value && mentionItems.value.length) {
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault()
|
||||
@@ -907,7 +907,7 @@ function focus(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* UX-260617-16:未发送输入保护。检测 inputText/pendingImages/pendingSkill 任一非空,
|
||||
* 未发送输入保护。检测 inputText/pendingImages/pendingSkill 任一非空,
|
||||
* 用于 beforeunload 拦截(分离窗口关闭/刷新)与未来 onBeforeUnmount 拦截(面板切换)。
|
||||
*/
|
||||
function hasUnsentInput(): boolean {
|
||||
|
||||
@@ -322,7 +322,7 @@ function onActionsOutsideClick(e: MouseEvent) {
|
||||
}
|
||||
|
||||
/**
|
||||
* UX-260617-23:搜索结果点击切对话后清空 searchQuery,退出搜索模式回原时间分组视图。
|
||||
* 搜索结果点击切对话后清空 searchQuery,退出搜索模式回原时间分组视图。
|
||||
* M3:命中项可能在折叠的时间分组或折叠的归档分组内,选中态不可见。切回分组视图后展开所在分组
|
||||
* 并 scrollIntoView,确保选中项立即可见(原仅切 searchQuery,选中项落在折叠组里看不到高亮)。
|
||||
*/
|
||||
@@ -425,7 +425,7 @@ const searchResults = computed<AiConversationSummary[] | null>(
|
||||
() => store.filteredConversations.value,
|
||||
)
|
||||
|
||||
// ── UX-2025-16: 侧栏宽度拖拽 ──
|
||||
// ── 侧栏宽度拖拽 ──
|
||||
const resizeState = { startX: 0, startWidth: 0 }
|
||||
function onResizeMove(e: MouseEvent) {
|
||||
store.setSidebarWidth(resizeState.startWidth + (e.clientX - resizeState.startX))
|
||||
@@ -451,11 +451,11 @@ onMounted(() => {
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('click', onActionsOutsideClick)
|
||||
// UX-2025-16:拖拽中途卸载兜底摘临时 document listener(原 AiChat 同段迁移)
|
||||
// 拖拽中途卸载兜底摘临时 document listener(原 AiChat 同段迁移)
|
||||
document.removeEventListener('mousemove', onResizeMove)
|
||||
document.removeEventListener('mouseup', onResizeEnd)
|
||||
document.body.classList.remove('ai-sidebar-resizing')
|
||||
// UX-2025-20:释放标题淡入计时器
|
||||
// 释放标题淡入计时器
|
||||
if (_titleFlashTimer) { clearTimeout(_titleFlashTimer); _titleFlashTimer = null }
|
||||
})
|
||||
|
||||
@@ -482,7 +482,7 @@ defineExpose({
|
||||
height: 100%;
|
||||
background: var(--df-bg);
|
||||
}
|
||||
/* UX-2025-16:右边缘 2px 拖拽条。hover/active 显高亮;拖拽中 body.ai-sidebar-resizing 全局锁 cursor 防抖 */
|
||||
/* 右边缘 2px 拖拽条。hover/active 显高亮;拖拽中 body.ai-sidebar-resizing 全局锁 cursor 防抖 */
|
||||
.ai-sidebar-resize-handle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -662,7 +662,7 @@ body.ai-sidebar-resizing * {
|
||||
font-size: 9px;
|
||||
color: var(--df-text-dim);
|
||||
}
|
||||
/* F-09: 多会话并发生成指示器(会话项标题行内脉冲点,多会话可同时显示) */
|
||||
/* 多会话并发生成指示器(会话项标题行内脉冲点,多会话可同时显示) */
|
||||
.ai-conv-gen-dot {
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
@@ -706,7 +706,7 @@ body.ai-sidebar-resizing * {
|
||||
background: var(--df-sidebar-hover);
|
||||
color: var(--df-text);
|
||||
}
|
||||
/* CR-22-1: 已置顶对话视觉强调(左边线,不依赖菜单展开,一眼识别)。
|
||||
/* 已置顶对话视觉强调(左边线,不依赖菜单展开,一眼识别)。
|
||||
pinned 类挂在 .ai-conv-item 上(操作收进更多菜单后,按钮不再常驻,改 item 级类)。 */
|
||||
.ai-conv-item--pinned {
|
||||
box-shadow: inset 2px 0 0 var(--df-accent, var(--df-text));
|
||||
|
||||
@@ -70,7 +70,7 @@ const { confirmState, confirmDialog, answerConfirm } = useConfirm()
|
||||
// 首轮慢响应(看门狗已超时清 streaming)之后——此时 streaming=false 但后端 generating=true、
|
||||
// generatingConvs 含 conv(useAiEvents handleEvent 对非完成/错误事件 add)。若守卫仍 && streaming,
|
||||
// 挂起弹窗永不显示 → loop 无声卡死(用户发消息无响应)。去 streaming,isGenerating(conv) 单条件覆盖。
|
||||
// F-09: 多会话并发 — isGenerating(active) 替代单值比对
|
||||
// 多会话并发 — isGenerating(active) 替代单值比对
|
||||
const isViewingGenerating = computed(() =>
|
||||
store.isGenerating(store.state.activeConversationId),
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ const emit = defineEmits<{
|
||||
(e: 'go-to-settings'): void
|
||||
}>()
|
||||
|
||||
// ── UX-2025-20: 空状态示例问题 ──
|
||||
// ── 空状态示例问题 ──
|
||||
// 示例问题卡片(点击自动填入并发送);文案走 i18n key 数组,模板按 key 翻译。
|
||||
const examplePrompts = [
|
||||
'aiChat.examplePrompt1',
|
||||
@@ -85,7 +85,7 @@ const examplePrompts = [
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
}
|
||||
/* UX-2025-20: 无 provider 引导 — 跳 Settings 按钮(克制:边框线 + accent 文字,不喧宾) */
|
||||
/* 无 provider 引导 — 跳 Settings 按钮(克制:边框线 + accent 文字,不喧宾) */
|
||||
.ai-empty-action {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -43,7 +43,7 @@ const { t } = useI18n()
|
||||
// 耗时长,130s 流式看门狗极易在迭代间隙超时清 streaming → 卡片因 streaming=false 不弹 → 用户看不到
|
||||
// "继续/停止" → generating 永真卡死。达 max 比 DirAuthAuth 更易触发(必经多轮)。去 streaming,
|
||||
// isGenerating(conv) 单条件覆盖挂起态(达 max 时 generatingConvs 含 conv,useAiEvents handleEvent 对非完成/错误事件 add)。
|
||||
// F-09: 多会话并发 — isGenerating(active) 替代单值比对
|
||||
// 多会话并发 — isGenerating(active) 替代单值比对
|
||||
const isViewingGenerating = computed(() =>
|
||||
store.isGenerating(store.state.activeConversationId),
|
||||
)
|
||||
|
||||
@@ -28,7 +28,7 @@ import { formatRelative, formatDate } from '../../utils/time'
|
||||
import type { AiMessage } from '../../api/types'
|
||||
|
||||
defineProps<{
|
||||
/** UX-09:当前正在编辑的消息 id(父持有,经 ChatInput 透传);末条 user 编辑按钮据此隐藏 */
|
||||
/** 当前正在编辑的消息 id(父持有,经 ChatInput 透传);末条 user 编辑按钮据此隐藏 */
|
||||
editingMsgId?: string | null
|
||||
}>()
|
||||
|
||||
@@ -42,8 +42,8 @@ const emit = defineEmits<{
|
||||
const store = useAiStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
// ═══ Markdown 渲染(ARC-260615-08:自研块级 memo,借鉴方案D流式核心) ═══
|
||||
// B-23:渲染基础设施(_marked/_purify/mdReady/_mdCache/loadMarkdown/escapeFallback/
|
||||
// ═══ Markdown 渲染自研块级 memo,借鉴方案D流式核心) ═══
|
||||
// 渲染基础设施(_marked/_purify/mdReady/_mdCache/loadMarkdown/escapeFallback/
|
||||
// renderMd)抽至 composables/useMarkdown(模块级单例),与 TaskDetail/AiChat 共享。
|
||||
// 流式核心(splitBlocks/parseBlock/renderStreamingBlocks/scheduleStreamParse)抽至
|
||||
// composables/ai/useStreamRenderer(本组件经 opts 注入 saveSelection/restoreSelection/
|
||||
@@ -54,14 +54,14 @@ const { t } = useI18n()
|
||||
// scheduleStreamParse/renderStreamingBlocks/clearStreamingState 均由 composable 返回。
|
||||
const { loadMarkdown } = useMarkdown()
|
||||
|
||||
// ── UX-2025-20: 空状态示例问题(examplePrompts 已随 EmptyState.vue 提取) ──
|
||||
// ── 空状态示例问题(examplePrompts 已随 EmptyState.vue 提取) ──
|
||||
|
||||
const messagesContainer = ref<HTMLDivElement>()
|
||||
// 工具卡片列表(子组件 ToolCardList 自治折叠态,父级仅经 expose 调 collapseInactive 收起旧卡)
|
||||
const toolCardListRef = ref<InstanceType<typeof ToolCardList>>()
|
||||
|
||||
// 当前视图是否正在生成(切走后台生成时光标不显示)
|
||||
// F-09: 多会话并发 — isGenerating(active) 替代单值比对
|
||||
// 多会话并发 — isGenerating(active) 替代单值比对
|
||||
const isViewingGenerating = computed(() =>
|
||||
store.state.streaming && store.isGenerating(store.state.activeConversationId)
|
||||
)
|
||||
@@ -72,10 +72,10 @@ function isLastAi(msg: AiMessage): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* B-260618-19: 是否渲染某条消息(供 .ai-msg-slot v-if 守卫)。
|
||||
* 是否渲染某条消息(供 .ai-msg-slot v-if 守卫)。
|
||||
*
|
||||
* 背景:agentic 多轮下,LLM 某轮只发 tool_calls 不输出文本时(GLM anthropic_compat 常见),
|
||||
* 该轮 assistant 消息 content='' + toolCalls=[...];变中间条后,旧 v-else-if(UX-260617-22)
|
||||
* 该轮 assistant 消息 content='' + toolCalls=[...];变中间条后,旧 v-else-if
|
||||
* 漏判 toolCalls 把它整条过滤 → 工具卡片消失,且外层 .ai-msg-slot 恒渲染留空 div,
|
||||
* flex gap 堆积成大片空白,末条光标气泡随每轮 AiAgentRound push 往下移。
|
||||
*
|
||||
@@ -207,14 +207,14 @@ async function handleMessagesScroll(): Promise<void> {
|
||||
}
|
||||
|
||||
watch(() => store.state.messages.length, onContentChange)
|
||||
// SW-260618-07: currentText → onContentChange 合并到下方 scheduleStreamParse watch(同源 currentText,单 callback 顺序执行)
|
||||
// currentText → onContentChange 合并到下方 scheduleStreamParse watch(同源 currentText,单 callback 顺序执行)
|
||||
|
||||
// 流式 Markdown 渲染驱动(ARC-260615-08 块级 memo):
|
||||
// - currentText 变化(每 delta):rAF 节流 scheduleStreamParse,块级 memo 只重 parse 末块
|
||||
// - streaming 翻转:结束清 rAF + 清 streamingBlocks(回 renderMd 走 final marked + 缓存)
|
||||
// - mdReady 翻转(CR-260615-06):首屏 marked 慢 + 暂无新 delta 时末段停留 escapeFallback
|
||||
// - mdReady 翻转首屏 marked 慢 + 暂无新 delta 时末段停留 escapeFallback
|
||||
// 纯文本;marked 就绪后主动重算一次,使纯文本转格式化
|
||||
// SW-260618-07: 合并 currentText 双 watch(原 :2040 onContentChange 滚动 + 本 watch scheduleStreamParse 分块)。
|
||||
// 合并 currentText 双 watch(原 :2040 onContentChange 滚动 + 本 watch scheduleStreamParse 分块)。
|
||||
// 顺序:onContentChange(滚动跟随)先 → scheduleStreamParse(rAF 分块渲染)后,保持原注册顺序。
|
||||
watch(() => store.state.currentText, (text) => {
|
||||
// 流式滚动跟随由 scheduleStreamParse 的 rAF 回调承接(DOM 高度就绪后滚),
|
||||
@@ -244,7 +244,7 @@ watch(() => store.state.streaming, (s) => {
|
||||
// streamingBlocks(依赖 currentText watch 的 !text 分支按新轮时机清,BUG-260624-01)。
|
||||
cancelPendingRaf()
|
||||
if (s) {
|
||||
// UX-260616-03: 新一轮生成开始 → 收起上一轮已完成的旧工具卡,聚焦当前生成内容。
|
||||
// 新一轮生成开始 → 收起上一轮已完成的旧工具卡,聚焦当前生成内容。
|
||||
// 仅当前视图正在生成时触发(isGenerating(activeConversationId)),
|
||||
// 切走后台生成/恢复会话(streaming 初值已 true 的 onMounted 路径)不误触。
|
||||
// buildActiveToolIds/collapseAllToolLists 是 function 声明,提升可用。
|
||||
@@ -252,7 +252,7 @@ watch(() => store.state.streaming, (s) => {
|
||||
collapseAllToolLists(buildActiveToolIds(store.state.messages))
|
||||
}
|
||||
} else {
|
||||
// UX-2025-01: 流式→完成切换,模板从 v-for 分块切到 v-html 整段,DOM 重建
|
||||
// 流式→完成切换,模板从 v-for 分块切到 v-html 整段,DOM 重建
|
||||
saveSelection()
|
||||
clearStreamingState()
|
||||
nextTick(() => restoreSelection())
|
||||
@@ -270,7 +270,7 @@ watch(() => store.state.streaming, (s) => {
|
||||
// 只在 toolCall 状态翻转(新卡片/完成/拒绝)时触发,与折叠语义精确对齐。
|
||||
// snap/MESSAGE_CAP 兜底机制保持不变(deep watch 仍做变化检测,只是 body 短路掉无意义重算)。
|
||||
//
|
||||
// UX-260616-03: 触发时机扩展。除原 messages deep watch(toolCall 状态翻转/新消息条数),
|
||||
// 触发时机扩展。除原 messages deep watch(toolCall 状态翻转/新消息条数),
|
||||
// 另加 agentRound watch(AiAgentRound 新轮次 → 收起上一轮工具卡,聚焦当前轮)。
|
||||
// active Set 推导逻辑复用 buildActiveToolIds,两 watch 共用。
|
||||
function buildActiveToolIds(msgs: typeof store.state.messages): Set<string> {
|
||||
@@ -330,7 +330,7 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
// UX-260616-03: AiAgentRound 新轮次到达时收起上一轮工具卡(round 递增)。
|
||||
// AiAgentRound 新轮次到达时收起上一轮工具卡(round 递增)。
|
||||
// 首次加载不触发(等真到达更"新"的轮次才收起,避免历史会话加载即折叠)。
|
||||
watch(
|
||||
() => store.state.agentRound,
|
||||
@@ -340,12 +340,12 @@ watch(
|
||||
},
|
||||
)
|
||||
|
||||
// CR-260615-06: mdReady 就绪后主动触发首屏 Markdown 重渲染
|
||||
// mdReady 就绪后主动触发首屏 Markdown 重渲染
|
||||
// (marked 加载慢 + 无新 delta 时末段停留纯文本,不触发模板重算)
|
||||
const _mdRenderKey = ref(0)
|
||||
function forceUpdate() { _mdRenderKey.value++ }
|
||||
|
||||
// ── UX-2025-01: 选文字保持(save/restore Selection) ──
|
||||
// ── 选文字保持(save/restore Selection) ──
|
||||
// v-html 整体替换 innerHTML 会销毁子 DOM 节点 → Node 引用断连 → 选区丢失。
|
||||
// 方案: 保存为「气泡内文本偏移量 + 选中文字」,重建后按偏移量重新定位。
|
||||
// 偏移量对 Markdown 渲染结果稳定(同内容→同DOM文本节点序列→同偏移)。
|
||||
@@ -484,7 +484,7 @@ watch(mdReady, (ready) => {
|
||||
}
|
||||
})
|
||||
|
||||
// ── 消息气泡拷贝(F-260615-13) ──
|
||||
// ── 消息气泡拷贝──
|
||||
// 必须在 click 事件内(或其同步调用链)调 clipboard API (浏览器安全限制)
|
||||
// navigator.clipboard 在非 secure context (Electron file:// / 旧版 webview) 不可用,
|
||||
// 回退到 execCommand('copy') 兜底;两者均失败时提示复制失败而非假成功。
|
||||
@@ -558,7 +558,7 @@ async function copyMsgAsImage(msg: AiMessage): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
// ── UX-03: 错误气泡操作入口 ──
|
||||
// ── 错误气泡操作入口 ──
|
||||
// errorType 经 useAiEvents push 错误消息时附带(types.ts AiMessage 未含此字段,
|
||||
// 经 cast 读写闭环在 AiChat.vue / useAiEvents 两端,不污染 types.ts 定义)。
|
||||
interface AiMessageWithErrorType extends AiMessage {
|
||||
@@ -572,7 +572,7 @@ function canOpenSettings(msg: AiMessage): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* UX-260616-01: 错误是否可重试(显隐「重试」按钮)。
|
||||
* 错误是否可重试(显隐「重试」按钮)。
|
||||
* Fatal 类错误(鉴权/Provider 配置错——4xx/401/403/invalid_api_key)重试必再败,
|
||||
* 隐藏重试按钮,引导用户走「去设置」。network/timeout/unknown 为瞬时错误,可重试。
|
||||
* retryable 推断依据后端 error_type(本批前端推断;后端 AiStreamRetry/AiError 加 retryable 字段下批 df-ai 增强)。
|
||||
@@ -587,7 +587,7 @@ function canRetry(msg: AiMessage): boolean {
|
||||
/**
|
||||
* 错误气泡「重试」:取错误前的末尾 user 消息重发。
|
||||
* 复用 regenerate 链路(后端弹末尾 AI 回复——错误消息本身——取触发它的 user 消息重跑)。
|
||||
* 语义等价"重新生成",但语义场景是错误恢复,文案/入口独立(UX-03)。
|
||||
* 语义等价"重新生成",但语义场景是错误恢复,文案/入口独立。
|
||||
*/
|
||||
async function handleRetry(): Promise<void> {
|
||||
try {
|
||||
@@ -741,9 +741,9 @@ const renderItems = computed<RenderItem[]>(() => {
|
||||
return items
|
||||
})
|
||||
|
||||
// UX-09:切换对话/新对话时由父取消编辑态;滚动边沿/跟随意图重置在此自管(SW-260618-18)。
|
||||
// 切换对话/新对话时由父取消编辑态;滚动边沿/跟随意图重置在此自管。
|
||||
watch(() => store.state.activeConversationId, () => {
|
||||
// SW-260618-18: 切会话重置滚动边沿检测 + 跟随意图(新会话默认跟随底部)
|
||||
// 切会话重置滚动边沿检测 + 跟随意图(新会话默认跟随底部)
|
||||
setFollowing(true)
|
||||
// BUG-260624-01(消息重叠根治·论证 workflow 完整性高优):MessageList 是持久单实例(AiChat.vue
|
||||
// 无 :key/v-if 卸载),实例级 streamingBlocks(:69)跨会话/跨轮残留是消息重叠主根因之一。
|
||||
@@ -1043,7 +1043,7 @@ defineExpose({
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
/* UX-2025-19: .ai-messages 的直接 flex 子(B-260618-01:虚拟滚动 sentinel 已移除,
|
||||
/* .ai-messages 的直接 flex 子虚拟滚动 sentinel 已移除,
|
||||
现仅作消息分组的语义容器,gap:14px 由 .ai-messages 承担)。 */
|
||||
.ai-msg-slot { display: flex; flex-direction: column; }
|
||||
|
||||
@@ -1148,7 +1148,7 @@ defineExpose({
|
||||
color: var(--df-danger);
|
||||
border-color: rgba(240,101,101,0.3);
|
||||
}
|
||||
/* B-260618-06:修正 UX-260617-21 的 display:block 方案——display:block 让 table 失去表格
|
||||
/* 修正 UX-260617-21 的 display:block 方案——display:block 让 table 失去表格
|
||||
布局上下文,tr/td 脱离 table 上下文塌成块级堆叠丢列对齐(用户截图反馈测试报告四列表格错乱)。
|
||||
正解:table 保持默认 display:table(列对齐恢复)+ max-width:100%;宽表格横向滚动交由气泡
|
||||
容器 .ai-msg-bubble--ai.ai-md 的 overflow-x:auto 承载(CSS spec 中 table-display 下
|
||||
@@ -1253,7 +1253,7 @@ defineExpose({
|
||||
.ai-msg-chip--idea { background: rgba(255, 255, 255, 0.22); border: 1px solid rgba(255, 255, 255, 0.35); }
|
||||
.ai-msg-chip--skill { background: rgba(0, 0, 0, 0.18); }
|
||||
|
||||
/* ── 消息时间戳(UX-2025-13) ── */
|
||||
/* ── 消息时间戳── */
|
||||
.ai-msg-time {
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
@@ -1268,7 +1268,7 @@ defineExpose({
|
||||
align-self: flex-end; /* 用户消息:靠气泡右对齐 */
|
||||
}
|
||||
|
||||
/* ── 消息气泡拷贝按钮(F-260615-13) ── */
|
||||
/* ── 消息气泡拷贝按钮── */
|
||||
.ai-copy-btn {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
@@ -1299,7 +1299,7 @@ defineExpose({
|
||||
right: 30px;
|
||||
}
|
||||
|
||||
/* ── UX-09: 末条 user 消息编辑按钮(hover 显示,回填输入框编辑重生成) ── */
|
||||
/* ── 末条 user 消息编辑按钮(hover 显示,回填输入框编辑重生成) ── */
|
||||
.ai-msg-edit-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1324,7 +1324,7 @@ defineExpose({
|
||||
color: var(--df-text);
|
||||
}
|
||||
|
||||
/* ── UX-02/UX-03: 消息操作栏(hover 显示 / 错误气泡常显) ── */
|
||||
/* ── UX-02/消息操作栏(hover 显示 / 错误气泡常显) ── */
|
||||
.ai-msg-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -1338,7 +1338,7 @@ defineExpose({
|
||||
.ai-msg-actions--error {
|
||||
opacity: 1;
|
||||
}
|
||||
/* UX-260616-01: Fatal 错误(不可重试)的提示文案——替代隐藏的「重试」按钮,
|
||||
/* Fatal 错误(不可重试)的提示文案——替代隐藏的「重试」按钮,
|
||||
说明为何无重试入口,引导走「去设置」。 */
|
||||
.ai-msg-not-retryable {
|
||||
font-size: 10.5px;
|
||||
@@ -1492,7 +1492,7 @@ defineExpose({
|
||||
background: var(--df-bg);
|
||||
}
|
||||
|
||||
/* ═══ F-15: 压缩摘要 system 块(compressed 原文已展开,摘要置顶提示发给 AI 的是它) ═══
|
||||
/* ═══ 压缩摘要 system 块(compressed 原文已展开,摘要置顶提示发给 AI 的是它) ═══
|
||||
用户诉求:压缩只后端省 token,界面不藏消息。原文 compressed 照常 user/ai 气泡展开,
|
||||
摘要 system 块置顶 accent 提示,让用户知情"AI 看到的是摘要"。 */
|
||||
.ai-msg-system {
|
||||
|
||||
@@ -334,7 +334,7 @@ watch(() => store.state.activeConversationId, () => {
|
||||
}
|
||||
})
|
||||
|
||||
// 切 provider 后模型 override 同步(BUG-2026-07-07:provider 切换后 header 模型名不刷新):
|
||||
// 切 provider 后模型 override 同步provider 切换后 header 模型名不刷新):
|
||||
// - modelOverride 是模块级 ref(useAiSend)不持久化,切 provider 后仍持旧 provider 的 model_id,
|
||||
// 该 id 不在新 provider 池里 → 下拉显示错位(UI 显示 X,实际后端用路由默认 Y)。
|
||||
// - 守卫:当前选择对新 provider 模型池无效(空/不在池)时,默认选权重最高模型。
|
||||
@@ -400,7 +400,7 @@ const historyExpanded = ref(false)
|
||||
function scrollToMessage(msgId: string) {
|
||||
historyExpanded.value = false
|
||||
// 消息列表已有 data-msg-id 属性,直接 DOM 查询定位。
|
||||
// BUG-260802:msgId 由后端生成,可能含 CSS 选择器元字符(: . [ ] / 空格 等),
|
||||
// msgId 由后端生成,可能含 CSS 选择器元字符(: . [ ] / 空格 等),
|
||||
// 模板拼 selector 会被选择器解析器当伪类/类名异常 → querySelector 抛错或返回 null。
|
||||
// 用 CSS.escape 转义 msgId,确保任意字符串安全嵌入属性选择器。
|
||||
// 旧浏览器无 CSS.escape 的兜底:encodeURIComponent + attr*= 模糊匹配(非精确,降级路径)。
|
||||
@@ -542,18 +542,18 @@ async function removeGoal(i: number) {
|
||||
function cycleProvider() {
|
||||
const ps = store.state.providers
|
||||
if (ps.length < 2) return
|
||||
// F-260616-10: activeProvider 为 null 时从 DB 默认(is_default)起算,避免 findIndex=-1 跳到 providers[0]
|
||||
// activeProvider 为 null 时从 DB 默认(is_default)起算,避免 findIndex=-1 跳到 providers[0]
|
||||
const startId = store.state.activeProvider || ps.find(p => p.is_default)?.id || null
|
||||
const idx = startId ? ps.findIndex(p => p.id === startId) : -1
|
||||
// idx=-1 且无默认 provider 时 fallback 到 0(极端边界,正常路径不会走到)
|
||||
const next = ps[(Math.max(idx, 0) + 1) % ps.length]
|
||||
store.setProvider(next.id)
|
||||
// UX-2025-14: 切换反馈 — toast 提示当前 model + 临时 bar 展示 model 名(2s 后淡出)。
|
||||
// 切换反馈 — toast 提示当前 model + 临时 bar 展示 model 名(2s 后淡出)。
|
||||
// toast 机制:复用父自管 reactive showToast(分离窗口无 App.vue 根 toast),经 emit 回父弹。
|
||||
showProviderSwitched(next.name || next.id)
|
||||
}
|
||||
|
||||
// ── UX-2025-14: Provider 切换临时 bar ──
|
||||
// ── Provider 切换临时 bar ──
|
||||
// 切换后展示当前 model 名小字,0.15s 淡入,2s 后淡出。本子组件自管 bar 显隐(ref + Transition),
|
||||
// toast 经 emit 回父(父持有 toast reactive 容器)。
|
||||
const providerBarVisible = ref(false)
|
||||
@@ -567,7 +567,7 @@ function showProviderSwitched(modelName: string) {
|
||||
_providerBarTimer = setTimeout(() => { providerBarVisible.value = false }, 2000)
|
||||
}
|
||||
|
||||
// UX-2025-14:释放 provider 切换 bar 计时器(从 AiChat onBeforeUnmount 迁移,与 _providerBarTimer 同处)
|
||||
// 释放 provider 切换 bar 计时器(从 AiChat onBeforeUnmount 迁移,与 _providerBarTimer 同处)
|
||||
onBeforeUnmount(() => {
|
||||
if (_providerBarTimer) { clearTimeout(_providerBarTimer); _providerBarTimer = null }
|
||||
})
|
||||
@@ -604,7 +604,7 @@ onBeforeUnmount(() => {
|
||||
white-space: nowrap;
|
||||
max-width: 200px;
|
||||
}
|
||||
/* AE-2025-02:审批徽标(待审批>0 时显于头部,点击跳首个审批卡) */
|
||||
/* 审批徽标(待审批>0 时显于头部,点击跳首个审批卡) */
|
||||
.ai-approval-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -1014,7 +1014,7 @@ onBeforeUnmount(() => {
|
||||
font-size: 11px;
|
||||
color: var(--df-danger);
|
||||
}
|
||||
/* UX-2025-14: Provider 切换临时 bar(淡入淡出 0.15s) */
|
||||
/* Provider 切换临时 bar(淡入淡出 0.15s) */
|
||||
.ai-provider-switch-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
Reference in New Issue
Block a user