@huyooo/ai-chat-frontend-vue 0.2.13 → 0.2.14
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/README.md +85 -77
- package/dist/_baseUniq-Berj5otS.js +477 -0
- package/dist/arc-D4b-De5i.js +83 -0
- package/dist/architecture-U656AL7Q-B1R4o7wt.js +5 -0
- package/dist/architectureDiagram-VXUJARFQ-0H1LzSrf.js +4668 -0
- package/dist/blockDiagram-VD42YOAC-Dd6DMjLf.js +2265 -0
- package/dist/c4Diagram-YG6GDRKO-c6mpIonb.js +1581 -0
- package/dist/channel-CQSBI1fo.js +5 -0
- package/dist/chunk-4BX2VUAB-CfWC7lTG.js +9 -0
- package/dist/chunk-55IACEB6-joM8m2Zv.js +8 -0
- package/dist/chunk-B4BG7PRW-DN94Y3mS.js +1376 -0
- package/dist/chunk-DI55MBZ5-CPRGoFPG.js +1382 -0
- package/dist/chunk-FMBD7UC4-dUKl2TcN.js +19 -0
- package/dist/chunk-QN33PNHL-o3WmqquO.js +20 -0
- package/dist/chunk-QZHKN3VN-Btf62lRF.js +15 -0
- package/dist/chunk-TZMSLE5B-QCPpnm8Y.js +64 -0
- package/dist/classDiagram-2ON5EDUG-ERF9RE9K.js +16 -0
- package/dist/classDiagram-v2-WZHVMYZB-ERF9RE9K.js +16 -0
- package/dist/clone-B_B1yyxj.js +8 -0
- package/dist/components/ChatPanel.vue.d.ts +11 -7
- package/dist/components/ChatPanel.vue.d.ts.map +1 -1
- package/dist/components/common/SettingsPanel.vue.d.ts +10 -1
- package/dist/components/common/SettingsPanel.vue.d.ts.map +1 -1
- package/dist/components/common/ToggleSwitch.vue.d.ts.map +1 -1
- package/dist/components/input/ChatInput.vue.d.ts +4 -1
- package/dist/components/input/ChatInput.vue.d.ts.map +1 -1
- package/dist/components/input/DropdownSelector.vue.d.ts +10 -0
- package/dist/components/input/DropdownSelector.vue.d.ts.map +1 -1
- package/dist/components/message/MessageBubble.vue.d.ts +2 -2
- package/dist/components/message/MessageBubble.vue.d.ts.map +1 -1
- package/dist/components/message/PartsRenderer.vue.d.ts +6 -0
- package/dist/components/message/PartsRenderer.vue.d.ts.map +1 -1
- package/dist/components/message/parts/CollapsibleCard.vue.d.ts.map +1 -1
- package/dist/components/message/parts/ImagePart.vue.d.ts.map +1 -1
- package/dist/components/message/parts/SearchPart.vue.d.ts.map +1 -1
- package/dist/components/message/parts/TextPart.vue.d.ts +3 -1
- package/dist/components/message/parts/TextPart.vue.d.ts.map +1 -1
- package/dist/components/message/parts/ToolCallPart.vue.d.ts +9 -4
- package/dist/components/message/parts/ToolCallPart.vue.d.ts.map +1 -1
- package/dist/components/message/parts/index.d.ts +5 -1
- package/dist/components/message/parts/index.d.ts.map +1 -1
- package/dist/components/message/parts/visual-predicate.d.ts +7 -0
- package/dist/components/message/parts/visual-predicate.d.ts.map +1 -0
- package/dist/components/message/parts/visual-render.d.ts +2 -0
- package/dist/components/message/parts/visual-render.d.ts.map +1 -0
- package/dist/components/message/parts/visual.d.ts +3 -0
- package/dist/components/message/parts/visual.d.ts.map +1 -0
- package/dist/composables/useChat.d.ts +34 -10
- package/dist/composables/useChat.d.ts.map +1 -1
- package/dist/composables/useImageUpload.d.ts +7 -0
- package/dist/composables/useImageUpload.d.ts.map +1 -1
- package/dist/cose-bilkent-S5V4N54A-C-CkEnIH.js +2608 -0
- package/dist/cytoscape.esm-CjI2IsL8.js +18735 -0
- package/dist/dagre-6UL2VRFP-BbmZysl9.js +446 -0
- package/dist/defaultLocale-D7EN2tov.js +171 -0
- package/dist/diagram-PSM6KHXK-CMnope5e.js +533 -0
- package/dist/diagram-QEK2KX5R-Bc87skpo.js +221 -0
- package/dist/diagram-S2PKOQOG-BtkzC5-o.js +143 -0
- package/dist/erDiagram-Q2GNP2WA-CwQLdjh9.js +842 -0
- package/dist/flowDiagram-NV44I4VS-8ehMD0M4.js +1627 -0
- package/dist/ganttDiagram-JELNMOA3-dLX8Pbvy.js +2670 -0
- package/dist/gitGraph-F6HP7TQM-BBk5qfnx.js +5 -0
- package/dist/gitGraphDiagram-NY62KEGX-DG_fvWrq.js +712 -0
- package/dist/graph-B7BnGhuR.js +381 -0
- package/dist/index-Cr3tTH72.js +83883 -0
- package/dist/index.d.ts +7 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -56433
- package/dist/info-NVLQJR56-eZr6nVr1.js +5 -0
- package/dist/infoDiagram-WHAUD3N6-BcVYTft_.js +24 -0
- package/dist/init-DjUOC4st.js +16 -0
- package/dist/journeyDiagram-XKPGCS4Q-D4C72gYB.js +834 -0
- package/dist/kanban-definition-3W4ZIXB7-BuSjAlm6.js +721 -0
- package/dist/layout-LjYblgUu.js +1441 -0
- package/dist/linear-DtPqj295.js +259 -0
- package/dist/mermaid-parser.core-DdOZxjrw.js +15189 -0
- package/dist/min-DYbgfxu0.js +38 -0
- package/dist/mindmap-definition-VGOIOE7T-BZxsr4M_.js +787 -0
- package/dist/ordinal-DfAQgscy.js +61 -0
- package/dist/packet-BFZMPI3H-XfNBjRzf.js +5 -0
- package/dist/pie-7BOR55EZ-pJDdOv19.js +5 -0
- package/dist/pieDiagram-ADFJNKIX-DDwfWBNn.js +161 -0
- package/dist/quadrantDiagram-AYHSOK5B-Ddpxuto0.js +1024 -0
- package/dist/radar-NHE76QYJ-jE07d-4Z.js +5 -0
- package/dist/requirementDiagram-UZGBJVZJ-B_TLWQ-N.js +852 -0
- package/dist/sankeyDiagram-TZEHDZUN-C1TWrUpG.js +810 -0
- package/dist/sequenceDiagram-WL72ISMW-B8pqb5YX.js +2518 -0
- package/dist/stateDiagram-FKZM4ZOC-DwNT1ej6.js +263 -0
- package/dist/stateDiagram-v2-4FDKWEC3-CZQUitzd.js +16 -0
- package/dist/style.css +1 -1
- package/dist/timeline-definition-IT6M3QCI-BGdS1wvf.js +799 -0
- package/dist/treemap-KMMF4GRG-B_eEU0Q4.js +5 -0
- package/dist/types/index.d.ts +36 -37
- package/dist/types/index.d.ts.map +1 -1
- package/dist/xychartDiagram-PRI3JC2R-DtwXQqN4.js +1340 -0
- package/package.json +3 -3
- package/src/components/ChatPanel.vue +75 -13
- package/src/components/common/SettingsPanel.vue +169 -0
- package/src/components/input/ChatInput.vue +49 -34
- package/src/components/input/DropdownSelector.vue +175 -18
- package/src/components/message/MessageBubble.vue +61 -40
- package/src/components/message/PartsRenderer.vue +106 -27
- package/src/components/message/parts/CollapsibleCard.vue +8 -3
- package/src/components/message/parts/ImagePart.vue +0 -1
- package/src/components/message/parts/SearchPart.vue +2 -0
- package/src/components/message/parts/TextPart.vue +753 -11
- package/src/components/message/parts/ToolCallPart.vue +315 -246
- package/src/components/message/parts/index.ts +5 -1
- package/src/components/message/parts/visual-predicate.ts +43 -0
- package/src/components/message/parts/visual-render.ts +19 -0
- package/src/components/message/parts/visual.ts +12 -0
- package/src/composables/useChat.ts +560 -92
- package/src/composables/useImageUpload.ts +115 -11
- package/src/index.ts +6 -8
- package/src/styles.css +124 -0
- package/src/types/index.ts +52 -44
- package/dist/components/message/blocks/CodeBlock.vue.d.ts +0 -11
- package/dist/components/message/blocks/CodeBlock.vue.d.ts.map +0 -1
- package/dist/components/message/blocks/TextBlock.vue.d.ts +0 -7
- package/dist/components/message/blocks/TextBlock.vue.d.ts.map +0 -1
- package/dist/components/message/blocks/index.d.ts +0 -6
- package/dist/components/message/blocks/index.d.ts.map +0 -1
- package/dist/components/message/parts/ToolResultPart.vue.d.ts +0 -14
- package/dist/components/message/parts/ToolResultPart.vue.d.ts.map +0 -1
- package/dist/components/message/tool-results/DefaultToolResult.vue.d.ts +0 -4
- package/dist/components/message/tool-results/DefaultToolResult.vue.d.ts.map +0 -1
- package/dist/components/message/tool-results/SearchResults.vue.d.ts +0 -4
- package/dist/components/message/tool-results/SearchResults.vue.d.ts.map +0 -1
- package/dist/components/message/tool-results/WeatherCard.vue.d.ts +0 -4
- package/dist/components/message/tool-results/WeatherCard.vue.d.ts.map +0 -1
- package/dist/components/message/tool-results/index.d.ts +0 -7
- package/dist/components/message/tool-results/index.d.ts.map +0 -1
- package/src/components/message/ContentRenderer.vue +0 -61
- package/src/components/message/ToolResultRenderer.vue +0 -27
- package/src/components/message/blocks/CodeBlock.vue +0 -113
- package/src/components/message/blocks/TextBlock.vue +0 -21
- package/src/components/message/blocks/index.ts +0 -6
- package/src/components/message/parts/ToolResultPart.vue +0 -78
- package/src/components/message/tool-results/DefaultToolResult.vue +0 -45
- package/src/components/message/tool-results/SearchResults.vue +0 -133
- package/src/components/message/tool-results/WeatherCard.vue +0 -139
- package/src/components/message/tool-results/index.ts +0 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huyooo/ai-chat-frontend-vue",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.14",
|
|
4
4
|
"description": "AI Chat Frontend - Vue components with adapter pattern",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"vue": "^3.4.0"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@huyooo/ai-chat-bridge-electron": "^0.2.
|
|
35
|
-
"@huyooo/ai-chat-shared": "^0.2.
|
|
34
|
+
"@huyooo/ai-chat-bridge-electron": "^0.2.14",
|
|
35
|
+
"@huyooo/ai-chat-shared": "^0.2.14",
|
|
36
36
|
"@iconify/vue": "^4.1.2",
|
|
37
37
|
"vscode-uri": "^3.1.0",
|
|
38
38
|
"marked": "^12.0.0",
|
|
@@ -19,7 +19,10 @@
|
|
|
19
19
|
<SettingsPanel
|
|
20
20
|
v-model:visible="settingsPanelVisible"
|
|
21
21
|
v-model:config="autoRunConfig"
|
|
22
|
+
:all-tools="allTools"
|
|
23
|
+
:enabled-tools="enabledTools"
|
|
22
24
|
@save="handleSaveSettings"
|
|
25
|
+
@update:enabled-tools="handleUpdateEnabledTools"
|
|
23
26
|
/>
|
|
24
27
|
|
|
25
28
|
<!-- 顶部标题栏 -->
|
|
@@ -28,7 +31,7 @@
|
|
|
28
31
|
:sessions="sessions"
|
|
29
32
|
:current-session-id="currentSessionId"
|
|
30
33
|
:show-close="!!onClose"
|
|
31
|
-
@new-session="
|
|
34
|
+
@new-session="handleNewSession"
|
|
32
35
|
@switch-session="switchSession"
|
|
33
36
|
@delete-session="deleteSession"
|
|
34
37
|
@hide-session="hideSession"
|
|
@@ -61,6 +64,7 @@
|
|
|
61
64
|
:adapter="props.adapter"
|
|
62
65
|
:auto-run-config="autoRunConfig"
|
|
63
66
|
:on-save-config="saveAutoRunConfig"
|
|
67
|
+
:on-cancel-tool-call="handleCancelToolCall"
|
|
64
68
|
@copy="copyMessage(msg.id)"
|
|
65
69
|
@regenerate="regenerateMessage(index)"
|
|
66
70
|
@send="(text) => handleResend(index, text)"
|
|
@@ -105,7 +109,7 @@
|
|
|
105
109
|
<script setup lang="ts">
|
|
106
110
|
import { ref, computed, watch, nextTick, onMounted, provide, toRef, type Component } from 'vue';
|
|
107
111
|
import { useChat } from '../composables/useChat';
|
|
108
|
-
import type { ChatAdapter } from '../adapter';
|
|
112
|
+
import type { ChatAdapter, ImageData } from '../adapter';
|
|
109
113
|
import type { ModelOption, ChatMode } from '../types';
|
|
110
114
|
import type { AutoRunConfig } from '@huyooo/ai-chat-bridge-electron/renderer';
|
|
111
115
|
import { Icon } from '@iconify/vue';
|
|
@@ -137,8 +141,8 @@ interface Props {
|
|
|
137
141
|
className?: string;
|
|
138
142
|
/** 欢迎页配置 */
|
|
139
143
|
welcomeConfig?: Partial<WelcomeConfig>;
|
|
140
|
-
/**
|
|
141
|
-
|
|
144
|
+
/** 自定义 Part 渲染器 - 根据 part.type 选择渲染组件(如 weather, stock 等) */
|
|
145
|
+
partRenderers?: Record<string, Component>;
|
|
142
146
|
/**
|
|
143
147
|
* 执行步骤折叠模式
|
|
144
148
|
* - 'open': 始终展开
|
|
@@ -156,7 +160,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
156
160
|
onClose: undefined,
|
|
157
161
|
className: '',
|
|
158
162
|
welcomeConfig: undefined,
|
|
159
|
-
|
|
163
|
+
partRenderers: () => ({}),
|
|
160
164
|
stepsExpandedType: 'auto',
|
|
161
165
|
});
|
|
162
166
|
|
|
@@ -184,6 +188,9 @@ const {
|
|
|
184
188
|
model,
|
|
185
189
|
webSearch,
|
|
186
190
|
thinking,
|
|
191
|
+
// 工具管理
|
|
192
|
+
allTools,
|
|
193
|
+
enabledTools,
|
|
187
194
|
loadSessions,
|
|
188
195
|
switchSession,
|
|
189
196
|
createNewSession,
|
|
@@ -204,15 +211,24 @@ const {
|
|
|
204
211
|
setWorkingDirectory,
|
|
205
212
|
autoRunConfig,
|
|
206
213
|
saveAutoRunConfig,
|
|
214
|
+
saveEnabledTools,
|
|
207
215
|
} = chat;
|
|
208
216
|
|
|
217
|
+
function handleCancelToolCall() {
|
|
218
|
+
cancelRequest()
|
|
219
|
+
}
|
|
220
|
+
|
|
209
221
|
// 消息容器引用
|
|
210
222
|
const messagesRef = ref<HTMLDivElement | null>(null);
|
|
211
223
|
|
|
212
224
|
// 是否应该自动滚动(用户在底部附近时才自动滚动)
|
|
213
225
|
const shouldAutoScroll = ref(true);
|
|
214
226
|
// 距离底部多少像素内算"在底部"
|
|
215
|
-
const SCROLL_THRESHOLD =
|
|
227
|
+
const SCROLL_THRESHOLD = 25;
|
|
228
|
+
// 上次滚动位置(用于检测滚动方向)
|
|
229
|
+
let lastScrollTop = 0;
|
|
230
|
+
// 是否正在程序化滚动(用于区分用户手动滚动)
|
|
231
|
+
let isProgrammaticScroll = false;
|
|
216
232
|
|
|
217
233
|
// ChatInput 引用
|
|
218
234
|
const chatInputRef = ref<InstanceType<typeof ChatInput> | null>(null);
|
|
@@ -291,8 +307,8 @@ provide('chatInputState', {
|
|
|
291
307
|
setThinking,
|
|
292
308
|
});
|
|
293
309
|
|
|
294
|
-
// Provide
|
|
295
|
-
provide('
|
|
310
|
+
// Provide 自定义 Part 渲染器(用于 PartsRenderer)
|
|
311
|
+
provide('partRenderers', props.partRenderers);
|
|
296
312
|
|
|
297
313
|
// 初始化
|
|
298
314
|
onMounted(() => {
|
|
@@ -308,17 +324,45 @@ function isNearBottom(): boolean {
|
|
|
308
324
|
return scrollHeight - scrollTop - clientHeight < SCROLL_THRESHOLD;
|
|
309
325
|
}
|
|
310
326
|
|
|
311
|
-
//
|
|
327
|
+
// 处理滚动事件(优化:检测滚动方向,区分程序化滚动)
|
|
312
328
|
function handleScroll() {
|
|
313
|
-
|
|
329
|
+
if (!messagesRef.value) return;
|
|
330
|
+
|
|
331
|
+
// 忽略程序化滚动触发的事件
|
|
332
|
+
if (isProgrammaticScroll) return;
|
|
333
|
+
|
|
334
|
+
const { scrollTop } = messagesRef.value;
|
|
335
|
+
const isScrollingUp = scrollTop < lastScrollTop;
|
|
336
|
+
lastScrollTop = scrollTop;
|
|
337
|
+
|
|
338
|
+
// 用户向上滚动时,立即停止自动滚动
|
|
339
|
+
if (isScrollingUp) {
|
|
340
|
+
shouldAutoScroll.value = false;
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 用户向下滚动到底部附近时,恢复自动滚动
|
|
345
|
+
if (isNearBottom()) {
|
|
346
|
+
shouldAutoScroll.value = true;
|
|
347
|
+
}
|
|
314
348
|
}
|
|
315
349
|
|
|
316
350
|
// 滚动到底部
|
|
317
351
|
async function scrollToBottom(force = false) {
|
|
318
352
|
await nextTick();
|
|
319
353
|
if (messagesRef.value && (force || shouldAutoScroll.value)) {
|
|
354
|
+
// 标记为程序化滚动,避免触发 handleScroll 逻辑
|
|
355
|
+
isProgrammaticScroll = true;
|
|
320
356
|
messagesRef.value.scrollTop = messagesRef.value.scrollHeight;
|
|
321
|
-
|
|
357
|
+
lastScrollTop = messagesRef.value.scrollTop;
|
|
358
|
+
// 强制滚动时才恢复自动滚动
|
|
359
|
+
if (force) {
|
|
360
|
+
shouldAutoScroll.value = true;
|
|
361
|
+
}
|
|
362
|
+
// 延迟重置标志,确保 scroll 事件已处理
|
|
363
|
+
requestAnimationFrame(() => {
|
|
364
|
+
isProgrammaticScroll = false;
|
|
365
|
+
});
|
|
322
366
|
}
|
|
323
367
|
}
|
|
324
368
|
|
|
@@ -340,8 +384,10 @@ watch(isLoading, (loading, prevLoading) => {
|
|
|
340
384
|
});
|
|
341
385
|
|
|
342
386
|
// 发送消息
|
|
343
|
-
function handleSend(text: string) {
|
|
344
|
-
|
|
387
|
+
function handleSend(text: string, images?: ImageData[]) {
|
|
388
|
+
// 将 ImageData[] 转换为 string[] (data URL)
|
|
389
|
+
const imageUrls = images?.map(img => `data:${img.mimeType};base64,${img.base64}`)
|
|
390
|
+
sendMessage(text, imageUrls);
|
|
345
391
|
}
|
|
346
392
|
|
|
347
393
|
// 快捷操作
|
|
@@ -360,6 +406,15 @@ function handleAtContext() {
|
|
|
360
406
|
console.log('@ 上下文');
|
|
361
407
|
}
|
|
362
408
|
|
|
409
|
+
// 创建新会话(重置输入框状态)
|
|
410
|
+
async function handleNewSession() {
|
|
411
|
+
await createNewSession();
|
|
412
|
+
// 重置输入框状态
|
|
413
|
+
nextTick(() => {
|
|
414
|
+
chatInputRef.value?.clear();
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
363
418
|
// 关闭
|
|
364
419
|
function handleClose() {
|
|
365
420
|
if (props.onClose) {
|
|
@@ -438,6 +493,10 @@ async function handleSaveSettings(config: AutoRunConfig) {
|
|
|
438
493
|
}
|
|
439
494
|
}
|
|
440
495
|
|
|
496
|
+
function handleUpdateEnabledTools(tools: string[] | undefined) {
|
|
497
|
+
saveEnabledTools(tools);
|
|
498
|
+
}
|
|
499
|
+
|
|
441
500
|
// 模式变更现在在 ToolCallPart 中处理,不再需要此函数
|
|
442
501
|
|
|
443
502
|
// 暴露给外部的方法
|
|
@@ -487,6 +546,9 @@ defineExpose({
|
|
|
487
546
|
overflow-y: auto;
|
|
488
547
|
padding: 12px;
|
|
489
548
|
scroll-behavior: smooth;
|
|
549
|
+
display: flex;
|
|
550
|
+
flex-direction: column;
|
|
551
|
+
gap: 8px;
|
|
490
552
|
}
|
|
491
553
|
|
|
492
554
|
/* 滚动到底部按钮 */
|
|
@@ -45,6 +45,47 @@
|
|
|
45
45
|
/>
|
|
46
46
|
</div>
|
|
47
47
|
</div>
|
|
48
|
+
|
|
49
|
+
<!-- 工具管理 -->
|
|
50
|
+
<div class="setting-section">
|
|
51
|
+
<div class="setting-section-header">
|
|
52
|
+
<div class="setting-info">
|
|
53
|
+
<div class="setting-label">工具管理</div>
|
|
54
|
+
<div class="setting-description">控制哪些工具可以被 AI 使用</div>
|
|
55
|
+
</div>
|
|
56
|
+
<button
|
|
57
|
+
class="disable-all-btn"
|
|
58
|
+
@click="isAllToolsDisabled ? handleEnableAllTools() : handleDisableAllTools()"
|
|
59
|
+
:disabled="!allTools || allTools.length === 0"
|
|
60
|
+
>
|
|
61
|
+
{{ isAllToolsDisabled ? '启用所有工具' : '禁用所有工具' }}
|
|
62
|
+
</button>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="tools-list">
|
|
65
|
+
<template v-if="allTools && allTools.length > 0">
|
|
66
|
+
<div
|
|
67
|
+
v-for="tool in allTools"
|
|
68
|
+
:key="tool.name"
|
|
69
|
+
class="tool-item"
|
|
70
|
+
>
|
|
71
|
+
<div class="tool-info">
|
|
72
|
+
<div class="tool-name">{{ tool.name }}</div>
|
|
73
|
+
<div class="tool-description">{{ tool.description }}</div>
|
|
74
|
+
</div>
|
|
75
|
+
<ToggleSwitch
|
|
76
|
+
:checked="isToolEnabled(tool.name)"
|
|
77
|
+
@change="(checked) => handleToolToggle(tool.name, checked)"
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
</template>
|
|
81
|
+
<div v-else class="no-tools">
|
|
82
|
+
<div>暂无可用工具</div>
|
|
83
|
+
<div style="font-size: 12px; color: var(--chat-text-muted, #888); margin-top: 8px;">
|
|
84
|
+
工具需要在创建 Electron Bridge 时通过 <code>tools</code> 参数注入
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
48
89
|
</template>
|
|
49
90
|
|
|
50
91
|
<!-- 索引与文档设置 -->
|
|
@@ -69,8 +110,15 @@ import IndexingSettings from './IndexingSettings.vue'
|
|
|
69
110
|
const visible = defineModel<boolean>('visible', { required: true })
|
|
70
111
|
const config = defineModel<AutoRunConfig>('config', { required: true })
|
|
71
112
|
|
|
113
|
+
// Props:工具相关
|
|
114
|
+
const props = defineProps<{
|
|
115
|
+
allTools?: Array<{ name: string; description: string }>
|
|
116
|
+
enabledTools?: string[] | undefined
|
|
117
|
+
}>()
|
|
118
|
+
|
|
72
119
|
const emit = defineEmits<{
|
|
73
120
|
save: [config: AutoRunConfig]
|
|
121
|
+
'update:enabledTools': [tools: string[] | undefined]
|
|
74
122
|
}>()
|
|
75
123
|
|
|
76
124
|
// 设置分类
|
|
@@ -102,6 +150,52 @@ function updateConfig<K extends keyof AutoRunConfig>(key: K, value: AutoRunConfi
|
|
|
102
150
|
function handleModeChange(value: string) {
|
|
103
151
|
updateConfig('mode', value as AutoRunMode)
|
|
104
152
|
}
|
|
153
|
+
|
|
154
|
+
/** 判断工具是否启用 */
|
|
155
|
+
function isToolEnabled(toolName: string): boolean {
|
|
156
|
+
// undefined 表示全部启用
|
|
157
|
+
if (props.enabledTools === undefined) return true
|
|
158
|
+
return props.enabledTools.includes(toolName)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** 处理工具开关切换 */
|
|
162
|
+
function handleToolToggle(toolName: string, checked: boolean) {
|
|
163
|
+
const allToolNames = (props.allTools ?? []).map((t) => t.name)
|
|
164
|
+
let newEnabledTools: string[] | undefined
|
|
165
|
+
|
|
166
|
+
// undefined 表示“全部启用”,因此第一次“禁用某个工具”需要从全量集合扣掉该工具
|
|
167
|
+
if (props.enabledTools === undefined) {
|
|
168
|
+
if (checked) return
|
|
169
|
+
newEnabledTools = allToolNames.filter((n) => n !== toolName)
|
|
170
|
+
emit('update:enabledTools', newEnabledTools)
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const set = new Set(props.enabledTools)
|
|
175
|
+
if (checked) set.add(toolName)
|
|
176
|
+
else set.delete(toolName)
|
|
177
|
+
|
|
178
|
+
// 如果所有工具都启用,回到 undefined(更符合默认语义)
|
|
179
|
+
if (allToolNames.length > 0 && set.size === allToolNames.length) {
|
|
180
|
+
newEnabledTools = undefined
|
|
181
|
+
} else {
|
|
182
|
+
newEnabledTools = Array.from(set)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
emit('update:enabledTools', newEnabledTools)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** 一键禁用所有工具 */
|
|
189
|
+
function handleDisableAllTools() {
|
|
190
|
+
emit('update:enabledTools', [])
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** 一键启用所有工具(恢复默认:undefined 表示全部启用) */
|
|
194
|
+
function handleEnableAllTools() {
|
|
195
|
+
emit('update:enabledTools', undefined)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const isAllToolsDisabled = computed(() => props.enabledTools !== undefined && props.enabledTools.length === 0)
|
|
105
199
|
</script>
|
|
106
200
|
|
|
107
201
|
<style scoped>
|
|
@@ -290,4 +384,79 @@ function handleModeChange(value: string) {
|
|
|
290
384
|
.setting-control {
|
|
291
385
|
flex-shrink: 0;
|
|
292
386
|
}
|
|
387
|
+
|
|
388
|
+
.setting-section {
|
|
389
|
+
margin-top: 24px;
|
|
390
|
+
padding-top: 24px;
|
|
391
|
+
border-top: 1px solid var(--chat-border, #333);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.setting-section-header {
|
|
395
|
+
display: flex;
|
|
396
|
+
align-items: center;
|
|
397
|
+
justify-content: space-between;
|
|
398
|
+
margin-bottom: 16px;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.disable-all-btn {
|
|
402
|
+
padding: 6px 12px;
|
|
403
|
+
background: transparent;
|
|
404
|
+
border: 1px solid var(--chat-border, #333);
|
|
405
|
+
border-radius: 6px;
|
|
406
|
+
color: var(--chat-text, #fff);
|
|
407
|
+
font-size: 13px;
|
|
408
|
+
cursor: pointer;
|
|
409
|
+
transition: all 0.15s;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
.disable-all-btn:hover:not(:disabled) {
|
|
413
|
+
background: var(--chat-muted, #2a2a2a);
|
|
414
|
+
border-color: var(--chat-text-muted, #888);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.disable-all-btn:disabled {
|
|
418
|
+
opacity: 0.5;
|
|
419
|
+
cursor: not-allowed;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
.tools-list {
|
|
423
|
+
display: flex;
|
|
424
|
+
flex-direction: column;
|
|
425
|
+
gap: 12px;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.tool-item {
|
|
429
|
+
display: flex;
|
|
430
|
+
align-items: center;
|
|
431
|
+
justify-content: space-between;
|
|
432
|
+
padding: 12px;
|
|
433
|
+
background: var(--chat-muted, #2a2a2a);
|
|
434
|
+
border: 1px solid var(--chat-border, #333);
|
|
435
|
+
border-radius: 8px;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
.tool-info {
|
|
439
|
+
flex: 1;
|
|
440
|
+
margin-right: 16px;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.tool-name {
|
|
444
|
+
font-size: 14px;
|
|
445
|
+
font-weight: 500;
|
|
446
|
+
color: var(--chat-text, #fff);
|
|
447
|
+
margin-bottom: 4px;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.tool-description {
|
|
451
|
+
font-size: 12px;
|
|
452
|
+
color: var(--chat-text-muted, #888);
|
|
453
|
+
line-height: 1.4;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.no-tools {
|
|
457
|
+
padding: 24px;
|
|
458
|
+
text-align: center;
|
|
459
|
+
color: var(--chat-text-muted, #888);
|
|
460
|
+
font-size: 14px;
|
|
461
|
+
}
|
|
293
462
|
</style>
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
:placeholder="placeholder"
|
|
44
44
|
rows="1"
|
|
45
45
|
class="input-field chat-scrollbar"
|
|
46
|
+
:readonly="voiceIsRecording"
|
|
46
47
|
spellcheck="false"
|
|
47
48
|
autocorrect="off"
|
|
48
49
|
autocomplete="off"
|
|
@@ -69,7 +70,7 @@
|
|
|
69
70
|
<!-- 模型选择(使用后端返回的分组数据) -->
|
|
70
71
|
<DropdownSelector
|
|
71
72
|
:value="model"
|
|
72
|
-
:
|
|
73
|
+
:options="modelOptions"
|
|
73
74
|
:container-ref="containerRef"
|
|
74
75
|
@select="selectModel"
|
|
75
76
|
/>
|
|
@@ -77,8 +78,9 @@
|
|
|
77
78
|
|
|
78
79
|
<!-- 右侧:功能按钮 -->
|
|
79
80
|
<div class="input-right">
|
|
80
|
-
<!--
|
|
81
|
+
<!-- 深度思考(ask 模式隐藏,仅支持的模型显示) -->
|
|
81
82
|
<button
|
|
83
|
+
v-if="mode !== 'ask' && currentModelSupportsThinking"
|
|
82
84
|
:class="['toggle-btn', { active: thinkingEnabled }]"
|
|
83
85
|
title="深度思考"
|
|
84
86
|
@click="$emit('update:thinking', !thinkingEnabled)"
|
|
@@ -86,8 +88,9 @@
|
|
|
86
88
|
<Icon icon="lucide:sparkles" width="18" />
|
|
87
89
|
</button>
|
|
88
90
|
|
|
89
|
-
<!--
|
|
91
|
+
<!-- 联网搜索(ask 模式隐藏) -->
|
|
90
92
|
<button
|
|
93
|
+
v-if="mode !== 'ask'"
|
|
91
94
|
:class="['toggle-btn', { active: webSearchEnabled }]"
|
|
92
95
|
title="联网搜索"
|
|
93
96
|
@click="$emit('update:webSearch', !webSearchEnabled)"
|
|
@@ -131,7 +134,7 @@
|
|
|
131
134
|
:class="{ connecting: voiceInput.status.value === 'connecting', recording: voiceIsRecording }"
|
|
132
135
|
:title="voiceInput.status.value === 'connecting' ? '正在连接,点击取消' : voiceIsRecording ? '点击停止' : '点击录音'"
|
|
133
136
|
@click.prevent="toggleVoiceInput"
|
|
134
|
-
:disabled="isLoading"
|
|
137
|
+
:disabled="isLoading || !props.adapter"
|
|
135
138
|
>
|
|
136
139
|
<Icon v-if="voiceInput.status.value === 'connecting'" icon="lucide:loader-2" width="18" class="spin" />
|
|
137
140
|
<Icon v-else-if="voiceIsRecording" icon="streamline-flex:button-pause-circle-remix" width="18" />
|
|
@@ -141,7 +144,7 @@
|
|
|
141
144
|
<button
|
|
142
145
|
class="send-btn"
|
|
143
146
|
:class="{ loading: isLoading }"
|
|
144
|
-
:disabled="(!inputText.trim() && !imageUpload.hasImages.value) || voiceIsRecording || voiceInput.status.value === 'connecting'"
|
|
147
|
+
:disabled="(!inputText.trim() && !imageUpload.hasImages.value && !isLoading) || voiceIsRecording || voiceInput.status.value === 'connecting'"
|
|
145
148
|
:title="
|
|
146
149
|
isLoading
|
|
147
150
|
? '停止'
|
|
@@ -156,7 +159,7 @@
|
|
|
156
159
|
@click="handleSendOrCancel"
|
|
157
160
|
>
|
|
158
161
|
<Icon v-if="isLoading" icon="streamline-flex:button-pause-circle-remix" width="18" />
|
|
159
|
-
<Icon v-else icon="
|
|
162
|
+
<Icon v-else icon="uil:message" width="18" />
|
|
160
163
|
</button>
|
|
161
164
|
</div>
|
|
162
165
|
</div>
|
|
@@ -171,7 +174,7 @@ import { Icon } from '@iconify/vue';
|
|
|
171
174
|
import type { ChatMode, ModelOption } from '../../types';
|
|
172
175
|
import AtFilePicker from './AtFilePicker.vue';
|
|
173
176
|
import ImagePreviewModal from './ImagePreviewModal.vue';
|
|
174
|
-
import DropdownSelector, { type GroupedOptions } from './DropdownSelector.vue';
|
|
177
|
+
import DropdownSelector, { type GroupedOptions, type DropdownOption } from './DropdownSelector.vue';
|
|
175
178
|
import type { ChatAdapter, ImageData } from '../../adapter';
|
|
176
179
|
import { useImageUpload } from '../../composables/useImageUpload';
|
|
177
180
|
import { useVoiceToTextInput } from '../../composables/useVoiceToTextInput';
|
|
@@ -182,12 +185,14 @@ const props = withDefaults(
|
|
|
182
185
|
variant?: 'input' | 'message';
|
|
183
186
|
/** 受控值(用于历史消息编辑) */
|
|
184
187
|
value?: string;
|
|
188
|
+
/** 图片数据(用于历史消息编辑) */
|
|
189
|
+
images?: string[];
|
|
185
190
|
isLoading?: boolean;
|
|
186
191
|
/** 由宿主显式传入(@ 面板需要) */
|
|
187
192
|
adapter?: ChatAdapter;
|
|
188
193
|
mode?: ChatMode;
|
|
189
194
|
model?: string;
|
|
190
|
-
/**
|
|
195
|
+
/** 模型列表(tooltip 由后端下发,前端仅透传渲染) */
|
|
191
196
|
models?: ModelOption[];
|
|
192
197
|
webSearchEnabled?: boolean;
|
|
193
198
|
thinkingEnabled?: boolean;
|
|
@@ -195,6 +200,7 @@ const props = withDefaults(
|
|
|
195
200
|
{
|
|
196
201
|
variant: 'input',
|
|
197
202
|
value: '',
|
|
203
|
+
images: () => [],
|
|
198
204
|
isLoading: false,
|
|
199
205
|
adapter: undefined,
|
|
200
206
|
mode: 'agent',
|
|
@@ -326,37 +332,39 @@ watch(
|
|
|
326
332
|
}
|
|
327
333
|
);
|
|
328
334
|
|
|
335
|
+
// 同步外部 images(用于历史消息编辑)
|
|
336
|
+
watch(
|
|
337
|
+
() => props.images,
|
|
338
|
+
(newImages) => {
|
|
339
|
+
if (newImages.length > 0) {
|
|
340
|
+
imageUpload.initImages(newImages);
|
|
341
|
+
} else {
|
|
342
|
+
imageUpload.clearImages();
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
{ immediate: true }
|
|
346
|
+
);
|
|
347
|
+
|
|
329
348
|
// 模式选项(用于 DropdownSelector)
|
|
330
349
|
const modeOptions = computed(() => [
|
|
331
350
|
{ value: 'agent', label: 'Agent', icon: 'lucide:zap' },
|
|
332
351
|
{ value: 'ask', label: 'Ask', icon: 'lucide:message-circle' },
|
|
333
352
|
]);
|
|
334
353
|
|
|
335
|
-
// 模型选项(用于 DropdownSelector
|
|
336
|
-
//
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
value: m.modelId,
|
|
350
|
-
label: m.displayName,
|
|
351
|
-
});
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
// 对每个分组内的选项进行排序
|
|
355
|
-
Object.keys(groups).forEach(groupName => {
|
|
356
|
-
groups[groupName].sort((a, b) => a.label.localeCompare(b.label));
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
return groups;
|
|
354
|
+
// 模型选项(用于 DropdownSelector)
|
|
355
|
+
// 模型选项:tooltip/能力由后端(ai-chat-core)统一提供,前端只负责渲染
|
|
356
|
+
const modelOptions = computed<DropdownOption[]>(() =>
|
|
357
|
+
props.models.map((m) => ({
|
|
358
|
+
value: m.modelId,
|
|
359
|
+
label: m.displayName,
|
|
360
|
+
tooltip: m.tooltip,
|
|
361
|
+
}))
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
// 当前模型是否支持深度思考
|
|
365
|
+
const currentModelSupportsThinking = computed(() => {
|
|
366
|
+
const currentModel = props.models.find(m => m.modelId === props.model);
|
|
367
|
+
return currentModel?.supportsThinking ?? false;
|
|
360
368
|
});
|
|
361
369
|
|
|
362
370
|
// 选择模式
|
|
@@ -494,6 +502,14 @@ defineExpose({
|
|
|
494
502
|
clear: () => {
|
|
495
503
|
inputText.value = '';
|
|
496
504
|
imageUpload.clearImages();
|
|
505
|
+
// 重置语音输入状态
|
|
506
|
+
if (voiceInput.status.value !== 'idle') {
|
|
507
|
+
voiceInput.cancel();
|
|
508
|
+
}
|
|
509
|
+
// 重置 @ 选择器
|
|
510
|
+
atPickerVisible.value = false;
|
|
511
|
+
replaceRange.value = null;
|
|
512
|
+
// 重置输入框高度
|
|
497
513
|
if (inputRef.value) {
|
|
498
514
|
inputRef.value.style.height = 'auto';
|
|
499
515
|
}
|
|
@@ -512,7 +528,6 @@ defineExpose({
|
|
|
512
528
|
|
|
513
529
|
.chat-input.message-variant {
|
|
514
530
|
padding: 0;
|
|
515
|
-
margin-bottom: 16px;
|
|
516
531
|
}
|
|
517
532
|
|
|
518
533
|
.input-container {
|