@opentiny/tiny-robot-kit 0.2.0-alpha.2 → 0.2.0-alpha.4

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/index.mjs CHANGED
@@ -1,723 +1,3 @@
1
- // src/providers/base.ts
2
- var BaseModelProvider = class {
3
- /**
4
- * @param config AI模型配置
5
- */
6
- constructor(config) {
7
- this.config = config;
8
- }
9
- /**
10
- * 更新配置
11
- * @param config 新的AI模型配置
12
- */
13
- updateConfig(config) {
14
- this.config = { ...this.config, ...config };
15
- }
16
- /**
17
- * 获取当前配置
18
- * @returns AI模型配置
19
- */
20
- getConfig() {
21
- return { ...this.config };
22
- }
23
- /**
24
- * 验证请求参数
25
- * @param request 聊天请求参数
26
- */
27
- validateRequest(request) {
28
- if (!request.messages || !Array.isArray(request.messages) || request.messages.length === 0) {
29
- throw new Error("\u8BF7\u6C42\u5FC5\u987B\u5305\u542B\u81F3\u5C11\u4E00\u6761\u6D88\u606F");
30
- }
31
- for (const message of request.messages) {
32
- if (!message.role || !message.content) {
33
- throw new Error("\u6BCF\u6761\u6D88\u606F\u5FC5\u987B\u5305\u542B\u89D2\u8272\u548C\u5185\u5BB9");
34
- }
35
- }
36
- }
37
- };
1
+ var M=class{constructor(e){this.config=e}updateConfig(e){this.config={...this.config,...e}}getConfig(){return{...this.config}}validateRequest(e){if(!e.messages||!Array.isArray(e.messages)||e.messages.length===0)throw new Error("\u8BF7\u6C42\u5FC5\u987B\u5305\u542B\u81F3\u5C11\u4E00\u6761\u6D88\u606F");for(let o of e.messages)if(!o.role||!o.content)throw new Error("\u6BCF\u6761\u6D88\u606F\u5FC5\u987B\u5305\u542B\u89D2\u8272\u548C\u5185\u5BB9")}};var P=(p=>(p.NETWORK_ERROR="network_error",p.AUTHENTICATION_ERROR="authentication_error",p.RATE_LIMIT_ERROR="rate_limit_error",p.SERVER_ERROR="server_error",p.MODEL_ERROR="model_error",p.TIMEOUT_ERROR="timeout_error",p.UNKNOWN_ERROR="unknown_error",p))(P||{}),U=(l=>(l.DATA="data",l.ERROR="error",l.DONE="done",l))(U||{});function f(r){return{type:r.type||"unknown_error",message:r.message||"\u672A\u77E5\u9519\u8BEF",statusCode:r.statusCode,originalError:r.originalError}}function y(r){if(!r.response)return f({type:"network_error",message:"\u7F51\u7EDC\u8FDE\u63A5\u9519\u8BEF\uFF0C\u8BF7\u68C0\u67E5\u60A8\u7684\u7F51\u7EDC\u8FDE\u63A5",originalError:r});if(r.response){let{status:e,data:o}=r.response;return e===401||e===403?f({type:"authentication_error",message:"\u8EAB\u4EFD\u9A8C\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u60A8\u7684API\u5BC6\u94A5",statusCode:e,originalError:r}):e===429?f({type:"rate_limit_error",message:"\u8D85\u51FAAPI\u8C03\u7528\u9650\u5236\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",statusCode:e,originalError:r}):e>=500?f({type:"server_error",message:"\u670D\u52A1\u5668\u9519\u8BEF\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",statusCode:e,originalError:r}):f({type:"unknown_error",message:o?.error?.message||`\u8BF7\u6C42\u5931\u8D25\uFF0C\u72B6\u6001\u7801: ${e}`,statusCode:e,originalError:r})}return r.code==="ECONNABORTED"?f({type:"timeout_error",message:"\u8BF7\u6C42\u8D85\u65F6\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",originalError:r}):f({type:"unknown_error",message:r.message||"\u53D1\u751F\u672A\u77E5\u9519\u8BEF",originalError:r})}async function w(r,e,o){let l=r.body?.getReader();if(!l)throw new Error("Response body is null");let g=new TextDecoder,a="";o&&o.addEventListener("abort",()=>{l.cancel().catch(t=>console.error("Error cancelling reader:",t))},{once:!0});try{for(;;){if(o?.aborted){await l.cancel();break}let{done:t,value:p}=await l.read();if(t)break;let d=g.decode(p,{stream:!0});a+=d;let c=a.split(`
38
2
 
39
- // src/types.ts
40
- var ErrorType = /* @__PURE__ */ ((ErrorType2) => {
41
- ErrorType2["NETWORK_ERROR"] = "network_error";
42
- ErrorType2["AUTHENTICATION_ERROR"] = "authentication_error";
43
- ErrorType2["RATE_LIMIT_ERROR"] = "rate_limit_error";
44
- ErrorType2["SERVER_ERROR"] = "server_error";
45
- ErrorType2["MODEL_ERROR"] = "model_error";
46
- ErrorType2["TIMEOUT_ERROR"] = "timeout_error";
47
- ErrorType2["UNKNOWN_ERROR"] = "unknown_error";
48
- return ErrorType2;
49
- })(ErrorType || {});
50
- var StreamEventType = /* @__PURE__ */ ((StreamEventType2) => {
51
- StreamEventType2["DATA"] = "data";
52
- StreamEventType2["ERROR"] = "error";
53
- StreamEventType2["DONE"] = "done";
54
- return StreamEventType2;
55
- })(StreamEventType || {});
56
-
57
- // src/error.ts
58
- function createError(error) {
59
- return {
60
- type: error.type || "unknown_error" /* UNKNOWN_ERROR */,
61
- message: error.message || "\u672A\u77E5\u9519\u8BEF",
62
- statusCode: error.statusCode,
63
- originalError: error.originalError
64
- };
65
- }
66
- function handleRequestError(error) {
67
- if (!error.response) {
68
- return createError({
69
- type: "network_error" /* NETWORK_ERROR */,
70
- message: "\u7F51\u7EDC\u8FDE\u63A5\u9519\u8BEF\uFF0C\u8BF7\u68C0\u67E5\u60A8\u7684\u7F51\u7EDC\u8FDE\u63A5",
71
- originalError: error
72
- });
73
- }
74
- if (error.response) {
75
- const { status, data } = error.response;
76
- if (status === 401 || status === 403) {
77
- return createError({
78
- type: "authentication_error" /* AUTHENTICATION_ERROR */,
79
- message: "\u8EAB\u4EFD\u9A8C\u8BC1\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u60A8\u7684API\u5BC6\u94A5",
80
- statusCode: status,
81
- originalError: error
82
- });
83
- }
84
- if (status === 429) {
85
- return createError({
86
- type: "rate_limit_error" /* RATE_LIMIT_ERROR */,
87
- message: "\u8D85\u51FAAPI\u8C03\u7528\u9650\u5236\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",
88
- statusCode: status,
89
- originalError: error
90
- });
91
- }
92
- if (status >= 500) {
93
- return createError({
94
- type: "server_error" /* SERVER_ERROR */,
95
- message: "\u670D\u52A1\u5668\u9519\u8BEF\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",
96
- statusCode: status,
97
- originalError: error
98
- });
99
- }
100
- return createError({
101
- type: "unknown_error" /* UNKNOWN_ERROR */,
102
- message: data?.error?.message || `\u8BF7\u6C42\u5931\u8D25\uFF0C\u72B6\u6001\u7801: ${status}`,
103
- statusCode: status,
104
- originalError: error
105
- });
106
- }
107
- if (error.code === "ECONNABORTED") {
108
- return createError({
109
- type: "timeout_error" /* TIMEOUT_ERROR */,
110
- message: "\u8BF7\u6C42\u8D85\u65F6\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5",
111
- originalError: error
112
- });
113
- }
114
- return createError({
115
- type: "unknown_error" /* UNKNOWN_ERROR */,
116
- message: error.message || "\u53D1\u751F\u672A\u77E5\u9519\u8BEF",
117
- originalError: error
118
- });
119
- }
120
-
121
- // src/utils.ts
122
- async function handleSSEStream(response, handler, signal) {
123
- const reader = response.body?.getReader();
124
- if (!reader) {
125
- throw new Error("Response body is null");
126
- }
127
- const decoder = new TextDecoder();
128
- let buffer = "";
129
- if (signal) {
130
- signal.addEventListener(
131
- "abort",
132
- () => {
133
- reader.cancel().catch((err) => console.error("Error cancelling reader:", err));
134
- },
135
- { once: true }
136
- );
137
- }
138
- try {
139
- while (true) {
140
- if (signal?.aborted) {
141
- await reader.cancel();
142
- break;
143
- }
144
- const { done, value } = await reader.read();
145
- if (done) break;
146
- const chunk = decoder.decode(value, { stream: true });
147
- buffer += chunk;
148
- const lines = buffer.split("\n\n");
149
- buffer = lines.pop() || "";
150
- for (const line of lines) {
151
- if (line.trim() === "") continue;
152
- if (line.trim() === "data: [DONE]") {
153
- handler.onDone();
154
- continue;
155
- }
156
- try {
157
- const dataMatch = line.match(/^data: (.+)$/m);
158
- if (!dataMatch) continue;
159
- const data = JSON.parse(dataMatch[1]);
160
- handler.onData(data);
161
- } catch (error) {
162
- console.error("Error parsing SSE message:", error);
163
- }
164
- }
165
- }
166
- if (buffer.trim() === "data: [DONE]" || signal?.aborted) {
167
- handler.onDone();
168
- }
169
- } catch (error) {
170
- if (signal?.aborted) return;
171
- throw error;
172
- }
173
- }
174
- function formatMessages(messages) {
175
- return messages.map((msg) => {
176
- if (typeof msg === "object" && "role" in msg && "content" in msg) {
177
- return {
178
- role: msg.role,
179
- content: String(msg.content),
180
- ...msg.name ? { name: msg.name } : {}
181
- };
182
- }
183
- if (typeof msg === "string") {
184
- return {
185
- role: "user",
186
- content: msg
187
- };
188
- }
189
- return {
190
- role: "user",
191
- content: String(msg)
192
- };
193
- });
194
- }
195
- function extractTextFromResponse(response) {
196
- if (!response.choices || !response.choices.length) {
197
- return "";
198
- }
199
- return response.choices[0].message?.content || "";
200
- }
201
-
202
- // src/providers/openai.ts
203
- var OpenAIProvider = class extends BaseModelProvider {
204
- /**
205
- * @param config AI模型配置
206
- */
207
- constructor(config) {
208
- super(config);
209
- this.defaultModel = "gpt-3.5-turbo";
210
- this.baseURL = config.apiUrl || "https://api.openai.com/v1";
211
- this.apiKey = config.apiKey || "";
212
- if (config.defaultModel) {
213
- this.defaultModel = config.defaultModel;
214
- }
215
- if (!this.apiKey) {
216
- console.warn("API key is not provided. Authentication will likely fail.");
217
- }
218
- }
219
- /**
220
- * 发送聊天请求并获取响应
221
- * @param request 聊天请求参数
222
- * @returns 聊天响应
223
- */
224
- async chat(request) {
225
- try {
226
- this.validateRequest(request);
227
- const requestData = {
228
- model: request.options?.model || this.config.defaultModel || this.defaultModel,
229
- messages: request.messages,
230
- ...request.options,
231
- stream: false
232
- };
233
- const options = {
234
- method: "POST",
235
- headers: { "Content-Type": "application/json" },
236
- body: JSON.stringify(requestData)
237
- };
238
- if (this.apiKey) {
239
- Object.assign(options.headers, { Authorization: `Bearer ${this.apiKey}` });
240
- }
241
- const response = await fetch(`${this.baseURL}/chat/completions`, options);
242
- if (!response.ok) {
243
- const errorText = await response.text();
244
- throw new Error(`HTTP error! status: ${response.status}, details: ${errorText}`);
245
- }
246
- return await response.json();
247
- } catch (error) {
248
- throw handleRequestError(error);
249
- }
250
- }
251
- /**
252
- * 发送流式聊天请求并通过处理器处理响应
253
- * @param request 聊天请求参数
254
- * @param handler 流式响应处理器
255
- */
256
- async chatStream(request, handler) {
257
- const { signal, ...options } = request.options || {};
258
- try {
259
- this.validateRequest(request);
260
- const requestData = {
261
- model: request.options?.model || this.config.defaultModel || this.defaultModel,
262
- messages: request.messages,
263
- ...options,
264
- stream: true
265
- };
266
- const requestOptions = {
267
- method: "POST",
268
- headers: {
269
- "Content-Type": "application/json",
270
- Authorization: `Bearer ${this.apiKey}`,
271
- Accept: "text/event-stream"
272
- },
273
- body: JSON.stringify(requestData),
274
- signal
275
- };
276
- if (this.apiKey) {
277
- Object.assign(requestOptions.headers, { Authorization: `Bearer ${this.apiKey}` });
278
- }
279
- const response = await fetch(`${this.baseURL}/chat/completions`, requestOptions);
280
- if (!response.ok) {
281
- const errorText = await response.text();
282
- throw new Error(`HTTP error! status: ${response.status}, details: ${errorText}`);
283
- }
284
- await handleSSEStream(response, handler, signal);
285
- } catch (error) {
286
- if (signal?.aborted) return;
287
- handler.onError(handleRequestError(error));
288
- }
289
- }
290
- /**
291
- * 更新配置
292
- * @param config 新的AI模型配置
293
- */
294
- updateConfig(config) {
295
- super.updateConfig(config);
296
- if (config.apiUrl) {
297
- this.baseURL = config.apiUrl;
298
- }
299
- if (config.apiKey) {
300
- this.apiKey = config.apiKey;
301
- }
302
- if (config.defaultModel) {
303
- this.defaultModel = config.defaultModel;
304
- }
305
- }
306
- };
307
-
308
- // src/client.ts
309
- var AIClient = class {
310
- /**
311
- * 构造函数
312
- * @param config AI模型配置
313
- */
314
- constructor(config) {
315
- this.config = config;
316
- this.provider = this.createProvider(config);
317
- }
318
- /**
319
- * 创建提供商实例
320
- * @param config AI模型配置
321
- * @returns 提供商实例
322
- */
323
- createProvider(config) {
324
- if (config.provider === "custom" && "providerImplementation" in config) {
325
- return config.providerImplementation;
326
- }
327
- switch (config.provider) {
328
- case "deepseek":
329
- const defaultConfig = {
330
- defaultModel: "deepseek-chat",
331
- apiUrl: "https://api.deepseek.com/v1"
332
- };
333
- return new OpenAIProvider({ ...defaultConfig, ...config });
334
- case "openai":
335
- default:
336
- return new OpenAIProvider(config);
337
- }
338
- }
339
- /**
340
- * 发送聊天请求并获取响应
341
- * @param request 聊天请求参数
342
- * @returns 聊天响应
343
- */
344
- async chat(request) {
345
- return this.provider.chat(request);
346
- }
347
- /**
348
- * 发送流式聊天请求并通过处理器处理响应
349
- * @param request 聊天请求参数
350
- * @param handler 流式响应处理器
351
- */
352
- async chatStream(request, handler) {
353
- const streamRequest = {
354
- ...request,
355
- options: {
356
- ...request.options,
357
- stream: true
358
- }
359
- };
360
- return this.provider.chatStream(streamRequest, handler);
361
- }
362
- /**
363
- * 获取当前配置
364
- * @returns AI模型配置
365
- */
366
- getConfig() {
367
- return { ...this.config };
368
- }
369
- /**
370
- * 更新配置
371
- * @param config 新的AI模型配置
372
- */
373
- updateConfig(config) {
374
- this.config = { ...this.config, ...config };
375
- if (config.provider && config.provider !== this.config.provider) {
376
- this.provider = this.createProvider(this.config);
377
- } else {
378
- this.provider.updateConfig(this.config);
379
- }
380
- }
381
- };
382
-
383
- // src/vue/message/useMessage.ts
384
- import { reactive, ref, toRaw } from "vue";
385
- var STATUS = /* @__PURE__ */ ((STATUS2) => {
386
- STATUS2["INIT"] = "init";
387
- STATUS2["PROCESSING"] = "processing";
388
- STATUS2["STREAMING"] = "streaming";
389
- STATUS2["FINISHED"] = "finished";
390
- STATUS2["ABORTED"] = "aborted";
391
- STATUS2["ERROR"] = "error";
392
- return STATUS2;
393
- })(STATUS || {});
394
- var GeneratingStatus = ["processing" /* PROCESSING */, "streaming" /* STREAMING */];
395
- var FinalStatus = ["finished" /* FINISHED */, "aborted" /* ABORTED */, "error" /* ERROR */];
396
- function useMessage(options) {
397
- const { client, useStreamByDefault = true, errorMessage = "\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5", initialMessages = [] } = options;
398
- const messages = ref([...initialMessages]);
399
- const inputMessage = ref("");
400
- const useStream = ref(useStreamByDefault);
401
- let abortController = null;
402
- const messageState = reactive({
403
- status: "init" /* INIT */,
404
- errorMsg: null
405
- });
406
- const chat = async (abortController2) => {
407
- const response = await client.chat({
408
- messages: toRaw(messages.value),
409
- options: {
410
- stream: false,
411
- signal: abortController2.signal
412
- }
413
- });
414
- const assistantMessage = {
415
- role: "assistant",
416
- content: response.choices[0].message.content
417
- };
418
- messages.value.push(assistantMessage);
419
- };
420
- const streamChat = async (abortController2) => {
421
- await client.chatStream(
422
- {
423
- messages: toRaw(messages.value),
424
- options: {
425
- stream: true,
426
- signal: abortController2.signal
427
- }
428
- },
429
- {
430
- onData: (data) => {
431
- messageState.status = "streaming" /* STREAMING */;
432
- if (messages.value[messages.value.length - 1].role === "user") {
433
- messages.value.push({ role: "assistant", content: "" });
434
- }
435
- const choice = data.choices?.[0];
436
- if (choice && choice.delta.content) {
437
- messages.value[messages.value.length - 1].content += choice.delta.content;
438
- }
439
- },
440
- onError: (error) => {
441
- messageState.status = "error" /* ERROR */;
442
- messageState.errorMsg = errorMessage;
443
- console.error("Stream request error:", error);
444
- },
445
- onDone: () => {
446
- messageState.status = "finished" /* FINISHED */;
447
- }
448
- }
449
- );
450
- };
451
- const chatRequest = async () => {
452
- messageState.status = "processing" /* PROCESSING */;
453
- messageState.errorMsg = null;
454
- abortController = new AbortController();
455
- try {
456
- if (useStream.value) {
457
- await streamChat(abortController);
458
- } else {
459
- await chat(abortController);
460
- }
461
- messageState.status = "finished" /* FINISHED */;
462
- } catch (error) {
463
- messageState.errorMsg = errorMessage;
464
- messageState.status = "error" /* ERROR */;
465
- console.error("Send message error:", error);
466
- } finally {
467
- abortController = null;
468
- }
469
- };
470
- const sendMessage = async (content = inputMessage.value, clearInput = true) => {
471
- if (!content?.trim() || GeneratingStatus.includes(messageState.status)) {
472
- return;
473
- }
474
- const userMessage = {
475
- role: "user",
476
- content
477
- };
478
- messages.value.push(userMessage);
479
- if (clearInput) {
480
- inputMessage.value = "";
481
- }
482
- await chatRequest();
483
- };
484
- const abortRequest = () => {
485
- if (abortController) {
486
- abortController.abort();
487
- abortController = null;
488
- messageState.status = "aborted" /* ABORTED */;
489
- }
490
- };
491
- const retryRequest = async (msgIndex) => {
492
- if (msgIndex === 0 || !messages.value[msgIndex] || messages.value[msgIndex].role === "user") {
493
- return;
494
- }
495
- messages.value.splice(msgIndex);
496
- await chatRequest();
497
- };
498
- const clearMessages = () => {
499
- messages.value = [];
500
- messageState.errorMsg = null;
501
- };
502
- const addMessage = (message) => {
503
- messages.value.push(message);
504
- };
505
- return {
506
- messages,
507
- messageState,
508
- inputMessage,
509
- useStream,
510
- sendMessage,
511
- clearMessages,
512
- addMessage,
513
- abortRequest,
514
- retryRequest
515
- };
516
- }
517
-
518
- // src/vue/conversation/useConversation.ts
519
- import { reactive as reactive2, watch } from "vue";
520
- var LocalStorageStrategy = class {
521
- constructor(storageKey = "tiny-robot-ai-conversations") {
522
- this.storageKey = storageKey;
523
- }
524
- saveConversations(conversations) {
525
- try {
526
- localStorage.setItem(this.storageKey, JSON.stringify(conversations));
527
- } catch (error) {
528
- console.error("\u4FDD\u5B58\u4F1A\u8BDD\u5931\u8D25:", error);
529
- }
530
- }
531
- loadConversations() {
532
- try {
533
- const data = localStorage.getItem(this.storageKey);
534
- return data ? JSON.parse(data) : [];
535
- } catch (error) {
536
- console.error("\u52A0\u8F7D\u4F1A\u8BDD\u5931\u8D25:", error);
537
- return [];
538
- }
539
- }
540
- };
541
- function generateId() {
542
- return Date.now().toString(36) + Math.random().toString(36).substring(2, 9);
543
- }
544
- function useConversation(options) {
545
- const {
546
- client,
547
- storage = new LocalStorageStrategy(),
548
- autoSave = true,
549
- useStreamByDefault = true,
550
- errorMessage = "\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"
551
- } = options;
552
- const state = reactive2({
553
- conversations: [],
554
- currentId: null,
555
- loading: false
556
- });
557
- const messageManager = useMessage({
558
- client,
559
- useStreamByDefault,
560
- errorMessage,
561
- initialMessages: []
562
- });
563
- watch(
564
- () => messageManager.messages.value,
565
- (messages) => {
566
- if (state.currentId && messages.length > 0) {
567
- const index = state.conversations.findIndex((row) => row.id === state.currentId);
568
- if (index !== -1) {
569
- state.conversations[index].messages = [...messages];
570
- state.conversations[index].updatedAt = Date.now();
571
- if (autoSave) {
572
- saveConversations();
573
- }
574
- }
575
- }
576
- },
577
- { deep: true }
578
- );
579
- const createConversation = (title = "\u65B0\u4F1A\u8BDD", metadata = {}) => {
580
- const id = generateId();
581
- const newConversation = {
582
- id,
583
- title,
584
- createdAt: Date.now(),
585
- updatedAt: Date.now(),
586
- messages: [],
587
- metadata
588
- };
589
- state.conversations.unshift(newConversation);
590
- switchConversation(id);
591
- if (autoSave) {
592
- saveConversations();
593
- }
594
- return id;
595
- };
596
- const switchConversation = (id) => {
597
- const conversation = state.conversations.find((conv) => conv.id === id);
598
- if (conversation) {
599
- state.currentId = id;
600
- messageManager.clearMessages();
601
- if (conversation.messages.length > 0) {
602
- conversation.messages.forEach((msg) => messageManager.addMessage(msg));
603
- }
604
- }
605
- };
606
- const deleteConversation = (id) => {
607
- const index = state.conversations.findIndex((conv) => conv.id === id);
608
- if (index !== -1) {
609
- state.conversations.splice(index, 1);
610
- if (state.currentId === id) {
611
- if (state.conversations.length > 0) {
612
- switchConversation(state.conversations[0].id);
613
- } else {
614
- state.currentId = null;
615
- messageManager.clearMessages();
616
- }
617
- }
618
- if (autoSave) {
619
- saveConversations();
620
- }
621
- }
622
- };
623
- const updateTitle = (id, title) => {
624
- const conversation = state.conversations.find((conv) => conv.id === id);
625
- if (conversation) {
626
- conversation.title = title;
627
- conversation.updatedAt = Date.now();
628
- if (autoSave) {
629
- saveConversations();
630
- }
631
- }
632
- };
633
- const updateMetadata = (id, metadata) => {
634
- const conversation = state.conversations.find((conv) => conv.id === id);
635
- if (conversation) {
636
- conversation.metadata = { ...conversation.metadata, ...metadata };
637
- conversation.updatedAt = Date.now();
638
- if (autoSave) {
639
- saveConversations();
640
- }
641
- }
642
- };
643
- const saveConversations = async () => {
644
- try {
645
- await storage.saveConversations(state.conversations);
646
- } catch (error) {
647
- console.error("\u4FDD\u5B58\u4F1A\u8BDD\u5931\u8D25:", error);
648
- }
649
- };
650
- const loadConversations = async () => {
651
- state.loading = true;
652
- try {
653
- const conversations = await storage.loadConversations();
654
- state.conversations = conversations;
655
- if (conversations.length > 0 && !state.currentId) {
656
- switchConversation(conversations[0].id);
657
- }
658
- } catch (error) {
659
- console.error("\u52A0\u8F7D\u4F1A\u8BDD\u5931\u8D25:", error);
660
- } finally {
661
- state.loading = false;
662
- }
663
- };
664
- const generateTitle = async (id) => {
665
- const conversation = state.conversations.find((conv) => conv.id === id);
666
- if (!conversation || conversation.messages.length < 2) {
667
- return conversation?.title || "\u65B0\u4F1A\u8BDD";
668
- }
669
- try {
670
- const prompt = {
671
- role: "system",
672
- content: "\u8BF7\u6839\u636E\u4EE5\u4E0B\u5BF9\u8BDD\u5185\u5BB9\uFF0C\u751F\u6210\u4E00\u4E2A\u7B80\u77ED\u7684\u6807\u9898\uFF08\u4E0D\u8D85\u8FC720\u4E2A\u5B57\u7B26\uFF09\u3002\u53EA\u9700\u8981\u8FD4\u56DE\u6807\u9898\u6587\u672C\uFF0C\u4E0D\u9700\u8981\u4EFB\u4F55\u89E3\u91CA\u6216\u989D\u5916\u5185\u5BB9\u3002"
673
- };
674
- const contextMessages = conversation.messages.slice(0, Math.min(4, conversation.messages.length));
675
- const response = await client.chat({
676
- messages: [prompt, ...contextMessages],
677
- options: {
678
- stream: false,
679
- max_tokens: 30
680
- }
681
- });
682
- const title = response.choices[0].message.content.trim();
683
- updateTitle(id, title);
684
- return title;
685
- } catch (error) {
686
- console.error("\u751F\u6210\u6807\u9898\u5931\u8D25:", error);
687
- return conversation.title;
688
- }
689
- };
690
- const getCurrentConversation = () => {
691
- if (!state.currentId) return null;
692
- return state.conversations.find((conv) => conv.id === state.currentId) || null;
693
- };
694
- loadConversations();
695
- return {
696
- state,
697
- messageManager,
698
- createConversation,
699
- switchConversation,
700
- deleteConversation,
701
- updateTitle,
702
- updateMetadata,
703
- saveConversations,
704
- loadConversations,
705
- generateTitle,
706
- getCurrentConversation
707
- };
708
- }
709
- export {
710
- AIClient,
711
- BaseModelProvider,
712
- ErrorType,
713
- FinalStatus,
714
- GeneratingStatus,
715
- LocalStorageStrategy,
716
- OpenAIProvider,
717
- STATUS,
718
- StreamEventType,
719
- extractTextFromResponse,
720
- formatMessages,
721
- useConversation,
722
- useMessage
723
- };
3
+ `);a=c.pop()||"";for(let C of c)if(C.trim()!==""){if(C.trim()==="data: [DONE]"){e.onDone();continue}try{let m=C.match(/^data: (.+)$/m);if(!m)continue;let v=JSON.parse(m[1]);e.onData(v)}catch(m){console.error("Error parsing SSE message:",m)}}}(a.trim()==="data: [DONE]"||o?.aborted)&&e.onDone()}catch(t){if(o?.aborted)return;throw t}}function D(r){return r.map(e=>typeof e=="object"&&"role"in e&&"content"in e?{role:e.role,content:String(e.content),...e.name?{name:e.name}:{}}:typeof e=="string"?{role:"user",content:e}:{role:"user",content:String(e)})}function _(r){return!r.choices||!r.choices.length?"":r.choices[0].message?.content||""}var R=class extends M{constructor(o){super(o);this.defaultModel="gpt-3.5-turbo";this.baseURL=o.apiUrl||"https://api.openai.com/v1",this.apiKey=o.apiKey||"",o.defaultModel&&(this.defaultModel=o.defaultModel),this.apiKey||console.warn("API key is not provided. Authentication will likely fail.")}async chat(o){try{this.validateRequest(o);let l={model:o.options?.model||this.config.defaultModel||this.defaultModel,messages:o.messages,...o.options,stream:!1},g={method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(l)};this.apiKey&&Object.assign(g.headers,{Authorization:`Bearer ${this.apiKey}`});let a=await fetch(`${this.baseURL}/chat/completions`,g);if(!a.ok){let t=await a.text();throw new Error(`HTTP error! status: ${a.status}, details: ${t}`)}return await a.json()}catch(l){throw y(l)}}async chatStream(o,l){let{signal:g,...a}=o.options||{};try{this.validateRequest(o);let t={model:o.options?.model||this.config.defaultModel||this.defaultModel,messages:o.messages,...a,stream:!0},p={method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`,Accept:"text/event-stream"},body:JSON.stringify(t),signal:g};this.apiKey&&Object.assign(p.headers,{Authorization:`Bearer ${this.apiKey}`});let d=await fetch(`${this.baseURL}/chat/completions`,p);if(!d.ok){let c=await d.text();throw new Error(`HTTP error! status: ${d.status}, details: ${c}`)}await w(d,l,g)}catch(t){if(g?.aborted)return;l.onError(y(t))}}updateConfig(o){super.updateConfig(o),o.apiUrl&&(this.baseURL=o.apiUrl),o.apiKey&&(this.apiKey=o.apiKey),o.defaultModel&&(this.defaultModel=o.defaultModel)}};var A=class{constructor(e){this.config=e,this.provider=this.createProvider(e)}createProvider(e){if(e.provider==="custom"&&"providerImplementation"in e)return e.providerImplementation;switch(e.provider){case"deepseek":let o={defaultModel:"deepseek-chat",apiUrl:"https://api.deepseek.com/v1"};return new R({...o,...e});case"openai":default:return new R(e)}}async chat(e){return this.provider.chat(e)}async chatStream(e,o){let l={...e,options:{...e.options,stream:!0}};return this.provider.chatStream(l,o)}getConfig(){return{...this.config}}updateConfig(e){this.config={...this.config,...e},e.provider&&e.provider!==this.config.provider?this.provider=this.createProvider(this.config):this.provider.updateConfig(this.config)}};import{reactive as k,ref as E,toRaw as T}from"vue";var K=(t=>(t.INIT="init",t.PROCESSING="processing",t.STREAMING="streaming",t.FINISHED="finished",t.ABORTED="aborted",t.ERROR="error",t))(K||{}),q=["processing","streaming"],te=["finished","aborted","error"];function N(r){let{client:e,useStreamByDefault:o=!0,errorMessage:l="\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5",initialMessages:g=[]}=r,a=E([...g]),t=E(""),p=E(o),d=null,c=k({status:"init",errorMsg:null}),C=async s=>{let u={role:"assistant",content:(await e.chat({messages:T(a.value),options:{stream:!1,signal:s.signal}})).choices[0].message.content};a.value.push(u)},m=async s=>{await e.chatStream({messages:T(a.value),options:{stream:!0,signal:s.signal}},{onData:n=>{c.status="streaming",a.value[a.value.length-1].role==="user"&&a.value.push({role:"assistant",content:""});let u=n.choices?.[0];u&&u.delta.content&&(a.value[a.value.length-1].content+=u.delta.content)},onError:n=>{c.status="error",c.errorMsg=l,console.error("Stream request error:",n)},onDone:()=>{c.status="finished"}})},v=async()=>{c.status="processing",c.errorMsg=null,d=new AbortController;try{p.value?await m(d):await C(d),c.status="finished"}catch(s){c.errorMsg=l,c.status="error",console.error("Send message error:",s)}finally{d=null}};return{messages:a,messageState:c,inputMessage:t,useStream:p,sendMessage:async(s=t.value,n=!0)=>{if(!s?.trim()||q.includes(c.status))return;let u={role:"user",content:s};a.value.push(u),n&&(t.value=""),await v()},clearMessages:()=>{a.value=[],c.errorMsg=null},addMessage:s=>{a.value.push(s)},abortRequest:()=>{d&&(d.abort(),d=null,c.status="aborted")},retryRequest:async s=>{s===0||!a.value[s]||a.value[s].role==="user"||(a.value.splice(s),await v())}}}import{reactive as H,watch as B}from"vue";var I=class{constructor(e="tiny-robot-ai-conversations"){this.storageKey=e}saveConversations(e){try{localStorage.setItem(this.storageKey,JSON.stringify(e))}catch(o){console.error("\u4FDD\u5B58\u4F1A\u8BDD\u5931\u8D25:",o)}}loadConversations(){try{let e=localStorage.getItem(this.storageKey);return e?JSON.parse(e):[]}catch(e){return console.error("\u52A0\u8F7D\u4F1A\u8BDD\u5931\u8D25:",e),[]}}};function j(){return Date.now().toString(36)+Math.random().toString(36).substring(2,9)}function ne(r){let{client:e,storage:o=new I,autoSave:l=!0,useStreamByDefault:g=!0,errorMessage:a="\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5"}=r,t=H({conversations:[],currentId:null,loading:!1}),p=N({client:e,useStreamByDefault:g,errorMessage:a,initialMessages:[]});B(()=>p.messages.value,i=>{if(t.currentId&&i.length>0){let s=t.conversations.findIndex(n=>n.id===t.currentId);s!==-1&&(t.conversations[s].messages=[...i],t.conversations[s].updatedAt=Date.now(),l&&h())}},{deep:!0});let d=(i="\u65B0\u4F1A\u8BDD",s={})=>{let n=j(),u={id:n,title:i,createdAt:Date.now(),updatedAt:Date.now(),messages:[],metadata:s};return t.conversations.unshift(u),c(n),l&&h(),n},c=i=>{let s=t.conversations.find(n=>n.id===i);s&&(t.currentId=i,p.clearMessages(),s.messages.length>0&&s.messages.forEach(n=>p.addMessage(n)))},C=i=>{let s=t.conversations.findIndex(n=>n.id===i);s!==-1&&(t.conversations.splice(s,1),t.currentId===i&&(t.conversations.length>0?c(t.conversations[0].id):(t.currentId=null,p.clearMessages())),l&&h())},m=(i,s)=>{let n=t.conversations.find(u=>u.id===i);n&&(n.title=s,n.updatedAt=Date.now(),l&&h())},v=(i,s)=>{let n=t.conversations.find(u=>u.id===i);n&&(n.metadata={...n.metadata,...s},n.updatedAt=Date.now(),l&&h())},h=async()=>{try{await o.saveConversations(t.conversations)}catch(i){console.error("\u4FDD\u5B58\u4F1A\u8BDD\u5931\u8D25:",i)}},S=async()=>{t.loading=!0;try{let i=await o.loadConversations();t.conversations=i,i.length>0&&!t.currentId&&c(i[0].id)}catch(i){console.error("\u52A0\u8F7D\u4F1A\u8BDD\u5931\u8D25:",i)}finally{t.loading=!1}},b=async i=>{let s=t.conversations.find(n=>n.id===i);if(!s||s.messages.length<2)return s?.title||"\u65B0\u4F1A\u8BDD";try{let n={role:"system",content:"\u8BF7\u6839\u636E\u4EE5\u4E0B\u5BF9\u8BDD\u5185\u5BB9\uFF0C\u751F\u6210\u4E00\u4E2A\u7B80\u77ED\u7684\u6807\u9898\uFF08\u4E0D\u8D85\u8FC720\u4E2A\u5B57\u7B26\uFF09\u3002\u53EA\u9700\u8981\u8FD4\u56DE\u6807\u9898\u6587\u672C\uFF0C\u4E0D\u9700\u8981\u4EFB\u4F55\u89E3\u91CA\u6216\u989D\u5916\u5185\u5BB9\u3002"},u=s.messages.slice(0,Math.min(4,s.messages.length)),O=(await e.chat({messages:[n,...u],options:{stream:!1,max_tokens:30}})).choices[0].message.content.trim();return m(i,O),O}catch(n){return console.error("\u751F\u6210\u6807\u9898\u5931\u8D25:",n),s.title}},x=()=>t.currentId&&t.conversations.find(i=>i.id===t.currentId)||null;return S(),{state:t,messageManager:p,createConversation:d,switchConversation:c,deleteConversation:C,updateTitle:m,updateMetadata:v,saveConversations:h,loadConversations:S,generateTitle:b,getCurrentConversation:x}}export{A as AIClient,M as BaseModelProvider,P as ErrorType,te as FinalStatus,q as GeneratingStatus,I as LocalStorageStrategy,R as OpenAIProvider,K as STATUS,U as StreamEventType,_ as extractTextFromResponse,D as formatMessages,ne as useConversation,N as useMessage};