@huyooo/ai-chat-frontend-vue 0.1.4 → 0.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.
- package/README.md +367 -0
- package/dist/adapter.d.ts +7 -7
- package/dist/adapter.d.ts.map +1 -1
- package/dist/components/ChatPanel.vue.d.ts +120 -9
- package/dist/components/ChatPanel.vue.d.ts.map +1 -1
- package/dist/components/common/ConfirmDialog.vue.d.ts +30 -0
- package/dist/components/common/ConfirmDialog.vue.d.ts.map +1 -0
- package/dist/components/common/CopyButton.vue.d.ts +18 -0
- package/dist/components/common/CopyButton.vue.d.ts.map +1 -0
- package/dist/components/common/IndexingSettings.vue.d.ts +3 -0
- package/dist/components/common/IndexingSettings.vue.d.ts.map +1 -0
- package/dist/components/common/SettingsPanel.vue.d.ts +16 -0
- package/dist/components/common/SettingsPanel.vue.d.ts.map +1 -0
- package/dist/components/common/Toast.vue.d.ts +18 -0
- package/dist/components/common/Toast.vue.d.ts.map +1 -0
- package/dist/components/common/ToggleSwitch.vue.d.ts +10 -0
- package/dist/components/common/ToggleSwitch.vue.d.ts.map +1 -0
- package/dist/components/{chat/ui → header}/ChatHeader.vue.d.ts +5 -3
- package/dist/components/header/ChatHeader.vue.d.ts.map +1 -0
- package/dist/components/input/AtFilePicker.vue.d.ts +21 -0
- package/dist/components/input/AtFilePicker.vue.d.ts.map +1 -0
- package/dist/components/{ChatInput.vue.d.ts → input/ChatInput.vue.d.ts} +16 -14
- package/dist/components/input/ChatInput.vue.d.ts.map +1 -0
- package/dist/components/input/DropdownSelector.vue.d.ts +42 -0
- package/dist/components/input/DropdownSelector.vue.d.ts.map +1 -0
- package/dist/components/input/ImagePreviewModal.vue.d.ts +17 -0
- package/dist/components/input/ImagePreviewModal.vue.d.ts.map +1 -0
- package/dist/components/input/at-views/AtBranchView.vue.d.ts +18 -0
- package/dist/components/input/at-views/AtBranchView.vue.d.ts.map +1 -0
- package/dist/components/input/at-views/AtBrowserView.vue.d.ts +18 -0
- package/dist/components/input/at-views/AtBrowserView.vue.d.ts.map +1 -0
- package/dist/components/input/at-views/AtChatsView.vue.d.ts +18 -0
- package/dist/components/input/at-views/AtChatsView.vue.d.ts.map +1 -0
- package/dist/components/input/at-views/AtDocsView.vue.d.ts +18 -0
- package/dist/components/input/at-views/AtDocsView.vue.d.ts.map +1 -0
- package/dist/components/input/at-views/AtFilesView.vue.d.ts +23 -0
- package/dist/components/input/at-views/AtFilesView.vue.d.ts.map +1 -0
- package/dist/components/input/at-views/AtTerminalsView.vue.d.ts +18 -0
- package/dist/components/input/at-views/AtTerminalsView.vue.d.ts.map +1 -0
- package/dist/components/message/MessageBubble.vue.d.ts +45 -0
- package/dist/components/message/MessageBubble.vue.d.ts.map +1 -0
- package/dist/components/message/PartsRenderer.vue.d.ts +15 -0
- package/dist/components/message/PartsRenderer.vue.d.ts.map +1 -0
- package/dist/components/message/WelcomeMessage.vue.d.ts +14 -0
- package/dist/components/message/WelcomeMessage.vue.d.ts.map +1 -0
- package/dist/components/message/blocks/CodeBlock.vue.d.ts +11 -0
- package/dist/components/message/blocks/CodeBlock.vue.d.ts.map +1 -0
- package/dist/components/{chat/SearchResultBlock.vue.d.ts → message/blocks/TextBlock.vue.d.ts} +3 -4
- package/dist/components/message/blocks/TextBlock.vue.d.ts.map +1 -0
- package/dist/components/message/blocks/index.d.ts +6 -0
- package/dist/components/message/blocks/index.d.ts.map +1 -0
- package/dist/components/message/parts/CollapsibleCard.vue.d.ts +45 -0
- package/dist/components/message/parts/CollapsibleCard.vue.d.ts.map +1 -0
- package/dist/components/{chat/ToolCallBlock.vue.d.ts → message/parts/ErrorPart.vue.d.ts} +4 -5
- package/dist/components/message/parts/ErrorPart.vue.d.ts.map +1 -0
- package/dist/components/{chat/ThinkingBlock.vue.d.ts → message/parts/ImagePart.vue.d.ts} +3 -3
- package/dist/components/message/parts/ImagePart.vue.d.ts.map +1 -0
- package/dist/components/message/parts/SearchPart.vue.d.ts +12 -0
- package/dist/components/message/parts/SearchPart.vue.d.ts.map +1 -0
- package/dist/components/{chat/messages/ExecutionSteps.vue.d.ts → message/parts/TextPart.vue.d.ts} +2 -9
- package/dist/components/message/parts/TextPart.vue.d.ts.map +1 -0
- package/dist/components/message/parts/ThinkingPart.vue.d.ts +12 -0
- package/dist/components/message/parts/ThinkingPart.vue.d.ts.map +1 -0
- package/dist/components/message/parts/ToolCallPart.vue.d.ts +19 -0
- package/dist/components/message/parts/ToolCallPart.vue.d.ts.map +1 -0
- package/dist/components/message/parts/ToolResultPart.vue.d.ts +14 -0
- package/dist/components/message/parts/ToolResultPart.vue.d.ts.map +1 -0
- package/dist/components/message/parts/index.d.ts +12 -0
- package/dist/components/message/parts/index.d.ts.map +1 -0
- package/dist/components/message/tool-results/DefaultToolResult.vue.d.ts +4 -0
- package/dist/components/message/tool-results/DefaultToolResult.vue.d.ts.map +1 -0
- package/dist/components/message/tool-results/SearchResults.vue.d.ts +4 -0
- package/dist/components/message/tool-results/SearchResults.vue.d.ts.map +1 -0
- package/dist/components/message/tool-results/WeatherCard.vue.d.ts +4 -0
- package/dist/components/message/tool-results/WeatherCard.vue.d.ts.map +1 -0
- package/dist/components/message/tool-results/index.d.ts +7 -0
- package/dist/components/message/tool-results/index.d.ts.map +1 -0
- package/dist/components/message/welcome-types.d.ts +28 -0
- package/dist/components/message/welcome-types.d.ts.map +1 -0
- package/dist/composables/useChat.d.ts +99 -44
- package/dist/composables/useChat.d.ts.map +1 -1
- package/dist/composables/useImageUpload.d.ts +55 -0
- package/dist/composables/useImageUpload.d.ts.map +1 -0
- package/dist/index.d.ts +28 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +55889 -1359
- package/dist/style.css +1 -1
- package/dist/types/index.d.ts +113 -53
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/fileIcon.d.ts +13 -0
- package/dist/utils/fileIcon.d.ts.map +1 -0
- package/package.json +12 -6
- package/src/adapter.ts +12 -70
- package/src/components/ChatPanel.vue +329 -110
- package/src/components/common/ConfirmDialog.vue +208 -0
- package/src/components/common/CopyButton.vue +71 -0
- package/src/components/common/IndexingSettings.vue +580 -0
- package/src/components/common/SettingsPanel.vue +293 -0
- package/src/components/common/Toast.vue +90 -0
- package/src/components/common/ToggleSwitch.vue +75 -0
- package/src/components/{chat/ui → header}/ChatHeader.vue +170 -93
- package/src/components/input/AtFilePicker.vue +657 -0
- package/src/components/input/ChatInput.vue +653 -0
- package/src/components/input/DropdownSelector.vue +322 -0
- package/src/components/input/ImagePreviewModal.vue +238 -0
- package/src/components/input/at-views/AtBranchView.vue +63 -0
- package/src/components/input/at-views/AtBrowserView.vue +63 -0
- package/src/components/input/at-views/AtChatsView.vue +63 -0
- package/src/components/input/at-views/AtDocsView.vue +63 -0
- package/src/components/input/at-views/AtFilesView.vue +255 -0
- package/src/components/input/at-views/AtTerminalsView.vue +63 -0
- package/src/components/message/ContentRenderer.vue +61 -0
- package/src/components/message/MessageBubble.vue +411 -0
- package/src/components/message/PartsRenderer.vue +101 -0
- package/src/components/message/ToolResultRenderer.vue +27 -0
- package/src/components/message/WelcomeMessage.vue +308 -0
- package/src/components/message/blocks/CodeBlock.vue +113 -0
- package/src/components/message/blocks/TextBlock.vue +21 -0
- package/src/components/message/blocks/index.ts +6 -0
- package/src/components/message/parts/CollapsibleCard.vue +135 -0
- package/src/components/message/parts/ErrorPart.vue +51 -0
- package/src/components/message/parts/ImagePart.vue +98 -0
- package/src/components/message/parts/SearchPart.vue +101 -0
- package/src/components/message/parts/TextPart.vue +28 -0
- package/src/components/message/parts/ThinkingPart.vue +54 -0
- package/src/components/message/parts/ToolCallPart.vue +460 -0
- package/src/components/message/parts/ToolResultPart.vue +78 -0
- package/src/components/message/parts/index.ts +13 -0
- package/src/components/message/tool-results/DefaultToolResult.vue +43 -0
- package/src/components/message/tool-results/SearchResults.vue +133 -0
- package/src/components/message/tool-results/WeatherCard.vue +139 -0
- package/src/components/message/tool-results/index.ts +7 -0
- package/src/components/message/welcome-types.ts +47 -0
- package/src/composables/useChat.ts +807 -155
- package/src/composables/useImageUpload.ts +228 -0
- package/src/index.ts +96 -49
- package/src/styles.css +47 -0
- package/src/types/index.ts +146 -98
- package/src/utils/fileIcon.ts +49 -0
- package/dist/components/ChatInput.vue.d.ts.map +0 -1
- package/dist/components/chat/SearchResultBlock.vue.d.ts.map +0 -1
- package/dist/components/chat/ThinkingBlock.vue.d.ts.map +0 -1
- package/dist/components/chat/ToolCallBlock.vue.d.ts.map +0 -1
- package/dist/components/chat/messages/ExecutionSteps.vue.d.ts.map +0 -1
- package/dist/components/chat/messages/MessageBubble.vue.d.ts +0 -28
- package/dist/components/chat/messages/MessageBubble.vue.d.ts.map +0 -1
- package/dist/components/chat/ui/ChatHeader.vue.d.ts.map +0 -1
- package/dist/components/chat/ui/WelcomeMessage.vue.d.ts +0 -7
- package/dist/components/chat/ui/WelcomeMessage.vue.d.ts.map +0 -1
- package/dist/preload/preload.d.ts +0 -6
- package/dist/preload/preload.d.ts.map +0 -1
- package/src/components/ChatInput.vue +0 -649
- package/src/components/chat/SearchResultBlock.vue +0 -155
- package/src/components/chat/ThinkingBlock.vue +0 -109
- package/src/components/chat/ToolCallBlock.vue +0 -213
- package/src/components/chat/messages/ExecutionSteps.vue +0 -281
- package/src/components/chat/messages/MessageBubble.vue +0 -272
- package/src/components/chat/ui/WelcomeMessage.vue +0 -135
- package/src/preload/preload.ts +0 -79
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="chat-panel">
|
|
3
|
+
<!-- 确认弹窗 -->
|
|
4
|
+
<ConfirmDialog
|
|
5
|
+
v-model:visible="confirmDialog.visible"
|
|
6
|
+
:title="confirmDialog.title"
|
|
7
|
+
:message="confirmDialog.message"
|
|
8
|
+
:type="confirmDialog.type"
|
|
9
|
+
:confirm-text="confirmDialog.confirmText"
|
|
10
|
+
@confirm="confirmDialog.onConfirm"
|
|
11
|
+
/>
|
|
12
|
+
|
|
13
|
+
<!-- Toast 消息 -->
|
|
14
|
+
<Toast v-model:visible="toast.visible" :message="toast.message" :type="toast.type" />
|
|
15
|
+
|
|
16
|
+
<!-- 工具批准现在内嵌在 ToolCallPart 中,不再需要全局对话框 -->
|
|
17
|
+
|
|
18
|
+
<!-- 设置面板 -->
|
|
19
|
+
<SettingsPanel
|
|
20
|
+
v-model:visible="settingsPanelVisible"
|
|
21
|
+
v-model:config="autoRunConfig"
|
|
22
|
+
@save="handleSaveSettings"
|
|
23
|
+
/>
|
|
24
|
+
|
|
3
25
|
<!-- 顶部标题栏 -->
|
|
4
26
|
<ChatHeader
|
|
5
27
|
v-if="!hideHeader"
|
|
@@ -9,6 +31,7 @@
|
|
|
9
31
|
@new-session="createNewSession"
|
|
10
32
|
@switch-session="switchSession"
|
|
11
33
|
@delete-session="deleteSession"
|
|
34
|
+
@hide-session="hideSession"
|
|
12
35
|
@close="handleClose"
|
|
13
36
|
@clear-all="handleClearAll"
|
|
14
37
|
@close-others="handleCloseOthers"
|
|
@@ -18,36 +41,49 @@
|
|
|
18
41
|
@settings="handleSettings"
|
|
19
42
|
/>
|
|
20
43
|
|
|
21
|
-
<!--
|
|
22
|
-
<div
|
|
23
|
-
<
|
|
24
|
-
v-if="messages.length === 0"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
<!-- 消息列表容器 -->
|
|
45
|
+
<div class="messages-wrapper">
|
|
46
|
+
<div ref="messagesRef" class="messages-container chat-scrollbar" @scroll="handleScroll">
|
|
47
|
+
<WelcomeMessage v-if="messages.length === 0" :config="props.welcomeConfig" @quick-action="handleQuickAction" />
|
|
48
|
+
<template v-else>
|
|
49
|
+
<MessageBubble
|
|
50
|
+
v-for="(msg, index) in messages"
|
|
51
|
+
:key="msg.id"
|
|
52
|
+
:role="msg.role"
|
|
53
|
+
:parts="msg.parts"
|
|
54
|
+
:model="msg.model"
|
|
55
|
+
:mode="msg.mode"
|
|
56
|
+
:images="msg.images"
|
|
57
|
+
:copied="msg.copied"
|
|
58
|
+
:loading="msg.loading"
|
|
59
|
+
:timestamp="msg.timestamp"
|
|
60
|
+
:steps-expanded-type="stepsExpandedType"
|
|
61
|
+
:adapter="props.adapter"
|
|
62
|
+
:auto-run-config="autoRunConfig"
|
|
63
|
+
:on-save-config="saveAutoRunConfig"
|
|
64
|
+
@copy="copyMessage(msg.id)"
|
|
65
|
+
@regenerate="regenerateMessage(index)"
|
|
66
|
+
@send="(text) => handleResend(index, text)"
|
|
67
|
+
/>
|
|
68
|
+
</template>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<!-- 滚动到底部按钮 -->
|
|
72
|
+
<Transition name="fade">
|
|
73
|
+
<button
|
|
74
|
+
v-if="!shouldAutoScroll && messages.length > 0"
|
|
75
|
+
class="scroll-to-bottom-btn"
|
|
76
|
+
@click="scrollToBottom(true)"
|
|
77
|
+
title="滚动到底部"
|
|
78
|
+
>
|
|
79
|
+
<Icon icon="lucide:arrow-down" width="16" />
|
|
80
|
+
</button>
|
|
81
|
+
</Transition>
|
|
46
82
|
</div>
|
|
47
83
|
|
|
48
84
|
<!-- 输入区域 -->
|
|
49
85
|
<ChatInput
|
|
50
|
-
|
|
86
|
+
ref="chatInputRef"
|
|
51
87
|
:is-loading="isLoading"
|
|
52
88
|
:mode="mode"
|
|
53
89
|
:model="model"
|
|
@@ -56,8 +92,6 @@
|
|
|
56
92
|
:thinking-enabled="thinking"
|
|
57
93
|
@send="handleSend"
|
|
58
94
|
@cancel="cancelRequest"
|
|
59
|
-
@remove-image="handleRemoveImage"
|
|
60
|
-
@upload-image="handleUploadImage"
|
|
61
95
|
@at-context="handleAtContext"
|
|
62
96
|
@update:mode="setMode"
|
|
63
97
|
@update:model="setModel"
|
|
@@ -68,48 +102,67 @@
|
|
|
68
102
|
</template>
|
|
69
103
|
|
|
70
104
|
<script setup lang="ts">
|
|
71
|
-
import { ref, watch, nextTick, onMounted, provide, toRef } from 'vue';
|
|
105
|
+
import { ref, computed, watch, nextTick, onMounted, provide, toRef, type Component } from 'vue';
|
|
72
106
|
import { useChat } from '../composables/useChat';
|
|
73
107
|
import type { ChatAdapter } from '../adapter';
|
|
74
|
-
import type {
|
|
75
|
-
import {
|
|
76
|
-
import
|
|
77
|
-
import
|
|
78
|
-
import
|
|
79
|
-
import
|
|
108
|
+
import type { ModelOption, ChatMode } from '../types';
|
|
109
|
+
import type { AutoRunConfig } from '@huyooo/ai-chat-bridge-electron/renderer';
|
|
110
|
+
import { Icon } from '@iconify/vue';
|
|
111
|
+
import ChatHeader from './header/ChatHeader.vue';
|
|
112
|
+
import WelcomeMessage from './message/WelcomeMessage.vue';
|
|
113
|
+
import MessageBubble from './message/MessageBubble.vue';
|
|
114
|
+
import ChatInput from './input/ChatInput.vue';
|
|
115
|
+
import ConfirmDialog from './common/ConfirmDialog.vue';
|
|
116
|
+
import Toast from './common/Toast.vue';
|
|
117
|
+
// ToolApprovalDialog 已移除,工具批准现在内嵌在 ToolCallPart 中
|
|
118
|
+
import SettingsPanel from './common/SettingsPanel.vue';
|
|
119
|
+
import type { ToolCompleteEvent } from '../composables/useChat';
|
|
120
|
+
import type { WelcomeConfig } from './message/welcome-types';
|
|
80
121
|
|
|
81
122
|
interface Props {
|
|
82
123
|
/** Adapter 实例 */
|
|
83
|
-
adapter
|
|
84
|
-
/** 工作目录 */
|
|
85
|
-
workingDir?: string;
|
|
124
|
+
adapter: ChatAdapter;
|
|
86
125
|
/** 默认模型 */
|
|
87
126
|
defaultModel?: string;
|
|
88
127
|
/** 默认模式 */
|
|
89
128
|
defaultMode?: ChatMode;
|
|
90
129
|
/** 可用模型列表 */
|
|
91
|
-
models?:
|
|
130
|
+
models?: ModelOption[];
|
|
92
131
|
/** 隐藏标题栏 */
|
|
93
132
|
hideHeader?: boolean;
|
|
94
133
|
/** 关闭回调(有此属性时显示关闭按钮) */
|
|
95
134
|
onClose?: () => void;
|
|
96
135
|
/** 自定义类名 */
|
|
97
136
|
className?: string;
|
|
137
|
+
/** 欢迎页配置 */
|
|
138
|
+
welcomeConfig?: Partial<WelcomeConfig>;
|
|
139
|
+
/** 自定义工具结果渲染器 - 根据工具名称选择渲染组件 */
|
|
140
|
+
toolRenderers?: Record<string, Component>;
|
|
141
|
+
/**
|
|
142
|
+
* 执行步骤折叠模式
|
|
143
|
+
* - 'open': 始终展开
|
|
144
|
+
* - 'close': 始终折叠
|
|
145
|
+
* - 'auto': 执行时展开,完成后折叠
|
|
146
|
+
*/
|
|
147
|
+
stepsExpandedType?: 'open' | 'close' | 'auto';
|
|
98
148
|
}
|
|
99
149
|
|
|
100
150
|
const props = withDefaults(defineProps<Props>(), {
|
|
101
|
-
adapter: undefined,
|
|
102
|
-
workingDir: undefined,
|
|
103
151
|
defaultModel: 'anthropic/claude-opus-4.5',
|
|
104
152
|
defaultMode: 'agent',
|
|
105
|
-
models: () =>
|
|
153
|
+
models: () => [],
|
|
106
154
|
hideHeader: false,
|
|
107
155
|
onClose: undefined,
|
|
108
156
|
className: '',
|
|
157
|
+
welcomeConfig: undefined,
|
|
158
|
+
toolRenderers: () => ({}),
|
|
159
|
+
stepsExpandedType: 'auto',
|
|
109
160
|
});
|
|
110
161
|
|
|
111
162
|
const emit = defineEmits<{
|
|
112
163
|
close: [];
|
|
164
|
+
/** 工具执行完成事件 */
|
|
165
|
+
'tool-complete': [event: ToolCompleteEvent];
|
|
113
166
|
}>();
|
|
114
167
|
|
|
115
168
|
// 使用 useChat composable
|
|
@@ -117,6 +170,7 @@ const chat = useChat({
|
|
|
117
170
|
adapter: props.adapter,
|
|
118
171
|
defaultModel: props.defaultModel,
|
|
119
172
|
defaultMode: props.defaultMode,
|
|
173
|
+
onToolComplete: (event) => emit('tool-complete', event),
|
|
120
174
|
});
|
|
121
175
|
|
|
122
176
|
// 解构状态
|
|
@@ -133,62 +187,160 @@ const {
|
|
|
133
187
|
switchSession,
|
|
134
188
|
createNewSession,
|
|
135
189
|
deleteSession,
|
|
190
|
+
hideSession,
|
|
191
|
+
clearAllSessions,
|
|
192
|
+
hideOtherSessions,
|
|
193
|
+
exportCurrentSession,
|
|
136
194
|
sendMessage,
|
|
137
195
|
cancelRequest,
|
|
138
196
|
copyMessage,
|
|
139
197
|
regenerateMessage,
|
|
198
|
+
resendFromIndex,
|
|
140
199
|
setMode,
|
|
141
200
|
setModel,
|
|
142
201
|
setWebSearch,
|
|
143
202
|
setThinking,
|
|
144
203
|
setWorkingDirectory,
|
|
204
|
+
autoRunConfig,
|
|
205
|
+
saveAutoRunConfig,
|
|
145
206
|
} = chat;
|
|
146
207
|
|
|
147
|
-
// 选中的图片
|
|
148
|
-
const selectedImages = ref<string[]>([]);
|
|
149
|
-
|
|
150
208
|
// 消息容器引用
|
|
151
209
|
const messagesRef = ref<HTMLDivElement | null>(null);
|
|
152
210
|
|
|
153
|
-
//
|
|
154
|
-
const
|
|
211
|
+
// 是否应该自动滚动(用户在底部附近时才自动滚动)
|
|
212
|
+
const shouldAutoScroll = ref(true);
|
|
213
|
+
// 距离底部多少像素内算"在底部"
|
|
214
|
+
const SCROLL_THRESHOLD = 100;
|
|
215
|
+
|
|
216
|
+
// ChatInput 引用
|
|
217
|
+
const chatInputRef = ref<InstanceType<typeof ChatInput> | null>(null);
|
|
218
|
+
|
|
219
|
+
// 设置面板状态
|
|
220
|
+
const settingsPanelVisible = ref(false);
|
|
221
|
+
|
|
222
|
+
// 确认弹窗状态
|
|
223
|
+
const confirmDialog = ref({
|
|
224
|
+
visible: false,
|
|
225
|
+
title: '确认',
|
|
226
|
+
message: '',
|
|
227
|
+
type: 'warning' as 'info' | 'warning' | 'danger',
|
|
228
|
+
confirmText: '确定',
|
|
229
|
+
onConfirm: () => {},
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Toast 消息状态
|
|
233
|
+
const toast = ref({
|
|
234
|
+
visible: false,
|
|
235
|
+
message: '',
|
|
236
|
+
type: 'info' as 'info' | 'success' | 'warning' | 'error',
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
/** 显示 Toast 消息 */
|
|
240
|
+
function showToast(message: string, type: 'info' | 'success' | 'warning' | 'error' = 'info') {
|
|
241
|
+
toast.value = { visible: true, message, type };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** 显示确认弹窗 */
|
|
245
|
+
function showConfirm(options: {
|
|
246
|
+
title?: string;
|
|
247
|
+
message: string;
|
|
248
|
+
type?: 'info' | 'warning' | 'danger';
|
|
249
|
+
confirmText?: string;
|
|
250
|
+
onConfirm: () => void;
|
|
251
|
+
}) {
|
|
252
|
+
confirmDialog.value = {
|
|
253
|
+
visible: true,
|
|
254
|
+
title: options.title || '确认',
|
|
255
|
+
message: options.message,
|
|
256
|
+
type: options.type || 'warning',
|
|
257
|
+
confirmText: options.confirmText || '确定',
|
|
258
|
+
onConfirm: options.onConfirm,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 可用模型(如果没有传入,从后端获取)
|
|
263
|
+
const models = ref<ModelOption[]>(props.models || []);
|
|
264
|
+
|
|
265
|
+
// 从后端获取模型列表
|
|
266
|
+
onMounted(async () => {
|
|
267
|
+
try {
|
|
268
|
+
models.value = await props.adapter.getModels();
|
|
269
|
+
} catch (err) {
|
|
270
|
+
console.warn('获取模型列表失败:', err);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
155
273
|
|
|
156
|
-
//
|
|
157
|
-
|
|
274
|
+
// 注意:cwd 已解耦,不再通过 prop 传递
|
|
275
|
+
// FileBrowser 会直接通过 adapter.setCwd() 同步到 Agent
|
|
276
|
+
// getCwdTool 会自动从 Agent 的 context.cwd 读取最新值
|
|
277
|
+
// 如果需要 cwd 给子组件使用,可以通过 getCwdTool 获取
|
|
278
|
+
|
|
279
|
+
// Provide 全局 input 状态给子组件(让 message 中的 input 也能修改)
|
|
280
|
+
provide('chatInputState', {
|
|
281
|
+
mode,
|
|
282
|
+
model,
|
|
283
|
+
webSearch,
|
|
284
|
+
thinking,
|
|
285
|
+
models,
|
|
286
|
+
isLoading,
|
|
287
|
+
setMode,
|
|
288
|
+
setModel,
|
|
289
|
+
setWebSearch,
|
|
290
|
+
setThinking,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Provide 自定义工具渲染器(用于 ToolResultPart 组件)
|
|
294
|
+
provide('toolRenderers', props.toolRenderers);
|
|
158
295
|
|
|
159
296
|
// 初始化
|
|
160
297
|
onMounted(() => {
|
|
161
298
|
loadSessions();
|
|
162
299
|
});
|
|
163
300
|
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
301
|
+
// 注意:cwd 已解耦,不再需要监听 prop 变化
|
|
302
|
+
|
|
303
|
+
// 检查是否在底部附近
|
|
304
|
+
function isNearBottom(): boolean {
|
|
305
|
+
if (!messagesRef.value) return true;
|
|
306
|
+
const { scrollTop, scrollHeight, clientHeight } = messagesRef.value;
|
|
307
|
+
return scrollHeight - scrollTop - clientHeight < SCROLL_THRESHOLD;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// 处理滚动事件
|
|
311
|
+
function handleScroll() {
|
|
312
|
+
shouldAutoScroll.value = isNearBottom();
|
|
313
|
+
}
|
|
174
314
|
|
|
175
315
|
// 滚动到底部
|
|
176
|
-
async function scrollToBottom() {
|
|
316
|
+
async function scrollToBottom(force = false) {
|
|
177
317
|
await nextTick();
|
|
178
|
-
if (messagesRef.value) {
|
|
318
|
+
if (messagesRef.value && (force || shouldAutoScroll.value)) {
|
|
179
319
|
messagesRef.value.scrollTop = messagesRef.value.scrollHeight;
|
|
320
|
+
shouldAutoScroll.value = true;
|
|
180
321
|
}
|
|
181
322
|
}
|
|
182
323
|
|
|
183
|
-
//
|
|
184
|
-
watch(
|
|
185
|
-
|
|
186
|
-
|
|
324
|
+
// 消息变化时滚动(只在用户在底部时)
|
|
325
|
+
watch(
|
|
326
|
+
messages,
|
|
327
|
+
() => {
|
|
328
|
+
scrollToBottom();
|
|
329
|
+
},
|
|
330
|
+
{ flush: 'post', deep: true }
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
// 发送新消息时强制滚动到底部
|
|
334
|
+
watch(isLoading, (loading, prevLoading) => {
|
|
335
|
+
// 开始加载时(发送消息时)强制滚动到底部
|
|
336
|
+
if (loading && !prevLoading) {
|
|
337
|
+
scrollToBottom(true);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
187
340
|
|
|
188
341
|
// 发送消息
|
|
189
342
|
function handleSend(text: string) {
|
|
190
|
-
sendMessage(text
|
|
191
|
-
selectedImages.value = [];
|
|
343
|
+
sendMessage(text);
|
|
192
344
|
}
|
|
193
345
|
|
|
194
346
|
// 快捷操作
|
|
@@ -198,22 +350,7 @@ function handleQuickAction(text: string) {
|
|
|
198
350
|
|
|
199
351
|
// 重新发送(编辑后)
|
|
200
352
|
function handleResend(index: number, text: string) {
|
|
201
|
-
|
|
202
|
-
messages.value = messages.value.slice(0, index);
|
|
203
|
-
sendMessage(text);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// 移除图片
|
|
207
|
-
function handleRemoveImage(index: number) {
|
|
208
|
-
if (index >= 0 && index < selectedImages.value.length) {
|
|
209
|
-
selectedImages.value.splice(index, 1);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// 上传图片
|
|
214
|
-
function handleUploadImage() {
|
|
215
|
-
// TODO: 实现图片上传
|
|
216
|
-
console.log('上传图片');
|
|
353
|
+
resendFromIndex(index, text);
|
|
217
354
|
}
|
|
218
355
|
|
|
219
356
|
// @ 上下文
|
|
@@ -232,27 +369,50 @@ function handleClose() {
|
|
|
232
369
|
|
|
233
370
|
// 清空所有对话
|
|
234
371
|
function handleClearAll() {
|
|
235
|
-
|
|
236
|
-
|
|
372
|
+
showConfirm({
|
|
373
|
+
title: '清空所有对话',
|
|
374
|
+
message: '确定要清空所有对话吗?此操作不可恢复。',
|
|
375
|
+
type: 'danger',
|
|
376
|
+
confirmText: '清空',
|
|
377
|
+
onConfirm: () => clearAllSessions(),
|
|
378
|
+
});
|
|
237
379
|
}
|
|
238
380
|
|
|
239
381
|
// 关闭其他对话
|
|
240
|
-
function handleCloseOthers() {
|
|
241
|
-
|
|
242
|
-
console.log('关闭其他对话');
|
|
382
|
+
async function handleCloseOthers() {
|
|
383
|
+
await hideOtherSessions()
|
|
243
384
|
}
|
|
244
385
|
|
|
245
386
|
// 导出对话
|
|
246
387
|
function handleExport() {
|
|
247
|
-
|
|
248
|
-
|
|
388
|
+
const data = exportCurrentSession()
|
|
389
|
+
if (!data) {
|
|
390
|
+
showToast('当前会话没有内容可导出', 'warning')
|
|
391
|
+
return
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// 创建下载链接
|
|
395
|
+
const blob = new Blob([data], { type: 'application/json' })
|
|
396
|
+
const url = URL.createObjectURL(blob)
|
|
397
|
+
const a = document.createElement('a')
|
|
398
|
+
const session = sessions.value.find((s) => s.id === currentSessionId.value)
|
|
399
|
+
const filename = `chat-${session?.title || 'export'}-${new Date().toISOString().slice(0, 10)}.json`
|
|
400
|
+
a.href = url
|
|
401
|
+
a.download = filename
|
|
402
|
+
document.body.appendChild(a)
|
|
403
|
+
a.click()
|
|
404
|
+
document.body.removeChild(a)
|
|
405
|
+
URL.revokeObjectURL(url)
|
|
249
406
|
}
|
|
250
407
|
|
|
251
|
-
//
|
|
252
|
-
function handleCopyId() {
|
|
253
|
-
if (currentSessionId.value)
|
|
254
|
-
|
|
255
|
-
|
|
408
|
+
// 复制会话 ID
|
|
409
|
+
async function handleCopyId() {
|
|
410
|
+
if (!currentSessionId.value) return
|
|
411
|
+
try {
|
|
412
|
+
await navigator.clipboard.writeText(currentSessionId.value)
|
|
413
|
+
showToast('已复制会话 ID', 'success')
|
|
414
|
+
} catch (error) {
|
|
415
|
+
console.error('复制失败:', error)
|
|
256
416
|
}
|
|
257
417
|
}
|
|
258
418
|
|
|
@@ -264,23 +424,62 @@ function handleFeedback() {
|
|
|
264
424
|
|
|
265
425
|
// 设置
|
|
266
426
|
function handleSettings() {
|
|
267
|
-
|
|
268
|
-
console.log('Agent 设置');
|
|
427
|
+
settingsPanelVisible.value = true;
|
|
269
428
|
}
|
|
429
|
+
|
|
430
|
+
// 保存设置(静默保存,不显示提示)
|
|
431
|
+
async function handleSaveSettings(config: AutoRunConfig) {
|
|
432
|
+
try {
|
|
433
|
+
await saveAutoRunConfig(config);
|
|
434
|
+
} catch (error) {
|
|
435
|
+
console.error('保存设置失败:', error);
|
|
436
|
+
showToast('保存设置失败', 'error');
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// 模式变更现在在 ToolCallPart 中处理,不再需要此函数
|
|
441
|
+
|
|
442
|
+
// 暴露给外部的方法
|
|
443
|
+
defineExpose({
|
|
444
|
+
/** 设置输入框内容 */
|
|
445
|
+
setInputText: (text: string) => {
|
|
446
|
+
chatInputRef.value?.setText(text);
|
|
447
|
+
},
|
|
448
|
+
/** 在光标位置插入文本(用于 @ 上下文) */
|
|
449
|
+
insertInputText: (text: string) => {
|
|
450
|
+
chatInputRef.value?.insertText?.(text);
|
|
451
|
+
},
|
|
452
|
+
/** 聚焦输入框 */
|
|
453
|
+
focusInput: () => {
|
|
454
|
+
chatInputRef.value?.focus();
|
|
455
|
+
},
|
|
456
|
+
/** 发送消息 */
|
|
457
|
+
sendMessage: (text: string) => {
|
|
458
|
+
sendMessage(text);
|
|
459
|
+
},
|
|
460
|
+
/** 当前工作目录 */
|
|
461
|
+
setCwd: setWorkingDirectory,
|
|
462
|
+
});
|
|
270
463
|
</script>
|
|
271
464
|
|
|
272
465
|
<style scoped>
|
|
273
466
|
.chat-panel {
|
|
274
467
|
display: flex;
|
|
275
468
|
flex-direction: column;
|
|
276
|
-
width:
|
|
469
|
+
width: 100%;
|
|
277
470
|
height: 100%;
|
|
278
471
|
background: var(--chat-bg, #1e1e1e);
|
|
279
|
-
border-left: 1px solid var(--chat-border, #333);
|
|
280
|
-
flex-shrink: 0;
|
|
281
472
|
overflow: hidden;
|
|
282
473
|
}
|
|
283
474
|
|
|
475
|
+
.messages-wrapper {
|
|
476
|
+
flex: 1;
|
|
477
|
+
min-height: 0;
|
|
478
|
+
position: relative;
|
|
479
|
+
display: flex;
|
|
480
|
+
flex-direction: column;
|
|
481
|
+
}
|
|
482
|
+
|
|
284
483
|
.messages-container {
|
|
285
484
|
flex: 1;
|
|
286
485
|
min-height: 0;
|
|
@@ -289,21 +488,41 @@ function handleSettings() {
|
|
|
289
488
|
scroll-behavior: smooth;
|
|
290
489
|
}
|
|
291
490
|
|
|
292
|
-
/*
|
|
293
|
-
.
|
|
294
|
-
|
|
491
|
+
/* 滚动到底部按钮 */
|
|
492
|
+
.scroll-to-bottom-btn {
|
|
493
|
+
position: absolute;
|
|
494
|
+
bottom: 16px;
|
|
495
|
+
right: 16px;
|
|
496
|
+
display: flex;
|
|
497
|
+
align-items: center;
|
|
498
|
+
justify-content: center;
|
|
499
|
+
width: 32px;
|
|
500
|
+
height: 32px;
|
|
501
|
+
border-radius: 50%;
|
|
502
|
+
background: var(--chat-bubble-bg, #2d2d2d);
|
|
503
|
+
border: 1px solid var(--chat-border, #3d3d3d);
|
|
504
|
+
color: var(--chat-text-secondary, #888);
|
|
505
|
+
cursor: pointer;
|
|
506
|
+
transition: all 0.2s ease;
|
|
507
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
|
508
|
+
z-index: 10;
|
|
509
|
+
pointer-events: auto;
|
|
295
510
|
}
|
|
296
511
|
|
|
297
|
-
.
|
|
298
|
-
background:
|
|
512
|
+
.scroll-to-bottom-btn:hover {
|
|
513
|
+
background: var(--chat-bubble-bg-hover, #3a3a3a);
|
|
514
|
+
color: var(--chat-text, #e0e0e0);
|
|
515
|
+
transform: scale(1.1);
|
|
299
516
|
}
|
|
300
517
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
518
|
+
/* 淡入淡出动画 */
|
|
519
|
+
.fade-enter-active,
|
|
520
|
+
.fade-leave-active {
|
|
521
|
+
transition: opacity 0.2s ease;
|
|
304
522
|
}
|
|
305
523
|
|
|
306
|
-
.
|
|
307
|
-
|
|
524
|
+
.fade-enter-from,
|
|
525
|
+
.fade-leave-to {
|
|
526
|
+
opacity: 0;
|
|
308
527
|
}
|
|
309
528
|
</style>
|