@xenosystem/agent-conversation 0.1.42 → 0.1.44
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/components/agent/AgentView.d.ts.map +1 -1
- package/dist/components/agent/AgentView.js +11 -2
- package/dist/components/agent/AgentView.js.map +1 -1
- package/dist/components/agent/transcript/ThreadScrubber.d.ts +45 -0
- package/dist/components/agent/transcript/ThreadScrubber.d.ts.map +1 -0
- package/dist/components/agent/transcript/ThreadScrubber.js +137 -0
- package/dist/components/agent/transcript/ThreadScrubber.js.map +1 -0
- package/package.json +5 -5
- package/src/transcript.css +31 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The thread scrubber — a map of the conversation on its right edge.
|
|
3
|
+
*
|
|
4
|
+
* One rectangular tick per message (a longer one for the person's, a short one for the reply), the
|
|
5
|
+
* tick under the viewport bright, ▲/▼ at the ends; hover a tick for who said it and its first line,
|
|
6
|
+
* click to go there. It appears once the thread scrolls in earnest (1.25 viewports) and stays
|
|
7
|
+
* a 6 px rail until hovered. Keys work without the rail: `Alt+↑ / Alt+↓` step between the person's
|
|
8
|
+
* turns, `Alt+Home / Alt+End` go to the first and last.
|
|
9
|
+
*
|
|
10
|
+
* Benchmarked 2026-09-18: Grok ships this natively; ChatGPT and Gemini leave a hole that a whole
|
|
11
|
+
* market of extensions fills (a bar per message, hover preview, click to jump, "my questions only").
|
|
12
|
+
* Two things those get wrong that this does not: the ticks are fed from STATE, never scraped from
|
|
13
|
+
* the DOM (Gemini's lazy-loaded thread makes a DOM-fed navigator lie about the thread's length), and
|
|
14
|
+
* the rail is decoration over a real `listbox` plus keys, so it is reachable without a pointer.
|
|
15
|
+
*
|
|
16
|
+
* Above the benchmark: a tick is MARKED when the turn did work — searched, edited files — so the
|
|
17
|
+
* rail is also a map of where the work happened, which no shipping rail shows.
|
|
18
|
+
*/
|
|
19
|
+
import { type RefObject } from 'react';
|
|
20
|
+
export interface ScrubberTurn {
|
|
21
|
+
id: string;
|
|
22
|
+
role: 'user' | 'assistant';
|
|
23
|
+
/** Who — "You", the model's name, the agent's name. */
|
|
24
|
+
label: string;
|
|
25
|
+
/** The first line of the message, for the preview card. */
|
|
26
|
+
firstLine: string;
|
|
27
|
+
/** The turn did work (searched, edited files) — its tick is marked. */
|
|
28
|
+
marked?: boolean;
|
|
29
|
+
}
|
|
30
|
+
export interface ThreadScrubberProps {
|
|
31
|
+
turns: ScrubberTurn[];
|
|
32
|
+
/** The scroll container the thread lives in. */
|
|
33
|
+
scrollerRef: RefObject<HTMLElement | null>;
|
|
34
|
+
/** The element of a turn, by id — defaults to `[data-turn="<id>"]` inside the scroller. */
|
|
35
|
+
elementFor?: (id: string, scroller: HTMLElement) => HTMLElement | null;
|
|
36
|
+
/** Show once the thread is this many viewports tall (default 1.25 — as soon as it scrolls in earnest). */
|
|
37
|
+
minViewports?: number;
|
|
38
|
+
/** Inline offsets for the rail's box, when the scroller carries a dock at its bottom. */
|
|
39
|
+
style?: React.CSSProperties;
|
|
40
|
+
className?: string;
|
|
41
|
+
}
|
|
42
|
+
/** The first non-empty line of a message, trimmed for the card. */
|
|
43
|
+
export declare function firstLineOf(text: string, max?: number): string;
|
|
44
|
+
export declare function ThreadScrubber({ turns, scrollerRef, elementFor, minViewports, style, className }: ThreadScrubberProps): import("react").JSX.Element | null;
|
|
45
|
+
//# sourceMappingURL=ThreadScrubber.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThreadScrubber.d.ts","sourceRoot":"","sources":["../../../../src/components/agent/transcript/ThreadScrubber.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAsE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAE1G,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,GAAG,WAAW,CAAA;IAC1B,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAA;IACb,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAA;IACjB,uEAAuE;IACvE,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,YAAY,EAAE,CAAA;IACrB,gDAAgD;IAChD,WAAW,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IAC1C,2FAA2F;IAC3F,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,KAAK,WAAW,GAAG,IAAI,CAAA;IACtE,0GAA0G;IAC1G,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,yFAAyF;IACzF,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAA;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAKD,mEAAmE;AACnE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,SAAK,GAAG,MAAM,CAG1D;AAED,wBAAgB,cAAc,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,UAA8B,EAAE,YAAmB,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE,mBAAmB,sCA2GhJ"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The thread scrubber — a map of the conversation on its right edge.
|
|
4
|
+
*
|
|
5
|
+
* One rectangular tick per message (a longer one for the person's, a short one for the reply), the
|
|
6
|
+
* tick under the viewport bright, ▲/▼ at the ends; hover a tick for who said it and its first line,
|
|
7
|
+
* click to go there. It appears once the thread scrolls in earnest (1.25 viewports) and stays
|
|
8
|
+
* a 6 px rail until hovered. Keys work without the rail: `Alt+↑ / Alt+↓` step between the person's
|
|
9
|
+
* turns, `Alt+Home / Alt+End` go to the first and last.
|
|
10
|
+
*
|
|
11
|
+
* Benchmarked 2026-09-18: Grok ships this natively; ChatGPT and Gemini leave a hole that a whole
|
|
12
|
+
* market of extensions fills (a bar per message, hover preview, click to jump, "my questions only").
|
|
13
|
+
* Two things those get wrong that this does not: the ticks are fed from STATE, never scraped from
|
|
14
|
+
* the DOM (Gemini's lazy-loaded thread makes a DOM-fed navigator lie about the thread's length), and
|
|
15
|
+
* the rail is decoration over a real `listbox` plus keys, so it is reachable without a pointer.
|
|
16
|
+
*
|
|
17
|
+
* Above the benchmark: a tick is MARKED when the turn did work — searched, edited files — so the
|
|
18
|
+
* rail is also a map of where the work happened, which no shipping rail shows.
|
|
19
|
+
*/
|
|
20
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
21
|
+
const defaultElementFor = (id, scroller) => scroller.querySelector(`[data-turn="${CSS.escape(id)}"]`);
|
|
22
|
+
/** The first non-empty line of a message, trimmed for the card. */
|
|
23
|
+
export function firstLineOf(text, max = 90) {
|
|
24
|
+
const line = (text || '').split(/\r?\n/).map((part) => part.replace(/^[#>*\-\s`]+/, '').trim()).find(Boolean) || '';
|
|
25
|
+
return line.length > max ? `${line.slice(0, max - 1).trimEnd()}…` : line;
|
|
26
|
+
}
|
|
27
|
+
export function ThreadScrubber({ turns, scrollerRef, elementFor = defaultElementFor, minViewports = 1.25, style, className }) {
|
|
28
|
+
const [tall, setTall] = useState(false);
|
|
29
|
+
const [current, setCurrent] = useState(null);
|
|
30
|
+
const [hover, setHover] = useState(null);
|
|
31
|
+
const visibleRef = useRef(new Set());
|
|
32
|
+
const order = useMemo(() => new Map(turns.map((turn, index) => [turn.id, index])), [turns]);
|
|
33
|
+
// tall enough to need a map? — measured, and re-measured as the thread grows or the pane resizes
|
|
34
|
+
useLayoutEffect(() => {
|
|
35
|
+
const scroller = scrollerRef.current;
|
|
36
|
+
if (!scroller)
|
|
37
|
+
return;
|
|
38
|
+
const measure = () => setTall(scroller.scrollHeight > minViewports * Math.max(1, scroller.clientHeight));
|
|
39
|
+
measure();
|
|
40
|
+
const observer = new ResizeObserver(measure);
|
|
41
|
+
observer.observe(scroller);
|
|
42
|
+
return () => observer.disconnect();
|
|
43
|
+
}, [scrollerRef, minViewports, turns.length]);
|
|
44
|
+
// which turn is under the viewport: the first visible one in thread order — no scroll handler
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
const scroller = scrollerRef.current;
|
|
47
|
+
if (!scroller || !tall)
|
|
48
|
+
return;
|
|
49
|
+
const elements = turns.map((turn) => [turn.id, elementFor(turn.id, scroller)]).filter((pair) => !!pair[1]);
|
|
50
|
+
const byElement = new Map(elements.map(([id, element]) => [element, id]));
|
|
51
|
+
visibleRef.current.clear();
|
|
52
|
+
const observer = new IntersectionObserver((entries) => {
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
const id = byElement.get(entry.target);
|
|
55
|
+
if (!id)
|
|
56
|
+
continue;
|
|
57
|
+
if (entry.isIntersecting)
|
|
58
|
+
visibleRef.current.add(id);
|
|
59
|
+
else
|
|
60
|
+
visibleRef.current.delete(id);
|
|
61
|
+
}
|
|
62
|
+
let first = null;
|
|
63
|
+
for (const turn of turns)
|
|
64
|
+
if (visibleRef.current.has(turn.id)) {
|
|
65
|
+
first = turn.id;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
setCurrent((previous) => (first ?? previous));
|
|
69
|
+
}, { root: scroller, threshold: 0, rootMargin: '-15% 0px -60% 0px' });
|
|
70
|
+
for (const [, element] of elements)
|
|
71
|
+
observer.observe(element);
|
|
72
|
+
return () => observer.disconnect();
|
|
73
|
+
}, [scrollerRef, elementFor, turns, tall]);
|
|
74
|
+
const goTo = useCallback((id) => {
|
|
75
|
+
const scroller = scrollerRef.current;
|
|
76
|
+
const element = scroller && elementFor(id, scroller);
|
|
77
|
+
if (!element)
|
|
78
|
+
return;
|
|
79
|
+
const reduced = typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
80
|
+
element.scrollIntoView({ behavior: reduced ? 'auto' : 'smooth', block: 'start' });
|
|
81
|
+
setCurrent(id);
|
|
82
|
+
}, [scrollerRef, elementFor]);
|
|
83
|
+
const step = useCallback((direction) => {
|
|
84
|
+
const users = turns.filter((turn) => turn.role === 'user');
|
|
85
|
+
if (!users.length)
|
|
86
|
+
return;
|
|
87
|
+
const at = current ? (order.get(current) ?? -1) : -1;
|
|
88
|
+
const next = direction > 0
|
|
89
|
+
? users.find((turn) => (order.get(turn.id) ?? 0) > at)
|
|
90
|
+
: [...users].reverse().find((turn) => (order.get(turn.id) ?? 0) < at);
|
|
91
|
+
if (next)
|
|
92
|
+
goTo(next.id);
|
|
93
|
+
}, [turns, current, order, goTo]);
|
|
94
|
+
// the keys are the real navigation; the rail is the picture of it
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
if (!tall)
|
|
97
|
+
return;
|
|
98
|
+
const onKey = (event) => {
|
|
99
|
+
if (!event.altKey || event.ctrlKey || event.metaKey)
|
|
100
|
+
return;
|
|
101
|
+
if (event.key === 'ArrowUp') {
|
|
102
|
+
event.preventDefault();
|
|
103
|
+
step(-1);
|
|
104
|
+
}
|
|
105
|
+
else if (event.key === 'ArrowDown') {
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
step(1);
|
|
108
|
+
}
|
|
109
|
+
else if (event.key === 'Home' && turns[0]) {
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
goTo(turns[0].id);
|
|
112
|
+
}
|
|
113
|
+
else if (event.key === 'End' && turns.length) {
|
|
114
|
+
event.preventDefault();
|
|
115
|
+
goTo(turns[turns.length - 1].id);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
window.addEventListener('keydown', onKey);
|
|
119
|
+
return () => window.removeEventListener('keydown', onKey);
|
|
120
|
+
}, [tall, step, goTo, turns]);
|
|
121
|
+
if (!tall || turns.length < 2)
|
|
122
|
+
return null;
|
|
123
|
+
const hovered = hover ? turns.find((turn) => turn.id === hover) : null;
|
|
124
|
+
return (_jsxs("div", { className: `xa-scrub${className ? ` ${className}` : ''}`, style: style, "data-scrubber": true, onMouseLeave: () => setHover(null), children: [_jsx("button", { type: "button", className: "xa-scrub-end", "aria-label": "Previous turn (Alt+\u2191)", onClick: () => step(-1), children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "square", "aria-hidden": "true", children: _jsx("path", { d: "M6 15l6-6 6 6" }) }) }), _jsx("div", { className: "xa-scrub-ticks", role: "listbox", "aria-label": "Conversation map", "aria-activedescendant": current ? `xa-tick-${current}` : undefined, children: turns.map((turn) => (_jsx("button", { type: "button", id: `xa-tick-${turn.id}`, role: "option", "aria-selected": turn.id === current, "aria-label": `${turn.label}: ${turn.firstLine}`, className: `xa-tick xa-tick-${turn.role}${turn.id === current ? ' xa-cur' : ''}${turn.marked ? ' xa-mark' : ''}`, "data-tick": turn.id, onMouseEnter: () => setHover(turn.id), onFocus: () => setHover(turn.id), onClick: () => goTo(turn.id) }, turn.id))) }), _jsx("button", { type: "button", className: "xa-scrub-end", "aria-label": "Next turn (Alt+\u2193)", onClick: () => step(1), children: _jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.4", strokeLinecap: "square", "aria-hidden": "true", children: _jsx("path", { d: "M6 9l6 6 6-6" }) }) }), hovered && (_jsxs("div", { className: "xa-scrub-card", role: "tooltip", style: { top: tickTop(hovered.id) }, children: [_jsx("span", { className: "xa-scrub-who", children: hovered.label }), _jsx("span", { className: "xa-scrub-line", children: hovered.firstLine || '…' })] }))] }));
|
|
125
|
+
}
|
|
126
|
+
/** The card sits beside the hovered tick — read from the DOM at render, which is where the tick is. */
|
|
127
|
+
function tickTop(id) {
|
|
128
|
+
if (typeof document === 'undefined')
|
|
129
|
+
return undefined;
|
|
130
|
+
const tick = document.getElementById(`xa-tick-${id}`);
|
|
131
|
+
const rail = tick?.closest('.xa-scrub');
|
|
132
|
+
if (!tick || !rail)
|
|
133
|
+
return undefined;
|
|
134
|
+
const top = tick.getBoundingClientRect().top - rail.getBoundingClientRect().top;
|
|
135
|
+
return Math.max(0, Math.min(top - 18, rail.clientHeight - 64));
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=ThreadScrubber.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ThreadScrubber.js","sourceRoot":"","sources":["../../../../src/components/agent/transcript/ThreadScrubber.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xenosystem/agent-conversation",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.44",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"description": "The XENO Agent conversation surface: the transcript, the composer, and the logic behind them.",
|
|
6
6
|
"type": "module",
|
|
@@ -43,10 +43,10 @@
|
|
|
43
43
|
"react-markdown": "^10.1.0",
|
|
44
44
|
"remark-gfm": "^4.0.1",
|
|
45
45
|
"zustand": "^5.0.2",
|
|
46
|
-
"@xenosystem/agent-interface-
|
|
47
|
-
"@xenosystem/agent-interface-
|
|
48
|
-
"@xenosystem/agent-interface-
|
|
49
|
-
"@xenosystem/agent-interface-
|
|
46
|
+
"@xenosystem/agent-interface-client": "0.1.44",
|
|
47
|
+
"@xenosystem/agent-interface-contract": "0.1.44",
|
|
48
|
+
"@xenosystem/agent-interface-core": "0.1.44",
|
|
49
|
+
"@xenosystem/agent-interface-hub-adapter": "0.1.44"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
52
|
"react": "^19.0.0",
|
package/src/transcript.css
CHANGED
|
@@ -512,6 +512,37 @@ img.xa-fav { padding: 1px; background: #fff; }
|
|
|
512
512
|
.xa-citecard-title { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; font-size: 12.5px; font-weight: 500; line-height: 1.3; color: var(--xa-t1); }
|
|
513
513
|
.xa-citecard-site { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--xa-t4); }
|
|
514
514
|
|
|
515
|
+
/* ── the thread scrubber: a map of the conversation on the right edge ─────────────────────
|
|
516
|
+
6 px of rail until hovered, then 14 px; rectangular ticks (never dots), the person's longer than
|
|
517
|
+
the reply's, the one under the viewport bright, a marked tick where the turn did work. The card
|
|
518
|
+
is the citation card's plate. Hidden below two viewports of thread — the component decides. */
|
|
519
|
+
.xa-scrub { position: absolute; top: 8px; right: 6px; bottom: 8px; z-index: 6; display: flex; flex-direction: column; align-items: flex-end; width: 22px; padding: 2px 0; opacity: 0.85; transition: opacity 160ms linear; }
|
|
520
|
+
.xa-scrub:hover, .xa-scrub:focus-within { opacity: 1; }
|
|
521
|
+
.xa-scrub-end { flex: none; width: 16px; height: 16px; margin-right: 1px; border-radius: 3px; color: var(--xa-t5); opacity: 0; transition: opacity 160ms linear, background 120ms linear, color 120ms linear; }
|
|
522
|
+
.xa-scrub:hover .xa-scrub-end, .xa-scrub:focus-within .xa-scrub-end { opacity: 1; }
|
|
523
|
+
.xa-scrub-end:hover { color: var(--xa-t2); background: var(--xa-wash-hover); }
|
|
524
|
+
.xa-scrub-end svg { width: 11px; height: 11px; }
|
|
525
|
+
.xa-scrub-ticks { flex: 1; min-height: 0; display: flex; flex-direction: column; justify-content: space-evenly; width: 100%; overflow: hidden; padding: 4px 0; }
|
|
526
|
+
/* the rail IS the scrollbar while it shows — Grok's move: two scroll affordances on one edge fight for the pointer */
|
|
527
|
+
.xa-app:has(> .xa-scrub) > .xa-transcript { scrollbar-width: none; }
|
|
528
|
+
.xa-app:has(> .xa-scrub) > .xa-transcript::-webkit-scrollbar { display: none; }
|
|
529
|
+
.xa-tick { flex: none; align-self: flex-end; height: 2px; border-radius: 1px; background: var(--xa-t4); opacity: 0.9; transition: width 160ms var(--xa-ease), background 120ms linear, opacity 120ms linear; cursor: pointer; padding: 0; box-sizing: content-box; }
|
|
530
|
+
.xa-tick-user { width: 12px; }
|
|
531
|
+
.xa-tick-assistant { width: 7px; opacity: 0.5; }
|
|
532
|
+
.xa-scrub:hover .xa-tick-user, .xa-scrub:focus-within .xa-tick-user { width: 18px; }
|
|
533
|
+
.xa-scrub:hover .xa-tick-assistant, .xa-scrub:focus-within .xa-tick-assistant { width: 11px; }
|
|
534
|
+
.xa-tick.xa-cur { background: var(--xa-t1); opacity: 1; height: 3px; }
|
|
535
|
+
.xa-tick.xa-mark { background: var(--xa-t2); opacity: 1; }
|
|
536
|
+
.xa-tick.xa-mark.xa-cur { background: var(--xa-t1); }
|
|
537
|
+
.xa-tick:hover, .xa-tick:focus-visible { background: var(--xa-t2); opacity: 1; outline: none; }
|
|
538
|
+
/* a bigger target than the tick itself */
|
|
539
|
+
.xa-tick::before { content: ''; position: absolute; right: 0; width: 22px; height: 9px; margin-top: -3.5px; }
|
|
540
|
+
.xa-tick { position: relative; }
|
|
541
|
+
.xa-scrub-card { position: absolute; right: 26px; z-index: 40; display: flex; flex-direction: column; gap: 3px; width: 240px; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--xa-line-tip); background: var(--xa-menu); box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35); pointer-events: none; }
|
|
542
|
+
.xa-scrub-who { font-size: 10.5px; color: var(--xa-t5); }
|
|
543
|
+
.xa-scrub-line { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; font-size: 12px; line-height: 1.35; color: var(--xa-t1); }
|
|
544
|
+
@media (prefers-reduced-motion: reduce) { .xa-tick, .xa-scrub, .xa-scrub-end { transition: none; } }
|
|
545
|
+
|
|
515
546
|
/* ── cards: A's card, Z's cadence — Cancel left, the way forward right ─── */
|
|
516
547
|
.xa-row .xa-brk { flex: 0 0 100%; height: 0; }
|
|
517
548
|
.xa-row .xa-brk + .xa-cardslot { margin-top: -9px; }
|