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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jupyter-chat-components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Components to displayed in jupyter chat",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"jupyter",
|
|
@@ -57,11 +57,13 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@jupyterlab/application": "^4.5.0",
|
|
60
|
+
"@jupyterlab/coreutils": "^6.5.4",
|
|
60
61
|
"@jupyterlab/rendermime": "^4.5.0",
|
|
61
62
|
"@jupyterlab/rendermime-interfaces": "^3.8.0",
|
|
62
63
|
"@jupyterlab/translation": "^4.5.0",
|
|
63
64
|
"@lumino/coreutils": "^2.2.2",
|
|
64
|
-
"@lumino/widgets": "^2.1.0"
|
|
65
|
+
"@lumino/widgets": "^2.1.0",
|
|
66
|
+
"diff": "^8.0.0"
|
|
65
67
|
},
|
|
66
68
|
"devDependencies": {
|
|
67
69
|
"@jupyterlab/builder": "^4.0.0",
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { PathExt } from '@jupyterlab/coreutils';
|
|
2
|
+
import { nullTranslator, TranslationBundle } from '@jupyterlab/translation';
|
|
3
|
+
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import { structuredPatch } from 'diff';
|
|
6
|
+
import type { StructuredPatchHunk } from 'diff';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
IInlineDiff,
|
|
10
|
+
IInlineDiffMetadata,
|
|
11
|
+
IInlineDiffNotebookCellTarget
|
|
12
|
+
} from '../token';
|
|
13
|
+
|
|
14
|
+
/** Maximum number of rendered lines before truncation. */
|
|
15
|
+
const MAX_DIFF_LINES = 20;
|
|
16
|
+
|
|
17
|
+
interface IDiffLineInfo {
|
|
18
|
+
cssClass: string;
|
|
19
|
+
prefix: string;
|
|
20
|
+
text: string;
|
|
21
|
+
key: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface IInlineDiffProps extends IInlineDiffMetadata {
|
|
25
|
+
trans?: TranslationBundle;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getDiffFilename(path: string): string {
|
|
29
|
+
return PathExt.basename(path);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getNotebookCellLabel(target: IInlineDiffNotebookCellTarget): string {
|
|
33
|
+
if (typeof target.cellIndex === 'number') {
|
|
34
|
+
return `Cell ${target.cellIndex + 1}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (target.cellId) {
|
|
38
|
+
return `Cell ${target.cellId}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return 'Notebook Cell';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getInlineDiffPatchPath(diff: IInlineDiff): string {
|
|
45
|
+
const target = diff.target;
|
|
46
|
+
|
|
47
|
+
if (target.kind === 'file') {
|
|
48
|
+
return target.path;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return [target.notebookPath, target.cellId ?? target.cellIndex]
|
|
52
|
+
.filter(value => value !== undefined && value !== '')
|
|
53
|
+
.join('#');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getInlineDiffLabel(diff: IInlineDiff): string {
|
|
57
|
+
if (diff.label) {
|
|
58
|
+
return diff.label;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const target = diff.target;
|
|
62
|
+
|
|
63
|
+
if (target.kind === 'file') {
|
|
64
|
+
return getDiffFilename(target.path);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const notebookName = getDiffFilename(target.notebookPath);
|
|
68
|
+
const cellLabel = getNotebookCellLabel(target);
|
|
69
|
+
|
|
70
|
+
return [notebookName, cellLabel].join(' · ');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getInlineDiffTitle(diff: IInlineDiff): string {
|
|
74
|
+
const target = diff.target;
|
|
75
|
+
|
|
76
|
+
if (target.kind === 'file') {
|
|
77
|
+
return target.path;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const cellLabel = getNotebookCellLabel(target);
|
|
81
|
+
const cellIdLabel =
|
|
82
|
+
typeof target.cellIndex === 'number' && target.cellId
|
|
83
|
+
? `Cell ID ${target.cellId}`
|
|
84
|
+
: null;
|
|
85
|
+
|
|
86
|
+
return [target.notebookPath, cellLabel, cellIdLabel]
|
|
87
|
+
.filter(part => part !== null && part !== undefined && part !== '')
|
|
88
|
+
.join(' · ');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function toLineInfo(
|
|
92
|
+
type: 'added' | 'removed' | 'context',
|
|
93
|
+
text: string,
|
|
94
|
+
key: string
|
|
95
|
+
): IDiffLineInfo {
|
|
96
|
+
switch (type) {
|
|
97
|
+
case 'added':
|
|
98
|
+
return {
|
|
99
|
+
cssClass: 'jp-mod-added',
|
|
100
|
+
prefix: '+',
|
|
101
|
+
text,
|
|
102
|
+
key
|
|
103
|
+
};
|
|
104
|
+
case 'removed':
|
|
105
|
+
return {
|
|
106
|
+
cssClass: 'jp-mod-removed',
|
|
107
|
+
prefix: '-',
|
|
108
|
+
text,
|
|
109
|
+
key
|
|
110
|
+
};
|
|
111
|
+
case 'context':
|
|
112
|
+
return {
|
|
113
|
+
cssClass: 'jp-mod-context',
|
|
114
|
+
prefix: ' ',
|
|
115
|
+
text,
|
|
116
|
+
key
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function buildDiffLinesFromHunk(
|
|
122
|
+
hunk: StructuredPatchHunk,
|
|
123
|
+
hunkIndex: number
|
|
124
|
+
): IDiffLineInfo[] {
|
|
125
|
+
return hunk.lines
|
|
126
|
+
.filter(line => !line.startsWith('\\'))
|
|
127
|
+
.map((line, lineIndex) => {
|
|
128
|
+
const prefix = line[0] ?? ' ';
|
|
129
|
+
const text = line.slice(1);
|
|
130
|
+
const key = `${hunkIndex}-${hunk.oldStart}-${hunk.newStart}-${lineIndex}`;
|
|
131
|
+
|
|
132
|
+
if (prefix === '+') {
|
|
133
|
+
return toLineInfo('added', text, key);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (prefix === '-') {
|
|
137
|
+
return toLineInfo('removed', text, key);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return toLineInfo('context', text, key);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function buildDiffLines(diff: IInlineDiff): IDiffLineInfo[] {
|
|
145
|
+
const patchPath = getInlineDiffPatchPath(diff);
|
|
146
|
+
const patch = structuredPatch(
|
|
147
|
+
patchPath,
|
|
148
|
+
patchPath,
|
|
149
|
+
diff.oldText ?? '',
|
|
150
|
+
diff.newText,
|
|
151
|
+
undefined,
|
|
152
|
+
undefined,
|
|
153
|
+
{ context: Infinity }
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
return patch.hunks.reduce<IDiffLineInfo[]>((lines, hunk, index) => {
|
|
157
|
+
lines.push(...buildDiffLinesFromHunk(hunk, index));
|
|
158
|
+
return lines;
|
|
159
|
+
}, []);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function DiffBlock({
|
|
163
|
+
diff,
|
|
164
|
+
trans
|
|
165
|
+
}: {
|
|
166
|
+
diff: IInlineDiff;
|
|
167
|
+
trans: TranslationBundle;
|
|
168
|
+
}): JSX.Element {
|
|
169
|
+
const filename = getInlineDiffLabel(diff);
|
|
170
|
+
const title = getInlineDiffTitle(diff);
|
|
171
|
+
const [expanded, setExpanded] = React.useState(false);
|
|
172
|
+
const allLines = React.useMemo(() => buildDiffLines(diff), [diff]);
|
|
173
|
+
const canTruncate = allLines.length > MAX_DIFF_LINES;
|
|
174
|
+
const visibleLines =
|
|
175
|
+
canTruncate && !expanded ? allLines.slice(0, MAX_DIFF_LINES) : allLines;
|
|
176
|
+
const hiddenCount = allLines.length - MAX_DIFF_LINES;
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<div className="jp-ai-inline-diff-block">
|
|
180
|
+
<div className="jp-ai-inline-diff-header" title={title}>
|
|
181
|
+
{filename}
|
|
182
|
+
</div>
|
|
183
|
+
<div className="jp-ai-inline-diff-content">
|
|
184
|
+
{visibleLines.length ? (
|
|
185
|
+
visibleLines.map(line => (
|
|
186
|
+
<div
|
|
187
|
+
key={line.key}
|
|
188
|
+
className={`jp-ai-inline-diff-line ${line.cssClass}`}
|
|
189
|
+
>
|
|
190
|
+
<span className="jp-ai-inline-diff-line-prefix">
|
|
191
|
+
{line.prefix}
|
|
192
|
+
</span>
|
|
193
|
+
<span className="jp-ai-inline-diff-line-text">{line.text}</span>
|
|
194
|
+
</div>
|
|
195
|
+
))
|
|
196
|
+
) : (
|
|
197
|
+
<div className="jp-ai-inline-diff-empty">
|
|
198
|
+
{trans.__('No changes')}
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
{canTruncate && !expanded && (
|
|
202
|
+
<button
|
|
203
|
+
className="jp-ai-inline-diff-toggle"
|
|
204
|
+
onClick={() => setExpanded(true)}
|
|
205
|
+
type="button"
|
|
206
|
+
>
|
|
207
|
+
{trans.__('... %1 more lines', hiddenCount)}
|
|
208
|
+
</button>
|
|
209
|
+
)}
|
|
210
|
+
{canTruncate && expanded && (
|
|
211
|
+
<button
|
|
212
|
+
className="jp-ai-inline-diff-toggle"
|
|
213
|
+
onClick={() => setExpanded(false)}
|
|
214
|
+
type="button"
|
|
215
|
+
>
|
|
216
|
+
{trans.__('Show less')}
|
|
217
|
+
</button>
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* React component for rendering one or more inline diffs.
|
|
226
|
+
*/
|
|
227
|
+
export const InlineDiff: React.FC<IInlineDiffProps> = ({ diffs, trans }) => {
|
|
228
|
+
const transBundle = trans ?? nullTranslator.load('jupyterlab');
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<div className="jp-ai-inline-diff-container">
|
|
232
|
+
{diffs.map((diff, index) => (
|
|
233
|
+
<DiffBlock
|
|
234
|
+
key={`${getInlineDiffPatchPath(diff)}-${index}`}
|
|
235
|
+
diff={diff}
|
|
236
|
+
trans={transBundle}
|
|
237
|
+
/>
|
|
238
|
+
))}
|
|
239
|
+
</div>
|
|
240
|
+
);
|
|
241
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
IComponentProps,
|
|
5
|
+
IMessageQueueMetadata,
|
|
6
|
+
RemoveQueuedMessage
|
|
7
|
+
} from '../token';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Props for the MessageQueue component.
|
|
11
|
+
*/
|
|
12
|
+
export interface IMessageQueueProps
|
|
13
|
+
extends IComponentProps, IMessageQueueMetadata {
|
|
14
|
+
removeQueuedMessage?: RemoveQueuedMessage;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* React component that displays a list of queued messages by
|
|
19
|
+
* showing each pending message as a bubble in the chat
|
|
20
|
+
*/
|
|
21
|
+
export const MessageQueue: React.FC<IMessageQueueProps> = ({
|
|
22
|
+
messages,
|
|
23
|
+
targetId,
|
|
24
|
+
trans,
|
|
25
|
+
removeQueuedMessage
|
|
26
|
+
}) => {
|
|
27
|
+
if (!messages || messages.length === 0) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className="jp-chat-message-queue">
|
|
33
|
+
{messages.map(msg => (
|
|
34
|
+
<div key={msg.id} className="jp-chat-message-queue-bubble">
|
|
35
|
+
<span className="jp-chat-message-queue-text">{msg.body}</span>
|
|
36
|
+
{removeQueuedMessage && targetId && (
|
|
37
|
+
<button
|
|
38
|
+
className="jp-chat-message-queue-remove"
|
|
39
|
+
onClick={() => removeQueuedMessage(targetId, msg.id)}
|
|
40
|
+
title={trans.__('Remove from queue')}
|
|
41
|
+
type="button"
|
|
42
|
+
>
|
|
43
|
+
✕
|
|
44
|
+
</button>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
))}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
};
|
|
@@ -2,7 +2,7 @@ import { TranslationBundle } from '@jupyterlab/translation';
|
|
|
2
2
|
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { IComponentProps, ToolCallApproval } from '../token';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Configuration for rendering tool call status.
|
|
@@ -13,6 +13,17 @@ interface IStatusConfig {
|
|
|
13
13
|
open?: boolean;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Tool call status types.
|
|
18
|
+
*/
|
|
19
|
+
export type ToolCallStatus =
|
|
20
|
+
| 'pending'
|
|
21
|
+
| 'awaiting_approval'
|
|
22
|
+
| 'approved'
|
|
23
|
+
| 'rejected'
|
|
24
|
+
| 'completed'
|
|
25
|
+
| 'error';
|
|
26
|
+
|
|
16
27
|
const STATUS_CONFIG: Record<ToolCallStatus, IStatusConfig> = {
|
|
17
28
|
pending: {
|
|
18
29
|
cssClass: 'jp-ai-tool-pending',
|
|
@@ -44,9 +55,21 @@ const STATUS_CONFIG: Record<ToolCallStatus, IStatusConfig> = {
|
|
|
44
55
|
/**
|
|
45
56
|
* Options for building tool call HTML.
|
|
46
57
|
*/
|
|
47
|
-
export interface
|
|
48
|
-
|
|
49
|
-
|
|
58
|
+
export interface IToolCallMetadata {
|
|
59
|
+
toolName: string;
|
|
60
|
+
input: string;
|
|
61
|
+
status: ToolCallStatus;
|
|
62
|
+
summary?: string;
|
|
63
|
+
output?: string;
|
|
64
|
+
targetId?: string;
|
|
65
|
+
approvalId?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Options for building tool call HTML.
|
|
70
|
+
*/
|
|
71
|
+
export interface IToolCallProps extends IComponentProps, IToolCallMetadata {
|
|
72
|
+
toolCallApproval?: ToolCallApproval;
|
|
50
73
|
}
|
|
51
74
|
|
|
52
75
|
export function escapeHtml(value: string): string {
|
|
@@ -90,11 +113,6 @@ const getStatusText = (
|
|
|
90
113
|
}
|
|
91
114
|
};
|
|
92
115
|
|
|
93
|
-
/**
|
|
94
|
-
* React component props for ToolCall.
|
|
95
|
-
*/
|
|
96
|
-
export interface IToolCallProps extends IToolCallHtmlOptions {}
|
|
97
|
-
|
|
98
116
|
/**
|
|
99
117
|
* React functional component for displaying a tool call.
|
|
100
118
|
*
|
|
@@ -117,6 +135,12 @@ export const ToolCall: React.FC<IToolCallProps> = ({
|
|
|
117
135
|
const resultLabel =
|
|
118
136
|
status === 'error' ? trans.__('Error') : trans.__('Result');
|
|
119
137
|
|
|
138
|
+
if (status === 'awaiting_approval' && !toolCallApproval) {
|
|
139
|
+
console.error(
|
|
140
|
+
'The tool call has no approval function, approval, it will not work as expected'
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
120
144
|
return (
|
|
121
145
|
<details
|
|
122
146
|
className={`jp-ai-tool-call ${config.cssClass}`}
|
package/src/factory.tsx
CHANGED
|
@@ -4,17 +4,21 @@ import { nullTranslator, TranslationBundle } from '@jupyterlab/translation';
|
|
|
4
4
|
|
|
5
5
|
import { ReactWidget } from '@jupyterlab/ui-components';
|
|
6
6
|
|
|
7
|
+
import { ReadonlyPartialJSONValue } from '@lumino/coreutils';
|
|
8
|
+
|
|
7
9
|
import * as React from 'react';
|
|
8
10
|
|
|
11
|
+
import { InlineDiff, MessageQueue, ToolCall } from './components';
|
|
12
|
+
|
|
13
|
+
import { ComponentRegistry } from './registry';
|
|
14
|
+
|
|
9
15
|
import {
|
|
16
|
+
IComponentRegistry,
|
|
10
17
|
IComponentsRendererFactory,
|
|
11
|
-
|
|
18
|
+
RemoveQueuedMessage,
|
|
12
19
|
ToolCallApproval
|
|
13
20
|
} from './token';
|
|
14
21
|
|
|
15
|
-
import { IToolCallHtmlOptions, ToolCall } from './components/tool-call';
|
|
16
|
-
import { ReadonlyPartialJSONValue } from '@lumino/coreutils';
|
|
17
|
-
|
|
18
22
|
/**
|
|
19
23
|
* The default mime type for the extension.
|
|
20
24
|
*/
|
|
@@ -36,11 +40,21 @@ interface IComponentsRendererOptions extends IRenderMime.IRendererOptions {
|
|
|
36
40
|
/**
|
|
37
41
|
* The callback to approve or reject a tool.
|
|
38
42
|
*/
|
|
39
|
-
toolCallApproval
|
|
43
|
+
toolCallApproval?: ToolCallApproval;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The callback to remove a queued message.
|
|
47
|
+
*/
|
|
48
|
+
removeQueuedMessage?: RemoveQueuedMessage;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The component registry.
|
|
52
|
+
*/
|
|
53
|
+
registry: IComponentRegistry;
|
|
40
54
|
}
|
|
41
55
|
|
|
42
56
|
/**
|
|
43
|
-
* A widget for rendering .
|
|
57
|
+
* A widget for rendering components from mime bundle.
|
|
44
58
|
*/
|
|
45
59
|
export class ComponentsRenderer
|
|
46
60
|
extends ReactWidget
|
|
@@ -54,6 +68,8 @@ export class ComponentsRenderer
|
|
|
54
68
|
this._trans = (options.translator ?? nullTranslator).load('jupyterlab');
|
|
55
69
|
this._mimeType = options.mimeType;
|
|
56
70
|
this._toolCallApproval = options.toolCallApproval;
|
|
71
|
+
this._removeQueuedMessage = options.removeQueuedMessage;
|
|
72
|
+
this._registry = options.registry;
|
|
57
73
|
this.addClass(CLASS_NAME);
|
|
58
74
|
}
|
|
59
75
|
|
|
@@ -62,25 +78,40 @@ export class ComponentsRenderer
|
|
|
62
78
|
*/
|
|
63
79
|
async renderModel(model: IRenderMime.IMimeModel): Promise<void> {
|
|
64
80
|
this._data = model.data[this._mimeType] as string;
|
|
65
|
-
|
|
81
|
+
const metadata = model.metadata;
|
|
82
|
+
this._metadata = (metadata[this._mimeType] as ReadonlyPartialJSONValue) ?? {
|
|
83
|
+
...metadata
|
|
84
|
+
};
|
|
66
85
|
return this.update();
|
|
67
86
|
}
|
|
68
87
|
|
|
69
88
|
protected render(): ReactRenderElement | null {
|
|
89
|
+
if (!this._data) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const Component = this._registry.get(this._data);
|
|
93
|
+
if (!Component) {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const componentsProps = { ...(this._metadata as any) };
|
|
98
|
+
|
|
70
99
|
if (this._data === 'tool-call') {
|
|
71
|
-
|
|
72
|
-
...(this._metadata as unknown as IToolCallMetadata),
|
|
73
|
-
trans: this._trans,
|
|
74
|
-
toolCallApproval: this._toolCallApproval
|
|
75
|
-
};
|
|
76
|
-
return <ToolCall {...toolCallOptions} />;
|
|
100
|
+
componentsProps.toolCallApproval = this._toolCallApproval;
|
|
77
101
|
}
|
|
78
|
-
|
|
102
|
+
|
|
103
|
+
if (this._data === 'message-queue') {
|
|
104
|
+
componentsProps.removeQueuedMessage = this._removeQueuedMessage;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return <Component {...componentsProps} trans={this._trans} />;
|
|
79
108
|
}
|
|
80
109
|
|
|
81
110
|
private _trans: TranslationBundle;
|
|
82
111
|
private _mimeType: string;
|
|
83
|
-
private _toolCallApproval
|
|
112
|
+
private _toolCallApproval?: ToolCallApproval;
|
|
113
|
+
private _removeQueuedMessage?: RemoveQueuedMessage;
|
|
114
|
+
private _registry: IComponentRegistry;
|
|
84
115
|
private _data: string | null = null;
|
|
85
116
|
private _metadata: ReadonlyPartialJSONValue | null = null;
|
|
86
117
|
}
|
|
@@ -92,11 +123,23 @@ export class RendererFactory implements IComponentsRendererFactory {
|
|
|
92
123
|
readonly safe = true;
|
|
93
124
|
readonly mimeTypes = [MIME_TYPE];
|
|
94
125
|
readonly defaultRank = 100;
|
|
126
|
+
readonly registry: ComponentRegistry;
|
|
95
127
|
toolCallApproval: ToolCallApproval = null;
|
|
128
|
+
removeQueuedMessage: RemoveQueuedMessage = null;
|
|
129
|
+
|
|
130
|
+
constructor() {
|
|
131
|
+
this.registry = new ComponentRegistry();
|
|
132
|
+
this.registry.add('tool-call', ToolCall);
|
|
133
|
+
this.registry.add('inline-diff', InlineDiff);
|
|
134
|
+
this.registry.add('message-queue', MessageQueue);
|
|
135
|
+
}
|
|
136
|
+
|
|
96
137
|
createRenderer = (options: IRenderMime.IRendererOptions) => {
|
|
97
138
|
return new ComponentsRenderer({
|
|
98
139
|
...options,
|
|
99
|
-
toolCallApproval: this.toolCallApproval
|
|
140
|
+
toolCallApproval: this.toolCallApproval,
|
|
141
|
+
removeQueuedMessage: this.removeQueuedMessage,
|
|
142
|
+
registry: this.registry
|
|
100
143
|
});
|
|
101
144
|
};
|
|
102
145
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,10 +5,10 @@ import {
|
|
|
5
5
|
|
|
6
6
|
import { IRenderMimeRegistry } from '@jupyterlab/rendermime';
|
|
7
7
|
|
|
8
|
-
import { IComponentsRendererFactory } from './token';
|
|
9
|
-
|
|
10
8
|
import { RendererFactory } from './factory';
|
|
11
9
|
|
|
10
|
+
import { IComponentsRendererFactory } from './token';
|
|
11
|
+
|
|
12
12
|
/**
|
|
13
13
|
* The plugin providing the chat component renderer.
|
|
14
14
|
*/
|
|
@@ -30,4 +30,7 @@ const factory: JupyterFrontEndPlugin<IComponentsRendererFactory> = {
|
|
|
30
30
|
|
|
31
31
|
export * from './token';
|
|
32
32
|
export * from './factory';
|
|
33
|
+
export * from './registry';
|
|
34
|
+
export * from './components';
|
|
35
|
+
|
|
33
36
|
export default factory;
|
package/src/registry.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
import { IComponentRegistry } from './token';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A registry for React components.
|
|
7
|
+
*/
|
|
8
|
+
export class ComponentRegistry implements IComponentRegistry {
|
|
9
|
+
/**
|
|
10
|
+
* Register a React component.
|
|
11
|
+
*
|
|
12
|
+
* @param name - The unique name/identifier for the component
|
|
13
|
+
* @param component - The React component
|
|
14
|
+
*/
|
|
15
|
+
add(name: string, component: React.ComponentType<any>): void {
|
|
16
|
+
if (this._components.has(name)) {
|
|
17
|
+
console.warn(
|
|
18
|
+
`Component '${name}' is already registered and will be overwritten.`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
this._components.set(name, component);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get a registered component by name.
|
|
26
|
+
*
|
|
27
|
+
* @param name - The name of the component
|
|
28
|
+
* @returns the React component, or undefined if not found
|
|
29
|
+
*/
|
|
30
|
+
get(name: string): React.ComponentType<any> | undefined {
|
|
31
|
+
return this._components.get(name);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Check if a component is registered.
|
|
36
|
+
*
|
|
37
|
+
* @param name - The name of the component
|
|
38
|
+
* @returns whether the component is registered
|
|
39
|
+
*/
|
|
40
|
+
has(name: string): boolean {
|
|
41
|
+
return this._components.has(name);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get all registered component names.
|
|
46
|
+
*
|
|
47
|
+
* @returns the component names
|
|
48
|
+
*/
|
|
49
|
+
getNames(): string[] {
|
|
50
|
+
return Array.from(this._components.keys());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private _components: Map<string, React.ComponentType<any>> = new Map();
|
|
54
|
+
}
|