@opensumi/ide-ai-native 3.8.3-next-1741917543.0 → 3.8.3-next-1741920696.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 +69 -31
- 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 +60 -6
- package/lib/browser/components/ChatEditor.js.map +1 -1
- package/lib/browser/components/ChatHistory.d.ts.map +1 -1
- package/lib/browser/components/ChatHistory.js +15 -15
- package/lib/browser/components/ChatHistory.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 +229 -0
- package/lib/browser/components/ChatMentionInput.js.map +1 -0
- package/lib/browser/components/ChatThinking.d.ts +1 -1
- package/lib/browser/components/ChatThinking.d.ts.map +1 -1
- package/lib/browser/components/ChatThinking.js +8 -2
- package/lib/browser/components/ChatThinking.js.map +1 -1
- package/lib/browser/components/change-list.module.less +2 -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/chat-history.module.less +33 -16
- package/lib/browser/components/components.module.less +39 -3
- 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 +763 -0
- package/lib/browser/components/mention-input/mention-input.js.map +1 -0
- package/lib/browser/components/mention-input/mention-input.module.less +333 -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 +78 -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 +76 -2
- package/lib/browser/context/llm-context.service.js.map +1 -1
- package/lib/browser/mcp/config/components/mcp-config.module.less +61 -30
- package/lib/browser/mcp/config/components/mcp-config.view.d.ts.map +1 -1
- package/lib/browser/mcp/config/components/mcp-config.view.js +17 -19
- package/lib/browser/mcp/config/components/mcp-config.view.js.map +1 -1
- package/lib/browser/mcp/config/components/mcp-server-form.js +1 -1
- package/lib/browser/mcp/config/components/mcp-server-form.js.map +1 -1
- package/lib/browser/mcp/config/components/mcp-server-form.module.less +3 -0
- package/lib/browser/mcp/mcp-server-proxy.service.d.ts +1 -0
- package/lib/browser/mcp/mcp-server-proxy.service.d.ts.map +1 -1
- package/lib/common/index.d.ts +2 -1
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js +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 +95 -30
- package/lib/common/prompts/context-prompt-provider.js.map +1 -1
- package/lib/common/types.d.ts +1 -1
- package/lib/common/types.d.ts.map +1 -1
- package/lib/common/utils.d.ts +1 -0
- package/lib/common/utils.d.ts.map +1 -1
- package/lib/common/utils.js +9 -1
- package/lib/common/utils.js.map +1 -1
- package/lib/node/mcp/sumi-mcp-server.d.ts +3 -3
- package/lib/node/mcp/sumi-mcp-server.d.ts.map +1 -1
- package/lib/node/mcp/sumi-mcp-server.js +11 -7
- package/lib/node/mcp/sumi-mcp-server.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 +81 -24
- package/src/browser/components/ChatEditor.tsx +128 -9
- package/src/browser/components/ChatHistory.tsx +16 -30
- package/src/browser/components/ChatMentionInput.tsx +276 -0
- package/src/browser/components/ChatThinking.tsx +10 -4
- package/src/browser/components/change-list.module.less +2 -0
- package/src/browser/components/{ChatContext → chat-context}/index.tsx +1 -1
- package/src/browser/components/chat-history.module.less +33 -16
- package/src/browser/components/components.module.less +39 -3
- package/src/browser/components/mention-input/mention-input.module.less +333 -0
- package/src/browser/components/mention-input/mention-input.tsx +952 -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 +84 -0
- package/src/browser/context/llm-context.service.ts +85 -3
- package/src/browser/mcp/config/components/mcp-config.module.less +61 -30
- package/src/browser/mcp/config/components/mcp-config.view.tsx +49 -32
- package/src/browser/mcp/config/components/mcp-server-form.module.less +3 -0
- package/src/browser/mcp/config/components/mcp-server-form.tsx +1 -1
- package/src/common/index.ts +2 -2
- package/src/common/llm-context.ts +16 -1
- package/src/common/prompts/context-prompt-provider.ts +130 -36
- package/src/common/types.ts +1 -1
- package/src/common/utils.ts +8 -0
- package/src/node/mcp/sumi-mcp-server.ts +11 -7
- 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,84 @@
|
|
|
1
|
+
import type { LabelService } from '@opensumi/ide-core-browser';
|
|
2
|
+
import type { IWorkspaceService } from '@opensumi/ide-workspace';
|
|
3
|
+
|
|
4
|
+
export interface MentionItem {
|
|
5
|
+
id: string;
|
|
6
|
+
type: string;
|
|
7
|
+
text: string;
|
|
8
|
+
value?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
contextId?: string;
|
|
11
|
+
icon?: string;
|
|
12
|
+
getHighestLevelItems?: () => MentionItem[];
|
|
13
|
+
getItems?: (searchText: string) => Promise<MentionItem[]>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SecondLevelMenuConfig {
|
|
17
|
+
getDefaultItems: () => MentionItem[];
|
|
18
|
+
getHighestLevelItems: () => MentionItem[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface MentionPosition {
|
|
22
|
+
top: number;
|
|
23
|
+
left: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface MentionState {
|
|
27
|
+
active: boolean;
|
|
28
|
+
startPos: number | null;
|
|
29
|
+
filter: string;
|
|
30
|
+
position: MentionPosition;
|
|
31
|
+
activeIndex: number;
|
|
32
|
+
level: number; // 0: 一级菜单, 1: 二级菜单
|
|
33
|
+
parentType: string | null; // 二级菜单的父类型
|
|
34
|
+
secondLevelFilter: string; // 二级菜单的筛选文本
|
|
35
|
+
inlineSearchActive: boolean; // 是否在输入框中进行二级搜索
|
|
36
|
+
inlineSearchStartPos: number | null; // 内联搜索的起始位置
|
|
37
|
+
loading: boolean; // 加载状态
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ModelOption {
|
|
41
|
+
label: string;
|
|
42
|
+
value: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export enum FooterButtonPosition {
|
|
46
|
+
LEFT = 'left',
|
|
47
|
+
RIGHT = 'right',
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export enum MentionType {
|
|
51
|
+
FILE = 'file',
|
|
52
|
+
FOLDER = 'folder',
|
|
53
|
+
CODE = 'code',
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface FooterButton {
|
|
57
|
+
id: string;
|
|
58
|
+
icon: string;
|
|
59
|
+
title: string;
|
|
60
|
+
onClick?: () => void;
|
|
61
|
+
position: FooterButtonPosition;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface FooterConfig {
|
|
65
|
+
modelOptions?: ModelOption[];
|
|
66
|
+
defaultModel?: string;
|
|
67
|
+
buttons?: FooterButton[];
|
|
68
|
+
showModelSelector?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface MentionInputProps {
|
|
72
|
+
mentionItems?: MentionItem[]; // 简化为单一菜单项配置
|
|
73
|
+
onSend?: (content: string, config?: { model: string; [key: string]: any }) => void;
|
|
74
|
+
onStop?: () => void;
|
|
75
|
+
placeholder?: string;
|
|
76
|
+
loading?: boolean;
|
|
77
|
+
onSelectionChange?: (value: string) => void;
|
|
78
|
+
footerConfig?: FooterConfig; // 新增配置项
|
|
79
|
+
mentionKeyword?: string;
|
|
80
|
+
labelService?: LabelService;
|
|
81
|
+
workspaceService?: IWorkspaceService;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
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,67 @@ 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
|
+
try {
|
|
222
|
+
const stat = await this.fileService.getFileStat(folder);
|
|
223
|
+
|
|
224
|
+
for (const child of stat?.children || []) {
|
|
225
|
+
const relativePath = new URI(folder).relative(new URI(child.uri))!.toString();
|
|
226
|
+
|
|
227
|
+
if (child.isSymbolicLink) {
|
|
228
|
+
// 处理软链接
|
|
229
|
+
const target = await this.fileService.getFileStat(child.realUri || child.uri);
|
|
230
|
+
if (target) {
|
|
231
|
+
result.push(`${relativePath} -> ${target} (symbolic link)`);
|
|
232
|
+
} else {
|
|
233
|
+
result.push(`${relativePath} (broken symbolic link)`);
|
|
234
|
+
}
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (child.type === FileType.Directory) {
|
|
239
|
+
result.push(`${relativePath}/`);
|
|
240
|
+
if (level > 1) {
|
|
241
|
+
const subDirStructure = await this.getPartiaFolderStructure(child.uri, level - 1);
|
|
242
|
+
result.push(...subDirStructure.map((subEntry) => `${relativePath}/${subEntry}`));
|
|
243
|
+
}
|
|
244
|
+
} else if (child.type === FileType.File) {
|
|
245
|
+
result.push(relativePath);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
} catch {
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return result;
|
|
253
|
+
}
|
|
254
|
+
|
|
173
255
|
private serializeRecentlyViewFiles(files: FileContext[], workspaceRoot: URI): string[] {
|
|
174
256
|
return files
|
|
175
257
|
.map((file) => workspaceRoot.relative(file.uri)?.toString() || file.uri.parent.toString())
|
|
@@ -45,34 +45,86 @@
|
|
|
45
45
|
.serverItem {
|
|
46
46
|
padding: 16px;
|
|
47
47
|
border-radius: 8px;
|
|
48
|
-
background-color: var(--
|
|
49
|
-
border: 1px solid var(--border-color);
|
|
48
|
+
background-color: var(--editor-background);
|
|
50
49
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
51
50
|
transition: all 0.2s ease;
|
|
52
|
-
|
|
51
|
+
.serverHeader {
|
|
52
|
+
display: flex;
|
|
53
|
+
justify-content: space-between;
|
|
54
|
+
align-items: center;
|
|
55
|
+
margin-bottom: 12px;
|
|
56
|
+
.iconButton {
|
|
57
|
+
padding: 2px 5px;
|
|
58
|
+
margin-left: 5px;
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
border-radius: 4px;
|
|
61
|
+
line-height: 20px;
|
|
62
|
+
font-size: 14px;
|
|
63
|
+
&:hover {
|
|
64
|
+
background-color: var(--badge-background);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
.serverActionButton {
|
|
68
|
+
height: 24px;
|
|
69
|
+
background-color: var(--badge-background);
|
|
70
|
+
color: var(--badge-foreground);
|
|
71
|
+
span {
|
|
72
|
+
min-width: 52px;
|
|
73
|
+
}
|
|
74
|
+
&.active {
|
|
75
|
+
i {
|
|
76
|
+
color: var(--testing-iconPassed);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
i {
|
|
80
|
+
color: var(--testing-iconErrored);
|
|
81
|
+
margin-right: 3px;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
53
85
|
&:hover {
|
|
54
86
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
|
55
87
|
border-color: var(--focusBorder);
|
|
56
88
|
}
|
|
57
89
|
}
|
|
58
90
|
|
|
59
|
-
.
|
|
91
|
+
.serverTitleRow {
|
|
60
92
|
display: flex;
|
|
61
|
-
justify-content: space-between;
|
|
62
93
|
align-items: center;
|
|
63
|
-
|
|
94
|
+
gap: 8px;
|
|
95
|
+
text-indent: 12px;
|
|
64
96
|
}
|
|
65
97
|
|
|
66
|
-
.
|
|
98
|
+
.serverActions {
|
|
67
99
|
display: flex;
|
|
68
100
|
align-items: center;
|
|
69
|
-
|
|
101
|
+
flex: 1;
|
|
102
|
+
justify-content: flex-end;
|
|
70
103
|
}
|
|
71
104
|
|
|
72
105
|
.serverName {
|
|
73
106
|
margin: 0;
|
|
74
107
|
font-size: 14px;
|
|
75
108
|
font-weight: 500;
|
|
109
|
+
display: flex;
|
|
110
|
+
align-items: center;
|
|
111
|
+
justify-content: center;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.serverStatusIcon {
|
|
115
|
+
border-radius: 50%;
|
|
116
|
+
width: 6px;
|
|
117
|
+
height: 6px;
|
|
118
|
+
margin-left: 12px;
|
|
119
|
+
display: inline-block;
|
|
120
|
+
&.active {
|
|
121
|
+
background-color: var(--testing-iconPassed);
|
|
122
|
+
box-shadow: 0 0 0 1px var(--testing-iconPassed);
|
|
123
|
+
}
|
|
124
|
+
&.inactive {
|
|
125
|
+
background-color: var(--testing-iconErrored);
|
|
126
|
+
box-shadow: 0 0 0 1px var(--testing-iconErrored);
|
|
127
|
+
}
|
|
76
128
|
}
|
|
77
129
|
|
|
78
130
|
.serverStatus,
|
|
@@ -104,26 +156,7 @@
|
|
|
104
156
|
color: var(--notification-error-foreground);
|
|
105
157
|
}
|
|
106
158
|
|
|
107
|
-
.serverActions {
|
|
108
|
-
display: flex;
|
|
109
|
-
gap: 4px;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.iconButton {
|
|
113
|
-
padding: 4px;
|
|
114
|
-
border: none;
|
|
115
|
-
background: none;
|
|
116
|
-
color: var(--icon-foreground);
|
|
117
|
-
cursor: pointer;
|
|
118
|
-
border-radius: 4px;
|
|
119
|
-
|
|
120
|
-
&:hover {
|
|
121
|
-
background-color: var(--list-hover-background);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
159
|
.serverDetail {
|
|
126
|
-
margin-top: 12px;
|
|
127
160
|
padding: 12px;
|
|
128
161
|
border-radius: 6px;
|
|
129
162
|
background-color: var(--editor-background);
|
|
@@ -181,11 +214,9 @@
|
|
|
181
214
|
display: inline-flex;
|
|
182
215
|
align-items: center;
|
|
183
216
|
padding: 2px 8px;
|
|
184
|
-
background-color: var(--editorWidget-background);
|
|
185
|
-
color: var(--button-secondary-foreground);
|
|
186
217
|
border-radius: 12px;
|
|
187
218
|
font-size: 12px;
|
|
188
219
|
font-weight: 500;
|
|
189
220
|
transition: all 0.2s ease;
|
|
190
|
-
|
|
221
|
+
cursor: pointer;
|
|
191
222
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import cls from 'classnames';
|
|
2
2
|
import React, { useCallback } from 'react';
|
|
3
3
|
|
|
4
|
-
import { Badge } from '@opensumi/ide-components';
|
|
4
|
+
import { Badge, Button, Popover, PopoverTriggerType } 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
7
|
import { PreferenceScope, localize } from '@opensumi/ide-core-common';
|
|
@@ -178,43 +178,60 @@ export const MCPConfigView: React.FC = () => {
|
|
|
178
178
|
<div key={server.name} className={styles.serverItem}>
|
|
179
179
|
<div className={styles.serverHeader}>
|
|
180
180
|
<div className={styles.serverTitleRow}>
|
|
181
|
-
<h3 className={styles.serverName}>
|
|
181
|
+
<h3 className={styles.serverName}>
|
|
182
|
+
{server.name}
|
|
183
|
+
<span
|
|
184
|
+
className={cls(styles.serverStatusIcon, server.isStarted ? styles.active : styles.inactive)}
|
|
185
|
+
></span>
|
|
186
|
+
</h3>
|
|
182
187
|
</div>
|
|
183
188
|
<div className={styles.serverActions}>
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
className={styles.iconButton}
|
|
191
|
-
title={server.isStarted ? 'Stop' : 'Start'}
|
|
192
|
-
onClick={() => handleServerControl(server.name, !server.isStarted)}
|
|
189
|
+
<Popover
|
|
190
|
+
id='mcp-server-action-popover'
|
|
191
|
+
trigger={PopoverTriggerType.hover}
|
|
192
|
+
content={
|
|
193
|
+
server.isStarted ? localize('ai.native.mcp.disable.title') : localize('ai.native.mcp.enable.title')
|
|
194
|
+
}
|
|
193
195
|
>
|
|
194
|
-
<
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
196
|
+
<Button
|
|
197
|
+
type='default'
|
|
198
|
+
className={cls(styles.serverActionButton, server.isStarted && styles.active)}
|
|
199
|
+
onClick={() => handleServerControl(server.name, !server.isStarted)}
|
|
200
|
+
>
|
|
201
|
+
<i
|
|
202
|
+
className={`codicon ${
|
|
203
|
+
loadingServer === server.name
|
|
204
|
+
? 'codicon-loading kt-icon-loading'
|
|
205
|
+
: server.isStarted
|
|
206
|
+
? 'codicon-check'
|
|
207
|
+
: 'codicon-circle'
|
|
208
|
+
}`}
|
|
209
|
+
/>
|
|
210
|
+
<span>{localize(server.isStarted ? 'ai.native.mcp.enabled' : 'ai.native.mcp.disabled')}</span>
|
|
211
|
+
</Button>
|
|
212
|
+
</Popover>
|
|
213
|
+
{server.name !== BUILTIN_MCP_SERVER_NAME && (
|
|
214
|
+
<Button
|
|
215
|
+
type='icon'
|
|
216
|
+
iconClass='codicon codicon-edit'
|
|
217
|
+
className={styles.iconButton}
|
|
218
|
+
title='Edit'
|
|
219
|
+
onClick={() => handleEditServer(server)}
|
|
202
220
|
/>
|
|
203
|
-
|
|
221
|
+
)}
|
|
222
|
+
|
|
204
223
|
{server.name !== BUILTIN_MCP_SERVER_NAME && (
|
|
205
|
-
<
|
|
206
|
-
|
|
207
|
-
|
|
224
|
+
<Button
|
|
225
|
+
type='icon'
|
|
226
|
+
iconClass='codicon codicon-trash'
|
|
227
|
+
className={styles.iconButton}
|
|
228
|
+
title='Delete'
|
|
229
|
+
onClick={() => handleDeleteServer(server.name)}
|
|
230
|
+
/>
|
|
208
231
|
)}
|
|
209
232
|
</div>
|
|
210
233
|
</div>
|
|
211
234
|
<div className={styles.serverDetail}>
|
|
212
|
-
<div className={styles.detailRow}>
|
|
213
|
-
<span className={styles.detailLabel}>Status:</span>
|
|
214
|
-
<span className={`${styles.serverStatus} ${server.isStarted ? styles.running : styles.stopped}`}>
|
|
215
|
-
{server.isStarted ? localize('ai.native.mcp.running') : localize('ai.native.mcp.stopped')}
|
|
216
|
-
</span>
|
|
217
|
-
</div>
|
|
218
235
|
{server.type && (
|
|
219
236
|
<div className={styles.detailRow}>
|
|
220
237
|
<span className={styles.detailLabel}>Type:</span>
|
|
@@ -228,9 +245,9 @@ export const MCPConfigView: React.FC = () => {
|
|
|
228
245
|
<span className={styles.detailLabel}>Tools:</span>
|
|
229
246
|
<span className={styles.detailContent}>
|
|
230
247
|
{server.tools.map((tool, index) => (
|
|
231
|
-
<
|
|
232
|
-
{tool}
|
|
233
|
-
</
|
|
248
|
+
<Badge key={index} className={styles.toolTag} title={tool.description}>
|
|
249
|
+
{tool.name}
|
|
250
|
+
</Badge>
|
|
234
251
|
))}
|
|
235
252
|
</span>
|
|
236
253
|
</div>
|
|
@@ -263,7 +263,7 @@ export const MCPServerForm: FC<Props> = ({ visible, initialData, onSave, onCance
|
|
|
263
263
|
</div>
|
|
264
264
|
{renderFormItems()}
|
|
265
265
|
<div className={styles.formActions}>
|
|
266
|
-
<Button onClick={onCancel} type='
|
|
266
|
+
<Button onClick={onCancel} type='primary' className={styles.secondaryButton}>
|
|
267
267
|
{localize('ai.native.mcp.buttonCancel')}
|
|
268
268
|
</Button>
|
|
269
269
|
<Button onClick={handleSubmit} type='primary'>
|
package/src/common/index.ts
CHANGED
|
@@ -37,7 +37,7 @@ export const AI_CHAT_LOGO_AVATAR_ID = 'AI-Chat-Logo-Avatar';
|
|
|
37
37
|
export const AI_MENU_BAR_DEBUG_TOOLBAR = 'AI_MENU_BAR_DEBUG_TOOLBAR';
|
|
38
38
|
|
|
39
39
|
// 内置 MCP 服务器名称
|
|
40
|
-
export const BUILTIN_MCP_SERVER_NAME = '
|
|
40
|
+
export const BUILTIN_MCP_SERVER_NAME = 'Builtin';
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
43
|
* @deprecated Use {@link DESIGN_MENUBAR_CONTAINER_VIEW_ID} instead
|
|
@@ -134,7 +134,7 @@ export interface ISumiMCPServerBackend {
|
|
|
134
134
|
initBuiltinMCPServer(enabled: boolean): void;
|
|
135
135
|
initExternalMCPServers(servers: MCPServerDescription[]): void;
|
|
136
136
|
getAllMCPTools(): Promise<MCPTool[]>;
|
|
137
|
-
getServers(): Promise<Array<{ name: string; isStarted: boolean }>>;
|
|
137
|
+
getServers(): Promise<Array<{ name: string; isStarted: boolean; tools: MCPTool[] }>>;
|
|
138
138
|
startServer(serverName: string): Promise<void>;
|
|
139
139
|
stopServer(serverName: string): Promise<void>;
|
|
140
140
|
addOrUpdateServer(description: MCPServerDescription): void;
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import { Event, URI } from '@opensumi/ide-core-common/lib/utils';
|
|
2
2
|
|
|
3
3
|
export interface LLMContextService {
|
|
4
|
+
/**
|
|
5
|
+
* 开始自动收集
|
|
6
|
+
*/
|
|
4
7
|
startAutoCollection(): void;
|
|
5
8
|
|
|
9
|
+
/**
|
|
10
|
+
* 停止自动收集
|
|
11
|
+
*/
|
|
6
12
|
stopAutoCollection(): void;
|
|
7
13
|
|
|
8
14
|
/**
|
|
@@ -10,11 +16,19 @@ export interface LLMContextService {
|
|
|
10
16
|
*/
|
|
11
17
|
addFileToContext(uri: URI, selection?: [number, number], isManual?: boolean): void;
|
|
12
18
|
|
|
19
|
+
/**
|
|
20
|
+
* 添加文件夹到 context 中
|
|
21
|
+
*/
|
|
22
|
+
addFolderToContext(uri: URI, isManual?: boolean): void;
|
|
23
|
+
|
|
13
24
|
/**
|
|
14
25
|
* 清除上下文
|
|
15
26
|
*/
|
|
16
27
|
cleanFileContext(): void;
|
|
17
28
|
|
|
29
|
+
/**
|
|
30
|
+
* 上下文文件变化事件
|
|
31
|
+
*/
|
|
18
32
|
onDidContextFilesChangeEvent: Event<{ viewed: FileContext[]; attached: FileContext[]; version: number }>;
|
|
19
33
|
|
|
20
34
|
/**
|
|
@@ -24,7 +38,7 @@ export interface LLMContextService {
|
|
|
24
38
|
removeFileFromContext(uri: URI, isManual?: boolean): void;
|
|
25
39
|
|
|
26
40
|
/** 导出为可序列化格式 */
|
|
27
|
-
serialize(): SerializedContext
|
|
41
|
+
serialize(): Promise<SerializedContext>;
|
|
28
42
|
}
|
|
29
43
|
|
|
30
44
|
export interface FileContext {
|
|
@@ -44,4 +58,5 @@ export interface AttachFileContext {
|
|
|
44
58
|
export interface SerializedContext {
|
|
45
59
|
recentlyViewFiles: string[];
|
|
46
60
|
attachedFiles: Array<AttachFileContext>;
|
|
61
|
+
attachedFolders: string[];
|
|
47
62
|
}
|