@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
@@ -23,7 +23,8 @@ export const LobeOpenRouterAI = LobeOpenAICompatibleFactory({
23
23
  description: model.description,
24
24
  displayName: model.name,
25
25
  enabled: LOBE_DEFAULT_MODEL_LIST.find((m) => model.id.endsWith(m.id))?.enabled || false,
26
- functionCall: model.description.includes('function calling'),
26
+ functionCall:
27
+ model.description.includes('function calling') || model.description.includes('tools'),
27
28
  id: model.id,
28
29
  maxTokens:
29
30
  typeof model.top_provider.max_completion_tokens === 'number'
@@ -22,6 +22,7 @@ export interface CreateChatCompletionOptions {
22
22
  }
23
23
 
24
24
  export enum ModelProvider {
25
+ Ai21 = 'ai21',
25
26
  Ai360 = 'ai360',
26
27
  Anthropic = 'anthropic',
27
28
  Azure = 'azure',
@@ -29,6 +30,7 @@ export enum ModelProvider {
29
30
  Bedrock = 'bedrock',
30
31
  DeepSeek = 'deepseek',
31
32
  FireworksAI = 'fireworksai',
33
+ Github = 'github',
32
34
  Google = 'google',
33
35
  Groq = 'groq',
34
36
  Minimax = 'minimax',
@@ -7,6 +7,12 @@ export default {
7
7
  agentDefaultMessageWithSystemRole: '你好,我是 **{{name}}**,{{systemRole}},让我们开始对话吧!',
8
8
  agentDefaultMessageWithoutEdit: '你好,我是 **{{name}}**,让我们开始对话吧!',
9
9
  agentsAndConversations: '助手与会话',
10
+ artifact: {
11
+ generating: '生成中',
12
+ thinking: '思考中',
13
+ thought: '思考过程',
14
+ unknownTitle: '未命名作品',
15
+ },
10
16
  backToBottom: '跳转至当前',
11
17
  chatList: {
12
18
  longMessageDetail: '查看详情',
@@ -160,6 +166,7 @@ export default {
160
166
  clear: '删除语音',
161
167
  },
162
168
  updateAgent: '更新助理信息',
169
+
163
170
  upload: {
164
171
  action: {
165
172
  fileUpload: '上传文件',
@@ -107,6 +107,9 @@ export default {
107
107
  SubscriptionPlanLimit:
108
108
  '您的订阅额度已用尽,无法使用该功能,请升级到更高计划,或购买资源包后继续使用',
109
109
 
110
+ // Github Token
111
+ InvalidGithubToken: 'Github PAT 不正确或为空,请检查 Github PAT 后重试',
112
+
110
113
  /* eslint-enable */
111
114
  },
112
115
  stt: {
@@ -52,6 +52,13 @@ export default {
52
52
  title: '使用自定义 Bedrock 鉴权信息',
53
53
  },
54
54
  },
55
+ github: {
56
+ personalAccessToken: {
57
+ desc: '填入你的 Github PAT,点击[这里](https://github.com/settings/tokens) 创建',
58
+ placeholder: 'ghp_xxxxxx',
59
+ title: 'Github PAT',
60
+ },
61
+ },
55
62
  ollama: {
56
63
  checker: {
57
64
  desc: '测试代理地址是否正确填写',
@@ -6,13 +6,29 @@ export default {
6
6
  file: '文件',
7
7
  },
8
8
  },
9
+ Plugins: '插件',
9
10
  actions: {
10
11
  genAiMessage: '创建助手消息',
11
12
  summary: '总结',
12
13
  summaryTooltip: '总结当前内容',
13
14
  },
15
+ artifacts: {
16
+ display: {
17
+ code: '代码',
18
+ preview: '预览',
19
+ },
20
+ svg: {
21
+ copyAsImage: '复制为图片',
22
+ copyFail: '复制失败,错误原因:{{error}}',
23
+ copySuccess: '图片复制成功',
24
+ download: {
25
+ png: '下载为 PNG',
26
+ svg: '下载为 SVG',
27
+ },
28
+ },
29
+ },
14
30
  emptyArtifactList: '当前 Artifacts 列表为空,请在会话中按需使用插件后再查看',
15
- emptyKnowledgeList: '当前知识列表为空,请在会话中按需开启知识库后再查看',
31
+ emptyKnowledgeList: '当前知识列表为空',
16
32
  files: '文件',
17
33
  messageDetail: '消息详情',
18
34
  title: '工作区',
@@ -6,6 +6,7 @@ import { getLLMConfig } from '@/config/llm';
6
6
  import {
7
7
  BedrockProviderCard,
8
8
  FireworksAIProviderCard,
9
+ GithubProviderCard,
9
10
  GoogleProviderCard,
10
11
  GroqProviderCard,
11
12
  NovitaProviderCard,
@@ -45,6 +46,9 @@ export const getServerGlobalConfig = () => {
45
46
  ENABLED_GROQ,
46
47
  GROQ_MODEL_LIST,
47
48
 
49
+ ENABLED_GITHUB,
50
+ GITHUB_MODEL_LIST,
51
+
48
52
  ENABLED_DEEPSEEK,
49
53
  ENABLED_PERPLEXITY,
50
54
  ENABLED_ANTHROPIC,
@@ -60,6 +64,7 @@ export const getServerGlobalConfig = () => {
60
64
  ENABLED_STEPFUN,
61
65
  ENABLED_BAICHUAN,
62
66
  ENABLED_TAICHU,
67
+ ENABLED_AI21,
63
68
  ENABLED_AI360,
64
69
 
65
70
  ENABLED_SILICONCLOUD,
@@ -97,6 +102,7 @@ export const getServerGlobalConfig = () => {
97
102
  enabledAccessCode: ACCESS_CODES?.length > 0,
98
103
  enabledOAuthSSO: enableNextAuth,
99
104
  languageModel: {
105
+ ai21: { enabled: ENABLED_AI21 },
100
106
  ai360: { enabled: ENABLED_AI360 },
101
107
  anthropic: {
102
108
  enabled: ENABLED_ANTHROPIC,
@@ -130,6 +136,14 @@ export const getServerGlobalConfig = () => {
130
136
  }),
131
137
  },
132
138
 
139
+ github: {
140
+ enabled: ENABLED_GITHUB,
141
+ enabledModels: extractEnabledModels(GITHUB_MODEL_LIST),
142
+ serverModelCards: transformToChatModelCards({
143
+ defaultChatModels: GithubProviderCard.chatModels,
144
+ modelString: GITHUB_MODEL_LIST,
145
+ }),
146
+ },
133
147
  google: {
134
148
  enabled: ENABLED_GOOGLE,
135
149
  enabledModels: extractEnabledModels(GOOGLE_MODEL_LIST),
@@ -16,7 +16,7 @@ import { MetaData } from '@/types/meta';
16
16
  import { merge } from '@/utils/merge';
17
17
 
18
18
  import { chatHelpers } from '../../helpers';
19
- import type { ChatStore } from '../../store';
19
+ import type { ChatStoreState } from '../../initialState';
20
20
 
21
21
  const getMeta = (message: ChatMessage) => {
22
22
  switch (message.role) {
@@ -36,10 +36,10 @@ const getMeta = (message: ChatMessage) => {
36
36
  }
37
37
  };
38
38
 
39
- const currentChatKey = (s: ChatStore) => messageMapKey(s.activeId, s.activeTopicId);
39
+ const currentChatKey = (s: ChatStoreState) => messageMapKey(s.activeId, s.activeTopicId);
40
40
 
41
41
  // 当前激活的消息列表
42
- const currentChats = (s: ChatStore): ChatMessage[] => {
42
+ const currentChats = (s: ChatStoreState): ChatMessage[] => {
43
43
  if (!s.activeId) return [];
44
44
 
45
45
  const messages = s.messagesMap[currentChatKey(s)] || [];
@@ -47,19 +47,19 @@ const currentChats = (s: ChatStore): ChatMessage[] => {
47
47
  return messages.map((i) => ({ ...i, meta: getMeta(i) }));
48
48
  };
49
49
 
50
- const currentToolMessages = (s: ChatStore) => {
50
+ const currentToolMessages = (s: ChatStoreState) => {
51
51
  const messages = currentChats(s);
52
52
 
53
53
  return messages.filter((m) => m.role === 'tool');
54
54
  };
55
55
 
56
- const currentUserMessages = (s: ChatStore) => {
56
+ const currentUserMessages = (s: ChatStoreState) => {
57
57
  const messages = currentChats(s);
58
58
 
59
59
  return messages.filter((m) => m.role === 'user');
60
60
  };
61
61
 
62
- const currentUserFiles = (s: ChatStore) => {
62
+ const currentUserFiles = (s: ChatStoreState) => {
63
63
  const userMessages = currentUserMessages(s);
64
64
 
65
65
  return userMessages
@@ -70,7 +70,7 @@ const currentUserFiles = (s: ChatStore) => {
70
70
 
71
71
  const initTime = Date.now();
72
72
 
73
- const showInboxWelcome = (s: ChatStore): boolean => {
73
+ const showInboxWelcome = (s: ChatStoreState): boolean => {
74
74
  const isInbox = s.activeId === INBOX_SESSION_ID;
75
75
  if (!isInbox) return false;
76
76
 
@@ -83,7 +83,7 @@ const showInboxWelcome = (s: ChatStore): boolean => {
83
83
  // Custom message for new assistant initialization
84
84
  const currentChatsWithGuideMessage =
85
85
  (meta: MetaData) =>
86
- (s: ChatStore): ChatMessage[] => {
86
+ (s: ChatStoreState): ChatMessage[] => {
87
87
  // skip tool message
88
88
  const data = currentChats(s).filter((m) => m.role !== 'tool');
89
89
 
@@ -120,47 +120,49 @@ const currentChatsWithGuideMessage =
120
120
  return [emptyInboxGuideMessage];
121
121
  };
122
122
 
123
- const currentChatIDsWithGuideMessage = (s: ChatStore) => {
123
+ const currentChatIDsWithGuideMessage = (s: ChatStoreState) => {
124
124
  const meta = sessionMetaSelectors.currentAgentMeta(useSessionStore.getState());
125
125
 
126
126
  return currentChatsWithGuideMessage(meta)(s).map((s) => s.id);
127
127
  };
128
128
 
129
- const currentChatsWithHistoryConfig = (s: ChatStore): ChatMessage[] => {
129
+ const currentChatsWithHistoryConfig = (s: ChatStoreState): ChatMessage[] => {
130
130
  const chats = currentChats(s);
131
131
  const config = agentSelectors.currentAgentChatConfig(useAgentStore.getState());
132
132
 
133
133
  return chatHelpers.getSlicedMessagesWithConfig(chats, config);
134
134
  };
135
135
 
136
- const chatsMessageString = (s: ChatStore): string => {
136
+ const chatsMessageString = (s: ChatStoreState): string => {
137
137
  const chats = currentChatsWithHistoryConfig(s);
138
138
  return chats.map((m) => m.content).join('');
139
139
  };
140
140
 
141
- const getMessageById = (id: string) => (s: ChatStore) =>
141
+ const getMessageById = (id: string) => (s: ChatStoreState) =>
142
142
  chatHelpers.getMessageById(currentChats(s), id);
143
143
 
144
- const getMessageByToolCallId = (id: string) => (s: ChatStore) => {
144
+ const getMessageByToolCallId = (id: string) => (s: ChatStoreState) => {
145
145
  const messages = currentChats(s);
146
146
  return messages.find((m) => m.tool_call_id === id);
147
147
  };
148
- const getTraceIdByMessageId = (id: string) => (s: ChatStore) => getMessageById(id)(s)?.traceId;
148
+ const getTraceIdByMessageId = (id: string) => (s: ChatStoreState) => getMessageById(id)(s)?.traceId;
149
149
 
150
- const latestMessage = (s: ChatStore) => currentChats(s).at(-1);
150
+ const latestMessage = (s: ChatStoreState) => currentChats(s).at(-1);
151
151
 
152
- const currentChatLoadingState = (s: ChatStore) => !s.messagesInit;
152
+ const currentChatLoadingState = (s: ChatStoreState) => !s.messagesInit;
153
153
 
154
- const isCurrentChatLoaded = (s: ChatStore) => !!s.messagesMap[currentChatKey(s)];
154
+ const isCurrentChatLoaded = (s: ChatStoreState) => !!s.messagesMap[currentChatKey(s)];
155
155
 
156
- const isMessageEditing = (id: string) => (s: ChatStore) => s.messageEditingIds.includes(id);
157
- const isMessageLoading = (id: string) => (s: ChatStore) => s.messageLoadingIds.includes(id);
156
+ const isMessageEditing = (id: string) => (s: ChatStoreState) => s.messageEditingIds.includes(id);
157
+ const isMessageLoading = (id: string) => (s: ChatStoreState) => s.messageLoadingIds.includes(id);
158
158
 
159
- const isMessageGenerating = (id: string) => (s: ChatStore) => s.chatLoadingIds.includes(id);
160
- const isMessageInRAGFlow = (id: string) => (s: ChatStore) => s.messageRAGLoadingIds.includes(id);
161
- const isPluginApiInvoking = (id: string) => (s: ChatStore) => s.pluginApiLoadingIds.includes(id);
159
+ const isMessageGenerating = (id: string) => (s: ChatStoreState) => s.chatLoadingIds.includes(id);
160
+ const isMessageInRAGFlow = (id: string) => (s: ChatStoreState) =>
161
+ s.messageRAGLoadingIds.includes(id);
162
+ const isPluginApiInvoking = (id: string) => (s: ChatStoreState) =>
163
+ s.pluginApiLoadingIds.includes(id);
162
164
 
163
- const isToolCallStreaming = (id: string, index: number) => (s: ChatStore) => {
165
+ const isToolCallStreaming = (id: string, index: number) => (s: ChatStoreState) => {
164
166
  const isLoading = s.toolCallingStreamIds[id];
165
167
 
166
168
  if (!isLoading) return false;
@@ -168,15 +170,15 @@ const isToolCallStreaming = (id: string, index: number) => (s: ChatStore) => {
168
170
  return isLoading[index];
169
171
  };
170
172
 
171
- const isAIGenerating = (s: ChatStore) => s.chatLoadingIds.length > 0;
172
- const isInRAGFlow = (s: ChatStore) => s.messageRAGLoadingIds.length > 0;
173
- const isCreatingMessage = (s: ChatStore) => s.isCreatingMessage;
174
- const isHasMessageLoading = (s: ChatStore) => s.messageLoadingIds.length > 0;
173
+ const isAIGenerating = (s: ChatStoreState) => s.chatLoadingIds.length > 0;
174
+ const isInRAGFlow = (s: ChatStoreState) => s.messageRAGLoadingIds.length > 0;
175
+ const isCreatingMessage = (s: ChatStoreState) => s.isCreatingMessage;
176
+ const isHasMessageLoading = (s: ChatStoreState) => s.messageLoadingIds.length > 0;
175
177
 
176
178
  /**
177
179
  * this function is used to determine whether the send button should be disabled
178
180
  */
179
- const isSendButtonDisabledByMessage = (s: ChatStore) =>
181
+ const isSendButtonDisabledByMessage = (s: ChatStoreState) =>
180
182
  // 1. when there is message loading
181
183
  isHasMessageLoading(s) ||
182
184
  // 2. when is creating the topic
@@ -2,16 +2,17 @@ import { StateCreator } from 'zustand/vanilla';
2
2
 
3
3
  import { ChatStore } from '@/store/chat/store';
4
4
 
5
- import { PortalFile } from './initialState';
5
+ import { PortalArtifact, PortalFile } from './initialState';
6
6
 
7
7
  export interface ChatPortalAction {
8
+ closeArtifact: () => void;
8
9
  closeFilePreview: () => void;
9
10
  closeMessageDetail: () => void;
10
11
  closeToolUI: () => void;
12
+ openArtifact: (artifact: PortalArtifact) => void;
11
13
  openFilePreview: (portal: PortalFile) => void;
12
14
  openMessageDetail: (messageId: string) => void;
13
15
  openToolUI: (messageId: string, identifier: string) => void;
14
-
15
16
  togglePortal: (open?: boolean) => void;
16
17
  }
17
18
 
@@ -21,6 +22,10 @@ export const chatPortalSlice: StateCreator<
21
22
  [],
22
23
  ChatPortalAction
23
24
  > = (set, get) => ({
25
+ closeArtifact: () => {
26
+ get().togglePortal(false);
27
+ set({ portalArtifact: undefined }, false, 'closeArtifact');
28
+ },
24
29
  closeFilePreview: () => {
25
30
  set({ portalFile: undefined }, false, 'closeFilePreview');
26
31
  },
@@ -30,6 +35,11 @@ export const chatPortalSlice: StateCreator<
30
35
  closeToolUI: () => {
31
36
  set({ portalToolMessage: undefined }, false, 'closeToolUI');
32
37
  },
38
+ openArtifact: (artifact) => {
39
+ get().togglePortal(true);
40
+
41
+ set({ portalArtifact: artifact }, false, 'openArtifact');
42
+ },
33
43
  openFilePreview: (portal) => {
34
44
  get().togglePortal(true);
35
45
 
@@ -49,4 +59,7 @@ export const chatPortalSlice: StateCreator<
49
59
  const showInspector = open === undefined ? !get().showPortal : open;
50
60
  set({ showPortal: showInspector }, false, 'toggleInspector');
51
61
  },
62
+ // updateArtifactContent: (content) => {
63
+ // set({ portalArtifact: content }, false, 'updateArtifactContent');
64
+ // },
52
65
  });
@@ -4,7 +4,17 @@ export interface PortalFile {
4
4
  fileId: string;
5
5
  }
6
6
 
7
+ export interface PortalArtifact {
8
+ children?: string;
9
+ id: string;
10
+ identifier?: string;
11
+ title?: string;
12
+ type?: string;
13
+ }
14
+
7
15
  export interface ChatPortalState {
16
+ portalArtifact?: PortalArtifact;
17
+ portalArtifactDisplayMode?: 'code' | 'preview';
8
18
  portalFile?: PortalFile;
9
19
  portalMessageDetail?: string;
10
20
  portalToolMessage?: { id: string; identifier: string };
@@ -12,5 +22,6 @@ export interface ChatPortalState {
12
22
  }
13
23
 
14
24
  export const initialChatPortalState: ChatPortalState = {
25
+ portalArtifactDisplayMode: 'preview',
15
26
  showPortal: false,
16
27
  };
@@ -21,12 +21,12 @@ describe('chatDockSelectors', () => {
21
21
 
22
22
  describe('toolUIMessageId', () => {
23
23
  it('should return undefined when dockToolMessage is not set', () => {
24
- expect(chatPortalSelectors.artifactMessageId(createState())).toBeUndefined();
24
+ expect(chatPortalSelectors.toolMessageId(createState())).toBeUndefined();
25
25
  });
26
26
 
27
27
  it('should return the id when dockToolMessage is set', () => {
28
28
  const state = createState({ portalToolMessage: { id: 'test-id', identifier: 'test' } });
29
- expect(chatPortalSelectors.artifactMessageId(state)).toBe('test-id');
29
+ expect(chatPortalSelectors.toolMessageId(state)).toBe('test-id');
30
30
  });
31
31
  });
32
32
 
@@ -36,8 +36,8 @@ describe('chatDockSelectors', () => {
36
36
  portalToolMessage: { id: 'test-id', identifier: 'test' },
37
37
  showPortal: false,
38
38
  });
39
- expect(chatPortalSelectors.isArtifactMessageUIOpen('test-id')(state)).toBe(false);
40
- expect(chatPortalSelectors.isArtifactMessageUIOpen('other-id')(state)).toBe(false);
39
+ expect(chatPortalSelectors.isPluginUIOpen('test-id')(state)).toBe(false);
40
+ expect(chatPortalSelectors.isPluginUIOpen('other-id')(state)).toBe(false);
41
41
  });
42
42
 
43
43
  it('should return true when id matches and showDock is true', () => {
@@ -45,18 +45,18 @@ describe('chatDockSelectors', () => {
45
45
  portalToolMessage: { id: 'test-id', identifier: 'test' },
46
46
  showPortal: true,
47
47
  });
48
- expect(chatPortalSelectors.isArtifactMessageUIOpen('test-id')(state)).toBe(true);
48
+ expect(chatPortalSelectors.isPluginUIOpen('test-id')(state)).toBe(true);
49
49
  });
50
50
  });
51
51
 
52
52
  describe('showToolUI', () => {
53
53
  it('should return false when dockToolMessage is not set', () => {
54
- expect(chatPortalSelectors.showArtifactUI(createState())).toBe(false);
54
+ expect(chatPortalSelectors.showPluginUI(createState())).toBe(false);
55
55
  });
56
56
 
57
57
  it('should return true when dockToolMessage is set', () => {
58
58
  const state = createState({ portalToolMessage: { id: 'test-id', identifier: 'test' } });
59
- expect(chatPortalSelectors.showArtifactUI(state)).toBe(true);
59
+ expect(chatPortalSelectors.showPluginUI(state)).toBe(true);
60
60
  });
61
61
  });
62
62
 
@@ -70,4 +70,26 @@ describe('chatDockSelectors', () => {
70
70
  expect(chatPortalSelectors.toolUIIdentifier(state)).toBe('test');
71
71
  });
72
72
  });
73
+
74
+ describe('showFilePreview', () => {
75
+ it('should return false when portalFile is not set', () => {
76
+ expect(chatPortalSelectors.showFilePreview(createState())).toBe(false);
77
+ });
78
+
79
+ it('should return true when portalFile is set', () => {
80
+ const state = createState({ portalFile: { fileId: 'file-id', chunkText: 'chunk' } });
81
+ expect(chatPortalSelectors.showFilePreview(state)).toBe(true);
82
+ });
83
+ });
84
+
85
+ describe('previewFileId', () => {
86
+ it('should return undefined when portalFile is not set', () => {
87
+ expect(chatPortalSelectors.previewFileId(createState())).toBeUndefined();
88
+ });
89
+
90
+ it('should return the fileId when portalFile is set', () => {
91
+ const state = createState({ portalFile: { fileId: 'file-id', chunkText: 'chunk' } });
92
+ expect(chatPortalSelectors.previewFileId(state)).toBe('file-id');
93
+ });
94
+ });
73
95
  });
@@ -1,27 +1,71 @@
1
+ import { ARTIFACT_TAG_CLOSED_REGEX, ARTIFACT_TAG_REGEX } from '@/const/plugin';
1
2
  import type { ChatStoreState } from '@/store/chat';
2
3
 
3
- const artifactMessageId = (s: ChatStoreState) => s.portalToolMessage?.id;
4
+ import { chatSelectors } from '../message/selectors';
5
+
4
6
  const showPortal = (s: ChatStoreState) => s.showPortal;
5
7
 
6
- const isArtifactMessageUIOpen = (id: string) => (s: ChatStoreState) =>
7
- artifactMessageId(s) === id && showPortal(s);
8
+ const showMessageDetail = (s: ChatStoreState) => !!s.portalMessageDetail;
9
+ const messageDetailId = (s: ChatStoreState) => s.portalMessageDetail;
10
+
11
+ const showPluginUI = (s: ChatStoreState) => !!s.portalToolMessage;
12
+
13
+ const toolMessageId = (s: ChatStoreState) => s.portalToolMessage?.id;
14
+ const isPluginUIOpen = (id: string) => (s: ChatStoreState) =>
15
+ toolMessageId(s) === id && showPortal(s);
16
+ const toolUIIdentifier = (s: ChatStoreState) => s.portalToolMessage?.identifier;
8
17
 
9
- const showArtifactUI = (s: ChatStoreState) => !!s.portalToolMessage;
10
18
  const showFilePreview = (s: ChatStoreState) => !!s.portalFile;
11
- const showMessageDetail = (s: ChatStoreState) => !!s.portalMessageDetail;
12
19
  const previewFileId = (s: ChatStoreState) => s.portalFile?.fileId;
13
- const messageDetailId = (s: ChatStoreState) => s.portalMessageDetail;
14
20
  const chunkText = (s: ChatStoreState) => s.portalFile?.chunkText;
15
21
 
22
+ const showArtifactUI = (s: ChatStoreState) => !!s.portalArtifact;
23
+ const artifactTitle = (s: ChatStoreState) => s.portalArtifact?.title;
24
+ const artifactIdentifier = (s: ChatStoreState) => s.portalArtifact?.identifier || '';
25
+ const artifactMessageId = (s: ChatStoreState) => s.portalArtifact?.id;
26
+ const artifactType = (s: ChatStoreState) => s.portalArtifact?.type;
27
+
28
+ const artifactMessageContent = (id: string) => (s: ChatStoreState) => {
29
+ const message = chatSelectors.getMessageById(id)(s);
30
+ return message?.content || '';
31
+ };
32
+
33
+ const artifactCode = (id: string) => (s: ChatStoreState) => {
34
+ const messageContent = artifactMessageContent(id)(s);
35
+ const result = messageContent.match(ARTIFACT_TAG_REGEX);
36
+
37
+ return result?.groups?.content || '';
38
+ };
39
+
40
+ const isArtifactTagClosed = (id: string) => (s: ChatStoreState) => {
41
+ const content = artifactMessageContent(id)(s);
42
+
43
+ return ARTIFACT_TAG_CLOSED_REGEX.test(content || '');
44
+ };
45
+
46
+ /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
16
47
  export const chatPortalSelectors = {
17
- artifactMessageId,
18
- chunkText,
19
- isArtifactMessageUIOpen,
20
- messageDetailId,
48
+ isPluginUIOpen,
49
+
21
50
  previewFileId,
22
- showArtifactUI,
23
51
  showFilePreview,
52
+ chunkText,
53
+
54
+ messageDetailId,
24
55
  showMessageDetail,
56
+
57
+ showPluginUI,
25
58
  showPortal,
26
- toolUIIdentifier: (s: ChatStoreState) => s.portalToolMessage?.identifier,
59
+
60
+ toolMessageId,
61
+ toolUIIdentifier,
62
+
63
+ showArtifactUI,
64
+ artifactTitle,
65
+ artifactIdentifier,
66
+ artifactMessageId,
67
+ artifactType,
68
+ artifactCode,
69
+ artifactMessageContent,
70
+ isArtifactTagClosed,
27
71
  };
@@ -0,0 +1,28 @@
1
+ import { css } from 'antd-style';
2
+
3
+ export const dotLoading = css`
4
+ &::after {
5
+ content: '\\2026'; /* ascii code for the ellipsis character */
6
+
7
+ overflow: hidden;
8
+ display: inline-block;
9
+
10
+ width: 0;
11
+
12
+ vertical-align: bottom;
13
+
14
+ animation: ellipsis steps(4, end) 900ms infinite;
15
+ }
16
+
17
+ @keyframes ellipsis {
18
+ to {
19
+ width: 1.25em;
20
+ }
21
+ }
22
+
23
+ @keyframes ellipsis {
24
+ to {
25
+ width: 1.25em;
26
+ }
27
+ }
28
+ `;
@@ -0,0 +1,13 @@
1
+ import { systemPrompt } from '@/tools/artifacts/systemRole';
2
+ import { BuiltinToolManifest } from '@/types/tool';
3
+
4
+ export const ArtifactsManifest: BuiltinToolManifest = {
5
+ api: [],
6
+ identifier: 'lobe-artifacts',
7
+ meta: {
8
+ avatar: `data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNCIgaGVpZ2h0PSIxMDI0IiBmaWxsPSJub25lIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxMDI0IiBoZWlnaHQ9IjEwMjQiIHJ4PSI1MTIiIGZpbGw9IiNDNDc4NUIiLz48cGF0aCBkPSJNNTEyIDYxMmM1NS4yMjggMCAxMDAtNDQuNzcyIDEwMC0xMDBzLTQ0Ljc3Mi0xMDAtMTAwLTEwMC0xMDAgNDQuNzcyLTEwMCAxMDAgNDQuNzcyIDEwMCAxMDAgMTAwWiIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjU2IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBkPSJNNTEyIDY2MmExNDkuOTk4IDE0OS45OTggMCAwIDEtOTIuNTk3IDEzOC41ODIgMTUwLjAwMiAxNTAuMDAyIDAgMCAxLTIwNC41MjEtMTA5LjMxOCAxNDkuOTk4IDE0OS45OTggMCAwIDEgNjMuNzgzLTE1My45ODRBMTQ5Ljk5MiAxNDkuOTkyIDAgMCAxIDM2MiA1MTJhMTQ5Ljk5OSAxNDkuOTk5IDAgMSAxIDE1MC0xNTAgMTQ5Ljk5OCAxNDkuOTk4IDAgMCAxIDI5Ny4xMTgtMjkuMjYzQTE1MCAxNTAgMCAwIDEgNjYyIDUxMmMyOS42NjcgMCA1OC42NjggOC43OTcgODMuMzM1IDI1LjI4YTE0OS45OTUgMTQ5Ljk5NSAwIDAgMSA2My43ODMgMTUzLjk4NEExNTAgMTUwIDAgMCAxIDUxMiA2NjJaTTUxMiAzNjJ2NTBNMzYyIDUxMmg1ME02NjIgNTEyaC01ME01MTIgNjYydi01ME0zNzguNjY3IDM3OC42NjdsNjIuNjY2IDYyLjY2Nk01ODIuNjY3IDQ0MS4zMzNsNjIuNjY2LTYyLjY2Nk0zNzguNjY3IDY0NS4zMzNsNjIuNjY2LTYyLjY2Nk01ODIuNjY3IDU4Mi42NjdsNjIuNjY2IDYyLjY2NiIgc3Ryb2tlPSIjZmZmIiBzdHJva2Utd2lkdGg9IjU2IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48L3N2Zz4=`,
9
+ title: 'Artifacts',
10
+ },
11
+ systemRole: systemPrompt,
12
+ type: 'builtin',
13
+ };