@lobehub/chat 1.11.4 → 1.11.6
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/CHANGELOG.md +50 -0
- package/locales/ar/error.json +3 -1
- package/locales/bg-BG/error.json +3 -1
- package/locales/de-DE/error.json +3 -1
- package/locales/en-US/error.json +3 -1
- package/locales/es-ES/error.json +3 -1
- package/locales/fr-FR/error.json +3 -1
- package/locales/it-IT/error.json +3 -1
- package/locales/ja-JP/error.json +3 -1
- package/locales/ko-KR/error.json +3 -1
- package/locales/nl-NL/error.json +3 -1
- package/locales/pl-PL/error.json +3 -1
- package/locales/pt-BR/error.json +3 -1
- package/locales/ru-RU/error.json +3 -1
- package/locales/tr-TR/error.json +3 -1
- package/locales/vi-VN/error.json +3 -1
- package/locales/zh-CN/error.json +2 -0
- package/locales/zh-TW/error.json +3 -1
- package/package.json +2 -3
- package/src/app/(main)/chat/(workspace)/@portal/{features → Home}/Artifacts/ArtifactList/index.tsx +1 -1
- package/src/app/(main)/chat/(workspace)/@portal/{features → Home}/Artifacts/index.tsx +2 -2
- package/src/app/(main)/chat/(workspace)/@portal/Home/index.tsx +13 -0
- package/src/app/(main)/chat/(workspace)/@portal/_layout/Desktop.tsx +1 -1
- package/src/app/(main)/chat/(workspace)/@portal/components/SkeletonLoading.tsx +14 -0
- package/src/app/(main)/chat/(workspace)/@portal/default.tsx +6 -6
- package/src/app/(main)/chat/(workspace)/@portal/error.tsx +5 -0
- package/src/app/(main)/chat/(workspace)/@portal/features/Header.tsx +1 -0
- package/src/app/(main)/chat/(workspace)/@portal/loading.tsx +3 -0
- package/src/app/(main)/chat/(workspace)/@portal/router.tsx +19 -0
- package/src/app/metadata.ts +2 -3
- package/src/config/app.ts +2 -2
- package/src/const/message.ts +2 -0
- package/src/features/Conversation/Messages/Assistant/index.tsx +2 -2
- package/src/features/Conversation/Messages/Default.tsx +8 -3
- package/src/libs/agent-runtime/error.ts +1 -0
- package/src/libs/agent-runtime/utils/streams/openai.test.ts +42 -0
- package/src/libs/agent-runtime/utils/streams/openai.ts +63 -40
- package/src/libs/agent-runtime/utils/streams/protocol.ts +1 -1
- package/src/libs/trpc/client/edge.ts +1 -0
- package/src/libs/trpc/middleware/userAuth.ts +1 -0
- package/src/locales/default/error.ts +6 -1
- package/src/server/ld.ts +8 -10
- package/src/services/__tests__/chat.test.ts +0 -1
- package/src/store/agent/slices/chat/action.ts +2 -1
- package/src/store/chat/slices/message/action.test.ts +4 -4
- package/src/store/chat/slices/message/action.ts +2 -2
- package/src/store/session/slices/session/action.ts +2 -1
- package/src/store/tool/slices/plugin/action.ts +2 -1
- package/src/store/user/slices/settings/action.ts +3 -1
- package/src/types/fetch.ts +1 -0
- package/src/utils/downloadFile.ts +18 -0
- package/src/utils/{fetch.test.ts → fetch/__tests__/fetchSSE.test.ts} +168 -258
- package/src/utils/fetch/__tests__/parseError.test.ts +89 -0
- package/src/utils/fetch/__tests__/parseToolCalls.test.ts +123 -0
- package/src/utils/fetch/fetchEventSource/index.ts +110 -0
- package/src/utils/fetch/fetchEventSource/parse.ts +182 -0
- package/src/utils/{fetch.ts → fetch/fetchSSE.ts} +100 -118
- package/src/utils/fetch/index.ts +2 -0
- package/src/utils/fetch/parseError.ts +26 -0
- package/src/utils/fetch/parseToolCalls.ts +25 -0
- package/vitest.config.ts +1 -0
- package/src/app/(main)/chat/(workspace)/@portal/index.tsx +0 -24
- /package/src/app/(main)/chat/(workspace)/@portal/{features/ArtifactUI → Artifacts}/Footer.tsx +0 -0
- /package/src/app/(main)/chat/(workspace)/@portal/{features/ArtifactUI → Artifacts}/ToolRender.tsx +0 -0
- /package/src/app/(main)/chat/(workspace)/@portal/{features/ArtifactUI → Artifacts}/index.tsx +0 -0
- /package/src/app/(main)/chat/(workspace)/@portal/{features → Home}/Artifacts/ArtifactList/Item/index.tsx +0 -0
- /package/src/app/(main)/chat/(workspace)/@portal/{features → Home}/Artifacts/ArtifactList/Item/style.ts +0 -0
|
@@ -2,6 +2,8 @@ import { readableFromAsyncIterable } from 'ai';
|
|
|
2
2
|
import OpenAI from 'openai';
|
|
3
3
|
import type { Stream } from 'openai/streaming';
|
|
4
4
|
|
|
5
|
+
import { ChatMessageError } from '@/types/message';
|
|
6
|
+
|
|
5
7
|
import { ChatStreamCallbacks } from '../../types';
|
|
6
8
|
import {
|
|
7
9
|
StreamProtocolChunk,
|
|
@@ -15,53 +17,74 @@ import {
|
|
|
15
17
|
export const transformOpenAIStream = (chunk: OpenAI.ChatCompletionChunk): StreamProtocolChunk => {
|
|
16
18
|
// maybe need another structure to add support for multiple choices
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
try {
|
|
21
|
+
const item = chunk.choices[0];
|
|
22
|
+
if (!item) {
|
|
23
|
+
return { data: chunk, id: chunk.id, type: 'data' };
|
|
24
|
+
}
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
if (typeof item.delta?.content === 'string') {
|
|
27
|
+
return { data: item.delta.content, id: chunk.id, type: 'text' };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (item.delta?.tool_calls) {
|
|
31
|
+
return {
|
|
32
|
+
data: item.delta.tool_calls.map(
|
|
33
|
+
(value, index): StreamToolCallChunkData => ({
|
|
34
|
+
function: value.function,
|
|
35
|
+
id: value.id || generateToolCallId(index, value.function?.name),
|
|
36
|
+
|
|
37
|
+
// mistral's tool calling don't have index and function field, it's data like:
|
|
38
|
+
// [{"id":"xbhnmTtY7","function":{"name":"lobe-image-designer____text2image____builtin","arguments":"{\"prompts\": [\"A photo of a small, fluffy dog with a playful expression and wagging tail.\", \"A watercolor painting of a small, energetic dog with a glossy coat and bright eyes.\", \"A vector illustration of a small, adorable dog with a short snout and perky ears.\", \"A drawing of a small, scruffy dog with a mischievous grin and a wagging tail.\"], \"quality\": \"standard\", \"seeds\": [123456, 654321, 111222, 333444], \"size\": \"1024x1024\", \"style\": \"vivid\"}"}}]
|
|
39
|
+
|
|
40
|
+
// minimax's tool calling don't have index field, it's data like:
|
|
41
|
+
// [{"id":"call_function_4752059746","type":"function","function":{"name":"lobe-image-designer____text2image____builtin","arguments":"{\"prompts\": [\"一个流浪的地球,背景是浩瀚"}}]
|
|
42
|
+
|
|
43
|
+
// so we need to add these default values
|
|
44
|
+
index: typeof value.index !== 'undefined' ? value.index : index,
|
|
45
|
+
type: value.type || 'function',
|
|
46
|
+
}),
|
|
47
|
+
),
|
|
48
|
+
id: chunk.id,
|
|
49
|
+
type: 'tool_calls',
|
|
50
|
+
} as StreamProtocolToolCallChunk;
|
|
51
|
+
}
|
|
26
52
|
|
|
27
|
-
|
|
53
|
+
// 给定结束原因
|
|
54
|
+
if (item.finish_reason) {
|
|
55
|
+
return { data: item.finish_reason, id: chunk.id, type: 'stop' };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (item.delta?.content === null) {
|
|
59
|
+
return { data: item.delta, id: chunk.id, type: 'data' };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 其余情况下,返回 delta 和 index
|
|
28
63
|
return {
|
|
29
|
-
data: item.delta.
|
|
30
|
-
(value, index): StreamToolCallChunkData => ({
|
|
31
|
-
function: value.function,
|
|
32
|
-
id: value.id || generateToolCallId(index, value.function?.name),
|
|
33
|
-
|
|
34
|
-
// mistral's tool calling don't have index and function field, it's data like:
|
|
35
|
-
// [{"id":"xbhnmTtY7","function":{"name":"lobe-image-designer____text2image____builtin","arguments":"{\"prompts\": [\"A photo of a small, fluffy dog with a playful expression and wagging tail.\", \"A watercolor painting of a small, energetic dog with a glossy coat and bright eyes.\", \"A vector illustration of a small, adorable dog with a short snout and perky ears.\", \"A drawing of a small, scruffy dog with a mischievous grin and a wagging tail.\"], \"quality\": \"standard\", \"seeds\": [123456, 654321, 111222, 333444], \"size\": \"1024x1024\", \"style\": \"vivid\"}"}}]
|
|
36
|
-
|
|
37
|
-
// minimax's tool calling don't have index field, it's data like:
|
|
38
|
-
// [{"id":"call_function_4752059746","type":"function","function":{"name":"lobe-image-designer____text2image____builtin","arguments":"{\"prompts\": [\"一个流浪的地球,背景是浩瀚"}}]
|
|
39
|
-
|
|
40
|
-
// so we need to add these default values
|
|
41
|
-
index: typeof value.index !== 'undefined' ? value.index : index,
|
|
42
|
-
type: value.type || 'function',
|
|
43
|
-
}),
|
|
44
|
-
),
|
|
64
|
+
data: { delta: item.delta, id: chunk.id, index: item.index },
|
|
45
65
|
id: chunk.id,
|
|
46
|
-
type: '
|
|
47
|
-
}
|
|
48
|
-
}
|
|
66
|
+
type: 'data',
|
|
67
|
+
};
|
|
68
|
+
} catch (e) {
|
|
69
|
+
const errorName = 'StreamChunkError';
|
|
70
|
+
console.error(`[${errorName}]`, e);
|
|
71
|
+
console.error(`[${errorName}] raw chunk:`, chunk);
|
|
49
72
|
|
|
50
|
-
|
|
51
|
-
if (item.finish_reason) {
|
|
52
|
-
return { data: item.finish_reason, id: chunk.id, type: 'stop' };
|
|
53
|
-
}
|
|
73
|
+
const err = e as Error;
|
|
54
74
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
75
|
+
/* eslint-disable sort-keys-fix/sort-keys-fix */
|
|
76
|
+
const errorData = {
|
|
77
|
+
body: {
|
|
78
|
+
message:
|
|
79
|
+
'chat response streaming chunk parse error, please contact your API Provider to fix it.',
|
|
80
|
+
context: { error: { message: err.message, name: err.name }, chunk },
|
|
81
|
+
},
|
|
82
|
+
type: 'StreamChunkError',
|
|
83
|
+
} as ChatMessageError;
|
|
84
|
+
/* eslint-enable */
|
|
58
85
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
data: { delta: item.delta, id: chunk.id, index: item.index },
|
|
62
|
-
id: chunk.id,
|
|
63
|
-
type: 'data',
|
|
64
|
-
};
|
|
86
|
+
return { data: errorData, id: chunk.id, type: 'error' };
|
|
87
|
+
}
|
|
65
88
|
};
|
|
66
89
|
|
|
67
90
|
const chatStreamable = async function* (stream: AsyncIterable<OpenAI.ChatCompletionChunk>) {
|
|
@@ -13,7 +13,7 @@ export interface StreamStack {
|
|
|
13
13
|
export interface StreamProtocolChunk {
|
|
14
14
|
data: any;
|
|
15
15
|
id?: string;
|
|
16
|
-
type: 'text' | 'tool_calls' | 'data' | 'stop';
|
|
16
|
+
type: 'text' | 'tool_calls' | 'data' | 'stop' | 'error';
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
export interface StreamToolCallChunkData {
|
|
@@ -85,10 +85,15 @@ export default {
|
|
|
85
85
|
* @deprecated
|
|
86
86
|
*/
|
|
87
87
|
NoOpenAIAPIKey: 'OpenAI API Key 不正确或为空,请添加自定义 OpenAI API Key',
|
|
88
|
+
/**
|
|
89
|
+
* @deprecated
|
|
90
|
+
*/
|
|
88
91
|
OpenAIBizError: '请求 OpenAI 服务出错,请根据以下信息排查或重试',
|
|
89
92
|
|
|
90
93
|
InvalidBedrockCredentials: 'Bedrock 鉴权未通过,请检查 AccessKeyId/SecretAccessKey 后重试',
|
|
91
|
-
|
|
94
|
+
StreamChunkError:
|
|
95
|
+
'流式请求的消息块解析错误,请检查当前 API 接口是否符合标准规范,或联系你的 API 供应商咨询',
|
|
96
|
+
UnknownChatFetchError: '很抱歉,遇到未知请求错误,请根据以下信息排查或重试',
|
|
92
97
|
InvalidOllamaArgs: 'Ollama 配置不正确,请检查 Ollama 配置后重试',
|
|
93
98
|
OllamaBizError: '请求 Ollama 服务出错,请根据以下信息排查或重试',
|
|
94
99
|
OllamaServiceUnavailable:
|
package/src/server/ld.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import urlJoin from 'url-join';
|
|
2
2
|
|
|
3
|
-
import { getAppConfig } from '@/config/app';
|
|
4
3
|
import { EMAIL_BUSINESS, EMAIL_SUPPORT, OFFICIAL_SITE, OFFICIAL_URL, X } from '@/const/url';
|
|
5
4
|
|
|
6
5
|
import pkg from '../../package.json';
|
|
7
6
|
|
|
8
|
-
const { SITE_URL = OFFICIAL_URL } = getAppConfig();
|
|
9
7
|
const LAST_MODIFIED = new Date().toISOString();
|
|
10
8
|
export const AUTHOR_LIST = {
|
|
11
9
|
arvinxx: {
|
|
@@ -70,7 +68,7 @@ class Ld {
|
|
|
70
68
|
|
|
71
69
|
genOrganization() {
|
|
72
70
|
return {
|
|
73
|
-
'@id': this.getId(
|
|
71
|
+
'@id': this.getId(OFFICIAL_URL, '#organization'),
|
|
74
72
|
'@type': 'Organization',
|
|
75
73
|
'alternateName': 'LobeChat',
|
|
76
74
|
'contactPoint': {
|
|
@@ -102,7 +100,7 @@ class Ld {
|
|
|
102
100
|
|
|
103
101
|
getAuthors(ids: string[] = []) {
|
|
104
102
|
const defaultAuthor = {
|
|
105
|
-
'@id': this.getId(
|
|
103
|
+
'@id': this.getId(OFFICIAL_URL, '#organization'),
|
|
106
104
|
'@type': 'Organization',
|
|
107
105
|
};
|
|
108
106
|
if (!ids || ids.length === 0) return defaultAuthor;
|
|
@@ -142,7 +140,7 @@ class Ld {
|
|
|
142
140
|
'@id': fixedUrl,
|
|
143
141
|
'@type': 'WebPage',
|
|
144
142
|
'about': {
|
|
145
|
-
'@id': this.getId(
|
|
143
|
+
'@id': this.getId(OFFICIAL_URL, '#organization'),
|
|
146
144
|
},
|
|
147
145
|
'breadcrumbs': {
|
|
148
146
|
'@id': this.getId(fixedUrl, '#breadcrumb'),
|
|
@@ -155,7 +153,7 @@ class Ld {
|
|
|
155
153
|
},
|
|
156
154
|
'inLanguage': 'en-US',
|
|
157
155
|
'isPartOf': {
|
|
158
|
-
'@id': this.getId(
|
|
156
|
+
'@id': this.getId(OFFICIAL_URL, '#website'),
|
|
159
157
|
},
|
|
160
158
|
'name': this.fixTitle(title),
|
|
161
159
|
'primaryImageOfPage': {
|
|
@@ -188,15 +186,15 @@ class Ld {
|
|
|
188
186
|
|
|
189
187
|
genWebSite() {
|
|
190
188
|
const baseInfo: any = {
|
|
191
|
-
'@id': this.getId(
|
|
189
|
+
'@id': this.getId(OFFICIAL_URL, '#website'),
|
|
192
190
|
'@type': 'WebSite',
|
|
193
191
|
'description': pkg.description,
|
|
194
192
|
'inLanguage': 'en-US',
|
|
195
193
|
'name': 'LobeChat',
|
|
196
194
|
'publisher': {
|
|
197
|
-
'@id': this.getId(
|
|
195
|
+
'@id': this.getId(OFFICIAL_URL, '#organization'),
|
|
198
196
|
},
|
|
199
|
-
'url':
|
|
197
|
+
'url': OFFICIAL_URL,
|
|
200
198
|
};
|
|
201
199
|
|
|
202
200
|
return baseInfo;
|
|
@@ -211,7 +209,7 @@ class Ld {
|
|
|
211
209
|
}
|
|
212
210
|
|
|
213
211
|
private fixUrl(url: string) {
|
|
214
|
-
return urlJoin(
|
|
212
|
+
return urlJoin(OFFICIAL_URL, url);
|
|
215
213
|
}
|
|
216
214
|
}
|
|
217
215
|
|
|
@@ -4,6 +4,7 @@ import { SWRResponse, mutate } from 'swr';
|
|
|
4
4
|
import { DeepPartial } from 'utility-types';
|
|
5
5
|
import { StateCreator } from 'zustand/vanilla';
|
|
6
6
|
|
|
7
|
+
import { MESSAGE_CANCEL_FLAT } from '@/const/message';
|
|
7
8
|
import { INBOX_SESSION_ID } from '@/const/session';
|
|
8
9
|
import { DEFAULT_AGENT_CONFIG } from '@/const/settings';
|
|
9
10
|
import { useClientDataSWR, useOnlyFetchOnceSWR } from '@/libs/swr';
|
|
@@ -168,7 +169,7 @@ export const createChatSlice: StateCreator<
|
|
|
168
169
|
|
|
169
170
|
internal_createAbortController: (key) => {
|
|
170
171
|
const abortController = get()[key] as AbortController;
|
|
171
|
-
if (abortController) abortController.abort(
|
|
172
|
+
if (abortController) abortController.abort(MESSAGE_CANCEL_FLAT);
|
|
172
173
|
const controller = new AbortController();
|
|
173
174
|
set({ [key]: controller }, false, 'internal_createAbortController');
|
|
174
175
|
|
|
@@ -958,10 +958,10 @@ describe('chatMessage actions', () => {
|
|
|
958
958
|
it('should not do anything if there is no abortController', async () => {
|
|
959
959
|
const { result } = renderHook(() => useChatStore());
|
|
960
960
|
|
|
961
|
-
// 确保没有设置 abortController
|
|
962
|
-
useChatStore.setState({ abortController: undefined });
|
|
963
|
-
|
|
964
961
|
await act(async () => {
|
|
962
|
+
// 确保没有设置 abortController
|
|
963
|
+
useChatStore.setState({ abortController: undefined });
|
|
964
|
+
|
|
965
965
|
result.current.stopGenerateMessage();
|
|
966
966
|
});
|
|
967
967
|
|
|
@@ -1089,7 +1089,7 @@ describe('chatMessage actions', () => {
|
|
|
1089
1089
|
|
|
1090
1090
|
// Mock fetch to reject with an error
|
|
1091
1091
|
const errorMessage = 'Error fetching AI response';
|
|
1092
|
-
vi.mocked(fetch).
|
|
1092
|
+
vi.mocked(fetch).mockRejectedValueOnce(new Error(errorMessage));
|
|
1093
1093
|
|
|
1094
1094
|
await act(async () => {
|
|
1095
1095
|
expect(
|
|
@@ -7,7 +7,7 @@ import { template } from 'lodash-es';
|
|
|
7
7
|
import { SWRResponse, mutate } from 'swr';
|
|
8
8
|
import { StateCreator } from 'zustand/vanilla';
|
|
9
9
|
|
|
10
|
-
import { LOADING_FLAT } from '@/const/message';
|
|
10
|
+
import { LOADING_FLAT, MESSAGE_CANCEL_FLAT } from '@/const/message';
|
|
11
11
|
import { TraceEventType, TraceNameMap } from '@/const/trace';
|
|
12
12
|
import { useClientDataSWR } from '@/libs/swr';
|
|
13
13
|
import { chatService } from '@/services/chat';
|
|
@@ -399,7 +399,7 @@ export const chatMessage: StateCreator<
|
|
|
399
399
|
const { abortController, internal_toggleChatLoading } = get();
|
|
400
400
|
if (!abortController) return;
|
|
401
401
|
|
|
402
|
-
abortController.abort();
|
|
402
|
+
abortController.abort(MESSAGE_CANCEL_FLAT);
|
|
403
403
|
|
|
404
404
|
internal_toggleChatLoading(false, undefined, n('stopGenerateMessage') as string);
|
|
405
405
|
},
|
|
@@ -5,6 +5,7 @@ import { DeepPartial } from 'utility-types';
|
|
|
5
5
|
import { StateCreator } from 'zustand/vanilla';
|
|
6
6
|
|
|
7
7
|
import { message } from '@/components/AntdStaticMethods';
|
|
8
|
+
import { MESSAGE_CANCEL_FLAT } from '@/const/message';
|
|
8
9
|
import { DEFAULT_AGENT_LOBE_SESSION, INBOX_SESSION_ID } from '@/const/session';
|
|
9
10
|
import { useClientDataSWR } from '@/libs/swr';
|
|
10
11
|
import { sessionService } from '@/services/session';
|
|
@@ -188,7 +189,7 @@ export const createSessionSlice: StateCreator<
|
|
|
188
189
|
const { activeId, refreshSessions } = get();
|
|
189
190
|
|
|
190
191
|
const abortController = get().signalSessionMeta as AbortController;
|
|
191
|
-
if (abortController) abortController.abort(
|
|
192
|
+
if (abortController) abortController.abort(MESSAGE_CANCEL_FLAT);
|
|
192
193
|
const controller = new AbortController();
|
|
193
194
|
set({ signalSessionMeta: controller }, false, 'updateSessionMetaSignal');
|
|
194
195
|
|
|
@@ -2,6 +2,7 @@ import { Schema, ValidationResult } from '@cfworker/json-schema';
|
|
|
2
2
|
import useSWR, { SWRResponse } from 'swr';
|
|
3
3
|
import { StateCreator } from 'zustand/vanilla';
|
|
4
4
|
|
|
5
|
+
import { MESSAGE_CANCEL_FLAT } from '@/const/message';
|
|
5
6
|
import { pluginService } from '@/services/plugin';
|
|
6
7
|
import { merge } from '@/utils/merge';
|
|
7
8
|
|
|
@@ -46,7 +47,7 @@ export const createPluginSlice: StateCreator<
|
|
|
46
47
|
},
|
|
47
48
|
updatePluginSettings: async (id, settings) => {
|
|
48
49
|
const signal = get().updatePluginSettingsSignal;
|
|
49
|
-
if (signal) signal.abort(
|
|
50
|
+
if (signal) signal.abort(MESSAGE_CANCEL_FLAT);
|
|
50
51
|
|
|
51
52
|
const newSignal = new AbortController();
|
|
52
53
|
|
|
@@ -3,6 +3,7 @@ import isEqual from 'fast-deep-equal';
|
|
|
3
3
|
import { DeepPartial } from 'utility-types';
|
|
4
4
|
import type { StateCreator } from 'zustand/vanilla';
|
|
5
5
|
|
|
6
|
+
import { MESSAGE_CANCEL_FLAT } from '@/const/message';
|
|
6
7
|
import { shareService } from '@/services/share';
|
|
7
8
|
import { userService } from '@/services/user';
|
|
8
9
|
import type { UserStore } from '@/store/user';
|
|
@@ -63,7 +64,8 @@ export const createSettingsSlice: StateCreator<
|
|
|
63
64
|
|
|
64
65
|
internal_createSignal: () => {
|
|
65
66
|
const abortController = get().updateSettingsSignal;
|
|
66
|
-
if (abortController && !abortController.signal.aborted)
|
|
67
|
+
if (abortController && !abortController.signal.aborted)
|
|
68
|
+
abortController.abort(MESSAGE_CANCEL_FLAT);
|
|
67
69
|
|
|
68
70
|
const newSignal = new AbortController();
|
|
69
71
|
|
package/src/types/fetch.ts
CHANGED
|
@@ -12,6 +12,7 @@ export const ChatErrorType = {
|
|
|
12
12
|
NoOpenAIAPIKey: 'NoOpenAIAPIKey',
|
|
13
13
|
OllamaServiceUnavailable: 'OllamaServiceUnavailable', // 未启动/检测到 Ollama 服务
|
|
14
14
|
PluginFailToTransformArguments: 'PluginFailToTransformArguments',
|
|
15
|
+
UnknownChatFetchError: 'UnknownChatFetchError',
|
|
15
16
|
|
|
16
17
|
// ******* 客户端错误 ******* //
|
|
17
18
|
BadRequest: 400,
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const downloadFile = async (url: string, fileName: string) => {
|
|
2
|
+
try {
|
|
3
|
+
const res = await fetch(url);
|
|
4
|
+
const blob = await res.blob();
|
|
5
|
+
|
|
6
|
+
const blobUrl = window.URL.createObjectURL(blob);
|
|
7
|
+
const link = document.createElement('a');
|
|
8
|
+
link.href = blobUrl;
|
|
9
|
+
link.download = fileName;
|
|
10
|
+
link.style.display = 'none';
|
|
11
|
+
document.body.append(link);
|
|
12
|
+
link.click();
|
|
13
|
+
link.remove();
|
|
14
|
+
window.URL.revokeObjectURL(blobUrl);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error('Download failed:', error);
|
|
17
|
+
}
|
|
18
|
+
};
|