@dabalabs/agent 0.0.2-beta → 0.0.4-beta

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.
Files changed (40) hide show
  1. package/README.md +56 -3
  2. package/dist/chunk-3BQ6LE6X.cjs +222 -0
  3. package/dist/chunk-3BQ6LE6X.cjs.map +1 -0
  4. package/dist/{chunk-CMHPVBEF.js → chunk-ETI4LRM7.js} +841 -100
  5. package/dist/chunk-ETI4LRM7.js.map +1 -0
  6. package/dist/chunk-TYOHYQDK.js +220 -0
  7. package/dist/chunk-TYOHYQDK.js.map +1 -0
  8. package/dist/{chunk-IMPMGYCG.cjs → chunk-VAIC5ACS.cjs} +841 -99
  9. package/dist/chunk-VAIC5ACS.cjs.map +1 -0
  10. package/dist/custom-element.cjs +4 -4
  11. package/dist/custom-element.d.cts +191 -2
  12. package/dist/custom-element.d.ts +191 -2
  13. package/dist/custom-element.js +2 -2
  14. package/dist/daba-agent.cjs +5 -14
  15. package/dist/daba-agent.d.cts +2 -4
  16. package/dist/daba-agent.d.ts +2 -4
  17. package/dist/daba-agent.js +2 -3
  18. package/dist/daba-agent.min.js +141 -36
  19. package/dist/daba-agent.min.js.map +1 -1
  20. package/dist/react.cjs +41 -14
  21. package/dist/react.cjs.map +1 -1
  22. package/dist/react.d.cts +1 -1
  23. package/dist/react.d.ts +1 -1
  24. package/dist/react.js +38 -2
  25. package/dist/react.js.map +1 -1
  26. package/dist/{types-hdR_wQ1B.d.cts → types-BQRgqD4n.d.cts} +70 -4
  27. package/dist/{types-hdR_wQ1B.d.ts → types-BQRgqD4n.d.ts} +70 -4
  28. package/package.json +1 -1
  29. package/dist/chunk-7ENX2QZK.js +0 -193
  30. package/dist/chunk-7ENX2QZK.js.map +0 -1
  31. package/dist/chunk-CMHPVBEF.js.map +0 -1
  32. package/dist/chunk-GCU5OBSZ.js +0 -40
  33. package/dist/chunk-GCU5OBSZ.js.map +0 -1
  34. package/dist/chunk-HJPH4GNS.cjs +0 -196
  35. package/dist/chunk-HJPH4GNS.cjs.map +0 -1
  36. package/dist/chunk-IMPMGYCG.cjs.map +0 -1
  37. package/dist/chunk-JQMJMSFT.cjs +0 -46
  38. package/dist/chunk-JQMJMSFT.cjs.map +0 -1
  39. package/dist/custom-element-BZAUI4Dc.d.cts +0 -121
  40. package/dist/custom-element-PL2IXpG2.d.ts +0 -121
@@ -1,3 +1,17 @@
1
+ // src/utils/css-length.ts
2
+ var UNIT = /^-?(?:\d+\.?\d*|\.\d+)(?:px|rem|em|%|vh|vw|dvh|dvw|svh|lvh|ch|pt|cm|mm|in)$/i;
3
+ var BARE_NUMBER = /^-?(?:\d+\.?\d*|\.\d+)$/;
4
+ var FUNCTION = /^(?:calc|min|max|clamp)\((?:[^;{}()]|\([^;{}()]*\))*\)$/i;
5
+ function cssLength(value, allowFunctions = true) {
6
+ if (value === null || value === void 0) return null;
7
+ const raw = String(value).trim();
8
+ if (!raw) return null;
9
+ if (BARE_NUMBER.test(raw)) return `${raw}px`;
10
+ if (UNIT.test(raw)) return raw;
11
+ if (allowFunctions && FUNCTION.test(raw)) return raw;
12
+ return null;
13
+ }
14
+
1
15
  // src/api/agent-api.ts
2
16
  var DabaFetchError = class extends Error {
3
17
  constructor(dabaError, cause) {
@@ -292,6 +306,7 @@ async function sendChatTurn(gatewayUrl, projectId, apiKey, body) {
292
306
  // src/voice/live-transport.ts
293
307
  var DATA_CHANNEL = "oai-events";
294
308
  var ICE_TIMEOUT_MS = 2e3;
309
+ var TURN_END_MS = 3500;
295
310
  var LiveTransport = class {
296
311
  constructor(options) {
297
312
  this.options = options;
@@ -300,6 +315,8 @@ var LiveTransport = class {
300
315
  this.micStream = null;
301
316
  this.audioEl = null;
302
317
  this.stopped = false;
318
+ this.turnTimers = {};
319
+ this.openTurns = /* @__PURE__ */ new Set();
303
320
  }
304
321
  get isActive() {
305
322
  return this.pc !== null && !this.stopped;
@@ -402,21 +419,28 @@ var LiveTransport = class {
402
419
  } catch {
403
420
  return;
404
421
  }
422
+ this.handleEvent(msg);
423
+ }
424
+ handleEvent(msg) {
425
+ if (msg.type === "response.event") {
426
+ const inner = msg.event;
427
+ if (inner && typeof inner === "object") this.handleEvent(inner);
428
+ return;
429
+ }
405
430
  const { handlers } = this.options;
406
431
  switch (msg.type) {
407
432
  case "session.input_transcript.delta":
408
- this.emitTranscript("user", msg, false);
433
+ this.onDelta("user", msg);
409
434
  break;
410
435
  case "session.input_transcript.done":
411
- this.emitTranscript("user", msg, true);
436
+ this.endTurn("user");
412
437
  break;
413
438
  case "session.output_transcript.delta":
414
439
  handlers.onStatus("speaking");
415
- this.emitTranscript("agent", msg, false);
440
+ this.onDelta("agent", msg);
416
441
  break;
417
442
  case "session.output_transcript.done":
418
- this.emitTranscript("agent", msg, true);
419
- handlers.onStatus("listening");
443
+ this.endTurn("agent");
420
444
  break;
421
445
  case "response.output_item.done":
422
446
  this.emitToolCall(msg);
@@ -431,6 +455,20 @@ var LiveTransport = class {
431
455
  }
432
456
  }
433
457
  }
458
+ onDelta(role, msg) {
459
+ this.endTurn(role === "user" ? "agent" : "user");
460
+ this.emitTranscript(role, msg, false);
461
+ this.openTurns.add(role);
462
+ window.clearTimeout(this.turnTimers[role]);
463
+ this.turnTimers[role] = window.setTimeout(() => this.endTurn(role), TURN_END_MS);
464
+ }
465
+ endTurn(role) {
466
+ window.clearTimeout(this.turnTimers[role]);
467
+ this.turnTimers[role] = void 0;
468
+ if (!this.openTurns.delete(role)) return;
469
+ this.options.handlers.onTranscript(role, "", true);
470
+ if (role === "agent") this.options.handlers.onStatus("listening");
471
+ }
434
472
  emitTranscript(role, msg, final) {
435
473
  const text = msg.delta ?? msg.transcript ?? msg.text;
436
474
  if (!text && !final) return;
@@ -451,6 +489,10 @@ var LiveTransport = class {
451
489
  teardown() {
452
490
  if (this.stopped) return;
453
491
  this.stopped = true;
492
+ window.clearTimeout(this.turnTimers.user);
493
+ window.clearTimeout(this.turnTimers.agent);
494
+ this.turnTimers = {};
495
+ this.openTurns.clear();
454
496
  this.channel?.close();
455
497
  this.channel = null;
456
498
  this.micStream?.getTracks().forEach((track) => track.stop());
@@ -581,8 +623,8 @@ function labelForField(el) {
581
623
  }
582
624
  const closestLabel = el.closest("label");
583
625
  if (closestLabel && closestLabel.textContent) return cleanText(closestLabel.textContent);
584
- const ariaLabel = el.getAttribute("aria-label");
585
- if (ariaLabel) return cleanText(ariaLabel);
626
+ const ariaLabel2 = el.getAttribute("aria-label");
627
+ if (ariaLabel2) return cleanText(ariaLabel2);
586
628
  const placeholder = el.getAttribute("placeholder");
587
629
  if (placeholder) return cleanText(placeholder);
588
630
  const name = el.getAttribute("name");
@@ -644,8 +686,8 @@ function labelForField2(el) {
644
686
  }
645
687
  const closestLabel = el.closest("label");
646
688
  if (closestLabel && closestLabel.textContent) return cleanText2(closestLabel.textContent);
647
- const ariaLabel = el.getAttribute("aria-label");
648
- if (ariaLabel) return cleanText2(ariaLabel);
689
+ const ariaLabel2 = el.getAttribute("aria-label");
690
+ if (ariaLabel2) return cleanText2(ariaLabel2);
649
691
  const placeholder = el.getAttribute("placeholder");
650
692
  if (placeholder) return cleanText2(placeholder);
651
693
  const name = el.getAttribute("name");
@@ -1084,16 +1126,22 @@ var MAX_TRANSCRIPT_BYTES = 96 * 1024;
1084
1126
  function transcriptKey(projectId, conversationId) {
1085
1127
  return `${TRANSCRIPT_KEY_PREFIX}${projectId}:${conversationId}`;
1086
1128
  }
1129
+ function boundTranscript(messages) {
1130
+ let slice = messages.slice(-40);
1131
+ let raw = JSON.stringify(slice);
1132
+ while (raw.length > MAX_TRANSCRIPT_BYTES && slice.length > 1) {
1133
+ slice = slice.slice(1);
1134
+ raw = JSON.stringify(slice);
1135
+ }
1136
+ return slice;
1137
+ }
1087
1138
  function persistTranscript(projectId, conversationId, messages) {
1088
1139
  if (typeof sessionStorage === "undefined") return;
1089
- let slice = messages.slice(-40);
1090
1140
  try {
1091
- let raw = JSON.stringify(slice);
1092
- while (raw.length > MAX_TRANSCRIPT_BYTES && slice.length > 1) {
1093
- slice = slice.slice(1);
1094
- raw = JSON.stringify(slice);
1095
- }
1096
- sessionStorage.setItem(transcriptKey(projectId, conversationId), raw);
1141
+ sessionStorage.setItem(
1142
+ transcriptKey(projectId, conversationId),
1143
+ JSON.stringify(boundTranscript(messages))
1144
+ );
1097
1145
  } catch {
1098
1146
  }
1099
1147
  }
@@ -1118,6 +1166,110 @@ function clearTranscript(projectId, conversationId) {
1118
1166
  } catch {
1119
1167
  }
1120
1168
  }
1169
+ var HISTORY_INDEX_PREFIX = "daba-agent:history:";
1170
+ var HISTORY_BODY_PREFIX = "daba-agent:history-body:";
1171
+ var MAX_CONVERSATIONS = 20;
1172
+ var MAX_HISTORY_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
1173
+ var MAX_TITLE_CHARS = 60;
1174
+ function indexKey(projectId) {
1175
+ return HISTORY_INDEX_PREFIX + projectId;
1176
+ }
1177
+ function bodyKey(projectId, conversationId) {
1178
+ return `${HISTORY_BODY_PREFIX}${projectId}:${conversationId}`;
1179
+ }
1180
+ function deriveTitle(messages, fallback) {
1181
+ const first = messages.find((m) => m.role === "user" && m.text.trim());
1182
+ const text = first?.text.trim().replace(/\s+/g, " ") ?? "";
1183
+ if (!text) return fallback;
1184
+ const chars = [...text];
1185
+ return chars.length > MAX_TITLE_CHARS ? `${chars.slice(0, MAX_TITLE_CHARS).join("")}\u2026` : text;
1186
+ }
1187
+ function readIndex(projectId) {
1188
+ if (typeof localStorage === "undefined") return [];
1189
+ try {
1190
+ const raw = localStorage.getItem(indexKey(projectId));
1191
+ if (!raw) return [];
1192
+ const parsed = JSON.parse(raw);
1193
+ if (!Array.isArray(parsed)) return [];
1194
+ return parsed.filter(
1195
+ (c) => !!c && typeof c === "object" && typeof c.id === "string"
1196
+ );
1197
+ } catch {
1198
+ return [];
1199
+ }
1200
+ }
1201
+ function writeIndex(projectId, entries) {
1202
+ if (typeof localStorage === "undefined") return [];
1203
+ const cutoff = Date.now() - MAX_HISTORY_AGE_MS;
1204
+ const kept = entries.filter((c) => c.updatedAt > cutoff).sort((a, b) => b.updatedAt - a.updatedAt).slice(0, MAX_CONVERSATIONS);
1205
+ const keptIds = new Set(kept.map((c) => c.id));
1206
+ for (const dropped of entries) {
1207
+ if (keptIds.has(dropped.id)) continue;
1208
+ try {
1209
+ localStorage.removeItem(bodyKey(projectId, dropped.id));
1210
+ } catch {
1211
+ }
1212
+ }
1213
+ try {
1214
+ localStorage.setItem(indexKey(projectId), JSON.stringify(kept));
1215
+ } catch {
1216
+ }
1217
+ return kept;
1218
+ }
1219
+ function archiveConversation(projectId, conversationId, messages, path, fallbackTitle) {
1220
+ if (typeof localStorage === "undefined") return;
1221
+ const body = boundTranscript(messages);
1222
+ if (!body.length) return;
1223
+ try {
1224
+ localStorage.setItem(bodyKey(projectId, conversationId), JSON.stringify(body));
1225
+ } catch {
1226
+ return;
1227
+ }
1228
+ const entry = {
1229
+ id: conversationId,
1230
+ title: deriveTitle(body, fallbackTitle),
1231
+ updatedAt: Date.now(),
1232
+ messageCount: body.length,
1233
+ path: path.split("?")[0].split("#")[0]
1234
+ };
1235
+ const others = readIndex(projectId).filter((c) => c.id !== conversationId);
1236
+ writeIndex(projectId, [entry, ...others]);
1237
+ }
1238
+ function listConversations(projectId) {
1239
+ return writeIndex(projectId, readIndex(projectId));
1240
+ }
1241
+ function readConversation(projectId, conversationId) {
1242
+ if (typeof localStorage === "undefined") return [];
1243
+ try {
1244
+ const raw = localStorage.getItem(bodyKey(projectId, conversationId));
1245
+ if (!raw) return [];
1246
+ const parsed = JSON.parse(raw);
1247
+ return Array.isArray(parsed) ? parsed : [];
1248
+ } catch {
1249
+ return [];
1250
+ }
1251
+ }
1252
+ function deleteConversation(projectId, conversationId) {
1253
+ if (typeof localStorage === "undefined") return;
1254
+ try {
1255
+ localStorage.removeItem(bodyKey(projectId, conversationId));
1256
+ } catch {
1257
+ }
1258
+ writeIndex(projectId, readIndex(projectId).filter((c) => c.id !== conversationId));
1259
+ }
1260
+ function clearConversations(projectId) {
1261
+ if (typeof localStorage === "undefined") return;
1262
+ for (const entry of readIndex(projectId)) {
1263
+ try {
1264
+ localStorage.removeItem(bodyKey(projectId, entry.id));
1265
+ } catch {
1266
+ }
1267
+ }
1268
+ try {
1269
+ localStorage.removeItem(indexKey(projectId));
1270
+ } catch {
1271
+ }
1272
+ }
1121
1273
 
1122
1274
  // src/ui/styles.ts
1123
1275
  var STYLE_ELEMENT_ID = "daba-agent-styles";
@@ -1164,8 +1316,15 @@ var KNOWN_TOKENS = /* @__PURE__ */ new Set([
1164
1316
  "inset",
1165
1317
  "gap",
1166
1318
  "badge-size",
1319
+ "badge-icon-size",
1320
+ "badge-radius",
1167
1321
  "panel-w",
1168
- "panel-h"
1322
+ "panel-h",
1323
+ // The trigger icon's two recolourable fills. Written on the wrapper so
1324
+ // the panel header and intro marks pick them up too, not just the
1325
+ // launcher — see ui/icon.ts.
1326
+ "icon-bg",
1327
+ "icon-mark"
1169
1328
  ]);
1170
1329
  var UNSAFE_TOKEN_VALUE = /[;{}<>]|url\s*\(|expression\s*\(/i;
1171
1330
  function tokenProperty(key) {
@@ -1235,8 +1394,10 @@ var SHEET = `
1235
1394
  --daba-agent-inset: 20px;
1236
1395
  --daba-agent-gap: 12px;
1237
1396
  --daba-agent-badge-size: 56px;
1238
- --daba-agent-panel-w: 380px;
1239
- --daba-agent-panel-h: 560px;
1397
+ --daba-agent-badge-icon-size: 100%;
1398
+ --daba-agent-badge-radius: 10px;
1399
+ --daba-agent-panel-w: 400px;
1400
+ --daba-agent-panel-h: 740px;
1240
1401
  --daba-agent-anchor: flex-end;
1241
1402
  }
1242
1403
 
@@ -1405,27 +1566,34 @@ h1, h2, h3, h4, h5, h6) {
1405
1566
  }
1406
1567
 
1407
1568
  .daba-agent-badge--circle {
1408
- border-radius: var(--daba-agent-radius-pill);
1569
+ --daba-agent-badge-radius: var(--daba-agent-radius-pill);
1409
1570
  }
1410
1571
  .daba-agent-badge--rounded {
1411
- border-radius: calc(var(--daba-agent-radius) * 0.95);
1572
+ --daba-agent-badge-radius: calc(var(--daba-agent-radius) * 0.95);
1412
1573
  }
1413
1574
  .daba-agent-badge--pill {
1575
+ --daba-agent-badge-radius: var(--daba-agent-radius-pill);
1414
1576
  width: auto;
1415
1577
  height: calc(var(--daba-agent-badge-size) * 0.82);
1416
1578
  padding-inline: 8px 18px;
1417
1579
  gap: 10px;
1418
- border-radius: var(--daba-agent-radius-pill);
1419
1580
  }
1420
1581
 
1421
1582
  .daba-agent-badge__icon {
1422
1583
  display: block;
1423
- width: 100%;
1424
- height: 100%;
1584
+ width: var(--daba-agent-badge-icon-size);
1585
+ height: var(--daba-agent-badge-icon-size);
1586
+ margin: auto;
1425
1587
  border-radius: inherit;
1426
1588
  overflow: hidden;
1427
1589
  line-height: 0;
1428
1590
  }
1591
+ .daba-agent-badge__icon--text {
1592
+ display: grid;
1593
+ place-items: center;
1594
+ font-size: calc(var(--daba-agent-badge-size) * 0.48);
1595
+ line-height: 1;
1596
+ }
1429
1597
  .daba-agent-badge--pill .daba-agent-badge__icon {
1430
1598
  width: calc(var(--daba-agent-badge-size) * 0.55);
1431
1599
  height: calc(var(--daba-agent-badge-size) * 0.55);
@@ -1509,10 +1677,6 @@ h1, h2, h3, h4, h5, h6) {
1509
1677
  .daba-agent-window {
1510
1678
  display: flex;
1511
1679
  flex-direction: column;
1512
- width: var(--daba-agent-panel-w);
1513
- height: min(var(--daba-agent-panel-h), calc(100vh - 140px));
1514
- max-width: calc(100vw - 24px);
1515
- max-height: calc(100vh - 120px);
1516
1680
  background: var(--daba-agent-surface);
1517
1681
  color: var(--daba-agent-text);
1518
1682
  border: 1px solid var(--daba-agent-border);
@@ -2407,8 +2571,8 @@ h1, h2, h3, h4, h5, h6) {
2407
2571
  --ref-mark: #2b2b2b;
2408
2572
  --ref-user: #f2f2f2;
2409
2573
 
2410
- width: 400px;
2411
- height: 740px;
2574
+ width: var(--daba-agent-panel-w);
2575
+ height: var(--daba-agent-panel-h);
2412
2576
  /* The launcher sits above the panel in the same column, so the panel
2413
2577
  must leave room for it plus the viewport margins \u2014 otherwise on a
2414
2578
  laptop screen the two overlap at the corner. 40px badge + 12px gap
@@ -2832,9 +2996,121 @@ h1, h2, h3, h4, h5, h6) {
2832
2996
  background: #ffffff;
2833
2997
  }
2834
2998
 
2999
+ /* --- history: the same sheet, one level in -------------------------------- */
3000
+ .daba-agent-history {
3001
+ display: flex;
3002
+ flex-direction: column;
3003
+ flex: 1;
3004
+ min-height: 0;
3005
+ background: var(--daba-agent-surface);
3006
+ }
3007
+ .daba-agent-history__head {
3008
+ display: flex;
3009
+ align-items: center;
3010
+ gap: 8px;
3011
+ padding: 10px 12px;
3012
+ border-block-end: 1px solid var(--ref-line);
3013
+ }
3014
+ .daba-agent-history__title {
3015
+ margin: 0;
3016
+ font-size: 14px;
3017
+ font-weight: 600;
3018
+ color: var(--ref-ink);
3019
+ }
3020
+ .daba-agent-history__title:focus-visible {
3021
+ outline: 2px solid var(--daba-agent-accent);
3022
+ outline-offset: 2px;
3023
+ }
3024
+ .daba-agent-history__back {
3025
+ display: grid;
3026
+ place-items: center;
3027
+ width: 28px;
3028
+ height: 28px;
3029
+ border: 0;
3030
+ border-radius: var(--daba-agent-radius-sm);
3031
+ background: transparent;
3032
+ color: var(--ref-muted);
3033
+ cursor: pointer;
3034
+ }
3035
+ .daba-agent-history__back:hover { color: var(--ref-ink); background: var(--ref-user); }
3036
+ .daba-agent-history__list {
3037
+ flex: 1;
3038
+ min-height: 0;
3039
+ overflow-y: auto;
3040
+ padding: 6px;
3041
+ }
3042
+ .daba-agent-history__item {
3043
+ display: flex;
3044
+ align-items: stretch;
3045
+ gap: 2px;
3046
+ border-radius: var(--daba-agent-radius-sm);
3047
+ }
3048
+ .daba-agent-history__item:hover { background: var(--ref-user); }
3049
+ .daba-agent-history__open {
3050
+ flex: 1;
3051
+ min-width: 0;
3052
+ display: flex;
3053
+ flex-direction: column;
3054
+ gap: 2px;
3055
+ padding: 10px;
3056
+ border: 0;
3057
+ background: transparent;
3058
+ font: inherit;
3059
+ text-align: start;
3060
+ cursor: pointer;
3061
+ }
3062
+ .daba-agent-history__name {
3063
+ overflow: hidden;
3064
+ text-overflow: ellipsis;
3065
+ white-space: nowrap;
3066
+ font-size: 13px;
3067
+ color: var(--ref-ink);
3068
+ }
3069
+ .daba-agent-history__meta {
3070
+ font-size: 11px;
3071
+ color: var(--ref-faint);
3072
+ }
3073
+ .daba-agent-history__delete {
3074
+ display: grid;
3075
+ place-items: center;
3076
+ width: 32px;
3077
+ border: 0;
3078
+ border-radius: var(--daba-agent-radius-sm);
3079
+ background: transparent;
3080
+ color: var(--ref-faint);
3081
+ cursor: pointer;
3082
+ /* Revealed on hover: a delete button beside every row turns a list of
3083
+ conversations into a list of ways to lose one. */
3084
+ opacity: 0;
3085
+ }
3086
+ .daba-agent-history__item:hover .daba-agent-history__delete,
3087
+ .daba-agent-history__delete:focus-visible { opacity: 1; }
3088
+ .daba-agent-history__delete:hover { color: var(--daba-agent-danger); }
3089
+ .daba-agent-history__empty {
3090
+ margin: 0;
3091
+ padding: 24px 12px;
3092
+ text-align: center;
3093
+ font-size: 13px;
3094
+ color: var(--ref-muted);
3095
+ }
3096
+ .daba-agent-history__clear {
3097
+ display: block;
3098
+ width: 100%;
3099
+ margin-block-start: 8px;
3100
+ padding: 10px;
3101
+ border: 0;
3102
+ border-radius: var(--daba-agent-radius-sm);
3103
+ background: transparent;
3104
+ font: inherit;
3105
+ font-size: 12px;
3106
+ color: var(--ref-muted);
3107
+ cursor: pointer;
3108
+ }
3109
+ .daba-agent-history__clear:hover { color: var(--daba-agent-danger); }
3110
+
2835
3111
  /* --- launcher: darkens toward black, same hue ----------------------------- */
2836
3112
  .daba-agent-badge {
2837
- border-radius: 10px;
3113
+ border-radius: var(--daba-agent-badge-radius);
2838
3114
  transition: background-color var(--daba-ms) var(--daba-ease);
2839
3115
  }
2840
3116
  .daba-agent-badge:hover { background: color-mix(in srgb, var(--daba-agent-accent) 92%, #000000); }
@@ -3076,6 +3352,42 @@ var AGENT_LOGO_BADGE_SVG = `<svg viewBox="0 0 469 469" xmlns="http://www.w3.org/
3076
3352
  <g transform="translate(94 94) scale(1.905)"><path d="M73.751 0C114.483 0 147.502 33.0194 147.502 73.751C147.502 114.483 114.483 147.502 73.751 147.502C33.0194 147.502 0 114.483 0 73.751C8.24653e-06 33.0194 33.0194 8.2476e-06 73.751 0ZM73.751 116.118C60.7517 116.118 50.2141 120.334 50.2139 125.533C50.2139 130.733 60.7515 134.948 73.751 134.948C86.7504 134.948 97.2881 130.733 97.2881 125.533C97.2878 120.334 86.7502 116.118 73.751 116.118ZM38.9844 31.3828C27.962 31.3828 12.5537 47.8829 12.5537 73.751C12.5538 99.619 27.9621 116.118 38.9844 116.118C51.1171 116.118 56.4902 92.4737 56.4902 73.751C56.4902 55.0282 51.1171 31.3829 38.9844 31.3828ZM108.518 31.3828C96.3849 31.3831 91.0117 55.0283 91.0117 73.751C91.0117 92.4736 96.385 116.118 108.518 116.118C119.54 116.118 134.948 99.619 134.948 73.751C134.948 47.8829 119.54 31.3828 108.518 31.3828Z" fill="var(--daba-agent-icon-mark, #ffffff)"/></g>
3077
3353
  </svg>`;
3078
3354
 
3355
+ // src/utils/sanitize-svg.ts
3356
+ var BANNED_TAGS = ["script", "foreignobject", "iframe", "object", "embed", "animate", "set", "handler"];
3357
+ function sanitizeSvg(markup) {
3358
+ if (!markup) return null;
3359
+ const source = String(markup).trim();
3360
+ if (!source.startsWith("<")) return null;
3361
+ if (typeof DOMParser === "undefined") return null;
3362
+ let doc;
3363
+ try {
3364
+ doc = new DOMParser().parseFromString(source, "image/svg+xml");
3365
+ } catch {
3366
+ return null;
3367
+ }
3368
+ if (doc.querySelector("parsererror")) return null;
3369
+ const root = doc.documentElement;
3370
+ if (!root || root.nodeName.toLowerCase() !== "svg") return null;
3371
+ for (const el of Array.from(root.querySelectorAll("*")).concat(root)) {
3372
+ if (BANNED_TAGS.includes(el.nodeName.toLowerCase())) {
3373
+ el.remove();
3374
+ continue;
3375
+ }
3376
+ for (const attr of Array.from(el.attributes)) {
3377
+ const name = attr.name.toLowerCase();
3378
+ const value = attr.value.trim();
3379
+ const isReference = name === "href" || name.endsWith(":href") || name === "src";
3380
+ if (name.startsWith("on") || name === "style" || isReference && !value.startsWith("#")) {
3381
+ el.removeAttribute(attr.name);
3382
+ }
3383
+ }
3384
+ }
3385
+ if (!root.children.length) return null;
3386
+ root.setAttribute("aria-hidden", "true");
3387
+ root.setAttribute("focusable", "false");
3388
+ return root.outerHTML;
3389
+ }
3390
+
3079
3391
  // src/ui/badge.ts
3080
3392
  var STATUS_TEXT = {
3081
3393
  idle: "Click and talk",
@@ -3084,13 +3396,24 @@ var STATUS_TEXT = {
3084
3396
  thinking: "Thinking\u2026",
3085
3397
  speaking: "Speaking\u2026"
3086
3398
  };
3087
- function buildBadge(position, mode, accentColor, iconBackgroundColor, iconMarkColor) {
3399
+ var ACTION_TEXT = {
3400
+ idle: "Click and talk",
3401
+ connecting: "Click to cancel",
3402
+ listening: "Click to end the conversation",
3403
+ thinking: "Click to end the conversation",
3404
+ speaking: "Click to end the conversation"
3405
+ };
3406
+ function ariaLabel(status, hotkeyLabel2) {
3407
+ const action = status === "idle" ? "Talk to AI Assistant" : "End the conversation with AI Assistant";
3408
+ return hotkeyLabel2 ? `${action} (${hotkeyLabel2})` : action;
3409
+ }
3410
+ function buildBadge(position, mode, accentColor, iconBackgroundColor, iconMarkColor, hotkeyLabel2) {
3088
3411
  injectAgentStyles();
3089
3412
  const root = document.createElement("button");
3090
3413
  root.type = "button";
3091
3414
  root.className = `daba-agent-badge daba-agent-badge--idle daba-agent-badge--pos-${position}`;
3092
- root.setAttribute("aria-label", "Click and talk to AI Assistant (Ctrl+Space)");
3093
- root.title = STATUS_TEXT.idle;
3415
+ root.setAttribute("aria-label", ariaLabel("idle", hotkeyLabel2));
3416
+ root.title = ACTION_TEXT.idle;
3094
3417
  if (accentColor) root.style.setProperty("--daba-agent-accent", accentColor);
3095
3418
  if (iconBackgroundColor) root.style.setProperty("--daba-agent-icon-bg", iconBackgroundColor);
3096
3419
  if (iconMarkColor) root.style.setProperty("--daba-agent-icon-mark", iconMarkColor);
@@ -3107,28 +3430,41 @@ function buildBadge(position, mode, accentColor, iconBackgroundColor, iconMarkCo
3107
3430
  root.appendChild(label);
3108
3431
  return { root, label };
3109
3432
  }
3110
- function updateBadge(root, label, status, position) {
3433
+ function updateBadge(root, label, status, position, hotkeyLabel2) {
3111
3434
  root.className = `daba-agent-badge daba-agent-badge--${status} daba-agent-badge--pos-${position}`;
3112
- root.title = STATUS_TEXT[status];
3435
+ root.title = ACTION_TEXT[status];
3436
+ root.setAttribute("aria-label", ariaLabel(status, hotkeyLabel2));
3113
3437
  label.textContent = STATUS_TEXT[status];
3114
3438
  }
3115
- function setBadgeIcon(root, url) {
3439
+ function setBadgeIcon(root, mark) {
3116
3440
  const slot = root.querySelector(".daba-agent-badge__icon");
3117
3441
  if (!slot) return;
3118
- if (!url) {
3119
- slot.innerHTML = AGENT_LOGO_BADGE_SVG;
3442
+ slot.textContent = "";
3443
+ slot.classList.remove("daba-agent-badge__icon--text");
3444
+ const svg = sanitizeSvg(mark.svg);
3445
+ if (svg) {
3446
+ slot.innerHTML = svg;
3120
3447
  return;
3121
3448
  }
3122
- const img = document.createElement("img");
3123
- img.className = "daba-agent-badge__img";
3124
- img.alt = "";
3125
- img.decoding = "async";
3126
- img.addEventListener("error", () => {
3127
- slot.innerHTML = AGENT_LOGO_BADGE_SVG;
3128
- });
3129
- img.src = url;
3130
- slot.textContent = "";
3131
- slot.appendChild(img);
3449
+ const text = mark.text?.trim();
3450
+ if (text) {
3451
+ slot.classList.add("daba-agent-badge__icon--text");
3452
+ slot.textContent = text;
3453
+ return;
3454
+ }
3455
+ if (mark.url) {
3456
+ const img = document.createElement("img");
3457
+ img.className = "daba-agent-badge__img";
3458
+ img.alt = "";
3459
+ img.decoding = "async";
3460
+ img.addEventListener("error", () => {
3461
+ slot.innerHTML = AGENT_LOGO_BADGE_SVG;
3462
+ });
3463
+ img.src = mark.url;
3464
+ slot.appendChild(img);
3465
+ return;
3466
+ }
3467
+ slot.innerHTML = AGENT_LOGO_BADGE_SVG;
3132
3468
  }
3133
3469
 
3134
3470
  // src/ui/cards.ts
@@ -3616,6 +3952,9 @@ var ICON = {
3616
3952
  ticket: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 9V7a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v2a2 2 0 0 0 0 6v2a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-2a2 2 0 0 0 0-6z"/><path d="M12 7v2M12 15v2"/></svg>`,
3617
3953
  clock: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="8.5"/><path d="M12 7.5V12l3 1.8"/></svg>`,
3618
3954
  chevron: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>`,
3955
+ history: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 4v4h4"/><path d="M12 8v4l3 2"/></svg>`,
3956
+ back: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M15 5l-7 7 7 7"/></svg>`,
3957
+ trash: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M4 7h16"/><path d="M9 7V5h6v2"/><path d="M6 7l1 12h10l1-12"/></svg>`,
3619
3958
  keys: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="6" width="18" height="12" rx="2.5"/><path d="M7 10h.01M11 10h.01M15 10h.01M7 14h10"/></svg>`,
3620
3959
  pin: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 21s7-5.7 7-11a7 7 0 1 0-14 0c0 5.3 7 11 7 11z"/><circle cx="12" cy="10" r="2.5"/></svg>`
3621
3960
  };
@@ -3653,11 +3992,17 @@ function buildChatWindow(options) {
3653
3992
  mode,
3654
3993
  intro,
3655
3994
  logoUrl,
3995
+ logoSvg,
3656
3996
  hotkeyLabel: hotkeyLabel2,
3997
+ historyEnabled,
3657
3998
  onSendText,
3658
3999
  onMicClick,
3659
4000
  onCloseClick,
3660
- onNewConversation
4001
+ onNewConversation,
4002
+ onListHistory,
4003
+ onOpenConversation,
4004
+ onDeleteConversation,
4005
+ onClearHistory
3661
4006
  } = options;
3662
4007
  const voiceAvailable = mode !== "chat";
3663
4008
  const chatWindow = document.createElement("div");
@@ -3675,7 +4020,10 @@ function buildChatWindow(options) {
3675
4020
  headerLeft.appendChild(headerMarkSlot);
3676
4021
  const setHeaderLogo = (url) => {
3677
4022
  headerMarkSlot.textContent = "";
3678
- if (url) {
4023
+ const svg = sanitizeSvg(logoSvg);
4024
+ if (svg) {
4025
+ headerMarkSlot.appendChild(icon(svg, "daba-agent-header__mark"));
4026
+ } else if (url) {
3679
4027
  const logo = document.createElement("img");
3680
4028
  logo.className = "daba-agent-header__logo";
3681
4029
  logo.alt = "";
@@ -3699,6 +4047,14 @@ function buildChatWindow(options) {
3699
4047
  headerRight.className = "daba-agent-header__right";
3700
4048
  const micBtn = iconButton(ICON.mic, strings.startVoice, "daba-agent-header__btn daba-agent-header__btn--mic", onMicClick);
3701
4049
  micBtn.hidden = true;
4050
+ const historyBtn = iconButton(
4051
+ ICON.history,
4052
+ strings.history,
4053
+ "daba-agent-header__btn",
4054
+ () => setHistoryVisible(!historyOpen)
4055
+ );
4056
+ historyBtn.hidden = true;
4057
+ if (historyEnabled) headerRight.appendChild(historyBtn);
3702
4058
  headerRight.appendChild(
3703
4059
  iconButton(ICON.grid, strings.newConversation, "daba-agent-header__btn", onNewConversation)
3704
4060
  );
@@ -3717,7 +4073,10 @@ function buildChatWindow(options) {
3717
4073
  const setIntroLogo = (url) => {
3718
4074
  if (!introMarkSlot) return;
3719
4075
  introMarkSlot.textContent = "";
3720
- if (url) {
4076
+ const svg = sanitizeSvg(logoSvg);
4077
+ if (svg) {
4078
+ introMarkSlot.appendChild(icon(svg, "daba-agent-intro__mark"));
4079
+ } else if (url) {
3721
4080
  const img = document.createElement("img");
3722
4081
  img.className = "daba-agent-intro__mark daba-agent-intro__logo";
3723
4082
  img.alt = "";
@@ -3821,6 +4180,104 @@ function buildChatWindow(options) {
3821
4180
  thinkingEl?.remove();
3822
4181
  thinkingEl = null;
3823
4182
  };
4183
+ const historyEl = document.createElement("div");
4184
+ historyEl.className = "daba-agent-history";
4185
+ historyEl.hidden = true;
4186
+ const historyHead = document.createElement("div");
4187
+ historyHead.className = "daba-agent-history__head";
4188
+ const historyBack = iconButton(
4189
+ ICON.back,
4190
+ strings.historyBack,
4191
+ "daba-agent-history__back",
4192
+ () => setHistoryVisible(false)
4193
+ );
4194
+ historyHead.appendChild(historyBack);
4195
+ const historyTitle = document.createElement("h2");
4196
+ historyTitle.className = "daba-agent-history__title";
4197
+ historyTitle.textContent = strings.history;
4198
+ historyTitle.tabIndex = -1;
4199
+ historyHead.appendChild(historyTitle);
4200
+ historyEl.appendChild(historyHead);
4201
+ const historyList = document.createElement("div");
4202
+ historyList.className = "daba-agent-history__list";
4203
+ historyEl.appendChild(historyList);
4204
+ let historyOpen = false;
4205
+ const when = (ts) => {
4206
+ const day = 24 * 60 * 60 * 1e3;
4207
+ const age = Date.now() - ts;
4208
+ try {
4209
+ const rtf = new Intl.RelativeTimeFormat(lang, { numeric: "auto" });
4210
+ if (age < 60 * 60 * 1e3) return rtf.format(-Math.round(age / (60 * 1e3)) || 0, "minute");
4211
+ if (age < day) return rtf.format(-Math.round(age / (60 * 60 * 1e3)), "hour");
4212
+ if (age < 7 * day) return rtf.format(-Math.round(age / day), "day");
4213
+ return new Intl.DateTimeFormat(lang, { month: "short", day: "numeric" }).format(ts);
4214
+ } catch {
4215
+ return new Date(ts).toLocaleDateString();
4216
+ }
4217
+ };
4218
+ const renderHistory = () => {
4219
+ const entries = onListHistory?.() ?? [];
4220
+ historyList.textContent = "";
4221
+ if (!entries.length) {
4222
+ const empty = document.createElement("p");
4223
+ empty.className = "daba-agent-history__empty";
4224
+ empty.textContent = strings.historyEmpty;
4225
+ historyList.appendChild(empty);
4226
+ return entries;
4227
+ }
4228
+ for (const entry of entries) {
4229
+ const row = document.createElement("div");
4230
+ row.className = "daba-agent-history__item";
4231
+ const open = document.createElement("button");
4232
+ open.type = "button";
4233
+ open.className = "daba-agent-history__open";
4234
+ const name = document.createElement("span");
4235
+ name.className = "daba-agent-history__name";
4236
+ name.textContent = entry.title;
4237
+ const meta = document.createElement("span");
4238
+ meta.className = "daba-agent-history__meta";
4239
+ meta.textContent = when(entry.updatedAt);
4240
+ open.append(name, meta);
4241
+ open.addEventListener("click", () => {
4242
+ onOpenConversation?.(entry.id);
4243
+ setHistoryVisible(false);
4244
+ });
4245
+ row.appendChild(open);
4246
+ const remove = iconButton(ICON.trash, strings.historyDelete, "daba-agent-history__delete", () => {
4247
+ onDeleteConversation?.(entry.id);
4248
+ renderHistory();
4249
+ });
4250
+ row.appendChild(remove);
4251
+ historyList.appendChild(row);
4252
+ }
4253
+ const clear = document.createElement("button");
4254
+ clear.type = "button";
4255
+ clear.className = "daba-agent-history__clear";
4256
+ clear.textContent = strings.historyClear;
4257
+ clear.addEventListener("click", () => {
4258
+ onClearHistory?.();
4259
+ renderHistory();
4260
+ refreshHistory();
4261
+ });
4262
+ historyList.appendChild(clear);
4263
+ return entries;
4264
+ };
4265
+ const setHistoryVisible = (visible) => {
4266
+ if (!historyEnabled) return;
4267
+ historyOpen = visible;
4268
+ if (visible) renderHistory();
4269
+ historyEl.hidden = !visible;
4270
+ messagesContainer.hidden = visible;
4271
+ composer.hidden = visible;
4272
+ historyBtn.setAttribute("aria-expanded", String(visible));
4273
+ if (visible) historyTitle.focus();
4274
+ else inputElement.focus();
4275
+ };
4276
+ const refreshHistory = () => {
4277
+ if (!historyEnabled) return;
4278
+ historyBtn.hidden = (onListHistory?.() ?? []).length === 0;
4279
+ if (historyOpen) renderHistory();
4280
+ };
3824
4281
  const composer = document.createElement("div");
3825
4282
  composer.className = "daba-agent-composer";
3826
4283
  const inputForm = document.createElement("form");
@@ -3878,6 +4335,7 @@ function buildChatWindow(options) {
3878
4335
  footer.appendChild(footerRight);
3879
4336
  composer.appendChild(footer);
3880
4337
  chatWindow.appendChild(composer);
4338
+ if (historyEnabled) chatWindow.insertBefore(historyEl, composer);
3881
4339
  return {
3882
4340
  chatWindow,
3883
4341
  messagesContainer,
@@ -3896,6 +4354,14 @@ function buildChatWindow(options) {
3896
4354
  setStatusText(text) {
3897
4355
  statusEl.textContent = text;
3898
4356
  },
4357
+ clearMessages() {
4358
+ for (const child of Array.from(messagesContainer.children)) {
4359
+ if (child !== introEl) child.remove();
4360
+ }
4361
+ },
4362
+ setHistoryVisible,
4363
+ isHistoryVisible: () => historyOpen,
4364
+ refreshHistory,
3899
4365
  applyConfig(config) {
3900
4366
  if (config.logoUrl !== void 0) {
3901
4367
  setHeaderLogo(config.logoUrl);
@@ -3968,7 +4434,7 @@ var STRINGS = {
3968
4434
  close: "Close",
3969
4435
  minimize: "Minimize",
3970
4436
  newConversation: "New conversation",
3971
- poweredBy: "Powered by Dabalabs",
4437
+ poweredBy: "Powered by DabaLabs",
3972
4438
  errorGeneric: "Something went wrong. Please try again.",
3973
4439
  errorMicDenied: "Microphone access was blocked. Allow it in your browser settings to talk.",
3974
4440
  errorOffline: "You appear to be offline. Check your connection.",
@@ -3985,7 +4451,13 @@ var STRINGS = {
3985
4451
  retry: "Try again",
3986
4452
  typeOrTalk: "Type, or tap the mic to talk",
3987
4453
  voiceUnavailable: "Voice is unavailable on this device.",
3988
- cardsLabel: "Results"
4454
+ cardsLabel: "Results",
4455
+ history: "History",
4456
+ historyBack: "Back",
4457
+ historyEmpty: "No past conversations yet.",
4458
+ historyDelete: "Delete",
4459
+ historyClear: "Clear all",
4460
+ untitledConversation: "Untitled conversation"
3989
4461
  },
3990
4462
  ar: {
3991
4463
  assistantTitle: "\u0627\u0644\u0645\u0633\u0627\u0639\u062F \u0627\u0644\u0630\u0643\u064A",
@@ -4000,7 +4472,7 @@ var STRINGS = {
4000
4472
  close: "\u0625\u063A\u0644\u0627\u0642",
4001
4473
  minimize: "\u062A\u0635\u063A\u064A\u0631",
4002
4474
  newConversation: "\u0645\u062D\u0627\u062F\u062B\u0629 \u062C\u062F\u064A\u062F\u0629",
4003
- poweredBy: "\u0645\u062F\u0639\u0648\u0645 \u0645\u0646 Dabalabs",
4475
+ poweredBy: "\u0645\u062F\u0639\u0648\u0645 \u0645\u0646 DabaLabs",
4004
4476
  errorGeneric: "\u062D\u062F\u062B \u062E\u0637\u0623 \u0645\u0627. \u064A\u0631\u062C\u0649 \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629 \u0645\u0631\u0629 \u0623\u062E\u0631\u0649.",
4005
4477
  errorMicDenied: "\u062A\u0645 \u062D\u0638\u0631 \u0627\u0644\u0648\u0635\u0648\u0644 \u0625\u0644\u0649 \u0627\u0644\u0645\u064A\u0643\u0631\u0648\u0641\u0648\u0646. \u0627\u0633\u0645\u062D \u0628\u0647 \u0645\u0646 \u0625\u0639\u062F\u0627\u062F\u0627\u062A \u0627\u0644\u0645\u062A\u0635\u0641\u062D \u0644\u0644\u062A\u062D\u062F\u062B.",
4006
4478
  errorOffline: "\u064A\u0628\u062F\u0648 \u0623\u0646\u0643 \u063A\u064A\u0631 \u0645\u062A\u0635\u0644 \u0628\u0627\u0644\u0625\u0646\u062A\u0631\u0646\u062A. \u062A\u062D\u0642\u0642 \u0645\u0646 \u0627\u062A\u0635\u0627\u0644\u0643.",
@@ -4017,7 +4489,13 @@ var STRINGS = {
4017
4489
  retry: "\u0623\u0639\u062F \u0627\u0644\u0645\u062D\u0627\u0648\u0644\u0629",
4018
4490
  typeOrTalk: "\u0627\u0643\u062A\u0628 \u0623\u0648 \u0627\u0636\u063A\u0637 \u0639\u0644\u0649 \u0627\u0644\u0645\u064A\u0643\u0631\u0648\u0641\u0648\u0646 \u0644\u0644\u062A\u062D\u062F\u062B",
4019
4491
  voiceUnavailable: "\u0627\u0644\u0645\u062D\u0627\u062F\u062B\u0629 \u0627\u0644\u0635\u0648\u062A\u064A\u0629 \u063A\u064A\u0631 \u0645\u062A\u0627\u062D\u0629 \u0639\u0644\u0649 \u0647\u0630\u0627 \u0627\u0644\u062C\u0647\u0627\u0632.",
4020
- cardsLabel: "\u0627\u0644\u0646\u062A\u0627\u0626\u062C"
4492
+ cardsLabel: "\u0627\u0644\u0646\u062A\u0627\u0626\u062C",
4493
+ history: "\u0627\u0644\u0633\u062C\u0644",
4494
+ historyBack: "\u0631\u062C\u0648\u0639",
4495
+ historyEmpty: "\u0644\u0627 \u062A\u0648\u062C\u062F \u0645\u062D\u0627\u062F\u062B\u0627\u062A \u0633\u0627\u0628\u0642\u0629 \u0628\u0639\u062F.",
4496
+ historyDelete: "\u062D\u0630\u0641",
4497
+ historyClear: "\u0645\u0633\u062D \u0627\u0644\u0643\u0644",
4498
+ untitledConversation: "\u0645\u062D\u0627\u062F\u062B\u0629 \u0628\u062F\u0648\u0646 \u0639\u0646\u0648\u0627\u0646"
4021
4499
  },
4022
4500
  fr: {
4023
4501
  assistantTitle: "Assistant IA",
@@ -4032,7 +4510,7 @@ var STRINGS = {
4032
4510
  close: "Fermer",
4033
4511
  minimize: "R\xE9duire",
4034
4512
  newConversation: "Nouvelle conversation",
4035
- poweredBy: "Propuls\xE9 par Dabalabs",
4513
+ poweredBy: "Propuls\xE9 par DabaLabs",
4036
4514
  errorGeneric: "Une erreur s'est produite. Veuillez r\xE9essayer.",
4037
4515
  errorMicDenied: "L'acc\xE8s au microphone a \xE9t\xE9 bloqu\xE9. Autorisez-le dans les param\xE8tres du navigateur pour parler.",
4038
4516
  errorOffline: "Vous semblez \xEAtre hors ligne. V\xE9rifiez votre connexion.",
@@ -4049,7 +4527,13 @@ var STRINGS = {
4049
4527
  retry: "R\xE9essayer",
4050
4528
  typeOrTalk: "\xC9crivez, ou appuyez sur le micro pour parler",
4051
4529
  voiceUnavailable: "La voix n'est pas disponible sur cet appareil.",
4052
- cardsLabel: "R\xE9sultats"
4530
+ cardsLabel: "R\xE9sultats",
4531
+ history: "Historique",
4532
+ historyBack: "Retour",
4533
+ historyEmpty: "Aucune conversation pass\xE9e pour l'instant.",
4534
+ historyDelete: "Supprimer",
4535
+ historyClear: "Tout effacer",
4536
+ untitledConversation: "Conversation sans titre"
4053
4537
  },
4054
4538
  es: {
4055
4539
  assistantTitle: "Asistente de IA",
@@ -4064,7 +4548,7 @@ var STRINGS = {
4064
4548
  close: "Cerrar",
4065
4549
  minimize: "Minimizar",
4066
4550
  newConversation: "Nueva conversaci\xF3n",
4067
- poweredBy: "Con tecnolog\xEDa de Dabalabs",
4551
+ poweredBy: "Con tecnolog\xEDa de DabaLabs",
4068
4552
  errorGeneric: "Algo sali\xF3 mal. Int\xE9ntalo de nuevo.",
4069
4553
  errorMicDenied: "Se bloque\xF3 el acceso al micr\xF3fono. Perm\xEDtelo en la configuraci\xF3n del navegador para hablar.",
4070
4554
  errorOffline: "Parece que no tienes conexi\xF3n. Comprueba tu red.",
@@ -4081,7 +4565,13 @@ var STRINGS = {
4081
4565
  retry: "Reintentar",
4082
4566
  typeOrTalk: "Escribe, o toca el micr\xF3fono para hablar",
4083
4567
  voiceUnavailable: "La voz no est\xE1 disponible en este dispositivo.",
4084
- cardsLabel: "Resultados"
4568
+ cardsLabel: "Resultados",
4569
+ history: "Historial",
4570
+ historyBack: "Atr\xE1s",
4571
+ historyEmpty: "A\xFAn no hay conversaciones anteriores.",
4572
+ historyDelete: "Eliminar",
4573
+ historyClear: "Borrar todo",
4574
+ untitledConversation: "Conversaci\xF3n sin t\xEDtulo"
4085
4575
  },
4086
4576
  de: {
4087
4577
  assistantTitle: "KI-Assistent",
@@ -4096,7 +4586,7 @@ var STRINGS = {
4096
4586
  close: "Schlie\xDFen",
4097
4587
  minimize: "Minimieren",
4098
4588
  newConversation: "Neue Unterhaltung",
4099
- poweredBy: "Bereitgestellt von Dabalabs",
4589
+ poweredBy: "Bereitgestellt von DabaLabs",
4100
4590
  errorGeneric: "Etwas ist schiefgelaufen. Bitte versuchen Sie es erneut.",
4101
4591
  errorMicDenied: "Der Mikrofonzugriff wurde blockiert. Erlauben Sie ihn in den Browsereinstellungen, um zu sprechen.",
4102
4592
  errorOffline: "Sie scheinen offline zu sein. Pr\xFCfen Sie Ihre Verbindung.",
@@ -4113,7 +4603,13 @@ var STRINGS = {
4113
4603
  retry: "Erneut versuchen",
4114
4604
  typeOrTalk: "Schreiben oder aufs Mikrofon tippen, um zu sprechen",
4115
4605
  voiceUnavailable: "Die Sprachfunktion ist auf diesem Ger\xE4t nicht verf\xFCgbar.",
4116
- cardsLabel: "Ergebnisse"
4606
+ cardsLabel: "Ergebnisse",
4607
+ history: "Verlauf",
4608
+ historyBack: "Zur\xFCck",
4609
+ historyEmpty: "Noch keine fr\xFCheren Unterhaltungen.",
4610
+ historyDelete: "L\xF6schen",
4611
+ historyClear: "Alle l\xF6schen",
4612
+ untitledConversation: "Unbenannte Unterhaltung"
4117
4613
  },
4118
4614
  ru: {
4119
4615
  assistantTitle: "\u0418\u0418-\u0430\u0441\u0441\u0438\u0441\u0442\u0435\u043D\u0442",
@@ -4128,7 +4624,7 @@ var STRINGS = {
4128
4624
  close: "\u0417\u0430\u043A\u0440\u044B\u0442\u044C",
4129
4625
  minimize: "\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C",
4130
4626
  newConversation: "\u041D\u043E\u0432\u044B\u0439 \u0434\u0438\u0430\u043B\u043E\u0433",
4131
- poweredBy: "\u0420\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u043D\u0430 Dabalabs",
4627
+ poweredBy: "\u0420\u0430\u0431\u043E\u0442\u0430\u0435\u0442 \u043D\u0430 DabaLabs",
4132
4628
  errorGeneric: "\u0427\u0442\u043E-\u0442\u043E \u043F\u043E\u0448\u043B\u043E \u043D\u0435 \u0442\u0430\u043A. \u041F\u043E\u043F\u0440\u043E\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0451 \u0440\u0430\u0437.",
4133
4629
  errorMicDenied: "\u0414\u043E\u0441\u0442\u0443\u043F \u043A \u043C\u0438\u043A\u0440\u043E\u0444\u043E\u043D\u0443 \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D. \u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0435 \u0435\u0433\u043E \u0432 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u0445 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430, \u0447\u0442\u043E\u0431\u044B \u0433\u043E\u0432\u043E\u0440\u0438\u0442\u044C.",
4134
4630
  errorOffline: "\u041F\u043E\u0445\u043E\u0436\u0435, \u0432\u044B \u043D\u0435 \u0432 \u0441\u0435\u0442\u0438. \u041F\u0440\u043E\u0432\u0435\u0440\u044C\u0442\u0435 \u043F\u043E\u0434\u043A\u043B\u044E\u0447\u0435\u043D\u0438\u0435.",
@@ -4145,7 +4641,13 @@ var STRINGS = {
4145
4641
  retry: "\u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C",
4146
4642
  typeOrTalk: "\u041D\u0430\u043F\u0438\u0448\u0438\u0442\u0435 \u0438\u043B\u0438 \u043D\u0430\u0436\u043C\u0438\u0442\u0435 \u043D\u0430 \u043C\u0438\u043A\u0440\u043E\u0444\u043E\u043D, \u0447\u0442\u043E\u0431\u044B \u0433\u043E\u0432\u043E\u0440\u0438\u0442\u044C",
4147
4643
  voiceUnavailable: "\u0413\u043E\u043B\u043E\u0441\u043E\u0432\u043E\u0439 \u0440\u0435\u0436\u0438\u043C \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u043D\u0430 \u044D\u0442\u043E\u043C \u0443\u0441\u0442\u0440\u043E\u0439\u0441\u0442\u0432\u0435.",
4148
- cardsLabel: "\u0420\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B"
4644
+ cardsLabel: "\u0420\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u044B",
4645
+ history: "\u0418\u0441\u0442\u043E\u0440\u0438\u044F",
4646
+ historyBack: "\u041D\u0430\u0437\u0430\u0434",
4647
+ historyEmpty: "\u041F\u0440\u043E\u0448\u043B\u044B\u0445 \u0440\u0430\u0437\u0433\u043E\u0432\u043E\u0440\u043E\u0432 \u043F\u043E\u043A\u0430 \u043D\u0435\u0442.",
4648
+ historyDelete: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C",
4649
+ historyClear: "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C \u0432\u0441\u0435",
4650
+ untitledConversation: "\u0420\u0430\u0437\u0433\u043E\u0432\u043E\u0440 \u0431\u0435\u0437 \u043D\u0430\u0437\u0432\u0430\u043D\u0438\u044F"
4149
4651
  },
4150
4652
  tr: {
4151
4653
  assistantTitle: "Yapay Zek\xE2 Asistan\u0131",
@@ -4160,7 +4662,7 @@ var STRINGS = {
4160
4662
  close: "Kapat",
4161
4663
  minimize: "K\xFC\xE7\xFClt",
4162
4664
  newConversation: "Yeni sohbet",
4163
- poweredBy: "Dabalabs taraf\u0131ndan sa\u011Flan\u0131r",
4665
+ poweredBy: "DabaLabs taraf\u0131ndan sa\u011Flan\u0131r",
4164
4666
  errorGeneric: "Bir \u015Feyler ters gitti. L\xFCtfen tekrar deneyin.",
4165
4667
  errorMicDenied: "Mikrofon eri\u015Fimi engellendi. Konu\u015Fmak i\xE7in taray\u0131c\u0131 ayarlar\u0131ndan izin verin.",
4166
4668
  errorOffline: "\xC7evrimd\u0131\u015F\u0131 g\xF6r\xFCn\xFCyorsunuz. Ba\u011Flant\u0131n\u0131z\u0131 kontrol edin.",
@@ -4177,7 +4679,13 @@ var STRINGS = {
4177
4679
  retry: "Tekrar dene",
4178
4680
  typeOrTalk: "Yaz\u0131n veya konu\u015Fmak i\xE7in mikrofona dokunun",
4179
4681
  voiceUnavailable: "Sesli g\xF6r\xFC\u015Fme bu cihazda kullan\u0131lam\u0131yor.",
4180
- cardsLabel: "Sonu\xE7lar"
4682
+ cardsLabel: "Sonu\xE7lar",
4683
+ history: "Ge\xE7mi\u015F",
4684
+ historyBack: "Geri",
4685
+ historyEmpty: "Hen\xFCz ge\xE7mi\u015F sohbet yok.",
4686
+ historyDelete: "Sil",
4687
+ historyClear: "T\xFCm\xFCn\xFC temizle",
4688
+ untitledConversation: "Ads\u0131z sohbet"
4181
4689
  },
4182
4690
  ur: {
4183
4691
  assistantTitle: "\u0627\u06D2 \u0622\u0626\u06CC \u0627\u0633\u0633\u0679\u0646\u0679",
@@ -4192,7 +4700,7 @@ var STRINGS = {
4192
4700
  close: "\u0628\u0646\u062F \u06A9\u0631\u06CC\u06BA",
4193
4701
  minimize: "\u0686\u06BE\u0648\u0679\u0627 \u06A9\u0631\u06CC\u06BA",
4194
4702
  newConversation: "\u0646\u0626\u06CC \u06AF\u0641\u062A\u06AF\u0648",
4195
- poweredBy: "Dabalabs \u06A9\u06D2 \u062A\u0639\u0627\u0648\u0646 \u0633\u06D2",
4703
+ poweredBy: "DabaLabs \u06A9\u06D2 \u062A\u0639\u0627\u0648\u0646 \u0633\u06D2",
4196
4704
  errorGeneric: "\u06A9\u0686\u06BE \u063A\u0644\u0637 \u06C1\u0648 \u06AF\u06CC\u0627\u06D4 \u0628\u0631\u0627\u06C1 \u06A9\u0631\u0645 \u062F\u0648\u0628\u0627\u0631\u06C1 \u06A9\u0648\u0634\u0634 \u06A9\u0631\u06CC\u06BA\u06D4",
4197
4705
  errorMicDenied: "\u0645\u0627\u0626\u06CC\u06A9\u0631\u0648\u0641\u0648\u0646 \u062A\u06A9 \u0631\u0633\u0627\u0626\u06CC \u0628\u0644\u0627\u06A9 \u06A9\u0631 \u062F\u06CC \u06AF\u0626\u06CC \u06C1\u06D2\u06D4 \u0628\u0627\u062A \u06A9\u0631\u0646\u06D2 \u06A9\u06D2 \u0644\u06CC\u06D2 \u0628\u0631\u0627\u0624\u0632\u0631 \u06A9\u06CC \u062A\u0631\u062A\u06CC\u0628\u0627\u062A \u0645\u06CC\u06BA \u0627\u062C\u0627\u0632\u062A \u062F\u06CC\u06BA\u06D4",
4198
4706
  errorOffline: "\u0644\u06AF\u062A\u0627 \u06C1\u06D2 \u0622\u067E \u0622\u0641 \u0644\u0627\u0626\u0646 \u06C1\u06CC\u06BA\u06D4 \u0627\u067E\u0646\u0627 \u06A9\u0646\u06A9\u0634\u0646 \u0686\u06CC\u06A9 \u06A9\u0631\u06CC\u06BA\u06D4",
@@ -4209,7 +4717,13 @@ var STRINGS = {
4209
4717
  retry: "\u062F\u0648\u0628\u0627\u0631\u06C1 \u06A9\u0648\u0634\u0634 \u06A9\u0631\u06CC\u06BA",
4210
4718
  typeOrTalk: "\u0644\u06A9\u06BE\u06CC\u06BA \u06CC\u0627 \u0628\u0627\u062A \u06A9\u0631\u0646\u06D2 \u06A9\u06D2 \u0644\u06CC\u06D2 \u0645\u0627\u0626\u06CC\u06A9\u0631\u0648\u0641\u0648\u0646 \u062F\u0628\u0627\u0626\u06CC\u06BA",
4211
4719
  voiceUnavailable: "\u0627\u0633 \u0688\u06CC\u0648\u0627\u0626\u0633 \u067E\u0631 \u0635\u0648\u062A\u06CC \u06AF\u0641\u062A\u06AF\u0648 \u062F\u0633\u062A\u06CC\u0627\u0628 \u0646\u06C1\u06CC\u06BA \u06C1\u06D2\u06D4",
4212
- cardsLabel: "\u0646\u062A\u0627\u0626\u062C"
4720
+ cardsLabel: "\u0646\u062A\u0627\u0626\u062C",
4721
+ history: "\u062A\u0627\u0631\u06CC\u062E",
4722
+ historyBack: "\u0648\u0627\u067E\u0633",
4723
+ historyEmpty: "\u0627\u0628\u06BE\u06CC \u062A\u06A9 \u06A9\u0648\u0626\u06CC \u067E\u0686\u06BE\u0644\u06CC \u06AF\u0641\u062A\u06AF\u0648 \u0646\u06C1\u06CC\u06BA\u06D4",
4724
+ historyDelete: "\u062D\u0630\u0641 \u06A9\u0631\u06CC\u06BA",
4725
+ historyClear: "\u0633\u0628 \u0635\u0627\u0641 \u06A9\u0631\u06CC\u06BA",
4726
+ untitledConversation: "\u0628\u0644\u0627 \u0639\u0646\u0648\u0627\u0646 \u06AF\u0641\u062A\u06AF\u0648"
4213
4727
  },
4214
4728
  hi: {
4215
4729
  assistantTitle: "\u090F\u0906\u0908 \u0938\u0939\u093E\u092F\u0915",
@@ -4224,7 +4738,7 @@ var STRINGS = {
4224
4738
  close: "\u092C\u0902\u0926 \u0915\u0930\u0947\u0902",
4225
4739
  minimize: "\u091B\u094B\u091F\u093E \u0915\u0930\u0947\u0902",
4226
4740
  newConversation: "\u0928\u0908 \u092C\u093E\u0924\u091A\u0940\u0924",
4227
- poweredBy: "Dabalabs \u0926\u094D\u0935\u093E\u0930\u093E \u0938\u0902\u091A\u093E\u0932\u093F\u0924",
4741
+ poweredBy: "DabaLabs \u0926\u094D\u0935\u093E\u0930\u093E \u0938\u0902\u091A\u093E\u0932\u093F\u0924",
4228
4742
  errorGeneric: "\u0915\u0941\u091B \u0917\u0921\u093C\u092C\u0921\u093C \u0939\u094B \u0917\u0908\u0964 \u0915\u0943\u092A\u092F\u093E \u092B\u093F\u0930 \u0938\u0947 \u0915\u094B\u0936\u093F\u0936 \u0915\u0930\u0947\u0902\u0964",
4229
4743
  errorMicDenied: "\u092E\u093E\u0907\u0915\u094D\u0930\u094B\u092B\u093C\u094B\u0928 \u0915\u0940 \u0905\u0928\u0941\u092E\u0924\u093F \u0930\u094B\u0915 \u0926\u0940 \u0917\u0908 \u0939\u0948\u0964 \u092C\u093E\u0924 \u0915\u0930\u0928\u0947 \u0915\u0947 \u0932\u093F\u090F \u092C\u094D\u0930\u093E\u0909\u091C\u093C\u0930 \u0938\u0947\u091F\u093F\u0902\u0917\u094D\u0938 \u092E\u0947\u0902 \u0907\u0938\u0947 \u0905\u0928\u0941\u092E\u0924\u093F \u0926\u0947\u0902\u0964",
4230
4744
  errorOffline: "\u0906\u092A \u0911\u092B\u093C\u0932\u093E\u0907\u0928 \u0932\u0917 \u0930\u0939\u0947 \u0939\u0948\u0902\u0964 \u0905\u092A\u0928\u093E \u0915\u0928\u0947\u0915\u094D\u0936\u0928 \u091C\u093E\u0901\u091A\u0947\u0902\u0964",
@@ -4241,7 +4755,13 @@ var STRINGS = {
4241
4755
  retry: "\u092B\u093F\u0930 \u0938\u0947 \u0915\u094B\u0936\u093F\u0936 \u0915\u0930\u0947\u0902",
4242
4756
  typeOrTalk: "\u0932\u093F\u0916\u0947\u0902 \u092F\u093E \u092C\u093E\u0924 \u0915\u0930\u0928\u0947 \u0915\u0947 \u0932\u093F\u090F \u092E\u093E\u0907\u0915\u094D\u0930\u094B\u092B\u093C\u094B\u0928 \u0926\u092C\u093E\u090F\u0901",
4243
4757
  voiceUnavailable: "\u0907\u0938 \u0921\u093F\u0935\u093E\u0907\u0938 \u092A\u0930 \u0935\u0949\u0907\u0938 \u0909\u092A\u0932\u092C\u094D\u0927 \u0928\u0939\u0940\u0902 \u0939\u0948\u0964",
4244
- cardsLabel: "\u092A\u0930\u093F\u0923\u093E\u092E"
4758
+ cardsLabel: "\u092A\u0930\u093F\u0923\u093E\u092E",
4759
+ history: "\u0907\u0924\u093F\u0939\u093E\u0938",
4760
+ historyBack: "\u0935\u093E\u092A\u0938",
4761
+ historyEmpty: "\u0905\u092D\u0940 \u0924\u0915 \u0915\u094B\u0908 \u092A\u093F\u091B\u0932\u0940 \u092C\u093E\u0924\u091A\u0940\u0924 \u0928\u0939\u0940\u0902\u0964",
4762
+ historyDelete: "\u0939\u091F\u093E\u090F\u0901",
4763
+ historyClear: "\u0938\u092D\u0940 \u0938\u093E\u092B\u093C \u0915\u0930\u0947\u0902",
4764
+ untitledConversation: "\u092C\u093F\u0928\u093E \u0936\u0940\u0930\u094D\u0937\u0915 \u092C\u093E\u0924\u091A\u0940\u0924"
4245
4765
  },
4246
4766
  zh: {
4247
4767
  assistantTitle: "AI \u52A9\u624B",
@@ -4256,7 +4776,7 @@ var STRINGS = {
4256
4776
  close: "\u5173\u95ED",
4257
4777
  minimize: "\u6700\u5C0F\u5316",
4258
4778
  newConversation: "\u65B0\u5BF9\u8BDD",
4259
- poweredBy: "\u7531 Dabalabs \u63D0\u4F9B\u652F\u6301",
4779
+ poweredBy: "\u7531 DabaLabs \u63D0\u4F9B\u652F\u6301",
4260
4780
  errorGeneric: "\u51FA\u4E86\u70B9\u95EE\u9898\uFF0C\u8BF7\u91CD\u8BD5\u3002",
4261
4781
  errorMicDenied: "\u9EA6\u514B\u98CE\u6743\u9650\u88AB\u62D2\u7EDD\u3002\u8BF7\u5728\u6D4F\u89C8\u5668\u8BBE\u7F6E\u4E2D\u5141\u8BB8\u540E\u518D\u4F7F\u7528\u8BED\u97F3\u3002",
4262
4782
  errorOffline: "\u4F60\u4F3C\u4E4E\u5DF2\u79BB\u7EBF\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u8FDE\u63A5\u3002",
@@ -4273,7 +4793,13 @@ var STRINGS = {
4273
4793
  retry: "\u91CD\u8BD5",
4274
4794
  typeOrTalk: "\u8F93\u5165\u95EE\u9898\uFF0C\u6216\u70B9\u51FB\u9EA6\u514B\u98CE\u8BF4\u8BDD",
4275
4795
  voiceUnavailable: "\u6B64\u8BBE\u5907\u4E0D\u652F\u6301\u8BED\u97F3\u529F\u80FD\u3002",
4276
- cardsLabel: "\u7ED3\u679C"
4796
+ cardsLabel: "\u7ED3\u679C",
4797
+ history: "\u5386\u53F2\u8BB0\u5F55",
4798
+ historyBack: "\u8FD4\u56DE",
4799
+ historyEmpty: "\u8FD8\u6CA1\u6709\u5386\u53F2\u5BF9\u8BDD\u3002",
4800
+ historyDelete: "\u5220\u9664",
4801
+ historyClear: "\u5168\u90E8\u6E05\u9664",
4802
+ untitledConversation: "\u672A\u547D\u540D\u5BF9\u8BDD"
4277
4803
  },
4278
4804
  pt: {
4279
4805
  assistantTitle: "Assistente de IA",
@@ -4288,7 +4814,7 @@ var STRINGS = {
4288
4814
  close: "Fechar",
4289
4815
  minimize: "Minimizar",
4290
4816
  newConversation: "Nova conversa",
4291
- poweredBy: "Com tecnologia da Dabalabs",
4817
+ poweredBy: "Com tecnologia da DabaLabs",
4292
4818
  errorGeneric: "Algo deu errado. Tente novamente.",
4293
4819
  errorMicDenied: "O acesso ao microfone foi bloqueado. Permita-o nas configura\xE7\xF5es do navegador para falar.",
4294
4820
  errorOffline: "Parece que voc\xEA est\xE1 offline. Verifique sua conex\xE3o.",
@@ -4305,7 +4831,13 @@ var STRINGS = {
4305
4831
  retry: "Tentar novamente",
4306
4832
  typeOrTalk: "Escreva, ou toque no microfone para falar",
4307
4833
  voiceUnavailable: "A voz n\xE3o est\xE1 dispon\xEDvel neste dispositivo.",
4308
- cardsLabel: "Resultados"
4834
+ cardsLabel: "Resultados",
4835
+ history: "Hist\xF3rico",
4836
+ historyBack: "Voltar",
4837
+ historyEmpty: "Ainda n\xE3o h\xE1 conversas anteriores.",
4838
+ historyDelete: "Excluir",
4839
+ historyClear: "Limpar tudo",
4840
+ untitledConversation: "Conversa sem t\xEDtulo"
4309
4841
  },
4310
4842
  id: {
4311
4843
  assistantTitle: "Asisten AI",
@@ -4320,7 +4852,7 @@ var STRINGS = {
4320
4852
  close: "Tutup",
4321
4853
  minimize: "Perkecil",
4322
4854
  newConversation: "Percakapan baru",
4323
- poweredBy: "Didukung oleh Dabalabs",
4855
+ poweredBy: "Didukung oleh DabaLabs",
4324
4856
  errorGeneric: "Terjadi kesalahan. Silakan coba lagi.",
4325
4857
  errorMicDenied: "Akses mikrofon diblokir. Izinkan di pengaturan browser untuk berbicara.",
4326
4858
  errorOffline: "Sepertinya Anda sedang offline. Periksa koneksi Anda.",
@@ -4337,7 +4869,13 @@ var STRINGS = {
4337
4869
  retry: "Coba lagi",
4338
4870
  typeOrTalk: "Ketik, atau ketuk mikrofon untuk berbicara",
4339
4871
  voiceUnavailable: "Suara tidak tersedia di perangkat ini.",
4340
- cardsLabel: "Hasil"
4872
+ cardsLabel: "Hasil",
4873
+ history: "Riwayat",
4874
+ historyBack: "Kembali",
4875
+ historyEmpty: "Belum ada percakapan sebelumnya.",
4876
+ historyDelete: "Hapus",
4877
+ historyClear: "Hapus semua",
4878
+ untitledConversation: "Percakapan tanpa judul"
4341
4879
  }
4342
4880
  };
4343
4881
  var RTL_SUBTAGS = /* @__PURE__ */ new Set(["ar", "arc", "ckb", "dv", "fa", "he", "iw", "ku", "prs", "ps", "sd", "ug", "ur", "yi", "ji"]);
@@ -4390,9 +4928,41 @@ function stringsFor(lang) {
4390
4928
  }
4391
4929
 
4392
4930
  // src/utils/hotkey.ts
4931
+ var HOLD_MS = 600;
4932
+ var HOLD_KEYS = {
4933
+ ctrl: ["Control"],
4934
+ alt: ["Alt"],
4935
+ shift: ["Shift"],
4936
+ meta: ["Meta", "OS"]
4937
+ };
4938
+ function holdModifier(hotkey) {
4939
+ if (!hotkey.hold) return null;
4940
+ if (hotkey.ctrl) return "ctrl";
4941
+ if (hotkey.alt) return "alt";
4942
+ if (hotkey.shift) return "shift";
4943
+ if (hotkey.meta) return "meta";
4944
+ return null;
4945
+ }
4946
+ function holdHotkeyProblem(hotkey) {
4947
+ const parsed = parseHotkey(hotkey);
4948
+ if (!parsed.hold) return null;
4949
+ if (parsed.key !== "") {
4950
+ return `"hold:" waits on a modifier held by itself, so it cannot also carry the key "${parsed.key}"`;
4951
+ }
4952
+ const count = [parsed.ctrl, parsed.alt, parsed.shift, parsed.meta].filter(Boolean).length;
4953
+ if (count === 0) return `"hold:" needs one of ctrl, alt, shift or cmd after it`;
4954
+ if (count > 1) return `"hold:" waits on a single modifier, not a combination`;
4955
+ return null;
4956
+ }
4957
+ function isHoldKey(e, modifier) {
4958
+ return HOLD_KEYS[modifier].includes(e.key);
4959
+ }
4393
4960
  function parseHotkey(hotkey) {
4394
- const parts = hotkey.toLowerCase().split("+").map((p) => p.trim());
4961
+ const raw = hotkey.toLowerCase().trim();
4962
+ const hold = raw.startsWith("hold:");
4963
+ const parts = (hold ? raw.slice(5) : raw).split("+").map((p) => p.trim());
4395
4964
  return {
4965
+ hold,
4396
4966
  ctrl: parts.includes("ctrl") || parts.includes("control"),
4397
4967
  alt: parts.includes("alt"),
4398
4968
  shift: parts.includes("shift"),
@@ -4421,6 +4991,11 @@ function defaultHotkey() {
4421
4991
  }
4422
4992
  function hotkeyLabel(hotkey) {
4423
4993
  const parsed = parseHotkey(hotkey);
4994
+ const held = holdModifier(parsed);
4995
+ if (held) {
4996
+ const name = isApplePlatform() ? { ctrl: "\u2303", alt: "\u2325", shift: "\u21E7", meta: "\u2318" }[held] : { ctrl: "Ctrl", alt: "Alt", shift: "Shift", meta: "Win" }[held];
4997
+ return `Hold ${name}`;
4998
+ }
4424
4999
  const keyName = parsed.key === "space" ? "Space" : parsed.key.length === 1 ? parsed.key.toUpperCase() : parsed.key;
4425
5000
  if (isApplePlatform()) {
4426
5001
  const mods2 = [parsed.ctrl && "\u2303", parsed.alt && "\u2325", parsed.shift && "\u21E7", parsed.meta && "\u2318"].filter(Boolean);
@@ -4475,6 +5050,13 @@ function readConversationId(projectId) {
4475
5050
  var DabaAgent = class {
4476
5051
  constructor(options) {
4477
5052
  this.keydownHandler = (e) => this.handleKeydown(e);
5053
+ this.keyupHandler = (e) => this.handleKeyup(e);
5054
+ this.blurHandler = () => this.cancelHold();
5055
+ this.holdTimer = null;
5056
+ /** Set the moment a hold activates and cleared only on the matching
5057
+ * keyup, so one physical press cannot start two sessions if the browser
5058
+ * repeats the modifier keydown. */
5059
+ this.holdFired = false;
4478
5060
  this.status = "idle";
4479
5061
  this.isOpen = false;
4480
5062
  this.destroyed = false;
@@ -4487,6 +5069,7 @@ var DabaAgent = class {
4487
5069
  this.messages = [];
4488
5070
  this.currentStreamBubble = null;
4489
5071
  this.currentStreamRole = null;
5072
+ this.currentStreamText = "";
4490
5073
  this.chatInFlight = false;
4491
5074
  this.remoteConfig = null;
4492
5075
  this.live = null;
@@ -4508,6 +5091,16 @@ var DabaAgent = class {
4508
5091
  this.strings = stringsFor(this.uiLang);
4509
5092
  this.conversationId = readConversationId(options.projectId);
4510
5093
  this.restricted = isPageRestricted();
5094
+ const requested = options.hotkey ?? defaultHotkey();
5095
+ const holdProblem = options.hotkey === null ? null : holdHotkeyProblem(requested);
5096
+ if (holdProblem) {
5097
+ console.error(`daba-agent: ignoring hotkey "${requested}" \u2014 ${holdProblem}. Falling back to ${defaultHotkey()}.`);
5098
+ }
5099
+ const effective = holdProblem ? defaultHotkey() : requested;
5100
+ this.parsedHotkey = options.hotkey === null ? null : parseHotkey(effective);
5101
+ this.heldModifier = this.parsedHotkey ? holdModifier(this.parsedHotkey) : null;
5102
+ const touchOnly = typeof window.matchMedia === "function" && window.matchMedia("(hover: none) and (pointer: coarse)").matches;
5103
+ this.hotkeyText = this.parsedHotkey && !touchOnly ? hotkeyLabel(effective) : null;
4511
5104
  if (this.restricted) {
4512
5105
  console.warn(
4513
5106
  "daba-agent: this page has data-daba-agent-restricted set \u2014 the assistant will not read this page or start a session here."
@@ -4531,6 +5124,13 @@ var DabaAgent = class {
4531
5124
  },
4532
5125
  setStatusText: () => {
4533
5126
  },
5127
+ clearMessages: () => {
5128
+ },
5129
+ setHistoryVisible: () => {
5130
+ },
5131
+ isHistoryVisible: () => false,
5132
+ refreshHistory: () => {
5133
+ },
4534
5134
  applyConfig: () => {
4535
5135
  }
4536
5136
  };
@@ -4544,7 +5144,8 @@ var DabaAgent = class {
4544
5144
  this.mode,
4545
5145
  options.accentColor,
4546
5146
  options.iconBackgroundColor,
4547
- options.iconMarkColor
5147
+ options.iconMarkColor,
5148
+ this.hotkeyText
4548
5149
  );
4549
5150
  this.badge = badgeUI.root;
4550
5151
  this.badgeLabel = badgeUI.label;
@@ -4564,6 +5165,20 @@ var DabaAgent = class {
4564
5165
  }
4565
5166
  if (options.theme) applyThemeTokens(wrapper, options.theme);
4566
5167
  if (options.accentColor) applyThemeTokens(wrapper, { accent: options.accentColor });
5168
+ applyThemeTokens(wrapper, {
5169
+ "icon-bg": options.iconBackgroundColor,
5170
+ "icon-mark": options.iconMarkColor,
5171
+ "badge-size": cssLength(options.launcher?.size),
5172
+ "badge-icon-size": cssLength(options.launcher?.iconSize),
5173
+ "panel-w": cssLength(options.panel?.width),
5174
+ "panel-h": cssLength(options.panel?.height)
5175
+ });
5176
+ const mark = {
5177
+ svg: options.launcher?.iconSvg,
5178
+ text: options.launcher?.iconText,
5179
+ url: options.launcher?.iconUrl
5180
+ };
5181
+ if (mark.svg || mark.text || mark.url) setBadgeIcon(this.badge, mark);
4567
5182
  const chatUI = buildChatWindow({
4568
5183
  title: options.title ?? this.strings.assistantTitle,
4569
5184
  placeholder: options.placeholder ?? (this.mode === "chat" ? this.strings.inputPlaceholder : this.strings.typeOrTalk),
@@ -4573,12 +5188,18 @@ var DabaAgent = class {
4573
5188
  mode: this.mode,
4574
5189
  intro: options.intro ?? {},
4575
5190
  logoUrl: options.logoUrl,
5191
+ logoSvg: options.logoSvg,
4576
5192
  // null disables the shortcut entirely; the hint disappears with it.
4577
- hotkeyLabel: options.hotkey === null ? null : hotkeyLabel(options.hotkey ?? defaultHotkey()),
5193
+ hotkeyLabel: this.hotkeyText,
4578
5194
  onSendText: (text) => void this.sendText(text),
4579
5195
  onMicClick: () => void this.toggleVoice(),
4580
5196
  onCloseClick: () => this.closeChat(),
4581
- onNewConversation: () => this.resetConversation()
5197
+ historyEnabled: options.historyEnabled === true,
5198
+ onNewConversation: () => this.resetConversation(),
5199
+ onListHistory: () => this.listConversations(),
5200
+ onOpenConversation: (id) => this.openConversation(id),
5201
+ onDeleteConversation: (id) => this.deleteConversation(id),
5202
+ onClearHistory: () => this.clearHistory()
4582
5203
  });
4583
5204
  this.ui = chatUI;
4584
5205
  this.chatWindow = chatUI.chatWindow;
@@ -4592,8 +5213,13 @@ var DabaAgent = class {
4592
5213
  }
4593
5214
  if (options.hotkey !== null) {
4594
5215
  document.addEventListener("keydown", this.keydownHandler);
5216
+ document.addEventListener("keyup", this.keyupHandler);
5217
+ window.addEventListener("blur", this.blurHandler);
5218
+ document.addEventListener("pointerdown", this.blurHandler, true);
5219
+ document.addEventListener("visibilitychange", this.blurHandler);
4595
5220
  }
4596
5221
  this.restoreTranscript();
5222
+ this.ui.refreshHistory();
4597
5223
  void this.tryReattach();
4598
5224
  void this.loadRemoteConfig();
4599
5225
  }
@@ -4601,7 +5227,12 @@ var DabaAgent = class {
4601
5227
  * navigate() lands the visitor mid-conversation, not on a blank intro
4602
5228
  * while the model silently remembers the last four turns. */
4603
5229
  restoreTranscript() {
4604
- const saved = readTranscript(this.options.projectId, this.conversationId);
5230
+ this.renderMessages(readTranscript(this.options.projectId, this.conversationId));
5231
+ }
5232
+ /** Put a stored thread on screen and into `messages`. Shared by the
5233
+ * reload path and by opening something out of the archive, so a
5234
+ * reopened conversation behaves exactly like a resumed one. */
5235
+ renderMessages(saved) {
4605
5236
  if (!saved.length) return;
4606
5237
  for (const m of saved) {
4607
5238
  const cards = Array.isArray(m.cards) ? m.cards : [];
@@ -4625,11 +5256,54 @@ var DabaAgent = class {
4625
5256
  }
4626
5257
  saveTranscript() {
4627
5258
  persistConversationId(this.options.projectId, this.conversationId);
4628
- persistTranscript(
4629
- this.options.projectId,
4630
- this.conversationId,
4631
- this.messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, text: m.text, cards: m.cards, suggestions: m.suggestions, ts: m.timestamp }))
4632
- );
5259
+ const stored = this.messages.filter((m) => m.role !== "system").map((m) => ({ role: m.role, text: m.text, cards: m.cards, suggestions: m.suggestions, ts: m.timestamp }));
5260
+ persistTranscript(this.options.projectId, this.conversationId, stored);
5261
+ if (this.options.historyEnabled) {
5262
+ archiveConversation(
5263
+ this.options.projectId,
5264
+ this.conversationId,
5265
+ stored,
5266
+ window.location.pathname,
5267
+ this.strings.untitledConversation
5268
+ );
5269
+ this.ui.refreshHistory();
5270
+ }
5271
+ }
5272
+ /** The archive, newest first. Empty unless the embed opted in. */
5273
+ listConversations() {
5274
+ if (!this.options.historyEnabled || this.restricted) return [];
5275
+ return listConversations(this.options.projectId);
5276
+ }
5277
+ /**
5278
+ * Reopen an archived conversation as the LIVE one.
5279
+ *
5280
+ * Resuming rather than previewing: the gateway keeps its own thread
5281
+ * state keyed on the conversation id, so restoring the id means the
5282
+ * model still has the context the visitor is looking at. A read-only
5283
+ * view would show a conversation they cannot continue, which is the one
5284
+ * thing "go back to my chat" does not mean.
5285
+ */
5286
+ openConversation(id) {
5287
+ if (!this.options.historyEnabled || this.restricted) return;
5288
+ const saved = readConversation(this.options.projectId, id);
5289
+ if (!saved.length) return;
5290
+ this.messages = [];
5291
+ this.ui.clearMessages();
5292
+ this.conversationId = id;
5293
+ persistConversationId(this.options.projectId, id);
5294
+ persistTranscript(this.options.projectId, id, saved);
5295
+ this.renderMessages(saved);
5296
+ }
5297
+ deleteConversation(id) {
5298
+ if (!this.options.historyEnabled) return;
5299
+ deleteConversation(this.options.projectId, id);
5300
+ if (id === this.conversationId) this.resetConversation();
5301
+ this.ui.refreshHistory();
5302
+ }
5303
+ clearHistory() {
5304
+ if (!this.options.historyEnabled) return;
5305
+ clearConversations(this.options.projectId);
5306
+ this.ui.refreshHistory();
4633
5307
  }
4634
5308
  /**
4635
5309
  * Pull the project's own mode, theme, intro and locale from the gateway.
@@ -4668,8 +5342,11 @@ var DabaAgent = class {
4668
5342
  this.mode = config.mode;
4669
5343
  this.applyMode();
4670
5344
  }
4671
- const launcherIcon = this.options.launcher?.iconUrl ?? a.launcherIconUrl;
4672
- if (launcherIcon) setBadgeIcon(this.badge, launcherIcon);
5345
+ setBadgeIcon(this.badge, {
5346
+ svg: this.options.launcher?.iconSvg,
5347
+ text: this.options.launcher?.iconText,
5348
+ url: this.options.launcher?.iconUrl ?? a.launcherIconUrl
5349
+ });
4673
5350
  this.ui.applyConfig({
4674
5351
  logoUrl: this.options.logoUrl ?? a.logoUrl,
4675
5352
  title: this.options.title ?? config.title,
@@ -4693,17 +5370,60 @@ var DabaAgent = class {
4693
5370
  if (this.ui.composerMicBtn) this.ui.composerMicBtn.hidden = !showVoice;
4694
5371
  }
4695
5372
  handleKeydown(e) {
4696
- if (this.destroyed || this.restricted) return;
5373
+ if (this.destroyed || this.restricted || !this.parsedHotkey) return;
5374
+ if (this.heldModifier) {
5375
+ if (!isHoldKey(e, this.heldModifier)) {
5376
+ this.cancelHold();
5377
+ return;
5378
+ }
5379
+ if (e.repeat || this.holdTimer !== null || this.holdFired) return;
5380
+ this.holdTimer = window.setTimeout(() => {
5381
+ this.holdTimer = null;
5382
+ this.holdFired = true;
5383
+ void this.activate();
5384
+ }, HOLD_MS);
5385
+ return;
5386
+ }
4697
5387
  if (isEditableTarget(e.target) && e.target !== this.inputElement) return;
4698
5388
  if (e.repeat) return;
4699
- const hotkey = parseHotkey(this.options.hotkey ?? defaultHotkey());
4700
- if (!matchesHotkey(e, hotkey)) return;
5389
+ if (!matchesHotkey(e, this.parsedHotkey)) return;
4701
5390
  e.preventDefault();
4702
5391
  void this.toggle();
4703
5392
  }
5393
+ handleKeyup(e) {
5394
+ if (!this.heldModifier || !isHoldKey(e, this.heldModifier)) return;
5395
+ this.cancelHold();
5396
+ this.holdFired = false;
5397
+ }
5398
+ /**
5399
+ * What a completed hold does: START talking, never stop.
5400
+ *
5401
+ * The panel advertises this as "Hold ^ to talk". A visitor who keeps
5402
+ * holding while they speak — which is what those words invite — was
5403
+ * hanging up on themselves, because the hold used to call toggle() and
5404
+ * the second completed hold ended the session. Ending a conversation is
5405
+ * the badge's job, and the badge tooltip already says so.
5406
+ */
5407
+ async activate() {
5408
+ if (this.destroyed || this.restricted) return;
5409
+ if (this.mode === "chat") {
5410
+ this.openChat();
5411
+ return;
5412
+ }
5413
+ if (this.isActive) return;
5414
+ await this.startSession();
5415
+ }
5416
+ /** Releasing early, pressing another key, or leaving the tab all mean the
5417
+ * hold did not happen. Letting a stale timer survive any of those is how
5418
+ * a widget opens itself for no reason the visitor can see. */
5419
+ cancelHold() {
5420
+ if (this.holdTimer === null) return;
5421
+ window.clearTimeout(this.holdTimer);
5422
+ this.holdTimer = null;
5423
+ }
4704
5424
  setStatus(status) {
4705
5425
  this.status = status;
4706
- updateBadge(this.badge, this.badgeLabel, status, this.position);
5426
+ updateBadge(this.badge, this.badgeLabel, status, this.position, this.hotkeyText);
4707
5427
  this.options.onStatusChange?.(status);
4708
5428
  if (status === "listening" || status === "speaking") {
4709
5429
  this.micBtn.classList.add("daba-agent-header__btn--active");
@@ -4847,11 +5567,10 @@ var DabaAgent = class {
4847
5567
  this.messages = [];
4848
5568
  this.conversationId = newConversationId();
4849
5569
  persistConversationId(this.options.projectId, this.conversationId);
4850
- for (const child of Array.from(this.messagesContainer.children)) {
4851
- if (child !== this.ui.introEl) child.remove();
4852
- }
5570
+ this.ui.clearMessages();
4853
5571
  this.ui.setIntroVisible(true);
4854
5572
  this.ui.setTyping(false);
5573
+ this.ui.refreshHistory();
4855
5574
  }
4856
5575
  /** Voice, explicitly. Never reached in chat mode — the controls that
4857
5576
  * would call it are not rendered there. */
@@ -4863,14 +5582,28 @@ var DabaAgent = class {
4863
5582
  if (this.currentStreamRole !== role || !this.currentStreamBubble) {
4864
5583
  this.currentStreamBubble = appendMessage(this.messagesContainer, "", role);
4865
5584
  this.currentStreamRole = role;
5585
+ this.currentStreamText = "";
4866
5586
  }
4867
5587
  if (this.currentStreamBubble) {
5588
+ this.currentStreamText += text;
4868
5589
  this.currentStreamBubble.textContent += text;
4869
5590
  this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
4870
5591
  }
4871
5592
  if (isFinal) {
5593
+ const spoken = this.currentStreamText.trim();
5594
+ if (spoken) {
5595
+ this.messages.push({
5596
+ id: `${role}-${this.messages.length}-${Date.now()}`,
5597
+ role,
5598
+ text: spoken,
5599
+ timestamp: Date.now(),
5600
+ isFinal: true
5601
+ });
5602
+ this.saveTranscript();
5603
+ }
4872
5604
  this.currentStreamBubble = null;
4873
5605
  this.currentStreamRole = null;
5606
+ this.currentStreamText = "";
4874
5607
  }
4875
5608
  }
4876
5609
  async toggle() {
@@ -5137,6 +5870,9 @@ Allow this?`);
5137
5870
  }
5138
5871
  }
5139
5872
  stopSession() {
5873
+ const live = this.live;
5874
+ this.live = null;
5875
+ live?.stop();
5140
5876
  this.ws?.close(HARD_STOP_CLOSE_CODE, "user_stopped");
5141
5877
  clearPersistedSession(this.options.projectId);
5142
5878
  this.cleanupAfterClose();
@@ -5165,7 +5901,7 @@ Allow this?`);
5165
5901
  window.setTimeout(() => {
5166
5902
  this.badge.classList.remove("daba-agent-badge--error");
5167
5903
  if (this.status === "idle") {
5168
- updateBadge(this.badge, this.badgeLabel, "idle", this.position);
5904
+ updateBadge(this.badge, this.badgeLabel, "idle", this.position, this.hotkeyText);
5169
5905
  }
5170
5906
  }, 4e3);
5171
5907
  }
@@ -5184,12 +5920,17 @@ Allow this?`);
5184
5920
  destroy() {
5185
5921
  this.destroyed = true;
5186
5922
  document.removeEventListener("keydown", this.keydownHandler);
5187
- this.stopSession();
5923
+ document.removeEventListener("keyup", this.keyupHandler);
5924
+ window.removeEventListener("blur", this.blurHandler);
5925
+ document.removeEventListener("pointerdown", this.blurHandler, true);
5926
+ document.removeEventListener("visibilitychange", this.blurHandler);
5927
+ this.cancelHold();
5928
+ if (this.isActive || this.ws || this.live) this.stopSession();
5188
5929
  this.badge.parentElement?.remove();
5189
5930
  this.badge.remove();
5190
5931
  }
5191
5932
  };
5192
5933
 
5193
- export { DabaAgent };
5194
- //# sourceMappingURL=chunk-CMHPVBEF.js.map
5195
- //# sourceMappingURL=chunk-CMHPVBEF.js.map
5934
+ export { DabaAgent, cssLength };
5935
+ //# sourceMappingURL=chunk-ETI4LRM7.js.map
5936
+ //# sourceMappingURL=chunk-ETI4LRM7.js.map