@opensumi/ide-ai-native 3.8.3-next-1741752385.0 → 3.8.3-next-1741763229.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 (178) 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-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 +18 -1
  8. package/lib/browser/chat/chat-model.js.map +1 -1
  9. package/lib/browser/chat/chat.view.d.ts.map +1 -1
  10. package/lib/browser/chat/chat.view.js +49 -18
  11. package/lib/browser/chat/chat.view.js.map +1 -1
  12. package/lib/browser/components/ChatEditor.d.ts +5 -2
  13. package/lib/browser/components/ChatEditor.d.ts.map +1 -1
  14. package/lib/browser/components/ChatEditor.js +45 -6
  15. package/lib/browser/components/ChatEditor.js.map +1 -1
  16. package/lib/browser/components/ChatMentionInput.d.ts +25 -0
  17. package/lib/browser/components/ChatMentionInput.d.ts.map +1 -0
  18. package/lib/browser/components/ChatMentionInput.js +221 -0
  19. package/lib/browser/components/ChatMentionInput.js.map +1 -0
  20. package/lib/browser/components/ChatReply.d.ts.map +1 -1
  21. package/lib/browser/components/ChatReply.js +35 -17
  22. package/lib/browser/components/ChatReply.js.map +1 -1
  23. package/lib/browser/components/ChatThinking.js +1 -1
  24. package/lib/browser/components/ChatThinking.js.map +1 -1
  25. package/lib/browser/components/WelcomeMsg.js +1 -1
  26. package/lib/browser/components/WelcomeMsg.js.map +1 -1
  27. package/lib/browser/components/{ChatContext/ContextSelector.d.ts → chat-context/context-selector.d.ts} +1 -1
  28. package/lib/browser/components/chat-context/context-selector.d.ts.map +1 -0
  29. package/lib/browser/components/{ChatContext/ContextSelector.js → chat-context/context-selector.js} +1 -1
  30. package/lib/browser/components/chat-context/context-selector.js.map +1 -0
  31. package/lib/browser/components/chat-context/index.d.ts.map +1 -0
  32. package/lib/browser/components/{ChatContext → chat-context}/index.js +2 -2
  33. package/lib/browser/components/chat-context/index.js.map +1 -0
  34. package/lib/browser/components/components.module.less +43 -0
  35. package/lib/browser/components/mention-input/mention-input.d.ts +5 -0
  36. package/lib/browser/components/mention-input/mention-input.d.ts.map +1 -0
  37. package/lib/browser/components/mention-input/mention-input.js +753 -0
  38. package/lib/browser/components/mention-input/mention-input.js.map +1 -0
  39. package/lib/browser/components/mention-input/mention-input.module.less +327 -0
  40. package/lib/browser/components/mention-input/mention-item.d.ts +10 -0
  41. package/lib/browser/components/mention-input/mention-item.d.ts.map +1 -0
  42. package/lib/browser/components/mention-input/mention-item.js +16 -0
  43. package/lib/browser/components/mention-input/mention-item.js.map +1 -0
  44. package/lib/browser/components/mention-input/mention-panel.d.ts +15 -0
  45. package/lib/browser/components/mention-input/mention-panel.d.ts.map +1 -0
  46. package/lib/browser/components/mention-input/mention-panel.js +49 -0
  47. package/lib/browser/components/mention-input/mention-panel.js.map +1 -0
  48. package/lib/browser/components/mention-input/types.d.ts +76 -0
  49. package/lib/browser/components/mention-input/types.d.ts.map +1 -0
  50. package/lib/browser/components/mention-input/types.js +16 -0
  51. package/lib/browser/components/mention-input/types.js.map +1 -0
  52. package/lib/browser/context/llm-context.service.d.ts +10 -2
  53. package/lib/browser/context/llm-context.service.d.ts.map +1 -1
  54. package/lib/browser/context/llm-context.service.js +71 -2
  55. package/lib/browser/context/llm-context.service.js.map +1 -1
  56. package/lib/browser/contrib/inline-completions/prompt/matcher.js +2 -2
  57. package/lib/browser/contrib/inline-completions/prompt/similarSnippets.d.ts +1 -1
  58. package/lib/browser/contrib/inline-completions/prompt/similarSnippets.js +2 -2
  59. package/lib/browser/contrib/intelligent-completions/view/default.d.ts.map +1 -1
  60. package/lib/browser/contrib/intelligent-completions/view/default.js.map +1 -1
  61. package/lib/browser/mcp/config/components/mcp-config.view.d.ts.map +1 -1
  62. package/lib/browser/mcp/config/components/mcp-config.view.js +28 -18
  63. package/lib/browser/mcp/config/components/mcp-config.view.js.map +1 -1
  64. package/lib/browser/mcp/config/components/mcp-server-form.d.ts.map +1 -1
  65. package/lib/browser/mcp/config/components/mcp-server-form.js +33 -25
  66. package/lib/browser/mcp/config/components/mcp-server-form.js.map +1 -1
  67. package/lib/browser/mcp/mcp-server.feature.registry.js +1 -1
  68. package/lib/browser/mcp/mcp-server.feature.registry.js.map +1 -1
  69. package/lib/browser/mcp/tools/components/ExpandableFileList.d.ts.map +1 -1
  70. package/lib/browser/mcp/tools/components/ExpandableFileList.js +3 -1
  71. package/lib/browser/mcp/tools/components/ExpandableFileList.js.map +1 -1
  72. package/lib/browser/mcp/tools/components/Terminal.d.ts.map +1 -1
  73. package/lib/browser/mcp/tools/components/Terminal.js +6 -5
  74. package/lib/browser/mcp/tools/components/Terminal.js.map +1 -1
  75. package/lib/browser/mcp/tools/components/computeAnsiLogString.d.ts +4 -0
  76. package/lib/browser/mcp/tools/components/computeAnsiLogString.d.ts.map +1 -0
  77. package/lib/browser/mcp/tools/components/computeAnsiLogString.js +22 -0
  78. package/lib/browser/mcp/tools/components/computeAnsiLogString.js.map +1 -0
  79. package/lib/browser/mcp/tools/components/filterEraseMultipleLine.d.ts +18 -0
  80. package/lib/browser/mcp/tools/components/filterEraseMultipleLine.d.ts.map +1 -0
  81. package/lib/browser/mcp/tools/components/filterEraseMultipleLine.js +69 -0
  82. package/lib/browser/mcp/tools/components/filterEraseMultipleLine.js.map +1 -0
  83. package/lib/browser/mcp/tools/components/index.module.less +8 -5
  84. package/lib/browser/mcp/tools/createNewFileWithText.d.ts.map +1 -1
  85. package/lib/browser/mcp/tools/createNewFileWithText.js +1 -0
  86. package/lib/browser/mcp/tools/createNewFileWithText.js.map +1 -1
  87. package/lib/browser/mcp/tools/editFile.d.ts.map +1 -1
  88. package/lib/browser/mcp/tools/editFile.js +1 -0
  89. package/lib/browser/mcp/tools/editFile.js.map +1 -1
  90. package/lib/browser/mcp/tools/fileSearch.d.ts.map +1 -1
  91. package/lib/browser/mcp/tools/fileSearch.js +1 -0
  92. package/lib/browser/mcp/tools/fileSearch.js.map +1 -1
  93. package/lib/browser/mcp/tools/getDiagnosticsByPath.d.ts.map +1 -1
  94. package/lib/browser/mcp/tools/getDiagnosticsByPath.js +2 -1
  95. package/lib/browser/mcp/tools/getDiagnosticsByPath.js.map +1 -1
  96. package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.d.ts.map +1 -1
  97. package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.js +2 -0
  98. package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.js.map +1 -1
  99. package/lib/browser/mcp/tools/grepSearch.d.ts.map +1 -1
  100. package/lib/browser/mcp/tools/grepSearch.js +1 -0
  101. package/lib/browser/mcp/tools/grepSearch.js.map +1 -1
  102. package/lib/browser/mcp/tools/listDir.d.ts.map +1 -1
  103. package/lib/browser/mcp/tools/listDir.js +1 -0
  104. package/lib/browser/mcp/tools/listDir.js.map +1 -1
  105. package/lib/browser/mcp/tools/readFile.d.ts.map +1 -1
  106. package/lib/browser/mcp/tools/readFile.js +1 -0
  107. package/lib/browser/mcp/tools/readFile.js.map +1 -1
  108. package/lib/browser/mcp/tools/runTerminalCmd.d.ts.map +1 -1
  109. package/lib/browser/mcp/tools/runTerminalCmd.js +1 -0
  110. package/lib/browser/mcp/tools/runTerminalCmd.js.map +1 -1
  111. package/lib/browser/types.d.ts +1 -0
  112. package/lib/browser/types.d.ts.map +1 -1
  113. package/lib/browser/widget/inline-diff/inline-diff-manager.d.ts.map +1 -1
  114. package/lib/browser/widget/inline-diff/inline-diff-manager.js.map +1 -1
  115. package/lib/browser/widget/inline-stream-diff/live-preview.decoration.d.ts.map +1 -1
  116. package/lib/browser/widget/inline-stream-diff/live-preview.decoration.js +10 -5
  117. package/lib/browser/widget/inline-stream-diff/live-preview.decoration.js.map +1 -1
  118. package/lib/common/llm-context.d.ts +15 -1
  119. package/lib/common/llm-context.d.ts.map +1 -1
  120. package/lib/common/llm-context.js.map +1 -1
  121. package/lib/common/prompts/context-prompt-provider.d.ts +12 -2
  122. package/lib/common/prompts/context-prompt-provider.d.ts.map +1 -1
  123. package/lib/common/prompts/context-prompt-provider.js +94 -28
  124. package/lib/common/prompts/context-prompt-provider.js.map +1 -1
  125. package/lib/node/base-language-model.d.ts.map +1 -1
  126. package/lib/node/base-language-model.js +6 -0
  127. package/lib/node/base-language-model.js.map +1 -1
  128. package/package.json +24 -23
  129. package/src/browser/chat/chat-agent.service.ts +7 -7
  130. package/src/browser/chat/chat-model.ts +19 -2
  131. package/src/browser/chat/chat.view.tsx +63 -20
  132. package/src/browser/components/ChatEditor.tsx +72 -9
  133. package/src/browser/components/ChatMentionInput.tsx +268 -0
  134. package/src/browser/components/ChatReply.tsx +61 -18
  135. package/src/browser/components/ChatThinking.tsx +1 -1
  136. package/src/browser/components/WelcomeMsg.tsx +1 -1
  137. package/src/browser/components/{ChatContext → chat-context}/index.tsx +1 -1
  138. package/src/browser/components/components.module.less +43 -0
  139. package/src/browser/components/mention-input/mention-input.module.less +327 -0
  140. package/src/browser/components/mention-input/mention-input.tsx +943 -0
  141. package/src/browser/components/mention-input/mention-item.tsx +24 -0
  142. package/src/browser/components/mention-input/mention-panel.tsx +89 -0
  143. package/src/browser/components/mention-input/types.ts +82 -0
  144. package/src/browser/context/llm-context.service.ts +81 -3
  145. package/src/browser/contrib/inline-completions/prompt/matcher.ts +2 -2
  146. package/src/browser/contrib/inline-completions/prompt/similarSnippets.ts +2 -2
  147. package/src/browser/contrib/intelligent-completions/view/default.ts +0 -1
  148. package/src/browser/mcp/config/components/mcp-config.view.tsx +23 -12
  149. package/src/browser/mcp/config/components/mcp-server-form.tsx +68 -54
  150. package/src/browser/mcp/mcp-server.feature.registry.ts +1 -1
  151. package/src/browser/mcp/tools/components/ExpandableFileList.tsx +4 -1
  152. package/src/browser/mcp/tools/components/Terminal.tsx +4 -6
  153. package/src/browser/mcp/tools/components/computeAnsiLogString.ts +24 -0
  154. package/src/browser/mcp/tools/components/filterEraseMultipleLine.ts +71 -0
  155. package/src/browser/mcp/tools/components/index.module.less +8 -5
  156. package/src/browser/mcp/tools/createNewFileWithText.ts +1 -0
  157. package/src/browser/mcp/tools/editFile.ts +1 -0
  158. package/src/browser/mcp/tools/fileSearch.ts +1 -0
  159. package/src/browser/mcp/tools/getDiagnosticsByPath.ts +2 -1
  160. package/src/browser/mcp/tools/getOpenEditorFileDiagnostics.ts +2 -0
  161. package/src/browser/mcp/tools/grepSearch.ts +1 -0
  162. package/src/browser/mcp/tools/listDir.ts +1 -0
  163. package/src/browser/mcp/tools/readFile.ts +1 -0
  164. package/src/browser/mcp/tools/runTerminalCmd.ts +1 -0
  165. package/src/browser/types.ts +1 -0
  166. package/src/browser/widget/inline-diff/inline-diff-manager.tsx +0 -1
  167. package/src/browser/widget/inline-stream-diff/live-preview.decoration.tsx +9 -5
  168. package/src/common/llm-context.ts +16 -1
  169. package/src/common/prompts/context-prompt-provider.ts +126 -32
  170. package/src/node/base-language-model.ts +5 -0
  171. package/lib/browser/components/ChatContext/ContextSelector.d.ts.map +0 -1
  172. package/lib/browser/components/ChatContext/ContextSelector.js.map +0 -1
  173. package/lib/browser/components/ChatContext/index.d.ts.map +0 -1
  174. package/lib/browser/components/ChatContext/index.js.map +0 -1
  175. /package/lib/browser/components/{ChatContext → chat-context}/index.d.ts +0 -0
  176. /package/lib/browser/components/{ChatContext → chat-context}/style.module.less +0 -0
  177. /package/src/browser/components/{ChatContext/ContextSelector.tsx → chat-context/context-selector.tsx} +0 -0
  178. /package/src/browser/components/{ChatContext → chat-context}/style.module.less +0 -0
@@ -59,7 +59,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
59
59
  private readonly aiReporter: IAIReporter;
60
60
 
61
61
  @Autowired(LLMContextServiceToken)
62
- protected readonly contextService: LLMContextService;
62
+ protected readonly llmContextService: LLMContextService;
63
63
 
64
64
  @Autowired(ChatAgentPromptProvider)
65
65
  protected readonly promptProvider: ChatAgentPromptProvider;
@@ -74,7 +74,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
74
74
  super();
75
75
  this.addDispose(this._onDidChangeAgents);
76
76
  this.addDispose(
77
- this.contextService.onDidContextFilesChangeEvent((event) => {
77
+ this.llmContextService.onDidContextFilesChangeEvent((event) => {
78
78
  if (event.version !== this.contextVersion) {
79
79
  this.contextVersion = event.version;
80
80
  this.shouldUpdateContext = true;
@@ -152,9 +152,9 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
152
152
  if (!this.initialUserMessageMap.has(request.sessionId)) {
153
153
  this.initialUserMessageMap.set(request.sessionId, request.message);
154
154
  const rawMessage = request.message;
155
- request.message = this.provideContextMessage(rawMessage, request.sessionId);
155
+ request.message = await this.provideContextMessage(rawMessage, request.sessionId);
156
156
  } else if (this.shouldUpdateContext || request.regenerate || history.length === 0) {
157
- request.message = this.provideContextMessage(request.message, request.sessionId);
157
+ request.message = await this.provideContextMessage(request.message, request.sessionId);
158
158
  this.shouldUpdateContext = false;
159
159
  }
160
160
 
@@ -162,9 +162,9 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
162
162
  return result;
163
163
  }
164
164
 
165
- private provideContextMessage(message: string, sessionId: string) {
166
- const context = this.contextService.serialize();
167
- const fullMessage = this.promptProvider.provideContextPrompt(context, message);
165
+ private async provideContextMessage(message: string, sessionId: string) {
166
+ const context = await this.llmContextService.serialize();
167
+ const fullMessage = await this.promptProvider.provideContextPrompt(context, message);
168
168
  this.aiReporter.send({
169
169
  msgType: AIServiceType.Chat,
170
170
  actionType: ActionTypeEnum.ContextEnhance,
@@ -6,6 +6,7 @@ import {
6
6
  IChatComponent,
7
7
  IChatMarkdownContent,
8
8
  IChatProgress,
9
+ IChatReasoning,
9
10
  IChatToolContent,
10
11
  IChatTreeData,
11
12
  uuid,
@@ -33,7 +34,8 @@ export type IChatProgressResponseContent =
33
34
  | IChatAsyncContent
34
35
  | IChatTreeData
35
36
  | IChatComponent
36
- | IChatToolContent;
37
+ | IChatToolContent
38
+ | IChatReasoning;
37
39
 
38
40
  export class ChatResponseModel extends Disposable {
39
41
  #responseParts: IChatProgressResponseContent[] = [];
@@ -131,6 +133,18 @@ export class ChatResponseModel extends Disposable {
131
133
  };
132
134
  }
133
135
 
136
+ this.#updateResponseText();
137
+ } else if (progress.kind === 'reasoning') {
138
+ const lastResponsePart = this.#responseParts[responsePartLength];
139
+ if (!lastResponsePart || lastResponsePart.kind !== 'reasoning') {
140
+ // 去掉开头的 <think> 标签
141
+ this.#responseParts.push({ content: progress.content.replace(/^<think>/, ''), kind: 'reasoning' });
142
+ } else {
143
+ this.#responseParts[responsePartLength] = {
144
+ content: lastResponsePart.content + progress.content,
145
+ kind: 'reasoning',
146
+ };
147
+ }
134
148
  this.#updateResponseText();
135
149
  } else if (progress.kind === 'asyncContent') {
136
150
  // Add a new resolving part
@@ -181,6 +195,9 @@ export class ChatResponseModel extends Disposable {
181
195
  if (part.kind === 'toolCall') {
182
196
  return part.content.function.name;
183
197
  }
198
+ if (part.kind === 'reasoning') {
199
+ return '';
200
+ }
184
201
  return part.content.value;
185
202
  })
186
203
  .join('\n\n');
@@ -387,7 +404,7 @@ export class ChatModel extends Disposable implements IChatModel {
387
404
 
388
405
  const { kind } = progress;
389
406
 
390
- const basicKind = ['content', 'markdownContent', 'asyncContent', 'treeData', 'component', 'toolCall'];
407
+ const basicKind = ['content', 'markdownContent', 'asyncContent', 'treeData', 'component', 'toolCall', 'reasoning'];
391
408
 
392
409
  if (basicKind.includes(kind)) {
393
410
  request.response.updateContent(progress, quiet);
@@ -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 {
@@ -31,13 +38,14 @@ import { IMessageService } from '@opensumi/ide-overlay';
31
38
 
32
39
  import 'react-chat-elements/dist/main.css';
33
40
  import { AI_CHAT_VIEW_ID, IChatAgentService, IChatInternalService, IChatMessageStructure } from '../../common';
41
+ import { LLMContextService, LLMContextServiceToken } from '../../common/llm-context';
34
42
  import { CodeBlockData } from '../../common/types';
35
43
  import { FileChange, FileListDisplay } from '../components/ChangeList';
36
- import { ChatContext } from '../components/ChatContext';
37
44
  import { CodeBlockWrapperInput } from '../components/ChatEditor';
38
45
  import ChatHistory, { IChatHistoryItem } from '../components/ChatHistory';
39
46
  import { ChatInput } from '../components/ChatInput';
40
47
  import { ChatMarkdown } from '../components/ChatMarkdown';
48
+ import { ChatMentionInput } from '../components/ChatMentionInput';
41
49
  import { ChatNotify, ChatReply } from '../components/ChatReply';
42
50
  import { SlashCustomRender } from '../components/SlashCustomRender';
43
51
  import { MessageData, createMessageByAI, createMessageByUser } from '../components/utils';
@@ -105,6 +113,8 @@ export const AIChatView = () => {
105
113
  const chatFeatureRegistry = useInjectable<ChatFeatureRegistry>(ChatFeatureRegistryToken);
106
114
  const chatRenderRegistry = useInjectable<ChatRenderRegistry>(ChatRenderRegistryToken);
107
115
  const mcpServerRegistry = useInjectable<IMCPServerRegistry>(TokenMCPServerRegistry);
116
+ const aiNativeConfigService = useInjectable<AINativeConfigService>(AINativeConfigService);
117
+ const llmContextService = useInjectable<LLMContextService>(LLMContextServiceToken);
108
118
 
109
119
  const layoutService = useInjectable<IMainLayoutService>(IMainLayoutService);
110
120
  const msgHistoryManager = aiChatService.sessionModel.history;
@@ -114,6 +124,7 @@ export const AIChatView = () => {
114
124
  const editorService = useInjectable<WorkbenchEditorService>(WorkbenchEditorService);
115
125
  const appConfig = useInjectable<AppConfig>(AppConfig);
116
126
  const applyService = useInjectable<BaseApplyService>(BaseApplyService);
127
+ const labelService = useInjectable<LabelService>(LabelService);
117
128
  const [shortcutCommands, setShortcutCommands] = React.useState<ChatSlashCommandItemModel[]>([]);
118
129
 
119
130
  const [changeList, setChangeList] = React.useState<FileChange[]>(getFileChanges(applyService.getSessionCodeBlocks()));
@@ -184,6 +195,9 @@ export const AIChatView = () => {
184
195
  if (chatRenderRegistry.chatInputRender) {
185
196
  return chatRenderRegistry.chatInputRender;
186
197
  }
198
+ if (aiNativeConfigService.capabilities.supportsMCP) {
199
+ return ChatMentionInput;
200
+ }
187
201
  return ChatInput;
188
202
  }, [chatRenderRegistry.chatInputRender]);
189
203
 
@@ -262,7 +276,7 @@ export const AIChatView = () => {
262
276
  if (loading) {
263
277
  return;
264
278
  }
265
- await handleSend(message);
279
+ await handleSend(message.message, message.agentId, message.command);
266
280
  } else {
267
281
  if (message.agentId) {
268
282
  setAgentId(message.agentId);
@@ -349,6 +363,7 @@ export const AIChatView = () => {
349
363
  text={message}
350
364
  agentId={visibleAgentId}
351
365
  command={command}
366
+ labelService={labelService}
352
367
  />
353
368
  ),
354
369
  },
@@ -454,7 +469,13 @@ export const AIChatView = () => {
454
469
  text: ChatUserRoleRender ? (
455
470
  <ChatUserRoleRender content={message} agentId={visibleAgentId} command={command} />
456
471
  ) : (
457
- <CodeBlockWrapperInput relationId={relationId} text={message} agentId={visibleAgentId} command={command} />
472
+ <CodeBlockWrapperInput
473
+ labelService={labelService}
474
+ relationId={relationId}
475
+ text={message}
476
+ agentId={visibleAgentId}
477
+ command={command}
478
+ />
458
479
  ),
459
480
  },
460
481
  styles.chat_message_code,
@@ -638,11 +659,44 @@ export const AIChatView = () => {
638
659
  );
639
660
 
640
661
  const handleSend = React.useCallback(
641
- async (value: IChatMessageStructure) => {
642
- const { message, command, reportExtra } = value;
662
+ async (message: string, agentId?: string, command?: string) => {
663
+ const reportExtra = {
664
+ actionSource: ActionSourceEnum.Chat,
665
+ actionType: ActionTypeEnum.Send,
666
+ };
667
+ agentId = agentId ? agentId : ChatProxyService.AGENT_ID;
668
+ // 提取并替换 {{@file:xxx}} 中的文件内容
669
+ let processedContent = message;
670
+ const filePattern = /\{\{@file:(.*?)\}\}/g;
671
+ const fileMatches = message.match(filePattern);
672
+ let isCleanContext = false;
673
+ if (fileMatches) {
674
+ for (const match of fileMatches) {
675
+ const filePath = match.replace(/\{\{@file:(.*?)\}\}/, '$1');
676
+ if (filePath && !isCleanContext) {
677
+ isCleanContext = true;
678
+ llmContextService.cleanFileContext();
679
+ }
680
+ const fileUri = new URI(filePath);
681
+ llmContextService.addFileToContext(fileUri, undefined, true);
682
+ // 获取文件内容
683
+ // 替换占位符,后续支持自定义渲染时可替换为自定义渲染标签
684
+ processedContent = processedContent.replace(match, `\`<attached_file>${fileUri.displayName}\``);
685
+ }
686
+ }
643
687
 
644
- const agentId = value.agentId ? value.agentId : ChatProxyService.AGENT_ID;
645
- return handleAgentReply({ message, agentId, command, reportExtra });
688
+ const folderPattern = /\{\{@folder:(.*?)\}\}/g;
689
+ const folderMatches = processedContent.match(folderPattern);
690
+ if (folderMatches) {
691
+ for (const match of folderMatches) {
692
+ const folderPath = match.replace(/\{\{@folder:(.*?)\}\}/, '$1');
693
+ const folderUri = new URI(folderPath);
694
+ llmContextService.addFolderToContext(folderUri);
695
+ // 替换占位符,后续支持自定义渲染时可替换为自定义渲染标签
696
+ processedContent = processedContent.replace(match, `\`<attached_folder>${folderUri.displayName}\``);
697
+ }
698
+ }
699
+ return handleAgentReply({ message: processedContent, agentId, command, reportExtra });
646
700
  },
647
701
  [handleAgentReply],
648
702
  );
@@ -759,7 +813,6 @@ export const AIChatView = () => {
759
813
  </div>
760
814
  ) : null}
761
815
  <div className={styles.chat_input_wrap}>
762
- <ChatContext />
763
816
  <div className={styles.header_operate}>
764
817
  <div className={styles.header_operate_left}>
765
818
  {shortcutCommands.map((command) => (
@@ -790,17 +843,7 @@ export const AIChatView = () => {
790
843
  />
791
844
  )}
792
845
  <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
- }
846
+ onSend={handleSend}
804
847
  disabled={loading}
805
848
  enableOptions={true}
806
849
  theme={theme}
@@ -2,14 +2,15 @@ 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 { IClipboardService, LabelService, getIcon, useInjectable, uuid } from '@opensumi/ide-core-browser';
6
+ import { Icon, Popover } from '@opensumi/ide-core-browser/lib/components';
7
7
  import { EnhanceIcon } from '@opensumi/ide-core-browser/lib/components/ai-native';
8
8
  import {
9
9
  ActionSourceEnum,
10
10
  ActionTypeEnum,
11
11
  ChatFeatureRegistryToken,
12
12
  IAIReporter,
13
+ URI,
13
14
  localize,
14
15
  runWhenIdle,
15
16
  } from '@opensumi/ide-core-common';
@@ -139,16 +140,26 @@ const CodeBlock = ({
139
140
  renderText,
140
141
  agentId = '',
141
142
  command = '',
143
+ labelService,
142
144
  }: {
143
145
  content?: string;
144
146
  relationId: string;
145
147
  renderText?: (t: string) => React.ReactNode;
146
148
  agentId?: string;
147
149
  command?: string;
150
+ labelService?: LabelService;
148
151
  }) => {
149
152
  const rgInlineCode = /`([^`]+)`/g;
150
153
  const rgBlockCode = /```([^]+?)```/g;
151
154
  const rgBlockCodeBefore = /```([^]+)?/g;
155
+ const rgAttachedFile = /<attached_file>(.*)/g;
156
+ const rgAttachedFolder = /<attached_folder>(.*)/g;
157
+ const renderAttachment = (text: string, isFolder = false, key: string) => (
158
+ <span className={styles.attachment} key={key}>
159
+ <Icon iconClass={isFolder ? getIcon('folder') : labelService?.getIcon(new URI(text || 'file'))} />
160
+ <span className={styles.attachment_text}>{text}</span>
161
+ </span>
162
+ );
152
163
 
153
164
  const renderCodeEditor = (content: string) => {
154
165
  const language = content.split('\n')[0].trim().toLowerCase();
@@ -193,11 +204,47 @@ const CodeBlock = ({
193
204
  renderedContent.push(text);
194
205
  }
195
206
  } else {
196
- renderedContent.push(
197
- <span className={styles.code_inline} key={index}>
198
- {text}
199
- </span>,
200
- );
207
+ // 处理文件和文件夹标记
208
+ const processedText = text;
209
+ const fileMatches = [...text.matchAll(rgAttachedFile)];
210
+ const folderMatches = [...text.matchAll(rgAttachedFolder)];
211
+ if (fileMatches.length || folderMatches.length) {
212
+ let lastIndex = 0;
213
+ const fragments: (string | React.ReactNode)[] = [];
214
+
215
+ // 处理文件标记
216
+ fileMatches.forEach((match, matchIndex) => {
217
+ if (match.index !== undefined) {
218
+ const spanText = processedText.slice(lastIndex, match.index);
219
+ if (spanText) {
220
+ fragments.push(<span key={`${index}-${matchIndex}`}>{spanText}</span>);
221
+ }
222
+ fragments.push(renderAttachment(match[1], false, `${index}-tag-${matchIndex}`));
223
+ lastIndex = match.index + match[0].length;
224
+ }
225
+ });
226
+
227
+ // 处理文件夹标记
228
+ folderMatches.forEach((match, matchIndex) => {
229
+ if (match.index !== undefined) {
230
+ const spanText = processedText.slice(lastIndex, match.index);
231
+ if (spanText) {
232
+ fragments.push(<span key={`${index}-${matchIndex}`}>{spanText}</span>);
233
+ }
234
+ fragments.push(renderAttachment(match[1], true, `${index}-tag-${matchIndex}`));
235
+ lastIndex = match.index + match[0].length;
236
+ }
237
+ });
238
+
239
+ fragments.push(processedText.slice(lastIndex));
240
+ renderedContent.push(...fragments);
241
+ } else {
242
+ renderedContent.push(
243
+ <span className={styles.code_inline} key={index}>
244
+ {text}
245
+ </span>,
246
+ );
247
+ }
201
248
  }
202
249
  });
203
250
  } else {
@@ -216,15 +263,23 @@ export const CodeBlockWrapper = ({
216
263
  renderText,
217
264
  relationId,
218
265
  agentId,
266
+ labelService,
219
267
  }: {
220
268
  text?: string;
221
269
  relationId: string;
222
270
  renderText?: (t: string) => React.ReactNode;
223
271
  agentId?: string;
272
+ labelService?: LabelService;
224
273
  }) => (
225
274
  <div className={styles.ai_chat_code_wrapper}>
226
275
  <div className={styles.render_text}>
227
- <CodeBlock content={text} renderText={renderText} relationId={relationId} agentId={agentId} />
276
+ <CodeBlock
277
+ content={text}
278
+ labelService={labelService}
279
+ renderText={renderText}
280
+ relationId={relationId}
281
+ agentId={agentId}
282
+ />
228
283
  </div>
229
284
  </div>
230
285
  );
@@ -234,11 +289,13 @@ export const CodeBlockWrapperInput = ({
234
289
  relationId,
235
290
  agentId,
236
291
  command,
292
+ labelService,
237
293
  }: {
238
294
  text: string;
239
295
  relationId: string;
240
296
  agentId?: string;
241
297
  command?: string;
298
+ labelService?: LabelService;
242
299
  }) => {
243
300
  const chatFeatureRegistry = useInjectable<ChatFeatureRegistry>(ChatFeatureRegistryToken);
244
301
  const [tag, setTag] = useState<string>('');
@@ -271,7 +328,13 @@ export const CodeBlockWrapperInput = ({
271
328
  </div>
272
329
  )}
273
330
  {command && <div className={styles.tag}>/ {command}</div>}
274
- <CodeBlock content={txt} relationId={relationId} agentId={agentId} command={command} />
331
+ <CodeBlock
332
+ content={txt}
333
+ labelService={labelService}
334
+ relationId={relationId}
335
+ agentId={agentId}
336
+ command={command}
337
+ />
275
338
  </div>
276
339
  </div>
277
340
  );
@@ -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
+ });