@opensumi/ide-ai-native 3.8.3-next-1741763546.0 → 3.8.3-next-1741767755.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.
Files changed (74) hide show
  1. package/lib/browser/chat/chat-agent.service.d.ts +1 -1
  2. package/lib/browser/chat/chat-agent.service.d.ts.map +1 -1
  3. package/lib/browser/chat/chat-agent.service.js +7 -7
  4. package/lib/browser/chat/chat-agent.service.js.map +1 -1
  5. package/lib/browser/chat/chat.view.d.ts.map +1 -1
  6. package/lib/browser/chat/chat.view.js +52 -18
  7. package/lib/browser/chat/chat.view.js.map +1 -1
  8. package/lib/browser/components/ChatEditor.d.ts +11 -2
  9. package/lib/browser/components/ChatEditor.d.ts.map +1 -1
  10. package/lib/browser/components/ChatEditor.js +66 -6
  11. package/lib/browser/components/ChatEditor.js.map +1 -1
  12. package/lib/browser/components/ChatMentionInput.d.ts +25 -0
  13. package/lib/browser/components/ChatMentionInput.d.ts.map +1 -0
  14. package/lib/browser/components/ChatMentionInput.js +221 -0
  15. package/lib/browser/components/ChatMentionInput.js.map +1 -0
  16. package/lib/browser/components/{ChatContext/ContextSelector.d.ts → chat-context/context-selector.d.ts} +1 -1
  17. package/lib/browser/components/chat-context/context-selector.d.ts.map +1 -0
  18. package/lib/browser/components/{ChatContext/ContextSelector.js → chat-context/context-selector.js} +1 -1
  19. package/lib/browser/components/chat-context/context-selector.js.map +1 -0
  20. package/lib/browser/components/chat-context/index.d.ts.map +1 -0
  21. package/lib/browser/components/{ChatContext → chat-context}/index.js +2 -2
  22. package/lib/browser/components/chat-context/index.js.map +1 -0
  23. package/lib/browser/components/components.module.less +25 -0
  24. package/lib/browser/components/mention-input/mention-input.d.ts +5 -0
  25. package/lib/browser/components/mention-input/mention-input.d.ts.map +1 -0
  26. package/lib/browser/components/mention-input/mention-input.js +753 -0
  27. package/lib/browser/components/mention-input/mention-input.js.map +1 -0
  28. package/lib/browser/components/mention-input/mention-input.module.less +328 -0
  29. package/lib/browser/components/mention-input/mention-item.d.ts +10 -0
  30. package/lib/browser/components/mention-input/mention-item.d.ts.map +1 -0
  31. package/lib/browser/components/mention-input/mention-item.js +16 -0
  32. package/lib/browser/components/mention-input/mention-item.js.map +1 -0
  33. package/lib/browser/components/mention-input/mention-panel.d.ts +15 -0
  34. package/lib/browser/components/mention-input/mention-panel.d.ts.map +1 -0
  35. package/lib/browser/components/mention-input/mention-panel.js +49 -0
  36. package/lib/browser/components/mention-input/mention-panel.js.map +1 -0
  37. package/lib/browser/components/mention-input/types.d.ts +76 -0
  38. package/lib/browser/components/mention-input/types.d.ts.map +1 -0
  39. package/lib/browser/components/mention-input/types.js +16 -0
  40. package/lib/browser/components/mention-input/types.js.map +1 -0
  41. package/lib/browser/context/llm-context.service.d.ts +10 -2
  42. package/lib/browser/context/llm-context.service.d.ts.map +1 -1
  43. package/lib/browser/context/llm-context.service.js +71 -2
  44. package/lib/browser/context/llm-context.service.js.map +1 -1
  45. package/lib/common/llm-context.d.ts +15 -1
  46. package/lib/common/llm-context.d.ts.map +1 -1
  47. package/lib/common/llm-context.js.map +1 -1
  48. package/lib/common/prompts/context-prompt-provider.d.ts +12 -2
  49. package/lib/common/prompts/context-prompt-provider.d.ts.map +1 -1
  50. package/lib/common/prompts/context-prompt-provider.js +94 -30
  51. package/lib/common/prompts/context-prompt-provider.js.map +1 -1
  52. package/package.json +23 -23
  53. package/src/browser/chat/chat-agent.service.ts +7 -7
  54. package/src/browser/chat/chat.view.tsx +72 -21
  55. package/src/browser/components/ChatEditor.tsx +126 -9
  56. package/src/browser/components/ChatMentionInput.tsx +268 -0
  57. package/src/browser/components/{ChatContext → chat-context}/index.tsx +1 -1
  58. package/src/browser/components/components.module.less +25 -0
  59. package/src/browser/components/mention-input/mention-input.module.less +328 -0
  60. package/src/browser/components/mention-input/mention-input.tsx +943 -0
  61. package/src/browser/components/mention-input/mention-item.tsx +24 -0
  62. package/src/browser/components/mention-input/mention-panel.tsx +89 -0
  63. package/src/browser/components/mention-input/types.ts +82 -0
  64. package/src/browser/context/llm-context.service.ts +81 -3
  65. package/src/common/llm-context.ts +16 -1
  66. package/src/common/prompts/context-prompt-provider.ts +126 -36
  67. package/lib/browser/components/ChatContext/ContextSelector.d.ts.map +0 -1
  68. package/lib/browser/components/ChatContext/ContextSelector.js.map +0 -1
  69. package/lib/browser/components/ChatContext/index.d.ts.map +0 -1
  70. package/lib/browser/components/ChatContext/index.js.map +0 -1
  71. /package/lib/browser/components/{ChatContext → chat-context}/index.d.ts +0 -0
  72. /package/lib/browser/components/{ChatContext → chat-context}/style.module.less +0 -0
  73. /package/src/browser/components/{ChatContext/ContextSelector.tsx → chat-context/context-selector.tsx} +0 -0
  74. /package/src/browser/components/{ChatContext → chat-context}/style.module.less +0 -0
@@ -1,7 +1,14 @@
1
1
  import * as React from 'react';
2
2
  import { MessageList } from 'react-chat-elements';
3
3
 
4
- import { AppConfig, getIcon, useInjectable, useUpdateOnEvent } from '@opensumi/ide-core-browser';
4
+ import {
5
+ AINativeConfigService,
6
+ AppConfig,
7
+ LabelService,
8
+ getIcon,
9
+ useInjectable,
10
+ useUpdateOnEvent,
11
+ } from '@opensumi/ide-core-browser';
5
12
  import { Popover, PopoverPosition } from '@opensumi/ide-core-browser/lib/components';
6
13
  import { EnhanceIcon } from '@opensumi/ide-core-browser/lib/components/ai-native';
7
14
  import {
@@ -14,6 +21,7 @@ import {
14
21
  ChatMessageRole,
15
22
  ChatRenderRegistryToken,
16
23
  ChatServiceToken,
24
+ CommandService,
17
25
  Disposable,
18
26
  DisposableCollection,
19
27
  IAIReporter,
@@ -28,16 +36,18 @@ import {
28
36
  import { WorkbenchEditorService } from '@opensumi/ide-editor';
29
37
  import { IMainLayoutService } from '@opensumi/ide-main-layout';
30
38
  import { IMessageService } from '@opensumi/ide-overlay';
31
-
32
39
  import 'react-chat-elements/dist/main.css';
40
+ import { IWorkspaceService } from '@opensumi/ide-workspace';
41
+
33
42
  import { AI_CHAT_VIEW_ID, IChatAgentService, IChatInternalService, IChatMessageStructure } from '../../common';
43
+ import { LLMContextService, LLMContextServiceToken } from '../../common/llm-context';
34
44
  import { CodeBlockData } from '../../common/types';
35
45
  import { FileChange, FileListDisplay } from '../components/ChangeList';
36
- import { ChatContext } from '../components/ChatContext';
37
46
  import { CodeBlockWrapperInput } from '../components/ChatEditor';
38
47
  import ChatHistory, { IChatHistoryItem } from '../components/ChatHistory';
39
48
  import { ChatInput } from '../components/ChatInput';
40
49
  import { ChatMarkdown } from '../components/ChatMarkdown';
50
+ import { ChatMentionInput } from '../components/ChatMentionInput';
41
51
  import { ChatNotify, ChatReply } from '../components/ChatReply';
42
52
  import { SlashCustomRender } from '../components/SlashCustomRender';
43
53
  import { MessageData, createMessageByAI, createMessageByUser } from '../components/utils';
@@ -105,6 +115,8 @@ export const AIChatView = () => {
105
115
  const chatFeatureRegistry = useInjectable<ChatFeatureRegistry>(ChatFeatureRegistryToken);
106
116
  const chatRenderRegistry = useInjectable<ChatRenderRegistry>(ChatRenderRegistryToken);
107
117
  const mcpServerRegistry = useInjectable<IMCPServerRegistry>(TokenMCPServerRegistry);
118
+ const aiNativeConfigService = useInjectable<AINativeConfigService>(AINativeConfigService);
119
+ const llmContextService = useInjectable<LLMContextService>(LLMContextServiceToken);
108
120
 
109
121
  const layoutService = useInjectable<IMainLayoutService>(IMainLayoutService);
110
122
  const msgHistoryManager = aiChatService.sessionModel.history;
@@ -114,6 +126,9 @@ export const AIChatView = () => {
114
126
  const editorService = useInjectable<WorkbenchEditorService>(WorkbenchEditorService);
115
127
  const appConfig = useInjectable<AppConfig>(AppConfig);
116
128
  const applyService = useInjectable<BaseApplyService>(BaseApplyService);
129
+ const labelService = useInjectable<LabelService>(LabelService);
130
+ const workspaceService = useInjectable<IWorkspaceService>(IWorkspaceService);
131
+ const commandService = useInjectable<CommandService>(CommandService);
117
132
  const [shortcutCommands, setShortcutCommands] = React.useState<ChatSlashCommandItemModel[]>([]);
118
133
 
119
134
  const [changeList, setChangeList] = React.useState<FileChange[]>(getFileChanges(applyService.getSessionCodeBlocks()));
@@ -184,6 +199,9 @@ export const AIChatView = () => {
184
199
  if (chatRenderRegistry.chatInputRender) {
185
200
  return chatRenderRegistry.chatInputRender;
186
201
  }
202
+ if (aiNativeConfigService.capabilities.supportsMCP) {
203
+ return ChatMentionInput;
204
+ }
187
205
  return ChatInput;
188
206
  }, [chatRenderRegistry.chatInputRender]);
189
207
 
@@ -262,7 +280,7 @@ export const AIChatView = () => {
262
280
  if (loading) {
263
281
  return;
264
282
  }
265
- await handleSend(message);
283
+ await handleSend(message.message, message.agentId, message.command);
266
284
  } else {
267
285
  if (message.agentId) {
268
286
  setAgentId(message.agentId);
@@ -349,6 +367,9 @@ export const AIChatView = () => {
349
367
  text={message}
350
368
  agentId={visibleAgentId}
351
369
  command={command}
370
+ labelService={labelService}
371
+ workspaceService={workspaceService}
372
+ commandService={commandService}
352
373
  />
353
374
  ),
354
375
  },
@@ -454,7 +475,15 @@ export const AIChatView = () => {
454
475
  text: ChatUserRoleRender ? (
455
476
  <ChatUserRoleRender content={message} agentId={visibleAgentId} command={command} />
456
477
  ) : (
457
- <CodeBlockWrapperInput relationId={relationId} text={message} agentId={visibleAgentId} command={command} />
478
+ <CodeBlockWrapperInput
479
+ labelService={labelService}
480
+ relationId={relationId}
481
+ text={message}
482
+ agentId={visibleAgentId}
483
+ command={command}
484
+ workspaceService={workspaceService}
485
+ commandService={commandService}
486
+ />
458
487
  ),
459
488
  },
460
489
  styles.chat_message_code,
@@ -638,11 +667,44 @@ export const AIChatView = () => {
638
667
  );
639
668
 
640
669
  const handleSend = React.useCallback(
641
- async (value: IChatMessageStructure) => {
642
- const { message, command, reportExtra } = value;
670
+ async (message: string, agentId?: string, command?: string) => {
671
+ const reportExtra = {
672
+ actionSource: ActionSourceEnum.Chat,
673
+ actionType: ActionTypeEnum.Send,
674
+ };
675
+ agentId = agentId ? agentId : ChatProxyService.AGENT_ID;
676
+ // 提取并替换 {{@file:xxx}} 中的文件内容
677
+ let processedContent = message;
678
+ const filePattern = /\{\{@file:(.*?)\}\}/g;
679
+ const fileMatches = message.match(filePattern);
680
+ let isCleanContext = false;
681
+ if (fileMatches) {
682
+ for (const match of fileMatches) {
683
+ const filePath = match.replace(/\{\{@file:(.*?)\}\}/, '$1');
684
+ if (filePath && !isCleanContext) {
685
+ isCleanContext = true;
686
+ llmContextService.cleanFileContext();
687
+ }
688
+ const fileUri = new URI(filePath);
689
+ llmContextService.addFileToContext(fileUri, undefined, true);
690
+ // 获取文件内容
691
+ // 替换占位符,后续支持自定义渲染时可替换为自定义渲染标签
692
+ processedContent = processedContent.replace(match, `\`<attached_file>${fileUri.displayName}\``);
693
+ }
694
+ }
643
695
 
644
- const agentId = value.agentId ? value.agentId : ChatProxyService.AGENT_ID;
645
- return handleAgentReply({ message, agentId, command, reportExtra });
696
+ const folderPattern = /\{\{@folder:(.*?)\}\}/g;
697
+ const folderMatches = processedContent.match(folderPattern);
698
+ if (folderMatches) {
699
+ for (const match of folderMatches) {
700
+ const folderPath = match.replace(/\{\{@folder:(.*?)\}\}/, '$1');
701
+ const folderUri = new URI(folderPath);
702
+ llmContextService.addFolderToContext(folderUri);
703
+ // 替换占位符,后续支持自定义渲染时可替换为自定义渲染标签
704
+ processedContent = processedContent.replace(match, `\`<attached_folder>${folderUri.displayName}\``);
705
+ }
706
+ }
707
+ return handleAgentReply({ message: processedContent, agentId, command, reportExtra });
646
708
  },
647
709
  [handleAgentReply],
648
710
  );
@@ -759,7 +821,6 @@ export const AIChatView = () => {
759
821
  </div>
760
822
  ) : null}
761
823
  <div className={styles.chat_input_wrap}>
762
- <ChatContext />
763
824
  <div className={styles.header_operate}>
764
825
  <div className={styles.header_operate_left}>
765
826
  {shortcutCommands.map((command) => (
@@ -790,17 +851,7 @@ export const AIChatView = () => {
790
851
  />
791
852
  )}
792
853
  <ChatInputWrapperRender
793
- onSend={(value, agentId, command) =>
794
- handleSend({
795
- message: value,
796
- agentId,
797
- command,
798
- reportExtra: {
799
- actionSource: ActionSourceEnum.Chat,
800
- actionType: ActionTypeEnum.Send,
801
- },
802
- })
803
- }
854
+ onSend={handleSend}
804
855
  disabled={loading}
805
856
  enableOptions={true}
806
857
  theme={theme}
@@ -2,14 +2,24 @@ import capitalize from 'lodash/capitalize';
2
2
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
3
3
  import Highlight from 'react-highlight';
4
4
 
5
- import { IClipboardService, getIcon, useInjectable, uuid } from '@opensumi/ide-core-browser';
6
- import { Popover } from '@opensumi/ide-core-browser/lib/components';
5
+ import {
6
+ EDITOR_COMMANDS,
7
+ FILE_COMMANDS,
8
+ IClipboardService,
9
+ LabelService,
10
+ getIcon,
11
+ useInjectable,
12
+ uuid,
13
+ } from '@opensumi/ide-core-browser';
14
+ import { Icon, Popover } from '@opensumi/ide-core-browser/lib/components';
7
15
  import { EnhanceIcon } from '@opensumi/ide-core-browser/lib/components/ai-native';
8
16
  import {
9
17
  ActionSourceEnum,
10
18
  ActionTypeEnum,
11
19
  ChatFeatureRegistryToken,
20
+ CommandService,
12
21
  IAIReporter,
22
+ URI,
13
23
  localize,
14
24
  runWhenIdle,
15
25
  } from '@opensumi/ide-core-common';
@@ -22,6 +32,9 @@ import { ChatFeatureRegistry } from '../chat/chat.feature.registry';
22
32
 
23
33
  import styles from './components.module.less';
24
34
  import { highLightLanguageSupport } from './highLight';
35
+ import { MentionType } from './mention-input/types';
36
+
37
+ import type { IWorkspaceService } from '@opensumi/ide-workspace';
25
38
 
26
39
  import './highlightTheme.less';
27
40
 
@@ -139,16 +152,56 @@ const CodeBlock = ({
139
152
  renderText,
140
153
  agentId = '',
141
154
  command = '',
155
+ labelService,
156
+ commandService,
157
+ workspaceService,
142
158
  }: {
143
159
  content?: string;
144
160
  relationId: string;
145
161
  renderText?: (t: string) => React.ReactNode;
146
162
  agentId?: string;
147
163
  command?: string;
164
+ labelService?: LabelService;
165
+ commandService?: CommandService;
166
+ workspaceService?: IWorkspaceService;
148
167
  }) => {
149
168
  const rgInlineCode = /`([^`]+)`/g;
150
169
  const rgBlockCode = /```([^]+?)```/g;
151
170
  const rgBlockCodeBefore = /```([^]+)?/g;
171
+ const rgAttachedFile = /<attached_file>(.*)/g;
172
+ const rgAttachedFolder = /<attached_folder>(.*)/g;
173
+ const handleAttachmentClick = useCallback(
174
+ async (text: string, type: MentionType) => {
175
+ const roots = await workspaceService?.roots;
176
+ let uri;
177
+ if (!roots) {
178
+ return;
179
+ }
180
+ for (const root of roots) {
181
+ uri = new URI(root.uri).resolve(text);
182
+ try {
183
+ await commandService?.executeCommand(FILE_COMMANDS.LOCATION.id, uri);
184
+ if (type === MentionType.FILE) {
185
+ await commandService?.executeCommand(EDITOR_COMMANDS.OPEN_RESOURCE.id, uri);
186
+ }
187
+ break;
188
+ } catch {
189
+ continue;
190
+ }
191
+ }
192
+ },
193
+ [commandService, workspaceService],
194
+ );
195
+ const renderAttachment = (text: string, isFolder = false, key: string) => (
196
+ <span
197
+ className={styles.attachment}
198
+ key={key}
199
+ onClick={() => handleAttachmentClick(text, isFolder ? MentionType.FOLDER : MentionType.FILE)}
200
+ >
201
+ <Icon iconClass={isFolder ? getIcon('folder') : labelService?.getIcon(new URI(text || 'file'))} />
202
+ <span className={styles.attachment_text}>{text}</span>
203
+ </span>
204
+ );
152
205
 
153
206
  const renderCodeEditor = (content: string) => {
154
207
  const language = content.split('\n')[0].trim().toLowerCase();
@@ -193,11 +246,47 @@ const CodeBlock = ({
193
246
  renderedContent.push(text);
194
247
  }
195
248
  } else {
196
- renderedContent.push(
197
- <span className={styles.code_inline} key={index}>
198
- {text}
199
- </span>,
200
- );
249
+ // 处理文件和文件夹标记
250
+ const processedText = text;
251
+ const fileMatches = [...text.matchAll(rgAttachedFile)];
252
+ const folderMatches = [...text.matchAll(rgAttachedFolder)];
253
+ if (fileMatches.length || folderMatches.length) {
254
+ let lastIndex = 0;
255
+ const fragments: (string | React.ReactNode)[] = [];
256
+
257
+ // 处理文件标记
258
+ fileMatches.forEach((match, matchIndex) => {
259
+ if (match.index !== undefined) {
260
+ const spanText = processedText.slice(lastIndex, match.index);
261
+ if (spanText) {
262
+ fragments.push(<span key={`${index}-${matchIndex}`}>{spanText}</span>);
263
+ }
264
+ fragments.push(renderAttachment(match[1], false, `${index}-tag-${matchIndex}`));
265
+ lastIndex = match.index + match[0].length;
266
+ }
267
+ });
268
+
269
+ // 处理文件夹标记
270
+ folderMatches.forEach((match, matchIndex) => {
271
+ if (match.index !== undefined) {
272
+ const spanText = processedText.slice(lastIndex, match.index);
273
+ if (spanText) {
274
+ fragments.push(<span key={`${index}-${matchIndex}`}>{spanText}</span>);
275
+ }
276
+ fragments.push(renderAttachment(match[1], true, `${index}-tag-${matchIndex}`));
277
+ lastIndex = match.index + match[0].length;
278
+ }
279
+ });
280
+
281
+ fragments.push(processedText.slice(lastIndex));
282
+ renderedContent.push(...fragments);
283
+ } else {
284
+ renderedContent.push(
285
+ <span className={styles.code_inline} key={index}>
286
+ {text}
287
+ </span>,
288
+ );
289
+ }
201
290
  }
202
291
  });
203
292
  } else {
@@ -216,15 +305,29 @@ export const CodeBlockWrapper = ({
216
305
  renderText,
217
306
  relationId,
218
307
  agentId,
308
+ labelService,
309
+ commandService,
310
+ workspaceService,
219
311
  }: {
220
312
  text?: string;
221
313
  relationId: string;
222
314
  renderText?: (t: string) => React.ReactNode;
223
315
  agentId?: string;
316
+ labelService?: LabelService;
317
+ commandService?: CommandService;
318
+ workspaceService?: IWorkspaceService;
224
319
  }) => (
225
320
  <div className={styles.ai_chat_code_wrapper}>
226
321
  <div className={styles.render_text}>
227
- <CodeBlock content={text} renderText={renderText} relationId={relationId} agentId={agentId} />
322
+ <CodeBlock
323
+ content={text}
324
+ labelService={labelService}
325
+ renderText={renderText}
326
+ relationId={relationId}
327
+ agentId={agentId}
328
+ commandService={commandService}
329
+ workspaceService={workspaceService}
330
+ />
228
331
  </div>
229
332
  </div>
230
333
  );
@@ -234,11 +337,17 @@ export const CodeBlockWrapperInput = ({
234
337
  relationId,
235
338
  agentId,
236
339
  command,
340
+ labelService,
341
+ workspaceService,
342
+ commandService,
237
343
  }: {
238
344
  text: string;
239
345
  relationId: string;
240
346
  agentId?: string;
241
347
  command?: string;
348
+ labelService?: LabelService;
349
+ workspaceService?: IWorkspaceService;
350
+ commandService?: CommandService;
242
351
  }) => {
243
352
  const chatFeatureRegistry = useInjectable<ChatFeatureRegistry>(ChatFeatureRegistryToken);
244
353
  const [tag, setTag] = useState<string>('');
@@ -271,7 +380,15 @@ export const CodeBlockWrapperInput = ({
271
380
  </div>
272
381
  )}
273
382
  {command && <div className={styles.tag}>/ {command}</div>}
274
- <CodeBlock content={txt} relationId={relationId} agentId={agentId} command={command} />
383
+ <CodeBlock
384
+ content={txt}
385
+ labelService={labelService}
386
+ relationId={relationId}
387
+ agentId={agentId}
388
+ command={command}
389
+ workspaceService={workspaceService}
390
+ commandService={commandService}
391
+ />
275
392
  </div>
276
393
  </div>
277
394
  );
@@ -0,0 +1,268 @@
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+
3
+ import { LabelService, RecentFilesManager, useInjectable } from '@opensumi/ide-core-browser';
4
+ import { getIcon } from '@opensumi/ide-core-browser/lib/components';
5
+ import { URI, localize } from '@opensumi/ide-core-common';
6
+ import { CommandService } from '@opensumi/ide-core-common/lib/command';
7
+ import { WorkbenchEditorService } from '@opensumi/ide-editor';
8
+ import { FileSearchServicePath, IFileSearchService } from '@opensumi/ide-file-search';
9
+ import { IWorkspaceService } from '@opensumi/ide-workspace';
10
+
11
+ import { IChatInternalService } from '../../common';
12
+ import { ChatInternalService } from '../chat/chat.internal.service';
13
+ import { OPEN_MCP_CONFIG_COMMAND } from '../mcp/config/mcp-config.commands';
14
+
15
+ import styles from './components.module.less';
16
+ import { MentionInput } from './mention-input/mention-input';
17
+ import { FooterButtonPosition, FooterConfig, MentionItem, MentionType } from './mention-input/types';
18
+
19
+ export interface IChatMentionInputProps {
20
+ onSend: (value: string, agentId?: string, command?: string, option?: { model: string; [key: string]: any }) => void;
21
+ onValueChange?: (value: string) => void;
22
+ onExpand?: (value: boolean) => void;
23
+ placeholder?: string;
24
+ enableOptions?: boolean;
25
+ disabled?: boolean;
26
+ sendBtnClassName?: string;
27
+ defaultHeight?: number;
28
+ value?: string;
29
+ autoFocus?: boolean;
30
+ theme?: string | null;
31
+ setTheme: (theme: string | null) => void;
32
+ agentId: string;
33
+ setAgentId: (id: string) => void;
34
+ defaultAgentId?: string;
35
+ command: string;
36
+ setCommand: (command: string) => void;
37
+ }
38
+
39
+ // 指令命令激活组件
40
+ export const ChatMentionInput = React.forwardRef((props: IChatMentionInputProps) => {
41
+ const { onSend, disabled = false } = props;
42
+
43
+ const [value, setValue] = useState(props.value || '');
44
+ const aiChatService = useInjectable<ChatInternalService>(IChatInternalService);
45
+ const commandService = useInjectable<CommandService>(CommandService);
46
+ const searchService = useInjectable<IFileSearchService>(FileSearchServicePath);
47
+ const recentFilesManager = useInjectable<RecentFilesManager>(RecentFilesManager);
48
+ const workspaceService = useInjectable<IWorkspaceService>(IWorkspaceService);
49
+ const editorService = useInjectable<WorkbenchEditorService>(WorkbenchEditorService);
50
+ const labelService = useInjectable<LabelService>(LabelService);
51
+
52
+ const handleShowMCPConfig = React.useCallback(() => {
53
+ commandService.executeCommand(OPEN_MCP_CONFIG_COMMAND.id);
54
+ }, [commandService]);
55
+
56
+ useEffect(() => {
57
+ if (props.value !== value) {
58
+ setValue(props.value || '');
59
+ }
60
+ }, [props.value]);
61
+
62
+ // 默认菜单项
63
+ const defaultMenuItems: MentionItem[] = [
64
+ // {
65
+ // id: 'code',
66
+ // type: 'code',
67
+ // text: 'Code',
68
+ // icon: getIcon('codebraces'),
69
+ // getHighestLevelItems: () => [],
70
+ // getItems: async (searchText: string) => {
71
+ // const currentEditor = editorService.currentEditor;
72
+ // if (!currentEditor) {
73
+ // return [];
74
+ // }
75
+ // const currentDocumentModel = currentEditor.currentDocumentModel;
76
+ // if (!currentDocumentModel) {
77
+ // return [];
78
+ // }
79
+ // const symbols = await commandService.executeCommand('_executeFormatDocumentProvider', currentDocumentModel.uri.codeUri);
80
+ // return [];
81
+ // },
82
+ // },
83
+ {
84
+ id: MentionType.FILE,
85
+ type: MentionType.FILE,
86
+ text: 'File',
87
+ icon: getIcon('file'),
88
+ getHighestLevelItems: () => {
89
+ const currentEditor = editorService.currentEditor;
90
+ const currentUri = currentEditor?.currentUri;
91
+ if (!currentUri) {
92
+ return [];
93
+ }
94
+ return [
95
+ {
96
+ id: currentUri.codeUri.fsPath,
97
+ type: MentionType.FILE,
98
+ text: currentUri.displayName,
99
+ value: currentUri.codeUri.fsPath,
100
+ description: `(${localize('aiNative.chat.defaultContextFile')})`,
101
+ contextId: currentUri.codeUri.fsPath,
102
+ icon: labelService.getIcon(currentUri),
103
+ },
104
+ ];
105
+ },
106
+ getItems: async (searchText: string) => {
107
+ if (!searchText) {
108
+ const recentFile = await recentFilesManager.getMostRecentlyOpenedFiles();
109
+ return Promise.all(
110
+ recentFile.map(async (file) => {
111
+ const uri = new URI(file);
112
+ return {
113
+ id: uri.codeUri.fsPath,
114
+ type: MentionType.FILE,
115
+ text: uri.displayName,
116
+ value: uri.codeUri.fsPath,
117
+ description: (await workspaceService.asRelativePath(uri.parent))?.path || '',
118
+ contextId: uri.codeUri.fsPath,
119
+ icon: labelService.getIcon(uri),
120
+ };
121
+ }),
122
+ );
123
+ } else {
124
+ const rootUris = (await workspaceService.roots).map((root) => new URI(root.uri).codeUri.fsPath.toString());
125
+ const results = await searchService.find(searchText, {
126
+ rootUris,
127
+ useGitIgnore: true,
128
+ noIgnoreParent: true,
129
+ fuzzyMatch: true,
130
+ limit: 10,
131
+ });
132
+ return Promise.all(
133
+ results.map(async (file) => {
134
+ const uri = new URI(file);
135
+ return {
136
+ id: uri.codeUri.fsPath,
137
+ type: MentionType.FILE,
138
+ text: uri.displayName,
139
+ value: uri.codeUri.fsPath,
140
+ description: (await workspaceService.asRelativePath(uri.parent))?.path || '',
141
+ contextId: uri.codeUri.fsPath,
142
+ icon: labelService.getIcon(uri),
143
+ };
144
+ }),
145
+ );
146
+ }
147
+ },
148
+ },
149
+ {
150
+ id: MentionType.FOLDER,
151
+ type: MentionType.FOLDER,
152
+ text: 'Folder',
153
+ icon: getIcon('folder'),
154
+ getHighestLevelItems: () => {
155
+ const currentEditor = editorService.currentEditor;
156
+ const currentFolderUri = currentEditor?.currentUri?.parent;
157
+ if (!currentFolderUri) {
158
+ return [];
159
+ }
160
+ return [
161
+ {
162
+ id: currentFolderUri.codeUri.fsPath,
163
+ type: MentionType.FOLDER,
164
+ text: currentFolderUri.displayName,
165
+ value: currentFolderUri.codeUri.fsPath,
166
+ description: `(${localize('aiNative.chat.defaultContextFolder')})`,
167
+ contextId: currentFolderUri.codeUri.fsPath,
168
+ icon: getIcon('folder'),
169
+ },
170
+ ];
171
+ },
172
+ getItems: async (searchText: string) => {
173
+ if (!searchText) {
174
+ const recentFile = await recentFilesManager.getMostRecentlyOpenedFiles();
175
+ const recentFolder = Array.from(new Set(recentFile.map((file) => new URI(file).parent.codeUri.fsPath)));
176
+ return Promise.all(
177
+ recentFolder.map(async (folder) => {
178
+ const uri = new URI(folder);
179
+ const relativePath = await workspaceService.asRelativePath(uri);
180
+ return {
181
+ id: uri.codeUri.fsPath,
182
+ type: MentionType.FOLDER,
183
+ text: uri.displayName,
184
+ value: uri.codeUri.fsPath,
185
+ description: relativePath?.root ? relativePath.path : '',
186
+ contextId: uri.codeUri.fsPath,
187
+ icon: getIcon('folder'),
188
+ };
189
+ }),
190
+ );
191
+ } else {
192
+ const rootUris = (await workspaceService.roots).map((root) => new URI(root.uri).codeUri.fsPath.toString());
193
+ const results = await searchService.find(searchText, {
194
+ rootUris,
195
+ useGitIgnore: true,
196
+ noIgnoreParent: true,
197
+ fuzzyMatch: true,
198
+ limit: 10,
199
+ onlyFolders: true,
200
+ });
201
+ return Promise.all(
202
+ results.map(async (folder) => {
203
+ const uri = new URI(folder);
204
+ return {
205
+ id: uri.codeUri.fsPath,
206
+ type: MentionType.FOLDER,
207
+ text: uri.displayName,
208
+ value: uri.codeUri.fsPath,
209
+ description: (await workspaceService.asRelativePath(uri.parent))?.path || '',
210
+ contextId: uri.codeUri.fsPath,
211
+ icon: getIcon('folder'),
212
+ };
213
+ }),
214
+ );
215
+ }
216
+ },
217
+ },
218
+ ];
219
+
220
+ const defaultMentionInputFooterOptions: FooterConfig = useMemo(
221
+ () => ({
222
+ modelOptions: [
223
+ { label: 'QWQ 32B', value: 'qwq-32b' },
224
+ { label: 'DeepSeek R1', value: 'deepseek-r1' },
225
+ ],
226
+ defaultModel: 'deepseek-r1',
227
+ buttons: [
228
+ {
229
+ id: 'mcp-server',
230
+ icon: 'mcp',
231
+ title: 'MCP Server',
232
+ onClick: handleShowMCPConfig,
233
+ position: FooterButtonPosition.LEFT,
234
+ },
235
+ ],
236
+ showModelSelector: true,
237
+ }),
238
+ [handleShowMCPConfig],
239
+ );
240
+
241
+ const handleStop = useCallback(() => {
242
+ aiChatService.cancelRequest();
243
+ }, []);
244
+
245
+ const handleSend = useCallback(
246
+ async (content: string, option?: { model: string; [key: string]: any }) => {
247
+ if (disabled) {
248
+ return;
249
+ }
250
+ onSend(content, undefined, undefined, option);
251
+ },
252
+ [onSend, editorService, disabled],
253
+ );
254
+
255
+ return (
256
+ <div className={styles.chat_input_container}>
257
+ <MentionInput
258
+ mentionItems={defaultMenuItems}
259
+ onSend={handleSend}
260
+ onStop={handleStop}
261
+ loading={disabled}
262
+ labelService={labelService}
263
+ placeholder={localize('aiNative.chat.input.placeholder.default')}
264
+ footerConfig={defaultMentionInputFooterOptions}
265
+ />
266
+ </div>
267
+ );
268
+ });
@@ -15,7 +15,7 @@ import { WorkbenchEditorService } from '@opensumi/ide-editor/lib/browser/types';
15
15
 
16
16
  import { FileContext, LLMContextService, LLMContextServiceToken } from '../../../common/llm-context';
17
17
 
18
- import { ContextSelector } from './ContextSelector';
18
+ import { ContextSelector } from './context-selector';
19
19
  import styles from './style.module.less';
20
20
 
21
21
  const getCollapsedHeight = () => ({ height: 0, opacity: 0 });