@veltra/ai 2.2.0 → 2.2.2
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.
- package/dist/chat/fold.js +14 -2
- package/dist/chat/fold.js.map +1 -1
- package/dist/chat/session.d.ts +13 -6
- package/dist/chat/session.js.map +1 -1
- package/dist/chat/use-chat-session.js +3 -13
- package/dist/chat/use-chat-session.js.map +1 -1
- package/dist/components/ai-chat/ai-chat.js.map +1 -1
- package/dist/components/ai-chat/approval-banner.js.map +1 -1
- package/dist/components/ai-chat/ask-question.js.map +1 -1
- package/dist/components/ai-chat/chat-input.js.map +1 -1
- package/dist/components/ai-chat/job-bar.js.map +1 -1
- package/dist/components/ai-chat/message-item.js.map +1 -1
- package/dist/components/ai-chat/message-list.js +2 -2
- package/dist/components/ai-chat/message-list.js.map +1 -1
- package/dist/components/ai-chat/model-picker.js.map +1 -1
- package/dist/components/ai-chat/queue-list.js.map +1 -1
- package/dist/components/ai-chat/reasoning-picker.js.map +1 -1
- package/dist/components/ai-chat/side-panel.js.map +1 -1
- package/dist/components/ai-chat/tool-call.js.map +1 -1
- package/dist/components/ai-chat/tool-json.js.map +1 -1
- package/dist/components/ai-chat/turn-process.js.map +1 -1
- package/dist/components/ai-orb/ai-orb.js.map +1 -1
- package/package.json +3 -3
- package/src/chat/__test__/bedrock-harness-sim.test.ts +467 -0
- package/src/chat/__test__/fixtures/bedrock/harness.ts +735 -0
- package/src/chat/__test__/fixtures/bedrock/http.ts +117 -0
- package/src/chat/__test__/fixtures/bedrock/types.ts +15 -0
- package/src/chat/__test__/fold.test.ts +26 -1
- package/src/chat/__test__/session.test.ts +1 -1
- package/src/chat/__test__/use-chat.test.ts +6 -3
- package/src/chat/fold.ts +16 -1
- package/src/chat/session.ts +13 -6
- package/src/chat/transports/__test__/openai.test.ts +1 -1
- package/src/chat/use-chat-session.ts +4 -5
- package/src/components/ai-chat/__test__/ai-chat.test.ts +38 -1
- package/src/components/ai-chat/__test__/tool-icons.test.ts +1 -1
- package/src/components/ai-chat/message-list.vue +6 -5
- package/src/components/ai-orb/__test__/ai-orb.test.ts +1 -1
- package/src/tools/ask-question/__test__/ask-question.test.ts +1 -1
package/dist/chat/fold.js
CHANGED
|
@@ -124,15 +124,27 @@ function foldSessionEvent(state, event) {
|
|
|
124
124
|
});
|
|
125
125
|
break;
|
|
126
126
|
}
|
|
127
|
-
case "assistant/message":
|
|
127
|
+
case "assistant/message": {
|
|
128
|
+
const current = next.messages.find((m) => m.id === event.messageId);
|
|
129
|
+
if (event.history && current && current.status === "streaming") {
|
|
130
|
+
const longer = (a, b) => (b?.length ?? 0) > (a?.length ?? 0) ? b : a;
|
|
131
|
+
next.messages = patchMessage(next.messages, event.messageId, {
|
|
132
|
+
role: "assistant",
|
|
133
|
+
content: longer(current.content, event.content),
|
|
134
|
+
reasoning: longer(current.reasoning, event.reasoning),
|
|
135
|
+
toolCalls: event.toolCalls ?? current.toolCalls
|
|
136
|
+
});
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
128
139
|
next.messages = patchMessage(next.messages, event.messageId, {
|
|
129
140
|
role: "assistant",
|
|
130
141
|
status: "done",
|
|
131
142
|
content: event.content,
|
|
132
143
|
reasoning: event.reasoning,
|
|
133
|
-
toolCalls: event.toolCalls ??
|
|
144
|
+
toolCalls: event.toolCalls ?? current?.toolCalls
|
|
134
145
|
});
|
|
135
146
|
break;
|
|
147
|
+
}
|
|
136
148
|
case "tool/call": {
|
|
137
149
|
const call = {
|
|
138
150
|
id: event.callId,
|
package/dist/chat/fold.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fold.js","names":[],"sources":["../../src/chat/fold.ts"],"sourcesContent":["import type { AskQuestionItem } from '../tools'\nimport type { ChatSessionEvent } from './session'\nimport type { ChatJob, ChatMessage, ChatQueuedMessage, ChatTokenUsage, ChatToolCall } from './types'\n\nfunction eventSeq(event: ChatSessionEvent): number | undefined {\n return 'seq' in event ? event.seq : undefined\n}\n\nexport interface ChatPendingApproval {\n approvalId: string\n toolName: string\n callId?: string\n reason?: string\n rpcId: string\n}\n\n/** 历史回放与实时 onEvent 共用的折叠状态 */\nexport interface ChatFoldState {\n lastSeq: number | null\n messages: ChatMessage[]\n queue: ChatQueuedMessage[]\n jobs: ChatJob[]\n tokenUsage: ChatTokenUsage | null\n projections: Record<string, unknown>\n title: string | null\n running: boolean\n pendingApprovals: ChatPendingApproval[]\n pendingQuestion: { questions: AskQuestionItem[]; rpcId: string } | null\n error: { code: string; message: string } | null\n}\n\nexport function createFoldState(): ChatFoldState {\n return {\n lastSeq: null,\n messages: [],\n queue: [],\n jobs: [],\n tokenUsage: null,\n projections: {},\n title: null,\n running: false,\n pendingApprovals: [],\n pendingQuestion: null,\n error: null\n }\n}\n\nfunction mergeOptionalCount(a?: number, b?: number): number | undefined {\n if (a == null && b == null) return undefined\n return (a ?? 0) + (b ?? 0)\n}\n\nfunction addTokenUsage(a: ChatTokenUsage, b: ChatTokenUsage): ChatTokenUsage {\n const usage: ChatTokenUsage = {\n promptTokens: a.promptTokens + b.promptTokens,\n completionTokens: a.completionTokens + b.completionTokens,\n totalTokens: a.totalTokens + b.totalTokens\n }\n const cacheHitTokens = mergeOptionalCount(a.cacheHitTokens, b.cacheHitTokens)\n if (cacheHitTokens != null) usage.cacheHitTokens = cacheHitTokens\n const cacheMissTokens = mergeOptionalCount(a.cacheMissTokens, b.cacheMissTokens)\n if (cacheMissTokens != null) usage.cacheMissTokens = cacheMissTokens\n const cacheCreationTokens = mergeOptionalCount(a.cacheCreationTokens, b.cacheCreationTokens)\n if (cacheCreationTokens != null) usage.cacheCreationTokens = cacheCreationTokens\n return usage\n}\n\nfunction isTokenUsage(value: unknown): value is ChatTokenUsage {\n if (value == null || typeof value !== 'object') return false\n const usage = value as ChatTokenUsage\n return (\n typeof usage.promptTokens === 'number' &&\n typeof usage.completionTokens === 'number' &&\n typeof usage.totalTokens === 'number'\n )\n}\n\nfunction replaceAt<T>(list: T[], idx: number, item: T): T[] {\n const next = list.slice()\n next[idx] = item\n return next\n}\n\nfunction patchMessage(\n messages: ChatMessage[],\n id: string,\n patch: Partial<ChatMessage>\n): ChatMessage[] {\n const idx = messages.findIndex((m) => m.id === id)\n if (idx < 0) {\n return [...messages, { id, role: 'assistant', content: '', status: 'streaming', ...patch }]\n }\n return replaceAt(messages, idx, { ...messages[idx]!, ...patch })\n}\n\nfunction appendToolCall(messages: ChatMessage[], call: ChatToolCall): ChatMessage[] {\n const idx = messages.findLastIndex((m) => m.role === 'assistant')\n if (idx < 0) {\n return [\n ...messages,\n {\n id: `tool-host:${call.id}`,\n role: 'assistant',\n content: '',\n status: 'streaming',\n toolCalls: [call]\n }\n ]\n }\n const target = messages[idx]!\n return replaceAt(messages, idx, { ...target, toolCalls: [...(target.toolCalls ?? []), call] })\n}\n\nfunction updateToolCall(\n messages: ChatMessage[],\n callId: string,\n patch: Partial<ChatToolCall>\n): ChatMessage[] {\n const idx = messages.findIndex((m) => m.toolCalls?.some((c) => c.id === callId))\n if (idx < 0) return messages\n const msg = messages[idx]!\n const calls = (msg.toolCalls ?? []).slice()\n const callIdx = calls.findIndex((c) => c.id === callId)\n if (callIdx < 0) return messages\n calls[callIdx] = { ...calls[callIdx]!, ...patch }\n return replaceAt(messages, idx, { ...msg, toolCalls: calls })\n}\n\n/**\n * 纯函数 fold:历史回放与实时 onEvent 走同一套。\n * 后到的更小/重复 seq 不覆盖已应用事件。\n */\nexport function foldSessionEvent(state: ChatFoldState, event: ChatSessionEvent): ChatFoldState {\n const seq = eventSeq(event)\n if (seq != null && state.lastSeq != null && seq <= state.lastSeq) return state\n\n const next: ChatFoldState = {\n ...state,\n lastSeq: seq ?? state.lastSeq,\n projections: { ...state.projections }\n }\n\n switch (event.type) {\n case 'user/message': {\n if (next.messages.some((m) => m.id === event.messageId)) break\n const userMessage: ChatMessage = { id: event.messageId, role: 'user', content: event.content }\n if (event.attachments) userMessage.attachments = event.attachments\n next.messages = [...next.messages, userMessage]\n break\n }\n case 'assistant/chunk': {\n const current = next.messages.find((m) => m.id === event.messageId)\n next.messages = patchMessage(next.messages, event.messageId, {\n role: 'assistant',\n status: 'streaming',\n content: (current?.content ?? '') + event.delta,\n reasoning: event.reasoningDelta\n ? (current?.reasoning ?? '') + event.reasoningDelta\n : current?.reasoning\n })\n break\n }\n case 'assistant/message': {\n next.messages = patchMessage(next.messages, event.messageId, {\n role: 'assistant',\n status: 'done',\n content: event.content,\n reasoning: event.reasoning,\n toolCalls: event.toolCalls ?? next.messages.find((m) => m.id === event.messageId)?.toolCalls\n })\n break\n }\n case 'tool/call': {\n const call: ChatToolCall = {\n id: event.callId,\n name: event.name,\n arguments: event.arguments,\n status: 'pending'\n }\n if (event.view !== undefined) call.view = event.view\n next.messages = appendToolCall(next.messages, call)\n break\n }\n case 'tool/result': {\n const patch: Partial<ChatToolCall> = { status: event.status }\n if (event.result !== undefined) patch.result = event.result\n if (event.error !== undefined) patch.error = event.error\n if (event.view !== undefined) patch.view = event.view\n next.messages = updateToolCall(next.messages, event.callId, patch)\n break\n }\n case 'queue/snapshot': {\n next.queue = event.items.slice()\n break\n }\n case 'jobs/snapshot': {\n next.jobs = event.jobs.slice()\n break\n }\n case 'projection': {\n if (event.key === 'tokenUsage' && isTokenUsage(event.value)) {\n next.tokenUsage = next.tokenUsage\n ? addTokenUsage(next.tokenUsage, event.value)\n : event.value\n } else if (event.key === 'title' && typeof event.value === 'string') {\n next.title = event.value\n } else {\n next.projections[event.key] = event.value\n }\n break\n }\n case 'running': {\n next.running = event.running\n break\n }\n case 'finish': {\n next.running = false\n break\n }\n case 'error': {\n next.running = false\n next.error = { code: event.code, message: event.message }\n break\n }\n case 'approval/requested': {\n if (event.callId) {\n next.messages = updateToolCall(next.messages, event.callId, { status: 'awaiting-confirm' })\n }\n next.pendingApprovals = [\n ...next.pendingApprovals,\n {\n approvalId: event.approvalId,\n toolName: event.toolName,\n callId: event.callId,\n reason: event.reason,\n rpcId: event.rpcId\n }\n ]\n break\n }\n case 'approval/resolved': {\n const hit = next.pendingApprovals.find((a) => a.approvalId === event.approvalId)\n next.pendingApprovals = next.pendingApprovals.filter((a) => a.approvalId !== event.approvalId)\n if (hit?.callId && event.outcome === 'rejected') {\n next.messages = updateToolCall(next.messages, hit.callId, { status: 'rejected' })\n }\n break\n }\n case 'question/requested': {\n next.pendingQuestion = { questions: event.questions, rpcId: event.rpcId }\n break\n }\n case 'question/resolved': {\n if (next.pendingQuestion?.rpcId === event.questionRpcId) next.pendingQuestion = null\n break\n }\n }\n\n return next\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"fold.js","names":[],"sources":["../../src/chat/fold.ts"],"sourcesContent":["import type { AskQuestionItem } from '../tools'\nimport type { ChatSessionEvent } from './session'\nimport type { ChatJob, ChatMessage, ChatQueuedMessage, ChatTokenUsage, ChatToolCall } from './types'\n\nfunction eventSeq(event: ChatSessionEvent): number | undefined {\n return 'seq' in event ? event.seq : undefined\n}\n\nexport interface ChatPendingApproval {\n approvalId: string\n toolName: string\n callId?: string\n reason?: string\n rpcId: string\n}\n\n/** 历史回放与实时 onEvent 共用的折叠状态 */\nexport interface ChatFoldState {\n lastSeq: number | null\n messages: ChatMessage[]\n queue: ChatQueuedMessage[]\n jobs: ChatJob[]\n tokenUsage: ChatTokenUsage | null\n projections: Record<string, unknown>\n title: string | null\n running: boolean\n pendingApprovals: ChatPendingApproval[]\n pendingQuestion: { questions: AskQuestionItem[]; rpcId: string } | null\n error: { code: string; message: string } | null\n}\n\nexport function createFoldState(): ChatFoldState {\n return {\n lastSeq: null,\n messages: [],\n queue: [],\n jobs: [],\n tokenUsage: null,\n projections: {},\n title: null,\n running: false,\n pendingApprovals: [],\n pendingQuestion: null,\n error: null\n }\n}\n\nfunction mergeOptionalCount(a?: number, b?: number): number | undefined {\n if (a == null && b == null) return undefined\n return (a ?? 0) + (b ?? 0)\n}\n\nfunction addTokenUsage(a: ChatTokenUsage, b: ChatTokenUsage): ChatTokenUsage {\n const usage: ChatTokenUsage = {\n promptTokens: a.promptTokens + b.promptTokens,\n completionTokens: a.completionTokens + b.completionTokens,\n totalTokens: a.totalTokens + b.totalTokens\n }\n const cacheHitTokens = mergeOptionalCount(a.cacheHitTokens, b.cacheHitTokens)\n if (cacheHitTokens != null) usage.cacheHitTokens = cacheHitTokens\n const cacheMissTokens = mergeOptionalCount(a.cacheMissTokens, b.cacheMissTokens)\n if (cacheMissTokens != null) usage.cacheMissTokens = cacheMissTokens\n const cacheCreationTokens = mergeOptionalCount(a.cacheCreationTokens, b.cacheCreationTokens)\n if (cacheCreationTokens != null) usage.cacheCreationTokens = cacheCreationTokens\n return usage\n}\n\nfunction isTokenUsage(value: unknown): value is ChatTokenUsage {\n if (value == null || typeof value !== 'object') return false\n const usage = value as ChatTokenUsage\n return (\n typeof usage.promptTokens === 'number' &&\n typeof usage.completionTokens === 'number' &&\n typeof usage.totalTokens === 'number'\n )\n}\n\nfunction replaceAt<T>(list: T[], idx: number, item: T): T[] {\n const next = list.slice()\n next[idx] = item\n return next\n}\n\nfunction patchMessage(\n messages: ChatMessage[],\n id: string,\n patch: Partial<ChatMessage>\n): ChatMessage[] {\n const idx = messages.findIndex((m) => m.id === id)\n if (idx < 0) {\n return [...messages, { id, role: 'assistant', content: '', status: 'streaming', ...patch }]\n }\n return replaceAt(messages, idx, { ...messages[idx]!, ...patch })\n}\n\nfunction appendToolCall(messages: ChatMessage[], call: ChatToolCall): ChatMessage[] {\n const idx = messages.findLastIndex((m) => m.role === 'assistant')\n if (idx < 0) {\n return [\n ...messages,\n {\n id: `tool-host:${call.id}`,\n role: 'assistant',\n content: '',\n status: 'streaming',\n toolCalls: [call]\n }\n ]\n }\n const target = messages[idx]!\n return replaceAt(messages, idx, { ...target, toolCalls: [...(target.toolCalls ?? []), call] })\n}\n\nfunction updateToolCall(\n messages: ChatMessage[],\n callId: string,\n patch: Partial<ChatToolCall>\n): ChatMessage[] {\n const idx = messages.findIndex((m) => m.toolCalls?.some((c) => c.id === callId))\n if (idx < 0) return messages\n const msg = messages[idx]!\n const calls = (msg.toolCalls ?? []).slice()\n const callIdx = calls.findIndex((c) => c.id === callId)\n if (callIdx < 0) return messages\n calls[callIdx] = { ...calls[callIdx]!, ...patch }\n return replaceAt(messages, idx, { ...msg, toolCalls: calls })\n}\n\n/**\n * 纯函数 fold:历史回放与实时 onEvent 走同一套。\n * 后到的更小/重复 seq 不覆盖已应用事件。\n */\nexport function foldSessionEvent(state: ChatFoldState, event: ChatSessionEvent): ChatFoldState {\n const seq = eventSeq(event)\n if (seq != null && state.lastSeq != null && seq <= state.lastSeq) return state\n\n const next: ChatFoldState = {\n ...state,\n lastSeq: seq ?? state.lastSeq,\n projections: { ...state.projections }\n }\n\n switch (event.type) {\n case 'user/message': {\n if (next.messages.some((m) => m.id === event.messageId)) break\n const userMessage: ChatMessage = { id: event.messageId, role: 'user', content: event.content }\n if (event.attachments) userMessage.attachments = event.attachments\n next.messages = [...next.messages, userMessage]\n break\n }\n case 'assistant/chunk': {\n const current = next.messages.find((m) => m.id === event.messageId)\n next.messages = patchMessage(next.messages, event.messageId, {\n role: 'assistant',\n status: 'streaming',\n content: (current?.content ?? '') + event.delta,\n reasoning: event.reasoningDelta\n ? (current?.reasoning ?? '') + event.reasoningDelta\n : current?.reasoning\n })\n break\n }\n case 'assistant/message': {\n const current = next.messages.find((m) => m.id === event.messageId)\n if (event.history && current && current.status === 'streaming') {\n // 历史快照命中直播流式中的消息:快照可能落后(甚至为空),不能把消息\n // 降级为 done 或用更短内容覆盖 —— 否则断线补拉会让消息在「完成 / 思考中」\n // 之间来回翻转;终态由直播的 durable 帧(message_text / step 收尾)落定。\n const longer = (a: string | undefined, b: string | undefined) =>\n (b?.length ?? 0) > (a?.length ?? 0) ? b : a\n next.messages = patchMessage(next.messages, event.messageId, {\n role: 'assistant',\n content: longer(current.content, event.content),\n reasoning: longer(current.reasoning, event.reasoning),\n toolCalls: event.toolCalls ?? current.toolCalls\n })\n break\n }\n next.messages = patchMessage(next.messages, event.messageId, {\n role: 'assistant',\n status: 'done',\n content: event.content,\n reasoning: event.reasoning,\n toolCalls: event.toolCalls ?? current?.toolCalls\n })\n break\n }\n case 'tool/call': {\n const call: ChatToolCall = {\n id: event.callId,\n name: event.name,\n arguments: event.arguments,\n status: 'pending'\n }\n if (event.view !== undefined) call.view = event.view\n next.messages = appendToolCall(next.messages, call)\n break\n }\n case 'tool/result': {\n const patch: Partial<ChatToolCall> = { status: event.status }\n if (event.result !== undefined) patch.result = event.result\n if (event.error !== undefined) patch.error = event.error\n if (event.view !== undefined) patch.view = event.view\n next.messages = updateToolCall(next.messages, event.callId, patch)\n break\n }\n case 'queue/snapshot': {\n next.queue = event.items.slice()\n break\n }\n case 'jobs/snapshot': {\n next.jobs = event.jobs.slice()\n break\n }\n case 'projection': {\n if (event.key === 'tokenUsage' && isTokenUsage(event.value)) {\n next.tokenUsage = next.tokenUsage\n ? addTokenUsage(next.tokenUsage, event.value)\n : event.value\n } else if (event.key === 'title' && typeof event.value === 'string') {\n next.title = event.value\n } else {\n next.projections[event.key] = event.value\n }\n break\n }\n case 'running': {\n next.running = event.running\n break\n }\n case 'finish': {\n next.running = false\n break\n }\n case 'error': {\n next.running = false\n next.error = { code: event.code, message: event.message }\n break\n }\n case 'approval/requested': {\n if (event.callId) {\n next.messages = updateToolCall(next.messages, event.callId, { status: 'awaiting-confirm' })\n }\n next.pendingApprovals = [\n ...next.pendingApprovals,\n {\n approvalId: event.approvalId,\n toolName: event.toolName,\n callId: event.callId,\n reason: event.reason,\n rpcId: event.rpcId\n }\n ]\n break\n }\n case 'approval/resolved': {\n const hit = next.pendingApprovals.find((a) => a.approvalId === event.approvalId)\n next.pendingApprovals = next.pendingApprovals.filter((a) => a.approvalId !== event.approvalId)\n if (hit?.callId && event.outcome === 'rejected') {\n next.messages = updateToolCall(next.messages, hit.callId, { status: 'rejected' })\n }\n break\n }\n case 'question/requested': {\n next.pendingQuestion = { questions: event.questions, rpcId: event.rpcId }\n break\n }\n case 'question/resolved': {\n if (next.pendingQuestion?.rpcId === event.questionRpcId) next.pendingQuestion = null\n break\n }\n }\n\n return next\n}\n"],"mappings":"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"}
|
package/dist/chat/session.d.ts
CHANGED
|
@@ -2,32 +2,39 @@ import { ChatAttachment, ChatJob, ChatQueuedMessage, ChatToolCall } from "./type
|
|
|
2
2
|
import { AskQuestionItem } from "../tools/ask-question/ask-question.js";
|
|
3
3
|
import "../tools/index.js";
|
|
4
4
|
//#region src/chat/session.d.ts
|
|
5
|
-
/**
|
|
5
|
+
/** 归一化后的服务端会话事件(协议无关)。
|
|
6
|
+
* seq 为持久帧序号:瞬态/历史回放事件省略(不参与乱序门),durable 终态事件携带。 */
|
|
6
7
|
export type ChatSessionEvent = {
|
|
7
8
|
type: 'user/message';
|
|
8
9
|
messageId: string;
|
|
9
|
-
seq
|
|
10
|
+
seq?: number;
|
|
10
11
|
content: string;
|
|
11
12
|
attachments?: ChatAttachment[];
|
|
12
13
|
} | {
|
|
13
14
|
type: 'assistant/chunk';
|
|
14
15
|
messageId: string;
|
|
15
|
-
seq
|
|
16
|
+
seq?: number;
|
|
16
17
|
delta: string;
|
|
17
18
|
reasoningDelta?: string;
|
|
18
19
|
} | {
|
|
19
20
|
type: 'assistant/message';
|
|
20
21
|
messageId: string;
|
|
21
|
-
seq
|
|
22
|
+
seq?: number;
|
|
22
23
|
content: string;
|
|
23
24
|
reasoning?: string;
|
|
24
25
|
toolCalls?: ChatToolCall[];
|
|
26
|
+
/**
|
|
27
|
+
* 历史回放(REST 拉取/断线补拉)来源标记。
|
|
28
|
+
* 历史快照可能落后于直播流:fold 对流式中的消息不降级为 done、不用更短的
|
|
29
|
+
* 快照覆盖内容;直播(durable 终态帧)始终是状态权威。
|
|
30
|
+
*/
|
|
31
|
+
history?: boolean;
|
|
25
32
|
} | {
|
|
26
33
|
type: 'tool/call';
|
|
27
34
|
callId: string;
|
|
28
35
|
name: string;
|
|
29
36
|
arguments: string;
|
|
30
|
-
seq
|
|
37
|
+
seq?: number;
|
|
31
38
|
view?: unknown;
|
|
32
39
|
} | {
|
|
33
40
|
type: 'tool/result';
|
|
@@ -35,7 +42,7 @@ export type ChatSessionEvent = {
|
|
|
35
42
|
status: 'success' | 'error' | 'rejected';
|
|
36
43
|
result?: string;
|
|
37
44
|
error?: string;
|
|
38
|
-
seq
|
|
45
|
+
seq?: number;
|
|
39
46
|
view?: unknown;
|
|
40
47
|
} | {
|
|
41
48
|
type: 'approval/requested';
|
package/dist/chat/session.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","names":[],"sources":["../../src/chat/session.ts"],"sourcesContent":["import type { AskQuestionItem } from '../tools'\nimport type { ChatAttachment, ChatJob, ChatQueuedMessage, ChatToolCall } from './types'\n\n/**
|
|
1
|
+
{"version":3,"file":"session.js","names":[],"sources":["../../src/chat/session.ts"],"sourcesContent":["import type { AskQuestionItem } from '../tools'\nimport type { ChatAttachment, ChatJob, ChatQueuedMessage, ChatToolCall } from './types'\n\n/** 归一化后的服务端会话事件(协议无关)。\n * seq 为持久帧序号:瞬态/历史回放事件省略(不参与乱序门),durable 终态事件携带。 */\nexport type ChatSessionEvent =\n | {\n type: 'user/message'\n messageId: string\n seq?: number\n content: string\n attachments?: ChatAttachment[]\n }\n | {\n type: 'assistant/chunk'\n messageId: string\n seq?: number\n delta: string\n reasoningDelta?: string\n }\n | {\n type: 'assistant/message'\n messageId: string\n seq?: number\n content: string\n reasoning?: string\n toolCalls?: ChatToolCall[]\n /**\n * 历史回放(REST 拉取/断线补拉)来源标记。\n * 历史快照可能落后于直播流:fold 对流式中的消息不降级为 done、不用更短的\n * 快照覆盖内容;直播(durable 终态帧)始终是状态权威。\n */\n history?: boolean\n }\n | {\n type: 'tool/call'\n callId: string\n name: string\n arguments: string\n seq?: number\n view?: unknown\n }\n | {\n type: 'tool/result'\n callId: string\n status: 'success' | 'error' | 'rejected'\n result?: string\n error?: string\n seq?: number\n view?: unknown\n }\n | {\n type: 'approval/requested'\n approvalId: string\n toolName: string\n callId?: string\n reason?: string\n rpcId: string\n }\n | { type: 'approval/resolved'; approvalId: string; outcome: string }\n | { type: 'question/requested'; questions: AskQuestionItem[]; rpcId: string }\n | { type: 'question/resolved'; questionRpcId: string; outcome: 'answered' | 'cancelled' }\n | { type: 'queue/snapshot'; items: ChatQueuedMessage[] }\n | { type: 'jobs/snapshot'; jobs: ChatJob[] }\n | { type: 'projection'; key: string; value: unknown; seq: number }\n | { type: 'running'; running: boolean }\n | { type: 'finish' }\n | { type: 'error'; code: string; message: string }\n\n/** 服务端会话 transport:对象形态,与函数型 ChatTransport 互斥 */\nexport interface ChatSessionTransport {\n readonly kind: 'session'\n open(handlers: { onEvent(event: ChatSessionEvent): void; onDisconnect?(): void }): () => void\n send(content: string, attachments?: ChatAttachment[]): Promise<void>\n cancel(): Promise<void>\n respond(rpcId: string, ok: boolean, value?: unknown): Promise<void>\n fetchHistory(beforeSeq?: number): Promise<{ events: ChatSessionEvent[]; hasMore: boolean }>\n selectModel(provider: string, model: string): Promise<void>\n}\n\n/**\n * 协议翻译层:订阅事件流、把动作打到远端、拉历史。\n * 时序校验 / dispose / 断线补拉 / in-flight 去重由 createServerTransport 完成。\n */\nexport interface ChatSessionAdapter {\n subscribe(handlers: { onEvent(event: ChatSessionEvent): void; onDisconnect?(): void }): () => void\n send(content: string, attachments?: ChatAttachment[]): Promise<void>\n cancel(): Promise<void>\n respond(rpcId: string, ok: boolean, value?: unknown): Promise<void>\n fetchHistory(beforeSeq?: number): Promise<{ events: ChatSessionEvent[]; hasMore: boolean }>\n selectModel(provider: string, model: string): Promise<void>\n}\n\nfunction eventSeq(event: ChatSessionEvent): number | undefined {\n return 'seq' in event ? event.seq : undefined\n}\n\n/** 仅带 kind: 'session' 的对象为 true;函数 / null / 无 kind 均为 false */\nexport function isServerTransport(t: unknown): t is ChatSessionTransport {\n return typeof t === 'object' && t !== null && (t as { kind?: unknown }).kind === 'session'\n}\n\nfunction runExclusive(gate: { busy: boolean }, action: () => Promise<void>): Promise<void> {\n if (gate.busy) return Promise.resolve()\n gate.busy = true\n return action().finally(() => {\n gate.busy = false\n })\n}\n\n/**\n * 用 adapter 包出 ChatSessionTransport。\n * 包内完成 seq 校验、disposer、断线补拉、动作 in-flight 去重。\n */\nexport function createServerTransport(adapter: ChatSessionAdapter): ChatSessionTransport {\n let lastSeq: number | undefined\n const sendGate = { busy: false }\n const cancelGate = { busy: false }\n const respondGate = { busy: false }\n const selectModelGate = { busy: false }\n\n const deliver = (event: ChatSessionEvent, onEvent: (e: ChatSessionEvent) => void) => {\n const seq = eventSeq(event)\n if (seq != null && lastSeq != null && seq <= lastSeq) {\n console.warn(`[ChatSessionTransport] 丢弃乱序事件 seq=${seq} lastSeq=${lastSeq}`)\n return\n }\n if (seq != null) lastSeq = seq\n onEvent(event)\n }\n\n return {\n kind: 'session',\n open(handlers) {\n let disposed = false\n const unsubscribe = adapter.subscribe({\n onEvent(event) {\n if (!disposed) deliver(event, handlers.onEvent)\n },\n onDisconnect() {\n if (disposed) return\n void adapter.fetchHistory(lastSeq).then(\n ({ events }) => {\n if (disposed) return\n for (const event of events) deliver(event, handlers.onEvent)\n handlers.onDisconnect?.()\n },\n () => {\n if (!disposed) handlers.onDisconnect?.()\n }\n )\n }\n })\n return () => {\n disposed = true\n unsubscribe()\n }\n },\n send(content, attachments) {\n return runExclusive(sendGate, () => adapter.send(content, attachments))\n },\n cancel() {\n return runExclusive(cancelGate, () => adapter.cancel())\n },\n respond(rpcId, ok, value) {\n return runExclusive(respondGate, () => adapter.respond(rpcId, ok, value))\n },\n fetchHistory(beforeSeq) {\n return adapter.fetchHistory(beforeSeq)\n },\n selectModel(provider, model) {\n return runExclusive(selectModelGate, () => adapter.selectModel(provider, model))\n }\n }\n}\n"],"mappings":";AA6FA,SAAS,SAAS,OAA6C;CAC7D,OAAO,SAAS,QAAQ,MAAM,MAAM,KAAA;AACtC;;AAGA,SAAgB,kBAAkB,GAAuC;CACvE,OAAO,OAAO,MAAM,YAAY,MAAM,QAAS,EAAyB,SAAS;AACnF;AAEA,SAAS,aAAa,MAAyB,QAA4C;CACzF,IAAI,KAAK,MAAM,OAAO,QAAQ,QAAQ;CACtC,KAAK,OAAO;CACZ,OAAO,OAAO,CAAC,CAAC,cAAc;EAC5B,KAAK,OAAO;CACd,CAAC;AACH;;;;;AAMA,SAAgB,sBAAsB,SAAmD;CACvF,IAAI;CACJ,MAAM,WAAW,EAAE,MAAM,MAAM;CAC/B,MAAM,aAAa,EAAE,MAAM,MAAM;CACjC,MAAM,cAAc,EAAE,MAAM,MAAM;CAClC,MAAM,kBAAkB,EAAE,MAAM,MAAM;CAEtC,MAAM,WAAW,OAAyB,YAA2C;EACnF,MAAM,MAAM,SAAS,KAAK;EAC1B,IAAI,OAAO,QAAQ,WAAW,QAAQ,OAAO,SAAS;GACpD,QAAQ,KAAK,qCAAqC,IAAI,WAAW,SAAS;GAC1E;EACF;EACA,IAAI,OAAO,MAAM,UAAU;EAC3B,QAAQ,KAAK;CACf;CAEA,OAAO;EACL,MAAM;EACN,KAAK,UAAU;GACb,IAAI,WAAW;GACf,MAAM,cAAc,QAAQ,UAAU;IACpC,QAAQ,OAAO;KACb,IAAI,CAAC,UAAU,QAAQ,OAAO,SAAS,OAAO;IAChD;IACA,eAAe;KACb,IAAI,UAAU;KACd,QAAa,aAAa,OAAO,CAAC,CAAC,MAChC,EAAE,aAAa;MACd,IAAI,UAAU;MACd,KAAK,MAAM,SAAS,QAAQ,QAAQ,OAAO,SAAS,OAAO;MAC3D,SAAS,eAAe;KAC1B,SACM;MACJ,IAAI,CAAC,UAAU,SAAS,eAAe;KACzC,CACF;IACF;GACF,CAAC;GACD,aAAa;IACX,WAAW;IACX,YAAY;GACd;EACF;EACA,KAAK,SAAS,aAAa;GACzB,OAAO,aAAa,gBAAgB,QAAQ,KAAK,SAAS,WAAW,CAAC;EACxE;EACA,SAAS;GACP,OAAO,aAAa,kBAAkB,QAAQ,OAAO,CAAC;EACxD;EACA,QAAQ,OAAO,IAAI,OAAO;GACxB,OAAO,aAAa,mBAAmB,QAAQ,QAAQ,OAAO,IAAI,KAAK,CAAC;EAC1E;EACA,aAAa,WAAW;GACtB,OAAO,QAAQ,aAAa,SAAS;EACvC;EACA,YAAY,UAAU,OAAO;GAC3B,OAAO,aAAa,uBAAuB,QAAQ,YAAY,UAAU,KAAK,CAAC;EACjF;CACF;AACF"}
|
|
@@ -32,19 +32,9 @@ function createSessionRuntime(surface) {
|
|
|
32
32
|
fold = createFoldState();
|
|
33
33
|
syncFold(fold, surface);
|
|
34
34
|
let disposed = false;
|
|
35
|
-
const dispose = transport.open({
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
onDisconnect() {
|
|
40
|
-
if (disposed) return;
|
|
41
|
-
fold = {
|
|
42
|
-
...fold,
|
|
43
|
-
running: false
|
|
44
|
-
};
|
|
45
|
-
surface.running.value = false;
|
|
46
|
-
}
|
|
47
|
-
});
|
|
35
|
+
const dispose = transport.open({ onEvent(event) {
|
|
36
|
+
if (!disposed) apply(event);
|
|
37
|
+
} });
|
|
48
38
|
opened = true;
|
|
49
39
|
onCleanup(() => {
|
|
50
40
|
disposed = true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-chat-session.js","names":[],"sources":["../../src/chat/use-chat-session.ts"],"sourcesContent":["import type { Ref } from 'vue'\n\nimport type { AskQuestionItem } from '../tools'\nimport {\n createFoldState,\n foldSessionEvent,\n type ChatFoldState,\n type ChatPendingApproval\n} from './fold'\nimport type { ChatSessionEvent, ChatSessionTransport } from './session'\nimport type { ChatJob, ChatMessage, ChatQueuedMessage, ChatTokenUsage } from './types'\n\nexport interface SessionChatSurface {\n messages: Ref<ChatMessage[]>\n queue: Ref<ChatQueuedMessage[]>\n jobs: Ref<ChatJob[]>\n tokenUsage: Ref<ChatTokenUsage | null>\n projections: Ref<Record<string, unknown>>\n title: Ref<string | null>\n running: Ref<boolean>\n pendingApprovals: Ref<ChatPendingApproval[]>\n pendingQuestion: Ref<{ questions: AskQuestionItem[]; rpcId: string } | null>\n}\n\n/** 把 fold 状态同步到 useChat 的渲染面 */\nfunction syncFold(fold: ChatFoldState, surface: SessionChatSurface) {\n surface.messages.value = fold.messages\n surface.queue.value = fold.queue\n surface.jobs.value = fold.jobs\n surface.tokenUsage.value = fold.tokenUsage\n surface.projections.value = fold.projections\n surface.title.value = fold.title\n surface.running.value = fold.running\n surface.pendingApprovals.value = fold.pendingApprovals\n surface.pendingQuestion.value = fold.pendingQuestion\n}\n\n/**\n * session transport 的 open / fetchHistory / fold。\n * 历史回放与实时 onEvent 走同一套 fold。\n */\nexport function createSessionRuntime(surface: SessionChatSurface) {\n let fold = createFoldState()\n let opened = false\n\n const apply = (event: ChatSessionEvent) => {\n fold = foldSessionEvent(fold, event)\n syncFold(fold, surface)\n }\n\n return {\n get opened() {\n return opened\n },\n attach(transport: ChatSessionTransport, onCleanup: (fn: () => void) => void) {\n // isServerTransport 只认 kind,不保证 open 存在\n if (typeof transport.open !== 'function') return\n fold = createFoldState()\n syncFold(fold, surface)\n let disposed = false\n const dispose = transport.open({\n onEvent(event) {\n if (!disposed) apply(event)\n }
|
|
1
|
+
{"version":3,"file":"use-chat-session.js","names":[],"sources":["../../src/chat/use-chat-session.ts"],"sourcesContent":["import type { Ref } from 'vue'\n\nimport type { AskQuestionItem } from '../tools'\nimport {\n createFoldState,\n foldSessionEvent,\n type ChatFoldState,\n type ChatPendingApproval\n} from './fold'\nimport type { ChatSessionEvent, ChatSessionTransport } from './session'\nimport type { ChatJob, ChatMessage, ChatQueuedMessage, ChatTokenUsage } from './types'\n\nexport interface SessionChatSurface {\n messages: Ref<ChatMessage[]>\n queue: Ref<ChatQueuedMessage[]>\n jobs: Ref<ChatJob[]>\n tokenUsage: Ref<ChatTokenUsage | null>\n projections: Ref<Record<string, unknown>>\n title: Ref<string | null>\n running: Ref<boolean>\n pendingApprovals: Ref<ChatPendingApproval[]>\n pendingQuestion: Ref<{ questions: AskQuestionItem[]; rpcId: string } | null>\n}\n\n/** 把 fold 状态同步到 useChat 的渲染面 */\nfunction syncFold(fold: ChatFoldState, surface: SessionChatSurface) {\n surface.messages.value = fold.messages\n surface.queue.value = fold.queue\n surface.jobs.value = fold.jobs\n surface.tokenUsage.value = fold.tokenUsage\n surface.projections.value = fold.projections\n surface.title.value = fold.title\n surface.running.value = fold.running\n surface.pendingApprovals.value = fold.pendingApprovals\n surface.pendingQuestion.value = fold.pendingQuestion\n}\n\n/**\n * session transport 的 open / fetchHistory / fold。\n * 历史回放与实时 onEvent 走同一套 fold。\n */\nexport function createSessionRuntime(surface: SessionChatSurface) {\n let fold = createFoldState()\n let opened = false\n\n const apply = (event: ChatSessionEvent) => {\n fold = foldSessionEvent(fold, event)\n syncFold(fold, surface)\n }\n\n return {\n get opened() {\n return opened\n },\n attach(transport: ChatSessionTransport, onCleanup: (fn: () => void) => void) {\n // isServerTransport 只认 kind,不保证 open 存在\n if (typeof transport.open !== 'function') return\n fold = createFoldState()\n syncFold(fold, surface)\n let disposed = false\n // 断线 ≠ 会话停止:不注册 onDisconnect 把 running 置 false(保持乐观,由\n // 重连回放的 prompted / step_started / idle 帧收敛)。曾在此置 false ——\n // 步骤间隙断线会把过程块折叠成「已完成」,重连随即又展开,消息列表在\n // 「完成 / 思考中」之间来回跳。\n const dispose = transport.open({\n onEvent(event) {\n if (!disposed) apply(event)\n }\n })\n opened = true\n onCleanup(() => {\n disposed = true\n opened = false\n dispose()\n })\n void transport.fetchHistory().then(({ events }) => {\n if (disposed) return\n for (const event of events) apply(event)\n })\n },\n resetLocal() {\n fold = createFoldState()\n syncFold(fold, surface)\n }\n }\n}\n"],"mappings":";;;AAyBA,SAAS,SAAS,MAAqB,SAA6B;CAClE,QAAQ,SAAS,QAAQ,KAAK;CAC9B,QAAQ,MAAM,QAAQ,KAAK;CAC3B,QAAQ,KAAK,QAAQ,KAAK;CAC1B,QAAQ,WAAW,QAAQ,KAAK;CAChC,QAAQ,YAAY,QAAQ,KAAK;CACjC,QAAQ,MAAM,QAAQ,KAAK;CAC3B,QAAQ,QAAQ,QAAQ,KAAK;CAC7B,QAAQ,iBAAiB,QAAQ,KAAK;CACtC,QAAQ,gBAAgB,QAAQ,KAAK;AACvC;;;;;AAMA,SAAgB,qBAAqB,SAA6B;CAChE,IAAI,OAAO,gBAAgB;CAC3B,IAAI,SAAS;CAEb,MAAM,SAAS,UAA4B;EACzC,OAAO,iBAAiB,MAAM,KAAK;EACnC,SAAS,MAAM,OAAO;CACxB;CAEA,OAAO;EACL,IAAI,SAAS;GACX,OAAO;EACT;EACA,OAAO,WAAiC,WAAqC;GAE3E,IAAI,OAAO,UAAU,SAAS,YAAY;GAC1C,OAAO,gBAAgB;GACvB,SAAS,MAAM,OAAO;GACtB,IAAI,WAAW;GAKf,MAAM,UAAU,UAAU,KAAK,EAC7B,QAAQ,OAAO;IACb,IAAI,CAAC,UAAU,MAAM,KAAK;GAC5B,EACF,CAAC;GACD,SAAS;GACT,gBAAgB;IACd,WAAW;IACX,SAAS;IACT,QAAQ;GACV,CAAC;GACD,UAAe,aAAa,CAAC,CAAC,MAAM,EAAE,aAAa;IACjD,IAAI,UAAU;IACd,KAAK,MAAM,SAAS,QAAQ,MAAM,KAAK;GACzC,CAAC;EACH;EACA,aAAa;GACX,OAAO,gBAAgB;GACvB,SAAS,MAAM,OAAO;EACxB;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-chat.js","names":["$slots"],"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":";;;;;;;;;;;;;;;;AAqNA,MAAM,kBAAkB;AACxB,MAAM,iBAAiB;;AAEvB,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAxH3B,MAAM,QAAQ;EAEd,MAAM,OAAO;EASb,MAAM,MAAM,IAAI,SAAS;EAEzB,MAAM,QAAQ,SAAS;;;;;EAMvB,MAAM,UAAU,eAAoE;GAClF,IAAI,kBAAkB,MAAM,SAAS,GACnC,OAAO,OAAO,aAAa,MAAM,SAAS,CAAC,EAAA,CAAG,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;GAEvE,MAAM,WAAW,mBAAmB;GACpC,MAAM,QAAQ,IAAI,IAAI,SAAS,KAAK,MAAM,EAAE,IAAI,CAAC;GACjD,MAAM,QAAQ,CAAC,GAAG,UAAU,IAAI,MAAM,SAAS,CAAC,EAAA,CAAG,QAAQ,MAAM,CAAC,MAAM,IAAI,EAAE,IAAI,CAAC,CAAC;GACpF,OAAO,OAAO,YAAY,MAAM,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;EACzD,CAAC;EAED,MAAM,YAAY,eAAe,MAAM,SAAS;EAEhD,MAAM,EACJ,UACA,OACA,gBACA,SACA,OACA,MACA,YACA,eACA,kBACA,kBACA,iBACA,MACA,OACA,YACA,OACA,iBACA,gBACA,SACA,aACA,iBACE,QAAQ;GAAE;GAAO;EAAK,CAAC;;EAG3B,MAAM,UAAU,eAAe,CAAC,SAAS,MAAM,MAAM,QAAQ,IAAI,SAAS,MAAM,CAAC;;EAGjF,MAAM,YAAY,eACV,SAAS,MAAM,SAAS,KAAK,MAAM,MAAM,SAAS,KAAK,QAAQ,KACvE;;EAGA,MAAM,aAAa,eAAe;GAChC,MAAM,QAAwB,CAAC;GAC/B,KAAK,MAAM,OAAO,SAAS,OAAO;IAChC,IAAI,IAAI,SAAS,eAAe,CAAC,IAAI,WAAW;IAChD,KAAK,MAAM,QAAQ,IAAI,WACrB,IAAI,QAAQ,MAAM,KAAK,KAAK,EAAE,aAAa,SAAS,MAAM,KAAK,IAAI;GAEvE;GACA,OAAO;EACT,CAAC;;EAGD,MAAM,oBAAoB,IAAmB,IAAI;EAEjD,MAAM,kBAAkB,eAAe;GACrC,IAAI,CAAC,kBAAkB,OAAO,OAAO;GACrC,OAAO,WAAW,MAAM,MAAM,SAAS,KAAK,OAAO,kBAAkB,KAAK,KAAK;EACjF,CAAC;EAED,MAAM,YAAY,eAAe,gBAAgB,SAAS,IAAI;EAG9D,YACQ,WAAW,MAAM,WAAW,MAAM,SAAS,EAAE,EAAE,KACpD,OAAO;GACN,IAAI,IAAI,kBAAkB,QAAQ;EACpC,CACF;EAGA,MAAM,aAAa,UAAU;GAC3B,MAAM,KAAK,kBAAkB;GAC7B,IAAI,MAAM,CAAC,MAAM,MAAM,SAAS,KAAK,OAAO,EAAE,GAC5C,kBAAkB,QAAQ,MAAM,MAAM,SAAS,EAAE,EAAE,MAAM;EAE7D,CAAC;;EAGD,MAAM,aAAa,eAAuB;GACxC,IAAI,WAAW,MAAM,MAAM,SAAS,KAAK,OAAO,UAAU,GACxD,kBAAkB,QAAQ;EAE9B;EAEA,MAAM,mBAAmB;GACvB,kBAAkB,QAAQ;EAC5B;EAEA,QAAQ,aAAa;GAAE;GAAK;GAAO,OAAO;GAAS;GAAW;EAAU,CAAC;;;;;EAWzE,MAAM,YAAY,eAA6C,WAAW;EAC1E,MAAM,WAAW,eAA+C,UAAU;EAC1E,MAAM,aAAa,IAAI,eAAe;EACtC,MAAM,WAAW,IAAI,KAAK;EAE1B,MAAM,aAAa,eAAe;GAChC,OAAO,UAAU,QAAQ,CAAC,OAAO,GAAG,WAAW,MAAM,GAAG,IAAI,CAAC,KAAK;EACpE,CAAC;EAED,MAAM,wBAAwB,UAAU,OAAO,IAAA,EAAiC,eAAe;;EAG/F,MAAM,0BAA0B;GAC9B,MAAM,QAAQ,eAAe;GAC7B,OAAO,QAAQ,QAAQ,qBAAqB;EAC9C;;EAGA,MAAM,mBAAmB,UAAkB;GACzC,MAAM,SAAS,eAAe;GAC9B,MAAM,WAAW,SACb,KAAK,IAAI,iBAAiB,SAAS,cAAc,IACjD,OAAO;GACX,OAAO,KAAK,IAAI,KAAK,IAAI,OAAO,eAAe,GAAG,QAAQ;EAC5D;EAIA,MAAM,kBAAkB,MAAM,aAAa;GACzC,IAAI,CAAC,MAAM;GAEX,MAAM,QADY,QAAQ,MAAM,KAAK,KAAK,EAAE,eAChB,WAAW,KAAA,IAAY,kBAAkB;GACrE,IAAI,OAAO,UAAU,UAAU;GAC/B,WAAW,QAAQ,gBAAgB,KAAK;EAC1C,CAAC;EAGD,kBAAkB;GAChB,SAAS,eAAgB,SAAS,OAAO,OAAmC,KAAA,CAAS;GACrF,SAAS,SAAS;IAEhB,MAAM,aAAa,QAAQ,EAAE,EAAE,gBAAgB,EAAE,EAAE;IACnD,IAAI,CAAC,YAAY;IACjB,MAAM,QAAQ,KAAK,MAAM,UAAU;IAEnC,IAAI,KAAK,IAAI,QAAQ,WAAW,KAAK,IAAI,GAAG;IAC5C,WAAW,QAAQ;GACrB;EACF,CAAC;EAED,MAAM,eAAe,eAA+C,cAAc;;;;;;EAOlF,MAAM,mBAAmB,IAA+B,KAAA,CAAS;;EAGjE,MAAM,mBAAmB,OAAe;GACtC,eAAe;GAEf,MAAM,QAAQ,MAAM,MAAM,WAAW,SAAS,KAAK,OAAO,EAAE;GAC5D,IAAI,UAAU,IAAI;GAClB,iBAAiB,QAAQ,MAAM,MAAM,QAAQ,EAAE,EAAE,MAAM;GAEvD,MAAM,OAAO,aAAa,EAAE;GAC5B,IAAI,MAAM,aAAa,OAAO,WAAW,KAAK,OAAO;EACvD;;EAGA,MAAM,uBAAuB;GAC3B,IAAI,iBAAiB,UAAU,KAAA,GAAW;GAC1C,MAAM,UAAU,aAAa,OAAO,WAAW,CAAC,CAAC,KAAK;GACtD,IAAI,SAAS,QAAQ,SAAS,KAAA,GAAW,iBAAiB,SAAS,KAAA,CAAS;GAC5E,iBAAiB,QAAQ,KAAA;EAC3B;EAEA,MAAM,cAAc,SAAiB,gBAAkC;GACrE,IAAI,MAAM,UAAU;GAEpB,IAAI,iBAAiB,UAAU,KAAA,GAAW;IACxC,QAAQ,SAAS,aAAa,iBAAiB,SAAS,KAAA,CAAS;IACjE,iBAAiB,QAAQ,KAAA;IACzB;GACF;GACA,KAAK,SAAS,WAAW;EAC3B;EAEA,MAAM,wBAAwB,UAAmD;GAC/E,MAAM,UAAU,gBAAgB;GAChC,IAAI,CAAC,SAAS;GACd,eAAe,QAAQ,OAAO,MAAM,MAAM,QAAQ,KAAK,IAAI,QAAQ,MAAM,OAAO;EAClF;EAEA,SAA6B;GAC3B;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF,CAAC;;GApUC,OAAA,UAAA,GAAA,YAqEU,MAAA,OAAA,GAAA;IApEJ,SAAA;IAAJ,KAAI;IACH,OAAK,eAAA,CAAG,MAAA,GAAA,CAAG,CAAC,GAAG,MAAA,GAAA,CAAG,CAAC,GAAE,YAAa,SAAA,KAAQ,CAAA,CAAA;IAC1C,MAAM,WAAA;IACP,MAAK;IACJ,WAAW,UAAA;IACX,iBAAe,UAAA,QAAS,CAAI,gBAAgB,eAAe,IAAI,KAAA;IAC/D,eAAY,OAAA,OAAA,OAAA,MAAA,WAAE,SAAA,QAAQ;IACtB,aAAU,OAAA,OAAA,OAAA,MAAA,WAAE,SAAA,QAAQ;;IAErB,SAAA,cAkDM;KAlDN,mBAkDM,OAAA,EAlDA,OAAK,eAAA,CAAG,MAAA,GAAA,CAAG,CAAC,EAAC,MAAA,GAAU,MAAA,GAAA,CAAG,CAAC,GAAE,SAAU,QAAA,KAAO,CAAA,CAAA,EAAA,GAAA;MAClD,YAac,aAAA;OAZX,UAAU,MAAA,QAAA;OACV,SAAS,QAAA;OACT,SAAS,MAAA,OAAA;OACT,UAAU,QAAA;OACV,kBAAgB,QAAA;OAChB,WAAS,MAAA,eAAA;OACT,cAAY,MAAA,UAAA;OACZ,gBAAa,OAAA,OAAA,OAAA,MAAG,SAAS,WAAW,MAAI,CAAA,CAAA;MAEzBA,GAAAA,YAAAA,EAAAA,GAAAA,EAAAA,GAAAA,CAAAA,KAAAA,OAAO,UAAA;OAAU,MAAA;OAC/B,IAAA,cAAuB,CAAvB,WAAuB,KAAA,QAAA,SAAA,CAAA,CAAA;;;;;;;;;;;MAIhB,MAAA,eAAA,KAAX,UAAA,GAAA,mBAEM,OAAA;;OAFuB,OAAK,eAAE,MAAA,GAAA,CAAG,CAAC,EAAC,kBAAA,CAAA;MACvC,GAAA,CAAA,YAAqF,aAAA;OAAvE,WAAW,MAAA,eAAA,CAAe,CAAC;OAAY,UAAQ;;MAG/D,YAA0E,aAAA;OAAzD,WAAW,MAAA,gBAAA;OAAmB,WAAS,MAAA,cAAA;;MAExD,YAME,aAAA;OALC,OAAO,MAAA,KAAA;OACP,UAAU,QAAA;OACV,YAAW,MAAA,WAAA;OACX,QAAM;OACN,UAAQ,MAAA,YAAA;;;;;;;MAGX,YAAuB,aAAA,EAAd,MAAM,MAAA,IAAA,EAAI,GAAA,MAAA,GAAA,CAAA,MAAA,CAAA;MAGV,CAAA,QAAA,YADT,UAAA,GAAA,YAiBE,aAAA;;OAfI,SAAA;OAAJ,KAAI;OACI,OAAO,MAAA,KAAA;OAAA,kBAAA,OAAA,OAAA,OAAA,MAAA,WAAA,MAAA,KAAA,IAAA,MAAK,QAAA,SAAA;OACZ,mBAAiB,MAAA,cAAA;OAAA,2BAAA,OAAA,OAAA,OAAA,MAAA,WAAA,MAAA,cAAA,IAAA,eAAc,QAAA,SAAA;OACtC,SAAS,MAAA,OAAA;OACT,QAAQ,QAAA;OACR,aAAa,QAAA;OACb,QAAQ,QAAA;OACR,uBAAqB,QAAA;OACrB,WAAW,UAAA;OACX,eAAa,MAAA,UAAA;OACb,sBAAoB,MAAA,gBAAA;OACpB,sBAAoB,QAAA;OACpB,QAAM;OACN,SAAO,MAAA,KAAA;OACP,SAAO,MAAA,KAAA;;;;;;;;;;;;;;;;;KAIZ,mBAAA,yDAAA;KAEQ,gBAAA,SADR,UAAA,GAAA,YAKE,aAAA;;MAHI,SAAA;MAAJ,KAAI;MACH,aAAW,gBAAA;MACX,SAAO"}
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval-banner.js","names":[],"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,
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ask-question.js","names":[],"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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-input.js","names":[],"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.prevent=\"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 = () => {\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.prevent=\"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 = () => {\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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"job-bar.js","names":[],"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,
|
|
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"}
|