@huyooo/ai-chat-frontend-vue 0.1.2
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.
- package/dist/adapter.d.ts +87 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/components/ChatInput.vue.d.ts +54 -0
- package/dist/components/ChatInput.vue.d.ts.map +1 -0
- package/dist/components/ChatPanel.vue.d.ts +38 -0
- package/dist/components/ChatPanel.vue.d.ts.map +1 -0
- package/dist/components/chat/SearchResultBlock.vue.d.ts +8 -0
- package/dist/components/chat/SearchResultBlock.vue.d.ts.map +1 -0
- package/dist/components/chat/ThinkingBlock.vue.d.ts +7 -0
- package/dist/components/chat/ThinkingBlock.vue.d.ts.map +1 -0
- package/dist/components/chat/ToolCallBlock.vue.d.ts +9 -0
- package/dist/components/chat/ToolCallBlock.vue.d.ts.map +1 -0
- package/dist/components/chat/messages/ExecutionSteps.vue.d.ts +13 -0
- package/dist/components/chat/messages/ExecutionSteps.vue.d.ts.map +1 -0
- package/dist/components/chat/messages/MessageBubble.vue.d.ts +28 -0
- package/dist/components/chat/messages/MessageBubble.vue.d.ts.map +1 -0
- package/dist/components/chat/ui/ChatHeader.vue.d.ts +34 -0
- package/dist/components/chat/ui/ChatHeader.vue.d.ts.map +1 -0
- package/dist/components/chat/ui/WelcomeMessage.vue.d.ts +7 -0
- package/dist/components/chat/ui/WelcomeMessage.vue.d.ts.map +1 -0
- package/dist/composables/useChat.d.ts +96 -0
- package/dist/composables/useChat.d.ts.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1497 -0
- package/dist/preload/preload.d.ts +6 -0
- package/dist/preload/preload.d.ts.map +1 -0
- package/dist/style.css +1 -0
- package/dist/types/index.d.ts +107 -0
- package/dist/types/index.d.ts.map +1 -0
- package/package.json +59 -0
- package/src/adapter.ts +160 -0
- package/src/components/ChatInput.vue +649 -0
- package/src/components/ChatPanel.vue +309 -0
- package/src/components/chat/SearchResultBlock.vue +155 -0
- package/src/components/chat/ThinkingBlock.vue +109 -0
- package/src/components/chat/ToolCallBlock.vue +213 -0
- package/src/components/chat/messages/ExecutionSteps.vue +281 -0
- package/src/components/chat/messages/MessageBubble.vue +272 -0
- package/src/components/chat/ui/ChatHeader.vue +535 -0
- package/src/components/chat/ui/WelcomeMessage.vue +135 -0
- package/src/composables/useChat.ts +423 -0
- package/src/index.ts +82 -0
- package/src/preload/preload.ts +79 -0
- package/src/types/index.ts +164 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useChat Composable
|
|
3
|
+
* 管理聊天状态和与后端的通信
|
|
4
|
+
* 使用 Adapter 模式解耦后端通信
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ref, computed, shallowRef } from 'vue'
|
|
8
|
+
import type { ChatAdapter, ChatProgress } from '../adapter'
|
|
9
|
+
import { createNullAdapter } from '../adapter'
|
|
10
|
+
import type {
|
|
11
|
+
ChatMessage,
|
|
12
|
+
ChatMode,
|
|
13
|
+
SessionRecord,
|
|
14
|
+
MessageRecord,
|
|
15
|
+
SearchResult,
|
|
16
|
+
ToolCall,
|
|
17
|
+
} from '../types'
|
|
18
|
+
|
|
19
|
+
/** 生成唯一 ID */
|
|
20
|
+
function generateId(): string {
|
|
21
|
+
return Date.now().toString(36) + Math.random().toString(36).substr(2)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** 转换存储的消息为显示格式 */
|
|
25
|
+
function convertToMessage(record: MessageRecord): ChatMessage {
|
|
26
|
+
return {
|
|
27
|
+
id: record.id,
|
|
28
|
+
role: record.role,
|
|
29
|
+
content: record.content,
|
|
30
|
+
thinking: record.thinking || undefined,
|
|
31
|
+
thinkingComplete: true,
|
|
32
|
+
toolCalls: record.toolCalls ? JSON.parse(record.toolCalls) : undefined,
|
|
33
|
+
searchResults: record.searchResults ? JSON.parse(record.searchResults) : undefined,
|
|
34
|
+
searching: false,
|
|
35
|
+
timestamp: record.timestamp,
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface UseChatOptions {
|
|
40
|
+
/** Adapter 实例 */
|
|
41
|
+
adapter?: ChatAdapter
|
|
42
|
+
/** 默认模型 */
|
|
43
|
+
defaultModel?: string
|
|
44
|
+
/** 默认模式 */
|
|
45
|
+
defaultMode?: ChatMode
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 聊天状态管理 Composable
|
|
50
|
+
*/
|
|
51
|
+
export function useChat(options: UseChatOptions = {}) {
|
|
52
|
+
const {
|
|
53
|
+
adapter = createNullAdapter(),
|
|
54
|
+
defaultModel = 'anthropic/claude-opus-4.5',
|
|
55
|
+
defaultMode = 'agent',
|
|
56
|
+
} = options
|
|
57
|
+
|
|
58
|
+
// 会话状态
|
|
59
|
+
const sessions = ref<SessionRecord[]>([])
|
|
60
|
+
const currentSessionId = ref<string | null>(null)
|
|
61
|
+
const messages = ref<ChatMessage[]>([])
|
|
62
|
+
|
|
63
|
+
// 配置状态
|
|
64
|
+
const mode = ref<ChatMode>(defaultMode)
|
|
65
|
+
const model = ref(defaultModel)
|
|
66
|
+
const webSearch = ref(true)
|
|
67
|
+
const thinking = ref(true)
|
|
68
|
+
|
|
69
|
+
// 加载状态
|
|
70
|
+
const isLoading = ref(false)
|
|
71
|
+
|
|
72
|
+
// 取消标记
|
|
73
|
+
const abortController = shallowRef<AbortController | null>(null)
|
|
74
|
+
|
|
75
|
+
/** 加载会话列表 */
|
|
76
|
+
async function loadSessions() {
|
|
77
|
+
try {
|
|
78
|
+
const list = await adapter.getSessions()
|
|
79
|
+
sessions.value = list
|
|
80
|
+
// 如果有会话且没有当前会话,选择最新的
|
|
81
|
+
if (list.length > 0 && !currentSessionId.value) {
|
|
82
|
+
await switchSession(list[0].id)
|
|
83
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
console.error('加载会话失败:', error)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** 切换会话 */
|
|
90
|
+
async function switchSession(sessionId: string) {
|
|
91
|
+
if (currentSessionId.value === sessionId) return
|
|
92
|
+
|
|
93
|
+
currentSessionId.value = sessionId
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const savedMessages = await adapter.getMessages(sessionId)
|
|
97
|
+
messages.value = savedMessages.map(convertToMessage)
|
|
98
|
+
|
|
99
|
+
// 更新配置
|
|
100
|
+
const session = sessions.value.find((s) => s.id === sessionId)
|
|
101
|
+
if (session) {
|
|
102
|
+
mode.value = session.mode
|
|
103
|
+
model.value = session.model
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error('加载消息失败:', error)
|
|
107
|
+
messages.value = []
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** 创建新会话 */
|
|
112
|
+
async function createNewSession() {
|
|
113
|
+
try {
|
|
114
|
+
const session = await adapter.createSession({
|
|
115
|
+
title: '新对话',
|
|
116
|
+
model: model.value,
|
|
117
|
+
mode: mode.value,
|
|
118
|
+
})
|
|
119
|
+
sessions.value = [session, ...sessions.value]
|
|
120
|
+
currentSessionId.value = session.id
|
|
121
|
+
messages.value = []
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('创建会话失败:', error)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** 删除会话 */
|
|
128
|
+
async function deleteSession(sessionId: string) {
|
|
129
|
+
try {
|
|
130
|
+
await adapter.deleteSession(sessionId)
|
|
131
|
+
sessions.value = sessions.value.filter((s) => s.id !== sessionId)
|
|
132
|
+
|
|
133
|
+
// 如果删除的是当前会话,切换到下一个
|
|
134
|
+
if (currentSessionId.value === sessionId) {
|
|
135
|
+
if (sessions.value.length > 0) {
|
|
136
|
+
await switchSession(sessions.value[0].id)
|
|
137
|
+
} else {
|
|
138
|
+
currentSessionId.value = null
|
|
139
|
+
messages.value = []
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch (error) {
|
|
143
|
+
console.error('删除会话失败:', error)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** 删除当前会话 */
|
|
148
|
+
async function deleteCurrentSession() {
|
|
149
|
+
if (currentSessionId.value) {
|
|
150
|
+
await deleteSession(currentSessionId.value)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** 更新消息 */
|
|
155
|
+
function updateMessage(index: number, progress: ChatProgress) {
|
|
156
|
+
const msg = messages.value[index]
|
|
157
|
+
if (!msg) return
|
|
158
|
+
|
|
159
|
+
switch (progress.type) {
|
|
160
|
+
case 'thinking': {
|
|
161
|
+
const thinkingData = progress.data as { content: string; isComplete: boolean }
|
|
162
|
+
if (thinkingData.content) {
|
|
163
|
+
msg.thinking = (msg.thinking || '') + thinkingData.content
|
|
164
|
+
}
|
|
165
|
+
msg.thinkingComplete = thinkingData.isComplete
|
|
166
|
+
break
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case 'search_start':
|
|
170
|
+
msg.searching = true
|
|
171
|
+
break
|
|
172
|
+
|
|
173
|
+
case 'search_result': {
|
|
174
|
+
msg.searching = false
|
|
175
|
+
const searchData = progress.data as { results: SearchResult[] }
|
|
176
|
+
msg.searchResults = searchData.results || []
|
|
177
|
+
break
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
case 'tool_call': {
|
|
181
|
+
const toolData = progress.data as { name: string; args: Record<string, unknown> }
|
|
182
|
+
if (!msg.toolCalls) msg.toolCalls = []
|
|
183
|
+
msg.toolCalls.push({
|
|
184
|
+
name: toolData.name,
|
|
185
|
+
args: toolData.args,
|
|
186
|
+
status: 'running',
|
|
187
|
+
})
|
|
188
|
+
break
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
case 'tool_result': {
|
|
192
|
+
const resultData = progress.data as { name: string; result: string }
|
|
193
|
+
if (msg.toolCalls) {
|
|
194
|
+
msg.toolCalls = msg.toolCalls.map((t: ToolCall) => {
|
|
195
|
+
if (t.name === resultData.name && t.status === 'running') {
|
|
196
|
+
return { ...t, result: resultData.result, status: 'success' as const }
|
|
197
|
+
}
|
|
198
|
+
return t
|
|
199
|
+
})
|
|
200
|
+
}
|
|
201
|
+
break
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
case 'text_delta':
|
|
205
|
+
msg.content = (msg.content || '') + (progress.data as string)
|
|
206
|
+
break
|
|
207
|
+
|
|
208
|
+
case 'text':
|
|
209
|
+
if (!msg.content) {
|
|
210
|
+
msg.content = progress.data as string
|
|
211
|
+
}
|
|
212
|
+
break
|
|
213
|
+
|
|
214
|
+
case 'error':
|
|
215
|
+
msg.content = (msg.content || '') + `\n\n❌ 错误: ${progress.data}`
|
|
216
|
+
break
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 触发响应式更新
|
|
220
|
+
messages.value = [...messages.value]
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** 发送消息 */
|
|
224
|
+
async function sendMessage(text: string, images?: string[]) {
|
|
225
|
+
if (!text.trim() || isLoading.value) return
|
|
226
|
+
|
|
227
|
+
// 如果没有当前会话,先创建
|
|
228
|
+
let sessionId = currentSessionId.value
|
|
229
|
+
if (!sessionId) {
|
|
230
|
+
try {
|
|
231
|
+
const session = await adapter.createSession({
|
|
232
|
+
title: '新对话',
|
|
233
|
+
model: model.value,
|
|
234
|
+
mode: mode.value,
|
|
235
|
+
})
|
|
236
|
+
sessions.value = [session, ...sessions.value]
|
|
237
|
+
currentSessionId.value = session.id
|
|
238
|
+
sessionId = session.id
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.error('创建会话失败:', error)
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 添加用户消息
|
|
246
|
+
const userMsg: ChatMessage = {
|
|
247
|
+
id: generateId(),
|
|
248
|
+
role: 'user',
|
|
249
|
+
content: text,
|
|
250
|
+
images,
|
|
251
|
+
timestamp: new Date(),
|
|
252
|
+
}
|
|
253
|
+
messages.value = [...messages.value, userMsg]
|
|
254
|
+
|
|
255
|
+
// 保存用户消息
|
|
256
|
+
try {
|
|
257
|
+
await adapter.saveMessage({
|
|
258
|
+
sessionId,
|
|
259
|
+
role: 'user',
|
|
260
|
+
content: text,
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
// 更新会话标题(如果是第一条消息)
|
|
264
|
+
if (messages.value.length === 1) {
|
|
265
|
+
const title = text.slice(0, 20) + (text.length > 20 ? '...' : '')
|
|
266
|
+
await adapter.updateSession(sessionId, { title })
|
|
267
|
+
sessions.value = sessions.value.map((s) =>
|
|
268
|
+
s.id === sessionId ? { ...s, title } : s
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
} catch (error) {
|
|
272
|
+
console.error('保存消息失败:', error)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// 创建助手消息
|
|
276
|
+
const assistantMsgIndex = messages.value.length
|
|
277
|
+
const assistantMsg: ChatMessage = {
|
|
278
|
+
id: generateId(),
|
|
279
|
+
role: 'assistant',
|
|
280
|
+
content: '',
|
|
281
|
+
toolCalls: [],
|
|
282
|
+
thinkingComplete: false,
|
|
283
|
+
searching: false,
|
|
284
|
+
loading: true,
|
|
285
|
+
timestamp: new Date(),
|
|
286
|
+
}
|
|
287
|
+
messages.value = [...messages.value, assistantMsg]
|
|
288
|
+
|
|
289
|
+
isLoading.value = true
|
|
290
|
+
abortController.value = new AbortController()
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
// 使用异步迭代器接收消息流
|
|
294
|
+
for await (const progress of adapter.sendMessage(
|
|
295
|
+
text,
|
|
296
|
+
{
|
|
297
|
+
mode: mode.value,
|
|
298
|
+
model: model.value,
|
|
299
|
+
enableWebSearch: webSearch.value,
|
|
300
|
+
thinkingMode: thinking.value ? 'enabled' : 'disabled',
|
|
301
|
+
},
|
|
302
|
+
images
|
|
303
|
+
)) {
|
|
304
|
+
// 检查是否被取消
|
|
305
|
+
if (abortController.value?.signal.aborted) break
|
|
306
|
+
|
|
307
|
+
updateMessage(assistantMsgIndex, progress)
|
|
308
|
+
|
|
309
|
+
if (progress.type === 'done' || progress.type === 'error') {
|
|
310
|
+
break
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.error('发送消息失败:', error)
|
|
315
|
+
updateMessage(assistantMsgIndex, {
|
|
316
|
+
type: 'error',
|
|
317
|
+
data: error instanceof Error ? error.message : String(error),
|
|
318
|
+
})
|
|
319
|
+
} finally {
|
|
320
|
+
isLoading.value = false
|
|
321
|
+
|
|
322
|
+
// 标记加载完成
|
|
323
|
+
const finalMsg = messages.value[assistantMsgIndex]
|
|
324
|
+
if (finalMsg) {
|
|
325
|
+
finalMsg.loading = false
|
|
326
|
+
messages.value = [...messages.value]
|
|
327
|
+
|
|
328
|
+
// 保存助手消息
|
|
329
|
+
if (sessionId) {
|
|
330
|
+
adapter.saveMessage({
|
|
331
|
+
sessionId,
|
|
332
|
+
role: 'assistant',
|
|
333
|
+
content: finalMsg.content,
|
|
334
|
+
thinking: finalMsg.thinking,
|
|
335
|
+
toolCalls: finalMsg.toolCalls ? JSON.stringify(finalMsg.toolCalls) : undefined,
|
|
336
|
+
searchResults: finalMsg.searchResults
|
|
337
|
+
? JSON.stringify(finalMsg.searchResults)
|
|
338
|
+
: undefined,
|
|
339
|
+
}).catch((e: Error) => console.error('保存助手消息失败:', e))
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
abortController.value = null
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** 取消请求 */
|
|
348
|
+
function cancelRequest() {
|
|
349
|
+
adapter.cancel()
|
|
350
|
+
abortController.value?.abort()
|
|
351
|
+
isLoading.value = false
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** 复制消息 */
|
|
355
|
+
async function copyMessage(messageId: string) {
|
|
356
|
+
const msg = messages.value.find((m) => m.id === messageId)
|
|
357
|
+
if (!msg) return
|
|
358
|
+
|
|
359
|
+
try {
|
|
360
|
+
await navigator.clipboard.writeText(msg.content)
|
|
361
|
+
messages.value = messages.value.map((m) =>
|
|
362
|
+
m.id === messageId ? { ...m, copied: true } : m
|
|
363
|
+
)
|
|
364
|
+
setTimeout(() => {
|
|
365
|
+
messages.value = messages.value.map((m) =>
|
|
366
|
+
m.id === messageId ? { ...m, copied: false } : m
|
|
367
|
+
)
|
|
368
|
+
}, 2000)
|
|
369
|
+
} catch (err) {
|
|
370
|
+
console.error('复制失败:', err)
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** 重新生成消息 */
|
|
375
|
+
function regenerateMessage(messageIndex: number) {
|
|
376
|
+
if (messageIndex > 0 && messages.value[messageIndex - 1]?.role === 'user') {
|
|
377
|
+
const userMsg = messages.value[messageIndex - 1]
|
|
378
|
+
messages.value = messages.value.slice(0, messageIndex - 1)
|
|
379
|
+
sendMessage(userMsg.content, userMsg.images)
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** 设置工作目录 */
|
|
384
|
+
function setWorkingDirectory(dir: string) {
|
|
385
|
+
if (adapter.setWorkingDir) {
|
|
386
|
+
adapter.setWorkingDir(dir)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
// 状态
|
|
392
|
+
sessions,
|
|
393
|
+
currentSessionId,
|
|
394
|
+
messages,
|
|
395
|
+
isLoading,
|
|
396
|
+
mode,
|
|
397
|
+
model,
|
|
398
|
+
webSearch,
|
|
399
|
+
thinking,
|
|
400
|
+
|
|
401
|
+
// 会话方法
|
|
402
|
+
loadSessions,
|
|
403
|
+
switchSession,
|
|
404
|
+
createNewSession,
|
|
405
|
+
deleteSession,
|
|
406
|
+
deleteCurrentSession,
|
|
407
|
+
|
|
408
|
+
// 消息方法
|
|
409
|
+
sendMessage,
|
|
410
|
+
cancelRequest,
|
|
411
|
+
copyMessage,
|
|
412
|
+
regenerateMessage,
|
|
413
|
+
|
|
414
|
+
// 配置方法
|
|
415
|
+
setMode: (value: ChatMode) => { mode.value = value },
|
|
416
|
+
setModel: (value: string) => { model.value = value },
|
|
417
|
+
setWebSearch: (value: boolean) => { webSearch.value = value },
|
|
418
|
+
setThinking: (value: boolean) => { thinking.value = value },
|
|
419
|
+
|
|
420
|
+
// 工具方法
|
|
421
|
+
setWorkingDirectory,
|
|
422
|
+
}
|
|
423
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @huyooo/ai-chat-frontend
|
|
3
|
+
*
|
|
4
|
+
* AI Chat 前端组件库
|
|
5
|
+
*
|
|
6
|
+
* 使用 adapter 模式,与后端通信方式解耦
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// 导出 Adapter 接口和类型
|
|
10
|
+
export type {
|
|
11
|
+
ChatAdapter,
|
|
12
|
+
ChatProgress,
|
|
13
|
+
ChatProgressType,
|
|
14
|
+
ThinkingData,
|
|
15
|
+
ToolCallData,
|
|
16
|
+
ToolResultData,
|
|
17
|
+
ImageData,
|
|
18
|
+
SendMessageOptions,
|
|
19
|
+
CreateSessionOptions,
|
|
20
|
+
UpdateSessionOptions,
|
|
21
|
+
SaveMessageOptions,
|
|
22
|
+
} from './adapter';
|
|
23
|
+
export { createNullAdapter } from './adapter';
|
|
24
|
+
|
|
25
|
+
// 导出 composables
|
|
26
|
+
export { useChat } from './composables/useChat';
|
|
27
|
+
export type { UseChatOptions } from './composables/useChat';
|
|
28
|
+
|
|
29
|
+
// 导出主组件
|
|
30
|
+
export { default as ChatPanel } from './components/ChatPanel.vue';
|
|
31
|
+
|
|
32
|
+
// 导出输入组件
|
|
33
|
+
export { default as ChatInput } from './components/ChatInput.vue';
|
|
34
|
+
|
|
35
|
+
// 导出 Header 组件
|
|
36
|
+
export { default as ChatHeader } from './components/chat/ui/ChatHeader.vue';
|
|
37
|
+
|
|
38
|
+
// 导出欢迎消息组件
|
|
39
|
+
export { default as WelcomeMessage } from './components/chat/ui/WelcomeMessage.vue';
|
|
40
|
+
|
|
41
|
+
// 导出消息组件
|
|
42
|
+
export { default as MessageBubble } from './components/chat/messages/MessageBubble.vue';
|
|
43
|
+
export { default as ExecutionSteps } from './components/chat/messages/ExecutionSteps.vue';
|
|
44
|
+
|
|
45
|
+
// 导出聊天块组件
|
|
46
|
+
export { default as ThinkingBlock } from './components/chat/ThinkingBlock.vue';
|
|
47
|
+
export { default as ToolCallBlock } from './components/chat/ToolCallBlock.vue';
|
|
48
|
+
export { default as SearchResultBlock } from './components/chat/SearchResultBlock.vue';
|
|
49
|
+
|
|
50
|
+
// 导出类型
|
|
51
|
+
export type {
|
|
52
|
+
ChatMessage,
|
|
53
|
+
ChatMode,
|
|
54
|
+
ModelConfig,
|
|
55
|
+
ModelProvider,
|
|
56
|
+
ThinkingMode,
|
|
57
|
+
SessionRecord,
|
|
58
|
+
MessageRecord,
|
|
59
|
+
SearchResult,
|
|
60
|
+
ToolCall,
|
|
61
|
+
// 向后兼容
|
|
62
|
+
ChatSession,
|
|
63
|
+
MediaOperation,
|
|
64
|
+
AiModel,
|
|
65
|
+
DiffStat,
|
|
66
|
+
} from './types';
|
|
67
|
+
export { DEFAULT_MODELS, FileType } from './types';
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 使用说明:
|
|
71
|
+
*
|
|
72
|
+
* 1. 创建 adapter(使用桥接包):
|
|
73
|
+
* import { createElectronAdapter } from '@huyooo/ai-chat-bridge-electron/renderer';
|
|
74
|
+
* const adapter = createElectronAdapter();
|
|
75
|
+
*
|
|
76
|
+
* 2. 在 Vue 组件中使用:
|
|
77
|
+
* <ChatPanel :adapter="adapter" />
|
|
78
|
+
*
|
|
79
|
+
* 3. 或使用 useChat composable:
|
|
80
|
+
* const chat = useChat({ adapter });
|
|
81
|
+
* // 然后自定义 UI
|
|
82
|
+
*/
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Chat 模块的 Preload 脚本(完全独立实现)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { contextBridge, ipcRenderer } from 'electron';
|
|
6
|
+
|
|
7
|
+
// 暴露受保护的方法给渲染进程
|
|
8
|
+
contextBridge.exposeInMainWorld('aiChatAPI', {
|
|
9
|
+
// AI 图片保存
|
|
10
|
+
saveGeneratedImage: (base64Data: string, targetDir: string) =>
|
|
11
|
+
ipcRenderer.invoke('aiChat:saveGeneratedImage', base64Data, targetDir),
|
|
12
|
+
|
|
13
|
+
// 视频生成
|
|
14
|
+
generateVideo: (options: { prompt: string; imagePaths?: string[]; targetDir: string; apiKey?: string }) =>
|
|
15
|
+
ipcRenderer.invoke('aiChat:generateVideo', options),
|
|
16
|
+
|
|
17
|
+
// 视频生成进度监听
|
|
18
|
+
onVideoProgress: (callback: (data: { taskId: string; status: string; message: string; progress?: number }) => void) => {
|
|
19
|
+
const handler = (_event: Electron.IpcRendererEvent, data: { taskId: string; status: string; message: string; progress?: number }) => callback(data);
|
|
20
|
+
ipcRenderer.on('video:progress', handler);
|
|
21
|
+
return () => ipcRenderer.removeListener('video:progress', handler);
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
// Gemini API 请求(非流式)
|
|
25
|
+
streamChat: (options: {
|
|
26
|
+
contents: Array<{ role: string; parts: unknown[] }>;
|
|
27
|
+
tools?: Array<{ functionDeclarations: unknown[] }>;
|
|
28
|
+
generationConfig?: { maxOutputTokens?: number };
|
|
29
|
+
}) => ipcRenderer.invoke('aiChat:streamChat', options),
|
|
30
|
+
|
|
31
|
+
// Gemini API 非流式请求(用于图片分析等)
|
|
32
|
+
chat: (options: {
|
|
33
|
+
contents: Array<{ role: string; parts: unknown[] }>;
|
|
34
|
+
generationConfig?: { maxOutputTokens?: number };
|
|
35
|
+
}) => ipcRenderer.invoke('aiChat:chat', options),
|
|
36
|
+
|
|
37
|
+
// 文件系统操作(独立实现,不依赖主系统)
|
|
38
|
+
fs: {
|
|
39
|
+
readDirectory: (dirPath: string) => ipcRenderer.invoke('aiChat:fs:readDirectory', dirPath),
|
|
40
|
+
readFileContent: (filePath: string) => ipcRenderer.invoke('aiChat:fs:readFileContent', filePath),
|
|
41
|
+
getSystemPath: (pathId: string) => ipcRenderer.invoke('aiChat:fs:getSystemPath', pathId),
|
|
42
|
+
writeFileContent: (filePath: string, content: string) => ipcRenderer.invoke('aiChat:fs:writeFileContent', filePath, content),
|
|
43
|
+
deleteFiles: (paths: string[]) => ipcRenderer.invoke('aiChat:fs:deleteFiles', paths),
|
|
44
|
+
renameFile: (oldPath: string, newPath: string) => ipcRenderer.invoke('aiChat:fs:renameFile', oldPath, newPath),
|
|
45
|
+
createFolder: (parentDir: string, folderName: string) => ipcRenderer.invoke('aiChat:fs:createFolder', parentDir, folderName),
|
|
46
|
+
createFile: (parentDir: string, fileName: string, content?: string) => ipcRenderer.invoke('aiChat:fs:createFile', parentDir, fileName, content),
|
|
47
|
+
copyFilesToClipboard: (filePaths: string[]) => ipcRenderer.invoke('aiChat:fs:copyFilesToClipboard', filePaths),
|
|
48
|
+
getClipboardFiles: () => ipcRenderer.invoke('aiChat:fs:getClipboardFiles'),
|
|
49
|
+
pasteFiles: (targetDir: string, sourcePaths: string[]) => ipcRenderer.invoke('aiChat:fs:pasteFiles', targetDir, sourcePaths),
|
|
50
|
+
getFileInfo: (filePath: string) => ipcRenderer.invoke('aiChat:fs:getFileInfo', filePath),
|
|
51
|
+
readImageAsBase64: (imagePath: string) => ipcRenderer.invoke('aiChat:fs:readImageAsBase64', imagePath),
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// 操作历史(独立实现)
|
|
55
|
+
getHistory: () => ipcRenderer.invoke('aiChat:history:getList'),
|
|
56
|
+
undo: (operationId?: string) => ipcRenderer.invoke('aiChat:history:undo', operationId),
|
|
57
|
+
|
|
58
|
+
// 工具操作(独立实现)
|
|
59
|
+
tools: {
|
|
60
|
+
deleteFiles: (paths: string[]) => ipcRenderer.invoke('aiChat:tool:deleteFiles', paths),
|
|
61
|
+
renameFile: (oldPath: string, newName: string) => ipcRenderer.invoke('aiChat:tool:renameFile', oldPath, newName),
|
|
62
|
+
moveFiles: (paths: string[], targetDir: string) => ipcRenderer.invoke('aiChat:tool:moveFiles', paths, targetDir),
|
|
63
|
+
copyFiles: (paths: string[], targetDir: string) => ipcRenderer.invoke('aiChat:tool:copyFiles', paths, targetDir),
|
|
64
|
+
analyzeImages: (imagePaths: string[], question?: string) => ipcRenderer.invoke('aiChat:tool:analyzeImages', imagePaths, question),
|
|
65
|
+
generateImage: (prompt: string, referenceImagePaths?: string[], targetDir?: string) => ipcRenderer.invoke('aiChat:tool:generateImage', prompt, referenceImagePaths, targetDir),
|
|
66
|
+
generateVideo: (prompt: string, referenceImagePaths?: string[], targetDir?: string) => ipcRenderer.invoke('aiChat:tool:generateVideo', prompt, referenceImagePaths, targetDir),
|
|
67
|
+
clipVideo: (videoPath: string, startTime: number, endTime: number, outputPath?: string) => ipcRenderer.invoke('aiChat:tool:clipVideo', videoPath, startTime, endTime, outputPath),
|
|
68
|
+
transcodeVideo: (videoPath: string, outputFormat: string, resolution?: string, bitrate?: string, outputPath?: string) => ipcRenderer.invoke('aiChat:tool:transcodeVideo', videoPath, outputFormat, resolution, bitrate, outputPath),
|
|
69
|
+
mergeVideos: (videoPaths: string[], outputPath?: string) => ipcRenderer.invoke('aiChat:tool:mergeVideos', videoPaths, outputPath),
|
|
70
|
+
extractAudio: (videoPath: string, outputPath?: string) => ipcRenderer.invoke('aiChat:tool:extractAudio', videoPath, outputPath),
|
|
71
|
+
executeCommand: (command: string, workingDir?: string) => ipcRenderer.invoke('aiChat:tool:executeCommand', command, workingDir),
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// 默认导出(用于 index.ts)
|
|
76
|
+
export default {
|
|
77
|
+
// 如果需要,可以在这里添加一些配置或方法
|
|
78
|
+
};
|
|
79
|
+
|