anentrypoint-design 0.0.385 → 0.0.387
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/247420.css +33 -0
- package/dist/247420.js +53 -51
- package/package.json +1 -1
- package/src/components/agent-chat/controls.js +143 -0
- package/src/components/agent-chat/empty-state.js +63 -0
- package/src/components/agent-chat/message-rows.js +141 -0
- package/src/components/agent-chat/surface.js +206 -0
- package/src/components/agent-chat/thread-behaviour.js +50 -0
- package/src/components/agent-chat.js +6 -546
- package/src/components/chat/composer-affordances.js +109 -0
- package/src/components/chat/composer.js +256 -0
- package/src/components/chat/threads.js +105 -0
- package/src/components/chat-message-parts/agent-nodes.js +103 -0
- package/src/components/chat-message-parts/inline.js +106 -0
- package/src/components/chat-message-parts/prose-nodes.js +133 -0
- package/src/components/chat-message-parts/renderers.js +89 -0
- package/src/components/chat-message-parts.js +12 -408
- package/src/components/chat-minimap/minimap.js +167 -0
- package/src/components/chat-minimap/paint.js +73 -0
- package/src/components/chat-minimap/preview.js +41 -0
- package/src/components/chat-minimap.js +7 -263
- package/src/components/chat.js +20 -628
- package/src/components/community/chrome.js +63 -0
- package/src/components/community/navigation.js +167 -0
- package/src/components/community/presence.js +106 -0
- package/src/components/community/shell.js +17 -0
- package/src/components/community/views.js +131 -0
- package/src/components/community.js +18 -447
- package/src/components/files/chrome.js +140 -0
- package/src/components/files/entries.js +156 -0
- package/src/components/files/grid-controls.js +63 -0
- package/src/components/files/grid.js +177 -0
- package/src/components/files/types.js +69 -0
- package/src/components/files-modals/dialogs.js +118 -0
- package/src/components/files-modals/modal-shell.js +153 -0
- package/src/components/files-modals/preview-bodies.js +129 -0
- package/src/components/files-modals/preview-containers.js +96 -0
- package/src/components/files-modals.js +14 -475
- package/src/components/files.js +15 -564
- package/src/components/freddie/pages-config.js +3 -94
- package/src/components/freddie/pages-models.js +97 -0
- package/src/components/freddie.js +2 -1
- package/src/components/interaction-primitives/mobile.js +25 -0
- package/src/components/interaction-primitives/pointer.js +214 -0
- package/src/components/interaction-primitives/reorderable.js +47 -0
- package/src/components/interaction-primitives/shortcuts.js +119 -0
- package/src/components/interaction-primitives.js +16 -388
- package/src/components/sessions/conversation-list.js +230 -0
- package/src/components/sessions/dashboard.js +202 -0
- package/src/components/sessions/detail-bits.js +49 -0
- package/src/components/sessions/format.js +42 -0
- package/src/components/sessions/session-card.js +92 -0
- package/src/components/sessions.js +16 -579
- package/src/components/voice/audio-cue.js +39 -0
- package/src/components/voice/capture.js +90 -0
- package/src/components/voice/playback.js +75 -0
- package/src/components/voice/settings-modal.js +104 -0
- package/src/components/voice.js +16 -293
- package/src/css/app-shell/base.css +23 -0
- package/src/css/app-shell/states-interactions.css +10 -0
- package/src/kits/os/freddie/chat-protocol.js +32 -0
- package/src/kits/os/freddie/chat-transport.js +178 -0
- package/src/kits/os/freddie/pages-chat.js +10 -180
- package/src/kits/os/shell-chrome.js +163 -0
- package/src/kits/os/shell-geometry.js +59 -0
- package/src/kits/os/shell.js +178 -335
- package/src/page-html/client-script.js +151 -0
- package/src/page-html/head-tags.js +59 -0
- package/src/page-html/markdown.js +68 -0
- package/src/page-html/page-styles.js +44 -0
- package/src/page-html.js +14 -291
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Pure DOM paint: rebuilds the minimap's children from current `state`. Kept
|
|
2
|
+
// outside webjsx's own vdom diff (this subtree is imperative, like a canvas)
|
|
3
|
+
// because dot count/positions and hover/tooltip visibility change far more
|
|
4
|
+
// often than a full component re-render is warranted for.
|
|
5
|
+
|
|
6
|
+
import { messagePreview } from './preview.js';
|
|
7
|
+
|
|
8
|
+
const TOOLTIP_HEIGHT = 22;
|
|
9
|
+
const TOOLTIP_GAP = 2;
|
|
10
|
+
const TOOLTIP_WIDTH = 200;
|
|
11
|
+
|
|
12
|
+
export function paintMinimap(el, state, messages, width) {
|
|
13
|
+
el.style.display = state.visible ? '' : 'none';
|
|
14
|
+
el.innerHTML = '';
|
|
15
|
+
if (!state.visible) return;
|
|
16
|
+
|
|
17
|
+
const viewportBox = document.createElement('div');
|
|
18
|
+
viewportBox.className = 'chat-minimap-viewport';
|
|
19
|
+
viewportBox.style.top = (state.scrollRatio * (1 - state.viewportRatio) * 100) + '%';
|
|
20
|
+
viewportBox.style.height = (state.viewportRatio * 100) + '%';
|
|
21
|
+
el.appendChild(viewportBox);
|
|
22
|
+
|
|
23
|
+
const centerLine = document.createElement('div');
|
|
24
|
+
centerLine.className = 'chat-minimap-centerline';
|
|
25
|
+
el.appendChild(centerLine);
|
|
26
|
+
|
|
27
|
+
const nodes = state.nodes;
|
|
28
|
+
let nearestIndex = null;
|
|
29
|
+
if (state.mouseYRatio != null && nodes.length) {
|
|
30
|
+
let best = 0;
|
|
31
|
+
for (let i = 1; i < nodes.length; i++) {
|
|
32
|
+
if (Math.abs(nodes[i].topRatio - state.mouseYRatio) < Math.abs(nodes[best].topRatio - state.mouseYRatio)) best = i;
|
|
33
|
+
}
|
|
34
|
+
nearestIndex = nodes[best].index;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (const node of nodes) {
|
|
38
|
+
const dot = document.createElement('div');
|
|
39
|
+
const isUser = node.msg && node.msg.role === 'user';
|
|
40
|
+
dot.className = 'chat-minimap-dot ' + (isUser ? 'is-user' : 'is-assistant') + (state.hovered && nearestIndex === node.index ? ' is-nearest' : '');
|
|
41
|
+
dot.style.top = (node.topRatio * 100) + '%';
|
|
42
|
+
el.appendChild(dot);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (state.hovered && nodes.length) {
|
|
46
|
+
const minimapHeightPx = el.clientHeight || 600;
|
|
47
|
+
const positions = nodes.map((n) => Math.round(n.topRatio * minimapHeightPx - TOOLTIP_HEIGHT / 2));
|
|
48
|
+
for (let pass = 0; pass < 10; pass++) {
|
|
49
|
+
for (let i = 1; i < positions.length; i++) {
|
|
50
|
+
const minTop = positions[i - 1] + TOOLTIP_HEIGHT + TOOLTIP_GAP;
|
|
51
|
+
if (positions[i] < minTop) positions[i] = minTop;
|
|
52
|
+
}
|
|
53
|
+
for (let i = positions.length - 2; i >= 0; i--) {
|
|
54
|
+
const maxTop = positions[i + 1] - TOOLTIP_HEIGHT - TOOLTIP_GAP;
|
|
55
|
+
if (positions[i] > maxTop) positions[i] = maxTop;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
for (let i = 0; i < positions.length; i++) {
|
|
59
|
+
positions[i] = Math.max(0, Math.min(minimapHeightPx - TOOLTIP_HEIGHT, positions[i]));
|
|
60
|
+
}
|
|
61
|
+
nodes.forEach((node, i) => {
|
|
62
|
+
const preview = messagePreview(node.msg);
|
|
63
|
+
if (!preview) return;
|
|
64
|
+
const isNearest = nearestIndex === node.index;
|
|
65
|
+
const tip = document.createElement('div');
|
|
66
|
+
tip.className = 'chat-minimap-tooltip' + (isNearest ? ' is-nearest' : '') + (node.msg.role === 'user' ? ' is-user' : ' is-assistant');
|
|
67
|
+
tip.style.top = positions[i] + 'px';
|
|
68
|
+
tip.style.width = TOOLTIP_WIDTH + 'px';
|
|
69
|
+
tip.textContent = preview;
|
|
70
|
+
el.appendChild(tip);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Message-shape helpers the minimap measures and labels against: preview-text
|
|
2
|
+
// extraction across the three message shapes this kit carries, the has-text
|
|
3
|
+
// and mapped-role filters that decide which turns get a dot, and the message
|
|
4
|
+
// element resolver.
|
|
5
|
+
|
|
6
|
+
export const PREVIEW_CHARS = 200;
|
|
7
|
+
|
|
8
|
+
// Extract a short preview string the same way upstream's getMessagePreview
|
|
9
|
+
// does: prefer flat `text`, then `content` (string or array-of-parts), then
|
|
10
|
+
// `parts` (this kit's structured shape) joined and trimmed.
|
|
11
|
+
export function messagePreview(m) {
|
|
12
|
+
if (!m) return '';
|
|
13
|
+
if (typeof m.text === 'string' && m.text) return m.text.slice(0, PREVIEW_CHARS);
|
|
14
|
+
if (typeof m.content === 'string' && m.content) return m.content.slice(0, PREVIEW_CHARS);
|
|
15
|
+
const partsSrc = Array.isArray(m.content) ? m.content : (Array.isArray(m.parts) ? m.parts : null);
|
|
16
|
+
if (partsSrc) {
|
|
17
|
+
const joined = partsSrc
|
|
18
|
+
.map((p) => (typeof p === 'string' ? p : (p && (p.text || (p.type === 'text' && p.text)) || '')))
|
|
19
|
+
.filter(Boolean)
|
|
20
|
+
.join(' ');
|
|
21
|
+
if (joined) return joined.slice(0, PREVIEW_CHARS);
|
|
22
|
+
}
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// True when a message carries any renderable text (dots skip empty/tool-only
|
|
27
|
+
// turns the way upstream's hasTextContent does).
|
|
28
|
+
export function hasTextContent(m) {
|
|
29
|
+
return !!messagePreview(m);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isMappedRole(role) {
|
|
33
|
+
return role === 'user' || role === 'assistant';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Resolve the DOM node for message index `i`, via the host's getter or the
|
|
37
|
+
// data-msg-index fallback.
|
|
38
|
+
export function resolveMessageEl(threadEl, getMessageEl, i) {
|
|
39
|
+
if (typeof getMessageEl === 'function') return getMessageEl(i) || null;
|
|
40
|
+
return threadEl.querySelector('[data-msg-index="' + i + '"]') || null;
|
|
41
|
+
}
|
|
@@ -42,268 +42,12 @@
|
|
|
42
42
|
// omitted, so a host that tags its message rows with
|
|
43
43
|
// data-msg-index="N" needs no extra wiring.
|
|
44
44
|
// width : minimap strip width in px (default 36, matches upstream).
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const PREVIEW_CHARS = 200;
|
|
51
|
-
const MIN_SCROLLABLE_PX = 20;
|
|
52
|
-
|
|
53
|
-
import * as webjsx from '../../vendor/webjsx/index.js';
|
|
54
|
-
const h = webjsx.createElement;
|
|
55
|
-
|
|
56
|
-
// Extract a short preview string the same way upstream's getMessagePreview
|
|
57
|
-
// does: prefer flat `text`, then `content` (string or array-of-parts), then
|
|
58
|
-
// `parts` (this kit's structured shape) joined and trimmed.
|
|
59
|
-
function messagePreview(m) {
|
|
60
|
-
if (!m) return '';
|
|
61
|
-
if (typeof m.text === 'string' && m.text) return m.text.slice(0, PREVIEW_CHARS);
|
|
62
|
-
if (typeof m.content === 'string' && m.content) return m.content.slice(0, PREVIEW_CHARS);
|
|
63
|
-
const partsSrc = Array.isArray(m.content) ? m.content : (Array.isArray(m.parts) ? m.parts : null);
|
|
64
|
-
if (partsSrc) {
|
|
65
|
-
const joined = partsSrc
|
|
66
|
-
.map((p) => (typeof p === 'string' ? p : (p && (p.text || (p.type === 'text' && p.text)) || '')))
|
|
67
|
-
.filter(Boolean)
|
|
68
|
-
.join(' ');
|
|
69
|
-
if (joined) return joined.slice(0, PREVIEW_CHARS);
|
|
70
|
-
}
|
|
71
|
-
return '';
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// True when a message carries any renderable text (dots skip empty/tool-only
|
|
75
|
-
// turns the way upstream's hasTextContent does).
|
|
76
|
-
function hasTextContent(m) {
|
|
77
|
-
return !!messagePreview(m);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function isMappedRole(role) {
|
|
81
|
-
return role === 'user' || role === 'assistant';
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Resolve the DOM node for message index `i`, via the host's getter or the
|
|
85
|
-
// data-msg-index fallback.
|
|
86
|
-
function resolveMessageEl(threadEl, getMessageEl, i) {
|
|
87
|
-
if (typeof getMessageEl === 'function') return getMessageEl(i) || null;
|
|
88
|
-
return threadEl.querySelector('[data-msg-index="' + i + '"]') || null;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function ChatMinimap({ messages = [], getThreadEl, getMessageEl, width = CHAT_MINIMAP_WIDTH } = {}) {
|
|
92
|
-
// All mutable state lives on the container element itself (webjsx factories
|
|
93
|
-
// are pure-render; the ref callback owns the imperative lifecycle, same
|
|
94
|
-
// pattern as makeThreadAutoScroll in chat.js).
|
|
95
|
-
const state = {
|
|
96
|
-
scrollRatio: 0,
|
|
97
|
-
viewportRatio: 1,
|
|
98
|
-
visible: false,
|
|
99
|
-
nodes: /** @type {Array<{topRatio:number, heightRatio:number, msg:any, index:number}>} */ ([]),
|
|
100
|
-
hovered: false,
|
|
101
|
-
mouseYRatio: null,
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const containerRef = (el) => {
|
|
105
|
-
if (!el) return;
|
|
106
|
-
if (el._dsMinimapCleanup) return; // already wired for this DOM node
|
|
107
|
-
let measureTimer = null;
|
|
108
|
-
let ro = null;
|
|
109
|
-
let threadEl = null;
|
|
110
|
-
let scrollListenerEl = null;
|
|
111
|
-
|
|
112
|
-
const render = () => paintMinimap(el, state, messages, width);
|
|
113
|
-
|
|
114
|
-
const updateScroll = () => {
|
|
115
|
-
const t = typeof getThreadEl === 'function' ? getThreadEl() : null;
|
|
116
|
-
if (!t) return;
|
|
117
|
-
const totalH = t.scrollHeight;
|
|
118
|
-
const clientH = t.clientHeight;
|
|
119
|
-
const scrollable = totalH - clientH;
|
|
120
|
-
state.visible = scrollable > MIN_SCROLLABLE_PX;
|
|
121
|
-
if (scrollable <= 0) {
|
|
122
|
-
state.scrollRatio = 0;
|
|
123
|
-
state.viewportRatio = 1;
|
|
124
|
-
} else {
|
|
125
|
-
state.scrollRatio = t.scrollTop / scrollable;
|
|
126
|
-
state.viewportRatio = clientH / totalH;
|
|
127
|
-
}
|
|
128
|
-
render();
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
const measureNodes = () => {
|
|
132
|
-
if (measureTimer) return; // throttled — one pending pass at a time
|
|
133
|
-
measureTimer = setTimeout(() => {
|
|
134
|
-
measureTimer = null;
|
|
135
|
-
const t = typeof getThreadEl === 'function' ? getThreadEl() : null;
|
|
136
|
-
if (!t) return;
|
|
137
|
-
const totalH = t.scrollHeight;
|
|
138
|
-
if (totalH <= 0) return;
|
|
139
|
-
const containerRect = t.getBoundingClientRect();
|
|
140
|
-
const newNodes = [];
|
|
141
|
-
for (let i = 0; i < messages.length; i++) {
|
|
142
|
-
const msg = messages[i];
|
|
143
|
-
if (!isMappedRole(msg && msg.role)) continue;
|
|
144
|
-
if (!hasTextContent(msg)) continue;
|
|
145
|
-
const msgEl = resolveMessageEl(t, getMessageEl, i);
|
|
146
|
-
if (!msgEl) continue;
|
|
147
|
-
const elRect = msgEl.getBoundingClientRect();
|
|
148
|
-
const top = elRect.top - containerRect.top + t.scrollTop;
|
|
149
|
-
newNodes.push({
|
|
150
|
-
topRatio: top / totalH,
|
|
151
|
-
heightRatio: elRect.height / totalH,
|
|
152
|
-
msg,
|
|
153
|
-
index: newNodes.length,
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
state.nodes = newNodes;
|
|
157
|
-
render();
|
|
158
|
-
}, MEASURE_THROTTLE_MS);
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const syncLayout = () => { updateScroll(); measureNodes(); };
|
|
162
|
-
|
|
163
|
-
// Rebinds scroll listener + ResizeObserver to whichever thread element
|
|
164
|
-
// getThreadEl currently resolves to (it may be null on first paint and
|
|
165
|
-
// become available once the thread's own ref fires).
|
|
166
|
-
const rebind = () => {
|
|
167
|
-
const t = typeof getThreadEl === 'function' ? getThreadEl() : null;
|
|
168
|
-
if (t === threadEl) return;
|
|
169
|
-
if (scrollListenerEl) scrollListenerEl.removeEventListener('scroll', updateScroll);
|
|
170
|
-
if (ro) { ro.disconnect(); ro = null; }
|
|
171
|
-
threadEl = t;
|
|
172
|
-
scrollListenerEl = t;
|
|
173
|
-
if (!t) return;
|
|
174
|
-
t.addEventListener('scroll', updateScroll, { passive: true });
|
|
175
|
-
ro = new ResizeObserver(syncLayout);
|
|
176
|
-
ro.observe(t);
|
|
177
|
-
if (t.firstElementChild) ro.observe(t.firstElementChild);
|
|
178
|
-
syncLayout();
|
|
179
|
-
};
|
|
180
|
-
rebind();
|
|
181
|
-
// Thread element may not exist yet on first mount; poll briefly (mirrors
|
|
182
|
-
// upstream's 50ms post-message-change settle) until it appears, then the
|
|
183
|
-
// ResizeObserver takes over for everything after.
|
|
184
|
-
const rebindPoll = setInterval(rebind, 200);
|
|
185
|
-
|
|
186
|
-
// Drag-to-scroll + click-to-jump on the strip itself.
|
|
187
|
-
let dragging = false;
|
|
188
|
-
const scrollToRatio = (viewportTopRatio) => {
|
|
189
|
-
const t = typeof getThreadEl === 'function' ? getThreadEl() : null;
|
|
190
|
-
if (!t) return;
|
|
191
|
-
const scrollable = t.scrollHeight - t.clientHeight;
|
|
192
|
-
if (scrollable <= 0) return;
|
|
193
|
-
const clamped = Math.max(0, Math.min(1 - state.viewportRatio, viewportTopRatio));
|
|
194
|
-
t.scrollTop = (clamped / (1 - state.viewportRatio)) * scrollable;
|
|
195
|
-
};
|
|
196
|
-
const ratioFromEvent = (ev) => {
|
|
197
|
-
const rect = el.getBoundingClientRect();
|
|
198
|
-
return (ev.clientY - rect.top) / rect.height;
|
|
199
|
-
};
|
|
200
|
-
const onMouseDown = (ev) => {
|
|
201
|
-
if (!state.visible) return;
|
|
202
|
-
dragging = true;
|
|
203
|
-
const clickRatio = ratioFromEvent(ev);
|
|
204
|
-
const grabOffset = clickRatio - state.scrollRatio * (1 - state.viewportRatio);
|
|
205
|
-
const insideBox = grabOffset >= 0 && grabOffset <= state.viewportRatio;
|
|
206
|
-
const offset = insideBox ? grabOffset : state.viewportRatio / 2;
|
|
207
|
-
scrollToRatio(clickRatio - offset);
|
|
208
|
-
const onMove = (mv) => { if (dragging) scrollToRatio(ratioFromEvent(mv) - offset); };
|
|
209
|
-
const onUp = () => { dragging = false; window.removeEventListener('mousemove', onMove); window.removeEventListener('mouseup', onUp); };
|
|
210
|
-
window.addEventListener('mousemove', onMove);
|
|
211
|
-
window.addEventListener('mouseup', onUp);
|
|
212
|
-
};
|
|
213
|
-
const onMouseEnter = () => { state.hovered = true; render(); };
|
|
214
|
-
const onMouseLeave = () => { state.hovered = false; state.mouseYRatio = null; render(); };
|
|
215
|
-
const onMouseMove = (ev) => { state.mouseYRatio = ratioFromEvent(ev); render(); };
|
|
216
|
-
el.addEventListener('mousedown', onMouseDown);
|
|
217
|
-
el.addEventListener('mouseenter', onMouseEnter);
|
|
218
|
-
el.addEventListener('mouseleave', onMouseLeave);
|
|
219
|
-
el.addEventListener('mousemove', onMouseMove);
|
|
220
|
-
|
|
221
|
-
el._dsMinimapCleanup = () => {
|
|
222
|
-
clearInterval(rebindPoll);
|
|
223
|
-
if (measureTimer) clearTimeout(measureTimer);
|
|
224
|
-
if (scrollListenerEl) scrollListenerEl.removeEventListener('scroll', updateScroll);
|
|
225
|
-
if (ro) ro.disconnect();
|
|
226
|
-
el.removeEventListener('mousedown', onMouseDown);
|
|
227
|
-
el.removeEventListener('mouseenter', onMouseEnter);
|
|
228
|
-
el.removeEventListener('mouseleave', onMouseLeave);
|
|
229
|
-
el.removeEventListener('mousemove', onMouseMove);
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
render();
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
return h('div', {
|
|
236
|
-
class: 'chat-minimap',
|
|
237
|
-
ref: containerRef,
|
|
238
|
-
role: 'navigation',
|
|
239
|
-
'aria-label': 'conversation scroll overview',
|
|
240
|
-
style: 'width:' + width + 'px',
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Pure DOM paint: rebuilds the minimap's children from current `state`. Kept
|
|
245
|
-
// outside webjsx's own vdom diff (this subtree is imperative, like a canvas)
|
|
246
|
-
// because dot count/positions and hover/tooltip visibility change far more
|
|
247
|
-
// often than a full component re-render is warranted for.
|
|
248
|
-
function paintMinimap(el, state, messages, width) {
|
|
249
|
-
el.style.display = state.visible ? '' : 'none';
|
|
250
|
-
el.innerHTML = '';
|
|
251
|
-
if (!state.visible) return;
|
|
252
|
-
|
|
253
|
-
const viewportBox = document.createElement('div');
|
|
254
|
-
viewportBox.className = 'chat-minimap-viewport';
|
|
255
|
-
viewportBox.style.top = (state.scrollRatio * (1 - state.viewportRatio) * 100) + '%';
|
|
256
|
-
viewportBox.style.height = (state.viewportRatio * 100) + '%';
|
|
257
|
-
el.appendChild(viewportBox);
|
|
258
|
-
|
|
259
|
-
const centerLine = document.createElement('div');
|
|
260
|
-
centerLine.className = 'chat-minimap-centerline';
|
|
261
|
-
el.appendChild(centerLine);
|
|
262
|
-
|
|
263
|
-
const nodes = state.nodes;
|
|
264
|
-
let nearestIndex = null;
|
|
265
|
-
if (state.mouseYRatio != null && nodes.length) {
|
|
266
|
-
let best = 0;
|
|
267
|
-
for (let i = 1; i < nodes.length; i++) {
|
|
268
|
-
if (Math.abs(nodes[i].topRatio - state.mouseYRatio) < Math.abs(nodes[best].topRatio - state.mouseYRatio)) best = i;
|
|
269
|
-
}
|
|
270
|
-
nearestIndex = nodes[best].index;
|
|
271
|
-
}
|
|
45
|
+
//
|
|
46
|
+
// This module is a barrel: the message-shape helpers, the imperative paint,
|
|
47
|
+
// and the component's own lifecycle live in single-responsibility submodules
|
|
48
|
+
// under ./chat-minimap/, and the public export surface here is unchanged —
|
|
49
|
+
// no consumer import needs to move.
|
|
272
50
|
|
|
273
|
-
|
|
274
|
-
const dot = document.createElement('div');
|
|
275
|
-
const isUser = node.msg && node.msg.role === 'user';
|
|
276
|
-
dot.className = 'chat-minimap-dot ' + (isUser ? 'is-user' : 'is-assistant') + (state.hovered && nearestIndex === node.index ? ' is-nearest' : '');
|
|
277
|
-
dot.style.top = (node.topRatio * 100) + '%';
|
|
278
|
-
el.appendChild(dot);
|
|
279
|
-
}
|
|
51
|
+
import { ChatMinimap, CHAT_MINIMAP_WIDTH } from './chat-minimap/minimap.js';
|
|
280
52
|
|
|
281
|
-
|
|
282
|
-
const minimapHeightPx = el.clientHeight || 600;
|
|
283
|
-
const positions = nodes.map((n) => Math.round(n.topRatio * minimapHeightPx - TOOLTIP_HEIGHT / 2));
|
|
284
|
-
for (let pass = 0; pass < 10; pass++) {
|
|
285
|
-
for (let i = 1; i < positions.length; i++) {
|
|
286
|
-
const minTop = positions[i - 1] + TOOLTIP_HEIGHT + TOOLTIP_GAP;
|
|
287
|
-
if (positions[i] < minTop) positions[i] = minTop;
|
|
288
|
-
}
|
|
289
|
-
for (let i = positions.length - 2; i >= 0; i--) {
|
|
290
|
-
const maxTop = positions[i + 1] - TOOLTIP_HEIGHT - TOOLTIP_GAP;
|
|
291
|
-
if (positions[i] > maxTop) positions[i] = maxTop;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
for (let i = 0; i < positions.length; i++) {
|
|
295
|
-
positions[i] = Math.max(0, Math.min(minimapHeightPx - TOOLTIP_HEIGHT, positions[i]));
|
|
296
|
-
}
|
|
297
|
-
nodes.forEach((node, i) => {
|
|
298
|
-
const preview = messagePreview(node.msg);
|
|
299
|
-
if (!preview) return;
|
|
300
|
-
const isNearest = nearestIndex === node.index;
|
|
301
|
-
const tip = document.createElement('div');
|
|
302
|
-
tip.className = 'chat-minimap-tooltip' + (isNearest ? ' is-nearest' : '') + (node.msg.role === 'user' ? ' is-user' : ' is-assistant');
|
|
303
|
-
tip.style.top = positions[i] + 'px';
|
|
304
|
-
tip.style.width = TOOLTIP_WIDTH + 'px';
|
|
305
|
-
tip.textContent = preview;
|
|
306
|
-
el.appendChild(tip);
|
|
307
|
-
});
|
|
308
|
-
}
|
|
309
|
-
}
|
|
53
|
+
export { ChatMinimap, CHAT_MINIMAP_WIDTH };
|