新增: aichat前端体验四件与队列残留收口
- R4 代码块语言角标:MessageList decorateCodeHtml 捕获 language-xxx 输出 data-lang+CSS ::before 左上角标,hover 显,无语言裸块不渲染 - R6 工具结果路径可复制:ToolResultBody 五处 file-bar 的 path span→button(read_file/list_directory/search_files/grep/patch_file),点击复制完整路径+✓/✕ 1.2s 反馈+execCommand 降级链 - I6 重新生成 UI 入口:MessageItem user 气泡 hover 补重新生成按钮(canRegenerate=isLastUser&&非流式),复用 handleRetry→store.regenerate 链路 - V4 浮窗审批 High 风险二次确认:useToolApproval 导出 isHighRiskTool/highRiskConfirmText,ApprovalPopup.onApprove 前置 ConfirmDialog,数字键守卫不绕确认 - 9-28#5 两处全局清队列残留:useAiEvents/useAiLifecycleEvents 无 convId 的 AiError 分支改保留队列+console.warn(防误伤其他会话排队消息) - i18n zh/en 补 clickToCopy 键;裸 @ 改 @ 转义(vue-i18n linked-message 前缀吞噬)
This commit is contained in:
@@ -225,10 +225,12 @@ export function handleEvent(event: AiChatEvent) {
|
||||
if (event.type === 'AiCompleted') {
|
||||
emit('ai-drain-queue', { conversationId: event.conversation_id })
|
||||
} else if (event.type === 'AiError') {
|
||||
// 9-28#5 收口:无 convId 的 AiError 属异常防御路径(后端 emit 必带),清全部队列
|
||||
// 会误伤其他会话排队消息,改保留 + console.warn 留痕
|
||||
if (event.conversation_id) {
|
||||
state.queue = state.queue.filter(q => q.conversationId !== event.conversation_id)
|
||||
} else {
|
||||
state.queue = []
|
||||
console.warn('[ai] AiError 缺 conversation_id,跳过清队列(防误伤其他会话排队)')
|
||||
}
|
||||
}
|
||||
} else if (event.type === 'AiTextDelta' && convId && switchingConvs.has(convId)) {
|
||||
|
||||
@@ -208,8 +208,9 @@ export function handleLifecycleEvent(event: AiChatEvent): boolean {
|
||||
state.queue = state.queue.filter(q => q.conversationId !== event.conversation_id)
|
||||
notifyQueueCleared(dropped)
|
||||
} else {
|
||||
notifyQueueCleared(state.queue.length)
|
||||
state.queue = []
|
||||
// 9-28#5 收口:无 convId 的 AiError 属异常防御路径(后端 emit 必带),清全部队列
|
||||
// 会误伤其他会话排队消息,改保留 + console.warn 留痕(不再弹「已清 N 条」误导用户)
|
||||
console.warn('[ai] AiError 缺 conversation_id,跳过清队列(防误伤其他会话排队)')
|
||||
}
|
||||
state.messages.push({
|
||||
id: `err-${nextMsgId()}` as MessageId,
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
*/
|
||||
import { ref, reactive, computed, watch, onBeforeUnmount, type Ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import i18n from '@/i18n'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import { aiApi } from '@/api'
|
||||
import { getApprovalRemainingMs } from './aiShared'
|
||||
@@ -47,8 +48,9 @@ export function syncHighRiskTools(tools: { name: string; risk: string }[]): void
|
||||
if (high.length > 0) registryHighRisk = new Set(high)
|
||||
}
|
||||
|
||||
/** 审批二次确认门控:优先注册表投影,未拉到元数据时退兜底名单 */
|
||||
function isHighRiskTool(name: string): boolean {
|
||||
/** 审批二次确认门控:优先注册表投影,未拉到元数据时退兜底名单。
|
||||
* V4 导出:系统浮窗 ApprovalPopup 的批准路径与 ToolCard 同门槛复用。 */
|
||||
export function isHighRiskTool(name: string): boolean {
|
||||
return (registryHighRisk ?? HIGH_RISK_TOOLS_FALLBACK).has(name)
|
||||
}
|
||||
|
||||
@@ -80,6 +82,21 @@ export function dispatchApprovalIPC(
|
||||
return aiApi.approve(tc.id, approved)
|
||||
}
|
||||
|
||||
/** V4:High 风险工具二次确认文案(模块级,供 ApprovalPopup 在无组件上下文复用;
|
||||
* i18n 全局实例取 t,与 useI18n 同源 locale) */
|
||||
export function highRiskConfirmText(name: string): string {
|
||||
// i18n.global 整体访问触发 vue-i18n 泛型递归 TS2589,先断言窄化再取 t
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const g = i18n.global as any
|
||||
const gt = g.t as (key: string) => string
|
||||
if (name === 'run_command') return gt('aiTool.confirmHighExec')
|
||||
if (name === 'http_request') return gt('aiTool.confirmHighHttp')
|
||||
if (name === 'delete_task' || name === 'delete_project' || name === 'purge_project' || name === 'delete_file' || name === 'restore_project') {
|
||||
return gt('aiTool.confirmHighDelete')
|
||||
}
|
||||
return gt('aiTool.confirmHighGeneric')
|
||||
}
|
||||
|
||||
/** useToolApproval emit 签名 — 补全类型替代 any */
|
||||
interface ToolApprovalEmit {
|
||||
(event: 'approve', payload: { id: string; approved: boolean; decision?: 'once' | 'session' | 'always' | 'deny' }): void
|
||||
@@ -145,14 +162,9 @@ export function useToolApproval(tc: Ref<AiToolCallInfo>, emit: ToolApprovalEmit)
|
||||
_approveToastTimer = setTimeout(() => { approveToast.visible = false }, 3000)
|
||||
}
|
||||
|
||||
/** High 风险工具的二次确认文案(按操作类型) */
|
||||
/** High 风险工具的二次确认文案(按操作类型;V4 提升为模块级导出,浮窗复用) */
|
||||
function highRiskConfirmMsg(name: string): string {
|
||||
if (name === 'run_command') return t('aiTool.confirmHighExec')
|
||||
if (name === 'http_request') return t('aiTool.confirmHighHttp')
|
||||
if (name === 'delete_task' || name === 'delete_project' || name === 'purge_project' || name === 'delete_file' || name === 'restore_project') {
|
||||
return t('aiTool.confirmHighDelete')
|
||||
}
|
||||
return t('aiTool.confirmHighGeneric')
|
||||
return highRiskConfirmText(name)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user