jupyter-chat-components 0.1.3 → 0.3.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 +31 -1
- package/lib/components/index.d.ts +3 -0
- package/lib/components/index.js +3 -0
- package/lib/components/inline-diff.d.ts +13 -0
- package/lib/components/inline-diff.js +128 -0
- package/lib/components/message-queue.d.ts +13 -0
- package/lib/components/message-queue.js +13 -0
- package/lib/components/tool-call.d.ts +17 -8
- package/lib/components/tool-call.js +3 -0
- package/lib/factory.d.ts +18 -4
- package/lib/factory.js +31 -11
- package/lib/index.d.ts +2 -0
- package/lib/index.js +3 -1
- package/lib/registry.d.ts +35 -0
- package/lib/registry.js +46 -0
- package/lib/token.d.ts +131 -10
- package/package.json +4 -2
- package/src/components/index.ts +3 -0
- package/src/components/inline-diff.tsx +241 -0
- package/src/components/message-queue.tsx +50 -0
- package/src/components/tool-call.tsx +33 -9
- package/src/factory.tsx +59 -16
- package/src/index.ts +5 -2
- package/src/registry.ts +54 -0
- package/src/token.ts +150 -16
- package/style/base.css +180 -4
package/README.md
CHANGED
|
@@ -2,7 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/brichet/jupyter-chat-components/actions/workflows/build.yml)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
A library of React components designed for use in Jupyter chat interfaces, with a focus on AI-powered interactions. These components are intended to be integrated into JupyterLab extensions that provide chat functionality.
|
|
6
|
+
|
|
7
|
+
## MIME renderer
|
|
8
|
+
|
|
9
|
+
Components are exposed through a custom MIME type: `application/vnd.jupyter.chat.components`.
|
|
10
|
+
|
|
11
|
+
This extension registers a MIME renderer factory with JupyterLab's render MIME registry. To display a component, produce output with the MIME type above, where:
|
|
12
|
+
|
|
13
|
+
- the **data** value is the component name (e.g. `"tool-call"`)
|
|
14
|
+
- the **metadata** contains the props to pass to the component
|
|
15
|
+
|
|
16
|
+
The MIME renderer looks up the component name in the factory's registry and renders the corresponding React component.
|
|
17
|
+
|
|
18
|
+
## Component registry
|
|
19
|
+
|
|
20
|
+
The registry is available directly on the `IComponentsRendererFactory` token as the `registry` property. It maps component names to React components and exposes the following methods:
|
|
21
|
+
|
|
22
|
+
- `add(name, component)` — register a new React component under a unique name
|
|
23
|
+
- `get(name)` — retrieve a registered component by name
|
|
24
|
+
- `has(name)` — check whether a component is registered
|
|
25
|
+
- `getNames()` — list all registered component names
|
|
26
|
+
|
|
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
|
+
|
|
29
|
+
## Available components
|
|
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
|
+
More components are planned for future releases.
|
|
6
36
|
|
|
7
37
|
## Requirements
|
|
8
38
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { TranslationBundle } from '@jupyterlab/translation';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { IInlineDiff, IInlineDiffMetadata } from '../token';
|
|
4
|
+
export interface IInlineDiffProps extends IInlineDiffMetadata {
|
|
5
|
+
trans?: TranslationBundle;
|
|
6
|
+
}
|
|
7
|
+
export declare function getDiffFilename(path: string): string;
|
|
8
|
+
export declare function getInlineDiffLabel(diff: IInlineDiff): string;
|
|
9
|
+
export declare function getInlineDiffTitle(diff: IInlineDiff): string;
|
|
10
|
+
/**
|
|
11
|
+
* React component for rendering one or more inline diffs.
|
|
12
|
+
*/
|
|
13
|
+
export declare const InlineDiff: React.FC<IInlineDiffProps>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { PathExt } from '@jupyterlab/coreutils';
|
|
2
|
+
import { nullTranslator } from '@jupyterlab/translation';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { structuredPatch } from 'diff';
|
|
5
|
+
/** Maximum number of rendered lines before truncation. */
|
|
6
|
+
const MAX_DIFF_LINES = 20;
|
|
7
|
+
export function getDiffFilename(path) {
|
|
8
|
+
return PathExt.basename(path);
|
|
9
|
+
}
|
|
10
|
+
function getNotebookCellLabel(target) {
|
|
11
|
+
if (typeof target.cellIndex === 'number') {
|
|
12
|
+
return `Cell ${target.cellIndex + 1}`;
|
|
13
|
+
}
|
|
14
|
+
if (target.cellId) {
|
|
15
|
+
return `Cell ${target.cellId}`;
|
|
16
|
+
}
|
|
17
|
+
return 'Notebook Cell';
|
|
18
|
+
}
|
|
19
|
+
function getInlineDiffPatchPath(diff) {
|
|
20
|
+
var _a;
|
|
21
|
+
const target = diff.target;
|
|
22
|
+
if (target.kind === 'file') {
|
|
23
|
+
return target.path;
|
|
24
|
+
}
|
|
25
|
+
return [target.notebookPath, (_a = target.cellId) !== null && _a !== void 0 ? _a : target.cellIndex]
|
|
26
|
+
.filter(value => value !== undefined && value !== '')
|
|
27
|
+
.join('#');
|
|
28
|
+
}
|
|
29
|
+
export function getInlineDiffLabel(diff) {
|
|
30
|
+
if (diff.label) {
|
|
31
|
+
return diff.label;
|
|
32
|
+
}
|
|
33
|
+
const target = diff.target;
|
|
34
|
+
if (target.kind === 'file') {
|
|
35
|
+
return getDiffFilename(target.path);
|
|
36
|
+
}
|
|
37
|
+
const notebookName = getDiffFilename(target.notebookPath);
|
|
38
|
+
const cellLabel = getNotebookCellLabel(target);
|
|
39
|
+
return [notebookName, cellLabel].join(' · ');
|
|
40
|
+
}
|
|
41
|
+
export function getInlineDiffTitle(diff) {
|
|
42
|
+
const target = diff.target;
|
|
43
|
+
if (target.kind === 'file') {
|
|
44
|
+
return target.path;
|
|
45
|
+
}
|
|
46
|
+
const cellLabel = getNotebookCellLabel(target);
|
|
47
|
+
const cellIdLabel = typeof target.cellIndex === 'number' && target.cellId
|
|
48
|
+
? `Cell ID ${target.cellId}`
|
|
49
|
+
: null;
|
|
50
|
+
return [target.notebookPath, cellLabel, cellIdLabel]
|
|
51
|
+
.filter(part => part !== null && part !== undefined && part !== '')
|
|
52
|
+
.join(' · ');
|
|
53
|
+
}
|
|
54
|
+
function toLineInfo(type, text, key) {
|
|
55
|
+
switch (type) {
|
|
56
|
+
case 'added':
|
|
57
|
+
return {
|
|
58
|
+
cssClass: 'jp-mod-added',
|
|
59
|
+
prefix: '+',
|
|
60
|
+
text,
|
|
61
|
+
key
|
|
62
|
+
};
|
|
63
|
+
case 'removed':
|
|
64
|
+
return {
|
|
65
|
+
cssClass: 'jp-mod-removed',
|
|
66
|
+
prefix: '-',
|
|
67
|
+
text,
|
|
68
|
+
key
|
|
69
|
+
};
|
|
70
|
+
case 'context':
|
|
71
|
+
return {
|
|
72
|
+
cssClass: 'jp-mod-context',
|
|
73
|
+
prefix: ' ',
|
|
74
|
+
text,
|
|
75
|
+
key
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function buildDiffLinesFromHunk(hunk, hunkIndex) {
|
|
80
|
+
return hunk.lines
|
|
81
|
+
.filter(line => !line.startsWith('\\'))
|
|
82
|
+
.map((line, lineIndex) => {
|
|
83
|
+
var _a;
|
|
84
|
+
const prefix = (_a = line[0]) !== null && _a !== void 0 ? _a : ' ';
|
|
85
|
+
const text = line.slice(1);
|
|
86
|
+
const key = `${hunkIndex}-${hunk.oldStart}-${hunk.newStart}-${lineIndex}`;
|
|
87
|
+
if (prefix === '+') {
|
|
88
|
+
return toLineInfo('added', text, key);
|
|
89
|
+
}
|
|
90
|
+
if (prefix === '-') {
|
|
91
|
+
return toLineInfo('removed', text, key);
|
|
92
|
+
}
|
|
93
|
+
return toLineInfo('context', text, key);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
function buildDiffLines(diff) {
|
|
97
|
+
var _a;
|
|
98
|
+
const patchPath = getInlineDiffPatchPath(diff);
|
|
99
|
+
const patch = structuredPatch(patchPath, patchPath, (_a = diff.oldText) !== null && _a !== void 0 ? _a : '', diff.newText, undefined, undefined, { context: Infinity });
|
|
100
|
+
return patch.hunks.reduce((lines, hunk, index) => {
|
|
101
|
+
lines.push(...buildDiffLinesFromHunk(hunk, index));
|
|
102
|
+
return lines;
|
|
103
|
+
}, []);
|
|
104
|
+
}
|
|
105
|
+
function DiffBlock({ diff, trans }) {
|
|
106
|
+
const filename = getInlineDiffLabel(diff);
|
|
107
|
+
const title = getInlineDiffTitle(diff);
|
|
108
|
+
const [expanded, setExpanded] = React.useState(false);
|
|
109
|
+
const allLines = React.useMemo(() => buildDiffLines(diff), [diff]);
|
|
110
|
+
const canTruncate = allLines.length > MAX_DIFF_LINES;
|
|
111
|
+
const visibleLines = canTruncate && !expanded ? allLines.slice(0, MAX_DIFF_LINES) : allLines;
|
|
112
|
+
const hiddenCount = allLines.length - MAX_DIFF_LINES;
|
|
113
|
+
return (React.createElement("div", { className: "jp-ai-inline-diff-block" },
|
|
114
|
+
React.createElement("div", { className: "jp-ai-inline-diff-header", title: title }, filename),
|
|
115
|
+
React.createElement("div", { className: "jp-ai-inline-diff-content" },
|
|
116
|
+
visibleLines.length ? (visibleLines.map(line => (React.createElement("div", { key: line.key, className: `jp-ai-inline-diff-line ${line.cssClass}` },
|
|
117
|
+
React.createElement("span", { className: "jp-ai-inline-diff-line-prefix" }, line.prefix),
|
|
118
|
+
React.createElement("span", { className: "jp-ai-inline-diff-line-text" }, line.text))))) : (React.createElement("div", { className: "jp-ai-inline-diff-empty" }, trans.__('No changes'))),
|
|
119
|
+
canTruncate && !expanded && (React.createElement("button", { className: "jp-ai-inline-diff-toggle", onClick: () => setExpanded(true), type: "button" }, trans.__('... %1 more lines', hiddenCount))),
|
|
120
|
+
canTruncate && expanded && (React.createElement("button", { className: "jp-ai-inline-diff-toggle", onClick: () => setExpanded(false), type: "button" }, trans.__('Show less'))))));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* React component for rendering one or more inline diffs.
|
|
124
|
+
*/
|
|
125
|
+
export const InlineDiff = ({ diffs, trans }) => {
|
|
126
|
+
const transBundle = trans !== null && trans !== void 0 ? trans : nullTranslator.load('jupyterlab');
|
|
127
|
+
return (React.createElement("div", { className: "jp-ai-inline-diff-container" }, diffs.map((diff, index) => (React.createElement(DiffBlock, { key: `${getInlineDiffPatchPath(diff)}-${index}`, diff: diff, trans: transBundle })))));
|
|
128
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { IComponentProps, IMessageQueueMetadata, RemoveQueuedMessage } from '../token';
|
|
3
|
+
/**
|
|
4
|
+
* Props for the MessageQueue component.
|
|
5
|
+
*/
|
|
6
|
+
export interface IMessageQueueProps extends IComponentProps, IMessageQueueMetadata {
|
|
7
|
+
removeQueuedMessage?: RemoveQueuedMessage;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* React component that displays a list of queued messages by
|
|
11
|
+
* showing each pending message as a bubble in the chat
|
|
12
|
+
*/
|
|
13
|
+
export declare const MessageQueue: React.FC<IMessageQueueProps>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* React component that displays a list of queued messages by
|
|
4
|
+
* showing each pending message as a bubble in the chat
|
|
5
|
+
*/
|
|
6
|
+
export const MessageQueue = ({ messages, targetId, trans, removeQueuedMessage }) => {
|
|
7
|
+
if (!messages || messages.length === 0) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
return (React.createElement("div", { className: "jp-chat-message-queue" }, messages.map(msg => (React.createElement("div", { key: msg.id, className: "jp-chat-message-queue-bubble" },
|
|
11
|
+
React.createElement("span", { className: "jp-chat-message-queue-text" }, msg.body),
|
|
12
|
+
removeQueuedMessage && targetId && (React.createElement("button", { className: "jp-chat-message-queue-remove", onClick: () => removeQueuedMessage(targetId, msg.id), title: trans.__('Remove from queue'), type: "button" }, "\u2715")))))));
|
|
13
|
+
};
|
|
@@ -1,19 +1,28 @@
|
|
|
1
|
-
import { TranslationBundle } from '@jupyterlab/translation';
|
|
2
1
|
import * as React from 'react';
|
|
3
|
-
import {
|
|
2
|
+
import { IComponentProps, ToolCallApproval } from '../token';
|
|
3
|
+
/**
|
|
4
|
+
* Tool call status types.
|
|
5
|
+
*/
|
|
6
|
+
export type ToolCallStatus = 'pending' | 'awaiting_approval' | 'approved' | 'rejected' | 'completed' | 'error';
|
|
4
7
|
/**
|
|
5
8
|
* Options for building tool call HTML.
|
|
6
9
|
*/
|
|
7
|
-
export interface
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
export interface IToolCallMetadata {
|
|
11
|
+
toolName: string;
|
|
12
|
+
input: string;
|
|
13
|
+
status: ToolCallStatus;
|
|
14
|
+
summary?: string;
|
|
15
|
+
output?: string;
|
|
16
|
+
targetId?: string;
|
|
17
|
+
approvalId?: string;
|
|
10
18
|
}
|
|
11
|
-
export declare function escapeHtml(value: string): string;
|
|
12
19
|
/**
|
|
13
|
-
*
|
|
20
|
+
* Options for building tool call HTML.
|
|
14
21
|
*/
|
|
15
|
-
export interface IToolCallProps extends
|
|
22
|
+
export interface IToolCallProps extends IComponentProps, IToolCallMetadata {
|
|
23
|
+
toolCallApproval?: ToolCallApproval;
|
|
16
24
|
}
|
|
25
|
+
export declare function escapeHtml(value: string): string;
|
|
17
26
|
/**
|
|
18
27
|
* React functional component for displaying a tool call.
|
|
19
28
|
*
|
|
@@ -71,6 +71,9 @@ export const ToolCall = ({ toolName, input, status, summary, output, targetId, a
|
|
|
71
71
|
const config = STATUS_CONFIG[status];
|
|
72
72
|
const statusText = getStatusText(status, trans);
|
|
73
73
|
const resultLabel = status === 'error' ? trans.__('Error') : trans.__('Result');
|
|
74
|
+
if (status === 'awaiting_approval' && !toolCallApproval) {
|
|
75
|
+
console.error('The tool call has no approval function, approval, it will not work as expected');
|
|
76
|
+
}
|
|
74
77
|
return (React.createElement("details", { className: `jp-ai-tool-call ${config.cssClass}`, open: config.open },
|
|
75
78
|
React.createElement("summary", { className: "jp-ai-tool-header" },
|
|
76
79
|
React.createElement("div", { className: "jp-ai-tool-icon" }, "\u26A1"),
|
package/lib/factory.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
|
|
2
2
|
import { ReactWidget } from '@jupyterlab/ui-components';
|
|
3
3
|
import * as React from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { ComponentRegistry } from './registry';
|
|
5
|
+
import { IComponentRegistry, IComponentsRendererFactory, RemoveQueuedMessage, ToolCallApproval } from './token';
|
|
5
6
|
type ReactRenderElement = Array<React.ReactElement<any>> | React.ReactElement<any>;
|
|
6
7
|
/**
|
|
7
8
|
* The options for the chat components renderer.
|
|
@@ -10,10 +11,18 @@ interface IComponentsRendererOptions extends IRenderMime.IRendererOptions {
|
|
|
10
11
|
/**
|
|
11
12
|
* The callback to approve or reject a tool.
|
|
12
13
|
*/
|
|
13
|
-
toolCallApproval
|
|
14
|
+
toolCallApproval?: ToolCallApproval;
|
|
15
|
+
/**
|
|
16
|
+
* The callback to remove a queued message.
|
|
17
|
+
*/
|
|
18
|
+
removeQueuedMessage?: RemoveQueuedMessage;
|
|
19
|
+
/**
|
|
20
|
+
* The component registry.
|
|
21
|
+
*/
|
|
22
|
+
registry: IComponentRegistry;
|
|
14
23
|
}
|
|
15
24
|
/**
|
|
16
|
-
* A widget for rendering .
|
|
25
|
+
* A widget for rendering components from mime bundle.
|
|
17
26
|
*/
|
|
18
27
|
export declare class ComponentsRenderer extends ReactWidget implements IRenderMime.IRenderer {
|
|
19
28
|
/**
|
|
@@ -27,7 +36,9 @@ export declare class ComponentsRenderer extends ReactWidget implements IRenderMi
|
|
|
27
36
|
protected render(): ReactRenderElement | null;
|
|
28
37
|
private _trans;
|
|
29
38
|
private _mimeType;
|
|
30
|
-
private _toolCallApproval
|
|
39
|
+
private _toolCallApproval?;
|
|
40
|
+
private _removeQueuedMessage?;
|
|
41
|
+
private _registry;
|
|
31
42
|
private _data;
|
|
32
43
|
private _metadata;
|
|
33
44
|
}
|
|
@@ -38,7 +49,10 @@ export declare class RendererFactory implements IComponentsRendererFactory {
|
|
|
38
49
|
readonly safe = true;
|
|
39
50
|
readonly mimeTypes: string[];
|
|
40
51
|
readonly defaultRank = 100;
|
|
52
|
+
readonly registry: ComponentRegistry;
|
|
41
53
|
toolCallApproval: ToolCallApproval;
|
|
54
|
+
removeQueuedMessage: RemoveQueuedMessage;
|
|
55
|
+
constructor();
|
|
42
56
|
createRenderer: (options: IRenderMime.IRendererOptions) => ComponentsRenderer;
|
|
43
57
|
}
|
|
44
58
|
export {};
|
package/lib/factory.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { nullTranslator } from '@jupyterlab/translation';
|
|
2
2
|
import { ReactWidget } from '@jupyterlab/ui-components';
|
|
3
3
|
import * as React from 'react';
|
|
4
|
-
import { ToolCall } from './components
|
|
4
|
+
import { InlineDiff, MessageQueue, ToolCall } from './components';
|
|
5
|
+
import { ComponentRegistry } from './registry';
|
|
5
6
|
/**
|
|
6
7
|
* The default mime type for the extension.
|
|
7
8
|
*/
|
|
@@ -11,7 +12,7 @@ const MIME_TYPE = 'application/vnd.jupyter.chat.components';
|
|
|
11
12
|
*/
|
|
12
13
|
const CLASS_NAME = 'jp-RenderedChatComponents';
|
|
13
14
|
/**
|
|
14
|
-
* A widget for rendering .
|
|
15
|
+
* A widget for rendering components from mime bundle.
|
|
15
16
|
*/
|
|
16
17
|
export class ComponentsRenderer extends ReactWidget {
|
|
17
18
|
/**
|
|
@@ -25,26 +26,38 @@ export class ComponentsRenderer extends ReactWidget {
|
|
|
25
26
|
this._trans = ((_a = options.translator) !== null && _a !== void 0 ? _a : nullTranslator).load('jupyterlab');
|
|
26
27
|
this._mimeType = options.mimeType;
|
|
27
28
|
this._toolCallApproval = options.toolCallApproval;
|
|
29
|
+
this._removeQueuedMessage = options.removeQueuedMessage;
|
|
30
|
+
this._registry = options.registry;
|
|
28
31
|
this.addClass(CLASS_NAME);
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
31
34
|
* Render into this widget's node.
|
|
32
35
|
*/
|
|
33
36
|
async renderModel(model) {
|
|
37
|
+
var _a;
|
|
34
38
|
this._data = model.data[this._mimeType];
|
|
35
|
-
|
|
39
|
+
const metadata = model.metadata;
|
|
40
|
+
this._metadata = (_a = metadata[this._mimeType]) !== null && _a !== void 0 ? _a : {
|
|
41
|
+
...metadata
|
|
42
|
+
};
|
|
36
43
|
return this.update();
|
|
37
44
|
}
|
|
38
45
|
render() {
|
|
46
|
+
if (!this._data) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const Component = this._registry.get(this._data);
|
|
50
|
+
if (!Component) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const componentsProps = { ...this._metadata };
|
|
39
54
|
if (this._data === 'tool-call') {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
};
|
|
45
|
-
return React.createElement(ToolCall, { ...toolCallOptions });
|
|
55
|
+
componentsProps.toolCallApproval = this._toolCallApproval;
|
|
56
|
+
}
|
|
57
|
+
if (this._data === 'message-queue') {
|
|
58
|
+
componentsProps.removeQueuedMessage = this._removeQueuedMessage;
|
|
46
59
|
}
|
|
47
|
-
return
|
|
60
|
+
return React.createElement(Component, { ...componentsProps, trans: this._trans });
|
|
48
61
|
}
|
|
49
62
|
}
|
|
50
63
|
/**
|
|
@@ -56,11 +69,18 @@ export class RendererFactory {
|
|
|
56
69
|
this.mimeTypes = [MIME_TYPE];
|
|
57
70
|
this.defaultRank = 100;
|
|
58
71
|
this.toolCallApproval = null;
|
|
72
|
+
this.removeQueuedMessage = null;
|
|
59
73
|
this.createRenderer = (options) => {
|
|
60
74
|
return new ComponentsRenderer({
|
|
61
75
|
...options,
|
|
62
|
-
toolCallApproval: this.toolCallApproval
|
|
76
|
+
toolCallApproval: this.toolCallApproval,
|
|
77
|
+
removeQueuedMessage: this.removeQueuedMessage,
|
|
78
|
+
registry: this.registry
|
|
63
79
|
});
|
|
64
80
|
};
|
|
81
|
+
this.registry = new ComponentRegistry();
|
|
82
|
+
this.registry.add('tool-call', ToolCall);
|
|
83
|
+
this.registry.add('inline-diff', InlineDiff);
|
|
84
|
+
this.registry.add('message-queue', MessageQueue);
|
|
65
85
|
}
|
|
66
86
|
}
|
package/lib/index.d.ts
CHANGED
|
@@ -6,4 +6,6 @@ import { IComponentsRendererFactory } from './token';
|
|
|
6
6
|
declare const factory: JupyterFrontEndPlugin<IComponentsRendererFactory>;
|
|
7
7
|
export * from './token';
|
|
8
8
|
export * from './factory';
|
|
9
|
+
export * from './registry';
|
|
10
|
+
export * from './components';
|
|
9
11
|
export default factory;
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
2
|
-
import { IComponentsRendererFactory } from './token';
|
|
3
2
|
import { RendererFactory } from './factory';
|
|
3
|
+
import { IComponentsRendererFactory } from './token';
|
|
4
4
|
/**
|
|
5
5
|
* The plugin providing the chat component renderer.
|
|
6
6
|
*/
|
|
@@ -18,4 +18,6 @@ const factory = {
|
|
|
18
18
|
};
|
|
19
19
|
export * from './token';
|
|
20
20
|
export * from './factory';
|
|
21
|
+
export * from './registry';
|
|
22
|
+
export * from './components';
|
|
21
23
|
export default factory;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { IComponentRegistry } from './token';
|
|
3
|
+
/**
|
|
4
|
+
* A registry for React components.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ComponentRegistry implements IComponentRegistry {
|
|
7
|
+
/**
|
|
8
|
+
* Register a React component.
|
|
9
|
+
*
|
|
10
|
+
* @param name - The unique name/identifier for the component
|
|
11
|
+
* @param component - The React component
|
|
12
|
+
*/
|
|
13
|
+
add(name: string, component: React.ComponentType<any>): void;
|
|
14
|
+
/**
|
|
15
|
+
* Get a registered component by name.
|
|
16
|
+
*
|
|
17
|
+
* @param name - The name of the component
|
|
18
|
+
* @returns the React component, or undefined if not found
|
|
19
|
+
*/
|
|
20
|
+
get(name: string): React.ComponentType<any> | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Check if a component is registered.
|
|
23
|
+
*
|
|
24
|
+
* @param name - The name of the component
|
|
25
|
+
* @returns whether the component is registered
|
|
26
|
+
*/
|
|
27
|
+
has(name: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Get all registered component names.
|
|
30
|
+
*
|
|
31
|
+
* @returns the component names
|
|
32
|
+
*/
|
|
33
|
+
getNames(): string[];
|
|
34
|
+
private _components;
|
|
35
|
+
}
|
package/lib/registry.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A registry for React components.
|
|
3
|
+
*/
|
|
4
|
+
export class ComponentRegistry {
|
|
5
|
+
constructor() {
|
|
6
|
+
this._components = new Map();
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Register a React component.
|
|
10
|
+
*
|
|
11
|
+
* @param name - The unique name/identifier for the component
|
|
12
|
+
* @param component - The React component
|
|
13
|
+
*/
|
|
14
|
+
add(name, component) {
|
|
15
|
+
if (this._components.has(name)) {
|
|
16
|
+
console.warn(`Component '${name}' is already registered and will be overwritten.`);
|
|
17
|
+
}
|
|
18
|
+
this._components.set(name, component);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get a registered component by name.
|
|
22
|
+
*
|
|
23
|
+
* @param name - The name of the component
|
|
24
|
+
* @returns the React component, or undefined if not found
|
|
25
|
+
*/
|
|
26
|
+
get(name) {
|
|
27
|
+
return this._components.get(name);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check if a component is registered.
|
|
31
|
+
*
|
|
32
|
+
* @param name - The name of the component
|
|
33
|
+
* @returns whether the component is registered
|
|
34
|
+
*/
|
|
35
|
+
has(name) {
|
|
36
|
+
return this._components.has(name);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get all registered component names.
|
|
40
|
+
*
|
|
41
|
+
* @returns the component names
|
|
42
|
+
*/
|
|
43
|
+
getNames() {
|
|
44
|
+
return Array.from(this._components.keys());
|
|
45
|
+
}
|
|
46
|
+
}
|
package/lib/token.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
|
|
2
|
+
import { TranslationBundle } from '@jupyterlab/translation';
|
|
2
3
|
import { Token } from '@lumino/coreutils';
|
|
4
|
+
import * as React from 'react';
|
|
3
5
|
/**
|
|
4
6
|
* The token providing the chat components renderer.
|
|
5
7
|
*/
|
|
@@ -8,28 +10,147 @@ export declare const IComponentsRendererFactory: Token<IComponentsRendererFactor
|
|
|
8
10
|
* The callback to approve or reject a tool.
|
|
9
11
|
*/
|
|
10
12
|
export type ToolCallApproval = ((targetId: string, approvalId: string, approve: boolean) => void) | null;
|
|
13
|
+
/**
|
|
14
|
+
* The callback to remove a queued message.
|
|
15
|
+
*/
|
|
16
|
+
export type RemoveQueuedMessage = ((targetId: string, messageId: string) => void) | null;
|
|
11
17
|
/**
|
|
12
18
|
* The interface for components renderer factory.
|
|
13
19
|
*/
|
|
14
20
|
export interface IComponentsRendererFactory extends IRenderMime.IRendererFactory {
|
|
21
|
+
/**
|
|
22
|
+
* The registry of React components available for rendering.
|
|
23
|
+
*/
|
|
24
|
+
registry: IComponentRegistry;
|
|
15
25
|
/**
|
|
16
26
|
* The callback to approve or reject a tool.
|
|
17
27
|
*/
|
|
18
28
|
toolCallApproval: ToolCallApproval;
|
|
29
|
+
/**
|
|
30
|
+
* The callback to remove a queued message.
|
|
31
|
+
*/
|
|
32
|
+
removeQueuedMessage: RemoveQueuedMessage;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The interface for the component registry.
|
|
36
|
+
*/
|
|
37
|
+
export interface IComponentRegistry {
|
|
38
|
+
/**
|
|
39
|
+
* Register a React component.
|
|
40
|
+
*
|
|
41
|
+
* @param name - The unique name/identifier for the component
|
|
42
|
+
* @param component - The React component
|
|
43
|
+
*/
|
|
44
|
+
add(name: string, component: React.ComponentType<any>): void;
|
|
45
|
+
/**
|
|
46
|
+
* Get a registered component by name.
|
|
47
|
+
*
|
|
48
|
+
* @param name - The name of the component
|
|
49
|
+
* @returns The React component, or undefined if not found
|
|
50
|
+
*/
|
|
51
|
+
get(name: string): React.ComponentType<any> | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Check if a component is registered.
|
|
54
|
+
*
|
|
55
|
+
* @param name - The name of the component
|
|
56
|
+
* @returns True if the component is registered
|
|
57
|
+
*/
|
|
58
|
+
has(name: string): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Get all registered component names.
|
|
61
|
+
*
|
|
62
|
+
* @returns Array of component names
|
|
63
|
+
*/
|
|
64
|
+
getNames(): string[];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The minimal required properties for the component.
|
|
68
|
+
*/
|
|
69
|
+
export interface IComponentProps {
|
|
70
|
+
/**
|
|
71
|
+
* The translation bundle.
|
|
72
|
+
*/
|
|
73
|
+
trans: TranslationBundle;
|
|
19
74
|
}
|
|
20
75
|
/**
|
|
21
|
-
*
|
|
76
|
+
* A file diff target.
|
|
22
77
|
*/
|
|
23
|
-
export
|
|
78
|
+
export interface IInlineDiffFileTarget {
|
|
79
|
+
/**
|
|
80
|
+
* Discriminator for a regular file diff target.
|
|
81
|
+
*/
|
|
82
|
+
kind: 'file';
|
|
83
|
+
/**
|
|
84
|
+
* Path of the file being diffed.
|
|
85
|
+
*/
|
|
86
|
+
path: string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* A notebook cell diff target.
|
|
90
|
+
*/
|
|
91
|
+
export interface IInlineDiffNotebookCellTarget {
|
|
92
|
+
/**
|
|
93
|
+
* Discriminator for a notebook cell source diff target.
|
|
94
|
+
*/
|
|
95
|
+
kind: 'cell';
|
|
96
|
+
/**
|
|
97
|
+
* Path of the notebook containing the cell.
|
|
98
|
+
*/
|
|
99
|
+
notebookPath: string;
|
|
100
|
+
/**
|
|
101
|
+
* Stable cell identifier, when available from the producer.
|
|
102
|
+
*/
|
|
103
|
+
cellId?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Zero-based notebook cell index used for the default display label.
|
|
106
|
+
*/
|
|
107
|
+
cellIndex?: number;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* A supported inline diff target.
|
|
111
|
+
*/
|
|
112
|
+
export type IInlineDiffTarget = IInlineDiffFileTarget | IInlineDiffNotebookCellTarget;
|
|
113
|
+
/**
|
|
114
|
+
* A single inline diff entry.
|
|
115
|
+
*/
|
|
116
|
+
export interface IInlineDiff {
|
|
117
|
+
/**
|
|
118
|
+
* Structured target metadata.
|
|
119
|
+
*/
|
|
120
|
+
target: IInlineDiffTarget;
|
|
121
|
+
/**
|
|
122
|
+
* Optional explicit label for the diff header.
|
|
123
|
+
*/
|
|
124
|
+
label?: string;
|
|
125
|
+
/**
|
|
126
|
+
* Updated text content for the diff target.
|
|
127
|
+
*/
|
|
128
|
+
newText: string;
|
|
129
|
+
/**
|
|
130
|
+
* Previous text content for the diff target.
|
|
131
|
+
*/
|
|
132
|
+
oldText?: string;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Metadata for rendering inline diffs.
|
|
136
|
+
*/
|
|
137
|
+
export interface IInlineDiffMetadata {
|
|
138
|
+
/**
|
|
139
|
+
* List of inline diff entries to render.
|
|
140
|
+
*/
|
|
141
|
+
diffs: IInlineDiff[];
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* A single queued message entry.
|
|
145
|
+
*/
|
|
146
|
+
export interface IQueuedMessage {
|
|
147
|
+
id: string;
|
|
148
|
+
body: string;
|
|
149
|
+
}
|
|
24
150
|
/**
|
|
25
|
-
*
|
|
151
|
+
* Metadata for the message queue component.
|
|
26
152
|
*/
|
|
27
|
-
export interface
|
|
28
|
-
|
|
29
|
-
input: string;
|
|
30
|
-
status: ToolCallStatus;
|
|
31
|
-
summary?: string;
|
|
32
|
-
output?: string;
|
|
153
|
+
export interface IMessageQueueMetadata {
|
|
154
|
+
messages: IQueuedMessage[];
|
|
33
155
|
targetId?: string;
|
|
34
|
-
approvalId?: string;
|
|
35
156
|
}
|