@oyehello/chat 0.5.2 → 0.6.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/LICENSE +1 -1
- package/README.md +7 -5
- package/dist/index.cjs +116 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.global.js +116 -8
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +116 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -61,6 +61,16 @@ async function postMessage(opts) {
|
|
|
61
61
|
});
|
|
62
62
|
if (!res.ok) throw await asError(res, res.status === 401 ? "session_expired" : void 0);
|
|
63
63
|
}
|
|
64
|
+
async function getHistory(opts) {
|
|
65
|
+
const res = await opts.fetchImpl(`${opts.baseUrl}/chat/sessions/${opts.sessionId}/messages`, {
|
|
66
|
+
method: "GET",
|
|
67
|
+
credentials: "omit",
|
|
68
|
+
headers: { Authorization: `Bearer ${opts.sessionToken}` }
|
|
69
|
+
});
|
|
70
|
+
if (!res.ok) throw await asError(res, res.status === 401 ? "session_expired" : void 0);
|
|
71
|
+
const body = await res.json();
|
|
72
|
+
return body.messages;
|
|
73
|
+
}
|
|
64
74
|
async function postPageViews(opts) {
|
|
65
75
|
const res = await opts.fetchImpl(`${opts.baseUrl}/chat/sessions/${opts.sessionId}/page-views`, {
|
|
66
76
|
method: "POST",
|
|
@@ -317,17 +327,21 @@ var isSafeHttpUrl = (u) => {
|
|
|
317
327
|
return false;
|
|
318
328
|
}
|
|
319
329
|
};
|
|
330
|
+
var AFTER_RESULT = "__ohAfterResult";
|
|
320
331
|
var BUILTIN_TOOL_HANDLERS = {
|
|
321
332
|
navigate: (args) => {
|
|
322
333
|
const path = String(args.path ?? "");
|
|
323
334
|
if (!isSafeRelativePath(path)) return { ok: false, error: "unsafe navigate path" };
|
|
324
|
-
window.location.
|
|
325
|
-
|
|
335
|
+
const here = window.location.pathname + window.location.search + window.location.hash;
|
|
336
|
+
const target = new URL(path, window.location.href);
|
|
337
|
+
if (target.pathname + target.search + target.hash === here) return { ok: true, note: "already on this page" };
|
|
338
|
+
return { ok: true, [AFTER_RESULT]: () => window.location.assign(path) };
|
|
326
339
|
},
|
|
327
340
|
openUrl: (args) => {
|
|
328
341
|
const url = String(args.url ?? "");
|
|
329
342
|
if (!isSafeHttpUrl(url)) return { ok: false, error: "unsafe url" };
|
|
330
|
-
|
|
343
|
+
const sameHost = new URL(url, window.location.origin).hostname === window.location.hostname;
|
|
344
|
+
window.open(url, sameHost ? "_self" : "_blank", "noopener,noreferrer");
|
|
331
345
|
return { ok: true };
|
|
332
346
|
}
|
|
333
347
|
};
|
|
@@ -439,6 +453,23 @@ function createChatClient(options) {
|
|
|
439
453
|
} catch {
|
|
440
454
|
}
|
|
441
455
|
};
|
|
456
|
+
const callsKey = `oyehello-chat-calls:${options.agentId}`;
|
|
457
|
+
const handledCallIds = () => {
|
|
458
|
+
try {
|
|
459
|
+
const raw = store2?.getItem(callsKey);
|
|
460
|
+
const v = raw ? JSON.parse(raw) : null;
|
|
461
|
+
return v && session && v.sessionId === session.sessionId && Array.isArray(v.ids) ? v.ids : [];
|
|
462
|
+
} catch {
|
|
463
|
+
return [];
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
const rememberHandledCall = (callId) => {
|
|
467
|
+
if (!session) return;
|
|
468
|
+
try {
|
|
469
|
+
store2?.setItem(callsKey, JSON.stringify({ sessionId: session.sessionId, ids: [...handledCallIds(), callId].slice(-50) }));
|
|
470
|
+
} catch {
|
|
471
|
+
}
|
|
472
|
+
};
|
|
442
473
|
const emit = (e) => {
|
|
443
474
|
for (const l of listeners) l(e);
|
|
444
475
|
};
|
|
@@ -487,8 +518,9 @@ function createChatClient(options) {
|
|
|
487
518
|
} else if (ev.type === "error") {
|
|
488
519
|
emit({ type: "error", error: new Error(ev.message) });
|
|
489
520
|
} else if (ev.type === "tool_call") {
|
|
490
|
-
if (seenCallIds.has(ev.callId)) return;
|
|
521
|
+
if (seenCallIds.has(ev.callId) || handledCallIds().includes(ev.callId)) return;
|
|
491
522
|
seenCallIds.add(ev.callId);
|
|
523
|
+
rememberHandledCall(ev.callId);
|
|
492
524
|
const handler = toolHandlers.get(ev.name) ?? BUILTIN_TOOL_HANDLERS[ev.name];
|
|
493
525
|
void (async () => {
|
|
494
526
|
if (!session) return;
|
|
@@ -498,8 +530,16 @@ function createChatClient(options) {
|
|
|
498
530
|
return;
|
|
499
531
|
}
|
|
500
532
|
try {
|
|
501
|
-
const
|
|
533
|
+
const raw = await handler(ev.args);
|
|
534
|
+
let after = null;
|
|
535
|
+
let out = raw;
|
|
536
|
+
if (raw && typeof raw === "object" && typeof raw[AFTER_RESULT] === "function") {
|
|
537
|
+
const { [AFTER_RESULT]: run, ...rest } = raw;
|
|
538
|
+
after = run;
|
|
539
|
+
out = rest;
|
|
540
|
+
}
|
|
502
541
|
await postToolResult({ ...base, ok: true, result: typeof out === "string" ? out : JSON.stringify(out ?? { ok: true }) });
|
|
542
|
+
after?.();
|
|
503
543
|
} catch (err) {
|
|
504
544
|
await postToolResult({ ...base, ok: false, result: err.message });
|
|
505
545
|
}
|
|
@@ -581,6 +621,16 @@ function createChatClient(options) {
|
|
|
581
621
|
if (resumed) {
|
|
582
622
|
session = resumed;
|
|
583
623
|
emit({ type: "agent", agent: resumed.agent });
|
|
624
|
+
try {
|
|
625
|
+
const history2 = await getHistory({ baseUrl: options.baseUrl, sessionId: resumed.sessionId, sessionToken: resumed.sessionToken, fetchImpl });
|
|
626
|
+
for (const h of history2) {
|
|
627
|
+
const turnId = h.role === "assistant" || h.role === "human" ? h.turnId : void 0;
|
|
628
|
+
messages.push({ id: uid("m"), role: h.role, text: h.text, streaming: false, turnId });
|
|
629
|
+
if (h.turnId) seenTurnIds.add(h.turnId);
|
|
630
|
+
}
|
|
631
|
+
emitMessages();
|
|
632
|
+
} catch {
|
|
633
|
+
}
|
|
584
634
|
await openStream();
|
|
585
635
|
scheduleRefresh();
|
|
586
636
|
flushJourney();
|
|
@@ -721,6 +771,44 @@ function createChatClient(options) {
|
|
|
721
771
|
}
|
|
722
772
|
};
|
|
723
773
|
}
|
|
774
|
+
|
|
775
|
+
// src/page-theme.ts
|
|
776
|
+
var WATCHED = ["class", "data-theme", "data-mode", "style"];
|
|
777
|
+
function themeOf(node) {
|
|
778
|
+
for (const attr of ["data-theme", "data-mode"]) {
|
|
779
|
+
const v = node.getAttribute(attr);
|
|
780
|
+
if (v === "dark" || v === "light") return v;
|
|
781
|
+
}
|
|
782
|
+
if (node.classList.contains("dark")) return "dark";
|
|
783
|
+
if (node.classList.contains("light")) return "light";
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
function resolvePageTheme(el) {
|
|
787
|
+
for (let node = el.parentElement; node; node = node.parentElement) {
|
|
788
|
+
const t = themeOf(node);
|
|
789
|
+
if (t) return t;
|
|
790
|
+
}
|
|
791
|
+
const scheme = (document.documentElement.style.colorScheme || getComputedStyle(document.documentElement).colorScheme || "").trim();
|
|
792
|
+
return scheme === "dark" || scheme === "light" ? scheme : null;
|
|
793
|
+
}
|
|
794
|
+
function watchPageTheme(el, onChange) {
|
|
795
|
+
let last;
|
|
796
|
+
const check = () => {
|
|
797
|
+
const t = resolvePageTheme(el);
|
|
798
|
+
if (t !== last) {
|
|
799
|
+
last = t;
|
|
800
|
+
onChange(t);
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
check();
|
|
804
|
+
if (typeof MutationObserver === "undefined") return () => {
|
|
805
|
+
};
|
|
806
|
+
const mo = new MutationObserver(check);
|
|
807
|
+
for (let node = el.parentElement; node; node = node.parentElement) {
|
|
808
|
+
mo.observe(node, { attributes: true, attributeFilter: WATCHED });
|
|
809
|
+
}
|
|
810
|
+
return () => mo.disconnect();
|
|
811
|
+
}
|
|
724
812
|
marked.setOptions({ gfm: true, breaks: true });
|
|
725
813
|
var hookInstalled = false;
|
|
726
814
|
function installLinkHook() {
|
|
@@ -803,11 +891,15 @@ var STYLES = `
|
|
|
803
891
|
* body children (oyehello-chat { display:none }) can't silently kill the widget. Also pinned
|
|
804
892
|
* inline with !important at mount (see element.ts) to beat an external !important rule. */
|
|
805
893
|
display: block; visibility: visible; opacity: 1;
|
|
894
|
+
/* The closed panel still takes up its full size in the layout (it fades, it is not removed), so
|
|
895
|
+
* the host box covers ~384x620px of the page. The host and its wrapper must not catch clicks;
|
|
896
|
+
* only the visible parts opt back in (launcher, teaser while shown, panel while open). */
|
|
897
|
+
pointer-events: none;
|
|
806
898
|
font-family: 'Geist', system-ui, -apple-system, 'Helvetica Neue', sans-serif;
|
|
807
899
|
-webkit-font-smoothing: antialiased;
|
|
808
900
|
}
|
|
809
901
|
@media (prefers-color-scheme: dark) {
|
|
810
|
-
:host(:not([theme="light"])) {
|
|
902
|
+
:host(:not([theme="light"]):not([data-oc-page-theme="light"])) {
|
|
811
903
|
--oc-bg: #000000; --oc-bg-sunk: #000000;
|
|
812
904
|
--oc-fg: #FFFFFF; --oc-fg-muted: rgba(255,255,255,0.68); --oc-fg-dim: rgba(255,255,255,0.42);
|
|
813
905
|
--oc-border: rgba(255,255,255,0.09);
|
|
@@ -817,7 +909,9 @@ var STYLES = `
|
|
|
817
909
|
--oc-glass-border: rgba(255,255,255,0.12);
|
|
818
910
|
}
|
|
819
911
|
}
|
|
820
|
-
|
|
912
|
+
/* Explicit dark, or auto on a page that declares dark (data-oc-page-theme, set by page-theme.ts). The OS rule above
|
|
913
|
+
* only applies when the page declares nothing; a page that declares light keeps the light base. */
|
|
914
|
+
:host([theme="dark"]), :host(:not([theme="light"])[data-oc-page-theme="dark"]) {
|
|
821
915
|
--oc-bg: #000000; --oc-bg-sunk: #000000;
|
|
822
916
|
--oc-fg: #FFFFFF; --oc-fg-muted: rgba(255,255,255,0.68); --oc-fg-dim: rgba(255,255,255,0.42);
|
|
823
917
|
--oc-border: rgba(255,255,255,0.09);
|
|
@@ -829,7 +923,7 @@ var STYLES = `
|
|
|
829
923
|
* { box-sizing: border-box; }
|
|
830
924
|
button { font-family: inherit; }
|
|
831
925
|
|
|
832
|
-
.oc { display: flex; flex-direction: column; align-items: flex-end; gap: 14px; }
|
|
926
|
+
.oc { display: flex; flex-direction: column; align-items: flex-end; gap: 14px; pointer-events: none; }
|
|
833
927
|
|
|
834
928
|
/* \u2500\u2500 Launcher \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
|
|
835
929
|
.launcher {
|
|
@@ -838,6 +932,7 @@ var STYLES = `
|
|
|
838
932
|
box-shadow: 0 8px 22px -8px color-mix(in oklab, var(--oc-ac) 70%, transparent), 0 18px 48px -18px rgba(0,0,0,0.6);
|
|
839
933
|
color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer;
|
|
840
934
|
transition: transform .26s var(--oc-bounce), filter .2s var(--oc-ease); align-self: flex-end;
|
|
935
|
+
pointer-events: auto;
|
|
841
936
|
}
|
|
842
937
|
.launcher:hover { transform: scale(1.06); }
|
|
843
938
|
.launcher:active { transform: scale(0.93); }
|
|
@@ -1205,6 +1300,7 @@ var OyehelloChatElement = class extends HTMLElement {
|
|
|
1205
1300
|
// Uninstaller for the SPA history-navigation hooks (journey tracking), installed in
|
|
1206
1301
|
// connectedCallback and torn down in disconnectedCallback alongside everything else.
|
|
1207
1302
|
uninstallSpaHooks = null;
|
|
1303
|
+
unwatchPageTheme = null;
|
|
1208
1304
|
// ── shadow refs ──
|
|
1209
1305
|
oc;
|
|
1210
1306
|
bodyEl;
|
|
@@ -1238,6 +1334,11 @@ var OyehelloChatElement = class extends HTMLElement {
|
|
|
1238
1334
|
this.uninstallSpaHooks = null;
|
|
1239
1335
|
}
|
|
1240
1336
|
this.renderShell();
|
|
1337
|
+
this.unwatchPageTheme?.();
|
|
1338
|
+
this.unwatchPageTheme = watchPageTheme(this, (t) => {
|
|
1339
|
+
if (t) this.setAttribute("data-oc-page-theme", t);
|
|
1340
|
+
else this.removeAttribute("data-oc-page-theme");
|
|
1341
|
+
});
|
|
1241
1342
|
this.style.setProperty("display", "block", "important");
|
|
1242
1343
|
this.style.setProperty("visibility", "visible", "important");
|
|
1243
1344
|
this.style.setProperty("opacity", "1", "important");
|
|
@@ -1348,6 +1449,8 @@ var OyehelloChatElement = class extends HTMLElement {
|
|
|
1348
1449
|
this.teardownIdle();
|
|
1349
1450
|
this.uninstallSpaHooks?.();
|
|
1350
1451
|
this.uninstallSpaHooks = null;
|
|
1452
|
+
this.unwatchPageTheme?.();
|
|
1453
|
+
this.unwatchPageTheme = null;
|
|
1351
1454
|
for (const t of this.timers) clearTimeout(t);
|
|
1352
1455
|
for (const url of this.imagePreviewCache.values()) {
|
|
1353
1456
|
if (typeof URL.revokeObjectURL === "function") URL.revokeObjectURL(url);
|
|
@@ -1358,6 +1461,11 @@ var OyehelloChatElement = class extends HTMLElement {
|
|
|
1358
1461
|
if (this.client) this.client.registerTools(handlers);
|
|
1359
1462
|
else Object.assign(this.pendingTools, handlers);
|
|
1360
1463
|
}
|
|
1464
|
+
/** Open the chat window, exactly as a launcher click does. No-op if already open or not rendered. */
|
|
1465
|
+
open() {
|
|
1466
|
+
if (!this.oc || this.uiOpen) return;
|
|
1467
|
+
this.openPanel();
|
|
1468
|
+
}
|
|
1361
1469
|
key(k) {
|
|
1362
1470
|
return `oyehello-chat:${k}:${this.getAttribute("agent-id") ?? ""}`;
|
|
1363
1471
|
}
|