@lobehub/chat 1.51.1 → 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 +66 -0
- package/Dockerfile +1 -1
- package/Dockerfile.database +1 -1
- package/changelog/v1.json +21 -0
- package/docs/usage/providers/wenxin.mdx +16 -13
- package/docs/usage/providers/wenxin.zh-CN.mdx +11 -8
- package/next.config.ts +6 -0
- package/package.json +1 -2
- package/src/app/(main)/settings/llm/ProviderList/providers.tsx +2 -4
- package/src/config/aiModels/ai360.ts +22 -2
- package/src/config/aiModels/fireworksai.ts +3 -0
- package/src/config/aiModels/giteeai.ts +60 -0
- package/src/config/aiModels/github.ts +7 -0
- package/src/config/aiModels/google.ts +2 -0
- package/src/config/aiModels/groq.ts +12 -0
- package/src/config/aiModels/huggingface.ts +6 -0
- package/src/config/aiModels/internlm.ts +19 -2
- package/src/config/aiModels/ollama.ts +1 -0
- package/src/config/aiModels/openai.ts +10 -0
- package/src/config/aiModels/perplexity.ts +3 -0
- package/src/config/aiModels/qwen.ts +2 -0
- package/src/config/aiModels/siliconcloud.ts +4 -0
- package/src/config/aiModels/togetherai.ts +64 -1
- package/src/config/aiModels/wenxin.ts +125 -19
- package/src/config/aiModels/zhipu.ts +3 -0
- 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
@@ -147,4 +147,288 @@ describe('processWithArtifact', () => {
|
|
147
147
|
|
148
148
|
expect(output).toEqual(`<lobeThinking>这个词汇涉及了`);
|
149
149
|
});
|
150
|
+
|
151
|
+
it('should handle no empty line between lobeThinking and lobeArtifact', () => {
|
152
|
+
const input = `<lobeThinking>这是一个思考过程。</lobeThinking>
|
153
|
+
<lobeArtifact identifier="test" type="image/svg+xml" title="测试">
|
154
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
155
|
+
<rect width="100" height="100" fill="blue"/>
|
156
|
+
</svg>
|
157
|
+
</lobeArtifact>`;
|
158
|
+
|
159
|
+
const output = processWithArtifact(input);
|
160
|
+
|
161
|
+
expect(output).toEqual(`<lobeThinking>这是一个思考过程。</lobeThinking>
|
162
|
+
|
163
|
+
<lobeArtifact identifier="test" type="image/svg+xml" title="测试"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> <rect width="100" height="100" fill="blue"/></svg></lobeArtifact>`);
|
164
|
+
});
|
165
|
+
|
166
|
+
it('should remove fenced code block between lobeArtifact and HTML content', () => {
|
167
|
+
const input = `<lobeArtifact identifier="web-calculator" type="text/html" title="简单的 Web 计算器">
|
168
|
+
\`\`\`html
|
169
|
+
<!DOCTYPE html>
|
170
|
+
<html lang="zh">
|
171
|
+
<head>
|
172
|
+
<title>计算器</title>
|
173
|
+
</head>
|
174
|
+
<body>
|
175
|
+
<div>计算器</div>
|
176
|
+
</body>
|
177
|
+
</html>
|
178
|
+
\`\`\`
|
179
|
+
</lobeArtifact>`;
|
180
|
+
|
181
|
+
const output = processWithArtifact(input);
|
182
|
+
|
183
|
+
expect(output).toEqual(
|
184
|
+
`<lobeArtifact identifier="web-calculator" type="text/html" title="简单的 Web 计算器"><!DOCTYPE html><html lang="zh"><head> <title>计算器</title></head><body> <div>计算器</div></body></html></lobeArtifact>`,
|
185
|
+
);
|
186
|
+
});
|
187
|
+
|
188
|
+
it('should remove fenced code block between lobeArtifact and HTML content without doctype', () => {
|
189
|
+
const input = `<lobeArtifact identifier="web-calculator" type="text/html" title="简单的 Web 计算器">
|
190
|
+
\`\`\`html
|
191
|
+
<html lang="zh">
|
192
|
+
<head>
|
193
|
+
<title>计算器</title>
|
194
|
+
</head>
|
195
|
+
<body>
|
196
|
+
<div>计算器</div>
|
197
|
+
</body>
|
198
|
+
</html>
|
199
|
+
\`\`\`
|
200
|
+
</lobeArtifact>`;
|
201
|
+
|
202
|
+
const output = processWithArtifact(input);
|
203
|
+
|
204
|
+
expect(output).toEqual(
|
205
|
+
`<lobeArtifact identifier="web-calculator" type="text/html" title="简单的 Web 计算器"><html lang="zh"><head> <title>计算器</title></head><body> <div>计算器</div></body></html></lobeArtifact>`,
|
206
|
+
);
|
207
|
+
});
|
208
|
+
|
209
|
+
it('should remove outer fenced code block wrapping lobeThinking and lobeArtifact', () => {
|
210
|
+
const input =
|
211
|
+
'```tool_code\n<lobeThinking>这是一个思考过程。</lobeThinking>\n\n<lobeArtifact identifier="test" type="text/html" title="测试">\n<div>测试内容</div>\n</lobeArtifact>\n```';
|
212
|
+
|
213
|
+
const output = processWithArtifact(input);
|
214
|
+
|
215
|
+
expect(output).toEqual(
|
216
|
+
'<lobeThinking>这是一个思考过程。</lobeThinking>\n\n<lobeArtifact identifier="test" type="text/html" title="测试"><div>测试内容</div></lobeArtifact>',
|
217
|
+
);
|
218
|
+
});
|
219
|
+
|
220
|
+
it('should handle both outer code block and inner HTML code block', () => {
|
221
|
+
const input =
|
222
|
+
'```tool_code\n<lobeThinking>这是一个思考过程。</lobeThinking>\n\n<lobeArtifact identifier="test" type="text/html" title="测试">\n```html\n<!DOCTYPE html>\n<html>\n<body>\n<div>测试内容</div>\n</body>\n</html>\n```\n</lobeArtifact>\n```';
|
223
|
+
|
224
|
+
const output = processWithArtifact(input);
|
225
|
+
|
226
|
+
expect(output).toEqual(
|
227
|
+
'<lobeThinking>这是一个思考过程。</lobeThinking>\n\n<lobeArtifact identifier="test" type="text/html" title="测试"><!DOCTYPE html><html><body><div>测试内容</div></body></html></lobeArtifact>',
|
228
|
+
);
|
229
|
+
});
|
230
|
+
|
231
|
+
it('should handle complete conversation with text and tags', () => {
|
232
|
+
const input = `Sure, I can help you with that! Here is a basic calculator built using HTML, CSS, and JavaScript.
|
233
|
+
|
234
|
+
<lobeThinking>A web calculator is a substantial piece of code and a good candidate for an artifact. It's self-contained, and it's likely that the user will want to modify it. This is a new request, so I will create a new artifact.</lobeThinking>
|
235
|
+
|
236
|
+
<lobeArtifact identifier="web-calculator" type="text/html" title="Web Calculator">
|
237
|
+
\`\`\`html
|
238
|
+
<!DOCTYPE html>
|
239
|
+
<html lang="en">
|
240
|
+
<head>
|
241
|
+
<meta charset="UTF-8">
|
242
|
+
<title>Simple Calculator</title>
|
243
|
+
</head>
|
244
|
+
<body>
|
245
|
+
<div>Calculator</div>
|
246
|
+
</body>
|
247
|
+
</html>
|
248
|
+
\`\`\`
|
249
|
+
</lobeArtifact>
|
250
|
+
|
251
|
+
This code provides a basic calculator that can perform addition, subtraction, multiplication, and division.`;
|
252
|
+
|
253
|
+
const output = processWithArtifact(input);
|
254
|
+
|
255
|
+
expect(output)
|
256
|
+
.toEqual(`Sure, I can help you with that! Here is a basic calculator built using HTML, CSS, and JavaScript.
|
257
|
+
|
258
|
+
<lobeThinking>A web calculator is a substantial piece of code and a good candidate for an artifact. It's self-contained, and it's likely that the user will want to modify it. This is a new request, so I will create a new artifact.</lobeThinking>
|
259
|
+
|
260
|
+
<lobeArtifact identifier="web-calculator" type="text/html" title="Web Calculator"><!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Simple Calculator</title></head><body> <div>Calculator</div></body></html></lobeArtifact>
|
261
|
+
|
262
|
+
This code provides a basic calculator that can perform addition, subtraction, multiplication, and division.`);
|
263
|
+
});
|
264
|
+
});
|
265
|
+
|
266
|
+
describe('outer code block removal', () => {
|
267
|
+
it('should remove outer html code block', () => {
|
268
|
+
const input = `\`\`\`html
|
269
|
+
<lobeThinking>Test thinking</lobeThinking>
|
270
|
+
<lobeArtifact identifier="test" type="text/html" title="Test">
|
271
|
+
<!DOCTYPE html>
|
272
|
+
<html>
|
273
|
+
<body>Test</body>
|
274
|
+
</html>
|
275
|
+
</lobeArtifact>
|
276
|
+
\`\`\``;
|
277
|
+
|
278
|
+
const output = processWithArtifact(input);
|
279
|
+
|
280
|
+
expect(output).toEqual(`<lobeThinking>Test thinking</lobeThinking>
|
281
|
+
|
282
|
+
<lobeArtifact identifier="test" type="text/html" title="Test"><!DOCTYPE html><html><body>Test</body></html></lobeArtifact>`);
|
283
|
+
});
|
284
|
+
|
285
|
+
it('should remove outer tool_code code block', () => {
|
286
|
+
const input = `\`\`\`tool_code
|
287
|
+
<lobeThinking>Test thinking</lobeThinking>
|
288
|
+
<lobeArtifact identifier="test" type="text/html" title="Test">
|
289
|
+
<!DOCTYPE html>
|
290
|
+
<html>
|
291
|
+
<body>Test</body>
|
292
|
+
</html>
|
293
|
+
</lobeArtifact>
|
294
|
+
\`\`\``;
|
295
|
+
|
296
|
+
const output = processWithArtifact(input);
|
297
|
+
|
298
|
+
expect(output).toEqual(`<lobeThinking>Test thinking</lobeThinking>
|
299
|
+
|
300
|
+
<lobeArtifact identifier="test" type="text/html" title="Test"><!DOCTYPE html><html><body>Test</body></html></lobeArtifact>`);
|
301
|
+
});
|
302
|
+
|
303
|
+
it('should handle input without outer code block', () => {
|
304
|
+
const input = `<lobeThinking>Test thinking</lobeThinking>
|
305
|
+
<lobeArtifact identifier="test" type="text/html" title="Test">
|
306
|
+
<!DOCTYPE html>
|
307
|
+
<html>
|
308
|
+
<body>Test</body>
|
309
|
+
</html>
|
310
|
+
</lobeArtifact>`;
|
311
|
+
|
312
|
+
const output = processWithArtifact(input);
|
313
|
+
|
314
|
+
expect(output).toEqual(`<lobeThinking>Test thinking</lobeThinking>
|
315
|
+
|
316
|
+
<lobeArtifact identifier="test" type="text/html" title="Test"><!DOCTYPE html><html><body>Test</body></html></lobeArtifact>`);
|
317
|
+
});
|
318
|
+
|
319
|
+
it('should handle code block with content before and after', () => {
|
320
|
+
const input = `Some text before
|
321
|
+
|
322
|
+
\`\`\`html
|
323
|
+
<lobeThinking>Test thinking</lobeThinking>
|
324
|
+
|
325
|
+
<lobeArtifact identifier="test" type="text/html" title="Test">
|
326
|
+
<!DOCTYPE html>
|
327
|
+
<html>
|
328
|
+
<body>Test</body>
|
329
|
+
</html>
|
330
|
+
</lobeArtifact>
|
331
|
+
\`\`\`
|
332
|
+
|
333
|
+
Some text after`;
|
334
|
+
|
335
|
+
const output = processWithArtifact(input);
|
336
|
+
|
337
|
+
expect(output).toEqual(`Some text before
|
338
|
+
|
339
|
+
<lobeThinking>Test thinking</lobeThinking>
|
340
|
+
|
341
|
+
<lobeArtifact identifier="test" type="text/html" title="Test"><!DOCTYPE html><html><body>Test</body></html></lobeArtifact>
|
342
|
+
|
343
|
+
Some text after`);
|
344
|
+
});
|
345
|
+
|
346
|
+
it('should handle code block with only lobeArtifact tag', () => {
|
347
|
+
const input = `\`\`\`html
|
348
|
+
<lobeArtifact identifier="test" type="text/html" title="Test">
|
349
|
+
<!DOCTYPE html>
|
350
|
+
<html>
|
351
|
+
<body>Test</body>
|
352
|
+
</html>
|
353
|
+
</lobeArtifact>
|
354
|
+
\`\`\``;
|
355
|
+
|
356
|
+
const output = processWithArtifact(input);
|
357
|
+
|
358
|
+
expect(output).toEqual(
|
359
|
+
`<lobeArtifact identifier="test" type="text/html" title="Test"><!DOCTYPE html><html><body>Test</body></html></lobeArtifact>`,
|
360
|
+
);
|
361
|
+
});
|
362
|
+
|
363
|
+
it('should handle code block with surrounding text and both lobeThinking and lobeArtifact', () => {
|
364
|
+
const input = `---
|
365
|
+
|
366
|
+
\`\`\`tool_code
|
367
|
+
<lobeThinking>The user reported a \`SyntaxError\` in the browser console, indicating a problem with the JavaScript code in the calculator artifact. The error message "Identifier 'display' has already been declared" suggests a variable naming conflict. I need to review the JavaScript code and correct the issue. This is an update to the existing "calculator-web-artifact" artifact.</lobeThinking>
|
368
|
+
<lobeArtifact identifier="calculator-web-artifact" type="text/html" title="Simple Calculator">
|
369
|
+
<!DOCTYPE html>
|
370
|
+
<html lang="en">
|
371
|
+
...
|
372
|
+
</html>
|
373
|
+
</lobeArtifact>
|
374
|
+
\`\`\`
|
375
|
+
I've updated the calculator artifact. The issue was a naming conflict with the \`display\` variable. I've renamed the input element's ID to \`calc-display\` and the JavaScript variable to \`displayElement\` to avoid the conflict. The calculator should now function correctly.
|
376
|
+
|
377
|
+
---`;
|
378
|
+
|
379
|
+
const output = processWithArtifact(input);
|
380
|
+
|
381
|
+
expect(output).toEqual(`---
|
382
|
+
|
383
|
+
<lobeThinking>The user reported a \`SyntaxError\` in the browser console, indicating a problem with the JavaScript code in the calculator artifact. The error message "Identifier 'display' has already been declared" suggests a variable naming conflict. I need to review the JavaScript code and correct the issue. This is an update to the existing "calculator-web-artifact" artifact.</lobeThinking>
|
384
|
+
|
385
|
+
<lobeArtifact identifier="calculator-web-artifact" type="text/html" title="Simple Calculator"><!DOCTYPE html><html lang="en">...</html></lobeArtifact>
|
386
|
+
|
387
|
+
I've updated the calculator artifact. The issue was a naming conflict with the \`display\` variable. I've renamed the input element's ID to \`calc-display\` and the JavaScript variable to \`displayElement\` to avoid the conflict. The calculator should now function correctly.
|
388
|
+
|
389
|
+
---`);
|
390
|
+
});
|
391
|
+
|
392
|
+
it('should handle code block before lobeThinking and lobeArtifact', () => {
|
393
|
+
const input = `
|
394
|
+
Okay, I'll create a temperature converter with the logic wrapped in an IIFE and event listeners attached in Javascript.
|
395
|
+
|
396
|
+
\`\`\`html
|
397
|
+
<!DOCTYPE html>
|
398
|
+
<html lang="en">
|
399
|
+
...
|
400
|
+
</html>
|
401
|
+
\`\`\`
|
402
|
+
|
403
|
+
<lobeThinking>This is a good candidate for an artifact. It's a self-contained HTML document with embedded JavaScript that provides a functional temperature converter. It's more than a simple code snippet and can be reused or modified. This is a new request, so I'll create a new artifact with the identifier "temperature-converter".</lobeThinking>
|
404
|
+
|
405
|
+
<lobeArtifact identifier="temperature-converter" type="text/html" title="Temperature Converter">
|
406
|
+
\`\`\`html
|
407
|
+
<!DOCTYPE html>
|
408
|
+
<html lang="en">
|
409
|
+
...
|
410
|
+
</html>
|
411
|
+
\`\`\`
|
412
|
+
</lobeArtifact>
|
413
|
+
This HTML document includes the temperature converter with the requested features: the logic is wrapped in an IIFE, and event listeners are attached in JavaScript.
|
414
|
+
`;
|
415
|
+
|
416
|
+
const output = processWithArtifact(input);
|
417
|
+
|
418
|
+
expect(output)
|
419
|
+
.toEqual(`Okay, I'll create a temperature converter with the logic wrapped in an IIFE and event listeners attached in Javascript.
|
420
|
+
|
421
|
+
\`\`\`html
|
422
|
+
<!DOCTYPE html>
|
423
|
+
<html lang="en">
|
424
|
+
...
|
425
|
+
</html>
|
426
|
+
\`\`\`
|
427
|
+
|
428
|
+
<lobeThinking>This is a good candidate for an artifact. It's a self-contained HTML document with embedded JavaScript that provides a functional temperature converter. It's more than a simple code snippet and can be reused or modified. This is a new request, so I'll create a new artifact with the identifier "temperature-converter".</lobeThinking>
|
429
|
+
|
430
|
+
<lobeArtifact identifier="temperature-converter" type="text/html" title="Temperature Converter"><!DOCTYPE html><html lang="en">...</html></lobeArtifact>
|
431
|
+
|
432
|
+
This HTML document includes the temperature converter with the requested features: the logic is wrapped in an IIFE, and event listeners are attached in JavaScript.`);
|
433
|
+
});
|
150
434
|
});
|
@@ -4,24 +4,55 @@ import { ARTIFACT_TAG_REGEX, ARTIFACT_THINKING_TAG_REGEX } from '@/const/plugin'
|
|
4
4
|
* Replace all line breaks in the matched `lobeArtifact` tag with an empty string
|
5
5
|
*/
|
6
6
|
export const processWithArtifact = (input: string = '') => {
|
7
|
-
|
8
|
-
|
7
|
+
// First remove outer fenced code block if it exists
|
8
|
+
let output = input.replace(
|
9
|
+
/^([\S\s]*?)\s*```[^\n]*\n((?:<lobeThinking>[\S\s]*?<\/lobeThinking>\s*\n\s*)?<lobeArtifact[\S\s]*?<\/lobeArtifact>\s*)\n```\s*([\S\s]*?)$/,
|
10
|
+
(_, before = '', content, after = '') => {
|
11
|
+
return [before.trim(), content.trim(), after.trim()].filter(Boolean).join('\n\n');
|
12
|
+
},
|
13
|
+
);
|
14
|
+
|
15
|
+
const thinkMatch = ARTIFACT_THINKING_TAG_REGEX.exec(output);
|
9
16
|
|
10
17
|
// If the input contains the `lobeThinking` tag, replace all line breaks with an empty string
|
11
|
-
if (thinkMatch)
|
12
|
-
output =
|
18
|
+
if (thinkMatch) {
|
19
|
+
output = output.replace(ARTIFACT_THINKING_TAG_REGEX, (match) =>
|
13
20
|
match.replaceAll(/\r?\n|\r/g, ''),
|
14
21
|
);
|
22
|
+
}
|
15
23
|
|
16
|
-
|
24
|
+
// Add empty line between lobeThinking and lobeArtifact if they are adjacent
|
25
|
+
output = output.replace(/(<\/lobeThinking>)\r?\n(<lobeArtifact)/, '$1\n\n$2');
|
26
|
+
|
27
|
+
// Remove fenced code block between lobeArtifact and HTML content
|
28
|
+
output = output.replace(
|
29
|
+
/(<lobeArtifact[^>]*>)\s*```[^\n]*\n([\S\s]*?)(```\n)?(<\/lobeArtifact>)/,
|
30
|
+
(_, start, content, __, end) => {
|
31
|
+
if (content.trim().startsWith('<!DOCTYPE html') || content.trim().startsWith('<html')) {
|
32
|
+
return start + content.trim() + end;
|
33
|
+
}
|
34
|
+
return start + content + (__ || '') + end;
|
35
|
+
},
|
36
|
+
);
|
37
|
+
|
38
|
+
// Keep existing code blocks that are not part of lobeArtifact
|
39
|
+
output = output.replace(
|
40
|
+
/^([\S\s]*?)(<lobeThinking>[\S\s]*?<\/lobeThinking>\s*\n\s*<lobeArtifact[\S\s]*?<\/lobeArtifact>)([\S\s]*?)$/,
|
41
|
+
(_, before, content, after) => {
|
42
|
+
return [before.trim(), content.trim(), after.trim()].filter(Boolean).join('\n\n');
|
43
|
+
},
|
44
|
+
);
|
45
|
+
|
46
|
+
const match = ARTIFACT_TAG_REGEX.exec(output);
|
17
47
|
// If the input contains the `lobeArtifact` tag, replace all line breaks with an empty string
|
18
|
-
if (match)
|
19
|
-
|
48
|
+
if (match) {
|
49
|
+
output = output.replace(ARTIFACT_TAG_REGEX, (match) => match.replaceAll(/\r?\n|\r/g, ''));
|
50
|
+
}
|
20
51
|
|
21
52
|
// if not match, check if it's start with <lobeArtifact but not closed
|
22
53
|
const regex = /<lobeArtifact\b(?:(?!\/?>)[\S\s])*$/;
|
23
54
|
if (regex.test(output)) {
|
24
|
-
|
55
|
+
output = output.replace(regex, '<lobeArtifact>');
|
25
56
|
}
|
26
57
|
|
27
58
|
return output;
|
package/src/features/Conversation/components/MarkdownElements/LobeArtifact/rehypePlugin.test.ts
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
2
|
+
|
3
|
+
import rehypePlugin from './rehypePlugin';
|
4
|
+
|
5
|
+
describe('rehypePlugin', () => {
|
6
|
+
it('should transform <lobeArtifact> tags with attributes', () => {
|
7
|
+
const tree = {
|
8
|
+
type: 'root',
|
9
|
+
children: [
|
10
|
+
{
|
11
|
+
type: 'element',
|
12
|
+
tagName: 'p',
|
13
|
+
children: [
|
14
|
+
{
|
15
|
+
type: 'raw',
|
16
|
+
value: '<lobeArtifact identifier="test-id" type="image/svg+xml" title="Test Title">',
|
17
|
+
},
|
18
|
+
{ type: 'text', value: 'Artifact content' },
|
19
|
+
{ type: 'raw', value: '</lobeArtifact>' },
|
20
|
+
],
|
21
|
+
},
|
22
|
+
],
|
23
|
+
};
|
24
|
+
|
25
|
+
const expectedTree = {
|
26
|
+
type: 'root',
|
27
|
+
children: [
|
28
|
+
{
|
29
|
+
type: 'element',
|
30
|
+
tagName: 'lobeArtifact',
|
31
|
+
properties: {
|
32
|
+
identifier: 'test-id',
|
33
|
+
type: 'image/svg+xml',
|
34
|
+
title: 'Test Title',
|
35
|
+
},
|
36
|
+
children: [{ type: 'text', value: 'Artifact content' }],
|
37
|
+
},
|
38
|
+
],
|
39
|
+
};
|
40
|
+
|
41
|
+
const plugin = rehypePlugin();
|
42
|
+
plugin(tree);
|
43
|
+
|
44
|
+
expect(tree).toEqual(expectedTree);
|
45
|
+
});
|
46
|
+
|
47
|
+
it('should handle mixed content with thinking tags and plain text', () => {
|
48
|
+
const tree = {
|
49
|
+
type: 'root',
|
50
|
+
children: [
|
51
|
+
{
|
52
|
+
type: 'element',
|
53
|
+
tagName: 'p',
|
54
|
+
children: [{ type: 'text', value: 'Initial plain text paragraph' }],
|
55
|
+
},
|
56
|
+
{
|
57
|
+
type: 'element',
|
58
|
+
tagName: 'p',
|
59
|
+
children: [
|
60
|
+
{ type: 'raw', value: '<lobeThinking>' },
|
61
|
+
{ type: 'text', value: 'AI is thinking...' },
|
62
|
+
{ type: 'raw', value: '</lobeThinking>' },
|
63
|
+
],
|
64
|
+
},
|
65
|
+
{
|
66
|
+
type: 'element',
|
67
|
+
tagName: 'p',
|
68
|
+
children: [
|
69
|
+
{
|
70
|
+
type: 'raw',
|
71
|
+
value: '<lobeArtifact identifier="test-id" type="image/svg+xml" title="Test Title">',
|
72
|
+
},
|
73
|
+
{ type: 'text', value: 'Artifact content' },
|
74
|
+
{ type: 'raw', value: '</lobeArtifact>' },
|
75
|
+
],
|
76
|
+
},
|
77
|
+
{
|
78
|
+
type: 'element',
|
79
|
+
tagName: 'p',
|
80
|
+
children: [{ type: 'text', value: 'Final plain text paragraph' }],
|
81
|
+
},
|
82
|
+
],
|
83
|
+
};
|
84
|
+
|
85
|
+
const expectedTree = {
|
86
|
+
type: 'root',
|
87
|
+
children: [
|
88
|
+
{
|
89
|
+
type: 'element',
|
90
|
+
tagName: 'p',
|
91
|
+
children: [{ type: 'text', value: 'Initial plain text paragraph' }],
|
92
|
+
},
|
93
|
+
{
|
94
|
+
type: 'element',
|
95
|
+
tagName: 'p',
|
96
|
+
children: [
|
97
|
+
{ type: 'raw', value: '<lobeThinking>' },
|
98
|
+
{ type: 'text', value: 'AI is thinking...' },
|
99
|
+
{ type: 'raw', value: '</lobeThinking>' },
|
100
|
+
],
|
101
|
+
},
|
102
|
+
{
|
103
|
+
type: 'element',
|
104
|
+
tagName: 'lobeArtifact',
|
105
|
+
properties: {
|
106
|
+
identifier: 'test-id',
|
107
|
+
type: 'image/svg+xml',
|
108
|
+
title: 'Test Title',
|
109
|
+
},
|
110
|
+
children: [{ type: 'text', value: 'Artifact content' }],
|
111
|
+
},
|
112
|
+
{
|
113
|
+
type: 'element',
|
114
|
+
tagName: 'p',
|
115
|
+
children: [{ type: 'text', value: 'Final plain text paragraph' }],
|
116
|
+
},
|
117
|
+
],
|
118
|
+
};
|
119
|
+
|
120
|
+
const plugin = rehypePlugin();
|
121
|
+
plugin(tree);
|
122
|
+
|
123
|
+
expect(tree).toEqual(expectedTree);
|
124
|
+
});
|
125
|
+
});
|
@@ -0,0 +1,33 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { RefreshCw } from 'lucide-react';
|
4
|
+
import { memo } from 'react';
|
5
|
+
|
6
|
+
import Header from '../../features/Header';
|
7
|
+
import Table from '../../features/Table';
|
8
|
+
import { useCachePanelContext } from '../cacheProvider';
|
9
|
+
|
10
|
+
const DataTable = memo(() => {
|
11
|
+
const { entries, isLoading, refreshData } = useCachePanelContext();
|
12
|
+
return (
|
13
|
+
<>
|
14
|
+
<Header
|
15
|
+
actions={[
|
16
|
+
{
|
17
|
+
icon: RefreshCw,
|
18
|
+
onClick: () => refreshData(),
|
19
|
+
title: 'Refresh',
|
20
|
+
},
|
21
|
+
]}
|
22
|
+
title="Cache Entries"
|
23
|
+
/>
|
24
|
+
<Table
|
25
|
+
columns={['url', 'headers.content-type', 'body', 'kind', 'tags', 'revalidate', 'timestamp']}
|
26
|
+
dataSource={entries}
|
27
|
+
loading={isLoading}
|
28
|
+
/>
|
29
|
+
</>
|
30
|
+
);
|
31
|
+
});
|
32
|
+
|
33
|
+
export default DataTable;
|
@@ -0,0 +1,64 @@
|
|
1
|
+
'use client';
|
2
|
+
|
3
|
+
import { usePathname } from 'next/navigation';
|
4
|
+
import {
|
5
|
+
PropsWithChildren,
|
6
|
+
createContext,
|
7
|
+
useContext,
|
8
|
+
useEffect,
|
9
|
+
useState,
|
10
|
+
useTransition,
|
11
|
+
} from 'react';
|
12
|
+
|
13
|
+
import { getCacheFiles } from './getCacheEntries';
|
14
|
+
import type { NextCacheFileData } from './schema';
|
15
|
+
|
16
|
+
interface CachePanelContextProps {
|
17
|
+
entries: NextCacheFileData[];
|
18
|
+
isLoading: boolean;
|
19
|
+
refreshData: () => void;
|
20
|
+
setEntries: (value: NextCacheFileData[]) => void;
|
21
|
+
}
|
22
|
+
|
23
|
+
const CachePanelContext = createContext<CachePanelContextProps>({
|
24
|
+
entries: [],
|
25
|
+
isLoading: false,
|
26
|
+
refreshData: () => {},
|
27
|
+
setEntries: () => {},
|
28
|
+
});
|
29
|
+
|
30
|
+
export const useCachePanelContext = () => useContext(CachePanelContext);
|
31
|
+
|
32
|
+
export const CachePanelContextProvider = (
|
33
|
+
props: PropsWithChildren<{
|
34
|
+
entries: NextCacheFileData[];
|
35
|
+
}>,
|
36
|
+
) => {
|
37
|
+
const [isLoading, startTransition] = useTransition();
|
38
|
+
const [entries, setEntries] = useState(props.entries);
|
39
|
+
const pathname = usePathname();
|
40
|
+
|
41
|
+
const refreshData = () => {
|
42
|
+
startTransition(async () => {
|
43
|
+
const files = await getCacheFiles();
|
44
|
+
setEntries(files ?? []);
|
45
|
+
});
|
46
|
+
};
|
47
|
+
|
48
|
+
useEffect(() => {
|
49
|
+
refreshData();
|
50
|
+
}, [pathname]);
|
51
|
+
|
52
|
+
return (
|
53
|
+
<CachePanelContext.Provider
|
54
|
+
value={{
|
55
|
+
entries,
|
56
|
+
isLoading,
|
57
|
+
refreshData,
|
58
|
+
setEntries,
|
59
|
+
}}
|
60
|
+
>
|
61
|
+
{props.children}
|
62
|
+
</CachePanelContext.Provider>
|
63
|
+
);
|
64
|
+
};
|
@@ -0,0 +1,52 @@
|
|
1
|
+
'use server';
|
2
|
+
|
3
|
+
import { existsSync, promises } from 'node:fs';
|
4
|
+
import pMap from 'p-map';
|
5
|
+
import { ZodError } from 'zod';
|
6
|
+
|
7
|
+
import { type NextCacheFileData, nextCacheFileSchema } from './schema';
|
8
|
+
|
9
|
+
const cachePath = '.next/cache/fetch-cache';
|
10
|
+
|
11
|
+
export const getCacheFiles = async (): Promise<NextCacheFileData[]> => {
|
12
|
+
if (!existsSync(cachePath)) {
|
13
|
+
return [];
|
14
|
+
}
|
15
|
+
const files = await promises.readdir(cachePath);
|
16
|
+
let result: NextCacheFileData[] = (await pMap(files, async (file) => {
|
17
|
+
// ignore tags-manifest file
|
18
|
+
if (/manifest/.test(file)) return false;
|
19
|
+
try {
|
20
|
+
const fileContent = await promises.readFile(`${cachePath}/${file}`).catch((err) => {
|
21
|
+
throw new Error(`Error reading file ${file}`, {
|
22
|
+
cause: err,
|
23
|
+
});
|
24
|
+
});
|
25
|
+
|
26
|
+
const fileStats = await promises.stat(`${cachePath}/${file}`).catch((err) => {
|
27
|
+
throw new Error(`Error reading file ${file}`, {
|
28
|
+
cause: err,
|
29
|
+
});
|
30
|
+
});
|
31
|
+
|
32
|
+
const jsonData = JSON.parse(fileContent.toString());
|
33
|
+
|
34
|
+
return nextCacheFileSchema.parse({
|
35
|
+
...jsonData,
|
36
|
+
id: file,
|
37
|
+
timestamp: new Date(fileStats.birthtime),
|
38
|
+
});
|
39
|
+
} catch (error) {
|
40
|
+
if (error instanceof ZodError) {
|
41
|
+
const issues = error.issues;
|
42
|
+
console.error(`File ${file} do not match the schema`, issues);
|
43
|
+
}
|
44
|
+
console.error(`Error parsing ${file}`);
|
45
|
+
return false;
|
46
|
+
}
|
47
|
+
})) as NextCacheFileData[];
|
48
|
+
|
49
|
+
result = result.filter(Boolean) as NextCacheFileData[];
|
50
|
+
|
51
|
+
return result.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
52
|
+
};
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import { Empty } from 'antd';
|
2
|
+
import { Center } from 'react-layout-kit';
|
3
|
+
|
4
|
+
import DataTable from './DataTable';
|
5
|
+
import { CachePanelContextProvider } from './cacheProvider';
|
6
|
+
import { getCacheFiles } from './getCacheEntries';
|
7
|
+
|
8
|
+
const CacheViewer = async () => {
|
9
|
+
const files = await getCacheFiles();
|
10
|
+
|
11
|
+
if (!files || files.length === 0)
|
12
|
+
return (
|
13
|
+
<Center height={'80%'}>
|
14
|
+
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
15
|
+
</Center>
|
16
|
+
);
|
17
|
+
|
18
|
+
return (
|
19
|
+
<CachePanelContextProvider entries={files}>
|
20
|
+
<DataTable />
|
21
|
+
</CachePanelContextProvider>
|
22
|
+
);
|
23
|
+
};
|
24
|
+
|
25
|
+
export default CacheViewer;
|