anentrypoint-design 0.0.329 → 0.0.331
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.js +18 -18
- package/package.json +1 -1
- package/src/components/chat-message-parts.js +37 -13
- package/src/components/freddie/runtime.js +11 -1
- package/src/components/freddie.js +94 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anentrypoint-design",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.331",
|
|
4
4
|
"description": "247420 design system SDK — webjsx + modified ripple-ui, single-file ESM bundle for reproducible use of the AnEntrypoint design.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/247420.js",
|
|
@@ -130,6 +130,15 @@ export function injectCodeCopy(container) {
|
|
|
130
130
|
const MD_STREAM_THROTTLE_MS = 120;
|
|
131
131
|
const MD_STREAM_MIN_DELTA_CHARS = 40;
|
|
132
132
|
|
|
133
|
+
// requestIdleCallback with a setTimeout fallback (Safari/non-browser test
|
|
134
|
+
// contexts lack the real API). A settled historical message's parse is not
|
|
135
|
+
// latency-critical the way a streaming turn's is — deferring it off the
|
|
136
|
+
// critical path keeps a session-load burst of N historical bubbles from
|
|
137
|
+
// racing N synchronous parses on the same tick.
|
|
138
|
+
const scheduleIdle = typeof requestIdleCallback === 'function'
|
|
139
|
+
? (fn) => requestIdleCallback(fn, { timeout: 500 })
|
|
140
|
+
: (fn) => setTimeout(fn, 0);
|
|
141
|
+
|
|
133
142
|
function MdNode(p) {
|
|
134
143
|
const refSink = (el) => {
|
|
135
144
|
if (!el) return;
|
|
@@ -164,19 +173,34 @@ function MdNode(p) {
|
|
|
164
173
|
// arrive (an empty bubble until the CDN import resolves reads as a
|
|
165
174
|
// hang); the resolved render swaps in sanitized markdown in place.
|
|
166
175
|
if (isMarkdownDegraded()) el.textContent = p.text || '';
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
176
|
+
function doParse() {
|
|
177
|
+
renderMarkdownCached(p.text || '').then((html) => {
|
|
178
|
+
// The element may have been recycled (webjsx applyDiff reused this
|
|
179
|
+
// DOM node for a different message) or detached by the time an
|
|
180
|
+
// idle-deferred parse resolves -- re-check the source key still
|
|
181
|
+
// matches before swapping innerHTML into what could now be a
|
|
182
|
+
// completely different message's bubble.
|
|
183
|
+
if (el.dataset.mdSrc !== srcKey) return;
|
|
184
|
+
const swap = () => { el.innerHTML = html; injectCodeCopy(el); };
|
|
185
|
+
// Don't blow away an active text selection inside this bubble mid-swap
|
|
186
|
+
// (e.g. the user is mid-copy while a stream tick settles). Defer the
|
|
187
|
+
// swap once, until the selection changes (cleared or moved elsewhere).
|
|
188
|
+
const sel = typeof window !== 'undefined' ? window.getSelection() : null;
|
|
189
|
+
if (sel && sel.anchorNode && el.contains(sel.anchorNode)) {
|
|
190
|
+
const onSelChange = () => { document.removeEventListener('selectionchange', onSelChange); swap(); };
|
|
191
|
+
document.addEventListener('selectionchange', onSelChange, { once: true });
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
swap();
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
// Streaming turns parse immediately (latency-critical: the user is
|
|
198
|
+
// watching this bubble grow). A settled historical message (no
|
|
199
|
+
// streamingCaret) is deferred to an idle slot -- not on the critical
|
|
200
|
+
// render path, so a page mounting many historical bubbles at once
|
|
201
|
+
// (session load) doesn't burst-parse them all synchronously.
|
|
202
|
+
if (p.streamingCaret) doParse();
|
|
203
|
+
else scheduleIdle(doParse);
|
|
180
204
|
};
|
|
181
205
|
return h('div', { class: 'chat-bubble chat-md', ref: refSink });
|
|
182
206
|
}
|
|
@@ -44,6 +44,7 @@ export function makePage(setup, { initial = {} } = {}) {
|
|
|
44
44
|
return function pageRenderer(host) {
|
|
45
45
|
const state = { loading: true, error: null, ...initial };
|
|
46
46
|
const timers = [];
|
|
47
|
+
const cleanupFns = [];
|
|
47
48
|
let elRef = null;
|
|
48
49
|
let render = () => h('div', {});
|
|
49
50
|
const ctx = {
|
|
@@ -51,7 +52,16 @@ export function makePage(setup, { initial = {} } = {}) {
|
|
|
51
52
|
set(patch) { Object.assign(state, patch); ctx.rerender(); },
|
|
52
53
|
rerender() { if (elRef) { try { applyDiff(elRef, wrap()); } catch (e) { console.warn('[freddie page rerender]', e); } } },
|
|
53
54
|
interval(fn, ms) { const id = setInterval(fn, ms); timers.push(id); return id; },
|
|
54
|
-
|
|
55
|
+
// Register arbitrary teardown (WebSocket close, event listener removal,
|
|
56
|
+
// etc) alongside the existing interval-only cleanup() -- same
|
|
57
|
+
// unmount trigger (elRef going null in ref()), just not limited to
|
|
58
|
+
// setInterval ids.
|
|
59
|
+
onCleanup(fn) { cleanupFns.push(fn); },
|
|
60
|
+
cleanup() {
|
|
61
|
+
for (const id of timers) clearInterval(id); timers.length = 0;
|
|
62
|
+
for (const fn of cleanupFns) { try { fn(); } catch (e) { console.warn('[freddie page cleanup]', e); } }
|
|
63
|
+
cleanupFns.length = 0;
|
|
64
|
+
},
|
|
55
65
|
};
|
|
56
66
|
function wrap() {
|
|
57
67
|
let body;
|
|
@@ -10,6 +10,7 @@ import { getRecentPaths, saveRecentPath, skillLabel, renderChatMessages } from '
|
|
|
10
10
|
import { Panel, Row, Table, Kpi, PageHeader, SearchInput, TextField, Select } from './content.js';
|
|
11
11
|
import { Chip, Btn, Icon } from './shell.js';
|
|
12
12
|
import { formatTime } from '../locale.js';
|
|
13
|
+
import { register as registerDebug, unregister as unregisterDebug } from '../debug.js';
|
|
13
14
|
import { queueMessage, watchReconnect, isOnline } from '../idb-outbox.js';
|
|
14
15
|
import { ChatMessage, ChatComposer } from './chat.js';
|
|
15
16
|
import { fmtTime, fmtAgo } from './sessions.js';
|
|
@@ -704,6 +705,98 @@ export const health = makePage((ctx) => {
|
|
|
704
705
|
|
|
705
706
|
// ---- debug -----------------------------------------------------------------
|
|
706
707
|
|
|
708
|
+
// ---- logs --------------------------------------------------------------
|
|
709
|
+
|
|
710
|
+
const LOG_SEVERITY_TONE = { error: 'error', warning: 'warning', info: 'accent', debug: 'muted' };
|
|
711
|
+
|
|
712
|
+
export const logs = makePage((ctx) => {
|
|
713
|
+
Object.assign(ctx.state, { lines: [], subsystems: [], activeSubsystem: '', activeSeverity: '', q: '', connected: false, wsError: null });
|
|
714
|
+
const MAX_LINES = 500;
|
|
715
|
+
|
|
716
|
+
async function loadSubsystems() {
|
|
717
|
+
try { ctx.set({ subsystems: await api('/api/logs') }); }
|
|
718
|
+
catch (e) { /* non-fatal: subsystem list is a filter convenience, not required for the stream */ }
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
let unmounted = false;
|
|
722
|
+
let reconnectTimer = null;
|
|
723
|
+
function connect() {
|
|
724
|
+
if (unmounted) return;
|
|
725
|
+
if (typeof WebSocket === 'undefined') { ctx.set({ wsError: new Error('WebSocket not available in this environment') }); return }
|
|
726
|
+
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
727
|
+
const ws = new WebSocket(proto + '//' + location.host + '/api/logs/stream');
|
|
728
|
+
ws.onopen = () => ctx.set({ connected: true, wsError: null });
|
|
729
|
+
ws.onerror = () => ctx.set({ connected: false, wsError: new Error('log stream connection error') });
|
|
730
|
+
ws.onclose = () => { ctx.set({ connected: false }); if (!unmounted) reconnectTimer = setTimeout(connect, 3000); };
|
|
731
|
+
ws.onmessage = (ev) => {
|
|
732
|
+
let rec; try { rec = JSON.parse(ev.data); } catch { return; }
|
|
733
|
+
const next = [rec, ...ctx.state.lines].slice(0, MAX_LINES);
|
|
734
|
+
ctx.set({ lines: next });
|
|
735
|
+
};
|
|
736
|
+
currentWs = ws;
|
|
737
|
+
}
|
|
738
|
+
let currentWs = null;
|
|
739
|
+
|
|
740
|
+
loadSubsystems();
|
|
741
|
+
connect();
|
|
742
|
+
registerDebug('logs', () => ({ connected: ctx.state.connected, lineCount: ctx.state.lines.length, activeSubsystem: ctx.state.activeSubsystem, activeSeverity: ctx.state.activeSeverity }));
|
|
743
|
+
ctx.onCleanup(() => {
|
|
744
|
+
unmounted = true;
|
|
745
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
746
|
+
try { currentWs?.close(); } catch {}
|
|
747
|
+
unregisterDebug('logs');
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
function filtered() {
|
|
751
|
+
const s = ctx.state;
|
|
752
|
+
return s.lines.filter((l) => {
|
|
753
|
+
if (s.activeSubsystem && l.subsystem !== s.activeSubsystem) return false;
|
|
754
|
+
if (s.activeSeverity && l.severity !== s.activeSeverity) return false;
|
|
755
|
+
if (s.q && !String(l.msg || '').toLowerCase().includes(s.q.toLowerCase())) return false;
|
|
756
|
+
return true;
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
return () => {
|
|
761
|
+
const s = ctx.state;
|
|
762
|
+
const rows = filtered();
|
|
763
|
+
const severities = ['error', 'warning', 'info', 'debug'];
|
|
764
|
+
return [
|
|
765
|
+
PageHeader({
|
|
766
|
+
eyebrow: 'freddie', title: 'logs', lede: 'live JSONL log tail — /api/logs/stream',
|
|
767
|
+
right: s.connected ? Chip({ tone: 'ok', children: 'live' }) : Chip({ tone: 'miss', children: 'reconnecting…' }),
|
|
768
|
+
}),
|
|
769
|
+
s.wsError ? refreshError(s.wsError) : null,
|
|
770
|
+
h('div', { class: 'ds-toolbar' },
|
|
771
|
+
SearchInput({ value: s.q, placeholder: 'filter by message…', onInput: (v) => ctx.set({ q: v }), resultCount: rows.length }),
|
|
772
|
+
Select({
|
|
773
|
+
label: 'subsystem', value: s.activeSubsystem, placeholder: 'all subsystems',
|
|
774
|
+
options: (s.subsystems || []).map((name) => ({ value: name, label: name })),
|
|
775
|
+
onChange: (v) => ctx.set({ activeSubsystem: v }),
|
|
776
|
+
}),
|
|
777
|
+
Select({
|
|
778
|
+
label: 'severity', value: s.activeSeverity, placeholder: 'all severities',
|
|
779
|
+
options: severities.map((sv) => ({ value: sv, label: sv })),
|
|
780
|
+
onChange: (v) => ctx.set({ activeSeverity: v }),
|
|
781
|
+
}),
|
|
782
|
+
),
|
|
783
|
+
rows.length ? section('lines · ' + rows.length,
|
|
784
|
+
Table({
|
|
785
|
+
headers: ['time', 'subsystem', 'severity', 'message'],
|
|
786
|
+
rows: rows.map((l) => [
|
|
787
|
+
formatTime(l.ts ? Date.parse(l.ts) : Date.now()),
|
|
788
|
+
l.subsystem || '—',
|
|
789
|
+
Chip({ tone: LOG_SEVERITY_TONE[l.severity] || 'dim', children: l.severity || 'info' }),
|
|
790
|
+
truncSpan(l.msg, TRUNC_DESC),
|
|
791
|
+
]),
|
|
792
|
+
}),
|
|
793
|
+
) : emptyState(s.connected ? 'no log lines yet — waiting for activity' : 'connecting to log stream…'),
|
|
794
|
+
].filter(Boolean);
|
|
795
|
+
};
|
|
796
|
+
});
|
|
797
|
+
|
|
798
|
+
// ---- debug -----------------------------------------------------------------
|
|
799
|
+
|
|
707
800
|
export const debug = makePage((ctx) => {
|
|
708
801
|
Object.assign(ctx.state, { sub: null, logs: null });
|
|
709
802
|
async function load() { try { ctx.set({ loading: false, data: await api('/api/debug'), error: null }); } catch (e) { ctx.set({ loading: false, error: e }); } }
|
|
@@ -734,7 +827,7 @@ export const debug = makePage((ctx) => {
|
|
|
734
827
|
export const FREDDIE_PAGES = {
|
|
735
828
|
home, chat, voice, sessions, projects, agents, analytics,
|
|
736
829
|
models, cron, skills, config, env, tools, batch, gateway, chains,
|
|
737
|
-
machines, health, debug,
|
|
830
|
+
machines, health, debug, logs,
|
|
738
831
|
};
|
|
739
832
|
|
|
740
833
|
export { skillLabel, getRecentPaths, saveRecentPath, renderChatMessages };
|