@huyooo/ai-chat-frontend-vue 0.1.6 → 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.
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
@@ -0,0 +1,308 @@
1
+ <template>
2
+ <div class="welcome-message">
3
+ <!-- 标题区域 -->
4
+ <div class="welcome-header">
5
+ <div class="welcome-title-row">
6
+ <Icon :icon="config.icon" width="28" class="welcome-icon" />
7
+ <h2 class="welcome-title">{{ config.title }}</h2>
8
+ </div>
9
+ <p class="welcome-subtitle">{{ config.subtitle }}</p>
10
+ </div>
11
+
12
+ <!-- 能力标签 -->
13
+ <div v-if="config.features.length" class="features-section">
14
+ <div class="section-header">
15
+ <Icon icon="lucide:zap" width="14" class="section-icon" />
16
+ <span class="section-title">支持的能力</span>
17
+ </div>
18
+ <div class="features-list">
19
+ <div v-for="feature in config.features" :key="feature.name" class="feature-tag">
20
+ <Icon :icon="feature.icon" width="14" class="feature-icon" />
21
+ <span>{{ feature.name }}</span>
22
+ </div>
23
+ </div>
24
+ </div>
25
+
26
+ <!-- 快捷操作 -->
27
+ <div v-if="config.tasks.length" class="tasks-section">
28
+ <div class="section-header">
29
+ <Icon icon="lucide:rocket" width="14" class="section-icon" />
30
+ <span class="section-title">快捷操作</span>
31
+ </div>
32
+ <div class="tasks-grid" :class="tasksGridClass">
33
+ <button
34
+ v-for="task in config.tasks"
35
+ :key="task.name"
36
+ class="task-card"
37
+ @click="$emit('quick-action', task.prompt)"
38
+ >
39
+ <Icon :icon="task.icon" width="18" class="task-icon" />
40
+ <div class="task-content">
41
+ <div class="task-name">{{ task.name }}</div>
42
+ <div class="task-desc">{{ task.desc }}</div>
43
+ </div>
44
+ <Icon icon="lucide:arrow-right" width="14" class="task-arrow" />
45
+ </button>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ </template>
50
+
51
+ <script setup lang="ts">
52
+ import { computed } from 'vue';
53
+ import { Icon } from '@iconify/vue';
54
+ import { type WelcomeConfig, defaultWelcomeConfig } from './welcome-types';
55
+
56
+ const props = withDefaults(defineProps<{
57
+ /** 欢迎页配置(可部分配置,未配置项使用默认值) */
58
+ config?: Partial<WelcomeConfig>;
59
+ }>(), {
60
+ config: () => ({}),
61
+ });
62
+
63
+ defineEmits<{
64
+ 'quick-action': [prompt: string];
65
+ }>();
66
+
67
+ // 合并配置
68
+ const config = computed<WelcomeConfig>(() => ({
69
+ title: props.config?.title ?? defaultWelcomeConfig.title,
70
+ subtitle: props.config?.subtitle ?? defaultWelcomeConfig.subtitle,
71
+ icon: props.config?.icon ?? defaultWelcomeConfig.icon,
72
+ features: props.config?.features ?? defaultWelcomeConfig.features,
73
+ tasks: props.config?.tasks ?? defaultWelcomeConfig.tasks,
74
+ }));
75
+
76
+ // 根据任务数量动态选择网格布局
77
+ const tasksGridClass = computed(() => {
78
+ const count = config.value.tasks.length;
79
+ if (count === 1) return 'tasks-single';
80
+ if (count === 2) return 'tasks-two';
81
+ if (count === 3) return 'tasks-three';
82
+ return 'tasks-multi';
83
+ });
84
+ </script>
85
+
86
+ <style scoped>
87
+ .welcome-message {
88
+ display: flex;
89
+ flex-direction: column;
90
+ align-items: center;
91
+ justify-content: center;
92
+ gap: 28px;
93
+ padding: 40px 24px;
94
+ max-width: 640px;
95
+ margin: 0 auto;
96
+ min-height: 100%;
97
+ }
98
+
99
+ /* 标题区域 */
100
+ .welcome-header {
101
+ display: flex;
102
+ flex-direction: column;
103
+ align-items: center;
104
+ text-align: center;
105
+ gap: 8px;
106
+ }
107
+
108
+ .welcome-title-row {
109
+ display: flex;
110
+ align-items: center;
111
+ gap: 10px;
112
+ }
113
+
114
+ .welcome-icon {
115
+ color: var(--chat-text-muted, #888);
116
+ }
117
+
118
+ .welcome-title {
119
+ font-size: 24px;
120
+ font-weight: 600;
121
+ color: var(--chat-text, #fff);
122
+ margin: 0;
123
+ }
124
+
125
+ .welcome-subtitle {
126
+ font-size: 13px;
127
+ color: var(--chat-text-muted, #888);
128
+ margin: 0;
129
+ }
130
+
131
+ /* 区域标题 */
132
+ .section-header {
133
+ display: flex;
134
+ align-items: center;
135
+ gap: 6px;
136
+ margin-bottom: 12px;
137
+ padding-left: 2px;
138
+ }
139
+
140
+ .section-icon {
141
+ color: var(--chat-text-muted, #666);
142
+ }
143
+
144
+ .section-title {
145
+ font-size: 12px;
146
+ font-weight: 500;
147
+ color: var(--chat-text-muted, #888);
148
+ text-transform: uppercase;
149
+ letter-spacing: 0.5px;
150
+ }
151
+
152
+ /* 能力标签 */
153
+ .features-section {
154
+ width: 100%;
155
+ }
156
+
157
+ .features-list {
158
+ display: flex;
159
+ flex-wrap: wrap;
160
+ gap: 8px;
161
+ justify-content: center;
162
+ }
163
+
164
+ .feature-tag {
165
+ display: inline-flex;
166
+ align-items: center;
167
+ gap: 6px;
168
+ padding: 6px 12px;
169
+ background: var(--chat-muted, #2a2a2a);
170
+ border: 1px solid var(--chat-border, #3a3a3a);
171
+ border-radius: 20px;
172
+ font-size: 12px;
173
+ color: var(--chat-text, #ccc);
174
+ transition: all 0.15s;
175
+ }
176
+
177
+ .feature-tag:hover {
178
+ background: rgba(255, 255, 255, 0.08);
179
+ border-color: rgba(255, 255, 255, 0.15);
180
+ }
181
+
182
+ .feature-icon {
183
+ color: var(--chat-text-muted, #888);
184
+ }
185
+
186
+ /* 快捷操作 */
187
+ .tasks-section {
188
+ width: 100%;
189
+ }
190
+
191
+ .tasks-grid {
192
+ display: grid;
193
+ gap: 10px;
194
+ }
195
+
196
+ /* 单个任务:居中显示 */
197
+ .tasks-single {
198
+ grid-template-columns: minmax(200px, 320px);
199
+ justify-content: center;
200
+ }
201
+
202
+ /* 两个任务:两列 */
203
+ .tasks-two {
204
+ grid-template-columns: repeat(2, 1fr);
205
+ }
206
+
207
+ /* 三个任务:三列 */
208
+ .tasks-three {
209
+ grid-template-columns: repeat(3, 1fr);
210
+ }
211
+
212
+ /* 多个任务:两列自动换行 */
213
+ .tasks-multi {
214
+ grid-template-columns: repeat(2, 1fr);
215
+ }
216
+
217
+ .task-card {
218
+ display: flex;
219
+ align-items: center;
220
+ gap: 10px;
221
+ padding: 12px 14px;
222
+ background: var(--chat-muted, #2a2a2a);
223
+ border: 1px solid var(--chat-border, #3a3a3a);
224
+ border-radius: 10px;
225
+ color: var(--chat-text, #ccc);
226
+ text-align: left;
227
+ cursor: pointer;
228
+ transition: all 0.2s;
229
+ }
230
+
231
+ .task-card:hover {
232
+ background: rgba(255, 255, 255, 0.08);
233
+ border-color: rgba(59, 130, 246, 0.5);
234
+ transform: translateY(-1px);
235
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
236
+ }
237
+
238
+ .task-icon {
239
+ flex-shrink: 0;
240
+ width: 32px;
241
+ height: 32px;
242
+ display: flex;
243
+ align-items: center;
244
+ justify-content: center;
245
+ background: rgba(59, 130, 246, 0.15);
246
+ border-radius: 8px;
247
+ color: #60a5fa;
248
+ }
249
+
250
+ .task-card:hover .task-icon {
251
+ background: rgba(59, 130, 246, 0.25);
252
+ }
253
+
254
+ .task-content {
255
+ flex: 1;
256
+ min-width: 0;
257
+ }
258
+
259
+ .task-name {
260
+ font-size: 13px;
261
+ font-weight: 500;
262
+ color: var(--chat-text, #fff);
263
+ margin-bottom: 2px;
264
+ }
265
+
266
+ .task-desc {
267
+ font-size: 11px;
268
+ color: var(--chat-text-muted, #888);
269
+ white-space: nowrap;
270
+ overflow: hidden;
271
+ text-overflow: ellipsis;
272
+ }
273
+
274
+ .task-arrow {
275
+ flex-shrink: 0;
276
+ color: var(--chat-text-muted, #666);
277
+ opacity: 0;
278
+ transform: translateX(-4px);
279
+ transition: all 0.2s;
280
+ }
281
+
282
+ .task-card:hover .task-arrow {
283
+ opacity: 1;
284
+ transform: translateX(0);
285
+ }
286
+
287
+ /* 响应式 */
288
+ @media (max-width: 540px) {
289
+ .welcome-message {
290
+ padding: 32px 16px;
291
+ gap: 24px;
292
+ }
293
+
294
+ .welcome-title {
295
+ font-size: 24px;
296
+ }
297
+
298
+ .tasks-two,
299
+ .tasks-three,
300
+ .tasks-multi {
301
+ grid-template-columns: 1fr;
302
+ }
303
+
304
+ .task-arrow {
305
+ display: none;
306
+ }
307
+ }
308
+ </style>
@@ -0,0 +1,113 @@
1
+ <template>
2
+ <div class="code-block">
3
+ <!-- 头部:语言 + 操作按钮 -->
4
+ <div class="code-header">
5
+ <span class="code-language">{{ languageDisplay }}</span>
6
+ <div class="code-actions">
7
+ <CopyButton :text="block.content" title="复制代码" @copy="emit('copy', $event)" />
8
+ </div>
9
+ </div>
10
+ <!-- 代码内容 -->
11
+ <pre class="code-content"><code v-html="highlightedCode"></code></pre>
12
+ </div>
13
+ </template>
14
+
15
+ <script setup lang="ts">
16
+ import { computed } from 'vue'
17
+ import { highlightCode, getLanguageDisplayName } from '@huyooo/ai-chat-shared'
18
+ import type { CodeBlock } from '@huyooo/ai-chat-shared'
19
+ import CopyButton from '../../common/CopyButton.vue'
20
+
21
+ const props = defineProps<{
22
+ block: CodeBlock
23
+ }>()
24
+
25
+ const emit = defineEmits<{
26
+ copy: [code: string]
27
+ }>()
28
+
29
+ // 语言显示名称
30
+ const languageDisplay = computed(() =>
31
+ getLanguageDisplayName(props.block.language)
32
+ )
33
+
34
+ // 高亮后的代码
35
+ const highlightedCode = computed(() =>
36
+ highlightCode(props.block.content, props.block.language)
37
+ )
38
+ </script>
39
+
40
+ <style scoped>
41
+ .code-block {
42
+ background: var(--chat-code-bg, #1f2937);
43
+ border-radius: 8px;
44
+ overflow: hidden;
45
+ margin: 8px 0;
46
+ }
47
+
48
+ .code-header {
49
+ display: flex;
50
+ align-items: center;
51
+ justify-content: space-between;
52
+ padding: 8px 12px;
53
+ background: rgba(0, 0, 0, 0.2);
54
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
55
+ }
56
+
57
+ .code-language {
58
+ font-size: 12px;
59
+ color: var(--chat-text-muted, #888);
60
+ font-family: 'SF Mono', Monaco, monospace;
61
+ }
62
+
63
+ .code-content {
64
+ margin: 0;
65
+ padding: 12px;
66
+ overflow-x: auto;
67
+ font-family: 'SF Mono', Monaco, Consolas, monospace;
68
+ font-size: 13px;
69
+ line-height: 1.5;
70
+ color: var(--chat-code-text, #e5e7eb);
71
+ }
72
+
73
+ /* 统一滚动条样式 */
74
+ .code-content::-webkit-scrollbar {
75
+ width: 6px;
76
+ height: 6px;
77
+ }
78
+
79
+ .code-content::-webkit-scrollbar-track {
80
+ background: transparent;
81
+ }
82
+
83
+ .code-content::-webkit-scrollbar-thumb {
84
+ background: var(--chat-scrollbar, rgba(255, 255, 255, 0.2));
85
+ border-radius: 3px;
86
+ }
87
+
88
+ .code-content::-webkit-scrollbar-thumb:hover {
89
+ background: var(--chat-scrollbar-hover, rgba(255, 255, 255, 0.3));
90
+ }
91
+
92
+ .code-content code {
93
+ font-family: inherit;
94
+ }
95
+
96
+ /* highlight.js 主题覆盖 */
97
+ .code-content :deep(.hljs-keyword) { color: #c678dd; }
98
+ .code-content :deep(.hljs-string) { color: #98c379; }
99
+ .code-content :deep(.hljs-number) { color: #d19a66; }
100
+ .code-content :deep(.hljs-comment) { color: #5c6370; font-style: italic; }
101
+ .code-content :deep(.hljs-function) { color: #61afef; }
102
+ .code-content :deep(.hljs-class) { color: #e5c07b; }
103
+ .code-content :deep(.hljs-variable) { color: #e06c75; }
104
+ .code-content :deep(.hljs-operator) { color: #56b6c2; }
105
+ .code-content :deep(.hljs-punctuation) { color: #abb2bf; }
106
+ .code-content :deep(.hljs-property) { color: #e06c75; }
107
+ .code-content :deep(.hljs-attr) { color: #d19a66; }
108
+ .code-content :deep(.hljs-built_in) { color: #e5c07b; }
109
+ .code-content :deep(.hljs-title) { color: #61afef; }
110
+ .code-content :deep(.hljs-params) { color: #abb2bf; }
111
+ .code-content :deep(.hljs-literal) { color: #56b6c2; }
112
+ .code-content :deep(.hljs-type) { color: #e5c07b; }
113
+ </style>
@@ -0,0 +1,21 @@
1
+ <template>
2
+ <div class="text-block">{{ block.content }}</div>
3
+ </template>
4
+
5
+ <script setup lang="ts">
6
+ import type { TextBlock } from '@huyooo/ai-chat-shared'
7
+
8
+ defineProps<{
9
+ block: TextBlock
10
+ }>()
11
+ </script>
12
+
13
+ <style scoped>
14
+ .text-block {
15
+ font-size: 14px;
16
+ line-height: 1.7;
17
+ color: var(--chat-text, #ccc);
18
+ white-space: pre-wrap;
19
+ word-break: break-word;
20
+ }
21
+ </style>
@@ -0,0 +1,6 @@
1
+ /**
2
+ * 内容块组件导出
3
+ */
4
+
5
+ export { default as TextBlock } from './TextBlock.vue'
6
+ export { default as CodeBlock } from './CodeBlock.vue'
@@ -0,0 +1,135 @@
1
+ <template>
2
+ <div :class="['collapsible-card', { expanded: modelValue }]">
3
+ <div class="card-header">
4
+ <div class="card-header-left" @click="emit('update:modelValue', !modelValue)">
5
+ <div class="card-icon" :style="{ color: iconColor }">
6
+ <Icon v-if="spinning" icon="lucide:loader-2" width="14" class="spinning" />
7
+ <Icon v-else :icon="icon" width="14" />
8
+ </div>
9
+ <span class="card-title" :style="titleColor ? { color: titleColor } : {}">{{ title }}</span>
10
+ <span v-if="subtitle" class="card-subtitle">{{ subtitle }}</span>
11
+ </div>
12
+ <div class="card-header-right">
13
+ <slot name="header-actions" />
14
+ <Icon
15
+ v-if="collapsible"
16
+ :icon="modelValue ? 'lucide:chevron-up' : 'lucide:chevron-down'"
17
+ width="14"
18
+ class="card-chevron"
19
+ @click.stop="emit('update:modelValue', !modelValue)"
20
+ />
21
+ </div>
22
+ </div>
23
+ <div v-if="modelValue" class="card-content">
24
+ <slot />
25
+ </div>
26
+ </div>
27
+ </template>
28
+
29
+ <script setup lang="ts">
30
+ import { Icon } from '@iconify/vue'
31
+
32
+ withDefaults(defineProps<{
33
+ /** 图标名称 */
34
+ icon: string
35
+ /** 图标颜色 */
36
+ iconColor?: string
37
+ /** 标题 */
38
+ title: string
39
+ /** 标题颜色 */
40
+ titleColor?: string
41
+ /** 副标题 */
42
+ subtitle?: string
43
+ /** 是否展开 */
44
+ modelValue: boolean
45
+ /** 图标是否旋转(加载状态) */
46
+ spinning?: boolean
47
+ /** 是否可折叠(显示 chevron) */
48
+ collapsible?: boolean
49
+ }>(), {
50
+ iconColor: 'var(--chat-accent, #3b82f6)',
51
+ collapsible: true,
52
+ spinning: false,
53
+ })
54
+
55
+ const emit = defineEmits<{
56
+ 'update:modelValue': [value: boolean]
57
+ }>()
58
+ </script>
59
+
60
+ <style scoped>
61
+ .collapsible-card {
62
+ margin: 8px 0;
63
+ border-radius: 8px;
64
+ background: var(--chat-muted, #2a2a2a);
65
+ border: 1px solid var(--chat-border, #333);
66
+ overflow: hidden;
67
+ }
68
+
69
+ .card-header {
70
+ display: flex;
71
+ align-items: center;
72
+ justify-content: space-between;
73
+ gap: 8px;
74
+ padding: 8px 12px;
75
+ user-select: none;
76
+ }
77
+
78
+ .card-header-left {
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 8px;
82
+ flex: 1;
83
+ cursor: pointer;
84
+ }
85
+
86
+ .card-header-right {
87
+ display: flex;
88
+ align-items: center;
89
+ gap: 4px;
90
+ flex-shrink: 0;
91
+ }
92
+
93
+ .card-icon {
94
+ display: flex;
95
+ align-items: center;
96
+ justify-content: center;
97
+ flex-shrink: 0;
98
+ }
99
+
100
+ .spinning {
101
+ animation: spin 1s linear infinite;
102
+ }
103
+
104
+ @keyframes spin {
105
+ from { transform: rotate(0deg); }
106
+ to { transform: rotate(360deg); }
107
+ }
108
+
109
+ .card-title {
110
+ font-size: 13px;
111
+ color: var(--chat-text-muted, #888);
112
+ }
113
+
114
+ .card-subtitle {
115
+ font-size: 12px;
116
+ color: var(--chat-text-muted, #666);
117
+ }
118
+
119
+ .card-chevron {
120
+ color: var(--chat-text-muted, #666);
121
+ transition: transform 0.2s;
122
+ flex-shrink: 0;
123
+ cursor: pointer;
124
+ }
125
+
126
+ .collapsible-card.expanded .card-chevron {
127
+ transform: rotate(180deg);
128
+ }
129
+
130
+ .card-content {
131
+ padding: 12px;
132
+ min-width: 0;
133
+ overflow: hidden;
134
+ }
135
+ </style>
@@ -0,0 +1,51 @@
1
+ <template>
2
+ <CollapsibleCard
3
+ v-model="expanded"
4
+ icon="lucide:alert-circle"
5
+ icon-color="var(--chat-error, #ef4444)"
6
+ :title="categoryLabel"
7
+ title-color="var(--chat-error, #ef4444)"
8
+ >
9
+ <div class="error-content">{{ message }}</div>
10
+ </CollapsibleCard>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ import { ref, computed } from 'vue'
15
+ import CollapsibleCard from './CollapsibleCard.vue'
16
+
17
+ const props = defineProps<{
18
+ message: string
19
+ category?: string
20
+ retryable?: boolean
21
+ }>()
22
+
23
+ // 默认展开显示错误信息
24
+ const expanded = ref(true)
25
+
26
+ /** 分类标签映射 */
27
+ const categoryLabel = computed(() => {
28
+ const labels: Record<string, string> = {
29
+ api: 'API 错误',
30
+ network: '网络错误',
31
+ timeout: '请求超时',
32
+ auth: '认证失败',
33
+ rate_limit: '请求限流',
34
+ server: '服务器错误',
35
+ unknown: '未知错误',
36
+ }
37
+ return labels[props.category || 'unknown'] || props.category || '请求失败'
38
+ })
39
+ </script>
40
+
41
+ <style scoped>
42
+ .error-content {
43
+ font-size: 13px;
44
+ line-height: 1.5;
45
+ color: var(--chat-text-muted, #999);
46
+ white-space: pre-wrap;
47
+ word-break: break-word;
48
+ overflow-wrap: break-word;
49
+ margin: 0;
50
+ }
51
+ </style>