@theia/ai-chat-ui 1.71.0-next.8 → 1.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser/ai-chat-ui-contribution.d.ts.map +1 -1
- package/lib/browser/ai-chat-ui-contribution.js +10 -9
- package/lib/browser/ai-chat-ui-contribution.js.map +1 -1
- package/lib/browser/ai-chat-ui-frontend-module.d.ts +1 -0
- package/lib/browser/ai-chat-ui-frontend-module.d.ts.map +1 -1
- package/lib/browser/ai-chat-ui-frontend-module.js +3 -0
- package/lib/browser/ai-chat-ui-frontend-module.js.map +1 -1
- package/lib/browser/chat-input-mode-contribution.js +1 -1
- package/lib/browser/chat-input-mode-contribution.js.map +1 -1
- package/lib/browser/chat-input-widget.d.ts +57 -3
- package/lib/browser/chat-input-widget.d.ts.map +1 -1
- package/lib/browser/chat-input-widget.js +382 -21
- package/lib/browser/chat-input-widget.js.map +1 -1
- package/lib/browser/chat-response-part-renderer.d.ts +6 -0
- package/lib/browser/chat-response-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/delegation-tool-renderer.d.ts +2 -0
- package/lib/browser/chat-response-renderer/delegation-tool-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/delegation-tool-renderer.js +76 -5
- package/lib/browser/chat-response-renderer/delegation-tool-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/index.d.ts +1 -0
- package/lib/browser/chat-response-renderer/index.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/index.js +1 -0
- package/lib/browser/chat-response-renderer/index.js.map +1 -1
- package/lib/browser/chat-response-renderer/question-part-renderer.d.ts +3 -0
- package/lib/browser/chat-response-renderer/question-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/question-part-renderer.js +19 -6
- package/lib/browser/chat-response-renderer/question-part-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/tool-call-rendering.d.ts +21 -0
- package/lib/browser/chat-response-renderer/tool-call-rendering.d.ts.map +1 -0
- package/lib/browser/chat-response-renderer/tool-call-rendering.js +53 -0
- package/lib/browser/chat-response-renderer/tool-call-rendering.js.map +1 -0
- package/lib/browser/chat-response-renderer/tool-confirmation.d.ts +4 -0
- package/lib/browser/chat-response-renderer/tool-confirmation.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/tool-confirmation.js +22 -0
- package/lib/browser/chat-response-renderer/tool-confirmation.js.map +1 -1
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts +1 -0
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js +7 -18
- package/lib/browser/chat-response-renderer/toolcall-part-renderer.js.map +1 -1
- package/lib/browser/chat-response-renderer/toolcall-utils.d.ts +1 -0
- package/lib/browser/chat-response-renderer/toolcall-utils.d.ts.map +1 -1
- package/lib/browser/chat-response-renderer/toolcall-utils.js +3 -0
- package/lib/browser/chat-response-renderer/toolcall-utils.js.map +1 -1
- package/lib/browser/chat-token-usage-indicator-util.d.ts +35 -0
- package/lib/browser/chat-token-usage-indicator-util.d.ts.map +1 -0
- package/lib/browser/chat-token-usage-indicator-util.js +138 -0
- package/lib/browser/chat-token-usage-indicator-util.js.map +1 -0
- package/lib/browser/chat-token-usage-indicator-util.spec.d.ts +2 -0
- package/lib/browser/chat-token-usage-indicator-util.spec.d.ts.map +1 -0
- package/lib/browser/chat-token-usage-indicator-util.spec.js +226 -0
- package/lib/browser/chat-token-usage-indicator-util.spec.js.map +1 -0
- package/lib/browser/chat-tree-view/chat-view-tree-input-widget.d.ts +6 -1
- package/lib/browser/chat-tree-view/chat-view-tree-input-widget.d.ts.map +1 -1
- package/lib/browser/chat-tree-view/chat-view-tree-input-widget.js +7 -0
- package/lib/browser/chat-tree-view/chat-view-tree-input-widget.js.map +1 -1
- package/lib/browser/chat-tree-view/chat-view-tree-widget.d.ts.map +1 -1
- package/lib/browser/chat-tree-view/chat-view-tree-widget.js +20 -3
- package/lib/browser/chat-tree-view/chat-view-tree-widget.js.map +1 -1
- package/lib/browser/chat-view-preferences.d.ts +18 -0
- package/lib/browser/chat-view-preferences.d.ts.map +1 -0
- package/lib/browser/chat-view-preferences.js +69 -0
- package/lib/browser/chat-view-preferences.js.map +1 -0
- package/lib/browser/chat-view-widget-toolbar-contribution.d.ts.map +1 -1
- package/lib/browser/chat-view-widget-toolbar-contribution.js +3 -2
- package/lib/browser/chat-view-widget-toolbar-contribution.js.map +1 -1
- package/lib/browser/chat-view-widget.d.ts +1 -2
- package/lib/browser/chat-view-widget.d.ts.map +1 -1
- package/lib/browser/chat-view-widget.js +3 -7
- package/lib/browser/chat-view-widget.js.map +1 -1
- package/lib/browser/session-settings-dialog.d.ts +2 -5
- package/lib/browser/session-settings-dialog.d.ts.map +1 -1
- package/lib/browser/session-settings-dialog.js +15 -33
- package/lib/browser/session-settings-dialog.js.map +1 -1
- package/lib/common/toolcall-utils.d.ts +6 -0
- package/lib/common/toolcall-utils.d.ts.map +1 -0
- package/lib/common/toolcall-utils.js +40 -0
- package/lib/common/toolcall-utils.js.map +1 -0
- package/lib/common/toolcall-utils.spec.d.ts +2 -0
- package/lib/common/toolcall-utils.spec.d.ts.map +1 -0
- package/lib/common/toolcall-utils.spec.js +102 -0
- package/lib/common/toolcall-utils.spec.js.map +1 -0
- package/package.json +12 -12
- package/src/browser/ai-chat-ui-contribution.ts +10 -9
- package/src/browser/ai-chat-ui-frontend-module.ts +4 -0
- package/src/browser/chat-input-mode-contribution.ts +1 -1
- package/src/browser/chat-input-widget.tsx +481 -14
- package/src/browser/chat-response-part-renderer.ts +6 -0
- package/src/browser/chat-response-renderer/delegation-tool-renderer.tsx +105 -7
- package/src/browser/chat-response-renderer/index.ts +1 -0
- package/src/browser/chat-response-renderer/question-part-renderer.tsx +24 -8
- package/src/browser/chat-response-renderer/tool-call-rendering.tsx +89 -0
- package/src/browser/chat-response-renderer/tool-confirmation.tsx +26 -0
- package/src/browser/chat-response-renderer/toolcall-part-renderer.tsx +21 -18
- package/src/browser/chat-response-renderer/toolcall-utils.ts +2 -0
- package/src/browser/chat-token-usage-indicator-util.spec.ts +262 -0
- package/src/browser/chat-token-usage-indicator-util.ts +151 -0
- package/src/browser/chat-tree-view/chat-view-tree-input-widget.tsx +9 -1
- package/src/browser/chat-tree-view/chat-view-tree-widget.tsx +22 -3
- package/src/browser/chat-view-preferences.ts +87 -0
- package/src/browser/chat-view-widget-toolbar-contribution.tsx +3 -2
- package/src/browser/chat-view-widget.tsx +4 -7
- package/src/browser/session-settings-dialog.tsx +15 -97
- package/src/browser/style/index.css +164 -54
- package/src/browser/style/tool-call-rendering.css +327 -0
- package/src/common/toolcall-utils.spec.ts +118 -0
- package/src/common/toolcall-utils.ts +36 -0
|
@@ -21,6 +21,7 @@ const tslib_1 = require("tslib");
|
|
|
21
21
|
const ai_chat_1 = require("@theia/ai-chat");
|
|
22
22
|
const chat_agent_service_1 = require("@theia/ai-chat/lib/common/chat-agent-service");
|
|
23
23
|
const ai_core_1 = require("@theia/ai-core");
|
|
24
|
+
const frontend_language_model_service_1 = require("@theia/ai-core/lib/browser/frontend-language-model-service");
|
|
24
25
|
const change_set_decorator_service_1 = require("@theia/ai-chat/lib/browser/change-set-decorator-service");
|
|
25
26
|
const image_context_variable_1 = require("@theia/ai-chat/lib/common/image-context-variable");
|
|
26
27
|
const browser_1 = require("@theia/ai-core/lib/browser");
|
|
@@ -49,11 +50,18 @@ const chat_capabilities_panel_1 = require("./chat-capabilities-panel");
|
|
|
49
50
|
const chat_input_focus_service_1 = require("./chat-input-focus-service");
|
|
50
51
|
const generic_capabilities_service_1 = require("./generic-capabilities-service");
|
|
51
52
|
const generic_capabilities_section_1 = require("./generic-capabilities-section");
|
|
53
|
+
const preferences_1 = require("@theia/core/lib/common/preferences");
|
|
54
|
+
const chat_view_preferences_1 = require("./chat-view-preferences");
|
|
55
|
+
const chat_token_usage_indicator_util_1 = require("./chat-token-usage-indicator-util");
|
|
56
|
+
const chat_view_commands_1 = require("./chat-view-commands");
|
|
52
57
|
exports.AIChatInputConfiguration = Symbol('AIChatInputConfiguration');
|
|
53
58
|
let AIChatInputWidget = class AIChatInputWidget extends browser_2.ReactWidget {
|
|
54
59
|
constructor() {
|
|
55
60
|
super(...arguments);
|
|
56
61
|
this.fileValidationState = new Map();
|
|
62
|
+
this.tokenUsageEnabled = false;
|
|
63
|
+
/** Sessions we have already notified for the current warning cycle (re-armed when usage drops below the threshold). */
|
|
64
|
+
this.notifiedSessions = new Set();
|
|
57
65
|
this.editorRef = undefined;
|
|
58
66
|
this.editorReady = new promise_util_1.Deferred();
|
|
59
67
|
this.handleModeChange = async (mode) => {
|
|
@@ -62,6 +70,35 @@ let AIChatInputWidget = class AIChatInputWidget extends browser_2.ReactWidget {
|
|
|
62
70
|
await this.updateCapabilitiesForAgent(this.receivingAgent.agentId, mode);
|
|
63
71
|
}
|
|
64
72
|
};
|
|
73
|
+
this.handleReasoningChange = async (level) => {
|
|
74
|
+
const session = this.chatService.getSessions().find(s => s.model.id === this._chatModel?.id);
|
|
75
|
+
if (!session) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const currentSettings = session.model.settings ?? {};
|
|
79
|
+
const newSettings = {
|
|
80
|
+
...currentSettings,
|
|
81
|
+
commonSettings: {
|
|
82
|
+
...currentSettings.commonSettings,
|
|
83
|
+
reasoning: { level }
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
session.model.setSettings(newSettings);
|
|
87
|
+
// Auto-persist the reasoning selection per-agent so it is restored on the next session
|
|
88
|
+
// and the capabilities indicator does not light up for an unrelated configuration concern.
|
|
89
|
+
if (this.receivingAgent) {
|
|
90
|
+
try {
|
|
91
|
+
await this.aiSettingsService.updateAgentSettings(this.receivingAgent.agentId, {
|
|
92
|
+
reasoning: { level }
|
|
93
|
+
});
|
|
94
|
+
this.savedReasoning = { level };
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
console.error('Failed to persist reasoning selection:', error);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
this.update();
|
|
101
|
+
};
|
|
65
102
|
this.handleCapabilityChange = (fragmentId, enabled) => {
|
|
66
103
|
const defaultCapability = this.capabilityDefaults.find(c => c.fragmentId === fragmentId);
|
|
67
104
|
const sessionOverrides = new Map(this.userCapabilityOverrides);
|
|
@@ -182,6 +219,61 @@ let AIChatInputWidget = class AIChatInputWidget extends browser_2.ReactWidget {
|
|
|
182
219
|
const nextModeId = this.receivingAgent.modes[nextIndex].id;
|
|
183
220
|
this.handleModeChange(nextModeId);
|
|
184
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Resolves the reasoning level to display in the selector. Priority: session override →
|
|
224
|
+
* persisted per-agent selection (from {@link AISettingsService}) →
|
|
225
|
+
* `ai-features.reasoning.defaults` preference entry matching the current model/agent →
|
|
226
|
+
* model's declared default → `'off'`.
|
|
227
|
+
*/
|
|
228
|
+
getCurrentReasoningLevel() {
|
|
229
|
+
if (!this.currentReasoningSupport) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
const session = this.chatService.getSessions().find(s => s.model.id === this._chatModel?.id);
|
|
233
|
+
const sessionLevel = session?.model.settings?.commonSettings?.reasoning?.level;
|
|
234
|
+
if (sessionLevel) {
|
|
235
|
+
return sessionLevel;
|
|
236
|
+
}
|
|
237
|
+
if (this.savedReasoning?.level) {
|
|
238
|
+
return this.savedReasoning.level;
|
|
239
|
+
}
|
|
240
|
+
return this.resolvePreferenceReasoningLevel() ?? this.currentReasoningSupport.defaultLevel ?? 'off';
|
|
241
|
+
}
|
|
242
|
+
resolvePreferenceReasoningLevel() {
|
|
243
|
+
if (!this.preferenceService || !this.currentLanguageModelId) {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
const entries = this.preferenceService.get(ai_core_1.PREFERENCE_NAME_REASONING, []);
|
|
247
|
+
const [providerId, modelId] = this.currentLanguageModelId.split('/');
|
|
248
|
+
return (0, frontend_language_model_service_1.mergeReasoningSettings)(entries, modelId, providerId, this.receivingAgent?.agentId)?.reasoning?.level;
|
|
249
|
+
}
|
|
250
|
+
async updateReasoningSupport(agentId) {
|
|
251
|
+
let support;
|
|
252
|
+
let modelId;
|
|
253
|
+
if (agentId) {
|
|
254
|
+
const agent = this.chatAgentService.getAgent(agentId);
|
|
255
|
+
if (agent) {
|
|
256
|
+
for (const requirement of agent.languageModelRequirements ?? []) {
|
|
257
|
+
try {
|
|
258
|
+
const model = await this.languageModelRegistry.selectLanguageModel({ agent: agent.id, ...requirement });
|
|
259
|
+
if (model?.reasoningSupport) {
|
|
260
|
+
support = model.reasoningSupport;
|
|
261
|
+
modelId = model.id;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
console.warn('Failed to resolve language model for reasoning support:', error);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (support !== this.currentReasoningSupport || modelId !== this.currentLanguageModelId) {
|
|
272
|
+
this.currentReasoningSupport = support;
|
|
273
|
+
this.currentLanguageModelId = modelId;
|
|
274
|
+
this.update();
|
|
275
|
+
}
|
|
276
|
+
}
|
|
185
277
|
async updateCapabilitiesForAgent(agentId, modeId, preserveOverrides) {
|
|
186
278
|
const capabilities = await this.capabilitiesService.getCapabilitiesForAgent(agentId, modeId);
|
|
187
279
|
this.capabilityDefaults = capabilities;
|
|
@@ -190,19 +282,44 @@ let AIChatInputWidget = class AIChatInputWidget extends browser_2.ReactWidget {
|
|
|
190
282
|
const agentSettings = await this.aiSettingsService.getAgentSettings(agentId);
|
|
191
283
|
const savedOverrides = agentSettings?.capabilityOverrides;
|
|
192
284
|
const savedGenericSelections = agentSettings?.genericCapabilitySelections;
|
|
285
|
+
const savedReasoning = agentSettings?.reasoning;
|
|
193
286
|
// Store saved state for comparison
|
|
194
287
|
this.savedCapabilityOverrides = savedOverrides ? { ...savedOverrides } : undefined;
|
|
195
288
|
this.savedGenericCapabilitySelections = savedGenericSelections ? { ...savedGenericSelections } : undefined;
|
|
289
|
+
this.savedReasoning = savedReasoning ? { ...savedReasoning } : undefined;
|
|
196
290
|
// Initialize from saved settings, or empty if none
|
|
197
291
|
this.userCapabilityOverrides = savedOverrides
|
|
198
292
|
? new Map(Object.entries(savedOverrides))
|
|
199
293
|
: new Map();
|
|
200
294
|
this.genericCapabilitySelections = savedGenericSelections ?? {};
|
|
295
|
+
// Mirror the saved per-agent reasoning into the chat session so the selector reflects it
|
|
296
|
+
// immediately on session/agent switch.
|
|
297
|
+
this.applyReasoningToSession(savedReasoning);
|
|
201
298
|
}
|
|
202
299
|
// Update disabled generic capabilities (already used in agent prompt)
|
|
203
300
|
this.disabledGenericCapabilities = await this.capabilitiesService.getUsedGenericCapabilitiesForAgent(agentId, modeId);
|
|
204
301
|
this.update();
|
|
205
302
|
}
|
|
303
|
+
/** Updates the active chat session's `commonSettings.reasoning`; pass `undefined` to clear. */
|
|
304
|
+
applyReasoningToSession(reasoning) {
|
|
305
|
+
const session = this.chatService.getSessions().find(s => s.model.id === this._chatModel?.id);
|
|
306
|
+
if (!session) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const currentSettings = session.model.settings ?? {};
|
|
310
|
+
const currentCommon = currentSettings.commonSettings ?? {};
|
|
311
|
+
if ((currentCommon.reasoning?.level ?? undefined) === (reasoning?.level ?? undefined)) {
|
|
312
|
+
return; // no-op when already in sync
|
|
313
|
+
}
|
|
314
|
+
const newCommon = { ...currentCommon };
|
|
315
|
+
if (reasoning) {
|
|
316
|
+
newCommon.reasoning = { ...reasoning };
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
delete newCommon.reasoning;
|
|
320
|
+
}
|
|
321
|
+
session.model.setSettings({ ...currentSettings, commonSettings: newCommon });
|
|
322
|
+
}
|
|
206
323
|
async updateAvailableGenericCapabilities() {
|
|
207
324
|
if (!this.genericCapabilitiesService) {
|
|
208
325
|
return;
|
|
@@ -234,6 +351,18 @@ let AIChatInputWidget = class AIChatInputWidget extends browser_2.ReactWidget {
|
|
|
234
351
|
}
|
|
235
352
|
return new Map(Object.entries(overrides));
|
|
236
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Extracts the mode ID from the last request in the chat model.
|
|
356
|
+
* Used to restore the user's selected mode when switching sessions or on reload.
|
|
357
|
+
*/
|
|
358
|
+
getLastModeIdFromModel(chatModel) {
|
|
359
|
+
const requests = chatModel.getRequests();
|
|
360
|
+
if (requests.length === 0) {
|
|
361
|
+
return undefined;
|
|
362
|
+
}
|
|
363
|
+
const lastRequest = requests[requests.length - 1];
|
|
364
|
+
return lastRequest.request.modeId;
|
|
365
|
+
}
|
|
237
366
|
/**
|
|
238
367
|
* Extracts generic capability selections from the last request in the chat model.
|
|
239
368
|
* Used to restore user's selections when switching sessions or on reload.
|
|
@@ -304,12 +433,18 @@ let AIChatInputWidget = class AIChatInputWidget extends browser_2.ReactWidget {
|
|
|
304
433
|
}
|
|
305
434
|
/**
|
|
306
435
|
* Checks if there are any unsaved changes (capability overrides or generic selections).
|
|
436
|
+
* Reasoning is auto-persisted in {@link handleReasoningChange} and is intentionally excluded.
|
|
307
437
|
*/
|
|
308
438
|
hasAnyChangesFromSaved() {
|
|
309
|
-
|
|
439
|
+
if (this.receivingAgent === undefined) {
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
return this.hasCapabilityChangesFromSaved()
|
|
443
|
+
|| this.hasGenericCapabilityChangesFromSaved();
|
|
310
444
|
}
|
|
311
445
|
/**
|
|
312
446
|
* Saves current capability selections to settings.
|
|
447
|
+
* Reasoning is auto-persisted via {@link handleReasoningChange} and is not part of this flow.
|
|
313
448
|
*/
|
|
314
449
|
async saveCurrentSelectionsToSettings() {
|
|
315
450
|
if (!this.receivingAgent) {
|
|
@@ -388,6 +523,9 @@ let AIChatInputWidget = class AIChatInputWidget extends browser_2.ReactWidget {
|
|
|
388
523
|
this.userCapabilityOverrides = this.getLastCapabilityOverridesFromModel(chatModel);
|
|
389
524
|
this.genericCapabilitySelections = this.getLastGenericCapabilitySelectionsFromModel(chatModel);
|
|
390
525
|
this.onDisposeForChatModel.push(chatModel.onDidChange(event => {
|
|
526
|
+
if (event.kind === 'responseChanged') {
|
|
527
|
+
this.evaluateTokenUsageWarning(chatModel);
|
|
528
|
+
}
|
|
391
529
|
if (event.kind === 'addVariable') {
|
|
392
530
|
// Validate files added via any path (including LLM tool calls)
|
|
393
531
|
// Get the current variables and validate any new file variables
|
|
@@ -412,6 +550,16 @@ let AIChatInputWidget = class AIChatInputWidget extends browser_2.ReactWidget {
|
|
|
412
550
|
}
|
|
413
551
|
}));
|
|
414
552
|
this._chatModel = chatModel;
|
|
553
|
+
// Evaluate the warning on attach. `notifiedSessions` lives on this widget
|
|
554
|
+
// instance, so the warning fires at most once per (widget lifetime × session):
|
|
555
|
+
// - Within the same widget, switching between sessions that have already been
|
|
556
|
+
// notified does not re-notify.
|
|
557
|
+
// - Closing and reopening the chat view creates a fresh widget with an empty
|
|
558
|
+
// Set, so sessions still above the threshold will be warned about again the
|
|
559
|
+
// first time they are shown after reopen — once per session. Accepted as a
|
|
560
|
+
// rare corner case; promoting the state to the ChatSession would avoid it
|
|
561
|
+
// but isn't worth the coupling today.
|
|
562
|
+
this.evaluateTokenUsageWarning(chatModel);
|
|
415
563
|
this.scheduleUpdateReceivingAgent();
|
|
416
564
|
this.update();
|
|
417
565
|
}
|
|
@@ -424,8 +572,8 @@ let AIChatInputWidget = class AIChatInputWidget extends browser_2.ReactWidget {
|
|
|
424
572
|
this.id = AIChatInputWidget_1.ID;
|
|
425
573
|
this.title.closable = false;
|
|
426
574
|
this.toDispose.push(this.resources.add(this.getResourceUri(), ''));
|
|
427
|
-
this.toDispose.push(this.aiActivationService.
|
|
428
|
-
this.setEnabled(this.aiActivationService.
|
|
575
|
+
this.toDispose.push(this.aiActivationService.onDidChangeCanRun(() => {
|
|
576
|
+
this.setEnabled(this.aiActivationService.canRun);
|
|
429
577
|
}));
|
|
430
578
|
this.toDispose.push(this.chatAgentService.onDefaultAgentChanged(() => {
|
|
431
579
|
this.scheduleUpdateReceivingAgent();
|
|
@@ -437,15 +585,58 @@ let AIChatInputWidget = class AIChatInputWidget extends browser_2.ReactWidget {
|
|
|
437
585
|
this.updateReceivingAgentTimeout = undefined;
|
|
438
586
|
}
|
|
439
587
|
}));
|
|
440
|
-
this.setEnabled(this.aiActivationService.
|
|
588
|
+
this.setEnabled(this.aiActivationService.canRun);
|
|
441
589
|
this.historyService.init().then(() => {
|
|
442
590
|
this.navigationState = new chat_input_history_1.ChatInputNavigationState(this.historyService);
|
|
443
591
|
});
|
|
444
592
|
this.initializeContextKeys();
|
|
593
|
+
this.tokenUsageEnabled = this.preferenceService?.get(chat_view_preferences_1.CHAT_VIEW_TOKEN_USAGE_ENABLED, false) ?? false;
|
|
594
|
+
if (this.preferenceService) {
|
|
595
|
+
this.toDispose.push(this.preferenceService.onPreferenceChanged(change => {
|
|
596
|
+
if (change.preferenceName === chat_view_preferences_1.CHAT_VIEW_TOKEN_USAGE_ENABLED) {
|
|
597
|
+
this.tokenUsageEnabled = this.preferenceService?.get(chat_view_preferences_1.CHAT_VIEW_TOKEN_USAGE_ENABLED, false) ?? false;
|
|
598
|
+
this.update();
|
|
599
|
+
}
|
|
600
|
+
else if (change.preferenceName === ai_core_1.PREFERENCE_NAME_REASONING) {
|
|
601
|
+
// Refresh the reasoning selector display when the default preference changes.
|
|
602
|
+
this.update();
|
|
603
|
+
}
|
|
604
|
+
else if (change.preferenceName === chat_view_preferences_1.CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE) {
|
|
605
|
+
// Threshold changed: clear notified sessions so users are warned again
|
|
606
|
+
// at the new threshold (e.g. after raising it from the warning's
|
|
607
|
+
// "Open Settings" action), and re-evaluate the current session so the
|
|
608
|
+
// warning appears immediately if it's still above the new threshold.
|
|
609
|
+
this.notifiedSessions.clear();
|
|
610
|
+
if (this._chatModel) {
|
|
611
|
+
this.evaluateTokenUsageWarning(this._chatModel);
|
|
612
|
+
}
|
|
613
|
+
// Re-render so the indicator's color bands reflect the new threshold.
|
|
614
|
+
this.update();
|
|
615
|
+
}
|
|
616
|
+
else if (change.preferenceName === chat_view_preferences_1.CHAT_VIEW_TOKEN_USAGE_WARNING_ENABLED && this._chatModel) {
|
|
617
|
+
// If the user just enabled warnings for a session already above threshold,
|
|
618
|
+
// evaluate now so they get an immediate notification instead of waiting for
|
|
619
|
+
// the next response.
|
|
620
|
+
this.evaluateTokenUsageWarning(this._chatModel);
|
|
621
|
+
}
|
|
622
|
+
}));
|
|
623
|
+
}
|
|
445
624
|
// Listen for prompt fragment changes to refresh capabilities
|
|
446
625
|
this.toDispose.push(this.capabilitiesService.onDidChangeCapabilities(() => {
|
|
447
626
|
this.refreshCapabilities();
|
|
448
627
|
}));
|
|
628
|
+
// Refresh reasoning capability if the language model registry changes (model added/removed/alias re-resolved).
|
|
629
|
+
this.toDispose.push(this.languageModelRegistry.onChange(() => {
|
|
630
|
+
if (this.receivingAgent) {
|
|
631
|
+
this.updateReasoningSupport(this.receivingAgent.agentId);
|
|
632
|
+
}
|
|
633
|
+
}));
|
|
634
|
+
// When the default mode changes externally (e.g. via AI Configuration),
|
|
635
|
+
// sync the mode selector. Deferred via queueMicrotask so the prompt service's
|
|
636
|
+
// internal state is fully updated before we read agent.modes.
|
|
637
|
+
this.toDispose.push(this.promptService.onSelectedVariantChange(() => {
|
|
638
|
+
queueMicrotask(() => this.syncSelectedModeWithDefault());
|
|
639
|
+
}));
|
|
449
640
|
// Listen for generic capabilities changes
|
|
450
641
|
if (this.genericCapabilitiesService) {
|
|
451
642
|
this.updateAvailableGenericCapabilities();
|
|
@@ -490,6 +681,79 @@ let AIChatInputWidget = class AIChatInputWidget extends browser_2.ReactWidget {
|
|
|
490
681
|
this.chatInputFirstLineKey.set(isFirstVisualOverall);
|
|
491
682
|
this.chatInputLastLineKey.set(isLastVisualOverall);
|
|
492
683
|
}
|
|
684
|
+
/**
|
|
685
|
+
* Resolve the configured token usage warning threshold as an absolute token count.
|
|
686
|
+
* The preference is stored as a percentage of the context window; this method
|
|
687
|
+
* converts it using the current assumed context window size.
|
|
688
|
+
*/
|
|
689
|
+
getTokenUsageWarningThreshold() {
|
|
690
|
+
const percentage = this.getTokenUsageWarningThresholdPercentage();
|
|
691
|
+
return Math.round((percentage / 100) * chat_token_usage_indicator_util_1.CHAT_CONTEXT_WINDOW_SIZE);
|
|
692
|
+
}
|
|
693
|
+
getTokenUsageWarningThresholdPercentage() {
|
|
694
|
+
const value = this.preferenceService?.get(chat_view_preferences_1.CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE, chat_view_preferences_1.CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE_DEFAULT);
|
|
695
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 1 || value > 100) {
|
|
696
|
+
return chat_view_preferences_1.CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE_DEFAULT;
|
|
697
|
+
}
|
|
698
|
+
return value;
|
|
699
|
+
}
|
|
700
|
+
isTokenUsageWarningEnabled() {
|
|
701
|
+
return this.preferenceService?.get(chat_view_preferences_1.CHAT_VIEW_TOKEN_USAGE_WARNING_ENABLED, false) ?? false;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Called after a response changes on the currently attached session. Shows a
|
|
705
|
+
* warning the first time the total crosses the threshold, and re-arms when
|
|
706
|
+
* usage drops back below it.
|
|
707
|
+
*/
|
|
708
|
+
evaluateTokenUsageWarning(chatModel) {
|
|
709
|
+
// No point doing any work if the feature is off.
|
|
710
|
+
if (!this.isTokenUsageWarningEnabled()) {
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
// `responseChanged` fires on every streaming tick, but providers typically
|
|
714
|
+
// only set `tokenUsage` at completion. Skip in-progress responses so we do
|
|
715
|
+
// the walk + decision once per response rather than per chunk.
|
|
716
|
+
const lastRequest = chatModel.getRequests().at(-1);
|
|
717
|
+
if (lastRequest && !lastRequest.response.isComplete) {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
const decision = (0, chat_token_usage_indicator_util_1.decideTokenUsageWarning)({
|
|
721
|
+
totalTokens: (0, chat_token_usage_indicator_util_1.computeSessionTokenUsage)(chatModel),
|
|
722
|
+
threshold: this.getTokenUsageWarningThreshold(),
|
|
723
|
+
alreadyNotified: this.notifiedSessions.has(chatModel.id)
|
|
724
|
+
});
|
|
725
|
+
if (decision === 'reset') {
|
|
726
|
+
this.notifiedSessions.delete(chatModel.id);
|
|
727
|
+
}
|
|
728
|
+
else if (decision === 'notify') {
|
|
729
|
+
this.notifiedSessions.add(chatModel.id);
|
|
730
|
+
this.showTokenUsageWarning();
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
async showTokenUsageWarning() {
|
|
734
|
+
const percentage = this.getTokenUsageWarningThresholdPercentage();
|
|
735
|
+
const message = core_1.nls.localize('theia/ai/chat-ui/tokenUsageWarningMessage', 'Chat session token usage has reached {0}% of the context window. ' +
|
|
736
|
+
'Consider summarizing this session or starting a new one to avoid hitting the limit.', percentage);
|
|
737
|
+
const summarizeAction = core_1.nls.localize('theia/ai/chat-ui/tokenUsageWarningSummarizeAction', 'Summarize Current Session');
|
|
738
|
+
const newSessionAction = core_1.nls.localize('theia/ai/chat-ui/tokenUsageWarningNewSessionAction', 'Start New Chat');
|
|
739
|
+
const openSettingsAction = core_1.nls.localizeByDefault('Open Settings');
|
|
740
|
+
const selected = await this.messageService.warn(message, summarizeAction, newSessionAction, openSettingsAction);
|
|
741
|
+
if (selected === summarizeAction) {
|
|
742
|
+
this.commandService.executeCommand(chat_view_commands_1.ChatCommands.AI_CHAT_NEW_WITH_TASK_CONTEXT.id).catch(error => {
|
|
743
|
+
console.error(`Failed to execute '${chat_view_commands_1.ChatCommands.AI_CHAT_NEW_WITH_TASK_CONTEXT.id}' from token usage warning`, error);
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
else if (selected === newSessionAction) {
|
|
747
|
+
this.commandService.executeCommand(chat_view_commands_1.AI_CHAT_NEW_CHAT_WINDOW_COMMAND.id).catch(error => {
|
|
748
|
+
console.error(`Failed to execute '${chat_view_commands_1.AI_CHAT_NEW_CHAT_WINDOW_COMMAND.id}' from token usage warning`, error);
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
else if (selected === openSettingsAction) {
|
|
752
|
+
this.commandService.executeCommand(browser_2.CommonCommands.OPEN_PREFERENCES.id, chat_view_preferences_1.CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE).catch(error => {
|
|
753
|
+
console.error(`Failed to execute '${browser_2.CommonCommands.OPEN_PREFERENCES.id}' from token usage warning`, error);
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
}
|
|
493
757
|
scheduleUpdateReceivingAgent() {
|
|
494
758
|
if (this.queryInFlight) {
|
|
495
759
|
// Don't update capabilities while a query is being sent — the editor is being
|
|
@@ -571,7 +835,11 @@ let AIChatInputWidget = class AIChatInputWidget extends browser_2.ReactWidget {
|
|
|
571
835
|
if (agent && (agentId !== previousAgentId || needsRefresh)) {
|
|
572
836
|
const modes = agent.modes ?? [];
|
|
573
837
|
const defaultMode = modes.find(m => m.isDefault);
|
|
574
|
-
const
|
|
838
|
+
const hasPreviousRequests = this._chatModel.getRequests().length > 0;
|
|
839
|
+
const restoredModeId = needsRefresh && hasPreviousRequests
|
|
840
|
+
? this.getLastModeIdFromModel(this._chatModel)
|
|
841
|
+
: undefined;
|
|
842
|
+
const initialModeId = restoredModeId ?? defaultMode?.id;
|
|
575
843
|
this.receivingAgent = {
|
|
576
844
|
agentId: agentId,
|
|
577
845
|
modes,
|
|
@@ -579,15 +847,39 @@ let AIChatInputWidget = class AIChatInputWidget extends browser_2.ReactWidget {
|
|
|
579
847
|
};
|
|
580
848
|
this.chatInputHasModesKey.set(modes.length > 1);
|
|
581
849
|
// Only preserve overrides on forced refresh if the session has previous requests
|
|
582
|
-
const hasPreviousRequests = this._chatModel.getRequests().length > 0;
|
|
583
850
|
const shouldPreserveOverrides = needsRefresh && hasPreviousRequests;
|
|
584
851
|
await this.updateCapabilitiesForAgent(agentId, initialModeId, shouldPreserveOverrides);
|
|
852
|
+
this.updateReasoningSupport(agentId);
|
|
585
853
|
}
|
|
586
854
|
else if (!agent && this.receivingAgent !== undefined) {
|
|
587
855
|
this.receivingAgent = undefined;
|
|
588
856
|
this.capabilityDefaults = [];
|
|
589
857
|
this.userCapabilityOverrides = new Map();
|
|
590
858
|
this.chatInputHasModesKey.set(false);
|
|
859
|
+
this.currentReasoningSupport = undefined;
|
|
860
|
+
this.update();
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Syncs the selected mode in the UI with the agent's current default mode.
|
|
865
|
+
* Called when the default mode changes externally (e.g. via AI Configuration).
|
|
866
|
+
*/
|
|
867
|
+
syncSelectedModeWithDefault() {
|
|
868
|
+
if (!this.receivingAgent) {
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
const agent = this.chatAgentService.getAgent(this.receivingAgent.agentId);
|
|
872
|
+
if (!agent?.modes) {
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
const updatedModes = agent.modes;
|
|
876
|
+
const newDefault = updatedModes.find(m => m.isDefault);
|
|
877
|
+
if (newDefault && newDefault.id !== this.receivingAgent.currentModeId) {
|
|
878
|
+
this.receivingAgent = {
|
|
879
|
+
...this.receivingAgent,
|
|
880
|
+
modes: updatedModes,
|
|
881
|
+
currentModeId: newDefault.id
|
|
882
|
+
};
|
|
591
883
|
this.update();
|
|
592
884
|
}
|
|
593
885
|
}
|
|
@@ -698,6 +990,10 @@ let AIChatInputWidget = class AIChatInputWidget extends browser_2.ReactWidget {
|
|
|
698
990
|
currentMode: this.receivingAgent?.currentModeId,
|
|
699
991
|
onModeChange: this.handleModeChange,
|
|
700
992
|
keybindingHint: this.getModeKeybindingHint(),
|
|
993
|
+
}, reasoningSelectorProps: {
|
|
994
|
+
reasoningSupport: this.currentReasoningSupport,
|
|
995
|
+
currentLevel: this.getCurrentReasoningLevel(),
|
|
996
|
+
onReasoningChange: this.handleReasoningChange,
|
|
701
997
|
}, capabilitiesProps: {
|
|
702
998
|
capabilities: this.capabilityDefaults,
|
|
703
999
|
overrides: this.userCapabilityOverrides,
|
|
@@ -714,7 +1010,7 @@ let AIChatInputWidget = class AIChatInputWidget extends browser_2.ReactWidget {
|
|
|
714
1010
|
availableCapabilities: this.availableGenericCapabilities,
|
|
715
1011
|
disabledCapabilities: this.disabledGenericCapabilities,
|
|
716
1012
|
hoverService: this.hoverService,
|
|
717
|
-
} }));
|
|
1013
|
+
}, tokenUsageEnabled: this.tokenUsageEnabled, tokenUsageWarningThreshold: this.getTokenUsageWarningThreshold() }));
|
|
718
1014
|
}
|
|
719
1015
|
onDragOver(event) {
|
|
720
1016
|
event.preventDefault();
|
|
@@ -1051,6 +1347,27 @@ tslib_1.__decorate([
|
|
|
1051
1347
|
(0, inversify_1.inject)(common_1.AISettingsService),
|
|
1052
1348
|
tslib_1.__metadata("design:type", Object)
|
|
1053
1349
|
], AIChatInputWidget.prototype, "aiSettingsService", void 0);
|
|
1350
|
+
tslib_1.__decorate([
|
|
1351
|
+
(0, inversify_1.inject)(common_1.PromptService),
|
|
1352
|
+
tslib_1.__metadata("design:type", Object)
|
|
1353
|
+
], AIChatInputWidget.prototype, "promptService", void 0);
|
|
1354
|
+
tslib_1.__decorate([
|
|
1355
|
+
(0, inversify_1.inject)(ai_core_1.FrontendLanguageModelRegistry),
|
|
1356
|
+
tslib_1.__metadata("design:type", Object)
|
|
1357
|
+
], AIChatInputWidget.prototype, "languageModelRegistry", void 0);
|
|
1358
|
+
tslib_1.__decorate([
|
|
1359
|
+
(0, inversify_1.inject)(preferences_1.PreferenceService),
|
|
1360
|
+
(0, inversify_1.optional)(),
|
|
1361
|
+
tslib_1.__metadata("design:type", Object)
|
|
1362
|
+
], AIChatInputWidget.prototype, "preferenceService", void 0);
|
|
1363
|
+
tslib_1.__decorate([
|
|
1364
|
+
(0, inversify_1.inject)(core_1.MessageService),
|
|
1365
|
+
tslib_1.__metadata("design:type", core_1.MessageService)
|
|
1366
|
+
], AIChatInputWidget.prototype, "messageService", void 0);
|
|
1367
|
+
tslib_1.__decorate([
|
|
1368
|
+
(0, inversify_1.inject)(core_1.CommandService),
|
|
1369
|
+
tslib_1.__metadata("design:type", Object)
|
|
1370
|
+
], AIChatInputWidget.prototype, "commandService", void 0);
|
|
1054
1371
|
tslib_1.__decorate([
|
|
1055
1372
|
(0, inversify_1.postConstruct)(),
|
|
1056
1373
|
tslib_1.__metadata("design:type", Function),
|
|
@@ -1413,22 +1730,37 @@ const ChatInput = (props) => {
|
|
|
1413
1730
|
const contextUI = buildContextUI(props.context, props.labelProvider, props.onDeleteContextElement, props.onOpenContextElement, props.fileValidationState);
|
|
1414
1731
|
// Show mode selector if agent has multiple modes
|
|
1415
1732
|
const showModeSelector = (props.modeSelectorProps.receivingAgentModes?.length ?? 0) > 1;
|
|
1733
|
+
// Token usage computation (cheap pure function walking the model's request list)
|
|
1734
|
+
const totalTokens = props.tokenUsageEnabled ? (0, chat_token_usage_indicator_util_1.computeSessionTokenUsage)(props.chatModel) : 0;
|
|
1735
|
+
const showTokenUsage = props.tokenUsageEnabled && totalTokens > 0;
|
|
1736
|
+
const tokenColorClass = showTokenUsage ? (0, chat_token_usage_indicator_util_1.getUsageColorClass)(totalTokens, props.tokenUsageWarningThreshold) : '';
|
|
1737
|
+
const tokenIsWarningOrError = tokenColorClass === 'token-usage-yellow' || tokenColorClass === 'token-usage-red';
|
|
1738
|
+
const tokenTooltip = showTokenUsage ? (0, chat_token_usage_indicator_util_1.buildBarTooltip)((0, chat_token_usage_indicator_util_1.getLatestTokenUsage)(props.chatModel), totalTokens, props.tokenUsageWarningThreshold) : undefined;
|
|
1416
1739
|
return (React.createElement("div", { className: "theia-ChatInput", "data-ai-disabled": !props.isEnabled, onDragOver: props.onDragOver, onDrop: props.onDrop, ref: containerRef },
|
|
1417
1740
|
props.showSuggestions !== false && React.createElement(chat_input_agent_suggestions_1.ChatInputAgentSuggestions, { suggestions: props.suggestions, opener: props.openerService }),
|
|
1418
1741
|
props.showChangeSet && changeSetUI?.elements &&
|
|
1419
1742
|
React.createElement(ChangeSetBox, { changeSet: changeSetUI }),
|
|
1420
|
-
React.createElement("div", { className:
|
|
1743
|
+
React.createElement("div", { className: `theia-ChatInput-Editor-Box${tokenIsWarningOrError ? ` token-usage-border-${tokenColorClass}` : ''}` },
|
|
1421
1744
|
props.showCapabilities !== false && (React.createElement(CapabilitiesBar, { isOpen: props.capabilitiesProps.isOpen, capabilities: props.capabilitiesProps.capabilities, overrides: props.capabilitiesProps.overrides, onCapabilityChange: props.capabilitiesProps.onCapabilityChange, genericCapabilities: props.genericCapabilitiesProps.genericCapabilities, onGenericCapabilityChange: props.genericCapabilitiesProps.onGenericCapabilityChange, onResetGenericCapabilities: props.genericCapabilitiesProps.onResetGenericCapabilities, availableCapabilities: props.genericCapabilitiesProps.availableCapabilities, disabledCapabilities: props.genericCapabilitiesProps.disabledCapabilities, disabled: !props.isEnabled, hoverService: props.hoverService, hasUnsavedChanges: props.capabilitiesProps.hasUnsavedChanges, onSaveToSettings: props.capabilitiesProps.onSaveToSettings })),
|
|
1422
1745
|
React.createElement("div", { className: 'theia-ChatInput-Editor', ref: editorContainerRef, onKeyDown: onKeyDown, onFocus: handleInputFocus, onBlur: handleInputBlur },
|
|
1423
1746
|
React.createElement("div", { ref: placeholderRef, className: 'theia-ChatInput-Editor-Placeholder' }, placeholderText)),
|
|
1424
1747
|
props.context && props.context.length > 0 &&
|
|
1425
1748
|
React.createElement(ChatContext, { context: contextUI.context }),
|
|
1426
|
-
React.createElement(ChatInputOptions, { leftOptions: leftOptions, rightOptions: rightOptions, isEnabled: props.isEnabled, hoverService: props.hoverService,
|
|
1749
|
+
React.createElement(ChatInputOptions, { leftOptions: leftOptions, rightOptions: rightOptions, isEnabled: props.isEnabled, hoverService: props.hoverService, tokenUsage: showTokenUsage ? {
|
|
1750
|
+
percent: Math.min((totalTokens / chat_token_usage_indicator_util_1.CHAT_CONTEXT_WINDOW_SIZE) * 100, 100),
|
|
1751
|
+
colorClass: tokenColorClass,
|
|
1752
|
+
tooltip: tokenTooltip,
|
|
1753
|
+
} : undefined, modeSelectorProps: {
|
|
1427
1754
|
show: showModeSelector,
|
|
1428
1755
|
modes: props.modeSelectorProps.receivingAgentModes,
|
|
1429
1756
|
currentMode: props.modeSelectorProps.currentMode,
|
|
1430
1757
|
onModeChange: props.modeSelectorProps.onModeChange,
|
|
1431
1758
|
keybindingHint: props.modeSelectorProps.keybindingHint,
|
|
1759
|
+
}, reasoningSelectorProps: {
|
|
1760
|
+
show: !!props.reasoningSelectorProps.reasoningSupport,
|
|
1761
|
+
reasoningSupport: props.reasoningSelectorProps.reasoningSupport,
|
|
1762
|
+
currentLevel: props.reasoningSelectorProps.currentLevel,
|
|
1763
|
+
onReasoningChange: props.reasoningSelectorProps.onReasoningChange,
|
|
1432
1764
|
}, capabilitiesToggle: {
|
|
1433
1765
|
show: props.showCapabilities !== false,
|
|
1434
1766
|
isOpen: props.capabilitiesProps.isOpen,
|
|
@@ -1442,16 +1774,16 @@ const ChatInput = (props) => {
|
|
|
1442
1774
|
/**
|
|
1443
1775
|
* Returns an onMouseEnter handler that shows a hover tooltip via HoverService.
|
|
1444
1776
|
*/
|
|
1445
|
-
function hoverHandler(hoverService, content) {
|
|
1777
|
+
function hoverHandler(hoverService, content, position = 'bottom') {
|
|
1446
1778
|
return (e) => {
|
|
1447
1779
|
hoverService.requestHover({
|
|
1448
1780
|
content,
|
|
1449
1781
|
target: e.currentTarget,
|
|
1450
|
-
position
|
|
1782
|
+
position
|
|
1451
1783
|
});
|
|
1452
1784
|
};
|
|
1453
1785
|
}
|
|
1454
|
-
const ChatInputOptions = ({ leftOptions, rightOptions, isEnabled, hoverService, modeSelectorProps, capabilitiesToggle }) => {
|
|
1786
|
+
const ChatInputOptions = ({ leftOptions, rightOptions, isEnabled, hoverService, tokenUsage, modeSelectorProps, reasoningSelectorProps, capabilitiesToggle }) => {
|
|
1455
1787
|
const capabilitiesLabel = core_1.nls.localize('theia/ai/chat-ui/toggleCapabilitiesConfig', 'Toggle Capabilities Configuration');
|
|
1456
1788
|
const capabilitiesTitle = capabilitiesToggle.keybindingHint
|
|
1457
1789
|
? `${capabilitiesLabel} (${capabilitiesToggle.keybindingHint})`
|
|
@@ -1460,14 +1792,20 @@ const ChatInputOptions = ({ leftOptions, rightOptions, isEnabled, hoverService,
|
|
|
1460
1792
|
// Right options are rendered first in DOM for tab order (send button first when enabled)
|
|
1461
1793
|
// CSS order property positions them visually (left on left, right on right)
|
|
1462
1794
|
React.createElement("div", { className: "theia-ChatInputOptions" },
|
|
1463
|
-
React.createElement("div", { className: "theia-ChatInputOptions-right" },
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
React.createElement("span",
|
|
1470
|
-
|
|
1795
|
+
React.createElement("div", { className: "theia-ChatInputOptions-right" },
|
|
1796
|
+
tokenUsage && (React.createElement("span", { className: `token-usage-badge ${tokenUsage.colorClass}`, ...(tokenUsage.tooltip && { onMouseEnter: hoverHandler(hoverService, tokenUsage.tooltip, 'top') }) },
|
|
1797
|
+
React.createElement("span", { className: 'token-usage-ring', style: {
|
|
1798
|
+
background: `conic-gradient(var(--token-usage-fill) ${tokenUsage.percent}%, var(--token-usage-track) ${tokenUsage.percent}%)`
|
|
1799
|
+
} },
|
|
1800
|
+
React.createElement("span", { className: 'token-usage-ring-inner' })))),
|
|
1801
|
+
rightOptions.map((option, index) => (React.createElement("span", { key: index, className: `option${option.disabled ? ' disabled' : ''}${option.text?.align === 'right' ? ' reverse' : ''}`, "aria-label": option.title, role: 'button', tabIndex: option.disabled ? -1 : 0, onClick: option.handler, onMouseEnter: hoverHandler(hoverService, option.title), onKeyDown: e => {
|
|
1802
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
1803
|
+
e.preventDefault();
|
|
1804
|
+
option.handler();
|
|
1805
|
+
}
|
|
1806
|
+
} },
|
|
1807
|
+
React.createElement("span", null, option.text?.content),
|
|
1808
|
+
React.createElement("span", { className: `codicon ${option.className}` }))))),
|
|
1471
1809
|
React.createElement("div", { className: "theia-ChatInputOptions-left" },
|
|
1472
1810
|
leftOptions.map((option, index) => (React.createElement("span", { key: index, className: `option${option.disabled ? ' disabled' : ''}${option.text?.align === 'right' ? ' reverse' : ''}`, "aria-label": option.title, role: 'button', tabIndex: option.disabled ? -1 : 0, onClick: option.handler, onMouseEnter: hoverHandler(hoverService, option.title), onKeyDown: e => {
|
|
1473
1811
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
@@ -1485,7 +1823,8 @@ const ChatInputOptions = ({ leftOptions, rightOptions, isEnabled, hoverService,
|
|
|
1485
1823
|
}
|
|
1486
1824
|
} },
|
|
1487
1825
|
React.createElement("span", { className: "codicon codicon-tools" }),
|
|
1488
|
-
capabilitiesToggle.hasUnsavedChanges && (React.createElement("span", { className: "theia-capabilities-unsaved-indicator" }))))
|
|
1826
|
+
capabilitiesToggle.hasUnsavedChanges && (React.createElement("span", { className: "theia-capabilities-unsaved-indicator" })))),
|
|
1827
|
+
reasoningSelectorProps.show && reasoningSelectorProps.reasoningSupport && (React.createElement(ReasoningSelector, { reasoningSupport: reasoningSelectorProps.reasoningSupport, currentLevel: reasoningSelectorProps.currentLevel, onReasoningChange: reasoningSelectorProps.onReasoningChange, disabled: !isEnabled, hoverService: hoverService })))));
|
|
1489
1828
|
};
|
|
1490
1829
|
/**
|
|
1491
1830
|
* Combined capabilities bar that shows:
|
|
@@ -1530,6 +1869,28 @@ const ChatModeSelector = React.memo(({ modes, currentMode, onModeChange, disable
|
|
|
1530
1869
|
return (React.createElement("span", { onMouseEnter: hoverHandler(hoverService, title) },
|
|
1531
1870
|
React.createElement(select_component_1.SelectComponent, { className: `theia-ChatInput-ModeSelector${disabled ? ' disabled' : ''}`, options: options, defaultValue: currentMode ?? modes[0]?.id ?? '', onChange: handleChange })));
|
|
1532
1871
|
});
|
|
1872
|
+
const reasoningLevelLabel = (level) => {
|
|
1873
|
+
switch (level) {
|
|
1874
|
+
case 'off': return core_1.nls.localizeByDefault('Off');
|
|
1875
|
+
case 'minimal': return core_1.nls.localize('theia/ai/chat-ui/reasoning/minimal', 'Minimal');
|
|
1876
|
+
case 'low': return core_1.nls.localize('theia/ai/chat-ui/reasoning/low', 'Low');
|
|
1877
|
+
case 'medium': return core_1.nls.localize('theia/ai/chat-ui/reasoning/medium', 'Medium');
|
|
1878
|
+
case 'high': return core_1.nls.localize('theia/ai/chat-ui/reasoning/high', 'High');
|
|
1879
|
+
case 'auto': return core_1.nls.localizeByDefault('Auto');
|
|
1880
|
+
}
|
|
1881
|
+
};
|
|
1882
|
+
const ReasoningSelector = React.memo(({ reasoningSupport, currentLevel, onReasoningChange, disabled, hoverService }) => {
|
|
1883
|
+
const options = React.useMemo(() => reasoningSupport.supportedLevels.map(level => ({ value: level, label: reasoningLevelLabel(level) })), [reasoningSupport]);
|
|
1884
|
+
const handleChange = React.useCallback((option) => {
|
|
1885
|
+
if (option.value) {
|
|
1886
|
+
onReasoningChange(option.value);
|
|
1887
|
+
}
|
|
1888
|
+
}, [onReasoningChange]);
|
|
1889
|
+
const title = core_1.nls.localizeByDefault('Reasoning');
|
|
1890
|
+
const effectiveLevel = currentLevel ?? reasoningSupport.defaultLevel ?? reasoningSupport.supportedLevels[0] ?? 'off';
|
|
1891
|
+
return (React.createElement("span", { onMouseEnter: hoverHandler(hoverService, title) },
|
|
1892
|
+
React.createElement(select_component_1.SelectComponent, { className: `theia-ChatInput-ReasoningSelector reasoning-level-${effectiveLevel}${disabled ? ' disabled' : ''}`, options: options, defaultValue: effectiveLevel, onChange: handleChange })));
|
|
1893
|
+
});
|
|
1533
1894
|
const noPropagation = (handler) => (e) => {
|
|
1534
1895
|
handler();
|
|
1535
1896
|
e.stopPropagation();
|