@opensumi/ide-ai-native 3.8.3-next-1741945847.0 → 3.8.3-next-1741949132.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 (84) hide show
  1. package/lib/browser/chat/chat-manager.service.d.ts +1 -1
  2. package/lib/browser/chat/chat-manager.service.d.ts.map +1 -1
  3. package/lib/browser/chat/chat-manager.service.js +3 -2
  4. package/lib/browser/chat/chat-manager.service.js.map +1 -1
  5. package/lib/browser/chat/chat-model.d.ts.map +1 -1
  6. package/lib/browser/chat/chat-model.js +11 -2
  7. package/lib/browser/chat/chat-model.js.map +1 -1
  8. package/lib/browser/chat/chat-proxy.service.d.ts.map +1 -1
  9. package/lib/browser/chat/chat-proxy.service.js +1 -0
  10. package/lib/browser/chat/chat-proxy.service.js.map +1 -1
  11. package/lib/browser/chat/chat.feature.registry.d.ts +4 -1
  12. package/lib/browser/chat/chat.feature.registry.d.ts.map +1 -1
  13. package/lib/browser/chat/chat.feature.registry.js +6 -0
  14. package/lib/browser/chat/chat.feature.registry.js.map +1 -1
  15. package/lib/browser/chat/chat.internal.service.d.ts +1 -1
  16. package/lib/browser/chat/chat.internal.service.d.ts.map +1 -1
  17. package/lib/browser/chat/chat.internal.service.js +2 -2
  18. package/lib/browser/chat/chat.internal.service.js.map +1 -1
  19. package/lib/browser/chat/chat.view.d.ts.map +1 -1
  20. package/lib/browser/chat/chat.view.js +10 -7
  21. package/lib/browser/chat/chat.view.js.map +1 -1
  22. package/lib/browser/components/ChatEditor.d.ts +2 -1
  23. package/lib/browser/components/ChatEditor.d.ts.map +1 -1
  24. package/lib/browser/components/ChatEditor.js +5 -2
  25. package/lib/browser/components/ChatEditor.js.map +1 -1
  26. package/lib/browser/components/ChatInput.d.ts +1 -1
  27. package/lib/browser/components/ChatInput.d.ts.map +1 -1
  28. package/lib/browser/components/ChatInput.js +1 -1
  29. package/lib/browser/components/ChatInput.js.map +1 -1
  30. package/lib/browser/components/ChatMentionInput.d.ts +3 -1
  31. package/lib/browser/components/ChatMentionInput.d.ts.map +1 -1
  32. package/lib/browser/components/ChatMentionInput.js +51 -3
  33. package/lib/browser/components/ChatMentionInput.js.map +1 -1
  34. package/lib/browser/components/components.module.less +49 -0
  35. package/lib/browser/components/mention-input/mention-input.d.ts.map +1 -1
  36. package/lib/browser/components/mention-input/mention-input.js +74 -112
  37. package/lib/browser/components/mention-input/mention-input.js.map +1 -1
  38. package/lib/browser/components/mention-input/mention-input.module.less +1 -0
  39. package/lib/browser/components/mention-input/types.d.ts +3 -1
  40. package/lib/browser/components/mention-input/types.d.ts.map +1 -1
  41. package/lib/browser/components/mention-input/types.js.map +1 -1
  42. package/lib/browser/context/llm-context.service.d.ts.map +1 -1
  43. package/lib/browser/context/llm-context.service.js.map +1 -1
  44. package/lib/browser/mcp/config/components/mcp-config.view.d.ts.map +1 -1
  45. package/lib/browser/mcp/config/components/mcp-config.view.js +1 -15
  46. package/lib/browser/mcp/config/components/mcp-config.view.js.map +1 -1
  47. package/lib/browser/mcp/mcp-server-proxy.service.d.ts +0 -1
  48. package/lib/browser/mcp/mcp-server-proxy.service.d.ts.map +1 -1
  49. package/lib/browser/model/msg-history-manager.d.ts +1 -1
  50. package/lib/browser/model/msg-history-manager.d.ts.map +1 -1
  51. package/lib/browser/model/msg-history-manager.js.map +1 -1
  52. package/lib/browser/types.d.ts +7 -1
  53. package/lib/browser/types.d.ts.map +1 -1
  54. package/lib/browser/types.js.map +1 -1
  55. package/lib/common/index.d.ts +6 -1
  56. package/lib/common/index.d.ts.map +1 -1
  57. package/lib/common/index.js.map +1 -1
  58. package/lib/common/llm-context.d.ts.map +1 -1
  59. package/lib/common/llm-context.js.map +1 -1
  60. package/lib/node/base-language-model.d.ts +1 -1
  61. package/lib/node/base-language-model.d.ts.map +1 -1
  62. package/lib/node/base-language-model.js +15 -4
  63. package/lib/node/base-language-model.js.map +1 -1
  64. package/package.json +23 -23
  65. package/src/browser/chat/chat-manager.service.ts +3 -2
  66. package/src/browser/chat/chat-model.ts +10 -2
  67. package/src/browser/chat/chat-proxy.service.ts +1 -0
  68. package/src/browser/chat/chat.feature.registry.ts +10 -1
  69. package/src/browser/chat/chat.internal.service.ts +2 -2
  70. package/src/browser/chat/chat.view.tsx +18 -8
  71. package/src/browser/components/ChatEditor.tsx +8 -0
  72. package/src/browser/components/ChatInput.tsx +2 -2
  73. package/src/browser/components/ChatMentionInput.tsx +93 -6
  74. package/src/browser/components/components.module.less +49 -0
  75. package/src/browser/components/mention-input/mention-input.module.less +1 -0
  76. package/src/browser/components/mention-input/mention-input.tsx +81 -129
  77. package/src/browser/components/mention-input/types.ts +3 -1
  78. package/src/browser/context/llm-context.service.ts +2 -0
  79. package/src/browser/mcp/config/components/mcp-config.view.tsx +1 -15
  80. package/src/browser/model/msg-history-manager.ts +1 -1
  81. package/src/browser/types.ts +8 -1
  82. package/src/common/index.ts +7 -8
  83. package/src/common/llm-context.ts +2 -0
  84. package/src/node/base-language-model.ts +25 -3
@@ -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: (value: string, agentId?: string, command?: string, option?: { model: string; [key: string]: any }) => void;
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
+ iconClass: 'codicon codicon-file-media',
258
+ title: 'Upload Image',
259
+ onClick: () => {
260
+ const input = document.createElement('input');
261
+ input.type = 'file';
262
+ input.accept = 'image/*';
263
+ input.onchange = (e) => {
264
+ const file = (e.target as HTMLInputElement).files?.[0];
265
+ if (file) {
266
+ handleImageUpload(file);
267
+ }
268
+ };
269
+ input.click();
270
+ },
271
+ position: FooterButtonPosition.LEFT,
272
+ },
242
273
  ],
243
274
  showModelSelector: true,
244
275
  }),
@@ -254,13 +285,47 @@ export const ChatMentionInput = (props: IChatMentionInputProps) => {
254
285
  if (disabled) {
255
286
  return;
256
287
  }
257
- onSend(content, undefined, undefined, option);
288
+ onSend(
289
+ content,
290
+ images.map((image) => image.toString()),
291
+ undefined,
292
+ undefined,
293
+ option,
294
+ );
295
+ setImages(props.images || []);
296
+ },
297
+ [onSend, images, disabled],
298
+ );
299
+
300
+ const handleImageUpload = useCallback(
301
+ async (file: File) => {
302
+ const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif'];
303
+ if (!allowedTypes.includes(file.type)) {
304
+ messageService.error('Only JPG, PNG, WebP and GIF images are supported');
305
+ return;
306
+ }
307
+
308
+ const imageUploadProvider = chatFeatureRegistry.getImageUploadProvider();
309
+ if (!imageUploadProvider) {
310
+ messageService.error('No image upload provider found');
311
+ return;
312
+ }
313
+ const data = await imageUploadProvider.imageUpload(file);
314
+ setImages([...images, data]);
258
315
  },
259
- [onSend, editorService, disabled],
316
+ [images],
317
+ );
318
+
319
+ const handleDeleteImage = useCallback(
320
+ (index: number) => {
321
+ setImages(images.filter((_, i) => i !== index));
322
+ },
323
+ [images],
260
324
  );
261
325
 
262
326
  return (
263
327
  <div className={styles.chat_input_container}>
328
+ {images.length > 0 && <ImagePreviewer images={images} onDelete={handleDeleteImage} />}
264
329
  <MentionInput
265
330
  mentionItems={defaultMenuItems}
266
331
  onSend={handleSend}
@@ -270,7 +335,29 @@ export const ChatMentionInput = (props: IChatMentionInputProps) => {
270
335
  workspaceService={workspaceService}
271
336
  placeholder={localize('aiNative.chat.input.placeholder.default')}
272
337
  footerConfig={defaultMentionInputFooterOptions}
338
+ onImageUpload={handleImageUpload}
273
339
  />
274
340
  </div>
275
341
  );
276
342
  };
343
+
344
+ const ImagePreviewer = ({
345
+ images,
346
+ onDelete,
347
+ }: {
348
+ images: Array<DataContent | URL>;
349
+ onDelete: (index: number) => void;
350
+ }) => (
351
+ <div>
352
+ <div className={styles.thumbnail_container}>
353
+ {images.map((image, index) => (
354
+ <div key={index} className={styles.thumbnail}>
355
+ <Image src={image.toString()} />
356
+ <button onClick={() => onDelete(index)} className={styles.delete_button}>
357
+ <Icon iconClass='codicon codicon-close' />
358
+ </button>
359
+ </div>
360
+ ))}
361
+ </div>
362
+ </div>
363
+ );
@@ -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键且提及面板处于活动状态或内联搜索处于活动状态
@@ -318,74 +334,14 @@ export const MentionInput: React.FC<MentionInputProps> = ({
318
334
  inlineSearchStartPos: null,
319
335
  loading: false,
320
336
  });
321
- }
322
-
323
- // 处理上下方向键导航历史记录
324
- if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && !e.shiftKey && !e.ctrlKey && !e.metaKey && !e.altKey) {
325
- // 只有在非提及面板激活状态下才处理历史导航
326
- if (!mentionState.active && !mentionState.inlineSearchActive && editorRef.current && history.length > 0) {
327
- const currentContent = editorRef.current.innerHTML;
328
-
329
- // 检查是否应该触发历史导航
330
- const shouldTriggerHistory =
331
- // 当前内容为空
332
- !currentContent ||
333
- currentContent === '<br>' ||
334
- // 或者当前内容与历史记录中的某一项匹配(正在浏览历史)
335
- (isNavigatingHistory && historyIndex >= 0 && history[history.length - 1 - historyIndex] === currentContent);
336
-
337
- if (shouldTriggerHistory) {
338
- e.preventDefault();
339
-
340
- // 如果是第一次按上下键,保存当前输入
341
- if (!isNavigatingHistory) {
342
- setCurrentInput(currentContent);
343
- setIsNavigatingHistory(true);
344
- }
345
-
346
- // 计算新的历史索引
347
- let newIndex = historyIndex;
348
- if (e.key === 'ArrowUp') {
349
- // 向上导航到较早的历史记录
350
- newIndex = Math.min(history.length - 1, historyIndex + 1);
351
- } else {
352
- // 向下导航到较新的历史记录
353
- newIndex = Math.max(-1, historyIndex - 1);
354
- }
355
-
356
- setHistoryIndex(newIndex);
357
337
 
358
- // 更新编辑器内容
359
- if (newIndex === -1) {
360
- // 恢复到当前输入
361
- editorRef.current.innerHTML = currentInput;
362
- } else {
363
- // 显示历史记录
364
- editorRef.current.innerHTML = history[history.length - 1 - newIndex];
365
- }
366
-
367
- // 将光标移到末尾
368
- const range = document.createRange();
369
- range.selectNodeContents(editorRef.current);
370
- range.collapse(false);
371
- const selection = window.getSelection();
372
- if (selection) {
373
- selection.removeAllRanges();
374
- selection.addRange(range);
375
- }
376
-
377
- return;
378
- }
379
- }
380
- } else if (isNavigatingHistory && e.key !== 'ArrowUp' && e.key !== 'ArrowDown') {
381
- // 如果用户在浏览历史记录后开始输入其他内容,退出历史导航模式
382
- setIsNavigatingHistory(false);
383
- setHistoryIndex(-1);
338
+ // 不要阻止默认行为,让 @ 正常输入到编辑器中
384
339
  }
385
340
 
386
341
  // 添加对 Enter 键的处理,只有在按下 Shift+Enter 时才允许换行
387
342
  if (e.key === 'Enter') {
388
343
  // 检查是否是输入法的回车键
344
+ // isComposing 属性表示是否正在进行输入法组合输入
389
345
  if (e.nativeEvent.isComposing) {
390
346
  return; // 如果是输入法组合输入过程中的回车,不做任何处理
391
347
  }
@@ -399,6 +355,57 @@ export const MentionInput: React.FC<MentionInputProps> = ({
399
355
  }
400
356
  }
401
357
 
358
+ // 处理上下方向键导航历史记录
359
+ if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
360
+ // 只有在非提及面板激活状态下才处理历史导航
361
+ if (!mentionState.active && !mentionState.inlineSearchActive && editorRef.current) {
362
+ e.preventDefault();
363
+
364
+ // 如果是第一次按上下键,保存当前输入
365
+ if (!isNavigatingHistory) {
366
+ setCurrentInput(editorRef.current.innerHTML);
367
+ setIsNavigatingHistory(true);
368
+ }
369
+
370
+ // 计算新的历史索引
371
+ let newIndex = historyIndex;
372
+ if (e.key === 'ArrowUp') {
373
+ // 向上导航到较早的历史记录
374
+ newIndex = Math.min(history.length - 1, historyIndex + 1);
375
+ } else {
376
+ // 向下导航到较新的历史记录
377
+ newIndex = Math.max(-1, historyIndex - 1);
378
+ }
379
+
380
+ setHistoryIndex(newIndex);
381
+
382
+ // 更新编辑器内容
383
+ if (newIndex === -1) {
384
+ // 恢复到当前输入
385
+ editorRef.current.innerHTML = currentInput;
386
+ } else {
387
+ // 显示历史记录
388
+ editorRef.current.innerHTML = history[history.length - 1 - newIndex];
389
+ }
390
+
391
+ // 将光标移到末尾
392
+ const range = document.createRange();
393
+ range.selectNodeContents(editorRef.current);
394
+ range.collapse(false);
395
+ const selection = window.getSelection();
396
+ if (selection) {
397
+ selection.removeAllRanges();
398
+ selection.addRange(range);
399
+ }
400
+
401
+ return;
402
+ }
403
+ } else if (isNavigatingHistory && e.key !== 'ArrowUp' && e.key !== 'ArrowDown') {
404
+ // 如果用户在浏览历史记录后开始输入,退出历史导航模式
405
+ setIsNavigatingHistory(false);
406
+ setHistoryIndex(-1);
407
+ }
408
+
402
409
  // 如果提及面板未激活,不处理其他键盘事件
403
410
  if (!mentionState.active) {
404
411
  return;
@@ -413,6 +420,8 @@ export const MentionInput: React.FC<MentionInputProps> = ({
413
420
  filteredItems = filteredItems.filter((item) => item.text.toLowerCase().includes(searchText));
414
421
  }
415
422
 
423
+ // 二级菜单过滤已经在 getCurrentItems 中处理
424
+
416
425
  if (filteredItems.length === 0) {
417
426
  return;
418
427
  }
@@ -438,6 +447,14 @@ export const MentionInput: React.FC<MentionInputProps> = ({
438
447
  e.preventDefault();
439
448
  }
440
449
  }
450
+
451
+ // 处理 Backspace 键,检查是否需要清空编辑器
452
+ if (e.key === 'Backspace' && editorRef.current) {
453
+ const content = editorRef.current.innerHTML;
454
+ if (content === '<br>' || content === '<br/>') {
455
+ editorRef.current.innerHTML = '';
456
+ }
457
+ }
441
458
  };
442
459
 
443
460
  // 添加对输入法事件的处理
@@ -446,71 +463,6 @@ export const MentionInput: React.FC<MentionInputProps> = ({
446
463
  // 这里可以添加额外的逻辑,如果需要的话
447
464
  };
448
465
 
449
- // 添加粘贴事件处理
450
- const handlePaste = (e: React.ClipboardEvent<HTMLDivElement>) => {
451
- // 阻止默认粘贴行为
452
- e.preventDefault();
453
-
454
- // 获取剪贴板中的纯文本内容
455
- const text = e.clipboardData.getData('text/plain');
456
-
457
- // 处理文本,保留换行和缩进
458
- const processedText = text
459
- // 将制表符转换为4个空格
460
- .replace(/\t/g, ' ')
461
- // 将连续的换行符转换为单个换行
462
- .replace(/\n\s*\n/g, '\n\n')
463
- // 移除行尾空格
464
- .replace(/[ \t]+$/gm, '');
465
-
466
- const selection = window.getSelection();
467
- if (!selection || !selection.rangeCount) {
468
- return;
469
- }
470
-
471
- const range = selection.getRangeAt(0);
472
- range.deleteContents();
473
-
474
- // 将处理后的文本按行分割
475
- const lines = processedText.split('\n');
476
- const fragment = document.createDocumentFragment();
477
-
478
- lines.forEach((line, index) => {
479
- // 处理行首空格,将每个空格转换为 &nbsp;
480
- const processedLine = line.replace(/^[ ]+/g, (match) => {
481
- const span = document.createElement('span');
482
- span.innerHTML = '\u00A0'.repeat(match.length);
483
- return span.innerHTML;
484
- });
485
-
486
- // 创建一个临时容器来保持 HTML 内容
487
- const container = document.createElement('span');
488
- container.innerHTML = processedLine;
489
-
490
- // 将容器的内容添加到文档片段
491
- while (container.firstChild) {
492
- fragment.appendChild(container.firstChild);
493
- }
494
-
495
- // 如果不是最后一行,添加换行符
496
- if (index < lines.length - 1) {
497
- fragment.appendChild(document.createElement('br'));
498
- }
499
- });
500
-
501
- // 插入处理后的内容
502
- range.insertNode(fragment);
503
-
504
- // 将光标移动到插入内容的末尾
505
- range.setStartAfter(fragment);
506
- range.setEndAfter(fragment);
507
- selection.removeAllRanges();
508
- selection.addRange(range);
509
-
510
- // 触发 input 事件以更新状态
511
- handleInput();
512
- };
513
-
514
466
  // 初始化编辑器
515
467
  React.useEffect(() => {
516
468
  if (editorRef.current) {
@@ -931,7 +883,7 @@ export const MentionInput: React.FC<MentionInputProps> = ({
931
883
  title={button.title}
932
884
  >
933
885
  <EnhanceIcon
934
- className={cls(getIcon(button.icon), styles[`${button.id}_logo`])}
886
+ className={cls(button.icon ? getIcon(button.icon) : button.iconClass, styles[`${button.id}_logo`])}
935
887
  tabIndex={0}
936
888
  role='button'
937
889
  ariaLabel={button.title}
@@ -965,8 +917,8 @@ export const MentionInput: React.FC<MentionInputProps> = ({
965
917
  contentEditable={true}
966
918
  onInput={handleInput}
967
919
  onKeyDown={handleKeyDown}
968
- onCompositionEnd={handleCompositionEnd}
969
920
  onPaste={handlePaste}
921
+ onCompositionEnd={handleCompositionEnd}
970
922
  />
971
923
  </div>
972
924
  <div className={styles.footer}>
@@ -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';
@@ -26,21 +26,7 @@ export const MCPConfigView: React.FC = () => {
26
26
  const [editingServer, setEditingServer] = React.useState<MCPServerFormData | undefined>();
27
27
  const [loadingServer, setLoadingServer] = React.useState<string | undefined>();
28
28
  const loadServers = useCallback(async () => {
29
- const userServers = preferenceService.get<MCPServerDescription[]>(AINativeSettingSectionsId.MCPServers, []);
30
- const runningServers = await mcpServerProxyService.$getServers();
31
- const builtinServer = runningServers.find((server) => server.name === BUILTIN_MCP_SERVER_NAME);
32
- const allServers = userServers.map((server) => {
33
- const runningServer = runningServers.find((s) => s.name === server.name);
34
- return {
35
- ...server,
36
- name: server.name,
37
- isStarted: !!runningServer,
38
- tools: runningServer?.tools,
39
- };
40
- }) as MCPServer[];
41
- if (builtinServer) {
42
- allServers.unshift(builtinServer);
43
- }
29
+ const allServers = await mcpServerProxyService.$getServers();
44
30
  setServers(allServers);
45
31
  }, [mcpServerProxyService]);
46
32
 
@@ -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
 
@@ -129,6 +130,7 @@ export interface IChatSlashCommandHandler {
129
130
  }
130
131
 
131
132
  export interface IChatFeatureRegistry {
133
+ registerImageUploadProvider(provider: IImageUploadProvider): void;
132
134
  registerWelcome(content: IChatWelcomeMessageContent | React.ReactNode, sampleQuestions?: ISampleQuestions[]): void;
133
135
  registerSlashCommand(command: IChatSlashCommandItem, handler: IChatSlashCommandHandler): void;
134
136
  }
@@ -140,13 +142,14 @@ export type ChatWelcomeRender = (props: {
140
142
  export type ChatAIRoleRender = (props: { content: string }) => React.ReactElement | React.JSX.Element;
141
143
  export type ChatUserRoleRender = (props: {
142
144
  content: string;
145
+ images?: string[];
143
146
  agentId?: string;
144
147
  command?: string;
145
148
  }) => React.ReactElement | React.JSX.Element;
146
149
  export type ChatThinkingRender = (props: { thinkingText?: string }) => React.ReactElement | React.JSX.Element;
147
150
  export type ChatThinkingResultRender = (props: { thinkingResult?: string }) => React.ReactElement | React.JSX.Element;
148
151
  export type ChatInputRender = (props: {
149
- onSend: (value: string, agentId?: string, command?: string) => void;
152
+ onSend: (value: string, images?: string[], agentId?: string, command?: string) => void;
150
153
  onValueChange?: (value: string) => void;
151
154
  onExpand?: (value: boolean) => void;
152
155
  placeholder?: string;
@@ -289,6 +292,10 @@ export interface IProblemFixProviderRegistry {
289
292
  registerHoverFixProvider(handler: IHoverFixHandler): void;
290
293
  }
291
294
 
295
+ export interface IImageUploadProvider {
296
+ imageUpload(file: File): Promise<DataContent | URL>;
297
+ }
298
+
292
299
  export const AINativeCoreContribution = Symbol('AINativeCoreContribution');
293
300
 
294
301
  export interface AINativeCoreContribution {
@@ -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
  */
@@ -134,14 +138,7 @@ export interface ISumiMCPServerBackend {
134
138
  initBuiltinMCPServer(enabled: boolean): void;
135
139
  initExternalMCPServers(servers: MCPServerDescription[]): void;
136
140
  getAllMCPTools(): Promise<MCPTool[]>;
137
- getServers(): Promise<
138
- Array<{
139
- name: string;
140
- isStarted: boolean;
141
- type: string;
142
- tools: MCPTool[];
143
- }>
144
- >;
141
+ getServers(): Promise<Array<{ name: string; isStarted: boolean; tools: MCPTool[] }>>;
145
142
  startServer(serverName: string): Promise<void>;
146
143
  stopServer(serverName: string): Promise<void>;
147
144
  addOrUpdateServer(description: MCPServerDescription): void;
@@ -207,6 +204,7 @@ export interface IChatAgentRequest {
207
204
  requestId: string;
208
205
  command?: string;
209
206
  message: string;
207
+ images?: string[];
210
208
  regenerate?: boolean;
211
209
  }
212
210
 
@@ -244,6 +242,7 @@ export type IChatFollowup = IChatReplyFollowup | IChatResponseCommandFollowup;
244
242
 
245
243
  export interface IChatRequestMessage {
246
244
  prompt: string;
245
+ images?: string[];
247
246
  agentId: string;
248
247
  command?: string;
249
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 {