@opensumi/ide-ai-native 3.7.2-next-1739848467.0 → 3.7.2-next-1739945875.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 (227) hide show
  1. package/lib/browser/ai-core.contribution.d.ts +3 -0
  2. package/lib/browser/ai-core.contribution.d.ts.map +1 -1
  3. package/lib/browser/ai-core.contribution.js +68 -2
  4. package/lib/browser/ai-core.contribution.js.map +1 -1
  5. package/lib/browser/chat/chat-model.d.ts +2 -2
  6. package/lib/browser/chat/chat-model.d.ts.map +1 -1
  7. package/lib/browser/chat/chat-model.js +16 -5
  8. package/lib/browser/chat/chat-model.js.map +1 -1
  9. package/lib/browser/chat/chat-proxy.service.d.ts +4 -0
  10. package/lib/browser/chat/chat-proxy.service.d.ts.map +1 -1
  11. package/lib/browser/chat/chat-proxy.service.js +43 -0
  12. package/lib/browser/chat/chat-proxy.service.js.map +1 -1
  13. package/lib/browser/chat/chat.view.d.ts.map +1 -1
  14. package/lib/browser/chat/chat.view.js +29 -2
  15. package/lib/browser/chat/chat.view.js.map +1 -1
  16. package/lib/browser/components/ChatContext/ContextSelector.d.ts +12 -0
  17. package/lib/browser/components/ChatContext/ContextSelector.d.ts.map +1 -0
  18. package/lib/browser/components/ChatContext/ContextSelector.js +113 -0
  19. package/lib/browser/components/ChatContext/ContextSelector.js.map +1 -0
  20. package/lib/browser/components/ChatContext/index.d.ts +4 -0
  21. package/lib/browser/components/ChatContext/index.d.ts.map +1 -0
  22. package/lib/browser/components/ChatContext/index.js +84 -0
  23. package/lib/browser/components/ChatContext/index.js.map +1 -0
  24. package/lib/browser/components/ChatContext/style.module.less +189 -0
  25. package/lib/browser/components/ChatInput.d.ts.map +1 -1
  26. package/lib/browser/components/ChatInput.js.map +1 -1
  27. package/lib/browser/components/ChatReply.d.ts.map +1 -1
  28. package/lib/browser/components/ChatReply.js +25 -0
  29. package/lib/browser/components/ChatReply.js.map +1 -1
  30. package/lib/browser/components/ChatToolRender.d.ts +6 -0
  31. package/lib/browser/components/ChatToolRender.d.ts.map +1 -0
  32. package/lib/browser/components/ChatToolRender.js +53 -0
  33. package/lib/browser/components/ChatToolRender.js.map +1 -0
  34. package/lib/browser/components/ChatToolRender.module.less +86 -0
  35. package/lib/browser/components/components.module.less +32 -31
  36. package/lib/browser/components/utils.d.ts +2 -2
  37. package/lib/browser/context/llm-context.contribution.d.ts +7 -0
  38. package/lib/browser/context/llm-context.contribution.d.ts.map +1 -0
  39. package/lib/browser/context/llm-context.contribution.js +21 -0
  40. package/lib/browser/context/llm-context.contribution.js.map +1 -0
  41. package/lib/browser/context/llm-context.service.d.ts +24 -0
  42. package/lib/browser/context/llm-context.service.d.ts.map +1 -0
  43. package/lib/browser/context/llm-context.service.js +136 -0
  44. package/lib/browser/context/llm-context.service.js.map +1 -0
  45. package/lib/browser/index.d.ts +11 -3
  46. package/lib/browser/index.d.ts.map +1 -1
  47. package/lib/browser/index.js +56 -3
  48. package/lib/browser/index.js.map +1 -1
  49. package/lib/browser/mcp/mcp-server-proxy.service.d.ts +25 -0
  50. package/lib/browser/mcp/mcp-server-proxy.service.d.ts.map +1 -0
  51. package/lib/browser/mcp/mcp-server-proxy.service.js +56 -0
  52. package/lib/browser/mcp/mcp-server-proxy.service.js.map +1 -0
  53. package/lib/browser/mcp/mcp-server.feature.registry.d.ts +16 -0
  54. package/lib/browser/mcp/mcp-server.feature.registry.d.ts.map +1 -0
  55. package/lib/browser/mcp/mcp-server.feature.registry.js +53 -0
  56. package/lib/browser/mcp/mcp-server.feature.registry.js.map +1 -0
  57. package/lib/browser/mcp/mcp-tools-dialog.module.less +44 -0
  58. package/lib/browser/mcp/mcp-tools-dialog.view.d.ts +8 -0
  59. package/lib/browser/mcp/mcp-tools-dialog.view.d.ts.map +1 -0
  60. package/lib/browser/mcp/mcp-tools-dialog.view.js +16 -0
  61. package/lib/browser/mcp/mcp-tools-dialog.view.js.map +1 -0
  62. package/lib/browser/mcp/tools/createNewFileWithText.d.ts +9 -0
  63. package/lib/browser/mcp/tools/createNewFileWithText.d.ts.map +1 -0
  64. package/lib/browser/mcp/tools/createNewFileWithText.js +83 -0
  65. package/lib/browser/mcp/tools/createNewFileWithText.js.map +1 -0
  66. package/lib/browser/mcp/tools/findFilesByNameSubstring.d.ts +9 -0
  67. package/lib/browser/mcp/tools/findFilesByNameSubstring.d.ts.map +1 -0
  68. package/lib/browser/mcp/tools/findFilesByNameSubstring.js +92 -0
  69. package/lib/browser/mcp/tools/findFilesByNameSubstring.js.map +1 -0
  70. package/lib/browser/mcp/tools/getCurrentFilePath.d.ts +8 -0
  71. package/lib/browser/mcp/tools/getCurrentFilePath.d.ts.map +1 -0
  72. package/lib/browser/mcp/tools/getCurrentFilePath.js +49 -0
  73. package/lib/browser/mcp/tools/getCurrentFilePath.js.map +1 -0
  74. package/lib/browser/mcp/tools/getDiagnosticsByPath.d.ts +10 -0
  75. package/lib/browser/mcp/tools/getDiagnosticsByPath.d.ts.map +1 -0
  76. package/lib/browser/mcp/tools/getDiagnosticsByPath.js +119 -0
  77. package/lib/browser/mcp/tools/getDiagnosticsByPath.js.map +1 -0
  78. package/lib/browser/mcp/tools/getFileTextByPath.d.ts +9 -0
  79. package/lib/browser/mcp/tools/getFileTextByPath.d.ts.map +1 -0
  80. package/lib/browser/mcp/tools/getFileTextByPath.js +97 -0
  81. package/lib/browser/mcp/tools/getFileTextByPath.js.map +1 -0
  82. package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.d.ts +11 -0
  83. package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.d.ts.map +1 -0
  84. package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.js +119 -0
  85. package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.js.map +1 -0
  86. package/lib/browser/mcp/tools/getOpenEditorFileText.d.ts +8 -0
  87. package/lib/browser/mcp/tools/getOpenEditorFileText.d.ts.map +1 -0
  88. package/lib/browser/mcp/tools/getOpenEditorFileText.js +50 -0
  89. package/lib/browser/mcp/tools/getOpenEditorFileText.js.map +1 -0
  90. package/lib/browser/mcp/tools/getSelectedText.d.ts +8 -0
  91. package/lib/browser/mcp/tools/getSelectedText.d.ts.map +1 -0
  92. package/lib/browser/mcp/tools/getSelectedText.js +57 -0
  93. package/lib/browser/mcp/tools/getSelectedText.js.map +1 -0
  94. package/lib/browser/mcp/tools/handlers/ListDir.d.ts +21 -0
  95. package/lib/browser/mcp/tools/handlers/ListDir.d.ts.map +1 -0
  96. package/lib/browser/mcp/tools/handlers/ListDir.js +112 -0
  97. package/lib/browser/mcp/tools/handlers/ListDir.js.map +1 -0
  98. package/lib/browser/mcp/tools/handlers/ReadFile.d.ts +47 -0
  99. package/lib/browser/mcp/tools/handlers/ReadFile.d.ts.map +1 -0
  100. package/lib/browser/mcp/tools/handlers/ReadFile.js +147 -0
  101. package/lib/browser/mcp/tools/handlers/ReadFile.js.map +1 -0
  102. package/lib/browser/mcp/tools/listDir.d.ts +8 -0
  103. package/lib/browser/mcp/tools/listDir.d.ts.map +1 -0
  104. package/lib/browser/mcp/tools/listDir.js +65 -0
  105. package/lib/browser/mcp/tools/listDir.js.map +1 -0
  106. package/lib/browser/mcp/tools/readFile.d.ts +8 -0
  107. package/lib/browser/mcp/tools/readFile.d.ts.map +1 -0
  108. package/lib/browser/mcp/tools/readFile.js +82 -0
  109. package/lib/browser/mcp/tools/readFile.js.map +1 -0
  110. package/lib/browser/mcp/tools/replaceOpenEditorFile.d.ts +8 -0
  111. package/lib/browser/mcp/tools/replaceOpenEditorFile.d.ts.map +1 -0
  112. package/lib/browser/mcp/tools/replaceOpenEditorFile.js +79 -0
  113. package/lib/browser/mcp/tools/replaceOpenEditorFile.js.map +1 -0
  114. package/lib/browser/mcp/tools/replaceOpenEditorFileByDiffPreviewer.d.ts +8 -0
  115. package/lib/browser/mcp/tools/replaceOpenEditorFileByDiffPreviewer.d.ts.map +1 -0
  116. package/lib/browser/mcp/tools/replaceOpenEditorFileByDiffPreviewer.js +84 -0
  117. package/lib/browser/mcp/tools/replaceOpenEditorFileByDiffPreviewer.js.map +1 -0
  118. package/lib/browser/mcp/tools/runTerminalCmd.d.ts +18 -0
  119. package/lib/browser/mcp/tools/runTerminalCmd.d.ts.map +1 -0
  120. package/lib/browser/mcp/tools/runTerminalCmd.js +96 -0
  121. package/lib/browser/mcp/tools/runTerminalCmd.js.map +1 -0
  122. package/lib/browser/preferences/schema.d.ts.map +1 -1
  123. package/lib/browser/preferences/schema.js +60 -0
  124. package/lib/browser/preferences/schema.js.map +1 -1
  125. package/lib/browser/types.d.ts +45 -0
  126. package/lib/browser/types.d.ts.map +1 -1
  127. package/lib/browser/types.js +5 -1
  128. package/lib/browser/types.js.map +1 -1
  129. package/lib/common/index.d.ts +9 -0
  130. package/lib/common/index.d.ts.map +1 -1
  131. package/lib/common/index.js +4 -1
  132. package/lib/common/index.js.map +1 -1
  133. package/lib/common/llm-context.d.ts +37 -0
  134. package/lib/common/llm-context.d.ts.map +1 -0
  135. package/lib/common/llm-context.js +5 -0
  136. package/lib/common/llm-context.js.map +1 -0
  137. package/lib/common/mcp-server-manager.d.ts +40 -0
  138. package/lib/common/mcp-server-manager.d.ts.map +1 -0
  139. package/lib/common/mcp-server-manager.js +6 -0
  140. package/lib/common/mcp-server-manager.js.map +1 -0
  141. package/lib/common/tool-invocation-registry.d.ts +91 -0
  142. package/lib/common/tool-invocation-registry.d.ts.map +1 -0
  143. package/lib/common/tool-invocation-registry.js +90 -0
  144. package/lib/common/tool-invocation-registry.js.map +1 -0
  145. package/lib/common/types.d.ts +17 -0
  146. package/lib/common/types.d.ts.map +1 -1
  147. package/lib/node/anthropic/anthropic-language-model.d.ts +9 -0
  148. package/lib/node/anthropic/anthropic-language-model.d.ts.map +1 -0
  149. package/lib/node/anthropic/anthropic-language-model.js +26 -0
  150. package/lib/node/anthropic/anthropic-language-model.js.map +1 -0
  151. package/lib/node/base-language-model.d.ts +14 -0
  152. package/lib/node/base-language-model.d.ts.map +1 -0
  153. package/lib/node/base-language-model.js +136 -0
  154. package/lib/node/base-language-model.js.map +1 -0
  155. package/lib/node/deepseek/deepseek-language-model.d.ts +9 -0
  156. package/lib/node/deepseek/deepseek-language-model.d.ts.map +1 -0
  157. package/lib/node/deepseek/deepseek-language-model.js +26 -0
  158. package/lib/node/deepseek/deepseek-language-model.js.map +1 -0
  159. package/lib/node/index.d.ts.map +1 -1
  160. package/lib/node/index.js +19 -0
  161. package/lib/node/index.js.map +1 -1
  162. package/lib/node/mcp/sumi-mcp-server.d.ts +91 -0
  163. package/lib/node/mcp/sumi-mcp-server.d.ts.map +1 -0
  164. package/lib/node/mcp/sumi-mcp-server.js +172 -0
  165. package/lib/node/mcp/sumi-mcp-server.js.map +1 -0
  166. package/lib/node/mcp-server-manager-impl.d.ts +27 -0
  167. package/lib/node/mcp-server-manager-impl.d.ts.map +1 -0
  168. package/lib/node/mcp-server-manager-impl.js +127 -0
  169. package/lib/node/mcp-server-manager-impl.js.map +1 -0
  170. package/lib/node/mcp-server.d.ts +207 -0
  171. package/lib/node/mcp-server.d.ts.map +1 -0
  172. package/lib/node/mcp-server.js +91 -0
  173. package/lib/node/mcp-server.js.map +1 -0
  174. package/lib/node/openai/openai-language-model.d.ts +9 -0
  175. package/lib/node/openai/openai-language-model.d.ts.map +1 -0
  176. package/lib/node/openai/openai-language-model.js +29 -0
  177. package/lib/node/openai/openai-language-model.js.map +1 -0
  178. package/package.json +34 -22
  179. package/src/browser/ai-core.contribution.ts +77 -1
  180. package/src/browser/chat/chat-model.ts +24 -6
  181. package/src/browser/chat/chat-proxy.service.ts +42 -0
  182. package/src/browser/chat/chat.view.tsx +59 -6
  183. package/src/browser/components/ChatContext/ContextSelector.tsx +177 -0
  184. package/src/browser/components/ChatContext/index.tsx +135 -0
  185. package/src/browser/components/ChatContext/style.module.less +189 -0
  186. package/src/browser/components/ChatInput.tsx +1 -0
  187. package/src/browser/components/ChatReply.tsx +32 -0
  188. package/src/browser/components/ChatToolRender.module.less +86 -0
  189. package/src/browser/components/ChatToolRender.tsx +77 -0
  190. package/src/browser/components/components.module.less +32 -31
  191. package/src/browser/context/llm-context.contribution.ts +14 -0
  192. package/src/browser/context/llm-context.service.ts +156 -0
  193. package/src/browser/index.ts +68 -4
  194. package/src/browser/mcp/mcp-server-proxy.service.ts +53 -0
  195. package/src/browser/mcp/mcp-server.feature.registry.ts +54 -0
  196. package/src/browser/mcp/mcp-tools-dialog.module.less +44 -0
  197. package/src/browser/mcp/mcp-tools-dialog.view.tsx +24 -0
  198. package/src/browser/mcp/tools/createNewFileWithText.ts +83 -0
  199. package/src/browser/mcp/tools/findFilesByNameSubstring.ts +93 -0
  200. package/src/browser/mcp/tools/getCurrentFilePath.ts +49 -0
  201. package/src/browser/mcp/tools/getDiagnosticsByPath.ts +123 -0
  202. package/src/browser/mcp/tools/getFileTextByPath.ts +97 -0
  203. package/src/browser/mcp/tools/getOpenEditorFileDiagnostics.ts +121 -0
  204. package/src/browser/mcp/tools/getOpenEditorFileText.ts +50 -0
  205. package/src/browser/mcp/tools/getSelectedText.ts +57 -0
  206. package/src/browser/mcp/tools/handlers/ListDir.ts +117 -0
  207. package/src/browser/mcp/tools/handlers/ReadFile.ts +174 -0
  208. package/src/browser/mcp/tools/listDir.ts +66 -0
  209. package/src/browser/mcp/tools/readFile.ts +82 -0
  210. package/src/browser/mcp/tools/replaceOpenEditorFile.ts +80 -0
  211. package/src/browser/mcp/tools/replaceOpenEditorFileByDiffPreviewer.ts +91 -0
  212. package/src/browser/mcp/tools/runTerminalCmd.ts +107 -0
  213. package/src/browser/preferences/schema.ts +60 -0
  214. package/src/browser/types.ts +56 -0
  215. package/src/common/index.ts +14 -0
  216. package/src/common/llm-context.ts +41 -0
  217. package/src/common/mcp-server-manager.ts +46 -0
  218. package/src/common/tool-invocation-registry.ts +170 -0
  219. package/src/common/types.ts +22 -0
  220. package/src/node/anthropic/anthropic-language-model.ts +25 -0
  221. package/src/node/base-language-model.ts +163 -0
  222. package/src/node/deepseek/deepseek-language-model.ts +25 -0
  223. package/src/node/index.ts +21 -0
  224. package/src/node/mcp/sumi-mcp-server.ts +197 -0
  225. package/src/node/mcp-server-manager-impl.ts +148 -0
  226. package/src/node/mcp-server.ts +126 -0
  227. package/src/node/openai/openai-language-model.ts +25 -0
@@ -1,7 +1,13 @@
1
1
  import * as React from 'react';
2
2
  import { MessageList } from 'react-chat-elements';
3
3
 
4
- import { getIcon, useInjectable, useUpdateOnEvent } from '@opensumi/ide-core-browser';
4
+ import {
5
+ AINativeConfigService,
6
+ getIcon,
7
+ useEventEffect,
8
+ useInjectable,
9
+ useUpdateOnEvent,
10
+ } from '@opensumi/ide-core-browser';
5
11
  import { Popover, PopoverPosition } from '@opensumi/ide-core-browser/lib/components';
6
12
  import { EnhanceIcon } from '@opensumi/ide-core-browser/lib/components/ai-native';
7
13
  import {
@@ -18,13 +24,23 @@ import {
18
24
  IAIReporter,
19
25
  IChatComponent,
20
26
  IChatContent,
27
+ MessageType,
21
28
  localize,
22
29
  uuid,
23
30
  } from '@opensumi/ide-core-common';
24
31
  import { IMainLayoutService } from '@opensumi/ide-main-layout';
32
+ import { IDialogService } from '@opensumi/ide-overlay';
25
33
 
26
34
  import 'react-chat-elements/dist/main.css';
27
- import { AI_CHAT_VIEW_ID, IChatAgentService, IChatInternalService, IChatMessageStructure } from '../../common';
35
+ import {
36
+ AI_CHAT_VIEW_ID,
37
+ IChatAgentService,
38
+ IChatInternalService,
39
+ IChatMessageStructure,
40
+ TokenMCPServerProxyService,
41
+ } from '../../common';
42
+ import { LLMContextService, LLMContextServiceToken } from '../../common/llm-context';
43
+ import { ChatContext } from '../components/ChatContext';
28
44
  import { CodeBlockWrapperInput } from '../components/ChatEditor';
29
45
  import { ChatInput } from '../components/ChatInput';
30
46
  import { ChatMarkdown } from '../components/ChatMarkdown';
@@ -32,7 +48,9 @@ import { ChatNotify, ChatReply } from '../components/ChatReply';
32
48
  import { SlashCustomRender } from '../components/SlashCustomRender';
33
49
  import { MessageData, createMessageByAI, createMessageByUser } from '../components/utils';
34
50
  import { WelcomeMessage } from '../components/WelcomeMsg';
35
- import { ChatViewHeaderRender, TSlashCommandCustomRender } from '../types';
51
+ import { MCPServerProxyService } from '../mcp/mcp-server-proxy.service';
52
+ import { MCPToolsDialog } from '../mcp/mcp-tools-dialog.view';
53
+ import { ChatAgentPromptProvider, ChatViewHeaderRender, TSlashCommandCustomRender } from '../types';
36
54
 
37
55
  import { ChatRequestModel, ChatSlashCommandItemModel } from './chat-model';
38
56
  import { ChatProxyService } from './chat-proxy.service';
@@ -41,7 +59,6 @@ import { ChatFeatureRegistry } from './chat.feature.registry';
41
59
  import { ChatInternalService } from './chat.internal.service';
42
60
  import styles from './chat.module.less';
43
61
  import { ChatRenderRegistry } from './chat.render.registry';
44
-
45
62
  const SCROLL_CLASSNAME = 'chat_scroll';
46
63
 
47
64
  interface TDispatchAction {
@@ -56,10 +73,16 @@ export const AIChatView = () => {
56
73
  const chatAgentService = useInjectable<IChatAgentService>(IChatAgentService);
57
74
  const chatFeatureRegistry = useInjectable<ChatFeatureRegistry>(ChatFeatureRegistryToken);
58
75
  const chatRenderRegistry = useInjectable<ChatRenderRegistry>(ChatRenderRegistryToken);
76
+ const contextService = useInjectable<LLMContextService>(LLMContextServiceToken);
77
+ const promptProvider = useInjectable<ChatAgentPromptProvider>(ChatAgentPromptProvider);
78
+
59
79
  const layoutService = useInjectable<IMainLayoutService>(IMainLayoutService);
80
+ const mcpServerProxyService = useInjectable<MCPServerProxyService>(TokenMCPServerProxyService);
60
81
  const msgHistoryManager = aiChatService.sessionModel.history;
61
82
  const containerRef = React.useRef<HTMLDivElement>(null);
62
83
  const chatInputRef = React.useRef<{ setInputValue: (v: string) => void } | null>(null);
84
+ const dialogService = useInjectable<IDialogService>(IDialogService);
85
+ const aiNativeConfigService = useInjectable<AINativeConfigService>(AINativeConfigService);
63
86
 
64
87
  const [shortcutCommands, setShortcutCommands] = React.useState<ChatSlashCommandItemModel[]>([]);
65
88
 
@@ -81,6 +104,7 @@ export const AIChatView = () => {
81
104
  const [defaultAgentId, setDefaultAgentId] = React.useState<string>('');
82
105
  const [command, setCommand] = React.useState('');
83
106
  const [theme, setTheme] = React.useState<string | null>(null);
107
+ const [mcpToolsCount, setMcpToolsCount] = React.useState<number>(0);
84
108
 
85
109
  React.useEffect(() => {
86
110
  const featureSlashCommands = chatFeatureRegistry.getAllShortcutSlashCommand();
@@ -478,7 +502,10 @@ export const AIChatView = () => {
478
502
  const { message, agentId, command, reportExtra } = value;
479
503
  const { actionType, actionSource } = reportExtra || {};
480
504
 
481
- const request = aiChatService.createRequest(message, agentId!, command);
505
+ const context = contextService.serialize();
506
+ const fullMessage = await promptProvider.provideContextPrompt(context, message);
507
+
508
+ const request = aiChatService.createRequest(fullMessage, agentId!, command);
482
509
  if (!request) {
483
510
  return;
484
511
  }
@@ -626,6 +653,25 @@ export const AIChatView = () => {
626
653
  };
627
654
  }, [aiChatService.sessionModel]);
628
655
 
656
+ useEventEffect(
657
+ mcpServerProxyService.onChangeMCPServers,
658
+ () => {
659
+ mcpServerProxyService.getAllMCPTools().then((tools) => {
660
+ setMcpToolsCount(tools.length);
661
+ });
662
+ },
663
+ [mcpServerProxyService],
664
+ );
665
+
666
+ const handleShowMCPTools = React.useCallback(async () => {
667
+ const tools = await mcpServerProxyService.getAllMCPTools();
668
+ dialogService.open({
669
+ message: <MCPToolsDialog tools={tools} />,
670
+ type: MessageType.Empty,
671
+ buttons: ['关闭'],
672
+ });
673
+ }, [mcpServerProxyService, dialogService]);
674
+
629
675
  return (
630
676
  <div id={styles.ai_chat_view}>
631
677
  <div className={styles.header_container}>
@@ -643,6 +689,7 @@ export const AIChatView = () => {
643
689
  />
644
690
  </div>
645
691
  <div className={styles.chat_input_wrap}>
692
+ <ChatContext />
646
693
  <div className={styles.header_operate}>
647
694
  <div className={styles.header_operate_left}>
648
695
  {shortcutCommands.map((command) => (
@@ -657,7 +704,13 @@ export const AIChatView = () => {
657
704
  </Popover>
658
705
  ))}
659
706
  </div>
660
- <div className={styles.header_operate_right}></div>
707
+ <div className={styles.header_operate_right}>
708
+ {aiNativeConfigService.capabilities.supportsMCP && (
709
+ <div className={styles.tag} onClick={handleShowMCPTools}>
710
+ {`MCP Tools: ${mcpToolsCount}`}
711
+ </div>
712
+ )}
713
+ </div>
661
714
  </div>
662
715
  <ChatInputWrapperRender
663
716
  onSend={(value, agentId, command) =>
@@ -0,0 +1,177 @@
1
+ import cls from 'classnames';
2
+ import { debounce } from 'lodash';
3
+ import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
4
+
5
+ import { ClickOutside } from '@opensumi/ide-components/lib/click-outside';
6
+ import { AppConfig, LabelService } from '@opensumi/ide-core-browser';
7
+ import { Icon, Input, Scrollbars } from '@opensumi/ide-core-browser/lib/components';
8
+ import { RecentFilesManager } from '@opensumi/ide-core-browser/lib/quick-open/recent-files';
9
+ import { useInjectable } from '@opensumi/ide-core-browser/lib/react-hooks/injectable-hooks';
10
+ import { FileSearchServicePath, IFileSearchService } from '@opensumi/ide-file-search/lib/common/file-search';
11
+ import { URI } from '@opensumi/ide-utils';
12
+
13
+ import { FileContext } from '../../../common/llm-context';
14
+
15
+ import styles from './style.module.less';
16
+
17
+ interface CandidateFileProps {
18
+ uri: URI;
19
+ active: boolean;
20
+ selected: boolean;
21
+ onDidSelect: (val: URI) => void;
22
+ onDidDeselect: (val: URI) => void;
23
+ }
24
+
25
+ const CandidateFile = memo(({ uri, active, selected, onDidSelect, onDidDeselect }: CandidateFileProps) => {
26
+ const labelService = useInjectable<LabelService>(LabelService);
27
+ const appConfig = useInjectable<AppConfig>(AppConfig);
28
+ const itemsRef = useRef<HTMLDivElement | null>(null);
29
+
30
+ useEffect(() => {
31
+ if (active && itemsRef.current) {
32
+ const scrollBehavior: ScrollIntoViewOptions = {
33
+ behavior: 'instant',
34
+ block: 'end',
35
+ };
36
+ itemsRef.current.scrollIntoView(scrollBehavior);
37
+ }
38
+ }, [active, itemsRef.current]);
39
+
40
+ return (
41
+ <div
42
+ className={cls(styles.candidate_file, active && styles.active)}
43
+ ref={(ele) => (itemsRef.current = ele)}
44
+ onClick={() => (selected ? onDidDeselect(uri) : onDidSelect(uri))}
45
+ >
46
+ <Icon iconClass={labelService.getIcon(uri)} />
47
+ <span className={styles.basename}>{uri.path.base}</span>
48
+ <span className={styles.dir}>{URI.file(appConfig.workspaceDir).relative(uri.parent)?.toString()}</span>
49
+ {selected && <Icon icon='check' style={{ marginLeft: 'auto', color: 'var(--editorGutter-addedBackground)' }} />}
50
+ </div>
51
+ );
52
+ });
53
+
54
+ interface ContextSelectorProps {
55
+ addedFiles: FileContext[];
56
+ onDidSelect: (val: URI) => void;
57
+ onDidDeselect: (val: URI) => void;
58
+ onDidClose: () => void;
59
+ }
60
+
61
+ export const ContextSelector = memo(({ addedFiles, onDidDeselect, onDidSelect, onDidClose }: ContextSelectorProps) => {
62
+ const [candidateFiles, updateCandidateFiles] = useState<URI[]>([]);
63
+ const [activeFile, setActiveFile] = useState<URI | null>(null);
64
+ const [searching, toggleSearching] = useState(false);
65
+ const [searchResults, updateSearchResults] = useState<URI[]>([]);
66
+
67
+ const recentFilesManager: RecentFilesManager = useInjectable(RecentFilesManager);
68
+ const appConfig = useInjectable<AppConfig>(AppConfig);
69
+ const searchService = useInjectable<IFileSearchService>(FileSearchServicePath);
70
+
71
+ const container = useRef<HTMLDivElement | null>();
72
+
73
+ useEffect(() => {
74
+ if (candidateFiles.length === 0) {
75
+ recentFilesManager.getMostRecentlyOpenedFiles().then((files) => {
76
+ const addedUris = addedFiles.map((val) => val.uri);
77
+ const recentFiles = files.filter((file) => !addedUris.includes(new URI(file))).map((file) => new URI(file));
78
+ updateCandidateFiles(recentFiles);
79
+ setActiveFile(recentFiles[0] || null);
80
+ });
81
+ }
82
+ }, [addedFiles]);
83
+
84
+ const onDidInput = useCallback(
85
+ debounce((ev) => {
86
+ if (ev.target.value.trim() === '') {
87
+ updateSearchResults([]);
88
+ setActiveFile(candidateFiles[0]);
89
+ return;
90
+ }
91
+
92
+ toggleSearching(true);
93
+ searchService
94
+ .find(ev.target.value, {
95
+ rootUris: [appConfig.workspaceDir],
96
+ limit: 200,
97
+ useGitIgnore: true,
98
+ noIgnoreParent: true,
99
+ fuzzyMatch: true,
100
+ })
101
+ .then((res) => {
102
+ const results = res.map((val) => new URI(val));
103
+ updateSearchResults(results);
104
+ setActiveFile(results[0]);
105
+ })
106
+ .finally(() => {
107
+ toggleSearching(false);
108
+ });
109
+ }, 500),
110
+ [],
111
+ );
112
+
113
+ const onDidKeyDown = useCallback(
114
+ (event) => {
115
+ const { key } = event;
116
+ if (key === 'Escape') {
117
+ onDidClose();
118
+ return;
119
+ }
120
+
121
+ if (key === 'Enter' && activeFile) {
122
+ onDidSelect(activeFile);
123
+ return;
124
+ }
125
+
126
+ const validKeys = ['ArrowUp', 'ArrowDown'];
127
+
128
+ if (!validKeys.includes(key)) {
129
+ return;
130
+ }
131
+
132
+ const files = searchResults.length > 0 ? searchResults : candidateFiles;
133
+
134
+ if (files.length === 0) {
135
+ return;
136
+ }
137
+
138
+ const currentIndex = files.indexOf(activeFile!);
139
+ const safeIndex = currentIndex === -1 ? 0 : currentIndex;
140
+ const lastIndex = files.length - 1;
141
+
142
+ const nextIndex =
143
+ key === 'ArrowUp' ? (safeIndex > 0 ? safeIndex - 1 : lastIndex) : safeIndex < lastIndex ? safeIndex + 1 : 0;
144
+
145
+ setActiveFile(files[nextIndex]);
146
+ },
147
+ [activeFile, searchResults, candidateFiles],
148
+ );
149
+
150
+ return (
151
+ <ClickOutside mouseEvents={['click', 'contextmenu']} onOutsideClick={() => onDidClose()}>
152
+ <div className={styles.context_selector} onKeyDown={onDidKeyDown} tabIndex={-1}>
153
+ <div style={{ padding: '4px' }}>
154
+ <Input placeholder='Search files by name' autoFocus onInput={onDidInput} />
155
+ </div>
156
+ <Scrollbars forwardedRef={(el) => (el ? (container.current = el.ref) : null)}>
157
+ <div className={styles.context_list}>
158
+ {searching && <div className={styles.context_search_layer} />}
159
+ <span className={styles.list_desc}>
160
+ {searchResults.length > 0 ? 'Search Results' : 'Recent Opened Files'}
161
+ </span>
162
+ {(searchResults.length > 0 ? searchResults : candidateFiles).map((file) => (
163
+ <CandidateFile
164
+ key={file.toString()}
165
+ uri={file}
166
+ active={activeFile === file}
167
+ onDidSelect={onDidSelect}
168
+ onDidDeselect={onDidDeselect}
169
+ selected={!!addedFiles.find((val) => val.uri.isEqual(file))}
170
+ />
171
+ ))}
172
+ </div>
173
+ </Scrollbars>
174
+ </div>
175
+ </ClickOutside>
176
+ );
177
+ });
@@ -0,0 +1,135 @@
1
+ import Collapse, { Panel } from 'rc-collapse';
2
+ import React, { memo, useCallback, useEffect, useState } from 'react';
3
+
4
+ import 'rc-collapse/assets/index.css';
5
+
6
+ import { Icon } from '@opensumi/ide-components/lib/icon/icon';
7
+ import { Popover, getIcon } from '@opensumi/ide-core-browser/lib/components';
8
+ import { EnhanceIcon } from '@opensumi/ide-core-browser/lib/components/ai-native';
9
+ import { useInjectable } from '@opensumi/ide-core-browser/lib/react-hooks/injectable-hooks';
10
+ import { AppConfig } from '@opensumi/ide-core-browser/lib/react-providers/config-provider';
11
+ import { LabelService } from '@opensumi/ide-core-browser/lib/services/label-service';
12
+ import { localize } from '@opensumi/ide-core-common/lib/localize';
13
+ import { Event, URI } from '@opensumi/ide-core-common/lib/utils';
14
+ import { WorkbenchEditorService } from '@opensumi/ide-editor/lib/browser/types';
15
+
16
+ import { FileContext, LLMContextService, LLMContextServiceToken } from '../../../common/llm-context';
17
+
18
+ import { ContextSelector } from './ContextSelector';
19
+ import styles from './style.module.less';
20
+
21
+ export const ChatContext = memo(() => {
22
+ const [addedFiles, updateAddedFiles] = useState<FileContext[]>([]);
23
+ const [contextOverlay, toggleContextOverlay] = useState(false);
24
+
25
+ const labelService = useInjectable<LabelService>(LabelService);
26
+ const appConfig = useInjectable<AppConfig>(AppConfig);
27
+ const workbenchEditorService = useInjectable<WorkbenchEditorService>(WorkbenchEditorService);
28
+ const contextService = useInjectable<LLMContextService>(LLMContextServiceToken);
29
+
30
+ useEffect(() => {
31
+ const disposable = Event.debounce(
32
+ contextService.onDidContextFilesChangeEvent,
33
+ (_, e) => e!,
34
+ 50,
35
+ )((files) => {
36
+ if (files) {
37
+ updateAddedFiles(files);
38
+ }
39
+ }, contextService);
40
+
41
+ return () => {
42
+ disposable.dispose();
43
+ };
44
+ }, []);
45
+
46
+ const openContextOverlay = useCallback(() => {
47
+ toggleContextOverlay(true);
48
+ }, [addedFiles]);
49
+
50
+ const onDidSelect = useCallback((uri: URI) => {
51
+ contextService.addFileToContext(uri, undefined, true);
52
+ }, []);
53
+
54
+ const onDidDeselect = useCallback((uri: URI) => {
55
+ contextService.removeFileFromContext(uri);
56
+ }, []);
57
+
58
+ const onDidClickFile = useCallback((uri: URI) => {
59
+ workbenchEditorService.open(uri);
60
+ }, []);
61
+
62
+ const onDidCleanFiles = useCallback((e) => {
63
+ e.stopPropagation();
64
+ e.preventDefault();
65
+ contextService.cleanFileContext();
66
+ }, []);
67
+
68
+ const onDidRemoveFile = useCallback((e, uri: URI) => {
69
+ e.stopPropagation();
70
+ e.preventDefault();
71
+ onDidDeselect(uri);
72
+ }, []);
73
+
74
+ return (
75
+ <div className={styles.chat_context}>
76
+ <Collapse
77
+ // @ts-ignore
78
+ expandIcon={({ isActive }) => (isActive ? <Icon icon='down' /> : <Icon icon='right' />)}
79
+ defaultActiveKey={['context-panel']}
80
+ onChange={() => {}}
81
+ >
82
+ <Panel
83
+ header={
84
+ <div className={styles.context_header}>
85
+ <h3 className={styles.chat_context_title}>Context</h3>
86
+ <Popover
87
+ overlayClassName={styles.popover_icon}
88
+ id={'ai-context-header-clear'}
89
+ title={localize('aiNative.operate.clear.title')}
90
+ >
91
+ <EnhanceIcon
92
+ wrapperClassName={styles.action_btn}
93
+ className={getIcon('clear')}
94
+ onClick={onDidCleanFiles}
95
+ tabIndex={0}
96
+ role='button'
97
+ ariaLabel={localize('aiNative.operate.clear.title')}
98
+ />
99
+ </Popover>
100
+ </div>
101
+ }
102
+ key='context-panel'
103
+ >
104
+ <div className={styles.file_list}>
105
+ {addedFiles.map((file) => (
106
+ <div className={styles.selected_item} key={file.uri.toString()} onClick={() => onDidClickFile(file.uri)}>
107
+ <Icon iconClass={labelService.getIcon(file.uri)} />
108
+ <span className={styles.basename}>
109
+ {file.uri.path.base}
110
+ {file.selection ? ` (${file.selection[0]}-${file.selection[1]})` : ''}
111
+ </span>
112
+ <span className={styles.dir}>
113
+ {URI.file(appConfig.workspaceDir).relative(file.uri.parent)?.toString()}
114
+ </span>
115
+ <Icon icon='close' className={styles.close_icon} onClick={(e) => onDidRemoveFile(e, file.uri)} />
116
+ </div>
117
+ ))}
118
+ </div>
119
+ <div className={styles.add_context} onClick={openContextOverlay}>
120
+ <Icon icon='add' />
121
+ Add Files
122
+ </div>
123
+ </Panel>
124
+ </Collapse>
125
+ {contextOverlay && (
126
+ <ContextSelector
127
+ onDidClose={() => toggleContextOverlay(false)}
128
+ onDidDeselect={onDidDeselect}
129
+ onDidSelect={onDidSelect}
130
+ addedFiles={addedFiles}
131
+ />
132
+ )}
133
+ </div>
134
+ );
135
+ });
@@ -0,0 +1,189 @@
1
+ .chat_context {
2
+ position: relative;
3
+ margin-bottom: 10px;
4
+ background-color: var(--design-chatInput-background);
5
+ padding: 10px;
6
+ border-radius: 4px;
7
+ border: 1px solid var(--design-borderColor);
8
+
9
+ :global(.rc-collapse) {
10
+ background-color: transparent !important;
11
+ border-radius: none !important;
12
+ border: none !important;
13
+ }
14
+
15
+ :global(.rc-collapse-title) {
16
+ flex: 1 !important;
17
+ line-height: 22px;
18
+ }
19
+
20
+ :global(.rc-collapse-expand-icon) {
21
+ line-height: 1;
22
+ }
23
+
24
+ :global(.rc-collapse-header) {
25
+ padding: 0px !important;
26
+ line-height: 1 !important;
27
+ }
28
+
29
+ :global(.rc-collapse-content) {
30
+ padding: 0px !important;
31
+ background-color: transparent !important;
32
+ }
33
+
34
+ .context_header {
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: space-between;
38
+
39
+ .chat_context_title {
40
+ font-weight: 600;
41
+ font-size: 11px;
42
+ margin-bottom: 0px;
43
+ color: var(--descriptionForeground);
44
+ }
45
+ }
46
+
47
+ .context_selector {
48
+ position: absolute;
49
+ left: 0px;
50
+ bottom: 40px;
51
+ background-color: var(--design-container-background);
52
+ padding: 10px;
53
+ width: 100%;
54
+ padding: 0px;
55
+ border-radius: 4px;
56
+ height: 350px;
57
+ display: flex;
58
+ flex-direction: column;
59
+ user-select: none;
60
+ box-shadow: 0px 9px 28px 8px var(--design-boxShadow-primary), 0px 3px 6px -4px var(--design-boxShadow-secondary),
61
+ 0px 6px 16px 0px var(--design-boxShadow-tertiary) !important;
62
+
63
+ .context_list {
64
+ font-size: 12px;
65
+ overflow-y: auto;
66
+
67
+ .list_desc {
68
+ margin: 0px 10px;
69
+ height: 30px;
70
+ display: flex;
71
+ align-items: center;
72
+ font-weight: bold;
73
+ color: var(--descriptionForeground);
74
+ }
75
+
76
+ .candidate_file {
77
+ padding: 0px 10px;
78
+ display: flex;
79
+ align-items: center;
80
+ height: 24px;
81
+ cursor: pointer;
82
+
83
+ &:hover {
84
+ background-color: var(--button-hoverBackground);
85
+ }
86
+
87
+ .basename {
88
+ color: var(--foreground);
89
+ overflow: hidden;
90
+ text-overflow: ellipsis;
91
+ white-space: nowrap;
92
+ margin-left: 5px;
93
+ }
94
+
95
+ .dir {
96
+ color: var(--descriptionForeground);
97
+ margin-left: 15px;
98
+ text-overflow: ellipsis;
99
+ overflow: hidden;
100
+ white-space: nowrap;
101
+ }
102
+ }
103
+
104
+ .active {
105
+ background-color: var(--editor-background);
106
+ color: var(--foreground);
107
+ }
108
+ }
109
+
110
+ .context_search_layer {
111
+ position: absolute;
112
+ width: 100%;
113
+ height: 100%;
114
+ background-color: var(--inputOption-hoverBackground);
115
+ }
116
+ }
117
+
118
+ .file_list {
119
+ margin-top: 5px;
120
+ max-height: 300px;
121
+ overflow-x: hidden;
122
+ overflow-y: auto;
123
+
124
+ .selected_item {
125
+ display: flex;
126
+ align-items: center;
127
+ font-size: 11px;
128
+ margin-right: 5px;
129
+ width: 100%;
130
+ overflow: hidden;
131
+ text-overflow: ellipsis;
132
+ padding: 0px 4px;
133
+ margin-bottom: 4px;
134
+ cursor: pointer;
135
+ transition: all 0.2s;
136
+
137
+ .close_icon {
138
+ font-size: 12px !important;
139
+ margin-left: auto;
140
+ }
141
+
142
+ :global(.kt-icon) {
143
+ display: flex;
144
+ font-size: 11px;
145
+ }
146
+
147
+ .basename {
148
+ margin-left: 4px;
149
+ color: var(--editor-foreground);
150
+ white-space: nowrap;
151
+ text-overflow: ellipsis;
152
+ overflow: hidden;
153
+ width: 75%;
154
+
155
+ &:hover {
156
+ color: var(--badge-foreground);
157
+ }
158
+ }
159
+
160
+ .dir {
161
+ color: var(--descriptionForeground);
162
+ margin-left: 15px;
163
+ text-overflow: ellipsis;
164
+ overflow: hidden;
165
+ white-space: nowrap;
166
+ }
167
+ }
168
+ }
169
+
170
+ .add_context {
171
+ display: inline-flex;
172
+ justify-content: center;
173
+ cursor: pointer;
174
+ border-radius: 4px;
175
+ font-size: 11px;
176
+ margin-right: 5px;
177
+ height: 24px;
178
+ margin-top: 10px;
179
+ align-items: center;
180
+ user-select: none;
181
+ transition: all 0.2s;
182
+ color: var(--design-text-foreground);
183
+ padding: 0px 5px;
184
+
185
+ &:hover {
186
+ border-color: var(--kt-selectOption-activeBorder);
187
+ }
188
+ }
189
+ }
@@ -15,6 +15,7 @@ import { ChatProxyService } from '../chat/chat-proxy.service';
15
15
  import { ChatFeatureRegistry } from '../chat/chat.feature.registry';
16
16
  import { IChatSlashCommandItem } from '../types';
17
17
 
18
+ import { ChatContext } from './ChatContext';
18
19
  import styles from './components.module.less';
19
20
 
20
21
  const INSTRUCTION_BOTTOM = 8;