@jupyterlite/ai 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/lib/agent.d.ts +28 -114
  2. package/lib/agent.js +140 -100
  3. package/lib/chat-model-handler.d.ts +9 -11
  4. package/lib/chat-model-handler.js +9 -4
  5. package/lib/chat-model.d.ts +84 -13
  6. package/lib/chat-model.js +208 -136
  7. package/lib/completion/completion-provider.d.ts +2 -3
  8. package/lib/components/completion-status.d.ts +2 -2
  9. package/lib/components/model-select.d.ts +3 -3
  10. package/lib/components/save-button.d.ts +31 -0
  11. package/lib/components/save-button.js +41 -0
  12. package/lib/components/token-usage-display.d.ts +2 -3
  13. package/lib/components/tool-select.d.ts +3 -4
  14. package/lib/diff-manager.d.ts +2 -3
  15. package/lib/index.d.ts +2 -4
  16. package/lib/index.js +181 -23
  17. package/lib/models/settings-model.d.ts +11 -53
  18. package/lib/models/settings-model.js +37 -22
  19. package/lib/providers/built-in-providers.js +17 -36
  20. package/lib/tokens.d.ts +340 -36
  21. package/lib/tokens.js +11 -6
  22. package/lib/tools/commands.d.ts +2 -3
  23. package/lib/widgets/ai-settings.d.ts +3 -5
  24. package/lib/widgets/ai-settings.js +3 -0
  25. package/lib/widgets/main-area-chat.d.ts +2 -3
  26. package/lib/widgets/main-area-chat.js +9 -9
  27. package/lib/widgets/provider-config-dialog.d.ts +1 -2
  28. package/lib/widgets/provider-config-dialog.js +16 -29
  29. package/package.json +15 -9
  30. package/schema/settings-model.json +7 -1
  31. package/src/agent.ts +197 -242
  32. package/src/chat-model-handler.ts +25 -21
  33. package/src/chat-model.ts +304 -196
  34. package/src/completion/completion-provider.ts +7 -4
  35. package/src/components/completion-status.tsx +3 -3
  36. package/src/components/model-select.tsx +4 -3
  37. package/src/components/save-button.tsx +84 -0
  38. package/src/components/token-usage-display.tsx +2 -3
  39. package/src/components/tool-select.tsx +10 -4
  40. package/src/diff-manager.ts +4 -4
  41. package/src/index.ts +245 -49
  42. package/src/models/settings-model.ts +45 -88
  43. package/src/providers/built-in-providers.ts +17 -36
  44. package/src/tokens.ts +406 -52
  45. package/src/tools/commands.ts +2 -3
  46. package/src/widgets/ai-settings.tsx +27 -15
  47. package/src/widgets/main-area-chat.ts +15 -12
  48. package/src/widgets/provider-config-dialog.tsx +51 -56
  49. package/style/base.css +17 -195
  50. package/lib/approval-buttons.d.ts +0 -49
  51. package/lib/approval-buttons.js +0 -79
  52. package/src/approval-buttons.ts +0 -115
@@ -4,17 +4,16 @@ import { launchIcon } from '@jupyterlab/ui-components';
4
4
  import type { TranslationBundle } from '@jupyterlab/translation';
5
5
  import { CommandRegistry } from '@lumino/commands';
6
6
 
7
- import { ApprovalButtons } from '../approval-buttons';
8
7
  import { AIChatModel } from '../chat-model';
8
+ import { SaveComponentWidget } from '../components/save-button';
9
9
  import { TokenUsageWidget } from '../components/token-usage-display';
10
- import { AISettingsModel } from '../models/settings-model';
11
10
  import { RenderedMessageOutputAreaCompat } from '../rendered-message-outputarea';
12
- import { CommandIds } from '../tokens';
11
+ import { CommandIds, type IAISettingsModel } from '../tokens';
13
12
 
14
13
  export namespace MainAreaChat {
15
14
  export interface IOptions extends MainAreaWidget.IOptions<ChatWidget> {
16
15
  commands: CommandRegistry;
17
- settingsModel: AISettingsModel;
16
+ settingsModel: IAISettingsModel;
18
17
  trans: TranslationBundle;
19
18
  }
20
19
  }
@@ -29,7 +28,7 @@ export class MainAreaChat extends MainAreaWidget<ChatWidget> {
29
28
 
30
29
  const { trans } = options;
31
30
 
32
- // add the move to side button.
31
+ // Move to side button.
33
32
  this.toolbar.addItem(
34
33
  'moveToSide',
35
34
  new CommandToolbarButton({
@@ -43,6 +42,17 @@ export class MainAreaChat extends MainAreaWidget<ChatWidget> {
43
42
  })
44
43
  );
45
44
 
45
+ if (this.model.saveAvailable) {
46
+ // Save chat component
47
+ this.toolbar.addItem(
48
+ 'saveChat',
49
+ new SaveComponentWidget({
50
+ model: this.model,
51
+ translator: trans
52
+ })
53
+ );
54
+ }
55
+
46
56
  // Add the token usage button.
47
57
  const tokenUsageWidget = new TokenUsageWidget({
48
58
  tokenUsageChanged: this.model.tokenUsageChanged,
@@ -52,11 +62,6 @@ export class MainAreaChat extends MainAreaWidget<ChatWidget> {
52
62
  });
53
63
  this.toolbar.addItem('token-usage', tokenUsageWidget);
54
64
 
55
- // Add the approval button, tied to the chat widget.
56
- this._approvalButtons = new ApprovalButtons({
57
- chatPanel: this.content,
58
- agentManager: this.model.agentManager
59
- });
60
65
  // Temporary compat: keep output-area CSS context for MIME renderers
61
66
  // until jupyter-chat provides it natively.
62
67
  this._outputAreaCompat = new RenderedMessageOutputAreaCompat({
@@ -69,7 +74,6 @@ export class MainAreaChat extends MainAreaWidget<ChatWidget> {
69
74
  dispose(): void {
70
75
  super.dispose();
71
76
  // Dispose of the approval buttons widget when the chat is disposed.
72
- this._approvalButtons.dispose();
73
77
  this._outputAreaCompat.dispose();
74
78
  this.model.writersChanged.disconnect(this._writersChanged);
75
79
  }
@@ -103,6 +107,5 @@ export class MainAreaChat extends MainAreaWidget<ChatWidget> {
103
107
  }
104
108
  };
105
109
 
106
- private _approvalButtons: ApprovalButtons;
107
110
  private _outputAreaCompat: RenderedMessageOutputAreaCompat;
108
111
  }
@@ -31,8 +31,12 @@ import {
31
31
  } from '@mui/material';
32
32
  import type { TranslationBundle } from '@jupyterlab/translation';
33
33
  import React from 'react';
34
- import { IProviderConfig, IProviderParameters } from '../models/settings-model';
35
- import type { IProviderRegistry, IProviderToolCapabilities } from '../tokens';
34
+ import type {
35
+ IProviderConfig,
36
+ IProviderParameters,
37
+ IProviderRegistry,
38
+ IProviderToolCapabilities
39
+ } from '../tokens';
36
40
 
37
41
  /**
38
42
  * Default parameter values for provider configuration
@@ -187,7 +191,6 @@ export const ProviderConfigDialog: React.FC<IProviderConfigDialogProps> = ({
187
191
  label: info.name,
188
192
  models: info.defaultModels,
189
193
  apiKeyRequirement: info.apiKeyRequirement,
190
- allowCustomModel: id === 'generic', // Generic allows custom models
191
194
  supportsBaseURL: info.supportsBaseURL,
192
195
  description: info.description,
193
196
  baseUrls: info.baseUrls
@@ -200,9 +203,14 @@ export const ProviderConfigDialog: React.FC<IProviderConfigDialogProps> = ({
200
203
  React.useEffect(() => {
201
204
  if (open) {
202
205
  // Reset form when dialog opens
206
+ const initialProvider = initialConfig?.provider || 'anthropic';
207
+ const initialProviderInfo =
208
+ providerRegistry.getProviderInfo(initialProvider);
203
209
  setName(initialConfig?.name || '');
204
- setProvider(initialConfig?.provider || 'anthropic');
205
- setModel(initialConfig?.model || '');
210
+ setProvider(initialProvider);
211
+ setModel(
212
+ initialConfig?.model || initialProviderInfo?.defaultModels[0] || ''
213
+ );
206
214
  setApiKey(initialConfig?.apiKey || '');
207
215
  setBaseURL(initialConfig?.baseURL || '');
208
216
  setParameters(initialConfig?.parameters || {});
@@ -215,14 +223,7 @@ export const ProviderConfigDialog: React.FC<IProviderConfigDialogProps> = ({
215
223
  setDomainInputs(createEmptyDomainInputs());
216
224
  setExpandedAdvanced(false);
217
225
  }
218
- }, [open, initialConfig]);
219
-
220
- React.useEffect(() => {
221
- // Auto-select first model when provider changes
222
- if (selectedProvider && selectedProvider.models.length > 0 && !model) {
223
- setModel(selectedProvider.models[0]);
224
- }
225
- }, [provider, selectedProvider, model]);
226
+ }, [open, initialConfig, providerRegistry]);
226
227
 
227
228
  const handleRef = React.useCallback(
228
229
  (node: HTMLInputElement | null) => {
@@ -233,6 +234,15 @@ export const ProviderConfigDialog: React.FC<IProviderConfigDialogProps> = ({
233
234
  [provider, handleSecretField, open]
234
235
  );
235
236
 
237
+ const handleProviderChange = React.useCallback(
238
+ (newProvider: IProviderConfig['provider']) => {
239
+ const newProviderInfo = providerRegistry.getProviderInfo(newProvider);
240
+ setProvider(newProvider);
241
+ setModel(newProviderInfo?.defaultModels[0] || '');
242
+ },
243
+ [providerRegistry]
244
+ );
245
+
236
246
  const updateCustomSetting = React.useCallback(
237
247
  (section: 'webSearch' | 'webFetch', key: string, value: unknown) => {
238
248
  setCustomSettings(prev => {
@@ -450,7 +460,9 @@ export const ProviderConfigDialog: React.FC<IProviderConfigDialogProps> = ({
450
460
  value={provider}
451
461
  label={trans.__('Provider Type')}
452
462
  onChange={e =>
453
- setProvider(e.target.value as IProviderConfig['provider'])
463
+ handleProviderChange(
464
+ e.target.value as IProviderConfig['provider']
465
+ )
454
466
  }
455
467
  >
456
468
  {providerOptions.map(option => (
@@ -478,49 +490,32 @@ export const ProviderConfigDialog: React.FC<IProviderConfigDialogProps> = ({
478
490
  </Select>
479
491
  </FormControl>
480
492
 
481
- {selectedProvider?.allowCustomModel ? (
482
- <TextField
483
- fullWidth
484
- label={trans.__('Model')}
485
- value={model}
486
- onChange={e => setModel(e.target.value)}
487
- placeholder={trans.__('Enter model name')}
488
- helperText={trans.__('Enter any compatible model name')}
489
- required
490
- />
491
- ) : (
492
- <FormControl fullWidth required>
493
- <InputLabel>{trans.__('Model')}</InputLabel>
494
- <Select
495
- value={model}
493
+ <Autocomplete
494
+ freeSolo
495
+ fullWidth
496
+ options={selectedProvider?.models ?? []}
497
+ value={model}
498
+ onChange={(_, value) => {
499
+ setModel(typeof value === 'string' ? value : '');
500
+ }}
501
+ inputValue={model}
502
+ onInputChange={(_, value) => {
503
+ setModel(value);
504
+ }}
505
+ renderInput={params => (
506
+ <TextField
507
+ {...params}
508
+ fullWidth
496
509
  label={trans.__('Model')}
497
- onChange={e => setModel(e.target.value)}
498
- >
499
- {selectedProvider?.models.map(modelOption => (
500
- <MenuItem key={modelOption} value={modelOption}>
501
- <Box>
502
- <Typography variant="body1">{modelOption}</Typography>
503
- <Typography variant="caption" color="text.secondary">
504
- {modelOption.includes('sonnet')
505
- ? trans.__('Balanced performance')
506
- : modelOption.includes('opus')
507
- ? trans.__('Advanced reasoning')
508
- : modelOption.includes('haiku')
509
- ? trans.__('Fast and lightweight')
510
- : modelOption.includes('large')
511
- ? trans.__('Most capable model')
512
- : modelOption.includes('small')
513
- ? trans.__('Fast and efficient')
514
- : modelOption.includes('codestral')
515
- ? trans.__('Code-specialized')
516
- : trans.__('General purpose')}
517
- </Typography>
518
- </Box>
519
- </MenuItem>
520
- ))}
521
- </Select>
522
- </FormControl>
523
- )}
510
+ placeholder={trans.__('Select or type a model ID')}
511
+ required
512
+ helperText={trans.__(
513
+ 'Choose from the list or enter a custom model ID'
514
+ )}
515
+ />
516
+ )}
517
+ clearOnBlur={false}
518
+ />
524
519
 
525
520
  {selectedProvider &&
526
521
  selectedProvider?.apiKeyRequirement !== 'none' && (
package/style/base.css CHANGED
@@ -88,164 +88,6 @@
88
88
  border-bottom: none;
89
89
  }
90
90
 
91
- /* Modern Tool Call Card Styling */
92
- .jp-ai-tool-call {
93
- margin: 8px 0;
94
- border: 1px solid var(--jp-border-color1);
95
- border-radius: 6px;
96
- background: var(--jp-layout-color0);
97
- box-shadow: var(--jp-elevation-z2);
98
- transition: all 0.2s ease;
99
- overflow: hidden;
100
- }
101
-
102
- .jp-ai-tool-call:hover {
103
- border-color: var(--jp-border-color2);
104
- box-shadow: var(--jp-elevation-z4);
105
- }
106
-
107
- /* Tool Header - clickable summary */
108
- .jp-ai-tool-header {
109
- display: flex;
110
- align-items: center;
111
- padding: 4px 8px;
112
- background: var(--jp-layout-color1);
113
- cursor: pointer;
114
- user-select: none;
115
- gap: 8px;
116
- transition: background-color 0.2s ease;
117
- }
118
-
119
- .jp-ai-tool-header:hover {
120
- background: var(--jp-layout-color2);
121
- }
122
-
123
- .jp-ai-tool-header::marker {
124
- content: '';
125
- }
126
-
127
- .jp-ai-tool-header::before {
128
- content: '';
129
- width: 0;
130
- height: 0;
131
- border-left: 5px solid var(--jp-ui-font-color2);
132
- border-top: 3px solid transparent;
133
- border-bottom: 3px solid transparent;
134
- transition: transform 0.2s ease;
135
- }
136
-
137
- .jp-ai-tool-call[open] .jp-ai-tool-header::before {
138
- transform: rotate(90deg);
139
- }
140
-
141
- .jp-ai-tool-icon {
142
- font-size: 14px;
143
- opacity: 0.8;
144
- }
145
-
146
- .jp-ai-tool-title {
147
- font-family: var(--jp-ui-font-family);
148
- font-size: var(--jp-ui-font-size1);
149
- font-weight: 500;
150
- color: var(--jp-ui-font-color1);
151
- flex: 1;
152
- }
153
-
154
- .jp-ai-tool-summary {
155
- font-weight: 400;
156
- opacity: 0.7;
157
- font-size: var(--jp-ui-font-size0);
158
- }
159
-
160
- .jp-ai-tool-summary::before {
161
- content: ' ';
162
- white-space: pre;
163
- }
164
-
165
- .jp-ai-tool-status {
166
- font-size: var(--jp-ui-font-size0);
167
- font-weight: 500;
168
- padding: 2px 6px;
169
- border-radius: 3px;
170
- }
171
-
172
- .jp-ai-tool-status-pending {
173
- background: rgb(var(--jp-warn-color1-rgb) / 15%);
174
- color: var(--jp-warn-color1);
175
- }
176
-
177
- .jp-ai-tool-status-completed {
178
- background: rgb(var(--jp-success-color1-rgb) / 15%);
179
- color: var(--jp-success-color1);
180
- }
181
-
182
- .jp-ai-tool-status-error {
183
- background: rgb(var(--jp-error-color1-rgb) / 15%);
184
- color: var(--jp-error-color1);
185
- }
186
-
187
- .jp-ai-tool-status-approval {
188
- background: rgb(var(--jp-warn-color1-rgb) / 15%);
189
- color: var(--jp-warn-color1);
190
- }
191
-
192
- /* Tool Body */
193
- .jp-ai-tool-body {
194
- padding: 8px 12px 12px;
195
- }
196
-
197
- .jp-ai-tool-section {
198
- margin-bottom: 8px;
199
- }
200
-
201
- .jp-ai-tool-section:last-child {
202
- margin-bottom: 0;
203
- }
204
-
205
- .jp-ai-tool-label {
206
- font-family: var(--jp-ui-font-family);
207
- font-size: var(--jp-ui-font-size0);
208
- font-weight: 600;
209
- color: var(--jp-ui-font-color2);
210
- margin-bottom: 4px;
211
- text-transform: uppercase;
212
- letter-spacing: 0.5px;
213
- }
214
-
215
- .jp-ai-tool-code {
216
- background: var(--jp-layout-color2);
217
- border: 1px solid var(--jp-border-color1);
218
- border-radius: 4px;
219
- padding: 8px;
220
- margin: 0;
221
- font-family: var(--jp-code-font-family);
222
- font-size: var(--jp-code-font-size);
223
- line-height: 1.4;
224
- overflow: auto auto;
225
- max-height: 200px;
226
- }
227
-
228
- .jp-ai-tool-code code {
229
- background: none;
230
- padding: 0;
231
- border: none;
232
- font-family: inherit;
233
- font-size: inherit;
234
- }
235
-
236
- /* State-specific styling */
237
- .jp-ai-tool-pending {
238
- border-left: 4px solid var(--jp-warn-color1);
239
- }
240
-
241
- .jp-ai-tool-completed {
242
- border-left: 4px solid var(--jp-success-color1);
243
- }
244
-
245
- .jp-ai-tool-error {
246
- border-left: 4px solid var(--jp-error-color1);
247
- }
248
-
249
91
  .jp-AIChatToolbar {
250
92
  display: flex;
251
93
  flex-shrink: 0;
@@ -267,53 +109,33 @@
267
109
  padding: 0 0.4em;
268
110
  }
269
111
 
270
- /* Tool Approval Button Styles */
271
- .jp-ai-tool-approval-buttons,
272
- .jp-ai-group-approval-buttons {
112
+ /* Save button */
113
+ .jp-ai-SaveButton {
273
114
  display: flex;
274
- gap: 8px;
275
- margin-top: 12px;
276
- justify-content: flex-end;
277
- }
278
-
279
- .jp-ai-approval-btn {
280
- padding: 6px 12px;
281
- border: none;
115
+ align-items: center;
116
+ background-color: var(--jp-layout-color1);
117
+ border: 1px solid var(--jp-border-color1);
282
118
  border-radius: 4px;
283
- font-family: var(--jp-ui-font-family);
284
- font-size: var(--jp-ui-font-size0);
285
- font-weight: 500;
286
- cursor: pointer;
287
- transition: all 0.2s ease;
288
- min-width: 70px;
289
- display: inline-block;
290
- }
291
-
292
- .jp-ai-approval-approve {
293
- background: var(--jp-success-color1);
294
- color: var(--jp-ui-inverse-font-color1);
119
+ padding: 4px;
295
120
  }
296
121
 
297
- .jp-ai-approval-approve:hover:not(:disabled) {
298
- background: var(--jp-success-color0);
299
- transform: translateY(-1px);
300
- box-shadow: var(--jp-elevation-z4);
122
+ .jp-ai-SaveButton.lm-mod-toggled {
123
+ box-shadow: inset 0 0 2px 2px var(--neutral-fill-strong-active);
301
124
  }
302
125
 
303
- .jp-ai-approval-reject {
304
- background: var(--jp-error-color1);
305
- color: var(--jp-ui-inverse-font-color1);
126
+ .jp-ai-SaveButton .jp-ToolbarButtonComponent {
127
+ margin: unset;
128
+ height: 18px;
306
129
  }
307
130
 
308
- .jp-ai-approval-reject:hover:not(:disabled) {
309
- background: var(--jp-error-color0);
310
- transform: translateY(-1px);
311
- box-shadow: var(--jp-elevation-z4);
131
+ .jp-ai-SaveButton .jp-ai-AutoSaveButton {
132
+ min-width: unset;
133
+ width: 18px;
312
134
  }
313
135
 
314
- .jp-ai-approval-btn:disabled {
315
- cursor: not-allowed;
316
- opacity: 0.5;
136
+ .jp-ai-SaveButton .jp-ai-AutoSaveButton svg {
137
+ width: 12px;
138
+ height: 12px;
317
139
  }
318
140
 
319
141
  .jp-MainAreaWidget
@@ -1,49 +0,0 @@
1
- import { ChatWidget } from '@jupyter/chat';
2
- import { IDisposable } from '@lumino/disposable';
3
- import type { AgentManager } from './agent';
4
- /**
5
- * Handles click events for approval buttons in the chat panel.
6
- */
7
- export declare class ApprovalButtons implements IDisposable {
8
- constructor(options: ApprovalButtons.IOptions);
9
- get isDisposed(): boolean;
10
- /**
11
- * Dispose of the resources held by the object.
12
- */
13
- dispose(): void;
14
- /**
15
- * Handles click events using event delegation.
16
- * Detects clicks on approval buttons and calls the appropriate handler.
17
- */
18
- private _handleClick;
19
- /**
20
- * Handles approval/rejection of a tool call.
21
- */
22
- private _handleApproval;
23
- /**
24
- * Extracts the approval ID from an element's class list.
25
- * The ID is encoded in a class name like "jp-ai-approval-id--{id}".
26
- */
27
- private _extractApprovalId;
28
- private _chatPanel;
29
- private _isDisposed;
30
- private _agentManager;
31
- }
32
- /**
33
- * Namespace for ApprovalButtons statics.
34
- */
35
- export declare namespace ApprovalButtons {
36
- /**
37
- * The options for the constructor of the approval buttons.
38
- */
39
- interface IOptions {
40
- /**
41
- * The chat panel widget to wrap.
42
- */
43
- chatPanel: ChatWidget;
44
- /**
45
- * The agent manager for handling approvals.
46
- */
47
- agentManager: AgentManager;
48
- }
49
- }
@@ -1,79 +0,0 @@
1
- /**
2
- * Handles click events for approval buttons in the chat panel.
3
- */
4
- export class ApprovalButtons {
5
- constructor(options) {
6
- this._chatPanel = options.chatPanel;
7
- this._agentManager = options.agentManager;
8
- this._chatPanel.node.addEventListener('click', this._handleClick);
9
- }
10
- get isDisposed() {
11
- return this._isDisposed;
12
- }
13
- /**
14
- * Dispose of the resources held by the object.
15
- */
16
- dispose() {
17
- if (this._isDisposed) {
18
- return;
19
- }
20
- this._isDisposed = true;
21
- this._chatPanel.node.removeEventListener('click', this._handleClick);
22
- this._chatPanel = null;
23
- }
24
- /**
25
- * Handles click events using event delegation.
26
- * Detects clicks on approval buttons and calls the appropriate handler.
27
- */
28
- _handleClick = (event) => {
29
- const target = event.target;
30
- // Check if the click target is an approval button
31
- if (!target.classList.contains('jp-ai-approval-btn')) {
32
- return;
33
- }
34
- event.preventDefault();
35
- event.stopPropagation();
36
- const isApprove = target.classList.contains('jp-ai-approval-approve');
37
- this._handleApproval(target, isApprove);
38
- };
39
- /**
40
- * Handles approval/rejection of a tool call.
41
- */
42
- _handleApproval(target, isApprove) {
43
- const container = target.closest('.jp-ai-tool-approval-buttons');
44
- if (!container) {
45
- return;
46
- }
47
- // Extract approval ID from class name (encoded as jp-ai-approval-id--{id})
48
- const approvalId = this._extractApprovalId(container);
49
- if (!approvalId) {
50
- console.warn('No approval ID found for button');
51
- return;
52
- }
53
- // Disable buttons to prevent double-clicks
54
- const buttons = container.querySelectorAll('button');
55
- buttons.forEach(btn => btn.setAttribute('disabled', 'true'));
56
- if (isApprove) {
57
- this._agentManager.approveToolCall(approvalId);
58
- }
59
- else {
60
- this._agentManager.rejectToolCall(approvalId);
61
- }
62
- }
63
- /**
64
- * Extracts the approval ID from an element's class list.
65
- * The ID is encoded in a class name like "jp-ai-approval-id--{id}".
66
- */
67
- _extractApprovalId(element) {
68
- const prefix = 'jp-ai-approval-id--';
69
- for (const className of element.classList) {
70
- if (className.startsWith(prefix)) {
71
- return className.slice(prefix.length);
72
- }
73
- }
74
- return null;
75
- }
76
- _chatPanel;
77
- _isDisposed = false;
78
- _agentManager;
79
- }