@huyooo/ai-chat-frontend-vue 0.1.6 → 0.1.8

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 (159) hide show
  1. package/README.md +367 -0
  2. package/dist/adapter.d.ts +7 -7
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/components/ChatPanel.vue.d.ts +120 -9
  5. package/dist/components/ChatPanel.vue.d.ts.map +1 -1
  6. package/dist/components/common/ConfirmDialog.vue.d.ts +30 -0
  7. package/dist/components/common/ConfirmDialog.vue.d.ts.map +1 -0
  8. package/dist/components/common/CopyButton.vue.d.ts +18 -0
  9. package/dist/components/common/CopyButton.vue.d.ts.map +1 -0
  10. package/dist/components/common/IndexingSettings.vue.d.ts +3 -0
  11. package/dist/components/common/IndexingSettings.vue.d.ts.map +1 -0
  12. package/dist/components/common/SettingsPanel.vue.d.ts +16 -0
  13. package/dist/components/common/SettingsPanel.vue.d.ts.map +1 -0
  14. package/dist/components/common/Toast.vue.d.ts +18 -0
  15. package/dist/components/common/Toast.vue.d.ts.map +1 -0
  16. package/dist/components/common/ToggleSwitch.vue.d.ts +10 -0
  17. package/dist/components/common/ToggleSwitch.vue.d.ts.map +1 -0
  18. package/dist/components/{chat/ui → header}/ChatHeader.vue.d.ts +5 -3
  19. package/dist/components/header/ChatHeader.vue.d.ts.map +1 -0
  20. package/dist/components/input/AtFilePicker.vue.d.ts +21 -0
  21. package/dist/components/input/AtFilePicker.vue.d.ts.map +1 -0
  22. package/dist/components/{ChatInput.vue.d.ts → input/ChatInput.vue.d.ts} +16 -14
  23. package/dist/components/input/ChatInput.vue.d.ts.map +1 -0
  24. package/dist/components/input/DropdownSelector.vue.d.ts +42 -0
  25. package/dist/components/input/DropdownSelector.vue.d.ts.map +1 -0
  26. package/dist/components/input/ImagePreviewModal.vue.d.ts +17 -0
  27. package/dist/components/input/ImagePreviewModal.vue.d.ts.map +1 -0
  28. package/dist/components/input/at-views/AtBranchView.vue.d.ts +18 -0
  29. package/dist/components/input/at-views/AtBranchView.vue.d.ts.map +1 -0
  30. package/dist/components/input/at-views/AtBrowserView.vue.d.ts +18 -0
  31. package/dist/components/input/at-views/AtBrowserView.vue.d.ts.map +1 -0
  32. package/dist/components/input/at-views/AtChatsView.vue.d.ts +18 -0
  33. package/dist/components/input/at-views/AtChatsView.vue.d.ts.map +1 -0
  34. package/dist/components/input/at-views/AtDocsView.vue.d.ts +18 -0
  35. package/dist/components/input/at-views/AtDocsView.vue.d.ts.map +1 -0
  36. package/dist/components/input/at-views/AtFilesView.vue.d.ts +23 -0
  37. package/dist/components/input/at-views/AtFilesView.vue.d.ts.map +1 -0
  38. package/dist/components/input/at-views/AtTerminalsView.vue.d.ts +18 -0
  39. package/dist/components/input/at-views/AtTerminalsView.vue.d.ts.map +1 -0
  40. package/dist/components/message/MessageBubble.vue.d.ts +45 -0
  41. package/dist/components/message/MessageBubble.vue.d.ts.map +1 -0
  42. package/dist/components/message/PartsRenderer.vue.d.ts +15 -0
  43. package/dist/components/message/PartsRenderer.vue.d.ts.map +1 -0
  44. package/dist/components/message/WelcomeMessage.vue.d.ts +14 -0
  45. package/dist/components/message/WelcomeMessage.vue.d.ts.map +1 -0
  46. package/dist/components/message/blocks/CodeBlock.vue.d.ts +11 -0
  47. package/dist/components/message/blocks/CodeBlock.vue.d.ts.map +1 -0
  48. package/dist/components/{chat/SearchResultBlock.vue.d.ts → message/blocks/TextBlock.vue.d.ts} +3 -4
  49. package/dist/components/message/blocks/TextBlock.vue.d.ts.map +1 -0
  50. package/dist/components/message/blocks/index.d.ts +6 -0
  51. package/dist/components/message/blocks/index.d.ts.map +1 -0
  52. package/dist/components/message/parts/CollapsibleCard.vue.d.ts +45 -0
  53. package/dist/components/message/parts/CollapsibleCard.vue.d.ts.map +1 -0
  54. package/dist/components/{chat/ToolCallBlock.vue.d.ts → message/parts/ErrorPart.vue.d.ts} +4 -5
  55. package/dist/components/message/parts/ErrorPart.vue.d.ts.map +1 -0
  56. package/dist/components/{chat/ThinkingBlock.vue.d.ts → message/parts/ImagePart.vue.d.ts} +3 -3
  57. package/dist/components/message/parts/ImagePart.vue.d.ts.map +1 -0
  58. package/dist/components/message/parts/SearchPart.vue.d.ts +12 -0
  59. package/dist/components/message/parts/SearchPart.vue.d.ts.map +1 -0
  60. package/dist/components/{chat/messages/ExecutionSteps.vue.d.ts → message/parts/TextPart.vue.d.ts} +2 -9
  61. package/dist/components/message/parts/TextPart.vue.d.ts.map +1 -0
  62. package/dist/components/message/parts/ThinkingPart.vue.d.ts +12 -0
  63. package/dist/components/message/parts/ThinkingPart.vue.d.ts.map +1 -0
  64. package/dist/components/message/parts/ToolCallPart.vue.d.ts +19 -0
  65. package/dist/components/message/parts/ToolCallPart.vue.d.ts.map +1 -0
  66. package/dist/components/message/parts/ToolResultPart.vue.d.ts +14 -0
  67. package/dist/components/message/parts/ToolResultPart.vue.d.ts.map +1 -0
  68. package/dist/components/message/parts/index.d.ts +12 -0
  69. package/dist/components/message/parts/index.d.ts.map +1 -0
  70. package/dist/components/message/tool-results/DefaultToolResult.vue.d.ts +4 -0
  71. package/dist/components/message/tool-results/DefaultToolResult.vue.d.ts.map +1 -0
  72. package/dist/components/message/tool-results/SearchResults.vue.d.ts +4 -0
  73. package/dist/components/message/tool-results/SearchResults.vue.d.ts.map +1 -0
  74. package/dist/components/message/tool-results/WeatherCard.vue.d.ts +4 -0
  75. package/dist/components/message/tool-results/WeatherCard.vue.d.ts.map +1 -0
  76. package/dist/components/message/tool-results/index.d.ts +7 -0
  77. package/dist/components/message/tool-results/index.d.ts.map +1 -0
  78. package/dist/components/message/welcome-types.d.ts +28 -0
  79. package/dist/components/message/welcome-types.d.ts.map +1 -0
  80. package/dist/composables/useChat.d.ts +99 -44
  81. package/dist/composables/useChat.d.ts.map +1 -1
  82. package/dist/composables/useImageUpload.d.ts +55 -0
  83. package/dist/composables/useImageUpload.d.ts.map +1 -0
  84. package/dist/index.d.ts +25 -26
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +55871 -1252
  87. package/dist/style.css +1 -1
  88. package/dist/types/index.d.ts +113 -53
  89. package/dist/types/index.d.ts.map +1 -1
  90. package/dist/utils/fileIcon.d.ts +13 -0
  91. package/dist/utils/fileIcon.d.ts.map +1 -0
  92. package/package.json +12 -6
  93. package/src/adapter.ts +12 -70
  94. package/src/components/ChatPanel.vue +329 -110
  95. package/src/components/common/ConfirmDialog.vue +208 -0
  96. package/src/components/common/CopyButton.vue +71 -0
  97. package/src/components/common/IndexingSettings.vue +580 -0
  98. package/src/components/common/SettingsPanel.vue +293 -0
  99. package/src/components/common/Toast.vue +90 -0
  100. package/src/components/common/ToggleSwitch.vue +75 -0
  101. package/src/components/{chat/ui → header}/ChatHeader.vue +170 -93
  102. package/src/components/input/AtFilePicker.vue +657 -0
  103. package/src/components/input/ChatInput.vue +653 -0
  104. package/src/components/input/DropdownSelector.vue +322 -0
  105. package/src/components/input/ImagePreviewModal.vue +238 -0
  106. package/src/components/input/at-views/AtBranchView.vue +63 -0
  107. package/src/components/input/at-views/AtBrowserView.vue +63 -0
  108. package/src/components/input/at-views/AtChatsView.vue +63 -0
  109. package/src/components/input/at-views/AtDocsView.vue +63 -0
  110. package/src/components/input/at-views/AtFilesView.vue +255 -0
  111. package/src/components/input/at-views/AtTerminalsView.vue +63 -0
  112. package/src/components/message/ContentRenderer.vue +61 -0
  113. package/src/components/message/MessageBubble.vue +411 -0
  114. package/src/components/message/PartsRenderer.vue +101 -0
  115. package/src/components/message/ToolResultRenderer.vue +27 -0
  116. package/src/components/message/WelcomeMessage.vue +308 -0
  117. package/src/components/message/blocks/CodeBlock.vue +113 -0
  118. package/src/components/message/blocks/TextBlock.vue +21 -0
  119. package/src/components/message/blocks/index.ts +6 -0
  120. package/src/components/message/parts/CollapsibleCard.vue +135 -0
  121. package/src/components/message/parts/ErrorPart.vue +51 -0
  122. package/src/components/message/parts/ImagePart.vue +98 -0
  123. package/src/components/message/parts/SearchPart.vue +101 -0
  124. package/src/components/message/parts/TextPart.vue +28 -0
  125. package/src/components/message/parts/ThinkingPart.vue +54 -0
  126. package/src/components/message/parts/ToolCallPart.vue +460 -0
  127. package/src/components/message/parts/ToolResultPart.vue +78 -0
  128. package/src/components/message/parts/index.ts +13 -0
  129. package/src/components/message/tool-results/DefaultToolResult.vue +43 -0
  130. package/src/components/message/tool-results/SearchResults.vue +133 -0
  131. package/src/components/message/tool-results/WeatherCard.vue +139 -0
  132. package/src/components/message/tool-results/index.ts +7 -0
  133. package/src/components/message/welcome-types.ts +47 -0
  134. package/src/composables/useChat.ts +807 -155
  135. package/src/composables/useImageUpload.ts +228 -0
  136. package/src/index.ts +93 -46
  137. package/src/styles.css +47 -0
  138. package/src/types/index.ts +146 -98
  139. package/src/utils/fileIcon.ts +49 -0
  140. package/dist/components/ChatInput.vue.d.ts.map +0 -1
  141. package/dist/components/chat/SearchResultBlock.vue.d.ts.map +0 -1
  142. package/dist/components/chat/ThinkingBlock.vue.d.ts.map +0 -1
  143. package/dist/components/chat/ToolCallBlock.vue.d.ts.map +0 -1
  144. package/dist/components/chat/messages/ExecutionSteps.vue.d.ts.map +0 -1
  145. package/dist/components/chat/messages/MessageBubble.vue.d.ts +0 -28
  146. package/dist/components/chat/messages/MessageBubble.vue.d.ts.map +0 -1
  147. package/dist/components/chat/ui/ChatHeader.vue.d.ts.map +0 -1
  148. package/dist/components/chat/ui/WelcomeMessage.vue.d.ts +0 -7
  149. package/dist/components/chat/ui/WelcomeMessage.vue.d.ts.map +0 -1
  150. package/dist/preload/preload.d.ts +0 -6
  151. package/dist/preload/preload.d.ts.map +0 -1
  152. package/src/components/ChatInput.vue +0 -649
  153. package/src/components/chat/SearchResultBlock.vue +0 -155
  154. package/src/components/chat/ThinkingBlock.vue +0 -109
  155. package/src/components/chat/ToolCallBlock.vue +0 -213
  156. package/src/components/chat/messages/ExecutionSteps.vue +0 -281
  157. package/src/components/chat/messages/MessageBubble.vue +0 -272
  158. package/src/components/chat/ui/WelcomeMessage.vue +0 -135
  159. package/src/preload/preload.ts +0 -79
@@ -1,281 +0,0 @@
1
- <template>
2
- <div v-if="hasSteps" class="execution-steps">
3
- <!-- 思考过程 -->
4
- <div v-if="thinking || (loading && !hasContent && !toolCalls?.length)" class="step thinking-step">
5
- <div class="step-header" @click="thinkingExpanded = !thinkingExpanded">
6
- <div class="step-icon">
7
- <Loader2 v-if="!thinkingComplete" :size="14" class="animate-spin" />
8
- <Lightbulb v-else :size="14" />
9
- </div>
10
- <span class="step-title">
11
- {{ thinkingComplete ? '思考完成' : '正在思考...' }}
12
- </span>
13
- <ChevronDown
14
- v-if="thinking"
15
- :size="14"
16
- :class="['step-chevron', { expanded: thinkingExpanded }]"
17
- />
18
- </div>
19
- <div v-if="thinking && thinkingExpanded" class="step-content">
20
- <div class="thinking-content">{{ thinking }}</div>
21
- </div>
22
- </div>
23
-
24
- <!-- 搜索过程 -->
25
- <div v-if="searching || searchResults?.length" class="step search-step">
26
- <div class="step-header" @click="searchExpanded = !searchExpanded">
27
- <div class="step-icon">
28
- <Loader2 v-if="searching" :size="14" class="animate-spin" />
29
- <Globe v-else :size="14" />
30
- </div>
31
- <span class="step-title">
32
- {{ searching ? '正在搜索...' : `找到 ${searchResults?.length || 0} 条结果` }}
33
- </span>
34
- <ChevronDown
35
- v-if="searchResults?.length"
36
- :size="14"
37
- :class="['step-chevron', { expanded: searchExpanded }]"
38
- />
39
- </div>
40
- <div v-if="searchResults?.length && searchExpanded" class="step-content">
41
- <div class="search-results">
42
- <a
43
- v-for="(result, i) in searchResults"
44
- :key="i"
45
- :href="result.url"
46
- target="_blank"
47
- class="search-result"
48
- >
49
- <span class="result-title">{{ result.title }}</span>
50
- <span class="result-url">{{ getDomain(result.url) }}</span>
51
- </a>
52
- </div>
53
- </div>
54
- </div>
55
-
56
- <!-- 工具调用 -->
57
- <div v-for="(tool, i) in toolCalls" :key="i" class="step tool-step">
58
- <div class="step-header" @click="toggleToolExpanded(i)">
59
- <div class="step-icon">
60
- <Loader2 v-if="tool.status === 'running'" :size="14" class="animate-spin" />
61
- <Check v-else-if="tool.status === 'success'" :size="14" class="text-green" />
62
- <X v-else :size="14" class="text-red" />
63
- </div>
64
- <span class="step-title">
65
- {{ getToolDisplayName(tool.name) }}
66
- </span>
67
- <ChevronDown
68
- v-if="tool.result"
69
- :size="14"
70
- :class="['step-chevron', { expanded: toolExpandedMap[i] }]"
71
- />
72
- </div>
73
- <div v-if="tool.result && toolExpandedMap[i]" class="step-content">
74
- <pre class="tool-result">{{ tool.result }}</pre>
75
- </div>
76
- </div>
77
- </div>
78
- </template>
79
-
80
- <script setup lang="ts">
81
- import { ref, computed, reactive } from 'vue';
82
- import { Loader2, Lightbulb, Globe, Check, X, ChevronDown } from 'lucide-vue-next';
83
- import type { ToolCall, SearchResult } from '../../../types';
84
-
85
- const props = defineProps<{
86
- loading?: boolean;
87
- hasContent?: boolean;
88
- thinking?: string;
89
- thinkingComplete?: boolean;
90
- searching?: boolean;
91
- searchResults?: SearchResult[];
92
- toolCalls?: ToolCall[];
93
- }>();
94
-
95
- // 展开状态
96
- const thinkingExpanded = ref(false);
97
- const searchExpanded = ref(true);
98
- const toolExpandedMap = reactive<Record<number, boolean>>({});
99
-
100
- // 是否有步骤显示
101
- const hasSteps = computed(() => {
102
- return (
103
- props.thinking ||
104
- props.searching ||
105
- props.searchResults?.length ||
106
- props.toolCalls?.length ||
107
- (props.loading && !props.hasContent)
108
- );
109
- });
110
-
111
- // 切换工具展开
112
- function toggleToolExpanded(index: number) {
113
- toolExpandedMap[index] = !toolExpandedMap[index];
114
- }
115
-
116
- // 获取域名
117
- function getDomain(url: string) {
118
- try {
119
- return new URL(url).hostname;
120
- } catch {
121
- return url;
122
- }
123
- }
124
-
125
- // 获取工具显示名称
126
- function getToolDisplayName(name: string) {
127
- const nameMap: Record<string, string> = {
128
- web_search: '网页搜索',
129
- read_file: '读取文件',
130
- write_file: '写入文件',
131
- execute_command: '执行命令',
132
- list_directory: '列出目录',
133
- create_directory: '创建目录',
134
- delete_file: '删除文件',
135
- move_file: '移动文件',
136
- search_files: '搜索文件',
137
- analyze_image: '分析图片',
138
- };
139
- return nameMap[name] || name;
140
- }
141
- </script>
142
-
143
- <style scoped>
144
- .execution-steps {
145
- display: flex;
146
- flex-direction: column;
147
- gap: 4px;
148
- margin-bottom: 8px;
149
- }
150
-
151
- .step {
152
- background: var(--chat-muted, #2d2d2d);
153
- border-radius: 8px;
154
- overflow: hidden;
155
- }
156
-
157
- .step-header {
158
- display: flex;
159
- align-items: center;
160
- gap: 8px;
161
- padding: 8px 12px;
162
- cursor: pointer;
163
- transition: background 0.15s;
164
- }
165
-
166
- .step-header:hover {
167
- background: var(--chat-muted-hover, #3a3a3a);
168
- }
169
-
170
- .step-icon {
171
- display: flex;
172
- align-items: center;
173
- justify-content: center;
174
- width: 20px;
175
- height: 20px;
176
- color: var(--chat-text-muted, #888);
177
- }
178
-
179
- .step-title {
180
- flex: 1;
181
- font-size: 13px;
182
- color: var(--chat-text, #ccc);
183
- }
184
-
185
- .step-chevron {
186
- color: var(--chat-text-muted, #666);
187
- transition: transform 0.2s;
188
- }
189
-
190
- .step-chevron.expanded {
191
- transform: rotate(180deg);
192
- }
193
-
194
- .step-content {
195
- padding: 0 12px 12px;
196
- border-top: 1px solid var(--chat-border, #333);
197
- margin-top: 4px;
198
- padding-top: 8px;
199
- }
200
-
201
- .thinking-content {
202
- font-size: 13px;
203
- line-height: 1.6;
204
- color: var(--chat-text-muted, #999);
205
- white-space: pre-wrap;
206
- word-break: break-word;
207
- }
208
-
209
- .search-results {
210
- display: flex;
211
- flex-direction: column;
212
- gap: 8px;
213
- }
214
-
215
- .search-result {
216
- display: flex;
217
- flex-direction: column;
218
- gap: 2px;
219
- padding: 8px;
220
- background: var(--chat-bg, #1e1e1e);
221
- border-radius: 6px;
222
- text-decoration: none;
223
- transition: background 0.15s;
224
- }
225
-
226
- .search-result:hover {
227
- background: var(--chat-muted-hover, #333);
228
- }
229
-
230
- .result-title {
231
- font-size: 13px;
232
- color: var(--chat-text, #ccc);
233
- overflow: hidden;
234
- text-overflow: ellipsis;
235
- white-space: nowrap;
236
- }
237
-
238
- .search-result:hover .result-title {
239
- color: var(--chat-text, #fff);
240
- }
241
-
242
- .result-url {
243
- font-size: 11px;
244
- color: var(--chat-text-muted, #666);
245
- }
246
-
247
- .tool-result {
248
- font-size: 12px;
249
- line-height: 1.5;
250
- color: var(--chat-text-muted, #999);
251
- background: var(--chat-bg, #1e1e1e);
252
- padding: 8px;
253
- border-radius: 6px;
254
- overflow-x: auto;
255
- white-space: pre-wrap;
256
- word-break: break-word;
257
- font-family: 'SF Mono', Monaco, monospace;
258
- margin: 0;
259
- }
260
-
261
- .animate-spin {
262
- animation: spin 1s linear infinite;
263
- }
264
-
265
- @keyframes spin {
266
- from {
267
- transform: rotate(0deg);
268
- }
269
- to {
270
- transform: rotate(360deg);
271
- }
272
- }
273
-
274
- .text-green {
275
- color: var(--chat-success, #22c55e);
276
- }
277
-
278
- .text-red {
279
- color: var(--chat-destructive, #ef4444);
280
- }
281
- </style>
@@ -1,272 +0,0 @@
1
- <template>
2
- <div :class="['message-bubble', role]">
3
- <!-- 用户消息 - 复用 ChatInput 组件 -->
4
- <template v-if="role === 'user'">
5
- <ChatInput
6
- v-if="onSend"
7
- variant="message"
8
- :value="content"
9
- :selected-images="images"
10
- @send="(text) => $emit('send', text)"
11
- />
12
- <div v-else class="user-content">
13
- <div class="user-text">{{ content }}</div>
14
- <div v-if="images && images.length > 0" class="user-images">
15
- <img
16
- v-for="(img, i) in images"
17
- :key="i"
18
- :src="img"
19
- class="user-image"
20
- @click="$emit('view-image', img)"
21
- />
22
- </div>
23
- </div>
24
- </template>
25
-
26
- <!-- 助手消息 -->
27
- <template v-else>
28
- <div class="assistant-content">
29
- <!-- 执行步骤列表 -->
30
- <ExecutionSteps
31
- :loading="loading"
32
- :has-content="!!content"
33
- :thinking="thinking"
34
- :thinking-complete="thinkingComplete"
35
- :searching="searching"
36
- :search-results="searchResults"
37
- :tool-calls="toolCalls"
38
- />
39
-
40
- <!-- 文本内容 -->
41
- <div v-if="content" class="assistant-text" v-html="renderedContent"></div>
42
-
43
- <!-- 加载中 -->
44
- <div v-if="loading && !content && !thinking && !toolCalls?.length" class="loading-indicator">
45
- <span class="loading-dot"></span>
46
- <span class="loading-dot"></span>
47
- <span class="loading-dot"></span>
48
- </div>
49
- </div>
50
-
51
- <!-- 操作按钮 -->
52
- <div v-if="content && !loading" class="message-actions">
53
- <button class="action-btn" title="复制" @click="$emit('copy')">
54
- <component :is="copied ? Check : Copy" :size="14" />
55
- </button>
56
- <button class="action-btn" title="重新生成" @click="$emit('regenerate')">
57
- <RefreshCw :size="14" />
58
- </button>
59
- </div>
60
- </template>
61
- </div>
62
- </template>
63
-
64
- <script setup lang="ts">
65
- import { computed } from 'vue';
66
- import { Copy, Check, RefreshCw } from 'lucide-vue-next';
67
- import ChatInput from '../../ChatInput.vue';
68
- import ExecutionSteps from './ExecutionSteps.vue';
69
- import type { ToolCall, SearchResult } from '../../../types';
70
-
71
- const props = defineProps<{
72
- role: 'user' | 'assistant';
73
- content: string;
74
- images?: string[];
75
- thinking?: string;
76
- thinkingComplete?: boolean;
77
- searchResults?: SearchResult[];
78
- searching?: boolean;
79
- toolCalls?: ToolCall[];
80
- loading?: boolean;
81
- copied?: boolean;
82
- /** 是否支持重新发送(仅用户消息) */
83
- onSend?: (text: string) => void;
84
- }>();
85
-
86
- defineEmits<{
87
- copy: [];
88
- regenerate: [];
89
- 'view-image': [url: string];
90
- send: [text: string];
91
- }>();
92
-
93
- // Markdown 渲染
94
- const renderedContent = computed(() => {
95
- let html = props.content;
96
-
97
- // 代码块
98
- html = html.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre class="code-block"><code>$2</code></pre>');
99
-
100
- // 行内代码
101
- html = html.replace(/`([^`]+)`/g, '<code class="inline-code">$1</code>');
102
-
103
- // 粗体
104
- html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
105
-
106
- // 斜体
107
- html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');
108
-
109
- // 换行
110
- html = html.replace(/\n/g, '<br>');
111
-
112
- return html;
113
- });
114
- </script>
115
-
116
- <style scoped>
117
- .message-bubble {
118
- padding: 8px 0;
119
- animation: fadeIn 0.2s ease;
120
- }
121
-
122
- @keyframes fadeIn {
123
- from {
124
- opacity: 0;
125
- transform: translateY(4px);
126
- }
127
- to {
128
- opacity: 1;
129
- transform: translateY(0);
130
- }
131
- }
132
-
133
- /* 用户消息 */
134
- .message-bubble.user {
135
- width: 100%;
136
- }
137
-
138
- .user-content {
139
- width: 100%;
140
- background: var(--chat-muted, #2d2d2d);
141
- color: var(--chat-text, #ccc);
142
- padding: 12px;
143
- border-radius: 12px;
144
- border: 1px solid var(--chat-border, #444);
145
- }
146
-
147
- .user-text {
148
- font-size: 14px;
149
- line-height: 1.5;
150
- white-space: pre-wrap;
151
- word-break: break-word;
152
- }
153
-
154
- .user-images {
155
- display: flex;
156
- gap: 8px;
157
- margin-top: 8px;
158
- flex-wrap: wrap;
159
- }
160
-
161
- .user-image {
162
- width: 80px;
163
- height: 80px;
164
- object-fit: cover;
165
- border-radius: 8px;
166
- cursor: pointer;
167
- transition: transform 0.15s;
168
- }
169
-
170
- .user-image:hover {
171
- transform: scale(1.05);
172
- }
173
-
174
- /* 助手消息 */
175
- .message-bubble.assistant {
176
- position: relative;
177
- }
178
-
179
- .assistant-content {
180
- max-width: 100%;
181
- }
182
-
183
- .assistant-text {
184
- font-size: 14px;
185
- line-height: 1.7;
186
- color: var(--chat-text, #ccc);
187
- font-weight: 500;
188
- letter-spacing: 0.01em;
189
- }
190
-
191
- .assistant-text :deep(.code-block) {
192
- background: var(--chat-code-bg, #1f2937);
193
- color: var(--chat-code-text, #e5e7eb);
194
- padding: 12px;
195
- border-radius: 8px;
196
- margin: 8px 0;
197
- overflow-x: auto;
198
- font-family: 'SF Mono', Monaco, monospace;
199
- font-size: 13px;
200
- }
201
-
202
- .assistant-text :deep(.inline-code) {
203
- background: var(--chat-muted, #3c3c3c);
204
- color: var(--chat-text, #e5e7eb);
205
- padding: 2px 6px;
206
- border-radius: 4px;
207
- font-family: 'SF Mono', Monaco, monospace;
208
- font-size: 13px;
209
- }
210
-
211
- /* 加载动画 */
212
- .loading-indicator {
213
- display: flex;
214
- gap: 4px;
215
- padding: 8px 0;
216
- }
217
-
218
- .loading-dot {
219
- width: 6px;
220
- height: 6px;
221
- background: var(--chat-text-muted, #666);
222
- border-radius: 50%;
223
- animation: pulse 1.4s ease-in-out infinite;
224
- }
225
-
226
- .loading-dot:nth-child(2) {
227
- animation-delay: 0.2s;
228
- }
229
-
230
- .loading-dot:nth-child(3) {
231
- animation-delay: 0.4s;
232
- }
233
-
234
- @keyframes pulse {
235
- 0%,
236
- 100% {
237
- opacity: 0.4;
238
- transform: scale(0.8);
239
- }
240
- 50% {
241
- opacity: 1;
242
- transform: scale(1);
243
- }
244
- }
245
-
246
- /* 操作按钮 */
247
- .message-actions {
248
- display: flex;
249
- gap: 4px;
250
- margin-top: 8px;
251
- justify-content: flex-end;
252
- }
253
-
254
- .action-btn {
255
- display: flex;
256
- align-items: center;
257
- justify-content: center;
258
- width: 24px;
259
- height: 24px;
260
- border: none;
261
- background: transparent;
262
- border-radius: 4px;
263
- color: var(--chat-text-muted, #666);
264
- cursor: pointer;
265
- transition: all 0.15s;
266
- }
267
-
268
- .action-btn:hover {
269
- background: var(--chat-muted, #3c3c3c);
270
- color: var(--chat-text, #ccc);
271
- }
272
- </style>
@@ -1,135 +0,0 @@
1
- <template>
2
- <div class="welcome-message">
3
- <div class="welcome-icon">
4
- <Sparkles :size="48" />
5
- </div>
6
- <h3 class="welcome-title">AI Assistant</h3>
7
- <p class="welcome-desc">我可以帮你完成各种任务,试试下面的快捷操作:</p>
8
-
9
- <div class="quick-actions">
10
- <button
11
- v-for="action in quickActions"
12
- :key="action.id"
13
- class="quick-btn"
14
- @click="$emit('quick-action', action.prompt)"
15
- >
16
- <component :is="action.icon" :size="14" class="quick-icon" />
17
- <span>{{ action.label }}</span>
18
- </button>
19
- </div>
20
- </div>
21
- </template>
22
-
23
- <script setup lang="ts">
24
- import { markRaw } from 'vue';
25
- import {
26
- Sparkles,
27
- Code,
28
- FileText,
29
- Search,
30
- MessageCircle,
31
- } from 'lucide-vue-next';
32
-
33
- defineEmits<{
34
- 'quick-action': [prompt: string];
35
- }>();
36
-
37
- const quickActions = [
38
- {
39
- id: 'code',
40
- label: '写代码',
41
- prompt: '帮我写一段代码',
42
- icon: markRaw(Code),
43
- },
44
- {
45
- id: 'explain',
46
- label: '解释代码',
47
- prompt: '帮我解释这段代码',
48
- icon: markRaw(FileText),
49
- },
50
- {
51
- id: 'search',
52
- label: '搜索信息',
53
- prompt: '帮我搜索一下',
54
- icon: markRaw(Search),
55
- },
56
- {
57
- id: 'chat',
58
- label: '聊聊天',
59
- prompt: '我们聊聊天吧',
60
- icon: markRaw(MessageCircle),
61
- },
62
- ];
63
- </script>
64
-
65
- <style scoped>
66
- .welcome-message {
67
- display: flex;
68
- flex-direction: column;
69
- align-items: center;
70
- justify-content: center;
71
- height: 100%;
72
- padding: 32px 20px;
73
- text-align: center;
74
- }
75
-
76
- .welcome-icon {
77
- display: flex;
78
- align-items: center;
79
- justify-content: center;
80
- width: 80px;
81
- height: 80px;
82
- background: rgba(255, 255, 255, 0.1);
83
- border-radius: 20px;
84
- color: var(--chat-text, #ccc);
85
- margin-bottom: 20px;
86
- }
87
-
88
- .welcome-title {
89
- font-size: 20px;
90
- font-weight: 600;
91
- color: var(--chat-text, #ccc);
92
- margin-bottom: 8px;
93
- }
94
-
95
- .welcome-desc {
96
- font-size: 14px;
97
- color: var(--chat-text-muted, #888);
98
- margin-bottom: 24px;
99
- }
100
-
101
- .quick-actions {
102
- display: flex;
103
- flex-wrap: wrap;
104
- gap: 8px;
105
- justify-content: center;
106
- max-width: 320px;
107
- }
108
-
109
- .quick-btn {
110
- display: flex;
111
- align-items: center;
112
- gap: 6px;
113
- padding: 8px 14px;
114
- background: var(--chat-muted, #2d2d2d);
115
- border: 1px solid var(--chat-border, #444);
116
- border-radius: 8px;
117
- color: var(--chat-text, #ccc);
118
- font-size: 13px;
119
- cursor: pointer;
120
- transition: all 0.15s;
121
- }
122
-
123
- .quick-btn:hover {
124
- background: rgba(255, 255, 255, 0.1);
125
- border-color: rgba(255, 255, 255, 0.2);
126
- }
127
-
128
- .quick-icon {
129
- color: var(--chat-text-muted, #888);
130
- }
131
-
132
- .quick-btn:hover .quick-icon {
133
- color: var(--chat-text, #fff);
134
- }
135
- </style>