@theia/ai-chat-ui 1.71.0-next.8 → 1.71.1
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
|
@@ -16,18 +16,24 @@
|
|
|
16
16
|
import {
|
|
17
17
|
ChangeSet, ChangeSetElement, ChatAgent, ChatChangeEvent, ChatHierarchyBranch,
|
|
18
18
|
ChatModel, ChatRequestModel, ChatService, ChatSuggestion, EditableChatRequestModel,
|
|
19
|
-
ChatRequestParser, ChatMode, ChatSession
|
|
19
|
+
ChatRequestParser, ChatMode, ChatSession, MutableChatModel, ChatSessionSettings
|
|
20
20
|
} from '@theia/ai-chat';
|
|
21
21
|
import { ChatAgentService } from '@theia/ai-chat/lib/common/chat-agent-service';
|
|
22
22
|
import { ParsedChatRequest } from '@theia/ai-chat/lib/common/parsed-chat-request';
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
GenericCapabilitySelections, AIVariableResolutionRequest, ParsedCapability,
|
|
25
|
+
FrontendLanguageModelRegistry, ReasoningLevel, ReasoningSettings, ReasoningSupport,
|
|
26
|
+
PREFERENCE_NAME_REASONING, ReasoningPreferenceEntry
|
|
27
|
+
} from '@theia/ai-core';
|
|
28
|
+
import { mergeReasoningSettings } from '@theia/ai-core/lib/browser/frontend-language-model-service';
|
|
24
29
|
import { ChangeSetDecoratorService } from '@theia/ai-chat/lib/browser/change-set-decorator-service';
|
|
25
30
|
import { ImageContextVariable } from '@theia/ai-chat/lib/common/image-context-variable';
|
|
26
31
|
import { AgentCompletionNotificationService, FrontendVariableService, AIActivationService, CompletionNotificationOptions } from '@theia/ai-core/lib/browser';
|
|
27
|
-
import { AISettingsService } from '@theia/ai-core/lib/common';
|
|
32
|
+
import { AISettingsService, PromptService } from '@theia/ai-core/lib/common';
|
|
28
33
|
import { ApplicationShell } from '@theia/core/lib/browser/shell/application-shell';
|
|
29
|
-
import { DisposableCollection, Emitter, InMemoryResources, URI, nls, Disposable } from '@theia/core';
|
|
30
|
-
import { ContextMenuRenderer, HoverService, LabelProvider, Message, OpenerService, ReactWidget } from '@theia/core/lib/browser';
|
|
34
|
+
import { CommandService, DisposableCollection, Emitter, InMemoryResources, MessageService, URI, nls, Disposable } from '@theia/core';
|
|
35
|
+
import { CommonCommands, ContextMenuRenderer, HoverService, LabelProvider, Message, OpenerService, ReactWidget } from '@theia/core/lib/browser';
|
|
36
|
+
import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
|
|
31
37
|
import { SelectComponent, SelectOption } from '@theia/core/lib/browser/widgets/select-component';
|
|
32
38
|
import { ContextKey, ContextKeyService } from '@theia/core/lib/browser/context-key-service';
|
|
33
39
|
import { KeybindingRegistry } from '@theia/core/lib/browser/keybinding';
|
|
@@ -52,6 +58,22 @@ import { CapabilityChip, CapabilityChipsRow } from './chat-capabilities-panel';
|
|
|
52
58
|
import { ChatInputFocusService } from './chat-input-focus-service';
|
|
53
59
|
import { AvailableGenericCapabilities, GenericCapabilitiesService } from './generic-capabilities-service';
|
|
54
60
|
import { GenericCapabilitiesSection } from './generic-capabilities-section';
|
|
61
|
+
import { PreferenceService } from '@theia/core/lib/common/preferences';
|
|
62
|
+
import {
|
|
63
|
+
CHAT_VIEW_TOKEN_USAGE_ENABLED,
|
|
64
|
+
CHAT_VIEW_TOKEN_USAGE_WARNING_ENABLED,
|
|
65
|
+
CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE,
|
|
66
|
+
CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE_DEFAULT
|
|
67
|
+
} from './chat-view-preferences';
|
|
68
|
+
import {
|
|
69
|
+
buildBarTooltip,
|
|
70
|
+
CHAT_CONTEXT_WINDOW_SIZE,
|
|
71
|
+
computeSessionTokenUsage,
|
|
72
|
+
decideTokenUsageWarning,
|
|
73
|
+
getLatestTokenUsage,
|
|
74
|
+
getUsageColorClass
|
|
75
|
+
} from './chat-token-usage-indicator-util';
|
|
76
|
+
import { AI_CHAT_NEW_CHAT_WINDOW_COMMAND, ChatCommands } from './chat-view-commands';
|
|
55
77
|
|
|
56
78
|
type Query = (query: string, mode?: string, capabilityOverrides?: Record<string, boolean>, genericCapabilitySelections?: GenericCapabilitySelections) => Promise<void>;
|
|
57
79
|
type Unpin = () => void;
|
|
@@ -155,6 +177,25 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
155
177
|
@inject(AISettingsService)
|
|
156
178
|
protected readonly aiSettingsService: AISettingsService;
|
|
157
179
|
|
|
180
|
+
@inject(PromptService)
|
|
181
|
+
protected readonly promptService: PromptService;
|
|
182
|
+
|
|
183
|
+
@inject(FrontendLanguageModelRegistry)
|
|
184
|
+
protected readonly languageModelRegistry: FrontendLanguageModelRegistry;
|
|
185
|
+
|
|
186
|
+
@inject(PreferenceService) @optional()
|
|
187
|
+
protected readonly preferenceService: PreferenceService | undefined;
|
|
188
|
+
|
|
189
|
+
@inject(MessageService)
|
|
190
|
+
protected readonly messageService: MessageService;
|
|
191
|
+
|
|
192
|
+
@inject(CommandService)
|
|
193
|
+
protected readonly commandService: CommandService;
|
|
194
|
+
|
|
195
|
+
protected tokenUsageEnabled = false;
|
|
196
|
+
/** Sessions we have already notified for the current warning cycle (re-armed when usage drops below the threshold). */
|
|
197
|
+
protected readonly notifiedSessions = new Set<string>();
|
|
198
|
+
|
|
158
199
|
protected navigationState: ChatInputNavigationState;
|
|
159
200
|
|
|
160
201
|
protected editorRef: SimpleMonacoEditor | undefined = undefined;
|
|
@@ -221,6 +262,101 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
221
262
|
}
|
|
222
263
|
};
|
|
223
264
|
|
|
265
|
+
/** Reasoning capability of the model the receiving agent would currently use; undefined hides the selector. */
|
|
266
|
+
protected currentReasoningSupport?: ReasoningSupport;
|
|
267
|
+
/** Id (`provider/model`) of the model that backs {@link currentReasoningSupport}; used to resolve preference defaults. */
|
|
268
|
+
protected currentLanguageModelId?: string;
|
|
269
|
+
/** Saved reasoning selection for the receiving agent (loaded from {@link AISettingsService}); kept in sync with the persisted value. */
|
|
270
|
+
protected savedReasoning?: ReasoningSettings;
|
|
271
|
+
|
|
272
|
+
protected handleReasoningChange = async (level: ReasoningLevel): Promise<void> => {
|
|
273
|
+
const session = this.chatService.getSessions().find(s => s.model.id === this._chatModel?.id);
|
|
274
|
+
if (!session) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const currentSettings = session.model.settings ?? {};
|
|
278
|
+
const newSettings: ChatSessionSettings = {
|
|
279
|
+
...currentSettings,
|
|
280
|
+
commonSettings: {
|
|
281
|
+
...currentSettings.commonSettings,
|
|
282
|
+
reasoning: { level }
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
(session.model as MutableChatModel).setSettings(newSettings);
|
|
286
|
+
|
|
287
|
+
// Auto-persist the reasoning selection per-agent so it is restored on the next session
|
|
288
|
+
// and the capabilities indicator does not light up for an unrelated configuration concern.
|
|
289
|
+
if (this.receivingAgent) {
|
|
290
|
+
try {
|
|
291
|
+
await this.aiSettingsService.updateAgentSettings(this.receivingAgent.agentId, {
|
|
292
|
+
reasoning: { level }
|
|
293
|
+
});
|
|
294
|
+
this.savedReasoning = { level };
|
|
295
|
+
} catch (error) {
|
|
296
|
+
console.error('Failed to persist reasoning selection:', error);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
this.update();
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Resolves the reasoning level to display in the selector. Priority: session override →
|
|
305
|
+
* persisted per-agent selection (from {@link AISettingsService}) →
|
|
306
|
+
* `ai-features.reasoning.defaults` preference entry matching the current model/agent →
|
|
307
|
+
* model's declared default → `'off'`.
|
|
308
|
+
*/
|
|
309
|
+
protected getCurrentReasoningLevel(): ReasoningLevel | undefined {
|
|
310
|
+
if (!this.currentReasoningSupport) {
|
|
311
|
+
return undefined;
|
|
312
|
+
}
|
|
313
|
+
const session = this.chatService.getSessions().find(s => s.model.id === this._chatModel?.id);
|
|
314
|
+
const sessionLevel = session?.model.settings?.commonSettings?.reasoning?.level;
|
|
315
|
+
if (sessionLevel) {
|
|
316
|
+
return sessionLevel;
|
|
317
|
+
}
|
|
318
|
+
if (this.savedReasoning?.level) {
|
|
319
|
+
return this.savedReasoning.level;
|
|
320
|
+
}
|
|
321
|
+
return this.resolvePreferenceReasoningLevel() ?? this.currentReasoningSupport.defaultLevel ?? 'off';
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
protected resolvePreferenceReasoningLevel(): ReasoningLevel | undefined {
|
|
325
|
+
if (!this.preferenceService || !this.currentLanguageModelId) {
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
const entries = this.preferenceService.get<ReasoningPreferenceEntry[]>(PREFERENCE_NAME_REASONING, []);
|
|
329
|
+
const [providerId, modelId] = this.currentLanguageModelId.split('/');
|
|
330
|
+
return mergeReasoningSettings(entries, modelId, providerId, this.receivingAgent?.agentId)?.reasoning?.level;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
protected async updateReasoningSupport(agentId: string | undefined): Promise<void> {
|
|
334
|
+
let support: ReasoningSupport | undefined;
|
|
335
|
+
let modelId: string | undefined;
|
|
336
|
+
if (agentId) {
|
|
337
|
+
const agent = this.chatAgentService.getAgent(agentId);
|
|
338
|
+
if (agent) {
|
|
339
|
+
for (const requirement of agent.languageModelRequirements ?? []) {
|
|
340
|
+
try {
|
|
341
|
+
const model = await this.languageModelRegistry.selectLanguageModel({ agent: agent.id, ...requirement });
|
|
342
|
+
if (model?.reasoningSupport) {
|
|
343
|
+
support = model.reasoningSupport;
|
|
344
|
+
modelId = model.id;
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
} catch (error) {
|
|
348
|
+
console.warn('Failed to resolve language model for reasoning support:', error);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (support !== this.currentReasoningSupport || modelId !== this.currentLanguageModelId) {
|
|
354
|
+
this.currentReasoningSupport = support;
|
|
355
|
+
this.currentLanguageModelId = modelId;
|
|
356
|
+
this.update();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
224
360
|
protected handleCapabilityChange = (fragmentId: string, enabled: boolean): void => {
|
|
225
361
|
const defaultCapability = this.capabilityDefaults.find(c => c.fragmentId === fragmentId);
|
|
226
362
|
const sessionOverrides = new Map(this.userCapabilityOverrides);
|
|
@@ -259,16 +395,21 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
259
395
|
const agentSettings = await this.aiSettingsService.getAgentSettings(agentId);
|
|
260
396
|
const savedOverrides = agentSettings?.capabilityOverrides;
|
|
261
397
|
const savedGenericSelections = agentSettings?.genericCapabilitySelections;
|
|
398
|
+
const savedReasoning = agentSettings?.reasoning;
|
|
262
399
|
|
|
263
400
|
// Store saved state for comparison
|
|
264
401
|
this.savedCapabilityOverrides = savedOverrides ? { ...savedOverrides } : undefined;
|
|
265
402
|
this.savedGenericCapabilitySelections = savedGenericSelections ? { ...savedGenericSelections } : undefined;
|
|
403
|
+
this.savedReasoning = savedReasoning ? { ...savedReasoning } : undefined;
|
|
266
404
|
|
|
267
405
|
// Initialize from saved settings, or empty if none
|
|
268
406
|
this.userCapabilityOverrides = savedOverrides
|
|
269
407
|
? new Map(Object.entries(savedOverrides))
|
|
270
408
|
: new Map<string, boolean>();
|
|
271
409
|
this.genericCapabilitySelections = savedGenericSelections ?? {};
|
|
410
|
+
// Mirror the saved per-agent reasoning into the chat session so the selector reflects it
|
|
411
|
+
// immediately on session/agent switch.
|
|
412
|
+
this.applyReasoningToSession(savedReasoning);
|
|
272
413
|
}
|
|
273
414
|
|
|
274
415
|
// Update disabled generic capabilities (already used in agent prompt)
|
|
@@ -277,6 +418,26 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
277
418
|
this.update();
|
|
278
419
|
}
|
|
279
420
|
|
|
421
|
+
/** Updates the active chat session's `commonSettings.reasoning`; pass `undefined` to clear. */
|
|
422
|
+
protected applyReasoningToSession(reasoning: ReasoningSettings | undefined): void {
|
|
423
|
+
const session = this.chatService.getSessions().find(s => s.model.id === this._chatModel?.id);
|
|
424
|
+
if (!session) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
const currentSettings = session.model.settings ?? {};
|
|
428
|
+
const currentCommon = currentSettings.commonSettings ?? {};
|
|
429
|
+
if ((currentCommon.reasoning?.level ?? undefined) === (reasoning?.level ?? undefined)) {
|
|
430
|
+
return; // no-op when already in sync
|
|
431
|
+
}
|
|
432
|
+
const newCommon: typeof currentCommon = { ...currentCommon };
|
|
433
|
+
if (reasoning) {
|
|
434
|
+
newCommon.reasoning = { ...reasoning };
|
|
435
|
+
} else {
|
|
436
|
+
delete newCommon.reasoning;
|
|
437
|
+
}
|
|
438
|
+
(session.model as MutableChatModel).setSettings({ ...currentSettings, commonSettings: newCommon });
|
|
439
|
+
}
|
|
440
|
+
|
|
280
441
|
protected async updateAvailableGenericCapabilities(): Promise<void> {
|
|
281
442
|
if (!this.genericCapabilitiesService) {
|
|
282
443
|
return;
|
|
@@ -313,6 +474,19 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
313
474
|
return new Map(Object.entries(overrides));
|
|
314
475
|
}
|
|
315
476
|
|
|
477
|
+
/**
|
|
478
|
+
* Extracts the mode ID from the last request in the chat model.
|
|
479
|
+
* Used to restore the user's selected mode when switching sessions or on reload.
|
|
480
|
+
*/
|
|
481
|
+
protected getLastModeIdFromModel(chatModel: ChatModel): string | undefined {
|
|
482
|
+
const requests = chatModel.getRequests();
|
|
483
|
+
if (requests.length === 0) {
|
|
484
|
+
return undefined;
|
|
485
|
+
}
|
|
486
|
+
const lastRequest = requests[requests.length - 1];
|
|
487
|
+
return lastRequest.request.modeId;
|
|
488
|
+
}
|
|
489
|
+
|
|
316
490
|
/**
|
|
317
491
|
* Extracts generic capability selections from the last request in the chat model.
|
|
318
492
|
* Used to restore user's selections when switching sessions or on reload.
|
|
@@ -393,13 +567,19 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
393
567
|
|
|
394
568
|
/**
|
|
395
569
|
* Checks if there are any unsaved changes (capability overrides or generic selections).
|
|
570
|
+
* Reasoning is auto-persisted in {@link handleReasoningChange} and is intentionally excluded.
|
|
396
571
|
*/
|
|
397
572
|
public hasAnyChangesFromSaved(): boolean {
|
|
398
|
-
|
|
573
|
+
if (this.receivingAgent === undefined) {
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
return this.hasCapabilityChangesFromSaved()
|
|
577
|
+
|| this.hasGenericCapabilityChangesFromSaved();
|
|
399
578
|
}
|
|
400
579
|
|
|
401
580
|
/**
|
|
402
581
|
* Saves current capability selections to settings.
|
|
582
|
+
* Reasoning is auto-persisted via {@link handleReasoningChange} and is not part of this flow.
|
|
403
583
|
*/
|
|
404
584
|
public async saveCurrentSelectionsToSettings(): Promise<void> {
|
|
405
585
|
if (!this.receivingAgent) {
|
|
@@ -565,7 +745,11 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
565
745
|
// Restore capability overrides and generic selections from the last request in this session (if any)
|
|
566
746
|
this.userCapabilityOverrides = this.getLastCapabilityOverridesFromModel(chatModel);
|
|
567
747
|
this.genericCapabilitySelections = this.getLastGenericCapabilitySelectionsFromModel(chatModel);
|
|
748
|
+
|
|
568
749
|
this.onDisposeForChatModel.push(chatModel.onDidChange(event => {
|
|
750
|
+
if (event.kind === 'responseChanged') {
|
|
751
|
+
this.evaluateTokenUsageWarning(chatModel);
|
|
752
|
+
}
|
|
569
753
|
if (event.kind === 'addVariable') {
|
|
570
754
|
// Validate files added via any path (including LLM tool calls)
|
|
571
755
|
// Get the current variables and validate any new file variables
|
|
@@ -589,6 +773,16 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
589
773
|
}
|
|
590
774
|
}));
|
|
591
775
|
this._chatModel = chatModel;
|
|
776
|
+
// Evaluate the warning on attach. `notifiedSessions` lives on this widget
|
|
777
|
+
// instance, so the warning fires at most once per (widget lifetime × session):
|
|
778
|
+
// - Within the same widget, switching between sessions that have already been
|
|
779
|
+
// notified does not re-notify.
|
|
780
|
+
// - Closing and reopening the chat view creates a fresh widget with an empty
|
|
781
|
+
// Set, so sessions still above the threshold will be warned about again the
|
|
782
|
+
// first time they are shown after reopen — once per session. Accepted as a
|
|
783
|
+
// rare corner case; promoting the state to the ChatSession would avoid it
|
|
784
|
+
// but isn't worth the coupling today.
|
|
785
|
+
this.evaluateTokenUsageWarning(chatModel);
|
|
592
786
|
this.scheduleUpdateReceivingAgent();
|
|
593
787
|
this.update();
|
|
594
788
|
}
|
|
@@ -607,8 +801,8 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
607
801
|
this.id = AIChatInputWidget.ID;
|
|
608
802
|
this.title.closable = false;
|
|
609
803
|
this.toDispose.push(this.resources.add(this.getResourceUri(), ''));
|
|
610
|
-
this.toDispose.push(this.aiActivationService.
|
|
611
|
-
this.setEnabled(this.aiActivationService.
|
|
804
|
+
this.toDispose.push(this.aiActivationService.onDidChangeCanRun(() => {
|
|
805
|
+
this.setEnabled(this.aiActivationService.canRun);
|
|
612
806
|
}));
|
|
613
807
|
this.toDispose.push(this.chatAgentService.onDefaultAgentChanged(() => {
|
|
614
808
|
this.scheduleUpdateReceivingAgent();
|
|
@@ -620,16 +814,58 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
620
814
|
this.updateReceivingAgentTimeout = undefined;
|
|
621
815
|
}
|
|
622
816
|
}));
|
|
623
|
-
this.setEnabled(this.aiActivationService.
|
|
817
|
+
this.setEnabled(this.aiActivationService.canRun);
|
|
624
818
|
this.historyService.init().then(() => {
|
|
625
819
|
this.navigationState = new ChatInputNavigationState(this.historyService);
|
|
626
820
|
});
|
|
627
821
|
this.initializeContextKeys();
|
|
822
|
+
this.tokenUsageEnabled = this.preferenceService?.get<boolean>(CHAT_VIEW_TOKEN_USAGE_ENABLED, false) ?? false;
|
|
823
|
+
if (this.preferenceService) {
|
|
824
|
+
this.toDispose.push(this.preferenceService.onPreferenceChanged(change => {
|
|
825
|
+
if (change.preferenceName === CHAT_VIEW_TOKEN_USAGE_ENABLED) {
|
|
826
|
+
this.tokenUsageEnabled = this.preferenceService?.get<boolean>(CHAT_VIEW_TOKEN_USAGE_ENABLED, false) ?? false;
|
|
827
|
+
this.update();
|
|
828
|
+
} else if (change.preferenceName === PREFERENCE_NAME_REASONING) {
|
|
829
|
+
// Refresh the reasoning selector display when the default preference changes.
|
|
830
|
+
this.update();
|
|
831
|
+
} else if (change.preferenceName === CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE) {
|
|
832
|
+
// Threshold changed: clear notified sessions so users are warned again
|
|
833
|
+
// at the new threshold (e.g. after raising it from the warning's
|
|
834
|
+
// "Open Settings" action), and re-evaluate the current session so the
|
|
835
|
+
// warning appears immediately if it's still above the new threshold.
|
|
836
|
+
this.notifiedSessions.clear();
|
|
837
|
+
if (this._chatModel) {
|
|
838
|
+
this.evaluateTokenUsageWarning(this._chatModel);
|
|
839
|
+
}
|
|
840
|
+
// Re-render so the indicator's color bands reflect the new threshold.
|
|
841
|
+
this.update();
|
|
842
|
+
} else if (change.preferenceName === CHAT_VIEW_TOKEN_USAGE_WARNING_ENABLED && this._chatModel) {
|
|
843
|
+
// If the user just enabled warnings for a session already above threshold,
|
|
844
|
+
// evaluate now so they get an immediate notification instead of waiting for
|
|
845
|
+
// the next response.
|
|
846
|
+
this.evaluateTokenUsageWarning(this._chatModel);
|
|
847
|
+
}
|
|
848
|
+
}));
|
|
849
|
+
}
|
|
628
850
|
// Listen for prompt fragment changes to refresh capabilities
|
|
629
851
|
this.toDispose.push(this.capabilitiesService.onDidChangeCapabilities(() => {
|
|
630
852
|
this.refreshCapabilities();
|
|
631
853
|
}));
|
|
632
854
|
|
|
855
|
+
// Refresh reasoning capability if the language model registry changes (model added/removed/alias re-resolved).
|
|
856
|
+
this.toDispose.push(this.languageModelRegistry.onChange(() => {
|
|
857
|
+
if (this.receivingAgent) {
|
|
858
|
+
this.updateReasoningSupport(this.receivingAgent.agentId);
|
|
859
|
+
}
|
|
860
|
+
}));
|
|
861
|
+
|
|
862
|
+
// When the default mode changes externally (e.g. via AI Configuration),
|
|
863
|
+
// sync the mode selector. Deferred via queueMicrotask so the prompt service's
|
|
864
|
+
// internal state is fully updated before we read agent.modes.
|
|
865
|
+
this.toDispose.push(this.promptService.onSelectedVariantChange(() => {
|
|
866
|
+
queueMicrotask(() => this.syncSelectedModeWithDefault());
|
|
867
|
+
}));
|
|
868
|
+
|
|
633
869
|
// Listen for generic capabilities changes
|
|
634
870
|
if (this.genericCapabilitiesService) {
|
|
635
871
|
this.updateAvailableGenericCapabilities();
|
|
@@ -685,6 +921,88 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
685
921
|
this.chatInputLastLineKey.set(isLastVisualOverall);
|
|
686
922
|
}
|
|
687
923
|
|
|
924
|
+
/**
|
|
925
|
+
* Resolve the configured token usage warning threshold as an absolute token count.
|
|
926
|
+
* The preference is stored as a percentage of the context window; this method
|
|
927
|
+
* converts it using the current assumed context window size.
|
|
928
|
+
*/
|
|
929
|
+
protected getTokenUsageWarningThreshold(): number {
|
|
930
|
+
const percentage = this.getTokenUsageWarningThresholdPercentage();
|
|
931
|
+
return Math.round((percentage / 100) * CHAT_CONTEXT_WINDOW_SIZE);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
protected getTokenUsageWarningThresholdPercentage(): number {
|
|
935
|
+
const value = this.preferenceService?.get<number>(
|
|
936
|
+
CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE,
|
|
937
|
+
CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE_DEFAULT
|
|
938
|
+
);
|
|
939
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 1 || value > 100) {
|
|
940
|
+
return CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE_DEFAULT;
|
|
941
|
+
}
|
|
942
|
+
return value;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
protected isTokenUsageWarningEnabled(): boolean {
|
|
946
|
+
return this.preferenceService?.get<boolean>(CHAT_VIEW_TOKEN_USAGE_WARNING_ENABLED, false) ?? false;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* Called after a response changes on the currently attached session. Shows a
|
|
951
|
+
* warning the first time the total crosses the threshold, and re-arms when
|
|
952
|
+
* usage drops back below it.
|
|
953
|
+
*/
|
|
954
|
+
protected evaluateTokenUsageWarning(chatModel: ChatModel): void {
|
|
955
|
+
// No point doing any work if the feature is off.
|
|
956
|
+
if (!this.isTokenUsageWarningEnabled()) {
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
// `responseChanged` fires on every streaming tick, but providers typically
|
|
960
|
+
// only set `tokenUsage` at completion. Skip in-progress responses so we do
|
|
961
|
+
// the walk + decision once per response rather than per chunk.
|
|
962
|
+
const lastRequest = chatModel.getRequests().at(-1);
|
|
963
|
+
if (lastRequest && !lastRequest.response.isComplete) {
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
const decision = decideTokenUsageWarning({
|
|
967
|
+
totalTokens: computeSessionTokenUsage(chatModel),
|
|
968
|
+
threshold: this.getTokenUsageWarningThreshold(),
|
|
969
|
+
alreadyNotified: this.notifiedSessions.has(chatModel.id)
|
|
970
|
+
});
|
|
971
|
+
if (decision === 'reset') {
|
|
972
|
+
this.notifiedSessions.delete(chatModel.id);
|
|
973
|
+
} else if (decision === 'notify') {
|
|
974
|
+
this.notifiedSessions.add(chatModel.id);
|
|
975
|
+
this.showTokenUsageWarning();
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
protected async showTokenUsageWarning(): Promise<void> {
|
|
980
|
+
const percentage = this.getTokenUsageWarningThresholdPercentage();
|
|
981
|
+
const message = nls.localize(
|
|
982
|
+
'theia/ai/chat-ui/tokenUsageWarningMessage',
|
|
983
|
+
'Chat session token usage has reached {0}% of the context window. ' +
|
|
984
|
+
'Consider summarizing this session or starting a new one to avoid hitting the limit.',
|
|
985
|
+
percentage
|
|
986
|
+
);
|
|
987
|
+
const summarizeAction = nls.localize('theia/ai/chat-ui/tokenUsageWarningSummarizeAction', 'Summarize Current Session');
|
|
988
|
+
const newSessionAction = nls.localize('theia/ai/chat-ui/tokenUsageWarningNewSessionAction', 'Start New Chat');
|
|
989
|
+
const openSettingsAction = nls.localizeByDefault('Open Settings');
|
|
990
|
+
const selected = await this.messageService.warn(message, summarizeAction, newSessionAction, openSettingsAction);
|
|
991
|
+
if (selected === summarizeAction) {
|
|
992
|
+
this.commandService.executeCommand(ChatCommands.AI_CHAT_NEW_WITH_TASK_CONTEXT.id).catch(error => {
|
|
993
|
+
console.error(`Failed to execute '${ChatCommands.AI_CHAT_NEW_WITH_TASK_CONTEXT.id}' from token usage warning`, error);
|
|
994
|
+
});
|
|
995
|
+
} else if (selected === newSessionAction) {
|
|
996
|
+
this.commandService.executeCommand(AI_CHAT_NEW_CHAT_WINDOW_COMMAND.id).catch(error => {
|
|
997
|
+
console.error(`Failed to execute '${AI_CHAT_NEW_CHAT_WINDOW_COMMAND.id}' from token usage warning`, error);
|
|
998
|
+
});
|
|
999
|
+
} else if (selected === openSettingsAction) {
|
|
1000
|
+
this.commandService.executeCommand(CommonCommands.OPEN_PREFERENCES.id, CHAT_VIEW_TOKEN_USAGE_WARNING_THRESHOLD_PERCENTAGE).catch(error => {
|
|
1001
|
+
console.error(`Failed to execute '${CommonCommands.OPEN_PREFERENCES.id}' from token usage warning`, error);
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
688
1006
|
protected scheduleUpdateReceivingAgent(): void {
|
|
689
1007
|
if (this.queryInFlight) {
|
|
690
1008
|
// Don't update capabilities while a query is being sent — the editor is being
|
|
@@ -774,7 +1092,11 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
774
1092
|
if (agent && (agentId !== previousAgentId || needsRefresh)) {
|
|
775
1093
|
const modes = agent.modes ?? [];
|
|
776
1094
|
const defaultMode = modes.find(m => m.isDefault);
|
|
777
|
-
const
|
|
1095
|
+
const hasPreviousRequests = this._chatModel.getRequests().length > 0;
|
|
1096
|
+
const restoredModeId = needsRefresh && hasPreviousRequests
|
|
1097
|
+
? this.getLastModeIdFromModel(this._chatModel)
|
|
1098
|
+
: undefined;
|
|
1099
|
+
const initialModeId = restoredModeId ?? defaultMode?.id;
|
|
778
1100
|
this.receivingAgent = {
|
|
779
1101
|
agentId: agentId,
|
|
780
1102
|
modes,
|
|
@@ -782,14 +1104,39 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
782
1104
|
};
|
|
783
1105
|
this.chatInputHasModesKey.set(modes.length > 1);
|
|
784
1106
|
// Only preserve overrides on forced refresh if the session has previous requests
|
|
785
|
-
const hasPreviousRequests = this._chatModel.getRequests().length > 0;
|
|
786
1107
|
const shouldPreserveOverrides = needsRefresh && hasPreviousRequests;
|
|
787
1108
|
await this.updateCapabilitiesForAgent(agentId, initialModeId, shouldPreserveOverrides);
|
|
1109
|
+
this.updateReasoningSupport(agentId);
|
|
788
1110
|
} else if (!agent && this.receivingAgent !== undefined) {
|
|
789
1111
|
this.receivingAgent = undefined;
|
|
790
1112
|
this.capabilityDefaults = [];
|
|
791
1113
|
this.userCapabilityOverrides = new Map();
|
|
792
1114
|
this.chatInputHasModesKey.set(false);
|
|
1115
|
+
this.currentReasoningSupport = undefined;
|
|
1116
|
+
this.update();
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
/**
|
|
1121
|
+
* Syncs the selected mode in the UI with the agent's current default mode.
|
|
1122
|
+
* Called when the default mode changes externally (e.g. via AI Configuration).
|
|
1123
|
+
*/
|
|
1124
|
+
protected syncSelectedModeWithDefault(): void {
|
|
1125
|
+
if (!this.receivingAgent) {
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
const agent = this.chatAgentService.getAgent(this.receivingAgent.agentId);
|
|
1129
|
+
if (!agent?.modes) {
|
|
1130
|
+
return;
|
|
1131
|
+
}
|
|
1132
|
+
const updatedModes = agent.modes;
|
|
1133
|
+
const newDefault = updatedModes.find(m => m.isDefault);
|
|
1134
|
+
if (newDefault && newDefault.id !== this.receivingAgent.currentModeId) {
|
|
1135
|
+
this.receivingAgent = {
|
|
1136
|
+
...this.receivingAgent,
|
|
1137
|
+
modes: updatedModes,
|
|
1138
|
+
currentModeId: newDefault.id
|
|
1139
|
+
};
|
|
793
1140
|
this.update();
|
|
794
1141
|
}
|
|
795
1142
|
}
|
|
@@ -961,6 +1308,11 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
961
1308
|
onModeChange: this.handleModeChange,
|
|
962
1309
|
keybindingHint: this.getModeKeybindingHint(),
|
|
963
1310
|
}}
|
|
1311
|
+
reasoningSelectorProps={{
|
|
1312
|
+
reasoningSupport: this.currentReasoningSupport,
|
|
1313
|
+
currentLevel: this.getCurrentReasoningLevel(),
|
|
1314
|
+
onReasoningChange: this.handleReasoningChange,
|
|
1315
|
+
}}
|
|
964
1316
|
capabilitiesProps={{
|
|
965
1317
|
capabilities: this.capabilityDefaults,
|
|
966
1318
|
overrides: this.userCapabilityOverrides,
|
|
@@ -979,6 +1331,8 @@ export class AIChatInputWidget extends ReactWidget {
|
|
|
979
1331
|
disabledCapabilities: this.disabledGenericCapabilities,
|
|
980
1332
|
hoverService: this.hoverService,
|
|
981
1333
|
}}
|
|
1334
|
+
tokenUsageEnabled={this.tokenUsageEnabled}
|
|
1335
|
+
tokenUsageWarningThreshold={this.getTokenUsageWarningThreshold()}
|
|
982
1336
|
/>
|
|
983
1337
|
);
|
|
984
1338
|
}
|
|
@@ -1292,6 +1646,11 @@ interface ChatInputProperties {
|
|
|
1292
1646
|
onModeChange: (mode: string) => void;
|
|
1293
1647
|
keybindingHint?: string;
|
|
1294
1648
|
};
|
|
1649
|
+
reasoningSelectorProps: {
|
|
1650
|
+
reasoningSupport?: ReasoningSupport;
|
|
1651
|
+
currentLevel?: ReasoningLevel;
|
|
1652
|
+
onReasoningChange: (level: ReasoningLevel) => void;
|
|
1653
|
+
};
|
|
1295
1654
|
capabilitiesProps: {
|
|
1296
1655
|
capabilities: ParsedCapability[];
|
|
1297
1656
|
overrides: Map<string, boolean>;
|
|
@@ -1310,6 +1669,8 @@ interface ChatInputProperties {
|
|
|
1310
1669
|
disabledCapabilities: GenericCapabilitySelections;
|
|
1311
1670
|
hoverService: HoverService;
|
|
1312
1671
|
};
|
|
1672
|
+
tokenUsageEnabled?: boolean;
|
|
1673
|
+
tokenUsageWarningThreshold: number;
|
|
1313
1674
|
}
|
|
1314
1675
|
|
|
1315
1676
|
// Utility to check if we have task context in the chat model
|
|
@@ -1737,13 +2098,20 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
1737
2098
|
// Show mode selector if agent has multiple modes
|
|
1738
2099
|
const showModeSelector = (props.modeSelectorProps.receivingAgentModes?.length ?? 0) > 1;
|
|
1739
2100
|
|
|
2101
|
+
// Token usage computation (cheap pure function walking the model's request list)
|
|
2102
|
+
const totalTokens = props.tokenUsageEnabled ? computeSessionTokenUsage(props.chatModel) : 0;
|
|
2103
|
+
const showTokenUsage = props.tokenUsageEnabled && totalTokens > 0;
|
|
2104
|
+
const tokenColorClass = showTokenUsage ? getUsageColorClass(totalTokens, props.tokenUsageWarningThreshold) : '';
|
|
2105
|
+
const tokenIsWarningOrError = tokenColorClass === 'token-usage-yellow' || tokenColorClass === 'token-usage-red';
|
|
2106
|
+
const tokenTooltip = showTokenUsage ? buildBarTooltip(getLatestTokenUsage(props.chatModel), totalTokens, props.tokenUsageWarningThreshold) : undefined;
|
|
2107
|
+
|
|
1740
2108
|
return (
|
|
1741
2109
|
<div className="theia-ChatInput" data-ai-disabled={!props.isEnabled} onDragOver={props.onDragOver} onDrop={props.onDrop} ref={containerRef}>
|
|
1742
2110
|
{props.showSuggestions !== false && <ChatInputAgentSuggestions suggestions={props.suggestions} opener={props.openerService} />}
|
|
1743
2111
|
{props.showChangeSet && changeSetUI?.elements &&
|
|
1744
2112
|
<ChangeSetBox changeSet={changeSetUI} />
|
|
1745
2113
|
}
|
|
1746
|
-
<div className=
|
|
2114
|
+
<div className={`theia-ChatInput-Editor-Box${tokenIsWarningOrError ? ` token-usage-border-${tokenColorClass}` : ''}`}>
|
|
1747
2115
|
{props.showCapabilities !== false && (
|
|
1748
2116
|
<CapabilitiesBar
|
|
1749
2117
|
isOpen={props.capabilitiesProps.isOpen}
|
|
@@ -1772,6 +2140,11 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
1772
2140
|
rightOptions={rightOptions}
|
|
1773
2141
|
isEnabled={props.isEnabled}
|
|
1774
2142
|
hoverService={props.hoverService}
|
|
2143
|
+
tokenUsage={showTokenUsage ? {
|
|
2144
|
+
percent: Math.min((totalTokens / CHAT_CONTEXT_WINDOW_SIZE) * 100, 100),
|
|
2145
|
+
colorClass: tokenColorClass,
|
|
2146
|
+
tooltip: tokenTooltip,
|
|
2147
|
+
} : undefined}
|
|
1775
2148
|
modeSelectorProps={{
|
|
1776
2149
|
show: showModeSelector,
|
|
1777
2150
|
modes: props.modeSelectorProps.receivingAgentModes,
|
|
@@ -1779,6 +2152,12 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
1779
2152
|
onModeChange: props.modeSelectorProps.onModeChange,
|
|
1780
2153
|
keybindingHint: props.modeSelectorProps.keybindingHint,
|
|
1781
2154
|
}}
|
|
2155
|
+
reasoningSelectorProps={{
|
|
2156
|
+
show: !!props.reasoningSelectorProps.reasoningSupport,
|
|
2157
|
+
reasoningSupport: props.reasoningSelectorProps.reasoningSupport,
|
|
2158
|
+
currentLevel: props.reasoningSelectorProps.currentLevel,
|
|
2159
|
+
onReasoningChange: props.reasoningSelectorProps.onReasoningChange,
|
|
2160
|
+
}}
|
|
1782
2161
|
capabilitiesToggle={{
|
|
1783
2162
|
show: props.showCapabilities !== false,
|
|
1784
2163
|
isOpen: props.capabilitiesProps.isOpen,
|
|
@@ -1797,12 +2176,12 @@ const ChatInput: React.FunctionComponent<ChatInputProperties> = (props: ChatInpu
|
|
|
1797
2176
|
/**
|
|
1798
2177
|
* Returns an onMouseEnter handler that shows a hover tooltip via HoverService.
|
|
1799
2178
|
*/
|
|
1800
|
-
function hoverHandler(hoverService: HoverService, content: string): (e: React.MouseEvent) => void {
|
|
2179
|
+
function hoverHandler(hoverService: HoverService, content: string | MarkdownString, position: 'top' | 'bottom' = 'bottom'): (e: React.MouseEvent) => void {
|
|
1801
2180
|
return (e: React.MouseEvent) => {
|
|
1802
2181
|
hoverService.requestHover({
|
|
1803
2182
|
content,
|
|
1804
2183
|
target: e.currentTarget as HTMLElement,
|
|
1805
|
-
position
|
|
2184
|
+
position
|
|
1806
2185
|
});
|
|
1807
2186
|
};
|
|
1808
2187
|
}
|
|
@@ -1812,6 +2191,11 @@ interface ChatInputOptionsProps {
|
|
|
1812
2191
|
rightOptions: Option[];
|
|
1813
2192
|
isEnabled?: boolean;
|
|
1814
2193
|
hoverService: HoverService;
|
|
2194
|
+
tokenUsage?: {
|
|
2195
|
+
percent: number;
|
|
2196
|
+
colorClass: string;
|
|
2197
|
+
tooltip?: MarkdownString;
|
|
2198
|
+
};
|
|
1815
2199
|
modeSelectorProps: {
|
|
1816
2200
|
show: boolean;
|
|
1817
2201
|
modes?: ChatMode[];
|
|
@@ -1819,6 +2203,12 @@ interface ChatInputOptionsProps {
|
|
|
1819
2203
|
onModeChange: (mode: string) => void;
|
|
1820
2204
|
keybindingHint?: string;
|
|
1821
2205
|
};
|
|
2206
|
+
reasoningSelectorProps: {
|
|
2207
|
+
show: boolean;
|
|
2208
|
+
reasoningSupport?: ReasoningSupport;
|
|
2209
|
+
currentLevel?: ReasoningLevel;
|
|
2210
|
+
onReasoningChange: (level: ReasoningLevel) => void;
|
|
2211
|
+
};
|
|
1822
2212
|
capabilitiesToggle: {
|
|
1823
2213
|
show: boolean;
|
|
1824
2214
|
isOpen: boolean;
|
|
@@ -1834,7 +2224,9 @@ const ChatInputOptions: React.FunctionComponent<ChatInputOptionsProps> = ({
|
|
|
1834
2224
|
rightOptions,
|
|
1835
2225
|
isEnabled,
|
|
1836
2226
|
hoverService,
|
|
2227
|
+
tokenUsage,
|
|
1837
2228
|
modeSelectorProps,
|
|
2229
|
+
reasoningSelectorProps,
|
|
1838
2230
|
capabilitiesToggle
|
|
1839
2231
|
}) => {
|
|
1840
2232
|
const capabilitiesLabel = nls.localize('theia/ai/chat-ui/toggleCapabilitiesConfig', 'Toggle Capabilities Configuration');
|
|
@@ -1847,6 +2239,21 @@ const ChatInputOptions: React.FunctionComponent<ChatInputOptionsProps> = ({
|
|
|
1847
2239
|
// CSS order property positions them visually (left on left, right on right)
|
|
1848
2240
|
<div className="theia-ChatInputOptions">
|
|
1849
2241
|
<div className="theia-ChatInputOptions-right">
|
|
2242
|
+
{tokenUsage && (
|
|
2243
|
+
<span
|
|
2244
|
+
className={`token-usage-badge ${tokenUsage.colorClass}`}
|
|
2245
|
+
{...(tokenUsage.tooltip && { onMouseEnter: hoverHandler(hoverService, tokenUsage.tooltip, 'top') })}
|
|
2246
|
+
>
|
|
2247
|
+
<span
|
|
2248
|
+
className='token-usage-ring'
|
|
2249
|
+
style={{
|
|
2250
|
+
background: `conic-gradient(var(--token-usage-fill) ${tokenUsage.percent}%, var(--token-usage-track) ${tokenUsage.percent}%)`
|
|
2251
|
+
}}
|
|
2252
|
+
>
|
|
2253
|
+
<span className='token-usage-ring-inner' />
|
|
2254
|
+
</span>
|
|
2255
|
+
</span>
|
|
2256
|
+
)}
|
|
1850
2257
|
{rightOptions.map((option, index) => (
|
|
1851
2258
|
<span
|
|
1852
2259
|
key={index}
|
|
@@ -1922,6 +2329,15 @@ const ChatInputOptions: React.FunctionComponent<ChatInputOptionsProps> = ({
|
|
|
1922
2329
|
)}
|
|
1923
2330
|
</span>
|
|
1924
2331
|
)}
|
|
2332
|
+
{reasoningSelectorProps.show && reasoningSelectorProps.reasoningSupport && (
|
|
2333
|
+
<ReasoningSelector
|
|
2334
|
+
reasoningSupport={reasoningSelectorProps.reasoningSupport}
|
|
2335
|
+
currentLevel={reasoningSelectorProps.currentLevel}
|
|
2336
|
+
onReasoningChange={reasoningSelectorProps.onReasoningChange}
|
|
2337
|
+
disabled={!isEnabled}
|
|
2338
|
+
hoverService={hoverService}
|
|
2339
|
+
/>
|
|
2340
|
+
)}
|
|
1925
2341
|
</div>
|
|
1926
2342
|
</div>
|
|
1927
2343
|
);
|
|
@@ -2078,6 +2494,57 @@ const ChatModeSelector: React.FunctionComponent<ChatModeSelectorProps> = React.m
|
|
|
2078
2494
|
);
|
|
2079
2495
|
});
|
|
2080
2496
|
|
|
2497
|
+
interface ReasoningSelectorProps {
|
|
2498
|
+
reasoningSupport: ReasoningSupport;
|
|
2499
|
+
currentLevel?: ReasoningLevel;
|
|
2500
|
+
onReasoningChange: (level: ReasoningLevel) => void;
|
|
2501
|
+
disabled?: boolean;
|
|
2502
|
+
hoverService: HoverService;
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2505
|
+
const reasoningLevelLabel = (level: ReasoningLevel): string => {
|
|
2506
|
+
switch (level) {
|
|
2507
|
+
case 'off': return nls.localizeByDefault('Off');
|
|
2508
|
+
case 'minimal': return nls.localize('theia/ai/chat-ui/reasoning/minimal', 'Minimal');
|
|
2509
|
+
case 'low': return nls.localize('theia/ai/chat-ui/reasoning/low', 'Low');
|
|
2510
|
+
case 'medium': return nls.localize('theia/ai/chat-ui/reasoning/medium', 'Medium');
|
|
2511
|
+
case 'high': return nls.localize('theia/ai/chat-ui/reasoning/high', 'High');
|
|
2512
|
+
case 'auto': return nls.localizeByDefault('Auto');
|
|
2513
|
+
}
|
|
2514
|
+
};
|
|
2515
|
+
|
|
2516
|
+
const ReasoningSelector: React.FunctionComponent<ReasoningSelectorProps> = React.memo(({
|
|
2517
|
+
reasoningSupport, currentLevel, onReasoningChange, disabled, hoverService
|
|
2518
|
+
}) => {
|
|
2519
|
+
const options: SelectOption[] = React.useMemo(
|
|
2520
|
+
() => reasoningSupport.supportedLevels.map(level => ({ value: level, label: reasoningLevelLabel(level) })),
|
|
2521
|
+
[reasoningSupport]
|
|
2522
|
+
);
|
|
2523
|
+
|
|
2524
|
+
const handleChange = React.useCallback(
|
|
2525
|
+
(option: SelectOption) => {
|
|
2526
|
+
if (option.value) {
|
|
2527
|
+
onReasoningChange(option.value as ReasoningLevel);
|
|
2528
|
+
}
|
|
2529
|
+
},
|
|
2530
|
+
[onReasoningChange]
|
|
2531
|
+
);
|
|
2532
|
+
|
|
2533
|
+
const title = nls.localizeByDefault('Reasoning');
|
|
2534
|
+
const effectiveLevel = currentLevel ?? reasoningSupport.defaultLevel ?? reasoningSupport.supportedLevels[0] ?? 'off';
|
|
2535
|
+
|
|
2536
|
+
return (
|
|
2537
|
+
<span onMouseEnter={hoverHandler(hoverService, title)}>
|
|
2538
|
+
<SelectComponent
|
|
2539
|
+
className={`theia-ChatInput-ReasoningSelector reasoning-level-${effectiveLevel}${disabled ? ' disabled' : ''}`}
|
|
2540
|
+
options={options}
|
|
2541
|
+
defaultValue={effectiveLevel}
|
|
2542
|
+
onChange={handleChange}
|
|
2543
|
+
/>
|
|
2544
|
+
</span>
|
|
2545
|
+
);
|
|
2546
|
+
});
|
|
2547
|
+
|
|
2081
2548
|
const noPropagation = (handler: () => void) => (e: React.MouseEvent) => {
|
|
2082
2549
|
handler();
|
|
2083
2550
|
e.stopPropagation();
|