@veltra/ai 2.1.6 → 2.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +42 -0
  2. package/dist/chat/fold.d.ts +41 -0
  3. package/dist/chat/fold.js +208 -0
  4. package/dist/chat/fold.js.map +1 -0
  5. package/dist/chat/index.d.ts +4 -2
  6. package/dist/chat/session.d.ts +123 -0
  7. package/dist/chat/session.js +79 -0
  8. package/dist/chat/session.js.map +1 -0
  9. package/dist/chat/types.d.ts +38 -1
  10. package/dist/chat/use-chat-helpers.js +39 -0
  11. package/dist/chat/use-chat-helpers.js.map +1 -0
  12. package/dist/chat/use-chat-session.js +68 -0
  13. package/dist/chat/use-chat-session.js.map +1 -0
  14. package/dist/chat/use-chat.d.ts +50 -2
  15. package/dist/chat/use-chat.js +73 -39
  16. package/dist/chat/use-chat.js.map +1 -1
  17. package/dist/components/ai-chat/ai-chat.js +49 -14
  18. package/dist/components/ai-chat/ai-chat.js.map +1 -1
  19. package/dist/components/ai-chat/approval-banner.js +54 -0
  20. package/dist/components/ai-chat/approval-banner.js.map +1 -0
  21. package/dist/components/ai-chat/ask-question.js +32 -51
  22. package/dist/components/ai-chat/ask-question.js.map +1 -1
  23. package/dist/components/ai-chat/di.js.map +1 -1
  24. package/dist/components/ai-chat/job-bar.js +48 -0
  25. package/dist/components/ai-chat/job-bar.js.map +1 -0
  26. package/dist/components/ai-chat/message-list.js +5 -2
  27. package/dist/components/ai-chat/message-list.js.map +1 -1
  28. package/dist/components/ai-chat/queue-list.js +9 -3
  29. package/dist/components/ai-chat/queue-list.js.map +1 -1
  30. package/dist/components/ai-chat/side-panel.js +12 -30
  31. package/dist/components/ai-chat/side-panel.js.map +1 -1
  32. package/dist/components/ai-chat/style2.css +146 -8
  33. package/dist/components/ai-chat/tool-call.js +51 -40
  34. package/dist/components/ai-chat/tool-call.js.map +1 -1
  35. package/dist/components/ai-chat/tool-icons.js +31 -0
  36. package/dist/components/ai-chat/tool-icons.js.map +1 -0
  37. package/dist/components/ai-chat/tool-json.js +40 -0
  38. package/dist/components/ai-chat/tool-json.js.map +1 -0
  39. package/dist/components/ai-orb/style2.css +1 -1
  40. package/dist/index.d.ts +6 -4
  41. package/dist/index.js +3 -1
  42. package/dist/tools/ask-question/ask-question.js +48 -2
  43. package/dist/tools/ask-question/ask-question.js.map +1 -1
  44. package/dist/types/ai-chat.d.ts +17 -6
  45. package/package.json +7 -7
  46. package/src/chat/__test__/fold.test.ts +136 -0
  47. package/src/chat/__test__/session.test.ts +191 -0
  48. package/src/chat/__test__/use-chat.test.ts +268 -1
  49. package/src/chat/fold.ts +260 -0
  50. package/src/chat/index.ts +2 -0
  51. package/src/chat/session.ts +168 -0
  52. package/src/chat/types.ts +40 -0
  53. package/src/chat/use-chat-helpers.ts +39 -0
  54. package/src/chat/use-chat-session.ts +87 -0
  55. package/src/chat/use-chat.ts +90 -44
  56. package/src/components/ai-chat/__test__/ai-chat.test.ts +405 -7
  57. package/src/components/ai-chat/__test__/tool-icons.test.ts +33 -0
  58. package/src/components/ai-chat/ai-chat.vue +39 -4
  59. package/src/components/ai-chat/approval-banner.vue +37 -0
  60. package/src/components/ai-chat/ask-question.vue +38 -54
  61. package/src/components/ai-chat/di.ts +6 -4
  62. package/src/components/ai-chat/job-bar.vue +41 -0
  63. package/src/components/ai-chat/message-list.vue +4 -1
  64. package/src/components/ai-chat/queue-list.vue +3 -1
  65. package/src/components/ai-chat/side-panel.vue +15 -13
  66. package/src/components/ai-chat/style.scss +158 -6
  67. package/src/components/ai-chat/tool-call.vue +46 -34
  68. package/src/components/ai-chat/tool-icons.ts +33 -0
  69. package/src/components/ai-chat/tool-json.vue +52 -0
  70. package/src/tools/ask-question/ask-question.ts +53 -4
  71. package/src/types/ai-chat.ts +17 -5
@@ -0,0 +1,33 @@
1
+ import { EditPen, Folder, Internet, QuestionFilled, Terminal, Tools } from '@veltra/icons/normal'
2
+ import type { Component } from 'vue'
3
+
4
+ /**
5
+ * 内置 toolName → icon:按名称子串匹配,顺序即优先级。
6
+ * 未命中任何规则时走兜底图标,不得 throw。
7
+ */
8
+ const TOOL_ICON_RULES: [needles: string[], icon: Component][] = [
9
+ [['bash', 'terminal'], Terminal],
10
+ [['fs', 'file'], Folder],
11
+ [['web', 'globe'], Internet],
12
+ [['edit', 'pencil'], EditPen],
13
+ [['ask', 'question'], QuestionFilled]
14
+ ]
15
+
16
+ /** 未知名工具的兜底图标 */
17
+ export const FALLBACK_TOOL_ICON: Component = Tools
18
+
19
+ /**
20
+ * 按工具名解析图标。
21
+ * 宿主 `toolIcons` 精确名覆盖优先于内置规则;都未命中则兜底。
22
+ */
23
+ export function resolveToolIcon(name: string, overrides?: Record<string, Component>): Component {
24
+ const key = String(name ?? '')
25
+ const fromOverride = overrides?.[key]
26
+ if (fromOverride) return fromOverride
27
+
28
+ const lower = key.toLowerCase()
29
+ for (const [needles, icon] of TOOL_ICON_RULES) {
30
+ if (needles.some((needle) => lower.includes(needle))) return icon
31
+ }
32
+ return FALLBACK_TOOL_ICON
33
+ }
@@ -0,0 +1,52 @@
1
+ <template>
2
+ <div :class="cls.e('tool-call-json')">
3
+ <div :class="cls.e('tool-call-json-head')">
4
+ <span :class="cls.e('tool-call-section')">{{ title }}</span>
5
+ <UButton
6
+ v-if="overLimit"
7
+ :class="cls.e('tool-call-json-toggle')"
8
+ size="small"
9
+ text
10
+ type="primary"
11
+ @click="expanded = !expanded"
12
+ >
13
+ {{ expanded ? '收起' : '展开' }}
14
+ </UButton>
15
+ </div>
16
+ <pre :class="[cls.e('tool-call-code'), bem.is('truncated', overLimit && !expanded)]">{{
17
+ shown
18
+ }}</pre>
19
+ </div>
20
+ </template>
21
+
22
+ <script lang="ts" setup>
23
+ import { UButton } from '@veltra/desktop'
24
+ import { bem } from '@veltra/utils'
25
+ import { computed, inject, ref } from 'vue'
26
+
27
+ import { AiChatDIKey } from './di'
28
+
29
+ defineOptions({ name: 'UAiChatToolJson' })
30
+
31
+ /** 超过该字符数截断,展开后可见全文 */
32
+ const PREVIEW_LIMIT = 480
33
+
34
+ const { title, text } = defineProps<{
35
+ /** 区块标题(参数 / 结果 / 视图) */
36
+ title: string
37
+ /** 已格式化的 JSON 或原文 */
38
+ text: string
39
+ }>()
40
+
41
+ const di = inject(AiChatDIKey)
42
+ const cls = di?.cls ?? bem('ai-chat')
43
+
44
+ const expanded = ref(false)
45
+
46
+ const overLimit = computed(() => text.length > PREVIEW_LIMIT)
47
+
48
+ const shown = computed(() => {
49
+ if (expanded.value || !overLimit.value) return text
50
+ return `${text.slice(0, PREVIEW_LIMIT)}…`
51
+ })
52
+ </script>
@@ -1,9 +1,9 @@
1
1
  import { QuestionFilled } from '@veltra/icons/normal'
2
- import type { Component } from 'vue'
2
+ import { computed, defineComponent, h, type Component, type PropType } from 'vue'
3
3
 
4
- import type { ChatTool } from '../../chat/types'
4
+ import type { ChatTool, ChatToolCall } from '../../chat/types'
5
5
  import AskQuestion from '../../components/ai-chat/ask-question.vue'
6
- import { deferAskQuestion, takeAskQuestionDeferred } from './deferred'
6
+ import { deferAskQuestion, resolveAskQuestion, takeAskQuestionDeferred } from './deferred'
7
7
 
8
8
  /** 单个提问项 */
9
9
  export interface AskQuestionItem {
@@ -75,6 +75,55 @@ const QUESTIONS_SCHEMA: Record<string, unknown> = {
75
75
  required: ['questions']
76
76
  }
77
77
 
78
+ function parseQuestions(toolCall: ChatToolCall): AskQuestionItem[] {
79
+ try {
80
+ const args = JSON.parse(toolCall.arguments || '{}') as Partial<AskQuestionArgs>
81
+ return Array.isArray(args.questions) ? args.questions : []
82
+ } catch {
83
+ return []
84
+ }
85
+ }
86
+
87
+ function parseAnswers(toolCall: ChatToolCall): AskQuestionAnswer[] | undefined {
88
+ if (toolCall.status !== 'success' || !toolCall.result) return undefined
89
+ try {
90
+ const result = JSON.parse(toolCall.result) as Partial<AskQuestionResult>
91
+ return Array.isArray(result.answers) ? result.answers : undefined
92
+ } catch {
93
+ return undefined
94
+ }
95
+ }
96
+
97
+ function statusError(toolCall: ChatToolCall): string | undefined {
98
+ if (toolCall.status !== 'error') return undefined
99
+ const err = toolCall.error ?? ''
100
+ if (err.includes('Abort') || err.includes('取消')) return '已取消'
101
+ return err || '提问失败'
102
+ }
103
+
104
+ /** 把 toolCall 转成提问表单的 questions / onSubmit;提交仍走 resolveAskQuestion */
105
+ const AskQuestionRender = defineComponent({
106
+ name: 'AskQuestionRender',
107
+ props: { toolCall: { type: Object as PropType<ChatToolCall>, required: true } },
108
+ setup(props) {
109
+ const questions = computed(() => parseQuestions(props.toolCall))
110
+ const answers = computed(() => parseAnswers(props.toolCall))
111
+ const error = computed(() => statusError(props.toolCall))
112
+ const onSubmit = (value: AskQuestionAnswer[] | AskQuestionResult) => {
113
+ resolveAskQuestion(props.toolCall.id, {
114
+ answers: Array.isArray(value) ? value : value.answers
115
+ })
116
+ }
117
+ return () =>
118
+ h(AskQuestion, {
119
+ questions: questions.value,
120
+ answers: answers.value,
121
+ error: error.value,
122
+ onSubmit
123
+ })
124
+ }
125
+ })
126
+
78
127
  /**
79
128
  * 创建内置提问工具:execute 挂起等待用户在 UI 中作答,提交后结果回灌模型。
80
129
  * 工具卡片由内联提问表单渲染(多题分页导航,提交后展示问答摘要,不自动折叠)。
@@ -95,7 +144,7 @@ export function createAskQuestionTool(
95
144
  parameters: QUESTIONS_SCHEMA,
96
145
  icon,
97
146
  label,
98
- render: AskQuestion,
147
+ render: AskQuestionRender,
99
148
  execute: (_args, { toolCall, signal }) =>
100
149
  new Promise<AskQuestionResult>((resolve, reject) => {
101
150
  deferAskQuestion(toolCall.id, { resolve, reject })
@@ -1,6 +1,7 @@
1
1
  import type { DeconstructValue } from '@veltra/utils'
2
- import type { Ref } from 'vue'
2
+ import type { Component, Ref } from 'vue'
3
3
 
4
+ import type { ChatSessionTransport } from '../chat/session'
4
5
  import type {
5
6
  ChatAttachment,
6
7
  ChatMessage,
@@ -8,15 +9,19 @@ import type {
8
9
  ChatTokenUsage,
9
10
  ChatTool,
10
11
  ChatToolCall,
12
+ ChatToolMeta,
11
13
  ChatTransport
12
14
  } from '../chat/types'
13
15
  import type { ChatModelOption } from '../providers'
14
16
 
15
17
  export interface AiChatProps {
16
- /** 传输层(必填),可使用导出的 createOpenAITransport() 创建 */
17
- transport: ChatTransport
18
- /** 工具定义列表,不同场景传入不同工具赋予助手不同能力 */
19
- tools?: ChatTool[]
18
+ /** 传输层(必填);函数型 ChatTransport 或 session 对象,可使用 createOpenAITransport / createServerTransport */
19
+ transport: ChatTransport | ChatSessionTransport
20
+ /**
21
+ * 工具列表。函数 transport 传入 ChatTool[](必有 execute);
22
+ * session 下可为 ChatToolMeta[](仅渲染元信息)。ChatTool[] 仍可赋值。
23
+ */
24
+ tools?: (ChatTool | ChatToolMeta)[]
20
25
  /** 系统提示词 */
21
26
  systemPrompt?: string
22
27
  /**
@@ -50,6 +55,13 @@ export interface AiChatProps {
50
55
  * 默认 false:仅在拿到 usage 时显示会话累计「总 token」。接口未返回 usage 时不展示。
51
56
  */
52
57
  tokenUsageDetail?: boolean
58
+ /**
59
+ * 覆盖包内 toolName → icon 映射(精确名优先于内置名称规则)。
60
+ * 缺省不传则走内置表;未命中的名称仍用兜底图标,不得 throw。
61
+ */
62
+ toolIcons?: Record<string, Component>
63
+ /** 只读:不展示输入区、欢迎语不可发送、队列无插队/编辑/移除。默认 false */
64
+ readonly?: boolean
53
65
  }
54
66
 
55
67
  export interface AiChatEmits {