62 lines
2.2 KiB
TypeScript
62 lines
2.2 KiB
TypeScript
// 받아쓰기 결과 라우팅 포트.
|
|
// 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
|
|
}
|