jupyter-chat-components 0.1.2 → 0.2.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/__tests__/jupyter_ai_chat_components.spec.d.ts +3 -0
- package/lib/__tests__/jupyter_ai_chat_components.spec.js +9 -0
- package/lib/components/index.d.ts +2 -0
- package/lib/components/index.js +2 -0
- package/lib/components/inline-diff.d.ts +11 -0
- package/lib/components/inline-diff.js +82 -0
- package/lib/components/tool-call.d.ts +32 -0
- package/lib/components/tool-call.js +96 -0
- package/lib/factory.d.ts +52 -0
- package/lib/factory.js +79 -0
- package/lib/index.d.ts +7 -28
- package/lib/index.js +8 -61
- package/lib/registry.d.ts +35 -0
- package/lib/registry.js +46 -0
- package/lib/token.d.ts +56 -11
- package/package.json +4 -2
- package/src/components/index.ts +2 -0
- package/src/components/inline-diff.tsx +176 -0
- package/src/components/tool-call.tsx +200 -0
- package/src/factory.tsx +130 -0
- package/src/index.ts +10 -89
- package/src/registry.ts +54 -0
- package/src/token.ts +64 -17
- package/style/base.css +122 -4
- package/lib/tool-call.d.ts +0 -14
- package/lib/tool-call.js +0 -152
- package/src/tool-call.ts +0 -224
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,11 @@
|
|
|
1
|
+
import { TranslationBundle } from '@jupyterlab/translation';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { IInlineDiffMetadata } from '../token';
|
|
4
|
+
export interface IInlineDiffProps extends IInlineDiffMetadata {
|
|
5
|
+
trans?: TranslationBundle;
|
|
6
|
+
}
|
|
7
|
+
export declare function getDiffFilename(path: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* React component for rendering one or more inline file diffs.
|
|
10
|
+
*/
|
|
11
|
+
export declare const InlineDiff: React.FC<IInlineDiffProps>;
|
|
@@ -0,0 +1,82 @@
|
|
|
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 toLineInfo(type, text, key) {
|
|
11
|
+
switch (type) {
|
|
12
|
+
case 'added':
|
|
13
|
+
return {
|
|
14
|
+
cssClass: 'jp-mod-added',
|
|
15
|
+
prefix: '+',
|
|
16
|
+
text,
|
|
17
|
+
key
|
|
18
|
+
};
|
|
19
|
+
case 'removed':
|
|
20
|
+
return {
|
|
21
|
+
cssClass: 'jp-mod-removed',
|
|
22
|
+
prefix: '-',
|
|
23
|
+
text,
|
|
24
|
+
key
|
|
25
|
+
};
|
|
26
|
+
case 'context':
|
|
27
|
+
return {
|
|
28
|
+
cssClass: 'jp-mod-context',
|
|
29
|
+
prefix: ' ',
|
|
30
|
+
text,
|
|
31
|
+
key
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function buildDiffLinesFromHunk(hunk, hunkIndex) {
|
|
36
|
+
return hunk.lines
|
|
37
|
+
.filter(line => !line.startsWith('\\'))
|
|
38
|
+
.map((line, lineIndex) => {
|
|
39
|
+
var _a;
|
|
40
|
+
const prefix = (_a = line[0]) !== null && _a !== void 0 ? _a : ' ';
|
|
41
|
+
const text = line.slice(1);
|
|
42
|
+
const key = `${hunkIndex}-${hunk.oldStart}-${hunk.newStart}-${lineIndex}`;
|
|
43
|
+
if (prefix === '+') {
|
|
44
|
+
return toLineInfo('added', text, key);
|
|
45
|
+
}
|
|
46
|
+
if (prefix === '-') {
|
|
47
|
+
return toLineInfo('removed', text, key);
|
|
48
|
+
}
|
|
49
|
+
return toLineInfo('context', text, key);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
function buildDiffLines(diff) {
|
|
53
|
+
var _a;
|
|
54
|
+
const patch = structuredPatch(diff.path, diff.path, (_a = diff.oldText) !== null && _a !== void 0 ? _a : '', diff.newText, undefined, undefined, { context: Infinity });
|
|
55
|
+
return patch.hunks.reduce((lines, hunk, index) => {
|
|
56
|
+
lines.push(...buildDiffLinesFromHunk(hunk, index));
|
|
57
|
+
return lines;
|
|
58
|
+
}, []);
|
|
59
|
+
}
|
|
60
|
+
function DiffBlock({ diff, trans }) {
|
|
61
|
+
const filename = getDiffFilename(diff.path);
|
|
62
|
+
const [expanded, setExpanded] = React.useState(false);
|
|
63
|
+
const allLines = React.useMemo(() => buildDiffLines(diff), [diff]);
|
|
64
|
+
const canTruncate = allLines.length > MAX_DIFF_LINES;
|
|
65
|
+
const visibleLines = canTruncate && !expanded ? allLines.slice(0, MAX_DIFF_LINES) : allLines;
|
|
66
|
+
const hiddenCount = allLines.length - MAX_DIFF_LINES;
|
|
67
|
+
return (React.createElement("div", { className: "jp-ai-inline-diff-block" },
|
|
68
|
+
React.createElement("div", { className: "jp-ai-inline-diff-header", title: diff.path }, filename),
|
|
69
|
+
React.createElement("div", { className: "jp-ai-inline-diff-content" },
|
|
70
|
+
visibleLines.length ? (visibleLines.map(line => (React.createElement("div", { key: line.key, className: `jp-ai-inline-diff-line ${line.cssClass}` },
|
|
71
|
+
React.createElement("span", { className: "jp-ai-inline-diff-line-prefix" }, line.prefix),
|
|
72
|
+
React.createElement("span", { className: "jp-ai-inline-diff-line-text" }, line.text))))) : (React.createElement("div", { className: "jp-ai-inline-diff-empty" }, trans.__('No changes'))),
|
|
73
|
+
canTruncate && !expanded && (React.createElement("button", { className: "jp-ai-inline-diff-toggle", onClick: () => setExpanded(true), type: "button" }, trans.__('... %1 more lines', hiddenCount))),
|
|
74
|
+
canTruncate && expanded && (React.createElement("button", { className: "jp-ai-inline-diff-toggle", onClick: () => setExpanded(false), type: "button" }, trans.__('Show less'))))));
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* React component for rendering one or more inline file diffs.
|
|
78
|
+
*/
|
|
79
|
+
export const InlineDiff = ({ diffs, trans }) => {
|
|
80
|
+
const transBundle = trans !== null && trans !== void 0 ? trans : nullTranslator.load('jupyterlab');
|
|
81
|
+
return (React.createElement("div", { className: "jp-ai-inline-diff-container" }, diffs.map((diff, index) => (React.createElement(DiffBlock, { key: `${diff.path}-${index}`, diff: diff, trans: transBundle })))));
|
|
82
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { IComponentProps, ToolCallApproval } from '../token';
|
|
3
|
+
/**
|
|
4
|
+
* Tool call status types.
|
|
5
|
+
*/
|
|
6
|
+
export type ToolCallStatus = 'pending' | 'awaiting_approval' | 'approved' | 'rejected' | 'completed' | 'error';
|
|
7
|
+
/**
|
|
8
|
+
* Options for building tool call HTML.
|
|
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;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Options for building tool call HTML.
|
|
21
|
+
*/
|
|
22
|
+
export interface IToolCallProps extends IComponentProps, IToolCallMetadata {
|
|
23
|
+
toolCallApproval?: ToolCallApproval;
|
|
24
|
+
}
|
|
25
|
+
export declare function escapeHtml(value: string): string;
|
|
26
|
+
/**
|
|
27
|
+
* React functional component for displaying a tool call.
|
|
28
|
+
*
|
|
29
|
+
* Renders a collapsible details element showing tool execution information
|
|
30
|
+
* including input, output, and approval buttons if needed.
|
|
31
|
+
*/
|
|
32
|
+
export declare const ToolCall: React.FC<IToolCallProps>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
const STATUS_CONFIG = {
|
|
3
|
+
pending: {
|
|
4
|
+
cssClass: 'jp-ai-tool-pending',
|
|
5
|
+
statusClass: 'jp-ai-tool-status-pending'
|
|
6
|
+
},
|
|
7
|
+
awaiting_approval: {
|
|
8
|
+
cssClass: 'jp-ai-tool-pending',
|
|
9
|
+
statusClass: 'jp-ai-tool-status-approval',
|
|
10
|
+
open: true
|
|
11
|
+
},
|
|
12
|
+
approved: {
|
|
13
|
+
cssClass: 'jp-ai-tool-pending',
|
|
14
|
+
statusClass: 'jp-ai-tool-status-completed'
|
|
15
|
+
},
|
|
16
|
+
rejected: {
|
|
17
|
+
cssClass: 'jp-ai-tool-error',
|
|
18
|
+
statusClass: 'jp-ai-tool-status-error'
|
|
19
|
+
},
|
|
20
|
+
completed: {
|
|
21
|
+
cssClass: 'jp-ai-tool-completed',
|
|
22
|
+
statusClass: 'jp-ai-tool-status-completed'
|
|
23
|
+
},
|
|
24
|
+
error: {
|
|
25
|
+
cssClass: 'jp-ai-tool-error',
|
|
26
|
+
statusClass: 'jp-ai-tool-status-error'
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
export function escapeHtml(value) {
|
|
30
|
+
// Prefer the same native escaping approach used in JupyterLab itself
|
|
31
|
+
// (e.g. `@jupyterlab/completer`).
|
|
32
|
+
if (typeof document !== 'undefined') {
|
|
33
|
+
const node = document.createElement('span');
|
|
34
|
+
node.textContent = value;
|
|
35
|
+
return node.innerHTML;
|
|
36
|
+
}
|
|
37
|
+
// Fallback
|
|
38
|
+
return value
|
|
39
|
+
.replace(/&/g, '&')
|
|
40
|
+
.replace(/</g, '<')
|
|
41
|
+
.replace(/>/g, '>')
|
|
42
|
+
.replace(/"/g, '"')
|
|
43
|
+
.replace(/'/g, ''');
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Returns the translated status text for a given tool status.
|
|
47
|
+
*/
|
|
48
|
+
const getStatusText = (status, trans) => {
|
|
49
|
+
switch (status) {
|
|
50
|
+
case 'pending':
|
|
51
|
+
return trans.__('Running...');
|
|
52
|
+
case 'awaiting_approval':
|
|
53
|
+
return trans.__('Awaiting Approval');
|
|
54
|
+
case 'approved':
|
|
55
|
+
return trans.__('Approved - Executing...');
|
|
56
|
+
case 'rejected':
|
|
57
|
+
return trans.__('Rejected');
|
|
58
|
+
case 'completed':
|
|
59
|
+
return trans.__('Completed');
|
|
60
|
+
case 'error':
|
|
61
|
+
return trans.__('Error');
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* React functional component for displaying a tool call.
|
|
66
|
+
*
|
|
67
|
+
* Renders a collapsible details element showing tool execution information
|
|
68
|
+
* including input, output, and approval buttons if needed.
|
|
69
|
+
*/
|
|
70
|
+
export const ToolCall = ({ toolName, input, status, summary, output, targetId, approvalId, trans, toolCallApproval }) => {
|
|
71
|
+
const config = STATUS_CONFIG[status];
|
|
72
|
+
const statusText = getStatusText(status, trans);
|
|
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
|
+
}
|
|
77
|
+
return (React.createElement("details", { className: `jp-ai-tool-call ${config.cssClass}`, open: config.open },
|
|
78
|
+
React.createElement("summary", { className: "jp-ai-tool-header" },
|
|
79
|
+
React.createElement("div", { className: "jp-ai-tool-icon" }, "\u26A1"),
|
|
80
|
+
React.createElement("div", { className: "jp-ai-tool-title" },
|
|
81
|
+
toolName,
|
|
82
|
+
summary && React.createElement("span", { className: "jp-ai-tool-summary" }, summary)),
|
|
83
|
+
React.createElement("div", { className: `jp-ai-tool-status ${config.statusClass}` }, statusText)),
|
|
84
|
+
React.createElement("div", { className: "jp-ai-tool-body" },
|
|
85
|
+
React.createElement("div", { className: "jp-ai-tool-section" },
|
|
86
|
+
React.createElement("div", { className: "jp-ai-tool-label" }, trans.__('Input')),
|
|
87
|
+
React.createElement("pre", { className: "jp-ai-tool-code" },
|
|
88
|
+
React.createElement("code", null, input))),
|
|
89
|
+
status === 'awaiting_approval' && approvalId && targetId && (React.createElement("div", { className: `jp-ai-tool-approval-buttons jp-ai-approval-id--${approvalId}` },
|
|
90
|
+
React.createElement("button", { className: "jp-ai-approval-btn jp-ai-approval-approve", onClick: () => toolCallApproval === null || toolCallApproval === void 0 ? void 0 : toolCallApproval(targetId, approvalId, true) }, trans.__('Approve')),
|
|
91
|
+
React.createElement("button", { className: "jp-ai-approval-btn jp-ai-approval-reject", onClick: () => toolCallApproval === null || toolCallApproval === void 0 ? void 0 : toolCallApproval(targetId, approvalId, false) }, trans.__('Reject')))),
|
|
92
|
+
output !== undefined && (React.createElement("div", { className: "jp-ai-tool-section" },
|
|
93
|
+
React.createElement("div", { className: "jp-ai-tool-label" }, resultLabel),
|
|
94
|
+
React.createElement("pre", { className: "jp-ai-tool-code" },
|
|
95
|
+
React.createElement("code", null, output)))))));
|
|
96
|
+
};
|
package/lib/factory.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
|
|
2
|
+
import { ReactWidget } from '@jupyterlab/ui-components';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { ComponentRegistry } from './registry';
|
|
5
|
+
import { IComponentRegistry, IComponentsRendererFactory, ToolCallApproval } from './token';
|
|
6
|
+
type ReactRenderElement = Array<React.ReactElement<any>> | React.ReactElement<any>;
|
|
7
|
+
/**
|
|
8
|
+
* The options for the chat components renderer.
|
|
9
|
+
*/
|
|
10
|
+
interface IComponentsRendererOptions extends IRenderMime.IRendererOptions {
|
|
11
|
+
/**
|
|
12
|
+
* The callback to approve or reject a tool.
|
|
13
|
+
*/
|
|
14
|
+
toolCallApproval?: ToolCallApproval;
|
|
15
|
+
/**
|
|
16
|
+
* The component registry.
|
|
17
|
+
*/
|
|
18
|
+
registry: IComponentRegistry;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A widget for rendering components from mime bundle.
|
|
22
|
+
*/
|
|
23
|
+
export declare class ComponentsRenderer extends ReactWidget implements IRenderMime.IRenderer {
|
|
24
|
+
/**
|
|
25
|
+
* Construct a new output widget.
|
|
26
|
+
*/
|
|
27
|
+
constructor(options: IComponentsRendererOptions);
|
|
28
|
+
/**
|
|
29
|
+
* Render into this widget's node.
|
|
30
|
+
*/
|
|
31
|
+
renderModel(model: IRenderMime.IMimeModel): Promise<void>;
|
|
32
|
+
protected render(): ReactRenderElement | null;
|
|
33
|
+
private _trans;
|
|
34
|
+
private _mimeType;
|
|
35
|
+
private _toolCallApproval?;
|
|
36
|
+
private _registry;
|
|
37
|
+
private _data;
|
|
38
|
+
private _metadata;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A mime renderer factory for chat components.
|
|
42
|
+
*/
|
|
43
|
+
export declare class RendererFactory implements IComponentsRendererFactory {
|
|
44
|
+
readonly safe = true;
|
|
45
|
+
readonly mimeTypes: string[];
|
|
46
|
+
readonly defaultRank = 100;
|
|
47
|
+
readonly registry: ComponentRegistry;
|
|
48
|
+
toolCallApproval: ToolCallApproval;
|
|
49
|
+
constructor();
|
|
50
|
+
createRenderer: (options: IRenderMime.IRendererOptions) => ComponentsRenderer;
|
|
51
|
+
}
|
|
52
|
+
export {};
|
package/lib/factory.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { nullTranslator } from '@jupyterlab/translation';
|
|
2
|
+
import { ReactWidget } from '@jupyterlab/ui-components';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { InlineDiff, ToolCall } from './components';
|
|
5
|
+
import { ComponentRegistry } from './registry';
|
|
6
|
+
/**
|
|
7
|
+
* The default mime type for the extension.
|
|
8
|
+
*/
|
|
9
|
+
const MIME_TYPE = 'application/vnd.jupyter.chat.components';
|
|
10
|
+
/**
|
|
11
|
+
* The class name added to the extension.
|
|
12
|
+
*/
|
|
13
|
+
const CLASS_NAME = 'jp-RenderedChatComponents';
|
|
14
|
+
/**
|
|
15
|
+
* A widget for rendering components from mime bundle.
|
|
16
|
+
*/
|
|
17
|
+
export class ComponentsRenderer extends ReactWidget {
|
|
18
|
+
/**
|
|
19
|
+
* Construct a new output widget.
|
|
20
|
+
*/
|
|
21
|
+
constructor(options) {
|
|
22
|
+
var _a;
|
|
23
|
+
super();
|
|
24
|
+
this._data = null;
|
|
25
|
+
this._metadata = null;
|
|
26
|
+
this._trans = ((_a = options.translator) !== null && _a !== void 0 ? _a : nullTranslator).load('jupyterlab');
|
|
27
|
+
this._mimeType = options.mimeType;
|
|
28
|
+
this._toolCallApproval = options.toolCallApproval;
|
|
29
|
+
this._registry = options.registry;
|
|
30
|
+
this.addClass(CLASS_NAME);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Render into this widget's node.
|
|
34
|
+
*/
|
|
35
|
+
async renderModel(model) {
|
|
36
|
+
var _a;
|
|
37
|
+
this._data = model.data[this._mimeType];
|
|
38
|
+
const metadata = model.metadata;
|
|
39
|
+
this._metadata = (_a = metadata[this._mimeType]) !== null && _a !== void 0 ? _a : {
|
|
40
|
+
...metadata
|
|
41
|
+
};
|
|
42
|
+
return this.update();
|
|
43
|
+
}
|
|
44
|
+
render() {
|
|
45
|
+
if (!this._data) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const Component = this._registry.get(this._data);
|
|
49
|
+
if (!Component) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const componentsProps = { ...this._metadata };
|
|
53
|
+
if (this._data === 'tool-call') {
|
|
54
|
+
componentsProps.toolCallApproval = this._toolCallApproval;
|
|
55
|
+
}
|
|
56
|
+
return React.createElement(Component, { ...componentsProps, trans: this._trans });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A mime renderer factory for chat components.
|
|
61
|
+
*/
|
|
62
|
+
export class RendererFactory {
|
|
63
|
+
constructor() {
|
|
64
|
+
this.safe = true;
|
|
65
|
+
this.mimeTypes = [MIME_TYPE];
|
|
66
|
+
this.defaultRank = 100;
|
|
67
|
+
this.toolCallApproval = null;
|
|
68
|
+
this.createRenderer = (options) => {
|
|
69
|
+
return new ComponentsRenderer({
|
|
70
|
+
...options,
|
|
71
|
+
toolCallApproval: this.toolCallApproval,
|
|
72
|
+
registry: this.registry
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
this.registry = new ComponentRegistry();
|
|
76
|
+
this.registry.add('tool-call', ToolCall);
|
|
77
|
+
this.registry.add('inline-diff', InlineDiff);
|
|
78
|
+
}
|
|
79
|
+
}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,32 +1,11 @@
|
|
|
1
1
|
import { JupyterFrontEndPlugin } from '@jupyterlab/application';
|
|
2
|
-
import {
|
|
3
|
-
import { Widget } from '@lumino/widgets';
|
|
4
|
-
import { IComponentsRendererFactory, ToolCallApproval } from './token';
|
|
2
|
+
import { IComponentsRendererFactory } from './token';
|
|
5
3
|
/**
|
|
6
|
-
* The
|
|
4
|
+
* The plugin providing the chat component renderer.
|
|
7
5
|
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* The callback to approve or reject a tool.
|
|
11
|
-
*/
|
|
12
|
-
toolCallApproval: ToolCallApproval;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* A widget for rendering .
|
|
16
|
-
*/
|
|
17
|
-
export declare class ComponentsRenderer extends Widget implements IRenderMime.IRenderer {
|
|
18
|
-
/**
|
|
19
|
-
* Construct a new output widget.
|
|
20
|
-
*/
|
|
21
|
-
constructor(options: IComponentsRendererOptions);
|
|
22
|
-
/**
|
|
23
|
-
* Render into this widget's node.
|
|
24
|
-
*/
|
|
25
|
-
renderModel(model: IRenderMime.IMimeModel): Promise<void>;
|
|
26
|
-
private _trans;
|
|
27
|
-
private _mimeType;
|
|
28
|
-
private _toolCallApproval;
|
|
29
|
-
}
|
|
30
|
-
declare const plugin: JupyterFrontEndPlugin<IComponentsRendererFactory>;
|
|
6
|
+
declare const factory: JupyterFrontEndPlugin<IComponentsRendererFactory>;
|
|
31
7
|
export * from './token';
|
|
32
|
-
export
|
|
8
|
+
export * from './factory';
|
|
9
|
+
export * from './registry';
|
|
10
|
+
export * from './components';
|
|
11
|
+
export default factory;
|
package/lib/index.js
CHANGED
|
@@ -1,67 +1,11 @@
|
|
|
1
1
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
2
|
-
import {
|
|
3
|
-
import { Widget } from '@lumino/widgets';
|
|
2
|
+
import { RendererFactory } from './factory';
|
|
4
3
|
import { IComponentsRendererFactory } from './token';
|
|
5
|
-
import { buildToolCallHtml } from './tool-call';
|
|
6
4
|
/**
|
|
7
|
-
* The
|
|
5
|
+
* The plugin providing the chat component renderer.
|
|
8
6
|
*/
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
* The class name added to the extension.
|
|
12
|
-
*/
|
|
13
|
-
const CLASS_NAME = 'jp-RenderedChatComponents';
|
|
14
|
-
/**
|
|
15
|
-
* A widget for rendering .
|
|
16
|
-
*/
|
|
17
|
-
export class ComponentsRenderer extends Widget {
|
|
18
|
-
/**
|
|
19
|
-
* Construct a new output widget.
|
|
20
|
-
*/
|
|
21
|
-
constructor(options) {
|
|
22
|
-
var _a;
|
|
23
|
-
super();
|
|
24
|
-
this._trans = ((_a = options.translator) !== null && _a !== void 0 ? _a : nullTranslator).load('jupyterlab');
|
|
25
|
-
this._mimeType = options.mimeType;
|
|
26
|
-
this._toolCallApproval = options.toolCallApproval;
|
|
27
|
-
this.addClass(CLASS_NAME);
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Render into this widget's node.
|
|
31
|
-
*/
|
|
32
|
-
renderModel(model) {
|
|
33
|
-
const data = model.data[this._mimeType];
|
|
34
|
-
const metadata = { ...model.metadata };
|
|
35
|
-
if (data === 'tool-call') {
|
|
36
|
-
const toolCallOptions = {
|
|
37
|
-
...metadata,
|
|
38
|
-
trans: this._trans,
|
|
39
|
-
toolCallApproval: this._toolCallApproval
|
|
40
|
-
};
|
|
41
|
-
this.node.appendChild(buildToolCallHtml(toolCallOptions));
|
|
42
|
-
}
|
|
43
|
-
return Promise.resolve();
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* A mime renderer factory for chat components.
|
|
48
|
-
*/
|
|
49
|
-
class RendererFactory {
|
|
50
|
-
constructor() {
|
|
51
|
-
this.safe = true;
|
|
52
|
-
this.mimeTypes = [MIME_TYPE];
|
|
53
|
-
this.defaultRank = 100;
|
|
54
|
-
this.toolCallApproval = null;
|
|
55
|
-
this.createRenderer = (options) => {
|
|
56
|
-
return new ComponentsRenderer({
|
|
57
|
-
...options,
|
|
58
|
-
toolCallApproval: this.toolCallApproval
|
|
59
|
-
});
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
const plugin = {
|
|
64
|
-
id: 'jupyter-chat-components:plugin',
|
|
7
|
+
const factory = {
|
|
8
|
+
id: 'jupyter-chat-components:factory',
|
|
65
9
|
description: 'Adds MIME type renderer for chat components',
|
|
66
10
|
autoStart: true,
|
|
67
11
|
provides: IComponentsRendererFactory,
|
|
@@ -73,4 +17,7 @@ const plugin = {
|
|
|
73
17
|
}
|
|
74
18
|
};
|
|
75
19
|
export * from './token';
|
|
76
|
-
export
|
|
20
|
+
export * from './factory';
|
|
21
|
+
export * from './registry';
|
|
22
|
+
export * from './components';
|
|
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
|
+
}
|