@opensumi/ide-ai-native 3.8.3-next-1741763277.0 → 3.8.3-next-1741766394.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.
- package/lib/browser/chat/chat-agent.service.d.ts +1 -1
- package/lib/browser/chat/chat-agent.service.d.ts.map +1 -1
- package/lib/browser/chat/chat-agent.service.js +7 -7
- package/lib/browser/chat/chat-agent.service.js.map +1 -1
- package/lib/browser/chat/chat.view.d.ts.map +1 -1
- package/lib/browser/chat/chat.view.js +52 -18
- package/lib/browser/chat/chat.view.js.map +1 -1
- package/lib/browser/components/ChatEditor.d.ts +11 -2
- package/lib/browser/components/ChatEditor.d.ts.map +1 -1
- package/lib/browser/components/ChatEditor.js +66 -6
- package/lib/browser/components/ChatEditor.js.map +1 -1
- package/lib/browser/components/ChatMentionInput.d.ts +25 -0
- package/lib/browser/components/ChatMentionInput.d.ts.map +1 -0
- package/lib/browser/components/ChatMentionInput.js +221 -0
- package/lib/browser/components/ChatMentionInput.js.map +1 -0
- package/lib/browser/components/{ChatContext/ContextSelector.d.ts → chat-context/context-selector.d.ts} +1 -1
- package/lib/browser/components/chat-context/context-selector.d.ts.map +1 -0
- package/lib/browser/components/{ChatContext/ContextSelector.js → chat-context/context-selector.js} +1 -1
- package/lib/browser/components/chat-context/context-selector.js.map +1 -0
- package/lib/browser/components/chat-context/index.d.ts.map +1 -0
- package/lib/browser/components/{ChatContext → chat-context}/index.js +2 -2
- package/lib/browser/components/chat-context/index.js.map +1 -0
- package/lib/browser/components/components.module.less +20 -0
- package/lib/browser/components/mention-input/mention-input.d.ts +5 -0
- package/lib/browser/components/mention-input/mention-input.d.ts.map +1 -0
- package/lib/browser/components/mention-input/mention-input.js +753 -0
- package/lib/browser/components/mention-input/mention-input.js.map +1 -0
- package/lib/browser/components/mention-input/mention-input.module.less +327 -0
- package/lib/browser/components/mention-input/mention-item.d.ts +10 -0
- package/lib/browser/components/mention-input/mention-item.d.ts.map +1 -0
- package/lib/browser/components/mention-input/mention-item.js +16 -0
- package/lib/browser/components/mention-input/mention-item.js.map +1 -0
- package/lib/browser/components/mention-input/mention-panel.d.ts +15 -0
- package/lib/browser/components/mention-input/mention-panel.d.ts.map +1 -0
- package/lib/browser/components/mention-input/mention-panel.js +49 -0
- package/lib/browser/components/mention-input/mention-panel.js.map +1 -0
- package/lib/browser/components/mention-input/types.d.ts +76 -0
- package/lib/browser/components/mention-input/types.d.ts.map +1 -0
- package/lib/browser/components/mention-input/types.js +16 -0
- package/lib/browser/components/mention-input/types.js.map +1 -0
- package/lib/browser/context/llm-context.service.d.ts +10 -2
- package/lib/browser/context/llm-context.service.d.ts.map +1 -1
- package/lib/browser/context/llm-context.service.js +71 -2
- package/lib/browser/context/llm-context.service.js.map +1 -1
- package/lib/common/llm-context.d.ts +15 -1
- package/lib/common/llm-context.d.ts.map +1 -1
- package/lib/common/llm-context.js.map +1 -1
- package/lib/common/prompts/context-prompt-provider.d.ts +12 -2
- package/lib/common/prompts/context-prompt-provider.d.ts.map +1 -1
- package/lib/common/prompts/context-prompt-provider.js +94 -30
- package/lib/common/prompts/context-prompt-provider.js.map +1 -1
- package/package.json +23 -23
- package/src/browser/chat/chat-agent.service.ts +7 -7
- package/src/browser/chat/chat.view.tsx +72 -21
- package/src/browser/components/ChatEditor.tsx +126 -9
- package/src/browser/components/ChatMentionInput.tsx +268 -0
- package/src/browser/components/{ChatContext → chat-context}/index.tsx +1 -1
- package/src/browser/components/components.module.less +20 -0
- package/src/browser/components/mention-input/mention-input.module.less +327 -0
- package/src/browser/components/mention-input/mention-input.tsx +943 -0
- package/src/browser/components/mention-input/mention-item.tsx +24 -0
- package/src/browser/components/mention-input/mention-panel.tsx +89 -0
- package/src/browser/components/mention-input/types.ts +82 -0
- package/src/browser/context/llm-context.service.ts +81 -3
- package/src/common/llm-context.ts +16 -1
- package/src/common/prompts/context-prompt-provider.ts +126 -36
- package/lib/browser/components/ChatContext/ContextSelector.d.ts.map +0 -1
- package/lib/browser/components/ChatContext/ContextSelector.js.map +0 -1
- package/lib/browser/components/ChatContext/index.d.ts.map +0 -1
- package/lib/browser/components/ChatContext/index.js.map +0 -1
- /package/lib/browser/components/{ChatContext → chat-context}/index.d.ts +0 -0
- /package/lib/browser/components/{ChatContext → chat-context}/style.module.less +0 -0
- /package/src/browser/components/{ChatContext/ContextSelector.tsx → chat-context/context-selector.tsx} +0 -0
- /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 {
|
|
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
|
|
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 (
|
|
642
|
-
const
|
|
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
|
|
645
|
-
|
|
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={
|
|
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 {
|
|
6
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
|
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
|
|
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 './
|
|
18
|
+
import { ContextSelector } from './context-selector';
|
|
19
19
|
import styles from './style.module.less';
|
|
20
20
|
|
|
21
21
|
const getCollapsedHeight = () => ({ height: 0, opacity: 0 });
|