@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.
Files changed (162) hide show
  1. package/lib/agent.d.ts +233 -0
  2. package/lib/agent.js +604 -0
  3. package/lib/chat-model.d.ts +195 -0
  4. package/lib/chat-model.js +590 -0
  5. package/lib/completion/completion-provider.d.ts +83 -0
  6. package/lib/completion/completion-provider.js +209 -0
  7. package/lib/completion/index.d.ts +1 -0
  8. package/lib/completion/index.js +1 -0
  9. package/lib/components/clear-button.d.ts +18 -0
  10. package/lib/components/clear-button.js +31 -0
  11. package/lib/components/index.d.ts +3 -0
  12. package/lib/components/index.js +3 -0
  13. package/lib/components/model-select.d.ts +19 -0
  14. package/lib/components/model-select.js +154 -0
  15. package/lib/components/stop-button.d.ts +3 -3
  16. package/lib/components/stop-button.js +8 -9
  17. package/lib/components/token-usage-display.d.ts +45 -0
  18. package/lib/components/token-usage-display.js +74 -0
  19. package/lib/components/tool-select.d.ts +27 -0
  20. package/lib/components/tool-select.js +130 -0
  21. package/lib/icons.d.ts +3 -1
  22. package/lib/icons.js +10 -13
  23. package/lib/index.d.ts +4 -5
  24. package/lib/index.js +322 -167
  25. package/lib/mcp/browser.d.ts +68 -0
  26. package/lib/mcp/browser.js +132 -0
  27. package/lib/models/settings-model.d.ts +69 -0
  28. package/lib/models/settings-model.js +295 -0
  29. package/lib/providers/built-in-providers.d.ts +9 -0
  30. package/lib/providers/built-in-providers.js +192 -0
  31. package/lib/providers/models.d.ts +37 -0
  32. package/lib/providers/models.js +28 -0
  33. package/lib/providers/provider-registry.d.ts +94 -0
  34. package/lib/providers/provider-registry.js +155 -0
  35. package/lib/tokens.d.ts +157 -86
  36. package/lib/tokens.js +16 -12
  37. package/lib/tools/commands.d.ts +11 -0
  38. package/lib/tools/commands.js +126 -0
  39. package/lib/tools/file.d.ts +27 -0
  40. package/lib/tools/file.js +262 -0
  41. package/lib/tools/notebook.d.ts +40 -0
  42. package/lib/tools/notebook.js +762 -0
  43. package/lib/tools/tool-registry.d.ts +35 -0
  44. package/lib/tools/tool-registry.js +55 -0
  45. package/lib/widgets/ai-settings.d.ts +39 -0
  46. package/lib/widgets/ai-settings.js +506 -0
  47. package/lib/widgets/chat-wrapper.d.ts +144 -0
  48. package/lib/widgets/chat-wrapper.js +390 -0
  49. package/lib/widgets/provider-config-dialog.d.ts +13 -0
  50. package/lib/widgets/provider-config-dialog.js +104 -0
  51. package/package.json +150 -41
  52. package/schema/settings-model.json +153 -0
  53. package/src/agent.ts +800 -0
  54. package/src/chat-model.ts +770 -0
  55. package/src/completion/completion-provider.ts +308 -0
  56. package/src/completion/index.ts +1 -0
  57. package/src/components/clear-button.tsx +56 -0
  58. package/src/components/index.ts +3 -0
  59. package/src/components/model-select.tsx +245 -0
  60. package/src/components/stop-button.tsx +11 -11
  61. package/src/components/token-usage-display.tsx +130 -0
  62. package/src/components/tool-select.tsx +218 -0
  63. package/src/icons.ts +12 -14
  64. package/src/index.ts +468 -238
  65. package/src/mcp/browser.ts +213 -0
  66. package/src/models/settings-model.ts +409 -0
  67. package/src/providers/built-in-providers.ts +216 -0
  68. package/src/providers/models.ts +79 -0
  69. package/src/providers/provider-registry.ts +189 -0
  70. package/src/tokens.ts +203 -90
  71. package/src/tools/commands.ts +151 -0
  72. package/src/tools/file.ts +307 -0
  73. package/src/tools/notebook.ts +964 -0
  74. package/src/tools/tool-registry.ts +63 -0
  75. package/src/types.d.ts +4 -0
  76. package/src/widgets/ai-settings.tsx +1100 -0
  77. package/src/widgets/chat-wrapper.tsx +543 -0
  78. package/src/widgets/provider-config-dialog.tsx +256 -0
  79. package/style/base.css +335 -14
  80. package/style/icons/jupyternaut-lite.svg +1 -1
  81. package/lib/base-completer.d.ts +0 -49
  82. package/lib/base-completer.js +0 -14
  83. package/lib/chat-handler.d.ts +0 -56
  84. package/lib/chat-handler.js +0 -201
  85. package/lib/completion-provider.d.ts +0 -34
  86. package/lib/completion-provider.js +0 -32
  87. package/lib/default-prompts.d.ts +0 -2
  88. package/lib/default-prompts.js +0 -31
  89. package/lib/default-providers/Anthropic/completer.d.ts +0 -12
  90. package/lib/default-providers/Anthropic/completer.js +0 -46
  91. package/lib/default-providers/Anthropic/settings-schema.json +0 -70
  92. package/lib/default-providers/ChromeAI/completer.d.ts +0 -12
  93. package/lib/default-providers/ChromeAI/completer.js +0 -56
  94. package/lib/default-providers/ChromeAI/instructions.d.ts +0 -6
  95. package/lib/default-providers/ChromeAI/instructions.js +0 -42
  96. package/lib/default-providers/ChromeAI/settings-schema.json +0 -18
  97. package/lib/default-providers/Gemini/completer.d.ts +0 -12
  98. package/lib/default-providers/Gemini/completer.js +0 -48
  99. package/lib/default-providers/Gemini/instructions.d.ts +0 -2
  100. package/lib/default-providers/Gemini/instructions.js +0 -9
  101. package/lib/default-providers/Gemini/settings-schema.json +0 -64
  102. package/lib/default-providers/MistralAI/completer.d.ts +0 -13
  103. package/lib/default-providers/MistralAI/completer.js +0 -52
  104. package/lib/default-providers/MistralAI/instructions.d.ts +0 -2
  105. package/lib/default-providers/MistralAI/instructions.js +0 -18
  106. package/lib/default-providers/MistralAI/settings-schema.json +0 -75
  107. package/lib/default-providers/Ollama/completer.d.ts +0 -12
  108. package/lib/default-providers/Ollama/completer.js +0 -43
  109. package/lib/default-providers/Ollama/instructions.d.ts +0 -2
  110. package/lib/default-providers/Ollama/instructions.js +0 -70
  111. package/lib/default-providers/Ollama/settings-schema.json +0 -143
  112. package/lib/default-providers/OpenAI/completer.d.ts +0 -12
  113. package/lib/default-providers/OpenAI/completer.js +0 -43
  114. package/lib/default-providers/OpenAI/settings-schema.json +0 -628
  115. package/lib/default-providers/WebLLM/completer.d.ts +0 -21
  116. package/lib/default-providers/WebLLM/completer.js +0 -127
  117. package/lib/default-providers/WebLLM/instructions.d.ts +0 -6
  118. package/lib/default-providers/WebLLM/instructions.js +0 -32
  119. package/lib/default-providers/WebLLM/settings-schema.json +0 -19
  120. package/lib/default-providers/index.d.ts +0 -2
  121. package/lib/default-providers/index.js +0 -179
  122. package/lib/provider.d.ts +0 -144
  123. package/lib/provider.js +0 -412
  124. package/lib/settings/base.json +0 -7
  125. package/lib/settings/index.d.ts +0 -3
  126. package/lib/settings/index.js +0 -3
  127. package/lib/settings/panel.d.ts +0 -226
  128. package/lib/settings/panel.js +0 -510
  129. package/lib/settings/textarea.d.ts +0 -2
  130. package/lib/settings/textarea.js +0 -18
  131. package/lib/settings/utils.d.ts +0 -2
  132. package/lib/settings/utils.js +0 -4
  133. package/lib/types/ai-model.d.ts +0 -24
  134. package/lib/types/ai-model.js +0 -5
  135. package/schema/chat.json +0 -28
  136. package/schema/provider-registry.json +0 -29
  137. package/schema/system-prompts.json +0 -22
  138. package/src/base-completer.ts +0 -75
  139. package/src/chat-handler.ts +0 -262
  140. package/src/completion-provider.ts +0 -64
  141. package/src/default-prompts.ts +0 -33
  142. package/src/default-providers/Anthropic/completer.ts +0 -59
  143. package/src/default-providers/ChromeAI/completer.ts +0 -73
  144. package/src/default-providers/ChromeAI/instructions.ts +0 -45
  145. package/src/default-providers/Gemini/completer.ts +0 -61
  146. package/src/default-providers/Gemini/instructions.ts +0 -9
  147. package/src/default-providers/MistralAI/completer.ts +0 -69
  148. package/src/default-providers/MistralAI/instructions.ts +0 -18
  149. package/src/default-providers/Ollama/completer.ts +0 -54
  150. package/src/default-providers/Ollama/instructions.ts +0 -70
  151. package/src/default-providers/OpenAI/completer.ts +0 -54
  152. package/src/default-providers/WebLLM/completer.ts +0 -151
  153. package/src/default-providers/WebLLM/instructions.ts +0 -33
  154. package/src/default-providers/index.ts +0 -211
  155. package/src/global.d.ts +0 -9
  156. package/src/provider.ts +0 -514
  157. package/src/settings/index.ts +0 -3
  158. package/src/settings/panel.tsx +0 -773
  159. package/src/settings/textarea.tsx +0 -33
  160. package/src/settings/utils.ts +0 -5
  161. package/src/types/ai-model.ts +0 -37
  162. package/src/types/service-worker.d.ts +0 -6
@@ -0,0 +1,209 @@
1
+ import { NotebookPanel } from '@jupyterlab/notebook';
2
+ import { generateText } from 'ai';
3
+ import { createCompletionModel } from '../providers/models';
4
+ /**
5
+ * Default system prompt for code completion
6
+ */
7
+ const DEFAULT_COMPLETION_SYSTEM_PROMPT = `You are an AI code completion assistant. Complete the given code fragment with appropriate code.
8
+ Rules:
9
+ - Return only the completion text, no explanations or comments
10
+ - Do not include code block markers (\`\`\` or similar)
11
+ - Make completions contextually relevant to the surrounding code and notebook context
12
+ - Follow the language-specific conventions and style guidelines for the detected programming language
13
+ - Keep completions concise but functional
14
+ - Do not repeat the existing code that comes before the cursor
15
+ - Use variables, imports, functions, and other definitions from previous notebook cells when relevant`;
16
+ /**
17
+ * The generic completion provider to register to the completion provider manager.
18
+ */
19
+ export class AICompletionProvider {
20
+ /**
21
+ * Construct a new completion provider.
22
+ */
23
+ constructor(options) {
24
+ this._settingsModel = options.settingsModel;
25
+ this._completionProviderRegistry = options.completionProviderRegistry;
26
+ this._settingsModel.stateChanged.connect(() => {
27
+ this._updateModel();
28
+ });
29
+ this._updateModel();
30
+ }
31
+ /**
32
+ * The unique identifier of the provider.
33
+ */
34
+ identifier = '@jupyterlite/ai:completer';
35
+ /**
36
+ * Get the current completer name based on settings.
37
+ */
38
+ get name() {
39
+ const activeProvider = this._settingsModel.getCompleterProvider();
40
+ return activeProvider ? `${activeProvider.provider}-completer` : 'none';
41
+ }
42
+ /**
43
+ * Get the system prompt for the completion.
44
+ */
45
+ get systemPrompt() {
46
+ return DEFAULT_COMPLETION_SYSTEM_PROMPT;
47
+ }
48
+ /**
49
+ * Fetch completion items based on the request and context.
50
+ */
51
+ async fetch(request, context) {
52
+ if (!this._model) {
53
+ return { items: [] };
54
+ }
55
+ const { text, offset: cursorOffset } = request;
56
+ const prompt = text.slice(0, cursorOffset);
57
+ const suffix = text.slice(cursorOffset);
58
+ // Get current provider settings
59
+ const activeProvider = this._settingsModel.getCompleterProvider();
60
+ if (!activeProvider) {
61
+ return { items: [] };
62
+ }
63
+ const provider = activeProvider.provider;
64
+ const providerConfig = this._getProviderCompletionConfig(provider);
65
+ try {
66
+ let completionPrompt;
67
+ // Check if we're in a notebook or file and handle context accordingly
68
+ if (context.widget instanceof NotebookPanel) {
69
+ // Extract notebook context with surrounding cells
70
+ const contextString = this._extractNotebookContext(context, request);
71
+ completionPrompt = contextString;
72
+ }
73
+ else {
74
+ // For files, use simpler approach
75
+ completionPrompt = prompt.trim();
76
+ if (providerConfig.customPromptFormat && suffix.trim()) {
77
+ completionPrompt = providerConfig.customPromptFormat(prompt, suffix);
78
+ }
79
+ }
80
+ const { text: completion } = await generateText({
81
+ model: this._model,
82
+ prompt: completionPrompt,
83
+ system: this.systemPrompt,
84
+ temperature: providerConfig.temperature || 0.3
85
+ });
86
+ // Clean up provider-specific artifacts if cleanup function is provided
87
+ let cleanCompletion = completion;
88
+ if (providerConfig.cleanupCompletion) {
89
+ cleanCompletion = providerConfig.cleanupCompletion(completion);
90
+ }
91
+ const items = [
92
+ {
93
+ insertText: cleanCompletion,
94
+ filterText: providerConfig.useFilterText
95
+ ? prompt.substring(completionPrompt.length)
96
+ : undefined
97
+ }
98
+ ];
99
+ return { items };
100
+ }
101
+ catch (error) {
102
+ console.error(`Error fetching completions from ${provider}:`, error);
103
+ return { items: [] };
104
+ }
105
+ }
106
+ /**
107
+ * Update the language model based on current settings.
108
+ */
109
+ _updateModel() {
110
+ const activeProvider = this._settingsModel.getCompleterProvider();
111
+ if (!activeProvider) {
112
+ this._model = null;
113
+ return;
114
+ }
115
+ const provider = activeProvider.provider;
116
+ const model = activeProvider.model;
117
+ const apiKey = this._settingsModel.getApiKey(activeProvider.id);
118
+ try {
119
+ this._model = createCompletionModel({
120
+ provider,
121
+ model,
122
+ apiKey
123
+ }, this._completionProviderRegistry);
124
+ }
125
+ catch (error) {
126
+ console.error(`Error creating model for ${provider}:`, error);
127
+ this._model = null;
128
+ }
129
+ }
130
+ /**
131
+ * Extract context from notebook cells
132
+ */
133
+ _extractNotebookContext(context, request) {
134
+ const { text, offset: cursorOffset } = request;
135
+ let codeBeforeCursor = text.slice(0, cursorOffset);
136
+ let codeAfterCursor = text.slice(cursorOffset);
137
+ const notebookPanel = context.widget;
138
+ const notebook = notebookPanel.content;
139
+ const currentCellIndex = notebook.activeCellIndex;
140
+ const cells = notebook.widgets;
141
+ // For notebooks, include context from surrounding cells
142
+ const cellsAbove = [];
143
+ const cellsBelow = [];
144
+ // Get content from cells above current cell
145
+ for (let i = 0; i < currentCellIndex; i++) {
146
+ const cell = cells[i];
147
+ if (cell.model.type === 'code') {
148
+ const source = cell.model.sharedModel.source;
149
+ if (source.trim()) {
150
+ cellsAbove.push(source.trim());
151
+ }
152
+ }
153
+ }
154
+ // Get content from cells below current cell
155
+ for (let i = currentCellIndex + 1; i < cells.length; i++) {
156
+ const cell = cells[i];
157
+ if (cell.model.type === 'code') {
158
+ const source = cell.model.sharedModel.source;
159
+ if (source.trim()) {
160
+ cellsBelow.push(source.trim());
161
+ }
162
+ }
163
+ }
164
+ // Include cells above in the code before cursor
165
+ if (cellsAbove.length > 0) {
166
+ const cellsAboveText = cellsAbove
167
+ .map((cell, index) => `# Cell ${index + 1}:\n${cell}`)
168
+ .join('\n\n');
169
+ codeBeforeCursor = `${cellsAboveText}\n\n# Current cell:\n${codeBeforeCursor}`;
170
+ }
171
+ // Include cells below in the code after cursor
172
+ if (cellsBelow.length > 0) {
173
+ const cellsBelowText = cellsBelow
174
+ .map((cell, index) => `# Cell ${index + 1}:\n${cell}`)
175
+ .join('\n\n');
176
+ codeAfterCursor = `${codeAfterCursor}\n\n# Cells below:\n${cellsBelowText}`;
177
+ }
178
+ const parts = [];
179
+ // Add code before cursor
180
+ if (codeBeforeCursor) {
181
+ parts.push('# Code before cursor:');
182
+ parts.push(codeBeforeCursor);
183
+ }
184
+ // Add completion instruction
185
+ parts.push('# Complete the code at cursor position');
186
+ // Add code after cursor
187
+ if (codeAfterCursor) {
188
+ parts.push('# Code after cursor:');
189
+ parts.push(codeAfterCursor);
190
+ }
191
+ return parts.length > 1 ? parts.join('\n\n') + '\n\n' : '';
192
+ }
193
+ /**
194
+ * Get provider-specific completion configuration from registry
195
+ */
196
+ _getProviderCompletionConfig(provider) {
197
+ const providerInfo = this._completionProviderRegistry?.getProviderInfo(provider);
198
+ const completionConfig = providerInfo?.customSettings?.completionConfig;
199
+ // Return provider config or default config
200
+ return (completionConfig || {
201
+ temperature: 0.3,
202
+ supportsFillInMiddle: false,
203
+ useFilterText: false
204
+ });
205
+ }
206
+ _settingsModel;
207
+ _completionProviderRegistry;
208
+ _model = null;
209
+ }
@@ -0,0 +1 @@
1
+ export * from './completion-provider';
@@ -0,0 +1 @@
1
+ export * from './completion-provider';
@@ -0,0 +1,18 @@
1
+ import { InputToolbarRegistry } from '@jupyter/chat';
2
+ /**
3
+ * Properties of the clear button.
4
+ */
5
+ export interface IClearButtonProps extends InputToolbarRegistry.IToolbarItemProps {
6
+ /**
7
+ * The function to clear messages.
8
+ */
9
+ clearMessages: () => void;
10
+ }
11
+ /**
12
+ * The clear button component.
13
+ */
14
+ export declare function ClearButton(props: IClearButtonProps): JSX.Element;
15
+ /**
16
+ * Factory returning the clear button toolbar item.
17
+ */
18
+ export declare function clearItem(): InputToolbarRegistry.IToolbarItem;
@@ -0,0 +1,31 @@
1
+ import { TooltippedButton } from '@jupyter/chat';
2
+ import ClearIcon from '@mui/icons-material/Clear';
3
+ import React from 'react';
4
+ /**
5
+ * The clear button component.
6
+ */
7
+ export function ClearButton(props) {
8
+ const tooltip = 'Clear chat';
9
+ return (React.createElement(TooltippedButton, { onClick: props.clearMessages, tooltip: tooltip, buttonProps: {
10
+ size: 'small',
11
+ variant: 'outlined',
12
+ color: 'secondary',
13
+ title: tooltip
14
+ } },
15
+ React.createElement(ClearIcon, null)));
16
+ }
17
+ /**
18
+ * Factory returning the clear button toolbar item.
19
+ */
20
+ export function clearItem() {
21
+ return {
22
+ element: (props) => {
23
+ const { model } = props;
24
+ const clearMessages = () => model.chatContext.clearMessages();
25
+ const clearProps = { ...props, clearMessages };
26
+ return ClearButton(clearProps);
27
+ },
28
+ position: 0,
29
+ hidden: false
30
+ };
31
+ }
@@ -0,0 +1,3 @@
1
+ export * from './clear-button';
2
+ export * from './stop-button';
3
+ export * from './tool-select';
@@ -0,0 +1,3 @@
1
+ export * from './clear-button';
2
+ export * from './stop-button';
3
+ export * from './tool-select';
@@ -0,0 +1,19 @@
1
+ import { InputToolbarRegistry } from '@jupyter/chat';
2
+ import { AISettingsModel } from '../models/settings-model';
3
+ /**
4
+ * Properties for the model select component.
5
+ */
6
+ export interface IModelSelectProps extends InputToolbarRegistry.IToolbarItemProps {
7
+ /**
8
+ * The settings model to get available models and current selection from.
9
+ */
10
+ settingsModel: AISettingsModel;
11
+ }
12
+ /**
13
+ * The model select component for choosing AI models.
14
+ */
15
+ export declare function ModelSelect(props: IModelSelectProps): JSX.Element;
16
+ /**
17
+ * Factory function returning the toolbar item for model selection.
18
+ */
19
+ export declare function createModelSelectItem(settingsModel: AISettingsModel): InputToolbarRegistry.IToolbarItem;
@@ -0,0 +1,154 @@
1
+ import { 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
+ const SELECT_ITEM_CLASS = 'labai-model-select-item';
6
+ /**
7
+ * The model select component for choosing AI models.
8
+ */
9
+ export function ModelSelect(props) {
10
+ const { settingsModel } = props;
11
+ const [currentProvider, setCurrentProvider] = useState('');
12
+ const [currentModel, setCurrentModel] = useState('');
13
+ const [menuAnchorEl, setMenuAnchorEl] = useState(null);
14
+ const [menuOpen, setMenuOpen] = useState(false);
15
+ // Get configured providers from settings model
16
+ const configuredProviders = settingsModel.providers;
17
+ const openMenu = useCallback((el) => {
18
+ setMenuAnchorEl(el);
19
+ setMenuOpen(true);
20
+ }, []);
21
+ const closeMenu = useCallback(() => {
22
+ setMenuOpen(false);
23
+ }, []);
24
+ const selectModel = useCallback(async (providerId) => {
25
+ // Set the active provider using the provider ID
26
+ await settingsModel.setActiveProvider(providerId);
27
+ closeMenu();
28
+ // Provider selected successfully
29
+ }, [settingsModel, closeMenu]);
30
+ // Update current selection when settings model changes
31
+ useEffect(() => {
32
+ const updateCurrentSelection = () => {
33
+ const activeProvider = settingsModel.getActiveProvider();
34
+ if (activeProvider) {
35
+ setCurrentProvider(activeProvider.id);
36
+ setCurrentModel(activeProvider.model);
37
+ }
38
+ };
39
+ updateCurrentSelection();
40
+ settingsModel.stateChanged.connect(updateCurrentSelection);
41
+ return () => {
42
+ settingsModel.stateChanged.disconnect(updateCurrentSelection);
43
+ };
44
+ }, [settingsModel]);
45
+ // Get current provider label for display
46
+ const activeProvider = settingsModel.getActiveProvider();
47
+ const currentProviderLabel = activeProvider?.name || currentProvider;
48
+ // Use all configured providers (they're already validated when added)
49
+ const availableProviders = configuredProviders;
50
+ // Get available model combinations from configured providers
51
+ const availableModels = availableProviders.map(provider => ({
52
+ provider: provider.id,
53
+ providerLabel: provider.name,
54
+ model: provider.model,
55
+ isSelected: provider.id === currentProvider && provider.model === currentModel
56
+ }));
57
+ // Show a message if no providers are configured
58
+ if (availableModels.length === 0) {
59
+ return (React.createElement(TooltippedButton, { onClick: () => { }, tooltip: "No providers configured. Please go to AI Settings to add a provider.", buttonProps: {
60
+ size: 'small',
61
+ variant: 'outlined',
62
+ color: 'warning',
63
+ disabled: true,
64
+ title: 'No Providers Available'
65
+ }, sx: {
66
+ minWidth: 'auto',
67
+ display: 'flex',
68
+ alignItems: 'center',
69
+ height: '29px'
70
+ } },
71
+ React.createElement(Typography, { variant: "caption", sx: { fontSize: '0.7rem', fontWeight: 500 } }, "No Providers")));
72
+ }
73
+ return (React.createElement(React.Fragment, null,
74
+ React.createElement(TooltippedButton, { onClick: e => {
75
+ openMenu(e.currentTarget);
76
+ }, tooltip: `Current Model: ${currentProviderLabel} - ${currentModel}`, buttonProps: {
77
+ size: 'small',
78
+ variant: 'contained',
79
+ color: 'primary',
80
+ title: 'Select AI Model',
81
+ onKeyDown: e => {
82
+ if (e.key !== 'Enter' && e.key !== ' ') {
83
+ return;
84
+ }
85
+ openMenu(e.currentTarget);
86
+ // Stop propagation to prevent sending message
87
+ e.stopPropagation();
88
+ }
89
+ }, sx: {
90
+ minWidth: 'auto',
91
+ display: 'flex',
92
+ alignItems: 'center',
93
+ height: '29px'
94
+ } },
95
+ React.createElement(Typography, { variant: "caption", sx: { fontSize: '0.7rem', fontWeight: 500, textTransform: 'none' } }, currentProviderLabel)),
96
+ React.createElement(Menu, { open: menuOpen, onClose: closeMenu, anchorEl: menuAnchorEl, anchorOrigin: {
97
+ vertical: 'top',
98
+ horizontal: 'right'
99
+ }, transformOrigin: {
100
+ vertical: 'bottom',
101
+ horizontal: 'right'
102
+ }, sx: {
103
+ '& .MuiPaper-root': {
104
+ maxHeight: '300px',
105
+ overflowY: 'auto'
106
+ },
107
+ '& .MuiMenuItem-root': {
108
+ padding: '0.5em',
109
+ paddingRight: '2em',
110
+ minWidth: '200px'
111
+ }
112
+ } }, availableModels.map(({ provider, providerLabel, isSelected }) => (React.createElement(MenuItem, { key: provider, className: SELECT_ITEM_CLASS, onClick: async (e) => {
113
+ await selectModel(provider);
114
+ // Prevent sending message on model selection
115
+ e.stopPropagation();
116
+ }, sx: {
117
+ backgroundColor: isSelected
118
+ ? 'var(--jp-brand-color3, rgba(33, 150, 243, 0.1))'
119
+ : 'transparent',
120
+ '&:hover': {
121
+ backgroundColor: isSelected
122
+ ? 'var(--jp-brand-color3, rgba(33, 150, 243, 0.15))'
123
+ : 'var(--jp-layout-color1)'
124
+ },
125
+ display: 'flex',
126
+ alignItems: 'center',
127
+ gap: '8px'
128
+ } },
129
+ isSelected ? (React.createElement(CheckIcon, { sx: {
130
+ color: 'var(--jp-brand-color1, #2196F3)',
131
+ fontSize: 16
132
+ } })) : (React.createElement("div", { style: { width: '16px' } })),
133
+ React.createElement(Typography, { variant: "body2", component: "div", sx: {
134
+ fontWeight: isSelected ? 600 : 400,
135
+ color: isSelected
136
+ ? 'var(--jp-brand-color1, #2196F3)'
137
+ : 'inherit'
138
+ } }, providerLabel)))))));
139
+ }
140
+ /**
141
+ * Factory function returning the toolbar item for model selection.
142
+ */
143
+ export function createModelSelectItem(settingsModel) {
144
+ return {
145
+ element: (props) => {
146
+ const modelSelectProps = {
147
+ ...props,
148
+ settingsModel
149
+ };
150
+ return React.createElement(ModelSelect, { ...modelSelectProps });
151
+ },
152
+ position: 0.5
153
+ };
154
+ }
@@ -9,10 +9,10 @@ export interface IStopButtonProps extends InputToolbarRegistry.IToolbarItemProps
9
9
  stopStreaming: () => void;
10
10
  }
11
11
  /**
12
- * The stop button.
12
+ * The stop button component.
13
13
  */
14
14
  export declare function StopButton(props: IStopButtonProps): JSX.Element;
15
15
  /**
16
- * factory returning the toolbar item.
16
+ * Factory returning the stop button toolbar item.
17
17
  */
18
- export declare function stopItem(stopStreaming: () => void): InputToolbarRegistry.IToolbarItem;
18
+ export declare function stopItem(): InputToolbarRegistry.IToolbarItem;
@@ -1,32 +1,31 @@
1
- /*
2
- * Copyright (c) Jupyter Development Team.
3
- * Distributed under the terms of the Modified BSD License.
4
- */
1
+ import { TooltippedButton } from '@jupyter/chat';
5
2
  import StopIcon from '@mui/icons-material/Stop';
6
3
  import React from 'react';
7
- import { TooltippedButton } from '@jupyter/chat';
8
4
  /**
9
- * The stop button.
5
+ * The stop button component.
10
6
  */
11
7
  export function StopButton(props) {
12
8
  const tooltip = 'Stop streaming';
13
9
  return (React.createElement(TooltippedButton, { onClick: props.stopStreaming, tooltip: tooltip, buttonProps: {
14
10
  size: 'small',
15
11
  variant: 'contained',
12
+ color: 'error',
16
13
  title: tooltip
17
14
  } },
18
15
  React.createElement(StopIcon, null)));
19
16
  }
20
17
  /**
21
- * factory returning the toolbar item.
18
+ * Factory returning the stop button toolbar item.
22
19
  */
23
- export function stopItem(stopStreaming) {
20
+ export function stopItem() {
24
21
  return {
25
22
  element: (props) => {
23
+ const { model } = props;
24
+ const stopStreaming = () => model.chatContext.stopStreaming();
26
25
  const stopProps = { ...props, stopStreaming };
27
26
  return StopButton(stopProps);
28
27
  },
29
28
  position: 50,
30
- hidden: true /* hidden by default */
29
+ hidden: true // Hidden by default, shown when streaming
31
30
  };
32
31
  }
@@ -0,0 +1,45 @@
1
+ import { ReactWidget } from '@jupyterlab/ui-components';
2
+ import React from 'react';
3
+ import { ISignal } from '@lumino/signaling';
4
+ import { AISettingsModel } from '../models/settings-model';
5
+ import { ITokenUsage } from '../tokens';
6
+ /**
7
+ * Props for the TokenUsageDisplay component.
8
+ */
9
+ export interface ITokenUsageDisplayProps {
10
+ /**
11
+ * The token usage changed signal
12
+ */
13
+ tokenUsageChanged: ISignal<any, ITokenUsage>;
14
+ /**
15
+ * The settings model instance for configuration options
16
+ */
17
+ settingsModel: AISettingsModel;
18
+ }
19
+ /**
20
+ * React component that displays token usage information.
21
+ * Shows input/output token counts with up/down arrows.
22
+ * Only renders when token usage display is enabled in settings.
23
+ */
24
+ export declare const TokenUsageDisplay: React.FC<ITokenUsageDisplayProps>;
25
+ /**
26
+ * JupyterLab widget wrapper for the TokenUsageDisplay component.
27
+ * Extends ReactWidget to integrate with the JupyterLab widget system.
28
+ */
29
+ export declare class TokenUsageWidget extends ReactWidget {
30
+ /**
31
+ * Creates a new TokenUsageWidget instance.
32
+ * @param options - Configuration options containing required models
33
+ */
34
+ constructor(options: {
35
+ tokenUsageChanged: ISignal<any, ITokenUsage>;
36
+ settingsModel: AISettingsModel;
37
+ });
38
+ /**
39
+ * Renders the React component within the widget.
40
+ * @returns The TokenUsageDisplay React element
41
+ */
42
+ protected render(): React.ReactElement;
43
+ private _tokenUsageChanged;
44
+ private _settingsModel;
45
+ }
@@ -0,0 +1,74 @@
1
+ import { ReactWidget, UseSignal } from '@jupyterlab/ui-components';
2
+ import React from 'react';
3
+ /**
4
+ * React component that displays token usage information.
5
+ * Shows input/output token counts with up/down arrows.
6
+ * Only renders when token usage display is enabled in settings.
7
+ */
8
+ export const TokenUsageDisplay = ({ tokenUsageChanged, settingsModel }) => {
9
+ return (React.createElement(UseSignal, { signal: settingsModel.stateChanged, initialArgs: undefined }, () => {
10
+ const config = settingsModel.config;
11
+ if (!config.showTokenUsage) {
12
+ return null;
13
+ }
14
+ return (React.createElement(UseSignal, { signal: tokenUsageChanged }, (_, tokenUsage) => {
15
+ if (!tokenUsage) {
16
+ return null;
17
+ }
18
+ const total = tokenUsage.inputTokens + tokenUsage.outputTokens;
19
+ if (total === 0) {
20
+ return null;
21
+ }
22
+ return (React.createElement("div", { style: {
23
+ display: 'flex',
24
+ alignItems: 'center',
25
+ gap: '6px',
26
+ fontSize: '12px',
27
+ color: 'var(--jp-ui-font-color2)',
28
+ padding: '4px 8px',
29
+ backgroundColor: 'var(--jp-layout-color1)',
30
+ border: '1px solid var(--jp-border-color1)',
31
+ borderRadius: '4px',
32
+ whiteSpace: 'nowrap'
33
+ }, title: `Token Usage - Sent: ${tokenUsage.inputTokens.toLocaleString()}, Received: ${tokenUsage.outputTokens.toLocaleString()}, Total: ${total.toLocaleString()}` },
34
+ React.createElement("span", { style: {
35
+ display: 'flex',
36
+ alignItems: 'center',
37
+ gap: '2px'
38
+ } },
39
+ React.createElement("span", null, "\u2191"),
40
+ React.createElement("span", null, tokenUsage.inputTokens.toLocaleString())),
41
+ React.createElement("span", { style: {
42
+ display: 'flex',
43
+ alignItems: 'center',
44
+ gap: '2px'
45
+ } },
46
+ React.createElement("span", null, "\u2193"),
47
+ React.createElement("span", null, tokenUsage.outputTokens.toLocaleString()))));
48
+ }));
49
+ }));
50
+ };
51
+ /**
52
+ * JupyterLab widget wrapper for the TokenUsageDisplay component.
53
+ * Extends ReactWidget to integrate with the JupyterLab widget system.
54
+ */
55
+ export class TokenUsageWidget extends ReactWidget {
56
+ /**
57
+ * Creates a new TokenUsageWidget instance.
58
+ * @param options - Configuration options containing required models
59
+ */
60
+ constructor(options) {
61
+ super();
62
+ this._tokenUsageChanged = options.tokenUsageChanged;
63
+ this._settingsModel = options.settingsModel;
64
+ }
65
+ /**
66
+ * Renders the React component within the widget.
67
+ * @returns The TokenUsageDisplay React element
68
+ */
69
+ render() {
70
+ return (React.createElement(TokenUsageDisplay, { tokenUsageChanged: this._tokenUsageChanged, settingsModel: this._settingsModel }));
71
+ }
72
+ _tokenUsageChanged;
73
+ _settingsModel;
74
+ }
@@ -0,0 +1,27 @@
1
+ import { InputToolbarRegistry } from '@jupyter/chat';
2
+ import { IToolRegistry } from '../tokens';
3
+ /**
4
+ * Properties for the tool select component.
5
+ */
6
+ export interface IToolSelectProps extends InputToolbarRegistry.IToolbarItemProps {
7
+ /**
8
+ * The tool registry to get available tools from.
9
+ */
10
+ toolRegistry: IToolRegistry;
11
+ /**
12
+ * Whether tools are enabled.
13
+ */
14
+ toolsEnabled: boolean;
15
+ /**
16
+ * Function to handle tool selection changes.
17
+ */
18
+ onToolSelectionChange: (selectedToolNames: string[]) => void;
19
+ }
20
+ /**
21
+ * The tool select component for choosing AI tools.
22
+ */
23
+ export declare function ToolSelect(props: IToolSelectProps): JSX.Element;
24
+ /**
25
+ * Factory function returning the toolbar item for tool selection.
26
+ */
27
+ export declare function createToolSelectItem(toolRegistry: IToolRegistry, toolsEnabled?: boolean): InputToolbarRegistry.IToolbarItem;