@lobehub/chat 1.18.2 → 1.19.1

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 (154) 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 -2
  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)/settings/llm/ProviderList/Github/index.tsx +53 -0
  96. package/src/app/(main)/settings/llm/ProviderList/providers.tsx +6 -1
  97. package/src/app/api/chat/agentRuntime.ts +14 -0
  98. package/src/components/SidebarHeader/index.tsx +1 -1
  99. package/src/config/llm.ts +14 -0
  100. package/src/config/modelProviders/ai21.ts +37 -0
  101. package/src/config/modelProviders/anthropic.ts +4 -0
  102. package/src/config/modelProviders/github.ts +209 -0
  103. package/src/config/modelProviders/index.ts +8 -0
  104. package/src/config/modelProviders/mistral.ts +38 -21
  105. package/src/config/modelProviders/upstage.ts +9 -9
  106. package/src/const/layoutTokens.ts +1 -1
  107. package/src/const/plugin.test.ts +80 -0
  108. package/src/const/plugin.ts +12 -0
  109. package/src/const/settings/llm.ts +10 -0
  110. package/src/features/Conversation/Error/APIKeyForm/index.tsx +4 -0
  111. package/src/features/Conversation/Messages/Tool/Inspector/index.tsx +1 -1
  112. package/src/features/Conversation/Messages/Tool/index.tsx +1 -1
  113. package/src/features/Conversation/components/ChatItem/index.tsx +24 -2
  114. package/src/features/Conversation/components/ChatItem/utils.test.ts +150 -0
  115. package/src/features/Conversation/components/ChatItem/utils.ts +28 -0
  116. package/src/features/Conversation/components/MarkdownElements/LobeArtifact/Render/Icon.tsx +96 -0
  117. package/src/features/Conversation/components/MarkdownElements/LobeArtifact/Render/index.tsx +129 -0
  118. package/src/features/Conversation/components/MarkdownElements/LobeArtifact/index.ts +10 -0
  119. package/src/features/Conversation/components/MarkdownElements/LobeArtifact/rehypePlugin.ts +74 -0
  120. package/src/features/Conversation/components/MarkdownElements/LobeThinking/Render.tsx +86 -0
  121. package/src/features/Conversation/components/MarkdownElements/LobeThinking/index.ts +12 -0
  122. package/src/features/Conversation/components/MarkdownElements/LobeThinking/rehypePlugin.test.ts +124 -0
  123. package/src/features/Conversation/components/MarkdownElements/LobeThinking/rehypePlugin.ts +51 -0
  124. package/src/features/Conversation/components/MarkdownElements/index.ts +4 -0
  125. package/src/features/Conversation/components/MarkdownElements/type.ts +7 -0
  126. package/src/libs/agent-runtime/AgentRuntime.ts +14 -0
  127. package/src/libs/agent-runtime/ai21/index.test.ts +255 -0
  128. package/src/libs/agent-runtime/ai21/index.ts +18 -0
  129. package/src/libs/agent-runtime/error.ts +2 -0
  130. package/src/libs/agent-runtime/github/index.test.ts +246 -0
  131. package/src/libs/agent-runtime/github/index.ts +15 -0
  132. package/src/libs/agent-runtime/openrouter/__snapshots__/index.test.ts.snap +2215 -28
  133. package/src/libs/agent-runtime/openrouter/fixtures/models.json +3345 -37
  134. package/src/libs/agent-runtime/openrouter/index.ts +2 -1
  135. package/src/libs/agent-runtime/types/type.ts +2 -0
  136. package/src/locales/default/chat.ts +7 -0
  137. package/src/locales/default/error.ts +3 -0
  138. package/src/locales/default/modelProvider.ts +7 -0
  139. package/src/locales/default/portal.ts +17 -1
  140. package/src/server/globalConfig/index.ts +14 -0
  141. package/src/store/chat/slices/message/selectors.ts +30 -28
  142. package/src/store/chat/slices/portal/action.ts +15 -2
  143. package/src/store/chat/slices/portal/initialState.ts +11 -0
  144. package/src/store/chat/slices/portal/selectors.test.ts +29 -7
  145. package/src/store/chat/slices/portal/selectors.ts +56 -12
  146. package/src/styles/loading.ts +28 -0
  147. package/src/tools/artifacts/index.ts +13 -0
  148. package/src/tools/artifacts/systemRole.ts +338 -0
  149. package/src/tools/index.ts +6 -0
  150. package/src/types/user/settings/keyVaults.ts +2 -0
  151. package/src/utils/clipboard.ts +53 -0
  152. /package/src/app/(main)/chat/(workspace)/@portal/Home/Body/{Artifacts → Plugins}/ArtifactList/Item/index.tsx +0 -0
  153. /package/src/app/(main)/chat/(workspace)/@portal/Home/Body/{Artifacts → Plugins}/ArtifactList/Item/style.ts +0 -0
  154. /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.2",
3
+ "version": "1.19.1",
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",
@@ -109,9 +109,10 @@
109
109
  "@azure/core-rest-pipeline": "1.16.0",
110
110
  "@azure/openai": "1.0.0-beta.12",
111
111
  "@cfworker/json-schema": "^2.0.0",
112
- "@clerk/localizations": "2.0.0",
112
+ "@clerk/localizations": "^3.0.4",
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",
@@ -256,6 +257,7 @@
256
257
  "@types/semver": "^7.5.8",
257
258
  "@types/systemjs": "^6.13.5",
258
259
  "@types/ua-parser-js": "^0.7.39",
260
+ "@types/unist": "^3.0.3",
259
261
  "@types/uuid": "^10.0.0",
260
262
  "@types/ws": "^8.5.12",
261
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}
@@ -0,0 +1,53 @@
1
+ 'use client';
2
+
3
+ import { Markdown } from '@lobehub/ui';
4
+ import { Input } from 'antd';
5
+ import { createStyles } from 'antd-style';
6
+ import { useTranslation } from 'react-i18next';
7
+
8
+ import { GithubProviderCard } from '@/config/modelProviders';
9
+ import { GlobalLLMProviderKey } from '@/types/user/settings';
10
+
11
+ import { KeyVaultsConfigKey, LLMProviderApiTokenKey } from '../../const';
12
+ import { ProviderItem } from '../../type';
13
+
14
+ const useStyles = createStyles(({ css, token }) => ({
15
+ markdown: css`
16
+ p {
17
+ color: ${token.colorTextDescription} !important;
18
+ }
19
+ `,
20
+ tip: css`
21
+ font-size: 12px;
22
+ color: ${token.colorTextDescription};
23
+ `,
24
+ }));
25
+
26
+ const providerKey: GlobalLLMProviderKey = 'github';
27
+
28
+ // Same as OpenAIProvider, but replace API Key with Github Personal Access Token
29
+ export const useGithubProvider = (): ProviderItem => {
30
+ const { t } = useTranslation('modelProvider');
31
+ const { styles } = useStyles();
32
+
33
+ return {
34
+ ...GithubProviderCard,
35
+ apiKeyItems: [
36
+ {
37
+ children: (
38
+ <Input.Password
39
+ autoComplete={'new-password'}
40
+ placeholder={t(`${providerKey}.personalAccessToken.placeholder`)}
41
+ />
42
+ ),
43
+ desc: (
44
+ <Markdown className={styles.markdown} fontSize={12} variant={'chat'}>
45
+ {t(`${providerKey}.personalAccessToken.desc`)}
46
+ </Markdown>
47
+ ),
48
+ label: t(`${providerKey}.personalAccessToken.title`),
49
+ name: [KeyVaultsConfigKey, providerKey, LLMProviderApiTokenKey],
50
+ },
51
+ ],
52
+ };
53
+ };