@kodaris/krubble-app-components 1.0.100 → 1.0.102

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/chatbot.d.ts CHANGED
@@ -261,6 +261,24 @@ export declare class KRChatbot extends LitElement {
261
261
  * in place; call requestUpdate() after changing it to re-render.
262
262
  */
263
263
  private _globalSkillPending;
264
+ /**
265
+ * The live Web Speech recognizer while dictating, else null. Not reactive —
266
+ * `_isDictating` drives the UI; this is just the handle used to stop it.
267
+ */
268
+ private _speechRecognition;
269
+ /**
270
+ * The composer text as it stood when dictation started. Each `result` event
271
+ * re-reports the whole utterance (final + interim), so the value is rebuilt as
272
+ * this base + the transcript rather than appended to — appending would repeat
273
+ * the interim text on every event.
274
+ */
275
+ private _speechBaseValue;
276
+ /**
277
+ * Transcript of utterances the recognizer has marked final this session.
278
+ * Held separately because `results` is pruned in continuous mode on some
279
+ * browsers; without this, earlier sentences would vanish mid-dictation.
280
+ */
281
+ private _speechFinalTranscript;
264
282
  /**
265
283
  * Callback function for sending messages. Receives the user's message and
266
284
  * current conversation history. Must return a Response with a streaming SSE body.
@@ -408,6 +426,8 @@ export declare class KRChatbot extends LitElement {
408
426
  private _skillBrowseSource;
409
427
  /** Search text in the browse dialog, routed to the active tab's search. */
410
428
  private _skillBrowseQuery;
429
+ /** True while the mic is live and speech is being transcribed into the composer. */
430
+ private _isDictating;
411
431
  private _panelEl;
412
432
  private _messagesEl;
413
433
  private _inputEl;
@@ -451,6 +471,16 @@ export declare class KRChatbot extends LitElement {
451
471
  private _showOverlay;
452
472
  private _handleHeaderMouseDown;
453
473
  private _handleResizeMouseDown;
474
+ /**
475
+ * Close the `/` skill menu when the click lands outside it.
476
+ *
477
+ * Bound at the document because a click anywhere — including outside this
478
+ * component entirely — should dismiss the menu. `composedPath()` rather than
479
+ * `e.target` since the menu lives in this component's shadow root, where
480
+ * `target` on a document listener is retargeted to the host element and every
481
+ * inside click would look like an outside one.
482
+ */
483
+ private _handleDocumentClick;
454
484
  private _handleMouseMove;
455
485
  private _handleMouseUp;
456
486
  private _handleToggle;
@@ -502,6 +532,16 @@ export declare class KRChatbot extends LitElement {
502
532
  private _handleGlobalSkillEnable;
503
533
  /** The `+` button toggles the skill/attachment menu open (showing all skills). */
504
534
  private _handlePlusClick;
535
+ /**
536
+ * The mic button toggles dictation. Speech is transcribed straight into the
537
+ * composer as editable text — the user still reviews and presses send, so a
538
+ * misheard word is fixable and nothing is sent by voice alone.
539
+ *
540
+ * Recognition runs entirely in the browser's Web Speech API (Chrome/Edge send
541
+ * audio to Google's service, Safari to Apple's). No audio reaches Kodaris, and
542
+ * there's no backend call or cost on our side.
543
+ */
544
+ private _handleMicClick;
505
545
  /** Insert `/code ` into the composer and focus it, ready for the message. */
506
546
  private _handleSkillSelect;
507
547
  /** Activate a skill card via keyboard (Enter/Space), since it's a div role=button. */
@@ -559,6 +599,13 @@ export declare class KRChatbot extends LitElement {
559
599
  private _handleDebugResizeMouseUp;
560
600
  private _readStreamChunk;
561
601
  private _handleStreamError;
602
+ /**
603
+ * The composer's controls: attach, the textarea, the mic, and send/stop.
604
+ *
605
+ * While dictating, only the mic swaps — to a stop button — so the textarea
606
+ * stays visible with the transcript streaming into it and send stays reachable.
607
+ */
608
+ private _renderComposerControls;
562
609
  /** The `/` skill menu shown above the composer while typing a slash command. */
563
610
  private _renderSkillMenu;
564
611
  /** The "Browse all skills" dialog — a searchable grid of Org / Kodaris cards. */
@@ -1 +1 @@
1
- {"version":3,"file":"chatbot.d.ts","sourceRoot":"","sources":["../src/chatbot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAYrD;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACxC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,iBAAiB,CAAC,EAAE,yBAAyB,EAAE,CAAC;CACjD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,kBAAkB;IACjC,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,cAAc,EAAE,MAAM,CAAC;IACvB,yCAAyC;IACzC,eAAe,EAAE,MAAM,CAAC;IACxB,iEAAiE;IACjE,kBAAkB,EAAE,MAAM,CAAC;IAC3B,wDAAwD;IACxD,mBAAmB,EAAE,MAAM,CAAC;IAC5B,8CAA8C;IAC9C,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,CAAC;IACzB,kEAAkE;IAClE,gBAAgB,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,eAAe,EAAE,MAAM,CAAC;IACxB,iDAAiD;IACjD,mBAAmB,EAAE,MAAM,CAAC;IAC5B,6CAA6C;IAC7C,gBAAgB,EAAE,MAAM,CAAC;IACzB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,uBAAuB,EAAE,MAAM,CAAC;IAChC,yDAAyD;IACzD,wBAAwB,EAAE,MAAM,CAAC;IACjC,6DAA6D;IAC7D,2BAA2B,EAAE,MAAM,CAAC;IACpC,8DAA8D;IAC9D,4BAA4B,EAAE,MAAM,CAAC;IACrC,mEAAmE;IACnE,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mEAAmE;IACnE,oBAAoB,EAAE,MAAM,CAAC;IAC7B,wDAAwD;IACxD,mBAAmB,EAAE,MAAM,CAAC;IAC5B,iDAAiD;IACjD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,2DAA2D;IAC3D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;IAClB,8FAA8F;IAC9F,IAAI,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,sFAAsF;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,iBAAiB,EAAE,iBAAiB,EAAE,CAAC;CACxC,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AA0BxB;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAChC,8BAA8B;IAC9B,IAAI,EAAE,IAAI,CAAC;IACX,gFAAgF;IAChF,SAAS,EAAE,OAAO,CAAC;CACpB;AAgBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,qBACa,SAAU,SAAQ,UAAU;IACvC,OAAgB,MAAM,0BAqxCpB;IAIF,OAAO,CAAC,OAAO,CAAwD;IAEvE,OAAO,CAAC,QAAQ,CAAqB;IAErC,OAAO,CAAC,OAAO,CAAM;IAErB,OAAO,CAAC,kBAAkB,CAAS;IAEnC,OAAO,CAAC,WAAW,CAAS;IAE5B,OAAO,CAAC,WAAW,CAAS;IAE5B,OAAO,CAAC,QAAQ,CAA4B;IAE5C,OAAO,CAAC,UAAU,CAAM;IAExB,OAAO,CAAC,WAAW,CAAK;IAExB,OAAO,CAAC,WAAW,CAAK;IAExB,OAAO,CAAC,cAAc,CAAK;IAE3B,OAAO,CAAC,aAAa,CAAK;IAE1B,OAAO,CAAC,aAAa,CAAK;IAE1B,OAAO,CAAC,aAAa,CAAK;IAE1B,6CAA6C;IAC7C,OAAO,CAAC,kBAAkB,CAAK;IAE/B,0CAA0C;IAC1C,OAAO,CAAC,kBAAkB,CAAK;IAE/B,4DAA4D;IAC5D,OAAO,CAAC,UAAU,CAAK;IAEvB,mDAAmD;IACnD,OAAO,CAAC,WAAW,CAAK;IAExB;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB,CAAQ;IAErC;;;;OAIG;IACH,OAAO,CAAC,kBAAkB,CAAK;IAE/B,OAAO,CAAC,qBAAqB,CAAK;IAElC,OAAO,CAAC,wBAAwB,CAAK;IAErC;;;;OAIG;IACH,OAAO,CAAC,mBAAmB,CAAqB;IAIhD;;;;;;OAMG;IAEH,IAAI,EAAE,aAAa,GAAG,IAAI,CAAQ;IAElC;;;;;;OAMG;IAEH,IAAI,EAAE,CAAC,MAAM,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,GAAG,IAAI,CAAQ;IAExD;;;OAGG;IAEH,QAAQ,EAAE,gBAAgB,EAAE,CAAM;IAElC;;OAEG;IAEH,KAAK,SAAS;IAEd;;OAEG;IAEH,WAAW,SAAuB;IAElC;;OAEG;IAKH,MAAM,UAAS;IAEf;;;;OAIG;IAMH,UAAU,UAAS;IAEnB;;;;;;;;;OASG;IAKH,OAAO,EAAE,UAAU,GAAG,QAAQ,CAAc;IAE5C;;;;;;;;;;;;OAYG;IAMH,cAAc,EAAE,MAAM,GAAG,IAAI,CAAQ;IAKrC,OAAO,CAAC,WAAW,CAAM;IAGzB,OAAO,CAAC,YAAY,CAAS;IAG7B,OAAO,CAAC,MAAM,CAAM;IAEpB;;;OAGG;IAEH,OAAO,CAAC,eAAe,CAAM;IAE7B;;;;OAIG;IAEH,OAAO,CAAC,oBAAoB,CAAS;IAErC,yEAAyE;IAEzE,OAAO,CAAC,oBAAoB,CAAM;IAElC;;;;OAIG;IAEH,OAAO,CAAC,UAAU,CAAmC;IAErD;;;;OAIG;IAEH,OAAO,CAAC,aAAa,CAA4B;IAEjD,8EAA8E;IAE9E,OAAO,CAAC,iBAAiB,CAAgD;IAEzE,qEAAqE;IAErE,OAAO,CAAC,eAAe,CAAS;IAEhC,2EAA2E;IAE3E,OAAO,CAAC,iBAAiB,CAAO;IAEhC,wEAAwE;IAExE,OAAO,CAAC,gBAAgB,CAAS;IAEjC,gEAAgE;IAEhE,OAAO,CAAC,yBAAyB,CAA0C;IAE3E,kFAAkF;IAElF,OAAO,CAAC,eAAe,CAAS;IAEhC,6DAA6D;IAE7D,OAAO,CAAC,gBAAgB,CAAS;IAEjC,4EAA4E;IAE5E,OAAO,CAAC,WAAW,CAAM;IAEzB;;;OAGG;IAEH,OAAO,CAAC,UAAU,CAAwB;IAE1C;;;;;OAKG;IAEH,OAAO,CAAC,aAAa,CAAwB;IAE7C;;;;;OAKG;IAEH,OAAO,CAAC,gBAAgB,CAAwB;IAEhD;;;;;OAKG;IAEH,OAAO,CAAC,kBAAkB,CAA+B;IAEzD,8EAA8E;IAE9E,OAAO,CAAC,iBAAiB,CAAK;IAE9B,gFAAgF;IAEhF,OAAO,CAAC,YAAY,CAAM;IAE1B,sDAAsD;IAEtD,OAAO,CAAC,kBAAkB,CAAS;IAEnC;;;OAGG;IAEH,OAAO,CAAC,kBAAkB,CAA4B;IAEtD,2EAA2E;IAE3E,OAAO,CAAC,iBAAiB,CAAM;IAK/B,OAAO,CAAC,QAAQ,CAAe;IAG/B,OAAO,CAAC,WAAW,CAAe;IAGlC,OAAO,CAAC,QAAQ,CAAuB;IAGvC,OAAO,CAAC,YAAY,CAAoB;IAGxC,OAAO,CAAC,eAAe,CAAe;IAGtC,OAAO,CAAC,YAAY,CAAe;IAI1B,iBAAiB;IAqEjB,oBAAoB;IAgBpB,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IA8B9C,4EAA4E;IAC5E,OAAO,CAAC,kBAAkB;IA0B1B;;OAEG;IACH,IAAI;IAIJ,2BAA2B;IAC3B,IAAI;IAMJ,4BAA4B;IAC5B,KAAK;IAML,yCAAyC;IACzC,MAAM;IAMN;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,YAAY;IASpB,OAAO,CAAC,sBAAsB;IAmC9B,OAAO,CAAC,sBAAsB;IAmC9B,OAAO,CAAC,gBAAgB,CA+CvB;IAED,OAAO,CAAC,cAAc,CAerB;IAED,OAAO,CAAC,aAAa;IAQrB,OAAO,CAAC,UAAU;IA8BlB,OAAO,CAAC,aAAa;IAyBrB,OAAO,CAAC,kBAAkB;IAyB1B,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,cAAc;IAuCtB,oFAAoF;IACpF,OAAO,CAAC,sBAAsB;IA0C9B;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,gBAAgB;IA6ExB;;;;;;;;;OASG;IACH,OAAO,CAAC,sBAAsB;IAsF9B;;;;;;;OAOG;IACH,OAAO,CAAC,mBAAmB;IAsB3B;;;;OAIG;IACH,OAAO,CAAC,wBAAwB;IAkChC,kFAAkF;IAClF,OAAO,CAAC,gBAAgB;IAWxB,6EAA6E;IAC7E,OAAO,CAAC,kBAAkB;IAe1B,sFAAsF;IACtF,OAAO,CAAC,uBAAuB;IAO/B,yEAAyE;IACzE,OAAO,CAAC,kBAAkB;IAW1B,OAAO,CAAC,sBAAsB;IAS9B,OAAO,CAAC,uBAAuB;IAI/B,OAAO,CAAC,wBAAwB;IAOhC,OAAO,CAAC,2BAA2B;IAQnC;;;;;;;OAOG;IACH,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,sBAAsB;IAS9B,OAAO,CAAC,YAAY;IAoBpB,OAAO,CAAC,gBAAgB;IASxB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,SAAS;IAIjB,4EAA4E;IAC5E,OAAO,CAAC,iBAAiB;IAQzB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IA2BjB,OAAO,CAAC,oBAAoB;IAQ5B,OAAO,CAAC,aAAa;IASrB,iEAAiE;IACjE,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,aAAa;IA0CrB;;;;;OAKG;IACH,OAAO,CAAC,KAAK;IAmMb,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,qBAAqB;IAU7B,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,4BAA4B;IAIpC,OAAO,CAAC,uBAAuB;IAK/B,OAAO,CAAC,2BAA2B;IAUnC,OAAO,CAAC,2BAA2B,CAYjC;IAEF,OAAO,CAAC,yBAAyB,CAK/B;IAEF,OAAO,CAAC,gBAAgB;IAwIxB,OAAO,CAAC,kBAAkB;IAqB1B,gFAAgF;IAChF,OAAO,CAAC,gBAAgB;IAuFxB,iFAAiF;IACjF,OAAO,CAAC,kBAAkB;IA8B1B;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAwCxB;;;;OAIG;IACH,OAAO,CAAC,kBAAkB;IAQ1B,8EAA8E;IAC9E,OAAO,CAAC,UAAU;IAOlB,iFAAiF;IACjF,OAAO,CAAC,QAAQ;IAOhB;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAqIhB,MAAM;CA4bhB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,YAAY,EAAE,SAAS,CAAC;KACzB;CACF"}
1
+ {"version":3,"file":"chatbot.d.ts","sourceRoot":"","sources":["../src/chatbot.ts"],"names":[],"mappings":"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"}
package/dist/chatbot.js CHANGED
@@ -113,6 +113,24 @@ let KRChatbot = class KRChatbot extends LitElement {
113
113
  * in place; call requestUpdate() after changing it to re-render.
114
114
  */
115
115
  this._globalSkillPending = new Set();
116
+ /**
117
+ * The live Web Speech recognizer while dictating, else null. Not reactive —
118
+ * `_isDictating` drives the UI; this is just the handle used to stop it.
119
+ */
120
+ this._speechRecognition = null;
121
+ /**
122
+ * The composer text as it stood when dictation started. Each `result` event
123
+ * re-reports the whole utterance (final + interim), so the value is rebuilt as
124
+ * this base + the transcript rather than appended to — appending would repeat
125
+ * the interim text on every event.
126
+ */
127
+ this._speechBaseValue = '';
128
+ /**
129
+ * Transcript of utterances the recognizer has marked final this session.
130
+ * Held separately because `results` is pruned in continuous mode on some
131
+ * browsers; without this, earlier sentences would vanish mid-dictation.
132
+ */
133
+ this._speechFinalTranscript = '';
116
134
  // --- @property (public API) ---
117
135
  /**
118
136
  * Callback function for sending messages. Receives the user's message and
@@ -262,6 +280,33 @@ let KRChatbot = class KRChatbot extends LitElement {
262
280
  this._skillBrowseSource = 'org';
263
281
  /** Search text in the browse dialog, routed to the active tab's search. */
264
282
  this._skillBrowseQuery = '';
283
+ /** True while the mic is live and speech is being transcribed into the composer. */
284
+ this._isDictating = false;
285
+ /**
286
+ * Close the `/` skill menu when the click lands outside it.
287
+ *
288
+ * Bound at the document because a click anywhere — including outside this
289
+ * component entirely — should dismiss the menu. `composedPath()` rather than
290
+ * `e.target` since the menu lives in this component's shadow root, where
291
+ * `target` on a document listener is retargeted to the host element and every
292
+ * inside click would look like an outside one.
293
+ */
294
+ this._handleDocumentClick = (e) => {
295
+ if (!this._skillMenuOpened) {
296
+ return;
297
+ }
298
+ // Excluded: the textarea, since clicking it keeps editing the same `/` command
299
+ // and dismissing there would fight the user still typing it; and the plus
300
+ // button, which owns the menu's open/close itself. The plus exclusion matters
301
+ // in both directions — this listener runs after the button's own handler, so
302
+ // without it a click that just opened the menu would immediately close it
303
+ // again, and a click meant to toggle it shut would be double-handled.
304
+ const path = e.composedPath();
305
+ if (path.some((el) => el instanceof HTMLElement && (el.classList.contains('skill-menu') || el.classList.contains('input') || el.classList.contains('input-plus')))) {
306
+ return;
307
+ }
308
+ this._skillMenuOpened = false;
309
+ };
265
310
  this._handleMouseMove = (e) => {
266
311
  if (this._isDragging) {
267
312
  let newLeft = this._dragStartLeft + (e.clientX - this._dragStartX);
@@ -405,6 +450,7 @@ let KRChatbot = class KRChatbot extends LitElement {
405
450
  this.opened = true;
406
451
  }
407
452
  this._restoreState();
453
+ document.addEventListener('click', this._handleDocumentClick);
408
454
  }
409
455
  disconnectedCallback() {
410
456
  super.disconnectedCallback();
@@ -414,6 +460,15 @@ let KRChatbot = class KRChatbot extends LitElement {
414
460
  }
415
461
  document.removeEventListener('mousemove', this._handleMouseMove);
416
462
  document.removeEventListener('mouseup', this._handleMouseUp);
463
+ document.removeEventListener('click', this._handleDocumentClick);
464
+ // Release the mic if the chatbot is torn down mid-dictation — otherwise the
465
+ // recognizer keeps the browser's recording indicator lit with nothing to
466
+ // transcribe into. abort() (not stop()) since the discarded text has nowhere to go.
467
+ if (this._speechRecognition) {
468
+ this._speechRecognition.abort();
469
+ this._speechRecognition = null;
470
+ this._isDictating = false;
471
+ }
417
472
  // Release any object URLs still held by staged attachments.
418
473
  this._pendingConversationFiles.forEach((p) => {
419
474
  if (p.previewUrl) {
@@ -627,7 +682,12 @@ let KRChatbot = class KRChatbot extends LitElement {
627
682
  }
628
683
  const state = JSON.parse(raw);
629
684
  // Draft text and the debug drawer are persisted for all variants.
630
- if (typeof state.message === 'string') {
685
+ // A lone `/` is dropped — it's the in-progress half of a menu interaction,
686
+ // not a draft, and restoring it would revive a stale menu the user never
687
+ // used. Anything with an actual command after the slash (`/code`, `/code do
688
+ // the thing`) is a real draft and restores normally. Trimmed before testing
689
+ // so a stray space around the slash doesn't defeat the check.
690
+ if (typeof state.message === 'string' && state.message.trim() !== '/') {
631
691
  this._inputValue = state.message;
632
692
  }
633
693
  this._debugPanelOpen = state.debugPanelOpen === 'true';
@@ -1020,6 +1080,92 @@ let KRChatbot = class KRChatbot extends LitElement {
1020
1080
  this._skillMenuOpened = true;
1021
1081
  this._searchInvocableSkills('');
1022
1082
  }
1083
+ /**
1084
+ * The mic button toggles dictation. Speech is transcribed straight into the
1085
+ * composer as editable text — the user still reviews and presses send, so a
1086
+ * misheard word is fixable and nothing is sent by voice alone.
1087
+ *
1088
+ * Recognition runs entirely in the browser's Web Speech API (Chrome/Edge send
1089
+ * audio to Google's service, Safari to Apple's). No audio reaches Kodaris, and
1090
+ * there's no backend call or cost on our side.
1091
+ */
1092
+ _handleMicClick() {
1093
+ // Second click stops. `stop()` flushes the pending utterance as a final
1094
+ // result, so the last words aren't lost; onend clears the state.
1095
+ if (this._speechRecognition) {
1096
+ this._speechRecognition.stop();
1097
+ return;
1098
+ }
1099
+ // Prefixed in Chrome/Edge/Safari; the unprefixed name is checked first for
1100
+ // whenever they drop the prefix. The mic button isn't rendered without one of
1101
+ // these, so reaching here unsupported means a mid-session change — bail quietly.
1102
+ const SpeechRecognitionCtor = window.SpeechRecognition || window.webkitSpeechRecognition;
1103
+ if (!SpeechRecognitionCtor) {
1104
+ return;
1105
+ }
1106
+ const recognition = new SpeechRecognitionCtor();
1107
+ // Keep listening through natural pauses — a chat message is often several
1108
+ // sentences, and the default stops at the first one.
1109
+ recognition.continuous = true;
1110
+ // Show words as they're recognized rather than only at end of utterance.
1111
+ recognition.interimResults = true;
1112
+ // `lang` is deliberately left unset so the recognizer follows the page/OS
1113
+ // language, which is correct for every consumer of this component.
1114
+ // Dictation ADDS to whatever is already typed rather than replacing it, so
1115
+ // the user can type half a message and speak the rest. The separating space
1116
+ // is part of the base so the transcript never runs into the existing word.
1117
+ this._speechBaseValue = this._inputValue;
1118
+ if (this._speechBaseValue && !this._speechBaseValue.endsWith(' ')) {
1119
+ this._speechBaseValue += ' ';
1120
+ }
1121
+ this._speechFinalTranscript = '';
1122
+ recognition.onresult = (e) => {
1123
+ // Everything from resultIndex on is new since the last event: finalized
1124
+ // pieces move to _speechFinalTranscript permanently, while non-final ones
1125
+ // are rebuilt each event because the browser revises its guess as it hears
1126
+ // more. Rebuilding the whole value (base + final + interim) is what keeps
1127
+ // those revisions from stacking up as duplicated text.
1128
+ let interim = '';
1129
+ for (let i = e.resultIndex; i < e.results.length; i++) {
1130
+ if (e.results[i].isFinal) {
1131
+ this._speechFinalTranscript += e.results[i][0].transcript;
1132
+ }
1133
+ else {
1134
+ interim += e.results[i][0].transcript;
1135
+ }
1136
+ }
1137
+ this._inputValue = this._speechBaseValue + this._speechFinalTranscript + interim;
1138
+ this._saveState();
1139
+ // Grow the textarea as dictated text wraps — the same clamp the typing path
1140
+ // uses, since @input doesn't fire for programmatic value changes.
1141
+ if (this._inputEl) {
1142
+ this._inputEl.style.height = 'auto';
1143
+ this._inputEl.style.height = Math.min(this._inputEl.scrollHeight, 120) + 'px';
1144
+ }
1145
+ };
1146
+ recognition.onerror = (e) => {
1147
+ // Denied mic permission is the one error worth explaining — the user has to
1148
+ // fix it in browser settings, and the button would otherwise look broken.
1149
+ if (e.error === 'not-allowed' || e.error === 'service-not-allowed') {
1150
+ this._error = 'Microphone access is blocked. Allow it in your browser settings to use voice input.';
1151
+ return;
1152
+ }
1153
+ // `no-speech` (silence) and `aborted` (user stopped) are normal; anything
1154
+ // else is a transient service hiccup. onend still runs, so the mic resets.
1155
+ };
1156
+ // Fires on stop(), on error, and when the browser ends recognition on its own
1157
+ // (e.g. a long silence) — the single place the live state is torn down.
1158
+ recognition.onend = () => {
1159
+ this._speechRecognition = null;
1160
+ this._isDictating = false;
1161
+ };
1162
+ // start() is what triggers the browser's mic permission prompt.
1163
+ this._speechRecognition = recognition;
1164
+ this._isDictating = true;
1165
+ this._error = '';
1166
+ recognition.start();
1167
+ this._inputEl?.focus();
1168
+ }
1023
1169
  /** Insert `/code ` into the composer and focus it, ready for the message. */
1024
1170
  _handleSkillSelect(skill) {
1025
1171
  // A global skill must be installed before it can be chosen — inserting /code
@@ -1089,6 +1235,15 @@ let KRChatbot = class KRChatbot extends LitElement {
1089
1235
  // the menu interaction, and the picker is a separate window so the menu would
1090
1236
  // otherwise linger open behind it (and after a file is chosen).
1091
1237
  this._skillMenuOpened = false;
1238
+ // A lone `/` was only the means of reaching this action, so it shouldn't be
1239
+ // left behind in the composer once the picker opens. Nothing else is touched:
1240
+ // a real command (`/code`) stays because `/code` plus an attachment is a valid
1241
+ // invocation, and the plus button reaches this same method with an arbitrary
1242
+ // draft in the composer, which must survive.
1243
+ if (this._inputValue.trim() === '/') {
1244
+ this._inputValue = '';
1245
+ this._saveState();
1246
+ }
1092
1247
  this._pendingAttachAnalyze = aiAnalyze;
1093
1248
  this._fileInputEl?.click();
1094
1249
  }
@@ -1290,6 +1445,13 @@ let KRChatbot = class KRChatbot extends LitElement {
1290
1445
  previewUrl: p.previewUrl,
1291
1446
  }));
1292
1447
  }
1448
+ // The message is on its way, so stop the mic if it's still live — otherwise
1449
+ // it would keep transcribing into the now-cleared composer. abort() rather
1450
+ // than stop() because a trailing pending utterance belongs to the message
1451
+ // just sent; flushing it would drop stray words into the next one.
1452
+ if (this._speechRecognition) {
1453
+ this._speechRecognition.abort();
1454
+ }
1293
1455
  this.messages = [...this.messages, userMessage];
1294
1456
  this._inputValue = '';
1295
1457
  this._saveState();
@@ -1643,6 +1805,122 @@ let KRChatbot = class KRChatbot extends LitElement {
1643
1805
  }
1644
1806
  }
1645
1807
  // --- Render ---
1808
+ /**
1809
+ * The composer's controls: attach, the textarea, the mic, and send/stop.
1810
+ *
1811
+ * While dictating, only the mic swaps — to a stop button — so the textarea
1812
+ * stays visible with the transcript streaming into it and send stays reachable.
1813
+ */
1814
+ _renderComposerControls() {
1815
+ return html `
1816
+ <button
1817
+ class="input-plus"
1818
+ @click=${this._handlePlusClick}
1819
+ >+</button>
1820
+ <textarea
1821
+ class="input"
1822
+ .value=${this._inputValue}
1823
+ placeholder=${this.placeholder}
1824
+ rows="1"
1825
+ @input=${this._handleInputChange}
1826
+ @keydown=${this._handleKeyDown}
1827
+ @paste=${this._handlePaste}
1828
+ ></textarea>
1829
+ ${window.SpeechRecognition || window.webkitSpeechRecognition ? html `
1830
+ ${this._isDictating ? html `
1831
+ <button
1832
+ class="mic-stop"
1833
+ title="Stop dictation"
1834
+ aria-label="Stop dictation"
1835
+ @click=${this._handleMicClick}
1836
+ >
1837
+ <svg
1838
+ xmlns="http://www.w3.org/2000/svg"
1839
+ fill="currentColor"
1840
+ viewBox="0 0 24 24"
1841
+ class="mic-stop-icon"
1842
+ aria-hidden="true"
1843
+ >
1844
+ <rect
1845
+ x="6"
1846
+ y="6"
1847
+ width="12"
1848
+ height="12"
1849
+ rx="2"
1850
+ />
1851
+ </svg>
1852
+ </button>
1853
+ ` : html `
1854
+ <button
1855
+ class="mic"
1856
+ title="Dictate a message"
1857
+ aria-label="Dictate a message"
1858
+ @click=${this._handleMicClick}
1859
+ >
1860
+ <svg
1861
+ xmlns="http://www.w3.org/2000/svg"
1862
+ viewBox="0 0 24 24"
1863
+ fill="none"
1864
+ stroke="currentColor"
1865
+ stroke-width="1.75"
1866
+ stroke-linecap="round"
1867
+ stroke-linejoin="round"
1868
+ class="mic-icon"
1869
+ aria-hidden="true"
1870
+ >
1871
+ <path d="M12 15a3 3 0 0 0 3-3V6a3 3 0 0 0-6 0v6a3 3 0 0 0 3 3Z"/>
1872
+ <path d="M19 11a7 7 0 0 1-14 0"/>
1873
+ <path d="M12 18v3"/>
1874
+ </svg>
1875
+ </button>
1876
+ `}
1877
+ ` : nothing}
1878
+ ${this._isStreaming ? html `
1879
+ <button
1880
+ class="stop"
1881
+ @click=${this._handleStop}
1882
+ title="Stop generating"
1883
+ >
1884
+ <svg
1885
+ xmlns="http://www.w3.org/2000/svg"
1886
+ fill="currentColor"
1887
+ viewBox="0 0 24 24"
1888
+ class="stop-icon"
1889
+ >
1890
+ <rect
1891
+ x="6"
1892
+ y="6"
1893
+ width="12"
1894
+ height="12"
1895
+ rx="2"
1896
+ />
1897
+ </svg>
1898
+ </button>
1899
+ ` : html `
1900
+ <button
1901
+ class=${classMap({
1902
+ 'send': true,
1903
+ 'send--disabled': !this._inputValue.trim() && !this._pendingConversationFiles.length,
1904
+ })}
1905
+ @click=${this._handleSubmit}
1906
+ title="Send message"
1907
+ >
1908
+ <svg
1909
+ width="16"
1910
+ height="16"
1911
+ viewBox="0 0 24 24"
1912
+ fill="none"
1913
+ stroke="currentColor"
1914
+ stroke-width="2.5"
1915
+ stroke-linecap="round"
1916
+ class="send-icon"
1917
+ >
1918
+ <path d="M12 19V5M5 12l7-7 7 7"/>
1919
+ </svg>
1920
+ </button>
1921
+ `}
1922
+ `;
1923
+ }
1646
1924
  /** The `/` skill menu shown above the composer while typing a slash command. */
1647
1925
  _renderSkillMenu() {
1648
1926
  const skills = this._invocableSkills;
@@ -2349,63 +2627,7 @@ let KRChatbot = class KRChatbot extends LitElement {
2349
2627
  />
2350
2628
  ${this._skillMenuOpened ? this._renderSkillMenu() : nothing}
2351
2629
  <div class="input-wrapper">
2352
- <button
2353
- class="input-plus"
2354
- @click=${this._handlePlusClick}
2355
- >+</button>
2356
- <textarea
2357
- class="input"
2358
- .value=${this._inputValue}
2359
- placeholder=${this.placeholder}
2360
- rows="1"
2361
- @input=${this._handleInputChange}
2362
- @keydown=${this._handleKeyDown}
2363
- @paste=${this._handlePaste}
2364
- ></textarea>
2365
- ${this._isStreaming ? html `
2366
- <button
2367
- class="stop"
2368
- @click=${this._handleStop}
2369
- title="Stop generating"
2370
- >
2371
- <svg
2372
- xmlns="http://www.w3.org/2000/svg"
2373
- fill="currentColor"
2374
- viewBox="0 0 24 24"
2375
- class="stop-icon"
2376
- >
2377
- <rect
2378
- x="6"
2379
- y="6"
2380
- width="12"
2381
- height="12"
2382
- rx="2"
2383
- />
2384
- </svg>
2385
- </button>
2386
- ` : html `
2387
- <button
2388
- class=${classMap({
2389
- 'send': true,
2390
- 'send--disabled': !this._inputValue.trim() && !this._pendingConversationFiles.length,
2391
- })}
2392
- @click=${this._handleSubmit}
2393
- title="Send message"
2394
- >
2395
- <svg
2396
- width="16"
2397
- height="16"
2398
- viewBox="0 0 24 24"
2399
- fill="none"
2400
- stroke="currentColor"
2401
- stroke-width="2.5"
2402
- stroke-linecap="round"
2403
- class="send-icon"
2404
- >
2405
- <path d="M12 19V5M5 12l7-7 7 7"/>
2406
- </svg>
2407
- </button>
2408
- `}
2630
+ ${this._renderComposerControls()}
2409
2631
  </div>
2410
2632
  </div>
2411
2633
  ${this._skillBrowseOpened ? this._renderSkillBrowse() : nothing}
@@ -3205,6 +3427,71 @@ KRChatbot.styles = css `
3205
3427
  color: #b0b0b0;
3206
3428
  }
3207
3429
 
3430
+ /* margin-right rather than a wider .input-wrapper gap, which would also push
3431
+ the plus button and textarea apart. Applied to both mic states so the
3432
+ spacing doesn't shift when dictation swaps one for the other. */
3433
+ .mic,
3434
+ .mic-stop {
3435
+ margin-right: 4px;
3436
+ }
3437
+
3438
+ .mic {
3439
+ width: 34px;
3440
+ height: 34px;
3441
+ border: none;
3442
+ background: transparent;
3443
+ color: var(--kr-chatbot-text);
3444
+ border-radius: 50%;
3445
+ cursor: pointer;
3446
+ display: flex;
3447
+ align-items: center;
3448
+ justify-content: center;
3449
+ flex-shrink: 0;
3450
+ transition: background 0.15s, color 0.15s;
3451
+ }
3452
+
3453
+ .mic:hover {
3454
+ background: #f8f9fc;
3455
+ }
3456
+
3457
+ .mic-icon {
3458
+ width: 21px;
3459
+ height: 21px;
3460
+ }
3461
+
3462
+ /* Dictation swaps the mic for this stop button, sitting in the mic's slot so
3463
+ the composer's layout is unchanged and the textarea stays visible with the
3464
+ transcript streaming into it. Structurally identical to .stop; only the
3465
+ colors differ — dictation isn't an error state, so it takes a neutral grey
3466
+ instead of the error red, and that also keeps it distinct from the solid
3467
+ send button sitting right beside it. The icon is dark rather than white
3468
+ because white on this grey wouldn't meet contrast. */
3469
+ .mic-stop {
3470
+ width: 34px;
3471
+ height: 34px;
3472
+ border-radius: 50%;
3473
+ background: #eae7e7;
3474
+ color: #1f1f1f;
3475
+ border: none;
3476
+ cursor: pointer;
3477
+ display: flex;
3478
+ align-items: center;
3479
+ justify-content: center;
3480
+ flex-shrink: 0;
3481
+ transition: all 0.15s;
3482
+ }
3483
+
3484
+ .mic-stop:hover {
3485
+ background: #ded9d9;
3486
+ }
3487
+
3488
+ /* Larger than .stop-icon's 14px: the rect fills only half its 24-unit
3489
+ viewBox, so the drawn square is half this value. */
3490
+ .mic-stop-icon {
3491
+ width: 20px;
3492
+ height: 20px;
3493
+ }
3494
+
3208
3495
  .send {
3209
3496
  width: 34px;
3210
3497
  height: 34px;
@@ -3832,6 +4119,9 @@ __decorate([
3832
4119
  __decorate([
3833
4120
  state()
3834
4121
  ], KRChatbot.prototype, "_skillBrowseQuery", void 0);
4122
+ __decorate([
4123
+ state()
4124
+ ], KRChatbot.prototype, "_isDictating", void 0);
3835
4125
  __decorate([
3836
4126
  query('.panel')
3837
4127
  ], KRChatbot.prototype, "_panelEl", void 0);