jupyter-chat-components 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -32
- package/lib/components/error.d.ts +7 -0
- package/lib/components/error.js +10 -0
- package/lib/components/grouped-tool-calls.d.ts +2 -10
- package/lib/components/grouped-tool-calls.js +111 -60
- package/lib/components/index.d.ts +1 -0
- package/lib/components/index.js +1 -0
- package/lib/factory.js +2 -1
- package/lib/icons.d.ts +2 -0
- package/lib/icons.js +5 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/token.d.ts +4 -0
- package/package.json +2 -1
- package/src/components/error.tsx +26 -0
- package/src/components/grouped-tool-calls.tsx +200 -73
- package/src/components/index.ts +1 -0
- package/src/factory.tsx +3 -1
- package/src/icons.ts +7 -0
- package/src/index.ts +1 -0
- package/src/token.ts +4 -0
- package/style/base.css +98 -11
package/README.md
CHANGED
|
@@ -26,38 +26,7 @@ The registry is available directly on the `IComponentsRendererFactory` token as
|
|
|
26
26
|
|
|
27
27
|
Other JupyterLab extensions can consume the `IComponentsRendererFactory` token and use `registry.add()` to register their own components, which will then be available for rendering via the MIME bundle.
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
### `tool-call`
|
|
32
|
-
|
|
33
|
-
Renders an AI tool call, displaying the tool name, input arguments, and output in a structured and readable format. Useful for visualizing function calls made by AI assistants during a conversation.
|
|
34
|
-
|
|
35
|
-
### `grouped-tool-calls`
|
|
36
|
-
|
|
37
|
-
Renders grouped tool calls, including:
|
|
38
|
-
|
|
39
|
-
- grouped in-progress, completed, and failed tool rows
|
|
40
|
-
- expandable raw output and file path details
|
|
41
|
-
- inline file diffs for edit operations
|
|
42
|
-
- permission options for approval flows
|
|
43
|
-
|
|
44
|
-
The component uses a camelCase metadata API.
|
|
45
|
-
|
|
46
|
-
If your host extension wants the approval buttons to be interactive, set the renderer factory callbacks:
|
|
47
|
-
|
|
48
|
-
```ts
|
|
49
|
-
rendererFactory.toolCallPermissionDecision = async (
|
|
50
|
-
sessionId,
|
|
51
|
-
toolCallId,
|
|
52
|
-
optionId
|
|
53
|
-
) => {
|
|
54
|
-
// submit the decision to your backend
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
rendererFactory.openToolCallPath = path => {
|
|
58
|
-
// optionally open the referenced file or resource
|
|
59
|
-
};
|
|
60
|
-
```
|
|
29
|
+
For live end-to-end metadata examples, see the deployed demo notebook at [brichet.github.io/jupyter-chat-components/lab/index.html?path=components_demo.ipynb](https://brichet.github.io/jupyter-chat-components/lab/index.html?path=components_demo.ipynb). The source notebook lives at [demo/contents/components_demo.ipynb](./demo/contents/components_demo.ipynb).
|
|
61
30
|
|
|
62
31
|
## Requirements
|
|
63
32
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { errorIcon } from '../icons';
|
|
3
|
+
export const ErrorMessage = ({ errorMessage, title = 'Error' }) => {
|
|
4
|
+
return (React.createElement("div", { className: "jp-ai-error-message" },
|
|
5
|
+
React.createElement("div", { className: "jp-ai-error-icon" },
|
|
6
|
+
React.createElement(errorIcon.react, { tag: "span", width: "18px", height: "18px" })),
|
|
7
|
+
React.createElement("div", { className: "jp-ai-error-body" },
|
|
8
|
+
React.createElement("div", { className: "jp-ai-error-title" }, title),
|
|
9
|
+
React.createElement("div", { className: "jp-ai-error-text" }, errorMessage))));
|
|
10
|
+
};
|
|
@@ -12,13 +12,9 @@ export interface IGroupedToolCallsProps extends IComponentProps, IToolCallsMetad
|
|
|
12
12
|
*/
|
|
13
13
|
export declare function toServerRelativePath(absolutePath: string): string;
|
|
14
14
|
/**
|
|
15
|
-
* Format tool output for display.
|
|
15
|
+
* Format tool payload (input/output) for display.
|
|
16
16
|
*/
|
|
17
|
-
export declare function
|
|
18
|
-
/**
|
|
19
|
-
* Format tool input for display.
|
|
20
|
-
*/
|
|
21
|
-
export declare function formatToolCallInput(input: unknown): string;
|
|
17
|
+
export declare function formatToolCallIO(payload: unknown): string;
|
|
22
18
|
/**
|
|
23
19
|
* Compute the line title shown for a tool call.
|
|
24
20
|
*/
|
|
@@ -28,10 +24,6 @@ export declare function getToolCallDisplayTitle(toolCall: IToolCallsEntry): stri
|
|
|
28
24
|
* title alone is enough.
|
|
29
25
|
*/
|
|
30
26
|
export declare function buildPermissionDetail(toolCall: IToolCallsEntry): string | null;
|
|
31
|
-
/**
|
|
32
|
-
* Build the expandable detail lines shown for completed and failed tool calls.
|
|
33
|
-
*/
|
|
34
|
-
export declare function buildToolCallDetailsLines(toolCall: IToolCallsEntry): string[];
|
|
35
27
|
/**
|
|
36
28
|
* React component for rendering grouped tool calls.
|
|
37
29
|
*/
|
|
@@ -4,10 +4,10 @@ import * as React from 'react';
|
|
|
4
4
|
import { structuredPatch } from 'diff';
|
|
5
5
|
/** Maximum number of rendered diff lines before truncation. */
|
|
6
6
|
const MAX_DIFF_LINES = 20;
|
|
7
|
+
/** Maximum number of lines shown in an expanded detail. */
|
|
8
|
+
const MAX_DETAIL_LINES = 15;
|
|
7
9
|
/** Tool kinds where expanded view shows file paths from locations. */
|
|
8
10
|
const FILE_KINDS = new Set(['read', 'edit', 'delete', 'move']);
|
|
9
|
-
/** Tool kinds where expanded view shows raw output. */
|
|
10
|
-
const OUTPUT_KINDS = new Set(['search', 'execute', 'think', 'fetch']);
|
|
11
11
|
const TOOL_KIND_LABELS = {
|
|
12
12
|
read: 'Reading',
|
|
13
13
|
edit: 'Editing',
|
|
@@ -47,38 +47,19 @@ export function toServerRelativePath(absolutePath) {
|
|
|
47
47
|
return relativePath;
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
* Format tool output for display.
|
|
50
|
+
* Format tool payload (input/output) for display.
|
|
51
51
|
*/
|
|
52
|
-
export function
|
|
53
|
-
if (typeof
|
|
54
|
-
return
|
|
52
|
+
export function formatToolCallIO(payload) {
|
|
53
|
+
if (typeof payload === 'string') {
|
|
54
|
+
return payload;
|
|
55
55
|
}
|
|
56
|
-
if (Array.isArray(
|
|
57
|
-
|
|
56
|
+
if (Array.isArray(payload) &&
|
|
57
|
+
payload.every(item => typeof item === 'object' &&
|
|
58
58
|
item !== null &&
|
|
59
59
|
typeof item.text === 'string')) {
|
|
60
|
-
return
|
|
60
|
+
return payload.map(item => item.text).join('\n');
|
|
61
61
|
}
|
|
62
|
-
return JSON.stringify(
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Format tool input for display.
|
|
66
|
-
*/
|
|
67
|
-
export function formatToolCallInput(input) {
|
|
68
|
-
if (typeof input === 'string') {
|
|
69
|
-
return input;
|
|
70
|
-
}
|
|
71
|
-
if (typeof input !== 'object' || input === null || Array.isArray(input)) {
|
|
72
|
-
return JSON.stringify(input, null, 2);
|
|
73
|
-
}
|
|
74
|
-
const entries = Object.entries(input);
|
|
75
|
-
const isFlat = entries.every(([, value]) => typeof value === 'string' ||
|
|
76
|
-
typeof value === 'number' ||
|
|
77
|
-
typeof value === 'boolean');
|
|
78
|
-
if (isFlat) {
|
|
79
|
-
return entries.map(([key, value]) => `${key}: ${value}`).join('\n');
|
|
80
|
-
}
|
|
81
|
-
return JSON.stringify(input, null, 2);
|
|
62
|
+
return JSON.stringify(payload, null, 2);
|
|
82
63
|
}
|
|
83
64
|
function getLocationSummary(toolCall) {
|
|
84
65
|
var _a;
|
|
@@ -143,7 +124,7 @@ export function buildPermissionDetail(toolCall) {
|
|
|
143
124
|
result[key] = value;
|
|
144
125
|
return result;
|
|
145
126
|
}, {});
|
|
146
|
-
const params = paramEntries.length > 0 ?
|
|
127
|
+
const params = paramEntries.length > 0 ? formatToolCallIO(filteredParams) : null;
|
|
147
128
|
if (purpose && params) {
|
|
148
129
|
return `${purpose}\n${params}`;
|
|
149
130
|
}
|
|
@@ -156,29 +137,21 @@ export function buildPermissionDetail(toolCall) {
|
|
|
156
137
|
return null;
|
|
157
138
|
}
|
|
158
139
|
if (rawInput !== null && rawInput !== undefined) {
|
|
159
|
-
return
|
|
140
|
+
return formatToolCallIO(rawInput);
|
|
160
141
|
}
|
|
161
142
|
return null;
|
|
162
143
|
}
|
|
163
144
|
/**
|
|
164
|
-
*
|
|
145
|
+
* Returns true when a completed/failed tool call has expandable detail content.
|
|
165
146
|
*/
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
const
|
|
147
|
+
function hasDetailContent(toolCall) {
|
|
148
|
+
const { kind, locations, rawInput, rawOutput } = toolCall;
|
|
149
|
+
const hasInput = rawInput !== null && rawInput !== undefined;
|
|
150
|
+
const hasOutput = rawOutput !== null && rawOutput !== undefined;
|
|
169
151
|
if (kind && FILE_KINDS.has(kind) && (locations === null || locations === void 0 ? void 0 : locations.length)) {
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
else if (kind &&
|
|
173
|
-
OUTPUT_KINDS.has(kind) &&
|
|
174
|
-
rawOutput !== null &&
|
|
175
|
-
rawOutput !== undefined) {
|
|
176
|
-
lines.push(formatToolCallOutput(rawOutput));
|
|
152
|
+
return true;
|
|
177
153
|
}
|
|
178
|
-
|
|
179
|
-
lines.push(rawOutput);
|
|
180
|
-
}
|
|
181
|
-
return lines;
|
|
154
|
+
return hasInput || hasOutput;
|
|
182
155
|
}
|
|
183
156
|
function toDiffLineInfo(type, text, key) {
|
|
184
157
|
switch (type) {
|
|
@@ -230,6 +203,67 @@ function buildDiffLines(diff) {
|
|
|
230
203
|
return lines;
|
|
231
204
|
}, []);
|
|
232
205
|
}
|
|
206
|
+
/**
|
|
207
|
+
* A labeled section with a capped height and show-all/show-less toggle.
|
|
208
|
+
*/
|
|
209
|
+
function ToolCallSection({ label, children, trans }) {
|
|
210
|
+
const [expanded, setExpanded] = React.useState(false);
|
|
211
|
+
const [isOverflowing, setIsOverflowing] = React.useState(false);
|
|
212
|
+
const preRef = React.useRef(null);
|
|
213
|
+
React.useEffect(() => {
|
|
214
|
+
var _a;
|
|
215
|
+
const details = (_a = preRef.current) === null || _a === void 0 ? void 0 : _a.closest('details');
|
|
216
|
+
if (!details) {
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
const handleToggle = () => {
|
|
220
|
+
if (!details.open) {
|
|
221
|
+
setExpanded(false);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
details.addEventListener('toggle', handleToggle);
|
|
225
|
+
return () => details.removeEventListener('toggle', handleToggle);
|
|
226
|
+
}, []);
|
|
227
|
+
React.useLayoutEffect(() => {
|
|
228
|
+
const el = preRef.current;
|
|
229
|
+
if (!el) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const measure = () => {
|
|
233
|
+
if (!expanded) {
|
|
234
|
+
setIsOverflowing(el.scrollHeight > el.clientHeight);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
const observer = new ResizeObserver(measure);
|
|
238
|
+
observer.observe(el);
|
|
239
|
+
measure();
|
|
240
|
+
return () => observer.disconnect();
|
|
241
|
+
}, [expanded]);
|
|
242
|
+
return (React.createElement("div", { className: "jp-ai-tool-call-detail-section" },
|
|
243
|
+
React.createElement("div", { className: "jp-ai-tool-call-detail-label" }, label),
|
|
244
|
+
React.createElement("pre", { ref: preRef, className: "jp-ai-tool-call-detail-code", style: {
|
|
245
|
+
maxHeight: expanded
|
|
246
|
+
? undefined
|
|
247
|
+
: `calc(${MAX_DETAIL_LINES} * var(--jp-content-line-height) * var(--jp-ui-font-size1))`
|
|
248
|
+
} },
|
|
249
|
+
React.createElement("code", null, children)),
|
|
250
|
+
!expanded && isOverflowing && (React.createElement("button", { className: "jp-ai-tool-call-diff-toggle", onClick: () => setExpanded(true), type: "button" }, trans.__('Show all'))),
|
|
251
|
+
expanded && (React.createElement("button", { className: "jp-ai-tool-call-diff-toggle", onClick: () => setExpanded(false), type: "button" }, trans.__('Show less')))));
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Expandable detail view for a completed or failed tool call.
|
|
255
|
+
*/
|
|
256
|
+
function ToolCallDetail({ toolCall, trans }) {
|
|
257
|
+
const { kind, locations, rawInput, rawOutput } = toolCall;
|
|
258
|
+
const hasInput = rawInput !== null && rawInput !== undefined;
|
|
259
|
+
const hasOutput = rawOutput !== null && rawOutput !== undefined;
|
|
260
|
+
if (kind && FILE_KINDS.has(kind) && (locations === null || locations === void 0 ? void 0 : locations.length)) {
|
|
261
|
+
return (React.createElement("div", { className: "jp-ai-tool-call-item-detail" }, locations.map((loc, i) => (React.createElement("div", { key: i, className: "jp-ai-tool-call-item-detail-path" }, toServerRelativePath(loc))))));
|
|
262
|
+
}
|
|
263
|
+
return (React.createElement("div", { className: "jp-ai-tool-call-item-detail" },
|
|
264
|
+
hasInput && (React.createElement(ToolCallSection, { label: trans.__('Input'), trans: trans }, formatToolCallIO(rawInput))),
|
|
265
|
+
hasOutput && (React.createElement(ToolCallSection, { label: trans.__('Output'), trans: trans }, formatToolCallIO(rawOutput)))));
|
|
266
|
+
}
|
|
233
267
|
function ToolCallDiffBlock({ diff, trans, openToolCallPath, pendingPermission }) {
|
|
234
268
|
const [expanded, setExpanded] = React.useState(false);
|
|
235
269
|
const allLines = React.useMemo(() => buildDiffLines(diff), [diff]);
|
|
@@ -300,10 +334,10 @@ function ToolCallRow({ toolCall, trans, openToolCallPath, toolCallPermissionDeci
|
|
|
300
334
|
var _a, _b, _c, _d;
|
|
301
335
|
const displayTitle = getToolCallDisplayTitle(toolCall);
|
|
302
336
|
const selectedOption = (_a = toolCall.permissionOptions) === null || _a === void 0 ? void 0 : _a.find(option => option.optionId === toolCall.selectedOptionId);
|
|
337
|
+
const status = (_b = toolCall.status) !== null && _b !== void 0 ? _b : 'in_progress';
|
|
303
338
|
const isRejected = toolCall.permissionStatus === 'resolved' &&
|
|
304
|
-
!!((
|
|
339
|
+
(!!((_c = selectedOption === null || selectedOption === void 0 ? void 0 : selectedOption.kind) === null || _c === void 0 ? void 0 : _c.includes('reject')) || status === 'rejected');
|
|
305
340
|
const hasPendingPermission = toolCall.permissionStatus === 'pending';
|
|
306
|
-
const status = (_c = toolCall.status) !== null && _c !== void 0 ? _c : 'in_progress';
|
|
307
341
|
const isInProgress = !isRejected &&
|
|
308
342
|
(status === 'in_progress' || status === 'pending' || hasPendingPermission);
|
|
309
343
|
const isCompleted = status === 'completed';
|
|
@@ -316,15 +350,21 @@ function ToolCallRow({ toolCall, trans, openToolCallPath, toolCallPermissionDeci
|
|
|
316
350
|
? '\u2717'
|
|
317
351
|
: '\u2022';
|
|
318
352
|
const effectiveStatus = (isRejected ? 'failed' : status).replace(/_/g, '-');
|
|
319
|
-
const cssClass = `jp-ai-tool-call-item jp-ai-tool-call-item-${effectiveStatus}`;
|
|
320
353
|
const hasDiffs = !!((_d = toolCall.diffs) === null || _d === void 0 ? void 0 : _d.length);
|
|
354
|
+
const hasExpandableContent = hasDiffs ||
|
|
355
|
+
(!hasDiffs && (isCompleted || isFailed) && hasDetailContent(toolCall));
|
|
356
|
+
const cssClass = `jp-ai-tool-call-item jp-ai-tool-call-item-${effectiveStatus}${!hasExpandableContent && !hasPendingPermission ? ' jp-ai-tool-call-item-no-detail' : ''}`;
|
|
321
357
|
if (hasDiffs && hasPendingPermission) {
|
|
322
358
|
return (React.createElement("div", { className: cssClass },
|
|
323
359
|
React.createElement("details", { open: true },
|
|
324
360
|
React.createElement("summary", null,
|
|
325
361
|
React.createElement("span", { className: "jp-ai-tool-call-item-icon" }, icon),
|
|
326
362
|
' ',
|
|
327
|
-
React.createElement("
|
|
363
|
+
React.createElement("div", { className: "jp-ai-tool-call-item-title" },
|
|
364
|
+
displayTitle,
|
|
365
|
+
toolCall.summary && (React.createElement("span", { className: "jp-ai-tool-call-item-summary" },
|
|
366
|
+
' ',
|
|
367
|
+
toolCall.summary)))),
|
|
328
368
|
React.createElement(ToolCallDiffView, { diffs: toolCall.diffs, trans: trans, openToolCallPath: openToolCallPath, pendingPermission: true })),
|
|
329
369
|
React.createElement(PermissionButtons, { toolCall: toolCall, trans: trans, toolCallPermissionDecision: toolCallPermissionDecision })));
|
|
330
370
|
}
|
|
@@ -336,35 +376,46 @@ function ToolCallRow({ toolCall, trans, openToolCallPath, toolCallPermissionDeci
|
|
|
336
376
|
React.createElement("summary", null,
|
|
337
377
|
React.createElement("span", { className: "jp-ai-tool-call-item-icon" }, icon),
|
|
338
378
|
' ',
|
|
339
|
-
React.createElement("
|
|
379
|
+
React.createElement("div", { className: "jp-ai-tool-call-item-title" },
|
|
380
|
+
displayTitle,
|
|
381
|
+
toolCall.summary && (React.createElement("span", { className: "jp-ai-tool-call-item-summary" },
|
|
382
|
+
' ',
|
|
383
|
+
toolCall.summary)))),
|
|
340
384
|
React.createElement("div", { className: "jp-ai-tool-call-item-detail" }, permissionDetail)),
|
|
341
385
|
React.createElement(PermissionButtons, { toolCall: toolCall, trans: trans, toolCallPermissionDecision: toolCallPermissionDecision })));
|
|
342
386
|
}
|
|
343
387
|
}
|
|
344
|
-
const detailLines = !hasDiffs && (isCompleted || isFailed)
|
|
345
|
-
? buildToolCallDetailsLines(toolCall)
|
|
346
|
-
: [];
|
|
347
|
-
const hasExpandableContent = hasDiffs || detailLines.length > 0;
|
|
348
388
|
if ((isCompleted || isFailed) && hasExpandableContent) {
|
|
349
389
|
return (React.createElement("details", { className: cssClass },
|
|
350
390
|
React.createElement("summary", null,
|
|
351
391
|
React.createElement("span", { className: "jp-ai-tool-call-item-icon" }, icon),
|
|
352
392
|
' ',
|
|
353
|
-
|
|
393
|
+
React.createElement("div", { className: "jp-ai-tool-call-item-title" },
|
|
394
|
+
displayTitle,
|
|
395
|
+
toolCall.summary && (React.createElement("span", { className: "jp-ai-tool-call-item-summary" },
|
|
396
|
+
' ',
|
|
397
|
+
toolCall.summary))),
|
|
354
398
|
React.createElement(PermissionLabel, { toolCall: toolCall })),
|
|
355
|
-
hasDiffs ? (React.createElement(ToolCallDiffView, { diffs: toolCall.diffs, trans: trans, openToolCallPath: openToolCallPath })) : (React.createElement(
|
|
399
|
+
hasDiffs ? (React.createElement(ToolCallDiffView, { diffs: toolCall.diffs, trans: trans, openToolCallPath: openToolCallPath })) : (React.createElement(ToolCallDetail, { toolCall: toolCall, trans: trans }))));
|
|
356
400
|
}
|
|
357
401
|
if (isInProgress) {
|
|
358
402
|
return (React.createElement("div", { className: cssClass },
|
|
359
403
|
React.createElement("span", { className: "jp-ai-tool-call-item-icon" }, icon),
|
|
360
404
|
' ',
|
|
361
|
-
React.createElement("
|
|
405
|
+
React.createElement("div", { className: "jp-ai-tool-call-item-title" },
|
|
406
|
+
displayTitle,
|
|
407
|
+
toolCall.summary && (React.createElement("span", { className: "jp-ai-tool-call-item-summary" },
|
|
408
|
+
' ',
|
|
409
|
+
toolCall.summary))),
|
|
362
410
|
React.createElement(PermissionButtons, { toolCall: toolCall, trans: trans, toolCallPermissionDecision: toolCallPermissionDecision })));
|
|
363
411
|
}
|
|
364
412
|
return (React.createElement("div", { className: cssClass },
|
|
365
413
|
React.createElement("span", { className: "jp-ai-tool-call-item-icon" }, icon),
|
|
366
|
-
" ",
|
|
367
|
-
|
|
414
|
+
React.createElement("div", { className: "jp-ai-tool-call-item-title" },
|
|
415
|
+
displayTitle,
|
|
416
|
+
toolCall.summary && (React.createElement("span", { className: "jp-ai-tool-call-item-summary" },
|
|
417
|
+
' ',
|
|
418
|
+
toolCall.summary))),
|
|
368
419
|
React.createElement(PermissionLabel, { toolCall: toolCall })));
|
|
369
420
|
}
|
|
370
421
|
/**
|
package/lib/components/index.js
CHANGED
package/lib/factory.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { nullTranslator } from '@jupyterlab/translation';
|
|
2
2
|
import { ReactWidget } from '@jupyterlab/ui-components';
|
|
3
3
|
import * as React from 'react';
|
|
4
|
-
import { GroupedToolCalls, InlineDiff, MessageQueue, ToolCall } from './components';
|
|
4
|
+
import { GroupedToolCalls, InlineDiff, MessageQueue, ToolCall, ErrorMessage } from './components';
|
|
5
5
|
import { ComponentRegistry } from './registry';
|
|
6
6
|
/**
|
|
7
7
|
* The default mime type for the extension.
|
|
@@ -94,5 +94,6 @@ export class RendererFactory {
|
|
|
94
94
|
this.registry.add('grouped-tool-calls', GroupedToolCalls);
|
|
95
95
|
this.registry.add('inline-diff', InlineDiff);
|
|
96
96
|
this.registry.add('message-queue', MessageQueue);
|
|
97
|
+
this.registry.add('error', ErrorMessage);
|
|
97
98
|
}
|
|
98
99
|
}
|
package/lib/icons.d.ts
ADDED
package/lib/icons.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { LabIcon } from '@jupyterlab/ui-components';
|
|
2
|
+
export const errorIcon = new LabIcon({
|
|
3
|
+
name: 'jupyter-chat:error',
|
|
4
|
+
svgstr: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z"/></svg>'
|
|
5
|
+
});
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
package/lib/token.d.ts
CHANGED
|
@@ -134,6 +134,10 @@ export interface IToolCallsEntry {
|
|
|
134
134
|
* Human-readable title displayed in the UI.
|
|
135
135
|
*/
|
|
136
136
|
title?: string;
|
|
137
|
+
/**
|
|
138
|
+
* Human-readable summary from tool input displayed with the title
|
|
139
|
+
*/
|
|
140
|
+
summary?: string;
|
|
137
141
|
/**
|
|
138
142
|
* Tool operation category.
|
|
139
143
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyter-chat-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Components to displayed in jupyter chat",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"@jupyterlab/rendermime": "^4.5.0",
|
|
62
62
|
"@jupyterlab/rendermime-interfaces": "^3.8.0",
|
|
63
63
|
"@jupyterlab/translation": "^4.5.0",
|
|
64
|
+
"@jupyterlab/ui-components": "^4.5.0",
|
|
64
65
|
"@lumino/coreutils": "^2.2.2",
|
|
65
66
|
"@lumino/widgets": "^2.1.0",
|
|
66
67
|
"diff": "^8.0.0"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { IComponentProps } from '../token';
|
|
4
|
+
import { errorIcon } from '../icons';
|
|
5
|
+
|
|
6
|
+
export interface IErrorProps extends IComponentProps {
|
|
7
|
+
errorMessage: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const ErrorMessage: React.FC<IErrorProps> = ({
|
|
12
|
+
errorMessage,
|
|
13
|
+
title = 'Error'
|
|
14
|
+
}) => {
|
|
15
|
+
return (
|
|
16
|
+
<div className="jp-ai-error-message">
|
|
17
|
+
<div className="jp-ai-error-icon">
|
|
18
|
+
<errorIcon.react tag="span" width="18px" height="18px" />
|
|
19
|
+
</div>
|
|
20
|
+
<div className="jp-ai-error-body">
|
|
21
|
+
<div className="jp-ai-error-title">{title}</div>
|
|
22
|
+
<div className="jp-ai-error-text">{errorMessage}</div>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
@@ -17,12 +17,12 @@ import {
|
|
|
17
17
|
/** Maximum number of rendered diff lines before truncation. */
|
|
18
18
|
const MAX_DIFF_LINES = 20;
|
|
19
19
|
|
|
20
|
+
/** Maximum number of lines shown in an expanded detail. */
|
|
21
|
+
const MAX_DETAIL_LINES = 15;
|
|
22
|
+
|
|
20
23
|
/** Tool kinds where expanded view shows file paths from locations. */
|
|
21
24
|
const FILE_KINDS = new Set(['read', 'edit', 'delete', 'move']);
|
|
22
25
|
|
|
23
|
-
/** Tool kinds where expanded view shows raw output. */
|
|
24
|
-
const OUTPUT_KINDS = new Set(['search', 'execute', 'think', 'fetch']);
|
|
25
|
-
|
|
26
26
|
const TOOL_KIND_LABELS: Record<string, string> = {
|
|
27
27
|
read: 'Reading',
|
|
28
28
|
edit: 'Editing',
|
|
@@ -88,53 +88,26 @@ export function toServerRelativePath(absolutePath: string): string {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
|
-
* Format tool output for display.
|
|
91
|
+
* Format tool payload (input/output) for display.
|
|
92
92
|
*/
|
|
93
|
-
export function
|
|
94
|
-
if (typeof
|
|
95
|
-
return
|
|
93
|
+
export function formatToolCallIO(payload: unknown): string {
|
|
94
|
+
if (typeof payload === 'string') {
|
|
95
|
+
return payload;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
if (
|
|
99
|
-
Array.isArray(
|
|
100
|
-
|
|
99
|
+
Array.isArray(payload) &&
|
|
100
|
+
payload.every(
|
|
101
101
|
item =>
|
|
102
102
|
typeof item === 'object' &&
|
|
103
103
|
item !== null &&
|
|
104
104
|
typeof (item as { text?: unknown }).text === 'string'
|
|
105
105
|
)
|
|
106
106
|
) {
|
|
107
|
-
return
|
|
107
|
+
return payload.map(item => (item as { text: string }).text).join('\n');
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
return JSON.stringify(
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Format tool input for display.
|
|
115
|
-
*/
|
|
116
|
-
export function formatToolCallInput(input: unknown): string {
|
|
117
|
-
if (typeof input === 'string') {
|
|
118
|
-
return input;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (typeof input !== 'object' || input === null || Array.isArray(input)) {
|
|
122
|
-
return JSON.stringify(input, null, 2);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const entries = Object.entries(input as Record<string, unknown>);
|
|
126
|
-
const isFlat = entries.every(
|
|
127
|
-
([, value]) =>
|
|
128
|
-
typeof value === 'string' ||
|
|
129
|
-
typeof value === 'number' ||
|
|
130
|
-
typeof value === 'boolean'
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
if (isFlat) {
|
|
134
|
-
return entries.map(([key, value]) => `${key}: ${value}`).join('\n');
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return JSON.stringify(input, null, 2);
|
|
110
|
+
return JSON.stringify(payload, null, 2);
|
|
138
111
|
}
|
|
139
112
|
|
|
140
113
|
function getLocationSummary(toolCall: IToolCallsEntry): string | null {
|
|
@@ -231,7 +204,7 @@ export function buildPermissionDetail(
|
|
|
231
204
|
{}
|
|
232
205
|
);
|
|
233
206
|
const params =
|
|
234
|
-
paramEntries.length > 0 ?
|
|
207
|
+
paramEntries.length > 0 ? formatToolCallIO(filteredParams) : null;
|
|
235
208
|
|
|
236
209
|
if (purpose && params) {
|
|
237
210
|
return `${purpose}\n${params}`;
|
|
@@ -249,33 +222,24 @@ export function buildPermissionDetail(
|
|
|
249
222
|
}
|
|
250
223
|
|
|
251
224
|
if (rawInput !== null && rawInput !== undefined) {
|
|
252
|
-
return
|
|
225
|
+
return formatToolCallIO(rawInput);
|
|
253
226
|
}
|
|
254
227
|
|
|
255
228
|
return null;
|
|
256
229
|
}
|
|
257
230
|
|
|
258
231
|
/**
|
|
259
|
-
*
|
|
232
|
+
* Returns true when a completed/failed tool call has expandable detail content.
|
|
260
233
|
*/
|
|
261
|
-
|
|
262
|
-
const
|
|
263
|
-
const
|
|
234
|
+
function hasDetailContent(toolCall: IToolCallsEntry): boolean {
|
|
235
|
+
const { kind, locations, rawInput, rawOutput } = toolCall;
|
|
236
|
+
const hasInput = rawInput !== null && rawInput !== undefined;
|
|
237
|
+
const hasOutput = rawOutput !== null && rawOutput !== undefined;
|
|
264
238
|
|
|
265
239
|
if (kind && FILE_KINDS.has(kind) && locations?.length) {
|
|
266
|
-
|
|
267
|
-
} else if (
|
|
268
|
-
kind &&
|
|
269
|
-
OUTPUT_KINDS.has(kind) &&
|
|
270
|
-
rawOutput !== null &&
|
|
271
|
-
rawOutput !== undefined
|
|
272
|
-
) {
|
|
273
|
-
lines.push(formatToolCallOutput(rawOutput));
|
|
274
|
-
} else if (typeof rawOutput === 'string') {
|
|
275
|
-
lines.push(rawOutput);
|
|
240
|
+
return true;
|
|
276
241
|
}
|
|
277
|
-
|
|
278
|
-
return lines;
|
|
242
|
+
return hasInput || hasOutput;
|
|
279
243
|
}
|
|
280
244
|
|
|
281
245
|
function toDiffLineInfo(
|
|
@@ -348,6 +312,130 @@ function buildDiffLines(diff: IToolCallDiff): IDiffLineInfo[] {
|
|
|
348
312
|
}, []);
|
|
349
313
|
}
|
|
350
314
|
|
|
315
|
+
/**
|
|
316
|
+
* A labeled section with a capped height and show-all/show-less toggle.
|
|
317
|
+
*/
|
|
318
|
+
function ToolCallSection({
|
|
319
|
+
label,
|
|
320
|
+
children,
|
|
321
|
+
trans
|
|
322
|
+
}: {
|
|
323
|
+
label: string;
|
|
324
|
+
children: React.ReactNode;
|
|
325
|
+
trans: TranslationBundle;
|
|
326
|
+
}): JSX.Element {
|
|
327
|
+
const [expanded, setExpanded] = React.useState(false);
|
|
328
|
+
const [isOverflowing, setIsOverflowing] = React.useState(false);
|
|
329
|
+
const preRef = React.useRef<HTMLPreElement>(null);
|
|
330
|
+
|
|
331
|
+
React.useEffect(() => {
|
|
332
|
+
const details = preRef.current?.closest('details');
|
|
333
|
+
if (!details) {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const handleToggle = () => {
|
|
337
|
+
if (!details.open) {
|
|
338
|
+
setExpanded(false);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
details.addEventListener('toggle', handleToggle);
|
|
342
|
+
return () => details.removeEventListener('toggle', handleToggle);
|
|
343
|
+
}, []);
|
|
344
|
+
|
|
345
|
+
React.useLayoutEffect(() => {
|
|
346
|
+
const el = preRef.current;
|
|
347
|
+
if (!el) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
const measure = () => {
|
|
351
|
+
if (!expanded) {
|
|
352
|
+
setIsOverflowing(el.scrollHeight > el.clientHeight);
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
const observer = new ResizeObserver(measure);
|
|
356
|
+
observer.observe(el);
|
|
357
|
+
measure();
|
|
358
|
+
return () => observer.disconnect();
|
|
359
|
+
}, [expanded]);
|
|
360
|
+
|
|
361
|
+
return (
|
|
362
|
+
<div className="jp-ai-tool-call-detail-section">
|
|
363
|
+
<div className="jp-ai-tool-call-detail-label">{label}</div>
|
|
364
|
+
<pre
|
|
365
|
+
ref={preRef}
|
|
366
|
+
className="jp-ai-tool-call-detail-code"
|
|
367
|
+
style={{
|
|
368
|
+
maxHeight: expanded
|
|
369
|
+
? undefined
|
|
370
|
+
: `calc(${MAX_DETAIL_LINES} * var(--jp-content-line-height) * var(--jp-ui-font-size1))`
|
|
371
|
+
}}
|
|
372
|
+
>
|
|
373
|
+
<code>{children}</code>
|
|
374
|
+
</pre>
|
|
375
|
+
{!expanded && isOverflowing && (
|
|
376
|
+
<button
|
|
377
|
+
className="jp-ai-tool-call-diff-toggle"
|
|
378
|
+
onClick={() => setExpanded(true)}
|
|
379
|
+
type="button"
|
|
380
|
+
>
|
|
381
|
+
{trans.__('Show all')}
|
|
382
|
+
</button>
|
|
383
|
+
)}
|
|
384
|
+
{expanded && (
|
|
385
|
+
<button
|
|
386
|
+
className="jp-ai-tool-call-diff-toggle"
|
|
387
|
+
onClick={() => setExpanded(false)}
|
|
388
|
+
type="button"
|
|
389
|
+
>
|
|
390
|
+
{trans.__('Show less')}
|
|
391
|
+
</button>
|
|
392
|
+
)}
|
|
393
|
+
</div>
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Expandable detail view for a completed or failed tool call.
|
|
399
|
+
*/
|
|
400
|
+
function ToolCallDetail({
|
|
401
|
+
toolCall,
|
|
402
|
+
trans
|
|
403
|
+
}: {
|
|
404
|
+
toolCall: IToolCallsEntry;
|
|
405
|
+
trans: TranslationBundle;
|
|
406
|
+
}): JSX.Element {
|
|
407
|
+
const { kind, locations, rawInput, rawOutput } = toolCall;
|
|
408
|
+
const hasInput = rawInput !== null && rawInput !== undefined;
|
|
409
|
+
const hasOutput = rawOutput !== null && rawOutput !== undefined;
|
|
410
|
+
|
|
411
|
+
if (kind && FILE_KINDS.has(kind) && locations?.length) {
|
|
412
|
+
return (
|
|
413
|
+
<div className="jp-ai-tool-call-item-detail">
|
|
414
|
+
{locations.map((loc, i) => (
|
|
415
|
+
<div key={i} className="jp-ai-tool-call-item-detail-path">
|
|
416
|
+
{toServerRelativePath(loc)}
|
|
417
|
+
</div>
|
|
418
|
+
))}
|
|
419
|
+
</div>
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return (
|
|
424
|
+
<div className="jp-ai-tool-call-item-detail">
|
|
425
|
+
{hasInput && (
|
|
426
|
+
<ToolCallSection label={trans.__('Input')} trans={trans}>
|
|
427
|
+
{formatToolCallIO(rawInput)}
|
|
428
|
+
</ToolCallSection>
|
|
429
|
+
)}
|
|
430
|
+
{hasOutput && (
|
|
431
|
+
<ToolCallSection label={trans.__('Output')} trans={trans}>
|
|
432
|
+
{formatToolCallIO(rawOutput)}
|
|
433
|
+
</ToolCallSection>
|
|
434
|
+
)}
|
|
435
|
+
</div>
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
351
439
|
function ToolCallDiffBlock({
|
|
352
440
|
diff,
|
|
353
441
|
trans,
|
|
@@ -554,11 +642,12 @@ function ToolCallRow({
|
|
|
554
642
|
const selectedOption = toolCall.permissionOptions?.find(
|
|
555
643
|
option => option.optionId === toolCall.selectedOptionId
|
|
556
644
|
);
|
|
645
|
+
const status = toolCall.status ?? 'in_progress';
|
|
646
|
+
|
|
557
647
|
const isRejected =
|
|
558
648
|
toolCall.permissionStatus === 'resolved' &&
|
|
559
|
-
!!selectedOption?.kind?.includes('reject');
|
|
649
|
+
(!!selectedOption?.kind?.includes('reject') || status === 'rejected');
|
|
560
650
|
const hasPendingPermission = toolCall.permissionStatus === 'pending';
|
|
561
|
-
const status = toolCall.status ?? 'in_progress';
|
|
562
651
|
const isInProgress =
|
|
563
652
|
!isRejected &&
|
|
564
653
|
(status === 'in_progress' || status === 'pending' || hasPendingPermission);
|
|
@@ -572,8 +661,13 @@ function ToolCallRow({
|
|
|
572
661
|
? '\u2717'
|
|
573
662
|
: '\u2022';
|
|
574
663
|
const effectiveStatus = (isRejected ? 'failed' : status).replace(/_/g, '-');
|
|
575
|
-
|
|
664
|
+
|
|
576
665
|
const hasDiffs = !!toolCall.diffs?.length;
|
|
666
|
+
const hasExpandableContent =
|
|
667
|
+
hasDiffs ||
|
|
668
|
+
(!hasDiffs && (isCompleted || isFailed) && hasDetailContent(toolCall));
|
|
669
|
+
|
|
670
|
+
const cssClass = `jp-ai-tool-call-item jp-ai-tool-call-item-${effectiveStatus}${!hasExpandableContent && !hasPendingPermission ? ' jp-ai-tool-call-item-no-detail' : ''}`;
|
|
577
671
|
|
|
578
672
|
if (hasDiffs && hasPendingPermission) {
|
|
579
673
|
return (
|
|
@@ -581,7 +675,15 @@ function ToolCallRow({
|
|
|
581
675
|
<details open>
|
|
582
676
|
<summary>
|
|
583
677
|
<span className="jp-ai-tool-call-item-icon">{icon}</span>{' '}
|
|
584
|
-
<
|
|
678
|
+
<div className="jp-ai-tool-call-item-title">
|
|
679
|
+
{displayTitle}
|
|
680
|
+
{toolCall.summary && (
|
|
681
|
+
<span className="jp-ai-tool-call-item-summary">
|
|
682
|
+
{' '}
|
|
683
|
+
{toolCall.summary}
|
|
684
|
+
</span>
|
|
685
|
+
)}
|
|
686
|
+
</div>
|
|
585
687
|
</summary>
|
|
586
688
|
<ToolCallDiffView
|
|
587
689
|
diffs={toolCall.diffs!}
|
|
@@ -608,7 +710,15 @@ function ToolCallRow({
|
|
|
608
710
|
<details open>
|
|
609
711
|
<summary>
|
|
610
712
|
<span className="jp-ai-tool-call-item-icon">{icon}</span>{' '}
|
|
611
|
-
<
|
|
713
|
+
<div className="jp-ai-tool-call-item-title">
|
|
714
|
+
{displayTitle}
|
|
715
|
+
{toolCall.summary && (
|
|
716
|
+
<span className="jp-ai-tool-call-item-summary">
|
|
717
|
+
{' '}
|
|
718
|
+
{toolCall.summary}
|
|
719
|
+
</span>
|
|
720
|
+
)}
|
|
721
|
+
</div>
|
|
612
722
|
</summary>
|
|
613
723
|
<div className="jp-ai-tool-call-item-detail">
|
|
614
724
|
{permissionDetail}
|
|
@@ -624,18 +734,20 @@ function ToolCallRow({
|
|
|
624
734
|
}
|
|
625
735
|
}
|
|
626
736
|
|
|
627
|
-
const detailLines =
|
|
628
|
-
!hasDiffs && (isCompleted || isFailed)
|
|
629
|
-
? buildToolCallDetailsLines(toolCall)
|
|
630
|
-
: [];
|
|
631
|
-
const hasExpandableContent = hasDiffs || detailLines.length > 0;
|
|
632
|
-
|
|
633
737
|
if ((isCompleted || isFailed) && hasExpandableContent) {
|
|
634
738
|
return (
|
|
635
739
|
<details className={cssClass}>
|
|
636
740
|
<summary>
|
|
637
741
|
<span className="jp-ai-tool-call-item-icon">{icon}</span>{' '}
|
|
638
|
-
|
|
742
|
+
<div className="jp-ai-tool-call-item-title">
|
|
743
|
+
{displayTitle}
|
|
744
|
+
{toolCall.summary && (
|
|
745
|
+
<span className="jp-ai-tool-call-item-summary">
|
|
746
|
+
{' '}
|
|
747
|
+
{toolCall.summary}
|
|
748
|
+
</span>
|
|
749
|
+
)}
|
|
750
|
+
</div>
|
|
639
751
|
<PermissionLabel toolCall={toolCall} />
|
|
640
752
|
</summary>
|
|
641
753
|
{hasDiffs ? (
|
|
@@ -645,9 +757,7 @@ function ToolCallRow({
|
|
|
645
757
|
openToolCallPath={openToolCallPath}
|
|
646
758
|
/>
|
|
647
759
|
) : (
|
|
648
|
-
<
|
|
649
|
-
{detailLines.join('\n')}
|
|
650
|
-
</div>
|
|
760
|
+
<ToolCallDetail toolCall={toolCall} trans={trans} />
|
|
651
761
|
)}
|
|
652
762
|
</details>
|
|
653
763
|
);
|
|
@@ -657,7 +767,15 @@ function ToolCallRow({
|
|
|
657
767
|
return (
|
|
658
768
|
<div className={cssClass}>
|
|
659
769
|
<span className="jp-ai-tool-call-item-icon">{icon}</span>{' '}
|
|
660
|
-
<
|
|
770
|
+
<div className="jp-ai-tool-call-item-title">
|
|
771
|
+
{displayTitle}
|
|
772
|
+
{toolCall.summary && (
|
|
773
|
+
<span className="jp-ai-tool-call-item-summary">
|
|
774
|
+
{' '}
|
|
775
|
+
{toolCall.summary}
|
|
776
|
+
</span>
|
|
777
|
+
)}
|
|
778
|
+
</div>
|
|
661
779
|
<PermissionButtons
|
|
662
780
|
toolCall={toolCall}
|
|
663
781
|
trans={trans}
|
|
@@ -669,7 +787,16 @@ function ToolCallRow({
|
|
|
669
787
|
|
|
670
788
|
return (
|
|
671
789
|
<div className={cssClass}>
|
|
672
|
-
<span className="jp-ai-tool-call-item-icon">{icon}</span>
|
|
790
|
+
<span className="jp-ai-tool-call-item-icon">{icon}</span>
|
|
791
|
+
<div className="jp-ai-tool-call-item-title">
|
|
792
|
+
{displayTitle}
|
|
793
|
+
{toolCall.summary && (
|
|
794
|
+
<span className="jp-ai-tool-call-item-summary">
|
|
795
|
+
{' '}
|
|
796
|
+
{toolCall.summary}
|
|
797
|
+
</span>
|
|
798
|
+
)}
|
|
799
|
+
</div>
|
|
673
800
|
<PermissionLabel toolCall={toolCall} />
|
|
674
801
|
</div>
|
|
675
802
|
);
|
package/src/components/index.ts
CHANGED
package/src/factory.tsx
CHANGED
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
GroupedToolCalls,
|
|
13
13
|
InlineDiff,
|
|
14
14
|
MessageQueue,
|
|
15
|
-
ToolCall
|
|
15
|
+
ToolCall,
|
|
16
|
+
ErrorMessage
|
|
16
17
|
} from './components';
|
|
17
18
|
|
|
18
19
|
import { ComponentRegistry } from './registry';
|
|
@@ -162,6 +163,7 @@ export class RendererFactory implements IComponentsRendererFactory {
|
|
|
162
163
|
this.registry.add('grouped-tool-calls', GroupedToolCalls);
|
|
163
164
|
this.registry.add('inline-diff', InlineDiff);
|
|
164
165
|
this.registry.add('message-queue', MessageQueue);
|
|
166
|
+
this.registry.add('error', ErrorMessage);
|
|
165
167
|
}
|
|
166
168
|
|
|
167
169
|
createRenderer = (options: IRenderMime.IRendererOptions) => {
|
package/src/icons.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { LabIcon } from '@jupyterlab/ui-components';
|
|
2
|
+
|
|
3
|
+
export const errorIcon = new LabIcon({
|
|
4
|
+
name: 'jupyter-chat:error',
|
|
5
|
+
svgstr:
|
|
6
|
+
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z"/></svg>'
|
|
7
|
+
});
|
package/src/index.ts
CHANGED
package/src/token.ts
CHANGED
|
@@ -169,6 +169,10 @@ export interface IToolCallsEntry {
|
|
|
169
169
|
* Human-readable title displayed in the UI.
|
|
170
170
|
*/
|
|
171
171
|
title?: string;
|
|
172
|
+
/**
|
|
173
|
+
* Human-readable summary from tool input displayed with the title
|
|
174
|
+
*/
|
|
175
|
+
summary?: string;
|
|
172
176
|
/**
|
|
173
177
|
* Tool operation category.
|
|
174
178
|
*/
|
package/style/base.css
CHANGED
|
@@ -233,15 +233,6 @@
|
|
|
233
233
|
.jp-ai-tool-call-item {
|
|
234
234
|
padding: 1px 0;
|
|
235
235
|
font-family: var(--jp-content-font-family);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
.jp-ai-tool-call-item-in-progress,
|
|
239
|
-
.jp-ai-tool-call-item-pending {
|
|
240
|
-
color: var(--jp-ui-font-color2);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
.jp-ai-tool-call-item-completed,
|
|
244
|
-
.jp-ai-tool-call-item-failed {
|
|
245
236
|
color: var(--jp-ui-font-color2);
|
|
246
237
|
}
|
|
247
238
|
|
|
@@ -256,7 +247,30 @@
|
|
|
256
247
|
details.jp-ai-tool-call-item summary,
|
|
257
248
|
.jp-ai-tool-call-item details summary {
|
|
258
249
|
cursor: pointer;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
details.jp-ai-tool-call-item summary,
|
|
253
|
+
.jp-ai-tool-call-item details summary,
|
|
254
|
+
.jp-ai-tool-call-item.jp-ai-tool-call-item-no-detail {
|
|
259
255
|
list-style: none;
|
|
256
|
+
display: flex;
|
|
257
|
+
align-items: center;
|
|
258
|
+
padding: 4px 8px;
|
|
259
|
+
background: var(--jp-layout-color1);
|
|
260
|
+
gap: 8px;
|
|
261
|
+
transition: background-color 0.2s ease;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.jp-ai-tool-call-item-title {
|
|
265
|
+
font-size: var(--jp-ui-font-size1);
|
|
266
|
+
font-weight: 500;
|
|
267
|
+
flex: 1;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.jp-ai-tool-call-item-summary {
|
|
271
|
+
font-weight: 400;
|
|
272
|
+
opacity: 0.8;
|
|
273
|
+
font-size: var(--jp-ui-font-size0);
|
|
260
274
|
}
|
|
261
275
|
|
|
262
276
|
details.jp-ai-tool-call-item summary::-webkit-details-marker,
|
|
@@ -284,11 +298,42 @@ details[open].jp-ai-tool-call-item summary::after,
|
|
|
284
298
|
|
|
285
299
|
.jp-ai-tool-call-item-detail {
|
|
286
300
|
margin: 2px 0 2px 20px;
|
|
287
|
-
font-size: var(--jp-
|
|
301
|
+
font-size: var(--jp-ui-font-size1);
|
|
288
302
|
font-family: var(--jp-code-font-family);
|
|
289
|
-
color: var(--jp-ui-font-color2);
|
|
290
303
|
white-space: pre-wrap;
|
|
291
304
|
word-break: break-all;
|
|
305
|
+
padding-left: 5px;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.jp-ai-tool-call-detail-section {
|
|
309
|
+
margin-bottom: 8px;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.jp-ai-tool-call-detail-section:last-child {
|
|
313
|
+
margin-bottom: 0;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.jp-ai-tool-call-detail-label {
|
|
317
|
+
font-family: var(--jp-ui-font-family);
|
|
318
|
+
font-size: var(--jp-ui-font-size0);
|
|
319
|
+
font-weight: 600;
|
|
320
|
+
margin-bottom: 4px;
|
|
321
|
+
text-transform: uppercase;
|
|
322
|
+
letter-spacing: 0.5px;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.jp-ai-tool-call-detail-code {
|
|
326
|
+
background: var(--jp-layout-color2) !important;
|
|
327
|
+
border: 1px solid var(--jp-border-color1) !important;
|
|
328
|
+
border-radius: 4px !important;
|
|
329
|
+
padding: 8px !important;
|
|
330
|
+
margin: 0 !important;
|
|
331
|
+
font-size: var(--jp-ui-font-size1) !important;
|
|
332
|
+
overflow: auto;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.jp-ai-tool-call-detail-code code {
|
|
336
|
+
font-size: var(--jp-ui-font-size1) !important;
|
|
292
337
|
}
|
|
293
338
|
|
|
294
339
|
.jp-ai-tool-call-permission-buttons {
|
|
@@ -297,6 +342,7 @@ details[open].jp-ai-tool-call-item summary::after,
|
|
|
297
342
|
margin: 4px 0;
|
|
298
343
|
align-items: center;
|
|
299
344
|
flex-wrap: wrap;
|
|
345
|
+
padding-left: 5px;
|
|
300
346
|
}
|
|
301
347
|
|
|
302
348
|
.jp-ai-tool-call-permission-tree {
|
|
@@ -598,3 +644,44 @@ details[open].jp-ai-tool-call-item summary::after,
|
|
|
598
644
|
.jp-chat-message-queue-bubble:hover .jp-chat-message-queue-remove {
|
|
599
645
|
opacity: 1;
|
|
600
646
|
}
|
|
647
|
+
|
|
648
|
+
/* Error Component Styling */
|
|
649
|
+
.jp-ai-error-message {
|
|
650
|
+
display: flex;
|
|
651
|
+
align-items: flex-start;
|
|
652
|
+
gap: 12px;
|
|
653
|
+
background-color: var(--jp-layout-color2);
|
|
654
|
+
border: 1px solid var(--jp-border-color1);
|
|
655
|
+
border-left: 4px solid var(--jp-error-color1);
|
|
656
|
+
border-radius: 6px;
|
|
657
|
+
padding: 12px 16px;
|
|
658
|
+
margin: 8px 0;
|
|
659
|
+
box-shadow: var(--jp-elevation-z2);
|
|
660
|
+
color: var(--jp-ui-font-color1);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.jp-ai-error-icon {
|
|
664
|
+
color: var(--jp-error-color1);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.jp-ai-error-body {
|
|
668
|
+
display: flex;
|
|
669
|
+
flex-direction: column;
|
|
670
|
+
gap: 4px;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
.jp-ai-error-title {
|
|
674
|
+
font-family: var(--jp-ui-font-family);
|
|
675
|
+
font-size: var(--jp-ui-font-size1);
|
|
676
|
+
font-weight: 600;
|
|
677
|
+
color: var(--jp-error-color1);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
.jp-ai-error-text {
|
|
681
|
+
font-family: var(--jp-ui-font-family);
|
|
682
|
+
font-size: var(--jp-ui-font-size1);
|
|
683
|
+
word-break: break-word;
|
|
684
|
+
white-space: pre-wrap;
|
|
685
|
+
color: var(--jp-ui-font-color2);
|
|
686
|
+
line-height: 1.4;
|
|
687
|
+
}
|