@lobehub/lobehub 2.0.0-next.216 → 2.0.0-next.218

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.
Files changed (28) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/changelog/v1.json +17 -0
  3. package/package.json +1 -1
  4. package/packages/builtin-tool-cloud-sandbox/src/ExecutionRuntime/index.ts +18 -31
  5. package/packages/builtin-tool-cloud-sandbox/src/types.ts +3 -3
  6. package/packages/utils/src/server/index.ts +0 -1
  7. package/src/app/[variants]/(main)/chat/profile/features/EditorCanvas/TypoBar.tsx +1 -11
  8. package/src/app/[variants]/(main)/group/profile/features/EditorCanvas/TypoBar.tsx +1 -11
  9. package/src/app/[variants]/(main)/image/features/PromptInput/index.tsx +44 -0
  10. package/src/app/[variants]/(main)/memory/features/EditableModal/index.tsx +8 -101
  11. package/src/features/ChatInput/InputEditor/index.tsx +1 -0
  12. package/src/features/ChatInput/TypoBar/index.tsx +0 -11
  13. package/src/features/CommandMenu/AskAIMenu.tsx +47 -14
  14. package/src/features/Conversation/ChatItem/components/MessageContent/index.tsx +11 -12
  15. package/src/features/EditorModal/EditorCanvas.tsx +81 -0
  16. package/src/features/EditorModal/TextareCanvas.tsx +28 -0
  17. package/src/features/{Conversation/ChatItem/components/MessageContent → EditorModal}/Typobar.tsx +0 -11
  18. package/src/features/EditorModal/index.tsx +51 -0
  19. package/src/features/ModelSwitchPanel/index.tsx +21 -1
  20. package/src/features/PageEditor/EditorCanvas/InlineToolbar.tsx +1 -17
  21. package/src/server/routers/tools/market.ts +118 -102
  22. package/src/server/services/discover/index.ts +10 -5
  23. package/src/services/codeInterpreter.ts +12 -20
  24. package/src/store/chat/slices/plugin/actions/pluginTypes.ts +13 -86
  25. package/packages/utils/src/server/__tests__/geo.test.ts +0 -116
  26. package/packages/utils/src/server/geo.ts +0 -60
  27. package/src/app/[variants]/(main)/memory/features/EditableModal/Typobar.tsx +0 -150
  28. package/src/features/Conversation/ChatItem/components/MessageContent/EditableModal.tsx +0 -119
package/CHANGELOG.md CHANGED
@@ -2,6 +2,64 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.218](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.217...v2.0.0-next.218)
6
+
7
+ <sup>Released on **2026-01-05**</sup>
8
+
9
+ #### ✨ Features
10
+
11
+ - **misc**: Update the sandbox export files & save files way.
12
+
13
+ #### 🐛 Bug Fixes
14
+
15
+ - **misc**: Fix editor modal when Markdown rendering off.
16
+
17
+ <br/>
18
+
19
+ <details>
20
+ <summary><kbd>Improvements and Fixes</kbd></summary>
21
+
22
+ #### What's improved
23
+
24
+ - **misc**: Update the sandbox export files & save files way, closes [#11249](https://github.com/lobehub/lobe-chat/issues/11249) ([039b0a1](https://github.com/lobehub/lobe-chat/commit/039b0a1))
25
+
26
+ #### What's fixed
27
+
28
+ - **misc**: Fix editor modal when Markdown rendering off, closes [#11251](https://github.com/lobehub/lobe-chat/issues/11251) ([eb86d3b](https://github.com/lobehub/lobe-chat/commit/eb86d3b))
29
+
30
+ </details>
31
+
32
+ <div align="right">
33
+
34
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
35
+
36
+ </div>
37
+
38
+ ## [Version 2.0.0-next.217](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.216...v2.0.0-next.217)
39
+
40
+ <sup>Released on **2026-01-05**</sup>
41
+
42
+ #### ♻ Code Refactoring
43
+
44
+ - **utils**: Remove unused geo server utilities.
45
+
46
+ <br/>
47
+
48
+ <details>
49
+ <summary><kbd>Improvements and Fixes</kbd></summary>
50
+
51
+ #### Code refactoring
52
+
53
+ - **utils**: Remove unused geo server utilities, closes [#11243](https://github.com/lobehub/lobe-chat/issues/11243) ([ee474cc](https://github.com/lobehub/lobe-chat/commit/ee474cc))
54
+
55
+ </details>
56
+
57
+ <div align="right">
58
+
59
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
60
+
61
+ </div>
62
+
5
63
  ## [Version 2.0.0-next.216](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.215...v2.0.0-next.216)
6
64
 
7
65
  <sup>Released on **2026-01-05**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,21 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "features": [
5
+ "Update the sandbox export files & save files way."
6
+ ],
7
+ "fixes": [
8
+ "Fix editor modal when Markdown rendering off."
9
+ ]
10
+ },
11
+ "date": "2026-01-05",
12
+ "version": "2.0.0-next.218"
13
+ },
14
+ {
15
+ "children": {},
16
+ "date": "2026-01-05",
17
+ "version": "2.0.0-next.217"
18
+ },
2
19
  {
3
20
  "children": {
4
21
  "fixes": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.216",
3
+ "version": "2.0.0-next.218",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent 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",
@@ -425,51 +425,38 @@ export class CloudSandboxExecutionRuntime {
425
425
 
426
426
  /**
427
427
  * Export a file from the sandbox to cloud storage
428
- * 1. Get a pre-signed upload URL from our server
429
- * 2. Call the sandbox to upload the file to that URL
430
- * 3. Return the download URL to the user
428
+ * Uses a single tRPC call that handles:
429
+ * 1. Generate pre-signed upload URL
430
+ * 2. Call sandbox to upload file
431
+ * 3. Create persistent file record
432
+ * 4. Return permanent /f/:id URL
431
433
  */
432
434
  async exportFile(args: ExportFileParams): Promise<BuiltinServerRuntimeOutput> {
433
435
  try {
434
436
  // Extract filename from path
435
437
  const filename = args.path.split('/').pop() || 'exported_file';
436
438
 
437
- // Step 1: Get pre-signed upload URL from our server
438
- const uploadUrlResult = await codeInterpreterService.getExportFileUploadUrl(
439
+ // Single call that handles everything: upload URL generation, sandbox upload, and file record creation
440
+ const result = await codeInterpreterService.exportAndUploadFile(
441
+ args.path,
439
442
  filename,
440
443
  this.context.topicId,
441
444
  );
442
445
 
443
- if (!uploadUrlResult.success) {
444
- throw new Error(uploadUrlResult.error?.message || 'Failed to get upload URL');
445
- }
446
-
447
- // Step 2: Call the sandbox's exportFile tool with the upload URL
448
- // The sandbox will read the file and upload it to the pre-signed URL
449
- const result = await this.callTool('exportFile', {
450
- path: args.path,
451
- uploadUrl: uploadUrlResult.uploadUrl,
452
- });
453
-
454
- // Check if the sandbox upload was successful
455
- const uploadSuccess = result.success && result.result?.success !== false;
456
- const fileSize = result.result?.size;
457
- const mimeType = result.result?.mimeType;
458
- const fileContent = result.result?.content;
459
-
460
446
  const state: ExportFileState = {
461
- content: fileContent,
462
- downloadUrl: uploadSuccess ? uploadUrlResult.downloadUrl : '',
463
- filename,
464
- mimeType,
447
+ downloadUrl: result.success && result.url ? result.url : '',
448
+ fileId: result.fileId,
449
+ filename: result.filename,
450
+ mimeType: result.mimeType,
465
451
  path: args.path,
466
- size: fileSize,
467
- success: uploadSuccess,
452
+ size: result.size,
453
+ success: result.success,
468
454
  };
469
- if (!uploadSuccess) {
455
+
456
+ if (!result.success) {
470
457
  return {
471
458
  content: JSON.stringify({
472
- error: result.result?.error || 'Failed to upload file from sandbox',
459
+ error: result.error?.message || 'Failed to export file from sandbox',
473
460
  filename,
474
461
  success: false,
475
462
  }),
@@ -479,7 +466,7 @@ export class CloudSandboxExecutionRuntime {
479
466
  }
480
467
 
481
468
  return {
482
- content: `File exported successfully.\n\nFilename: ${filename}\nDownload URL: ${uploadUrlResult.downloadUrl}`,
469
+ content: `File exported successfully.\n\nFilename: ${filename}\nDownload URL: ${result.url}`,
483
470
  state,
484
471
  success: true,
485
472
  };
@@ -90,10 +90,10 @@ export interface GlobFilesState {
90
90
  }
91
91
 
92
92
  export interface ExportFileState {
93
- /** File content for text files (only when mimeType is text-like and size <= 1MB) */
94
- content?: string;
95
- /** The download URL for the exported file */
93
+ /** The download URL for the exported file (permanent /f/:id URL) */
96
94
  downloadUrl: string;
95
+ /** The file ID in database (returned from server) */
96
+ fileId?: string;
97
97
  /** The exported file name */
98
98
  filename: string;
99
99
  /** The MIME type of the file */
@@ -1,6 +1,5 @@
1
1
  export * from './auth';
2
2
  export * from './correctOIDCUrl';
3
- export * from './geo';
4
3
  export * from './response';
5
4
  export * from './responsive';
6
5
  export * from './sse';
@@ -1,6 +1,6 @@
1
1
  import { HotkeyEnum, getHotkeyById } from '@lobehub/editor';
2
2
  import { FloatActions } from '@lobehub/editor/react';
3
- import { type ChatInputActionsProps, CodeLanguageSelect } from '@lobehub/editor/react';
3
+ import { type ChatInputActionsProps } from '@lobehub/editor/react';
4
4
  import {
5
5
  BoldIcon,
6
6
  CodeXmlIcon,
@@ -116,16 +116,6 @@ const TypoBar = memo(() => {
116
116
  label: t('typobar.codeblock'),
117
117
  onClick: editorState.codeblock,
118
118
  },
119
- editorState.isCodeblock && {
120
- children: (
121
- <CodeLanguageSelect
122
- onSelect={(value) => editorState.updateCodeblockLang(value)}
123
- value={editorState.codeblockLang}
124
- />
125
- ),
126
- disabled: !editorState.isCodeblock,
127
- key: 'codeblockLang',
128
- },
129
119
  ].filter(Boolean) as ChatInputActionsProps['items'];
130
120
  }, [editorState, t]);
131
121
 
@@ -1,6 +1,6 @@
1
1
  import { HotkeyEnum, getHotkeyById } from '@lobehub/editor';
2
2
  import { FloatActions } from '@lobehub/editor/react';
3
- import { type ChatInputActionsProps, CodeLanguageSelect } from '@lobehub/editor/react';
3
+ import { type ChatInputActionsProps } from '@lobehub/editor/react';
4
4
  import {
5
5
  BoldIcon,
6
6
  CodeXmlIcon,
@@ -116,16 +116,6 @@ const TypoBar = memo(() => {
116
116
  label: t('typobar.codeblock'),
117
117
  onClick: editorState.codeblock,
118
118
  },
119
- editorState.isCodeblock && {
120
- children: (
121
- <CodeLanguageSelect
122
- onSelect={(value) => editorState.updateCodeblockLang(value)}
123
- value={editorState.codeblockLang}
124
- />
125
- ),
126
- disabled: !editorState.isCodeblock,
127
- key: 'codeblockLang',
128
- },
129
119
  ].filter(Boolean) as ChatInputActionsProps['items'];
130
120
  }, [editorState, t]);
131
121
 
@@ -5,11 +5,13 @@ import { Button, Flexbox, TextArea } from '@lobehub/ui';
5
5
  import { createStaticStyles, cx } from 'antd-style';
6
6
  import { Sparkles } from 'lucide-react';
7
7
  import type { KeyboardEvent } from 'react';
8
+ import { useEffect, useRef } from 'react';
8
9
  import { useTranslation } from 'react-i18next';
9
10
 
10
11
  import { loginRequired } from '@/components/Error/loginRequiredNotification';
11
12
  import { useGeminiChineseWarning } from '@/hooks/useGeminiChineseWarning';
12
13
  import { useIsDark } from '@/hooks/useIsDark';
14
+ import { useQueryState } from '@/hooks/useQueryParam';
13
15
  import { useImageStore } from '@/store/image';
14
16
  import { createImageSelectors } from '@/store/image/selectors';
15
17
  import { useGenerationConfigParam } from '@/store/image/slices/generationConfig/hooks';
@@ -49,6 +51,10 @@ const PromptInput = ({ showTitle = false }: PromptInputProps) => {
49
51
  const isLogin = useUserStore(authSelectors.isLogin);
50
52
  const checkGeminiChineseWarning = useGeminiChineseWarning();
51
53
 
54
+ // Read prompt from query parameter
55
+ const [promptParam, setPromptParam] = useQueryState('prompt');
56
+ const hasProcessedPrompt = useRef(false);
57
+
52
58
  const handleGenerate = async () => {
53
59
  if (!isLogin) {
54
60
  loginRequired.redirect({ timeout: 2000 });
@@ -66,6 +72,44 @@ const PromptInput = ({ showTitle = false }: PromptInputProps) => {
66
72
  await createImage();
67
73
  };
68
74
 
75
+ // Auto-fill and auto-send when prompt query parameter is present
76
+ useEffect(() => {
77
+ if (promptParam && !hasProcessedPrompt.current && isLogin) {
78
+ // Decode the prompt parameter
79
+ const decodedPrompt = decodeURIComponent(promptParam);
80
+
81
+ // Set the prompt value in the store
82
+ setValue(decodedPrompt);
83
+
84
+ // Mark as processed to avoid running this effect again
85
+ hasProcessedPrompt.current = true;
86
+
87
+ // Clear the query parameter
88
+ setPromptParam(null);
89
+
90
+ // Auto-trigger generation after a short delay to ensure state is updated
91
+ setTimeout(async () => {
92
+ const shouldContinue = await checkGeminiChineseWarning({
93
+ model: currentModel,
94
+ prompt: decodedPrompt,
95
+ scenario: 'image',
96
+ });
97
+
98
+ if (shouldContinue) {
99
+ await createImage();
100
+ }
101
+ }, 100);
102
+ }
103
+ }, [
104
+ promptParam,
105
+ isLogin,
106
+ setValue,
107
+ setPromptParam,
108
+ checkGeminiChineseWarning,
109
+ currentModel,
110
+ createImage,
111
+ ]);
112
+
69
113
  const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
70
114
  if (e.key === 'Enter' && !e.shiftKey && !e.nativeEvent.isComposing) {
71
115
  e.preventDefault();
@@ -1,66 +1,16 @@
1
- import {
2
- ReactCodePlugin,
3
- ReactCodemirrorPlugin,
4
- ReactHRPlugin,
5
- ReactLinkHighlightPlugin,
6
- ReactListPlugin,
7
- ReactMathPlugin,
8
- ReactTablePlugin,
9
- } from '@lobehub/editor';
10
- import { Editor, useEditor } from '@lobehub/editor/react';
11
- import { Flexbox, Modal } from '@lobehub/ui';
12
- import { memo, useMemo, useState } from 'react';
13
- import { useTranslation } from 'react-i18next';
1
+ import { memo } from 'react';
14
2
 
15
- import { useUserStore } from '@/store/user';
16
- import { labPreferSelectors } from '@/store/user/slices/preference/selectors';
3
+ import { EditorModal } from '@/features/EditorModal';
17
4
  import { useUserMemoryStore } from '@/store/userMemory';
18
5
  import { LayersEnum } from '@/types/userMemory';
19
6
 
20
- import TypoBar from './Typobar';
21
-
22
7
  const EditableModal = memo(() => {
23
- const { t } = useTranslation('common');
24
- const editor = useEditor();
25
- const [confirmLoading, setConfirmLoading] = useState(false);
26
8
  const editingMemoryId = useUserMemoryStore((s) => s.editingMemoryId);
27
9
  const editingMemoryContent = useUserMemoryStore((s) => s.editingMemoryContent);
28
10
  const editingMemoryLayer = useUserMemoryStore((s) => s.editingMemoryLayer);
29
11
  const clearEditingMemory = useUserMemoryStore((s) => s.clearEditingMemory);
30
12
  const updateMemory = useUserMemoryStore((s) => s.updateMemory);
31
13
 
32
- const enableRichRender = useUserStore(labPreferSelectors.enableInputMarkdown);
33
-
34
- const richRenderProps = useMemo(
35
- () =>
36
- !enableRichRender
37
- ? {
38
- enablePasteMarkdown: false,
39
- markdownOption: {
40
- bold: false,
41
- code: false,
42
- header: false,
43
- italic: false,
44
- quote: false,
45
- strikethrough: false,
46
- underline: false,
47
- underlineStrikethrough: false,
48
- },
49
- }
50
- : {
51
- plugins: [
52
- ReactListPlugin,
53
- ReactCodePlugin,
54
- ReactCodemirrorPlugin,
55
- ReactHRPlugin,
56
- ReactLinkHighlightPlugin,
57
- ReactTablePlugin,
58
- ReactMathPlugin,
59
- ],
60
- },
61
- [enableRichRender],
62
- );
63
-
64
14
  const layerMap = {
65
15
  context: LayersEnum.Context,
66
16
  experience: LayersEnum.Experience,
@@ -69,58 +19,15 @@ const EditableModal = memo(() => {
69
19
  };
70
20
 
71
21
  return (
72
- <Modal
73
- cancelText={t('cancel')}
74
- closable={false}
75
- confirmLoading={confirmLoading}
76
- destroyOnHidden
77
- okText={t('ok')}
22
+ <EditorModal
78
23
  onCancel={clearEditingMemory}
79
- onOk={async () => {
80
- if (!editor || !editingMemoryId || !editingMemoryLayer) return;
81
- setConfirmLoading(true);
82
- const newValue = editor.getDocument('markdown') as unknown as string;
83
- await updateMemory(editingMemoryId, newValue, layerMap[editingMemoryLayer]);
84
- setConfirmLoading(false);
24
+ onConfirm={async (value) => {
25
+ if (!editingMemoryId || !editingMemoryLayer) return;
26
+ await updateMemory(editingMemoryId, value, layerMap[editingMemoryLayer]);
85
27
  }}
86
28
  open={!!editingMemoryId}
87
- styles={{
88
- body: {
89
- overflow: 'hidden',
90
- padding: 0,
91
- },
92
- }}
93
- title={null}
94
- width={'min(90vw, 960px)'}
95
- >
96
- <TypoBar editor={editor} />
97
- <Flexbox
98
- onClick={() => {
99
- editor.focus();
100
- }}
101
- paddingBlock={16}
102
- paddingInline={48}
103
- style={{ cursor: 'text', maxHeight: '80vh', minHeight: '50vh', overflowY: 'auto' }}
104
- >
105
- <Editor
106
- autoFocus
107
- content={''}
108
- editor={editor}
109
- onInit={(editor) => {
110
- if (!editor) return;
111
- try {
112
- editor?.setDocument('markdown', editingMemoryContent);
113
- } catch {}
114
- }}
115
- style={{
116
- paddingBottom: 120,
117
- }}
118
- type={'text'}
119
- variant={'chat'}
120
- {...richRenderProps}
121
- />
122
- </Flexbox>
123
- </Modal>
29
+ value={editingMemoryContent}
30
+ />
124
31
  );
125
32
  });
126
33
 
@@ -99,6 +99,7 @@ const InputEditor = memo<{ defaultRows?: number }>(({ defaultRows = 2 }) => {
99
99
  underline: false,
100
100
  underlineStrikethrough: false,
101
101
  },
102
+ plugins: [ReactCodemirrorPlugin],
102
103
  }
103
104
  : {
104
105
  plugins: [
@@ -4,7 +4,6 @@ import {
4
4
  ChatInputActionBar,
5
5
  ChatInputActions,
6
6
  type ChatInputActionsProps,
7
- CodeLanguageSelect,
8
7
  } from '@lobehub/editor/react';
9
8
  import { cssVar } from 'antd-style';
10
9
  import {
@@ -122,16 +121,6 @@ const TypoBar = memo(() => {
122
121
  label: t('typobar.codeblock'),
123
122
  onClick: editorState.codeblock,
124
123
  },
125
- editorState.isCodeblock && {
126
- children: (
127
- <CodeLanguageSelect
128
- onSelect={(value) => editorState.updateCodeblockLang(value)}
129
- value={editorState.codeblockLang}
130
- />
131
- ),
132
- disabled: !editorState.isCodeblock,
133
- key: 'codeblockLang',
134
- },
135
124
  ].filter(Boolean) as ChatInputActionsProps['items'],
136
125
  [editorState],
137
126
  );
@@ -1,7 +1,8 @@
1
1
  import { DEFAULT_AVATAR, DEFAULT_INBOX_AVATAR } from '@lobechat/const';
2
2
  import { Avatar } from '@lobehub/ui';
3
+ import { GroupBotSquareIcon } from '@lobehub/ui/icons';
3
4
  import { Command } from 'cmdk';
4
- import { Image } from 'lucide-react';
5
+ import { Bot, Image } from 'lucide-react';
5
6
  import { memo } from 'react';
6
7
  import { useTranslation } from 'react-i18next';
7
8
  import { useNavigate } from 'react-router-dom';
@@ -10,11 +11,12 @@ import { useHomeStore } from '@/store/home';
10
11
  import { homeAgentListSelectors } from '@/store/home/selectors';
11
12
 
12
13
  import { useCommandMenuContext } from './CommandMenuContext';
14
+ import { CommandItem } from './components';
13
15
  import { styles } from './styles';
14
16
  import { useCommandMenu } from './useCommandMenu';
15
17
 
16
18
  const AskAIMenu = memo(() => {
17
- const { t } = useTranslation('common');
19
+ const { t } = useTranslation(['common', 'chat', 'home']);
18
20
  const navigate = useNavigate();
19
21
  const { handleAskLobeAI, handleAIPainting, closeCommandMenu } = useCommandMenu();
20
22
  const { search } = useCommandMenuContext();
@@ -27,6 +29,24 @@ const AskAIMenu = memo(() => {
27
29
  ? t('cmdk.askAIHeading', { query: `"${search.trim()}"` })
28
30
  : t('cmdk.askAIHeadingEmpty');
29
31
 
32
+ const handleAgentBuilder = () => {
33
+ const trimmedSearch = search.trim();
34
+ closeCommandMenu(); // Close immediately
35
+ if (trimmedSearch) {
36
+ // Use sendAsAgent to create a blank agent and open agent builder
37
+ useHomeStore.getState().sendAsAgent(trimmedSearch);
38
+ }
39
+ };
40
+
41
+ const handleGroupBuilder = () => {
42
+ const trimmedSearch = search.trim();
43
+ closeCommandMenu(); // Close immediately
44
+ if (trimmedSearch) {
45
+ // Use sendAsGroup to create a blank group and open group builder
46
+ useHomeStore.getState().sendAsGroup(trimmedSearch);
47
+ }
48
+ };
49
+
30
50
  const handleAgentSelect = (agentId: string) => {
31
51
  if (search.trim()) {
32
52
  const message = encodeURIComponent(search.trim());
@@ -45,6 +65,18 @@ const AskAIMenu = memo(() => {
45
65
  <div className={styles.itemLabel}>Lobe AI</div>
46
66
  </div>
47
67
  </Command.Item>
68
+ <Command.Item onSelect={handleAgentBuilder} value="agent-builder">
69
+ <Bot className={styles.icon} />
70
+ <div className={styles.itemContent}>
71
+ <div className={styles.itemLabel}>{t('agentBuilder.title', { ns: 'chat' })}</div>
72
+ </div>
73
+ </Command.Item>
74
+ <Command.Item onSelect={handleGroupBuilder} value="group-builder">
75
+ <GroupBotSquareIcon className={styles.icon} />
76
+ <div className={styles.itemContent}>
77
+ <div className={styles.itemLabel}>{t('starter.createGroup', { ns: 'home' })}</div>
78
+ </div>
79
+ </Command.Item>
48
80
  <Command.Item onSelect={handleAIPainting} value="ai-painting">
49
81
  <Image className={styles.icon} />
50
82
  <div className={styles.itemContent}>
@@ -53,21 +85,22 @@ const AskAIMenu = memo(() => {
53
85
  </Command.Item>
54
86
 
55
87
  {agents.map((agent) => (
56
- <Command.Item
88
+ <CommandItem
89
+ icon={
90
+ <Avatar
91
+ avatar={typeof agent.avatar === 'string' ? agent.avatar : DEFAULT_AVATAR}
92
+ emojiScaleWithBackground
93
+ shape="square"
94
+ size={18}
95
+ />
96
+ }
57
97
  key={agent.id}
58
98
  onSelect={() => handleAgentSelect(agent.id)}
99
+ title={agent.title || t('defaultAgent')}
100
+ trailingLabel={t('cmdk.search.agent')}
59
101
  value={`agent-${agent.id}`}
60
- >
61
- <Avatar
62
- avatar={typeof agent.avatar === 'string' ? agent.avatar : DEFAULT_AVATAR}
63
- emojiScaleWithBackground
64
- shape="square"
65
- size={18}
66
- />
67
- <div className={styles.itemContent}>
68
- <div className={styles.itemLabel}>{agent.title || t('defaultAgent')}</div>
69
- </div>
70
- </Command.Item>
102
+ variant="detailed"
103
+ />
71
104
  ))}
72
105
  </Command.Group>
73
106
  );
@@ -7,7 +7,10 @@ import { useConversationStore } from '@/features/Conversation/store';
7
7
 
8
8
  import { type ChatItemProps } from '../../type';
9
9
 
10
- const EditableModal = dynamic(() => import('./EditableModal'), { ssr: false });
10
+ const EditorModal = dynamic(
11
+ () => import('@/features/EditorModal').then((mode) => mode.EditorModal),
12
+ { ssr: false },
13
+ );
11
14
 
12
15
  export const MSG_CONTENT_CLASSNAME = 'msg_content_flag';
13
16
 
@@ -60,13 +63,6 @@ const MessageContent = memo<MessageContentProps>(
60
63
  s.updateMessageContent,
61
64
  ]);
62
65
 
63
- const onChange = useCallback(
64
- (value: string) => {
65
- updateMessageContent(id, value);
66
- },
67
- [id, updateMessageContent],
68
- );
69
-
70
66
  const onEditingChange = useCallback(
71
67
  (edit: boolean) => toggleMessageEditing(id, edit),
72
68
  [id, toggleMessageEditing],
@@ -90,10 +86,13 @@ const MessageContent = memo<MessageContentProps>(
90
86
  </Flexbox>
91
87
  <Suspense fallback={null}>
92
88
  {editing && (
93
- <EditableModal
94
- editing={editing}
95
- onChange={onChange}
96
- onEditingChange={onEditingChange}
89
+ <EditorModal
90
+ onCancel={() => onEditingChange(false)}
91
+ onConfirm={async (value) => {
92
+ await updateMessageContent(id, value);
93
+ onEditingChange(false);
94
+ }}
95
+ open={editing}
97
96
  value={message ? String(message) : ''}
98
97
  />
99
98
  )}