调整气泡累积逻辑

main
Bifang 2026-09-30 13:34:57 +08:00
parent b9fa3e4d5c
commit fb710fadff
2 changed files with 39 additions and 19 deletions

View File

@ -2021,14 +2021,23 @@ class Qwen3ASRService:
self,
ctx: ConnectionContext,
text: str,
*,
native_stream_snapshot: bool = False,
) -> str:
"""Expose only newly stabilized prefixes; the final event carries revisions."""
candidate = self._partial_display_text(ctx, text)
"""Publish a growing preview while leaving Qwen's revisable tail for finalization."""
if native_stream_snapshot:
# Native Qwen text is a rolling snapshot. Exposing its unstable tail
# can make the next correction fail the append-only check and freeze
# the bubble at its first preview. Only publish the confirmed prefix.
candidate = ctx.stable_partial_prefix.strip()
else:
candidate = self._partial_display_text(ctx, text)
previous = ctx.partial_preview_text
if not previous or candidate.startswith(previous):
if candidate and (not previous or candidate.startswith(previous)):
ctx.partial_preview_text = candidate
# Qwen may rewrite its rolling snapshot. Keep the already shown prefix
# fixed until finalization so the browser never retracts earlier words.
# If Qwen revises already published text, keep the preview fixed. The
# final segment event will replace it with the model's finalized result.
return ctx.partial_preview_text
def _is_suspicious_segment_text(
@ -3150,6 +3159,7 @@ class Qwen3ASRService:
partial_display = self._append_only_partial_preview(
ctx,
visible_observed,
native_stream_snapshot=has_native_stream_snapshot,
)
partial_display = self._clip_partial_text(partial_display, ctx)
if (

View File

@ -4211,24 +4211,34 @@ ${transcript}
) {
const safeText = (text || "").trim();
if (!safeText) return;
if (partial && state.liveTranscript.length) {
const last = state.liveTranscript[state.liveTranscript.length - 1];
if (last.partial && (segmentIndex === null || last.segmentIndex === segmentIndex)) {
last.label = label;
last.text = safeText;
last.speaker = speaker;
last.speakerPending = speakerPending;
last.segmentIndex = segmentIndex;
last.speakerKey = speakerKey || last.speakerKey || (segmentIndex === null ? "unknown" : `segment-${segmentIndex}`);
last.startTimeMs = startTimeMs;
last.endTimeMs = endTimeMs;
if (partial) {
const existingPartialIndex = state.liveTranscript.findIndex(
(item) => item.partial && (segmentIndex === null || item.segmentIndex === segmentIndex),
);
if (existingPartialIndex >= 0) {
const existingPartial = state.liveTranscript[existingPartialIndex];
existingPartial.label = label;
existingPartial.text = safeText;
existingPartial.speaker = speaker;
existingPartial.speakerPending = speakerPending;
existingPartial.segmentIndex = segmentIndex;
existingPartial.speakerKey = speakerKey || existingPartial.speakerKey || (segmentIndex === null ? "unknown" : `segment-${segmentIndex}`);
existingPartial.startTimeMs = startTimeMs;
existingPartial.endTimeMs = endTimeMs;
renderLiveTranscript();
return;
}
}
if (!partial && state.liveTranscript.length) {
const last = state.liveTranscript[state.liveTranscript.length - 1];
if (last.partial) state.liveTranscript.pop();
if (!partial) {
// A late speaker update for an older segment must not erase the live preview.
const partialIndex = state.liveTranscript.findIndex(
(item) => item.partial && (
segmentIndex === null
? item.segmentIndex === null
: item.segmentIndex === segmentIndex
),
);
if (partialIndex >= 0) state.liveTranscript.splice(partialIndex, 1);
}
if (!partial && segmentIndex !== null) {