fix(templates): route dictation results into active template sessions
This commit is contained in:
parent
11bf995409
commit
115e8488ac
7 changed files with 386 additions and 15 deletions
62
apps/desktop/src/main/voice-text-routing.ts
Normal file
62
apps/desktop/src/main/voice-text-routing.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// 받아쓰기 결과 라우팅 포트.
|
||||
// VoiceMode 세션이 끝나면 전사 결과를 여기로 넘긴다. 등록된 소비자(sink)가 결과를 가져가면
|
||||
// (예: 진행 중인 받아쓰기 템플릿 세션의 현재 필드) 그 세션은 "소비됨"으로 기록된다.
|
||||
// 일반 삽입 경로는 isVoiceSessionClaimed(sessionId)로 소비 여부를 확인하고 삽입을 건너뛸 수 있다.
|
||||
// 이 모듈은 소비자 구현을 모른다 — 소비자가 스스로 등록한다(의존성 역전).
|
||||
|
||||
import { getLogger } from './services/LoggerService'
|
||||
|
||||
const logger = getLogger('voice-text-routing')
|
||||
|
||||
export interface VoiceTextOffer {
|
||||
sessionId: string
|
||||
text: string
|
||||
}
|
||||
|
||||
/** 결과를 가져가면 true. 가져가지 않으면 false — 다음 소비자에게 넘어간다. */
|
||||
export type VoiceTextSink = (offer: VoiceTextOffer) => boolean
|
||||
|
||||
/** 소비 기록 보존 개수 — 최근 세션만 기억하면 된다. */
|
||||
const MAX_CLAIMED_SESSIONS = 32
|
||||
|
||||
const sinks = new Set<VoiceTextSink>()
|
||||
const claimedSessions: string[] = []
|
||||
|
||||
export function registerVoiceTextSink(sink: VoiceTextSink): () => void {
|
||||
sinks.add(sink)
|
||||
return () => {
|
||||
sinks.delete(sink)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 완료된 받아쓰기 결과를 소비자에게 제안한다. 같은 세션을 두 번 넘겨도 한 번만 소비된다.
|
||||
* @returns 소비자가 가져갔으면 true
|
||||
*/
|
||||
export function routeCompletedVoiceText(sessionId: string, text: string): boolean {
|
||||
if (claimedSessions.includes(sessionId)) return true
|
||||
for (const sink of [...sinks]) {
|
||||
let claimed = false
|
||||
try {
|
||||
claimed = sink({ sessionId, text })
|
||||
} catch (err) {
|
||||
logger.warn(`Voice text sink failed: ${err instanceof Error ? err.message : String(err)}`)
|
||||
}
|
||||
if (claimed) {
|
||||
claimedSessions.push(sessionId)
|
||||
if (claimedSessions.length > MAX_CLAIMED_SESSIONS) claimedSessions.shift()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/** 이 세션의 결과를 소비자가 가져갔는지 — 일반 삽입을 건너뛸지 판단할 때 쓴다. */
|
||||
export function isVoiceSessionClaimed(sessionId: string): boolean {
|
||||
return claimedSessions.includes(sessionId)
|
||||
}
|
||||
|
||||
export function resetVoiceTextRoutingForTests(): void {
|
||||
sinks.clear()
|
||||
claimedSessions.length = 0
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue