@opensumi/ide-ai-native 3.9.1-next-1748943529.0 → 3.9.1-next-1749003325.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 (29) hide show
  1. package/lib/browser/components/ChatMentionInput.d.ts.map +1 -1
  2. package/lib/browser/components/ChatMentionInput.js +18 -3
  3. package/lib/browser/components/ChatMentionInput.js.map +1 -1
  4. package/lib/browser/components/components.module.less +2 -7
  5. package/lib/browser/components/mention-input/mention-input.d.ts.map +1 -1
  6. package/lib/browser/components/mention-input/mention-input.js +39 -34
  7. package/lib/browser/components/mention-input/mention-input.js.map +1 -1
  8. package/lib/browser/components/mention-input/mention-input.module.less +0 -1
  9. package/lib/browser/components/mention-input/mention-select.d.ts +28 -0
  10. package/lib/browser/components/mention-input/mention-select.d.ts.map +1 -0
  11. package/lib/browser/components/mention-input/mention-select.js +136 -0
  12. package/lib/browser/components/mention-input/mention-select.js.map +1 -0
  13. package/lib/browser/components/mention-input/mention-select.module.less +297 -0
  14. package/lib/browser/components/mention-input/types.d.ts +7 -6
  15. package/lib/browser/components/mention-input/types.d.ts.map +1 -1
  16. package/lib/browser/components/mention-input/types.js.map +1 -1
  17. package/lib/browser/rules/rules.module.less +1 -0
  18. package/lib/browser/rules/rules.view.js +1 -1
  19. package/lib/browser/rules/rules.view.js.map +1 -1
  20. package/package.json +25 -25
  21. package/src/browser/components/ChatMentionInput.tsx +26 -4
  22. package/src/browser/components/components.module.less +2 -7
  23. package/src/browser/components/mention-input/mention-input.module.less +0 -1
  24. package/src/browser/components/mention-input/mention-input.tsx +36 -26
  25. package/src/browser/components/mention-input/mention-select.module.less +297 -0
  26. package/src/browser/components/mention-input/mention-select.tsx +256 -0
  27. package/src/browser/components/mention-input/types.ts +8 -7
  28. package/src/browser/rules/rules.module.less +1 -0
  29. package/src/browser/rules/rules.view.tsx +1 -1
@@ -0,0 +1,256 @@
1
+ import cls from 'classnames';
2
+ import React, { useEffect, useRef, useState } from 'react';
3
+
4
+ import { ClickOutside } from '@opensumi/ide-components/lib/click-outside';
5
+ import { Icon, getIcon } from '@opensumi/ide-core-browser/lib/components';
6
+
7
+ import styles from './mention-select.module.less';
8
+
9
+ export interface ExtendedModelOption {
10
+ label: string;
11
+ value: string;
12
+ icon?: string;
13
+ iconClass?: string;
14
+ tags?: string[];
15
+ features?: string[];
16
+ description?: string;
17
+ disabled?: boolean;
18
+ badge?: string;
19
+ badgeColor?: string;
20
+ selected?: boolean;
21
+ }
22
+
23
+ export interface MentionSelectProps {
24
+ options: ExtendedModelOption[];
25
+ value?: string;
26
+ onChange?: (value: string) => void;
27
+ placeholder?: string;
28
+ disabled?: boolean;
29
+ className?: string;
30
+ size?: 'small' | 'medium' | 'large';
31
+ showThinking?: boolean;
32
+ thinkingEnabled?: boolean;
33
+ onThinkingChange?: (enabled: boolean) => void;
34
+ }
35
+
36
+ const ThinkingToggle: React.FC<{
37
+ enabled: boolean;
38
+ onChange: (enabled: boolean) => void;
39
+ }> = ({ enabled, onChange }) => (
40
+ <div className={styles.thinking_toggle} onClick={() => onChange(!enabled)}>
41
+ <Icon
42
+ iconClass={getIcon(enabled ? 'check' : 'circle-outline')}
43
+ className={cls(styles.thinking_icon, {
44
+ [styles.enabled]: enabled,
45
+ })}
46
+ />
47
+ <span className={styles.thinking_label}>Thinking</span>
48
+ </div>
49
+ );
50
+
51
+ export const MentionSelect: React.FC<MentionSelectProps> = ({
52
+ options,
53
+ value,
54
+ onChange,
55
+ placeholder,
56
+ disabled = false,
57
+ className,
58
+ size = 'small',
59
+ showThinking = false,
60
+ thinkingEnabled = false,
61
+ onThinkingChange,
62
+ }) => {
63
+ const [isOpen, setIsOpen] = useState(false);
64
+ const [activeIndex, setActiveIndex] = useState(-1);
65
+ const [dropdownDirection, setDropdownDirection] = useState<'up' | 'down'>('up');
66
+ const selectRef = useRef<HTMLDivElement>(null);
67
+ const dropdownRef = useRef<HTMLDivElement>(null);
68
+
69
+ const selectedOption = options.find((option) => option.selected) || options.find((option) => option.value === value);
70
+
71
+ const handleToggle = () => {
72
+ if (!disabled) {
73
+ setIsOpen(!isOpen);
74
+ setActiveIndex(-1);
75
+ }
76
+ };
77
+
78
+ const handleSelect = (option: ExtendedModelOption) => {
79
+ if (!option.disabled) {
80
+ onChange?.(option.value);
81
+ setIsOpen(false);
82
+ setActiveIndex(-1);
83
+ }
84
+ };
85
+
86
+ const handleKeyDown = (e: React.KeyboardEvent) => {
87
+ if (disabled) {
88
+ return;
89
+ }
90
+
91
+ switch (e.key) {
92
+ case 'Enter':
93
+ case ' ':
94
+ e.preventDefault();
95
+ if (!isOpen) {
96
+ setIsOpen(true);
97
+ } else if (activeIndex >= 0) {
98
+ handleSelect(options[activeIndex]);
99
+ }
100
+ break;
101
+ case 'Escape':
102
+ e.preventDefault();
103
+ setIsOpen(false);
104
+ setActiveIndex(-1);
105
+ break;
106
+ case 'ArrowDown':
107
+ e.preventDefault();
108
+ if (!isOpen) {
109
+ setIsOpen(true);
110
+ } else {
111
+ setActiveIndex((prev) => (prev < options.length - 1 ? prev + 1 : 0));
112
+ }
113
+ break;
114
+ case 'ArrowUp':
115
+ e.preventDefault();
116
+ if (isOpen) {
117
+ setActiveIndex((prev) => (prev > 0 ? prev - 1 : options.length - 1));
118
+ }
119
+ break;
120
+ }
121
+ };
122
+
123
+ const handleClickOutside = () => {
124
+ setIsOpen(false);
125
+ setActiveIndex(-1);
126
+ };
127
+
128
+ useEffect(() => {
129
+ if (isOpen && selectRef.current) {
130
+ const selectRect = selectRef.current.getBoundingClientRect();
131
+ const viewportHeight = window.innerHeight;
132
+ const dropdownHeight = Math.min(400, options.length * 60);
133
+
134
+ const spaceAbove = selectRect.top;
135
+ const spaceBelow = viewportHeight - selectRect.bottom;
136
+
137
+ if (spaceAbove < dropdownHeight && spaceBelow > spaceAbove) {
138
+ setDropdownDirection('down');
139
+ } else {
140
+ setDropdownDirection('up');
141
+ }
142
+ }
143
+ }, [isOpen, options.length]);
144
+
145
+ useEffect(() => {
146
+ if (isOpen && activeIndex >= 0 && dropdownRef.current) {
147
+ const activeElement = dropdownRef.current.children[activeIndex] as HTMLElement;
148
+ if (activeElement) {
149
+ activeElement.scrollIntoView({
150
+ behavior: 'smooth',
151
+ block: 'nearest',
152
+ });
153
+ }
154
+ }
155
+ }, [isOpen, activeIndex]);
156
+
157
+ return (
158
+ <ClickOutside onOutsideClick={handleClickOutside}>
159
+ <div
160
+ ref={selectRef}
161
+ className={cls(
162
+ styles.mention_select,
163
+ styles[`size_${size}`],
164
+ {
165
+ [styles.disabled]: disabled,
166
+ [styles.open]: isOpen,
167
+ [styles.dropdown_up]: dropdownDirection === 'up',
168
+ [styles.dropdown_down]: dropdownDirection === 'down',
169
+ },
170
+ className,
171
+ )}
172
+ onClick={handleToggle}
173
+ onKeyDown={handleKeyDown}
174
+ tabIndex={disabled ? -1 : 0}
175
+ role='combobox'
176
+ aria-expanded={isOpen}
177
+ aria-haspopup='listbox'
178
+ >
179
+ <div className={styles.select_trigger}>
180
+ <div className={styles.select_content}>
181
+ {selectedOption ? (
182
+ <div className={styles.selected_option}>
183
+ <span className={styles.option_label}>{selectedOption.label}</span>
184
+ {selectedOption.badge && (
185
+ <span className={styles.option_badge} style={{ backgroundColor: selectedOption.badgeColor }}>
186
+ {selectedOption.badge}
187
+ </span>
188
+ )}
189
+ </div>
190
+ ) : (
191
+ <span className={styles.placeholder}>{placeholder}</span>
192
+ )}
193
+ </div>
194
+ <Icon
195
+ iconClass={getIcon('down-arrow')}
196
+ className={cls(styles.dropdown_arrow, {
197
+ [styles.open]: isOpen,
198
+ })}
199
+ />
200
+ </div>
201
+
202
+ {isOpen && (
203
+ <div ref={dropdownRef} className={styles.dropdown} role='listbox'>
204
+ {showThinking && onThinkingChange && (
205
+ <div className={styles.thinking_section}>
206
+ <ThinkingToggle enabled={thinkingEnabled} onChange={onThinkingChange} />
207
+ <div className={styles.divider} />
208
+ </div>
209
+ )}
210
+
211
+ {options.map((option, index) => (
212
+ <div
213
+ key={option.value}
214
+ className={cls(styles.option, {
215
+ [styles.active]: index === activeIndex,
216
+ [styles.selected]: option.selected || option.value === value,
217
+ [styles.disabled]: option.disabled,
218
+ })}
219
+ onClick={() => handleSelect(option)}
220
+ role='option'
221
+ aria-selected={option.selected || option.value === value}
222
+ >
223
+ <div className={styles.option_main}>
224
+ <div className={styles.option_header}>
225
+ <div className={styles.option_title}>
226
+ {option.icon && <Icon icon={option.icon} className={styles.option_icon} />}
227
+ {option.iconClass && <Icon iconClass={option.iconClass} className={styles.option_icon} />}
228
+ <span className={styles.option_label}>{option.label}</span>
229
+ {option.badge && (
230
+ <span className={styles.option_badge} style={{ backgroundColor: option.badgeColor }}>
231
+ {option.badge}
232
+ </span>
233
+ )}
234
+ </div>
235
+ </div>
236
+
237
+ {option.description && <div className={styles.option_description}>{option.description}</div>}
238
+
239
+ {option.tags && option.tags.length > 0 && (
240
+ <div className={styles.option_tags}>
241
+ {option.tags.map((tag, idx) => (
242
+ <span key={idx} className={styles.tag}>
243
+ {tag}
244
+ </span>
245
+ ))}
246
+ </div>
247
+ )}
248
+ </div>
249
+ </div>
250
+ ))}
251
+ </div>
252
+ )}
253
+ </div>
254
+ </ClickOutside>
255
+ );
256
+ };
@@ -46,21 +46,19 @@ export interface MentionState {
46
46
  interface ModelOption {
47
47
  label: string;
48
48
  value: string;
49
- }
50
-
51
- export interface ExtendedModelOption {
52
- label: string;
53
- value: string;
54
49
  icon?: string;
55
50
  iconClass?: string;
56
51
  tags?: string[];
57
- features?: string[];
58
52
  description?: string;
59
- disabled?: boolean;
60
53
  badge?: string;
61
54
  badgeColor?: string;
62
55
  }
63
56
 
57
+ export interface ExtendedModelOption extends ModelOption {
58
+ disabled?: boolean;
59
+ selected?: boolean; // 由外部控制选中状态
60
+ }
61
+
64
62
  export enum FooterButtonPosition {
65
63
  LEFT = 'left',
66
64
  RIGHT = 'right',
@@ -89,6 +87,9 @@ export interface FooterConfig {
89
87
  buttons?: FooterButton[];
90
88
  showModelSelector?: boolean;
91
89
  disableModelSelector?: boolean;
90
+ showThinking?: boolean;
91
+ thinkingEnabled?: boolean;
92
+ onThinkingChange?: (enabled: boolean) => void;
92
93
  }
93
94
 
94
95
  export interface MentionInputProps {
@@ -80,6 +80,7 @@
80
80
  }
81
81
 
82
82
  .project_rules_header_left {
83
+ width: 100%;
83
84
  display: flex;
84
85
  flex-direction: column;
85
86
  }
@@ -37,7 +37,7 @@ export const RulesView: React.FC = () => {
37
37
  [rulesService],
38
38
  );
39
39
 
40
- const getFileNameFromPath = (path: string) => path.split('/').pop() || path;
40
+ const getFileNameFromPath = (path: string) => decodeURIComponent(path.split('/').pop() || path);
41
41
 
42
42
  const hasWarning = (rule: ProjectRule) => !rule.description && !rule.alwaysApply && !rule.globs;
43
43