@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.
- 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-model.d.ts +2 -2
- package/lib/browser/chat/chat-model.d.ts.map +1 -1
- package/lib/browser/chat/chat-model.js +18 -1
- package/lib/browser/chat/chat-model.js.map +1 -1
- package/lib/browser/chat/chat.view.d.ts.map +1 -1
- package/lib/browser/chat/chat.view.js +49 -18
- package/lib/browser/chat/chat.view.js.map +1 -1
- package/lib/browser/components/ChatEditor.d.ts +5 -2
- package/lib/browser/components/ChatEditor.d.ts.map +1 -1
- package/lib/browser/components/ChatEditor.js +45 -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/ChatReply.d.ts.map +1 -1
- package/lib/browser/components/ChatReply.js +35 -17
- package/lib/browser/components/ChatReply.js.map +1 -1
- package/lib/browser/components/ChatThinking.js +1 -1
- package/lib/browser/components/ChatThinking.js.map +1 -1
- package/lib/browser/components/WelcomeMsg.js +1 -1
- package/lib/browser/components/WelcomeMsg.js.map +1 -1
- 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 +43 -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/browser/contrib/inline-completions/prompt/matcher.js +2 -2
- package/lib/browser/contrib/inline-completions/prompt/similarSnippets.d.ts +1 -1
- package/lib/browser/contrib/inline-completions/prompt/similarSnippets.js +2 -2
- package/lib/browser/contrib/intelligent-completions/view/default.d.ts.map +1 -1
- package/lib/browser/contrib/intelligent-completions/view/default.js.map +1 -1
- package/lib/browser/mcp/config/components/mcp-config.view.d.ts.map +1 -1
- package/lib/browser/mcp/config/components/mcp-config.view.js +28 -18
- package/lib/browser/mcp/config/components/mcp-config.view.js.map +1 -1
- package/lib/browser/mcp/config/components/mcp-server-form.d.ts.map +1 -1
- package/lib/browser/mcp/config/components/mcp-server-form.js +33 -25
- package/lib/browser/mcp/config/components/mcp-server-form.js.map +1 -1
- package/lib/browser/mcp/mcp-server.feature.registry.js +1 -1
- package/lib/browser/mcp/mcp-server.feature.registry.js.map +1 -1
- package/lib/browser/mcp/tools/components/ExpandableFileList.d.ts.map +1 -1
- package/lib/browser/mcp/tools/components/ExpandableFileList.js +3 -1
- package/lib/browser/mcp/tools/components/ExpandableFileList.js.map +1 -1
- package/lib/browser/mcp/tools/components/Terminal.d.ts.map +1 -1
- package/lib/browser/mcp/tools/components/Terminal.js +6 -5
- package/lib/browser/mcp/tools/components/Terminal.js.map +1 -1
- package/lib/browser/mcp/tools/components/computeAnsiLogString.d.ts +4 -0
- package/lib/browser/mcp/tools/components/computeAnsiLogString.d.ts.map +1 -0
- package/lib/browser/mcp/tools/components/computeAnsiLogString.js +22 -0
- package/lib/browser/mcp/tools/components/computeAnsiLogString.js.map +1 -0
- package/lib/browser/mcp/tools/components/filterEraseMultipleLine.d.ts +18 -0
- package/lib/browser/mcp/tools/components/filterEraseMultipleLine.d.ts.map +1 -0
- package/lib/browser/mcp/tools/components/filterEraseMultipleLine.js +69 -0
- package/lib/browser/mcp/tools/components/filterEraseMultipleLine.js.map +1 -0
- package/lib/browser/mcp/tools/components/index.module.less +8 -5
- package/lib/browser/mcp/tools/createNewFileWithText.d.ts.map +1 -1
- package/lib/browser/mcp/tools/createNewFileWithText.js +1 -0
- package/lib/browser/mcp/tools/createNewFileWithText.js.map +1 -1
- package/lib/browser/mcp/tools/editFile.d.ts.map +1 -1
- package/lib/browser/mcp/tools/editFile.js +1 -0
- package/lib/browser/mcp/tools/editFile.js.map +1 -1
- package/lib/browser/mcp/tools/fileSearch.d.ts.map +1 -1
- package/lib/browser/mcp/tools/fileSearch.js +1 -0
- package/lib/browser/mcp/tools/fileSearch.js.map +1 -1
- package/lib/browser/mcp/tools/getDiagnosticsByPath.d.ts.map +1 -1
- package/lib/browser/mcp/tools/getDiagnosticsByPath.js +2 -1
- package/lib/browser/mcp/tools/getDiagnosticsByPath.js.map +1 -1
- package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.d.ts.map +1 -1
- package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.js +2 -0
- package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.js.map +1 -1
- package/lib/browser/mcp/tools/grepSearch.d.ts.map +1 -1
- package/lib/browser/mcp/tools/grepSearch.js +1 -0
- package/lib/browser/mcp/tools/grepSearch.js.map +1 -1
- package/lib/browser/mcp/tools/listDir.d.ts.map +1 -1
- package/lib/browser/mcp/tools/listDir.js +1 -0
- package/lib/browser/mcp/tools/listDir.js.map +1 -1
- package/lib/browser/mcp/tools/readFile.d.ts.map +1 -1
- package/lib/browser/mcp/tools/readFile.js +1 -0
- package/lib/browser/mcp/tools/readFile.js.map +1 -1
- package/lib/browser/mcp/tools/runTerminalCmd.d.ts.map +1 -1
- package/lib/browser/mcp/tools/runTerminalCmd.js +1 -0
- package/lib/browser/mcp/tools/runTerminalCmd.js.map +1 -1
- package/lib/browser/types.d.ts +1 -0
- package/lib/browser/types.d.ts.map +1 -1
- package/lib/browser/widget/inline-diff/inline-diff-manager.d.ts.map +1 -1
- package/lib/browser/widget/inline-diff/inline-diff-manager.js.map +1 -1
- package/lib/browser/widget/inline-stream-diff/live-preview.decoration.d.ts.map +1 -1
- package/lib/browser/widget/inline-stream-diff/live-preview.decoration.js +10 -5
- package/lib/browser/widget/inline-stream-diff/live-preview.decoration.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 -28
- package/lib/common/prompts/context-prompt-provider.js.map +1 -1
- package/lib/node/base-language-model.d.ts.map +1 -1
- package/lib/node/base-language-model.js +6 -0
- package/lib/node/base-language-model.js.map +1 -1
- package/package.json +24 -23
- package/src/browser/chat/chat-agent.service.ts +7 -7
- package/src/browser/chat/chat-model.ts +19 -2
- package/src/browser/chat/chat.view.tsx +63 -20
- package/src/browser/components/ChatEditor.tsx +72 -9
- package/src/browser/components/ChatMentionInput.tsx +268 -0
- package/src/browser/components/ChatReply.tsx +61 -18
- package/src/browser/components/ChatThinking.tsx +1 -1
- package/src/browser/components/WelcomeMsg.tsx +1 -1
- package/src/browser/components/{ChatContext → chat-context}/index.tsx +1 -1
- package/src/browser/components/components.module.less +43 -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/browser/contrib/inline-completions/prompt/matcher.ts +2 -2
- package/src/browser/contrib/inline-completions/prompt/similarSnippets.ts +2 -2
- package/src/browser/contrib/intelligent-completions/view/default.ts +0 -1
- package/src/browser/mcp/config/components/mcp-config.view.tsx +23 -12
- package/src/browser/mcp/config/components/mcp-server-form.tsx +68 -54
- package/src/browser/mcp/mcp-server.feature.registry.ts +1 -1
- package/src/browser/mcp/tools/components/ExpandableFileList.tsx +4 -1
- package/src/browser/mcp/tools/components/Terminal.tsx +4 -6
- package/src/browser/mcp/tools/components/computeAnsiLogString.ts +24 -0
- package/src/browser/mcp/tools/components/filterEraseMultipleLine.ts +71 -0
- package/src/browser/mcp/tools/components/index.module.less +8 -5
- package/src/browser/mcp/tools/createNewFileWithText.ts +1 -0
- package/src/browser/mcp/tools/editFile.ts +1 -0
- package/src/browser/mcp/tools/fileSearch.ts +1 -0
- package/src/browser/mcp/tools/getDiagnosticsByPath.ts +2 -1
- package/src/browser/mcp/tools/getOpenEditorFileDiagnostics.ts +2 -0
- package/src/browser/mcp/tools/grepSearch.ts +1 -0
- package/src/browser/mcp/tools/listDir.ts +1 -0
- package/src/browser/mcp/tools/readFile.ts +1 -0
- package/src/browser/mcp/tools/runTerminalCmd.ts +1 -0
- package/src/browser/types.ts +1 -0
- package/src/browser/widget/inline-diff/inline-diff-manager.tsx +0 -1
- package/src/browser/widget/inline-stream-diff/live-preview.decoration.tsx +9 -5
- package/src/common/llm-context.ts +16 -1
- package/src/common/prompts/context-prompt-provider.ts +126 -32
- package/src/node/base-language-model.ts +5 -0
- 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
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import cls from 'classnames';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
import { Icon, getIcon } from '@opensumi/ide-core-browser/lib/components';
|
|
5
|
+
|
|
6
|
+
import styles from './mention-input.module.less';
|
|
7
|
+
import { MentionItem as MentionItemType } from './types';
|
|
8
|
+
|
|
9
|
+
interface MentionItemProps {
|
|
10
|
+
item: MentionItemType;
|
|
11
|
+
isActive: boolean;
|
|
12
|
+
onClick: (item: MentionItemType) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const MentionItem: React.FC<MentionItemProps> = ({ item, isActive, onClick }) => (
|
|
16
|
+
<div className={`${styles.mention_item} ${isActive ? styles.active : ''}`} onClick={() => onClick(item)}>
|
|
17
|
+
<div className={styles.mention_item_left}>
|
|
18
|
+
<Icon className={cls(styles.mention_item_icon, item.icon)} />
|
|
19
|
+
<span className={styles.mention_item_text}>{item.text}</span>
|
|
20
|
+
<span className={styles.mention_item_description}>{item.description}</span>
|
|
21
|
+
</div>
|
|
22
|
+
{item.getItems && <Icon className={cls(styles.mention_item_right, getIcon('arrowright'))} />}
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import styles from './mention-input.module.less';
|
|
4
|
+
import { MentionItem } from './mention-item';
|
|
5
|
+
import { MentionItem as MentionItemType, MentionPosition } from './types';
|
|
6
|
+
|
|
7
|
+
interface MentionPanelProps {
|
|
8
|
+
items: MentionItemType[];
|
|
9
|
+
activeIndex: number;
|
|
10
|
+
onSelectItem: (item: MentionItemType, isTriggerByClick?: boolean) => void;
|
|
11
|
+
position: MentionPosition;
|
|
12
|
+
filter: string;
|
|
13
|
+
visible: boolean;
|
|
14
|
+
level: number;
|
|
15
|
+
loading?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const MentionPanel: React.FC<MentionPanelProps> = ({
|
|
19
|
+
items,
|
|
20
|
+
activeIndex,
|
|
21
|
+
onSelectItem,
|
|
22
|
+
position,
|
|
23
|
+
filter,
|
|
24
|
+
visible,
|
|
25
|
+
level,
|
|
26
|
+
loading = false,
|
|
27
|
+
}) => {
|
|
28
|
+
const panelRef = React.useRef<HTMLDivElement>(null);
|
|
29
|
+
|
|
30
|
+
// 当活动项改变时滚动到可见区域
|
|
31
|
+
React.useEffect(() => {
|
|
32
|
+
if (visible && panelRef.current) {
|
|
33
|
+
const activeItem = panelRef.current.querySelector(`.${styles.mention_item}.${styles.active}`);
|
|
34
|
+
if (activeItem) {
|
|
35
|
+
activeItem.scrollIntoView({ block: 'nearest' });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}, [activeIndex, visible]);
|
|
39
|
+
|
|
40
|
+
if (!visible) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 根据过滤条件筛选项目
|
|
45
|
+
const getFilteredItems = () => {
|
|
46
|
+
let filteredItems = items;
|
|
47
|
+
|
|
48
|
+
if (level === 0) {
|
|
49
|
+
// 一级菜单根据 @ 后面的文本过滤
|
|
50
|
+
if (filter && filter.length > 1) {
|
|
51
|
+
const searchText = filter.substring(1).toLowerCase();
|
|
52
|
+
filteredItems = items.filter((item) => item.text.toLowerCase().includes(searchText));
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
// 二级菜单根据 @file: 后面的文本过滤
|
|
56
|
+
if (filter && filter.length > 0) {
|
|
57
|
+
filteredItems = items.filter((item) => item.text.toLowerCase().includes(filter.toLowerCase()));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return filteredItems;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const filteredItems = getFilteredItems();
|
|
65
|
+
|
|
66
|
+
if (level === 0 && filteredItems.length === 0) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div ref={panelRef} className={styles.mention_panel} style={{ top: position.top, left: position.left }}>
|
|
72
|
+
{loading && <div className={styles.loading_bar}></div>}
|
|
73
|
+
{items.length > 0 ? (
|
|
74
|
+
<ul className={styles.mention_list}>
|
|
75
|
+
{items.map((item, index) => (
|
|
76
|
+
<MentionItem
|
|
77
|
+
key={item.id}
|
|
78
|
+
item={item}
|
|
79
|
+
isActive={index === activeIndex}
|
|
80
|
+
onClick={() => onSelectItem(item, true)}
|
|
81
|
+
/>
|
|
82
|
+
))}
|
|
83
|
+
</ul>
|
|
84
|
+
) : (
|
|
85
|
+
<div className={styles.no_results}>{loading ? '正在搜索...' : '无匹配结果'}</div>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { LabelService } from '@opensumi/ide-core-browser';
|
|
2
|
+
|
|
3
|
+
export interface MentionItem {
|
|
4
|
+
id: string;
|
|
5
|
+
type: string;
|
|
6
|
+
text: string;
|
|
7
|
+
value?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
contextId?: string;
|
|
10
|
+
icon?: string;
|
|
11
|
+
getHighestLevelItems?: () => MentionItem[];
|
|
12
|
+
getItems?: (searchText: string) => Promise<MentionItem[]>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SecondLevelMenuConfig {
|
|
16
|
+
getDefaultItems: () => MentionItem[];
|
|
17
|
+
getHighestLevelItems: () => MentionItem[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface MentionPosition {
|
|
21
|
+
top: number;
|
|
22
|
+
left: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface MentionState {
|
|
26
|
+
active: boolean;
|
|
27
|
+
startPos: number | null;
|
|
28
|
+
filter: string;
|
|
29
|
+
position: MentionPosition;
|
|
30
|
+
activeIndex: number;
|
|
31
|
+
level: number; // 0: 一级菜单, 1: 二级菜单
|
|
32
|
+
parentType: string | null; // 二级菜单的父类型
|
|
33
|
+
secondLevelFilter: string; // 二级菜单的筛选文本
|
|
34
|
+
inlineSearchActive: boolean; // 是否在输入框中进行二级搜索
|
|
35
|
+
inlineSearchStartPos: number | null; // 内联搜索的起始位置
|
|
36
|
+
loading: boolean; // 加载状态
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface ModelOption {
|
|
40
|
+
label: string;
|
|
41
|
+
value: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export enum FooterButtonPosition {
|
|
45
|
+
LEFT = 'left',
|
|
46
|
+
RIGHT = 'right',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export enum MentionType {
|
|
50
|
+
FILE = 'file',
|
|
51
|
+
FOLDER = 'folder',
|
|
52
|
+
CODE = 'code',
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface FooterButton {
|
|
56
|
+
id: string;
|
|
57
|
+
icon: string;
|
|
58
|
+
title: string;
|
|
59
|
+
onClick?: () => void;
|
|
60
|
+
position: FooterButtonPosition;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface FooterConfig {
|
|
64
|
+
modelOptions?: ModelOption[];
|
|
65
|
+
defaultModel?: string;
|
|
66
|
+
buttons?: FooterButton[];
|
|
67
|
+
showModelSelector?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface MentionInputProps {
|
|
71
|
+
mentionItems?: MentionItem[]; // 简化为单一菜单项配置
|
|
72
|
+
onSend?: (content: string, config?: { model: string; [key: string]: any }) => void;
|
|
73
|
+
onStop?: () => void;
|
|
74
|
+
placeholder?: string;
|
|
75
|
+
loading?: boolean;
|
|
76
|
+
onSelectionChange?: (value: string) => void;
|
|
77
|
+
footerConfig?: FooterConfig; // 新增配置项
|
|
78
|
+
mentionKeyword?: string;
|
|
79
|
+
labelService?: LabelService;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const MENTION_KEYWORD = '@';
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
IEditorDocumentModelService,
|
|
11
11
|
} from '@opensumi/ide-editor/lib/browser/doc-model/types';
|
|
12
12
|
import { EditorSelectionChangeEvent } from '@opensumi/ide-editor/lib/browser/types';
|
|
13
|
+
import { FileType, IFileServiceClient } from '@opensumi/ide-file-service';
|
|
13
14
|
import { IMarkerService } from '@opensumi/ide-markers/lib/common/types';
|
|
14
15
|
import { Range } from '@opensumi/ide-monaco';
|
|
15
16
|
|
|
@@ -26,13 +27,18 @@ export class LLMContextServiceImpl extends WithEventBus implements LLMContextSer
|
|
|
26
27
|
@Autowired(IMarkerService)
|
|
27
28
|
protected readonly markerService: IMarkerService;
|
|
28
29
|
|
|
30
|
+
@Autowired(IFileServiceClient)
|
|
31
|
+
protected readonly fileService: IFileServiceClient;
|
|
32
|
+
|
|
29
33
|
private isAutoCollecting = false;
|
|
30
34
|
|
|
31
35
|
private contextVersion = 0;
|
|
32
36
|
|
|
33
37
|
private readonly maxAttachFilesLimit = 10;
|
|
38
|
+
private readonly maxAttachFoldersLimit = 10;
|
|
34
39
|
private readonly maxViewFilesLimit = 20;
|
|
35
|
-
private
|
|
40
|
+
private attachedFiles: FileContext[] = [];
|
|
41
|
+
private attachedFolders: FileContext[] = [];
|
|
36
42
|
private readonly recentlyViewFiles: FileContext[] = [];
|
|
37
43
|
private readonly onDidContextFilesChangeEmitter = new Emitter<{
|
|
38
44
|
viewed: FileContext[];
|
|
@@ -53,6 +59,18 @@ export class LLMContextServiceImpl extends WithEventBus implements LLMContextSer
|
|
|
53
59
|
}
|
|
54
60
|
}
|
|
55
61
|
|
|
62
|
+
private addFolderToList(folder: FileContext, list: FileContext[], maxLimit: number) {
|
|
63
|
+
const existingIndex = list.findIndex((f) => f.uri.toString() === folder.uri.toString());
|
|
64
|
+
if (existingIndex > -1) {
|
|
65
|
+
list.splice(existingIndex, 1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
list.push(folder);
|
|
69
|
+
if (list.length > maxLimit) {
|
|
70
|
+
list.shift();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
56
74
|
addFileToContext(uri: URI, selection?: [number, number], isManual = false): void {
|
|
57
75
|
if (!uri) {
|
|
58
76
|
return;
|
|
@@ -70,12 +88,24 @@ export class LLMContextServiceImpl extends WithEventBus implements LLMContextSer
|
|
|
70
88
|
this.notifyContextChange();
|
|
71
89
|
}
|
|
72
90
|
|
|
91
|
+
addFolderToContext(uri: URI): void {
|
|
92
|
+
if (!uri) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const file = { uri };
|
|
97
|
+
|
|
98
|
+
this.addFolderToList(file, this.attachedFolders, this.maxAttachFoldersLimit);
|
|
99
|
+
this.notifyContextChange();
|
|
100
|
+
}
|
|
101
|
+
|
|
73
102
|
private notifyContextChange(): void {
|
|
74
103
|
this.onDidContextFilesChangeEmitter.fire(this.getAllContextFiles());
|
|
75
104
|
}
|
|
76
105
|
|
|
77
106
|
cleanFileContext() {
|
|
78
|
-
this.attachedFiles
|
|
107
|
+
this.attachedFiles = [];
|
|
108
|
+
this.attachedFolders = [];
|
|
79
109
|
this.notifyContextChange();
|
|
80
110
|
}
|
|
81
111
|
|
|
@@ -83,6 +113,7 @@ export class LLMContextServiceImpl extends WithEventBus implements LLMContextSer
|
|
|
83
113
|
return {
|
|
84
114
|
viewed: this.recentlyViewFiles,
|
|
85
115
|
attached: this.attachedFiles,
|
|
116
|
+
attachedFolders: this.attachedFolders,
|
|
86
117
|
version: this.contextVersion++,
|
|
87
118
|
};
|
|
88
119
|
}
|
|
@@ -160,16 +191,63 @@ export class LLMContextServiceImpl extends WithEventBus implements LLMContextSer
|
|
|
160
191
|
this.dispose();
|
|
161
192
|
}
|
|
162
193
|
|
|
163
|
-
serialize(): SerializedContext {
|
|
194
|
+
async serialize(): Promise<SerializedContext> {
|
|
164
195
|
const files = this.getAllContextFiles();
|
|
165
196
|
const workspaceRoot = URI.file(this.appConfig.workspaceDir);
|
|
166
197
|
|
|
167
198
|
return {
|
|
168
199
|
recentlyViewFiles: this.serializeRecentlyViewFiles(files.viewed, workspaceRoot),
|
|
169
200
|
attachedFiles: this.serializeAttachedFiles(files.attached, workspaceRoot),
|
|
201
|
+
attachedFolders: await this.serializeAttachedFolders(files.attachedFolders, workspaceRoot),
|
|
170
202
|
};
|
|
171
203
|
}
|
|
172
204
|
|
|
205
|
+
private async serializeAttachedFolders(folders: FileContext[], workspaceRoot: URI): Promise<string[]> {
|
|
206
|
+
// 去重
|
|
207
|
+
const folderPath = Array.from(new Set(folders.map((folder) => folder.uri.toString())));
|
|
208
|
+
return Promise.all(
|
|
209
|
+
folderPath.map(async (folder) => {
|
|
210
|
+
const folderUri = new URI(folder);
|
|
211
|
+
const root = workspaceRoot.relative(folderUri)?.toString() || '/';
|
|
212
|
+
return `\`\`\`\n${root}\n${(await this.getPartiaFolderStructure(folderUri.codeUri.fsPath))
|
|
213
|
+
.map((line) => `- ${line}`)
|
|
214
|
+
.join('\n')}\n\`\`\`\n`;
|
|
215
|
+
}),
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private async getPartiaFolderStructure(folder: string, level = 2): Promise<string[]> {
|
|
220
|
+
const result: string[] = [];
|
|
221
|
+
const stat = await this.fileService.getFileStat(folder);
|
|
222
|
+
|
|
223
|
+
for (const child of stat?.children || []) {
|
|
224
|
+
const relativePath = new URI(folder).relative(new URI(child.uri))!.toString();
|
|
225
|
+
|
|
226
|
+
if (child.isSymbolicLink) {
|
|
227
|
+
// 处理软链接
|
|
228
|
+
const target = await this.fileService.getFileStat(child.realUri || child.uri);
|
|
229
|
+
if (target) {
|
|
230
|
+
result.push(`${relativePath} -> ${target} (symbolic link)`);
|
|
231
|
+
} else {
|
|
232
|
+
result.push(`${relativePath} (broken symbolic link)`);
|
|
233
|
+
}
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (child.type === FileType.Directory) {
|
|
238
|
+
result.push(`${relativePath}/`);
|
|
239
|
+
if (level > 1) {
|
|
240
|
+
const subDirStructure = await this.getPartiaFolderStructure(child.uri, level - 1);
|
|
241
|
+
result.push(...subDirStructure.map((subEntry) => `${relativePath}/${subEntry}`));
|
|
242
|
+
}
|
|
243
|
+
} else if (child.type === FileType.File) {
|
|
244
|
+
result.push(relativePath);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
|
|
173
251
|
private serializeRecentlyViewFiles(files: FileContext[], workspaceRoot: URI): string[] {
|
|
174
252
|
return files
|
|
175
253
|
.map((file) => workspaceRoot.relative(file.uri)?.toString() || file.uri.parent.toString())
|
|
@@ -244,8 +244,8 @@ export abstract class WindowedMatcher {
|
|
|
244
244
|
tokens.slice(startLine, endLine).forEach((token) => token.forEach((word) => size.add(word)));
|
|
245
245
|
cache.push(size);
|
|
246
246
|
}
|
|
247
|
-
const
|
|
248
|
-
const score = this.similarityScore(
|
|
247
|
+
const target = cache[index];
|
|
248
|
+
const score = this.similarityScore(target, this.referenceTokens);
|
|
249
249
|
snippets.push({
|
|
250
250
|
score,
|
|
251
251
|
startLine,
|
|
@@ -15,9 +15,9 @@ import {
|
|
|
15
15
|
import { MAX_NEIGHBOR_AGGREGATE_LENGTH } from './const';
|
|
16
16
|
import { FixedWindowSizeJaccardMatcher } from './jaccardMatcher';
|
|
17
17
|
|
|
18
|
-
export const getOpenedTabFileList = (
|
|
18
|
+
export const getOpenedTabFileList = (documents: IEditorDocumentModel[]) => {
|
|
19
19
|
// 过滤超大文档
|
|
20
|
-
const recentFiles =
|
|
20
|
+
const recentFiles = documents.filter((document) => isDocumentValid(document));
|
|
21
21
|
return recentFiles;
|
|
22
22
|
};
|
|
23
23
|
|
|
@@ -4,8 +4,8 @@ import React, { useCallback } from 'react';
|
|
|
4
4
|
import { Badge } from '@opensumi/ide-components';
|
|
5
5
|
import { AINativeSettingSectionsId, ILogger, useInjectable } from '@opensumi/ide-core-browser';
|
|
6
6
|
import { PreferenceService } from '@opensumi/ide-core-browser/lib/preferences';
|
|
7
|
-
import { localize } from '@opensumi/ide-core-common';
|
|
8
|
-
import { IMessageService } from '@opensumi/ide-overlay
|
|
7
|
+
import { PreferenceScope, localize } from '@opensumi/ide-core-common';
|
|
8
|
+
import { IMessageService } from '@opensumi/ide-overlay';
|
|
9
9
|
|
|
10
10
|
import { BUILTIN_MCP_SERVER_NAME, ISumiMCPServerBackend, SumiMCPServerProxyServicePath } from '../../../../common';
|
|
11
11
|
import { MCPServerDescription } from '../../../../common/mcp-server-manager';
|
|
@@ -18,13 +18,13 @@ import { MCPServerForm, MCPServerFormData } from './mcp-server-form';
|
|
|
18
18
|
export const MCPConfigView: React.FC = () => {
|
|
19
19
|
const mcpServerProxyService = useInjectable<MCPServerProxyService>(MCPServerProxyService);
|
|
20
20
|
const preferenceService = useInjectable<PreferenceService>(PreferenceService);
|
|
21
|
+
const messageService = useInjectable<IMessageService>(IMessageService);
|
|
21
22
|
const sumiMCPServerBackendProxy = useInjectable<ISumiMCPServerBackend>(SumiMCPServerProxyServicePath);
|
|
22
23
|
const logger = useInjectable<ILogger>(ILogger);
|
|
23
|
-
const messageService = useInjectable<IMessageService>(IMessageService);
|
|
24
24
|
const [servers, setServers] = React.useState<MCPServer[]>([]);
|
|
25
25
|
const [formVisible, setFormVisible] = React.useState(false);
|
|
26
26
|
const [editingServer, setEditingServer] = React.useState<MCPServerFormData | undefined>();
|
|
27
|
-
|
|
27
|
+
const [loadingServer, setLoadingServer] = React.useState<string | undefined>();
|
|
28
28
|
const loadServers = useCallback(async () => {
|
|
29
29
|
const allServers = await mcpServerProxyService.$getServers();
|
|
30
30
|
setServers(allServers);
|
|
@@ -44,6 +44,7 @@ export const MCPConfigView: React.FC = () => {
|
|
|
44
44
|
const handleServerControl = useCallback(
|
|
45
45
|
async (serverName: string, start: boolean) => {
|
|
46
46
|
try {
|
|
47
|
+
setLoadingServer(serverName);
|
|
47
48
|
if (start) {
|
|
48
49
|
await mcpServerProxyService.$startServer(serverName);
|
|
49
50
|
} else {
|
|
@@ -87,12 +88,14 @@ export const MCPConfigView: React.FC = () => {
|
|
|
87
88
|
});
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers);
|
|
91
|
+
await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers, PreferenceScope.User);
|
|
91
92
|
await loadServers();
|
|
93
|
+
setLoadingServer(undefined);
|
|
92
94
|
} catch (error) {
|
|
93
95
|
const msg = error.message || error;
|
|
94
96
|
logger.error(`Failed to ${start ? 'start' : 'stop'} server ${serverName}:`, error);
|
|
95
|
-
messageService.error(
|
|
97
|
+
messageService.error(error.message);
|
|
98
|
+
setLoadingServer(undefined);
|
|
96
99
|
}
|
|
97
100
|
},
|
|
98
101
|
[mcpServerProxyService, preferenceService, sumiMCPServerBackendProxy, loadServers],
|
|
@@ -121,7 +124,7 @@ export const MCPConfigView: React.FC = () => {
|
|
|
121
124
|
const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
|
|
122
125
|
const updatedServers = servers.filter((s) => s.name !== serverName);
|
|
123
126
|
sumiMCPServerBackendProxy.removeServer(serverName);
|
|
124
|
-
await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers);
|
|
127
|
+
await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers, PreferenceScope.User);
|
|
125
128
|
await loadServers();
|
|
126
129
|
},
|
|
127
130
|
[editingServer, formVisible],
|
|
@@ -140,7 +143,7 @@ export const MCPConfigView: React.FC = () => {
|
|
|
140
143
|
setServers(servers as MCPServer[]);
|
|
141
144
|
setFormVisible(false);
|
|
142
145
|
await sumiMCPServerBackendProxy.addOrUpdateServer(data as MCPServerDescription);
|
|
143
|
-
await preferenceService.set(AINativeSettingSectionsId.MCPServers, servers);
|
|
146
|
+
await preferenceService.set(AINativeSettingSectionsId.MCPServers, servers, PreferenceScope.User);
|
|
144
147
|
await loadServers();
|
|
145
148
|
},
|
|
146
149
|
[servers, formVisible, loadServers],
|
|
@@ -164,10 +167,10 @@ export const MCPConfigView: React.FC = () => {
|
|
|
164
167
|
<div className={styles.header}>
|
|
165
168
|
<div>
|
|
166
169
|
<h2 className={styles.title}>MCP Servers</h2>
|
|
167
|
-
<p className={styles.description}>
|
|
170
|
+
<p className={styles.description}>{localize('ai.native.mcp.manage.connections')}</p>
|
|
168
171
|
</div>
|
|
169
172
|
<button className={styles.addButton} onClick={handleAddServer}>
|
|
170
|
-
+
|
|
173
|
+
+ {localize('ai.native.mcp.addMCPServer.title')}
|
|
171
174
|
</button>
|
|
172
175
|
</div>
|
|
173
176
|
<div className={styles.serversList}>
|
|
@@ -188,7 +191,15 @@ export const MCPConfigView: React.FC = () => {
|
|
|
188
191
|
title={server.isStarted ? 'Stop' : 'Start'}
|
|
189
192
|
onClick={() => handleServerControl(server.name, !server.isStarted)}
|
|
190
193
|
>
|
|
191
|
-
<i
|
|
194
|
+
<i
|
|
195
|
+
className={`codicon ${
|
|
196
|
+
loadingServer === server.name
|
|
197
|
+
? 'codicon-loading kt-icon-loading'
|
|
198
|
+
: server.isStarted
|
|
199
|
+
? 'codicon-debug-stop'
|
|
200
|
+
: 'codicon-debug-start'
|
|
201
|
+
}`}
|
|
202
|
+
/>
|
|
192
203
|
</button>
|
|
193
204
|
{server.name !== BUILTIN_MCP_SERVER_NAME && (
|
|
194
205
|
<button className={styles.iconButton} title='Delete' onClick={() => handleDeleteServer(server.name)}>
|
|
@@ -201,7 +212,7 @@ export const MCPConfigView: React.FC = () => {
|
|
|
201
212
|
<div className={styles.detailRow}>
|
|
202
213
|
<span className={styles.detailLabel}>Status:</span>
|
|
203
214
|
<span className={`${styles.serverStatus} ${server.isStarted ? styles.running : styles.stopped}`}>
|
|
204
|
-
{server.isStarted ? '
|
|
215
|
+
{server.isStarted ? localize('ai.native.mcp.running') : localize('ai.native.mcp.stopped')}
|
|
205
216
|
</span>
|
|
206
217
|
</div>
|
|
207
218
|
{server.type && (
|
|
@@ -67,35 +67,75 @@ export const MCPServerForm: FC<Props> = ({ visible, initialData, onSave, onCance
|
|
|
67
67
|
);
|
|
68
68
|
}, [initialData]);
|
|
69
69
|
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
70
|
+
const validateForm = useCallback(
|
|
71
|
+
(formData: MCPServerFormData) => {
|
|
72
|
+
if (formData.name.trim() === '') {
|
|
73
|
+
messageService.error(localize('ai.native.mcp.name.isRequired'));
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
if (
|
|
77
|
+
!initialData &&
|
|
78
|
+
existingServers.some((server) => server.name.toLocaleLowerCase() === formData.name.toLocaleLowerCase())
|
|
79
|
+
) {
|
|
80
|
+
messageService.error(formatLocalize('ai.native.mcp.serverNameExists', formData.name));
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
if (formData.type === MCP_SERVER_TYPE.SSE) {
|
|
84
|
+
const isServerHostValid = formData.serverHost?.trim() !== '';
|
|
85
|
+
if (!isServerHostValid) {
|
|
86
|
+
messageService.error(localize('ai.native.mcp.serverHost.isRequired'));
|
|
87
|
+
}
|
|
88
|
+
return isServerHostValid;
|
|
89
|
+
}
|
|
90
|
+
const isCommandValid = formData.command?.trim() !== '';
|
|
91
|
+
if (!isCommandValid) {
|
|
92
|
+
messageService.error(localize('ai.native.mcp.command.isRequired'));
|
|
93
|
+
}
|
|
94
|
+
return isCommandValid;
|
|
95
|
+
},
|
|
96
|
+
[existingServers, initialData],
|
|
97
|
+
);
|
|
96
98
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
const handleSubmit = useCallback(
|
|
100
|
+
(e: FormEvent) => {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
const isValid = validateForm(formData);
|
|
103
|
+
if (!isValid) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const form = {
|
|
107
|
+
...formData,
|
|
108
|
+
};
|
|
109
|
+
if (formData.type === MCP_SERVER_TYPE.SSE) {
|
|
110
|
+
form.serverHost = form.serverHost?.trim();
|
|
111
|
+
} else {
|
|
112
|
+
const args = argsText.split(' ').filter(Boolean);
|
|
113
|
+
const env = envText
|
|
114
|
+
.split('\n')
|
|
115
|
+
.filter(Boolean)
|
|
116
|
+
.reduce((acc, line) => {
|
|
117
|
+
const [key, value] = line.split('=');
|
|
118
|
+
if (key && value) {
|
|
119
|
+
acc[key.trim()] = value.trim();
|
|
120
|
+
}
|
|
121
|
+
return acc;
|
|
122
|
+
}, {} as Record<string, string>);
|
|
123
|
+
form.args = args;
|
|
124
|
+
form.env = env;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
setFormData({
|
|
128
|
+
...formData,
|
|
129
|
+
command: '',
|
|
130
|
+
serverHost: '',
|
|
131
|
+
args: [],
|
|
132
|
+
env: {},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
onSave(form);
|
|
136
|
+
},
|
|
137
|
+
[formData, argsText, envText, onSave, validateForm],
|
|
138
|
+
);
|
|
99
139
|
|
|
100
140
|
const handleCommandChange = useCallback(
|
|
101
141
|
(e: ChangeEvent<HTMLInputElement>) => {
|
|
@@ -183,32 +223,6 @@ export const MCPServerForm: FC<Props> = ({ visible, initialData, onSave, onCance
|
|
|
183
223
|
}
|
|
184
224
|
}, [formData, argsText, envText]);
|
|
185
225
|
|
|
186
|
-
const validateForm = useCallback(
|
|
187
|
-
(formData: MCPServerFormData) => {
|
|
188
|
-
if (formData.name.trim() === '') {
|
|
189
|
-
messageService.error(localize('ai.native.mcp.name.isRequired'));
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
if (existingServers.some((server) => server.name.toLocaleLowerCase() === formData.name.toLocaleLowerCase())) {
|
|
193
|
-
messageService.error(formatLocalize('ai.native.mcp.serverNameExists', formData.name));
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
if (formData.type === MCP_SERVER_TYPE.SSE) {
|
|
197
|
-
const isServerHostValid = formData.serverHost?.trim() !== '';
|
|
198
|
-
if (!isServerHostValid) {
|
|
199
|
-
messageService.error(localize('ai.native.mcp.serverHost.isRequired'));
|
|
200
|
-
}
|
|
201
|
-
return isServerHostValid;
|
|
202
|
-
}
|
|
203
|
-
const isCommandValid = formData.command?.trim() !== '';
|
|
204
|
-
if (!isCommandValid) {
|
|
205
|
-
messageService.error(localize('ai.native.mcp.command.isRequired'));
|
|
206
|
-
}
|
|
207
|
-
return isCommandValid;
|
|
208
|
-
},
|
|
209
|
-
[existingServers],
|
|
210
|
-
);
|
|
211
|
-
|
|
212
226
|
return (
|
|
213
227
|
<Modal
|
|
214
228
|
title={initialData ? localize('ai.native.mcp.editMCPServer.title') : localize('ai.native.mcp.addMCPServer.title')}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
2
|
|
|
3
|
+
import { Icon } from '@opensumi/ide-components/lib/icon/icon';
|
|
3
4
|
import { CommandService, LabelService, URI, path, useInjectable } from '@opensumi/ide-core-browser';
|
|
4
5
|
import { WorkbenchEditorService } from '@opensumi/ide-editor';
|
|
5
6
|
import { IWorkspaceService } from '@opensumi/ide-workspace';
|
|
@@ -110,7 +111,9 @@ const ExpandableFileList: React.FC<ExpandableFileListProps> = ({
|
|
|
110
111
|
return (
|
|
111
112
|
<div className={styles.container}>
|
|
112
113
|
<div className={styles.header} onClick={() => setIsExpanded(!isExpanded)}>
|
|
113
|
-
<span style={{ transform: `rotate(${isExpanded ? '90deg' : '0deg'})
|
|
114
|
+
<span style={{ transform: `rotate(${isExpanded ? '90deg' : '0deg'})`, display: 'flex' }}>
|
|
115
|
+
<Icon iconClass={'codicon codicon-chevron-right'} />
|
|
116
|
+
</span>
|
|
114
117
|
<span>
|
|
115
118
|
{headerText} · {fileList.length} files
|
|
116
119
|
</span>
|