@jupyterlite/ai 0.8.0 → 0.9.0-a0
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 +233 -0
- package/lib/agent.js +604 -0
- package/lib/chat-model.d.ts +195 -0
- package/lib/chat-model.js +590 -0
- package/lib/completion/completion-provider.d.ts +83 -0
- package/lib/completion/completion-provider.js +209 -0
- package/lib/completion/index.d.ts +1 -0
- package/lib/completion/index.js +1 -0
- package/lib/components/clear-button.d.ts +18 -0
- package/lib/components/clear-button.js +31 -0
- package/lib/components/index.d.ts +3 -0
- package/lib/components/index.js +3 -0
- package/lib/components/model-select.d.ts +19 -0
- package/lib/components/model-select.js +154 -0
- package/lib/components/stop-button.d.ts +3 -3
- package/lib/components/stop-button.js +8 -9
- package/lib/components/token-usage-display.d.ts +45 -0
- package/lib/components/token-usage-display.js +74 -0
- package/lib/components/tool-select.d.ts +27 -0
- package/lib/components/tool-select.js +130 -0
- package/lib/icons.d.ts +3 -1
- package/lib/icons.js +10 -13
- package/lib/index.d.ts +4 -5
- package/lib/index.js +322 -167
- package/lib/mcp/browser.d.ts +68 -0
- package/lib/mcp/browser.js +132 -0
- package/lib/models/settings-model.d.ts +69 -0
- package/lib/models/settings-model.js +295 -0
- package/lib/providers/built-in-providers.d.ts +9 -0
- package/lib/providers/built-in-providers.js +192 -0
- package/lib/providers/models.d.ts +37 -0
- package/lib/providers/models.js +28 -0
- package/lib/providers/provider-registry.d.ts +94 -0
- package/lib/providers/provider-registry.js +155 -0
- package/lib/tokens.d.ts +157 -86
- package/lib/tokens.js +16 -12
- package/lib/tools/commands.d.ts +11 -0
- package/lib/tools/commands.js +126 -0
- package/lib/tools/file.d.ts +27 -0
- package/lib/tools/file.js +262 -0
- package/lib/tools/notebook.d.ts +40 -0
- package/lib/tools/notebook.js +762 -0
- package/lib/tools/tool-registry.d.ts +35 -0
- package/lib/tools/tool-registry.js +55 -0
- package/lib/widgets/ai-settings.d.ts +39 -0
- package/lib/widgets/ai-settings.js +506 -0
- package/lib/widgets/chat-wrapper.d.ts +144 -0
- package/lib/widgets/chat-wrapper.js +390 -0
- package/lib/widgets/provider-config-dialog.d.ts +13 -0
- package/lib/widgets/provider-config-dialog.js +104 -0
- package/package.json +150 -41
- package/schema/settings-model.json +153 -0
- package/src/agent.ts +800 -0
- package/src/chat-model.ts +770 -0
- package/src/completion/completion-provider.ts +308 -0
- package/src/completion/index.ts +1 -0
- package/src/components/clear-button.tsx +56 -0
- package/src/components/index.ts +3 -0
- package/src/components/model-select.tsx +245 -0
- package/src/components/stop-button.tsx +11 -11
- package/src/components/token-usage-display.tsx +130 -0
- package/src/components/tool-select.tsx +218 -0
- package/src/icons.ts +12 -14
- package/src/index.ts +468 -238
- package/src/mcp/browser.ts +213 -0
- package/src/models/settings-model.ts +409 -0
- package/src/providers/built-in-providers.ts +216 -0
- package/src/providers/models.ts +79 -0
- package/src/providers/provider-registry.ts +189 -0
- package/src/tokens.ts +203 -90
- package/src/tools/commands.ts +151 -0
- package/src/tools/file.ts +307 -0
- package/src/tools/notebook.ts +964 -0
- package/src/tools/tool-registry.ts +63 -0
- package/src/types.d.ts +4 -0
- package/src/widgets/ai-settings.tsx +1100 -0
- package/src/widgets/chat-wrapper.tsx +543 -0
- package/src/widgets/provider-config-dialog.tsx +256 -0
- package/style/base.css +335 -14
- package/style/icons/jupyternaut-lite.svg +1 -1
- package/lib/base-completer.d.ts +0 -49
- package/lib/base-completer.js +0 -14
- package/lib/chat-handler.d.ts +0 -56
- package/lib/chat-handler.js +0 -201
- package/lib/completion-provider.d.ts +0 -34
- package/lib/completion-provider.js +0 -32
- package/lib/default-prompts.d.ts +0 -2
- package/lib/default-prompts.js +0 -31
- package/lib/default-providers/Anthropic/completer.d.ts +0 -12
- package/lib/default-providers/Anthropic/completer.js +0 -46
- package/lib/default-providers/Anthropic/settings-schema.json +0 -70
- package/lib/default-providers/ChromeAI/completer.d.ts +0 -12
- package/lib/default-providers/ChromeAI/completer.js +0 -56
- package/lib/default-providers/ChromeAI/instructions.d.ts +0 -6
- package/lib/default-providers/ChromeAI/instructions.js +0 -42
- package/lib/default-providers/ChromeAI/settings-schema.json +0 -18
- package/lib/default-providers/Gemini/completer.d.ts +0 -12
- package/lib/default-providers/Gemini/completer.js +0 -48
- package/lib/default-providers/Gemini/instructions.d.ts +0 -2
- package/lib/default-providers/Gemini/instructions.js +0 -9
- package/lib/default-providers/Gemini/settings-schema.json +0 -64
- package/lib/default-providers/MistralAI/completer.d.ts +0 -13
- package/lib/default-providers/MistralAI/completer.js +0 -52
- package/lib/default-providers/MistralAI/instructions.d.ts +0 -2
- package/lib/default-providers/MistralAI/instructions.js +0 -18
- package/lib/default-providers/MistralAI/settings-schema.json +0 -75
- package/lib/default-providers/Ollama/completer.d.ts +0 -12
- package/lib/default-providers/Ollama/completer.js +0 -43
- package/lib/default-providers/Ollama/instructions.d.ts +0 -2
- package/lib/default-providers/Ollama/instructions.js +0 -70
- package/lib/default-providers/Ollama/settings-schema.json +0 -143
- package/lib/default-providers/OpenAI/completer.d.ts +0 -12
- package/lib/default-providers/OpenAI/completer.js +0 -43
- package/lib/default-providers/OpenAI/settings-schema.json +0 -628
- package/lib/default-providers/WebLLM/completer.d.ts +0 -21
- package/lib/default-providers/WebLLM/completer.js +0 -127
- package/lib/default-providers/WebLLM/instructions.d.ts +0 -6
- package/lib/default-providers/WebLLM/instructions.js +0 -32
- package/lib/default-providers/WebLLM/settings-schema.json +0 -19
- package/lib/default-providers/index.d.ts +0 -2
- package/lib/default-providers/index.js +0 -179
- package/lib/provider.d.ts +0 -144
- package/lib/provider.js +0 -412
- package/lib/settings/base.json +0 -7
- package/lib/settings/index.d.ts +0 -3
- package/lib/settings/index.js +0 -3
- package/lib/settings/panel.d.ts +0 -226
- package/lib/settings/panel.js +0 -510
- package/lib/settings/textarea.d.ts +0 -2
- package/lib/settings/textarea.js +0 -18
- package/lib/settings/utils.d.ts +0 -2
- package/lib/settings/utils.js +0 -4
- package/lib/types/ai-model.d.ts +0 -24
- package/lib/types/ai-model.js +0 -5
- package/schema/chat.json +0 -28
- package/schema/provider-registry.json +0 -29
- package/schema/system-prompts.json +0 -22
- package/src/base-completer.ts +0 -75
- package/src/chat-handler.ts +0 -262
- package/src/completion-provider.ts +0 -64
- package/src/default-prompts.ts +0 -33
- package/src/default-providers/Anthropic/completer.ts +0 -59
- package/src/default-providers/ChromeAI/completer.ts +0 -73
- package/src/default-providers/ChromeAI/instructions.ts +0 -45
- package/src/default-providers/Gemini/completer.ts +0 -61
- package/src/default-providers/Gemini/instructions.ts +0 -9
- package/src/default-providers/MistralAI/completer.ts +0 -69
- package/src/default-providers/MistralAI/instructions.ts +0 -18
- package/src/default-providers/Ollama/completer.ts +0 -54
- package/src/default-providers/Ollama/instructions.ts +0 -70
- package/src/default-providers/OpenAI/completer.ts +0 -54
- package/src/default-providers/WebLLM/completer.ts +0 -151
- package/src/default-providers/WebLLM/instructions.ts +0 -33
- package/src/default-providers/index.ts +0 -211
- package/src/global.d.ts +0 -9
- package/src/provider.ts +0 -514
- package/src/settings/index.ts +0 -3
- package/src/settings/panel.tsx +0 -773
- package/src/settings/textarea.tsx +0 -33
- package/src/settings/utils.ts +0 -5
- package/src/types/ai-model.ts +0 -37
- package/src/types/service-worker.d.ts +0 -6
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CompletionHandler,
|
|
3
|
+
IInlineCompletionContext,
|
|
4
|
+
IInlineCompletionList,
|
|
5
|
+
IInlineCompletionProvider
|
|
6
|
+
} from '@jupyterlab/completer';
|
|
7
|
+
import { NotebookPanel } from '@jupyterlab/notebook';
|
|
8
|
+
import { generateText, LanguageModel } from 'ai';
|
|
9
|
+
|
|
10
|
+
import { AISettingsModel } from '../models/settings-model';
|
|
11
|
+
import type { ICompletionProviderRegistry } from '../tokens';
|
|
12
|
+
import { createCompletionModel } from '../providers/models';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Configuration interface for provider-specific completion behavior
|
|
16
|
+
*/
|
|
17
|
+
export interface IProviderCompletionConfig {
|
|
18
|
+
/**
|
|
19
|
+
* Temperature setting for the provider
|
|
20
|
+
*/
|
|
21
|
+
temperature?: number;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Whether the provider supports fill-in-the-middle completion
|
|
25
|
+
*/
|
|
26
|
+
supportsFillInMiddle?: boolean;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whether to set filterText for this provider
|
|
30
|
+
*/
|
|
31
|
+
useFilterText?: boolean;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Custom prompt formatter for provider-specific requirements
|
|
35
|
+
*/
|
|
36
|
+
customPromptFormat?: (prompt: string, suffix: string) => string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Function to clean up provider-specific artifacts from completion text
|
|
40
|
+
*/
|
|
41
|
+
cleanupCompletion?: (completion: string) => string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Default system prompt for code completion
|
|
46
|
+
*/
|
|
47
|
+
const DEFAULT_COMPLETION_SYSTEM_PROMPT = `You are an AI code completion assistant. Complete the given code fragment with appropriate code.
|
|
48
|
+
Rules:
|
|
49
|
+
- Return only the completion text, no explanations or comments
|
|
50
|
+
- Do not include code block markers (\`\`\` or similar)
|
|
51
|
+
- Make completions contextually relevant to the surrounding code and notebook context
|
|
52
|
+
- Follow the language-specific conventions and style guidelines for the detected programming language
|
|
53
|
+
- Keep completions concise but functional
|
|
54
|
+
- Do not repeat the existing code that comes before the cursor
|
|
55
|
+
- Use variables, imports, functions, and other definitions from previous notebook cells when relevant`;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The generic completion provider to register to the completion provider manager.
|
|
59
|
+
*/
|
|
60
|
+
export class AICompletionProvider implements IInlineCompletionProvider {
|
|
61
|
+
/**
|
|
62
|
+
* Construct a new completion provider.
|
|
63
|
+
*/
|
|
64
|
+
constructor(options: AICompletionProvider.IOptions) {
|
|
65
|
+
this._settingsModel = options.settingsModel;
|
|
66
|
+
this._completionProviderRegistry = options.completionProviderRegistry;
|
|
67
|
+
this._settingsModel.stateChanged.connect(() => {
|
|
68
|
+
this._updateModel();
|
|
69
|
+
});
|
|
70
|
+
this._updateModel();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The unique identifier of the provider.
|
|
75
|
+
*/
|
|
76
|
+
readonly identifier = '@jupyterlite/ai:completer';
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get the current completer name based on settings.
|
|
80
|
+
*/
|
|
81
|
+
get name(): string {
|
|
82
|
+
const activeProvider = this._settingsModel.getCompleterProvider();
|
|
83
|
+
return activeProvider ? `${activeProvider.provider}-completer` : 'none';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get the system prompt for the completion.
|
|
88
|
+
*/
|
|
89
|
+
get systemPrompt(): string {
|
|
90
|
+
return DEFAULT_COMPLETION_SYSTEM_PROMPT;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Fetch completion items based on the request and context.
|
|
95
|
+
*/
|
|
96
|
+
async fetch(
|
|
97
|
+
request: CompletionHandler.IRequest,
|
|
98
|
+
context: IInlineCompletionContext
|
|
99
|
+
): Promise<IInlineCompletionList> {
|
|
100
|
+
if (!this._model) {
|
|
101
|
+
return { items: [] };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const { text, offset: cursorOffset } = request;
|
|
105
|
+
const prompt = text.slice(0, cursorOffset);
|
|
106
|
+
const suffix = text.slice(cursorOffset);
|
|
107
|
+
|
|
108
|
+
// Get current provider settings
|
|
109
|
+
const activeProvider = this._settingsModel.getCompleterProvider();
|
|
110
|
+
if (!activeProvider) {
|
|
111
|
+
return { items: [] };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const provider = activeProvider.provider;
|
|
115
|
+
const providerConfig = this._getProviderCompletionConfig(provider);
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
let completionPrompt: string;
|
|
119
|
+
|
|
120
|
+
// Check if we're in a notebook or file and handle context accordingly
|
|
121
|
+
if (context.widget instanceof NotebookPanel) {
|
|
122
|
+
// Extract notebook context with surrounding cells
|
|
123
|
+
const contextString = this._extractNotebookContext(context, request);
|
|
124
|
+
completionPrompt = contextString;
|
|
125
|
+
} else {
|
|
126
|
+
// For files, use simpler approach
|
|
127
|
+
completionPrompt = prompt.trim();
|
|
128
|
+
if (providerConfig.customPromptFormat && suffix.trim()) {
|
|
129
|
+
completionPrompt = providerConfig.customPromptFormat(prompt, suffix);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const { text: completion } = await generateText({
|
|
134
|
+
model: this._model,
|
|
135
|
+
prompt: completionPrompt,
|
|
136
|
+
system: this.systemPrompt,
|
|
137
|
+
temperature: providerConfig.temperature || 0.3
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Clean up provider-specific artifacts if cleanup function is provided
|
|
141
|
+
let cleanCompletion = completion;
|
|
142
|
+
if (providerConfig.cleanupCompletion) {
|
|
143
|
+
cleanCompletion = providerConfig.cleanupCompletion(completion);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const items = [
|
|
147
|
+
{
|
|
148
|
+
insertText: cleanCompletion,
|
|
149
|
+
filterText: providerConfig.useFilterText
|
|
150
|
+
? prompt.substring(completionPrompt.length)
|
|
151
|
+
: undefined
|
|
152
|
+
}
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
return { items };
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.error(`Error fetching completions from ${provider}:`, error);
|
|
158
|
+
return { items: [] };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Update the language model based on current settings.
|
|
164
|
+
*/
|
|
165
|
+
private _updateModel(): void {
|
|
166
|
+
const activeProvider = this._settingsModel.getCompleterProvider();
|
|
167
|
+
if (!activeProvider) {
|
|
168
|
+
this._model = null;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const provider = activeProvider.provider;
|
|
173
|
+
const model = activeProvider.model;
|
|
174
|
+
const apiKey = this._settingsModel.getApiKey(activeProvider.id);
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
this._model = createCompletionModel(
|
|
178
|
+
{
|
|
179
|
+
provider,
|
|
180
|
+
model,
|
|
181
|
+
apiKey
|
|
182
|
+
},
|
|
183
|
+
this._completionProviderRegistry
|
|
184
|
+
);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error(`Error creating model for ${provider}:`, error);
|
|
187
|
+
this._model = null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Extract context from notebook cells
|
|
193
|
+
*/
|
|
194
|
+
private _extractNotebookContext(
|
|
195
|
+
context: IInlineCompletionContext,
|
|
196
|
+
request: CompletionHandler.IRequest
|
|
197
|
+
): string {
|
|
198
|
+
const { text, offset: cursorOffset } = request;
|
|
199
|
+
let codeBeforeCursor = text.slice(0, cursorOffset);
|
|
200
|
+
let codeAfterCursor = text.slice(cursorOffset);
|
|
201
|
+
|
|
202
|
+
const notebookPanel = context.widget as NotebookPanel;
|
|
203
|
+
const notebook = notebookPanel.content;
|
|
204
|
+
const currentCellIndex = notebook.activeCellIndex;
|
|
205
|
+
const cells = notebook.widgets;
|
|
206
|
+
|
|
207
|
+
// For notebooks, include context from surrounding cells
|
|
208
|
+
const cellsAbove: string[] = [];
|
|
209
|
+
const cellsBelow: string[] = [];
|
|
210
|
+
|
|
211
|
+
// Get content from cells above current cell
|
|
212
|
+
for (let i = 0; i < currentCellIndex; i++) {
|
|
213
|
+
const cell = cells[i];
|
|
214
|
+
if (cell.model.type === 'code') {
|
|
215
|
+
const source = cell.model.sharedModel.source;
|
|
216
|
+
if (source.trim()) {
|
|
217
|
+
cellsAbove.push(source.trim());
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Get content from cells below current cell
|
|
223
|
+
for (let i = currentCellIndex + 1; i < cells.length; i++) {
|
|
224
|
+
const cell = cells[i];
|
|
225
|
+
if (cell.model.type === 'code') {
|
|
226
|
+
const source = cell.model.sharedModel.source;
|
|
227
|
+
if (source.trim()) {
|
|
228
|
+
cellsBelow.push(source.trim());
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Include cells above in the code before cursor
|
|
234
|
+
if (cellsAbove.length > 0) {
|
|
235
|
+
const cellsAboveText = cellsAbove
|
|
236
|
+
.map((cell, index) => `# Cell ${index + 1}:\n${cell}`)
|
|
237
|
+
.join('\n\n');
|
|
238
|
+
codeBeforeCursor = `${cellsAboveText}\n\n# Current cell:\n${codeBeforeCursor}`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Include cells below in the code after cursor
|
|
242
|
+
if (cellsBelow.length > 0) {
|
|
243
|
+
const cellsBelowText = cellsBelow
|
|
244
|
+
.map((cell, index) => `# Cell ${index + 1}:\n${cell}`)
|
|
245
|
+
.join('\n\n');
|
|
246
|
+
codeAfterCursor = `${codeAfterCursor}\n\n# Cells below:\n${cellsBelowText}`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const parts: string[] = [];
|
|
250
|
+
|
|
251
|
+
// Add code before cursor
|
|
252
|
+
if (codeBeforeCursor) {
|
|
253
|
+
parts.push('# Code before cursor:');
|
|
254
|
+
parts.push(codeBeforeCursor);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Add completion instruction
|
|
258
|
+
parts.push('# Complete the code at cursor position');
|
|
259
|
+
|
|
260
|
+
// Add code after cursor
|
|
261
|
+
if (codeAfterCursor) {
|
|
262
|
+
parts.push('# Code after cursor:');
|
|
263
|
+
parts.push(codeAfterCursor);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return parts.length > 1 ? parts.join('\n\n') + '\n\n' : '';
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Get provider-specific completion configuration from registry
|
|
271
|
+
*/
|
|
272
|
+
private _getProviderCompletionConfig(
|
|
273
|
+
provider: string
|
|
274
|
+
): IProviderCompletionConfig {
|
|
275
|
+
const providerInfo =
|
|
276
|
+
this._completionProviderRegistry?.getProviderInfo(provider);
|
|
277
|
+
const completionConfig = providerInfo?.customSettings?.completionConfig;
|
|
278
|
+
|
|
279
|
+
// Return provider config or default config
|
|
280
|
+
return (
|
|
281
|
+
completionConfig || {
|
|
282
|
+
temperature: 0.3,
|
|
283
|
+
supportsFillInMiddle: false,
|
|
284
|
+
useFilterText: false
|
|
285
|
+
}
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private _settingsModel: AISettingsModel;
|
|
290
|
+
private _completionProviderRegistry?: ICompletionProviderRegistry;
|
|
291
|
+
private _model: LanguageModel | null = null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export namespace AICompletionProvider {
|
|
295
|
+
/**
|
|
296
|
+
* The options for the constructor of the completion provider.
|
|
297
|
+
*/
|
|
298
|
+
export interface IOptions {
|
|
299
|
+
/**
|
|
300
|
+
* The AI settings model.
|
|
301
|
+
*/
|
|
302
|
+
settingsModel: AISettingsModel;
|
|
303
|
+
/**
|
|
304
|
+
* The completion provider registry.
|
|
305
|
+
*/
|
|
306
|
+
completionProviderRegistry?: ICompletionProviderRegistry;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './completion-provider';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { InputToolbarRegistry, TooltippedButton } from '@jupyter/chat';
|
|
2
|
+
|
|
3
|
+
import ClearIcon from '@mui/icons-material/Clear';
|
|
4
|
+
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
import { AIChatModel } from '../chat-model';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Properties of the clear button.
|
|
11
|
+
*/
|
|
12
|
+
export interface IClearButtonProps
|
|
13
|
+
extends InputToolbarRegistry.IToolbarItemProps {
|
|
14
|
+
/**
|
|
15
|
+
* The function to clear messages.
|
|
16
|
+
*/
|
|
17
|
+
clearMessages: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The clear button component.
|
|
22
|
+
*/
|
|
23
|
+
export function ClearButton(props: IClearButtonProps): JSX.Element {
|
|
24
|
+
const tooltip = 'Clear chat';
|
|
25
|
+
return (
|
|
26
|
+
<TooltippedButton
|
|
27
|
+
onClick={props.clearMessages}
|
|
28
|
+
tooltip={tooltip}
|
|
29
|
+
buttonProps={{
|
|
30
|
+
size: 'small',
|
|
31
|
+
variant: 'outlined',
|
|
32
|
+
color: 'secondary',
|
|
33
|
+
title: tooltip
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
<ClearIcon />
|
|
37
|
+
</TooltippedButton>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Factory returning the clear button toolbar item.
|
|
43
|
+
*/
|
|
44
|
+
export function clearItem(): InputToolbarRegistry.IToolbarItem {
|
|
45
|
+
return {
|
|
46
|
+
element: (props: InputToolbarRegistry.IToolbarItemProps) => {
|
|
47
|
+
const { model } = props;
|
|
48
|
+
const clearMessages = () =>
|
|
49
|
+
(model.chatContext as AIChatModel.IAIChatContext).clearMessages();
|
|
50
|
+
const clearProps: IClearButtonProps = { ...props, clearMessages };
|
|
51
|
+
return ClearButton(clearProps);
|
|
52
|
+
},
|
|
53
|
+
position: 0,
|
|
54
|
+
hidden: false
|
|
55
|
+
};
|
|
56
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { InputToolbarRegistry, TooltippedButton } from '@jupyter/chat';
|
|
2
|
+
import CheckIcon from '@mui/icons-material/Check';
|
|
3
|
+
import { Menu, MenuItem, Typography } from '@mui/material';
|
|
4
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
5
|
+
import { AISettingsModel } from '../models/settings-model';
|
|
6
|
+
|
|
7
|
+
const SELECT_ITEM_CLASS = 'labai-model-select-item';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Properties for the model select component.
|
|
11
|
+
*/
|
|
12
|
+
export interface IModelSelectProps
|
|
13
|
+
extends InputToolbarRegistry.IToolbarItemProps {
|
|
14
|
+
/**
|
|
15
|
+
* The settings model to get available models and current selection from.
|
|
16
|
+
*/
|
|
17
|
+
settingsModel: AISettingsModel;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The model select component for choosing AI models.
|
|
22
|
+
*/
|
|
23
|
+
export function ModelSelect(props: IModelSelectProps): JSX.Element {
|
|
24
|
+
const { settingsModel } = props;
|
|
25
|
+
|
|
26
|
+
const [currentProvider, setCurrentProvider] = useState<string>('');
|
|
27
|
+
const [currentModel, setCurrentModel] = useState<string>('');
|
|
28
|
+
const [menuAnchorEl, setMenuAnchorEl] = useState<HTMLElement | null>(null);
|
|
29
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
30
|
+
|
|
31
|
+
// Get configured providers from settings model
|
|
32
|
+
const configuredProviders = settingsModel.providers;
|
|
33
|
+
|
|
34
|
+
const openMenu = useCallback((el: HTMLElement | null) => {
|
|
35
|
+
setMenuAnchorEl(el);
|
|
36
|
+
setMenuOpen(true);
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
const closeMenu = useCallback(() => {
|
|
40
|
+
setMenuOpen(false);
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
const selectModel = useCallback(
|
|
44
|
+
async (providerId: string) => {
|
|
45
|
+
// Set the active provider using the provider ID
|
|
46
|
+
await settingsModel.setActiveProvider(providerId);
|
|
47
|
+
closeMenu();
|
|
48
|
+
|
|
49
|
+
// Provider selected successfully
|
|
50
|
+
},
|
|
51
|
+
[settingsModel, closeMenu]
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Update current selection when settings model changes
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
const updateCurrentSelection = () => {
|
|
57
|
+
const activeProvider = settingsModel.getActiveProvider();
|
|
58
|
+
if (activeProvider) {
|
|
59
|
+
setCurrentProvider(activeProvider.id);
|
|
60
|
+
setCurrentModel(activeProvider.model);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
updateCurrentSelection();
|
|
65
|
+
settingsModel.stateChanged.connect(updateCurrentSelection);
|
|
66
|
+
return () => {
|
|
67
|
+
settingsModel.stateChanged.disconnect(updateCurrentSelection);
|
|
68
|
+
};
|
|
69
|
+
}, [settingsModel]);
|
|
70
|
+
|
|
71
|
+
// Get current provider label for display
|
|
72
|
+
const activeProvider = settingsModel.getActiveProvider();
|
|
73
|
+
const currentProviderLabel = activeProvider?.name || currentProvider;
|
|
74
|
+
|
|
75
|
+
// Use all configured providers (they're already validated when added)
|
|
76
|
+
const availableProviders = configuredProviders;
|
|
77
|
+
|
|
78
|
+
// Get available model combinations from configured providers
|
|
79
|
+
const availableModels = availableProviders.map(provider => ({
|
|
80
|
+
provider: provider.id,
|
|
81
|
+
providerLabel: provider.name,
|
|
82
|
+
model: provider.model,
|
|
83
|
+
isSelected:
|
|
84
|
+
provider.id === currentProvider && provider.model === currentModel
|
|
85
|
+
}));
|
|
86
|
+
|
|
87
|
+
// Show a message if no providers are configured
|
|
88
|
+
if (availableModels.length === 0) {
|
|
89
|
+
return (
|
|
90
|
+
<TooltippedButton
|
|
91
|
+
onClick={() => {}}
|
|
92
|
+
tooltip="No providers configured. Please go to AI Settings to add a provider."
|
|
93
|
+
buttonProps={{
|
|
94
|
+
size: 'small',
|
|
95
|
+
variant: 'outlined',
|
|
96
|
+
color: 'warning',
|
|
97
|
+
disabled: true,
|
|
98
|
+
title: 'No Providers Available'
|
|
99
|
+
}}
|
|
100
|
+
sx={{
|
|
101
|
+
minWidth: 'auto',
|
|
102
|
+
display: 'flex',
|
|
103
|
+
alignItems: 'center',
|
|
104
|
+
height: '29px'
|
|
105
|
+
}}
|
|
106
|
+
>
|
|
107
|
+
<Typography
|
|
108
|
+
variant="caption"
|
|
109
|
+
sx={{ fontSize: '0.7rem', fontWeight: 500 }}
|
|
110
|
+
>
|
|
111
|
+
No Providers
|
|
112
|
+
</Typography>
|
|
113
|
+
</TooltippedButton>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<>
|
|
119
|
+
<TooltippedButton
|
|
120
|
+
onClick={e => {
|
|
121
|
+
openMenu(e.currentTarget);
|
|
122
|
+
}}
|
|
123
|
+
tooltip={`Current Model: ${currentProviderLabel} - ${currentModel}`}
|
|
124
|
+
buttonProps={{
|
|
125
|
+
size: 'small',
|
|
126
|
+
variant: 'contained',
|
|
127
|
+
color: 'primary',
|
|
128
|
+
title: 'Select AI Model',
|
|
129
|
+
onKeyDown: e => {
|
|
130
|
+
if (e.key !== 'Enter' && e.key !== ' ') {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
openMenu(e.currentTarget);
|
|
134
|
+
// Stop propagation to prevent sending message
|
|
135
|
+
e.stopPropagation();
|
|
136
|
+
}
|
|
137
|
+
}}
|
|
138
|
+
sx={{
|
|
139
|
+
minWidth: 'auto',
|
|
140
|
+
display: 'flex',
|
|
141
|
+
alignItems: 'center',
|
|
142
|
+
height: '29px'
|
|
143
|
+
}}
|
|
144
|
+
>
|
|
145
|
+
<Typography
|
|
146
|
+
variant="caption"
|
|
147
|
+
sx={{ fontSize: '0.7rem', fontWeight: 500, textTransform: 'none' }}
|
|
148
|
+
>
|
|
149
|
+
{currentProviderLabel}
|
|
150
|
+
</Typography>
|
|
151
|
+
</TooltippedButton>
|
|
152
|
+
|
|
153
|
+
<Menu
|
|
154
|
+
open={menuOpen}
|
|
155
|
+
onClose={closeMenu}
|
|
156
|
+
anchorEl={menuAnchorEl}
|
|
157
|
+
anchorOrigin={{
|
|
158
|
+
vertical: 'top',
|
|
159
|
+
horizontal: 'right'
|
|
160
|
+
}}
|
|
161
|
+
transformOrigin={{
|
|
162
|
+
vertical: 'bottom',
|
|
163
|
+
horizontal: 'right'
|
|
164
|
+
}}
|
|
165
|
+
sx={{
|
|
166
|
+
'& .MuiPaper-root': {
|
|
167
|
+
maxHeight: '300px',
|
|
168
|
+
overflowY: 'auto'
|
|
169
|
+
},
|
|
170
|
+
'& .MuiMenuItem-root': {
|
|
171
|
+
padding: '0.5em',
|
|
172
|
+
paddingRight: '2em',
|
|
173
|
+
minWidth: '200px'
|
|
174
|
+
}
|
|
175
|
+
}}
|
|
176
|
+
>
|
|
177
|
+
{availableModels.map(({ provider, providerLabel, isSelected }) => (
|
|
178
|
+
<MenuItem
|
|
179
|
+
key={provider}
|
|
180
|
+
className={SELECT_ITEM_CLASS}
|
|
181
|
+
onClick={async e => {
|
|
182
|
+
await selectModel(provider);
|
|
183
|
+
// Prevent sending message on model selection
|
|
184
|
+
e.stopPropagation();
|
|
185
|
+
}}
|
|
186
|
+
sx={{
|
|
187
|
+
backgroundColor: isSelected
|
|
188
|
+
? 'var(--jp-brand-color3, rgba(33, 150, 243, 0.1))'
|
|
189
|
+
: 'transparent',
|
|
190
|
+
'&:hover': {
|
|
191
|
+
backgroundColor: isSelected
|
|
192
|
+
? 'var(--jp-brand-color3, rgba(33, 150, 243, 0.15))'
|
|
193
|
+
: 'var(--jp-layout-color1)'
|
|
194
|
+
},
|
|
195
|
+
display: 'flex',
|
|
196
|
+
alignItems: 'center',
|
|
197
|
+
gap: '8px'
|
|
198
|
+
}}
|
|
199
|
+
>
|
|
200
|
+
{isSelected ? (
|
|
201
|
+
<CheckIcon
|
|
202
|
+
sx={{
|
|
203
|
+
color: 'var(--jp-brand-color1, #2196F3)',
|
|
204
|
+
fontSize: 16
|
|
205
|
+
}}
|
|
206
|
+
/>
|
|
207
|
+
) : (
|
|
208
|
+
<div style={{ width: '16px' }} />
|
|
209
|
+
)}
|
|
210
|
+
<Typography
|
|
211
|
+
variant="body2"
|
|
212
|
+
component="div"
|
|
213
|
+
sx={{
|
|
214
|
+
fontWeight: isSelected ? 600 : 400,
|
|
215
|
+
color: isSelected
|
|
216
|
+
? 'var(--jp-brand-color1, #2196F3)'
|
|
217
|
+
: 'inherit'
|
|
218
|
+
}}
|
|
219
|
+
>
|
|
220
|
+
{providerLabel}
|
|
221
|
+
</Typography>
|
|
222
|
+
</MenuItem>
|
|
223
|
+
))}
|
|
224
|
+
</Menu>
|
|
225
|
+
</>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Factory function returning the toolbar item for model selection.
|
|
231
|
+
*/
|
|
232
|
+
export function createModelSelectItem(
|
|
233
|
+
settingsModel: AISettingsModel
|
|
234
|
+
): InputToolbarRegistry.IToolbarItem {
|
|
235
|
+
return {
|
|
236
|
+
element: (props: InputToolbarRegistry.IToolbarItemProps) => {
|
|
237
|
+
const modelSelectProps: IModelSelectProps = {
|
|
238
|
+
...props,
|
|
239
|
+
settingsModel
|
|
240
|
+
};
|
|
241
|
+
return <ModelSelect {...modelSelectProps} />;
|
|
242
|
+
},
|
|
243
|
+
position: 0.5
|
|
244
|
+
};
|
|
245
|
+
}
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
* Copyright (c) Jupyter Development Team.
|
|
3
|
-
* Distributed under the terms of the Modified BSD License.
|
|
4
|
-
*/
|
|
1
|
+
import { InputToolbarRegistry, TooltippedButton } from '@jupyter/chat';
|
|
5
2
|
|
|
6
3
|
import StopIcon from '@mui/icons-material/Stop';
|
|
4
|
+
|
|
7
5
|
import React from 'react';
|
|
8
6
|
|
|
9
|
-
import {
|
|
7
|
+
import { AIChatModel } from '../chat-model';
|
|
10
8
|
|
|
11
9
|
/**
|
|
12
10
|
* Properties of the stop button.
|
|
@@ -20,7 +18,7 @@ export interface IStopButtonProps
|
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
/**
|
|
23
|
-
* The stop button.
|
|
21
|
+
* The stop button component.
|
|
24
22
|
*/
|
|
25
23
|
export function StopButton(props: IStopButtonProps): JSX.Element {
|
|
26
24
|
const tooltip = 'Stop streaming';
|
|
@@ -31,6 +29,7 @@ export function StopButton(props: IStopButtonProps): JSX.Element {
|
|
|
31
29
|
buttonProps={{
|
|
32
30
|
size: 'small',
|
|
33
31
|
variant: 'contained',
|
|
32
|
+
color: 'error',
|
|
34
33
|
title: tooltip
|
|
35
34
|
}}
|
|
36
35
|
>
|
|
@@ -40,17 +39,18 @@ export function StopButton(props: IStopButtonProps): JSX.Element {
|
|
|
40
39
|
}
|
|
41
40
|
|
|
42
41
|
/**
|
|
43
|
-
*
|
|
42
|
+
* Factory returning the stop button toolbar item.
|
|
44
43
|
*/
|
|
45
|
-
export function stopItem(
|
|
46
|
-
stopStreaming: () => void
|
|
47
|
-
): InputToolbarRegistry.IToolbarItem {
|
|
44
|
+
export function stopItem(): InputToolbarRegistry.IToolbarItem {
|
|
48
45
|
return {
|
|
49
46
|
element: (props: InputToolbarRegistry.IToolbarItemProps) => {
|
|
47
|
+
const { model } = props;
|
|
48
|
+
const stopStreaming = () =>
|
|
49
|
+
(model.chatContext as AIChatModel.IAIChatContext).stopStreaming();
|
|
50
50
|
const stopProps: IStopButtonProps = { ...props, stopStreaming };
|
|
51
51
|
return StopButton(stopProps);
|
|
52
52
|
},
|
|
53
53
|
position: 50,
|
|
54
|
-
hidden: true
|
|
54
|
+
hidden: true // Hidden by default, shown when streaming
|
|
55
55
|
};
|
|
56
56
|
}
|