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