Merge branch 'dev'
CI/CD / deploy (push) Failing after 2m37s
Details
CI/CD / deploy (push) Failing after 2m37s
Details
This commit is contained in:
commit
95585a7f58
|
|
@ -35,8 +35,6 @@ export class RStackMi extends HTMLElement {
|
|||
#shadow: ShadowRoot;
|
||||
#messages: MiMessage[] = [];
|
||||
#abortController: AbortController | null = null;
|
||||
#dictation: SpeechDictation | null = null;
|
||||
#interimText = "";
|
||||
#preferredModel: string = "";
|
||||
#minimized = false;
|
||||
#availableModels: MiModelConfig[] = [];
|
||||
|
|
@ -154,8 +152,7 @@ export class RStackMi extends HTMLElement {
|
|||
<span class="mi-icon">✧</span>
|
||||
<input class="mi-input-bar" id="mi-bar-input" type="text"
|
||||
placeholder="Ask mi anything..." autocomplete="off" />
|
||||
${SpeechDictation.isSupported() ? '<button class="mi-mic-btn" id="mi-mic" title="Voice dictation">🎤</button>' : ''}
|
||||
${SpeechDictation.isSupported() ? '<button class="mi-voice-btn" id="mi-voice-btn" title="miC voice conversation">🎙 miC</button>' : ''}
|
||||
${SpeechDictation.isSupported() ? '<button class="mi-mic-btn" id="mi-voice-btn" title="Voice mode">🎤</button>' : ''}
|
||||
</div>
|
||||
<div class="mi-panel" id="mi-panel">
|
||||
<div class="mi-panel-header">
|
||||
|
|
@ -163,7 +160,6 @@ export class RStackMi extends HTMLElement {
|
|||
<span class="mi-panel-title">mi</span>
|
||||
<select class="mi-model-select" id="mi-model-select" title="Select AI model"></select>
|
||||
<div class="mi-panel-spacer"></div>
|
||||
${SpeechDictation.isSupported() ? '<button class="mi-voice-panel-btn" id="mi-voice-panel-btn" title="miC voice mode">🎙 miC</button>' : ''}
|
||||
<button class="mi-panel-btn" id="mi-minimize" title="Minimize (Escape)">−</button>
|
||||
<button class="mi-panel-btn" id="mi-close" title="Close">×</button>
|
||||
</div>
|
||||
|
|
@ -315,51 +311,11 @@ export class RStackMi extends HTMLElement {
|
|||
}
|
||||
});
|
||||
|
||||
// Voice dictation
|
||||
const micBtn = this.#shadow.getElementById("mi-mic") as HTMLButtonElement | null;
|
||||
if (micBtn) {
|
||||
let baseText = "";
|
||||
this.#dictation = new SpeechDictation({
|
||||
onInterim: (text) => {
|
||||
this.#interimText = text;
|
||||
barInput.value = baseText + (baseText ? " " : "") + text;
|
||||
},
|
||||
onFinal: (text) => {
|
||||
this.#interimText = "";
|
||||
baseText += (baseText ? " " : "") + text;
|
||||
barInput.value = baseText;
|
||||
},
|
||||
onStateChange: (recording) => {
|
||||
micBtn.classList.toggle("recording", recording);
|
||||
if (!recording) {
|
||||
baseText = barInput.value;
|
||||
this.#interimText = "";
|
||||
}
|
||||
},
|
||||
onError: (err) => console.warn("MI dictation:", err),
|
||||
});
|
||||
|
||||
micBtn.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
if (this.#voiceMode) return; // Bar mic disabled during voice mode
|
||||
if (!this.#dictation!.isRecording) {
|
||||
baseText = barInput.value;
|
||||
}
|
||||
this.#dictation!.toggle();
|
||||
barInput.focus();
|
||||
});
|
||||
}
|
||||
|
||||
// Voice mode buttons
|
||||
const voiceBarBtn = this.#shadow.getElementById("mi-voice-btn");
|
||||
const voicePanelBtn = this.#shadow.getElementById("mi-voice-panel-btn");
|
||||
// Voice mode — single mic toggle (STT input → auto-submit → TTS response)
|
||||
const voiceBtn = this.#shadow.getElementById("mi-voice-btn");
|
||||
const voiceStopBtn = this.#shadow.getElementById("mi-voice-stop");
|
||||
|
||||
voiceBarBtn?.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
this.#toggleVoiceMode();
|
||||
});
|
||||
voicePanelBtn?.addEventListener("click", (e) => {
|
||||
voiceBtn?.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
this.#toggleVoiceMode();
|
||||
});
|
||||
|
|
@ -487,9 +443,6 @@ export class RStackMi extends HTMLElement {
|
|||
}
|
||||
|
||||
#activateVoiceMode() {
|
||||
// Stop existing bar dictation if recording
|
||||
if (this.#dictation?.isRecording) this.#dictation.stop();
|
||||
|
||||
this.#voiceMode = true;
|
||||
this.#voiceAccumulated = "";
|
||||
|
||||
|
|
@ -558,8 +511,7 @@ export class RStackMi extends HTMLElement {
|
|||
|
||||
const strip = this.#shadow.getElementById("mi-voice-strip");
|
||||
const label = this.#shadow.getElementById("mi-voice-label");
|
||||
const barBtn = this.#shadow.getElementById("mi-voice-btn");
|
||||
const panelBtn = this.#shadow.getElementById("mi-voice-panel-btn");
|
||||
const btn = this.#shadow.getElementById("mi-voice-btn");
|
||||
|
||||
// Update strip
|
||||
if (strip) {
|
||||
|
|
@ -576,9 +528,8 @@ export class RStackMi extends HTMLElement {
|
|||
label.textContent = labels[state];
|
||||
}
|
||||
|
||||
// Update buttons
|
||||
for (const btn of [barBtn, panelBtn]) {
|
||||
if (!btn) continue;
|
||||
// Update button
|
||||
if (btn) {
|
||||
btn.classList.remove("v-listening", "v-thinking", "v-speaking", "v-active");
|
||||
if (this.#voiceMode) {
|
||||
btn.classList.add("v-active");
|
||||
|
|
@ -646,10 +597,10 @@ export class RStackMi extends HTMLElement {
|
|||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
|
||||
// Truncate to ~4 sentences for snappy TTS
|
||||
// Truncate to ~2 sentences for succinct TTS
|
||||
const sentences = stripped.match(/[^.!?]+[.!?]+/g) || [stripped];
|
||||
if (sentences.length > 4) {
|
||||
stripped = sentences.slice(0, 4).join(" ").trim();
|
||||
if (sentences.length > 2) {
|
||||
stripped = sentences.slice(0, 2).join(" ").trim();
|
||||
}
|
||||
return stripped;
|
||||
}
|
||||
|
|
@ -1065,18 +1016,20 @@ const STYLES = `
|
|||
.mi-input-bar::placeholder { color: var(--rs-text-muted); }
|
||||
|
||||
.mi-mic-btn {
|
||||
background: none; border: none; cursor: pointer; padding: 2px 4px;
|
||||
font-size: 0.85rem; border-radius: 6px; transition: all 0.2s;
|
||||
flex-shrink: 0; line-height: 1;
|
||||
background: none; border: none; cursor: pointer; padding: 2px 6px;
|
||||
font-size: 0.9rem; border-radius: 6px; transition: all 0.2s;
|
||||
flex-shrink: 0; line-height: 1; opacity: 0.7;
|
||||
}
|
||||
.mi-mic-btn:hover, .mi-mic-btn:active { background: var(--rs-bg-hover); }
|
||||
.mi-mic-btn.recording {
|
||||
animation: micPulse 1.5s infinite;
|
||||
filter: saturate(2) brightness(1.1);
|
||||
.mi-mic-btn:hover, .mi-mic-btn:active { background: var(--rs-bg-hover); opacity: 1; }
|
||||
.mi-mic-btn.v-active { opacity: 1; }
|
||||
.mi-mic-btn.v-listening {
|
||||
animation: voicePulseRed 1.5s infinite;
|
||||
}
|
||||
@keyframes micPulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.15); }
|
||||
.mi-mic-btn.v-thinking {
|
||||
animation: voiceSpinAmber 2s linear infinite;
|
||||
}
|
||||
.mi-mic-btn.v-speaking {
|
||||
animation: voicePulseCyan 2s infinite;
|
||||
}
|
||||
|
||||
/* ── Rich panel ── */
|
||||
|
|
@ -1313,24 +1266,6 @@ const STYLES = `
|
|||
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
/* ── Voice mode buttons ── */
|
||||
.mi-voice-btn, .mi-voice-panel-btn {
|
||||
background: none; border: none; cursor: pointer; padding: 2px 4px;
|
||||
font-size: 0.85rem; border-radius: 6px; transition: all 0.2s;
|
||||
flex-shrink: 0; line-height: 1; opacity: 0.7;
|
||||
}
|
||||
.mi-voice-btn:hover, .mi-voice-panel-btn:hover { background: var(--rs-bg-hover); opacity: 1; }
|
||||
.mi-voice-btn.v-active, .mi-voice-panel-btn.v-active { opacity: 1; }
|
||||
.mi-voice-btn.v-listening, .mi-voice-panel-btn.v-listening {
|
||||
animation: voicePulseRed 1.5s infinite;
|
||||
}
|
||||
.mi-voice-btn.v-thinking, .mi-voice-panel-btn.v-thinking {
|
||||
animation: voiceSpinAmber 2s linear infinite;
|
||||
}
|
||||
.mi-voice-btn.v-speaking, .mi-voice-panel-btn.v-speaking {
|
||||
animation: voicePulseCyan 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes voicePulseRed {
|
||||
0%, 100% { filter: drop-shadow(0 0 2px transparent); }
|
||||
50% { filter: drop-shadow(0 0 6px rgba(239,68,68,0.7)); }
|
||||
|
|
|
|||
Loading…
Reference in New Issue