@oyehello/chat 0.5.1 → 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 +24 -12
- package/dist/index.cjs +139 -7
- 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 +139 -7
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +139 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -192,6 +192,7 @@ declare class OyehelloChatElement extends HTMLElement {
|
|
|
192
192
|
private idleTimer;
|
|
193
193
|
private idleReset;
|
|
194
194
|
private uninstallSpaHooks;
|
|
195
|
+
private unwatchPageTheme;
|
|
195
196
|
private oc;
|
|
196
197
|
private bodyEl;
|
|
197
198
|
private textareaEl;
|
|
@@ -209,6 +210,8 @@ declare class OyehelloChatElement extends HTMLElement {
|
|
|
209
210
|
private applyBrandLogo;
|
|
210
211
|
disconnectedCallback(): void;
|
|
211
212
|
registerTools(handlers: Record<string, ChatToolHandler>): void;
|
|
213
|
+
/** Open the chat window, exactly as a launcher click does. No-op if already open or not rendered. */
|
|
214
|
+
open(): void;
|
|
212
215
|
private key;
|
|
213
216
|
private get attrSuggestions();
|
|
214
217
|
private get chipLabels();
|
package/dist/index.d.ts
CHANGED
|
@@ -192,6 +192,7 @@ declare class OyehelloChatElement extends HTMLElement {
|
|
|
192
192
|
private idleTimer;
|
|
193
193
|
private idleReset;
|
|
194
194
|
private uninstallSpaHooks;
|
|
195
|
+
private unwatchPageTheme;
|
|
195
196
|
private oc;
|
|
196
197
|
private bodyEl;
|
|
197
198
|
private textareaEl;
|
|
@@ -209,6 +210,8 @@ declare class OyehelloChatElement extends HTMLElement {
|
|
|
209
210
|
private applyBrandLogo;
|
|
210
211
|
disconnectedCallback(): void;
|
|
211
212
|
registerTools(handlers: Record<string, ChatToolHandler>): void;
|
|
213
|
+
/** Open the chat window, exactly as a launcher click does. No-op if already open or not rendered. */
|
|
214
|
+
open(): void;
|
|
212
215
|
private key;
|
|
213
216
|
private get attrSuggestions();
|
|
214
217
|
private get chipLabels();
|
package/dist/index.global.js
CHANGED
|
@@ -61,6 +61,16 @@ var OyehelloChat = (function (exports) {
|
|
|
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",
|
|
@@ -308,6 +318,33 @@ var OyehelloChat = (function (exports) {
|
|
|
308
318
|
// src/client.ts
|
|
309
319
|
var seq = 0;
|
|
310
320
|
var uid = (p) => `${p}_${Date.now().toString(36)}_${(seq++).toString(36)}`;
|
|
321
|
+
var isSafeRelativePath = (p) => p.startsWith("/") && !p.startsWith("//");
|
|
322
|
+
var isSafeHttpUrl = (u3) => {
|
|
323
|
+
try {
|
|
324
|
+
const parsed = new URL(u3, window.location.origin);
|
|
325
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
326
|
+
} catch {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
var AFTER_RESULT = "__ohAfterResult";
|
|
331
|
+
var BUILTIN_TOOL_HANDLERS = {
|
|
332
|
+
navigate: (args) => {
|
|
333
|
+
const path = String(args.path ?? "");
|
|
334
|
+
if (!isSafeRelativePath(path)) return { ok: false, error: "unsafe navigate path" };
|
|
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) };
|
|
339
|
+
},
|
|
340
|
+
openUrl: (args) => {
|
|
341
|
+
const url = String(args.url ?? "");
|
|
342
|
+
if (!isSafeHttpUrl(url)) return { ok: false, error: "unsafe url" };
|
|
343
|
+
const sameHost = new URL(url, window.location.origin).hostname === window.location.hostname;
|
|
344
|
+
window.open(url, sameHost ? "_self" : "_blank", "noopener,noreferrer");
|
|
345
|
+
return { ok: true };
|
|
346
|
+
}
|
|
347
|
+
};
|
|
311
348
|
function createChatClient(options) {
|
|
312
349
|
const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
313
350
|
const ES = options.eventSource ?? globalThis.EventSource;
|
|
@@ -416,6 +453,23 @@ var OyehelloChat = (function (exports) {
|
|
|
416
453
|
} catch {
|
|
417
454
|
}
|
|
418
455
|
};
|
|
456
|
+
const callsKey = `oyehello-chat-calls:${options.agentId}`;
|
|
457
|
+
const handledCallIds = () => {
|
|
458
|
+
try {
|
|
459
|
+
const raw = store2?.getItem(callsKey);
|
|
460
|
+
const v2 = raw ? JSON.parse(raw) : null;
|
|
461
|
+
return v2 && session && v2.sessionId === session.sessionId && Array.isArray(v2.ids) ? v2.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
|
+
};
|
|
419
473
|
const emit = (e) => {
|
|
420
474
|
for (const l of listeners) l(e);
|
|
421
475
|
};
|
|
@@ -464,9 +518,10 @@ var OyehelloChat = (function (exports) {
|
|
|
464
518
|
} else if (ev.type === "error") {
|
|
465
519
|
emit({ type: "error", error: new Error(ev.message) });
|
|
466
520
|
} else if (ev.type === "tool_call") {
|
|
467
|
-
if (seenCallIds.has(ev.callId)) return;
|
|
521
|
+
if (seenCallIds.has(ev.callId) || handledCallIds().includes(ev.callId)) return;
|
|
468
522
|
seenCallIds.add(ev.callId);
|
|
469
|
-
|
|
523
|
+
rememberHandledCall(ev.callId);
|
|
524
|
+
const handler = toolHandlers.get(ev.name) ?? BUILTIN_TOOL_HANDLERS[ev.name];
|
|
470
525
|
void (async () => {
|
|
471
526
|
if (!session) return;
|
|
472
527
|
const base = { baseUrl: options.baseUrl, sessionId: session.sessionId, sessionToken: session.sessionToken, callId: ev.callId, fetchImpl };
|
|
@@ -475,8 +530,16 @@ var OyehelloChat = (function (exports) {
|
|
|
475
530
|
return;
|
|
476
531
|
}
|
|
477
532
|
try {
|
|
478
|
-
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
|
+
}
|
|
479
541
|
await postToolResult({ ...base, ok: true, result: typeof out === "string" ? out : JSON.stringify(out ?? { ok: true }) });
|
|
542
|
+
after?.();
|
|
480
543
|
} catch (err) {
|
|
481
544
|
await postToolResult({ ...base, ok: false, result: err.message });
|
|
482
545
|
}
|
|
@@ -558,13 +621,24 @@ var OyehelloChat = (function (exports) {
|
|
|
558
621
|
if (resumed) {
|
|
559
622
|
session = resumed;
|
|
560
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
|
+
}
|
|
561
634
|
await openStream();
|
|
562
635
|
scheduleRefresh();
|
|
563
636
|
flushJourney();
|
|
564
637
|
return;
|
|
565
638
|
}
|
|
566
639
|
if (deviceFingerprint === void 0) deviceFingerprint = await computeDeviceFingerprint().catch(() => null);
|
|
567
|
-
|
|
640
|
+
const advertisedTools = /* @__PURE__ */ new Set([...toolHandlers.keys(), ...Object.keys(BUILTIN_TOOL_HANDLERS)]);
|
|
641
|
+
session = await openSession({ baseUrl: options.baseUrl, apiKey: options.apiKey, agentId: options.agentId, user: options.user, clientTools: Array.from(advertisedTools), fingerprint: deviceFingerprint ?? void 0, fetchImpl });
|
|
568
642
|
persistSession(session);
|
|
569
643
|
if (!warnedProtocol && typeof session.protocol === "number" && session.protocol !== CHAT_PROTOCOL_VERSION) {
|
|
570
644
|
warnedProtocol = true;
|
|
@@ -698,6 +772,44 @@ var OyehelloChat = (function (exports) {
|
|
|
698
772
|
};
|
|
699
773
|
}
|
|
700
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 v2 = node.getAttribute(attr);
|
|
780
|
+
if (v2 === "dark" || v2 === "light") return v2;
|
|
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
|
+
}
|
|
812
|
+
|
|
701
813
|
// ../../node_modules/marked/lib/marked.esm.js
|
|
702
814
|
function A() {
|
|
703
815
|
return { async: false, breaks: false, extensions: null, gfm: true, hooks: null, pedantic: false, renderer: null, silent: false, tokenizer: null, walkTokens: null };
|
|
@@ -3659,11 +3771,15 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
3659
3771
|
* body children (oyehello-chat { display:none }) can't silently kill the widget. Also pinned
|
|
3660
3772
|
* inline with !important at mount (see element.ts) to beat an external !important rule. */
|
|
3661
3773
|
display: block; visibility: visible; opacity: 1;
|
|
3774
|
+
/* The closed panel still takes up its full size in the layout (it fades, it is not removed), so
|
|
3775
|
+
* the host box covers ~384x620px of the page. The host and its wrapper must not catch clicks;
|
|
3776
|
+
* only the visible parts opt back in (launcher, teaser while shown, panel while open). */
|
|
3777
|
+
pointer-events: none;
|
|
3662
3778
|
font-family: 'Geist', system-ui, -apple-system, 'Helvetica Neue', sans-serif;
|
|
3663
3779
|
-webkit-font-smoothing: antialiased;
|
|
3664
3780
|
}
|
|
3665
3781
|
@media (prefers-color-scheme: dark) {
|
|
3666
|
-
:host(:not([theme="light"])) {
|
|
3782
|
+
:host(:not([theme="light"]):not([data-oc-page-theme="light"])) {
|
|
3667
3783
|
--oc-bg: #000000; --oc-bg-sunk: #000000;
|
|
3668
3784
|
--oc-fg: #FFFFFF; --oc-fg-muted: rgba(255,255,255,0.68); --oc-fg-dim: rgba(255,255,255,0.42);
|
|
3669
3785
|
--oc-border: rgba(255,255,255,0.09);
|
|
@@ -3673,7 +3789,9 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
3673
3789
|
--oc-glass-border: rgba(255,255,255,0.12);
|
|
3674
3790
|
}
|
|
3675
3791
|
}
|
|
3676
|
-
|
|
3792
|
+
/* Explicit dark, or auto on a page that declares dark (data-oc-page-theme, set by page-theme.ts). The OS rule above
|
|
3793
|
+
* only applies when the page declares nothing; a page that declares light keeps the light base. */
|
|
3794
|
+
:host([theme="dark"]), :host(:not([theme="light"])[data-oc-page-theme="dark"]) {
|
|
3677
3795
|
--oc-bg: #000000; --oc-bg-sunk: #000000;
|
|
3678
3796
|
--oc-fg: #FFFFFF; --oc-fg-muted: rgba(255,255,255,0.68); --oc-fg-dim: rgba(255,255,255,0.42);
|
|
3679
3797
|
--oc-border: rgba(255,255,255,0.09);
|
|
@@ -3685,7 +3803,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
3685
3803
|
* { box-sizing: border-box; }
|
|
3686
3804
|
button { font-family: inherit; }
|
|
3687
3805
|
|
|
3688
|
-
.oc { display: flex; flex-direction: column; align-items: flex-end; gap: 14px; }
|
|
3806
|
+
.oc { display: flex; flex-direction: column; align-items: flex-end; gap: 14px; pointer-events: none; }
|
|
3689
3807
|
|
|
3690
3808
|
/* \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 */
|
|
3691
3809
|
.launcher {
|
|
@@ -3694,6 +3812,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
3694
3812
|
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);
|
|
3695
3813
|
color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer;
|
|
3696
3814
|
transition: transform .26s var(--oc-bounce), filter .2s var(--oc-ease); align-self: flex-end;
|
|
3815
|
+
pointer-events: auto;
|
|
3697
3816
|
}
|
|
3698
3817
|
.launcher:hover { transform: scale(1.06); }
|
|
3699
3818
|
.launcher:active { transform: scale(0.93); }
|
|
@@ -4061,6 +4180,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
4061
4180
|
// Uninstaller for the SPA history-navigation hooks (journey tracking), installed in
|
|
4062
4181
|
// connectedCallback and torn down in disconnectedCallback alongside everything else.
|
|
4063
4182
|
uninstallSpaHooks = null;
|
|
4183
|
+
unwatchPageTheme = null;
|
|
4064
4184
|
// ── shadow refs ──
|
|
4065
4185
|
oc;
|
|
4066
4186
|
bodyEl;
|
|
@@ -4094,6 +4214,11 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
4094
4214
|
this.uninstallSpaHooks = null;
|
|
4095
4215
|
}
|
|
4096
4216
|
this.renderShell();
|
|
4217
|
+
this.unwatchPageTheme?.();
|
|
4218
|
+
this.unwatchPageTheme = watchPageTheme(this, (t) => {
|
|
4219
|
+
if (t) this.setAttribute("data-oc-page-theme", t);
|
|
4220
|
+
else this.removeAttribute("data-oc-page-theme");
|
|
4221
|
+
});
|
|
4097
4222
|
this.style.setProperty("display", "block", "important");
|
|
4098
4223
|
this.style.setProperty("visibility", "visible", "important");
|
|
4099
4224
|
this.style.setProperty("opacity", "1", "important");
|
|
@@ -4204,6 +4329,8 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
4204
4329
|
this.teardownIdle();
|
|
4205
4330
|
this.uninstallSpaHooks?.();
|
|
4206
4331
|
this.uninstallSpaHooks = null;
|
|
4332
|
+
this.unwatchPageTheme?.();
|
|
4333
|
+
this.unwatchPageTheme = null;
|
|
4207
4334
|
for (const t of this.timers) clearTimeout(t);
|
|
4208
4335
|
for (const url of this.imagePreviewCache.values()) {
|
|
4209
4336
|
if (typeof URL.revokeObjectURL === "function") URL.revokeObjectURL(url);
|
|
@@ -4214,6 +4341,11 @@ Please report this to https://github.com/markedjs/marked.`, e) {
|
|
|
4214
4341
|
if (this.client) this.client.registerTools(handlers);
|
|
4215
4342
|
else Object.assign(this.pendingTools, handlers);
|
|
4216
4343
|
}
|
|
4344
|
+
/** Open the chat window, exactly as a launcher click does. No-op if already open or not rendered. */
|
|
4345
|
+
open() {
|
|
4346
|
+
if (!this.oc || this.uiOpen) return;
|
|
4347
|
+
this.openPanel();
|
|
4348
|
+
}
|
|
4217
4349
|
key(k2) {
|
|
4218
4350
|
return `oyehello-chat:${k2}:${this.getAttribute("agent-id") ?? ""}`;
|
|
4219
4351
|
}
|