@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,63 @@
1
+ <template>
2
+ <div class="at-placeholder-view">
3
+ <Icon icon="lucide:message-square" width="32" class="at-placeholder-icon" />
4
+ <div class="at-placeholder-title">历史对话</div>
5
+ <div class="at-placeholder-desc">功能待实现</div>
6
+ <div class="at-placeholder-hint">将支持引用之前的对话记录</div>
7
+ </div>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ import { Icon } from '@iconify/vue';
12
+
13
+ defineProps<{
14
+ query: string;
15
+ activeIndex: number;
16
+ }>();
17
+
18
+ defineEmits<{
19
+ select: [path: string];
20
+ 'set-active': [index: number];
21
+ 'update-count': [count: number];
22
+ }>();
23
+
24
+ // 暴露给父组件(空实现)
25
+ defineExpose({
26
+ getActivePath: () => null,
27
+ confirmActive: () => {},
28
+ });
29
+ </script>
30
+
31
+ <style scoped>
32
+ .at-placeholder-view {
33
+ display: flex;
34
+ flex-direction: column;
35
+ align-items: center;
36
+ justify-content: center;
37
+ padding: 32px 16px;
38
+ text-align: center;
39
+ }
40
+
41
+ .at-placeholder-icon {
42
+ color: #555;
43
+ margin-bottom: 12px;
44
+ }
45
+
46
+ .at-placeholder-title {
47
+ font-size: 14px;
48
+ font-weight: 500;
49
+ color: #888;
50
+ margin-bottom: 8px;
51
+ }
52
+
53
+ .at-placeholder-desc {
54
+ font-size: 13px;
55
+ color: #666;
56
+ margin-bottom: 4px;
57
+ }
58
+
59
+ .at-placeholder-hint {
60
+ font-size: 11px;
61
+ color: #555;
62
+ }
63
+ </style>
@@ -0,0 +1,63 @@
1
+ <template>
2
+ <div class="at-placeholder-view">
3
+ <Icon icon="lucide:book-open" width="32" class="at-placeholder-icon" />
4
+ <div class="at-placeholder-title">文档</div>
5
+ <div class="at-placeholder-desc">功能待实现</div>
6
+ <div class="at-placeholder-hint">将支持引用项目文档、API 文档等</div>
7
+ </div>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ import { Icon } from '@iconify/vue';
12
+
13
+ defineProps<{
14
+ query: string;
15
+ activeIndex: number;
16
+ }>();
17
+
18
+ defineEmits<{
19
+ select: [path: string];
20
+ 'set-active': [index: number];
21
+ 'update-count': [count: number];
22
+ }>();
23
+
24
+ // 暴露给父组件(空实现)
25
+ defineExpose({
26
+ getActivePath: () => null,
27
+ confirmActive: () => {},
28
+ });
29
+ </script>
30
+
31
+ <style scoped>
32
+ .at-placeholder-view {
33
+ display: flex;
34
+ flex-direction: column;
35
+ align-items: center;
36
+ justify-content: center;
37
+ padding: 32px 16px;
38
+ text-align: center;
39
+ }
40
+
41
+ .at-placeholder-icon {
42
+ color: #555;
43
+ margin-bottom: 12px;
44
+ }
45
+
46
+ .at-placeholder-title {
47
+ font-size: 14px;
48
+ font-weight: 500;
49
+ color: #888;
50
+ margin-bottom: 8px;
51
+ }
52
+
53
+ .at-placeholder-desc {
54
+ font-size: 13px;
55
+ color: #666;
56
+ margin-bottom: 4px;
57
+ }
58
+
59
+ .at-placeholder-hint {
60
+ font-size: 11px;
61
+ color: #555;
62
+ }
63
+ </style>
@@ -0,0 +1,255 @@
1
+ <template>
2
+ <div class="at-files-view">
3
+ <div class="at-view-section-title">文件和文件夹</div>
4
+
5
+ <!-- 路径栏 -->
6
+ <div class="at-view-pathbar">
7
+ <button class="at-view-pathbtn" title="返回上级" @click="goUp">
8
+ <Icon icon="lucide:arrow-up" width="12" />
9
+ </button>
10
+ <button class="at-view-pathbtn" title="主目录" @click="goHome">
11
+ <Icon icon="lucide:home" width="12" />
12
+ </button>
13
+ <div class="at-view-pathtext" :title="currentPath || ''">
14
+ {{ currentPath || '-' }}
15
+ </div>
16
+ </div>
17
+
18
+ <!-- 文件列表 -->
19
+ <div class="at-view-list">
20
+ <div v-if="loading" class="at-view-empty">加载中...</div>
21
+ <div v-else-if="filteredEntries.length === 0" class="at-view-empty">
22
+ {{ query.trim() ? '没有找到匹配项' : '目录为空' }}
23
+ </div>
24
+ <button
25
+ v-for="(f, i) in filteredEntries"
26
+ :key="f.path"
27
+ :class="['at-view-item', { active: activeIndex === i }]"
28
+ @mouseenter="$emit('set-active', i)"
29
+ @click="handleEntryClick(f)"
30
+ >
31
+ <Icon :icon="f.isDirectory ? 'lucide:folder' : getFileIcon(f.name)" width="14" class="at-view-item-icon" />
32
+ <span class="at-view-item-name">{{ f.name }}</span>
33
+ <span class="at-view-item-path">{{ f.path }}</span>
34
+ </button>
35
+ </div>
36
+ </div>
37
+ </template>
38
+
39
+ <script setup lang="ts">
40
+ import { computed, onMounted, ref, watch } from 'vue';
41
+ import { Icon } from '@iconify/vue';
42
+ import type { ChatAdapter } from '../../../adapter';
43
+ import type { FileInfo } from '@huyooo/ai-chat-bridge-electron/renderer';
44
+ import { getFileIcon } from '../../../utils/fileIcon';
45
+
46
+ const props = defineProps<{
47
+ adapter: ChatAdapter;
48
+ initialDir?: string;
49
+ query: string;
50
+ activeIndex: number;
51
+ }>();
52
+
53
+ const emit = defineEmits<{
54
+ select: [path: string];
55
+ 'set-active': [index: number];
56
+ 'update-count': [count: number];
57
+ }>();
58
+
59
+ // 状态
60
+ const loading = ref(false);
61
+ const currentPath = ref('');
62
+ const entries = ref<FileInfo[]>([]);
63
+
64
+ // 过滤后的文件列表
65
+ const filteredEntries = computed(() => {
66
+ const q = props.query.trim().toLowerCase();
67
+ if (!q) return entries.value;
68
+ return entries.value.filter((f) => f.name.toLowerCase().includes(q));
69
+ });
70
+
71
+ // 通知父组件条目数量变化
72
+ watch(filteredEntries, (list) => {
73
+ emit('update-count', list.length);
74
+ }, { immediate: true });
75
+
76
+ // 加载目录
77
+ async function loadDir(dir: string) {
78
+ if (!props.adapter.listDir) return;
79
+ loading.value = true;
80
+ try {
81
+ const resolved = (await props.adapter.resolvePath?.(dir)) || dir;
82
+ const list = await props.adapter.listDir(resolved);
83
+ entries.value = [...list].sort((a, b) => {
84
+ if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1;
85
+ return a.name.localeCompare(b.name);
86
+ });
87
+ currentPath.value = resolved;
88
+ emit('set-active', -1);
89
+ } finally {
90
+ loading.value = false;
91
+ }
92
+ }
93
+
94
+ // 返回上级目录
95
+ async function goUp() {
96
+ if (!props.adapter.parentDir) return;
97
+ const parent = await props.adapter.parentDir(currentPath.value);
98
+ if (parent && parent !== currentPath.value) {
99
+ await loadDir(parent);
100
+ }
101
+ }
102
+
103
+ // 返回主目录
104
+ async function goHome() {
105
+ if (!props.adapter.homeDir) return;
106
+ const home = await props.adapter.homeDir();
107
+ await loadDir(home);
108
+ }
109
+
110
+ // 点击条目
111
+ function handleEntryClick(f: FileInfo) {
112
+ if (f.isDirectory) {
113
+ loadDir(f.path);
114
+ return;
115
+ }
116
+ emit('select', f.path);
117
+ }
118
+
119
+ // 获取当前活动项路径(供父组件调用)
120
+ function getActivePath(): string | null {
121
+ if (props.activeIndex < 0) return null;
122
+ return filteredEntries.value[props.activeIndex]?.path || null;
123
+ }
124
+
125
+ // 确认选择(供父组件调用)
126
+ function confirmActive() {
127
+ const entry = filteredEntries.value[props.activeIndex];
128
+ if (entry) {
129
+ handleEntryClick(entry);
130
+ }
131
+ }
132
+
133
+ // 初始加载
134
+ onMounted(async () => {
135
+ const dir = props.initialDir || (props.adapter.homeDir ? await props.adapter.homeDir() : '');
136
+ if (dir) await loadDir(dir);
137
+ });
138
+
139
+ // 暴露给父组件
140
+ defineExpose({
141
+ getActivePath,
142
+ confirmActive,
143
+ });
144
+ </script>
145
+
146
+ <style scoped>
147
+ .at-files-view {
148
+ display: flex;
149
+ flex-direction: column;
150
+ }
151
+
152
+ .at-view-section-title {
153
+ font-size: 11px;
154
+ color: #888;
155
+ padding: 6px 8px 4px;
156
+ text-transform: uppercase;
157
+ letter-spacing: 0.5px;
158
+ }
159
+
160
+ .at-view-pathbar {
161
+ display: flex;
162
+ align-items: center;
163
+ gap: 4px;
164
+ padding: 4px 8px 8px;
165
+ }
166
+
167
+ .at-view-pathbtn {
168
+ width: 22px;
169
+ height: 22px;
170
+ border-radius: 4px;
171
+ background: transparent;
172
+ border: 1px solid rgba(255, 255, 255, 0.1);
173
+ color: #999;
174
+ cursor: pointer;
175
+ display: flex;
176
+ align-items: center;
177
+ justify-content: center;
178
+ }
179
+
180
+ .at-view-pathbtn:hover {
181
+ background: rgba(255, 255, 255, 0.06);
182
+ color: #ccc;
183
+ }
184
+
185
+ .at-view-pathtext {
186
+ flex: 1;
187
+ min-width: 0;
188
+ font-size: 11px;
189
+ color: #777;
190
+ white-space: nowrap;
191
+ overflow: hidden;
192
+ text-overflow: ellipsis;
193
+ }
194
+
195
+ .at-view-list {
196
+ display: flex;
197
+ flex-direction: column;
198
+ gap: 1px;
199
+ }
200
+
201
+ .at-view-item {
202
+ display: flex;
203
+ align-items: center;
204
+ gap: 8px;
205
+ text-align: left;
206
+ padding: 7px 10px;
207
+ border-radius: 6px;
208
+ border: 1px solid transparent;
209
+ background: transparent;
210
+ cursor: pointer;
211
+ color: #ccc;
212
+ width: 100%;
213
+ }
214
+
215
+ .at-view-item:hover {
216
+ background: rgba(255, 255, 255, 0.06);
217
+ }
218
+
219
+ .at-view-item.active {
220
+ background: rgba(59, 130, 246, 0.15);
221
+ border-color: rgba(59, 130, 246, 0.3);
222
+ }
223
+
224
+ .at-view-item-icon {
225
+ color: #999;
226
+ flex-shrink: 0;
227
+ }
228
+
229
+ .at-view-item-name {
230
+ font-size: 13px;
231
+ color: #ddd;
232
+ flex-shrink: 0;
233
+ max-width: 160px;
234
+ overflow: hidden;
235
+ text-overflow: ellipsis;
236
+ white-space: nowrap;
237
+ }
238
+
239
+ .at-view-item-path {
240
+ font-size: 11px;
241
+ color: #555;
242
+ min-width: 0;
243
+ overflow: hidden;
244
+ text-overflow: ellipsis;
245
+ white-space: nowrap;
246
+ flex: 1;
247
+ }
248
+
249
+ .at-view-empty {
250
+ padding: 12px 10px;
251
+ color: #666;
252
+ font-size: 12px;
253
+ text-align: center;
254
+ }
255
+ </style>
@@ -0,0 +1,63 @@
1
+ <template>
2
+ <div class="at-placeholder-view">
3
+ <Icon icon="lucide:terminal" width="32" class="at-placeholder-icon" />
4
+ <div class="at-placeholder-title">终端</div>
5
+ <div class="at-placeholder-desc">功能待实现</div>
6
+ <div class="at-placeholder-hint">将支持引用终端输出、命令历史等</div>
7
+ </div>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ import { Icon } from '@iconify/vue';
12
+
13
+ defineProps<{
14
+ query: string;
15
+ activeIndex: number;
16
+ }>();
17
+
18
+ defineEmits<{
19
+ select: [path: string];
20
+ 'set-active': [index: number];
21
+ 'update-count': [count: number];
22
+ }>();
23
+
24
+ // 暴露给父组件(空实现)
25
+ defineExpose({
26
+ getActivePath: () => null,
27
+ confirmActive: () => {},
28
+ });
29
+ </script>
30
+
31
+ <style scoped>
32
+ .at-placeholder-view {
33
+ display: flex;
34
+ flex-direction: column;
35
+ align-items: center;
36
+ justify-content: center;
37
+ padding: 32px 16px;
38
+ text-align: center;
39
+ }
40
+
41
+ .at-placeholder-icon {
42
+ color: #555;
43
+ margin-bottom: 12px;
44
+ }
45
+
46
+ .at-placeholder-title {
47
+ font-size: 14px;
48
+ font-weight: 500;
49
+ color: #888;
50
+ margin-bottom: 8px;
51
+ }
52
+
53
+ .at-placeholder-desc {
54
+ font-size: 13px;
55
+ color: #666;
56
+ margin-bottom: 4px;
57
+ }
58
+
59
+ .at-placeholder-hint {
60
+ font-size: 11px;
61
+ color: #555;
62
+ }
63
+ </style>
@@ -0,0 +1,61 @@
1
+ <template>
2
+ <div v-if="blocks.length" class="content-renderer">
3
+ <template v-for="block in blocks" :key="block.id">
4
+ <!-- 自定义块渲染器 -->
5
+ <component
6
+ v-if="customRenderers[block.type]"
7
+ :is="customRenderers[block.type]"
8
+ :block="block"
9
+ />
10
+ <!-- 内置文本块 -->
11
+ <TextBlock v-else-if="block.type === 'text'" :block="block" />
12
+ <!-- 内置代码块 -->
13
+ <CodeBlock v-else-if="block.type === 'code'" :block="block" @copy="handleCodeCopy" />
14
+ </template>
15
+ </div>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ import { computed, inject, type Component } from 'vue'
20
+ import { parseContent } from '@huyooo/ai-chat-shared'
21
+ import type { ContentBlock } from '@huyooo/ai-chat-shared'
22
+ import { TextBlock, CodeBlock } from './blocks'
23
+
24
+ const props = defineProps<{
25
+ /** 原始文本内容 */
26
+ content: string
27
+ /** 预解析的块列表(可选,用于流式更新) */
28
+ blocks?: ContentBlock[]
29
+ }>()
30
+
31
+ const emit = defineEmits<{
32
+ /** 代码复制事件 */
33
+ 'code-copy': [code: string]
34
+ }>()
35
+
36
+ // 从上层注入的自定义块渲染器
37
+ const customRenderers = inject<Record<string, Component>>('blockRenderers', {})
38
+
39
+ // 解析后的内容块
40
+ const blocks = computed(() => {
41
+ // 优先使用预解析的块
42
+ if (props.blocks?.length) {
43
+ return props.blocks
44
+ }
45
+ // 否则解析原始内容
46
+ return parseContent(props.content)
47
+ })
48
+
49
+ /** 代码复制处理 */
50
+ function handleCodeCopy(code: string) {
51
+ emit('code-copy', code)
52
+ }
53
+ </script>
54
+
55
+ <style scoped>
56
+ .content-renderer {
57
+ display: flex;
58
+ flex-direction: column;
59
+ gap: 4px;
60
+ }
61
+ </style>