@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,580 @@
1
+ <template>
2
+ <div class="indexing-settings">
3
+ <!-- 代码库索引部分 -->
4
+ <div class="setting-item indexing-section">
5
+ <div class="setting-info">
6
+ <div class="setting-label">代码库索引</div>
7
+ <p class="setting-description">
8
+ 嵌入代码库以改进上下文理解和知识。所有数据(嵌入向量、元数据和代码)都存储在本地。
9
+ </p>
10
+ <div class="indexing-content">
11
+ <!-- 进度条 -->
12
+ <div class="progress-container">
13
+ <div class="progress-bar">
14
+ <div
15
+ class="progress-fill"
16
+ :style="{ width: `${progressPercentage}%` }"
17
+ ></div>
18
+ </div>
19
+ <div class="progress-text">
20
+ <span v-if="isIndexing">
21
+ <span v-if="currentStage === 'scanning'">
22
+ 正在扫描文件... (已扫描 {{ progressIndexed.toLocaleString() }} 个文件)
23
+ </span>
24
+ <span v-else>
25
+ {{ progressPercentage }}% ({{ progressIndexed }}/{{ progressTotal }})
26
+ </span>
27
+ </span>
28
+ <span v-else>{{ indexedFiles.toLocaleString() }} 个文件</span>
29
+ </div>
30
+ </div>
31
+
32
+ <!-- 统计信息 -->
33
+ <div v-if="!isIndexing && indexedFiles > 0" class="stats-info">
34
+ <div class="stat-item">
35
+ <Icon icon="lucide:database" width="14" />
36
+ <span>索引大小: {{ indexSize }}</span>
37
+ </div>
38
+ <div v-if="lastUpdated" class="stat-item">
39
+ <Icon icon="lucide:clock" width="14" />
40
+ <span>最后更新: {{ lastUpdated }}</span>
41
+ </div>
42
+ </div>
43
+
44
+ <!-- 当前文件信息 -->
45
+ <div v-if="isIndexing && currentFile" class="current-file">
46
+ <Icon icon="lucide:file-text" width="14" />
47
+ <span class="file-path">{{ currentFile }}</span>
48
+ </div>
49
+
50
+ <!-- 操作按钮 -->
51
+ <div class="action-buttons">
52
+ <button
53
+ v-if="!isIndexing"
54
+ class="edit-btn"
55
+ @click="handleSync"
56
+ >
57
+ <Icon icon="lucide:refresh-cw" width="16" />
58
+ <span>同步</span>
59
+ </button>
60
+ <button
61
+ v-else
62
+ class="edit-btn"
63
+ @click="handleCancelIndex"
64
+ >
65
+ <Icon icon="lucide:x" width="16" />
66
+ <span>取消</span>
67
+ </button>
68
+ <button
69
+ class="edit-btn delete-btn"
70
+ :disabled="isIndexing"
71
+ @click="showDeleteConfirm = true"
72
+ >
73
+ <Icon icon="lucide:trash-2" width="16" />
74
+ <span>删除索引</span>
75
+ </button>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ </div>
80
+ </div>
81
+
82
+ <!-- 删除确认对话框 -->
83
+ <ConfirmDialog
84
+ v-model:visible="showDeleteConfirm"
85
+ type="danger"
86
+ title="删除索引"
87
+ :message="`确定要删除所有索引吗?\n\n这将删除 ${indexedFiles.toLocaleString()} 个文件的索引数据(${indexSize}),此操作无法撤销。`"
88
+ confirm-text="删除"
89
+ cancel-text="取消"
90
+ @confirm="handleDeleteIndex"
91
+ />
92
+ </template>
93
+
94
+ <script setup lang="ts">
95
+ import { ref, computed, onMounted, onUnmounted } from 'vue'
96
+ import { Icon } from '@iconify/vue'
97
+ import ConfirmDialog from './ConfirmDialog.vue'
98
+
99
+ // 索引状态
100
+ const indexedFiles = ref(0)
101
+ const isIndexing = ref(false)
102
+ const currentFile = ref<string | null>(null)
103
+ const progressPercentage = ref(100)
104
+ const progressIndexed = ref(0)
105
+ const progressTotal = ref(0)
106
+ const currentStage = ref<'scanning' | 'parsing' | 'embedding' | 'storing' | 'done' | null>(null)
107
+ const showDeleteConfirm = ref(false)
108
+ const indexSize = ref('0 B')
109
+ const lastUpdated = ref<string | null>(null)
110
+ const cancelIndexing = ref(false)
111
+
112
+ /** 格式化文件大小 */
113
+ function formatSize(bytes: number): string {
114
+ if (bytes < 1024) return `${bytes} B`
115
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
116
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
117
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`
118
+ }
119
+
120
+ /** 格式化日期 */
121
+ function formatDate(date: Date): string {
122
+ const now = new Date()
123
+ const diff = now.getTime() - date.getTime()
124
+ const days = Math.floor(diff / (1000 * 60 * 60 * 24))
125
+
126
+ if (days === 0) {
127
+ const hours = Math.floor(diff / (1000 * 60 * 60))
128
+ if (hours === 0) {
129
+ const minutes = Math.floor(diff / (1000 * 60))
130
+ return minutes <= 1 ? '刚刚' : `${minutes} 分钟前`
131
+ }
132
+ return `${hours} 小时前`
133
+ }
134
+
135
+ if (days === 1) return '昨天'
136
+ if (days < 7) return `${days} 天前`
137
+ if (days < 30) return `${Math.floor(days / 7)} 周前`
138
+ if (days < 365) return `${Math.floor(days / 30)} 个月前`
139
+ return date.toLocaleDateString('zh-CN', { year: 'numeric', month: 'short', day: 'numeric' })
140
+ }
141
+
142
+ /** 获取索引统计信息 */
143
+ async function fetchIndexStats() {
144
+ try {
145
+ const bridge = (window as any).aiChatBridge
146
+ if (bridge?.getIndexStats) {
147
+ const stats = await bridge.getIndexStats()
148
+ indexedFiles.value = stats.totalDocuments || 0
149
+ indexSize.value = formatSize(stats.indexSize || 0)
150
+ lastUpdated.value = stats.lastUpdated ? formatDate(new Date(stats.lastUpdated)) : null
151
+ } else {
152
+ // Bridge 方法未实现,显示默认值
153
+ indexedFiles.value = 0
154
+ indexSize.value = '0 B'
155
+ lastUpdated.value = null
156
+ console.warn('getIndexStats 方法未在 bridge 中实现')
157
+ }
158
+ } catch (error) {
159
+ console.error('获取索引统计失败:', error)
160
+ // 出错时显示默认值
161
+ indexedFiles.value = 0
162
+ indexSize.value = '0 B'
163
+ lastUpdated.value = null
164
+ }
165
+ }
166
+
167
+ /** 同步索引 */
168
+ async function handleSync() {
169
+ if (isIndexing.value) return
170
+
171
+ const bridge = (window as any).aiChatBridge
172
+ if (!bridge?.syncIndex) {
173
+ console.error('syncIndex 方法未在 bridge 中实现')
174
+ return
175
+ }
176
+
177
+ // 确保进度监听器已设置
178
+ if (!progressCleanup) {
179
+ setupProgressListener()
180
+ }
181
+
182
+ isIndexing.value = true
183
+ cancelIndexing.value = false
184
+ progressPercentage.value = 0
185
+ progressIndexed.value = 0
186
+ progressTotal.value = 0
187
+ currentStage.value = null
188
+ currentFile.value = null
189
+
190
+ try {
191
+ await bridge.syncIndex()
192
+ } catch (error) {
193
+ if (!cancelIndexing.value) {
194
+ console.error('同步索引失败:', error)
195
+ }
196
+ isIndexing.value = false
197
+ currentFile.value = null
198
+ }
199
+ }
200
+
201
+ /** 取消索引 */
202
+ async function handleCancelIndex() {
203
+ cancelIndexing.value = true
204
+ isIndexing.value = false
205
+ currentFile.value = null
206
+
207
+ const bridge = (window as any).aiChatBridge
208
+ if (bridge?.cancelIndex) {
209
+ try {
210
+ await bridge.cancelIndex()
211
+ } catch (error) {
212
+ console.error('取消索引失败:', error)
213
+ }
214
+ }
215
+ }
216
+
217
+
218
+ /** 删除索引 */
219
+ async function handleDeleteIndex() {
220
+ if (isIndexing.value) return
221
+
222
+ showDeleteConfirm.value = false
223
+
224
+ const bridge = (window as any).aiChatBridge
225
+ if (!bridge?.deleteIndex) {
226
+ console.error('deleteIndex 方法未在 bridge 中实现')
227
+ return
228
+ }
229
+
230
+ try {
231
+ await bridge.deleteIndex()
232
+
233
+ // 刷新统计
234
+ await fetchIndexStats()
235
+ } catch (error) {
236
+ console.error('删除索引失败:', error)
237
+ }
238
+ }
239
+
240
+ // 进度监听器清理函数
241
+ let progressCleanup: (() => void) | null = null
242
+
243
+ /** 设置进度监听器 */
244
+ function setupProgressListener() {
245
+ // 如果已有监听器,先清理
246
+ if (progressCleanup) {
247
+ progressCleanup()
248
+ progressCleanup = null
249
+ }
250
+
251
+ const bridge = (window as any).aiChatBridge
252
+ if (!bridge?.onIndexProgress) {
253
+ return
254
+ }
255
+
256
+ // 订阅进度更新(始终监听,即使没有主动触发索引)
257
+ progressCleanup = bridge.onIndexProgress((progress: {
258
+ indexed: number
259
+ total: number
260
+ currentFile?: string
261
+ stage: string
262
+ error?: string
263
+ }) => {
264
+ if (progress.error) {
265
+ console.error('索引错误:', progress.error)
266
+ isIndexing.value = false
267
+ currentFile.value = null
268
+ return
269
+ }
270
+
271
+ if (progress.stage === 'cancelled') {
272
+ isIndexing.value = false
273
+ currentStage.value = null
274
+ currentFile.value = null
275
+ cancelIndexing.value = true
276
+ return
277
+ }
278
+
279
+ // 如果收到进度更新,说明正在索引
280
+ if (progress.stage !== 'done') {
281
+ isIndexing.value = true
282
+ }
283
+
284
+ // 更新阶段
285
+ currentStage.value = progress.stage as 'scanning' | 'parsing' | 'embedding' | 'storing' | 'done' | null
286
+
287
+ // 扫描阶段:total 为 0,使用 indexed 表示已扫描的文件数
288
+ if (progress.stage === 'scanning') {
289
+ progressIndexed.value = progress.indexed
290
+ progressTotal.value = 0
291
+ progressPercentage.value = 0 // 扫描阶段不显示百分比
292
+ } else {
293
+ progressIndexed.value = progress.indexed
294
+ progressTotal.value = progress.total
295
+ progressPercentage.value = progress.total > 0
296
+ ? Math.round((progress.indexed / progress.total) * 100)
297
+ : 0
298
+ }
299
+ currentFile.value = progress.currentFile || null
300
+
301
+ if (progress.stage === 'done') {
302
+ isIndexing.value = false
303
+ currentStage.value = null
304
+ currentFile.value = null
305
+ // 同步完成后刷新统计
306
+ fetchIndexStats()
307
+ }
308
+ })
309
+ }
310
+
311
+ /** 检查索引状态并恢复进度显示 */
312
+ async function checkIndexStatus() {
313
+ const bridge = (window as any).aiChatBridge
314
+ if (!bridge?.getIndexStatus) {
315
+ return
316
+ }
317
+
318
+ try {
319
+ const status = await bridge.getIndexStatus()
320
+ if (status.isIndexing && status.lastProgress) {
321
+ // 如果有正在进行的索引任务,恢复进度显示
322
+ isIndexing.value = true
323
+ const progress = status.lastProgress
324
+
325
+ // 恢复进度状态
326
+ currentStage.value = progress.stage as 'scanning' | 'parsing' | 'embedding' | 'storing' | 'done' | null
327
+
328
+ if (progress.stage === 'scanning') {
329
+ progressIndexed.value = progress.indexed
330
+ progressTotal.value = 0
331
+ progressPercentage.value = 0
332
+ } else {
333
+ progressIndexed.value = progress.indexed
334
+ progressTotal.value = progress.total
335
+ progressPercentage.value = progress.total > 0
336
+ ? Math.round((progress.indexed / progress.total) * 100)
337
+ : 0
338
+ }
339
+
340
+ currentFile.value = progress.currentFile || null
341
+ }
342
+ } catch (error) {
343
+ console.error('检查索引状态失败:', error)
344
+ }
345
+ }
346
+
347
+ // 组件挂载时获取统计信息、注册监听器、检查索引状态并设置进度监听
348
+ onMounted(async () => {
349
+ const bridge = (window as any).aiChatBridge
350
+
351
+ // 注册索引进度监听器
352
+ if (bridge?.registerIndexListener) {
353
+ try {
354
+ await bridge.registerIndexListener()
355
+ } catch (error) {
356
+ console.error('注册索引进度监听器失败:', error)
357
+ }
358
+ }
359
+
360
+ await fetchIndexStats()
361
+ setupProgressListener()
362
+ await checkIndexStatus()
363
+ })
364
+
365
+ // 组件卸载时清理进度监听器
366
+ onUnmounted(() => {
367
+ const bridge = (window as any).aiChatBridge
368
+
369
+ // 注销索引进度监听器
370
+ if (bridge?.unregisterIndexListener) {
371
+ try {
372
+ bridge.unregisterIndexListener()
373
+ } catch (error) {
374
+ console.error('注销索引进度监听器失败:', error)
375
+ }
376
+ }
377
+
378
+ if (progressCleanup) {
379
+ progressCleanup()
380
+ progressCleanup = null
381
+ }
382
+ })
383
+ </script>
384
+
385
+ <style scoped>
386
+ .indexing-settings {
387
+ display: flex;
388
+ flex-direction: column;
389
+ gap: 0;
390
+ }
391
+
392
+ /* 索引部分 - 使用标准的 setting-item 样式 */
393
+ .indexing-section {
394
+ align-items: flex-start;
395
+ }
396
+
397
+
398
+ .indexing-content {
399
+ display: flex;
400
+ flex-direction: column;
401
+ gap: 12px;
402
+ margin-top: 12px;
403
+ width: 100%;
404
+ min-width: 0;
405
+ }
406
+
407
+ /* 进度条 */
408
+ .progress-container {
409
+ display: flex;
410
+ flex-direction: column;
411
+ gap: 6px;
412
+ }
413
+
414
+ .progress-bar {
415
+ width: 100%;
416
+ height: 4px;
417
+ background: var(--chat-border, #333);
418
+ border-radius: 2px;
419
+ overflow: hidden;
420
+ }
421
+
422
+ .progress-fill {
423
+ height: 100%;
424
+ background: var(--chat-accent, #3b82f6);
425
+ border-radius: 2px;
426
+ transition: width 0.3s ease;
427
+ }
428
+
429
+ .progress-text {
430
+ font-size: 13px;
431
+ color: var(--chat-text-muted, #888);
432
+ }
433
+
434
+ /* 统计信息 */
435
+ .stats-info {
436
+ display: flex;
437
+ flex-wrap: wrap;
438
+ gap: 12px;
439
+ padding: 8px 0;
440
+ }
441
+
442
+ .stat-item {
443
+ display: flex;
444
+ align-items: center;
445
+ gap: 6px;
446
+ font-size: 12px;
447
+ color: var(--chat-text-muted, #888);
448
+ }
449
+
450
+ /* 当前文件 */
451
+ .current-file {
452
+ display: flex;
453
+ align-items: center;
454
+ gap: 6px;
455
+ font-size: 12px;
456
+ color: var(--chat-text-muted, #888);
457
+ padding: 6px 8px;
458
+ background: var(--chat-muted, #2a2a2a);
459
+ border-radius: 4px;
460
+ width: 100%;
461
+ min-width: 0;
462
+ }
463
+
464
+ .file-path {
465
+ flex: 1;
466
+ overflow: hidden;
467
+ text-overflow: ellipsis;
468
+ white-space: nowrap;
469
+ min-width: 0;
470
+ }
471
+
472
+ /* 操作按钮 */
473
+ .action-buttons {
474
+ display: flex;
475
+ gap: 8px;
476
+ }
477
+
478
+ .action-buttons .edit-btn {
479
+ margin: 0;
480
+ }
481
+
482
+ .action-buttons .edit-btn:disabled {
483
+ opacity: 0.5;
484
+ cursor: not-allowed;
485
+ }
486
+
487
+ .action-buttons .delete-btn {
488
+ color: #ef4444;
489
+ }
490
+
491
+ .action-buttons .delete-btn:hover:not(:disabled) {
492
+ color: #ef4444;
493
+ }
494
+
495
+ .spinning {
496
+ animation: spin 1s linear infinite;
497
+ }
498
+
499
+ @keyframes spin {
500
+ from {
501
+ transform: rotate(0deg);
502
+ }
503
+ to {
504
+ transform: rotate(360deg);
505
+ }
506
+ }
507
+
508
+ /* 设置项 - 复用父组件的样式,但需要覆盖一些 */
509
+ .indexing-settings .setting-item {
510
+ display: flex;
511
+ align-items: center;
512
+ justify-content: space-between;
513
+ padding: 16px 0;
514
+ border-bottom: 1px solid var(--chat-border, #333);
515
+ }
516
+
517
+ .indexing-settings .setting-item:last-child {
518
+ border-bottom: none;
519
+ }
520
+
521
+ .indexing-settings .setting-info {
522
+ flex: 1;
523
+ margin-right: 24px;
524
+ min-width: 0;
525
+ }
526
+
527
+ .indexing-settings .setting-label {
528
+ font-size: 14px;
529
+ font-weight: 500;
530
+ color: var(--chat-text, #fff);
531
+ margin-bottom: 4px;
532
+ }
533
+
534
+ .indexing-settings .setting-description {
535
+ font-size: 13px;
536
+ color: var(--chat-text-muted, #888);
537
+ line-height: 1.5;
538
+ margin-bottom: 0;
539
+ }
540
+
541
+ .link-btn {
542
+ display: inline;
543
+ padding: 0;
544
+ background: transparent;
545
+ border: none;
546
+ color: var(--chat-accent, #3b82f6);
547
+ cursor: pointer;
548
+ text-decoration: underline;
549
+ font-size: inherit;
550
+ }
551
+
552
+ .link-btn:hover {
553
+ color: var(--chat-accent-hover, #60a5fa);
554
+ }
555
+
556
+ .indexing-settings .setting-control {
557
+ flex-shrink: 0;
558
+ }
559
+
560
+ .edit-btn {
561
+ display: flex;
562
+ align-items: center;
563
+ gap: 6px;
564
+ padding: 6px 12px;
565
+ font-size: 13px;
566
+ font-weight: 500;
567
+ border: 1px solid var(--chat-border, #333);
568
+ border-radius: 6px;
569
+ background: transparent;
570
+ color: var(--chat-text, #fff);
571
+ cursor: pointer;
572
+ transition: all 0.15s;
573
+ }
574
+
575
+ .edit-btn:hover {
576
+ background: var(--chat-muted, #2a2a2a);
577
+ border-color: var(--chat-border, #444);
578
+ }
579
+ </style>
580
+