@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.

Files changed (181) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/Dockerfile +2 -0
  3. package/Dockerfile.database +2 -0
  4. package/locales/ar/chat.json +6 -0
  5. package/locales/ar/error.json +1 -0
  6. package/locales/ar/modelProvider.json +7 -0
  7. package/locales/ar/portal.json +16 -0
  8. package/locales/bg-BG/chat.json +6 -0
  9. package/locales/bg-BG/error.json +1 -0
  10. package/locales/bg-BG/modelProvider.json +7 -0
  11. package/locales/bg-BG/portal.json +16 -0
  12. package/locales/de-DE/chat.json +6 -0
  13. package/locales/de-DE/error.json +1 -0
  14. package/locales/de-DE/modelProvider.json +7 -0
  15. package/locales/de-DE/portal.json +16 -0
  16. package/locales/en-US/chat.json +6 -0
  17. package/locales/en-US/error.json +1 -0
  18. package/locales/en-US/modelProvider.json +7 -0
  19. package/locales/en-US/portal.json +16 -0
  20. package/locales/es-ES/chat.json +6 -0
  21. package/locales/es-ES/error.json +1 -0
  22. package/locales/es-ES/modelProvider.json +7 -0
  23. package/locales/es-ES/portal.json +16 -0
  24. package/locales/fr-FR/chat.json +6 -0
  25. package/locales/fr-FR/error.json +1 -0
  26. package/locales/fr-FR/modelProvider.json +7 -0
  27. package/locales/fr-FR/portal.json +16 -0
  28. package/locales/it-IT/chat.json +6 -0
  29. package/locales/it-IT/error.json +1 -0
  30. package/locales/it-IT/modelProvider.json +7 -0
  31. package/locales/it-IT/portal.json +16 -0
  32. package/locales/ja-JP/chat.json +6 -0
  33. package/locales/ja-JP/error.json +1 -0
  34. package/locales/ja-JP/modelProvider.json +7 -0
  35. package/locales/ja-JP/portal.json +16 -0
  36. package/locales/ko-KR/chat.json +6 -0
  37. package/locales/ko-KR/error.json +1 -0
  38. package/locales/ko-KR/modelProvider.json +7 -0
  39. package/locales/ko-KR/portal.json +16 -0
  40. package/locales/nl-NL/chat.json +6 -0
  41. package/locales/nl-NL/error.json +1 -0
  42. package/locales/nl-NL/modelProvider.json +7 -0
  43. package/locales/nl-NL/portal.json +16 -0
  44. package/locales/pl-PL/chat.json +6 -0
  45. package/locales/pl-PL/error.json +1 -0
  46. package/locales/pl-PL/modelProvider.json +7 -0
  47. package/locales/pl-PL/portal.json +16 -0
  48. package/locales/pt-BR/chat.json +6 -0
  49. package/locales/pt-BR/error.json +1 -0
  50. package/locales/pt-BR/modelProvider.json +7 -0
  51. package/locales/pt-BR/portal.json +16 -0
  52. package/locales/ru-RU/chat.json +6 -0
  53. package/locales/ru-RU/error.json +1 -0
  54. package/locales/ru-RU/modelProvider.json +7 -0
  55. package/locales/ru-RU/portal.json +16 -0
  56. package/locales/tr-TR/chat.json +6 -0
  57. package/locales/tr-TR/error.json +1 -0
  58. package/locales/tr-TR/modelProvider.json +7 -0
  59. package/locales/tr-TR/portal.json +16 -0
  60. package/locales/vi-VN/chat.json +6 -0
  61. package/locales/vi-VN/error.json +1 -0
  62. package/locales/vi-VN/modelProvider.json +7 -0
  63. package/locales/vi-VN/portal.json +16 -0
  64. package/locales/zh-CN/chat.json +6 -0
  65. package/locales/zh-CN/error.json +2 -1
  66. package/locales/zh-CN/modelProvider.json +7 -0
  67. package/locales/zh-CN/portal.json +17 -1
  68. package/locales/zh-TW/chat.json +6 -0
  69. package/locales/zh-TW/error.json +1 -0
  70. package/locales/zh-TW/modelProvider.json +7 -0
  71. package/locales/zh-TW/portal.json +16 -0
  72. package/package.json +4 -1
  73. package/src/app/(main)/chat/(workspace)/@portal/Artifacts/Body/Renderer/HTML.tsx +25 -0
  74. package/src/app/(main)/chat/(workspace)/@portal/Artifacts/Body/Renderer/React.tsx +30 -0
  75. package/src/app/(main)/chat/(workspace)/@portal/Artifacts/Body/Renderer/SVG.tsx +114 -0
  76. package/src/app/(main)/chat/(workspace)/@portal/Artifacts/Body/Renderer/index.tsx +25 -0
  77. package/src/app/(main)/chat/(workspace)/@portal/Artifacts/Body/index.tsx +79 -0
  78. package/src/app/(main)/chat/(workspace)/@portal/Artifacts/Header.tsx +69 -0
  79. package/src/app/(main)/chat/(workspace)/@portal/Artifacts/index.ts +10 -0
  80. package/src/app/(main)/chat/(workspace)/@portal/Artifacts/useEnable.ts +4 -0
  81. package/src/app/(main)/chat/(workspace)/@portal/FilePreview/index.ts +2 -1
  82. package/src/app/(main)/chat/(workspace)/@portal/Home/Body/{Artifacts → Plugins}/index.tsx +1 -1
  83. package/src/app/(main)/chat/(workspace)/@portal/Home/Body/index.tsx +2 -2
  84. package/src/app/(main)/chat/(workspace)/@portal/MessageDetail/index.ts +2 -1
  85. package/src/app/(main)/chat/(workspace)/@portal/Plugins/Body/ToolRender.tsx +1 -1
  86. package/src/app/(main)/chat/(workspace)/@portal/Plugins/Body/index.tsx +1 -1
  87. package/src/app/(main)/chat/(workspace)/@portal/Plugins/Footer.tsx +1 -1
  88. package/src/app/(main)/chat/(workspace)/@portal/Plugins/index.ts +2 -1
  89. package/src/app/(main)/chat/(workspace)/@portal/Plugins/useEnable.ts +1 -1
  90. package/src/app/(main)/chat/(workspace)/@portal/_layout/Desktop.tsx +2 -4
  91. package/src/app/(main)/chat/(workspace)/@portal/features/Body.tsx +27 -0
  92. package/src/app/(main)/chat/(workspace)/@portal/router.tsx +3 -1
  93. package/src/app/(main)/chat/(workspace)/@portal/type.ts +7 -0
  94. package/src/app/(main)/chat/(workspace)/_layout/Desktop/Portal.tsx +3 -2
  95. package/src/app/(main)/discover/(detail)/assistant/[slug]/features/InfoSidebar/index.tsx +5 -8
  96. package/src/app/(main)/discover/(detail)/features/Back.tsx +0 -5
  97. package/src/app/(main)/discover/(detail)/features/DetailLayout.tsx +2 -34
  98. package/src/app/(main)/discover/(detail)/model/[...slugs]/features/InfoSidebar/index.tsx +3 -6
  99. package/src/app/(main)/discover/(detail)/model/[...slugs]/features/ProviderList/ProviderItem.tsx +14 -15
  100. package/src/app/(main)/discover/(detail)/plugin/[slug]/features/InfoSidebar/index.tsx +3 -6
  101. package/src/app/(main)/discover/(detail)/provider/[slug]/features/InfoSidebar/index.tsx +3 -6
  102. package/src/app/(main)/discover/(detail)/provider/[slug]/features/ModelList/ModelItem.tsx +14 -11
  103. package/src/app/(main)/discover/(list)/(home)/features/AssistantList.tsx +5 -11
  104. package/src/app/(main)/discover/(list)/(home)/features/ModelList.tsx +3 -3
  105. package/src/app/(main)/discover/(list)/(home)/features/PluginList.tsx +3 -6
  106. package/src/app/(main)/discover/(list)/assistants/features/List.tsx +7 -16
  107. package/src/app/(main)/discover/(list)/models/features/List.tsx +5 -11
  108. package/src/app/(main)/discover/(list)/plugins/features/List.tsx +7 -16
  109. package/src/app/(main)/discover/(list)/providers/features/List.tsx +5 -11
  110. package/src/app/(main)/discover/_layout/Desktop/index.tsx +3 -0
  111. package/src/app/(main)/discover/_layout/Mobile/index.tsx +8 -1
  112. package/src/app/(main)/settings/llm/ProviderList/Github/index.tsx +53 -0
  113. package/src/app/(main)/settings/llm/ProviderList/providers.tsx +6 -1
  114. package/src/app/api/chat/agentRuntime.ts +14 -0
  115. package/src/app/layout.tsx +2 -1
  116. package/src/components/NProgress/index.tsx +12 -0
  117. package/src/components/SidebarHeader/index.tsx +1 -1
  118. package/src/config/llm.ts +14 -0
  119. package/src/config/modelProviders/ai21.ts +37 -0
  120. package/src/config/modelProviders/anthropic.ts +4 -0
  121. package/src/config/modelProviders/github.ts +209 -0
  122. package/src/config/modelProviders/index.ts +8 -0
  123. package/src/const/layoutTokens.ts +1 -1
  124. package/src/const/plugin.test.ts +80 -0
  125. package/src/const/plugin.ts +12 -0
  126. package/src/const/settings/llm.ts +10 -0
  127. package/src/features/Conversation/Error/APIKeyForm/index.tsx +4 -0
  128. package/src/features/Conversation/Messages/Tool/Inspector/index.tsx +1 -1
  129. package/src/features/Conversation/Messages/Tool/index.tsx +1 -1
  130. package/src/features/Conversation/components/ChatItem/index.tsx +24 -2
  131. package/src/features/Conversation/components/ChatItem/utils.test.ts +150 -0
  132. package/src/features/Conversation/components/ChatItem/utils.ts +28 -0
  133. package/src/features/Conversation/components/InboxWelcome/AgentsSuggest.tsx +3 -6
  134. package/src/features/Conversation/components/MarkdownElements/LobeArtifact/Render/Icon.tsx +96 -0
  135. package/src/features/Conversation/components/MarkdownElements/LobeArtifact/Render/index.tsx +129 -0
  136. package/src/features/Conversation/components/MarkdownElements/LobeArtifact/index.ts +10 -0
  137. package/src/features/Conversation/components/MarkdownElements/LobeArtifact/rehypePlugin.ts +74 -0
  138. package/src/features/Conversation/components/MarkdownElements/LobeThinking/Render.tsx +86 -0
  139. package/src/features/Conversation/components/MarkdownElements/LobeThinking/index.ts +12 -0
  140. package/src/features/Conversation/components/MarkdownElements/LobeThinking/rehypePlugin.test.ts +124 -0
  141. package/src/features/Conversation/components/MarkdownElements/LobeThinking/rehypePlugin.ts +51 -0
  142. package/src/features/Conversation/components/MarkdownElements/index.ts +4 -0
  143. package/src/features/Conversation/components/MarkdownElements/type.ts +7 -0
  144. package/src/hooks/useInterceptingRoutes.ts +1 -16
  145. package/src/libs/agent-runtime/AgentRuntime.ts +14 -0
  146. package/src/libs/agent-runtime/ai21/index.test.ts +255 -0
  147. package/src/libs/agent-runtime/ai21/index.ts +18 -0
  148. package/src/libs/agent-runtime/error.ts +2 -0
  149. package/src/libs/agent-runtime/github/index.test.ts +246 -0
  150. package/src/libs/agent-runtime/github/index.ts +15 -0
  151. package/src/libs/agent-runtime/openrouter/__snapshots__/index.test.ts.snap +2215 -28
  152. package/src/libs/agent-runtime/openrouter/fixtures/models.json +3345 -37
  153. package/src/libs/agent-runtime/openrouter/index.ts +2 -1
  154. package/src/libs/agent-runtime/types/type.ts +2 -0
  155. package/src/locales/default/chat.ts +7 -0
  156. package/src/locales/default/error.ts +3 -0
  157. package/src/locales/default/modelProvider.ts +7 -0
  158. package/src/locales/default/portal.ts +17 -1
  159. package/src/server/globalConfig/index.ts +14 -0
  160. package/src/store/chat/slices/message/selectors.ts +30 -28
  161. package/src/store/chat/slices/portal/action.ts +15 -2
  162. package/src/store/chat/slices/portal/initialState.ts +11 -0
  163. package/src/store/chat/slices/portal/selectors.test.ts +29 -7
  164. package/src/store/chat/slices/portal/selectors.ts +56 -12
  165. package/src/styles/loading.ts +28 -0
  166. package/src/tools/artifacts/index.ts +13 -0
  167. package/src/tools/artifacts/systemRole.ts +338 -0
  168. package/src/tools/index.ts +6 -0
  169. package/src/types/user/settings/keyVaults.ts +2 -0
  170. package/src/utils/clipboard.ts +53 -0
  171. package/src/app/@modal/(.)discover/assistant/[slug]/page.tsx +0 -1
  172. package/src/app/@modal/(.)discover/layout.tsx +0 -29
  173. package/src/app/@modal/(.)discover/loading.tsx +0 -3
  174. package/src/app/@modal/(.)discover/model/[...slugs]/page.tsx +0 -1
  175. package/src/app/@modal/(.)discover/plugin/[slug]/page.tsx +0 -1
  176. package/src/app/@modal/(.)discover/provider/[slug]/page.tsx +0 -1
  177. package/src/app/redirect/page.tsx +0 -15
  178. package/src/components/InterceptingLink/index.tsx +0 -27
  179. /package/src/app/(main)/chat/(workspace)/@portal/Home/Body/{Artifacts → Plugins}/ArtifactList/Item/index.tsx +0 -0
  180. /package/src/app/(main)/chat/(workspace)/@portal/Home/Body/{Artifacts → Plugins}/ArtifactList/Item/style.ts +0 -0
  181. /package/src/app/(main)/chat/(workspace)/@portal/Home/Body/{Artifacts → Plugins}/ArtifactList/index.tsx +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.18.1",
3
+ "version": "1.19.0",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -112,6 +112,7 @@
112
112
  "@clerk/localizations": "2.0.0",
113
113
  "@clerk/nextjs": "^5.3.3",
114
114
  "@clerk/themes": "^2.1.27",
115
+ "@codesandbox/sandpack-react": "^2.19.8",
115
116
  "@cyntler/react-doc-viewer": "^1.16.6",
116
117
  "@google/generative-ai": "^0.16.0",
117
118
  "@icons-pack/react-simple-icons": "9.6.0",
@@ -172,6 +173,7 @@
172
173
  "next-auth": "beta",
173
174
  "next-mdx-remote": "^4.4.1",
174
175
  "next-sitemap": "^4.2.3",
176
+ "nextjs-toploader": "^3.6.15",
175
177
  "numeral": "^2.0.6",
176
178
  "nuqs": "^1.17.8",
177
179
  "officeparser": "^4.1.1",
@@ -255,6 +257,7 @@
255
257
  "@types/semver": "^7.5.8",
256
258
  "@types/systemjs": "^6.13.5",
257
259
  "@types/ua-parser-js": "^0.7.39",
260
+ "@types/unist": "^3.0.3",
258
261
  "@types/uuid": "^10.0.0",
259
262
  "@types/ws": "^8.5.12",
260
263
  "@vitest/coverage-v8": "~1.2.2",
@@ -0,0 +1,25 @@
1
+ import { memo, useEffect, useRef } from 'react';
2
+
3
+ interface HTMLRendererProps {
4
+ height?: string;
5
+ htmlContent: string;
6
+ width?: string;
7
+ }
8
+ const HTMLRenderer = memo<HTMLRendererProps>(({ htmlContent, width = '100%', height = '100%' }) => {
9
+ const iframeRef = useRef<HTMLIFrameElement>(null);
10
+
11
+ useEffect(() => {
12
+ if (!iframeRef.current) return;
13
+
14
+ const doc = iframeRef.current.contentDocument;
15
+ if (!doc) return;
16
+
17
+ doc.open();
18
+ doc.write(htmlContent);
19
+ doc.close();
20
+ }, [htmlContent]);
21
+
22
+ return <iframe ref={iframeRef} style={{ border: 'none', height, width }} title="html-renderer" />;
23
+ });
24
+
25
+ export default HTMLRenderer;
@@ -0,0 +1,30 @@
1
+ import { SandpackLayout, SandpackPreview, SandpackProvider } from '@codesandbox/sandpack-react';
2
+ import { memo } from 'react';
3
+
4
+ interface ReactRendererProps {
5
+ code: string;
6
+ }
7
+ const ReactRenderer = memo<ReactRendererProps>(({ code }) => {
8
+ return (
9
+ <SandpackProvider
10
+ customSetup={{
11
+ dependencies: {
12
+ antd: 'latest',
13
+ },
14
+ }}
15
+ files={{
16
+ 'App.js': code,
17
+ }}
18
+ options={{ externalResources: ['https://cdn.tailwindcss.com'] }}
19
+ style={{ height: '100%' }}
20
+ template="react"
21
+ theme="auto"
22
+ >
23
+ <SandpackLayout style={{ height: '100%' }}>
24
+ <SandpackPreview style={{ height: '100%' }} />
25
+ </SandpackLayout>
26
+ </SandpackProvider>
27
+ );
28
+ });
29
+
30
+ export default ReactRenderer;
@@ -0,0 +1,114 @@
1
+ import { Icon, Tooltip } from '@lobehub/ui';
2
+ import { App, Button, Dropdown, Space } from 'antd';
3
+ import { css, cx } from 'antd-style';
4
+ import { CopyIcon, DownloadIcon } from 'lucide-react';
5
+ import { domToPng } from 'modern-screenshot';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Center, Flexbox } from 'react-layout-kit';
8
+
9
+ import { BRANDING_NAME } from '@/const/branding';
10
+ import { useChatStore } from '@/store/chat';
11
+ import { chatPortalSelectors } from '@/store/chat/selectors';
12
+ import { copyImageToClipboard } from '@/utils/clipboard';
13
+
14
+ const svgContainer = css`
15
+ width: 100%;
16
+ height: 100%;
17
+
18
+ > svg {
19
+ width: 100%;
20
+ height: 100%;
21
+ }
22
+ `;
23
+
24
+ const actions = css`
25
+ position: absolute;
26
+ inset-block-end: 8px;
27
+ inset-inline-end: 8px;
28
+ `;
29
+
30
+ const DOM_ID = 'artfact-svg';
31
+ interface SVGRendererProps {
32
+ content: string;
33
+ }
34
+
35
+ const SVGRenderer = ({ content }: SVGRendererProps) => {
36
+ const { t } = useTranslation('portal');
37
+ const { message } = App.useApp();
38
+
39
+ const generatePng = async () => {
40
+ return domToPng(document.querySelector(`#${DOM_ID}`) as HTMLDivElement, {
41
+ features: {
42
+ // 不启用移除控制符,否则会导致 safari emoji 报错
43
+ removeControlCharacter: false,
44
+ },
45
+ scale: 2,
46
+ });
47
+ };
48
+
49
+ const downloadImage = async (type: string) => {
50
+ let dataUrl = '';
51
+ if (type === 'png') dataUrl = await generatePng();
52
+ else if (type === 'svg') {
53
+ const blob = new Blob([content], { type: 'image/svg+xml' });
54
+
55
+ dataUrl = URL.createObjectURL(blob);
56
+ }
57
+
58
+ const title = chatPortalSelectors.artifactTitle(useChatStore.getState());
59
+
60
+ const link = document.createElement('a');
61
+ link.download = `${BRANDING_NAME}_${title}.${type}`;
62
+ link.href = dataUrl;
63
+ link.click();
64
+ link.remove();
65
+ };
66
+
67
+ return (
68
+ <Flexbox
69
+ align={'center'}
70
+ className="svg-renderer"
71
+ height={'100%'}
72
+ style={{ position: 'relative' }}
73
+ >
74
+ <Center
75
+ className={cx(svgContainer)}
76
+ dangerouslySetInnerHTML={{ __html: content }}
77
+ id={DOM_ID}
78
+ />
79
+ <Flexbox className={cx(actions)}>
80
+ <Space.Compact>
81
+ <Dropdown
82
+ menu={{
83
+ items: [
84
+ { key: 'png', label: t('artifacts.svg.download.png') },
85
+ { key: 'svg', label: t('artifacts.svg.download.svg') },
86
+ ],
87
+ onClick: ({ key }) => {
88
+ downloadImage(key);
89
+ },
90
+ }}
91
+ >
92
+ <Button icon={<Icon icon={DownloadIcon} />} />
93
+ </Dropdown>
94
+ <Tooltip title={t('artifacts.svg.copyAsImage')}>
95
+ <Button
96
+ icon={<Icon icon={CopyIcon} />}
97
+ onClick={async () => {
98
+ const dataUrl = await generatePng();
99
+ try {
100
+ await copyImageToClipboard(dataUrl);
101
+ message.success(t('artifacts.svg.copySuccess'));
102
+ } catch (e) {
103
+ message.error(t('artifacts.svg.copyFail', { error: e }));
104
+ }
105
+ }}
106
+ />
107
+ </Tooltip>
108
+ </Space.Compact>
109
+ </Flexbox>
110
+ </Flexbox>
111
+ );
112
+ };
113
+
114
+ export default SVGRenderer;
@@ -0,0 +1,25 @@
1
+ import dynamic from 'next/dynamic';
2
+ import { memo } from 'react';
3
+
4
+ import HTMLRenderer from './HTML';
5
+ import SVGRender from './SVG';
6
+
7
+ const ReactRenderer = dynamic(() => import('./React'), { ssr: false });
8
+
9
+ const Renderer = memo<{ content: string; type?: string }>(({ content, type }) => {
10
+ switch (type) {
11
+ case 'application/lobe.artifacts.react': {
12
+ return <ReactRenderer code={content} />;
13
+ }
14
+
15
+ case 'image/svg+xml': {
16
+ return <SVGRender content={content} />;
17
+ }
18
+
19
+ default: {
20
+ return <HTMLRenderer htmlContent={content} />;
21
+ }
22
+ }
23
+ });
24
+
25
+ export default Renderer;
@@ -0,0 +1,79 @@
1
+ import { Highlighter } from '@lobehub/ui';
2
+ import { memo, useEffect, useMemo } from 'react';
3
+ import { Flexbox } from 'react-layout-kit';
4
+
5
+ import { useChatStore } from '@/store/chat';
6
+ import { chatPortalSelectors, chatSelectors } from '@/store/chat/selectors';
7
+
8
+ import Renderer from './Renderer';
9
+
10
+ const ArtifactsUI = memo(() => {
11
+ const [
12
+ messageId,
13
+ displayMode,
14
+ isMessageGenerating,
15
+ artifactType,
16
+ artifactContent,
17
+
18
+ isArtifactTagClosed,
19
+ ] = useChatStore((s) => {
20
+ const messageId = chatPortalSelectors.artifactMessageId(s) || '';
21
+
22
+ return [
23
+ messageId,
24
+ s.portalArtifactDisplayMode,
25
+ chatSelectors.isMessageGenerating(messageId)(s),
26
+ chatPortalSelectors.artifactType(s),
27
+ chatPortalSelectors.artifactCode(messageId)(s),
28
+ chatPortalSelectors.isArtifactTagClosed(messageId)(s),
29
+ ];
30
+ });
31
+
32
+ useEffect(() => {
33
+ // when message generating , check whether the artifact is closed
34
+ // if close, move the display mode to preview
35
+ if (isMessageGenerating && isArtifactTagClosed && displayMode === 'code') {
36
+ useChatStore.setState({ portalArtifactDisplayMode: 'preview' });
37
+ }
38
+ }, [isMessageGenerating, displayMode, isArtifactTagClosed]);
39
+
40
+ const language = useMemo(() => {
41
+ switch (artifactType) {
42
+ case 'application/lobe.artifacts.react': {
43
+ return 'tsx';
44
+ }
45
+
46
+ case 'python': {
47
+ return 'python';
48
+ }
49
+
50
+ default: {
51
+ return 'html';
52
+ }
53
+ }
54
+ }, [artifactType]);
55
+
56
+ // make sure the message and id is valid
57
+ if (!messageId) return;
58
+
59
+ return (
60
+ <Flexbox
61
+ className={'portal-artifact'}
62
+ flex={1}
63
+ gap={8}
64
+ height={'100%'}
65
+ paddingInline={12}
66
+ style={{ overflow: 'hidden' }}
67
+ >
68
+ {!isArtifactTagClosed || displayMode === 'code' ? (
69
+ <Highlighter language={language} style={{ maxHeight: '100%', overflow: 'hidden' }}>
70
+ {artifactContent}
71
+ </Highlighter>
72
+ ) : (
73
+ <Renderer content={artifactContent} type={artifactType} />
74
+ )}
75
+ </Flexbox>
76
+ );
77
+ });
78
+
79
+ export default ArtifactsUI;
@@ -0,0 +1,69 @@
1
+ import { ActionIcon, Icon } from '@lobehub/ui';
2
+ import { ConfigProvider, Segmented, Typography } from 'antd';
3
+ import { cx } from 'antd-style';
4
+ import { ArrowLeft, CodeIcon, EyeIcon } from 'lucide-react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Flexbox } from 'react-layout-kit';
7
+
8
+ import { useChatStore } from '@/store/chat';
9
+ import { chatPortalSelectors } from '@/store/chat/selectors';
10
+ import { oneLineEllipsis } from '@/styles';
11
+
12
+ const Header = () => {
13
+ const { t } = useTranslation('portal');
14
+
15
+ const [displayMode, artifactTitle, isArtifactTagClosed, closeArtifact] = useChatStore((s) => {
16
+ const messageId = chatPortalSelectors.artifactMessageId(s) || '';
17
+
18
+ return [
19
+ s.portalArtifactDisplayMode,
20
+ chatPortalSelectors.artifactTitle(s),
21
+ chatPortalSelectors.isArtifactTagClosed(messageId)(s),
22
+ s.closeArtifact,
23
+ ];
24
+ });
25
+
26
+ return (
27
+ <Flexbox align={'center'} flex={1} gap={12} horizontal justify={'space-between'} width={'100%'}>
28
+ <Flexbox align={'center'} gap={4} horizontal>
29
+ <ActionIcon icon={ArrowLeft} onClick={() => closeArtifact()} />
30
+ <Typography.Text
31
+ className={cx(oneLineEllipsis)}
32
+ style={{ fontSize: 16 }}
33
+ type={'secondary'}
34
+ >
35
+ {artifactTitle}
36
+ </Typography.Text>
37
+ </Flexbox>
38
+ <ConfigProvider
39
+ theme={{
40
+ token: {
41
+ borderRadiusSM: 16,
42
+ borderRadiusXS: 16,
43
+ fontSize: 12,
44
+ },
45
+ }}
46
+ >
47
+ {isArtifactTagClosed && (
48
+ <Segmented
49
+ onChange={(value: 'code' | 'preview') => {
50
+ useChatStore.setState({ portalArtifactDisplayMode: value });
51
+ }}
52
+ options={[
53
+ {
54
+ icon: <Icon icon={EyeIcon} />,
55
+ label: t('artifacts.display.preview'),
56
+ value: 'preview',
57
+ },
58
+ { icon: <Icon icon={CodeIcon} />, label: t('artifacts.display.code'), value: 'code' },
59
+ ]}
60
+ size={'small'}
61
+ value={displayMode}
62
+ />
63
+ )}
64
+ </ConfigProvider>
65
+ </Flexbox>
66
+ );
67
+ };
68
+
69
+ export default Header;
@@ -0,0 +1,10 @@
1
+ import { PortalImpl } from '../type';
2
+ import Body from './Body';
3
+ import Header from './Header';
4
+ import { useEnable } from './useEnable';
5
+
6
+ export const Artifacts: PortalImpl = {
7
+ Body,
8
+ Header,
9
+ useEnable,
10
+ };
@@ -0,0 +1,4 @@
1
+ import { useChatStore } from '@/store/chat';
2
+ import { chatPortalSelectors } from '@/store/chat/selectors';
3
+
4
+ export const useEnable = () => useChatStore(chatPortalSelectors.showArtifactUI);
@@ -1,8 +1,9 @@
1
+ import { PortalImpl } from '../type';
1
2
  import Body from './Body';
2
3
  import Header from './Header';
3
4
  import { useEnable } from './useEnable';
4
5
 
5
- export const FilePreview = {
6
+ export const FilePreview: PortalImpl = {
6
7
  Body,
7
8
  Header,
8
9
  useEnable,
@@ -11,7 +11,7 @@ export const Artifacts = memo(() => {
11
11
  return (
12
12
  <Flexbox gap={8}>
13
13
  <Typography.Title level={5} style={{ marginInline: 12 }}>
14
- {t('Artifacts')}
14
+ {t('Plugins')}
15
15
  </Typography.Title>
16
16
  <ArtifactList />
17
17
  </Flexbox>
@@ -1,13 +1,13 @@
1
1
  import { Flexbox } from 'react-layout-kit';
2
2
 
3
- import Artifacts from './Artifacts';
4
3
  import Files from './Files';
4
+ import Plugins from './Plugins';
5
5
 
6
6
  const Home = () => {
7
7
  return (
8
8
  <Flexbox gap={12} height={'100%'}>
9
9
  <Files />
10
- <Artifacts />
10
+ <Plugins />
11
11
  </Flexbox>
12
12
  );
13
13
  };
@@ -1,8 +1,9 @@
1
+ import { PortalImpl } from '../type';
1
2
  import Body from './Body';
2
3
  import Header from './Header';
3
4
  import { useEnable } from './useEnable';
4
5
 
5
- export const MessageDetail = {
6
+ export const MessageDetail: PortalImpl = {
6
7
  Body,
7
8
  Header,
8
9
  useEnable,
@@ -8,7 +8,7 @@ import { BuiltinToolsPortals } from '@/tools/portals';
8
8
  import { safeParseJSON } from '@/utils/safeParseJSON';
9
9
 
10
10
  const ToolRender = memo(() => {
11
- const messageId = useChatStore(chatPortalSelectors.artifactMessageId);
11
+ const messageId = useChatStore(chatPortalSelectors.toolMessageId);
12
12
  const message = useChatStore(chatSelectors.getMessageById(messageId || ''), isEqual);
13
13
 
14
14
  // make sure the message and id is valid
@@ -9,7 +9,7 @@ import Footer from '../Footer';
9
9
  import ToolRender from './ToolRender';
10
10
 
11
11
  const ToolUI = () => {
12
- const messageId = useChatStore(chatPortalSelectors.artifactMessageId);
12
+ const messageId = useChatStore(chatPortalSelectors.toolMessageId);
13
13
  const message = useChatStore(chatSelectors.getMessageById(messageId || ''), isEqual);
14
14
 
15
15
  // make sure the message and id is valid
@@ -9,7 +9,7 @@ import { chatPortalSelectors, chatSelectors } from '@/store/chat/selectors';
9
9
 
10
10
  const Footer = () => {
11
11
  const [messageId, isAIGenerating, triggerAIMessage, summaryPluginContent] = useChatStore((s) => [
12
- chatPortalSelectors.artifactMessageId(s),
12
+ chatPortalSelectors.toolMessageId(s),
13
13
  chatSelectors.isAIGenerating(s),
14
14
  s.triggerAIMessage,
15
15
  s.summaryPluginContent,
@@ -1,8 +1,9 @@
1
+ import { PortalImpl } from '../type';
1
2
  import Body from './Body';
2
3
  import Header from './Header';
3
4
  import { useEnable } from './useEnable';
4
5
 
5
- export const Plugins = {
6
+ export const Plugins: PortalImpl = {
6
7
  Body,
7
8
  Header,
8
9
  useEnable,
@@ -2,5 +2,5 @@ import { useChatStore } from '@/store/chat';
2
2
  import { chatPortalSelectors } from '@/store/chat/selectors';
3
3
 
4
4
  export const useEnable = () => {
5
- return useChatStore(chatPortalSelectors.showArtifactUI);
5
+ return useChatStore(chatPortalSelectors.showPluginUI);
6
6
  };
@@ -1,15 +1,13 @@
1
1
  import { PropsWithChildren } from 'react';
2
- import { Flexbox } from 'react-layout-kit';
3
2
 
3
+ import Body from '../features/Body';
4
4
  import Header from '../features/Header';
5
5
 
6
6
  const Layout = ({ children }: PropsWithChildren) => {
7
7
  return (
8
8
  <>
9
9
  <Header />
10
- <Flexbox height={'100%'} style={{ position: 'relative' }} width={'100%'}>
11
- {children}
12
- </Flexbox>
10
+ <Body>{children}</Body>
13
11
  </>
14
12
  );
15
13
  };
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+
3
+ import { css, cx } from 'antd-style';
4
+ import { PropsWithChildren } from 'react';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ const body = css`
8
+ :has(.portal-artifact) {
9
+ overflow: hidden;
10
+ padding-block-end: 12px;
11
+ }
12
+ `;
13
+
14
+ const Body = ({ children }: PropsWithChildren) => {
15
+ return (
16
+ <Flexbox
17
+ className={cx(body, 'portal-body')}
18
+ height={'100%'}
19
+ style={{ position: 'relative' }}
20
+ width={'100%'}
21
+ >
22
+ {children}
23
+ </Flexbox>
24
+ );
25
+ };
26
+
27
+ export default Body;
@@ -2,12 +2,14 @@
2
2
 
3
3
  import { memo } from 'react';
4
4
 
5
+ import { Artifacts } from './Artifacts';
5
6
  import { FilePreview } from './FilePreview';
6
7
  import { HomeBody, HomeHeader } from './Home';
7
8
  import { MessageDetail } from './MessageDetail';
8
9
  import { Plugins } from './Plugins';
10
+ import { PortalImpl } from './type';
9
11
 
10
- const items = [MessageDetail, Plugins, FilePreview];
12
+ const items: PortalImpl[] = [MessageDetail, Artifacts, Plugins, FilePreview];
11
13
 
12
14
  export const PortalHeader = memo(() => {
13
15
  const enabledList: boolean[] = [];
@@ -0,0 +1,7 @@
1
+ import { FC } from 'react';
2
+
3
+ export interface PortalImpl {
4
+ Body: FC;
5
+ Header: FC;
6
+ useEnable: () => boolean;
7
+ }
@@ -39,8 +39,9 @@ const PortalPanel = memo(({ children }: PropsWithChildren) => {
39
39
  const { styles } = useStyles();
40
40
  const { md = true } = useResponsive();
41
41
 
42
- const [showInspector, showToolUI] = useChatStore((s) => [
42
+ const [showInspector, showToolUI, showArtifactUI] = useChatStore((s) => [
43
43
  chatPortalSelectors.showPortal(s),
44
+ chatPortalSelectors.showPluginUI(s),
44
45
  chatPortalSelectors.showArtifactUI(s),
45
46
  ]);
46
47
 
@@ -54,7 +55,7 @@ const PortalPanel = memo(({ children }: PropsWithChildren) => {
54
55
  expand
55
56
  hanlderStyle={{ display: 'none' }}
56
57
  maxWidth={MAX_WIDTH}
57
- minWidth={showToolUI ? CHAT_DOCK_TOOL_UI_WIDTH : CHAT_DOCK_WIDTH}
58
+ minWidth={showArtifactUI || showToolUI ? CHAT_DOCK_TOOL_UI_WIDTH : CHAT_DOCK_WIDTH}
58
59
  mode={md ? 'fixed' : 'float'}
59
60
  placement={'right'}
60
61
  showHandlerWhenUnexpand={false}
@@ -1,12 +1,12 @@
1
1
  'use client';
2
2
 
3
3
  import { Skeleton } from 'antd';
4
+ import Link from 'next/link';
4
5
  import { memo } from 'react';
5
6
  import { useTranslation } from 'react-i18next';
6
7
  import { Flexbox, FlexboxProps } from 'react-layout-kit';
7
8
  import urlJoin from 'url-join';
8
9
 
9
- import InterceptingLink from '@/components/InterceptingLink';
10
10
  import { DiscoverAssistantItem, DiscoverPlugintem } from '@/types/discover';
11
11
 
12
12
  import Block from '../../../../features/Block';
@@ -28,13 +28,13 @@ const InfoSidebar = memo<InfoSidebarProps>(({ pluginData, data, ...rest }) => {
28
28
  {pluginData && pluginData?.length > 0 && (
29
29
  <Block gap={12} title={t('assistants.plugins')}>
30
30
  {pluginData.map((item) => (
31
- <InterceptingLink
31
+ <Link
32
32
  href={urlJoin('/discover/plugin', item.identifier)}
33
33
  key={item.identifier}
34
34
  style={{ color: 'inherit' }}
35
35
  >
36
36
  <ToolItem {...item} />
37
- </InterceptingLink>
37
+ </Link>
38
38
  ))}
39
39
  </Block>
40
40
  )}
@@ -46,12 +46,9 @@ const InfoSidebar = memo<InfoSidebarProps>(({ pluginData, data, ...rest }) => {
46
46
  >
47
47
  {data?.suggestions?.length > 0 ? (
48
48
  data?.suggestions.map((item) => (
49
- <InterceptingLink
50
- href={urlJoin('/discover/assistant', item.identifier)}
51
- key={item.identifier}
52
- >
49
+ <Link href={urlJoin('/discover/assistant', item.identifier)} key={item.identifier}>
53
50
  <SuggestionItem {...item} />
54
- </InterceptingLink>
51
+ </Link>
55
52
  ))
56
53
  ) : (
57
54
  <Skeleton active paragraph={{ rows: 5 }} title={false} />
@@ -8,8 +8,6 @@ import { CSSProperties, memo } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
9
  import { Flexbox } from 'react-layout-kit';
10
10
 
11
- import { useInterceptingRoutes } from '@/hooks/useInterceptingRoutes';
12
-
13
11
  const useStyles = createStyles(({ css, token }) => {
14
12
  return {
15
13
  back: css`
@@ -23,12 +21,9 @@ const useStyles = createStyles(({ css, token }) => {
23
21
  });
24
22
 
25
23
  const Back = memo<{ href: string; style?: CSSProperties }>(({ href, style }) => {
26
- const { isIntercepted } = useInterceptingRoutes();
27
24
  const { t } = useTranslation('discover');
28
25
  const { styles } = useStyles();
29
26
 
30
- if (isIntercepted) return null;
31
-
32
27
  return (
33
28
  <Link className={styles.back} href={href} style={{ marginBottom: 8, ...style }}>
34
29
  <Flexbox align={'center'} gap={8} horizontal>