@pluno/product-agent-web 0.1.0 → 0.1.1

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/INTEGRATION.md CHANGED
@@ -65,7 +65,7 @@ redaction pipeline before data leaves the browser.
65
65
  ```html
66
66
  <script
67
67
  type="module"
68
- src="https://cdn.pluno.ai/product-agent/product-agent-widget.js"
68
+ src="https://app.pluno.ai/product-agent/product-agent-widget.js"
69
69
  data-pluno-token-endpoint="/api/pluno-product-agent-token"
70
70
  data-pluno-launcher-label="Ask AI..."
71
71
  data-pluno-accent-color="#18181b"
package/README.md CHANGED
@@ -40,7 +40,7 @@ The SDK always captures product network activity by patching `fetch` and `XMLHtt
40
40
  ```html
41
41
  <script
42
42
  type="module"
43
- src="https://cdn.pluno.ai/product-agent/product-agent-widget.js"
43
+ src="https://app.pluno.ai/product-agent/product-agent-widget.js"
44
44
  data-pluno-token-endpoint="/api/pluno-product-agent-token"
45
45
  data-pluno-launcher-label="Ask AI..."
46
46
  data-pluno-accent-color="#18181b"
package/dist/index.d.ts CHANGED
@@ -22,6 +22,7 @@ export type ProductAgentState = {
22
22
  status: "idle" | "connecting" | "connected" | "reconnecting" | "closed" | "error";
23
23
  user: ProductAgentUser | null;
24
24
  sessionId: string | null;
25
+ starterPrompts: string[];
25
26
  messages: ProductAgentMessage[];
26
27
  assistantDraft: string;
27
28
  isThinking: boolean;
@@ -3,7 +3,7 @@ const A = "pluno.productAgent.state.";
3
3
  class I {
4
4
  constructor(t) {
5
5
  this.options = t;
6
- const s = G(t.clientId);
6
+ const s = W(t.clientId);
7
7
  s && (this.state = {
8
8
  ...this.state,
9
9
  ...s,
@@ -28,6 +28,7 @@ class I {
28
28
  status: "idle",
29
29
  user: null,
30
30
  sessionId: null,
31
+ starterPrompts: [],
31
32
  messages: [],
32
33
  assistantDraft: "",
33
34
  isThinking: !1,
@@ -36,8 +37,8 @@ class I {
36
37
  static async init(t) {
37
38
  const s = new I({
38
39
  ...t,
39
- backendUrl: P(t.backendUrl ?? O),
40
- clientId: t.clientId ?? B()
40
+ backendUrl: H(t.backendUrl ?? O),
41
+ clientId: t.clientId ?? x()
41
42
  });
42
43
  try {
43
44
  s.enableNetworkCapture(), t.autoConnect !== !1 && await s.connect();
@@ -53,6 +54,7 @@ class I {
53
54
  getState() {
54
55
  return {
55
56
  ...this.state,
57
+ starterPrompts: [...this.state.starterPrompts],
56
58
  messages: [...this.state.messages]
57
59
  };
58
60
  }
@@ -82,7 +84,7 @@ class I {
82
84
  const s = t.trim();
83
85
  if (!s)
84
86
  return;
85
- const n = T(), r = {
87
+ const n = m(), r = {
86
88
  id: `local-${Date.now()}`,
87
89
  role: "user",
88
90
  content: s,
@@ -106,8 +108,13 @@ class I {
106
108
  this.state.sessionId && (this.send({ type: "run.stop", sessionId: this.state.sessionId }), this.setState({ isThinking: !1 }));
107
109
  }
108
110
  startNewSession() {
109
- this.state.sessionId && this.state.isThinking && this.sendNow({ type: "run.stop", sessionId: this.state.sessionId }), this.queuedClientEvents = this.queuedClientEvents.filter((t) => t.type !== "chat.user_message"), this.setState({
111
+ this.state.sessionId && this.state.isThinking && this.sendNow({ type: "run.stop", sessionId: this.state.sessionId }), this.queuedClientEvents = this.queuedClientEvents.filter((t) => t.type !== "chat.user_message"), this.send({
112
+ type: "session.reset",
113
+ sessionId: this.state.sessionId ?? void 0,
114
+ page: m()
115
+ }), this.setState({
110
116
  sessionId: null,
117
+ starterPrompts: [...this.state.starterPrompts],
111
118
  messages: [],
112
119
  assistantDraft: "",
113
120
  isThinking: !1,
@@ -134,13 +141,17 @@ class I {
134
141
  }
135
142
  handleServerEvent(t) {
136
143
  if (t.type === "auth.ok") {
137
- this.setState({ user: x(t.user), status: "connected" });
144
+ this.setState({
145
+ user: $(t.user),
146
+ starterPrompts: B(t, "starterPrompts"),
147
+ status: "connected"
148
+ });
138
149
  return;
139
150
  }
140
151
  if (t.type === "conversation.state") {
141
152
  this.setState({
142
153
  sessionId: _(t.session, "id") ?? this.state.sessionId,
143
- messages: $(t.items)
154
+ messages: j(t.items)
144
155
  });
145
156
  return;
146
157
  }
@@ -151,7 +162,7 @@ class I {
151
162
  if (t.type === "session.item") {
152
163
  const s = R(t.item);
153
164
  s && (this.setState({
154
- messages: z(this.state.messages, s),
165
+ messages: F(this.state.messages, s),
155
166
  assistantDraft: s.role === "assistant" ? "" : this.state.assistantDraft
156
167
  }), this.emit("message", s));
157
168
  return;
@@ -177,9 +188,9 @@ class I {
177
188
  }
178
189
  async executeToolCall(t) {
179
190
  const s = typeof t.sessionId == "string" ? t.sessionId : null, n = typeof t.callId == "string" ? t.callId : null, r = t.rawInput;
180
- if (!s || !n || !K(r))
191
+ if (!s || !n || !Y(r))
181
192
  return;
182
- const i = await H(r).catch((a) => ({
193
+ const i = await P(r).catch((a) => ({
183
194
  ok: !1,
184
195
  exception: {
185
196
  message: a instanceof Error ? a.message : String(a)
@@ -200,19 +211,19 @@ class I {
200
211
  return;
201
212
  const t = window.fetch.bind(window), s = XMLHttpRequest.prototype.open, n = XMLHttpRequest.prototype.setRequestHeader, r = XMLHttpRequest.prototype.send;
202
213
  window.fetch = async (i, a) => {
203
- const o = Date.now(), c = new Date(o).toISOString(), u = i instanceof Request ? i : null, f = typeof i == "string" ? i : i instanceof URL ? i.toString() : u?.url ?? "", S = (a?.method ?? u?.method ?? "GET").toUpperCase(), w = k(new Headers(a?.headers ?? u?.headers ?? void 0)), E = await Y(u, a);
214
+ const o = Date.now(), c = new Date(o).toISOString(), u = i instanceof Request ? i : null, f = typeof i == "string" ? i : i instanceof URL ? i.toString() : u?.url ?? "", w = (a?.method ?? u?.method ?? "GET").toUpperCase(), E = k(new Headers(a?.headers ?? u?.headers ?? void 0)), T = await V(u, a);
204
215
  try {
205
216
  const l = await t(i, a);
206
217
  return this.enqueueNetworkEvent({
207
218
  requestId: g("fetch"),
208
219
  url: f,
209
- method: S,
210
- requestHeaders: w,
211
- requestBody: E,
220
+ method: w,
221
+ requestHeaders: E,
222
+ requestBody: T,
212
223
  resourceType: "fetch",
213
224
  responseStatus: l.status,
214
225
  responseHeaders: k(l.headers),
215
- responseBody: await V(l),
226
+ responseBody: await Z(l),
216
227
  startedAt: c,
217
228
  durationMs: Date.now() - o
218
229
  }), l;
@@ -220,9 +231,9 @@ class I {
220
231
  throw this.enqueueNetworkEvent({
221
232
  requestId: g("fetch"),
222
233
  url: f,
223
- method: S,
224
- requestHeaders: w,
225
- requestBody: E,
234
+ method: w,
235
+ requestHeaders: E,
236
+ requestBody: T,
226
237
  resourceType: "fetch",
227
238
  errorText: l instanceof Error ? l.message : String(l),
228
239
  startedAt: c,
@@ -265,8 +276,8 @@ class I {
265
276
  requestBody: typeof this.__plunoMeta.requestBody == "string" ? this.__plunoMeta.requestBody : void 0,
266
277
  resourceType: "xhr",
267
278
  responseStatus: this.status,
268
- responseHeaders: Q(this.getAllResponseHeaders()),
269
- responseBody: Z(this),
279
+ responseHeaders: tt(this.getAllResponseHeaders()),
280
+ responseBody: Q(this),
270
281
  errorText: this.status === 0 ? "XHR request failed or was aborted" : void 0,
271
282
  startedAt: String(this.__plunoMeta.startedAt),
272
283
  durationMs: Date.now() - Number(this.__plunoMeta.startedAtMs ?? Date.now())
@@ -285,7 +296,7 @@ class I {
285
296
  s.length === 0 || this.socket?.readyState !== WebSocket.OPEN || this.sendNow({
286
297
  type: "network.batch",
287
298
  sessionId: this.state.sessionId ?? void 0,
288
- page: T(),
299
+ page: m(),
289
300
  events: s
290
301
  });
291
302
  }, 1e3));
@@ -306,13 +317,13 @@ class I {
306
317
  this.heartbeatTimer !== null && (window.clearInterval(this.heartbeatTimer), this.heartbeatTimer = null);
307
318
  }
308
319
  setState(t) {
309
- this.state = { ...this.state, ...t }, W(this.options.clientId, this.state), this.emit("state", this.getState());
320
+ this.state = { ...this.state, ...t }, J(this.options.clientId, this.state), this.emit("state", this.getState());
310
321
  }
311
322
  emit(t, s) {
312
323
  this.listeners[t]?.forEach((r) => r(s));
313
324
  }
314
325
  }
315
- async function H(e) {
326
+ async function P(e) {
316
327
  const t = Object.getPrototypeOf(async function() {
317
328
  }).constructor, s = e.timeoutMs ?? 15e3, n = Date.now(), r = [], i = {
318
329
  log: console.log,
@@ -358,14 +369,14 @@ async function H(e) {
358
369
  console.log = i.log, console.info = i.info, console.warn = i.warn, console.error = i.error;
359
370
  }
360
371
  }
361
- function P(e) {
372
+ function H(e) {
362
373
  return e.replace(/\/+$/, "");
363
374
  }
364
375
  function U(e) {
365
376
  const t = new URL("/api/product-agent/embed/ws", e);
366
377
  return t.protocol = t.protocol === "https:" ? "wss:" : "ws:", t.toString();
367
378
  }
368
- function T() {
379
+ function m() {
369
380
  return {
370
381
  url: location.href,
371
382
  title: document.title,
@@ -380,14 +391,20 @@ function L(e) {
380
391
  return { type: "error", message: "Invalid server event" };
381
392
  }
382
393
  }
383
- function B() {
394
+ function B(e, t) {
395
+ if (!e || typeof e != "object")
396
+ return [];
397
+ const s = e[t];
398
+ return Array.isArray(s) ? s.filter((n) => typeof n == "string" && n.trim().length > 0) : [];
399
+ }
400
+ function x() {
384
401
  const e = "pluno.productAgent.clientId", t = window.localStorage.getItem(e);
385
402
  if (t)
386
403
  return t;
387
404
  const s = crypto.randomUUID();
388
405
  return window.localStorage.setItem(e, s), s;
389
406
  }
390
- function x(e) {
407
+ function $(e) {
391
408
  if (!e || typeof e != "object")
392
409
  return null;
393
410
  const t = e, s = typeof t.id == "string" ? t.id : null;
@@ -398,7 +415,7 @@ function x(e) {
398
415
  avatarUrl: typeof t.avatarUrl == "string" ? t.avatarUrl : null
399
416
  } : null;
400
417
  }
401
- function $(e) {
418
+ function j(e) {
402
419
  return Array.isArray(e) ? e.map(R).filter((t) => t !== null) : [];
403
420
  }
404
421
  function R(e) {
@@ -413,20 +430,20 @@ function R(e) {
413
430
  return {
414
431
  id: String(t.id ?? crypto.randomUUID()),
415
432
  role: r,
416
- content: v(n.content),
433
+ content: z(n.content),
417
434
  createdAt: typeof t.createdAt == "string" ? t.createdAt : (/* @__PURE__ */ new Date()).toISOString()
418
435
  };
419
436
  }
420
437
  return n.type === "function_call" || n.type === "tool_call" || n.type === "web_search_call" ? {
421
438
  id: String(t.id ?? crypto.randomUUID()),
422
439
  role: "tool",
423
- content: j(n),
440
+ content: X(n),
424
441
  createdAt: typeof t.createdAt == "string" ? t.createdAt : (/* @__PURE__ */ new Date()).toISOString(),
425
442
  dataType: String(n.type),
426
443
  loading: n.localExecutionStatus === "running"
427
444
  } : null;
428
445
  }
429
- function j(e) {
446
+ function X(e) {
430
447
  if (e.type === "web_search_call") {
431
448
  const t = e.action;
432
449
  if (t && typeof t == "object") {
@@ -438,9 +455,9 @@ function j(e) {
438
455
  }
439
456
  return "Searching the web";
440
457
  }
441
- return X(e);
458
+ return v(e);
442
459
  }
443
- function X(e) {
460
+ function v(e) {
444
461
  if (typeof e.summary == "string" && e.summary.trim().length > 0)
445
462
  return e.summary;
446
463
  if (typeof e.arguments == "string")
@@ -453,7 +470,7 @@ function X(e) {
453
470
  }
454
471
  return typeof e.name == "string" && e.name.trim().length > 0 ? e.name : "Run tool";
455
472
  }
456
- function v(e) {
473
+ function z(e) {
457
474
  return typeof e == "string" ? e : Array.isArray(e) ? e.map((t) => {
458
475
  if (!t || typeof t != "object")
459
476
  return "";
@@ -461,14 +478,14 @@ function v(e) {
461
478
  return typeof s.text == "string" ? s.text : typeof s.content == "string" ? s.content : "";
462
479
  }).filter(Boolean).join("") : "";
463
480
  }
464
- function z(e, t) {
465
- const s = F(e, t), n = s.findIndex((i) => i.id === t.id);
481
+ function F(e, t) {
482
+ const s = G(e, t), n = s.findIndex((i) => i.id === t.id);
466
483
  if (n === -1)
467
484
  return [...s, t];
468
485
  const r = [...s];
469
486
  return r[n] = t, r;
470
487
  }
471
- function F(e, t) {
488
+ function G(e, t) {
472
489
  if (t.role !== "user")
473
490
  return e;
474
491
  const s = e.findIndex(
@@ -482,12 +499,12 @@ function F(e, t) {
482
499
  function _(e, t) {
483
500
  return e && typeof e == "object" && typeof e[t] == "string" ? e[t] : null;
484
501
  }
485
- function G(e) {
502
+ function W(e) {
486
503
  try {
487
504
  const t = window.localStorage.getItem(`${A}${e}`);
488
505
  if (!t)
489
506
  return null;
490
- const s = JSON.parse(t), n = Array.isArray(s.messages) ? s.messages.map(J).filter((r) => !!r) : [];
507
+ const s = JSON.parse(t), n = Array.isArray(s.messages) ? s.messages.map(K).filter((r) => !!r) : [];
491
508
  return {
492
509
  sessionId: typeof s.sessionId == "string" ? s.sessionId : null,
493
510
  messages: n
@@ -496,7 +513,7 @@ function G(e) {
496
513
  return null;
497
514
  }
498
515
  }
499
- function W(e, t) {
516
+ function J(e, t) {
500
517
  try {
501
518
  window.localStorage.setItem(
502
519
  `${A}${e}`,
@@ -509,7 +526,7 @@ function W(e, t) {
509
526
  return;
510
527
  }
511
528
  }
512
- function J(e) {
529
+ function K(e) {
513
530
  if (!e || typeof e != "object")
514
531
  return null;
515
532
  const t = e;
@@ -522,7 +539,7 @@ function J(e) {
522
539
  loading: typeof t.loading == "boolean" ? t.loading : void 0
523
540
  };
524
541
  }
525
- function K(e) {
542
+ function Y(e) {
526
543
  return !!e && typeof e == "object" && typeof e.summary == "string" && typeof e.javascript == "string";
527
544
  }
528
545
  function k(e) {
@@ -531,7 +548,7 @@ function k(e) {
531
548
  t[n] = s;
532
549
  }), t;
533
550
  }
534
- async function Y(e, t) {
551
+ async function V(e, t) {
535
552
  if (typeof t?.body < "u")
536
553
  return C(t.body);
537
554
  if (e)
@@ -541,14 +558,14 @@ async function Y(e, t) {
541
558
  return;
542
559
  }
543
560
  }
544
- async function V(e) {
561
+ async function Z(e) {
545
562
  try {
546
563
  return p(await e.clone().text());
547
564
  } catch (t) {
548
565
  return { error: t instanceof Error ? t.message : "unavailable" };
549
566
  }
550
567
  }
551
- function Z(e) {
568
+ function Q(e) {
552
569
  try {
553
570
  return e.responseType === "" || e.responseType === "text" ? p(e.responseText ?? "") : e.responseType === "json" ? p(JSON.stringify(e.response)) : `[unsupported xhr responseType: ${e.responseType || "unknown"}]`;
554
571
  } catch (t) {
@@ -574,7 +591,7 @@ function C(e) {
574
591
  }
575
592
  }
576
593
  }
577
- function Q(e) {
594
+ function tt(e) {
578
595
  const t = {};
579
596
  for (const s of e.trim().split(/[\r\n]+/)) {
580
597
  const n = s.indexOf(":");
@@ -588,16 +605,16 @@ function p(e) {
588
605
  function g(e) {
589
606
  return `${e}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
590
607
  }
591
- const m = "[REDACTED_SECRET]", d = "[REDACTED_TOKEN]", tt = "[REDACTED_SIGNED_URL]", et = 20, st = /^(access_token|accessToken|refresh_token|refreshToken|id_token|idToken|authToken|token|api_key|apiKey|apikey|key|secret|signature|sig|password|passwd|pwd|code|state|session|jwt|csrf|csrfToken|xsrf|xsrfToken)$/i, N = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi, D = /\bBasic\s+[A-Za-z0-9+/=-]{12,}/gi, M = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, b = /\b(access_token|accessToken|refresh_token|refreshToken|id_token|idToken|authToken|api_key|apiKey|apikey|client_secret|clientSecret|password|secret|token|jwt|csrf|csrfToken|xsrf|xsrfToken)\b\s*[:=]\s*["']?[^"',&\s}]+/gi, nt = /\bhttps?:\/\/[^\s"'<>]+(?:X-Amz-Signature|X-Goog-Signature|Signature|sig=)[^\s"'<>]*/gi, rt = /\bhttps?:\/\/[^\s"'<>]+/gi, ot = /[),.;\]]+$/;
608
+ const S = "[REDACTED_SECRET]", d = "[REDACTED_TOKEN]", et = "[REDACTED_SIGNED_URL]", st = 20, nt = /^(access_token|accessToken|refresh_token|refreshToken|id_token|idToken|authToken|token|api_key|apiKey|apikey|key|secret|signature|sig|password|passwd|pwd|code|state|session|jwt|csrf|csrfToken|xsrf|xsrfToken)$/i, N = /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}/gi, D = /\bBasic\s+[A-Za-z0-9+/=-]{12,}/gi, M = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, b = /\b(access_token|accessToken|refresh_token|refreshToken|id_token|idToken|authToken|api_key|apiKey|apikey|client_secret|clientSecret|password|secret|token|jwt|csrf|csrfToken|xsrf|xsrfToken)\b\s*[:=]\s*["']?[^"',&\s}]+/gi, rt = /\bhttps?:\/\/[^\s"'<>]+(?:X-Amz-Signature|X-Goog-Signature|Signature|sig=)[^\s"'<>]*/gi, ot = /\bhttps?:\/\/[^\s"'<>]+/gi, it = /[),.;\]]+$/;
592
609
  function h(e) {
593
610
  return y(e, 0, /* @__PURE__ */ new WeakSet());
594
611
  }
595
612
  function y(e, t, s) {
596
613
  if (typeof e == "string")
597
- return ct(e);
614
+ return ut(e);
598
615
  if (e === null || typeof e != "object")
599
616
  return e;
600
- if (t >= et)
617
+ if (t >= st)
601
618
  return "[REDACTED_MAX_DEPTH]";
602
619
  if (s.has(e))
603
620
  return "[REDACTED_CIRCULAR]";
@@ -605,34 +622,34 @@ function y(e, t, s) {
605
622
  return e.map((r) => y(r, t + 1, s));
606
623
  const n = {};
607
624
  for (const [r, i] of Object.entries(e))
608
- it(r) ? n[r] = m : n[r] = r.toLowerCase() === "url" ? at(i) : y(i, t + 1, s);
625
+ at(r) ? n[r] = S : n[r] = r.toLowerCase() === "url" ? ct(i) : y(i, t + 1, s);
609
626
  return n;
610
627
  }
611
- function it(e) {
628
+ function at(e) {
612
629
  const t = e.replace(/[^a-z0-9]/gi, "").toLowerCase();
613
630
  return t.includes("authorization") || t.includes("cookie") || t.includes("password") || t.includes("passwd") || t === "pwd" || t.includes("secret") || t === "token" || t.endsWith("token") || t.includes("apikey") || t.includes("csrf") || t.includes("xsrf") || t === "jwt" || t === "session" || t === "signature" || t.includes("privatekey");
614
631
  }
615
- function at(e) {
632
+ function ct(e) {
616
633
  return typeof e == "string" ? q(e) : y(e, 0, /* @__PURE__ */ new WeakSet());
617
634
  }
618
635
  function q(e) {
619
636
  try {
620
637
  const t = new URL(e, location.href);
621
638
  for (const s of Array.from(t.searchParams.keys()))
622
- st.test(s) && t.searchParams.set(s, d);
639
+ nt.test(s) && t.searchParams.set(s, d);
623
640
  return t.username && (t.username = d), t.password && (t.password = d), t.toString();
624
641
  } catch {
625
- return ut(e);
642
+ return lt(e);
626
643
  }
627
644
  }
628
- function ct(e) {
629
- return e.replace(nt, tt).replace(rt, lt).replace(N, `Bearer ${d}`).replace(D, `Basic ${d}`).replace(M, d).replace(b, (t, s) => `${s}: ${m}`);
630
- }
631
645
  function ut(e) {
632
- return e.replace(N, `Bearer ${d}`).replace(D, `Basic ${d}`).replace(M, d).replace(b, (t, s) => `${s}: ${m}`);
646
+ return e.replace(rt, et).replace(ot, dt).replace(N, `Bearer ${d}`).replace(D, `Basic ${d}`).replace(M, d).replace(b, (t, s) => `${s}: ${S}`);
633
647
  }
634
648
  function lt(e) {
635
- const t = e.match(ot)?.[0] ?? "", s = t ? e.slice(0, -t.length) : e;
649
+ return e.replace(N, `Bearer ${d}`).replace(D, `Basic ${d}`).replace(M, d).replace(b, (t, s) => `${s}: ${S}`);
650
+ }
651
+ function dt(e) {
652
+ const t = e.match(it)?.[0] ?? "", s = t ? e.slice(0, -t.length) : e;
636
653
  return `${q(s)}${t}`;
637
654
  }
638
655
  export {
@@ -1,25 +1,27 @@
1
- import { PlunoProductAgent as $ } from "./product-agent-sdk.js";
2
- const y = {
1
+ import { PlunoProductAgent as q } from "./product-agent-sdk.js";
2
+ const L = {
3
3
  launcherLabel: "Ask AI...",
4
4
  accentColor: "#18181b",
5
5
  colorScheme: "light",
6
6
  position: "bottom-right"
7
7
  };
8
- async function I(e = {}) {
9
- const o = {
10
- launcherLabel: e.launcherLabel ?? y.launcherLabel,
11
- accentColor: e.accentColor ?? y.accentColor,
12
- colorScheme: e.colorScheme ?? y.colorScheme,
13
- position: e.position ?? y.position,
8
+ async function K(e = {}) {
9
+ const t = {
10
+ launcherLabel: e.launcherLabel ?? L.launcherLabel,
11
+ accentColor: e.accentColor ?? L.accentColor,
12
+ colorScheme: e.colorScheme ?? L.colorScheme,
13
+ position: e.position ?? L.position,
14
14
  token: e.token,
15
15
  tokenProvider: e.tokenProvider,
16
16
  tokenEndpoint: e.tokenEndpoint,
17
17
  backendUrl: e.backendUrl,
18
18
  webSocketFactory: e.webSocketFactory
19
- }, n = document.createElement("div");
20
- n.className = `pluno-pa-widget pluno-pa-widget--${o.position}`, n.innerHTML = `
19
+ }, o = document.createElement("div");
20
+ o.className = "pluno-pa-widget-host";
21
+ const n = o.attachShadow({ mode: "open" }), a = document.createElement("div");
22
+ a.className = `pluno-pa-widget pluno-pa-widget--${t.position}`, a.innerHTML = `
21
23
  <form class="pluno-pa-widget__launcher">
22
- <input class="pluno-pa-widget__launcher-input" type="text" placeholder="${A(o.launcherLabel)}" aria-label="${A(o.launcherLabel)}" />
24
+ <input class="pluno-pa-widget__launcher-input" type="text" placeholder="${W(t.launcherLabel)}" aria-label="${W(t.launcherLabel)}" />
23
25
  </form>
24
26
  <button type="button" class="pluno-pa-widget__close" aria-label="Close" hidden>
25
27
  <svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
@@ -50,278 +52,341 @@ async function I(e = {}) {
50
52
  </div>
51
53
  </form>
52
54
  </section>
53
- `, document.body.appendChild(n), n.classList.add(`pluno-pa-widget--${o.colorScheme}`), B(o.accentColor);
54
- const t = n.querySelector(".pluno-pa-widget__launcher"), r = n.querySelector(".pluno-pa-widget__launcher-input"), g = n.querySelector(".pluno-pa-widget__panel"), l = n.querySelector(".pluno-pa-widget__close"), c = n.querySelector(".pluno-pa-widget__composer"), a = n.querySelector(".pluno-pa-widget__input"), i = n.querySelector(".pluno-pa-widget__send"), s = n.querySelector(".pluno-pa-widget__new-chat"), h = n.querySelector(".pluno-pa-widget__timeline");
55
- if (!t || !r || !g || !l || !c || !a || !i || !s || !h)
55
+ `, n.appendChild(a);
56
+ const w = await R(), s = w.ownerDocument;
57
+ w.appendChild(o), a.classList.add(`pluno-pa-widget--${t.colorScheme}`), I(n, t.accentColor);
58
+ const l = a.querySelector(".pluno-pa-widget__launcher"), d = a.querySelector(".pluno-pa-widget__launcher-input"), r = a.querySelector(".pluno-pa-widget__panel"), c = a.querySelector(".pluno-pa-widget__close"), h = a.querySelector(".pluno-pa-widget__composer"), i = a.querySelector(".pluno-pa-widget__input"), p = a.querySelector(".pluno-pa-widget__send"), g = a.querySelector(".pluno-pa-widget__new-chat"), b = a.querySelector(".pluno-pa-widget__timeline");
59
+ if (!l || !d || !r || !c || !h || !i || !p || !g || !b)
56
60
  throw new Error("Failed to mount Product Agent widget");
57
- const p = o.token || o.webSocketFactory ? void 0 : o.tokenProvider ?? (async () => {
58
- if (!o.tokenEndpoint)
61
+ const _ = t.token || t.webSocketFactory ? void 0 : t.tokenProvider ?? (async () => {
62
+ if (!t.tokenEndpoint)
59
63
  throw new Error("Product Agent widget requires token or tokenEndpoint");
60
- const d = await fetch(o.tokenEndpoint, {
64
+ const u = await fetch(t.tokenEndpoint, {
61
65
  method: "POST",
62
66
  credentials: "include",
63
67
  headers: { "content-type": "application/json" },
64
68
  body: JSON.stringify({ origin: location.origin, url: location.href })
65
69
  });
66
- if (!d.ok)
70
+ if (!u.ok)
67
71
  throw new Error("Failed to load Product Agent token");
68
- const k = await d.json();
69
- if (!k.token)
72
+ const y = await u.json();
73
+ if (!y.token)
70
74
  throw new Error("Product Agent token endpoint did not return a token");
71
- return k.token;
72
- }), u = await $.init({
73
- token: o.token,
74
- tokenProvider: p,
75
- backendUrl: o.backendUrl,
76
- webSocketFactory: o.webSocketFactory
77
- }), m = F(o), v = new Set(m.openToolGroupKeys);
78
- let f = null;
79
- const b = () => {
80
- H(o, {
81
- isOpen: n.classList.contains("pluno-pa-widget--open"),
82
- composerValue: a.value,
83
- openToolGroupKeys: [...v]
75
+ return y.token;
76
+ }), m = await q.init({
77
+ token: t.token,
78
+ tokenProvider: _,
79
+ backendUrl: t.backendUrl,
80
+ webSocketFactory: t.webSocketFactory
81
+ }), E = J(t), S = new Set(E.openToolGroupKeys);
82
+ let x = null, z = !1;
83
+ const H = () => {
84
+ z || typeof document > "u" || (I(n, t.accentColor), o.isConnected || s.body?.appendChild(o));
85
+ }, P = new MutationObserver(H);
86
+ P.observe(s.documentElement, { childList: !0, subtree: !0 });
87
+ const v = () => {
88
+ Z(t, {
89
+ isOpen: a.classList.contains("pluno-pa-widget--open"),
90
+ composerValue: i.value,
91
+ openToolGroupKeys: [...S]
84
92
  });
85
- }, _ = (d = "", k = !0) => {
86
- if (f !== null && (window.clearTimeout(f), f = null), g.hidden = !1, l.hidden = !1, n.classList.remove("pluno-pa-widget--closing"), !k) {
87
- n.classList.add("pluno-pa-widget--open"), t.hidden = !0, d && (a.value = d, x(a)), a.focus(), a.setSelectionRange(a.value.length, a.value.length), b();
93
+ }, k = (u = "", y = !0) => {
94
+ if (x !== null && (window.clearTimeout(x), x = null), r.hidden = !1, c.hidden = !1, a.classList.remove("pluno-pa-widget--closing"), !y) {
95
+ a.classList.add("pluno-pa-widget--open"), l.hidden = !0, u && (i.value = u, C(i)), i.focus(), i.setSelectionRange(i.value.length, i.value.length), v();
88
96
  return;
89
97
  }
90
98
  window.requestAnimationFrame(() => {
91
- n.classList.add("pluno-pa-widget--open"), d && (a.value = d, x(a)), a.focus(), a.setSelectionRange(a.value.length, a.value.length), b(), f = window.setTimeout(() => {
92
- t.hidden = !0;
99
+ a.classList.add("pluno-pa-widget--open"), u && (i.value = u, C(i)), i.focus(), i.setSelectionRange(i.value.length, i.value.length), v(), x = window.setTimeout(() => {
100
+ l.hidden = !0;
93
101
  }, 220);
94
102
  });
95
- }, S = () => {
96
- f !== null && (window.clearTimeout(f), f = null), t.hidden = !1, n.classList.add("pluno-pa-widget--closing"), n.classList.remove("pluno-pa-widget--open"), b(), f = window.setTimeout(() => {
97
- g.hidden = !0, l.hidden = !0, n.classList.remove("pluno-pa-widget--closing");
103
+ }, A = () => {
104
+ x !== null && (window.clearTimeout(x), x = null), l.hidden = !1, a.classList.add("pluno-pa-widget--closing"), a.classList.remove("pluno-pa-widget--open"), v(), x = window.setTimeout(() => {
105
+ r.hidden = !0, c.hidden = !0, a.classList.remove("pluno-pa-widget--closing");
98
106
  }, 220);
99
107
  };
100
- t.addEventListener("submit", (d) => {
101
- d.preventDefault(), _(r.value), r.value = "";
102
- }), r.addEventListener("focus", () => {
103
- _(r.value), r.value = "";
104
- }), r.addEventListener("input", () => {
105
- if (!r.value)
108
+ l.addEventListener("submit", (u) => {
109
+ u.preventDefault(), k(d.value), d.value = "";
110
+ }), d.addEventListener("focus", () => {
111
+ k(d.value), d.value = "";
112
+ }), d.addEventListener("input", () => {
113
+ if (!d.value)
106
114
  return;
107
- const d = r.value;
108
- _(d), r.value = "";
109
- }), l.addEventListener("click", S);
110
- const L = (d) => {
111
- if (!(!d.metaKey || d.key.toLowerCase() !== "k")) {
112
- if (d.preventDefault(), g.hidden || !n.classList.contains("pluno-pa-widget--open")) {
113
- _();
115
+ const u = d.value;
116
+ k(u), d.value = "";
117
+ }), c.addEventListener("click", A);
118
+ const M = (u) => {
119
+ if (!(!u.metaKey || u.key.toLowerCase() !== "k")) {
120
+ if (u.preventDefault(), r.hidden || !a.classList.contains("pluno-pa-widget--open")) {
121
+ k();
114
122
  return;
115
123
  }
116
- S();
124
+ A();
117
125
  }
118
126
  };
119
- return document.addEventListener("keydown", L), c.addEventListener("submit", (d) => {
120
- d.preventDefault(), E(u, a);
121
- }), i.addEventListener("click", () => {
122
- if (u.getState().isThinking) {
123
- u.stop();
127
+ return s.addEventListener("keydown", M), h.addEventListener("submit", (u) => {
128
+ u.preventDefault(), N(m, i);
129
+ }), p.addEventListener("click", () => {
130
+ if (m.getState().isThinking) {
131
+ m.stop();
124
132
  return;
125
133
  }
126
- E(u, a);
127
- }), s.addEventListener("click", () => {
128
- u.startNewSession(), v.clear(), a.value = "", x(a), b(), a.focus();
129
- }), a.addEventListener("input", () => {
130
- x(a), b();
131
- }), a.addEventListener("keydown", (d) => {
132
- d.key !== "Enter" || d.shiftKey || (d.preventDefault(), E(u, a));
133
- }), m.composerValue && (a.value = m.composerValue, x(a)), m.isOpen && _(a.value, !1), u.on(
134
+ N(m, i);
135
+ }), g.addEventListener("click", () => {
136
+ m.startNewSession(), S.clear(), i.value = "", C(i), v(), i.focus();
137
+ }), i.addEventListener("input", () => {
138
+ C(i), v();
139
+ }), i.addEventListener("keydown", (u) => {
140
+ u.key !== "Enter" || u.shiftKey || (u.preventDefault(), N(m, i));
141
+ }), E.composerValue && (i.value = E.composerValue, C(i)), E.isOpen && k(i.value, !1), m.on(
134
142
  "state",
135
- (d) => T(h, i, s, d, v, b)
136
- ), T(h, i, s, u.getState(), v, b), {
137
- agent: u,
143
+ (u) => $(b, p, g, u, S, v, (y) => {
144
+ m.sendMessage(y);
145
+ })
146
+ ), $(b, p, g, m.getState(), S, v, (u) => {
147
+ m.sendMessage(u);
148
+ }), {
149
+ agent: m,
138
150
  destroy: () => {
139
- u.destroy(), document.removeEventListener("keydown", L), n.remove();
151
+ z = !0, P.disconnect(), m.destroy(), s.removeEventListener("keydown", M), o.remove();
140
152
  }
141
153
  };
142
154
  }
143
- function E(e, o) {
144
- const n = o.value.trim();
145
- n && (o.value = "", o.style.height = "", o.style.overflowY = "hidden", e.sendMessage(n), o.dispatchEvent(new Event("input", { bubbles: !0 })));
155
+ function N(e, t) {
156
+ const o = t.value.trim();
157
+ o && (t.value = "", t.style.height = "", t.style.overflowY = "hidden", e.sendMessage(o), t.dispatchEvent(new Event("input", { bubbles: !0 })));
146
158
  }
147
- function x(e) {
159
+ function C(e) {
148
160
  e.style.height = "0px";
149
- const o = Math.min(e.scrollHeight, 140);
150
- e.style.height = `${o}px`, e.style.overflowY = e.scrollHeight > 140 ? "auto" : "hidden";
161
+ const t = Math.min(e.scrollHeight, 140);
162
+ e.style.height = `${t}px`, e.style.overflowY = e.scrollHeight > 140 ? "auto" : "hidden";
151
163
  }
152
- function T(e, o, n, t, r, g) {
153
- const l = [...t.messages];
154
- e.innerHTML = "", O(o, t.isThinking);
155
- const c = l.length > 0 || !!t.assistantDraft;
156
- if (n.hidden = !c, l.length === 0 && !t.assistantDraft && !t.lastError) {
157
- e.innerHTML = '<div class="pluno-pa-widget__empty">What can I help you with?</div>';
164
+ function $(e, t, o, n, a, w, s) {
165
+ const l = [...n.messages];
166
+ e.innerHTML = "", V(t, n.isThinking);
167
+ const d = l.length > 0 || !!n.assistantDraft;
168
+ if (o.hidden = !d, l.length === 0 && !n.assistantDraft && !n.lastError) {
169
+ const p = document.createElement("div");
170
+ p.className = "pluno-pa-widget__empty-state";
171
+ const g = document.createElement("div");
172
+ if (g.className = "pluno-pa-widget__empty", g.textContent = "What can I help you with?", p.appendChild(g), n.starterPrompts.length > 0) {
173
+ const b = document.createElement("div");
174
+ b.className = "pluno-pa-widget__starter-prompts";
175
+ for (const _ of n.starterPrompts) {
176
+ const m = document.createElement("button");
177
+ m.type = "button", m.className = "pluno-pa-widget__starter-prompt", m.textContent = _, m.addEventListener("click", () => {
178
+ s(_);
179
+ }), b.appendChild(m);
180
+ }
181
+ p.appendChild(b);
182
+ }
183
+ e.appendChild(p);
158
184
  return;
159
185
  }
160
- let a = null, i = null, s = [];
161
- const h = () => {
162
- if (s.length === 0)
186
+ let r = null, c = null, h = [];
187
+ const i = () => {
188
+ if (h.length === 0)
163
189
  return;
164
- const p = W(s, r, g);
165
- e.appendChild(p), a = null, i = p, s = [];
190
+ const p = G(h, a, w);
191
+ e.appendChild(p), r = null, c = p, h = [];
166
192
  };
167
193
  for (let p = 0; p < l.length; p += 1) {
168
- const u = l[p];
169
- if (u.role === "tool") {
170
- s.push(u);
194
+ const g = l[p];
195
+ if (g.role === "tool") {
196
+ h.push(g);
171
197
  continue;
172
198
  }
173
- if (u.role === "system")
199
+ if (g.role === "system")
174
200
  continue;
175
- h();
176
- const m = document.createElement("div");
177
- m.className = `pluno-pa-widget__message pluno-pa-widget__message--${u.role}`, u.role === "assistant" ? N(m, u.content) : m.textContent = u.content, e.appendChild(m), a = m, i = null;
201
+ i();
202
+ const b = O(g.content), _ = document.createElement("div");
203
+ _.className = `pluno-pa-widget__message pluno-pa-widget__message--${g.role}`, g.role === "assistant" ? B(_, b) : _.textContent = b, _.appendChild(D(b)), e.appendChild(_), r = _, c = null;
178
204
  }
179
- if (h(), t.assistantDraft) {
180
- const p = document.createElement("div");
181
- p.className = "pluno-pa-widget__message pluno-pa-widget__message--assistant", N(p, t.assistantDraft), e.appendChild(p), a = p, i = null;
205
+ if (i(), n.assistantDraft) {
206
+ const p = O(n.assistantDraft), g = document.createElement("div");
207
+ g.className = "pluno-pa-widget__message pluno-pa-widget__message--assistant", B(g, p), g.appendChild(D(p)), e.appendChild(g), r = g, c = null;
182
208
  }
183
- if (t.isThinking && !t.assistantDraft) {
209
+ if (n.isThinking && !n.assistantDraft) {
184
210
  const p = document.createElement("div");
185
- p.className = "pluno-pa-widget__status", p.textContent = "Thinking...", e.appendChild(p), a = null, i = p;
211
+ p.className = "pluno-pa-widget__status", p.textContent = "Thinking...", e.appendChild(p), r = null, c = p;
186
212
  }
187
- if (t.lastError) {
213
+ if (n.lastError) {
188
214
  const p = document.createElement("div");
189
- p.className = "pluno-pa-widget__error", p.textContent = t.lastError, e.appendChild(p), a = p, i = null;
215
+ p.className = "pluno-pa-widget__error", p.textContent = n.lastError, e.appendChild(p), r = p, c = null;
190
216
  }
191
- a?.classList.add("pluno-pa-widget__message--bottom-reserve"), i?.classList.add("pluno-pa-widget__bottom-reserve-compact"), e.scrollTop = e.scrollHeight;
217
+ r?.classList.add("pluno-pa-widget__message--bottom-reserve"), c?.classList.add("pluno-pa-widget__bottom-reserve-compact"), e.scrollTop = e.scrollHeight;
218
+ }
219
+ function D(e) {
220
+ const t = document.createElement("button");
221
+ return t.type = "button", t.className = "pluno-pa-widget__message-copy", t.setAttribute("aria-label", "Copy message"), t.title = "Copy message", t.innerHTML = '<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg>', t.addEventListener("click", (o) => {
222
+ o.preventDefault(), o.stopPropagation(), navigator.clipboard.writeText(e);
223
+ }), t;
192
224
  }
193
- function W(e, o, n) {
194
- const t = document.createElement("article");
195
- t.className = "pluno-pa-widget__tool-group", t.title = new Date(e[e.length - 1].createdAt).toLocaleString();
196
- const r = document.createElement("details"), g = M(e);
197
- r.open = o.has(g), r.addEventListener("toggle", () => {
198
- r.open ? o.add(g) : o.delete(g), n();
225
+ function O(e) {
226
+ return e.replace(/\uE200[^\uE201]*(?:\uE201|$)/g, "");
227
+ }
228
+ function G(e, t, o) {
229
+ const n = document.createElement("article");
230
+ n.className = "pluno-pa-widget__tool-group", n.title = new Date(e[e.length - 1].createdAt).toLocaleString();
231
+ const a = document.createElement("details"), w = U(e);
232
+ a.open = t.has(w), a.addEventListener("toggle", () => {
233
+ a.open ? t.add(w) : t.delete(w), o();
199
234
  });
200
- const l = document.createElement("summary");
201
- l.className = "pluno-pa-widget__tool-summary", e.some((i) => i.loading) && l.appendChild(z());
202
- const a = document.createElement("span");
203
- if (a.textContent = e[e.length - 1].content || "Run tool", l.appendChild(a), r.appendChild(l), e.length > 0) {
204
- const i = document.createElement("div");
205
- i.className = "pluno-pa-widget__tool-lines";
206
- for (const s of e) {
235
+ const s = document.createElement("summary");
236
+ s.className = "pluno-pa-widget__tool-summary", e.some((r) => r.loading) && s.appendChild(j());
237
+ const d = document.createElement("span");
238
+ if (d.textContent = e[e.length - 1].content || "Run tool", s.appendChild(d), a.appendChild(s), e.length > 0) {
239
+ const r = document.createElement("div");
240
+ r.className = "pluno-pa-widget__tool-lines";
241
+ for (const c of e) {
207
242
  const h = document.createElement("div");
208
- h.className = "pluno-pa-widget__tool-line", s.loading && h.appendChild(z());
209
- const p = document.createElement("span");
210
- p.textContent = s.content || "Run tool", h.appendChild(p), i.appendChild(h);
243
+ h.className = "pluno-pa-widget__tool-line", c.loading && h.appendChild(j());
244
+ const i = document.createElement("span");
245
+ i.textContent = c.content || "Run tool", h.appendChild(i), r.appendChild(h);
211
246
  }
212
- r.appendChild(i);
247
+ a.appendChild(r);
213
248
  }
214
- return t.appendChild(r), t;
249
+ return n.appendChild(a), n;
215
250
  }
216
- function M(e) {
217
- return `tools:${e.map((o) => o.id).join("|")}`;
251
+ function U(e) {
252
+ return `tools:${e.map((t) => t.id).join("|")}`;
218
253
  }
219
- function z() {
254
+ function j() {
220
255
  const e = document.createElement("span");
221
256
  return e.className = "pluno-pa-widget__spinner", e.setAttribute("aria-hidden", "true"), e;
222
257
  }
223
- function O(e, o) {
224
- e.setAttribute("aria-label", o ? "Stop" : "Send"), e.classList.toggle("pluno-pa-widget__send--stop", o), e.innerHTML = o ? '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="2" /></svg>' : '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="19" x2="12" y2="5" /><polyline points="5 12 12 5 19 12" /></svg>';
258
+ function V(e, t) {
259
+ e.setAttribute("aria-label", t ? "Stop" : "Send"), e.classList.toggle("pluno-pa-widget__send--stop", t), e.innerHTML = t ? '<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="2" /></svg>' : '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="12" y1="19" x2="12" y2="5" /><polyline points="5 12 12 5 19 12" /></svg>';
225
260
  }
226
- function N(e, o) {
227
- const n = o.replace(/\r\n/g, `
261
+ function B(e, t) {
262
+ const o = t.replace(/\r\n/g, `
228
263
  `).split(`
229
264
  `);
230
- let t = 0;
231
- for (; t < n.length; ) {
232
- const r = n[t];
233
- if (!r.trim()) {
234
- t += 1;
265
+ let n = 0;
266
+ for (; n < o.length; ) {
267
+ const a = o[n];
268
+ if (!a.trim()) {
269
+ n += 1;
235
270
  continue;
236
271
  }
237
- if (r.match(/^```(\w+)?\s*$/)) {
238
- const i = [];
239
- for (t += 1; t < n.length && !/^```\s*$/.test(n[t]); )
240
- i.push(n[t]), t += 1;
241
- t += t < n.length ? 1 : 0;
242
- const s = document.createElement("pre"), h = document.createElement("code");
243
- h.textContent = i.join(`
244
- `), s.appendChild(h), e.appendChild(s);
272
+ if (a.match(/^```(\w+)?\s*$/)) {
273
+ const r = [];
274
+ for (n += 1; n < o.length && !/^```\s*$/.test(o[n]); )
275
+ r.push(o[n]), n += 1;
276
+ n += n < o.length ? 1 : 0;
277
+ const c = document.createElement("pre"), h = document.createElement("code");
278
+ h.textContent = r.join(`
279
+ `), c.appendChild(h), e.appendChild(c);
245
280
  continue;
246
281
  }
247
- const l = r.match(/^(#{1,3})\s+(.+)$/);
248
- if (l) {
249
- const i = document.createElement(`h${l[1].length + 2}`);
250
- C(i, l[2]), e.appendChild(i), t += 1;
282
+ const s = a.match(/^(#{1,3})\s+(.+)$/);
283
+ if (s) {
284
+ const r = document.createElement(`h${s[1].length + 2}`);
285
+ T(r, s[2]), e.appendChild(r), n += 1;
251
286
  continue;
252
287
  }
253
- if (/^\s*[-*+]\s+/.test(r)) {
254
- const i = document.createElement("ul");
255
- for (; t < n.length && /^\s*[-*+]\s+/.test(n[t]); ) {
256
- const s = document.createElement("li");
257
- C(s, n[t].replace(/^\s*[-*+]\s+/, "")), i.appendChild(s), t += 1;
288
+ if (/^\s*[-*+]\s+/.test(a)) {
289
+ const r = document.createElement("ul");
290
+ for (; n < o.length && /^\s*[-*+]\s+/.test(o[n]); ) {
291
+ const c = document.createElement("li");
292
+ T(c, o[n].replace(/^\s*[-*+]\s+/, "")), r.appendChild(c), n += 1;
258
293
  }
259
- e.appendChild(i);
294
+ e.appendChild(r);
260
295
  continue;
261
296
  }
262
- if (/^\s*\d+\.\s+/.test(r)) {
263
- const i = document.createElement("ol");
264
- for (; t < n.length && /^\s*\d+\.\s+/.test(n[t]); ) {
265
- const s = document.createElement("li");
266
- C(s, n[t].replace(/^\s*\d+\.\s+/, "")), i.appendChild(s), t += 1;
297
+ if (/^\s*\d+\.\s+/.test(a)) {
298
+ const r = document.createElement("ol");
299
+ for (; n < o.length && /^\s*\d+\.\s+/.test(o[n]); ) {
300
+ const c = document.createElement("li");
301
+ T(c, o[n].replace(/^\s*\d+\.\s+/, "")), r.appendChild(c), n += 1;
267
302
  }
268
- e.appendChild(i);
303
+ e.appendChild(r);
269
304
  continue;
270
305
  }
271
- const c = [r];
272
- for (t += 1; t < n.length && n[t].trim() && !/^```/.test(n[t]) && !/^(#{1,3})\s+/.test(n[t]) && !/^\s*[-*+]\s+/.test(n[t]) && !/^\s*\d+\.\s+/.test(n[t]); )
273
- c.push(n[t]), t += 1;
274
- const a = document.createElement("p");
275
- c.forEach((i, s) => {
276
- s > 0 && a.appendChild(document.createElement("br")), C(a, i);
277
- }), e.appendChild(a);
306
+ const l = [a];
307
+ for (n += 1; n < o.length && o[n].trim() && !/^```/.test(o[n]) && !/^(#{1,3})\s+/.test(o[n]) && !/^\s*[-*+]\s+/.test(o[n]) && !/^\s*\d+\.\s+/.test(o[n]); )
308
+ l.push(o[n]), n += 1;
309
+ const d = document.createElement("p");
310
+ l.forEach((r, c) => {
311
+ c > 0 && d.appendChild(document.createElement("br")), T(d, r);
312
+ }), e.appendChild(d);
278
313
  }
279
314
  }
280
- function C(e, o) {
281
- const n = /(`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*|\[[^\]]+\]\(([^)\s]+)\))/g;
282
- let t = 0;
283
- for (const r of o.matchAll(n)) {
284
- const g = r.index ?? 0;
285
- g > t && e.appendChild(document.createTextNode(o.slice(t, g)));
286
- const l = r[0];
287
- if (l.startsWith("`")) {
288
- const c = document.createElement("code");
289
- c.textContent = l.slice(1, -1), e.appendChild(c);
290
- } else if (l.startsWith("**")) {
291
- const c = document.createElement("strong");
292
- c.textContent = l.slice(2, -2), e.appendChild(c);
293
- } else if (l.startsWith("*")) {
294
- const c = document.createElement("em");
295
- c.textContent = l.slice(1, -1), e.appendChild(c);
315
+ function T(e, t) {
316
+ const o = /(`[^`]+`|\*\*[^*]+\*\*|\*[^*]+\*|\[[^\]]+\]\(([^)\s]+)\))/g;
317
+ let n = 0;
318
+ for (const a of t.matchAll(o)) {
319
+ const w = a.index ?? 0;
320
+ w > n && e.appendChild(document.createTextNode(t.slice(n, w)));
321
+ const s = a[0];
322
+ if (s.startsWith("`")) {
323
+ const l = document.createElement("code");
324
+ l.textContent = s.slice(1, -1), e.appendChild(l);
325
+ } else if (s.startsWith("**")) {
326
+ const l = document.createElement("strong");
327
+ l.textContent = s.slice(2, -2), e.appendChild(l);
328
+ } else if (s.startsWith("*")) {
329
+ const l = document.createElement("em");
330
+ l.textContent = s.slice(1, -1), e.appendChild(l);
296
331
  } else {
297
- const c = l.match(/^\[([^\]]+)\]\(([^)\s]+)\)$/);
298
- if (c && j(c[2])) {
299
- const a = document.createElement("a");
300
- a.href = c[2], a.target = "_top", a.rel = "noreferrer", a.textContent = c[1], e.appendChild(a);
332
+ const l = s.match(/^\[([^\]]+)\]\(([^)\s]+)\)$/);
333
+ if (l && Y(l[2])) {
334
+ const d = document.createElement("a");
335
+ d.href = l[2], d.target = "_top", d.rel = "noreferrer", d.textContent = l[1], e.appendChild(d);
301
336
  } else
302
- e.appendChild(document.createTextNode(l));
337
+ e.appendChild(document.createTextNode(s));
303
338
  }
304
- t = g + l.length;
339
+ n = w + s.length;
305
340
  }
306
- t < o.length && e.appendChild(document.createTextNode(o.slice(t)));
341
+ n < t.length && e.appendChild(document.createTextNode(t.slice(n)));
307
342
  }
308
- function j(e) {
343
+ function Y(e) {
309
344
  if (e.startsWith("/") || e.startsWith("#"))
310
345
  return !0;
311
346
  try {
312
- const o = new URL(e);
313
- return ["http:", "https:", "mailto:"].includes(o.protocol);
347
+ const t = new URL(e);
348
+ return ["http:", "https:", "mailto:"].includes(t.protocol);
314
349
  } catch {
315
350
  return !1;
316
351
  }
317
352
  }
318
- function B(e) {
319
- if (document.getElementById("pluno-pa-widget-styles"))
353
+ function I(e, t) {
354
+ if (e.getElementById("pluno-pa-widget-styles"))
320
355
  return;
321
- const o = document.createElement("style");
356
+ const o = e.ownerDocument.createElement("style");
322
357
  o.id = "pluno-pa-widget-styles", o.textContent = `
358
+ :host {
359
+ all: initial;
360
+ color-scheme: light;
361
+ }
362
+ *,
363
+ *::before,
364
+ *::after {
365
+ box-sizing: border-box;
366
+ }
367
+ input,
368
+ textarea,
369
+ button {
370
+ margin: 0;
371
+ font: inherit;
372
+ color: inherit;
373
+ -webkit-appearance: none;
374
+ appearance: none;
375
+ }
376
+ button,
377
+ summary {
378
+ cursor: pointer;
379
+ }
380
+ input,
381
+ textarea {
382
+ cursor: text;
383
+ }
384
+ svg {
385
+ display: block;
386
+ flex: 0 0 auto;
387
+ }
323
388
  .pluno-pa-widget {
324
- --pluno-pa-accent: ${e};
389
+ --pluno-pa-accent: ${t};
325
390
  --pluno-pa-bg: #fafaf9;
326
391
  --pluno-pa-surface: #f4f4f2;
327
392
  --pluno-pa-bubble: #eeeded;
@@ -356,9 +421,6 @@ function B(e) {
356
421
  --pluno-pa-accent-soft: rgba(245, 245, 244, 0.22);
357
422
  --pluno-pa-accent-contrast: #18181b;
358
423
  }
359
- .pluno-pa-widget * {
360
- box-sizing: border-box;
361
- }
362
424
  .pluno-pa-widget [hidden] {
363
425
  display: none !important;
364
426
  }
@@ -581,6 +643,7 @@ function B(e) {
581
643
  background: transparent;
582
644
  }
583
645
  .pluno-pa-widget__message {
646
+ position: relative;
584
647
  min-width: 0;
585
648
  color: var(--pluno-pa-text);
586
649
  font-size: 14px;
@@ -601,6 +664,43 @@ function B(e) {
601
664
  align-self: stretch;
602
665
  padding: 0 2px;
603
666
  }
667
+ .pluno-pa-widget__message-copy {
668
+ position: absolute;
669
+ bottom: -24px;
670
+ z-index: 3;
671
+ width: 22px;
672
+ height: 22px;
673
+ display: inline-flex;
674
+ align-items: center;
675
+ justify-content: center;
676
+ border: 1px solid var(--pluno-pa-divider);
677
+ border-radius: 999px;
678
+ background: color-mix(in srgb, var(--pluno-pa-bg) 92%, transparent);
679
+ color: var(--pluno-pa-muted);
680
+ box-shadow: 0 4px 10px rgba(24, 24, 27, 0.08);
681
+ cursor: pointer;
682
+ opacity: 0;
683
+ pointer-events: none;
684
+ transform: translateY(-2px);
685
+ transition: opacity 120ms ease, transform 120ms ease, background 120ms ease, color 120ms ease;
686
+ }
687
+ .pluno-pa-widget__message--user .pluno-pa-widget__message-copy {
688
+ right: 4px;
689
+ }
690
+ .pluno-pa-widget__message--assistant .pluno-pa-widget__message-copy {
691
+ left: 2px;
692
+ }
693
+ .pluno-pa-widget__message:hover .pluno-pa-widget__message-copy,
694
+ .pluno-pa-widget__message:focus-within .pluno-pa-widget__message-copy {
695
+ opacity: 1;
696
+ pointer-events: auto;
697
+ transform: translateY(0);
698
+ }
699
+ .pluno-pa-widget__message-copy:hover,
700
+ .pluno-pa-widget__message-copy:focus-visible {
701
+ background: var(--pluno-pa-surface);
702
+ color: var(--pluno-pa-text);
703
+ }
604
704
  .pluno-pa-widget__message--bottom-reserve {
605
705
  margin-bottom: 46px;
606
706
  }
@@ -613,6 +713,9 @@ function B(e) {
613
713
  .pluno-pa-widget__message--assistant > :last-child {
614
714
  margin-bottom: 0;
615
715
  }
716
+ .pluno-pa-widget__message--assistant > :nth-last-child(2) {
717
+ margin-bottom: 0;
718
+ }
616
719
  .pluno-pa-widget__message--assistant p,
617
720
  .pluno-pa-widget__message--assistant ul,
618
721
  .pluno-pa-widget__message--assistant ol,
@@ -814,6 +917,40 @@ function B(e) {
814
917
  margin: auto;
815
918
  padding: 24px 0;
816
919
  }
920
+ .pluno-pa-widget__empty-state {
921
+ margin: auto;
922
+ display: grid;
923
+ gap: 12px;
924
+ justify-items: center;
925
+ width: 100%;
926
+ padding: 24px 0;
927
+ }
928
+ .pluno-pa-widget__empty-state .pluno-pa-widget__empty {
929
+ margin: 0;
930
+ padding: 0;
931
+ }
932
+ .pluno-pa-widget__starter-prompts {
933
+ display: grid;
934
+ gap: 8px;
935
+ width: min(100%, 320px);
936
+ }
937
+ .pluno-pa-widget__starter-prompt {
938
+ border: 1px solid var(--pluno-pa-border);
939
+ border-radius: 16px;
940
+ background: var(--pluno-pa-surface);
941
+ color: var(--pluno-pa-text);
942
+ font: inherit;
943
+ font-size: 12px;
944
+ line-height: 1.45;
945
+ padding: 10px 12px;
946
+ text-align: left;
947
+ cursor: pointer;
948
+ transition: border-color 120ms ease, background 120ms ease;
949
+ }
950
+ .pluno-pa-widget__starter-prompt:hover {
951
+ border-color: rgba(255, 255, 255, 0.18);
952
+ background: rgba(255, 255, 255, 0.05);
953
+ }
817
954
  .pluno-pa-widget__status {
818
955
  display: inline-flex;
819
956
  align-items: center;
@@ -853,49 +990,57 @@ function B(e) {
853
990
  .pluno-pa-widget__error.pluno-pa-widget__message--bottom-reserve {
854
991
  margin-bottom: 46px;
855
992
  }
856
- `, document.head.appendChild(o);
993
+ `, e.appendChild(o);
994
+ }
995
+ async function R() {
996
+ return document.body ? document.body : await new Promise((e) => {
997
+ const t = new MutationObserver(() => {
998
+ document.body && (t.disconnect(), e(document.body));
999
+ });
1000
+ t.observe(document.documentElement, { childList: !0 });
1001
+ });
857
1002
  }
858
- function A(e) {
859
- const o = document.createElement("span");
860
- return o.textContent = e, o.innerHTML;
1003
+ function W(e) {
1004
+ const t = document.createElement("span");
1005
+ return t.textContent = e, t.innerHTML;
861
1006
  }
862
- function P(e) {
1007
+ function F(e) {
863
1008
  return `pluno.productAgent.widgetState.${location.origin}.${e.backendUrl ?? ""}.${e.tokenEndpoint ?? ""}.${e.position ?? ""}`;
864
1009
  }
865
- function F(e) {
1010
+ function J(e) {
866
1011
  try {
867
- const o = window.localStorage.getItem(P(e));
868
- if (!o)
1012
+ const t = window.localStorage.getItem(F(e));
1013
+ if (!t)
869
1014
  return { isOpen: !1, composerValue: "", openToolGroupKeys: [] };
870
- const n = JSON.parse(o);
1015
+ const o = JSON.parse(t);
871
1016
  return {
872
- isOpen: n.isOpen === !0,
873
- composerValue: typeof n.composerValue == "string" ? n.composerValue : "",
874
- openToolGroupKeys: Array.isArray(n.openToolGroupKeys) ? n.openToolGroupKeys.filter((t) => typeof t == "string") : []
1017
+ isOpen: o.isOpen === !0,
1018
+ composerValue: typeof o.composerValue == "string" ? o.composerValue : "",
1019
+ openToolGroupKeys: Array.isArray(o.openToolGroupKeys) ? o.openToolGroupKeys.filter((n) => typeof n == "string") : []
875
1020
  };
876
1021
  } catch {
877
1022
  return { isOpen: !1, composerValue: "", openToolGroupKeys: [] };
878
1023
  }
879
1024
  }
880
- function H(e, o) {
1025
+ function Z(e, t) {
881
1026
  try {
882
- window.localStorage.setItem(P(e), JSON.stringify(o));
1027
+ window.localStorage.setItem(F(e), JSON.stringify(t));
883
1028
  } catch {
884
1029
  return;
885
1030
  }
886
1031
  }
887
- const w = document.currentScript;
888
- w && w.dataset.plunoAutoMount !== "false" && I({
889
- token: w?.dataset.plunoToken,
890
- tokenEndpoint: w?.dataset.plunoTokenEndpoint,
891
- backendUrl: w?.dataset.plunoBackendUrl,
892
- launcherLabel: w?.dataset.plunoLauncherLabel,
893
- accentColor: w?.dataset.plunoAccentColor,
894
- colorScheme: w?.dataset.plunoColorScheme === "dark" ? "dark" : "light",
895
- position: w?.dataset.plunoPosition === "bottom-left" ? "bottom-left" : "bottom-right"
1032
+ const f = document.currentScript;
1033
+ f && f.dataset.plunoAutoMount !== "false" && K({
1034
+ token: f?.dataset.plunoToken,
1035
+ tokenEndpoint: f?.dataset.plunoTokenEndpoint,
1036
+ backendUrl: f?.dataset.plunoBackendUrl,
1037
+ launcherLabel: f?.dataset.plunoLauncherLabel,
1038
+ accentColor: f?.dataset.plunoAccentColor,
1039
+ colorScheme: f?.dataset.plunoColorScheme === "dark" ? "dark" : "light",
1040
+ position: f?.dataset.plunoPosition === "bottom-left" ? "bottom-left" : "bottom-right"
896
1041
  }).catch((e) => {
897
1042
  console.error("Failed to mount Pluno Product Agent widget", e);
898
1043
  });
899
1044
  export {
900
- I as mountPlunoProductAgentWidget
1045
+ K as mountPlunoProductAgentWidget
901
1046
  };
package/package.json CHANGED
@@ -1,17 +1,12 @@
1
1
  {
2
2
  "name": "@pluno/product-agent-web",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Browser SDK and default widget for embedding Pluno Product Agent into customer web apps.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "main": "dist/product-agent-sdk.js",
8
8
  "module": "dist/product-agent-sdk.js",
9
9
  "types": "dist/index.d.ts",
10
- "repository": {
11
- "type": "git",
12
- "url": "git+https://github.com/unbrainedgmbh/pluno.git",
13
- "directory": "product-agent-web-sdk"
14
- },
15
10
  "homepage": "https://pluno.ai",
16
11
  "bugs": {
17
12
  "url": "https://github.com/unbrainedgmbh/pluno/issues"
@@ -50,6 +45,6 @@
50
45
  "jsdom": "^26.1.0",
51
46
  "typescript": "^5.9.2",
52
47
  "vite": "^7.1.3",
53
- "vitest": "^3.2.4"
48
+ "vitest": "3.2.4"
54
49
  }
55
- }
50
+ }