@opensumi/ide-ai-native 3.8.3-next-1741934038.0 → 3.8.3-next-1741942697.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/lib/browser/ai-core.contribution.d.ts +1 -0
  2. package/lib/browser/ai-core.contribution.d.ts.map +1 -1
  3. package/lib/browser/ai-core.contribution.js +7 -1
  4. package/lib/browser/ai-core.contribution.js.map +1 -1
  5. package/lib/browser/chat/chat-manager.service.d.ts +1 -1
  6. package/lib/browser/chat/chat-manager.service.d.ts.map +1 -1
  7. package/lib/browser/chat/chat-manager.service.js +3 -2
  8. package/lib/browser/chat/chat-manager.service.js.map +1 -1
  9. package/lib/browser/chat/chat-proxy.service.d.ts.map +1 -1
  10. package/lib/browser/chat/chat-proxy.service.js +1 -0
  11. package/lib/browser/chat/chat-proxy.service.js.map +1 -1
  12. package/lib/browser/chat/chat.internal.service.d.ts +1 -1
  13. package/lib/browser/chat/chat.internal.service.d.ts.map +1 -1
  14. package/lib/browser/chat/chat.internal.service.js +2 -2
  15. package/lib/browser/chat/chat.internal.service.js.map +1 -1
  16. package/lib/browser/chat/chat.view.d.ts.map +1 -1
  17. package/lib/browser/chat/chat.view.js +10 -7
  18. package/lib/browser/chat/chat.view.js.map +1 -1
  19. package/lib/browser/components/ChatEditor.d.ts +2 -1
  20. package/lib/browser/components/ChatEditor.d.ts.map +1 -1
  21. package/lib/browser/components/ChatEditor.js +5 -2
  22. package/lib/browser/components/ChatEditor.js.map +1 -1
  23. package/lib/browser/components/ChatInput.d.ts +1 -1
  24. package/lib/browser/components/ChatInput.d.ts.map +1 -1
  25. package/lib/browser/components/ChatInput.js +1 -1
  26. package/lib/browser/components/ChatInput.js.map +1 -1
  27. package/lib/browser/components/ChatMentionInput.d.ts +3 -1
  28. package/lib/browser/components/ChatMentionInput.d.ts.map +1 -1
  29. package/lib/browser/components/ChatMentionInput.js +51 -3
  30. package/lib/browser/components/ChatMentionInput.js.map +1 -1
  31. package/lib/browser/components/components.module.less +49 -0
  32. package/lib/browser/components/mention-input/mention-input.d.ts.map +1 -1
  33. package/lib/browser/components/mention-input/mention-input.js +17 -3
  34. package/lib/browser/components/mention-input/mention-input.js.map +1 -1
  35. package/lib/browser/components/mention-input/mention-input.module.less +1 -0
  36. package/lib/browser/components/mention-input/types.d.ts +3 -1
  37. package/lib/browser/components/mention-input/types.d.ts.map +1 -1
  38. package/lib/browser/components/mention-input/types.js.map +1 -1
  39. package/lib/browser/context/llm-context.service.d.ts.map +1 -1
  40. package/lib/browser/context/llm-context.service.js.map +1 -1
  41. package/lib/browser/contrib/image-upload/image-upload.feature.registry.d.ts +8 -0
  42. package/lib/browser/contrib/image-upload/image-upload.feature.registry.d.ts.map +1 -0
  43. package/lib/browser/contrib/image-upload/image-upload.feature.registry.js +19 -0
  44. package/lib/browser/contrib/image-upload/image-upload.feature.registry.js.map +1 -0
  45. package/lib/browser/index.d.ts.map +1 -1
  46. package/lib/browser/index.js +5 -0
  47. package/lib/browser/index.js.map +1 -1
  48. package/lib/browser/model/msg-history-manager.d.ts +1 -1
  49. package/lib/browser/model/msg-history-manager.d.ts.map +1 -1
  50. package/lib/browser/model/msg-history-manager.js.map +1 -1
  51. package/lib/browser/types.d.ts +14 -1
  52. package/lib/browser/types.d.ts.map +1 -1
  53. package/lib/browser/types.js.map +1 -1
  54. package/lib/common/index.d.ts +6 -0
  55. package/lib/common/index.d.ts.map +1 -1
  56. package/lib/common/index.js.map +1 -1
  57. package/lib/common/llm-context.d.ts.map +1 -1
  58. package/lib/common/llm-context.js.map +1 -1
  59. package/lib/node/base-language-model.d.ts +1 -1
  60. package/lib/node/base-language-model.d.ts.map +1 -1
  61. package/lib/node/base-language-model.js +15 -4
  62. package/lib/node/base-language-model.js.map +1 -1
  63. package/package.json +23 -23
  64. package/src/browser/ai-core.contribution.ts +6 -0
  65. package/src/browser/chat/chat-manager.service.ts +3 -2
  66. package/src/browser/chat/chat-proxy.service.ts +1 -0
  67. package/src/browser/chat/chat.internal.service.ts +2 -2
  68. package/src/browser/chat/chat.view.tsx +18 -8
  69. package/src/browser/components/ChatEditor.tsx +8 -0
  70. package/src/browser/components/ChatInput.tsx +2 -2
  71. package/src/browser/components/ChatMentionInput.tsx +92 -4
  72. package/src/browser/components/components.module.less +49 -0
  73. package/src/browser/components/mention-input/mention-input.module.less +1 -0
  74. package/src/browser/components/mention-input/mention-input.tsx +18 -1
  75. package/src/browser/components/mention-input/types.ts +3 -1
  76. package/src/browser/context/llm-context.service.ts +2 -0
  77. package/src/browser/contrib/image-upload/image-upload.feature.registry.ts +18 -0
  78. package/src/browser/index.ts +8 -0
  79. package/src/browser/model/msg-history-manager.ts +1 -1
  80. package/src/browser/types.ts +17 -1
  81. package/src/common/index.ts +6 -0
  82. package/src/common/llm-context.ts +2 -0
  83. package/src/node/base-language-model.ts +25 -3
@@ -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: { message: string; agentId?: string; relationId: string; command?: string }) => {
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,23 +1,34 @@
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';
6
+ import { Icon, getIcon } from '@opensumi/ide-core-browser/lib/components';
5
7
  import { 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';
12
15
  import { ChatInternalService } from '../chat/chat.internal.service';
16
+ import { ImageUploadProviderRegistryToken } from '../contrib/image-upload/image-upload.feature.registry';
13
17
  import { OPEN_MCP_CONFIG_COMMAND } from '../mcp/config/mcp-config.commands';
18
+ import { IImageUploadProviderRegistry } from '../types';
14
19
 
15
20
  import styles from './components.module.less';
16
21
  import { MentionInput } from './mention-input/mention-input';
17
22
  import { FooterButtonPosition, FooterConfig, MentionItem, MentionType } from './mention-input/types';
18
23
 
19
24
  export interface IChatMentionInputProps {
20
- onSend: (value: string, agentId?: string, command?: string, option?: { model: string; [key: string]: any }) => void;
25
+ onSend: (
26
+ value: string,
27
+ images?: string[],
28
+ agentId?: string,
29
+ command?: string,
30
+ option?: { model: string; [key: string]: any },
31
+ ) => void;
21
32
  onValueChange?: (value: string) => void;
22
33
  onExpand?: (value: boolean) => void;
23
34
  placeholder?: string;
@@ -26,6 +37,7 @@ export interface IChatMentionInputProps {
26
37
  sendBtnClassName?: string;
27
38
  defaultHeight?: number;
28
39
  value?: string;
40
+ images?: Array<DataContent | URL>;
29
41
  autoFocus?: boolean;
30
42
  theme?: string | null;
31
43
  setTheme: (theme: string | null) => void;
@@ -41,6 +53,7 @@ export const ChatMentionInput = (props: IChatMentionInputProps) => {
41
53
  const { onSend, disabled = false } = props;
42
54
 
43
55
  const [value, setValue] = useState(props.value || '');
56
+ const [images, setImages] = useState(props.images || []);
44
57
  const aiChatService = useInjectable<ChatInternalService>(IChatInternalService);
45
58
  const commandService = useInjectable<CommandService>(CommandService);
46
59
  const searchService = useInjectable<IFileSearchService>(FileSearchServicePath);
@@ -48,6 +61,8 @@ export const ChatMentionInput = (props: IChatMentionInputProps) => {
48
61
  const workspaceService = useInjectable<IWorkspaceService>(IWorkspaceService);
49
62
  const editorService = useInjectable<WorkbenchEditorService>(WorkbenchEditorService);
50
63
  const labelService = useInjectable<LabelService>(LabelService);
64
+ const messageService = useInjectable<IMessageService>(IMessageService);
65
+ const imageUploadProviderRegistry = useInjectable<IImageUploadProviderRegistry>(ImageUploadProviderRegistryToken);
51
66
 
52
67
  const handleShowMCPConfig = React.useCallback(() => {
53
68
  commandService.executeCommand(OPEN_MCP_CONFIG_COMMAND.id);
@@ -239,6 +254,24 @@ export const ChatMentionInput = (props: IChatMentionInputProps) => {
239
254
  onClick: handleShowMCPConfig,
240
255
  position: FooterButtonPosition.LEFT,
241
256
  },
257
+ {
258
+ id: 'upload-image',
259
+ iconClass: 'codicon codicon-file-media',
260
+ title: 'Upload Image',
261
+ onClick: () => {
262
+ const input = document.createElement('input');
263
+ input.type = 'file';
264
+ input.accept = 'image/*';
265
+ input.onchange = (e) => {
266
+ const file = (e.target as HTMLInputElement).files?.[0];
267
+ if (file) {
268
+ handleImageUpload(file);
269
+ }
270
+ };
271
+ input.click();
272
+ },
273
+ position: FooterButtonPosition.LEFT,
274
+ },
242
275
  ],
243
276
  showModelSelector: true,
244
277
  }),
@@ -254,13 +287,46 @@ export const ChatMentionInput = (props: IChatMentionInputProps) => {
254
287
  if (disabled) {
255
288
  return;
256
289
  }
257
- onSend(content, undefined, undefined, option);
290
+ onSend(
291
+ content,
292
+ images.map((image) => image.toString()),
293
+ undefined,
294
+ undefined,
295
+ option,
296
+ );
258
297
  },
259
- [onSend, editorService, disabled],
298
+ [onSend, images, disabled],
299
+ );
300
+
301
+ const handleImageUpload = useCallback(
302
+ async (file: File) => {
303
+ const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif'];
304
+ if (!allowedTypes.includes(file.type)) {
305
+ messageService.error('Only JPG, PNG, WebP and GIF images are supported');
306
+ return;
307
+ }
308
+
309
+ const imageUploadProvider = imageUploadProviderRegistry.getImageUploadProvider();
310
+ if (!imageUploadProvider) {
311
+ messageService.error('No image upload provider found');
312
+ return;
313
+ }
314
+ const data = await imageUploadProvider.imageUpload(file);
315
+ setImages([...images, data]);
316
+ },
317
+ [images],
318
+ );
319
+
320
+ const handleDeleteImage = useCallback(
321
+ (index: number) => {
322
+ setImages(images.filter((_, i) => i !== index));
323
+ },
324
+ [images],
260
325
  );
261
326
 
262
327
  return (
263
328
  <div className={styles.chat_input_container}>
329
+ {images.length > 0 && <ImagePreviewer images={images} onDelete={handleDeleteImage} />}
264
330
  <MentionInput
265
331
  mentionItems={defaultMenuItems}
266
332
  onSend={handleSend}
@@ -270,7 +336,29 @@ export const ChatMentionInput = (props: IChatMentionInputProps) => {
270
336
  workspaceService={workspaceService}
271
337
  placeholder={localize('aiNative.chat.input.placeholder.default')}
272
338
  footerConfig={defaultMentionInputFooterOptions}
339
+ onImageUpload={handleImageUpload}
273
340
  />
274
341
  </div>
275
342
  );
276
343
  };
344
+
345
+ const ImagePreviewer = ({
346
+ images,
347
+ onDelete,
348
+ }: {
349
+ images: Array<DataContent | URL>;
350
+ onDelete: (index: number) => void;
351
+ }) => (
352
+ <div>
353
+ <div className={styles.thumbnail_container}>
354
+ {images.map((image, index) => (
355
+ <div key={index} className={styles.thumbnail}>
356
+ <Image src={image.toString()} />
357
+ <button onClick={() => onDelete(index)} className={styles.delete_button}>
358
+ <Icon iconClass='codicon codicon-close' />
359
+ </button>
360
+ </div>
361
+ ))}
362
+ </div>
363
+ </div>
364
+ );
@@ -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
+ }
@@ -50,6 +50,7 @@
50
50
  display: flex;
51
51
  align-items: center;
52
52
  cursor: pointer;
53
+ text-wrap: nowrap;
53
54
  white-space: nowrap;
54
55
  box-sizing: border-box;
55
56
 
@@ -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',
@@ -275,6 +276,21 @@ export const MentionInput: React.FC<MentionInputProps> = ({
275
276
  }
276
277
  };
277
278
 
279
+ // 处理图片粘贴事件
280
+ const handlePaste = (e: React.ClipboardEvent<HTMLDivElement>) => {
281
+ const items = e.clipboardData.items;
282
+ // eslint-disable-next-line @typescript-eslint/prefer-for-of
283
+ for (let i = 0; i < items.length; i++) {
284
+ if (items[i].kind === 'file' && items[i].type.startsWith('image/')) {
285
+ const file = items[i].getAsFile();
286
+ if (file && onImageUpload) {
287
+ e.preventDefault();
288
+ onImageUpload(file);
289
+ }
290
+ }
291
+ }
292
+ };
293
+
278
294
  // 处理键盘事件
279
295
  const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
280
296
  // 如果按下ESC键且提及面板处于活动状态或内联搜索处于活动状态
@@ -867,7 +883,7 @@ export const MentionInput: React.FC<MentionInputProps> = ({
867
883
  title={button.title}
868
884
  >
869
885
  <EnhanceIcon
870
- className={cls(getIcon(button.icon), styles[`${button.id}_logo`])}
886
+ className={cls(button.icon ? getIcon(button.icon) : button.iconClass, styles[`${button.id}_logo`])}
871
887
  tabIndex={0}
872
888
  role='button'
873
889
  ariaLabel={button.title}
@@ -901,6 +917,7 @@ export const MentionInput: React.FC<MentionInputProps> = ({
901
917
  contentEditable={true}
902
918
  onInput={handleInput}
903
919
  onKeyDown={handleKeyDown}
920
+ onPaste={handlePaste}
904
921
  onCompositionEnd={handleCompositionEnd}
905
922
  />
906
923
  </div>
@@ -55,7 +55,8 @@ export enum MentionType {
55
55
 
56
56
  interface FooterButton {
57
57
  id: string;
58
- icon: string;
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?: (file: File) => 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';
@@ -0,0 +1,18 @@
1
+ import { Injectable } from '@opensumi/di';
2
+
3
+ import { IImageUploadProvider, IImageUploadProviderRegistry } from '../../types';
4
+
5
+ @Injectable()
6
+ export class ImageUploadProviderRegistry implements IImageUploadProviderRegistry {
7
+ private imageUploadProvider: IImageUploadProvider | undefined;
8
+
9
+ registerImageUploadProvider(provider: IImageUploadProvider): void {
10
+ this.imageUploadProvider = provider;
11
+ }
12
+
13
+ getImageUploadProvider(): IImageUploadProvider | undefined {
14
+ return this.imageUploadProvider;
15
+ }
16
+ }
17
+
18
+ export const ImageUploadProviderRegistryToken = Symbol('ImageUploadProviderRegistry');
@@ -46,6 +46,10 @@ import { ChatRenderRegistry } from './chat/chat.render.registry';
46
46
  import { LlmContextContribution } from './context/llm-context.contribution';
47
47
  import { LLMContextServiceImpl } from './context/llm-context.service';
48
48
  import { AICodeActionContribution } from './contrib/code-action/code-action.contribution';
49
+ import {
50
+ ImageUploadProviderRegistry,
51
+ ImageUploadProviderRegistryToken,
52
+ } from './contrib/image-upload/image-upload.feature.registry';
49
53
  import { AIInlineCompletionsProvider } from './contrib/inline-completions/completeProvider';
50
54
  import { IntelligentCompletionsContribution } from './contrib/intelligent-completions/intelligent-completions.contribution';
51
55
  import { IntelligentCompletionsRegistry } from './contrib/intelligent-completions/intelligent-completions.feature.registry';
@@ -186,6 +190,10 @@ export class AINativeModule extends BrowserModule {
186
190
  token: TerminalRegistryToken,
187
191
  useClass: TerminalFeatureRegistry,
188
192
  },
193
+ {
194
+ token: ImageUploadProviderRegistryToken,
195
+ useClass: ImageUploadProviderRegistry,
196
+ },
189
197
  {
190
198
  token: LanguageParserService,
191
199
  useClass: LanguageParserService,
@@ -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,
@@ -1,3 +1,4 @@
1
+ import { DataContent } from 'ai';
1
2
  import React from 'react';
2
3
  import { ZodSchema } from 'zod';
3
4
 
@@ -140,13 +141,14 @@ export type ChatWelcomeRender = (props: {
140
141
  export type ChatAIRoleRender = (props: { content: string }) => React.ReactElement | React.JSX.Element;
141
142
  export type ChatUserRoleRender = (props: {
142
143
  content: string;
144
+ images?: string[];
143
145
  agentId?: string;
144
146
  command?: string;
145
147
  }) => React.ReactElement | React.JSX.Element;
146
148
  export type ChatThinkingRender = (props: { thinkingText?: string }) => React.ReactElement | React.JSX.Element;
147
149
  export type ChatThinkingResultRender = (props: { thinkingResult?: string }) => React.ReactElement | React.JSX.Element;
148
150
  export type ChatInputRender = (props: {
149
- onSend: (value: string, agentId?: string, command?: string) => void;
151
+ onSend: (value: string, images?: string[], agentId?: string, command?: string) => void;
150
152
  onValueChange?: (value: string) => void;
151
153
  onExpand?: (value: boolean) => void;
152
154
  placeholder?: string;
@@ -289,6 +291,15 @@ export interface IProblemFixProviderRegistry {
289
291
  registerHoverFixProvider(handler: IHoverFixHandler): void;
290
292
  }
291
293
 
294
+ export interface IImageUploadProvider {
295
+ imageUpload(file: File): Promise<DataContent | URL>;
296
+ }
297
+
298
+ export interface IImageUploadProviderRegistry {
299
+ registerImageUploadProvider(provider: IImageUploadProvider): void;
300
+ getImageUploadProvider(): IImageUploadProvider | undefined;
301
+ }
302
+
292
303
  export const AINativeCoreContribution = Symbol('AINativeCoreContribution');
293
304
 
294
305
  export interface AINativeCoreContribution {
@@ -332,6 +343,11 @@ export interface AINativeCoreContribution {
332
343
  * @param provider
333
344
  */
334
345
  registerChatAgentPromptProvider?(): void;
346
+
347
+ /**
348
+ * 注册图片上传的能力
349
+ */
350
+ registerImageUploadProvider?(registry: IImageUploadProviderRegistry): void;
335
351
  }
336
352
 
337
353
  // MCP Server 的 贡献点
@@ -52,6 +52,10 @@ export interface IChatMessageStructure {
52
52
  * 用于 chat 面板展示
53
53
  */
54
54
  message: string;
55
+ /**
56
+ * 图片
57
+ */
58
+ images?: string[];
55
59
  /**
56
60
  * 实际调用的 prompt
57
61
  */
@@ -200,6 +204,7 @@ export interface IChatAgentRequest {
200
204
  requestId: string;
201
205
  command?: string;
202
206
  message: string;
207
+ images?: string[];
203
208
  regenerate?: boolean;
204
209
  }
205
210
 
@@ -237,6 +242,7 @@ export type IChatFollowup = IChatReplyFollowup | IChatResponseCommandFollowup;
237
242
 
238
243
  export interface IChatRequestMessage {
239
244
  prompt: string;
245
+ images?: string[];
240
246
  agentId: string;
241
247
  command?: string;
242
248
  }
@@ -1,3 +1,5 @@
1
+ import { DataContent } from 'ai';
2
+
1
3
  import { Event, URI } from '@opensumi/ide-core-common/lib/utils';
2
4
 
3
5
  export interface LLMContextService {
@@ -1,4 +1,13 @@
1
- import { CoreMessage, ToolExecutionOptions, jsonSchema, streamText, tool } from 'ai';
1
+ import {
2
+ CoreMessage,
3
+ CoreUserMessage,
4
+ ImagePart,
5
+ TextPart,
6
+ ToolExecutionOptions,
7
+ jsonSchema,
8
+ streamText,
9
+ tool,
10
+ } from 'ai';
2
11
 
3
12
  import { Autowired, Injectable } from '@opensumi/di';
4
13
  import { IAIBackServiceOption } from '@opensumi/ide-core-common';
@@ -48,6 +57,7 @@ export abstract class BaseLanguageModel {
48
57
  options.trimTexts,
49
58
  options.system,
50
59
  options.maxTokens,
60
+ options.images,
51
61
  cancellationToken,
52
62
  );
53
63
  }
@@ -77,6 +87,7 @@ export abstract class BaseLanguageModel {
77
87
  trimTexts?: [string, string],
78
88
  systemPrompt?: string,
79
89
  maxTokens?: number,
90
+ images?: string[],
80
91
  cancellationToken?: CancellationToken,
81
92
  ): Promise<any> {
82
93
  try {
@@ -89,7 +100,18 @@ export abstract class BaseLanguageModel {
89
100
  });
90
101
  }
91
102
 
92
- const messages: CoreMessage[] = [...history, { role: 'user', content: request }];
103
+ const messages: CoreMessage[] = [
104
+ ...history,
105
+ {
106
+ role: 'user',
107
+ content: images?.length
108
+ ? [
109
+ { type: 'text', text: request } as TextPart,
110
+ ...images.map((image) => ({ type: 'image', image: new URL(image) } as ImagePart)),
111
+ ]
112
+ : request,
113
+ },
114
+ ];
93
115
  const modelInfo = modelId ? this.getModelInfo(modelId) : undefined;
94
116
  const stream = streamText({
95
117
  model: this.getModelIdentifier(provider, modelId),
@@ -101,9 +123,9 @@ export abstract class BaseLanguageModel {
101
123
  maxTokens,
102
124
  temperature: modelInfo?.temperature || 0,
103
125
  topP: modelInfo?.topP || 0.8,
104
- topK: modelInfo?.topK || 1,
105
126
  system: systemPrompt,
106
127
  providerOptions,
128
+ ...(!images?.length && { topK: modelInfo?.topK || 1 }),
107
129
  });
108
130
 
109
131
  // 状态跟踪变量