@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,144 @@
|
|
|
1
|
+
import { CommandRegistry } from '@lumino/commands';
|
|
2
|
+
import { Panel, Widget } from '@lumino/widgets';
|
|
3
|
+
import { AIChatModel } from '../chat-model';
|
|
4
|
+
import { AISettingsModel } from '../models/settings-model';
|
|
5
|
+
/**
|
|
6
|
+
* A widget wrapper for the chat panel that provides toolbar functionality
|
|
7
|
+
* and approval button handling for AI tool calls.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ChatWrapperWidget extends Panel {
|
|
10
|
+
/**
|
|
11
|
+
* Constructs a new ChatWrapperWidget.
|
|
12
|
+
*
|
|
13
|
+
* @param options - Configuration options for the widget
|
|
14
|
+
*/
|
|
15
|
+
constructor(options: ChatWrapperWidget.IOptions);
|
|
16
|
+
/**
|
|
17
|
+
* Creates and configures the toolbar with token usage display and settings button.
|
|
18
|
+
*
|
|
19
|
+
* TODO: integrate with IToolbarRegistry to allow adding custom toolbar items
|
|
20
|
+
*
|
|
21
|
+
* @returns The configured toolbar widget
|
|
22
|
+
*/
|
|
23
|
+
private _createToolbar;
|
|
24
|
+
/**
|
|
25
|
+
* Sets up event handlers for existing approval buttons in the chat panel.
|
|
26
|
+
*/
|
|
27
|
+
private _setupApprovalHandlers;
|
|
28
|
+
/**
|
|
29
|
+
* Adds click event handler to an approval button.
|
|
30
|
+
*
|
|
31
|
+
* @param button - The button element to add handler to
|
|
32
|
+
*/
|
|
33
|
+
private _addButtonHandler;
|
|
34
|
+
/**
|
|
35
|
+
* Handles click events for individual approval buttons.
|
|
36
|
+
*
|
|
37
|
+
* @param event - The click event
|
|
38
|
+
*/
|
|
39
|
+
private _handleButtonClick;
|
|
40
|
+
/**
|
|
41
|
+
* Adds click event handler to a grouped approval button.
|
|
42
|
+
*
|
|
43
|
+
* @param button - The button element to add handler to
|
|
44
|
+
*/
|
|
45
|
+
private _addGroupedButtonHandler;
|
|
46
|
+
/**
|
|
47
|
+
* Handles click events for grouped approval buttons.
|
|
48
|
+
*
|
|
49
|
+
* @param event - The click event
|
|
50
|
+
*/
|
|
51
|
+
private _handleGroupedButtonClick;
|
|
52
|
+
/**
|
|
53
|
+
* Shows approval status by replacing buttons with status indicator.
|
|
54
|
+
*
|
|
55
|
+
* @param buttonsContainer - The container element holding the buttons
|
|
56
|
+
* @param isApprove - Whether the action was approval or rejection
|
|
57
|
+
*/
|
|
58
|
+
private _showApprovalStatus;
|
|
59
|
+
/**
|
|
60
|
+
* Shows group approval status by replacing buttons with status indicator.
|
|
61
|
+
*
|
|
62
|
+
* @param buttonsContainer - The container element holding the buttons
|
|
63
|
+
* @param isApprove - Whether the action was approval or rejection
|
|
64
|
+
* @param toolCount - The number of tools that were approved/rejected
|
|
65
|
+
*/
|
|
66
|
+
private _showGroupApprovalStatus;
|
|
67
|
+
/**
|
|
68
|
+
* Sets up mutation observer to watch for new messages and process approval buttons.
|
|
69
|
+
*/
|
|
70
|
+
private _setupMessageProcessing;
|
|
71
|
+
/**
|
|
72
|
+
* Processes text nodes to replace approval button placeholders with actual button elements.
|
|
73
|
+
*
|
|
74
|
+
* @param element - The element to search for approval button placeholders
|
|
75
|
+
*/
|
|
76
|
+
private _processApprovalButtons;
|
|
77
|
+
/**
|
|
78
|
+
* Creates an approval button element with appropriate styling and classes.
|
|
79
|
+
*
|
|
80
|
+
* @param text - The button text
|
|
81
|
+
* @param isApprove - Whether this is an approve or reject button
|
|
82
|
+
* @param additionalClasses - Additional CSS classes to add
|
|
83
|
+
* @returns The created button element
|
|
84
|
+
*/
|
|
85
|
+
private _createApprovalButton;
|
|
86
|
+
/**
|
|
87
|
+
* Creates and inserts approval buttons for a single tool call.
|
|
88
|
+
*
|
|
89
|
+
* @param textNode - The text node to replace with buttons
|
|
90
|
+
* @param interruptionId - The interruption ID for the tool call
|
|
91
|
+
*/
|
|
92
|
+
private _createSingleApprovalButtons;
|
|
93
|
+
/**
|
|
94
|
+
* Creates and inserts approval buttons for grouped tool calls.
|
|
95
|
+
*
|
|
96
|
+
* @param textNode - The text node to replace with buttons
|
|
97
|
+
* @param groupId - The group ID for the tool calls
|
|
98
|
+
* @param interruptionIds - Comma-separated interruption IDs
|
|
99
|
+
*/
|
|
100
|
+
private _createGroupedApprovalButtons;
|
|
101
|
+
/**
|
|
102
|
+
* Finds the message ID by traversing up the DOM tree from a text node.
|
|
103
|
+
*
|
|
104
|
+
* @param textNode - The text node to start searching from
|
|
105
|
+
* @returns The message ID if found, null otherwise
|
|
106
|
+
*/
|
|
107
|
+
private _findMessageId;
|
|
108
|
+
/**
|
|
109
|
+
* Fixes focus issue by replacing the global click handler with a more selective one.
|
|
110
|
+
* Only focuses the input when clicking empty areas, not on interactive elements.
|
|
111
|
+
*/
|
|
112
|
+
private _fixCopyFocusIssue;
|
|
113
|
+
private _chatPanel;
|
|
114
|
+
private _chatModel;
|
|
115
|
+
private _settingsModel;
|
|
116
|
+
private _toolbar;
|
|
117
|
+
private _commands;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Namespace for ChatWrapperWidget statics.
|
|
121
|
+
*/
|
|
122
|
+
export declare namespace ChatWrapperWidget {
|
|
123
|
+
/**
|
|
124
|
+
* The options for the constructor of the chat wrapper widget.
|
|
125
|
+
*/
|
|
126
|
+
interface IOptions {
|
|
127
|
+
/**
|
|
128
|
+
* The chat panel widget to wrap.
|
|
129
|
+
*/
|
|
130
|
+
chatPanel: Widget;
|
|
131
|
+
/**
|
|
132
|
+
* The command registry for the chat wrapper.
|
|
133
|
+
*/
|
|
134
|
+
commands: CommandRegistry;
|
|
135
|
+
/**
|
|
136
|
+
* The chat model for the chat wrapper.
|
|
137
|
+
*/
|
|
138
|
+
chatModel: AIChatModel;
|
|
139
|
+
/**
|
|
140
|
+
* The settings model for the chat wrapper.
|
|
141
|
+
*/
|
|
142
|
+
settingsModel: AISettingsModel;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
import { settingsIcon, Toolbar, ToolbarButton } from '@jupyterlab/ui-components';
|
|
2
|
+
import { Panel } from '@lumino/widgets';
|
|
3
|
+
import { TokenUsageWidget } from '../components/token-usage-display';
|
|
4
|
+
/**
|
|
5
|
+
* CSS class for the chat toolbar
|
|
6
|
+
*/
|
|
7
|
+
const CHAT_TOOLBAR_CLASS = 'jp-AIChatToolbar';
|
|
8
|
+
/**
|
|
9
|
+
* CSS class for the chat panel
|
|
10
|
+
*/
|
|
11
|
+
const CHAT_PANEL_CLASS = 'jp-AIChatPanel';
|
|
12
|
+
/**
|
|
13
|
+
* A widget wrapper for the chat panel that provides toolbar functionality
|
|
14
|
+
* and approval button handling for AI tool calls.
|
|
15
|
+
*/
|
|
16
|
+
export class ChatWrapperWidget extends Panel {
|
|
17
|
+
/**
|
|
18
|
+
* Constructs a new ChatWrapperWidget.
|
|
19
|
+
*
|
|
20
|
+
* @param options - Configuration options for the widget
|
|
21
|
+
*/
|
|
22
|
+
constructor(options) {
|
|
23
|
+
super();
|
|
24
|
+
this._chatPanel = options.chatPanel;
|
|
25
|
+
this._chatModel = options.chatModel;
|
|
26
|
+
this._settingsModel = options.settingsModel;
|
|
27
|
+
this._commands = options.commands;
|
|
28
|
+
this._toolbar = this._createToolbar();
|
|
29
|
+
this.id = '@jupyterlite/ai:chat-wrapper';
|
|
30
|
+
this.title.caption = 'Chat with AI assistant';
|
|
31
|
+
this.title.icon = this._chatPanel.title.icon;
|
|
32
|
+
this.addClass('jp-AIChatWrapper');
|
|
33
|
+
this._toolbar.addClass(CHAT_TOOLBAR_CLASS);
|
|
34
|
+
this._chatPanel.addClass(CHAT_PANEL_CLASS);
|
|
35
|
+
// Add widgets to the panel
|
|
36
|
+
this.addWidget(this._toolbar);
|
|
37
|
+
this.addWidget(this._chatPanel);
|
|
38
|
+
// Set up approval button event handling
|
|
39
|
+
this._setupApprovalHandlers();
|
|
40
|
+
// Set up message processing for approval buttons
|
|
41
|
+
this._setupMessageProcessing();
|
|
42
|
+
// Fix the focus issue: override the global click handler
|
|
43
|
+
// TODO: remove after https://github.com/jupyterlab/jupyter-chat/issues/267
|
|
44
|
+
this._fixCopyFocusIssue();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Creates and configures the toolbar with token usage display and settings button.
|
|
48
|
+
*
|
|
49
|
+
* TODO: integrate with IToolbarRegistry to allow adding custom toolbar items
|
|
50
|
+
*
|
|
51
|
+
* @returns The configured toolbar widget
|
|
52
|
+
*/
|
|
53
|
+
_createToolbar() {
|
|
54
|
+
const toolbar = new Toolbar();
|
|
55
|
+
const tokenUsageWidget = new TokenUsageWidget({
|
|
56
|
+
tokenUsageChanged: this._chatModel.tokenUsageChanged,
|
|
57
|
+
settingsModel: this._settingsModel
|
|
58
|
+
});
|
|
59
|
+
toolbar.addItem('token-usage', tokenUsageWidget);
|
|
60
|
+
toolbar.addItem('spacer', Toolbar.createSpacerItem());
|
|
61
|
+
toolbar.addItem('settings', new ToolbarButton({
|
|
62
|
+
icon: settingsIcon,
|
|
63
|
+
onClick: () => {
|
|
64
|
+
this._commands.execute('@jupyterlite/ai:open-settings');
|
|
65
|
+
},
|
|
66
|
+
tooltip: 'Open AI Settings'
|
|
67
|
+
}));
|
|
68
|
+
return toolbar;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Sets up event handlers for existing approval buttons in the chat panel.
|
|
72
|
+
*/
|
|
73
|
+
_setupApprovalHandlers() {
|
|
74
|
+
// This method will be called to add handlers to existing buttons
|
|
75
|
+
// New buttons get handlers added in _processApprovalButtons
|
|
76
|
+
const existingButtons = this._chatPanel.node.querySelectorAll('.jp-ai-approval-btn');
|
|
77
|
+
existingButtons.forEach(button => {
|
|
78
|
+
this._addButtonHandler(button);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Adds click event handler to an approval button.
|
|
83
|
+
*
|
|
84
|
+
* @param button - The button element to add handler to
|
|
85
|
+
*/
|
|
86
|
+
_addButtonHandler(button) {
|
|
87
|
+
// Remove any existing listeners to avoid duplicates
|
|
88
|
+
button.removeEventListener('click', this._handleButtonClick);
|
|
89
|
+
button.addEventListener('click', this._handleButtonClick);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Handles click events for individual approval buttons.
|
|
93
|
+
*
|
|
94
|
+
* @param event - The click event
|
|
95
|
+
*/
|
|
96
|
+
_handleButtonClick = async (event) => {
|
|
97
|
+
const target = event.target;
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
event.stopPropagation();
|
|
100
|
+
const buttonsContainer = target.closest('.jp-ai-tool-approval-buttons');
|
|
101
|
+
if (!buttonsContainer) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const interruptionId = buttonsContainer.getAttribute('data-interruption-id');
|
|
105
|
+
if (!interruptionId) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Get message ID for updating the tool call box
|
|
109
|
+
const messageId = buttonsContainer.getAttribute('data-message-id');
|
|
110
|
+
// Hide buttons immediately and show status
|
|
111
|
+
const isApprove = target.classList.contains('jp-ai-approval-approve');
|
|
112
|
+
this._showApprovalStatus(buttonsContainer, isApprove);
|
|
113
|
+
if (isApprove) {
|
|
114
|
+
// Execute approval with message ID for updating the tool call box
|
|
115
|
+
await this._chatModel.approveToolCall(interruptionId, messageId || undefined);
|
|
116
|
+
}
|
|
117
|
+
else if (target.classList.contains('jp-ai-approval-reject')) {
|
|
118
|
+
// Execute rejection with message ID for updating the tool call box
|
|
119
|
+
await this._chatModel.rejectToolCall(interruptionId, messageId || undefined);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Adds click event handler to a grouped approval button.
|
|
124
|
+
*
|
|
125
|
+
* @param button - The button element to add handler to
|
|
126
|
+
*/
|
|
127
|
+
_addGroupedButtonHandler(button) {
|
|
128
|
+
// Remove any existing listeners to avoid duplicates
|
|
129
|
+
button.removeEventListener('click', this._handleGroupedButtonClick);
|
|
130
|
+
button.addEventListener('click', this._handleGroupedButtonClick);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Handles click events for grouped approval buttons.
|
|
134
|
+
*
|
|
135
|
+
* @param event - The click event
|
|
136
|
+
*/
|
|
137
|
+
_handleGroupedButtonClick = async (event) => {
|
|
138
|
+
const target = event.target;
|
|
139
|
+
event.preventDefault();
|
|
140
|
+
event.stopPropagation();
|
|
141
|
+
const buttonsContainer = target.closest('.jp-ai-group-approval-buttons');
|
|
142
|
+
if (!buttonsContainer) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const groupId = buttonsContainer.getAttribute('data-group-id');
|
|
146
|
+
const interruptionIdsStr = buttonsContainer.getAttribute('data-interruption-ids');
|
|
147
|
+
if (!groupId || !interruptionIdsStr) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const interruptionIds = interruptionIdsStr.split(',');
|
|
151
|
+
const messageId = buttonsContainer.getAttribute('data-message-id');
|
|
152
|
+
// Hide buttons immediately and show status
|
|
153
|
+
const isApprove = target.classList.contains('jp-ai-group-approve-all');
|
|
154
|
+
this._showGroupApprovalStatus(buttonsContainer, isApprove);
|
|
155
|
+
if (isApprove) {
|
|
156
|
+
// Execute grouped approval
|
|
157
|
+
await this._chatModel.approveGroupedToolCalls(groupId, interruptionIds, messageId || undefined);
|
|
158
|
+
}
|
|
159
|
+
else if (target.classList.contains('jp-ai-group-reject-all')) {
|
|
160
|
+
// Execute grouped rejection
|
|
161
|
+
await this._chatModel.rejectGroupedToolCalls(groupId, interruptionIds, messageId || undefined);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* Shows approval status by replacing buttons with status indicator.
|
|
166
|
+
*
|
|
167
|
+
* @param buttonsContainer - The container element holding the buttons
|
|
168
|
+
* @param isApprove - Whether the action was approval or rejection
|
|
169
|
+
*/
|
|
170
|
+
_showApprovalStatus(buttonsContainer, isApprove) {
|
|
171
|
+
// Clear the container and add status indicator
|
|
172
|
+
buttonsContainer.innerHTML = '';
|
|
173
|
+
const statusDiv = document.createElement('div');
|
|
174
|
+
statusDiv.className = `jp-ai-approval-status ${isApprove ? 'jp-ai-approval-status-approved' : 'jp-ai-approval-status-rejected'}`;
|
|
175
|
+
const icon = document.createElement('span');
|
|
176
|
+
icon.className = 'jp-ai-approval-icon';
|
|
177
|
+
icon.textContent = isApprove ? '✅' : '❌';
|
|
178
|
+
const text = document.createElement('span');
|
|
179
|
+
text.textContent = isApprove ? 'Tools approved' : 'Tools rejected';
|
|
180
|
+
statusDiv.appendChild(icon);
|
|
181
|
+
statusDiv.appendChild(text);
|
|
182
|
+
buttonsContainer.appendChild(statusDiv);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Shows group approval status by replacing buttons with status indicator.
|
|
186
|
+
*
|
|
187
|
+
* @param buttonsContainer - The container element holding the buttons
|
|
188
|
+
* @param isApprove - Whether the action was approval or rejection
|
|
189
|
+
* @param toolCount - The number of tools that were approved/rejected
|
|
190
|
+
*/
|
|
191
|
+
_showGroupApprovalStatus(buttonsContainer, isApprove) {
|
|
192
|
+
// Clear the container and add status indicator
|
|
193
|
+
buttonsContainer.innerHTML = '';
|
|
194
|
+
const statusDiv = document.createElement('div');
|
|
195
|
+
statusDiv.className = `jp-ai-group-approval-status ${isApprove ? 'jp-ai-group-approval-status-approved' : 'jp-ai-group-approval-status-rejected'}`;
|
|
196
|
+
const icon = document.createElement('span');
|
|
197
|
+
icon.className = 'jp-ai-approval-icon';
|
|
198
|
+
icon.textContent = isApprove ? '✅' : '❌';
|
|
199
|
+
const text = document.createElement('span');
|
|
200
|
+
text.textContent = isApprove ? 'Tools approved' : 'Tools rejected';
|
|
201
|
+
statusDiv.appendChild(icon);
|
|
202
|
+
statusDiv.appendChild(text);
|
|
203
|
+
buttonsContainer.appendChild(statusDiv);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Sets up mutation observer to watch for new messages and process approval buttons.
|
|
207
|
+
*/
|
|
208
|
+
_setupMessageProcessing() {
|
|
209
|
+
// Use a MutationObserver to watch for new messages and process approval buttons
|
|
210
|
+
const observer = new MutationObserver(mutations => {
|
|
211
|
+
mutations.forEach(mutation => {
|
|
212
|
+
mutation.addedNodes.forEach(node => {
|
|
213
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
214
|
+
const element = node;
|
|
215
|
+
this._processApprovalButtons(element);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
observer.observe(this._chatPanel.node, {
|
|
221
|
+
childList: true,
|
|
222
|
+
subtree: true
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Processes text nodes to replace approval button placeholders with actual button elements.
|
|
227
|
+
*
|
|
228
|
+
* @param element - The element to search for approval button placeholders
|
|
229
|
+
*/
|
|
230
|
+
_processApprovalButtons(element) {
|
|
231
|
+
// Find all text nodes that contain approval buttons and replace them with actual buttons
|
|
232
|
+
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null);
|
|
233
|
+
const textNodes = [];
|
|
234
|
+
let node;
|
|
235
|
+
while ((node = walker.nextNode())) {
|
|
236
|
+
textNodes.push(node);
|
|
237
|
+
}
|
|
238
|
+
textNodes.forEach(textNode => {
|
|
239
|
+
const text = textNode.textContent || '';
|
|
240
|
+
// Handle single tool approval buttons [APPROVAL_BUTTONS:id]
|
|
241
|
+
const singleMatch = text.match(/\[APPROVAL_BUTTONS:([^\]]+)\]/);
|
|
242
|
+
if (singleMatch) {
|
|
243
|
+
this._createSingleApprovalButtons(textNode, singleMatch[1]);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// Handle grouped tool approval buttons [GROUP_APPROVAL_BUTTONS:groupId:id1,id2,id3]
|
|
247
|
+
const groupMatch = text.match(/\[GROUP_APPROVAL_BUTTONS:([^:]+):([^\]]+)\]/);
|
|
248
|
+
if (groupMatch) {
|
|
249
|
+
this._createGroupedApprovalButtons(textNode, groupMatch[1], groupMatch[2]);
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Creates an approval button element with appropriate styling and classes.
|
|
256
|
+
*
|
|
257
|
+
* @param text - The button text
|
|
258
|
+
* @param isApprove - Whether this is an approve or reject button
|
|
259
|
+
* @param additionalClasses - Additional CSS classes to add
|
|
260
|
+
* @returns The created button element
|
|
261
|
+
*/
|
|
262
|
+
_createApprovalButton(text, isApprove, additionalClasses = '') {
|
|
263
|
+
const button = document.createElement('button');
|
|
264
|
+
const baseClass = isApprove
|
|
265
|
+
? 'jp-ai-approval-approve'
|
|
266
|
+
: 'jp-ai-approval-reject';
|
|
267
|
+
button.className = `jp-ai-approval-btn ${baseClass}${additionalClasses ? ' ' + additionalClasses : ''}`;
|
|
268
|
+
button.textContent = text;
|
|
269
|
+
return button;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Creates and inserts approval buttons for a single tool call.
|
|
273
|
+
*
|
|
274
|
+
* @param textNode - The text node to replace with buttons
|
|
275
|
+
* @param interruptionId - The interruption ID for the tool call
|
|
276
|
+
*/
|
|
277
|
+
_createSingleApprovalButtons(textNode, interruptionId) {
|
|
278
|
+
// Create approval buttons for single tool
|
|
279
|
+
const buttonContainer = document.createElement('div');
|
|
280
|
+
buttonContainer.className = 'jp-ai-tool-approval-buttons';
|
|
281
|
+
buttonContainer.setAttribute('data-interruption-id', interruptionId);
|
|
282
|
+
// Try to find the message ID from the closest message container
|
|
283
|
+
const messageId = this._findMessageId(textNode);
|
|
284
|
+
if (messageId) {
|
|
285
|
+
buttonContainer.setAttribute('data-message-id', messageId);
|
|
286
|
+
}
|
|
287
|
+
const approveBtn = this._createApprovalButton('Approve', true);
|
|
288
|
+
const rejectBtn = this._createApprovalButton('Reject', false);
|
|
289
|
+
// Add click handlers directly to the buttons
|
|
290
|
+
this._addButtonHandler(approveBtn);
|
|
291
|
+
this._addButtonHandler(rejectBtn);
|
|
292
|
+
buttonContainer.appendChild(approveBtn);
|
|
293
|
+
buttonContainer.appendChild(rejectBtn);
|
|
294
|
+
// Replace the text node with the button container
|
|
295
|
+
const parent = textNode.parentNode;
|
|
296
|
+
if (parent) {
|
|
297
|
+
parent.replaceChild(buttonContainer, textNode);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Creates and inserts approval buttons for grouped tool calls.
|
|
302
|
+
*
|
|
303
|
+
* @param textNode - The text node to replace with buttons
|
|
304
|
+
* @param groupId - The group ID for the tool calls
|
|
305
|
+
* @param interruptionIds - Comma-separated interruption IDs
|
|
306
|
+
*/
|
|
307
|
+
_createGroupedApprovalButtons(textNode, groupId, interruptionIds) {
|
|
308
|
+
// Create approval buttons for grouped tools
|
|
309
|
+
const buttonContainer = document.createElement('div');
|
|
310
|
+
buttonContainer.className = 'jp-ai-group-approval-buttons';
|
|
311
|
+
buttonContainer.setAttribute('data-group-id', groupId);
|
|
312
|
+
buttonContainer.setAttribute('data-interruption-ids', interruptionIds);
|
|
313
|
+
// Try to find the message ID from the closest message container
|
|
314
|
+
const messageId = this._findMessageId(textNode);
|
|
315
|
+
if (messageId) {
|
|
316
|
+
buttonContainer.setAttribute('data-message-id', messageId);
|
|
317
|
+
}
|
|
318
|
+
const approveBtn = this._createApprovalButton('Approve', true, 'jp-ai-group-approve-all');
|
|
319
|
+
const rejectBtn = this._createApprovalButton('Reject', false, 'jp-ai-group-reject-all');
|
|
320
|
+
// Add click handlers for grouped approvals
|
|
321
|
+
this._addGroupedButtonHandler(approveBtn);
|
|
322
|
+
this._addGroupedButtonHandler(rejectBtn);
|
|
323
|
+
buttonContainer.appendChild(approveBtn);
|
|
324
|
+
buttonContainer.appendChild(rejectBtn);
|
|
325
|
+
// Replace the text node with the button container
|
|
326
|
+
const parent = textNode.parentNode;
|
|
327
|
+
if (parent) {
|
|
328
|
+
parent.replaceChild(buttonContainer, textNode);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Finds the message ID by traversing up the DOM tree from a text node.
|
|
333
|
+
*
|
|
334
|
+
* @param textNode - The text node to start searching from
|
|
335
|
+
* @returns The message ID if found, null otherwise
|
|
336
|
+
*/
|
|
337
|
+
_findMessageId(textNode) {
|
|
338
|
+
let messageElement = textNode.parentNode;
|
|
339
|
+
while (messageElement && messageElement !== document.body) {
|
|
340
|
+
if (messageElement.nodeType === Node.ELEMENT_NODE) {
|
|
341
|
+
const element = messageElement;
|
|
342
|
+
// Look for common message container attributes or classes
|
|
343
|
+
const messageId = element.getAttribute('data-message-id') ||
|
|
344
|
+
element.getAttribute('id') ||
|
|
345
|
+
element
|
|
346
|
+
.querySelector('[data-message-id]')
|
|
347
|
+
?.getAttribute('data-message-id');
|
|
348
|
+
if (messageId) {
|
|
349
|
+
return messageId;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
messageElement = messageElement.parentNode;
|
|
353
|
+
}
|
|
354
|
+
return null;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Fixes focus issue by replacing the global click handler with a more selective one.
|
|
358
|
+
* Only focuses the input when clicking empty areas, not on interactive elements.
|
|
359
|
+
*/
|
|
360
|
+
_fixCopyFocusIssue() {
|
|
361
|
+
// Remove the global click handler that causes focus on any click
|
|
362
|
+
// The original handler is: this.node.onclick = () => this.model.input.focus();
|
|
363
|
+
if (this._chatPanel.node.onclick) {
|
|
364
|
+
this._chatPanel.node.onclick = null;
|
|
365
|
+
}
|
|
366
|
+
// Add a more selective click handler that only focuses when clicking empty areas
|
|
367
|
+
this._chatPanel.node.addEventListener('click', (event) => {
|
|
368
|
+
const target = event.target;
|
|
369
|
+
// Don't focus if clicking on selectable text elements
|
|
370
|
+
const selection = window.getSelection();
|
|
371
|
+
if (target.closest('pre') ||
|
|
372
|
+
target.closest('code') ||
|
|
373
|
+
target.closest('.jp-RenderedMarkdown') ||
|
|
374
|
+
target.closest('.jp-ai-tool-call') ||
|
|
375
|
+
target.closest('button') ||
|
|
376
|
+
target.closest('.jp-chat-input-container') ||
|
|
377
|
+
(selection && selection.toString().length > 0)) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
// Only focus input when clicking empty chat areas
|
|
381
|
+
this._chatModel.input.focus();
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
// Private fields
|
|
385
|
+
_chatPanel;
|
|
386
|
+
_chatModel;
|
|
387
|
+
_settingsModel;
|
|
388
|
+
_toolbar;
|
|
389
|
+
_commands;
|
|
390
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { IProviderConfig } from '../models/settings-model';
|
|
3
|
+
import type { IChatProviderRegistry } from '../tokens';
|
|
4
|
+
interface IProviderConfigDialogProps {
|
|
5
|
+
open: boolean;
|
|
6
|
+
onClose: () => void;
|
|
7
|
+
onSave: (config: Omit<IProviderConfig, 'id'>) => void;
|
|
8
|
+
initialConfig?: IProviderConfig;
|
|
9
|
+
mode: 'add' | 'edit';
|
|
10
|
+
chatProviderRegistry: IChatProviderRegistry;
|
|
11
|
+
}
|
|
12
|
+
export declare const ProviderConfigDialog: React.FC<IProviderConfigDialogProps>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import Visibility from '@mui/icons-material/Visibility';
|
|
2
|
+
import VisibilityOff from '@mui/icons-material/VisibilityOff';
|
|
3
|
+
import { Alert, Box, Button, Chip, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, IconButton, InputAdornment, InputLabel, MenuItem, Select, TextField, Typography } from '@mui/material';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
export const ProviderConfigDialog = ({ open, onClose, onSave, initialConfig, mode, chatProviderRegistry }) => {
|
|
6
|
+
const [name, setName] = React.useState(initialConfig?.name || '');
|
|
7
|
+
const [provider, setProvider] = React.useState(initialConfig?.provider || 'anthropic');
|
|
8
|
+
const [model, setModel] = React.useState(initialConfig?.model || '');
|
|
9
|
+
const [apiKey, setApiKey] = React.useState(initialConfig?.apiKey || '');
|
|
10
|
+
const [baseURL, setBaseURL] = React.useState(initialConfig?.baseURL || '');
|
|
11
|
+
const [showApiKey, setShowApiKey] = React.useState(false);
|
|
12
|
+
// Get provider options from registry
|
|
13
|
+
const providerOptions = React.useMemo(() => {
|
|
14
|
+
const providers = chatProviderRegistry.providers;
|
|
15
|
+
return Object.keys(providers).map(id => {
|
|
16
|
+
const info = providers[id];
|
|
17
|
+
return {
|
|
18
|
+
value: id,
|
|
19
|
+
label: info.name,
|
|
20
|
+
models: info.defaultModels,
|
|
21
|
+
requiresApiKey: info.requiresApiKey,
|
|
22
|
+
allowCustomModel: id === 'ollama' // Only Ollama allows custom models for now
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
}, [chatProviderRegistry]);
|
|
26
|
+
const selectedProvider = providerOptions.find(p => p.value === provider);
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
if (open) {
|
|
29
|
+
// Reset form when dialog opens
|
|
30
|
+
setName(initialConfig?.name || '');
|
|
31
|
+
setProvider(initialConfig?.provider || 'anthropic');
|
|
32
|
+
setModel(initialConfig?.model || '');
|
|
33
|
+
setApiKey(initialConfig?.apiKey || '');
|
|
34
|
+
setBaseURL(initialConfig?.baseURL || '');
|
|
35
|
+
setShowApiKey(false);
|
|
36
|
+
}
|
|
37
|
+
}, [open, initialConfig]);
|
|
38
|
+
React.useEffect(() => {
|
|
39
|
+
// Auto-select first model when provider changes
|
|
40
|
+
if (selectedProvider && selectedProvider.models.length > 0 && !model) {
|
|
41
|
+
setModel(selectedProvider.models[0]);
|
|
42
|
+
}
|
|
43
|
+
}, [provider, selectedProvider, model]);
|
|
44
|
+
const handleSave = () => {
|
|
45
|
+
if (!name.trim() || !provider || !model) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const config = {
|
|
49
|
+
name: name.trim(),
|
|
50
|
+
provider: provider,
|
|
51
|
+
model,
|
|
52
|
+
...(selectedProvider?.requiresApiKey && apiKey && { apiKey }),
|
|
53
|
+
...(baseURL && { baseURL })
|
|
54
|
+
};
|
|
55
|
+
onSave(config);
|
|
56
|
+
onClose();
|
|
57
|
+
};
|
|
58
|
+
const isValid = name.trim() &&
|
|
59
|
+
provider &&
|
|
60
|
+
model &&
|
|
61
|
+
(!selectedProvider?.requiresApiKey || apiKey);
|
|
62
|
+
return (React.createElement(Dialog, { open: open, onClose: onClose, maxWidth: "md", fullWidth: true },
|
|
63
|
+
React.createElement(DialogTitle, null, mode === 'add' ? 'Add New Provider' : 'Edit Provider'),
|
|
64
|
+
React.createElement(DialogContent, null,
|
|
65
|
+
React.createElement(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2, pt: 1 } },
|
|
66
|
+
React.createElement(TextField, { fullWidth: true, label: "Provider Name", value: name, onChange: e => setName(e.target.value), placeholder: "e.g., My Anthropic Config, Work Provider", helperText: "A friendly name to identify this provider configuration", required: true }),
|
|
67
|
+
React.createElement(FormControl, { fullWidth: true, required: true },
|
|
68
|
+
React.createElement(InputLabel, null, "Provider Type"),
|
|
69
|
+
React.createElement(Select, { value: provider, label: "Provider Type", onChange: e => setProvider(e.target.value) }, providerOptions.map(option => (React.createElement(MenuItem, { key: option.value, value: option.value },
|
|
70
|
+
React.createElement(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 } },
|
|
71
|
+
option.label,
|
|
72
|
+
option.requiresApiKey && (React.createElement(Chip, { size: "small", label: "API Key", color: "default", variant: "outlined" })))))))),
|
|
73
|
+
selectedProvider?.allowCustomModel ? (React.createElement(TextField, { fullWidth: true, label: "Model", value: model, onChange: e => setModel(e.target.value), placeholder: "Enter model name", helperText: "Enter any compatible model name", required: true })) : (React.createElement(FormControl, { fullWidth: true, required: true },
|
|
74
|
+
React.createElement(InputLabel, null, "Model"),
|
|
75
|
+
React.createElement(Select, { value: model, label: "Model", onChange: e => setModel(e.target.value) }, selectedProvider?.models.map(modelOption => (React.createElement(MenuItem, { key: modelOption, value: modelOption },
|
|
76
|
+
React.createElement(Box, null,
|
|
77
|
+
React.createElement(Typography, { variant: "body1" }, modelOption),
|
|
78
|
+
React.createElement(Typography, { variant: "caption", color: "text.secondary" }, modelOption.includes('sonnet')
|
|
79
|
+
? 'Balanced performance'
|
|
80
|
+
: modelOption.includes('opus')
|
|
81
|
+
? 'Advanced reasoning'
|
|
82
|
+
: modelOption.includes('haiku')
|
|
83
|
+
? 'Fast and lightweight'
|
|
84
|
+
: modelOption.includes('large')
|
|
85
|
+
? 'Most capable model'
|
|
86
|
+
: modelOption.includes('small')
|
|
87
|
+
? 'Fast and efficient'
|
|
88
|
+
: modelOption.includes('codestral')
|
|
89
|
+
? 'Code-specialized'
|
|
90
|
+
: 'General purpose')))))))),
|
|
91
|
+
selectedProvider?.requiresApiKey && (React.createElement(TextField, { fullWidth: true, label: "API Key", type: showApiKey ? 'text' : 'password', value: apiKey, onChange: e => setApiKey(e.target.value), placeholder: "Enter your API key...", required: selectedProvider.requiresApiKey, InputProps: {
|
|
92
|
+
endAdornment: (React.createElement(InputAdornment, { position: "end" },
|
|
93
|
+
React.createElement(IconButton, { onClick: () => setShowApiKey(!showApiKey), edge: "end" }, showApiKey ? React.createElement(VisibilityOff, null) : React.createElement(Visibility, null))))
|
|
94
|
+
} })),
|
|
95
|
+
(provider === 'ollama' || selectedProvider?.allowCustomModel) && (React.createElement(TextField, { fullWidth: true, label: "Base URL (Optional)", value: baseURL, onChange: e => setBaseURL(e.target.value), placeholder: provider === 'ollama'
|
|
96
|
+
? 'http://localhost:11434/api'
|
|
97
|
+
: 'Custom API endpoint', helperText: provider === 'ollama'
|
|
98
|
+
? 'Ollama server endpoint'
|
|
99
|
+
: 'Custom API base URL if needed' })),
|
|
100
|
+
!selectedProvider?.requiresApiKey && (React.createElement(Alert, { severity: "info" }, "This provider does not require an API key.")))),
|
|
101
|
+
React.createElement(DialogActions, null,
|
|
102
|
+
React.createElement(Button, { onClick: onClose }, "Cancel"),
|
|
103
|
+
React.createElement(Button, { onClick: handleSave, variant: "contained", disabled: !isValid }, mode === 'add' ? 'Add Provider' : 'Save Changes'))));
|
|
104
|
+
};
|