@jupyterlite/ai 0.15.0 → 0.17.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/agent.d.ts +12 -2
- package/lib/agent.js +112 -17
- package/lib/chat-commands/clear.js +1 -1
- package/lib/chat-model-handler.js +4 -1
- package/lib/chat-model.d.ts +25 -24
- package/lib/chat-model.js +262 -132
- package/lib/components/clear-button.d.ts +1 -1
- package/lib/components/clear-button.js +1 -1
- package/lib/components/index.d.ts +1 -1
- package/lib/components/index.js +1 -1
- package/lib/components/{token-usage-display.d.ts → usage-display.d.ts} +11 -11
- package/lib/components/usage-display.js +109 -0
- package/lib/index.js +205 -20
- package/lib/models/settings-model.js +1 -0
- package/lib/providers/built-in-providers.js +5 -0
- package/lib/providers/generated-context-windows.d.ts +8 -0
- package/lib/providers/generated-context-windows.js +96 -0
- package/lib/providers/model-info.d.ts +3 -0
- package/lib/providers/model-info.js +58 -0
- package/lib/tokens.d.ts +34 -3
- package/lib/tokens.js +8 -7
- package/lib/widgets/ai-settings.js +9 -0
- package/lib/widgets/main-area-chat.d.ts +1 -0
- package/lib/widgets/main-area-chat.js +10 -4
- package/lib/widgets/provider-config-dialog.js +18 -5
- package/package.json +3 -2
- package/schema/settings-model.json +11 -0
- package/src/agent.ts +151 -21
- package/src/chat-commands/clear.ts +1 -1
- package/src/chat-model-handler.ts +6 -1
- package/src/chat-model.ts +350 -175
- package/src/components/clear-button.tsx +3 -3
- package/src/components/index.ts +1 -1
- package/src/components/usage-display.tsx +208 -0
- package/src/index.ts +250 -26
- package/src/models/settings-model.ts +1 -0
- package/src/providers/built-in-providers.ts +5 -0
- package/src/providers/generated-context-windows.ts +102 -0
- package/src/providers/model-info.ts +88 -0
- package/src/tokens.ts +46 -10
- package/src/widgets/ai-settings.tsx +42 -0
- package/src/widgets/main-area-chat.ts +12 -4
- package/src/widgets/provider-config-dialog.tsx +45 -5
- package/lib/components/token-usage-display.js +0 -72
- package/src/components/token-usage-display.tsx +0 -137
|
@@ -16,7 +16,7 @@ export interface IClearButtonProps
|
|
|
16
16
|
/**
|
|
17
17
|
* The function to clear messages.
|
|
18
18
|
*/
|
|
19
|
-
clearMessages: () => void
|
|
19
|
+
clearMessages: () => Promise<void>;
|
|
20
20
|
/**
|
|
21
21
|
* The application language translator.
|
|
22
22
|
*/
|
|
@@ -53,8 +53,8 @@ export function clearItem(
|
|
|
53
53
|
return {
|
|
54
54
|
element: (props: InputToolbarRegistry.IToolbarItemProps) => {
|
|
55
55
|
const { model } = props;
|
|
56
|
-
const clearMessages = () =>
|
|
57
|
-
(model.chatContext as AIChatModel.IAIChatContext).clearMessages();
|
|
56
|
+
const clearMessages = async () =>
|
|
57
|
+
await (model.chatContext as AIChatModel.IAIChatContext).clearMessages();
|
|
58
58
|
const clearProps: IClearButtonProps = {
|
|
59
59
|
...props,
|
|
60
60
|
clearMessages,
|
package/src/components/index.ts
CHANGED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { ReactWidget, UseSignal } from '@jupyterlab/ui-components';
|
|
2
|
+
import type { TranslationBundle } from '@jupyterlab/translation';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { ISignal } from '@lumino/signaling';
|
|
5
|
+
import type { IAISettingsModel, ITokenUsage } from '../tokens';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Props for the UsageDisplay component.
|
|
9
|
+
*/
|
|
10
|
+
export interface IUsageDisplayProps {
|
|
11
|
+
/**
|
|
12
|
+
* The token usage changed signal
|
|
13
|
+
*/
|
|
14
|
+
tokenUsageChanged: ISignal<any, ITokenUsage>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The settings model instance for configuration options
|
|
18
|
+
*/
|
|
19
|
+
settingsModel: IAISettingsModel;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Initial token usage.
|
|
23
|
+
*/
|
|
24
|
+
initialTokenUsage?: ITokenUsage;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The application language translator.
|
|
28
|
+
*/
|
|
29
|
+
translator: TranslationBundle;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* React component that displays usage information.
|
|
34
|
+
* Shows input/output token counts and optional estimated context usage.
|
|
35
|
+
* Only renders when token or context usage display is enabled in settings.
|
|
36
|
+
*/
|
|
37
|
+
export const UsageDisplay: React.FC<IUsageDisplayProps> = ({
|
|
38
|
+
tokenUsageChanged,
|
|
39
|
+
settingsModel,
|
|
40
|
+
initialTokenUsage,
|
|
41
|
+
translator: trans
|
|
42
|
+
}) => {
|
|
43
|
+
const formatContextPercent = (value: number): string => {
|
|
44
|
+
return Math.round(value).toLocaleString();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const badgeStyle: React.CSSProperties = {
|
|
48
|
+
display: 'flex',
|
|
49
|
+
alignItems: 'center',
|
|
50
|
+
gap: '6px',
|
|
51
|
+
fontSize: '12px',
|
|
52
|
+
color: 'var(--jp-ui-font-color2)',
|
|
53
|
+
padding: '4px 8px',
|
|
54
|
+
backgroundColor: 'var(--jp-layout-color1)',
|
|
55
|
+
border: '1px solid var(--jp-border-color1)',
|
|
56
|
+
borderRadius: '4px',
|
|
57
|
+
whiteSpace: 'nowrap'
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<UseSignal signal={settingsModel.stateChanged} initialArgs={undefined}>
|
|
62
|
+
{() => {
|
|
63
|
+
const config = settingsModel.config;
|
|
64
|
+
const showTokenUsage = config.showTokenUsage;
|
|
65
|
+
const showContextUsage = config.showContextUsage;
|
|
66
|
+
if (!showTokenUsage && !showContextUsage) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<UseSignal signal={tokenUsageChanged} initialArgs={initialTokenUsage}>
|
|
72
|
+
{(_, tokenUsage: ITokenUsage | null | undefined) => {
|
|
73
|
+
if (!tokenUsage) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const total = tokenUsage.inputTokens + tokenUsage.outputTokens;
|
|
78
|
+
const hasKnownContextWindow =
|
|
79
|
+
showContextUsage && tokenUsage.contextWindow !== undefined;
|
|
80
|
+
const contextUsagePercent =
|
|
81
|
+
tokenUsage.lastRequestInputTokens !== undefined &&
|
|
82
|
+
tokenUsage.contextWindow !== undefined &&
|
|
83
|
+
tokenUsage.contextWindow > 0
|
|
84
|
+
? Math.max(
|
|
85
|
+
0,
|
|
86
|
+
Math.min(
|
|
87
|
+
100,
|
|
88
|
+
(tokenUsage.lastRequestInputTokens /
|
|
89
|
+
tokenUsage.contextWindow) *
|
|
90
|
+
100
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
: undefined;
|
|
94
|
+
const hasContextEstimate =
|
|
95
|
+
hasKnownContextWindow &&
|
|
96
|
+
contextUsagePercent !== undefined &&
|
|
97
|
+
tokenUsage.lastRequestInputTokens !== undefined;
|
|
98
|
+
|
|
99
|
+
const contextLabel = hasContextEstimate
|
|
100
|
+
? `${formatContextPercent(contextUsagePercent)}%`
|
|
101
|
+
: hasKnownContextWindow
|
|
102
|
+
? '0%'
|
|
103
|
+
: '?';
|
|
104
|
+
|
|
105
|
+
const contextTitle = hasContextEstimate
|
|
106
|
+
? trans.__(
|
|
107
|
+
'Context Usage (estimated): %1% (%2 / %3 tokens)',
|
|
108
|
+
formatContextPercent(contextUsagePercent),
|
|
109
|
+
tokenUsage.lastRequestInputTokens!.toLocaleString(),
|
|
110
|
+
tokenUsage.contextWindow!.toLocaleString()
|
|
111
|
+
)
|
|
112
|
+
: hasKnownContextWindow
|
|
113
|
+
? trans.__(
|
|
114
|
+
'Context usage estimate will appear after the next request. Showing 0% until then. Context window: %1 tokens',
|
|
115
|
+
tokenUsage.contextWindow!.toLocaleString()
|
|
116
|
+
)
|
|
117
|
+
: trans.__(
|
|
118
|
+
'Context Usage unavailable. Configure a context window for the active provider/model to enable estimation.'
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<div
|
|
123
|
+
style={{
|
|
124
|
+
display: 'flex',
|
|
125
|
+
alignItems: 'center',
|
|
126
|
+
gap: '6px'
|
|
127
|
+
}}
|
|
128
|
+
>
|
|
129
|
+
{showTokenUsage && (
|
|
130
|
+
<span
|
|
131
|
+
style={badgeStyle}
|
|
132
|
+
title={trans.__(
|
|
133
|
+
'Token Usage - Sent: %1, Received: %2, Total: %3',
|
|
134
|
+
tokenUsage.inputTokens.toLocaleString(),
|
|
135
|
+
tokenUsage.outputTokens.toLocaleString(),
|
|
136
|
+
total.toLocaleString()
|
|
137
|
+
)}
|
|
138
|
+
>
|
|
139
|
+
<span
|
|
140
|
+
style={{
|
|
141
|
+
display: 'flex',
|
|
142
|
+
alignItems: 'center',
|
|
143
|
+
gap: '2px'
|
|
144
|
+
}}
|
|
145
|
+
>
|
|
146
|
+
<span>↑</span>
|
|
147
|
+
<span>{tokenUsage.inputTokens.toLocaleString()}</span>
|
|
148
|
+
</span>
|
|
149
|
+
<span
|
|
150
|
+
style={{
|
|
151
|
+
display: 'flex',
|
|
152
|
+
alignItems: 'center',
|
|
153
|
+
gap: '2px'
|
|
154
|
+
}}
|
|
155
|
+
>
|
|
156
|
+
<span>↓</span>
|
|
157
|
+
<span>{tokenUsage.outputTokens.toLocaleString()}</span>
|
|
158
|
+
</span>
|
|
159
|
+
</span>
|
|
160
|
+
)}
|
|
161
|
+
{showContextUsage && (
|
|
162
|
+
<span style={badgeStyle} title={contextTitle}>
|
|
163
|
+
<span
|
|
164
|
+
style={{
|
|
165
|
+
display: 'flex',
|
|
166
|
+
alignItems: 'center',
|
|
167
|
+
gap: '2px'
|
|
168
|
+
}}
|
|
169
|
+
>
|
|
170
|
+
<span>ctx</span>
|
|
171
|
+
<span>{contextLabel}</span>
|
|
172
|
+
</span>
|
|
173
|
+
</span>
|
|
174
|
+
)}
|
|
175
|
+
</div>
|
|
176
|
+
);
|
|
177
|
+
}}
|
|
178
|
+
</UseSignal>
|
|
179
|
+
);
|
|
180
|
+
}}
|
|
181
|
+
</UseSignal>
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* JupyterLab widget wrapper for the UsageDisplay component.
|
|
187
|
+
* Extends ReactWidget to integrate with the JupyterLab widget system.
|
|
188
|
+
*/
|
|
189
|
+
export class UsageWidget extends ReactWidget {
|
|
190
|
+
/**
|
|
191
|
+
* Creates a new UsageWidget instance.
|
|
192
|
+
* @param options - Configuration options containing required models
|
|
193
|
+
*/
|
|
194
|
+
constructor(options: IUsageDisplayProps) {
|
|
195
|
+
super();
|
|
196
|
+
this._options = options;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Renders the React component within the widget.
|
|
201
|
+
* @returns The UsageDisplay React element
|
|
202
|
+
*/
|
|
203
|
+
protected render(): React.ReactElement {
|
|
204
|
+
return <UsageDisplay {...this._options} />;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private _options: IUsageDisplayProps;
|
|
208
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -114,7 +114,7 @@ import {
|
|
|
114
114
|
createToolSelectItem,
|
|
115
115
|
stopItem,
|
|
116
116
|
CompletionStatusWidget,
|
|
117
|
-
|
|
117
|
+
UsageWidget
|
|
118
118
|
} from './components';
|
|
119
119
|
|
|
120
120
|
import { AISettingsModel } from './models/settings-model';
|
|
@@ -357,6 +357,27 @@ const chatModelHandler: JupyterFrontEndPlugin<IChatModelHandler> = {
|
|
|
357
357
|
}
|
|
358
358
|
};
|
|
359
359
|
|
|
360
|
+
/**
|
|
361
|
+
* The active cell manager plugin, to allow copying code from chat to notebook.
|
|
362
|
+
*/
|
|
363
|
+
const activeCellManager: JupyterFrontEndPlugin<void> = {
|
|
364
|
+
id: '@jupyterlite/ai:activeCellManager',
|
|
365
|
+
description: 'Add the active cell manager to the model handler',
|
|
366
|
+
autoStart: true,
|
|
367
|
+
requires: [IChatModelHandler, INotebookTracker],
|
|
368
|
+
activate: (
|
|
369
|
+
app: JupyterFrontEnd,
|
|
370
|
+
modelHandler: IChatModelHandler,
|
|
371
|
+
notebookTracker: INotebookTracker
|
|
372
|
+
) => {
|
|
373
|
+
const activeCellManager = new ActiveCellManager({
|
|
374
|
+
tracker: notebookTracker,
|
|
375
|
+
shell: app.shell
|
|
376
|
+
});
|
|
377
|
+
modelHandler.activeCellManager = activeCellManager;
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
|
|
360
381
|
/**
|
|
361
382
|
* Initialization data for the extension.
|
|
362
383
|
*/
|
|
@@ -376,7 +397,6 @@ const plugin: JupyterFrontEndPlugin<IChatTracker> = {
|
|
|
376
397
|
IThemeManager,
|
|
377
398
|
ILayoutRestorer,
|
|
378
399
|
ILabShell,
|
|
379
|
-
INotebookTracker,
|
|
380
400
|
ITranslator,
|
|
381
401
|
IComponentsRendererFactory,
|
|
382
402
|
ICommandPalette,
|
|
@@ -392,7 +412,6 @@ const plugin: JupyterFrontEndPlugin<IChatTracker> = {
|
|
|
392
412
|
themeManager?: IThemeManager,
|
|
393
413
|
restorer?: ILayoutRestorer,
|
|
394
414
|
labShell?: ILabShell,
|
|
395
|
-
notebookTracker?: INotebookTracker,
|
|
396
415
|
translator?: ITranslator,
|
|
397
416
|
chatComponentsFactory?: IComponentsRendererFactory,
|
|
398
417
|
palette?: ICommandPalette,
|
|
@@ -416,17 +435,6 @@ const plugin: JupyterFrontEndPlugin<IChatTracker> = {
|
|
|
416
435
|
}
|
|
417
436
|
};
|
|
418
437
|
|
|
419
|
-
// Create ActiveCellManager if notebook tracker is available, and add it to the
|
|
420
|
-
// model registry.
|
|
421
|
-
let activeCellManager: ActiveCellManager | undefined;
|
|
422
|
-
if (notebookTracker) {
|
|
423
|
-
activeCellManager = new ActiveCellManager({
|
|
424
|
-
tracker: notebookTracker,
|
|
425
|
-
shell: app.shell
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
modelHandler.activeCellManager = activeCellManager;
|
|
429
|
-
|
|
430
438
|
// Creating the tracker for the chat widgets
|
|
431
439
|
const namespace = 'ai-chat';
|
|
432
440
|
const tracker = new WidgetTracker<MainAreaChat | ChatWidget>({ namespace });
|
|
@@ -516,7 +524,7 @@ const plugin: JupyterFrontEndPlugin<IChatTracker> = {
|
|
|
516
524
|
chatPanel.disposed.connect(disconnectSettingsButtonListener);
|
|
517
525
|
}
|
|
518
526
|
|
|
519
|
-
let
|
|
527
|
+
let usageWidget: UsageWidget | null = null;
|
|
520
528
|
chatPanel.chatOpened.connect((_, widget) => {
|
|
521
529
|
const model = widget.model as AIChatModel;
|
|
522
530
|
|
|
@@ -527,6 +535,18 @@ const plugin: JupyterFrontEndPlugin<IChatTracker> = {
|
|
|
527
535
|
tracker.save(widget);
|
|
528
536
|
}
|
|
529
537
|
|
|
538
|
+
function updateToolbarTitleOverlay() {
|
|
539
|
+
const titleNode = chatPanel.current?.toolbar.node
|
|
540
|
+
.getElementsByClassName('jp-chat-sidepanel-widget-title')
|
|
541
|
+
.item(0);
|
|
542
|
+
if (titleNode) {
|
|
543
|
+
titleNode.setAttribute('title', model.title ?? model.name);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
model.titleChanged.connect(updateToolbarTitleOverlay);
|
|
548
|
+
updateToolbarTitleOverlay();
|
|
549
|
+
|
|
530
550
|
// Update the tracker if the model name changed.
|
|
531
551
|
model.nameChanged.connect(saveTracker);
|
|
532
552
|
|
|
@@ -534,19 +554,15 @@ const plugin: JupyterFrontEndPlugin<IChatTracker> = {
|
|
|
534
554
|
model.agentManager.activeProviderChanged.connect(saveTracker);
|
|
535
555
|
|
|
536
556
|
// Update the token usage widget.
|
|
537
|
-
|
|
557
|
+
usageWidget?.dispose();
|
|
538
558
|
|
|
539
|
-
|
|
559
|
+
usageWidget = new UsageWidget({
|
|
540
560
|
tokenUsageChanged: model.tokenUsageChanged,
|
|
541
561
|
settingsModel,
|
|
542
562
|
initialTokenUsage: model.agentManager.tokenUsage,
|
|
543
563
|
translator: trans
|
|
544
564
|
});
|
|
545
|
-
chatPanel.current?.toolbar.insertBefore(
|
|
546
|
-
'markRead',
|
|
547
|
-
'token-usage',
|
|
548
|
-
tokenUsageWidget
|
|
549
|
-
);
|
|
565
|
+
chatPanel.current?.toolbar.insertBefore('markRead', 'usage', usageWidget);
|
|
550
566
|
|
|
551
567
|
if (model.saveAvailable) {
|
|
552
568
|
const saveChatButton = new SaveComponentWidget({
|
|
@@ -586,6 +602,7 @@ const plugin: JupyterFrontEndPlugin<IChatTracker> = {
|
|
|
586
602
|
});
|
|
587
603
|
|
|
588
604
|
widget.disposed.connect(() => {
|
|
605
|
+
model.titleChanged.disconnect(updateToolbarTitleOverlay);
|
|
589
606
|
model.nameChanged.disconnect(saveTracker);
|
|
590
607
|
model.agentManager.activeProviderChanged.disconnect(saveTracker);
|
|
591
608
|
model.writersChanged?.disconnect(writersChanged);
|
|
@@ -738,7 +755,7 @@ function registerCommands(
|
|
|
738
755
|
}
|
|
739
756
|
});
|
|
740
757
|
|
|
741
|
-
const openInMain = (model: AIChatModel) => {
|
|
758
|
+
const openInMain = (model: AIChatModel): MainAreaChat => {
|
|
742
759
|
const content = new ChatWidget({
|
|
743
760
|
model,
|
|
744
761
|
rmRegistry,
|
|
@@ -771,6 +788,64 @@ function registerCommands(
|
|
|
771
788
|
model.nameChanged.disconnect(saveTracker);
|
|
772
789
|
model.agentManager.activeProviderChanged.disconnect(saveTracker);
|
|
773
790
|
});
|
|
791
|
+
|
|
792
|
+
return widget;
|
|
793
|
+
};
|
|
794
|
+
|
|
795
|
+
const focusOnChat = (
|
|
796
|
+
area: 'main' | 'side',
|
|
797
|
+
widget?: ChatWidget | MainAreaChat
|
|
798
|
+
) => {
|
|
799
|
+
if (area === 'main' && widget) {
|
|
800
|
+
app.shell.activateById(widget.id);
|
|
801
|
+
} else {
|
|
802
|
+
app.shell.activateById(chatPanel.id);
|
|
803
|
+
}
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
const applyInputArgs = (model: IChatModel, args: any) => {
|
|
807
|
+
const input = typeof args.input === 'string' ? args.input : undefined;
|
|
808
|
+
const autoSend = args.autoSend === true;
|
|
809
|
+
const shouldFocus = args.focus !== false;
|
|
810
|
+
|
|
811
|
+
if (input !== undefined) {
|
|
812
|
+
model.input.value = input;
|
|
813
|
+
}
|
|
814
|
+
if (autoSend && input !== undefined) {
|
|
815
|
+
model.input.send(model.input.value);
|
|
816
|
+
}
|
|
817
|
+
if (shouldFocus) {
|
|
818
|
+
model.input.focus();
|
|
819
|
+
}
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
const findChatWidget = (
|
|
823
|
+
name?: string,
|
|
824
|
+
provider?: string
|
|
825
|
+
): ChatWidget | MainAreaChat | undefined => {
|
|
826
|
+
if (!name && !provider) {
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
return tracker.find(widget => {
|
|
830
|
+
const model = widget.model as AIChatModel;
|
|
831
|
+
return (
|
|
832
|
+
(!name || widget.model.name === name) &&
|
|
833
|
+
(!provider || model.agentManager.activeProvider === provider)
|
|
834
|
+
);
|
|
835
|
+
});
|
|
836
|
+
};
|
|
837
|
+
|
|
838
|
+
const disposeSideChatModel = (model: IChatModel): boolean => {
|
|
839
|
+
const loadedName = chatPanel
|
|
840
|
+
.getLoadedModelNames()
|
|
841
|
+
.find(name => chatPanel.getLoadedModel(name) === model);
|
|
842
|
+
|
|
843
|
+
if (!loadedName) {
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
chatPanel.disposeLoadedModel(loadedName);
|
|
848
|
+
return true;
|
|
774
849
|
};
|
|
775
850
|
|
|
776
851
|
commands.addCommand(CommandIds.openChat, {
|
|
@@ -805,11 +880,18 @@ function registerCommands(
|
|
|
805
880
|
return false;
|
|
806
881
|
}
|
|
807
882
|
|
|
883
|
+
const shouldFocus = args.focus === true;
|
|
884
|
+
let widget: ChatWidget | MainAreaChat | undefined;
|
|
808
885
|
if (area === 'main') {
|
|
809
|
-
openInMain(model);
|
|
886
|
+
widget = openInMain(model);
|
|
810
887
|
} else {
|
|
811
|
-
chatPanel.open({ model });
|
|
888
|
+
widget = chatPanel.open({ model });
|
|
889
|
+
}
|
|
890
|
+
if (shouldFocus) {
|
|
891
|
+
focusOnChat(area, widget);
|
|
812
892
|
}
|
|
893
|
+
applyInputArgs(model, { ...args, focus: shouldFocus });
|
|
894
|
+
|
|
813
895
|
return true;
|
|
814
896
|
},
|
|
815
897
|
describedBy: {
|
|
@@ -828,6 +910,137 @@ function registerCommands(
|
|
|
828
910
|
provider: {
|
|
829
911
|
type: 'string',
|
|
830
912
|
description: trans.__('The provider/model to use with this chat')
|
|
913
|
+
},
|
|
914
|
+
input: {
|
|
915
|
+
type: 'string',
|
|
916
|
+
description: trans.__('The input text to prefill in the chat')
|
|
917
|
+
},
|
|
918
|
+
focus: {
|
|
919
|
+
type: 'boolean',
|
|
920
|
+
description: trans.__(
|
|
921
|
+
'Whether to focus the chat input after opening it'
|
|
922
|
+
)
|
|
923
|
+
},
|
|
924
|
+
autoSend: {
|
|
925
|
+
type: 'boolean',
|
|
926
|
+
description: trans.__(
|
|
927
|
+
'Whether to auto-send the provided input after opening the chat'
|
|
928
|
+
)
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
});
|
|
934
|
+
|
|
935
|
+
commands.addCommand(CommandIds.openOrRevealChat, {
|
|
936
|
+
label: trans.__('Open or reveal the chat panel'),
|
|
937
|
+
execute: async (args): Promise<boolean> => {
|
|
938
|
+
const area = (args.area as string) === 'main' ? 'main' : 'side';
|
|
939
|
+
const provider = (args.provider as string) ?? undefined;
|
|
940
|
+
const name = (args.name as string) ?? undefined;
|
|
941
|
+
const shouldFocus = args.focus === true;
|
|
942
|
+
|
|
943
|
+
let existingWidget = findChatWidget(name, provider);
|
|
944
|
+
if (!existingWidget && !name) {
|
|
945
|
+
const providerConfig = provider
|
|
946
|
+
? settingsModel.getProvider(provider)
|
|
947
|
+
: settingsModel.getDefaultProvider();
|
|
948
|
+
existingWidget = findChatWidget(undefined, providerConfig?.id);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
// If the side chat model is loaded but not currently displayed, reveal it first.
|
|
952
|
+
if (!existingWidget && name) {
|
|
953
|
+
const loadedModel = chatPanel.getLoadedModel(name);
|
|
954
|
+
if (loadedModel) {
|
|
955
|
+
existingWidget = chatPanel.open({ model: loadedModel });
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
if (!existingWidget) {
|
|
960
|
+
return commands.execute(CommandIds.openChat, {
|
|
961
|
+
...args,
|
|
962
|
+
focus: shouldFocus
|
|
963
|
+
}) as Promise<boolean>;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
const currentArea =
|
|
967
|
+
existingWidget instanceof MainAreaChat ? 'main' : 'side';
|
|
968
|
+
if (currentArea !== area) {
|
|
969
|
+
const targetName = existingWidget.model.name;
|
|
970
|
+
const moved = (await commands.execute(CommandIds.moveChat, {
|
|
971
|
+
name: targetName,
|
|
972
|
+
area
|
|
973
|
+
})) as boolean;
|
|
974
|
+
if (!moved) {
|
|
975
|
+
return false;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
const movedWidget = findChatWidget(targetName);
|
|
979
|
+
if (!movedWidget) {
|
|
980
|
+
return false;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
if (area === 'side') {
|
|
984
|
+
chatPanel.open({ model: movedWidget.model });
|
|
985
|
+
}
|
|
986
|
+
if (shouldFocus) {
|
|
987
|
+
focusOnChat(area, movedWidget);
|
|
988
|
+
}
|
|
989
|
+
applyInputArgs(movedWidget.model, {
|
|
990
|
+
...args,
|
|
991
|
+
focus: shouldFocus
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
return true;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if (area === 'side') {
|
|
998
|
+
chatPanel.open({ model: existingWidget.model });
|
|
999
|
+
}
|
|
1000
|
+
if (shouldFocus) {
|
|
1001
|
+
focusOnChat(area, existingWidget);
|
|
1002
|
+
}
|
|
1003
|
+
applyInputArgs(existingWidget.model, {
|
|
1004
|
+
...args,
|
|
1005
|
+
focus: shouldFocus
|
|
1006
|
+
});
|
|
1007
|
+
|
|
1008
|
+
return true;
|
|
1009
|
+
},
|
|
1010
|
+
describedBy: {
|
|
1011
|
+
args: {
|
|
1012
|
+
type: 'object',
|
|
1013
|
+
properties: {
|
|
1014
|
+
area: {
|
|
1015
|
+
type: 'string',
|
|
1016
|
+
enum: ['main', 'side'],
|
|
1017
|
+
description: trans.__(
|
|
1018
|
+
'The name of the area to open or reveal the chat in'
|
|
1019
|
+
)
|
|
1020
|
+
},
|
|
1021
|
+
name: {
|
|
1022
|
+
type: 'string',
|
|
1023
|
+
description: trans.__('The name of the chat')
|
|
1024
|
+
},
|
|
1025
|
+
provider: {
|
|
1026
|
+
type: 'string',
|
|
1027
|
+
description: trans.__('The provider/model to use with this chat')
|
|
1028
|
+
},
|
|
1029
|
+
input: {
|
|
1030
|
+
type: 'string',
|
|
1031
|
+
description: trans.__('The input text to prefill in the chat')
|
|
1032
|
+
},
|
|
1033
|
+
focus: {
|
|
1034
|
+
type: 'boolean',
|
|
1035
|
+
description: trans.__(
|
|
1036
|
+
'Whether to focus the chat input after opening it'
|
|
1037
|
+
)
|
|
1038
|
+
},
|
|
1039
|
+
autoSend: {
|
|
1040
|
+
type: 'boolean',
|
|
1041
|
+
description: trans.__(
|
|
1042
|
+
'Whether to auto-send the provided input after opening the chat'
|
|
1043
|
+
)
|
|
831
1044
|
}
|
|
832
1045
|
}
|
|
833
1046
|
}
|
|
@@ -887,7 +1100,8 @@ function registerCommands(
|
|
|
887
1100
|
activeProvider: previousModel.agentManager.activeProvider,
|
|
888
1101
|
tokenUsage: previousModel.agentManager.tokenUsage,
|
|
889
1102
|
messages: previousModel.messages,
|
|
890
|
-
autosave: previousModel.autosave
|
|
1103
|
+
autosave: previousModel.autosave,
|
|
1104
|
+
title: previousModel.title
|
|
891
1105
|
});
|
|
892
1106
|
|
|
893
1107
|
// Wait (with timeout) for the tracker to have updated the previous widget.
|
|
@@ -907,6 +1121,15 @@ function registerCommands(
|
|
|
907
1121
|
|
|
908
1122
|
if (area === 'main') {
|
|
909
1123
|
openInMain(model);
|
|
1124
|
+
|
|
1125
|
+
if (previousWidget instanceof ChatWidget) {
|
|
1126
|
+
// Clean up the side-panel model entry before disposing the previous
|
|
1127
|
+
// widget/model state.
|
|
1128
|
+
if (!disposeSideChatModel(previousModel)) {
|
|
1129
|
+
previousWidget.dispose();
|
|
1130
|
+
previousModel.dispose();
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
910
1133
|
} else {
|
|
911
1134
|
previousWidget?.dispose();
|
|
912
1135
|
previousModel.dispose();
|
|
@@ -1516,6 +1739,7 @@ export default [
|
|
|
1516
1739
|
skillRegistryPlugin,
|
|
1517
1740
|
skillsCommandPlugin,
|
|
1518
1741
|
chatModelHandler,
|
|
1742
|
+
activeCellManager,
|
|
1519
1743
|
plugin,
|
|
1520
1744
|
toolRegistry,
|
|
1521
1745
|
agentManagerFactory,
|
|
@@ -4,6 +4,7 @@ import { createMistral } from '@ai-sdk/mistral';
|
|
|
4
4
|
import { createOpenAI } from '@ai-sdk/openai';
|
|
5
5
|
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
6
6
|
|
|
7
|
+
import { BUILT_IN_PROVIDER_MODEL_INFO } from './generated-context-windows';
|
|
7
8
|
import type { IProviderInfo } from '../tokens';
|
|
8
9
|
import type { IModelOptions } from './models';
|
|
9
10
|
|
|
@@ -30,6 +31,7 @@ export const anthropicProvider: IProviderInfo = {
|
|
|
30
31
|
'claude-sonnet-4-0',
|
|
31
32
|
'claude-sonnet-4-20250514'
|
|
32
33
|
],
|
|
34
|
+
modelInfo: BUILT_IN_PROVIDER_MODEL_INFO.anthropic,
|
|
33
35
|
supportsBaseURL: true,
|
|
34
36
|
supportsHeaders: true,
|
|
35
37
|
providerToolCapabilities: {
|
|
@@ -77,6 +79,7 @@ export const googleProvider: IProviderInfo = {
|
|
|
77
79
|
'gemini-flash-latest',
|
|
78
80
|
'gemini-flash-lite-latest'
|
|
79
81
|
],
|
|
82
|
+
modelInfo: BUILT_IN_PROVIDER_MODEL_INFO.google,
|
|
80
83
|
supportsBaseURL: true,
|
|
81
84
|
factory: (options: IModelOptions) => {
|
|
82
85
|
if (!options.apiKey) {
|
|
@@ -113,6 +116,7 @@ export const mistralProvider: IProviderInfo = {
|
|
|
113
116
|
'codestral-latest',
|
|
114
117
|
'devstral-latest'
|
|
115
118
|
],
|
|
119
|
+
modelInfo: BUILT_IN_PROVIDER_MODEL_INFO.mistral,
|
|
116
120
|
supportsBaseURL: true,
|
|
117
121
|
factory: (options: IModelOptions) => {
|
|
118
122
|
if (!options.apiKey) {
|
|
@@ -182,6 +186,7 @@ export const openaiProvider: IProviderInfo = {
|
|
|
182
186
|
'gpt-3.5-turbo',
|
|
183
187
|
'gpt-3.5-turbo-0125'
|
|
184
188
|
],
|
|
189
|
+
modelInfo: BUILT_IN_PROVIDER_MODEL_INFO.openai,
|
|
185
190
|
supportsBaseURL: true,
|
|
186
191
|
supportsHeaders: true,
|
|
187
192
|
providerToolCapabilities: {
|