@opensumi/ide-ai-native 3.9.1-next-1748593694.0 → 3.9.1-next-1748943529.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 (118) hide show
  1. package/lib/browser/chat/chat-model.d.ts.map +1 -1
  2. package/lib/browser/chat/chat-model.js +3 -1
  3. package/lib/browser/chat/chat-model.js.map +1 -1
  4. package/lib/browser/chat/chat-proxy.service.d.ts +0 -2
  5. package/lib/browser/chat/chat-proxy.service.d.ts.map +1 -1
  6. package/lib/browser/chat/chat-proxy.service.js +50 -57
  7. package/lib/browser/chat/chat-proxy.service.js.map +1 -1
  8. package/lib/browser/chat/chat.view.d.ts.map +1 -1
  9. package/lib/browser/chat/chat.view.js +11 -7
  10. package/lib/browser/chat/chat.view.js.map +1 -1
  11. package/lib/browser/components/ChatMentionInput.d.ts.map +1 -1
  12. package/lib/browser/components/ChatMentionInput.js +123 -27
  13. package/lib/browser/components/ChatMentionInput.js.map +1 -1
  14. package/lib/browser/components/ChatToolRender.module.less +1 -0
  15. package/lib/browser/components/components.module.less +7 -0
  16. package/lib/browser/components/mention-input/mention-input.d.ts.map +1 -1
  17. package/lib/browser/components/mention-input/mention-input.js +155 -13
  18. package/lib/browser/components/mention-input/mention-input.js.map +1 -1
  19. package/lib/browser/components/mention-input/mention-input.module.less +165 -0
  20. package/lib/browser/components/mention-input/types.d.ts +15 -1
  21. package/lib/browser/components/mention-input/types.d.ts.map +1 -1
  22. package/lib/browser/components/mention-input/types.js +1 -0
  23. package/lib/browser/components/mention-input/types.js.map +1 -1
  24. package/lib/browser/context/llm-context.service.d.ts +21 -2
  25. package/lib/browser/context/llm-context.service.d.ts.map +1 -1
  26. package/lib/browser/context/llm-context.service.js +162 -20
  27. package/lib/browser/context/llm-context.service.js.map +1 -1
  28. package/lib/browser/contrib/intelligent-completions/decoration/additions-deletions.decoration.d.ts.map +1 -1
  29. package/lib/browser/contrib/intelligent-completions/decoration/additions-deletions.decoration.js.map +1 -1
  30. package/lib/browser/contrib/intelligent-completions/diff-computer.js +1 -1
  31. package/lib/browser/contrib/intelligent-completions/diff-computer.js.map +1 -1
  32. package/lib/browser/contrib/terminal/terminal.feature.registry.js.map +1 -1
  33. package/lib/browser/index.d.ts.map +1 -1
  34. package/lib/browser/index.js +7 -0
  35. package/lib/browser/index.js.map +1 -1
  36. package/lib/browser/mcp/base-apply.service.d.ts +2 -2
  37. package/lib/browser/mcp/base-apply.service.d.ts.map +1 -1
  38. package/lib/browser/mcp/base-apply.service.js.map +1 -1
  39. package/lib/browser/mcp/tools/getDiagnosticsByPath.js.map +1 -1
  40. package/lib/browser/mcp/tools/getOpenEditorFileDiagnostics.js.map +1 -1
  41. package/lib/browser/mcp/tools/handlers/ListDir.js.map +1 -1
  42. package/lib/browser/mcp/tools/runTerminalCmd.js.map +1 -1
  43. package/lib/browser/preferences/schema.d.ts.map +1 -1
  44. package/lib/browser/preferences/schema.js +5 -0
  45. package/lib/browser/preferences/schema.js.map +1 -1
  46. package/lib/browser/rules/rules.contribution.d.ts +29 -0
  47. package/lib/browser/rules/rules.contribution.d.ts.map +1 -0
  48. package/lib/browser/rules/rules.contribution.js +94 -0
  49. package/lib/browser/rules/rules.contribution.js.map +1 -0
  50. package/lib/browser/rules/rules.module.less +174 -0
  51. package/lib/browser/rules/rules.service.d.ts +25 -0
  52. package/lib/browser/rules/rules.service.d.ts.map +1 -0
  53. package/lib/browser/rules/rules.service.js +180 -0
  54. package/lib/browser/rules/rules.service.js.map +1 -0
  55. package/lib/browser/rules/rules.view.d.ts +3 -0
  56. package/lib/browser/rules/rules.view.d.ts.map +1 -0
  57. package/lib/browser/rules/rules.view.js +76 -0
  58. package/lib/browser/rules/rules.view.js.map +1 -0
  59. package/lib/browser/widget/inline-stream-diff/live-preview.component.d.ts.map +1 -1
  60. package/lib/browser/widget/inline-stream-diff/live-preview.component.js.map +1 -1
  61. package/lib/common/MDC_PARSER_README.md +171 -0
  62. package/lib/common/index.d.ts +2 -0
  63. package/lib/common/index.d.ts.map +1 -1
  64. package/lib/common/index.js +2 -0
  65. package/lib/common/index.js.map +1 -1
  66. package/lib/common/llm-context.d.ts +19 -0
  67. package/lib/common/llm-context.d.ts.map +1 -1
  68. package/lib/common/llm-context.js.map +1 -1
  69. package/lib/common/mdc-parser.d.ts +60 -0
  70. package/lib/common/mdc-parser.d.ts.map +1 -0
  71. package/lib/common/mdc-parser.js +246 -0
  72. package/lib/common/mdc-parser.js.map +1 -0
  73. package/lib/common/prompts/context-prompt-provider.d.ts +0 -2
  74. package/lib/common/prompts/context-prompt-provider.d.ts.map +1 -1
  75. package/lib/common/prompts/context-prompt-provider.js +35 -29
  76. package/lib/common/prompts/context-prompt-provider.js.map +1 -1
  77. package/lib/common/prompts/system-prompt.d.ts +2 -0
  78. package/lib/common/prompts/system-prompt.d.ts.map +1 -0
  79. package/lib/common/prompts/system-prompt.js +5 -0
  80. package/lib/common/prompts/system-prompt.js.map +1 -0
  81. package/lib/common/types.d.ts +7 -0
  82. package/lib/common/types.d.ts.map +1 -1
  83. package/lib/node/base-language-model.d.ts.map +1 -1
  84. package/lib/node/base-language-model.js.map +1 -1
  85. package/package.json +25 -24
  86. package/src/browser/chat/chat-model.ts +3 -1
  87. package/src/browser/chat/chat-proxy.service.ts +68 -81
  88. package/src/browser/chat/chat.view.tsx +19 -7
  89. package/src/browser/components/ChatMentionInput.tsx +143 -31
  90. package/src/browser/components/ChatToolRender.module.less +1 -0
  91. package/src/browser/components/components.module.less +7 -0
  92. package/src/browser/components/mention-input/mention-input.module.less +165 -0
  93. package/src/browser/components/mention-input/mention-input.tsx +244 -29
  94. package/src/browser/components/mention-input/types.ts +15 -0
  95. package/src/browser/context/llm-context.service.ts +182 -21
  96. package/src/browser/contrib/intelligent-completions/decoration/additions-deletions.decoration.ts +1 -1
  97. package/src/browser/contrib/intelligent-completions/diff-computer.ts +1 -1
  98. package/src/browser/contrib/terminal/terminal.feature.registry.ts +1 -1
  99. package/src/browser/index.ts +8 -0
  100. package/src/browser/mcp/base-apply.service.ts +0 -1
  101. package/src/browser/mcp/tools/getDiagnosticsByPath.ts +1 -1
  102. package/src/browser/mcp/tools/getOpenEditorFileDiagnostics.ts +1 -1
  103. package/src/browser/mcp/tools/handlers/ListDir.ts +1 -1
  104. package/src/browser/mcp/tools/runTerminalCmd.ts +1 -1
  105. package/src/browser/preferences/schema.ts +5 -0
  106. package/src/browser/rules/rules.contribution.ts +105 -0
  107. package/src/browser/rules/rules.module.less +174 -0
  108. package/src/browser/rules/rules.service.ts +189 -0
  109. package/src/browser/rules/rules.view.tsx +127 -0
  110. package/src/browser/widget/inline-stream-diff/live-preview.component.tsx +0 -1
  111. package/src/common/MDC_PARSER_README.md +171 -0
  112. package/src/common/index.ts +3 -0
  113. package/src/common/llm-context.ts +23 -0
  114. package/src/common/mdc-parser.ts +295 -0
  115. package/src/common/prompts/context-prompt-provider.ts +55 -40
  116. package/src/common/prompts/system-prompt.ts +2 -0
  117. package/src/common/types.ts +8 -0
  118. package/src/node/base-language-model.ts +0 -1
@@ -29,7 +29,9 @@
29
29
  word-break: break-word;
30
30
 
31
31
  .mention_tag {
32
+ height: 20px;
32
33
  margin: 0 2px;
34
+ margin-top: -3px;
33
35
  vertical-align: middle;
34
36
  }
35
37
  }
@@ -95,11 +97,13 @@
95
97
  .stop_logo {
96
98
  background-color: var(--badge-background);
97
99
  color: var(--badge-foreground);
100
+
98
101
  .stop_logo_icon {
99
102
  line-height: 16px;
100
103
  color: var(--kt-dangerButton-background);
101
104
  }
102
105
  }
106
+
103
107
  .send_logo {
104
108
  &:hover {
105
109
  background-color: var(--kt-primaryButton-background);
@@ -140,31 +144,38 @@
140
144
  display: flex;
141
145
  justify-content: center;
142
146
  }
147
+
143
148
  .context_container {
144
149
  display: flex;
145
150
  align-items: center;
146
151
  justify-content: center;
147
152
  cursor: pointer;
153
+
148
154
  .context_icon {
149
155
  flex-grow: 0;
150
156
  flex-shrink: 0;
157
+
151
158
  :global(.kt-icon) {
152
159
  font-size: 12px;
153
160
  }
161
+
154
162
  :global(.kticon-close) {
155
163
  display: none;
156
164
  }
157
165
  }
166
+
158
167
  &:hover {
159
168
  .context_icon {
160
169
  :global(.kticon-close) {
161
170
  display: block;
162
171
  }
172
+
163
173
  :global(.kticon-out-link) {
164
174
  display: none;
165
175
  }
166
176
  }
167
177
  }
178
+
168
179
  .context_description {
169
180
  flex: 1;
170
181
  margin-left: 3px;
@@ -254,6 +265,7 @@
254
265
  display: inline;
255
266
  flex: 1;
256
267
  direction: rtl;
268
+ unicode-bidi: plaintext;
257
269
  text-overflow: ellipsis;
258
270
  overflow: hidden;
259
271
  white-space: nowrap;
@@ -341,6 +353,10 @@
341
353
  justify-content: center;
342
354
  margin-right: 3px;
343
355
  font-size: 12px;
356
+ &::before {
357
+ font-size: 12px;
358
+ background-size: 12px !important;
359
+ }
344
360
  }
345
361
 
346
362
  .empty_state {
@@ -404,3 +420,152 @@
404
420
  color: #666;
405
421
  font-style: italic;
406
422
  }
423
+
424
+ .context_preview_container {
425
+ background-color: var(--kt-editorWidget-background);
426
+ border: 1px solid var(--kt-editorWidget-border);
427
+ margin: 0 16px;
428
+ animation: slideIn 0.3s ease-out;
429
+ display: flex;
430
+ flex-wrap: wrap;
431
+ gap: 4px;
432
+ align-items: flex-start;
433
+ margin-bottom: 3px;
434
+ width: calc(100% - 32px);
435
+ }
436
+
437
+ .context_preview_title {
438
+ cursor: pointer;
439
+ display: flex;
440
+ align-items: center;
441
+ justify-content: center;
442
+ padding: 2px 3px;
443
+ height: 18px;
444
+ width: auto;
445
+ box-sizing: border-box;
446
+ border-radius: 4px;
447
+ border: 1px solid color-mix(in srgb, var(--editor-foreground) 10%, transparent);
448
+ outline: none;
449
+ flex-shrink: 0;
450
+ color: var(--descriptionForeground);
451
+ font-size: 11px;
452
+
453
+ &::before {
454
+ content: '@';
455
+ margin-right: 4px;
456
+ }
457
+ &.has_context {
458
+ width: 18px;
459
+ &::before {
460
+ content: '@';
461
+ margin-right: 0;
462
+ }
463
+ }
464
+
465
+ &:hover {
466
+ color: var(--badge-foreground);
467
+ }
468
+ }
469
+
470
+ .context_preview_item {
471
+ cursor: pointer;
472
+ display: flex;
473
+ align-items: center;
474
+ justify-content: flex-start;
475
+ padding: 2px 4px;
476
+ height: 18px;
477
+ width: auto;
478
+ min-width: fit-content;
479
+ max-width: 300px;
480
+ box-sizing: border-box;
481
+ border-radius: 4px;
482
+ border: 1px solid color-mix(in srgb, var(--badge-background) 30%, transparent);
483
+ outline: none;
484
+ flex: 0 1 auto;
485
+ margin-left: 3px;
486
+
487
+ .icon {
488
+ width: 12px !important;
489
+ height: 12px !important;
490
+ line-height: 12px !important;
491
+ margin-right: 2px;
492
+ flex-shrink: 0;
493
+ }
494
+
495
+ .close_icon {
496
+ margin-right: 2px;
497
+ display: none !important;
498
+ width: 12px !important;
499
+ height: 12px !important;
500
+ line-height: 12px !important;
501
+ flex-shrink: 0;
502
+ opacity: 0.8;
503
+
504
+ &:hover {
505
+ opacity: 1;
506
+ }
507
+ }
508
+
509
+ &:hover {
510
+ opacity: 1;
511
+ .icon {
512
+ display: none;
513
+ }
514
+ .close_icon {
515
+ display: inline-block !important;
516
+ font-size: 12px !important;
517
+ }
518
+ }
519
+ }
520
+
521
+ .context_preview_item_icon {
522
+ display: flex;
523
+ align-items: center;
524
+ justify-content: center;
525
+ flex-shrink: 0;
526
+ opacity: 0.8;
527
+ &::before {
528
+ background-position: 2px !important;
529
+ font-size: 12px;
530
+ background-size: 11px !important;
531
+ }
532
+ }
533
+
534
+ .context_preview_item_text {
535
+ flex: 1;
536
+ overflow: hidden;
537
+ text-overflow: ellipsis;
538
+ color: var(--descriptionForeground);
539
+ white-space: nowrap;
540
+ font-weight: 400;
541
+ line-height: 1.2;
542
+ font-size: 11px;
543
+ max-width: 200px;
544
+ }
545
+
546
+ .context_preview_item_remove {
547
+ cursor: pointer;
548
+ opacity: 0.5;
549
+ font-size: 12px;
550
+ margin-left: 4px;
551
+ padding: 2px;
552
+ border-radius: 50%;
553
+ display: flex;
554
+ align-items: center;
555
+ justify-content: center;
556
+ flex-shrink: 0;
557
+ transition: all 0.2s ease;
558
+ width: 16px;
559
+ height: 16px;
560
+
561
+ &:hover {
562
+ opacity: 1;
563
+ background-color: var(--kt-dangerButton-background);
564
+ color: var(--kt-dangerButton-foreground);
565
+ transform: scale(1.1);
566
+ }
567
+
568
+ &:active {
569
+ transform: scale(0.9);
570
+ }
571
+ }
@@ -1,12 +1,13 @@
1
1
  import cls from 'classnames';
2
2
  import * as React from 'react';
3
3
 
4
- import { formatLocalize, getSymbolIcon, localize } from '@opensumi/ide-core-browser';
4
+ import { getSymbolIcon, localize } from '@opensumi/ide-core-browser';
5
5
  import { Icon, Popover, PopoverPosition, Select, getIcon } from '@opensumi/ide-core-browser/lib/components';
6
6
  import { EnhanceIcon } from '@opensumi/ide-core-browser/lib/components/ai-native';
7
7
  import { URI } from '@opensumi/ide-utils';
8
8
 
9
9
  import { FileContext } from '../../../common/llm-context';
10
+ import { ProjectRule } from '../../../common/types';
10
11
 
11
12
  import styles from './mention-input.module.less';
12
13
  import { MentionPanel } from './mention-panel';
@@ -67,11 +68,22 @@ export const MentionInput: React.FC<MentionInputProps> = ({
67
68
  const [attachedFiles, setAttachedFiles] = React.useState<{
68
69
  files: FileContext[];
69
70
  folders: FileContext[];
71
+ rules: ProjectRule[];
70
72
  }>({
71
73
  files: [],
72
74
  folders: [],
75
+ rules: [],
73
76
  });
74
77
 
78
+ // 添加用于跟踪 mention_tag 的状态
79
+ const prevMentionTagsRef = React.useRef<
80
+ Array<{
81
+ id: string;
82
+ type: string;
83
+ contextId: string;
84
+ }>
85
+ >([]);
86
+
75
87
  const getCurrentItems = (): MentionItem[] => {
76
88
  if (mentionState.level === 0) {
77
89
  return mentionItems;
@@ -177,14 +189,14 @@ export const MentionInput: React.FC<MentionInputProps> = ({
177
189
  }, [debouncedSecondLevelFilter, mentionState.level, mentionState.parentType]);
178
190
 
179
191
  React.useEffect(() => {
180
- const disposable = contextService?.onDidContextFilesChangeEvent(({ attached, attachedFolders }) => {
181
- setAttachedFiles({ files: attached, folders: attachedFolders });
192
+ const disposable = contextService?.onDidContextFilesChangeEvent(({ attached, attachedFolders, attachedRules }) => {
193
+ setAttachedFiles({ files: attached, folders: attachedFolders, rules: attachedRules });
182
194
  });
183
195
 
184
196
  return () => {
185
197
  disposable?.dispose();
186
198
  };
187
- }, [contextService]);
199
+ }, []);
188
200
 
189
201
  // 获取光标位置
190
202
  const getCursorPosition = (element: HTMLElement): number => {
@@ -207,6 +219,45 @@ export const MentionInput: React.FC<MentionInputProps> = ({
207
219
  setHistoryIndex(-1);
208
220
  }
209
221
 
222
+ // 检测 mention_tag 的删除
223
+ if (editorRef.current) {
224
+ const currentMentionTags = Array.from(editorRef.current.querySelectorAll(`.${styles.mention_tag}`)).map(
225
+ (tag) => ({
226
+ id: tag.getAttribute('data-id') || '',
227
+ type: tag.getAttribute('data-type') || '',
228
+ contextId: tag.getAttribute('data-context-id') || '',
229
+ }),
230
+ );
231
+
232
+ // 找出被删除的 mention_tag
233
+ const deletedTags = prevMentionTagsRef.current.filter(
234
+ (prevTag) =>
235
+ !currentMentionTags.some(
236
+ (currentTag) =>
237
+ currentTag.id === prevTag.id &&
238
+ currentTag.type === prevTag.type &&
239
+ currentTag.contextId === prevTag.contextId,
240
+ ),
241
+ );
242
+
243
+ // 清理被删除的 mention_tag 对应的 context
244
+ deletedTags.forEach((deletedTag) => {
245
+ if (deletedTag.contextId) {
246
+ const uri = new URI(deletedTag.contextId);
247
+ if (deletedTag.type === MentionType.FILE) {
248
+ removeContext(MentionType.FILE, uri);
249
+ } else if (deletedTag.type === MentionType.FOLDER) {
250
+ removeContext(MentionType.FOLDER, uri);
251
+ } else if (deletedTag.type === MentionType.RULE) {
252
+ removeContext(MentionType.RULE, uri);
253
+ }
254
+ }
255
+ });
256
+
257
+ // 更新 mention_tag 状态
258
+ prevMentionTagsRef.current = currentMentionTags;
259
+ }
260
+
210
261
  const selection = window.getSelection();
211
262
  if (!selection || !selection.rangeCount || !editorRef.current) {
212
263
  return;
@@ -490,7 +541,7 @@ export const MentionInput: React.FC<MentionInputProps> = ({
490
541
  const imageFiles: File[] = [];
491
542
  // eslint-disable-next-line @typescript-eslint/prefer-for-of
492
543
  for (let i = 0; i < items.length; i++) {
493
- if (items[i].kind === 'file' && items[i].type.startsWith('image/')) {
544
+ if (items[i].kind === MentionType.FILE && items[i].type.startsWith('image/')) {
494
545
  const file = items[i].getAsFile();
495
546
  if (file) {
496
547
  imageFiles.push(file);
@@ -572,6 +623,16 @@ export const MentionInput: React.FC<MentionInputProps> = ({
572
623
  if (placeholder && !editorRef.current.textContent) {
573
624
  editorRef.current.setAttribute('data-placeholder', placeholder);
574
625
  }
626
+
627
+ // 初始化 mention_tag 状态
628
+ const initialMentionTags = Array.from(editorRef.current.querySelectorAll(`.${styles.mention_tag}`)).map(
629
+ (tag) => ({
630
+ id: tag.getAttribute('data-id') || '',
631
+ type: tag.getAttribute('data-type') || '',
632
+ contextId: tag.getAttribute('data-context-id') || '',
633
+ }),
634
+ );
635
+ prevMentionTagsRef.current = initialMentionTags;
575
636
  }
576
637
  }, [placeholder]);
577
638
 
@@ -723,6 +784,11 @@ export const MentionInput: React.FC<MentionInputProps> = ({
723
784
  true,
724
785
  );
725
786
  }
787
+ } else if (item.type === MentionType.RULE) {
788
+ const iconSpan = document.createElement('span');
789
+ iconSpan.className = cls(styles.mention_icon, getIcon('rules'));
790
+ mentionTag.appendChild(iconSpan);
791
+ contextService?.addRuleToContext(new URI(item.contextId), true);
726
792
  }
727
793
  const workspace = workspaceService?.workspace;
728
794
  let relativePath = item.text;
@@ -822,12 +888,12 @@ export const MentionInput: React.FC<MentionInputProps> = ({
822
888
  mentionTag.contentEditable = 'false';
823
889
 
824
890
  // 为 file 和 folder 类型添加图标
825
- if (item.type === 'file' || item.type === 'folder') {
891
+ if (item.type === MentionType.FILE || item.type === 'folder') {
826
892
  // 创建图标容器
827
893
  const iconSpan = document.createElement('span');
828
894
  iconSpan.className = cls(
829
895
  styles.mention_icon,
830
- item.type === 'file' ? labelService?.getIcon(new URI(item.text)) : getIcon('folder'),
896
+ item.type === MentionType.FILE ? labelService?.getIcon(new URI(item.text)) : getIcon('folder'),
831
897
  );
832
898
  mentionTag.appendChild(iconSpan);
833
899
  }
@@ -983,6 +1049,65 @@ export const MentionInput: React.FC<MentionInputProps> = ({
983
1049
  contextService?.cleanFileContext();
984
1050
  }, [contextService]);
985
1051
 
1052
+ const handleTitleClick = React.useCallback(() => {
1053
+ if (!editorRef.current) {
1054
+ return;
1055
+ }
1056
+
1057
+ // 聚焦输入框
1058
+ editorRef.current.focus();
1059
+
1060
+ // 获取当前光标位置
1061
+ const selection = window.getSelection();
1062
+ if (!selection) {
1063
+ return;
1064
+ }
1065
+
1066
+ // 在当前位置插入 @ 符号
1067
+ const range = document.createRange();
1068
+
1069
+ // 如果编辑器为空,直接插入
1070
+ if (!editorRef.current.textContent || editorRef.current.textContent.trim() === '') {
1071
+ editorRef.current.innerHTML = '@';
1072
+ range.setStart(editorRef.current.firstChild || editorRef.current, 1);
1073
+ range.setEnd(editorRef.current.firstChild || editorRef.current, 1);
1074
+ } else {
1075
+ // 当输入框有内容时,总是在末尾插入 @ 符号
1076
+ const textNode = document.createTextNode(' @');
1077
+
1078
+ // 移动到编辑器末尾
1079
+ range.selectNodeContents(editorRef.current);
1080
+ range.collapse(false); // 移动到末尾
1081
+
1082
+ // 在末尾插入空格和 @ 符号
1083
+ range.insertNode(textNode);
1084
+ range.setStartAfter(textNode);
1085
+ range.setEndAfter(textNode);
1086
+ }
1087
+
1088
+ // 设置新的光标位置
1089
+ selection.removeAllRanges();
1090
+ selection.addRange(range);
1091
+
1092
+ // 获取插入后的光标位置
1093
+ const newCursorPos = getCursorPosition(editorRef.current);
1094
+
1095
+ // 激活菜单状态
1096
+ setMentionState({
1097
+ active: true,
1098
+ startPos: newCursorPos,
1099
+ filter: '@',
1100
+ position: { top: 0, left: 0 },
1101
+ activeIndex: 0,
1102
+ level: 0,
1103
+ parentType: null,
1104
+ secondLevelFilter: '',
1105
+ inlineSearchActive: false,
1106
+ inlineSearchStartPos: null,
1107
+ loading: false,
1108
+ });
1109
+ }, []);
1110
+
986
1111
  const handleStop = React.useCallback(() => {
987
1112
  if (onStop) {
988
1113
  onStop();
@@ -1015,7 +1140,7 @@ export const MentionInput: React.FC<MentionInputProps> = ({
1015
1140
  );
1016
1141
 
1017
1142
  const hasContext = React.useMemo(
1018
- () => attachedFiles.files.length > 0 || attachedFiles.folders.length > 0,
1143
+ () => attachedFiles.files.length > 0 || attachedFiles.folders.length > 0 || attachedFiles.rules.length > 0,
1019
1144
  [attachedFiles],
1020
1145
  );
1021
1146
 
@@ -1037,8 +1162,119 @@ export const MentionInput: React.FC<MentionInputProps> = ({
1037
1162
  [footerConfig.disableModelSelector],
1038
1163
  );
1039
1164
 
1165
+ const removeContext = React.useCallback(
1166
+ (type: MentionType, uri: URI) => {
1167
+ if (type === MentionType.FILE) {
1168
+ contextService?.removeFileFromContext(uri, true);
1169
+ } else if (type === MentionType.FOLDER) {
1170
+ contextService?.removeFolderFromContext(uri);
1171
+ } else if (type === MentionType.RULE) {
1172
+ contextService?.removeRuleFromContext(uri);
1173
+ }
1174
+ },
1175
+ [contextService],
1176
+ );
1177
+
1178
+ const renderContextPreview = React.useCallback(
1179
+ () => (
1180
+ <div className={styles.context_preview_container}>
1181
+ <span
1182
+ className={cls(styles.context_preview_title, hasContext && styles.has_context)}
1183
+ onClick={handleTitleClick}
1184
+ >
1185
+ {!hasContext ? localize('aiNative.chat.context.title') : ''}
1186
+ </span>
1187
+ {attachedFiles.files.map((file, index) => (
1188
+ <div
1189
+ key={`file-${index}`}
1190
+ className={styles.context_preview_item}
1191
+ data-type={MentionType.FILE}
1192
+ onClick={() => contextService?.removeFileFromContext(file.uri, true)}
1193
+ >
1194
+ <Icon
1195
+ iconClass={cls(
1196
+ labelService?.getIcon(file.uri) || MentionType.FILE,
1197
+ styles.context_preview_item_icon,
1198
+ styles.icon,
1199
+ )}
1200
+ />
1201
+ <Icon
1202
+ iconClass={cls(styles.close_icon, getIcon('close'))}
1203
+ onClick={() => removeContext(MentionType.FILE, file.uri)}
1204
+ />
1205
+ <span className={styles.context_preview_item_text}>
1206
+ {workspaceService?.workspace
1207
+ ? file.uri.toString().replace(workspaceService.workspace.uri.toString(), '').slice(1)
1208
+ : file.uri.toString()}
1209
+ </span>
1210
+ </div>
1211
+ ))}
1212
+
1213
+ {attachedFiles.folders.map((folder, index) => (
1214
+ <div
1215
+ key={`folder-${index}`}
1216
+ className={styles.context_preview_item}
1217
+ data-type='folder'
1218
+ onClick={() => contextService?.removeFileFromContext(folder.uri, true)}
1219
+ >
1220
+ <Icon iconClass={cls(getIcon('folder'), styles.context_preview_item_icon, styles.icon)} />
1221
+ <Icon
1222
+ iconClass={cls(styles.close_icon, getIcon('close'))}
1223
+ onClick={() => removeContext(MentionType.FOLDER, folder.uri)}
1224
+ />
1225
+ <span className={styles.context_preview_item_text}>
1226
+ {workspaceService?.workspace
1227
+ ? folder.uri.toString().replace(workspaceService.workspace.uri.toString(), '').slice(1)
1228
+ : folder.uri.toString()}
1229
+ </span>
1230
+ </div>
1231
+ ))}
1232
+
1233
+ {attachedFiles.rules.map((rule, index) => (
1234
+ <div
1235
+ key={`rule-${index}`}
1236
+ className={styles.context_preview_item}
1237
+ data-type='rule'
1238
+ onClick={() => {
1239
+ // 由于没有专门的删除规则方法,我们重新构建规则列表
1240
+ contextService?.cleanFileContext();
1241
+ // 重新添加除了当前要删除的规则之外的所有上下文
1242
+ attachedFiles.files.forEach((file) => contextService?.addFileToContext(file.uri, file.selection, true));
1243
+ attachedFiles.folders.forEach((folder) => contextService?.addFolderToContext(folder.uri, true));
1244
+ attachedFiles.rules.forEach((r, i) => {
1245
+ if (i !== index) {
1246
+ contextService?.addRuleToContext(new URI(r.path), true);
1247
+ }
1248
+ });
1249
+ }}
1250
+ >
1251
+ <Icon iconClass={cls(getIcon('rules'), styles.context_preview_item_icon, styles.icon)} />
1252
+ <Icon
1253
+ iconClass={cls(styles.close_icon, getIcon('close'))}
1254
+ onClick={() => removeContext(MentionType.RULE, new URI(rule.path))}
1255
+ />
1256
+ <span className={styles.context_preview_item_text}>
1257
+ {rule.path.split('/').pop()?.replace('.mdc', '') || 'Unknown Rule'}
1258
+ </span>
1259
+ </div>
1260
+ ))}
1261
+ </div>
1262
+ ),
1263
+ [
1264
+ handleClearContext,
1265
+ hasContext,
1266
+ attachedFiles,
1267
+ workspaceService,
1268
+ labelService,
1269
+ contextService,
1270
+ handleTitleClick,
1271
+ removeContext,
1272
+ ],
1273
+ );
1274
+
1040
1275
  return (
1041
1276
  <div className={styles.input_container}>
1277
+ {renderContextPreview()}
1042
1278
  {mentionState.active && (
1043
1279
  <div className={styles.mention_panel_container}>
1044
1280
  <MentionPanel
@@ -1081,27 +1317,6 @@ export const MentionInput: React.FC<MentionInputProps> = ({
1081
1317
  </div>
1082
1318
  <div className={styles.right_control}>
1083
1319
  {renderButtons(FooterButtonPosition.RIGHT)}
1084
- {hasContext && (
1085
- <Popover
1086
- overlayClassName={styles.popover_icon}
1087
- id={'ai-chat-clear-context'}
1088
- position={PopoverPosition.top}
1089
- content={localize('aiNative.chat.context.clear')}
1090
- >
1091
- <div className={styles.context_container} onClick={handleClearContext}>
1092
- <div className={styles.context_icon}>
1093
- <Icon icon='out-link' />
1094
- <Icon icon='close' />
1095
- </div>
1096
- <div className={styles.context_description}>
1097
- {formatLocalize(
1098
- 'aiNative.chat.context.description',
1099
- attachedFiles.files.length + attachedFiles.folders.length,
1100
- )}
1101
- </div>
1102
- </div>
1103
- </Popover>
1104
- )}
1105
1320
  <Popover
1106
1321
  overlayClassName={styles.popover_icon}
1107
1322
  id={'ai-chat-send'}
@@ -48,6 +48,19 @@ interface ModelOption {
48
48
  value: string;
49
49
  }
50
50
 
51
+ export interface ExtendedModelOption {
52
+ label: string;
53
+ value: string;
54
+ icon?: string;
55
+ iconClass?: string;
56
+ tags?: string[];
57
+ features?: string[];
58
+ description?: string;
59
+ disabled?: boolean;
60
+ badge?: string;
61
+ badgeColor?: string;
62
+ }
63
+
51
64
  export enum FooterButtonPosition {
52
65
  LEFT = 'left',
53
66
  RIGHT = 'right',
@@ -57,6 +70,7 @@ export enum MentionType {
57
70
  FILE = 'file',
58
71
  FOLDER = 'folder',
59
72
  CODE = 'code',
73
+ RULE = 'rule',
60
74
  }
61
75
 
62
76
  interface FooterButton {
@@ -70,6 +84,7 @@ interface FooterButton {
70
84
 
71
85
  export interface FooterConfig {
72
86
  modelOptions?: ModelOption[];
87
+ extendedModelOptions?: ExtendedModelOption[];
73
88
  defaultModel?: string;
74
89
  buttons?: FooterButton[];
75
90
  showModelSelector?: boolean;