@lobehub/chat 1.51.2 → 1.51.3
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 +41 -0
- package/Dockerfile +1 -1
- package/Dockerfile.database +1 -1
- package/changelog/v1.json +12 -0
- package/docs/usage/providers/wenxin.mdx +16 -13
- package/docs/usage/providers/wenxin.zh-CN.mdx +11 -8
- package/package.json +1 -2
- package/src/app/(main)/settings/llm/ProviderList/providers.tsx +2 -4
- package/src/config/aiModels/wenxin.ts +125 -19
- package/src/config/llm.ts +3 -5
- package/src/config/modelProviders/wenxin.ts +100 -23
- package/src/const/auth.ts +0 -3
- package/src/features/Conversation/Error/APIKeyForm/index.tsx +0 -3
- package/src/features/Conversation/components/ChatItem/utils.test.ts +284 -0
- package/src/features/Conversation/components/ChatItem/utils.ts +39 -8
- package/src/features/Conversation/components/MarkdownElements/LobeArtifact/rehypePlugin.test.ts +125 -0
- package/src/features/DevPanel/CacheViewer/DataTable/index.tsx +33 -0
- package/src/features/DevPanel/CacheViewer/cacheProvider.tsx +64 -0
- package/src/features/DevPanel/CacheViewer/getCacheEntries.ts +52 -0
- package/src/features/DevPanel/CacheViewer/index.tsx +25 -0
- package/src/features/DevPanel/CacheViewer/schema.ts +49 -0
- package/src/features/DevPanel/FeatureFlagViewer/Form.tsx +93 -0
- package/src/features/DevPanel/FeatureFlagViewer/index.tsx +11 -0
- package/src/features/DevPanel/MetadataViewer/Ld.tsx +25 -0
- package/src/features/DevPanel/MetadataViewer/MetaData.tsx +30 -0
- package/src/features/DevPanel/MetadataViewer/Og.tsx +75 -0
- package/src/features/DevPanel/MetadataViewer/index.tsx +80 -0
- package/src/features/DevPanel/MetadataViewer/useHead.ts +16 -0
- package/src/features/DevPanel/PostgresViewer/DataTable/index.tsx +39 -49
- package/src/features/DevPanel/PostgresViewer/{TableColumns.tsx → SchemaSidebar/Columns.tsx} +6 -4
- package/src/features/DevPanel/PostgresViewer/{Schema.tsx → SchemaSidebar/index.tsx} +49 -55
- package/src/features/DevPanel/PostgresViewer/index.tsx +4 -2
- package/src/features/DevPanel/features/FloatPanel.tsx +218 -0
- package/src/features/DevPanel/features/Header.tsx +50 -0
- package/src/features/DevPanel/features/Table/TableCell.tsx +73 -0
- package/src/features/DevPanel/features/Table/TooltipContent.tsx +39 -0
- package/src/features/DevPanel/{PostgresViewer/DataTable/Table.tsx → features/Table/index.tsx} +12 -14
- package/src/features/DevPanel/index.tsx +29 -5
- package/src/libs/agent-runtime/AgentRuntime.test.ts +0 -1
- package/src/libs/agent-runtime/AgentRuntime.ts +7 -0
- package/src/libs/agent-runtime/wenxin/index.ts +10 -107
- package/src/locales/default/modelProvider.ts +0 -20
- package/src/server/modules/AgentRuntime/index.test.ts +0 -21
- package/src/services/_auth.ts +0 -14
- package/src/store/chat/slices/portal/selectors.test.ts +169 -3
- package/src/store/chat/slices/portal/selectors.ts +6 -1
- package/src/store/user/slices/modelList/selectors/keyVaults.ts +0 -2
- package/src/types/aiProvider.ts +0 -1
- package/src/types/user/settings/keyVaults.ts +1 -6
- package/src/app/(backend)/webapi/chat/wenxin/route.test.ts +0 -27
- package/src/app/(backend)/webapi/chat/wenxin/route.ts +0 -30
- package/src/app/(main)/settings/llm/ProviderList/Wenxin/index.tsx +0 -44
- package/src/app/(main)/settings/provider/(detail)/wenxin/page.tsx +0 -61
- package/src/features/Conversation/Error/APIKeyForm/Wenxin.tsx +0 -49
- package/src/features/DevPanel/FloatPanel.tsx +0 -136
- package/src/features/DevPanel/PostgresViewer/DataTable/TableCell.tsx +0 -34
- package/src/libs/agent-runtime/utils/streams/wenxin.test.ts +0 -153
- package/src/libs/agent-runtime/utils/streams/wenxin.ts +0 -38
- package/src/libs/agent-runtime/wenxin/type.ts +0 -84
package/src/features/DevPanel/{PostgresViewer/DataTable/Table.tsx → features/Table/index.tsx}
RENAMED
@@ -1,9 +1,10 @@
|
|
1
|
+
import { Icon } from '@lobehub/ui';
|
1
2
|
import { createStyles } from 'antd-style';
|
3
|
+
import { Loader2Icon } from 'lucide-react';
|
2
4
|
import React from 'react';
|
3
5
|
import { Center } from 'react-layout-kit';
|
4
6
|
import { TableVirtuoso } from 'react-virtuoso';
|
5
7
|
|
6
|
-
import { usePgTable, useTableColumns } from '../usePgTable';
|
7
8
|
import TableCell from './TableCell';
|
8
9
|
|
9
10
|
const useStyles = createStyles(({ token, css }) => ({
|
@@ -88,24 +89,21 @@ const useStyles = createStyles(({ token, css }) => ({
|
|
88
89
|
}));
|
89
90
|
|
90
91
|
interface TableProps {
|
91
|
-
|
92
|
+
columns: string[];
|
93
|
+
dataSource: any[];
|
94
|
+
loading?: boolean;
|
92
95
|
}
|
93
96
|
|
94
|
-
const Table = ({
|
97
|
+
const Table = ({ columns, dataSource, loading }: TableProps) => {
|
95
98
|
const { styles } = useStyles();
|
96
99
|
|
97
|
-
|
100
|
+
if (loading)
|
101
|
+
return (
|
102
|
+
<Center height={'100%'}>
|
103
|
+
<Icon icon={Loader2Icon} spin />
|
104
|
+
</Center>
|
105
|
+
);
|
98
106
|
|
99
|
-
const tableData = usePgTable(tableName);
|
100
|
-
|
101
|
-
const columns = tableColumns.data?.map((t) => t.name) || [];
|
102
|
-
const isLoading = tableColumns.isLoading || tableData.isLoading;
|
103
|
-
|
104
|
-
if (!tableName) return <Center height={'80%'}>Select a table to view data</Center>;
|
105
|
-
|
106
|
-
if (isLoading) return <Center height={'80%'}>Loading...</Center>;
|
107
|
-
|
108
|
-
const dataSource = tableData.data?.data || [];
|
109
107
|
const header = (
|
110
108
|
<tr>
|
111
109
|
{columns.map((column) => (
|
@@ -1,12 +1,36 @@
|
|
1
|
-
|
1
|
+
import { BookText, DatabaseIcon, FlagIcon, GlobeLockIcon } from 'lucide-react';
|
2
2
|
|
3
|
-
import
|
3
|
+
import CacheViewer from './CacheViewer';
|
4
|
+
import FeatureFlagViewer from './FeatureFlagViewer';
|
5
|
+
import MetadataViewer from './MetadataViewer';
|
4
6
|
import PostgresViewer from './PostgresViewer';
|
7
|
+
import FloatPanel from './features/FloatPanel';
|
5
8
|
|
6
9
|
const DevPanel = () => (
|
7
|
-
<FloatPanel
|
8
|
-
|
9
|
-
|
10
|
+
<FloatPanel
|
11
|
+
items={[
|
12
|
+
{
|
13
|
+
children: <PostgresViewer />,
|
14
|
+
icon: <DatabaseIcon size={16} />,
|
15
|
+
key: 'Postgres Viewer',
|
16
|
+
},
|
17
|
+
{
|
18
|
+
children: <MetadataViewer />,
|
19
|
+
icon: <BookText size={16} />,
|
20
|
+
key: 'SEO Metadata',
|
21
|
+
},
|
22
|
+
{
|
23
|
+
children: <CacheViewer />,
|
24
|
+
icon: <GlobeLockIcon size={16} />,
|
25
|
+
key: 'NextJS Caches',
|
26
|
+
},
|
27
|
+
{
|
28
|
+
children: <FeatureFlagViewer />,
|
29
|
+
icon: <FlagIcon size={16} />,
|
30
|
+
key: 'Feature Flags',
|
31
|
+
},
|
32
|
+
]}
|
33
|
+
/>
|
10
34
|
);
|
11
35
|
|
12
36
|
export default DevPanel;
|
@@ -26,7 +26,6 @@ import {
|
|
26
26
|
ModelProvider,
|
27
27
|
} from '@/libs/agent-runtime';
|
28
28
|
import { LobeStepfunAI } from '@/libs/agent-runtime/stepfun';
|
29
|
-
import LobeWenxinAI from '@/libs/agent-runtime/wenxin';
|
30
29
|
import { createTraceOptions } from '@/server/modules/AgentRuntime';
|
31
30
|
|
32
31
|
import { AgentChatOptions } from './AgentRuntime';
|
@@ -47,6 +47,7 @@ import {
|
|
47
47
|
TextToSpeechPayload,
|
48
48
|
} from './types';
|
49
49
|
import { LobeUpstageAI } from './upstage';
|
50
|
+
import { LobeWenxinAI } from './wenxin';
|
50
51
|
import { LobeXAI } from './xai';
|
51
52
|
import { LobeZeroOneAI } from './zeroone';
|
52
53
|
import { LobeZhipuAI } from './zhipu';
|
@@ -167,6 +168,7 @@ class AgentRuntime {
|
|
167
168
|
taichu: Partial<ClientOptions>;
|
168
169
|
togetherai: Partial<ClientOptions>;
|
169
170
|
upstage: Partial<ClientOptions>;
|
171
|
+
wenxin: Partial<ClientOptions>;
|
170
172
|
xai: Partial<ClientOptions>;
|
171
173
|
zeroone: Partial<ClientOptions>;
|
172
174
|
zhipu: Partial<ClientOptions>;
|
@@ -370,6 +372,11 @@ class AgentRuntime {
|
|
370
372
|
runtimeModel = new LobeDoubaoAI(params.doubao);
|
371
373
|
break;
|
372
374
|
}
|
375
|
+
|
376
|
+
case ModelProvider.Wenxin: {
|
377
|
+
runtimeModel = new LobeWenxinAI(params.wenxin);
|
378
|
+
break;
|
379
|
+
}
|
373
380
|
}
|
374
381
|
return new AgentRuntime(runtimeModel);
|
375
382
|
}
|
@@ -1,107 +1,10 @@
|
|
1
|
-
import {
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
import { debugStream } from '../utils/debugStream';
|
12
|
-
import { StreamingResponse } from '../utils/response';
|
13
|
-
import { convertIterableToStream } from '../utils/streams';
|
14
|
-
import { WenxinStream } from '../utils/streams/wenxin';
|
15
|
-
import { ChatResp } from './type';
|
16
|
-
|
17
|
-
interface ChatErrorCode {
|
18
|
-
error_code: number;
|
19
|
-
error_msg: string;
|
20
|
-
}
|
21
|
-
|
22
|
-
export interface LobeWenxinAIParams {
|
23
|
-
accessKey?: string;
|
24
|
-
baseURL?: string;
|
25
|
-
secretKey?: string;
|
26
|
-
}
|
27
|
-
|
28
|
-
export class LobeWenxinAI implements LobeRuntimeAI {
|
29
|
-
private client: any;
|
30
|
-
baseURL?: string;
|
31
|
-
|
32
|
-
constructor({ accessKey, baseURL, secretKey }: LobeWenxinAIParams = {}) {
|
33
|
-
if (!accessKey || !secretKey)
|
34
|
-
throw AgentRuntimeError.createError(AgentRuntimeErrorType.InvalidProviderAPIKey);
|
35
|
-
|
36
|
-
this.client = new ChatCompletion({
|
37
|
-
QIANFAN_ACCESS_KEY: accessKey,
|
38
|
-
QIANFAN_SECRET_KEY: secretKey,
|
39
|
-
});
|
40
|
-
this.baseURL = baseURL;
|
41
|
-
}
|
42
|
-
|
43
|
-
async chat(payload: ChatStreamPayload, options?: ChatCompetitionOptions) {
|
44
|
-
try {
|
45
|
-
const result = await this.client.chat(
|
46
|
-
{ messages: payload.messages as any, stream: true, user_id: options?.user },
|
47
|
-
payload.model,
|
48
|
-
);
|
49
|
-
|
50
|
-
const wenxinStream = convertIterableToStream(result as AsyncIterable<ChatResp>);
|
51
|
-
|
52
|
-
const [prod, useForDebug] = wenxinStream.tee();
|
53
|
-
|
54
|
-
if (process.env.DEBUG_WENXIN_CHAT_COMPLETION === '1') {
|
55
|
-
debugStream(useForDebug).catch();
|
56
|
-
}
|
57
|
-
|
58
|
-
const stream = WenxinStream(prod, options?.callback);
|
59
|
-
|
60
|
-
// Respond with the stream
|
61
|
-
return StreamingResponse(stream, { headers: options?.headers });
|
62
|
-
} catch (e) {
|
63
|
-
const err = e as Error;
|
64
|
-
|
65
|
-
const error: ChatErrorCode | undefined = safeParseJSON(err.message);
|
66
|
-
|
67
|
-
if (!error) {
|
68
|
-
throw AgentRuntimeError.createError(AgentRuntimeErrorType.AgentRuntimeError, {
|
69
|
-
message: err.message,
|
70
|
-
name: err.name,
|
71
|
-
});
|
72
|
-
}
|
73
|
-
|
74
|
-
// 文心一言错误码
|
75
|
-
// https://cloud.baidu.com/doc/WENXINWORKSHOP/s/tlmyncueh
|
76
|
-
switch (error.error_code) {
|
77
|
-
// Invalid API key or access key
|
78
|
-
case 100:
|
79
|
-
case 13:
|
80
|
-
case 14: {
|
81
|
-
throw AgentRuntimeError.createError(AgentRuntimeErrorType.InvalidProviderAPIKey, error);
|
82
|
-
}
|
83
|
-
|
84
|
-
// quota limit
|
85
|
-
case 4:
|
86
|
-
case 17:
|
87
|
-
case 18:
|
88
|
-
case 19:
|
89
|
-
case 336_501:
|
90
|
-
case 336_502:
|
91
|
-
case 336_503:
|
92
|
-
case 336_504:
|
93
|
-
case 336_505:
|
94
|
-
case 336_507: {
|
95
|
-
throw AgentRuntimeError.createError(AgentRuntimeErrorType.QuotaLimitReached, {
|
96
|
-
errorCode: error.error_code,
|
97
|
-
message: `${error.error_msg} | you can visit https://cloud.baidu.com/doc/WENXINWORKSHOP/s/tlmyncueh for more information about the error code`,
|
98
|
-
});
|
99
|
-
}
|
100
|
-
}
|
101
|
-
|
102
|
-
throw AgentRuntimeError.createError(AgentRuntimeErrorType.ProviderBizError, error);
|
103
|
-
}
|
104
|
-
}
|
105
|
-
}
|
106
|
-
|
107
|
-
export default LobeWenxinAI;
|
1
|
+
import { ModelProvider } from '../types';
|
2
|
+
import { LobeOpenAICompatibleFactory } from '../utils/openaiCompatibleFactory';
|
3
|
+
|
4
|
+
export const LobeWenxinAI = LobeOpenAICompatibleFactory({
|
5
|
+
baseURL: 'https://qianfan.baidubce.com/v2',
|
6
|
+
debug: {
|
7
|
+
chatCompletion: () => process.env.DEBUG_WENXIN_CHAT_COMPLETION === '1',
|
8
|
+
},
|
9
|
+
provider: ModelProvider.Wenxin,
|
10
|
+
});
|
@@ -296,26 +296,6 @@ export default {
|
|
296
296
|
tooltip: '更新服务商基础配置',
|
297
297
|
updateSuccess: '更新成功',
|
298
298
|
},
|
299
|
-
wenxin: {
|
300
|
-
accessKey: {
|
301
|
-
desc: '填入百度千帆平台的 Access Key',
|
302
|
-
placeholder: 'Qianfan Access Key',
|
303
|
-
title: 'Access Key',
|
304
|
-
},
|
305
|
-
checker: {
|
306
|
-
desc: '测试 AccessKey / SecretAccess 是否填写正确',
|
307
|
-
},
|
308
|
-
secretKey: {
|
309
|
-
desc: '填入百度千帆平台 Secret Key',
|
310
|
-
placeholder: 'Qianfan Secret Key',
|
311
|
-
title: 'Secret Key',
|
312
|
-
},
|
313
|
-
unlock: {
|
314
|
-
customRegion: '自定义服务区域',
|
315
|
-
description: '输入你的 AccessKey / SecretKey 即可开始会话。应用不会记录你的鉴权配置',
|
316
|
-
title: '使用自定义文心一言鉴权信息',
|
317
|
-
},
|
318
|
-
},
|
319
299
|
zeroone: {
|
320
300
|
title: '01.AI 零一万物',
|
321
301
|
},
|
@@ -25,7 +25,6 @@ import {
|
|
25
25
|
} from '@/libs/agent-runtime';
|
26
26
|
import { AgentRuntime } from '@/libs/agent-runtime';
|
27
27
|
import { LobeStepfunAI } from '@/libs/agent-runtime/stepfun';
|
28
|
-
import LobeWenxinAI from '@/libs/agent-runtime/wenxin';
|
29
28
|
|
30
29
|
import { initAgentRuntimeWithUserPayload } from './index';
|
31
30
|
|
@@ -55,9 +54,6 @@ vi.mock('@/config/llm', () => ({
|
|
55
54
|
TOGETHERAI_API_KEY: 'test-togetherai-key',
|
56
55
|
QWEN_API_KEY: 'test-qwen-key',
|
57
56
|
STEPFUN_API_KEY: 'test-stepfun-key',
|
58
|
-
|
59
|
-
WENXIN_ACCESS_KEY: 'test-wenxin-access-key',
|
60
|
-
WENXIN_SECRET_KEY: 'test-wenxin-secret-key',
|
61
57
|
})),
|
62
58
|
}));
|
63
59
|
|
@@ -207,16 +203,6 @@ describe('initAgentRuntimeWithUserPayload method', () => {
|
|
207
203
|
expect(runtime['_runtime']).toBeInstanceOf(LobeStepfunAI);
|
208
204
|
});
|
209
205
|
|
210
|
-
it.skip('Wenxin AI provider: with apikey', async () => {
|
211
|
-
const jwtPayload: JWTPayload = {
|
212
|
-
wenxinAccessKey: 'user-wenxin-accessKey',
|
213
|
-
wenxinSecretKey: 'wenxin-secret-key',
|
214
|
-
};
|
215
|
-
const runtime = await initAgentRuntimeWithUserPayload(ModelProvider.Wenxin, jwtPayload);
|
216
|
-
expect(runtime).toBeInstanceOf(AgentRuntime);
|
217
|
-
expect(runtime['_runtime']).toBeInstanceOf(LobeWenxinAI);
|
218
|
-
});
|
219
|
-
|
220
206
|
it('Unknown Provider: with apikey and endpoint, should initialize to OpenAi', async () => {
|
221
207
|
const jwtPayload: JWTPayload = {
|
222
208
|
apiKey: 'user-unknown-key',
|
@@ -364,13 +350,6 @@ describe('initAgentRuntimeWithUserPayload method', () => {
|
|
364
350
|
expect(runtime['_runtime']).toBeInstanceOf(LobeTogetherAI);
|
365
351
|
});
|
366
352
|
|
367
|
-
it.skip('Wenxin AI provider: without apikey', async () => {
|
368
|
-
const jwtPayload = {};
|
369
|
-
const runtime = await initAgentRuntimeWithUserPayload(ModelProvider.Wenxin, jwtPayload);
|
370
|
-
expect(runtime).toBeInstanceOf(AgentRuntime);
|
371
|
-
expect(runtime['_runtime']).toBeInstanceOf(LobeWenxinAI);
|
372
|
-
});
|
373
|
-
|
374
353
|
it('OpenAI provider: without apikey with OPENAI_PROXY_URL', async () => {
|
375
354
|
process.env.OPENAI_PROXY_URL = 'https://proxy.example.com/v1';
|
376
355
|
|
package/src/services/_auth.ts
CHANGED
@@ -9,7 +9,6 @@ import {
|
|
9
9
|
AzureOpenAIKeyVault,
|
10
10
|
CloudflareKeyVault,
|
11
11
|
OpenAICompatibleKeyVault,
|
12
|
-
WenxinKeyVault,
|
13
12
|
} from '@/types/user/settings';
|
14
13
|
import { createJWT } from '@/utils/jwt';
|
15
14
|
|
@@ -18,7 +17,6 @@ export const getProviderAuthPayload = (
|
|
18
17
|
keyVaults: OpenAICompatibleKeyVault &
|
19
18
|
AzureOpenAIKeyVault &
|
20
19
|
AWSBedrockKeyVault &
|
21
|
-
WenxinKeyVault &
|
22
20
|
CloudflareKeyVault,
|
23
21
|
) => {
|
24
22
|
switch (provider) {
|
@@ -47,18 +45,6 @@ export const getProviderAuthPayload = (
|
|
47
45
|
};
|
48
46
|
}
|
49
47
|
|
50
|
-
case ModelProvider.Wenxin: {
|
51
|
-
const { secretKey, accessKey } = keyVaults;
|
52
|
-
|
53
|
-
const apiKey = (accessKey || '') + (secretKey || '');
|
54
|
-
|
55
|
-
return {
|
56
|
-
apiKey,
|
57
|
-
wenxinAccessKey: accessKey,
|
58
|
-
wenxinSecretKey: secretKey,
|
59
|
-
};
|
60
|
-
}
|
61
|
-
|
62
48
|
case ModelProvider.Azure: {
|
63
49
|
return {
|
64
50
|
apiKey: keyVaults.apiKey,
|
@@ -1,16 +1,23 @@
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
2
2
|
|
3
3
|
import type { ChatStoreState } from '@/store/chat';
|
4
|
+
import { ChatMessage } from '@/types/message';
|
4
5
|
|
5
6
|
import { chatPortalSelectors } from './selectors';
|
6
7
|
|
7
8
|
describe('chatDockSelectors', () => {
|
8
|
-
const createState = (overrides?: Partial<ChatStoreState>) =>
|
9
|
-
|
9
|
+
const createState = (overrides?: Partial<ChatStoreState>) => {
|
10
|
+
const state = {
|
10
11
|
showPortal: false,
|
11
12
|
portalToolMessage: undefined,
|
13
|
+
messagesMap: {},
|
14
|
+
activeId: 'test-id',
|
15
|
+
activeTopicId: undefined,
|
12
16
|
...overrides,
|
13
|
-
}
|
17
|
+
} as ChatStoreState;
|
18
|
+
|
19
|
+
return state;
|
20
|
+
};
|
14
21
|
|
15
22
|
describe('showDock', () => {
|
16
23
|
it('should return the showDock state', () => {
|
@@ -92,4 +99,163 @@ describe('chatDockSelectors', () => {
|
|
92
99
|
expect(chatPortalSelectors.previewFileId(state)).toBe('file-id');
|
93
100
|
});
|
94
101
|
});
|
102
|
+
|
103
|
+
describe('artifactMessageContent', () => {
|
104
|
+
it('should return empty string when message not found', () => {
|
105
|
+
const state = createState();
|
106
|
+
expect(chatPortalSelectors.artifactMessageContent('non-existent-id')(state)).toBe('');
|
107
|
+
});
|
108
|
+
|
109
|
+
it('should return message content when message exists', () => {
|
110
|
+
const messageContent = 'Test message content';
|
111
|
+
const state = createState({
|
112
|
+
messagesMap: {
|
113
|
+
'test-id_null': [
|
114
|
+
{
|
115
|
+
id: 'test-id',
|
116
|
+
content: messageContent,
|
117
|
+
createdAt: Date.now(),
|
118
|
+
updatedAt: Date.now(),
|
119
|
+
role: 'user',
|
120
|
+
meta: {},
|
121
|
+
sessionId: 'test-id',
|
122
|
+
} as ChatMessage,
|
123
|
+
],
|
124
|
+
},
|
125
|
+
});
|
126
|
+
expect(chatPortalSelectors.artifactMessageContent('test-id')(state)).toBe(messageContent);
|
127
|
+
});
|
128
|
+
});
|
129
|
+
|
130
|
+
describe('artifactCode', () => {
|
131
|
+
it('should return empty string when no artifact tag found', () => {
|
132
|
+
const state = createState({
|
133
|
+
messagesMap: {
|
134
|
+
'test-id_null': [
|
135
|
+
{
|
136
|
+
id: 'test-id',
|
137
|
+
content: 'No artifact tag here',
|
138
|
+
createdAt: Date.now(),
|
139
|
+
updatedAt: Date.now(),
|
140
|
+
role: 'user',
|
141
|
+
meta: {},
|
142
|
+
sessionId: 'test-id',
|
143
|
+
} as ChatMessage,
|
144
|
+
],
|
145
|
+
},
|
146
|
+
});
|
147
|
+
expect(chatPortalSelectors.artifactCode('test-id')(state)).toBe('');
|
148
|
+
});
|
149
|
+
|
150
|
+
it('should extract content from artifact tag', () => {
|
151
|
+
const artifactContent = 'Test artifact content';
|
152
|
+
const state = createState({
|
153
|
+
messagesMap: {
|
154
|
+
'test-id_null': [
|
155
|
+
{
|
156
|
+
id: 'test-id',
|
157
|
+
content: `<lobeArtifact type="text">${artifactContent}</lobeArtifact>`,
|
158
|
+
createdAt: Date.now(),
|
159
|
+
updatedAt: Date.now(),
|
160
|
+
role: 'user',
|
161
|
+
meta: {},
|
162
|
+
sessionId: 'test-id',
|
163
|
+
} as ChatMessage,
|
164
|
+
],
|
165
|
+
},
|
166
|
+
});
|
167
|
+
expect(chatPortalSelectors.artifactCode('test-id')(state)).toBe(artifactContent);
|
168
|
+
});
|
169
|
+
|
170
|
+
it('should remove markdown code block wrapping HTML content', () => {
|
171
|
+
const htmlContent = `<!DOCTYPE html>
|
172
|
+
<html>
|
173
|
+
<head>
|
174
|
+
<title>Test</title>
|
175
|
+
</head>
|
176
|
+
<body>
|
177
|
+
<div>Test content</div>
|
178
|
+
</body>
|
179
|
+
</html>`;
|
180
|
+
const state = createState({
|
181
|
+
messagesMap: {
|
182
|
+
'test-id_null': [
|
183
|
+
{
|
184
|
+
id: 'test-id',
|
185
|
+
content: `<lobeArtifact type="text/html">
|
186
|
+
\`\`\`html
|
187
|
+
${htmlContent}
|
188
|
+
\`\`\`
|
189
|
+
</lobeArtifact>`,
|
190
|
+
createdAt: Date.now(),
|
191
|
+
updatedAt: Date.now(),
|
192
|
+
role: 'user',
|
193
|
+
meta: {},
|
194
|
+
sessionId: 'test-id',
|
195
|
+
} as ChatMessage,
|
196
|
+
],
|
197
|
+
},
|
198
|
+
});
|
199
|
+
expect(chatPortalSelectors.artifactCode('test-id')(state)).toBe(htmlContent);
|
200
|
+
});
|
201
|
+
});
|
202
|
+
|
203
|
+
describe('isArtifactTagClosed', () => {
|
204
|
+
it('should return false for unclosed artifact tag', () => {
|
205
|
+
const state = createState({
|
206
|
+
messagesMap: {
|
207
|
+
'test-id_null': [
|
208
|
+
{
|
209
|
+
id: 'test-id',
|
210
|
+
content: '<lobeArtifact type="text">Test content',
|
211
|
+
createdAt: Date.now(),
|
212
|
+
updatedAt: Date.now(),
|
213
|
+
role: 'user',
|
214
|
+
meta: {},
|
215
|
+
sessionId: 'test-id',
|
216
|
+
} as ChatMessage,
|
217
|
+
],
|
218
|
+
},
|
219
|
+
});
|
220
|
+
expect(chatPortalSelectors.isArtifactTagClosed('test-id')(state)).toBe(false);
|
221
|
+
});
|
222
|
+
|
223
|
+
it('should return true for closed artifact tag', () => {
|
224
|
+
const state = createState({
|
225
|
+
messagesMap: {
|
226
|
+
'test-id_null': [
|
227
|
+
{
|
228
|
+
id: 'test-id',
|
229
|
+
content: '<lobeArtifact type="text">Test content</lobeArtifact>',
|
230
|
+
createdAt: Date.now(),
|
231
|
+
updatedAt: Date.now(),
|
232
|
+
role: 'user',
|
233
|
+
meta: {},
|
234
|
+
sessionId: 'test-id',
|
235
|
+
} as ChatMessage,
|
236
|
+
],
|
237
|
+
},
|
238
|
+
});
|
239
|
+
expect(chatPortalSelectors.isArtifactTagClosed('test-id')(state)).toBe(true);
|
240
|
+
});
|
241
|
+
|
242
|
+
it('should return false when no artifact tag exists', () => {
|
243
|
+
const state = createState({
|
244
|
+
messagesMap: {
|
245
|
+
'test-id_null': [
|
246
|
+
{
|
247
|
+
id: 'test-id',
|
248
|
+
content: 'No artifact tag here',
|
249
|
+
createdAt: Date.now(),
|
250
|
+
updatedAt: Date.now(),
|
251
|
+
role: 'user',
|
252
|
+
meta: {},
|
253
|
+
sessionId: 'test-id',
|
254
|
+
} as ChatMessage,
|
255
|
+
],
|
256
|
+
},
|
257
|
+
});
|
258
|
+
expect(chatPortalSelectors.isArtifactTagClosed('test-id')(state)).toBe(false);
|
259
|
+
});
|
260
|
+
});
|
95
261
|
});
|
@@ -35,7 +35,12 @@ const artifactCode = (id: string) => (s: ChatStoreState) => {
|
|
35
35
|
const messageContent = artifactMessageContent(id)(s);
|
36
36
|
const result = messageContent.match(ARTIFACT_TAG_REGEX);
|
37
37
|
|
38
|
-
|
38
|
+
let content = result?.groups?.content || '';
|
39
|
+
|
40
|
+
// Remove markdown code block if content is wrapped
|
41
|
+
content = content.replace(/^\s*```[^\n]*\n([\S\s]*?)\n```\s*$/, '$1');
|
42
|
+
|
43
|
+
return content;
|
39
44
|
};
|
40
45
|
|
41
46
|
const isArtifactTagClosed = (id: string) => (s: ChatStoreState) => {
|
@@ -14,7 +14,6 @@ export const keyVaultsSettings = (s: UserStore): UserKeyVaults =>
|
|
14
14
|
|
15
15
|
const openAIConfig = (s: UserStore) => keyVaultsSettings(s).openai || {};
|
16
16
|
const bedrockConfig = (s: UserStore) => keyVaultsSettings(s).bedrock || {};
|
17
|
-
const wenxinConfig = (s: UserStore) => keyVaultsSettings(s).wenxin || {};
|
18
17
|
const ollamaConfig = (s: UserStore) => keyVaultsSettings(s).ollama || {};
|
19
18
|
const azureConfig = (s: UserStore) => keyVaultsSettings(s).azure || {};
|
20
19
|
const cloudflareConfig = (s: UserStore) => keyVaultsSettings(s).cloudflare || {};
|
@@ -45,5 +44,4 @@ export const keyVaultsConfigSelectors = {
|
|
45
44
|
ollamaConfig,
|
46
45
|
openAIConfig,
|
47
46
|
password,
|
48
|
-
wenxinConfig,
|
49
47
|
};
|
package/src/types/aiProvider.ts
CHANGED
@@ -25,11 +25,6 @@ export interface CloudflareKeyVault {
|
|
25
25
|
baseURLOrAccountID?: string;
|
26
26
|
}
|
27
27
|
|
28
|
-
export interface WenxinKeyVault {
|
29
|
-
accessKey?: string;
|
30
|
-
secretKey?: string;
|
31
|
-
}
|
32
|
-
|
33
28
|
export interface UserKeyVaults {
|
34
29
|
ai21?: OpenAICompatibleKeyVault;
|
35
30
|
ai360?: OpenAICompatibleKeyVault;
|
@@ -68,7 +63,7 @@ export interface UserKeyVaults {
|
|
68
63
|
taichu?: OpenAICompatibleKeyVault;
|
69
64
|
togetherai?: OpenAICompatibleKeyVault;
|
70
65
|
upstage?: OpenAICompatibleKeyVault;
|
71
|
-
wenxin?:
|
66
|
+
wenxin?: OpenAICompatibleKeyVault;
|
72
67
|
xai?: OpenAICompatibleKeyVault;
|
73
68
|
zeroone?: OpenAICompatibleKeyVault;
|
74
69
|
zhipu?: OpenAICompatibleKeyVault;
|
@@ -1,27 +0,0 @@
|
|
1
|
-
// @vitest-environment edge-runtime
|
2
|
-
import { describe, expect, it, vi } from 'vitest';
|
3
|
-
|
4
|
-
import { POST as UniverseRoute } from '../[provider]/route';
|
5
|
-
import { POST, runtime } from './route';
|
6
|
-
|
7
|
-
// 模拟 '../[provider]/route'
|
8
|
-
vi.mock('../[provider]/route', () => ({
|
9
|
-
POST: vi.fn().mockResolvedValue('mocked response'),
|
10
|
-
}));
|
11
|
-
|
12
|
-
describe('Configuration tests', () => {
|
13
|
-
it('should have runtime set to "edge"', () => {
|
14
|
-
expect(runtime).toBe('nodejs');
|
15
|
-
});
|
16
|
-
});
|
17
|
-
|
18
|
-
describe('Wenxin POST function tests', () => {
|
19
|
-
it('should call UniverseRoute with correct parameters', async () => {
|
20
|
-
const mockRequest = new Request('https://example.com', { method: 'POST' });
|
21
|
-
await POST(mockRequest);
|
22
|
-
expect(UniverseRoute).toHaveBeenCalledWith(mockRequest, {
|
23
|
-
createRuntime: expect.anything(),
|
24
|
-
params: Promise.resolve({ provider: 'wenxin' }),
|
25
|
-
});
|
26
|
-
});
|
27
|
-
});
|
@@ -1,30 +0,0 @@
|
|
1
|
-
import { getLLMConfig } from '@/config/llm';
|
2
|
-
import { AgentRuntime, ModelProvider } from '@/libs/agent-runtime';
|
3
|
-
import LobeWenxinAI from '@/libs/agent-runtime/wenxin';
|
4
|
-
|
5
|
-
import { POST as UniverseRoute } from '../[provider]/route';
|
6
|
-
|
7
|
-
export const runtime = 'nodejs';
|
8
|
-
|
9
|
-
export const maxDuration = 30;
|
10
|
-
|
11
|
-
export const POST = async (req: Request) =>
|
12
|
-
UniverseRoute(req, {
|
13
|
-
createRuntime: (payload) => {
|
14
|
-
const { WENXIN_ACCESS_KEY, WENXIN_SECRET_KEY } = getLLMConfig();
|
15
|
-
let accessKey: string | undefined = WENXIN_ACCESS_KEY;
|
16
|
-
let secretKey: string | undefined = WENXIN_SECRET_KEY;
|
17
|
-
|
18
|
-
// if the payload has the api key, use user
|
19
|
-
if (payload.apiKey) {
|
20
|
-
accessKey = payload?.wenxinAccessKey;
|
21
|
-
secretKey = payload?.wenxinSecretKey;
|
22
|
-
}
|
23
|
-
|
24
|
-
const params = { accessKey, secretKey };
|
25
|
-
const instance = new LobeWenxinAI(params);
|
26
|
-
|
27
|
-
return new AgentRuntime(instance);
|
28
|
-
},
|
29
|
-
params: Promise.resolve({ provider: ModelProvider.Wenxin }),
|
30
|
-
});
|