fix[examples/transcript]: fix transitional deltas (#275)
* fix transcript deltas * Update mcp-app.css
This commit is contained in:
parent
c73d36343b
commit
b94982cc35
2 changed files with 68 additions and 9 deletions
|
|
@ -66,9 +66,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.transcript-entry.interim {
|
.transcript-entry.interim {
|
||||||
opacity: 0.6;
|
|
||||||
font-style: italic;
|
|
||||||
border-left: 3px solid var(--color-primary);
|
border-left: 3px solid var(--color-primary);
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unstable/interim text within entry - dimmed and italic */
|
||||||
|
.transcript-entry.interim .interim-delta {
|
||||||
|
opacity: 0.5;
|
||||||
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.transcript-entry.sent {
|
.transcript-entry.sent {
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,8 @@ let animationFrame: number | null = null;
|
||||||
|
|
||||||
// Speech Recognition
|
// Speech Recognition
|
||||||
let recognition: SpeechRecognition | null = null;
|
let recognition: SpeechRecognition | null = null;
|
||||||
|
let committedResultCount = 0; // How many results have been committed as entries
|
||||||
|
let interimTranscript = ""; // Current interim (unstable) text
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// MCP App Setup
|
// MCP App Setup
|
||||||
|
|
@ -155,22 +157,44 @@ function startSpeechRecognition(): boolean {
|
||||||
|
|
||||||
recognition.onstart = () => {
|
recognition.onstart = () => {
|
||||||
log.info("Speech recognition started");
|
log.info("Speech recognition started");
|
||||||
|
committedResultCount = 0;
|
||||||
|
interimTranscript = "";
|
||||||
};
|
};
|
||||||
|
|
||||||
recognition.onresult = (event) => {
|
recognition.onresult = (event) => {
|
||||||
const e = event as SpeechRecognitionEvent;
|
const e = event as SpeechRecognitionEvent;
|
||||||
for (let i = e.resultIndex; i < e.results.length; i++) {
|
|
||||||
|
// Process results, committing newly-finalized ones
|
||||||
|
interimTranscript = "";
|
||||||
|
|
||||||
|
for (let i = 0; i < e.results.length; i++) {
|
||||||
const result = e.results[i];
|
const result = e.results[i];
|
||||||
const transcript = result[0].transcript;
|
const transcript = result[0].transcript;
|
||||||
|
|
||||||
if (result.isFinal) {
|
if (result.isFinal) {
|
||||||
addTranscriptEntry(transcript, true);
|
// Only commit if this result hasn't been committed yet
|
||||||
updateSendButton();
|
if (i >= committedResultCount) {
|
||||||
updateModelContext();
|
const text = transcript.trim();
|
||||||
|
if (text) {
|
||||||
|
clearInterimTranscript();
|
||||||
|
addTranscriptEntry(text, true);
|
||||||
|
updateSendButton();
|
||||||
|
updateModelContext();
|
||||||
|
}
|
||||||
|
committedResultCount = i + 1;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
updateInterimTranscript(transcript);
|
// Accumulate all interim text
|
||||||
|
interimTranscript += transcript;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show interim text if any
|
||||||
|
if (interimTranscript.trim()) {
|
||||||
|
updateInterimTranscript("", interimTranscript);
|
||||||
|
} else {
|
||||||
|
clearInterimTranscript();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
recognition.onerror = (event) => {
|
recognition.onerror = (event) => {
|
||||||
|
|
@ -204,6 +228,9 @@ function startSpeechRecognition(): boolean {
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopSpeechRecognition() {
|
function stopSpeechRecognition() {
|
||||||
|
// Commit any accumulated final transcript before stopping
|
||||||
|
commitFinalTranscript();
|
||||||
|
|
||||||
if (recognition) {
|
if (recognition) {
|
||||||
try {
|
try {
|
||||||
recognition.stop();
|
recognition.stop();
|
||||||
|
|
@ -214,6 +241,19 @@ function stopSpeechRecognition() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function commitFinalTranscript() {
|
||||||
|
// Commit any remaining interim text when stopping
|
||||||
|
const textToCommit = interimTranscript.trim();
|
||||||
|
if (textToCommit) {
|
||||||
|
clearInterimTranscript();
|
||||||
|
addTranscriptEntry(textToCommit, true);
|
||||||
|
updateSendButton();
|
||||||
|
updateModelContext();
|
||||||
|
}
|
||||||
|
committedResultCount = 0;
|
||||||
|
interimTranscript = "";
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// UI Helpers
|
// UI Helpers
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -271,7 +311,7 @@ function addTranscriptEntry(text: string, isFinal: boolean) {
|
||||||
transcriptEl.appendChild(entry);
|
transcriptEl.appendChild(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateInterimTranscript(text: string) {
|
function updateInterimTranscript(finalText: string, interimText: string) {
|
||||||
clearTranscriptPlaceholder();
|
clearTranscriptPlaceholder();
|
||||||
|
|
||||||
let interim = transcriptEl.querySelector(
|
let interim = transcriptEl.querySelector(
|
||||||
|
|
@ -284,7 +324,21 @@ function updateInterimTranscript(text: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const timestamp = new Date().toLocaleTimeString();
|
const timestamp = new Date().toLocaleTimeString();
|
||||||
interim.innerHTML = `<div class="timestamp">${timestamp}</div>${escapeHtml(text)}`;
|
|
||||||
|
// Show final text (stable) in normal style, interim text (unstable) in delta style
|
||||||
|
const finalHtml = escapeHtml(finalText);
|
||||||
|
const interimHtml = interimText
|
||||||
|
? `<span class="interim-delta">${escapeHtml(interimText)}</span>`
|
||||||
|
: "";
|
||||||
|
|
||||||
|
interim.innerHTML = `<div class="timestamp">${timestamp}</div>${finalHtml}${interimHtml}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearInterimTranscript() {
|
||||||
|
const interim = transcriptEl.querySelector(".transcript-entry.interim");
|
||||||
|
if (interim) {
|
||||||
|
interim.remove();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(text: string): string {
|
function escapeHtml(text: string): string {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue