@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
@@ -1,649 +0,0 @@
1
- <template>
2
- <div :class="['chat-input', { 'message-variant': isMessageVariant }]">
3
- <div
4
- ref="containerRef"
5
- :class="['input-container', { focused: isFocused }]"
6
- >
7
- <!-- 附件预览 -->
8
- <div v-if="selectedImages.length > 0" class="attachment-preview">
9
- <div class="preview-images">
10
- <template v-for="(img, index) in selectedPreview" :key="`${img}-${index}`">
11
- <div class="preview-item">
12
- <img
13
- :src="getImageUrl(img)"
14
- class="preview-thumb"
15
- @error="handleImageError"
16
- />
17
- <button
18
- v-if="!isMessageVariant"
19
- class="remove-btn"
20
- :title="`移除图片 ${index + 1}`"
21
- @click="removeAttachment(index)"
22
- >
23
- <X :size="10" />
24
- </button>
25
- </div>
26
- </template>
27
- <div v-if="selectedImages.length > 3" class="preview-more">
28
- +{{ selectedImages.length - 3 }}
29
- </div>
30
- </div>
31
- </div>
32
-
33
- <!-- 输入框区域 -->
34
- <div class="input-field-wrapper">
35
- <textarea
36
- ref="inputRef"
37
- v-model="inputText"
38
- :placeholder="placeholder"
39
- rows="1"
40
- class="input-field"
41
- @keydown="handleKeydown"
42
- @input="adjustTextareaHeight"
43
- @focus="isFocused = true"
44
- ></textarea>
45
- </div>
46
-
47
- <!-- 底部控制栏 -->
48
- <div v-if="showToolbar" class="input-controls">
49
- <!-- 左侧:模式和模型选择 -->
50
- <div class="input-left">
51
- <!-- 模式选择 -->
52
- <div class="selector mode-selector" @click.stop="toggleModeMenu">
53
- <component :is="currentModeIcon" :size="12" />
54
- <span>{{ currentModeName }}</span>
55
- <ChevronDown :size="10" class="chevron" />
56
-
57
- <div v-if="modeMenuOpen" class="dropdown-menu" @click.stop>
58
- <button
59
- v-for="m in modes"
60
- :key="m.value"
61
- :class="['dropdown-item', { active: mode === m.value }]"
62
- @click="selectMode(m.value)"
63
- >
64
- <component :is="m.icon" :size="14" />
65
- <span>{{ m.label }}</span>
66
- <Check v-if="mode === m.value" :size="14" class="check-icon" />
67
- </button>
68
- </div>
69
- </div>
70
-
71
- <!-- 模型选择 -->
72
- <div class="selector model-selector" @click.stop="toggleModelMenu">
73
- <span>{{ currentModelName }}</span>
74
- <ChevronDown :size="10" class="chevron" />
75
-
76
- <div v-if="modelMenuOpen" class="dropdown-menu" @click.stop>
77
- <button
78
- v-for="m in models"
79
- :key="m.model"
80
- :class="['dropdown-item', { active: model === m.model }]"
81
- @click="selectModel(m)"
82
- >
83
- <span>{{ m.displayName }}</span>
84
- <Check v-if="model === m.model" :size="14" class="check-icon" />
85
- </button>
86
- </div>
87
- </div>
88
- </div>
89
-
90
- <!-- 右侧:功能按钮 -->
91
- <div class="input-right">
92
- <!-- @ 上下文 -->
93
- <button
94
- class="icon-btn"
95
- title="提及上下文 (@)"
96
- @click="$emit('at-context')"
97
- >
98
- <AtSign :size="14" />
99
- </button>
100
-
101
- <!-- 深度思考 -->
102
- <button
103
- :class="['toggle-btn', { active: thinkingEnabled }]"
104
- title="深度思考"
105
- @click="$emit('update:thinking', !thinkingEnabled)"
106
- >
107
- <Sparkles :size="14" />
108
- </button>
109
-
110
- <!-- 联网搜索 -->
111
- <button
112
- :class="['toggle-btn', { active: webSearchEnabled }]"
113
- title="联网搜索"
114
- @click="$emit('update:webSearch', !webSearchEnabled)"
115
- >
116
- <Globe :size="14" />
117
- </button>
118
-
119
- <!-- 上传图片 -->
120
- <button
121
- class="icon-btn"
122
- title="上传图片"
123
- @click="$emit('upload-image')"
124
- >
125
- <ImageIcon :size="14" />
126
- </button>
127
-
128
- <!-- 发送/停止按钮 -->
129
- <button
130
- v-if="inputText.trim() || isLoading"
131
- :class="['send-btn', { loading: isLoading }]"
132
- :title="isLoading ? '停止' : isMessageVariant ? '重新发送' : '发送'"
133
- @click="handleSendOrCancel"
134
- >
135
- <Square v-if="isLoading" :size="14" />
136
- <ArrowUp v-else :size="14" />
137
- </button>
138
- <button v-else class="icon-btn" title="语音输入">
139
- <Mic :size="14" />
140
- </button>
141
- </div>
142
- </div>
143
- </div>
144
- </div>
145
- </template>
146
-
147
- <script setup lang="ts">
148
- import { computed, ref, onMounted, onUnmounted, watch, markRaw } from 'vue';
149
- import {
150
- X,
151
- ChevronDown,
152
- Check,
153
- Globe,
154
- Sparkles,
155
- ImageIcon,
156
- Square,
157
- ArrowUp,
158
- Zap,
159
- MessageCircle,
160
- AtSign,
161
- Mic,
162
- } from 'lucide-vue-next';
163
- import type { ChatMode, ModelConfig } from '../types';
164
- import { DEFAULT_MODELS } from '../types';
165
-
166
- const props = withDefaults(
167
- defineProps<{
168
- /** 变体模式:input-底部输入框,message-历史消息 */
169
- variant?: 'input' | 'message';
170
- /** 受控值(用于历史消息编辑) */
171
- value?: string;
172
- selectedImages?: string[];
173
- isLoading?: boolean;
174
- mode?: ChatMode;
175
- model?: string;
176
- models?: ModelConfig[];
177
- webSearchEnabled?: boolean;
178
- thinkingEnabled?: boolean;
179
- }>(),
180
- {
181
- variant: 'input',
182
- value: '',
183
- selectedImages: () => [],
184
- isLoading: false,
185
- mode: 'agent',
186
- model: '',
187
- models: () => DEFAULT_MODELS,
188
- webSearchEnabled: false,
189
- thinkingEnabled: false,
190
- }
191
- );
192
-
193
- const emit = defineEmits<{
194
- send: [text: string];
195
- 'remove-image': [index: number];
196
- cancel: [];
197
- 'upload-image': [];
198
- 'at-context': [];
199
- 'update:mode': [mode: ChatMode];
200
- 'update:model': [model: string];
201
- 'update:webSearch': [enabled: boolean];
202
- 'update:thinking': [enabled: boolean];
203
- }>();
204
-
205
- // 是否为历史消息模式
206
- const isMessageVariant = computed(() => props.variant === 'message');
207
-
208
- const inputText = ref(props.value);
209
- const inputRef = ref<HTMLTextAreaElement | null>(null);
210
- const containerRef = ref<HTMLDivElement | null>(null);
211
- const isFocused = ref(false);
212
-
213
- // 同步外部 value
214
- watch(
215
- () => props.value,
216
- (newVal) => {
217
- inputText.value = newVal;
218
- }
219
- );
220
-
221
- // 模式配置
222
- const modes = [
223
- { value: 'agent' as const, label: 'Agent', icon: markRaw(Zap) },
224
- { value: 'ask' as const, label: 'Ask', icon: markRaw(MessageCircle) },
225
- ];
226
-
227
- // 菜单状态
228
- const modeMenuOpen = ref(false);
229
- const modelMenuOpen = ref(false);
230
-
231
- // 是否显示工具栏
232
- const showToolbar = computed(() => !isMessageVariant.value || isFocused.value);
233
-
234
- // 预览
235
- const selectedPreview = computed(() => props.selectedImages.slice(0, 3));
236
-
237
- // 占位符
238
- const placeholder = computed(() => {
239
- if (props.selectedImages.length > 0) return '描述你想要的效果...';
240
- if (props.mode === 'ask') return '有什么问题想问我?';
241
- return '描述任务,@ 添加上下文';
242
- });
243
-
244
- // 当前模式
245
- const currentModeName = computed(() => {
246
- const m = modes.find((item) => item.value === props.mode);
247
- return m?.label || 'Agent';
248
- });
249
-
250
- const currentModeIcon = computed(() => {
251
- const m = modes.find((item) => item.value === props.mode);
252
- return m?.icon || Zap;
253
- });
254
-
255
- // 当前模型
256
- const currentModelName = computed(() => {
257
- const m = props.models.find((item) => item.model === props.model);
258
- return m?.displayName || 'Auto';
259
- });
260
-
261
- // 切换菜单
262
- function toggleModeMenu() {
263
- modeMenuOpen.value = !modeMenuOpen.value;
264
- modelMenuOpen.value = false;
265
- }
266
-
267
- function toggleModelMenu() {
268
- modelMenuOpen.value = !modelMenuOpen.value;
269
- modeMenuOpen.value = false;
270
- }
271
-
272
- // 选择
273
- function selectMode(value: ChatMode) {
274
- emit('update:mode', value);
275
- modeMenuOpen.value = false;
276
- }
277
-
278
- function selectModel(m: ModelConfig) {
279
- emit('update:model', m.model);
280
- modelMenuOpen.value = false;
281
- }
282
-
283
- // 图片相关
284
- function getImageUrl(path: string): string {
285
- if (
286
- path.startsWith('app://') ||
287
- path.startsWith('file://') ||
288
- path.startsWith('data:') ||
289
- path.startsWith('http')
290
- ) {
291
- return path;
292
- }
293
- if (path.match(/^[A-Z]:\\/i)) {
294
- return `app://file${encodeURIComponent(path.replace(/\\/g, '/'))}`;
295
- }
296
- return `app://file${encodeURIComponent(path)}`;
297
- }
298
-
299
- function handleImageError(event: Event) {
300
- const img = event.target as HTMLImageElement;
301
- img.style.display = 'none';
302
- }
303
-
304
- function removeAttachment(index: number) {
305
- emit('remove-image', index);
306
- }
307
-
308
- // 自动调整高度
309
- function adjustTextareaHeight() {
310
- if (inputRef.value) {
311
- inputRef.value.style.height = 'auto';
312
- const scrollHeight = inputRef.value.scrollHeight;
313
- inputRef.value.style.height = `${Math.min(scrollHeight, 150)}px`;
314
- }
315
- }
316
-
317
- // 发送或取消
318
- function handleSendOrCancel() {
319
- if (props.isLoading) {
320
- emit('cancel');
321
- return;
322
- }
323
-
324
- const text = inputText.value.trim();
325
- if (!text) return;
326
-
327
- emit('send', text);
328
-
329
- if (!isMessageVariant.value) {
330
- inputText.value = '';
331
- if (inputRef.value) {
332
- inputRef.value.style.height = 'auto';
333
- }
334
- inputRef.value?.focus();
335
- } else {
336
- isFocused.value = false;
337
- }
338
- }
339
-
340
- function handleKeydown(event: KeyboardEvent) {
341
- if (event.key === 'Enter' && !event.shiftKey) {
342
- event.preventDefault();
343
- handleSendOrCancel();
344
- } else {
345
- setTimeout(adjustTextareaHeight, 0);
346
- }
347
- }
348
-
349
- // 点击外部
350
- function handleClickOutside(event: MouseEvent) {
351
- const target = event.target as HTMLElement;
352
- if (!target.closest('.selector')) {
353
- modeMenuOpen.value = false;
354
- modelMenuOpen.value = false;
355
- }
356
- if (
357
- isMessageVariant.value &&
358
- containerRef.value &&
359
- !containerRef.value.contains(target)
360
- ) {
361
- isFocused.value = false;
362
- }
363
- }
364
-
365
- onMounted(() => {
366
- document.addEventListener('click', handleClickOutside);
367
- });
368
-
369
- onUnmounted(() => {
370
- document.removeEventListener('click', handleClickOutside);
371
- });
372
-
373
- defineExpose({
374
- focus: () => inputRef.value?.focus(),
375
- setText: (text: string) => {
376
- inputText.value = text;
377
- },
378
- clear: () => {
379
- inputText.value = '';
380
- },
381
- });
382
- </script>
383
-
384
- <style scoped>
385
- .chat-input {
386
- padding: 12px;
387
- }
388
-
389
- .chat-input.message-variant {
390
- padding: 0;
391
- margin-bottom: 16px;
392
- }
393
-
394
- .input-container {
395
- display: flex;
396
- flex-direction: column;
397
- background: var(--chat-input-bg, #2d2d2d);
398
- border: 1px solid var(--chat-border, #444);
399
- border-radius: 12px;
400
- padding: 12px;
401
- transition: border-color 0.15s;
402
- }
403
-
404
- .input-container.focused {
405
- border-color: rgba(255, 255, 255, 0.2);
406
- }
407
-
408
- /* 附件预览 */
409
- .attachment-preview {
410
- margin-bottom: 8px;
411
- }
412
-
413
- .preview-images {
414
- display: flex;
415
- gap: 6px;
416
- flex-wrap: wrap;
417
- }
418
-
419
- .preview-item {
420
- position: relative;
421
- }
422
-
423
- .preview-thumb {
424
- width: 48px;
425
- height: 48px;
426
- object-fit: cover;
427
- border-radius: 8px;
428
- border: 1px solid var(--chat-border, #444);
429
- }
430
-
431
- .preview-more {
432
- display: flex;
433
- align-items: center;
434
- justify-content: center;
435
- width: 48px;
436
- height: 48px;
437
- background: var(--chat-muted, #3c3c3c);
438
- border-radius: 8px;
439
- color: var(--chat-text-muted, #888);
440
- font-size: 12px;
441
- }
442
-
443
- .remove-btn {
444
- position: absolute;
445
- top: -4px;
446
- right: -4px;
447
- width: 16px;
448
- height: 16px;
449
- display: flex;
450
- align-items: center;
451
- justify-content: center;
452
- background: var(--chat-bg, #1e1e1e);
453
- border: 1px solid var(--chat-border, #444);
454
- border-radius: 50%;
455
- color: var(--chat-text-muted, #888);
456
- cursor: pointer;
457
- padding: 0;
458
- }
459
-
460
- .remove-btn:hover {
461
- background: var(--chat-muted, #3c3c3c);
462
- color: var(--chat-text, #ccc);
463
- }
464
-
465
- /* 输入框 */
466
- .input-field-wrapper {
467
- margin-bottom: 8px;
468
- }
469
-
470
- .input-field {
471
- width: 100%;
472
- background: transparent;
473
- border: none;
474
- padding: 0;
475
- color: var(--chat-text, #ccc);
476
- font-size: 14px;
477
- resize: none;
478
- min-height: 24px;
479
- max-height: 150px;
480
- line-height: 1.5;
481
- font-family: inherit;
482
- }
483
-
484
- .input-field:focus {
485
- outline: none;
486
- }
487
-
488
- .input-field::placeholder {
489
- color: var(--chat-text-muted, #666);
490
- }
491
-
492
- /* 底部控制栏 */
493
- .input-controls {
494
- display: flex;
495
- align-items: center;
496
- justify-content: space-between;
497
- gap: 8px;
498
- }
499
-
500
- .input-left {
501
- display: flex;
502
- align-items: center;
503
- gap: 4px;
504
- }
505
-
506
- /* 选择器 */
507
- .selector {
508
- position: relative;
509
- display: flex;
510
- align-items: center;
511
- gap: 4px;
512
- padding: 4px 8px;
513
- background: var(--chat-muted, #3c3c3c);
514
- border: none;
515
- border-radius: 6px;
516
- font-size: 12px;
517
- color: var(--chat-text-muted, #888);
518
- cursor: pointer;
519
- transition: all 0.15s;
520
- }
521
-
522
- .selector:hover {
523
- background: var(--chat-muted-hover, #444);
524
- color: var(--chat-text, #ccc);
525
- }
526
-
527
- .chevron {
528
- color: var(--chat-text-muted, #666);
529
- }
530
-
531
- /* 下拉菜单 */
532
- .dropdown-menu {
533
- position: absolute;
534
- bottom: 100%;
535
- left: 0;
536
- margin-bottom: 4px;
537
- min-width: 160px;
538
- background: var(--chat-dropdown-bg, #252526);
539
- border: 1px solid rgba(255, 255, 255, 0.1);
540
- border-radius: 8px;
541
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
542
- z-index: 100;
543
- padding: 4px;
544
- }
545
-
546
- .dropdown-item {
547
- display: flex;
548
- align-items: center;
549
- gap: 8px;
550
- width: 100%;
551
- padding: 8px 10px;
552
- border: none;
553
- background: transparent;
554
- border-radius: 4px;
555
- font-size: 13px;
556
- color: var(--chat-text-muted, #999);
557
- cursor: pointer;
558
- transition: all 0.15s;
559
- }
560
-
561
- .dropdown-item:hover {
562
- background: rgba(255, 255, 255, 0.08);
563
- color: var(--chat-text, #ccc);
564
- }
565
-
566
- .dropdown-item.active {
567
- background: rgba(255, 255, 255, 0.1);
568
- color: var(--chat-text, #fff);
569
- }
570
-
571
- .check-icon {
572
- margin-left: auto;
573
- color: var(--chat-text, #ccc);
574
- }
575
-
576
- /* 右侧按钮 */
577
- .input-right {
578
- display: flex;
579
- align-items: center;
580
- gap: 2px;
581
- }
582
-
583
- .icon-btn {
584
- display: flex;
585
- align-items: center;
586
- justify-content: center;
587
- width: 28px;
588
- height: 28px;
589
- background: transparent;
590
- border: none;
591
- border-radius: 6px;
592
- color: var(--chat-text-muted, #666);
593
- cursor: pointer;
594
- transition: all 0.15s;
595
- }
596
-
597
- .icon-btn:hover {
598
- color: var(--chat-text, #ccc);
599
- }
600
-
601
- .toggle-btn {
602
- display: flex;
603
- align-items: center;
604
- justify-content: center;
605
- width: 28px;
606
- height: 28px;
607
- background: transparent;
608
- border: none;
609
- border-radius: 6px;
610
- color: var(--chat-text-muted, #666);
611
- cursor: pointer;
612
- transition: all 0.15s;
613
- }
614
-
615
- .toggle-btn:hover {
616
- color: var(--chat-text, #ccc);
617
- }
618
-
619
- .toggle-btn.active {
620
- color: var(--chat-text, #fff);
621
- background: rgba(255, 255, 255, 0.1);
622
- }
623
-
624
- .send-btn {
625
- display: flex;
626
- align-items: center;
627
- justify-content: center;
628
- width: 28px;
629
- height: 28px;
630
- background: rgba(255, 255, 255, 0.9);
631
- border: none;
632
- border-radius: 6px;
633
- color: #1e1e1e;
634
- cursor: pointer;
635
- transition: all 0.15s;
636
- }
637
-
638
- .send-btn:hover {
639
- background: #fff;
640
- }
641
-
642
- .send-btn.loading {
643
- background: var(--chat-destructive, #ef4444);
644
- }
645
-
646
- .send-btn.loading:hover {
647
- background: var(--chat-destructive-hover, #dc2626);
648
- }
649
- </style>