@windowkit/agent-chat 0.1.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/dist/view/CodeBlock.d.ts.map +1 -1
- package/dist/view/CodeBlock.js +18 -11
- package/dist/view/CodeBlock.js.map +1 -1
- package/dist/view/TextBlock.d.ts.map +1 -1
- package/dist/view/TextBlock.js +4 -1
- package/dist/view/TextBlock.js.map +1 -1
- package/dist/view/Transcript.d.ts.map +1 -1
- package/dist/view/Transcript.js +11 -2
- package/dist/view/Transcript.js.map +1 -1
- package/dist/view/look.d.ts +7 -0
- package/dist/view/look.d.ts.map +1 -1
- package/dist/view/look.js +10 -0
- package/dist/view/look.js.map +1 -1
- package/package.json +3 -3
- package/src/view/CodeBlock.tsx +18 -11
- package/src/view/TextBlock.tsx +4 -1
- package/src/view/Transcript.tsx +11 -1
- package/src/view/look.ts +17 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeBlock.d.ts","sourceRoot":"","sources":["../../src/view/CodeBlock.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"CodeBlock.d.ts","sourceRoot":"","sources":["../../src/view/CodeBlock.tsx"],"names":[],"mappings":"AAKA,OAAO,EAA+B,KAAK,YAAY,EAAE,MAAM,OAAO,CAAC;AAGvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAEhE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAKrD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,EAAE,YAAY,CAAC;CACvB;AAED,wBAAgB,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,cAAc,GAAG,YAAY,CA8F1E"}
|
package/dist/view/CodeBlock.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react-x11/jsx-runtime";
|
|
2
2
|
// A fenced code block with a header of actions: Copy by default, and
|
|
3
3
|
// whatever the app's `codeActions` add. Nothing here runs code; an action
|
|
4
|
-
// that does is the app's.
|
|
4
|
+
// that does is the app's. It is drawn as `<Markdown>` draws a fence, with
|
|
5
|
+
// the header inside the block's fill.
|
|
5
6
|
import { useEffect, useRef, useState } from 'react';
|
|
6
7
|
import { Button, useClipboard } from 'react-x11';
|
|
7
8
|
import { Code } from '@react-x11/components/code';
|
|
8
9
|
import { useChat } from './context.js';
|
|
9
|
-
import { useLook } from './look.js';
|
|
10
|
+
import { useCodeLook, useLook } from './look.js';
|
|
10
11
|
export function CodeBlock({ fence, message }) {
|
|
11
|
-
const { codeActions, session } = useChat();
|
|
12
|
+
const { codeActions, session, fontSize } = useChat();
|
|
12
13
|
const look = useLook();
|
|
14
|
+
const code = useCodeLook(fontSize);
|
|
13
15
|
const clipboard = useClipboard();
|
|
14
16
|
const [done, setDone] = useState(null);
|
|
15
17
|
const timer = useRef(null);
|
|
@@ -40,21 +42,26 @@ export function CodeBlock({ fence, message }) {
|
|
|
40
42
|
};
|
|
41
43
|
return (_jsxs("box", { style: {
|
|
42
44
|
flexDirection: 'column',
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
borderRadius: 6,
|
|
45
|
+
backgroundColor: code.fill,
|
|
46
|
+
borderRadius: code.radius,
|
|
46
47
|
overflow: 'hidden',
|
|
47
48
|
}, children: [_jsxs("box", { style: {
|
|
48
49
|
flexDirection: 'row',
|
|
49
50
|
alignItems: 'center',
|
|
50
51
|
gap: 4,
|
|
51
|
-
paddingLeft:
|
|
52
|
-
paddingRight:
|
|
52
|
+
paddingLeft: code.padding,
|
|
53
|
+
paddingRight: Math.round(code.padding / 3),
|
|
54
|
+
paddingTop: Math.round(code.padding / 3),
|
|
53
55
|
minHeight: 26,
|
|
54
|
-
borderBottomWidth: 1,
|
|
55
|
-
borderBottomColor: look.border,
|
|
56
56
|
}, children: [_jsx("text", { style: { color: look.muted, fontSize: 11, flexGrow: 1 }, children: fence.lang || 'text' }), actions.map((action) => (_jsx(Button, { size: "small", variant: "ghost", label: done === action.id
|
|
57
57
|
? (action.doneLabel ?? action.label)
|
|
58
|
-
: action.label, onPress: () => run(action) }, action.id)))] }), _jsx(Code, { source: fence.text, ...(fence.lang ? { lang: fence.lang } : {}),
|
|
58
|
+
: action.label, onPress: () => run(action) }, action.id)))] }), _jsx(Code, { source: fence.text, ...(fence.lang ? { lang: fence.lang } : {}), fontSize: code.size, wrap: true,
|
|
59
|
+
// the frame holds the fill: the code's own, a tint, would lay a
|
|
60
|
+
// second over it
|
|
61
|
+
style: {
|
|
62
|
+
backgroundColor: 'transparent',
|
|
63
|
+
borderRadius: 0,
|
|
64
|
+
paddingTop: Math.round(code.padding / 3),
|
|
65
|
+
} })] }));
|
|
59
66
|
}
|
|
60
67
|
//# sourceMappingURL=CodeBlock.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeBlock.js","sourceRoot":"","sources":["../../src/view/CodeBlock.tsx"],"names":[],"mappings":";AAAA,qEAAqE;AACrE,0EAA0E;AAC1E,
|
|
1
|
+
{"version":3,"file":"CodeBlock.js","sourceRoot":"","sources":["../../src/view/CodeBlock.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextBlock.d.ts","sourceRoot":"","sources":["../../src/view/TextBlock.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,OAAO,CAAC;AAGzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"TextBlock.d.ts","sourceRoot":"","sources":["../../src/view/TextBlock.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAiB,KAAK,YAAY,EAAE,MAAM,OAAO,CAAC;AAGzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAMrD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,YAAY,CAAC;CACvB;AAED,eAAO,MAAM,SAAS,sEAInB,cAAc,KAAG,YAAY,CAsC9B,CAAC"}
|
package/dist/view/TextBlock.js
CHANGED
|
@@ -5,12 +5,15 @@ import { memo, useMemo } from 'react';
|
|
|
5
5
|
import { Markdown } from '@react-x11/components/markdown';
|
|
6
6
|
import { CodeBlock } from './CodeBlock.js';
|
|
7
7
|
import { useChat } from './context.js';
|
|
8
|
+
import { useCodeLook } from './look.js';
|
|
8
9
|
import { splitFences } from './fences.js';
|
|
9
10
|
export const TextBlock = memo(function TextBlock({ text, streaming, message, }) {
|
|
10
11
|
const { fences, components, onLink, fontSize } = useChat();
|
|
12
|
+
// segments alternate prose and fences: the gap is the space a fence keeps
|
|
13
|
+
const { spacing } = useCodeLook(fontSize);
|
|
11
14
|
const segments = useMemo(() => splitFences(text, streaming), [text, streaming]);
|
|
12
15
|
const link = useMemo(() => (onLink ? (href) => onLink(href) : undefined), [onLink]);
|
|
13
|
-
return (_jsx("box", { style: { flexDirection: 'column', gap:
|
|
16
|
+
return (_jsx("box", { style: { flexDirection: 'column', gap: spacing }, children: segments.map((s, i) => {
|
|
14
17
|
if (s.type === 'prose') {
|
|
15
18
|
return (_jsx(Markdown, { source: s.text, partial: streaming && i === segments.length - 1, ...(fences ? { fences } : {}), ...(components ? { components } : {}), ...(link ? { onLink: link } : {}), ...(fontSize ? { fontSize } : {}) }, i));
|
|
16
19
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextBlock.js","sourceRoot":"","sources":["../../src/view/TextBlock.tsx"],"names":[],"mappings":";AAAA,4EAA4E;AAC5E,mEAAmE;AAEnE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAqB,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAG1D,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAQ1C,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,SAAS,CAAC,EAC/C,IAAI,EACJ,SAAS,EACT,OAAO,GACQ;IACf,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC;IAC3D,MAAM,QAAQ,GAAG,OAAO,CACtB,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,SAAS,CAAC,EAClC,CAAC,IAAI,EAAE,SAAS,CAAC,CAClB,CAAC;IACF,MAAM,IAAI,GAAG,OAAO,CAClB,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,EAC3D,CAAC,MAAM,CAAC,CACT,CAAC;IACF,OAAO,CACL,cAAK,KAAK,EAAE,EAAE,aAAa,EAAE,QAAQ,EAAE,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"TextBlock.js","sourceRoot":"","sources":["../../src/view/TextBlock.tsx"],"names":[],"mappings":";AAAA,4EAA4E;AAC5E,mEAAmE;AAEnE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAqB,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAG1D,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAQ1C,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,SAAS,CAAC,EAC/C,IAAI,EACJ,SAAS,EACT,OAAO,GACQ;IACf,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC;IAC3D,0EAA0E;IAC1E,MAAM,EAAE,OAAO,EAAE,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,OAAO,CACtB,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,SAAS,CAAC,EAClC,CAAC,IAAI,EAAE,SAAS,CAAC,CAClB,CAAC;IACF,MAAM,IAAI,GAAG,OAAO,CAClB,GAAG,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,EAC3D,CAAC,MAAM,CAAC,CACT,CAAC;IACF,OAAO,CACL,cAAK,KAAK,EAAE,EAAE,aAAa,EAAE,QAAQ,EAAE,GAAG,EAAE,OAAO,EAAE,YAClD,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACrB,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBACvB,OAAO,CACL,KAAC,QAAQ,IAEP,MAAM,EAAE,CAAC,CAAC,IAAI,EACd,OAAO,EAAE,SAAS,IAAI,CAAC,KAAK,QAAQ,CAAC,MAAM,GAAG,CAAC,KAC3C,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAC1B,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAClC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,KAC9B,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,IAN7B,CAAC,CAON,CACH,CAAC;YACJ,CAAC;YACD,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;YACjE,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAChC,OAAO,MAAM,CAAC,CAAC,CAAC,CACd,wBAAc,MAAM,CAAC,KAAK,CAAC,IAAjB,CAAC,CAAuB,CACnC,CAAC,CAAC,CAAC,CACF,KAAC,SAAS,IAAS,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,IAAjC,CAAC,CAAoC,CACtD,CAAC;QACJ,CAAC,CAAC,GACE,CACP,CAAC;AACJ,CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Transcript.d.ts","sourceRoot":"","sources":["../../src/view/Transcript.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Transcript.d.ts","sourceRoot":"","sources":["../../src/view/Transcript.tsx"],"names":[],"mappings":"AAMA,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAWf,wBAAgB,UAAU,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,GAAG,YAAY,CA6CzE"}
|
package/dist/view/Transcript.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react-x11/jsx-runtime";
|
|
2
2
|
// The scrolling transcript. It follows the newest message while the reader
|
|
3
|
-
// is at the bottom, and stops following once they scroll up to read.
|
|
3
|
+
// is at the bottom, and stops following once they scroll up to read. It
|
|
4
|
+
// follows content that grows after it was laid out too: a fence the app
|
|
5
|
+
// draws itself (a diagram compiled after mount) would otherwise push the end
|
|
6
|
+
// of the reply out of view.
|
|
4
7
|
import { useLayoutEffect, useRef, } from 'react';
|
|
5
8
|
import { useChat } from './context.js';
|
|
6
9
|
import { useLook } from './look.js';
|
|
@@ -20,12 +23,18 @@ export function Transcript({ empty }) {
|
|
|
20
23
|
if (stick.current)
|
|
21
24
|
pane.current?.scrollTo(Infinity);
|
|
22
25
|
}, [snap.messages, snap.status]);
|
|
26
|
+
// fires from layout whenever the content or viewport size changes, not
|
|
27
|
+
// only when the snapshot does
|
|
28
|
+
const onViewport = () => {
|
|
29
|
+
if (stick.current)
|
|
30
|
+
pane.current?.scrollTo(Infinity);
|
|
31
|
+
};
|
|
23
32
|
const onScroll = (ev) => {
|
|
24
33
|
stick.current =
|
|
25
34
|
ev.scrollY + ev.viewportHeight >= ev.contentHeight - STICK_PX;
|
|
26
35
|
};
|
|
27
36
|
const activity = activityOf(snap.status, snap.messages);
|
|
28
|
-
return (_jsx("box", { ref: pane, onScroll: onScroll, style: { flexGrow: 1, flexShrink: 1, minHeight: 0, overflow: 'scroll' }, "data-testname": "agent-chat-transcript", children: _jsxs("box", { style: { flexDirection: 'column', gap: 14, padding: 14 }, children: [snap.messages.length ? (_jsx(MessageList, { messages: snap.messages })) : ((empty ?? null)), activity ? (_jsx("text", { style: { color: look.muted, fontSize: 12 }, children: activity })) : null] }) }));
|
|
37
|
+
return (_jsx("box", { ref: pane, onScroll: onScroll, onViewport: onViewport, style: { flexGrow: 1, flexShrink: 1, minHeight: 0, overflow: 'scroll' }, "data-testname": "agent-chat-transcript", children: _jsxs("box", { style: { flexDirection: 'column', gap: 14, padding: 14 }, children: [snap.messages.length ? (_jsx(MessageList, { messages: snap.messages })) : ((empty ?? null)), activity ? (_jsx("text", { style: { color: look.muted, fontSize: 12 }, children: activity })) : null] }) }));
|
|
29
38
|
}
|
|
30
39
|
function activityOf(status, messages) {
|
|
31
40
|
switch (status) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Transcript.js","sourceRoot":"","sources":["../../src/view/Transcript.tsx"],"names":[],"mappings":";AAAA,2EAA2E;AAC3E,
|
|
1
|
+
{"version":3,"file":"Transcript.js","sourceRoot":"","sources":["../../src/view/Transcript.tsx"],"names":[],"mappings":"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"}
|
package/dist/view/look.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type CodeBlockLook } from '@react-x11/components/codeblock';
|
|
1
2
|
export interface ChatLook {
|
|
2
3
|
text: string;
|
|
3
4
|
muted: string;
|
|
@@ -13,6 +14,12 @@ export interface ChatLook {
|
|
|
13
14
|
bubble: string;
|
|
14
15
|
}
|
|
15
16
|
export declare function useLook(): ChatLook;
|
|
17
|
+
/**
|
|
18
|
+
* What a block of code looks like here: what `<Markdown>` draws a fence
|
|
19
|
+
* with, at the chat's text size, so a fence the chat draws itself (to carry
|
|
20
|
+
* its actions) has the fill, radius, inset and spacing of one it does not.
|
|
21
|
+
*/
|
|
22
|
+
export declare function useCodeLook(fontSize: number | undefined): CodeBlockLook;
|
|
16
23
|
/** At most `max` lines: the first and last halves, and how many were cut. */
|
|
17
24
|
export declare function clipLines(text: string, max?: number): string;
|
|
18
25
|
//# sourceMappingURL=look.d.ts.map
|
package/dist/view/look.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"look.d.ts","sourceRoot":"","sources":["../../src/view/look.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"look.d.ts","sourceRoot":"","sources":["../../src/view/look.ts"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,aAAa,EACnB,MAAM,iCAAiC,CAAC;AAEzC,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,OAAO,IAAI,QAAQ,CAkBlC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,aAAa,CAMvE;AAED,6EAA6E;AAC7E,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAM,GAAG,MAAM,CASzD"}
|
package/dist/view/look.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { useMemo } from 'react';
|
|
4
4
|
import { useTheme } from 'react-x11';
|
|
5
5
|
import { tint } from 'react-x11/style';
|
|
6
|
+
import { codeBlockLook, } from '@react-x11/components/codeblock';
|
|
6
7
|
export function useLook() {
|
|
7
8
|
const theme = useTheme();
|
|
8
9
|
return useMemo(() => {
|
|
@@ -22,6 +23,15 @@ export function useLook() {
|
|
|
22
23
|
};
|
|
23
24
|
}, [theme]);
|
|
24
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* What a block of code looks like here: what `<Markdown>` draws a fence
|
|
28
|
+
* with, at the chat's text size, so a fence the chat draws itself (to carry
|
|
29
|
+
* its actions) has the fill, radius, inset and spacing of one it does not.
|
|
30
|
+
*/
|
|
31
|
+
export function useCodeLook(fontSize) {
|
|
32
|
+
const theme = useTheme();
|
|
33
|
+
return useMemo(() => codeBlockLook(theme, fontSize ? { baseSize: fontSize } : {}), [theme, fontSize]);
|
|
34
|
+
}
|
|
25
35
|
/** At most `max` lines: the first and last halves, and how many were cut. */
|
|
26
36
|
export function clipLines(text, max = 200) {
|
|
27
37
|
const lines = text.split('\n');
|
package/dist/view/look.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"look.js","sourceRoot":"","sources":["../../src/view/look.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,+DAA+D;AAE/D,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"look.js","sourceRoot":"","sources":["../../src/view/look.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,+DAA+D;AAE/D,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvC,OAAO,EACL,aAAa,GAEd,MAAM,iCAAiC,CAAC;AAiBzC,MAAM,UAAU,OAAO;IACrB,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,OAAO,OAAO,CAAC,GAAG,EAAE;QAClB,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,SAAS,CAAC,CAAC;QAC7C,OAAO;YACL,IAAI;YACJ,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,SAAS,IAAI,SAAS,CAAC;YAC3C,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,SAAS,CAAC;YACzC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,SAAS,CAAC;YAC3C,UAAU,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU,IAAI,OAAO,CAAC;YAC/C,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,SAAS,CAAC;YACzC,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,IAAI,SAAS,CAAC;YACzC,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,SAAS,CAAC;YAC3C,OAAO,EAAE,MAAM,CAAC,KAAK,CAAC,OAAO,IAAI,SAAS,CAAC;YAC3C,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,UAAU,IAAI,WAAW,CAAC;YAC7C,MAAM,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC;SACzB,CAAC;IACJ,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,QAA4B;IACtD,MAAM,KAAK,GAAG,QAAQ,EAAwC,CAAC;IAC/D,OAAO,OAAO,CACZ,GAAG,EAAE,CAAC,aAAa,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAClE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAClB,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,GAAG,GAAG,GAAG;IAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,IAAI,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;IACjC,OAAO;QACL,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC;QACvB,KAAK,KAAK,CAAC,MAAM,GAAG,GAAG,eAAe;QACtC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;KACtB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windowkit/agent-chat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "An embeddable chat UI for local AI agent harnesses, for react-x11: the Claude Agent SDK first, your own tools as functions, your own renderers, and every action interceptable.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-x11",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
70
|
"@anthropic-ai/claude-agent-sdk": ">=0.3.300 <0.4.0",
|
|
71
|
-
"@react-x11/components": "
|
|
71
|
+
"@react-x11/components": "^0.44.0",
|
|
72
72
|
"react": "^19.0.0",
|
|
73
73
|
"react-x11": "^2.56.3"
|
|
74
74
|
},
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
|
81
81
|
"@anthropic-ai/claude-agent-sdk": "^0.3.319",
|
|
82
|
-
"@react-x11/components": "^0.
|
|
82
|
+
"@react-x11/components": "^0.44.0",
|
|
83
83
|
"@types/node": "^26.2.0",
|
|
84
84
|
"@types/pngjs": "^6.0.5",
|
|
85
85
|
"@types/react": "^19.2.17",
|
package/src/view/CodeBlock.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// A fenced code block with a header of actions: Copy by default, and
|
|
2
2
|
// whatever the app's `codeActions` add. Nothing here runs code; an action
|
|
3
|
-
// that does is the app's.
|
|
3
|
+
// that does is the app's. It is drawn as `<Markdown>` draws a fence, with
|
|
4
|
+
// the header inside the block's fill.
|
|
4
5
|
|
|
5
6
|
import { useEffect, useRef, useState, type ReactElement } from 'react';
|
|
6
7
|
import { Button, useClipboard } from 'react-x11';
|
|
@@ -9,7 +10,7 @@ import type { FenceInfo } from '@react-x11/components/markdown';
|
|
|
9
10
|
|
|
10
11
|
import type { AgentMessage } from '../core/types.js';
|
|
11
12
|
import { useChat } from './context.js';
|
|
12
|
-
import { useLook } from './look.js';
|
|
13
|
+
import { useCodeLook, useLook } from './look.js';
|
|
13
14
|
import type { CodeAction, CodeActionContext } from './types.js';
|
|
14
15
|
|
|
15
16
|
export interface CodeBlockProps {
|
|
@@ -18,8 +19,9 @@ export interface CodeBlockProps {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export function CodeBlock({ fence, message }: CodeBlockProps): ReactElement {
|
|
21
|
-
const { codeActions, session } = useChat();
|
|
22
|
+
const { codeActions, session, fontSize } = useChat();
|
|
22
23
|
const look = useLook();
|
|
24
|
+
const code = useCodeLook(fontSize);
|
|
23
25
|
const clipboard = useClipboard();
|
|
24
26
|
const [done, setDone] = useState<string | null>(null);
|
|
25
27
|
const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
@@ -62,9 +64,8 @@ export function CodeBlock({ fence, message }: CodeBlockProps): ReactElement {
|
|
|
62
64
|
<box
|
|
63
65
|
style={{
|
|
64
66
|
flexDirection: 'column',
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
borderRadius: 6,
|
|
67
|
+
backgroundColor: code.fill,
|
|
68
|
+
borderRadius: code.radius,
|
|
68
69
|
overflow: 'hidden',
|
|
69
70
|
}}
|
|
70
71
|
>
|
|
@@ -73,11 +74,10 @@ export function CodeBlock({ fence, message }: CodeBlockProps): ReactElement {
|
|
|
73
74
|
flexDirection: 'row',
|
|
74
75
|
alignItems: 'center',
|
|
75
76
|
gap: 4,
|
|
76
|
-
paddingLeft:
|
|
77
|
-
paddingRight:
|
|
77
|
+
paddingLeft: code.padding,
|
|
78
|
+
paddingRight: Math.round(code.padding / 3),
|
|
79
|
+
paddingTop: Math.round(code.padding / 3),
|
|
78
80
|
minHeight: 26,
|
|
79
|
-
borderBottomWidth: 1,
|
|
80
|
-
borderBottomColor: look.border,
|
|
81
81
|
}}
|
|
82
82
|
>
|
|
83
83
|
<text style={{ color: look.muted, fontSize: 11, flexGrow: 1 }}>
|
|
@@ -100,8 +100,15 @@ export function CodeBlock({ fence, message }: CodeBlockProps): ReactElement {
|
|
|
100
100
|
<Code
|
|
101
101
|
source={fence.text}
|
|
102
102
|
{...(fence.lang ? { lang: fence.lang } : {})}
|
|
103
|
+
fontSize={code.size}
|
|
103
104
|
wrap
|
|
104
|
-
|
|
105
|
+
// the frame holds the fill: the code's own, a tint, would lay a
|
|
106
|
+
// second over it
|
|
107
|
+
style={{
|
|
108
|
+
backgroundColor: 'transparent',
|
|
109
|
+
borderRadius: 0,
|
|
110
|
+
paddingTop: Math.round(code.padding / 3),
|
|
111
|
+
}}
|
|
105
112
|
/>
|
|
106
113
|
</box>
|
|
107
114
|
);
|
package/src/view/TextBlock.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import { Markdown } from '@react-x11/components/markdown';
|
|
|
7
7
|
import type { AgentMessage } from '../core/types.js';
|
|
8
8
|
import { CodeBlock } from './CodeBlock.js';
|
|
9
9
|
import { useChat } from './context.js';
|
|
10
|
+
import { useCodeLook } from './look.js';
|
|
10
11
|
import { splitFences } from './fences.js';
|
|
11
12
|
|
|
12
13
|
export interface TextBlockProps {
|
|
@@ -21,6 +22,8 @@ export const TextBlock = memo(function TextBlock({
|
|
|
21
22
|
message,
|
|
22
23
|
}: TextBlockProps): ReactElement {
|
|
23
24
|
const { fences, components, onLink, fontSize } = useChat();
|
|
25
|
+
// segments alternate prose and fences: the gap is the space a fence keeps
|
|
26
|
+
const { spacing } = useCodeLook(fontSize);
|
|
24
27
|
const segments = useMemo(
|
|
25
28
|
() => splitFences(text, streaming),
|
|
26
29
|
[text, streaming],
|
|
@@ -30,7 +33,7 @@ export const TextBlock = memo(function TextBlock({
|
|
|
30
33
|
[onLink],
|
|
31
34
|
);
|
|
32
35
|
return (
|
|
33
|
-
<box style={{ flexDirection: 'column', gap:
|
|
36
|
+
<box style={{ flexDirection: 'column', gap: spacing }}>
|
|
34
37
|
{segments.map((s, i) => {
|
|
35
38
|
if (s.type === 'prose') {
|
|
36
39
|
return (
|
package/src/view/Transcript.tsx
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// The scrolling transcript. It follows the newest message while the reader
|
|
2
|
-
// is at the bottom, and stops following once they scroll up to read.
|
|
2
|
+
// is at the bottom, and stops following once they scroll up to read. It
|
|
3
|
+
// follows content that grows after it was laid out too: a fence the app
|
|
4
|
+
// draws itself (a diagram compiled after mount) would otherwise push the end
|
|
5
|
+
// of the reply out of view.
|
|
3
6
|
|
|
4
7
|
import {
|
|
5
8
|
useLayoutEffect,
|
|
@@ -30,6 +33,12 @@ export function Transcript({ empty }: { empty?: ReactNode }): ReactElement {
|
|
|
30
33
|
if (stick.current) pane.current?.scrollTo(Infinity);
|
|
31
34
|
}, [snap.messages, snap.status]);
|
|
32
35
|
|
|
36
|
+
// fires from layout whenever the content or viewport size changes, not
|
|
37
|
+
// only when the snapshot does
|
|
38
|
+
const onViewport = () => {
|
|
39
|
+
if (stick.current) pane.current?.scrollTo(Infinity);
|
|
40
|
+
};
|
|
41
|
+
|
|
33
42
|
const onScroll = (ev: ScrollEvent) => {
|
|
34
43
|
stick.current =
|
|
35
44
|
ev.scrollY + ev.viewportHeight >= ev.contentHeight - STICK_PX;
|
|
@@ -40,6 +49,7 @@ export function Transcript({ empty }: { empty?: ReactNode }): ReactElement {
|
|
|
40
49
|
<box
|
|
41
50
|
ref={pane}
|
|
42
51
|
onScroll={onScroll}
|
|
52
|
+
onViewport={onViewport}
|
|
43
53
|
style={{ flexGrow: 1, flexShrink: 1, minHeight: 0, overflow: 'scroll' }}
|
|
44
54
|
data-testname="agent-chat-transcript"
|
|
45
55
|
>
|
package/src/view/look.ts
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
import { useMemo } from 'react';
|
|
5
5
|
import { useTheme } from 'react-x11';
|
|
6
6
|
import { tint } from 'react-x11/style';
|
|
7
|
+
import {
|
|
8
|
+
codeBlockLook,
|
|
9
|
+
type CodeBlockLook,
|
|
10
|
+
} from '@react-x11/components/codeblock';
|
|
7
11
|
|
|
8
12
|
export interface ChatLook {
|
|
9
13
|
text: string;
|
|
@@ -40,6 +44,19 @@ export function useLook(): ChatLook {
|
|
|
40
44
|
}, [theme]);
|
|
41
45
|
}
|
|
42
46
|
|
|
47
|
+
/**
|
|
48
|
+
* What a block of code looks like here: what `<Markdown>` draws a fence
|
|
49
|
+
* with, at the chat's text size, so a fence the chat draws itself (to carry
|
|
50
|
+
* its actions) has the fill, radius, inset and spacing of one it does not.
|
|
51
|
+
*/
|
|
52
|
+
export function useCodeLook(fontSize: number | undefined): CodeBlockLook {
|
|
53
|
+
const theme = useTheme() as unknown as Record<string, unknown>;
|
|
54
|
+
return useMemo(
|
|
55
|
+
() => codeBlockLook(theme, fontSize ? { baseSize: fontSize } : {}),
|
|
56
|
+
[theme, fontSize],
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
43
60
|
/** At most `max` lines: the first and last halves, and how many were cut. */
|
|
44
61
|
export function clipLines(text: string, max = 200): string {
|
|
45
62
|
const lines = text.split('\n');
|