@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.
Files changed (59) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/Dockerfile +1 -1
  3. package/Dockerfile.database +1 -1
  4. package/changelog/v1.json +12 -0
  5. package/docs/usage/providers/wenxin.mdx +16 -13
  6. package/docs/usage/providers/wenxin.zh-CN.mdx +11 -8
  7. package/package.json +1 -2
  8. package/src/app/(main)/settings/llm/ProviderList/providers.tsx +2 -4
  9. package/src/config/aiModels/wenxin.ts +125 -19
  10. package/src/config/llm.ts +3 -5
  11. package/src/config/modelProviders/wenxin.ts +100 -23
  12. package/src/const/auth.ts +0 -3
  13. package/src/features/Conversation/Error/APIKeyForm/index.tsx +0 -3
  14. package/src/features/Conversation/components/ChatItem/utils.test.ts +284 -0
  15. package/src/features/Conversation/components/ChatItem/utils.ts +39 -8
  16. package/src/features/Conversation/components/MarkdownElements/LobeArtifact/rehypePlugin.test.ts +125 -0
  17. package/src/features/DevPanel/CacheViewer/DataTable/index.tsx +33 -0
  18. package/src/features/DevPanel/CacheViewer/cacheProvider.tsx +64 -0
  19. package/src/features/DevPanel/CacheViewer/getCacheEntries.ts +52 -0
  20. package/src/features/DevPanel/CacheViewer/index.tsx +25 -0
  21. package/src/features/DevPanel/CacheViewer/schema.ts +49 -0
  22. package/src/features/DevPanel/FeatureFlagViewer/Form.tsx +93 -0
  23. package/src/features/DevPanel/FeatureFlagViewer/index.tsx +11 -0
  24. package/src/features/DevPanel/MetadataViewer/Ld.tsx +25 -0
  25. package/src/features/DevPanel/MetadataViewer/MetaData.tsx +30 -0
  26. package/src/features/DevPanel/MetadataViewer/Og.tsx +75 -0
  27. package/src/features/DevPanel/MetadataViewer/index.tsx +80 -0
  28. package/src/features/DevPanel/MetadataViewer/useHead.ts +16 -0
  29. package/src/features/DevPanel/PostgresViewer/DataTable/index.tsx +39 -49
  30. package/src/features/DevPanel/PostgresViewer/{TableColumns.tsx → SchemaSidebar/Columns.tsx} +6 -4
  31. package/src/features/DevPanel/PostgresViewer/{Schema.tsx → SchemaSidebar/index.tsx} +49 -55
  32. package/src/features/DevPanel/PostgresViewer/index.tsx +4 -2
  33. package/src/features/DevPanel/features/FloatPanel.tsx +218 -0
  34. package/src/features/DevPanel/features/Header.tsx +50 -0
  35. package/src/features/DevPanel/features/Table/TableCell.tsx +73 -0
  36. package/src/features/DevPanel/features/Table/TooltipContent.tsx +39 -0
  37. package/src/features/DevPanel/{PostgresViewer/DataTable/Table.tsx → features/Table/index.tsx} +12 -14
  38. package/src/features/DevPanel/index.tsx +29 -5
  39. package/src/libs/agent-runtime/AgentRuntime.test.ts +0 -1
  40. package/src/libs/agent-runtime/AgentRuntime.ts +7 -0
  41. package/src/libs/agent-runtime/wenxin/index.ts +10 -107
  42. package/src/locales/default/modelProvider.ts +0 -20
  43. package/src/server/modules/AgentRuntime/index.test.ts +0 -21
  44. package/src/services/_auth.ts +0 -14
  45. package/src/store/chat/slices/portal/selectors.test.ts +169 -3
  46. package/src/store/chat/slices/portal/selectors.ts +6 -1
  47. package/src/store/user/slices/modelList/selectors/keyVaults.ts +0 -2
  48. package/src/types/aiProvider.ts +0 -1
  49. package/src/types/user/settings/keyVaults.ts +1 -6
  50. package/src/app/(backend)/webapi/chat/wenxin/route.test.ts +0 -27
  51. package/src/app/(backend)/webapi/chat/wenxin/route.ts +0 -30
  52. package/src/app/(main)/settings/llm/ProviderList/Wenxin/index.tsx +0 -44
  53. package/src/app/(main)/settings/provider/(detail)/wenxin/page.tsx +0 -61
  54. package/src/features/Conversation/Error/APIKeyForm/Wenxin.tsx +0 -49
  55. package/src/features/DevPanel/FloatPanel.tsx +0 -136
  56. package/src/features/DevPanel/PostgresViewer/DataTable/TableCell.tsx +0 -34
  57. package/src/libs/agent-runtime/utils/streams/wenxin.test.ts +0 -153
  58. package/src/libs/agent-runtime/utils/streams/wenxin.ts +0 -38
  59. package/src/libs/agent-runtime/wenxin/type.ts +0 -84
@@ -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
- tableName?: string;
92
+ columns: string[];
93
+ dataSource: any[];
94
+ loading?: boolean;
92
95
  }
93
96
 
94
- const Table = ({ tableName }: TableProps) => {
97
+ const Table = ({ columns, dataSource, loading }: TableProps) => {
95
98
  const { styles } = useStyles();
96
99
 
97
- const tableColumns = useTableColumns(tableName);
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
- 'use client';
1
+ import { BookText, DatabaseIcon, FlagIcon, GlobeLockIcon } from 'lucide-react';
2
2
 
3
- import FloatPanel from './FloatPanel';
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
- <PostgresViewer />
9
- </FloatPanel>
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 { ChatCompletion } from '@baiducloud/qianfan';
2
-
3
- // 如果引入了这个类型,那么在跑 type-check 的 tsc 检查中就会抛错,大无语
4
- // import type QianFanClient from '@baiducloud/qianfan/src/ChatCompletion/index';
5
- import { safeParseJSON } from '@/utils/safeParseJSON';
6
-
7
- import { LobeRuntimeAI } from '../BaseAI';
8
- import { AgentRuntimeErrorType } from '../error';
9
- import { ChatCompetitionOptions, ChatStreamPayload } from '../types';
10
- import { AgentRuntimeError } from '../utils/createError';
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
 
@@ -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
- }) as ChatStoreState;
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
- return result?.groups?.content || '';
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
  };
@@ -23,7 +23,6 @@ export const AiProviderSDKEnum = {
23
23
  Huggingface: 'huggingface',
24
24
  Ollama: 'ollama',
25
25
  Openai: 'openai',
26
- Wenxin: 'wenxin',
27
26
  } as const;
28
27
 
29
28
  export type AiProviderSDKType = (typeof AiProviderSDKEnum)[keyof typeof AiProviderSDKEnum];
@@ -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?: WenxinKeyVault;
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
- });