@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/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();
@@ -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",
@@ -317,17 +327,21 @@ var OyehelloChat = (function (exports) {
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.assign(path);
325
- return { ok: true };
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
- window.open(url, args.newTab === false ? "_self" : "_blank", "noopener,noreferrer");
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 @@ var OyehelloChat = (function (exports) {
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 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
+ };
442
473
  const emit = (e) => {
443
474
  for (const l of listeners) l(e);
444
475
  };
@@ -487,8 +518,9 @@ var OyehelloChat = (function (exports) {
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 @@ var OyehelloChat = (function (exports) {
498
530
  return;
499
531
  }
500
532
  try {
501
- const out = await handler(ev.args);
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 @@ var OyehelloChat = (function (exports) {
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();
@@ -722,6 +772,44 @@ var OyehelloChat = (function (exports) {
722
772
  };
723
773
  }
724
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
+
725
813
  // ../../node_modules/marked/lib/marked.esm.js
726
814
  function A() {
727
815
  return { async: false, breaks: false, extensions: null, gfm: true, hooks: null, pedantic: false, renderer: null, silent: false, tokenizer: null, walkTokens: null };
@@ -3683,11 +3771,15 @@ Please report this to https://github.com/markedjs/marked.`, e) {
3683
3771
  * body children (oyehello-chat { display:none }) can't silently kill the widget. Also pinned
3684
3772
  * inline with !important at mount (see element.ts) to beat an external !important rule. */
3685
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;
3686
3778
  font-family: 'Geist', system-ui, -apple-system, 'Helvetica Neue', sans-serif;
3687
3779
  -webkit-font-smoothing: antialiased;
3688
3780
  }
3689
3781
  @media (prefers-color-scheme: dark) {
3690
- :host(:not([theme="light"])) {
3782
+ :host(:not([theme="light"]):not([data-oc-page-theme="light"])) {
3691
3783
  --oc-bg: #000000; --oc-bg-sunk: #000000;
3692
3784
  --oc-fg: #FFFFFF; --oc-fg-muted: rgba(255,255,255,0.68); --oc-fg-dim: rgba(255,255,255,0.42);
3693
3785
  --oc-border: rgba(255,255,255,0.09);
@@ -3697,7 +3789,9 @@ Please report this to https://github.com/markedjs/marked.`, e) {
3697
3789
  --oc-glass-border: rgba(255,255,255,0.12);
3698
3790
  }
3699
3791
  }
3700
- :host([theme="dark"]) {
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"]) {
3701
3795
  --oc-bg: #000000; --oc-bg-sunk: #000000;
3702
3796
  --oc-fg: #FFFFFF; --oc-fg-muted: rgba(255,255,255,0.68); --oc-fg-dim: rgba(255,255,255,0.42);
3703
3797
  --oc-border: rgba(255,255,255,0.09);
@@ -3709,7 +3803,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
3709
3803
  * { box-sizing: border-box; }
3710
3804
  button { font-family: inherit; }
3711
3805
 
3712
- .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; }
3713
3807
 
3714
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 */
3715
3809
  .launcher {
@@ -3718,6 +3812,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
3718
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);
3719
3813
  color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer;
3720
3814
  transition: transform .26s var(--oc-bounce), filter .2s var(--oc-ease); align-self: flex-end;
3815
+ pointer-events: auto;
3721
3816
  }
3722
3817
  .launcher:hover { transform: scale(1.06); }
3723
3818
  .launcher:active { transform: scale(0.93); }
@@ -4085,6 +4180,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4085
4180
  // Uninstaller for the SPA history-navigation hooks (journey tracking), installed in
4086
4181
  // connectedCallback and torn down in disconnectedCallback alongside everything else.
4087
4182
  uninstallSpaHooks = null;
4183
+ unwatchPageTheme = null;
4088
4184
  // ── shadow refs ──
4089
4185
  oc;
4090
4186
  bodyEl;
@@ -4118,6 +4214,11 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4118
4214
  this.uninstallSpaHooks = null;
4119
4215
  }
4120
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
+ });
4121
4222
  this.style.setProperty("display", "block", "important");
4122
4223
  this.style.setProperty("visibility", "visible", "important");
4123
4224
  this.style.setProperty("opacity", "1", "important");
@@ -4228,6 +4329,8 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4228
4329
  this.teardownIdle();
4229
4330
  this.uninstallSpaHooks?.();
4230
4331
  this.uninstallSpaHooks = null;
4332
+ this.unwatchPageTheme?.();
4333
+ this.unwatchPageTheme = null;
4231
4334
  for (const t of this.timers) clearTimeout(t);
4232
4335
  for (const url of this.imagePreviewCache.values()) {
4233
4336
  if (typeof URL.revokeObjectURL === "function") URL.revokeObjectURL(url);
@@ -4238,6 +4341,11 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4238
4341
  if (this.client) this.client.registerTools(handlers);
4239
4342
  else Object.assign(this.pendingTools, handlers);
4240
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
+ }
4241
4349
  key(k2) {
4242
4350
  return `oyehello-chat:${k2}:${this.getAttribute("agent-id") ?? ""}`;
4243
4351
  }