@lobehub/chat 1.18.1 → 1.19.0
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.
Potentially problematic release.
This version of @lobehub/chat might be problematic. Click here for more details.
- package/CHANGELOG.md +52 -0
- package/Dockerfile +2 -0
- package/Dockerfile.database +2 -0
- package/locales/ar/chat.json +6 -0
- package/locales/ar/error.json +1 -0
- package/locales/ar/modelProvider.json +7 -0
- package/locales/ar/portal.json +16 -0
- package/locales/bg-BG/chat.json +6 -0
- package/locales/bg-BG/error.json +1 -0
- package/locales/bg-BG/modelProvider.json +7 -0
- package/locales/bg-BG/portal.json +16 -0
- package/locales/de-DE/chat.json +6 -0
- package/locales/de-DE/error.json +1 -0
- package/locales/de-DE/modelProvider.json +7 -0
- package/locales/de-DE/portal.json +16 -0
- package/locales/en-US/chat.json +6 -0
- package/locales/en-US/error.json +1 -0
- package/locales/en-US/modelProvider.json +7 -0
- package/locales/en-US/portal.json +16 -0
- package/locales/es-ES/chat.json +6 -0
- package/locales/es-ES/error.json +1 -0
- package/locales/es-ES/modelProvider.json +7 -0
- package/locales/es-ES/portal.json +16 -0
- package/locales/fr-FR/chat.json +6 -0
- package/locales/fr-FR/error.json +1 -0
- package/locales/fr-FR/modelProvider.json +7 -0
- package/locales/fr-FR/portal.json +16 -0
- package/locales/it-IT/chat.json +6 -0
- package/locales/it-IT/error.json +1 -0
- package/locales/it-IT/modelProvider.json +7 -0
- package/locales/it-IT/portal.json +16 -0
- package/locales/ja-JP/chat.json +6 -0
- package/locales/ja-JP/error.json +1 -0
- package/locales/ja-JP/modelProvider.json +7 -0
- package/locales/ja-JP/portal.json +16 -0
- package/locales/ko-KR/chat.json +6 -0
- package/locales/ko-KR/error.json +1 -0
- package/locales/ko-KR/modelProvider.json +7 -0
- package/locales/ko-KR/portal.json +16 -0
- package/locales/nl-NL/chat.json +6 -0
- package/locales/nl-NL/error.json +1 -0
- package/locales/nl-NL/modelProvider.json +7 -0
- package/locales/nl-NL/portal.json +16 -0
- package/locales/pl-PL/chat.json +6 -0
- package/locales/pl-PL/error.json +1 -0
- package/locales/pl-PL/modelProvider.json +7 -0
- package/locales/pl-PL/portal.json +16 -0
- package/locales/pt-BR/chat.json +6 -0
- package/locales/pt-BR/error.json +1 -0
- package/locales/pt-BR/modelProvider.json +7 -0
- package/locales/pt-BR/portal.json +16 -0
- package/locales/ru-RU/chat.json +6 -0
- package/locales/ru-RU/error.json +1 -0
- package/locales/ru-RU/modelProvider.json +7 -0
- package/locales/ru-RU/portal.json +16 -0
- package/locales/tr-TR/chat.json +6 -0
- package/locales/tr-TR/error.json +1 -0
- package/locales/tr-TR/modelProvider.json +7 -0
- package/locales/tr-TR/portal.json +16 -0
- package/locales/vi-VN/chat.json +6 -0
- package/locales/vi-VN/error.json +1 -0
- package/locales/vi-VN/modelProvider.json +7 -0
- package/locales/vi-VN/portal.json +16 -0
- package/locales/zh-CN/chat.json +6 -0
- package/locales/zh-CN/error.json +2 -1
- package/locales/zh-CN/modelProvider.json +7 -0
- package/locales/zh-CN/portal.json +17 -1
- package/locales/zh-TW/chat.json +6 -0
- package/locales/zh-TW/error.json +1 -0
- package/locales/zh-TW/modelProvider.json +7 -0
- package/locales/zh-TW/portal.json +16 -0
- package/package.json +4 -1
- package/src/app/(main)/chat/(workspace)/@portal/Artifacts/Body/Renderer/HTML.tsx +25 -0
- package/src/app/(main)/chat/(workspace)/@portal/Artifacts/Body/Renderer/React.tsx +30 -0
- package/src/app/(main)/chat/(workspace)/@portal/Artifacts/Body/Renderer/SVG.tsx +114 -0
- package/src/app/(main)/chat/(workspace)/@portal/Artifacts/Body/Renderer/index.tsx +25 -0
- package/src/app/(main)/chat/(workspace)/@portal/Artifacts/Body/index.tsx +79 -0
- package/src/app/(main)/chat/(workspace)/@portal/Artifacts/Header.tsx +69 -0
- package/src/app/(main)/chat/(workspace)/@portal/Artifacts/index.ts +10 -0
- package/src/app/(main)/chat/(workspace)/@portal/Artifacts/useEnable.ts +4 -0
- package/src/app/(main)/chat/(workspace)/@portal/FilePreview/index.ts +2 -1
- package/src/app/(main)/chat/(workspace)/@portal/Home/Body/{Artifacts → Plugins}/index.tsx +1 -1
- package/src/app/(main)/chat/(workspace)/@portal/Home/Body/index.tsx +2 -2
- package/src/app/(main)/chat/(workspace)/@portal/MessageDetail/index.ts +2 -1
- package/src/app/(main)/chat/(workspace)/@portal/Plugins/Body/ToolRender.tsx +1 -1
- package/src/app/(main)/chat/(workspace)/@portal/Plugins/Body/index.tsx +1 -1
- package/src/app/(main)/chat/(workspace)/@portal/Plugins/Footer.tsx +1 -1
- package/src/app/(main)/chat/(workspace)/@portal/Plugins/index.ts +2 -1
- package/src/app/(main)/chat/(workspace)/@portal/Plugins/useEnable.ts +1 -1
- package/src/app/(main)/chat/(workspace)/@portal/_layout/Desktop.tsx +2 -4
- package/src/app/(main)/chat/(workspace)/@portal/features/Body.tsx +27 -0
- package/src/app/(main)/chat/(workspace)/@portal/router.tsx +3 -1
- package/src/app/(main)/chat/(workspace)/@portal/type.ts +7 -0
- package/src/app/(main)/chat/(workspace)/_layout/Desktop/Portal.tsx +3 -2
- package/src/app/(main)/discover/(detail)/assistant/[slug]/features/InfoSidebar/index.tsx +5 -8
- package/src/app/(main)/discover/(detail)/features/Back.tsx +0 -5
- package/src/app/(main)/discover/(detail)/features/DetailLayout.tsx +2 -34
- package/src/app/(main)/discover/(detail)/model/[...slugs]/features/InfoSidebar/index.tsx +3 -6
- package/src/app/(main)/discover/(detail)/model/[...slugs]/features/ProviderList/ProviderItem.tsx +14 -15
- package/src/app/(main)/discover/(detail)/plugin/[slug]/features/InfoSidebar/index.tsx +3 -6
- package/src/app/(main)/discover/(detail)/provider/[slug]/features/InfoSidebar/index.tsx +3 -6
- package/src/app/(main)/discover/(detail)/provider/[slug]/features/ModelList/ModelItem.tsx +14 -11
- package/src/app/(main)/discover/(list)/(home)/features/AssistantList.tsx +5 -11
- package/src/app/(main)/discover/(list)/(home)/features/ModelList.tsx +3 -3
- package/src/app/(main)/discover/(list)/(home)/features/PluginList.tsx +3 -6
- package/src/app/(main)/discover/(list)/assistants/features/List.tsx +7 -16
- package/src/app/(main)/discover/(list)/models/features/List.tsx +5 -11
- package/src/app/(main)/discover/(list)/plugins/features/List.tsx +7 -16
- package/src/app/(main)/discover/(list)/providers/features/List.tsx +5 -11
- package/src/app/(main)/discover/_layout/Desktop/index.tsx +3 -0
- package/src/app/(main)/discover/_layout/Mobile/index.tsx +8 -1
- package/src/app/(main)/settings/llm/ProviderList/Github/index.tsx +53 -0
- package/src/app/(main)/settings/llm/ProviderList/providers.tsx +6 -1
- package/src/app/api/chat/agentRuntime.ts +14 -0
- package/src/app/layout.tsx +2 -1
- package/src/components/NProgress/index.tsx +12 -0
- package/src/components/SidebarHeader/index.tsx +1 -1
- package/src/config/llm.ts +14 -0
- package/src/config/modelProviders/ai21.ts +37 -0
- package/src/config/modelProviders/anthropic.ts +4 -0
- package/src/config/modelProviders/github.ts +209 -0
- package/src/config/modelProviders/index.ts +8 -0
- package/src/const/layoutTokens.ts +1 -1
- package/src/const/plugin.test.ts +80 -0
- package/src/const/plugin.ts +12 -0
- package/src/const/settings/llm.ts +10 -0
- package/src/features/Conversation/Error/APIKeyForm/index.tsx +4 -0
- package/src/features/Conversation/Messages/Tool/Inspector/index.tsx +1 -1
- package/src/features/Conversation/Messages/Tool/index.tsx +1 -1
- package/src/features/Conversation/components/ChatItem/index.tsx +24 -2
- package/src/features/Conversation/components/ChatItem/utils.test.ts +150 -0
- package/src/features/Conversation/components/ChatItem/utils.ts +28 -0
- package/src/features/Conversation/components/InboxWelcome/AgentsSuggest.tsx +3 -6
- package/src/features/Conversation/components/MarkdownElements/LobeArtifact/Render/Icon.tsx +96 -0
- package/src/features/Conversation/components/MarkdownElements/LobeArtifact/Render/index.tsx +129 -0
- package/src/features/Conversation/components/MarkdownElements/LobeArtifact/index.ts +10 -0
- package/src/features/Conversation/components/MarkdownElements/LobeArtifact/rehypePlugin.ts +74 -0
- package/src/features/Conversation/components/MarkdownElements/LobeThinking/Render.tsx +86 -0
- package/src/features/Conversation/components/MarkdownElements/LobeThinking/index.ts +12 -0
- package/src/features/Conversation/components/MarkdownElements/LobeThinking/rehypePlugin.test.ts +124 -0
- package/src/features/Conversation/components/MarkdownElements/LobeThinking/rehypePlugin.ts +51 -0
- package/src/features/Conversation/components/MarkdownElements/index.ts +4 -0
- package/src/features/Conversation/components/MarkdownElements/type.ts +7 -0
- package/src/hooks/useInterceptingRoutes.ts +1 -16
- package/src/libs/agent-runtime/AgentRuntime.ts +14 -0
- package/src/libs/agent-runtime/ai21/index.test.ts +255 -0
- package/src/libs/agent-runtime/ai21/index.ts +18 -0
- package/src/libs/agent-runtime/error.ts +2 -0
- package/src/libs/agent-runtime/github/index.test.ts +246 -0
- package/src/libs/agent-runtime/github/index.ts +15 -0
- package/src/libs/agent-runtime/openrouter/__snapshots__/index.test.ts.snap +2215 -28
- package/src/libs/agent-runtime/openrouter/fixtures/models.json +3345 -37
- package/src/libs/agent-runtime/openrouter/index.ts +2 -1
- package/src/libs/agent-runtime/types/type.ts +2 -0
- package/src/locales/default/chat.ts +7 -0
- package/src/locales/default/error.ts +3 -0
- package/src/locales/default/modelProvider.ts +7 -0
- package/src/locales/default/portal.ts +17 -1
- package/src/server/globalConfig/index.ts +14 -0
- package/src/store/chat/slices/message/selectors.ts +30 -28
- package/src/store/chat/slices/portal/action.ts +15 -2
- package/src/store/chat/slices/portal/initialState.ts +11 -0
- package/src/store/chat/slices/portal/selectors.test.ts +29 -7
- package/src/store/chat/slices/portal/selectors.ts +56 -12
- package/src/styles/loading.ts +28 -0
- package/src/tools/artifacts/index.ts +13 -0
- package/src/tools/artifacts/systemRole.ts +338 -0
- package/src/tools/index.ts +6 -0
- package/src/types/user/settings/keyVaults.ts +2 -0
- package/src/utils/clipboard.ts +53 -0
- package/src/app/@modal/(.)discover/assistant/[slug]/page.tsx +0 -1
- package/src/app/@modal/(.)discover/layout.tsx +0 -29
- package/src/app/@modal/(.)discover/loading.tsx +0 -3
- package/src/app/@modal/(.)discover/model/[...slugs]/page.tsx +0 -1
- package/src/app/@modal/(.)discover/plugin/[slug]/page.tsx +0 -1
- package/src/app/@modal/(.)discover/provider/[slug]/page.tsx +0 -1
- package/src/app/redirect/page.tsx +0 -15
- package/src/components/InterceptingLink/index.tsx +0 -27
- /package/src/app/(main)/chat/(workspace)/@portal/Home/Body/{Artifacts → Plugins}/ArtifactList/Item/index.tsx +0 -0
- /package/src/app/(main)/chat/(workspace)/@portal/Home/Body/{Artifacts → Plugins}/ArtifactList/Item/style.ts +0 -0
- /package/src/app/(main)/chat/(workspace)/@portal/Home/Body/{Artifacts → Plugins}/ArtifactList/index.tsx +0 -0
@@ -51,6 +51,10 @@ const APIKeyForm = memo<APIKeyFormProps>(({ id, provider }) => {
|
|
51
51
|
return 'sk-********************************';
|
52
52
|
}
|
53
53
|
|
54
|
+
case ModelProvider.Github: {
|
55
|
+
return 'ghp_*****************************';
|
56
|
+
}
|
57
|
+
|
54
58
|
default: {
|
55
59
|
return '*********************************';
|
56
60
|
}
|
@@ -52,7 +52,7 @@ const Inspector = memo<InspectorProps>(
|
|
52
52
|
const { styles } = useStyles();
|
53
53
|
const [open, setOpen] = useState(false);
|
54
54
|
const [isMessageToolUIOpen, openToolUI, togglePortal] = useChatStore((s) => [
|
55
|
-
chatPortalSelectors.
|
55
|
+
chatPortalSelectors.isPluginUIOpen(id)(s),
|
56
56
|
s.openToolUI,
|
57
57
|
s.togglePortal,
|
58
58
|
]);
|
@@ -17,7 +17,7 @@ import Inspector from './Inspector';
|
|
17
17
|
const Message = memo<ChatMessage>(({ id, content, pluginState, plugin }) => {
|
18
18
|
const [loading, isMessageToolUIOpen] = useChatStore((s) => [
|
19
19
|
chatSelectors.isPluginApiInvoking(id)(s),
|
20
|
-
chatPortalSelectors.
|
20
|
+
chatPortalSelectors.isPluginUIOpen(id)(s),
|
21
21
|
]);
|
22
22
|
const { direction } = useContext(ConfigProvider.ConfigContext);
|
23
23
|
const { t } = useTranslation('plugin');
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { ChatItem } from '@lobehub/ui';
|
2
2
|
import { createStyles } from 'antd-style';
|
3
3
|
import isEqual from 'fast-deep-equal';
|
4
|
-
import { ReactNode, memo, useCallback } from 'react';
|
4
|
+
import { ReactNode, memo, useCallback, useMemo } from 'react';
|
5
5
|
import { useTranslation } from 'react-i18next';
|
6
6
|
|
7
7
|
import { useAgentStore } from '@/store/agent';
|
@@ -22,8 +22,12 @@ import {
|
|
22
22
|
renderMessages,
|
23
23
|
useAvatarsClick,
|
24
24
|
} from '../../Messages';
|
25
|
+
import { markdownElements } from '../MarkdownElements';
|
25
26
|
import ActionsBar from './ActionsBar';
|
26
27
|
import HistoryDivider from './HistoryDivider';
|
28
|
+
import { processWithArtifact } from './utils';
|
29
|
+
|
30
|
+
const rehypePlugins = markdownElements.map((element) => element.rehypePlugin);
|
27
31
|
|
28
32
|
const useStyles = createStyles(({ css, prefixCls }) => ({
|
29
33
|
loading: css`
|
@@ -143,6 +147,22 @@ const Item = memo<ChatListItemProps>(({ index, id }) => {
|
|
143
147
|
);
|
144
148
|
});
|
145
149
|
|
150
|
+
// remove line breaks in artifact tag to make the ast transform easier
|
151
|
+
const message =
|
152
|
+
!editing && item?.role === 'assistant' ? processWithArtifact(item?.content) : item?.content;
|
153
|
+
|
154
|
+
const components = useMemo(
|
155
|
+
() =>
|
156
|
+
Object.fromEntries(
|
157
|
+
markdownElements.map((element) => {
|
158
|
+
const Component = element.Component;
|
159
|
+
|
160
|
+
return [element.tag, (props: any) => <Component {...props} id={id} />];
|
161
|
+
}),
|
162
|
+
),
|
163
|
+
[id],
|
164
|
+
);
|
165
|
+
|
146
166
|
return (
|
147
167
|
item && (
|
148
168
|
<>
|
@@ -165,9 +185,11 @@ const Item = memo<ChatListItemProps>(({ index, id }) => {
|
|
165
185
|
fontSize={fontSize}
|
166
186
|
loading={isProcessing}
|
167
187
|
markdownProps={{
|
188
|
+
components,
|
168
189
|
customRender: markdownCustomRender,
|
190
|
+
rehypePlugins,
|
169
191
|
}}
|
170
|
-
message={
|
192
|
+
message={message}
|
171
193
|
messageExtra={<MessageExtra data={item} />}
|
172
194
|
onAvatarClick={onAvatarsClick?.(item.role)}
|
173
195
|
onChange={(value) => updateMessageContent(item.id, value)}
|
@@ -0,0 +1,150 @@
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
2
|
+
|
3
|
+
import { processWithArtifact } from './utils';
|
4
|
+
|
5
|
+
describe('processWithArtifact', () => {
|
6
|
+
it('should removeLineBreaks with closed tag', () => {
|
7
|
+
const input = `好的
|
8
|
+
|
9
|
+
<lobeArtifact identifier="sleep-interpretation-card" type="image/svg+xml" title="睡觉的新解释">
|
10
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 600">
|
11
|
+
<defs>
|
12
|
+
<style>
|
13
|
+
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;700&display=swap');
|
14
|
+
</style>
|
15
|
+
</defs>
|
16
|
+
<!-- 背景 -->
|
17
|
+
<rect width="400" height="600" fill="#F0EAD6"/>
|
18
|
+
<!-- 总结 -->
|
19
|
+
<text x="200" y="500" font-family="'Noto Serif SC', serif" font-size="20" text-anchor="middle" fill="#8B4513">睡觉:生产力的假死,创造力的重生。</text>
|
20
|
+
</svg>
|
21
|
+
</lobeArtifact>`;
|
22
|
+
|
23
|
+
const output = processWithArtifact(input);
|
24
|
+
|
25
|
+
expect(output).toEqual(`好的
|
26
|
+
|
27
|
+
<lobeArtifact identifier="sleep-interpretation-card" type="image/svg+xml" title="睡觉的新解释"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 600"><defs><style>@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;700&display=swap');</style></defs><!-- 背景 --><rect width="400" height="600" fill="#F0EAD6"/><!-- 总结 --><text x="200" y="500" font-family="'Noto Serif SC', serif" font-size="20" text-anchor="middle" fill="#8B4513">睡觉:生产力的假死,创造力的重生。</text></svg></lobeArtifact>`);
|
28
|
+
});
|
29
|
+
|
30
|
+
it('should removeLineBreaks with open tag', () => {
|
31
|
+
const input = `好的
|
32
|
+
|
33
|
+
<lobeArtifact identifier="ai-interpretation-card" type="image/svg+xml" title="人工智能新解卡片">
|
34
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 600">
|
35
|
+
<defs>
|
36
|
+
<style>
|
37
|
+
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;700&display=swap');
|
38
|
+
</style>
|
39
|
+
</defs>
|
40
|
+
`;
|
41
|
+
|
42
|
+
const output = processWithArtifact(input);
|
43
|
+
|
44
|
+
expect(output).toEqual(`好的
|
45
|
+
|
46
|
+
<lobeArtifact identifier="ai-interpretation-card" type="image/svg+xml" title="人工智能新解卡片"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 600"> <defs> <style> @import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;700&display=swap'); </style> </defs>`);
|
47
|
+
});
|
48
|
+
it('should not throw error with empty', () => {
|
49
|
+
const input = '';
|
50
|
+
|
51
|
+
const output = processWithArtifact(input);
|
52
|
+
|
53
|
+
expect(output).toEqual('');
|
54
|
+
});
|
55
|
+
|
56
|
+
describe('close the <lobeArtifact tag', () => {
|
57
|
+
it('close tag for <lobeArtifact', () => {
|
58
|
+
const input = '<lobeArtifact';
|
59
|
+
|
60
|
+
const output = processWithArtifact(input);
|
61
|
+
|
62
|
+
expect(output).toEqual('<lobeArtifact>');
|
63
|
+
});
|
64
|
+
|
65
|
+
it('close tag for <lobeArtifact identifier="something"', () => {
|
66
|
+
const input = '<lobeArtifact identifier="something"';
|
67
|
+
|
68
|
+
const output = processWithArtifact(input);
|
69
|
+
|
70
|
+
expect(output).toEqual('<lobeArtifact>');
|
71
|
+
});
|
72
|
+
|
73
|
+
it('close tag for <lobeArtifact identifier="ai-interpretation" type="image/svg+xml" titl', () => {
|
74
|
+
const input = '<lobeArtifact identifier="ai-interpretation" type="image/svg+xml" titl';
|
75
|
+
|
76
|
+
const output = processWithArtifact(input);
|
77
|
+
|
78
|
+
expect(output).toEqual('<lobeArtifact>');
|
79
|
+
});
|
80
|
+
|
81
|
+
it('only change the <lobeArtifact> part', () => {
|
82
|
+
const input = `好的,让我来用新的视角解释"人工智能"这个词汇。
|
83
|
+
|
84
|
+
<lobeThinking>这个词汇涉及了当代科技和社会热点,需要用批判性和幽默感来解读其本质。我会用隐喻和讽刺来表达,同时保持简洁有力。</lobeThinking>
|
85
|
+
|
86
|
+
<lobeArtifact identifier="ai-new-interpretation" type="image/svg+xml" t`;
|
87
|
+
|
88
|
+
const output = processWithArtifact(input);
|
89
|
+
|
90
|
+
expect(output).toEqual(`好的,让我来用新的视角解释"人工智能"这个词汇。
|
91
|
+
|
92
|
+
<lobeThinking>这个词汇涉及了当代科技和社会热点,需要用批判性和幽默感来解读其本质。我会用隐喻和讽刺来表达,同时保持简洁有力。</lobeThinking>
|
93
|
+
|
94
|
+
<lobeArtifact>`);
|
95
|
+
});
|
96
|
+
|
97
|
+
it('not change for <lobeArtifact />', () => {
|
98
|
+
const input = '<lobeArtifact/>';
|
99
|
+
|
100
|
+
const output = processWithArtifact(input);
|
101
|
+
|
102
|
+
expect(output).toEqual(input);
|
103
|
+
});
|
104
|
+
});
|
105
|
+
|
106
|
+
it('should removeLinkBreaks for lobeThinking', () => {
|
107
|
+
const input = `好的,让我以一个特别的视角来解释"人工智能"这个词汇。
|
108
|
+
|
109
|
+
<lobeThinking>
|
110
|
+
这个词汇涉及了当代科技和社会热点,需要用批判性、幽默而深刻的视角来解读。我会运用隐喻和讽刺,抓住其本质,并以精练的方式表达出来。这符合一个好的artifact的标准,因为它是一个独立的、可能被用户修改或重用的内容。我将创建一个新的SVG artifact来呈现这个解释。
|
111
|
+
</lobeThinking>
|
112
|
+
|
113
|
+
<lobeArtifact identifier="ai-new-interpretation" type="image/svg+xml" title="人工智能的新解释">
|
114
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 600">
|
115
|
+
<defs>
|
116
|
+
<style>
|
117
|
+
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;700&display=swap');
|
118
|
+
.background { fill: #f0f0f0; }
|
119
|
+
.title { font-family: 'Noto Serif SC', serif; font-size: 28px; font-weight: 700; fill: #333; }
|
120
|
+
.content { font-family: 'Noto Serif SC', serif; font-size: 18px; fill: #555; }
|
121
|
+
.divider { stroke: #999; stroke-width: 1; }
|
122
|
+
.decoration { fill: none; stroke: #999; stroke-width: 1; }
|
123
|
+
</style>
|
124
|
+
</defs>
|
125
|
+
</svg>
|
126
|
+
</lobeArtifact>
|
127
|
+
|
128
|
+
我为"人工智能"这个词创建了一个新的解释,并将其呈现在一个SVG卡片中。这个解释采用了批判性和幽默的视角,试图揭示这个概念背后的一些潜在问题。`;
|
129
|
+
|
130
|
+
const output = processWithArtifact(input);
|
131
|
+
|
132
|
+
expect(output).toEqual(`好的,让我以一个特别的视角来解释"人工智能"这个词汇。
|
133
|
+
|
134
|
+
<lobeThinking>这个词汇涉及了当代科技和社会热点,需要用批判性、幽默而深刻的视角来解读。我会运用隐喻和讽刺,抓住其本质,并以精练的方式表达出来。这符合一个好的artifact的标准,因为它是一个独立的、可能被用户修改或重用的内容。我将创建一个新的SVG artifact来呈现这个解释。</lobeThinking>
|
135
|
+
|
136
|
+
<lobeArtifact identifier="ai-new-interpretation" type="image/svg+xml" title="人工智能的新解释"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 600"> <defs> <style> @import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;700&display=swap'); .background { fill: #f0f0f0; } .title { font-family: 'Noto Serif SC', serif; font-size: 28px; font-weight: 700; fill: #333; } .content { font-family: 'Noto Serif SC', serif; font-size: 18px; fill: #555; } .divider { stroke: #999; stroke-width: 1; } .decoration { fill: none; stroke: #999; stroke-width: 1; } </style> </defs></svg></lobeArtifact>
|
137
|
+
|
138
|
+
我为"人工智能"这个词创建了一个新的解释,并将其呈现在一个SVG卡片中。这个解释采用了批判性和幽默的视角,试图揭示这个概念背后的一些潜在问题。`);
|
139
|
+
});
|
140
|
+
|
141
|
+
it('should removeLinkBreaks for lobeThinking', () => {
|
142
|
+
const input = `<lobeThinking>
|
143
|
+
这个词汇涉及了
|
144
|
+
`;
|
145
|
+
|
146
|
+
const output = processWithArtifact(input);
|
147
|
+
|
148
|
+
expect(output).toEqual(`<lobeThinking>这个词汇涉及了`);
|
149
|
+
});
|
150
|
+
});
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import { ARTIFACT_TAG_REGEX, ARTIFACT_THINKING_TAG_REGEX } from '@/const/plugin';
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Replace all line breaks in the matched `lobeArtifact` tag with an empty string
|
5
|
+
*/
|
6
|
+
export const processWithArtifact = (input: string = '') => {
|
7
|
+
let output = input;
|
8
|
+
const thinkMatch = ARTIFACT_THINKING_TAG_REGEX.exec(input);
|
9
|
+
|
10
|
+
// If the input contains the `lobeThinking` tag, replace all line breaks with an empty string
|
11
|
+
if (thinkMatch)
|
12
|
+
output = input.replace(ARTIFACT_THINKING_TAG_REGEX, (match) =>
|
13
|
+
match.replaceAll(/\r?\n|\r/g, ''),
|
14
|
+
);
|
15
|
+
|
16
|
+
const match = ARTIFACT_TAG_REGEX.exec(input);
|
17
|
+
// If the input contains the `lobeArtifact` tag, replace all line breaks with an empty string
|
18
|
+
if (match)
|
19
|
+
return output.replace(ARTIFACT_TAG_REGEX, (match) => match.replaceAll(/\r?\n|\r/g, ''));
|
20
|
+
|
21
|
+
// if not match, check if it's start with <lobeArtifact but not closed
|
22
|
+
const regex = /<lobeArtifact\b(?:(?!\/?>)[\S\s])*$/;
|
23
|
+
if (regex.test(output)) {
|
24
|
+
return output.replace(regex, '<lobeArtifact>');
|
25
|
+
}
|
26
|
+
|
27
|
+
return output;
|
28
|
+
};
|
@@ -4,13 +4,13 @@ import { ActionIcon, Avatar, Grid } from '@lobehub/ui';
|
|
4
4
|
import { Skeleton, Typography } from 'antd';
|
5
5
|
import { createStyles } from 'antd-style';
|
6
6
|
import { RefreshCw } from 'lucide-react';
|
7
|
+
import Link from 'next/link';
|
7
8
|
import { memo, useState } from 'react';
|
8
9
|
import { useTranslation } from 'react-i18next';
|
9
10
|
import { Flexbox } from 'react-layout-kit';
|
10
11
|
import useSWR from 'swr';
|
11
12
|
import urlJoin from 'url-join';
|
12
13
|
|
13
|
-
import InterceptingLink from '@/components/InterceptingLink';
|
14
14
|
import { assistantService } from '@/services/assistant';
|
15
15
|
import { useUserStore } from '@/store/user';
|
16
16
|
import { userGeneralSettingsSelectors } from '@/store/user/selectors';
|
@@ -105,10 +105,7 @@ const AgentsSuggest = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
105
105
|
: assistantList
|
106
106
|
.slice(sliceStart, sliceStart + agentLength)
|
107
107
|
.map((item: DiscoverAssistantItem) => (
|
108
|
-
<
|
109
|
-
href={urlJoin('/discover/assistant/', item.identifier)}
|
110
|
-
key={item.identifier}
|
111
|
-
>
|
108
|
+
<Link href={urlJoin('/discover/assistant/', item.identifier)} key={item.identifier}>
|
112
109
|
<Flexbox className={styles.card} gap={8} horizontal>
|
113
110
|
<Avatar avatar={item.meta.avatar} style={{ flex: 'none' }} />
|
114
111
|
<Flexbox gap={mobile ? 2 : 8} style={{ overflow: 'hidden', width: '100%' }}>
|
@@ -120,7 +117,7 @@ const AgentsSuggest = memo<{ mobile?: boolean }>(({ mobile }) => {
|
|
120
117
|
</Paragraph>
|
121
118
|
</Flexbox>
|
122
119
|
</Flexbox>
|
123
|
-
</
|
120
|
+
</Link>
|
124
121
|
))}
|
125
122
|
</Grid>
|
126
123
|
</Flexbox>
|
@@ -0,0 +1,96 @@
|
|
1
|
+
import { SiReact } from '@icons-pack/react-simple-icons';
|
2
|
+
import { Icon } from '@lobehub/ui';
|
3
|
+
import { createStyles } from 'antd-style';
|
4
|
+
import { CodeXml, GlobeIcon, ImageIcon, Loader2, OrigamiIcon } from 'lucide-react';
|
5
|
+
import { memo } from 'react';
|
6
|
+
|
7
|
+
const useStyles = createStyles(({ css, token, isDarkMode }) => ({
|
8
|
+
container: css`
|
9
|
+
cursor: pointer;
|
10
|
+
|
11
|
+
margin-block-start: 12px;
|
12
|
+
|
13
|
+
color: ${token.colorText};
|
14
|
+
|
15
|
+
border: 1px solid ${token.colorBorder};
|
16
|
+
border-radius: 8px;
|
17
|
+
|
18
|
+
&:hover {
|
19
|
+
background: ${isDarkMode ? '' : token.colorFillSecondary};
|
20
|
+
}
|
21
|
+
`,
|
22
|
+
|
23
|
+
desc: css`
|
24
|
+
color: ${token.colorTextTertiary};
|
25
|
+
`,
|
26
|
+
title: css`
|
27
|
+
overflow: hidden;
|
28
|
+
display: -webkit-box;
|
29
|
+
-webkit-box-orient: vertical;
|
30
|
+
-webkit-line-clamp: 1;
|
31
|
+
|
32
|
+
font-size: 16px;
|
33
|
+
text-overflow: ellipsis;
|
34
|
+
`,
|
35
|
+
}));
|
36
|
+
|
37
|
+
interface ArtifactProps {
|
38
|
+
type: string;
|
39
|
+
}
|
40
|
+
|
41
|
+
const SIZE = 28;
|
42
|
+
const ArtifactIcon = memo<ArtifactProps>(({ type }) => {
|
43
|
+
const { theme } = useStyles();
|
44
|
+
|
45
|
+
if (!type)
|
46
|
+
return (
|
47
|
+
<Icon
|
48
|
+
icon={Loader2}
|
49
|
+
size={{ fontSize: SIZE }}
|
50
|
+
spin
|
51
|
+
style={{ color: theme.colorTextSecondary }}
|
52
|
+
/>
|
53
|
+
);
|
54
|
+
|
55
|
+
switch (type) {
|
56
|
+
case 'application/lobe.artifacts.code': {
|
57
|
+
return (
|
58
|
+
<Icon
|
59
|
+
icon={CodeXml}
|
60
|
+
size={{ fontSize: SIZE }}
|
61
|
+
style={{ color: theme.colorTextSecondary }}
|
62
|
+
/>
|
63
|
+
);
|
64
|
+
}
|
65
|
+
|
66
|
+
case 'application/lobe.artifacts.react': {
|
67
|
+
return <SiReact size={SIZE} style={{ color: theme.colorTextSecondary }} />;
|
68
|
+
}
|
69
|
+
|
70
|
+
case 'image/svg+xml': {
|
71
|
+
return (
|
72
|
+
<Icon
|
73
|
+
icon={ImageIcon}
|
74
|
+
size={{ fontSize: SIZE }}
|
75
|
+
style={{ color: theme.colorTextSecondary }}
|
76
|
+
/>
|
77
|
+
);
|
78
|
+
}
|
79
|
+
case 'text/html': {
|
80
|
+
return (
|
81
|
+
<Icon
|
82
|
+
icon={GlobeIcon}
|
83
|
+
size={{ fontSize: SIZE }}
|
84
|
+
style={{ color: theme.colorTextSecondary }}
|
85
|
+
/>
|
86
|
+
);
|
87
|
+
}
|
88
|
+
default: {
|
89
|
+
return (
|
90
|
+
<Icon color={theme.purple} icon={OrigamiIcon} size={{ fontSize: SIZE, strokeWidth: 1.2 }} />
|
91
|
+
);
|
92
|
+
}
|
93
|
+
}
|
94
|
+
});
|
95
|
+
|
96
|
+
export default ArtifactIcon;
|
@@ -0,0 +1,129 @@
|
|
1
|
+
import { Icon } from '@lobehub/ui';
|
2
|
+
import { createStyles } from 'antd-style';
|
3
|
+
import { Loader2 } from 'lucide-react';
|
4
|
+
import { memo, useEffect } from 'react';
|
5
|
+
import { useTranslation } from 'react-i18next';
|
6
|
+
import { Center, Flexbox } from 'react-layout-kit';
|
7
|
+
|
8
|
+
import { useChatStore } from '@/store/chat';
|
9
|
+
import { chatPortalSelectors, chatSelectors } from '@/store/chat/selectors';
|
10
|
+
import { dotLoading } from '@/styles/loading';
|
11
|
+
|
12
|
+
import { MarkdownElementProps } from '../../type';
|
13
|
+
import ArtifactIcon from './Icon';
|
14
|
+
|
15
|
+
const useStyles = createStyles(({ css, token, isDarkMode }) => ({
|
16
|
+
avatar: css`
|
17
|
+
background: ${token.colorFillQuaternary};
|
18
|
+
border-inline-end: 1px solid ${token.colorSplit};
|
19
|
+
`,
|
20
|
+
container: css`
|
21
|
+
cursor: pointer;
|
22
|
+
|
23
|
+
margin-block-start: 12px;
|
24
|
+
|
25
|
+
color: ${token.colorText};
|
26
|
+
|
27
|
+
border: 1px solid ${token.colorBorder};
|
28
|
+
border-radius: 8px;
|
29
|
+
box-shadow: ${isDarkMode ? token.boxShadowSecondary : token.boxShadowTertiary};
|
30
|
+
|
31
|
+
&:hover {
|
32
|
+
background: ${token.colorFillQuaternary};
|
33
|
+
}
|
34
|
+
`,
|
35
|
+
desc: css`
|
36
|
+
font-size: 12px;
|
37
|
+
color: ${token.colorTextTertiary};
|
38
|
+
`,
|
39
|
+
title: css`
|
40
|
+
overflow: hidden;
|
41
|
+
display: -webkit-box;
|
42
|
+
-webkit-box-orient: vertical;
|
43
|
+
-webkit-line-clamp: 1;
|
44
|
+
|
45
|
+
text-overflow: ellipsis;
|
46
|
+
`,
|
47
|
+
}));
|
48
|
+
|
49
|
+
interface ArtifactProps extends MarkdownElementProps {
|
50
|
+
identifier: string;
|
51
|
+
title: string;
|
52
|
+
type: string;
|
53
|
+
}
|
54
|
+
|
55
|
+
const Render = memo<ArtifactProps>(({ identifier, title, type, children, id }) => {
|
56
|
+
const { t } = useTranslation('chat');
|
57
|
+
const { styles, cx } = useStyles();
|
58
|
+
|
59
|
+
const hasChildren = !!children;
|
60
|
+
const str = ((children as string) || '').toString?.();
|
61
|
+
|
62
|
+
const [isGenerating, isArtifactTagClosed, currentArtifactMessageId, openArtifact, closeArtifact] =
|
63
|
+
useChatStore((s) => {
|
64
|
+
return [
|
65
|
+
chatSelectors.isMessageGenerating(id)(s),
|
66
|
+
chatPortalSelectors.isArtifactTagClosed(id)(s),
|
67
|
+
chatPortalSelectors.artifactMessageId(s),
|
68
|
+
s.openArtifact,
|
69
|
+
s.closeArtifact,
|
70
|
+
];
|
71
|
+
});
|
72
|
+
|
73
|
+
const openArtifactUI = () => {
|
74
|
+
openArtifact({ id, identifier, title, type });
|
75
|
+
};
|
76
|
+
|
77
|
+
useEffect(() => {
|
78
|
+
if (!hasChildren || !isGenerating) return;
|
79
|
+
|
80
|
+
openArtifactUI();
|
81
|
+
}, [isGenerating, hasChildren, str, identifier, title, type, id]);
|
82
|
+
|
83
|
+
return (
|
84
|
+
<p>
|
85
|
+
<Flexbox
|
86
|
+
className={styles.container}
|
87
|
+
gap={16}
|
88
|
+
onClick={() => {
|
89
|
+
if (currentArtifactMessageId === id) {
|
90
|
+
closeArtifact();
|
91
|
+
} else {
|
92
|
+
openArtifactUI();
|
93
|
+
}
|
94
|
+
}}
|
95
|
+
width={'100%'}
|
96
|
+
>
|
97
|
+
<Flexbox align={'center'} flex={1} horizontal>
|
98
|
+
<Center className={styles.avatar} height={64} horizontal width={64}>
|
99
|
+
<ArtifactIcon type={type} />
|
100
|
+
</Center>
|
101
|
+
<Flexbox gap={4} paddingBlock={8} paddingInline={12}>
|
102
|
+
{!title && isGenerating ? (
|
103
|
+
<Flexbox className={cx(dotLoading)} horizontal>
|
104
|
+
{t('artifact.generating')}
|
105
|
+
</Flexbox>
|
106
|
+
) : (
|
107
|
+
<Flexbox className={cx(styles.title)}>{title || t('artifact.unknownTitle')}</Flexbox>
|
108
|
+
)}
|
109
|
+
{hasChildren && (
|
110
|
+
<Flexbox className={styles.desc} horizontal>
|
111
|
+
{identifier} ·{' '}
|
112
|
+
<Flexbox gap={2} horizontal>
|
113
|
+
{!isArtifactTagClosed && (
|
114
|
+
<div>
|
115
|
+
<Icon icon={Loader2} spin />
|
116
|
+
</div>
|
117
|
+
)}
|
118
|
+
{str?.length}
|
119
|
+
</Flexbox>
|
120
|
+
</Flexbox>
|
121
|
+
)}
|
122
|
+
</Flexbox>
|
123
|
+
</Flexbox>
|
124
|
+
</Flexbox>
|
125
|
+
</p>
|
126
|
+
);
|
127
|
+
});
|
128
|
+
|
129
|
+
export default Render;
|
@@ -0,0 +1,74 @@
|
|
1
|
+
import { SKIP, visit } from 'unist-util-visit';
|
2
|
+
|
3
|
+
import { ARTIFACT_TAG } from '@/const/plugin';
|
4
|
+
|
5
|
+
function rehypeAntArtifact() {
|
6
|
+
return (tree: any) => {
|
7
|
+
visit(tree, (node, index, parent) => {
|
8
|
+
if (node.type === 'element' && node.tagName === 'p' && node.children.length > 0) {
|
9
|
+
const firstChild = node.children[0];
|
10
|
+
if (firstChild.type === 'raw' && firstChild.value.startsWith(`<${ARTIFACT_TAG}`)) {
|
11
|
+
// 提取 lobeArtifact 的属性
|
12
|
+
const attributes: Record<string, string> = {};
|
13
|
+
const attributeRegex = /(\w+)="([^"]*)"/g;
|
14
|
+
let match;
|
15
|
+
while ((match = attributeRegex.exec(firstChild.value)) !== null) {
|
16
|
+
attributes[match[1]] = match[2];
|
17
|
+
}
|
18
|
+
|
19
|
+
// 创建新的 lobeArtifact 节点
|
20
|
+
const newNode = {
|
21
|
+
children: [
|
22
|
+
{
|
23
|
+
type: 'text',
|
24
|
+
value: node.children
|
25
|
+
.slice(1, -1)
|
26
|
+
.map((child: any) => {
|
27
|
+
if (child.type === 'raw') {
|
28
|
+
return child.value;
|
29
|
+
} else if (child.type === 'text') {
|
30
|
+
return child.value;
|
31
|
+
} else if (child.type === 'element' && child.tagName === 'a') {
|
32
|
+
return child.children[0].value;
|
33
|
+
}
|
34
|
+
return '';
|
35
|
+
})
|
36
|
+
.join('')
|
37
|
+
.trim(),
|
38
|
+
},
|
39
|
+
],
|
40
|
+
properties: attributes,
|
41
|
+
tagName: ARTIFACT_TAG,
|
42
|
+
type: 'element',
|
43
|
+
};
|
44
|
+
|
45
|
+
// 替换原来的 p 节点
|
46
|
+
parent.children.splice(index, 1, newNode);
|
47
|
+
return [SKIP, index];
|
48
|
+
}
|
49
|
+
}
|
50
|
+
// 如果字符串是 <lobeArtifact identifier="ai-new-interpretation" type="image/svg+xml" title="人工智能新解释">
|
51
|
+
// 得到的节点就是:
|
52
|
+
// {
|
53
|
+
// type: 'raw',
|
54
|
+
// value:
|
55
|
+
// '<lobeArtifact identifier="ai-new-interpretation" type="image/svg+xml" title="人工智能新解释">',
|
56
|
+
// }
|
57
|
+
else if (node.type === 'raw' && node.value.startsWith(`<${ARTIFACT_TAG}`)) {
|
58
|
+
// 创建新的 lobeArtifact 节点
|
59
|
+
const newNode = {
|
60
|
+
children: [],
|
61
|
+
properties: {},
|
62
|
+
tagName: ARTIFACT_TAG,
|
63
|
+
type: 'element',
|
64
|
+
};
|
65
|
+
|
66
|
+
// 替换原来的 p 节点
|
67
|
+
parent.children.splice(index, 1, newNode);
|
68
|
+
return [SKIP, index];
|
69
|
+
}
|
70
|
+
});
|
71
|
+
};
|
72
|
+
}
|
73
|
+
|
74
|
+
export default rehypeAntArtifact;
|