jupyter-chat-components 0.2.0 → 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/lib/components/index.d.ts +1 -0
- package/lib/components/index.js +1 -0
- package/lib/components/inline-diff.d.ts +4 -2
- package/lib/components/inline-diff.js +51 -5
- package/lib/components/message-queue.d.ts +13 -0
- package/lib/components/message-queue.js +13 -0
- package/lib/factory.d.ts +7 -1
- package/lib/factory.js +8 -1
- package/lib/token.d.ts +78 -2
- package/package.json +1 -1
- package/src/components/index.ts +1 -0
- package/src/components/inline-diff.tsx +72 -7
- package/src/components/message-queue.tsx +50 -0
- package/src/factory.tsx +16 -1
- package/src/token.ts +89 -2
- package/style/base.css +58 -0
package/lib/components/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { TranslationBundle } from '@jupyterlab/translation';
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import { IInlineDiffMetadata } from '../token';
|
|
3
|
+
import { IInlineDiff, IInlineDiffMetadata } from '../token';
|
|
4
4
|
export interface IInlineDiffProps extends IInlineDiffMetadata {
|
|
5
5
|
trans?: TranslationBundle;
|
|
6
6
|
}
|
|
7
7
|
export declare function getDiffFilename(path: string): string;
|
|
8
|
+
export declare function getInlineDiffLabel(diff: IInlineDiff): string;
|
|
9
|
+
export declare function getInlineDiffTitle(diff: IInlineDiff): string;
|
|
8
10
|
/**
|
|
9
|
-
* React component for rendering one or more inline
|
|
11
|
+
* React component for rendering one or more inline diffs.
|
|
10
12
|
*/
|
|
11
13
|
export declare const InlineDiff: React.FC<IInlineDiffProps>;
|
|
@@ -7,6 +7,50 @@ const MAX_DIFF_LINES = 20;
|
|
|
7
7
|
export function getDiffFilename(path) {
|
|
8
8
|
return PathExt.basename(path);
|
|
9
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
|
+
}
|
|
10
54
|
function toLineInfo(type, text, key) {
|
|
11
55
|
switch (type) {
|
|
12
56
|
case 'added':
|
|
@@ -51,21 +95,23 @@ function buildDiffLinesFromHunk(hunk, hunkIndex) {
|
|
|
51
95
|
}
|
|
52
96
|
function buildDiffLines(diff) {
|
|
53
97
|
var _a;
|
|
54
|
-
const
|
|
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 });
|
|
55
100
|
return patch.hunks.reduce((lines, hunk, index) => {
|
|
56
101
|
lines.push(...buildDiffLinesFromHunk(hunk, index));
|
|
57
102
|
return lines;
|
|
58
103
|
}, []);
|
|
59
104
|
}
|
|
60
105
|
function DiffBlock({ diff, trans }) {
|
|
61
|
-
const filename =
|
|
106
|
+
const filename = getInlineDiffLabel(diff);
|
|
107
|
+
const title = getInlineDiffTitle(diff);
|
|
62
108
|
const [expanded, setExpanded] = React.useState(false);
|
|
63
109
|
const allLines = React.useMemo(() => buildDiffLines(diff), [diff]);
|
|
64
110
|
const canTruncate = allLines.length > MAX_DIFF_LINES;
|
|
65
111
|
const visibleLines = canTruncate && !expanded ? allLines.slice(0, MAX_DIFF_LINES) : allLines;
|
|
66
112
|
const hiddenCount = allLines.length - MAX_DIFF_LINES;
|
|
67
113
|
return (React.createElement("div", { className: "jp-ai-inline-diff-block" },
|
|
68
|
-
React.createElement("div", { className: "jp-ai-inline-diff-header", title:
|
|
114
|
+
React.createElement("div", { className: "jp-ai-inline-diff-header", title: title }, filename),
|
|
69
115
|
React.createElement("div", { className: "jp-ai-inline-diff-content" },
|
|
70
116
|
visibleLines.length ? (visibleLines.map(line => (React.createElement("div", { key: line.key, className: `jp-ai-inline-diff-line ${line.cssClass}` },
|
|
71
117
|
React.createElement("span", { className: "jp-ai-inline-diff-line-prefix" }, line.prefix),
|
|
@@ -74,9 +120,9 @@ function DiffBlock({ diff, trans }) {
|
|
|
74
120
|
canTruncate && expanded && (React.createElement("button", { className: "jp-ai-inline-diff-toggle", onClick: () => setExpanded(false), type: "button" }, trans.__('Show less'))))));
|
|
75
121
|
}
|
|
76
122
|
/**
|
|
77
|
-
* React component for rendering one or more inline
|
|
123
|
+
* React component for rendering one or more inline diffs.
|
|
78
124
|
*/
|
|
79
125
|
export const InlineDiff = ({ diffs, trans }) => {
|
|
80
126
|
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
|
|
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 })))));
|
|
82
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
|
+
};
|
package/lib/factory.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { IRenderMime } from '@jupyterlab/rendermime-interfaces';
|
|
|
2
2
|
import { ReactWidget } from '@jupyterlab/ui-components';
|
|
3
3
|
import * as React from 'react';
|
|
4
4
|
import { ComponentRegistry } from './registry';
|
|
5
|
-
import { IComponentRegistry, IComponentsRendererFactory, ToolCallApproval } from './token';
|
|
5
|
+
import { IComponentRegistry, IComponentsRendererFactory, RemoveQueuedMessage, ToolCallApproval } from './token';
|
|
6
6
|
type ReactRenderElement = Array<React.ReactElement<any>> | React.ReactElement<any>;
|
|
7
7
|
/**
|
|
8
8
|
* The options for the chat components renderer.
|
|
@@ -12,6 +12,10 @@ interface IComponentsRendererOptions extends IRenderMime.IRendererOptions {
|
|
|
12
12
|
* The callback to approve or reject a tool.
|
|
13
13
|
*/
|
|
14
14
|
toolCallApproval?: ToolCallApproval;
|
|
15
|
+
/**
|
|
16
|
+
* The callback to remove a queued message.
|
|
17
|
+
*/
|
|
18
|
+
removeQueuedMessage?: RemoveQueuedMessage;
|
|
15
19
|
/**
|
|
16
20
|
* The component registry.
|
|
17
21
|
*/
|
|
@@ -33,6 +37,7 @@ export declare class ComponentsRenderer extends ReactWidget implements IRenderMi
|
|
|
33
37
|
private _trans;
|
|
34
38
|
private _mimeType;
|
|
35
39
|
private _toolCallApproval?;
|
|
40
|
+
private _removeQueuedMessage?;
|
|
36
41
|
private _registry;
|
|
37
42
|
private _data;
|
|
38
43
|
private _metadata;
|
|
@@ -46,6 +51,7 @@ export declare class RendererFactory implements IComponentsRendererFactory {
|
|
|
46
51
|
readonly defaultRank = 100;
|
|
47
52
|
readonly registry: ComponentRegistry;
|
|
48
53
|
toolCallApproval: ToolCallApproval;
|
|
54
|
+
removeQueuedMessage: RemoveQueuedMessage;
|
|
49
55
|
constructor();
|
|
50
56
|
createRenderer: (options: IRenderMime.IRendererOptions) => ComponentsRenderer;
|
|
51
57
|
}
|
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 { InlineDiff, ToolCall } from './components';
|
|
4
|
+
import { InlineDiff, MessageQueue, ToolCall } from './components';
|
|
5
5
|
import { ComponentRegistry } from './registry';
|
|
6
6
|
/**
|
|
7
7
|
* The default mime type for the extension.
|
|
@@ -26,6 +26,7 @@ export class ComponentsRenderer extends ReactWidget {
|
|
|
26
26
|
this._trans = ((_a = options.translator) !== null && _a !== void 0 ? _a : nullTranslator).load('jupyterlab');
|
|
27
27
|
this._mimeType = options.mimeType;
|
|
28
28
|
this._toolCallApproval = options.toolCallApproval;
|
|
29
|
+
this._removeQueuedMessage = options.removeQueuedMessage;
|
|
29
30
|
this._registry = options.registry;
|
|
30
31
|
this.addClass(CLASS_NAME);
|
|
31
32
|
}
|
|
@@ -53,6 +54,9 @@ export class ComponentsRenderer extends ReactWidget {
|
|
|
53
54
|
if (this._data === 'tool-call') {
|
|
54
55
|
componentsProps.toolCallApproval = this._toolCallApproval;
|
|
55
56
|
}
|
|
57
|
+
if (this._data === 'message-queue') {
|
|
58
|
+
componentsProps.removeQueuedMessage = this._removeQueuedMessage;
|
|
59
|
+
}
|
|
56
60
|
return React.createElement(Component, { ...componentsProps, trans: this._trans });
|
|
57
61
|
}
|
|
58
62
|
}
|
|
@@ -65,15 +69,18 @@ export class RendererFactory {
|
|
|
65
69
|
this.mimeTypes = [MIME_TYPE];
|
|
66
70
|
this.defaultRank = 100;
|
|
67
71
|
this.toolCallApproval = null;
|
|
72
|
+
this.removeQueuedMessage = null;
|
|
68
73
|
this.createRenderer = (options) => {
|
|
69
74
|
return new ComponentsRenderer({
|
|
70
75
|
...options,
|
|
71
76
|
toolCallApproval: this.toolCallApproval,
|
|
77
|
+
removeQueuedMessage: this.removeQueuedMessage,
|
|
72
78
|
registry: this.registry
|
|
73
79
|
});
|
|
74
80
|
};
|
|
75
81
|
this.registry = new ComponentRegistry();
|
|
76
82
|
this.registry.add('tool-call', ToolCall);
|
|
77
83
|
this.registry.add('inline-diff', InlineDiff);
|
|
84
|
+
this.registry.add('message-queue', MessageQueue);
|
|
78
85
|
}
|
|
79
86
|
}
|
package/lib/token.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ export declare const IComponentsRendererFactory: Token<IComponentsRendererFactor
|
|
|
10
10
|
* The callback to approve or reject a tool.
|
|
11
11
|
*/
|
|
12
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;
|
|
13
17
|
/**
|
|
14
18
|
* The interface for components renderer factory.
|
|
15
19
|
*/
|
|
@@ -22,6 +26,10 @@ export interface IComponentsRendererFactory extends IRenderMime.IRendererFactory
|
|
|
22
26
|
* The callback to approve or reject a tool.
|
|
23
27
|
*/
|
|
24
28
|
toolCallApproval: ToolCallApproval;
|
|
29
|
+
/**
|
|
30
|
+
* The callback to remove a queued message.
|
|
31
|
+
*/
|
|
32
|
+
removeQueuedMessage: RemoveQueuedMessage;
|
|
25
33
|
}
|
|
26
34
|
/**
|
|
27
35
|
* The interface for the component registry.
|
|
@@ -65,16 +73,84 @@ export interface IComponentProps {
|
|
|
65
73
|
trans: TranslationBundle;
|
|
66
74
|
}
|
|
67
75
|
/**
|
|
68
|
-
* A
|
|
76
|
+
* A file diff target.
|
|
69
77
|
*/
|
|
70
|
-
export interface
|
|
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
|
+
*/
|
|
71
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
|
+
*/
|
|
72
128
|
newText: string;
|
|
129
|
+
/**
|
|
130
|
+
* Previous text content for the diff target.
|
|
131
|
+
*/
|
|
73
132
|
oldText?: string;
|
|
74
133
|
}
|
|
75
134
|
/**
|
|
76
135
|
* Metadata for rendering inline diffs.
|
|
77
136
|
*/
|
|
78
137
|
export interface IInlineDiffMetadata {
|
|
138
|
+
/**
|
|
139
|
+
* List of inline diff entries to render.
|
|
140
|
+
*/
|
|
79
141
|
diffs: IInlineDiff[];
|
|
80
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* A single queued message entry.
|
|
145
|
+
*/
|
|
146
|
+
export interface IQueuedMessage {
|
|
147
|
+
id: string;
|
|
148
|
+
body: string;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Metadata for the message queue component.
|
|
152
|
+
*/
|
|
153
|
+
export interface IMessageQueueMetadata {
|
|
154
|
+
messages: IQueuedMessage[];
|
|
155
|
+
targetId?: string;
|
|
156
|
+
}
|
package/package.json
CHANGED
package/src/components/index.ts
CHANGED
|
@@ -5,7 +5,11 @@ import * as React from 'react';
|
|
|
5
5
|
import { structuredPatch } from 'diff';
|
|
6
6
|
import type { StructuredPatchHunk } from 'diff';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
IInlineDiff,
|
|
10
|
+
IInlineDiffMetadata,
|
|
11
|
+
IInlineDiffNotebookCellTarget
|
|
12
|
+
} from '../token';
|
|
9
13
|
|
|
10
14
|
/** Maximum number of rendered lines before truncation. */
|
|
11
15
|
const MAX_DIFF_LINES = 20;
|
|
@@ -25,6 +29,65 @@ export function getDiffFilename(path: string): string {
|
|
|
25
29
|
return PathExt.basename(path);
|
|
26
30
|
}
|
|
27
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
|
+
|
|
28
91
|
function toLineInfo(
|
|
29
92
|
type: 'added' | 'removed' | 'context',
|
|
30
93
|
text: string,
|
|
@@ -79,9 +142,10 @@ function buildDiffLinesFromHunk(
|
|
|
79
142
|
}
|
|
80
143
|
|
|
81
144
|
function buildDiffLines(diff: IInlineDiff): IDiffLineInfo[] {
|
|
145
|
+
const patchPath = getInlineDiffPatchPath(diff);
|
|
82
146
|
const patch = structuredPatch(
|
|
83
|
-
|
|
84
|
-
|
|
147
|
+
patchPath,
|
|
148
|
+
patchPath,
|
|
85
149
|
diff.oldText ?? '',
|
|
86
150
|
diff.newText,
|
|
87
151
|
undefined,
|
|
@@ -102,7 +166,8 @@ function DiffBlock({
|
|
|
102
166
|
diff: IInlineDiff;
|
|
103
167
|
trans: TranslationBundle;
|
|
104
168
|
}): JSX.Element {
|
|
105
|
-
const filename =
|
|
169
|
+
const filename = getInlineDiffLabel(diff);
|
|
170
|
+
const title = getInlineDiffTitle(diff);
|
|
106
171
|
const [expanded, setExpanded] = React.useState(false);
|
|
107
172
|
const allLines = React.useMemo(() => buildDiffLines(diff), [diff]);
|
|
108
173
|
const canTruncate = allLines.length > MAX_DIFF_LINES;
|
|
@@ -112,7 +177,7 @@ function DiffBlock({
|
|
|
112
177
|
|
|
113
178
|
return (
|
|
114
179
|
<div className="jp-ai-inline-diff-block">
|
|
115
|
-
<div className="jp-ai-inline-diff-header" title={
|
|
180
|
+
<div className="jp-ai-inline-diff-header" title={title}>
|
|
116
181
|
{filename}
|
|
117
182
|
</div>
|
|
118
183
|
<div className="jp-ai-inline-diff-content">
|
|
@@ -157,7 +222,7 @@ function DiffBlock({
|
|
|
157
222
|
}
|
|
158
223
|
|
|
159
224
|
/**
|
|
160
|
-
* React component for rendering one or more inline
|
|
225
|
+
* React component for rendering one or more inline diffs.
|
|
161
226
|
*/
|
|
162
227
|
export const InlineDiff: React.FC<IInlineDiffProps> = ({ diffs, trans }) => {
|
|
163
228
|
const transBundle = trans ?? nullTranslator.load('jupyterlab');
|
|
@@ -166,7 +231,7 @@ export const InlineDiff: React.FC<IInlineDiffProps> = ({ diffs, trans }) => {
|
|
|
166
231
|
<div className="jp-ai-inline-diff-container">
|
|
167
232
|
{diffs.map((diff, index) => (
|
|
168
233
|
<DiffBlock
|
|
169
|
-
key={`${diff
|
|
234
|
+
key={`${getInlineDiffPatchPath(diff)}-${index}`}
|
|
170
235
|
diff={diff}
|
|
171
236
|
trans={transBundle}
|
|
172
237
|
/>
|
|
@@ -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
|
+
};
|
package/src/factory.tsx
CHANGED
|
@@ -8,13 +8,14 @@ import { ReadonlyPartialJSONValue } from '@lumino/coreutils';
|
|
|
8
8
|
|
|
9
9
|
import * as React from 'react';
|
|
10
10
|
|
|
11
|
-
import { InlineDiff, ToolCall } from './components';
|
|
11
|
+
import { InlineDiff, MessageQueue, ToolCall } from './components';
|
|
12
12
|
|
|
13
13
|
import { ComponentRegistry } from './registry';
|
|
14
14
|
|
|
15
15
|
import {
|
|
16
16
|
IComponentRegistry,
|
|
17
17
|
IComponentsRendererFactory,
|
|
18
|
+
RemoveQueuedMessage,
|
|
18
19
|
ToolCallApproval
|
|
19
20
|
} from './token';
|
|
20
21
|
|
|
@@ -41,6 +42,11 @@ interface IComponentsRendererOptions extends IRenderMime.IRendererOptions {
|
|
|
41
42
|
*/
|
|
42
43
|
toolCallApproval?: ToolCallApproval;
|
|
43
44
|
|
|
45
|
+
/**
|
|
46
|
+
* The callback to remove a queued message.
|
|
47
|
+
*/
|
|
48
|
+
removeQueuedMessage?: RemoveQueuedMessage;
|
|
49
|
+
|
|
44
50
|
/**
|
|
45
51
|
* The component registry.
|
|
46
52
|
*/
|
|
@@ -62,6 +68,7 @@ export class ComponentsRenderer
|
|
|
62
68
|
this._trans = (options.translator ?? nullTranslator).load('jupyterlab');
|
|
63
69
|
this._mimeType = options.mimeType;
|
|
64
70
|
this._toolCallApproval = options.toolCallApproval;
|
|
71
|
+
this._removeQueuedMessage = options.removeQueuedMessage;
|
|
65
72
|
this._registry = options.registry;
|
|
66
73
|
this.addClass(CLASS_NAME);
|
|
67
74
|
}
|
|
@@ -93,12 +100,17 @@ export class ComponentsRenderer
|
|
|
93
100
|
componentsProps.toolCallApproval = this._toolCallApproval;
|
|
94
101
|
}
|
|
95
102
|
|
|
103
|
+
if (this._data === 'message-queue') {
|
|
104
|
+
componentsProps.removeQueuedMessage = this._removeQueuedMessage;
|
|
105
|
+
}
|
|
106
|
+
|
|
96
107
|
return <Component {...componentsProps} trans={this._trans} />;
|
|
97
108
|
}
|
|
98
109
|
|
|
99
110
|
private _trans: TranslationBundle;
|
|
100
111
|
private _mimeType: string;
|
|
101
112
|
private _toolCallApproval?: ToolCallApproval;
|
|
113
|
+
private _removeQueuedMessage?: RemoveQueuedMessage;
|
|
102
114
|
private _registry: IComponentRegistry;
|
|
103
115
|
private _data: string | null = null;
|
|
104
116
|
private _metadata: ReadonlyPartialJSONValue | null = null;
|
|
@@ -113,17 +125,20 @@ export class RendererFactory implements IComponentsRendererFactory {
|
|
|
113
125
|
readonly defaultRank = 100;
|
|
114
126
|
readonly registry: ComponentRegistry;
|
|
115
127
|
toolCallApproval: ToolCallApproval = null;
|
|
128
|
+
removeQueuedMessage: RemoveQueuedMessage = null;
|
|
116
129
|
|
|
117
130
|
constructor() {
|
|
118
131
|
this.registry = new ComponentRegistry();
|
|
119
132
|
this.registry.add('tool-call', ToolCall);
|
|
120
133
|
this.registry.add('inline-diff', InlineDiff);
|
|
134
|
+
this.registry.add('message-queue', MessageQueue);
|
|
121
135
|
}
|
|
122
136
|
|
|
123
137
|
createRenderer = (options: IRenderMime.IRendererOptions) => {
|
|
124
138
|
return new ComponentsRenderer({
|
|
125
139
|
...options,
|
|
126
140
|
toolCallApproval: this.toolCallApproval,
|
|
141
|
+
removeQueuedMessage: this.removeQueuedMessage,
|
|
127
142
|
registry: this.registry
|
|
128
143
|
});
|
|
129
144
|
};
|
package/src/token.ts
CHANGED
|
@@ -21,6 +21,13 @@ export type ToolCallApproval =
|
|
|
21
21
|
| ((targetId: string, approvalId: string, approve: boolean) => void)
|
|
22
22
|
| null;
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* The callback to remove a queued message.
|
|
26
|
+
*/
|
|
27
|
+
export type RemoveQueuedMessage =
|
|
28
|
+
| ((targetId: string, messageId: string) => void)
|
|
29
|
+
| null;
|
|
30
|
+
|
|
24
31
|
/**
|
|
25
32
|
* The interface for components renderer factory.
|
|
26
33
|
*/
|
|
@@ -35,6 +42,11 @@ export interface IComponentsRendererFactory
|
|
|
35
42
|
* The callback to approve or reject a tool.
|
|
36
43
|
*/
|
|
37
44
|
toolCallApproval: ToolCallApproval;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The callback to remove a queued message.
|
|
48
|
+
*/
|
|
49
|
+
removeQueuedMessage: RemoveQueuedMessage;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
/**
|
|
@@ -84,11 +96,67 @@ export interface IComponentProps {
|
|
|
84
96
|
}
|
|
85
97
|
|
|
86
98
|
/**
|
|
87
|
-
* A
|
|
99
|
+
* A file diff target.
|
|
88
100
|
*/
|
|
89
|
-
export interface
|
|
101
|
+
export interface IInlineDiffFileTarget {
|
|
102
|
+
/**
|
|
103
|
+
* Discriminator for a regular file diff target.
|
|
104
|
+
*/
|
|
105
|
+
kind: 'file';
|
|
106
|
+
/**
|
|
107
|
+
* Path of the file being diffed.
|
|
108
|
+
*/
|
|
90
109
|
path: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* A notebook cell diff target.
|
|
114
|
+
*/
|
|
115
|
+
export interface IInlineDiffNotebookCellTarget {
|
|
116
|
+
/**
|
|
117
|
+
* Discriminator for a notebook cell source diff target.
|
|
118
|
+
*/
|
|
119
|
+
kind: 'cell';
|
|
120
|
+
/**
|
|
121
|
+
* Path of the notebook containing the cell.
|
|
122
|
+
*/
|
|
123
|
+
notebookPath: string;
|
|
124
|
+
/**
|
|
125
|
+
* Stable cell identifier, when available from the producer.
|
|
126
|
+
*/
|
|
127
|
+
cellId?: string;
|
|
128
|
+
/**
|
|
129
|
+
* Zero-based notebook cell index used for the default display label.
|
|
130
|
+
*/
|
|
131
|
+
cellIndex?: number;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* A supported inline diff target.
|
|
136
|
+
*/
|
|
137
|
+
export type IInlineDiffTarget =
|
|
138
|
+
| IInlineDiffFileTarget
|
|
139
|
+
| IInlineDiffNotebookCellTarget;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* A single inline diff entry.
|
|
143
|
+
*/
|
|
144
|
+
export interface IInlineDiff {
|
|
145
|
+
/**
|
|
146
|
+
* Structured target metadata.
|
|
147
|
+
*/
|
|
148
|
+
target: IInlineDiffTarget;
|
|
149
|
+
/**
|
|
150
|
+
* Optional explicit label for the diff header.
|
|
151
|
+
*/
|
|
152
|
+
label?: string;
|
|
153
|
+
/**
|
|
154
|
+
* Updated text content for the diff target.
|
|
155
|
+
*/
|
|
91
156
|
newText: string;
|
|
157
|
+
/**
|
|
158
|
+
* Previous text content for the diff target.
|
|
159
|
+
*/
|
|
92
160
|
oldText?: string;
|
|
93
161
|
}
|
|
94
162
|
|
|
@@ -96,5 +164,24 @@ export interface IInlineDiff {
|
|
|
96
164
|
* Metadata for rendering inline diffs.
|
|
97
165
|
*/
|
|
98
166
|
export interface IInlineDiffMetadata {
|
|
167
|
+
/**
|
|
168
|
+
* List of inline diff entries to render.
|
|
169
|
+
*/
|
|
99
170
|
diffs: IInlineDiff[];
|
|
100
171
|
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* A single queued message entry.
|
|
175
|
+
*/
|
|
176
|
+
export interface IQueuedMessage {
|
|
177
|
+
id: string;
|
|
178
|
+
body: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Metadata for the message queue component.
|
|
183
|
+
*/
|
|
184
|
+
export interface IMessageQueueMetadata {
|
|
185
|
+
messages: IQueuedMessage[];
|
|
186
|
+
targetId?: string;
|
|
187
|
+
}
|
package/style/base.css
CHANGED
|
@@ -323,3 +323,61 @@
|
|
|
323
323
|
color: var(--jp-ui-font-color1);
|
|
324
324
|
text-decoration: underline;
|
|
325
325
|
}
|
|
326
|
+
|
|
327
|
+
/* Message Queue Styling */
|
|
328
|
+
.jp-chat-message-queue {
|
|
329
|
+
display: flex;
|
|
330
|
+
flex-direction: column;
|
|
331
|
+
align-items: flex-end;
|
|
332
|
+
gap: 8px;
|
|
333
|
+
pointer-events: none;
|
|
334
|
+
width: 100%;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.jp-chat-message-queue-bubble {
|
|
338
|
+
position: relative;
|
|
339
|
+
display: inline-flex;
|
|
340
|
+
align-items: center;
|
|
341
|
+
gap: 6px;
|
|
342
|
+
background-color: var(--jp-layout-color2);
|
|
343
|
+
border: 1px solid var(--jp-border-color1);
|
|
344
|
+
border-radius: 16px;
|
|
345
|
+
padding: 4px 28px 4px 12px;
|
|
346
|
+
pointer-events: auto;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.jp-chat-message-queue-text {
|
|
350
|
+
font-size: var(--jp-ui-font-size0);
|
|
351
|
+
overflow: hidden;
|
|
352
|
+
text-overflow: ellipsis;
|
|
353
|
+
white-space: nowrap;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.jp-chat-message-queue-remove {
|
|
357
|
+
position: absolute;
|
|
358
|
+
right: 6px;
|
|
359
|
+
top: 50%;
|
|
360
|
+
transform: translateY(-50%);
|
|
361
|
+
display: inline-flex;
|
|
362
|
+
align-items: center;
|
|
363
|
+
justify-content: center;
|
|
364
|
+
width: 16px;
|
|
365
|
+
height: 16px;
|
|
366
|
+
padding: 0;
|
|
367
|
+
border: none;
|
|
368
|
+
border-radius: 50%;
|
|
369
|
+
background: transparent;
|
|
370
|
+
color: var(--jp-ui-font-color2);
|
|
371
|
+
font-size: 10px;
|
|
372
|
+
cursor: pointer;
|
|
373
|
+
opacity: 0;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
.jp-chat-message-queue-remove:hover {
|
|
377
|
+
background: var(--jp-layout-color3);
|
|
378
|
+
color: var(--jp-ui-font-color1);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.jp-chat-message-queue-bubble:hover .jp-chat-message-queue-remove {
|
|
382
|
+
opacity: 1;
|
|
383
|
+
}
|