@veltra/ai 2.2.5 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/chat/fold.d.ts +0 -2
  2. package/dist/chat/fold.js +1 -2
  3. package/dist/chat/session.d.ts +0 -2
  4. package/dist/chat/session.js +1 -2
  5. package/dist/chat/transports/openai.d.ts +0 -2
  6. package/dist/chat/transports/openai.js +1 -2
  7. package/dist/chat/types.d.ts +0 -2
  8. package/dist/chat/use-chat-helpers.js +1 -2
  9. package/dist/chat/use-chat-session.js +1 -2
  10. package/dist/chat/use-chat.d.ts +0 -2
  11. package/dist/chat/use-chat.js +1 -2
  12. package/dist/components/ai-chat/ai-chat.js +1 -2
  13. package/dist/components/ai-chat/ai-chat.js.map +1 -1
  14. package/dist/components/ai-chat/approval-banner.js +1 -2
  15. package/dist/components/ai-chat/approval-banner.js.map +1 -1
  16. package/dist/components/ai-chat/ask-question.js +1 -2
  17. package/dist/components/ai-chat/ask-question.js.map +1 -1
  18. package/dist/components/ai-chat/chat-input.js +1 -2
  19. package/dist/components/ai-chat/chat-input.js.map +1 -1
  20. package/dist/components/ai-chat/di.js +1 -2
  21. package/dist/components/ai-chat/format-duration.js +1 -2
  22. package/dist/components/ai-chat/job-bar.js +1 -2
  23. package/dist/components/ai-chat/job-bar.js.map +1 -1
  24. package/dist/components/ai-chat/message-item.js +1 -2
  25. package/dist/components/ai-chat/message-item.js.map +1 -1
  26. package/dist/components/ai-chat/message-list.js +1 -2
  27. package/dist/components/ai-chat/message-list.js.map +1 -1
  28. package/dist/components/ai-chat/model-picker.js +1 -2
  29. package/dist/components/ai-chat/model-picker.js.map +1 -1
  30. package/dist/components/ai-chat/queue-list.js +1 -2
  31. package/dist/components/ai-chat/queue-list.js.map +1 -1
  32. package/dist/components/ai-chat/reasoning-picker.js +1 -2
  33. package/dist/components/ai-chat/reasoning-picker.js.map +1 -1
  34. package/dist/components/ai-chat/side-panel.js +1 -2
  35. package/dist/components/ai-chat/side-panel.js.map +1 -1
  36. package/dist/components/ai-chat/style2.css +20 -3
  37. package/dist/components/ai-chat/tool-call.js +1 -2
  38. package/dist/components/ai-chat/tool-call.js.map +1 -1
  39. package/dist/components/ai-chat/tool-icons.js +1 -2
  40. package/dist/components/ai-chat/tool-json.js +1 -2
  41. package/dist/components/ai-chat/tool-json.js.map +1 -1
  42. package/dist/components/ai-chat/turn-process.js +1 -2
  43. package/dist/components/ai-chat/turn-process.js.map +1 -1
  44. package/dist/components/ai-orb/ai-orb.js +1 -2
  45. package/dist/components/ai-orb/orb-renderer.js +1 -2
  46. package/dist/providers/types.d.ts +0 -2
  47. package/dist/tools/ask-question/ask-question.d.ts +0 -2
  48. package/dist/tools/ask-question/ask-question.js +1 -2
  49. package/dist/tools/ask-question/deferred.js +1 -2
  50. package/dist/tools/index.js +1 -2
  51. package/dist/types/ai-chat.d.ts +0 -2
  52. package/package.json +2 -2
  53. package/src/components/ai-chat/style.scss +26 -3
@@ -2,7 +2,6 @@ import { ChatJob, ChatMessage, ChatQueuedMessage, ChatTokenUsage } from "./types
2
2
  import { AskQuestionItem } from "../tools/ask-question/ask-question.js";
3
3
  import "../tools/index.js";
4
4
  import { ChatSessionEvent } from "./session.js";
5
- //#region src/chat/fold.d.ts
6
5
  export interface ChatPendingApproval {
7
6
  approvalId: string;
8
7
  toolName: string;
@@ -36,5 +35,4 @@ export declare function createFoldState(): ChatFoldState;
36
35
  * 后到的更小/重复 seq 不覆盖已应用事件。
37
36
  */
38
37
  export declare function foldSessionEvent(state: ChatFoldState, event: ChatSessionEvent): ChatFoldState;
39
- //#endregion
40
38
  //# sourceMappingURL=fold.d.ts.map
package/dist/chat/fold.js CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/chat/fold.ts
1
+ // src/chat/fold.ts
2
2
  function eventSeq(event) {
3
3
  return "seq" in event ? event.seq : void 0;
4
4
  }
@@ -214,7 +214,6 @@ function foldSessionEvent(state, event) {
214
214
  }
215
215
  return next;
216
216
  }
217
- //#endregion
218
217
  export { createFoldState, foldSessionEvent };
219
218
 
220
219
  //# sourceMappingURL=fold.js.map
@@ -1,7 +1,6 @@
1
1
  import { ChatAttachment, ChatJob, ChatQueuedMessage, ChatToolCall } from "./types.js";
2
2
  import { AskQuestionItem } from "../tools/ask-question/ask-question.js";
3
3
  import "../tools/index.js";
4
- //#region src/chat/session.d.ts
5
4
  /** 归一化后的服务端会话事件(协议无关)。
6
5
  * seq 为持久帧序号:瞬态/历史回放事件省略(不参与乱序门),durable 终态事件携带。 */
7
6
  export type ChatSessionEvent = {
@@ -125,5 +124,4 @@ export declare function isServerTransport(t: unknown): t is ChatSessionTransport
125
124
  * 包内完成 seq 校验、disposer、断线补拉、动作 in-flight 去重。
126
125
  */
127
126
  export declare function createServerTransport(adapter: ChatSessionAdapter): ChatSessionTransport;
128
- //#endregion
129
127
  //# sourceMappingURL=session.d.ts.map
@@ -1,4 +1,4 @@
1
- //#region src/chat/session.ts
1
+ // src/chat/session.ts
2
2
  function eventSeq(event) {
3
3
  return "seq" in event ? event.seq : void 0;
4
4
  }
@@ -73,7 +73,6 @@ function createServerTransport(adapter) {
73
73
  }
74
74
  };
75
75
  }
76
- //#endregion
77
76
  export { createServerTransport, isServerTransport };
78
77
 
79
78
  //# sourceMappingURL=session.js.map
@@ -1,6 +1,5 @@
1
1
  import { ChatTransport } from "../types.js";
2
2
  import { ChatModelOption, ChatProvider } from "../../providers/types.js";
3
- //#region src/chat/transports/openai.d.ts
4
3
  /** createOpenAITransport 的配置项(多 Provider) */
5
4
  export interface OpenAITransportOptions {
6
5
  /** 至少一个 Provider;模型 id 须跨 Provider 全局唯一 */
@@ -23,5 +22,4 @@ export type OpenAITransport = ChatTransport & {
23
22
  * 请求体构造、fetch、SSE 解析与错误处理复用 `@veltra/utils` 通用层。
24
23
  */
25
24
  export declare function createOpenAITransport(options: OpenAITransportOptions): OpenAITransport;
26
- //#endregion
27
25
  //# sourceMappingURL=openai.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { buildOpenAIBody, requestOpenAIStream } from "@veltra/utils";
2
- //#region src/chat/transports/openai.ts
2
+ // src/chat/transports/openai.ts
3
3
  /** 校验 providers 并展开扁平模型列表 */
4
4
  function flattenModels(providers) {
5
5
  if (!providers.length) throw new Error("[createOpenAITransport] providers 不能为空");
@@ -65,7 +65,6 @@ function createOpenAITransport(options) {
65
65
  defaultModel
66
66
  });
67
67
  }
68
- //#endregion
69
68
  export { createOpenAITransport };
70
69
 
71
70
  //# sourceMappingURL=openai.js.map
@@ -1,5 +1,4 @@
1
1
  import { Component } from "vue";
2
- //#region src/chat/types.d.ts
3
2
  /** 聊天附件(首版仅支持图片) */
4
3
  export interface ChatAttachment {
5
4
  /** 文件名 */
@@ -226,5 +225,4 @@ export interface ChatTransportHandlers {
226
225
  * 可传入内置的 createOpenAITransport(),也可自行实现接入任意后端。
227
226
  */
228
227
  export type ChatTransport = (request: ChatTransportRequest, handlers: ChatTransportHandlers) => Promise<void> | void;
229
- //#endregion
230
228
  //# sourceMappingURL=types.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { createBuiltinTools } from "../tools/index.js";
2
- //#region src/chat/use-chat-helpers.ts
2
+ // src/chat/use-chat-helpers.ts
3
3
  function serializeResult(result) {
4
4
  if (typeof result === "string") return result;
5
5
  try {
@@ -33,7 +33,6 @@ function addTokenUsage(a, b) {
33
33
  if (cacheCreationTokens != null) usage.cacheCreationTokens = cacheCreationTokens;
34
34
  return usage;
35
35
  }
36
- //#endregion
37
36
  export { addTokenUsage, resolveTools, serializeResult };
38
37
 
39
38
  //# sourceMappingURL=use-chat-helpers.js.map
@@ -1,5 +1,5 @@
1
1
  import { createFoldState, foldSessionEvent } from "./fold.js";
2
- //#region src/chat/use-chat-session.ts
2
+ // src/chat/use-chat-session.ts
3
3
  /** 把 fold 状态同步到 useChat 的渲染面 */
4
4
  function syncFold(fold, surface) {
5
5
  surface.messages.value = fold.messages;
@@ -52,7 +52,6 @@ function createSessionRuntime(surface) {
52
52
  }
53
53
  };
54
54
  }
55
- //#endregion
56
55
  export { createSessionRuntime };
57
56
 
58
57
  //# sourceMappingURL=use-chat-session.js.map
@@ -5,7 +5,6 @@ import { AiChatEmits, AiChatProps } from "../types/ai-chat.js";
5
5
  import "../types/index.js";
6
6
  import { ChatPendingApproval } from "./fold.js";
7
7
  import { Ref } from "vue";
8
- //#region src/chat/use-chat.d.ts
9
8
  export interface UseChatOptions {
10
9
  props: AiChatProps;
11
10
  emit: AiChatEmits;
@@ -138,5 +137,4 @@ export declare function useChat(options: UseChatOptions): {
138
137
  startQueued: (id: string) => void;
139
138
  removeQueued: (id: string) => ChatQueuedMessage | undefined;
140
139
  };
141
- //#endregion
142
140
  //# sourceMappingURL=use-chat.d.ts.map
@@ -3,7 +3,7 @@ import { addTokenUsage, resolveTools, serializeResult } from "./use-chat-helpers
3
3
  import { createSessionRuntime } from "./use-chat-session.js";
4
4
  import { useModel } from "@veltra/compositions";
5
5
  import { reactive, ref, watch } from "vue";
6
- //#region src/chat/use-chat.ts
6
+ // src/chat/use-chat.ts
7
7
  const uid = () => {
8
8
  return typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2)}`;
9
9
  };
@@ -405,7 +405,6 @@ function useChat(options) {
405
405
  removeQueued
406
406
  };
407
407
  }
408
- //#endregion
409
408
  export { useChat };
410
409
 
411
410
  //# sourceMappingURL=use-chat.js.map
@@ -13,7 +13,7 @@ import { useResizeObserver } from "@veltra/compositions";
13
13
  import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createVNode, defineComponent, isRef, normalizeClass, openBlock, provide, ref, renderSlot, unref, useSlots, useTemplateRef, watch, withCtx } from "vue";
14
14
  import { ULayout } from "@veltra/desktop";
15
15
  import { bem } from "@veltra/utils";
16
- //#region src/components/ai-chat/ai-chat.vue
16
+ // src/components/ai-chat/ai-chat.vue
17
17
  const PANEL_MIN_WIDTH = 320;
18
18
  const MAIN_MIN_WIDTH = 360;
19
19
  /** 面板打开时默认给会话区留的宽度(即面板默认尽可能大) */
@@ -313,7 +313,6 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
313
313
  };
314
314
  }
315
315
  });
316
- //#endregion
317
316
  export { _sfc_main as default };
318
317
 
319
318
  //# sourceMappingURL=ai-chat.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ai-chat.js","names":["$slots","_openBlock","_createBlock","_unref","_normalizeClass","_withCtx","_createElementVNode","_createVNode","MessageList","_createSlots","_renderSlot","_createElementBlock","AskQuestion","_createCommentVNode","ApprovalBanner","QueueList","JobBar","ChatInput","_isRef","SidePanel"],"sources":["../../../src/components/ai-chat/ai-chat.vue"],"sourcesContent":["<template>\n <ULayout\n ref=\"layoutRef\"\n :class=\"[cls.b, bem.is('resizing', resizing)]\"\n :cols=\"layoutCols\"\n rows=\"1fr\"\n :resizable=\"panelOpen\"\n :col-min-sizes=\"panelOpen ? [MAIN_MIN_WIDTH, PANEL_MIN_WIDTH] : undefined\"\n @resize-start=\"resizing = true\"\n @resize-end=\"resizing = false\"\n >\n <div :class=\"[cls.e('main'), bem.is('empty', isEmpty)]\">\n <MessageList\n :messages=\"messages\"\n :welcome=\"welcome\"\n :running=\"running\"\n :readonly=\"readonly\"\n :renderer-props=\"rendererProps\"\n @respond=\"respondToolCall\"\n @regenerate=\"regenerate\"\n @welcome-click=\"(text) => handleSend(text, [])\"\n >\n <template v-if=\"$slots.welcome\" #welcome>\n <slot name=\"welcome\" />\n </template>\n </MessageList>\n\n <div v-if=\"pendingQuestion\" :class=\"cls.e('session-question')\">\n <AskQuestion :questions=\"pendingQuestion.questions\" @submit=\"handleQuestionSubmit\" />\n </div>\n\n <ApprovalBanner :approvals=\"pendingApprovals\" @respond=\"respondSession\" />\n\n <QueueList\n :queue=\"queue\"\n :readonly=\"readonly\"\n @start-now=\"startQueued\"\n @edit=\"handleQueueEdit\"\n @remove=\"removeQueued\"\n />\n\n <JobBar :jobs=\"jobs\" />\n\n <ChatInput\n v-if=\"!readonly\"\n ref=\"chatInputRef\"\n v-model:model=\"model\"\n v-model:reasoning-level=\"reasoningLevel\"\n :running=\"running\"\n :models=\"models\"\n :placeholder=\"placeholder\"\n :accept=\"accept\"\n :max-attachment-size=\"maxAttachmentSize\"\n :clearable=\"clearable\"\n :token-usage=\"tokenUsage\"\n :last-request-usage=\"lastRequestUsage\"\n :token-usage-detail=\"tokenUsageDetail\"\n @send=\"handleSend\"\n @abort=\"abort\"\n @clear=\"clear\"\n />\n </div>\n\n <!-- 侧边面板(renderTo: 'panel' 的工具渲染区),与会话区宽度经 ULayout 拖拽手柄调节 -->\n <SidePanel\n v-if=\"activePanelCall\"\n ref=\"panelRef\"\n :tool-call=\"activePanelCall\"\n @close=\"closePanel\"\n />\n </ULayout>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useResizeObserver } from '@veltra/compositions'\nimport { ULayout } from '@veltra/desktop'\nimport { bem } from '@veltra/utils'\nimport { computed, provide, ref, useSlots, useTemplateRef, watch } from 'vue'\n\nimport { isServerTransport } from '../../chat/session'\nimport type { ChatAttachment, ChatTool, ChatToolCall, ChatToolMeta } from '../../chat/types'\nimport { useChat } from '../../chat/use-chat'\nimport { createBuiltinTools } from '../../tools'\nimport type { AskQuestionAnswer, AskQuestionResult } from '../../tools/ask-question/ask-question'\nimport type { _AiChatExposed, AiChatEmits, AiChatProps } from '../../types'\nimport ApprovalBanner from './approval-banner.vue'\nimport AskQuestion from './ask-question.vue'\nimport ChatInput from './chat-input.vue'\nimport { AiChatDIKey } from './di'\nimport JobBar from './job-bar.vue'\nimport MessageList from './message-list.vue'\nimport QueueList from './queue-list.vue'\nimport SidePanel from './side-panel.vue'\n\ndefineOptions({ name: 'UAiChat' })\n\nconst props = defineProps<AiChatProps>()\n\nconst emit = defineEmits<AiChatEmits>()\n\ndefineSlots<{\n /** 空闲欢迎区插槽(输入框上方;工作中跳到列表末尾) */\n welcome(): any\n /** 按工具名自定义工具结果展示,如 tool-getWeather */\n [name: `tool-${string}`]: (scope: { toolCall: ChatToolCall }) => any\n}>()\n\nconst cls = bem('ai-chat')\n\nconst slots = useSlots()\n\n/**\n * 按 name 查表:session 下 tools 仅为渲染元信息(不注入内置工具,忽略 execute 等)。\n * 函数 transport 仍注入 createBuiltinTools(同名内置优先)。\n */\nconst toolMap = computed((): Record<string, ChatTool | ChatToolMeta | undefined> => {\n if (isServerTransport(props.transport)) {\n return Object.fromEntries((props.tools ?? []).map((t) => [t.name, t]))\n }\n const builtins = createBuiltinTools()\n const names = new Set(builtins.map((t) => t.name))\n const tools = [...builtins, ...(props.tools ?? []).filter((t) => !names.has(t.name))]\n return Object.fromEntries(tools.map((t) => [t.name, t]))\n})\n\nconst toolIcons = computed(() => props.toolIcons)\n\nconst {\n messages,\n model,\n reasoningLevel,\n running,\n queue,\n jobs,\n tokenUsage,\n lastTurnUsage,\n lastRequestUsage,\n pendingApprovals,\n pendingQuestion,\n send,\n abort,\n regenerate,\n clear,\n respondToolCall,\n respondSession,\n enqueue,\n startQueued,\n removeQueued\n} = useChat({ props, emit })\n\n/** 空会话(无可见消息):输入区垂直居中;欢迎区钉在滚动容器外、贴于输入框上方 */\nconst isEmpty = computed(() => !messages.value.some((msg) => msg.role !== 'tool'))\n\n/** 有对话内容、队列或生成中才允许清除,避免误触空会话 */\nconst clearable = computed(\n () => messages.value.length > 0 || queue.value.length > 0 || running.value\n)\n\n/** 所有 renderTo: 'panel' 的工具调用(按消息顺序,面板内容的来源) */\nconst panelCalls = computed(() => {\n const calls: ChatToolCall[] = []\n for (const msg of messages.value) {\n if (msg.role !== 'assistant' || !msg.toolCalls) continue\n for (const call of msg.toolCalls) {\n if (toolMap.value[call.name]?.renderTo === 'panel') calls.push(call)\n }\n }\n return calls\n})\n\n/** 当前展示在右侧面板的工具调用 id;null 表示面板关闭 */\nconst activePanelCallId = ref<string | null>(null)\n\nconst activePanelCall = computed(() => {\n if (!activePanelCallId.value) return null\n return panelCalls.value.find((call) => call.id === activePanelCallId.value) ?? null\n})\n\nconst panelOpen = computed(() => activePanelCall.value != null)\n\n// 出现新的面板工具调用时自动打开并聚焦到它\nwatch(\n () => panelCalls.value[panelCalls.value.length - 1]?.id,\n (id) => {\n if (id) activePanelCallId.value = id\n }\n)\n\n// 聚焦的调用消失(清空消息 / 重新生成)时,回落到最近一次面板调用或关闭\nwatch(panelCalls, (calls) => {\n const id = activePanelCallId.value\n if (id && !calls.some((call) => call.id === id)) {\n activePanelCallId.value = calls[calls.length - 1]?.id ?? null\n }\n})\n\n/** 打开面板并聚焦到指定工具调用(工具卡片「查看面板」入口,经 DI 下发) */\nconst openPanel = (toolCallId: string) => {\n if (panelCalls.value.some((call) => call.id === toolCallId)) {\n activePanelCallId.value = toolCallId\n }\n}\n\nconst closePanel = () => {\n activePanelCallId.value = null\n}\n\nprovide(AiChatDIKey, { cls, slots, tools: toolMap, toolIcons, openPanel })\n\n/**\n * 面板宽度:经 ULayout 列轨施加(cols = 1fr + 面板宽度),\n * 拖拽手柄由 ULayout 提供(colMinSizes 约束会话区 / 面板最小宽度)。\n */\nconst PANEL_MIN_WIDTH = 320\nconst MAIN_MIN_WIDTH = 360\n/** 面板打开时默认给会话区留的宽度(即面板默认尽可能大) */\nconst MAIN_DEFAULT_WIDTH = 860\n\nconst layoutRef = useTemplateRef<InstanceType<typeof ULayout>>('layoutRef')\nconst panelRef = useTemplateRef<InstanceType<typeof SidePanel>>('panelRef')\nconst panelWidth = ref(PANEL_MIN_WIDTH)\nconst resizing = ref(false)\n\nconst layoutCols = computed(() => {\n return panelOpen.value ? ['1fr', `${panelWidth.value}px`] : ['1fr']\n})\n\nconst containerWidth = () => (layoutRef.value?.$el as HTMLElement | undefined)?.clientWidth ?? 0\n\n/** 面板默认宽度:容器宽减去会话区默认保留宽度(缺省给会话区留 MAIN_DEFAULT_WIDTH) */\nconst defaultPanelWidth = () => {\n const width = containerWidth()\n return width ? width - MAIN_DEFAULT_WIDTH : 420\n}\n\n/** 钳位面板宽度在 [PANEL_MIN_WIDTH, 容器宽 - MAIN_MIN_WIDTH] 区间 */\nconst clampPanelWidth = (width: number) => {\n const width0 = containerWidth()\n const maxWidth = width0\n ? Math.max(PANEL_MIN_WIDTH, width0 - MAIN_MIN_WIDTH)\n : Number.MAX_SAFE_INTEGER\n return Math.min(Math.max(width, PANEL_MIN_WIDTH), maxWidth)\n}\n\n// 聚焦到指定了 panelWidth 的工具调用时应用其宽度;面板从关闭到打开且工具未指定宽度时\n// 应用默认宽(会话区留 860px);面板已打开时切换聚焦保持当前宽度(含用户拖拽结果)\nwatch(activePanelCall, (call, prevCall) => {\n if (!call) return\n const toolWidth = toolMap.value[call.name]?.panelWidth\n const width = toolWidth ?? (prevCall ? undefined : defaultPanelWidth())\n if (typeof width !== 'number') return\n panelWidth.value = clampPanelWidth(width)\n})\n\n// 跟踪面板实际宽度(含用户拖拽结果):面板关闭重开、切换到未指定宽度的工具时保持\nuseResizeObserver({\n targets: computed(() => (panelRef.value?.$el as HTMLElement | undefined) ?? undefined),\n onResize(entries) {\n // borderBoxSize 随通知携带(含边框、零布局开销),不读 offsetWidth/clientWidth\n const inlineSize = entries[0]?.borderBoxSize?.[0]?.inlineSize\n if (!inlineSize) return\n const width = Math.round(inlineSize)\n // 滞回:忽略亚像素/边框级的微小偏差(< 2px),阻断任何「观测→回写→再观测」的微缩反馈回路\n if (Math.abs(width - panelWidth.value) < 2) return\n panelWidth.value = width\n }\n})\n\nconst chatInputRef = useTemplateRef<InstanceType<typeof ChatInput>>('chatInputRef')\n\n/**\n * 编辑中的队列项原后继 id(编辑态标记):\n * undefined 表示无编辑;null 表示原位置在队尾;否则重新提交时插回该后继之前,\n * 使前后项顺序不受编辑影响(前项已执行则该项自然成为最前待执行项)。\n */\nconst editingSuccessor = ref<string | null | undefined>(undefined)\n\n/** 编辑队列项:取回输入框重新编辑;已有未完成编辑时先按锚点还原,避免内容丢失 */\nconst handleQueueEdit = (id: string) => {\n restoreEditing()\n\n const index = queue.value.findIndex((item) => item.id === id)\n if (index === -1) return\n editingSuccessor.value = queue.value[index + 1]?.id ?? null\n\n const item = removeQueued(id)\n if (item) chatInputRef.value?.setContent(item.content)\n}\n\n/** 将输入框中未提交的编辑内容按原锚点插回队列 */\nconst restoreEditing = () => {\n if (editingSuccessor.value === undefined) return\n const content = chatInputRef.value?.getContent().trim()\n if (content) enqueue(content, undefined, editingSuccessor.value ?? undefined)\n editingSuccessor.value = undefined\n}\n\nconst handleSend = (content: string, attachments: ChatAttachment[]) => {\n if (props.readonly) return\n // 编辑态提交:插回原锚点位置(会话进行中则留在队列,空闲时由 enqueue 自动消耗队首)\n if (editingSuccessor.value !== undefined) {\n enqueue(content, attachments, editingSuccessor.value ?? undefined)\n editingSuccessor.value = undefined\n return\n }\n send(content, attachments)\n}\n\nconst handleQuestionSubmit = (value: AskQuestionAnswer[] | AskQuestionResult) => {\n const pending = pendingQuestion.value\n if (!pending) return\n respondSession(pending.rpcId, true, Array.isArray(value) ? value : value.answers)\n}\n\ndefineExpose<_AiChatExposed>({\n send,\n abort,\n regenerate,\n clear,\n tokenUsage,\n lastTurnUsage,\n queue,\n startQueued,\n removeQueued,\n enqueue\n})\n</script>\n"],"mappings":"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"}
1
+ {"version":3,"file":"ai-chat.js","names":["$slots","_openBlock","_createBlock","_unref","_normalizeClass","_withCtx","_createElementVNode","_createVNode","MessageList","_createSlots","_renderSlot","_createElementBlock","AskQuestion","_createCommentVNode","ApprovalBanner","QueueList","JobBar","ChatInput","_isRef","SidePanel"],"sources":["../../../src/components/ai-chat/ai-chat.vue"],"sourcesContent":["<template>\n <ULayout\n ref=\"layoutRef\"\n :class=\"[cls.b, bem.is('resizing', resizing)]\"\n :cols=\"layoutCols\"\n rows=\"1fr\"\n :resizable=\"panelOpen\"\n :col-min-sizes=\"panelOpen ? [MAIN_MIN_WIDTH, PANEL_MIN_WIDTH] : undefined\"\n @resize-start=\"resizing = true\"\n @resize-end=\"resizing = false\"\n >\n <div :class=\"[cls.e('main'), bem.is('empty', isEmpty)]\">\n <MessageList\n :messages=\"messages\"\n :welcome=\"welcome\"\n :running=\"running\"\n :readonly=\"readonly\"\n :renderer-props=\"rendererProps\"\n @respond=\"respondToolCall\"\n @regenerate=\"regenerate\"\n @welcome-click=\"(text) => handleSend(text, [])\"\n >\n <template v-if=\"$slots.welcome\" #welcome>\n <slot name=\"welcome\" />\n </template>\n </MessageList>\n\n <div v-if=\"pendingQuestion\" :class=\"cls.e('session-question')\">\n <AskQuestion :questions=\"pendingQuestion.questions\" @submit=\"handleQuestionSubmit\" />\n </div>\n\n <ApprovalBanner :approvals=\"pendingApprovals\" @respond=\"respondSession\" />\n\n <QueueList\n :queue=\"queue\"\n :readonly=\"readonly\"\n @start-now=\"startQueued\"\n @edit=\"handleQueueEdit\"\n @remove=\"removeQueued\"\n />\n\n <JobBar :jobs=\"jobs\" />\n\n <ChatInput\n v-if=\"!readonly\"\n ref=\"chatInputRef\"\n v-model:model=\"model\"\n v-model:reasoning-level=\"reasoningLevel\"\n :running=\"running\"\n :models=\"models\"\n :placeholder=\"placeholder\"\n :accept=\"accept\"\n :max-attachment-size=\"maxAttachmentSize\"\n :clearable=\"clearable\"\n :token-usage=\"tokenUsage\"\n :last-request-usage=\"lastRequestUsage\"\n :token-usage-detail=\"tokenUsageDetail\"\n @send=\"handleSend\"\n @abort=\"abort\"\n @clear=\"clear\"\n />\n </div>\n\n <!-- 侧边面板(renderTo: 'panel' 的工具渲染区),与会话区宽度经 ULayout 拖拽手柄调节 -->\n <SidePanel\n v-if=\"activePanelCall\"\n ref=\"panelRef\"\n :tool-call=\"activePanelCall\"\n @close=\"closePanel\"\n />\n </ULayout>\n</template>\n\n<script lang=\"ts\" setup>\nimport { useResizeObserver } from '@veltra/compositions'\nimport { ULayout } from '@veltra/desktop'\nimport { bem } from '@veltra/utils'\nimport { computed, provide, ref, useSlots, useTemplateRef, watch } from 'vue'\n\nimport { isServerTransport } from '../../chat/session'\nimport type { ChatAttachment, ChatTool, ChatToolCall, ChatToolMeta } from '../../chat/types'\nimport { useChat } from '../../chat/use-chat'\nimport { createBuiltinTools } from '../../tools'\nimport type { AskQuestionAnswer, AskQuestionResult } from '../../tools/ask-question/ask-question'\nimport type { _AiChatExposed, AiChatEmits, AiChatProps } from '../../types'\nimport ApprovalBanner from './approval-banner.vue'\nimport AskQuestion from './ask-question.vue'\nimport ChatInput from './chat-input.vue'\nimport { AiChatDIKey } from './di'\nimport JobBar from './job-bar.vue'\nimport MessageList from './message-list.vue'\nimport QueueList from './queue-list.vue'\nimport SidePanel from './side-panel.vue'\n\ndefineOptions({ name: 'UAiChat' })\n\nconst props = defineProps<AiChatProps>()\n\nconst emit = defineEmits<AiChatEmits>()\n\ndefineSlots<{\n /** 空闲欢迎区插槽(输入框上方;工作中跳到列表末尾) */\n welcome(): any\n /** 按工具名自定义工具结果展示,如 tool-getWeather */\n [name: `tool-${string}`]: (scope: { toolCall: ChatToolCall }) => any\n}>()\n\nconst cls = bem('ai-chat')\n\nconst slots = useSlots()\n\n/**\n * 按 name 查表:session 下 tools 仅为渲染元信息(不注入内置工具,忽略 execute 等)。\n * 函数 transport 仍注入 createBuiltinTools(同名内置优先)。\n */\nconst toolMap = computed((): Record<string, ChatTool | ChatToolMeta | undefined> => {\n if (isServerTransport(props.transport)) {\n return Object.fromEntries((props.tools ?? []).map((t) => [t.name, t]))\n }\n const builtins = createBuiltinTools()\n const names = new Set(builtins.map((t) => t.name))\n const tools = [...builtins, ...(props.tools ?? []).filter((t) => !names.has(t.name))]\n return Object.fromEntries(tools.map((t) => [t.name, t]))\n})\n\nconst toolIcons = computed(() => props.toolIcons)\n\nconst {\n messages,\n model,\n reasoningLevel,\n running,\n queue,\n jobs,\n tokenUsage,\n lastTurnUsage,\n lastRequestUsage,\n pendingApprovals,\n pendingQuestion,\n send,\n abort,\n regenerate,\n clear,\n respondToolCall,\n respondSession,\n enqueue,\n startQueued,\n removeQueued\n} = useChat({ props, emit })\n\n/** 空会话(无可见消息):输入区垂直居中;欢迎区钉在滚动容器外、贴于输入框上方 */\nconst isEmpty = computed(() => !messages.value.some((msg) => msg.role !== 'tool'))\n\n/** 有对话内容、队列或生成中才允许清除,避免误触空会话 */\nconst clearable = computed(\n () => messages.value.length > 0 || queue.value.length > 0 || running.value\n)\n\n/** 所有 renderTo: 'panel' 的工具调用(按消息顺序,面板内容的来源) */\nconst panelCalls = computed(() => {\n const calls: ChatToolCall[] = []\n for (const msg of messages.value) {\n if (msg.role !== 'assistant' || !msg.toolCalls) continue\n for (const call of msg.toolCalls) {\n if (toolMap.value[call.name]?.renderTo === 'panel') calls.push(call)\n }\n }\n return calls\n})\n\n/** 当前展示在右侧面板的工具调用 id;null 表示面板关闭 */\nconst activePanelCallId = ref<string | null>(null)\n\nconst activePanelCall = computed(() => {\n if (!activePanelCallId.value) return null\n return panelCalls.value.find((call) => call.id === activePanelCallId.value) ?? null\n})\n\nconst panelOpen = computed(() => activePanelCall.value != null)\n\n// 出现新的面板工具调用时自动打开并聚焦到它\nwatch(\n () => panelCalls.value[panelCalls.value.length - 1]?.id,\n (id) => {\n if (id) activePanelCallId.value = id\n }\n)\n\n// 聚焦的调用消失(清空消息 / 重新生成)时,回落到最近一次面板调用或关闭\nwatch(panelCalls, (calls) => {\n const id = activePanelCallId.value\n if (id && !calls.some((call) => call.id === id)) {\n activePanelCallId.value = calls[calls.length - 1]?.id ?? null\n }\n})\n\n/** 打开面板并聚焦到指定工具调用(工具卡片「查看面板」入口,经 DI 下发) */\nconst openPanel = (toolCallId: string) => {\n if (panelCalls.value.some((call) => call.id === toolCallId)) {\n activePanelCallId.value = toolCallId\n }\n}\n\nconst closePanel = () => {\n activePanelCallId.value = null\n}\n\nprovide(AiChatDIKey, { cls, slots, tools: toolMap, toolIcons, openPanel })\n\n/**\n * 面板宽度:经 ULayout 列轨施加(cols = 1fr + 面板宽度),\n * 拖拽手柄由 ULayout 提供(colMinSizes 约束会话区 / 面板最小宽度)。\n */\nconst PANEL_MIN_WIDTH = 320\nconst MAIN_MIN_WIDTH = 360\n/** 面板打开时默认给会话区留的宽度(即面板默认尽可能大) */\nconst MAIN_DEFAULT_WIDTH = 860\n\nconst layoutRef = useTemplateRef<InstanceType<typeof ULayout>>('layoutRef')\nconst panelRef = useTemplateRef<InstanceType<typeof SidePanel>>('panelRef')\nconst panelWidth = ref(PANEL_MIN_WIDTH)\nconst resizing = ref(false)\n\nconst layoutCols = computed(() => {\n return panelOpen.value ? ['1fr', `${panelWidth.value}px`] : ['1fr']\n})\n\nconst containerWidth = () => (layoutRef.value?.$el as HTMLElement | undefined)?.clientWidth ?? 0\n\n/** 面板默认宽度:容器宽减去会话区默认保留宽度(缺省给会话区留 MAIN_DEFAULT_WIDTH) */\nconst defaultPanelWidth = () => {\n const width = containerWidth()\n return width ? width - MAIN_DEFAULT_WIDTH : 420\n}\n\n/** 钳位面板宽度在 [PANEL_MIN_WIDTH, 容器宽 - MAIN_MIN_WIDTH] 区间 */\nconst clampPanelWidth = (width: number) => {\n const width0 = containerWidth()\n const maxWidth = width0\n ? Math.max(PANEL_MIN_WIDTH, width0 - MAIN_MIN_WIDTH)\n : Number.MAX_SAFE_INTEGER\n return Math.min(Math.max(width, PANEL_MIN_WIDTH), maxWidth)\n}\n\n// 聚焦到指定了 panelWidth 的工具调用时应用其宽度;面板从关闭到打开且工具未指定宽度时\n// 应用默认宽(会话区留 860px);面板已打开时切换聚焦保持当前宽度(含用户拖拽结果)\nwatch(activePanelCall, (call, prevCall) => {\n if (!call) return\n const toolWidth = toolMap.value[call.name]?.panelWidth\n const width = toolWidth ?? (prevCall ? undefined : defaultPanelWidth())\n if (typeof width !== 'number') return\n panelWidth.value = clampPanelWidth(width)\n})\n\n// 跟踪面板实际宽度(含用户拖拽结果):面板关闭重开、切换到未指定宽度的工具时保持\nuseResizeObserver({\n targets: computed(() => (panelRef.value?.$el as HTMLElement | undefined) ?? undefined),\n onResize(entries) {\n // borderBoxSize 随通知携带(含边框、零布局开销),不读 offsetWidth/clientWidth\n const inlineSize = entries[0]?.borderBoxSize?.[0]?.inlineSize\n if (!inlineSize) return\n const width = Math.round(inlineSize)\n // 滞回:忽略亚像素/边框级的微小偏差(< 2px),阻断任何「观测→回写→再观测」的微缩反馈回路\n if (Math.abs(width - panelWidth.value) < 2) return\n panelWidth.value = width\n }\n})\n\nconst chatInputRef = useTemplateRef<InstanceType<typeof ChatInput>>('chatInputRef')\n\n/**\n * 编辑中的队列项原后继 id(编辑态标记):\n * undefined 表示无编辑;null 表示原位置在队尾;否则重新提交时插回该后继之前,\n * 使前后项顺序不受编辑影响(前项已执行则该项自然成为最前待执行项)。\n */\nconst editingSuccessor = ref<string | null | undefined>(undefined)\n\n/** 编辑队列项:取回输入框重新编辑;已有未完成编辑时先按锚点还原,避免内容丢失 */\nconst handleQueueEdit = (id: string) => {\n restoreEditing()\n\n const index = queue.value.findIndex((item) => item.id === id)\n if (index === -1) return\n editingSuccessor.value = queue.value[index + 1]?.id ?? null\n\n const item = removeQueued(id)\n if (item) chatInputRef.value?.setContent(item.content)\n}\n\n/** 将输入框中未提交的编辑内容按原锚点插回队列 */\nconst restoreEditing = () => {\n if (editingSuccessor.value === undefined) return\n const content = chatInputRef.value?.getContent().trim()\n if (content) enqueue(content, undefined, editingSuccessor.value ?? undefined)\n editingSuccessor.value = undefined\n}\n\nconst handleSend = (content: string, attachments: ChatAttachment[]) => {\n if (props.readonly) return\n // 编辑态提交:插回原锚点位置(会话进行中则留在队列,空闲时由 enqueue 自动消耗队首)\n if (editingSuccessor.value !== undefined) {\n enqueue(content, attachments, editingSuccessor.value ?? undefined)\n editingSuccessor.value = undefined\n return\n }\n send(content, attachments)\n}\n\nconst handleQuestionSubmit = (value: AskQuestionAnswer[] | AskQuestionResult) => {\n const pending = pendingQuestion.value\n if (!pending) return\n respondSession(pending.rpcId, true, Array.isArray(value) ? value : value.answers)\n}\n\ndefineExpose<_AiChatExposed>({\n send,\n abort,\n regenerate,\n clear,\n tokenUsage,\n lastTurnUsage,\n queue,\n startQueued,\n removeQueued,\n enqueue\n})\n</script>\n"],"mappings":"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"}
@@ -2,7 +2,7 @@ import { AiChatDIKey } from "./di.js";
2
2
  import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, inject, normalizeClass, openBlock, renderList, toDisplayString, unref, withCtx } from "vue";
3
3
  import { UButton } from "@veltra/desktop";
4
4
  import { bem } from "@veltra/utils";
5
- //#region src/components/ai-chat/approval-banner.vue
5
+ // src/components/ai-chat/approval-banner.vue
6
6
  const _sfc_main = /*@__PURE__*/ defineComponent({
7
7
  name: "UAiChatApprovalBanner",
8
8
  __name: "approval-banner",
@@ -48,7 +48,6 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
48
48
  };
49
49
  }
50
50
  });
51
- //#endregion
52
51
  export { _sfc_main as default };
53
52
 
54
53
  //# sourceMappingURL=approval-banner.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"approval-banner.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref","_Fragment","_renderList","_createElementVNode","_toDisplayString","_createCommentVNode","_createVNode","_withCtx","_createTextVNode"],"sources":["../../../src/components/ai-chat/approval-banner.vue"],"sourcesContent":["<template>\n <div v-if=\"items.length\" :class=\"cls.e('approval-banner')\">\n <div v-for=\"item in items\" :key=\"item.approvalId\" :class=\"cls.e('approval-banner-card')\">\n <div :class=\"cls.e('approval-banner-title')\">需要确认</div>\n <div v-if=\"item.reason || item.toolName\" :class=\"cls.e('approval-banner-reason')\">\n {{ item.reason ?? item.toolName }}\n </div>\n <div :class=\"cls.e('approval-banner-actions')\">\n <UButton size=\"small\" type=\"primary\" @click=\"emit('respond', item.rpcId, true)\">\n 允许\n </UButton>\n <UButton size=\"small\" text @click=\"emit('respond', item.rpcId, false)\">拒绝</UButton>\n </div>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UButton } from '@veltra/desktop'\nimport { bem } from '@veltra/utils'\nimport { computed, inject } from 'vue'\n\nimport type { ChatPendingApproval } from '../../chat/fold'\nimport { AiChatDIKey } from './di'\n\ndefineOptions({ name: 'UAiChatApprovalBanner' })\n\nconst props = defineProps<{ approvals: ChatPendingApproval[] }>()\n\nconst emit = defineEmits<{ (e: 'respond', rpcId: string, ok: boolean): void }>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\n/** 仅无 callId 的审批走横幅;有 callId 的走工具卡确认 */\nconst items = computed(() => props.approvals.filter((item) => !item.callId))\n</script>\n"],"mappings":";;;;;;;;;;;EA2BA,MAAM,QAAQ;EAEd,MAAM,OAAO;EAGb,MAAM,MADK,OAAO,WACN,CAAA,EAAI,OAAO,IAAI,SAAS;;EAGpC,MAAM,QAAQ,eAAe,MAAM,UAAU,QAAQ,SAAS,CAAC,KAAK,MAAM,CAAC;;GAlC9D,OAAA,MAAA,MAAM,UAAjBA,UAAA,GAAAC,mBAaM,OAAA;;IAboB,OAAKC,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,iBAAA,CAAA;GACpC,GAAA,EAAAH,UAAA,IAAA,GAAAC,mBAWMG,UAAA,MAAAC,WAXc,MAAA,QAAR,SAAI;IAAhB,OAAAL,UAAA,GAAAC,mBAWM,OAAA;KAXsB,KAAK,KAAK;KAAa,OAAKC,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,sBAAA,CAAA;;KAC7DG,mBAAuD,OAAA,EAAjD,OAAKJ,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,uBAAA,CAAA,EAA2B,GAAA,QAAI,CAAA;KACtC,KAAK,UAAU,KAAK,YAA/BH,UAAA,GAAAC,mBAEM,OAAA;;MAFoC,OAAKC,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,wBAAA,CAAA;KACjD,GAAAI,gBAAA,KAAK,UAAU,KAAK,QAAQ,GAAA,CAAA,KAAAC,mBAAA,QAAA,IAAA;KAEjCF,mBAKM,OAAA,EALA,OAAKJ,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,yBAAA,CAAA,EAAA,GAChB,CAAAM,YAEUN,MAAA,OAAA,GAAA;MAFD,MAAK;MAAQ,MAAK;MAAW,UAAK,WAAE,KAAI,WAAY,KAAK,OAAK,IAAA;;MAAS,SAAAO,cAEhF,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAFgFC,gBAAA,QAEhF,EAAA,CAAA,EAAA,CAAA;;KACA,GAAA,GAAA,CAAA,SAAA,CAAA,GAAAF,YAAmFN,MAAA,OAAA,GAAA;MAA1E,MAAK;MAAQ,MAAA;MAAM,UAAK,WAAE,KAAI,WAAY,KAAK,OAAK,KAAA;;MAAU,SAAAO,cAAE,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAAFC,gBAAA,MAAE,EAAA,CAAA,EAAA,CAAA"}
1
+ {"version":3,"file":"approval-banner.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref","_Fragment","_renderList","_createElementVNode","_toDisplayString","_createCommentVNode","_createVNode","_withCtx","_createTextVNode"],"sources":["../../../src/components/ai-chat/approval-banner.vue"],"sourcesContent":["<template>\n <div v-if=\"items.length\" :class=\"cls.e('approval-banner')\">\n <div v-for=\"item in items\" :key=\"item.approvalId\" :class=\"cls.e('approval-banner-card')\">\n <div :class=\"cls.e('approval-banner-title')\">需要确认</div>\n <div v-if=\"item.reason || item.toolName\" :class=\"cls.e('approval-banner-reason')\">\n {{ item.reason ?? item.toolName }}\n </div>\n <div :class=\"cls.e('approval-banner-actions')\">\n <UButton size=\"small\" type=\"primary\" @click=\"emit('respond', item.rpcId, true)\">\n 允许\n </UButton>\n <UButton size=\"small\" text @click=\"emit('respond', item.rpcId, false)\">拒绝</UButton>\n </div>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UButton } from '@veltra/desktop'\nimport { bem } from '@veltra/utils'\nimport { computed, inject } from 'vue'\n\nimport type { ChatPendingApproval } from '../../chat/fold'\nimport { AiChatDIKey } from './di'\n\ndefineOptions({ name: 'UAiChatApprovalBanner' })\n\nconst props = defineProps<{ approvals: ChatPendingApproval[] }>()\n\nconst emit = defineEmits<{ (e: 'respond', rpcId: string, ok: boolean): void }>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\n/** 仅无 callId 的审批走横幅;有 callId 的走工具卡确认 */\nconst items = computed(() => props.approvals.filter((item) => !item.callId))\n</script>\n"],"mappings":";;;;;;;;;;;EA2BA,MAAM,QAAQ;EAEd,MAAM,OAAO;EAGb,MAAM,MADK,OAAO,WACN,CAAA,EAAI,OAAO,IAAI,SAAS;;EAGpC,MAAM,QAAQ,eAAe,MAAM,UAAU,QAAQ,SAAS,CAAC,KAAK,MAAM,CAAC;;GAlC9D,OAAA,MAAA,MAAM,UAAjBA,UAAA,GAAAC,mBAaM,OAAA;;IAboB,OAAKC,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,iBAAA,CAAA;G,KACpCH,UAAA,IAAA,GAAAC,mBAWMG,UAAA,MAAAC,WAXc,MAAA,QAAR,SAAI;IAAhB,OAAAL,UAAA,GAAAC,mBAWM,OAAA;KAXsB,KAAK,KAAK;KAAa,OAAKC,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,sBAAA,CAAA;;KAC7DG,mBAAuD,OAAA,EAAjD,OAAKJ,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,uBAAA,CAAA,EAA2B,GAAA,QAAI,CAAA;KACtC,KAAK,UAAU,KAAK,YAA/BH,UAAA,GAAAC,mBAEM,OAAA;;MAFoC,OAAKC,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,wBAAA,CAAA;KACjD,GAAAI,gBAAA,KAAK,UAAU,KAAK,QAAQ,GAAA,CAAA,K;KAEjCD,mBAKM,OAAA,EALA,OAAKJ,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,yBAAA,CAAA,E,IAChBM,YAEUN,MAAA,OAAA,GAAA;MAFD,MAAK;MAAQ,MAAK;MAAW,UAAK,WAAE,KAAI,WAAY,KAAK,OAAK,IAAA;;MAAS,SAAAO,cAEhF,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAFgFC,gBAAA,QAEhF,EAAA,C;;K,oBACAF,YAAmFN,MAAA,OAAA,GAAA;MAA1E,MAAK;MAAQ,MAAA;MAAM,UAAK,WAAE,KAAI,WAAY,KAAK,OAAK,KAAA;;MAAU,SAAAO,cAAE,CAAA,GAAA,OAAA,OAAA,OAAA,KAAA,CAAFC,gBAAA,MAAE,EAAA,C"}
@@ -2,7 +2,7 @@ import { AiChatDIKey } from "./di.js";
2
2
  import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, inject, normalizeClass, openBlock, ref, renderList, toDisplayString, unref, watch, withCtx } from "vue";
3
3
  import { UButton, UInput } from "@veltra/desktop";
4
4
  import { bem } from "@veltra/utils";
5
- //#region src/components/ai-chat/ask-question.vue
5
+ // src/components/ai-chat/ask-question.vue
6
6
  const _hoisted_1 = ["onClick"];
7
7
  const _hoisted_2 = ["aria-checked", "onClick"];
8
8
  const _sfc_main = /*@__PURE__*/ defineComponent({
@@ -162,7 +162,6 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
162
162
  };
163
163
  }
164
164
  });
165
- //#endregion
166
165
  export { _sfc_main as default };
167
166
 
168
167
  //# sourceMappingURL=ask-question.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ask-question.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref","_createCommentVNode","_Fragment","_createElementVNode","_toDisplayString","_renderList","_createTextVNode","_createVNode","_createBlock","_withCtx"],"sources":["../../../src/components/ai-chat/ask-question.vue"],"sourcesContent":["<template>\n <div :class=\"cls.e('ask-question')\">\n <!-- 作答中:分页向导 -->\n <template v-if=\"answering\">\n <div v-if=\"questions.length > 1\" :class=\"cls.e('ask-question-progress')\">\n <span :class=\"cls.e('ask-question-count')\">{{ current + 1 }} / {{ questions.length }}</span>\n <span :class=\"cls.e('ask-question-dots')\">\n <span\n v-for=\"(_, i) in questions\"\n :key=\"i\"\n :class=\"[\n cls.e('ask-question-dot'),\n bem.is('current', i === current),\n bem.is('answered', isAnswered(i))\n ]\"\n @click=\"goTo(i)\"\n />\n </span>\n </div>\n\n <div :class=\"cls.e('ask-question-q')\">\n <span v-if=\"questions.length > 1\" :class=\"cls.e('ask-question-no')\" aria-hidden=\"true\">\n {{ current + 1 }}\n </span>\n {{ currentQuestion?.question }}\n </div>\n\n <div\n v-if=\"currentQuestion?.options?.length\"\n :class=\"cls.e('ask-question-options')\"\n role=\"radiogroup\"\n >\n <button\n v-for=\"option in currentQuestion.options\"\n :key=\"option\"\n type=\"button\"\n role=\"radio\"\n :aria-checked=\"selections[current] === option\"\n :class=\"[cls.e('ask-question-opt'), bem.is('selected', selections[current] === option)]\"\n @click=\"pickOption(option)\"\n >\n <span\n :class=\"[\n cls.e('ask-question-glyph'),\n bem.is('selected', selections[current] === option)\n ]\"\n aria-hidden=\"true\"\n />\n <span :class=\"cls.e('ask-question-opt-text')\">{{ option }}</span>\n </button>\n </div>\n\n <UInput\n :model-value=\"customs[current]\"\n :placeholder=\"currentQuestion?.placeholder ?? '自定义回答…'\"\n @update:model-value=\"onCustomInput\"\n />\n\n <footer :class=\"cls.e('ask-question-actions')\">\n <UButton\n v-if=\"questions.length > 1\"\n size=\"small\"\n text\n :disabled=\"current === 0\"\n @click=\"goTo(current - 1)\"\n >\n 上一个\n </UButton>\n <UButton\n v-if=\"current < questions.length - 1\"\n size=\"small\"\n type=\"primary\"\n @click=\"goTo(current + 1)\"\n >\n 下一个\n </UButton>\n <UButton v-else size=\"small\" type=\"primary\" :disabled=\"!allAnswered\" @click=\"handleSubmit\">\n 提交\n </UButton>\n </footer>\n </template>\n\n <!-- 提交后:问答摘要 -->\n <template v-else-if=\"answers != null\">\n <div v-for=\"(item, i) in doneAnswers\" :key=\"i\" :class=\"cls.e('ask-question-result')\">\n <div :class=\"cls.e('ask-question-result-q')\">\n <span\n v-if=\"doneAnswers.length > 1\"\n :class=\"cls.e('ask-question-result-no')\"\n aria-hidden=\"true\"\n >\n {{ i + 1 }}\n </span>\n {{ item.question }}\n </div>\n <div :class=\"cls.e('ask-question-result-a')\">\n <span v-if=\"item.isOption\" :class=\"cls.e('ask-question-chip')\">{{ item.answer }}</span>\n <template v-else>{{ item.answer }}</template>\n </div>\n </div>\n </template>\n\n <!-- 异常 / 取消 -->\n <div v-else :class=\"cls.e('ask-question-status')\">{{ error }}</div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UButton, UInput } from '@veltra/desktop'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, ref, watch } from 'vue'\n\nimport type {\n AskQuestionAnswer,\n AskQuestionItem,\n AskQuestionResult\n} from '../../tools/ask-question/ask-question'\nimport { AiChatDIKey } from './di'\n\ndefineOptions({ name: 'UAiChatAskQuestion' })\n\nconst props = defineProps<{\n /** 问题列表;组件不再从 toolCall 解析参数 */\n questions: AskQuestionItem[]\n /** 已提交摘要;有值则展示结果而非表单 */\n answers?: AskQuestionAnswer[]\n /** 失败 / 取消文案 */\n error?: string\n}>()\n\nconst emit = defineEmits<{\n /** 提交问答;值为答案数组(与 AskQuestionResult 兼容) */\n (e: 'submit', value: AskQuestionAnswer[] | AskQuestionResult): void\n}>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\n/** 作答中:尚未提交、也没有失败文案 */\nconst answering = computed(() => props.answers == null && !props.error)\n\n/** 当前题索引 */\nconst current = ref(0)\n/** 各题选中的预设选项 */\nconst selections = ref<(string | null)[]>([])\n/** 各题的自定义输入 */\nconst customs = ref<string[]>([])\n\nwatch(\n () => props.questions,\n (list) => {\n if (props.answers != null) return\n selections.value = list.map(() => null)\n customs.value = list.map(() => '')\n },\n { immediate: true }\n)\n\nconst currentQuestion = computed(() => props.questions[current.value])\n\n/** 某题是否已作答:自定义输入非空优先,否则看选中项 */\nconst isAnswered = (index: number) => {\n return Boolean(customs.value[index]?.trim() || selections.value[index])\n}\n\nconst allAnswered = computed(() => {\n return props.questions.length > 0 && props.questions.every((_, i) => isAnswered(i))\n})\n\nconst goTo = (index: number) => {\n if (index < 0 || index >= props.questions.length) return\n current.value = index\n}\n\n/** 点选选项:再次点击取消选择;选中时清空自定义输入(两者互斥) */\nconst pickOption = (option: string) => {\n const i = current.value\n const selected = selections.value[i] === option ? null : option\n selections.value[i] = selected\n if (selected) customs.value[i] = ''\n}\n\n/** 自定义输入:有内容时清除已选选项(两者互斥) */\nconst onCustomInput = (value: string) => {\n customs.value[current.value] = value\n if (value) selections.value[current.value] = null\n}\n\nconst handleSubmit = () => {\n const firstUnanswered = props.questions.findIndex((_, i) => !isAnswered(i))\n if (firstUnanswered >= 0) {\n current.value = firstUnanswered\n return\n }\n emit(\n 'submit',\n props.questions.map((item, i) => ({\n question: item.question,\n answer: customs.value[i]?.trim() || selections.value[i] || ''\n }))\n )\n}\n\n/** 提交后的问答摘要:命中预设选项的回答渲染为 chip */\nconst doneAnswers = computed(() => {\n const list = props.answers\n if (!list?.length) return []\n return list.map((item) => ({\n question: item.question,\n answer: item.answer,\n isOption: props.questions.some(\n (q) => q.question === item.question && q.options?.includes(item.answer)\n )\n }))\n})\n</script>\n"],"mappings":"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"}
1
+ {"version":3,"file":"ask-question.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref","_createCommentVNode","_Fragment","_createElementVNode","_toDisplayString","_renderList","_createTextVNode","_createVNode","_createBlock","_withCtx"],"sources":["../../../src/components/ai-chat/ask-question.vue"],"sourcesContent":["<template>\n <div :class=\"cls.e('ask-question')\">\n <!-- 作答中:分页向导 -->\n <template v-if=\"answering\">\n <div v-if=\"questions.length > 1\" :class=\"cls.e('ask-question-progress')\">\n <span :class=\"cls.e('ask-question-count')\">{{ current + 1 }} / {{ questions.length }}</span>\n <span :class=\"cls.e('ask-question-dots')\">\n <span\n v-for=\"(_, i) in questions\"\n :key=\"i\"\n :class=\"[\n cls.e('ask-question-dot'),\n bem.is('current', i === current),\n bem.is('answered', isAnswered(i))\n ]\"\n @click=\"goTo(i)\"\n />\n </span>\n </div>\n\n <div :class=\"cls.e('ask-question-q')\">\n <span v-if=\"questions.length > 1\" :class=\"cls.e('ask-question-no')\" aria-hidden=\"true\">\n {{ current + 1 }}\n </span>\n {{ currentQuestion?.question }}\n </div>\n\n <div\n v-if=\"currentQuestion?.options?.length\"\n :class=\"cls.e('ask-question-options')\"\n role=\"radiogroup\"\n >\n <button\n v-for=\"option in currentQuestion.options\"\n :key=\"option\"\n type=\"button\"\n role=\"radio\"\n :aria-checked=\"selections[current] === option\"\n :class=\"[cls.e('ask-question-opt'), bem.is('selected', selections[current] === option)]\"\n @click=\"pickOption(option)\"\n >\n <span\n :class=\"[\n cls.e('ask-question-glyph'),\n bem.is('selected', selections[current] === option)\n ]\"\n aria-hidden=\"true\"\n />\n <span :class=\"cls.e('ask-question-opt-text')\">{{ option }}</span>\n </button>\n </div>\n\n <UInput\n :model-value=\"customs[current]\"\n :placeholder=\"currentQuestion?.placeholder ?? '自定义回答…'\"\n @update:model-value=\"onCustomInput\"\n />\n\n <footer :class=\"cls.e('ask-question-actions')\">\n <UButton\n v-if=\"questions.length > 1\"\n size=\"small\"\n text\n :disabled=\"current === 0\"\n @click=\"goTo(current - 1)\"\n >\n 上一个\n </UButton>\n <UButton\n v-if=\"current < questions.length - 1\"\n size=\"small\"\n type=\"primary\"\n @click=\"goTo(current + 1)\"\n >\n 下一个\n </UButton>\n <UButton v-else size=\"small\" type=\"primary\" :disabled=\"!allAnswered\" @click=\"handleSubmit\">\n 提交\n </UButton>\n </footer>\n </template>\n\n <!-- 提交后:问答摘要 -->\n <template v-else-if=\"answers != null\">\n <div v-for=\"(item, i) in doneAnswers\" :key=\"i\" :class=\"cls.e('ask-question-result')\">\n <div :class=\"cls.e('ask-question-result-q')\">\n <span\n v-if=\"doneAnswers.length > 1\"\n :class=\"cls.e('ask-question-result-no')\"\n aria-hidden=\"true\"\n >\n {{ i + 1 }}\n </span>\n {{ item.question }}\n </div>\n <div :class=\"cls.e('ask-question-result-a')\">\n <span v-if=\"item.isOption\" :class=\"cls.e('ask-question-chip')\">{{ item.answer }}</span>\n <template v-else>{{ item.answer }}</template>\n </div>\n </div>\n </template>\n\n <!-- 异常 / 取消 -->\n <div v-else :class=\"cls.e('ask-question-status')\">{{ error }}</div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UButton, UInput } from '@veltra/desktop'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, ref, watch } from 'vue'\n\nimport type {\n AskQuestionAnswer,\n AskQuestionItem,\n AskQuestionResult\n} from '../../tools/ask-question/ask-question'\nimport { AiChatDIKey } from './di'\n\ndefineOptions({ name: 'UAiChatAskQuestion' })\n\nconst props = defineProps<{\n /** 问题列表;组件不再从 toolCall 解析参数 */\n questions: AskQuestionItem[]\n /** 已提交摘要;有值则展示结果而非表单 */\n answers?: AskQuestionAnswer[]\n /** 失败 / 取消文案 */\n error?: string\n}>()\n\nconst emit = defineEmits<{\n /** 提交问答;值为答案数组(与 AskQuestionResult 兼容) */\n (e: 'submit', value: AskQuestionAnswer[] | AskQuestionResult): void\n}>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\n/** 作答中:尚未提交、也没有失败文案 */\nconst answering = computed(() => props.answers == null && !props.error)\n\n/** 当前题索引 */\nconst current = ref(0)\n/** 各题选中的预设选项 */\nconst selections = ref<(string | null)[]>([])\n/** 各题的自定义输入 */\nconst customs = ref<string[]>([])\n\nwatch(\n () => props.questions,\n (list) => {\n if (props.answers != null) return\n selections.value = list.map(() => null)\n customs.value = list.map(() => '')\n },\n { immediate: true }\n)\n\nconst currentQuestion = computed(() => props.questions[current.value])\n\n/** 某题是否已作答:自定义输入非空优先,否则看选中项 */\nconst isAnswered = (index: number) => {\n return Boolean(customs.value[index]?.trim() || selections.value[index])\n}\n\nconst allAnswered = computed(() => {\n return props.questions.length > 0 && props.questions.every((_, i) => isAnswered(i))\n})\n\nconst goTo = (index: number) => {\n if (index < 0 || index >= props.questions.length) return\n current.value = index\n}\n\n/** 点选选项:再次点击取消选择;选中时清空自定义输入(两者互斥) */\nconst pickOption = (option: string) => {\n const i = current.value\n const selected = selections.value[i] === option ? null : option\n selections.value[i] = selected\n if (selected) customs.value[i] = ''\n}\n\n/** 自定义输入:有内容时清除已选选项(两者互斥) */\nconst onCustomInput = (value: string) => {\n customs.value[current.value] = value\n if (value) selections.value[current.value] = null\n}\n\nconst handleSubmit = () => {\n const firstUnanswered = props.questions.findIndex((_, i) => !isAnswered(i))\n if (firstUnanswered >= 0) {\n current.value = firstUnanswered\n return\n }\n emit(\n 'submit',\n props.questions.map((item, i) => ({\n question: item.question,\n answer: customs.value[i]?.trim() || selections.value[i] || ''\n }))\n )\n}\n\n/** 提交后的问答摘要:命中预设选项的回答渲染为 chip */\nconst doneAnswers = computed(() => {\n const list = props.answers\n if (!list?.length) return []\n return list.map((item) => ({\n question: item.question,\n answer: item.answer,\n isOption: props.questions.some(\n (q) => q.question === item.question && q.options?.includes(item.answer)\n )\n }))\n})\n</script>\n"],"mappings":"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"}
@@ -5,7 +5,7 @@ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock
5
5
  import { Attach, Clear, Close, Up } from "@veltra/icons/normal";
6
6
  import { UButton, UDropdown, UFilePicker, UIcon, UPopConfirm } from "@veltra/desktop";
7
7
  import { bem } from "@veltra/utils";
8
- //#region src/components/ai-chat/chat-input.vue
8
+ // src/components/ai-chat/chat-input.vue
9
9
  const _hoisted_1 = ["src", "alt"];
10
10
  const _hoisted_2 = ["placeholder", "onKeydown"];
11
11
  const _hoisted_3 = ["stroke-dasharray", "stroke-linecap"];
@@ -322,7 +322,6 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
322
322
  };
323
323
  }
324
324
  });
325
- //#endregion
326
325
  export { _sfc_main as default };
327
326
 
328
327
  //# sourceMappingURL=chat-input.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"chat-input.js","names":["_useModel","_openBlock","_createElementBlock","_normalizeClass","_unref","_Fragment","_renderList","_createElementVNode","_createVNode","_withCtx","_createCommentVNode","_withDirectives","_withKeys","_withModifiers","_vModelText","_createBlock","_toDisplayString","ModelPicker","ReasoningPicker"],"sources":["../../../src/components/ai-chat/chat-input.vue"],"sourcesContent":["<template>\n <div :class=\"cls.e('input-area')\">\n <div v-if=\"attachments.length\" :class=\"cls.e('input-attachments')\">\n <div\n v-for=\"(att, index) in attachments\"\n :key=\"att.dataUrl\"\n :class=\"cls.e('input-attachment')\"\n >\n <img :src=\"att.dataUrl\" :alt=\"att.name\" />\n <UIcon :class=\"cls.e('input-attachment-remove')\" @click=\"attachments.splice(index, 1)\">\n <Close />\n </UIcon>\n </div>\n </div>\n\n <textarea\n ref=\"textareaRef\"\n v-model=\"text\"\n :class=\"cls.e('input')\"\n :placeholder=\"placeholderText\"\n rows=\"1\"\n @keydown.enter.exact=\"handleSend\"\n @input=\"autoResize\"\n />\n\n <div :class=\"cls.e('input-toolbar')\">\n <div :class=\"cls.e('input-toolbar-left')\">\n <UFilePicker multiple :accept=\"accept ?? 'image/*'\" @pick=\"handlePick\">\n <UIcon :class=\"cls.e('input-attach')\" title=\"添加图片\">\n <Attach />\n </UIcon>\n </UFilePicker>\n\n <span :class=\"[cls.e('input-clear-wrap'), bem.is('disabled', !clearable)]\">\n <UPopConfirm\n title=\"清空当前对话?进行中的生成将被中止。\"\n confirm-text=\"清空\"\n direction=\"top\"\n alignment=\"start\"\n @confirm=\"emit('clear')\"\n >\n <template #reference>\n <UIcon :class=\"cls.e('input-clear')\" title=\"清除会话\">\n <Clear />\n </UIcon>\n </template>\n </UPopConfirm>\n </span>\n </div>\n\n <!-- 右簇:token 用量环 → 模型/推理选择 → 发送或停止(互斥) -->\n <div :class=\"cls.e('input-toolbar-right')\">\n <UDropdown\n v-if=\"tokenUsage\"\n width=\"auto\"\n min-width=\"200px\"\n :content-class=\"cls.e('usage-panel')\"\n >\n <template #trigger>\n <button type=\"button\" :class=\"cls.e('usage-trigger')\" aria-label=\"Token 用量\">\n <svg :class=\"cls.e('usage-ring')\" viewBox=\"0 0 36 36\">\n <circle :class=\"cls.e('usage-ring-track')\" cx=\"18\" cy=\"18\" r=\"15.5\" />\n <circle\n :class=\"cls.e('usage-ring-value')\"\n cx=\"18\"\n cy=\"18\"\n r=\"15.5\"\n pathLength=\"100\"\n transform=\"rotate(-90 18 18)\"\n :stroke-dasharray=\"`${contextPercent ?? 0} 100`\"\n :stroke-linecap=\"contextPercent ? 'round' : undefined\"\n />\n </svg>\n </button>\n </template>\n\n <template #content>\n <div :class=\"cls.e('usage-list')\">\n <div v-if=\"contextText\" :class=\"cls.e('usage-row')\">\n <span :class=\"cls.e('usage-label')\">上下文占用</span>\n <span :class=\"cls.e('usage-value')\">{{ contextText }}</span>\n </div>\n <div v-for=\"row in usageRows\" :key=\"row.label\" :class=\"cls.e('usage-row')\">\n <span :class=\"cls.e('usage-label')\">{{ row.label }}</span>\n <span :class=\"cls.e('usage-value')\">{{ row.value }}</span>\n </div>\n </div>\n </template>\n </UDropdown>\n\n <ModelPicker v-if=\"models?.length\" v-model:model=\"model\" :models=\"models\" />\n\n <ReasoningPicker\n v-if=\"models?.length\"\n v-model:reasoning-level=\"reasoningLevel\"\n :levels=\"reasoningLevels\"\n />\n\n <UButton\n v-if=\"showStop\"\n key=\"stop\"\n size=\"small\"\n type=\"danger\"\n circle\n title=\"停止生成\"\n @click=\"emit('abort')\"\n >\n <span :class=\"cls.e('input-stop')\" />\n </UButton>\n <UButton\n v-else\n key=\"send\"\n size=\"small\"\n type=\"primary\"\n circle\n :disabled=\"!hasContent\"\n :icon=\"Up\"\n :title=\"running ? '加入待发送队列' : undefined\"\n @click=\"handleSend\"\n />\n </div>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UButton, UDropdown, UFilePicker, UIcon, UPopConfirm } from '@veltra/desktop'\nimport { Attach, Clear, Close, Up } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, nextTick, ref, shallowRef, watch } from 'vue'\n\nimport type { ChatAttachment, ChatTokenUsage } from '../../chat/types'\nimport type { ChatModelOption } from '../../providers'\nimport { AiChatDIKey } from './di'\nimport ModelPicker from './model-picker.vue'\nimport ReasoningPicker from './reasoning-picker.vue'\n\ndefineOptions({ name: 'UAiChatInput' })\n\nconst props = defineProps<{\n /** 是否生成中(空输入显示停止,有内容则发送入队) */\n running: boolean\n /** 可选模型列表;有值则显示选择器 */\n models?: ChatModelOption[]\n /** 输入框占位文本 */\n placeholder?: string\n /** 附件 accept 类型 */\n accept?: string\n /** 单个附件最大字节数 */\n maxAttachmentSize?: number\n /** 是否可清除(有消息 / 队列 / 生成中) */\n clearable?: boolean\n /** 会话累计 token;无 usage 时为 null,不展示 */\n tokenUsage?: ChatTokenUsage | null\n /** 最近一次单次请求的 token(未累计);配合模型 contextWindow 计算用量环占比 */\n lastRequestUsage?: ChatTokenUsage | null\n /** 是否展示缓存命中 / 未命中(默认只显示总 token) */\n tokenUsageDetail?: boolean\n}>()\n\nconst model = defineModel<string>('model')\nconst reasoningLevel = defineModel<string>('reasoningLevel')\n\n/** 当前模型的推理等级列表,交给独立选择器;不足两档时选择器自身不渲染 */\nconst reasoningLevels = computed(() => {\n return props.models?.find((m) => m.id === model.value)?.reasoningLevels ?? []\n})\n\nconst emit = defineEmits<{\n (e: 'send', content: string, attachments: ChatAttachment[]): void\n (e: 'abort'): void\n (e: 'clear'): void\n}>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\nconst text = ref('')\nconst attachments = ref<ChatAttachment[]>([])\nconst textareaRef = shallowRef<HTMLTextAreaElement>()\n\nconst hasContent = computed(() => !!text.value.trim() || attachments.value.length > 0)\n\n/** 生成中且输入为空时显示停止;有内容则显示发送(入队) */\nconst showStop = computed(() => props.running && !hasContent.value)\n\n/**\n * 用量数字:不到 1000 原样;≥1000 用 K;≥100 万用 M。\n * 最多 1 位小数,整数不写 `.0`(1000 → 1K,1500 → 1.5K)。\n */\nconst formatTokenCount = (n: number) => {\n const scaled = (value: number, unit: 'K' | 'M') => {\n const rounded = Math.round(value * 10) / 10\n return `${Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1)}${unit}`\n }\n if (n < 1000) return String(n)\n if (n < 1_000_000) {\n const k = Math.round((n / 1000) * 10) / 10\n // 四舍五入后会变成 1000K 时改用 M\n return k >= 1000 ? scaled(n / 1_000_000, 'M') : scaled(n / 1000, 'K')\n }\n return scaled(n / 1_000_000, 'M')\n}\n\n/** 百分比:最多 1 位小数,整数不写 `.0` */\nconst formatPercent = (p: number) => {\n const rounded = Math.round(p * 10) / 10\n return `${Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1)}%`\n}\n\n/** 当前模型声明的上下文窗口;未声明则用量环不填充、面板不显示占用 */\nconst contextWindow = computed(() => {\n return props.models?.find((m) => m.id === model.value)?.contextWindow\n})\n\n/** 上下文占用百分比:最近一次请求总 token / 窗口上限;缺数据时为 null */\nconst contextPercent = computed(() => {\n const win = contextWindow.value\n const total = props.lastRequestUsage?.totalTokens\n if (!win || total == null) return null\n return Math.min(100, (total / win) * 100)\n})\n\n/** 面板占用行文案:1.5K / 128K · 1.2% */\nconst contextText = computed(() => {\n const percent = contextPercent.value\n const win = contextWindow.value\n const total = props.lastRequestUsage?.totalTokens\n if (percent == null || !win || total == null) return ''\n return `${formatTokenCount(total)} / ${formatTokenCount(win)} · ${formatPercent(percent)}`\n})\n\n/** 明细面板行:总 / 输入 / 输出;明细开关下再拼有数据的缓存项,缺字段不写 0 */\nconst usageRows = computed(() => {\n const usage = props.tokenUsage\n if (!usage) return []\n const rows = [\n { label: '总 token', value: formatTokenCount(usage.totalTokens) },\n { label: '输入', value: formatTokenCount(usage.promptTokens) },\n { label: '输出', value: formatTokenCount(usage.completionTokens) }\n ]\n if (props.tokenUsageDetail) {\n if (usage.cacheHitTokens != null) {\n rows.push({ label: '缓存命中', value: formatTokenCount(usage.cacheHitTokens) })\n }\n if (usage.cacheMissTokens != null) {\n rows.push({ label: '缓存未命中', value: formatTokenCount(usage.cacheMissTokens) })\n }\n }\n return rows\n})\n\n/** 生成中提示用户消息将进入队列 */\nconst placeholderText = computed(() => {\n if (props.running) return '会话进行中,发送的消息将进入待发送队列'\n return props.placeholder ?? '输入消息,Enter 发送,Shift + Enter 换行'\n})\n\nconst MAX_INPUT_HEIGHT = 160\n\n/** 多行自适应高度,上限 160px */\nconst autoResize = () => {\n const el = textareaRef.value\n if (!el) return\n el.style.height = 'auto'\n const nextHeight = Math.min(el.scrollHeight, MAX_INPUT_HEIGHT)\n el.style.height = `${nextHeight}px`\n el.style.overflowY = el.scrollHeight > MAX_INPUT_HEIGHT ? 'auto' : 'hidden'\n}\n\nwatch(placeholderText, () => {\n void nextTick(autoResize)\n})\n\nconst handleSend = (e?: Event) => {\n // 输入法组合态的回车只是确认上屏,不发送:Chromium 在 compositionend 前发\n // isComposing=true 的 keydown;Safari 在 compositionend 后补发 keyCode 229 的\n // keydown。不过滤会把组合前的旧输入提前发出(串消息),或上屏即误发送。\n if (e instanceof KeyboardEvent) {\n if (e.isComposing || e.keyCode === 229) return\n e.preventDefault()\n }\n if (!hasContent.value) return\n emit('send', text.value.trim(), attachments.value)\n text.value = ''\n attachments.value = []\n autoResize()\n}\n\n/** 取回内容到输入框(队列编辑场景),并聚焦 */\nconst setContent = (content: string) => {\n text.value = content\n void nextTick(() => {\n autoResize()\n textareaRef.value?.focus()\n })\n}\n\n/** 读取当前输入框内容 */\nconst getContent = () => text.value\n\ndefineExpose({ setContent, getContent })\n\n/** 将拾取的文件转为 dataUrl 附件(超限忽略) */\nconst handlePick = (files: File[]) => {\n const maxSize = props.maxAttachmentSize ?? 10 * 1024 * 1024\n\n for (const file of files) {\n if (file.size > maxSize) {\n console.warn(`[UAiChat] 附件 ${file.name} 超过大小限制,已忽略`)\n continue\n }\n const reader = new FileReader()\n reader.onload = () => {\n attachments.value.push({\n name: file.name,\n mimeType: file.type,\n size: file.size,\n dataUrl: String(reader.result)\n })\n }\n reader.readAsDataURL(file)\n }\n}\n</script>\n"],"mappings":"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"}
1
+ {"version":3,"file":"chat-input.js","names":["_useModel","_openBlock","_createElementBlock","_normalizeClass","_unref","_Fragment","_renderList","_createElementVNode","_createVNode","_withCtx","_createCommentVNode","_withDirectives","_withKeys","_withModifiers","_vModelText","_createBlock","_toDisplayString","ModelPicker","ReasoningPicker"],"sources":["../../../src/components/ai-chat/chat-input.vue"],"sourcesContent":["<template>\n <div :class=\"cls.e('input-area')\">\n <div v-if=\"attachments.length\" :class=\"cls.e('input-attachments')\">\n <div\n v-for=\"(att, index) in attachments\"\n :key=\"att.dataUrl\"\n :class=\"cls.e('input-attachment')\"\n >\n <img :src=\"att.dataUrl\" :alt=\"att.name\" />\n <UIcon :class=\"cls.e('input-attachment-remove')\" @click=\"attachments.splice(index, 1)\">\n <Close />\n </UIcon>\n </div>\n </div>\n\n <textarea\n ref=\"textareaRef\"\n v-model=\"text\"\n :class=\"cls.e('input')\"\n :placeholder=\"placeholderText\"\n rows=\"1\"\n @keydown.enter.exact=\"handleSend\"\n @input=\"autoResize\"\n />\n\n <div :class=\"cls.e('input-toolbar')\">\n <div :class=\"cls.e('input-toolbar-left')\">\n <UFilePicker multiple :accept=\"accept ?? 'image/*'\" @pick=\"handlePick\">\n <UIcon :class=\"cls.e('input-attach')\" title=\"添加图片\">\n <Attach />\n </UIcon>\n </UFilePicker>\n\n <span :class=\"[cls.e('input-clear-wrap'), bem.is('disabled', !clearable)]\">\n <UPopConfirm\n title=\"清空当前对话?进行中的生成将被中止。\"\n confirm-text=\"清空\"\n direction=\"top\"\n alignment=\"start\"\n @confirm=\"emit('clear')\"\n >\n <template #reference>\n <UIcon :class=\"cls.e('input-clear')\" title=\"清除会话\">\n <Clear />\n </UIcon>\n </template>\n </UPopConfirm>\n </span>\n </div>\n\n <!-- 右簇:token 用量环 → 模型/推理选择 → 发送或停止(互斥) -->\n <div :class=\"cls.e('input-toolbar-right')\">\n <UDropdown\n v-if=\"tokenUsage\"\n width=\"auto\"\n min-width=\"200px\"\n :content-class=\"cls.e('usage-panel')\"\n >\n <template #trigger>\n <button type=\"button\" :class=\"cls.e('usage-trigger')\" aria-label=\"Token 用量\">\n <svg :class=\"cls.e('usage-ring')\" viewBox=\"0 0 36 36\">\n <circle :class=\"cls.e('usage-ring-track')\" cx=\"18\" cy=\"18\" r=\"15.5\" />\n <circle\n :class=\"cls.e('usage-ring-value')\"\n cx=\"18\"\n cy=\"18\"\n r=\"15.5\"\n pathLength=\"100\"\n transform=\"rotate(-90 18 18)\"\n :stroke-dasharray=\"`${contextPercent ?? 0} 100`\"\n :stroke-linecap=\"contextPercent ? 'round' : undefined\"\n />\n </svg>\n </button>\n </template>\n\n <template #content>\n <div :class=\"cls.e('usage-list')\">\n <div v-if=\"contextText\" :class=\"cls.e('usage-row')\">\n <span :class=\"cls.e('usage-label')\">上下文占用</span>\n <span :class=\"cls.e('usage-value')\">{{ contextText }}</span>\n </div>\n <div v-for=\"row in usageRows\" :key=\"row.label\" :class=\"cls.e('usage-row')\">\n <span :class=\"cls.e('usage-label')\">{{ row.label }}</span>\n <span :class=\"cls.e('usage-value')\">{{ row.value }}</span>\n </div>\n </div>\n </template>\n </UDropdown>\n\n <ModelPicker v-if=\"models?.length\" v-model:model=\"model\" :models=\"models\" />\n\n <ReasoningPicker\n v-if=\"models?.length\"\n v-model:reasoning-level=\"reasoningLevel\"\n :levels=\"reasoningLevels\"\n />\n\n <UButton\n v-if=\"showStop\"\n key=\"stop\"\n size=\"small\"\n type=\"danger\"\n circle\n title=\"停止生成\"\n @click=\"emit('abort')\"\n >\n <span :class=\"cls.e('input-stop')\" />\n </UButton>\n <UButton\n v-else\n key=\"send\"\n size=\"small\"\n type=\"primary\"\n circle\n :disabled=\"!hasContent\"\n :icon=\"Up\"\n :title=\"running ? '加入待发送队列' : undefined\"\n @click=\"handleSend\"\n />\n </div>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UButton, UDropdown, UFilePicker, UIcon, UPopConfirm } from '@veltra/desktop'\nimport { Attach, Clear, Close, Up } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { computed, inject, nextTick, ref, shallowRef, watch } from 'vue'\n\nimport type { ChatAttachment, ChatTokenUsage } from '../../chat/types'\nimport type { ChatModelOption } from '../../providers'\nimport { AiChatDIKey } from './di'\nimport ModelPicker from './model-picker.vue'\nimport ReasoningPicker from './reasoning-picker.vue'\n\ndefineOptions({ name: 'UAiChatInput' })\n\nconst props = defineProps<{\n /** 是否生成中(空输入显示停止,有内容则发送入队) */\n running: boolean\n /** 可选模型列表;有值则显示选择器 */\n models?: ChatModelOption[]\n /** 输入框占位文本 */\n placeholder?: string\n /** 附件 accept 类型 */\n accept?: string\n /** 单个附件最大字节数 */\n maxAttachmentSize?: number\n /** 是否可清除(有消息 / 队列 / 生成中) */\n clearable?: boolean\n /** 会话累计 token;无 usage 时为 null,不展示 */\n tokenUsage?: ChatTokenUsage | null\n /** 最近一次单次请求的 token(未累计);配合模型 contextWindow 计算用量环占比 */\n lastRequestUsage?: ChatTokenUsage | null\n /** 是否展示缓存命中 / 未命中(默认只显示总 token) */\n tokenUsageDetail?: boolean\n}>()\n\nconst model = defineModel<string>('model')\nconst reasoningLevel = defineModel<string>('reasoningLevel')\n\n/** 当前模型的推理等级列表,交给独立选择器;不足两档时选择器自身不渲染 */\nconst reasoningLevels = computed(() => {\n return props.models?.find((m) => m.id === model.value)?.reasoningLevels ?? []\n})\n\nconst emit = defineEmits<{\n (e: 'send', content: string, attachments: ChatAttachment[]): void\n (e: 'abort'): void\n (e: 'clear'): void\n}>()\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n\nconst text = ref('')\nconst attachments = ref<ChatAttachment[]>([])\nconst textareaRef = shallowRef<HTMLTextAreaElement>()\n\nconst hasContent = computed(() => !!text.value.trim() || attachments.value.length > 0)\n\n/** 生成中且输入为空时显示停止;有内容则显示发送(入队) */\nconst showStop = computed(() => props.running && !hasContent.value)\n\n/**\n * 用量数字:不到 1000 原样;≥1000 用 K;≥100 万用 M。\n * 最多 1 位小数,整数不写 `.0`(1000 → 1K,1500 → 1.5K)。\n */\nconst formatTokenCount = (n: number) => {\n const scaled = (value: number, unit: 'K' | 'M') => {\n const rounded = Math.round(value * 10) / 10\n return `${Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1)}${unit}`\n }\n if (n < 1000) return String(n)\n if (n < 1_000_000) {\n const k = Math.round((n / 1000) * 10) / 10\n // 四舍五入后会变成 1000K 时改用 M\n return k >= 1000 ? scaled(n / 1_000_000, 'M') : scaled(n / 1000, 'K')\n }\n return scaled(n / 1_000_000, 'M')\n}\n\n/** 百分比:最多 1 位小数,整数不写 `.0` */\nconst formatPercent = (p: number) => {\n const rounded = Math.round(p * 10) / 10\n return `${Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1)}%`\n}\n\n/** 当前模型声明的上下文窗口;未声明则用量环不填充、面板不显示占用 */\nconst contextWindow = computed(() => {\n return props.models?.find((m) => m.id === model.value)?.contextWindow\n})\n\n/** 上下文占用百分比:最近一次请求总 token / 窗口上限;缺数据时为 null */\nconst contextPercent = computed(() => {\n const win = contextWindow.value\n const total = props.lastRequestUsage?.totalTokens\n if (!win || total == null) return null\n return Math.min(100, (total / win) * 100)\n})\n\n/** 面板占用行文案:1.5K / 128K · 1.2% */\nconst contextText = computed(() => {\n const percent = contextPercent.value\n const win = contextWindow.value\n const total = props.lastRequestUsage?.totalTokens\n if (percent == null || !win || total == null) return ''\n return `${formatTokenCount(total)} / ${formatTokenCount(win)} · ${formatPercent(percent)}`\n})\n\n/** 明细面板行:总 / 输入 / 输出;明细开关下再拼有数据的缓存项,缺字段不写 0 */\nconst usageRows = computed(() => {\n const usage = props.tokenUsage\n if (!usage) return []\n const rows = [\n { label: '总 token', value: formatTokenCount(usage.totalTokens) },\n { label: '输入', value: formatTokenCount(usage.promptTokens) },\n { label: '输出', value: formatTokenCount(usage.completionTokens) }\n ]\n if (props.tokenUsageDetail) {\n if (usage.cacheHitTokens != null) {\n rows.push({ label: '缓存命中', value: formatTokenCount(usage.cacheHitTokens) })\n }\n if (usage.cacheMissTokens != null) {\n rows.push({ label: '缓存未命中', value: formatTokenCount(usage.cacheMissTokens) })\n }\n }\n return rows\n})\n\n/** 生成中提示用户消息将进入队列 */\nconst placeholderText = computed(() => {\n if (props.running) return '会话进行中,发送的消息将进入待发送队列'\n return props.placeholder ?? '输入消息,Enter 发送,Shift + Enter 换行'\n})\n\nconst MAX_INPUT_HEIGHT = 160\n\n/** 多行自适应高度,上限 160px */\nconst autoResize = () => {\n const el = textareaRef.value\n if (!el) return\n el.style.height = 'auto'\n const nextHeight = Math.min(el.scrollHeight, MAX_INPUT_HEIGHT)\n el.style.height = `${nextHeight}px`\n el.style.overflowY = el.scrollHeight > MAX_INPUT_HEIGHT ? 'auto' : 'hidden'\n}\n\nwatch(placeholderText, () => {\n void nextTick(autoResize)\n})\n\nconst handleSend = (e?: Event) => {\n // 输入法组合态的回车只是确认上屏,不发送:Chromium 在 compositionend 前发\n // isComposing=true 的 keydown;Safari 在 compositionend 后补发 keyCode 229 的\n // keydown。不过滤会把组合前的旧输入提前发出(串消息),或上屏即误发送。\n if (e instanceof KeyboardEvent) {\n if (e.isComposing || e.keyCode === 229) return\n e.preventDefault()\n }\n if (!hasContent.value) return\n emit('send', text.value.trim(), attachments.value)\n text.value = ''\n attachments.value = []\n autoResize()\n}\n\n/** 取回内容到输入框(队列编辑场景),并聚焦 */\nconst setContent = (content: string) => {\n text.value = content\n void nextTick(() => {\n autoResize()\n textareaRef.value?.focus()\n })\n}\n\n/** 读取当前输入框内容 */\nconst getContent = () => text.value\n\ndefineExpose({ setContent, getContent })\n\n/** 将拾取的文件转为 dataUrl 附件(超限忽略) */\nconst handlePick = (files: File[]) => {\n const maxSize = props.maxAttachmentSize ?? 10 * 1024 * 1024\n\n for (const file of files) {\n if (file.size > maxSize) {\n console.warn(`[UAiChat] 附件 ${file.name} 超过大小限制,已忽略`)\n continue\n }\n const reader = new FileReader()\n reader.onload = () => {\n attachments.value.push({\n name: file.name,\n mimeType: file.type,\n size: file.size,\n dataUrl: String(reader.result)\n })\n }\n reader.readAsDataURL(file)\n }\n}\n</script>\n"],"mappings":"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"}
@@ -1,6 +1,5 @@
1
- //#region src/components/ai-chat/di.ts
1
+ // src/components/ai-chat/di.ts
2
2
  const AiChatDIKey = Symbol("AiChatDIKey");
3
- //#endregion
4
3
  export { AiChatDIKey };
5
4
 
6
5
  //# sourceMappingURL=di.js.map
@@ -1,4 +1,4 @@
1
- //#region src/components/ai-chat/format-duration.ts
1
+ // src/components/ai-chat/format-duration.ts
2
2
  /** 格式化工作时长:整秒显示,≥60s 显示 Xm Ys */
3
3
  const formatDuration = (ms) => {
4
4
  const seconds = Math.round(ms / 1e3);
@@ -7,7 +7,6 @@ const formatDuration = (ms) => {
7
7
  const rest = seconds % 60;
8
8
  return rest ? `${minutes}m ${rest}s` : `${minutes}m`;
9
9
  };
10
- //#endregion
11
10
  export { formatDuration };
12
11
 
13
12
  //# sourceMappingURL=format-duration.js.map
@@ -4,7 +4,7 @@ import { Fragment, createBlock, createCommentVNode, createElementBlock, createEl
4
4
  import { ArrowRight } from "@veltra/icons/normal";
5
5
  import { UIcon } from "@veltra/desktop";
6
6
  import { bem } from "@veltra/utils";
7
- //#region src/components/ai-chat/job-bar.vue
7
+ // src/components/ai-chat/job-bar.vue
8
8
  const _sfc_main = /*@__PURE__*/ defineComponent({
9
9
  name: "UAiChatJobBar",
10
10
  __name: "job-bar",
@@ -42,7 +42,6 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
42
42
  };
43
43
  }
44
44
  });
45
- //#endregion
46
45
  export { _sfc_main as default };
47
46
 
48
47
  //# sourceMappingURL=job-bar.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"job-bar.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref","_createElementVNode","_toDisplayString","_createVNode","_withCtx","_Fragment","_renderList","_createBlock","_resolveDynamicComponent","_createCommentVNode"],"sources":["../../../src/components/ai-chat/job-bar.vue"],"sourcesContent":["<template>\n <div v-if=\"jobs.length\" :class=\"cls.e('job-bar')\">\n <button type=\"button\" :class=\"cls.e('job-bar-head')\" @click=\"expanded = !expanded\">\n <span>作业 · {{ jobs.length }}</span>\n <UIcon :class=\"[cls.e('job-bar-chevron'), bem.is('expanded', expanded)]\">\n <ArrowRight />\n </UIcon>\n </button>\n <div v-if=\"expanded\" :class=\"cls.e('job-bar-list')\">\n <div v-for=\"job in jobs\" :key=\"job.id\" :class=\"cls.e('job-bar-item')\">\n <UIcon :class=\"cls.e('job-bar-icon')\">\n <component :is=\"resolveToolIcon(job.kind)\" />\n </UIcon>\n <span :class=\"[cls.e('job-bar-label'), job.status === 'running' && 'u-shine']\">\n {{ job.label }}\n </span>\n <span :class=\"[cls.e('job-bar-dot'), bem.is(job.status)]\" />\n </div>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UIcon } from '@veltra/desktop'\nimport { ArrowRight } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { inject, ref } from 'vue'\n\nimport type { ChatJob } from '../../chat/types'\nimport { AiChatDIKey } from './di'\nimport { resolveToolIcon } from './tool-icons'\n\ndefineOptions({ name: 'UAiChatJobBar' })\n\ndefineProps<{ jobs: ChatJob[] }>()\n\nconst expanded = ref(true)\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n</script>\n"],"mappings":";;;;;;;;;;;;EAoCA,MAAM,WAAW,IAAI,IAAI;EAGzB,MAAM,MADK,OAAO,WACN,CAAA,EAAI,OAAO,IAAI,SAAS;;GAtCvB,OAAA,QAAA,KAAK,UAAhBA,UAAA,GAAAC,mBAkBM,OAAA;;IAlBmB,OAAKC,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,SAAA,CAAA;GACnC,GAAA,CAAAC,mBAKS,UAAA;IALD,MAAK;IAAU,OAAKF,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA;IAAmB,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,SAAA,QAAQ,CAAI,SAAA;GACvE,GAAA,CAAAC,mBAAmC,QAAA,MAA7B,UAAKC,gBAAG,QAAA,KAAK,MAAM,GAAA,CAAA,GACzBC,YAEQH,MAAA,KAAA,GAAA,EAFA,OAAKD,eAAA,CAAGC,MAAA,GAAA,CAAG,CAAC,EAAC,iBAAA,GAAqBA,MAAA,GAAA,CAAG,CAAC,GAAE,YAAa,SAAA,KAAQ,CAAA,CAAA,EAAA,GAAA;IACnE,SAAAI,cAAc,CAAdD,YAAcH,MAAA,UAAA,CAAA,CAAA,CAAA;;GAGP,GAAA,GAAA,CAAA,OAAA,CAAA,CAAA,GAAA,CAAA,GAAA,SAAA,SAAXH,UAAA,GAAAC,mBAUM,OAAA;;IAVgB,OAAKC,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA;GAChC,GAAA,EAAAH,UAAA,IAAA,GAAAC,mBAQMO,UAAA,MAAAC,WARa,QAAA,OAAP,QAAG;IAAf,OAAAT,UAAA,GAAAC,mBAQM,OAAA;KARoB,KAAK,IAAI;KAAK,OAAKC,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA;;KAClDG,YAEQH,MAAA,KAAA,GAAA,EAFA,OAAKD,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA,EAAA,GAAA;MAClB,SAAAI,cAA6C,EAA7CP,UAAA,GAAAU,YAA6CC,wBAA7BR,MAAA,eAAA,CAAe,CAAC,IAAI,IAAI,CAAA,CAAA,EAAA,CAAA;;;KAE1CC,mBAEO,QAAA,EAFA,OAAKF,eAAA,CAAGC,MAAA,GAAA,CAAG,CAAC,EAAC,eAAA,GAAmB,IAAI,WAAM,aAAA,SAAA,CAAA,EAC5C,GAAAE,gBAAA,IAAI,KAAK,GAAA,CAAA;KAEdD,mBAA4D,QAAA,EAArD,OAAKF,eAAA,CAAGC,MAAA,GAAA,CAAG,CAAC,EAAC,aAAA,GAAiBA,MAAA,GAAA,CAAG,CAAC,GAAG,IAAI,MAAM,CAAA,CAAA,EAAA,GAAA,MAAA,CAAA"}
1
+ {"version":3,"file":"job-bar.js","names":["_openBlock","_createElementBlock","_normalizeClass","_unref","_createElementVNode","_toDisplayString","_createVNode","_withCtx","_Fragment","_renderList","_createBlock","_resolveDynamicComponent","_createCommentVNode"],"sources":["../../../src/components/ai-chat/job-bar.vue"],"sourcesContent":["<template>\n <div v-if=\"jobs.length\" :class=\"cls.e('job-bar')\">\n <button type=\"button\" :class=\"cls.e('job-bar-head')\" @click=\"expanded = !expanded\">\n <span>作业 · {{ jobs.length }}</span>\n <UIcon :class=\"[cls.e('job-bar-chevron'), bem.is('expanded', expanded)]\">\n <ArrowRight />\n </UIcon>\n </button>\n <div v-if=\"expanded\" :class=\"cls.e('job-bar-list')\">\n <div v-for=\"job in jobs\" :key=\"job.id\" :class=\"cls.e('job-bar-item')\">\n <UIcon :class=\"cls.e('job-bar-icon')\">\n <component :is=\"resolveToolIcon(job.kind)\" />\n </UIcon>\n <span :class=\"[cls.e('job-bar-label'), job.status === 'running' && 'u-shine']\">\n {{ job.label }}\n </span>\n <span :class=\"[cls.e('job-bar-dot'), bem.is(job.status)]\" />\n </div>\n </div>\n </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { UIcon } from '@veltra/desktop'\nimport { ArrowRight } from '@veltra/icons/normal'\nimport { bem } from '@veltra/utils'\nimport { inject, ref } from 'vue'\n\nimport type { ChatJob } from '../../chat/types'\nimport { AiChatDIKey } from './di'\nimport { resolveToolIcon } from './tool-icons'\n\ndefineOptions({ name: 'UAiChatJobBar' })\n\ndefineProps<{ jobs: ChatJob[] }>()\n\nconst expanded = ref(true)\n\nconst di = inject(AiChatDIKey)\nconst cls = di?.cls ?? bem('ai-chat')\n</script>\n"],"mappings":";;;;;;;;;;;;EAoCA,MAAM,WAAW,IAAI,IAAI;EAGzB,MAAM,MADK,OAAO,WACN,CAAA,EAAI,OAAO,IAAI,SAAS;;GAtCvB,OAAA,QAAA,KAAK,UAAhBA,UAAA,GAAAC,mBAkBM,OAAA;;IAlBmB,OAAKC,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,SAAA,CAAA;G,IACnCC,mBAKS,UAAA;IALD,MAAK;IAAU,OAAKF,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA;IAAmB,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,SAAA,QAAQ,CAAI,SAAA;G,IACvEC,mBAAmC,QAAA,MAA7B,UAAKC,gBAAG,QAAA,KAAK,MAAM,GAAA,CAAA,GACzBC,YAEQH,MAAA,KAAA,GAAA,EAFA,OAAKD,eAAA,CAAGC,MAAA,GAAA,CAAG,CAAC,EAAC,iBAAA,GAAqBA,MAAA,GAAA,CAAG,CAAC,GAAE,YAAa,SAAA,KAAQ,CAAA,CAAA,E;IACnE,SAAAI,cAAc,CAAdD,YAAcH,MAAA,UAAA,CAAA,C;;G,uBAGP,SAAA,SAAXH,UAAA,GAAAC,mBAUM,OAAA;;IAVgB,OAAKC,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA;G,KAChCH,UAAA,IAAA,GAAAC,mBAQMO,UAAA,MAAAC,WARa,QAAA,OAAP,QAAG;IAAf,OAAAT,UAAA,GAAAC,mBAQM,OAAA;KARoB,KAAK,IAAI;KAAK,OAAKC,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA;;KAClDG,YAEQH,MAAA,KAAA,GAAA,EAFA,OAAKD,eAAEC,MAAA,GAAA,CAAG,CAAC,EAAC,cAAA,CAAA,E;MAClB,SAAAI,cAA6C,EAA7CP,UAAA,GAAAU,YAA6CC,wBAA7BR,MAAA,eAAA,CAAe,CAAC,IAAI,IAAI,CAAA,CAAA,E;;;KAE1CC,mBAEO,QAAA,EAFA,OAAKF,eAAA,CAAGC,MAAA,GAAA,CAAG,CAAC,EAAC,eAAA,GAAmB,IAAI,WAAM,aAAA,SAAA,CAAA,EAC5C,GAAAE,gBAAA,IAAI,KAAK,GAAA,CAAA;KAEdD,mBAA4D,QAAA,EAArD,OAAKF,eAAA,CAAGC,MAAA,GAAA,CAAG,CAAC,EAAC,aAAA,GAAiBA,MAAA,GAAA,CAAG,CAAC,GAAG,IAAI,MAAM,CAAA,CAAA,E"}
@@ -6,7 +6,7 @@ import { ArrowRight, CircleCheck, Copy, InfoCircle, Loading, Refresh } from "@ve
6
6
  import { UButton, UIcon, UScroll, message } from "@veltra/desktop";
7
7
  import { bem } from "@veltra/utils";
8
8
  import MarkdownRender from "markstream-vue";
9
- //#region src/components/ai-chat/message-item.vue
9
+ // src/components/ai-chat/message-item.vue
10
10
  const _hoisted_1 = ["src", "alt"];
11
11
  const _sfc_main = /*@__PURE__*/ defineComponent({
12
12
  name: "UAiChatMessageItem",
@@ -206,7 +206,6 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
206
206
  };
207
207
  }
208
208
  });
209
- //#endregion
210
209
  export { _sfc_main as default };
211
210
 
212
211
  //# sourceMappingURL=message-item.js.map