@opensumi/ide-ai-native 3.8.3-next-1742194253.0 → 3.8.3-next-1742203639.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/ai-core.contribution.js +2 -2
- package/lib/browser/ai-core.contribution.js.map +1 -1
- package/lib/browser/chat/chat-manager.service.d.ts +1 -1
- package/lib/browser/chat/chat-manager.service.d.ts.map +1 -1
- package/lib/browser/chat/chat-manager.service.js +3 -2
- package/lib/browser/chat/chat-manager.service.js.map +1 -1
- package/lib/browser/chat/chat-model.d.ts.map +1 -1
- package/lib/browser/chat/chat-model.js +11 -2
- package/lib/browser/chat/chat-model.js.map +1 -1
- package/lib/browser/chat/chat-proxy.service.d.ts.map +1 -1
- package/lib/browser/chat/chat-proxy.service.js +1 -0
- package/lib/browser/chat/chat-proxy.service.js.map +1 -1
- package/lib/browser/chat/chat.feature.registry.d.ts +4 -1
- package/lib/browser/chat/chat.feature.registry.d.ts.map +1 -1
- package/lib/browser/chat/chat.feature.registry.js +6 -0
- package/lib/browser/chat/chat.feature.registry.js.map +1 -1
- package/lib/browser/chat/chat.internal.service.d.ts +1 -1
- package/lib/browser/chat/chat.internal.service.d.ts.map +1 -1
- package/lib/browser/chat/chat.internal.service.js +2 -2
- package/lib/browser/chat/chat.internal.service.js.map +1 -1
- package/lib/browser/chat/chat.view.d.ts.map +1 -1
- package/lib/browser/chat/chat.view.js +10 -7
- package/lib/browser/chat/chat.view.js.map +1 -1
- package/lib/browser/components/ChatEditor.d.ts +2 -1
- package/lib/browser/components/ChatEditor.d.ts.map +1 -1
- package/lib/browser/components/ChatEditor.js +5 -2
- package/lib/browser/components/ChatEditor.js.map +1 -1
- package/lib/browser/components/ChatInput.d.ts +1 -1
- package/lib/browser/components/ChatInput.d.ts.map +1 -1
- package/lib/browser/components/ChatInput.js +1 -1
- package/lib/browser/components/ChatInput.js.map +1 -1
- package/lib/browser/components/ChatMentionInput.d.ts +3 -1
- package/lib/browser/components/ChatMentionInput.d.ts.map +1 -1
- package/lib/browser/components/ChatMentionInput.js +54 -3
- package/lib/browser/components/ChatMentionInput.js.map +1 -1
- package/lib/browser/components/components.module.less +49 -0
- package/lib/browser/components/mention-input/mention-input.d.ts.map +1 -1
- package/lib/browser/components/mention-input/mention-input.js +21 -9
- package/lib/browser/components/mention-input/mention-input.js.map +1 -1
- package/lib/browser/components/mention-input/types.d.ts +3 -1
- package/lib/browser/components/mention-input/types.d.ts.map +1 -1
- package/lib/browser/components/mention-input/types.js.map +1 -1
- package/lib/browser/context/llm-context.service.d.ts.map +1 -1
- package/lib/browser/context/llm-context.service.js.map +1 -1
- package/lib/browser/mcp/config/components/mcp-config.view.js +2 -2
- 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 +8 -1
- package/lib/browser/mcp/config/components/mcp-server-form.js.map +1 -1
- package/lib/browser/mcp/config/mcp-config.contribution.d.ts.map +1 -1
- package/lib/browser/mcp/config/mcp-config.contribution.js +3 -2
- package/lib/browser/mcp/config/mcp-config.contribution.js.map +1 -1
- package/lib/browser/mcp/mcp-server-proxy.service.js +4 -4
- package/lib/browser/mcp/mcp-server-proxy.service.js.map +1 -1
- package/lib/browser/model/msg-history-manager.d.ts +1 -1
- package/lib/browser/model/msg-history-manager.d.ts.map +1 -1
- package/lib/browser/model/msg-history-manager.js.map +1 -1
- package/lib/browser/types.d.ts +7 -1
- package/lib/browser/types.d.ts.map +1 -1
- package/lib/browser/types.js.map +1 -1
- package/lib/common/index.d.ts +14 -8
- package/lib/common/index.d.ts.map +1 -1
- package/lib/common/index.js.map +1 -1
- package/lib/common/llm-context.d.ts.map +1 -1
- package/lib/common/llm-context.js.map +1 -1
- package/lib/node/base-language-model.d.ts +1 -1
- package/lib/node/base-language-model.d.ts.map +1 -1
- package/lib/node/base-language-model.js +15 -4
- package/lib/node/base-language-model.js.map +1 -1
- package/lib/node/mcp/sumi-mcp-server.d.ts +9 -9
- package/lib/node/mcp/sumi-mcp-server.d.ts.map +1 -1
- package/lib/node/mcp/sumi-mcp-server.js +9 -9
- package/lib/node/mcp/sumi-mcp-server.js.map +1 -1
- package/lib/node/mcp-server-manager-impl.d.ts +2 -0
- package/lib/node/mcp-server-manager-impl.d.ts.map +1 -1
- package/lib/node/mcp-server-manager-impl.js +14 -2
- package/lib/node/mcp-server-manager-impl.js.map +1 -1
- package/package.json +23 -23
- package/src/browser/ai-core.contribution.ts +2 -2
- package/src/browser/chat/chat-manager.service.ts +3 -2
- package/src/browser/chat/chat-model.ts +10 -2
- package/src/browser/chat/chat-proxy.service.ts +1 -0
- package/src/browser/chat/chat.feature.registry.ts +10 -1
- package/src/browser/chat/chat.internal.service.ts +2 -2
- package/src/browser/chat/chat.view.tsx +18 -8
- package/src/browser/components/ChatEditor.tsx +8 -0
- package/src/browser/components/ChatInput.tsx +2 -2
- package/src/browser/components/ChatMentionInput.tsx +100 -6
- package/src/browser/components/components.module.less +49 -0
- package/src/browser/components/mention-input/mention-input.tsx +24 -8
- package/src/browser/components/mention-input/types.ts +3 -1
- package/src/browser/context/llm-context.service.ts +2 -0
- package/src/browser/mcp/config/components/mcp-config.view.tsx +2 -2
- package/src/browser/mcp/config/components/mcp-server-form.tsx +8 -1
- package/src/browser/mcp/config/mcp-config.contribution.ts +4 -3
- package/src/browser/mcp/mcp-server-proxy.service.ts +4 -4
- package/src/browser/model/msg-history-manager.ts +1 -1
- package/src/browser/types.ts +8 -1
- package/src/common/index.ts +14 -8
- package/src/common/llm-context.ts +2 -0
- package/src/node/base-language-model.ts +25 -3
- package/src/node/mcp/sumi-mcp-server.ts +9 -9
- package/src/node/mcp-server-manager-impl.ts +17 -2
|
@@ -150,7 +150,7 @@ export class ChatManagerService extends Disposable {
|
|
|
150
150
|
this.saveSessions();
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
createRequest(sessionId: string, message: string, agentId: string, command?: string) {
|
|
153
|
+
createRequest(sessionId: string, message: string, agentId: string, command?: string, images?: string[]) {
|
|
154
154
|
const model = this.getSession(sessionId);
|
|
155
155
|
if (!model) {
|
|
156
156
|
throw new Error(`Unknown session: ${sessionId}`);
|
|
@@ -160,7 +160,7 @@ export class ChatManagerService extends Disposable {
|
|
|
160
160
|
return;
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
return model.addRequest({ prompt: message, agentId, command });
|
|
163
|
+
return model.addRequest({ prompt: message, agentId, command, images });
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
async sendRequest(sessionId: string, request: ChatRequestModel, regenerate: boolean) {
|
|
@@ -191,6 +191,7 @@ export class ChatManagerService extends Disposable {
|
|
|
191
191
|
requestId: request.requestId,
|
|
192
192
|
message: request.message.prompt,
|
|
193
193
|
command: request.message.command,
|
|
194
|
+
images: request.message.images,
|
|
194
195
|
regenerate,
|
|
195
196
|
};
|
|
196
197
|
const result = await this.chatAgentService.invokeAgent(
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
import { MsgHistoryManager } from '../model/msg-history-manager';
|
|
28
28
|
import { IChatSlashCommandItem } from '../types';
|
|
29
29
|
|
|
30
|
-
import type { TextPart, ToolCallPart } from 'ai';
|
|
30
|
+
import type { ImagePart, TextPart, ToolCallPart } from 'ai';
|
|
31
31
|
|
|
32
32
|
export type IChatProgressResponseContent =
|
|
33
33
|
| IChatMarkdownContent
|
|
@@ -330,7 +330,15 @@ export class ChatModel extends Disposable implements IChatModel {
|
|
|
330
330
|
if (!request.response.isComplete) {
|
|
331
331
|
continue;
|
|
332
332
|
}
|
|
333
|
-
history.push({
|
|
333
|
+
history.push({
|
|
334
|
+
role: 'user',
|
|
335
|
+
content: request.message.images?.length
|
|
336
|
+
? [
|
|
337
|
+
{ type: 'text', text: request.message.prompt },
|
|
338
|
+
...request.message.images.map((image) => ({ type: 'image', image: new URL(image) } as ImagePart)),
|
|
339
|
+
]
|
|
340
|
+
: request.message.prompt,
|
|
341
|
+
});
|
|
334
342
|
for (const part of request.response.responseParts) {
|
|
335
343
|
if (part.kind === 'treeData' || part.kind === 'component') {
|
|
336
344
|
continue;
|
|
@@ -2,7 +2,7 @@ import { Injectable } from '@opensumi/di';
|
|
|
2
2
|
import { Disposable, Emitter, Event, getDebugLogger } from '@opensumi/ide-core-common';
|
|
3
3
|
|
|
4
4
|
import { IChatWelcomeMessageContent, ISampleQuestions, SLASH_SYMBOL } from '../../common';
|
|
5
|
-
import { IChatFeatureRegistry, IChatSlashCommandHandler, IChatSlashCommandItem } from '../types';
|
|
5
|
+
import { IChatFeatureRegistry, IChatSlashCommandHandler, IChatSlashCommandItem, IImageUploadProvider } from '../types';
|
|
6
6
|
|
|
7
7
|
import { ChatSlashCommandItemModel, ChatWelcomeMessageModel } from './chat-model';
|
|
8
8
|
import { ChatProxyService } from './chat-proxy.service';
|
|
@@ -12,6 +12,15 @@ export class ChatFeatureRegistry extends Disposable implements IChatFeatureRegis
|
|
|
12
12
|
private readonly logger = getDebugLogger();
|
|
13
13
|
private slashCommandsMap: Map<string, ChatSlashCommandItemModel> = new Map();
|
|
14
14
|
private slashCommandsHandlerMap: Map<string, IChatSlashCommandHandler> = new Map();
|
|
15
|
+
private imageUploadProvider: IImageUploadProvider | undefined;
|
|
16
|
+
|
|
17
|
+
public registerImageUploadProvider(provider: IImageUploadProvider): void {
|
|
18
|
+
this.imageUploadProvider = provider;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
public getImageUploadProvider(): IImageUploadProvider | undefined {
|
|
22
|
+
return this.imageUploadProvider;
|
|
23
|
+
}
|
|
15
24
|
|
|
16
25
|
public chatWelcomeMessageModel?: ChatWelcomeMessageModel;
|
|
17
26
|
|
|
@@ -62,8 +62,8 @@ export class ChatInternalService extends Disposable {
|
|
|
62
62
|
this._onChangeRequestId.fire(id);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
createRequest(input: string, agentId: string, command?: string) {
|
|
66
|
-
return this.chatManagerService.createRequest(this.#sessionModel.sessionId, input, agentId, command);
|
|
65
|
+
createRequest(input: string, agentId: string, images?: string[], command?: string) {
|
|
66
|
+
return this.chatManagerService.createRequest(this.#sessionModel.sessionId, input, agentId, command, images);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
sendRequest(request: ChatRequestModel, regenerate = false) {
|
|
@@ -281,7 +281,7 @@ export const AIChatView = () => {
|
|
|
281
281
|
if (loading) {
|
|
282
282
|
return;
|
|
283
283
|
}
|
|
284
|
-
await handleSend(message.message, message.agentId, message.command);
|
|
284
|
+
await handleSend(message.message, message.images, message.agentId, message.command);
|
|
285
285
|
} else {
|
|
286
286
|
if (message.agentId) {
|
|
287
287
|
setAgentId(message.agentId);
|
|
@@ -462,10 +462,16 @@ export const AIChatView = () => {
|
|
|
462
462
|
);
|
|
463
463
|
|
|
464
464
|
const renderUserMessage = React.useCallback(
|
|
465
|
-
async (renderModel: {
|
|
465
|
+
async (renderModel: {
|
|
466
|
+
message: string;
|
|
467
|
+
images?: string[];
|
|
468
|
+
agentId?: string;
|
|
469
|
+
relationId: string;
|
|
470
|
+
command?: string;
|
|
471
|
+
}) => {
|
|
466
472
|
const ChatUserRoleRender = chatRenderRegistry.chatUserRoleRender;
|
|
467
473
|
|
|
468
|
-
const { message, agentId, relationId, command } = renderModel;
|
|
474
|
+
const { message, images, agentId, relationId, command } = renderModel;
|
|
469
475
|
|
|
470
476
|
const visibleAgentId = agentId === ChatProxyService.AGENT_ID ? '' : agentId;
|
|
471
477
|
|
|
@@ -474,12 +480,13 @@ export const AIChatView = () => {
|
|
|
474
480
|
id: uuid(6),
|
|
475
481
|
relationId,
|
|
476
482
|
text: ChatUserRoleRender ? (
|
|
477
|
-
<ChatUserRoleRender content={message} agentId={visibleAgentId} command={command} />
|
|
483
|
+
<ChatUserRoleRender content={message} images={images} agentId={visibleAgentId} command={command} />
|
|
478
484
|
) : (
|
|
479
485
|
<CodeBlockWrapperInput
|
|
480
486
|
labelService={labelService}
|
|
481
487
|
relationId={relationId}
|
|
482
488
|
text={message}
|
|
489
|
+
images={images}
|
|
483
490
|
agentId={visibleAgentId}
|
|
484
491
|
command={command}
|
|
485
492
|
workspaceService={workspaceService}
|
|
@@ -598,10 +605,10 @@ export const AIChatView = () => {
|
|
|
598
605
|
|
|
599
606
|
const handleAgentReply = React.useCallback(
|
|
600
607
|
async (value: IChatMessageStructure) => {
|
|
601
|
-
const { message, agentId, command, reportExtra } = value;
|
|
608
|
+
const { message, images, agentId, command, reportExtra } = value;
|
|
602
609
|
const { actionType, actionSource } = reportExtra || {};
|
|
603
610
|
|
|
604
|
-
const request = aiChatService.createRequest(message, agentId!, command);
|
|
611
|
+
const request = aiChatService.createRequest(message, agentId!, images, command);
|
|
605
612
|
if (!request) {
|
|
606
613
|
return;
|
|
607
614
|
}
|
|
@@ -627,6 +634,7 @@ export const AIChatView = () => {
|
|
|
627
634
|
|
|
628
635
|
msgHistoryManager.addUserMessage({
|
|
629
636
|
content: message,
|
|
637
|
+
images: images || [],
|
|
630
638
|
agentId: agentId!,
|
|
631
639
|
agentCommand: command!,
|
|
632
640
|
relationId,
|
|
@@ -635,6 +643,7 @@ export const AIChatView = () => {
|
|
|
635
643
|
await renderUserMessage({
|
|
636
644
|
relationId,
|
|
637
645
|
message,
|
|
646
|
+
images,
|
|
638
647
|
command,
|
|
639
648
|
agentId,
|
|
640
649
|
});
|
|
@@ -668,7 +677,7 @@ export const AIChatView = () => {
|
|
|
668
677
|
);
|
|
669
678
|
|
|
670
679
|
const handleSend = React.useCallback(
|
|
671
|
-
async (message: string, agentId?: string, command?: string) => {
|
|
680
|
+
async (message: string, images?: string[], agentId?: string, command?: string) => {
|
|
672
681
|
const reportExtra = {
|
|
673
682
|
actionSource: ActionSourceEnum.Chat,
|
|
674
683
|
actionType: ActionTypeEnum.Send,
|
|
@@ -707,7 +716,7 @@ export const AIChatView = () => {
|
|
|
707
716
|
processedContent = processedContent.replace(match, `\`<attached_folder>${relativePath}\``);
|
|
708
717
|
}
|
|
709
718
|
}
|
|
710
|
-
return handleAgentReply({ message: processedContent, agentId, command, reportExtra });
|
|
719
|
+
return handleAgentReply({ message: processedContent, images, agentId, command, reportExtra });
|
|
711
720
|
},
|
|
712
721
|
[handleAgentReply],
|
|
713
722
|
);
|
|
@@ -750,6 +759,7 @@ export const AIChatView = () => {
|
|
|
750
759
|
message: msg.content,
|
|
751
760
|
agentId: msg.agentId,
|
|
752
761
|
command: msg.agentCommand,
|
|
762
|
+
images: msg.images,
|
|
753
763
|
});
|
|
754
764
|
} else if (msg.role === ChatMessageRole.Assistant && msg.requestId) {
|
|
755
765
|
const request = aiChatService.sessionModel.getRequest(msg.requestId)!;
|
|
@@ -2,6 +2,7 @@ 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 { Image } from '@opensumi/ide-components/lib/image';
|
|
5
6
|
import {
|
|
6
7
|
EDITOR_COMMANDS,
|
|
7
8
|
FILE_COMMANDS,
|
|
@@ -336,6 +337,7 @@ export const CodeBlockWrapper = ({
|
|
|
336
337
|
|
|
337
338
|
export const CodeBlockWrapperInput = ({
|
|
338
339
|
text,
|
|
340
|
+
images,
|
|
339
341
|
relationId,
|
|
340
342
|
agentId,
|
|
341
343
|
command,
|
|
@@ -344,6 +346,7 @@ export const CodeBlockWrapperInput = ({
|
|
|
344
346
|
commandService,
|
|
345
347
|
}: {
|
|
346
348
|
text: string;
|
|
349
|
+
images?: string[];
|
|
347
350
|
relationId: string;
|
|
348
351
|
agentId?: string;
|
|
349
352
|
command?: string;
|
|
@@ -370,6 +373,11 @@ export const CodeBlockWrapperInput = ({
|
|
|
370
373
|
|
|
371
374
|
return (
|
|
372
375
|
<div className={styles.ai_chat_code_wrapper}>
|
|
376
|
+
{images?.map((image) => (
|
|
377
|
+
<div className={styles.image_wrapper}>
|
|
378
|
+
<Image src={image} />
|
|
379
|
+
</div>
|
|
380
|
+
))}
|
|
373
381
|
<div className={styles.render_text}>
|
|
374
382
|
{tag && (
|
|
375
383
|
<div className={styles.tag_wrapper}>
|
|
@@ -162,7 +162,7 @@ const AgentWidget = ({ agentId, command }) => (
|
|
|
162
162
|
);
|
|
163
163
|
|
|
164
164
|
export interface IChatInputProps {
|
|
165
|
-
onSend: (value: string, agentId?: string, command?: string) => void;
|
|
165
|
+
onSend: (value: string, images?: string[], agentId?: string, command?: string) => void;
|
|
166
166
|
onValueChange?: (value: string) => void;
|
|
167
167
|
onExpand?: (value: boolean) => void;
|
|
168
168
|
placeholder?: string;
|
|
@@ -341,7 +341,7 @@ export const ChatInput = React.forwardRef((props: IChatInputProps, ref) => {
|
|
|
341
341
|
}
|
|
342
342
|
|
|
343
343
|
const handleSendLogic = (newValue: string = value) => {
|
|
344
|
-
onSend(newValue, agentId, command);
|
|
344
|
+
onSend(newValue, [], agentId, command);
|
|
345
345
|
setValue('');
|
|
346
346
|
setTheme('');
|
|
347
347
|
setAgentId('');
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
import { DataContent } from 'ai';
|
|
1
2
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
2
3
|
|
|
4
|
+
import { Image } from '@opensumi/ide-components/lib/image';
|
|
3
5
|
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 { Icon, getIcon } from '@opensumi/ide-core-browser/lib/components';
|
|
7
|
+
import { ChatFeatureRegistryToken, URI, localize } from '@opensumi/ide-core-common';
|
|
6
8
|
import { CommandService } from '@opensumi/ide-core-common/lib/command';
|
|
7
9
|
import { WorkbenchEditorService } from '@opensumi/ide-editor';
|
|
8
10
|
import { FileSearchServicePath, IFileSearchService } from '@opensumi/ide-file-search';
|
|
11
|
+
import { IMessageService } from '@opensumi/ide-overlay';
|
|
9
12
|
import { IWorkspaceService } from '@opensumi/ide-workspace';
|
|
10
13
|
|
|
11
14
|
import { IChatInternalService } from '../../common';
|
|
15
|
+
import { ChatFeatureRegistry } from '../chat/chat.feature.registry';
|
|
12
16
|
import { ChatInternalService } from '../chat/chat.internal.service';
|
|
13
17
|
import { OPEN_MCP_CONFIG_COMMAND } from '../mcp/config/mcp-config.commands';
|
|
14
18
|
|
|
@@ -17,7 +21,13 @@ import { MentionInput } from './mention-input/mention-input';
|
|
|
17
21
|
import { FooterButtonPosition, FooterConfig, MentionItem, MentionType } from './mention-input/types';
|
|
18
22
|
|
|
19
23
|
export interface IChatMentionInputProps {
|
|
20
|
-
onSend: (
|
|
24
|
+
onSend: (
|
|
25
|
+
value: string,
|
|
26
|
+
images?: string[],
|
|
27
|
+
agentId?: string,
|
|
28
|
+
command?: string,
|
|
29
|
+
option?: { model: string; [key: string]: any },
|
|
30
|
+
) => void;
|
|
21
31
|
onValueChange?: (value: string) => void;
|
|
22
32
|
onExpand?: (value: boolean) => void;
|
|
23
33
|
placeholder?: string;
|
|
@@ -26,6 +36,7 @@ export interface IChatMentionInputProps {
|
|
|
26
36
|
sendBtnClassName?: string;
|
|
27
37
|
defaultHeight?: number;
|
|
28
38
|
value?: string;
|
|
39
|
+
images?: Array<DataContent | URL>;
|
|
29
40
|
autoFocus?: boolean;
|
|
30
41
|
theme?: string | null;
|
|
31
42
|
setTheme: (theme: string | null) => void;
|
|
@@ -41,6 +52,7 @@ export const ChatMentionInput = (props: IChatMentionInputProps) => {
|
|
|
41
52
|
const { onSend, disabled = false } = props;
|
|
42
53
|
|
|
43
54
|
const [value, setValue] = useState(props.value || '');
|
|
55
|
+
const [images, setImages] = useState(props.images || []);
|
|
44
56
|
const aiChatService = useInjectable<ChatInternalService>(IChatInternalService);
|
|
45
57
|
const commandService = useInjectable<CommandService>(CommandService);
|
|
46
58
|
const searchService = useInjectable<IFileSearchService>(FileSearchServicePath);
|
|
@@ -48,7 +60,8 @@ export const ChatMentionInput = (props: IChatMentionInputProps) => {
|
|
|
48
60
|
const workspaceService = useInjectable<IWorkspaceService>(IWorkspaceService);
|
|
49
61
|
const editorService = useInjectable<WorkbenchEditorService>(WorkbenchEditorService);
|
|
50
62
|
const labelService = useInjectable<LabelService>(LabelService);
|
|
51
|
-
|
|
63
|
+
const messageService = useInjectable<IMessageService>(IMessageService);
|
|
64
|
+
const chatFeatureRegistry = useInjectable<ChatFeatureRegistry>(ChatFeatureRegistryToken);
|
|
52
65
|
const handleShowMCPConfig = React.useCallback(() => {
|
|
53
66
|
commandService.executeCommand(OPEN_MCP_CONFIG_COMMAND.id);
|
|
54
67
|
}, [commandService]);
|
|
@@ -239,6 +252,24 @@ export const ChatMentionInput = (props: IChatMentionInputProps) => {
|
|
|
239
252
|
onClick: handleShowMCPConfig,
|
|
240
253
|
position: FooterButtonPosition.LEFT,
|
|
241
254
|
},
|
|
255
|
+
{
|
|
256
|
+
id: 'upload-image',
|
|
257
|
+
icon: 'image',
|
|
258
|
+
title: localize('aiNative.chat.imageUpload'),
|
|
259
|
+
onClick: () => {
|
|
260
|
+
const input = document.createElement('input');
|
|
261
|
+
input.type = 'file';
|
|
262
|
+
input.accept = 'image/*';
|
|
263
|
+
input.onchange = (e) => {
|
|
264
|
+
const files = (e.target as HTMLInputElement).files;
|
|
265
|
+
if (files?.length) {
|
|
266
|
+
handleImageUpload(Array.from(files));
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
input.click();
|
|
270
|
+
},
|
|
271
|
+
position: FooterButtonPosition.LEFT,
|
|
272
|
+
},
|
|
242
273
|
],
|
|
243
274
|
showModelSelector: true,
|
|
244
275
|
}),
|
|
@@ -254,13 +285,54 @@ export const ChatMentionInput = (props: IChatMentionInputProps) => {
|
|
|
254
285
|
if (disabled) {
|
|
255
286
|
return;
|
|
256
287
|
}
|
|
257
|
-
onSend(
|
|
288
|
+
onSend(
|
|
289
|
+
content,
|
|
290
|
+
images.map((image) => image.toString()),
|
|
291
|
+
undefined,
|
|
292
|
+
undefined,
|
|
293
|
+
option,
|
|
294
|
+
);
|
|
295
|
+
setImages(props.images || []);
|
|
258
296
|
},
|
|
259
|
-
[onSend,
|
|
297
|
+
[onSend, images, disabled],
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const handleImageUpload = useCallback(
|
|
301
|
+
async (files: File[]) => {
|
|
302
|
+
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif'];
|
|
303
|
+
|
|
304
|
+
// Validate file types
|
|
305
|
+
const invalidFiles = files.filter((file) => !allowedTypes.includes(file.type));
|
|
306
|
+
if (invalidFiles.length > 0) {
|
|
307
|
+
messageService.error('Only JPG, PNG, WebP and GIF images are supported');
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const imageUploadProvider = chatFeatureRegistry.getImageUploadProvider();
|
|
312
|
+
if (!imageUploadProvider) {
|
|
313
|
+
messageService.error('No image upload provider found');
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Upload all files
|
|
318
|
+
const uploadedData = await Promise.all(files.map((file) => imageUploadProvider.imageUpload(file)));
|
|
319
|
+
|
|
320
|
+
const newImages = [...images, ...uploadedData];
|
|
321
|
+
setImages(newImages);
|
|
322
|
+
},
|
|
323
|
+
[images],
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
const handleDeleteImage = useCallback(
|
|
327
|
+
(index: number) => {
|
|
328
|
+
setImages(images.filter((_, i) => i !== index));
|
|
329
|
+
},
|
|
330
|
+
[images],
|
|
260
331
|
);
|
|
261
332
|
|
|
262
333
|
return (
|
|
263
334
|
<div className={styles.chat_input_container}>
|
|
335
|
+
{images.length > 0 && <ImagePreviewer images={images} onDelete={handleDeleteImage} />}
|
|
264
336
|
<MentionInput
|
|
265
337
|
mentionItems={defaultMenuItems}
|
|
266
338
|
onSend={handleSend}
|
|
@@ -270,7 +342,29 @@ export const ChatMentionInput = (props: IChatMentionInputProps) => {
|
|
|
270
342
|
workspaceService={workspaceService}
|
|
271
343
|
placeholder={localize('aiNative.chat.input.placeholder.default')}
|
|
272
344
|
footerConfig={defaultMentionInputFooterOptions}
|
|
345
|
+
onImageUpload={handleImageUpload}
|
|
273
346
|
/>
|
|
274
347
|
</div>
|
|
275
348
|
);
|
|
276
349
|
};
|
|
350
|
+
|
|
351
|
+
const ImagePreviewer = ({
|
|
352
|
+
images,
|
|
353
|
+
onDelete,
|
|
354
|
+
}: {
|
|
355
|
+
images: Array<DataContent | URL>;
|
|
356
|
+
onDelete: (index: number) => void;
|
|
357
|
+
}) => (
|
|
358
|
+
<div>
|
|
359
|
+
<div className={styles.thumbnail_container}>
|
|
360
|
+
{images.map((image, index) => (
|
|
361
|
+
<div key={index} className={styles.thumbnail}>
|
|
362
|
+
<Image src={image.toString()} />
|
|
363
|
+
<button onClick={() => onDelete(index)} className={styles.delete_button}>
|
|
364
|
+
<Icon iconClass='codicon codicon-close' />
|
|
365
|
+
</button>
|
|
366
|
+
</div>
|
|
367
|
+
))}
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
);
|
|
@@ -601,3 +601,52 @@
|
|
|
601
601
|
vertical-align: middle;
|
|
602
602
|
font-size: 12px;
|
|
603
603
|
}
|
|
604
|
+
|
|
605
|
+
.thumbnail_container {
|
|
606
|
+
display: flex;
|
|
607
|
+
gap: 4px;
|
|
608
|
+
padding: 4px 12px;
|
|
609
|
+
.thumbnail {
|
|
610
|
+
width: 36px;
|
|
611
|
+
height: 36px;
|
|
612
|
+
border-radius: 4px;
|
|
613
|
+
position: relative;
|
|
614
|
+
box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.3);
|
|
615
|
+
padding: 2px;
|
|
616
|
+
display: inline-flex;
|
|
617
|
+
align-items: center;
|
|
618
|
+
img {
|
|
619
|
+
width: 100%;
|
|
620
|
+
height: 100%;
|
|
621
|
+
object-fit: cover;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
.delete_button {
|
|
625
|
+
position: absolute;
|
|
626
|
+
top: -5px;
|
|
627
|
+
right: -5px;
|
|
628
|
+
font-size: 12px;
|
|
629
|
+
padding: 2px;
|
|
630
|
+
border: 0;
|
|
631
|
+
border-radius: 50%;
|
|
632
|
+
transition: transform 0.2s ease-in-out;
|
|
633
|
+
width: 16px;
|
|
634
|
+
height: 16px;
|
|
635
|
+
display: flex;
|
|
636
|
+
justify-content: center;
|
|
637
|
+
align-items: center;
|
|
638
|
+
background-color: var(--badge-background);
|
|
639
|
+
&:hover {
|
|
640
|
+
cursor: pointer;
|
|
641
|
+
transform: scale(1.2);
|
|
642
|
+
}
|
|
643
|
+
:global(.codicon) {
|
|
644
|
+
font-size: 12px;
|
|
645
|
+
color: var(--badge-foreground);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
.image_wrapper {
|
|
651
|
+
display: inline-block;
|
|
652
|
+
}
|
|
@@ -18,6 +18,7 @@ export const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
18
18
|
loading = false,
|
|
19
19
|
mentionKeyword = MENTION_KEYWORD,
|
|
20
20
|
onSelectionChange,
|
|
21
|
+
onImageUpload,
|
|
21
22
|
labelService,
|
|
22
23
|
workspaceService,
|
|
23
24
|
placeholder = 'Ask anything, @ to mention',
|
|
@@ -447,20 +448,35 @@ export const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
447
448
|
};
|
|
448
449
|
|
|
449
450
|
// 添加粘贴事件处理
|
|
450
|
-
const handlePaste = (e: React.ClipboardEvent<HTMLDivElement>) => {
|
|
451
|
-
|
|
451
|
+
const handlePaste = async (e: React.ClipboardEvent<HTMLDivElement>) => {
|
|
452
|
+
const items = e.clipboardData.items;
|
|
453
|
+
|
|
454
|
+
// 先收集所有图片文件
|
|
455
|
+
const imageFiles: File[] = [];
|
|
456
|
+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
|
457
|
+
for (let i = 0; i < items.length; i++) {
|
|
458
|
+
if (items[i].kind === 'file' && items[i].type.startsWith('image/')) {
|
|
459
|
+
const file = items[i].getAsFile();
|
|
460
|
+
if (file) {
|
|
461
|
+
imageFiles.push(file);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
452
466
|
e.preventDefault();
|
|
453
467
|
|
|
454
|
-
//
|
|
468
|
+
// 处理所有收集到的图片
|
|
469
|
+
if (imageFiles.length > 0 && onImageUpload) {
|
|
470
|
+
await onImageUpload(imageFiles);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
455
474
|
const text = e.clipboardData.getData('text/plain');
|
|
456
475
|
|
|
457
476
|
// 处理文本,保留换行和缩进
|
|
458
477
|
const processedText = text
|
|
459
|
-
// 将制表符转换为4个空格
|
|
460
478
|
.replace(/\t/g, ' ')
|
|
461
|
-
// 将连续的换行符转换为单个换行
|
|
462
479
|
.replace(/\n\s*\n/g, '\n\n')
|
|
463
|
-
// 移除行尾空格
|
|
464
480
|
.replace(/[ \t]+$/gm, '');
|
|
465
481
|
|
|
466
482
|
const selection = window.getSelection();
|
|
@@ -931,7 +947,7 @@ export const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
931
947
|
title={button.title}
|
|
932
948
|
>
|
|
933
949
|
<EnhanceIcon
|
|
934
|
-
className={cls(getIcon(button.icon), styles[`${button.id}_logo`])}
|
|
950
|
+
className={cls(button.icon ? getIcon(button.icon) : button.iconClass, styles[`${button.id}_logo`])}
|
|
935
951
|
tabIndex={0}
|
|
936
952
|
role='button'
|
|
937
953
|
ariaLabel={button.title}
|
|
@@ -965,8 +981,8 @@ export const MentionInput: React.FC<MentionInputProps> = ({
|
|
|
965
981
|
contentEditable={true}
|
|
966
982
|
onInput={handleInput}
|
|
967
983
|
onKeyDown={handleKeyDown}
|
|
968
|
-
onCompositionEnd={handleCompositionEnd}
|
|
969
984
|
onPaste={handlePaste}
|
|
985
|
+
onCompositionEnd={handleCompositionEnd}
|
|
970
986
|
/>
|
|
971
987
|
</div>
|
|
972
988
|
<div className={styles.footer}>
|
|
@@ -55,7 +55,8 @@ export enum MentionType {
|
|
|
55
55
|
|
|
56
56
|
interface FooterButton {
|
|
57
57
|
id: string;
|
|
58
|
-
icon
|
|
58
|
+
icon?: string;
|
|
59
|
+
iconClass?: string;
|
|
59
60
|
title: string;
|
|
60
61
|
onClick?: () => void;
|
|
61
62
|
position: FooterButtonPosition;
|
|
@@ -75,6 +76,7 @@ export interface MentionInputProps {
|
|
|
75
76
|
placeholder?: string;
|
|
76
77
|
loading?: boolean;
|
|
77
78
|
onSelectionChange?: (value: string) => void;
|
|
79
|
+
onImageUpload?: (files: File[]) => Promise<void>;
|
|
78
80
|
footerConfig?: FooterConfig; // 新增配置项
|
|
79
81
|
mentionKeyword?: string;
|
|
80
82
|
labelService?: LabelService;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { DataContent } from 'ai';
|
|
2
|
+
|
|
1
3
|
import { Autowired, Injectable } from '@opensumi/di';
|
|
2
4
|
import { AppConfig } from '@opensumi/ide-core-browser/lib/react-providers/config-provider';
|
|
3
5
|
import { WithEventBus } from '@opensumi/ide-core-common/lib/event-bus/event-decorator';
|
|
@@ -137,7 +137,7 @@ export const MCPConfigView: React.FC = () => {
|
|
|
137
137
|
async (serverName: string) => {
|
|
138
138
|
const servers = preferenceService.get<MCPServerFormData[]>(AINativeSettingSectionsId.MCPServers, []);
|
|
139
139
|
const updatedServers = servers.filter((s) => s.name !== serverName);
|
|
140
|
-
sumiMCPServerBackendProxy
|
|
140
|
+
sumiMCPServerBackendProxy.$removeServer(serverName);
|
|
141
141
|
await preferenceService.set(AINativeSettingSectionsId.MCPServers, updatedServers, PreferenceScope.User);
|
|
142
142
|
await loadServers();
|
|
143
143
|
},
|
|
@@ -156,7 +156,7 @@ export const MCPConfigView: React.FC = () => {
|
|
|
156
156
|
}
|
|
157
157
|
setServers(servers as MCPServer[]);
|
|
158
158
|
setFormVisible(false);
|
|
159
|
-
await sumiMCPServerBackendProxy
|
|
159
|
+
await sumiMCPServerBackendProxy.$addOrUpdateServer(data as MCPServerDescription);
|
|
160
160
|
await preferenceService.set(AINativeSettingSectionsId.MCPServers, servers, PreferenceScope.User);
|
|
161
161
|
await loadServers();
|
|
162
162
|
},
|
|
@@ -167,7 +167,14 @@ export const MCPServerForm: FC<Props> = ({ visible, initialData, onSave, onCance
|
|
|
167
167
|
|
|
168
168
|
const handleTypeChange = useCallback(
|
|
169
169
|
(value: MCP_SERVER_TYPE) => {
|
|
170
|
-
setFormData({
|
|
170
|
+
setFormData({
|
|
171
|
+
...formData,
|
|
172
|
+
type: value,
|
|
173
|
+
command: '',
|
|
174
|
+
args: [],
|
|
175
|
+
env: {},
|
|
176
|
+
serverHost: '',
|
|
177
|
+
});
|
|
171
178
|
},
|
|
172
179
|
[formData],
|
|
173
180
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Autowired } from '@opensumi/di';
|
|
2
|
+
import { getIcon } from '@opensumi/ide-components';
|
|
2
3
|
import { LabelService } from '@opensumi/ide-core-browser/lib/services';
|
|
3
|
-
import { Domain, URI } from '@opensumi/ide-core-common';
|
|
4
|
+
import { Domain, URI, localize } from '@opensumi/ide-core-common';
|
|
4
5
|
import {
|
|
5
6
|
BrowserEditorContribution,
|
|
6
7
|
EditorComponentRegistry,
|
|
@@ -53,8 +54,8 @@ export class MCPConfigContribution implements BrowserEditorContribution {
|
|
|
53
54
|
|
|
54
55
|
return {
|
|
55
56
|
uri,
|
|
56
|
-
name: '
|
|
57
|
-
icon: '
|
|
57
|
+
name: localize('ai.native.mcp.config.title'),
|
|
58
|
+
icon: getIcon('mcp'),
|
|
58
59
|
metadata: {
|
|
59
60
|
configType,
|
|
60
61
|
},
|
|
@@ -50,18 +50,18 @@ export class MCPServerProxyService implements IMCPServerProxyService {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
async getAllMCPTools() {
|
|
53
|
-
return this.sumiMCPServerProxyService
|
|
53
|
+
return this.sumiMCPServerProxyService.$getAllMCPTools();
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
async $getServers() {
|
|
57
|
-
return this.sumiMCPServerProxyService
|
|
57
|
+
return this.sumiMCPServerProxyService.$getServers();
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
async $startServer(serverName: string) {
|
|
61
|
-
await this.sumiMCPServerProxyService
|
|
61
|
+
await this.sumiMCPServerProxyService.$startServer(serverName);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
async $stopServer(serverName: string) {
|
|
65
|
-
await this.sumiMCPServerProxyService
|
|
65
|
+
await this.sumiMCPServerProxyService.$stopServer(serverName);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -62,7 +62,7 @@ export class MsgHistoryManager extends Disposable {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
public addUserMessage(
|
|
65
|
-
message: Required<Pick<IExcludeMessage, 'agentId' | 'agentCommand' | 'content' | 'relationId'>>,
|
|
65
|
+
message: Required<Pick<IExcludeMessage, 'agentId' | 'agentCommand' | 'content' | 'relationId' | 'images'>>,
|
|
66
66
|
): string {
|
|
67
67
|
return this.doAddMessage({
|
|
68
68
|
...message,
|