@kodaris/krubble-app-components 1.0.100 → 1.0.101
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 +30 -0
- package/dist/chatbot.d.ts.map +1 -1
- package/dist/chatbot.js +194 -0
- package/dist/chatbot.js.map +1 -1
- package/dist/krubble-app.bundled.js +194 -0
- package/dist/krubble-app.bundled.js.map +1 -1
- package/dist/krubble-app.bundled.min.js +89 -22
- package/dist/krubble-app.bundled.min.js.map +1 -1
- package/dist/krubble-app.umd.js +194 -0
- package/dist/krubble-app.umd.js.map +1 -1
- package/dist/krubble-app.umd.min.js +84 -17
- package/dist/krubble-app.umd.min.js.map +1 -1
- package/package.json +1 -1
|
@@ -5626,6 +5626,24 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
5626
5626
|
* in place; call requestUpdate() after changing it to re-render.
|
|
5627
5627
|
*/
|
|
5628
5628
|
this._globalSkillPending = new Set();
|
|
5629
|
+
/**
|
|
5630
|
+
* The live Web Speech recognizer while dictating, else null. Not reactive —
|
|
5631
|
+
* `_isDictating` drives the UI; this is just the handle used to stop it.
|
|
5632
|
+
*/
|
|
5633
|
+
this._speechRecognition = null;
|
|
5634
|
+
/**
|
|
5635
|
+
* The composer text as it stood when dictation started. Each `result` event
|
|
5636
|
+
* re-reports the whole utterance (final + interim), so the value is rebuilt as
|
|
5637
|
+
* this base + the transcript rather than appended to — appending would repeat
|
|
5638
|
+
* the interim text on every event.
|
|
5639
|
+
*/
|
|
5640
|
+
this._speechBaseValue = '';
|
|
5641
|
+
/**
|
|
5642
|
+
* Transcript of utterances the recognizer has marked final this session.
|
|
5643
|
+
* Held separately because `results` is pruned in continuous mode on some
|
|
5644
|
+
* browsers; without this, earlier sentences would vanish mid-dictation.
|
|
5645
|
+
*/
|
|
5646
|
+
this._speechFinalTranscript = '';
|
|
5629
5647
|
// --- @property (public API) ---
|
|
5630
5648
|
/**
|
|
5631
5649
|
* Callback function for sending messages. Receives the user's message and
|
|
@@ -5775,6 +5793,8 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
5775
5793
|
this._skillBrowseSource = 'org';
|
|
5776
5794
|
/** Search text in the browse dialog, routed to the active tab's search. */
|
|
5777
5795
|
this._skillBrowseQuery = '';
|
|
5796
|
+
/** True while the mic is live and speech is being transcribed into the composer. */
|
|
5797
|
+
this._isDictating = false;
|
|
5778
5798
|
this._handleMouseMove = (e) => {
|
|
5779
5799
|
if (this._isDragging) {
|
|
5780
5800
|
let newLeft = this._dragStartLeft + (e.clientX - this._dragStartX);
|
|
@@ -5927,6 +5947,14 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
5927
5947
|
}
|
|
5928
5948
|
document.removeEventListener('mousemove', this._handleMouseMove);
|
|
5929
5949
|
document.removeEventListener('mouseup', this._handleMouseUp);
|
|
5950
|
+
// Release the mic if the chatbot is torn down mid-dictation — otherwise the
|
|
5951
|
+
// recognizer keeps the browser's recording indicator lit with nothing to
|
|
5952
|
+
// transcribe into. abort() (not stop()) since the discarded text has nowhere to go.
|
|
5953
|
+
if (this._speechRecognition) {
|
|
5954
|
+
this._speechRecognition.abort();
|
|
5955
|
+
this._speechRecognition = null;
|
|
5956
|
+
this._isDictating = false;
|
|
5957
|
+
}
|
|
5930
5958
|
// Release any object URLs still held by staged attachments.
|
|
5931
5959
|
this._pendingConversationFiles.forEach((p) => {
|
|
5932
5960
|
if (p.previewUrl) {
|
|
@@ -6533,6 +6561,92 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
6533
6561
|
this._skillMenuOpened = true;
|
|
6534
6562
|
this._searchInvocableSkills('');
|
|
6535
6563
|
}
|
|
6564
|
+
/**
|
|
6565
|
+
* The mic button toggles dictation. Speech is transcribed straight into the
|
|
6566
|
+
* composer as editable text — the user still reviews and presses send, so a
|
|
6567
|
+
* misheard word is fixable and nothing is sent by voice alone.
|
|
6568
|
+
*
|
|
6569
|
+
* Recognition runs entirely in the browser's Web Speech API (Chrome/Edge send
|
|
6570
|
+
* audio to Google's service, Safari to Apple's). No audio reaches Kodaris, and
|
|
6571
|
+
* there's no backend call or cost on our side.
|
|
6572
|
+
*/
|
|
6573
|
+
_handleMicClick() {
|
|
6574
|
+
// Second click stops. `stop()` flushes the pending utterance as a final
|
|
6575
|
+
// result, so the last words aren't lost; onend clears the state.
|
|
6576
|
+
if (this._speechRecognition) {
|
|
6577
|
+
this._speechRecognition.stop();
|
|
6578
|
+
return;
|
|
6579
|
+
}
|
|
6580
|
+
// Prefixed in Chrome/Edge/Safari; the unprefixed name is checked first for
|
|
6581
|
+
// whenever they drop the prefix. The mic button isn't rendered without one of
|
|
6582
|
+
// these, so reaching here unsupported means a mid-session change — bail quietly.
|
|
6583
|
+
const SpeechRecognitionCtor = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
6584
|
+
if (!SpeechRecognitionCtor) {
|
|
6585
|
+
return;
|
|
6586
|
+
}
|
|
6587
|
+
const recognition = new SpeechRecognitionCtor();
|
|
6588
|
+
// Keep listening through natural pauses — a chat message is often several
|
|
6589
|
+
// sentences, and the default stops at the first one.
|
|
6590
|
+
recognition.continuous = true;
|
|
6591
|
+
// Show words as they're recognized rather than only at end of utterance.
|
|
6592
|
+
recognition.interimResults = true;
|
|
6593
|
+
// `lang` is deliberately left unset so the recognizer follows the page/OS
|
|
6594
|
+
// language, which is correct for every consumer of this component.
|
|
6595
|
+
// Dictation ADDS to whatever is already typed rather than replacing it, so
|
|
6596
|
+
// the user can type half a message and speak the rest. The separating space
|
|
6597
|
+
// is part of the base so the transcript never runs into the existing word.
|
|
6598
|
+
this._speechBaseValue = this._inputValue;
|
|
6599
|
+
if (this._speechBaseValue && !this._speechBaseValue.endsWith(' ')) {
|
|
6600
|
+
this._speechBaseValue += ' ';
|
|
6601
|
+
}
|
|
6602
|
+
this._speechFinalTranscript = '';
|
|
6603
|
+
recognition.onresult = (e) => {
|
|
6604
|
+
// Everything from resultIndex on is new since the last event: finalized
|
|
6605
|
+
// pieces move to _speechFinalTranscript permanently, while non-final ones
|
|
6606
|
+
// are rebuilt each event because the browser revises its guess as it hears
|
|
6607
|
+
// more. Rebuilding the whole value (base + final + interim) is what keeps
|
|
6608
|
+
// those revisions from stacking up as duplicated text.
|
|
6609
|
+
let interim = '';
|
|
6610
|
+
for (let i = e.resultIndex; i < e.results.length; i++) {
|
|
6611
|
+
if (e.results[i].isFinal) {
|
|
6612
|
+
this._speechFinalTranscript += e.results[i][0].transcript;
|
|
6613
|
+
}
|
|
6614
|
+
else {
|
|
6615
|
+
interim += e.results[i][0].transcript;
|
|
6616
|
+
}
|
|
6617
|
+
}
|
|
6618
|
+
this._inputValue = this._speechBaseValue + this._speechFinalTranscript + interim;
|
|
6619
|
+
this._saveState();
|
|
6620
|
+
// Grow the textarea as dictated text wraps — the same clamp the typing path
|
|
6621
|
+
// uses, since @input doesn't fire for programmatic value changes.
|
|
6622
|
+
if (this._inputEl) {
|
|
6623
|
+
this._inputEl.style.height = 'auto';
|
|
6624
|
+
this._inputEl.style.height = Math.min(this._inputEl.scrollHeight, 120) + 'px';
|
|
6625
|
+
}
|
|
6626
|
+
};
|
|
6627
|
+
recognition.onerror = (e) => {
|
|
6628
|
+
// Denied mic permission is the one error worth explaining — the user has to
|
|
6629
|
+
// fix it in browser settings, and the button would otherwise look broken.
|
|
6630
|
+
if (e.error === 'not-allowed' || e.error === 'service-not-allowed') {
|
|
6631
|
+
this._error = 'Microphone access is blocked. Allow it in your browser settings to use voice input.';
|
|
6632
|
+
return;
|
|
6633
|
+
}
|
|
6634
|
+
// `no-speech` (silence) and `aborted` (user stopped) are normal; anything
|
|
6635
|
+
// else is a transient service hiccup. onend still runs, so the mic resets.
|
|
6636
|
+
};
|
|
6637
|
+
// Fires on stop(), on error, and when the browser ends recognition on its own
|
|
6638
|
+
// (e.g. a long silence) — the single place the live state is torn down.
|
|
6639
|
+
recognition.onend = () => {
|
|
6640
|
+
this._speechRecognition = null;
|
|
6641
|
+
this._isDictating = false;
|
|
6642
|
+
};
|
|
6643
|
+
// start() is what triggers the browser's mic permission prompt.
|
|
6644
|
+
this._speechRecognition = recognition;
|
|
6645
|
+
this._isDictating = true;
|
|
6646
|
+
this._error = '';
|
|
6647
|
+
recognition.start();
|
|
6648
|
+
this._inputEl?.focus();
|
|
6649
|
+
}
|
|
6536
6650
|
/** Insert `/code ` into the composer and focus it, ready for the message. */
|
|
6537
6651
|
_handleSkillSelect(skill) {
|
|
6538
6652
|
// A global skill must be installed before it can be chosen — inserting /code
|
|
@@ -6803,6 +6917,13 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
6803
6917
|
previewUrl: p.previewUrl,
|
|
6804
6918
|
}));
|
|
6805
6919
|
}
|
|
6920
|
+
// The message is on its way, so stop the mic if it's still live — otherwise
|
|
6921
|
+
// it would keep transcribing into the now-cleared composer. abort() rather
|
|
6922
|
+
// than stop() because a trailing pending utterance belongs to the message
|
|
6923
|
+
// just sent; flushing it would drop stray words into the next one.
|
|
6924
|
+
if (this._speechRecognition) {
|
|
6925
|
+
this._speechRecognition.abort();
|
|
6926
|
+
}
|
|
6806
6927
|
this.messages = [...this.messages, userMessage];
|
|
6807
6928
|
this._inputValue = '';
|
|
6808
6929
|
this._saveState();
|
|
@@ -7875,6 +7996,31 @@ let KRChatbot = class KRChatbot extends i$3 {
|
|
|
7875
7996
|
@keydown=${this._handleKeyDown}
|
|
7876
7997
|
@paste=${this._handlePaste}
|
|
7877
7998
|
></textarea>
|
|
7999
|
+
${window.SpeechRecognition || window.webkitSpeechRecognition ? b `
|
|
8000
|
+
<button
|
|
8001
|
+
class=${e({
|
|
8002
|
+
mic: true,
|
|
8003
|
+
'mic--recording': this._isDictating,
|
|
8004
|
+
})}
|
|
8005
|
+
title=${this._isDictating ? 'Stop dictation' : 'Dictate a message'}
|
|
8006
|
+
@click=${this._handleMicClick}
|
|
8007
|
+
>
|
|
8008
|
+
<svg
|
|
8009
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
8010
|
+
viewBox="0 0 24 24"
|
|
8011
|
+
fill="none"
|
|
8012
|
+
stroke="currentColor"
|
|
8013
|
+
stroke-width="1.75"
|
|
8014
|
+
stroke-linecap="round"
|
|
8015
|
+
stroke-linejoin="round"
|
|
8016
|
+
class="mic-icon"
|
|
8017
|
+
>
|
|
8018
|
+
<path d="M12 15a3 3 0 0 0 3-3V6a3 3 0 0 0-6 0v6a3 3 0 0 0 3 3Z"/>
|
|
8019
|
+
<path d="M19 11a7 7 0 0 1-14 0"/>
|
|
8020
|
+
<path d="M12 18v3"/>
|
|
8021
|
+
</svg>
|
|
8022
|
+
</button>
|
|
8023
|
+
` : A}
|
|
7878
8024
|
${this._isStreaming ? b `
|
|
7879
8025
|
<button
|
|
7880
8026
|
class="stop"
|
|
@@ -8718,6 +8864,51 @@ KRChatbot.styles = i$6 `
|
|
|
8718
8864
|
color: #b0b0b0;
|
|
8719
8865
|
}
|
|
8720
8866
|
|
|
8867
|
+
.mic {
|
|
8868
|
+
width: 34px;
|
|
8869
|
+
height: 34px;
|
|
8870
|
+
border: none;
|
|
8871
|
+
background: transparent;
|
|
8872
|
+
color: var(--kr-chatbot-text);
|
|
8873
|
+
border-radius: 50%;
|
|
8874
|
+
cursor: pointer;
|
|
8875
|
+
display: flex;
|
|
8876
|
+
align-items: center;
|
|
8877
|
+
justify-content: center;
|
|
8878
|
+
flex-shrink: 0;
|
|
8879
|
+
transition: background 0.15s, color 0.15s;
|
|
8880
|
+
}
|
|
8881
|
+
|
|
8882
|
+
.mic:hover {
|
|
8883
|
+
background: #f8f9fc;
|
|
8884
|
+
}
|
|
8885
|
+
|
|
8886
|
+
/* Live dictation: red mic on a tinted circle, pulsing so it's clear the
|
|
8887
|
+
microphone is still open even when the user has stopped speaking. */
|
|
8888
|
+
.mic--recording {
|
|
8889
|
+
color: var(--kr-chatbot-error-text);
|
|
8890
|
+
background: var(--kr-chatbot-error-bg);
|
|
8891
|
+
animation: chatbot-mic-pulse 1.6s ease-in-out infinite;
|
|
8892
|
+
}
|
|
8893
|
+
|
|
8894
|
+
.mic--recording:hover {
|
|
8895
|
+
background: var(--kr-chatbot-error-bg);
|
|
8896
|
+
}
|
|
8897
|
+
|
|
8898
|
+
.mic-icon {
|
|
8899
|
+
width: 18px;
|
|
8900
|
+
height: 18px;
|
|
8901
|
+
}
|
|
8902
|
+
|
|
8903
|
+
@keyframes chatbot-mic-pulse {
|
|
8904
|
+
0%, 100% {
|
|
8905
|
+
opacity: 1;
|
|
8906
|
+
}
|
|
8907
|
+
50% {
|
|
8908
|
+
opacity: 0.55;
|
|
8909
|
+
}
|
|
8910
|
+
}
|
|
8911
|
+
|
|
8721
8912
|
.send {
|
|
8722
8913
|
width: 34px;
|
|
8723
8914
|
height: 34px;
|
|
@@ -9345,6 +9536,9 @@ __decorate([
|
|
|
9345
9536
|
__decorate([
|
|
9346
9537
|
r()
|
|
9347
9538
|
], KRChatbot.prototype, "_skillBrowseQuery", void 0);
|
|
9539
|
+
__decorate([
|
|
9540
|
+
r()
|
|
9541
|
+
], KRChatbot.prototype, "_isDictating", void 0);
|
|
9348
9542
|
__decorate([
|
|
9349
9543
|
e$3('.panel')
|
|
9350
9544
|
], KRChatbot.prototype, "_panelEl", void 0);
|