@lovelace_lol/polymer 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +226 -0
- package/dist/index.d.ts +2603 -0
- package/dist/index.js +1031 -0
- package/dist/test.cjs +1519 -0
- package/dist/worker/agency-worker.js +972 -0
- package/dist/worker/manifest.edn +1 -0
- package/package.json +57 -0
- package/shadow-cljs.edn +173 -0
- package/src/polymer/animation/agency.cljs +84 -0
- package/src/polymer/animation/domain.cljs +110 -0
- package/src/polymer/animation/planner.cljs +88 -0
- package/src/polymer/animation/runtime.cljs +137 -0
- package/src/polymer/animation/scheduler.cljs +178 -0
- package/src/polymer/animation/service.cljs +981 -0
- package/src/polymer/animation/state.cljs +112 -0
- package/src/polymer/blink/agency.cljs +131 -0
- package/src/polymer/blink/planner.cljs +115 -0
- package/src/polymer/blink/scheduler.cljs +90 -0
- package/src/polymer/blink/snippet.cljs +50 -0
- package/src/polymer/blink/state.cljs +102 -0
- package/src/polymer/camera_context/agency.cljs +132 -0
- package/src/polymer/camera_context/domain.cljs +186 -0
- package/src/polymer/camera_context/planner.cljs +50 -0
- package/src/polymer/camera_context/scheduler.cljs +113 -0
- package/src/polymer/camera_context/state.cljs +121 -0
- package/src/polymer/character.cljs +471 -0
- package/src/polymer/conversation/agency.cljs +83 -0
- package/src/polymer/conversation/domain.cljs +35 -0
- package/src/polymer/conversation/effector.cljs +130 -0
- package/src/polymer/conversation/planner.cljs +178 -0
- package/src/polymer/conversation/scheduler.cljs +202 -0
- package/src/polymer/conversation/service.cljs +329 -0
- package/src/polymer/conversation/state.cljs +186 -0
- package/src/polymer/core.cljs +172 -0
- package/src/polymer/eye_head/agency.cljs +75 -0
- package/src/polymer/eye_head/domain.cljs +133 -0
- package/src/polymer/eye_head/goap.cljs +106 -0
- package/src/polymer/eye_head/scheduler.cljs +176 -0
- package/src/polymer/eye_head/service.cljs +259 -0
- package/src/polymer/eye_head/snippet.cljs +92 -0
- package/src/polymer/eye_head/state.cljs +167 -0
- package/src/polymer/gaze/agency.cljs +85 -0
- package/src/polymer/gaze/domain.cljs +211 -0
- package/src/polymer/gaze/goap.cljs +208 -0
- package/src/polymer/gaze/scheduler.cljs +184 -0
- package/src/polymer/gaze/state.cljs +202 -0
- package/src/polymer/gesture/agency.cljs +185 -0
- package/src/polymer/gesture/goap.cljs +461 -0
- package/src/polymer/gesture/scheduler.cljs +122 -0
- package/src/polymer/gesture/snippet.cljs +198 -0
- package/src/polymer/gesture/state.cljs +273 -0
- package/src/polymer/lipsync/agency.cljs +593 -0
- package/src/polymer/lipsync/articulation/jaw.cljs +258 -0
- package/src/polymer/lipsync/articulation/snippet.cljs +605 -0
- package/src/polymer/lipsync/articulation/tongue.cljs +418 -0
- package/src/polymer/lipsync/articulation/visemes.cljs +429 -0
- package/src/polymer/lipsync/goap.cljs +132 -0
- package/src/polymer/lipsync/pronunciation/effector.cljs +51 -0
- package/src/polymer/lipsync/scheduler.cljs +302 -0
- package/src/polymer/lipsync/state.cljs +202 -0
- package/src/polymer/nlp/phonemes.cljs +164 -0
- package/src/polymer/prosodic/agency.cljs +196 -0
- package/src/polymer/prosodic/goap.cljs +122 -0
- package/src/polymer/prosodic/scheduler.cljs +99 -0
- package/src/polymer/prosodic/snippet.cljs +106 -0
- package/src/polymer/prosodic/state.cljs +139 -0
- package/src/polymer/runtime/embody.cljs +117 -0
- package/src/polymer/runtime/embody_wasm.cljs +177 -0
- package/src/polymer/runtime/rust_embody_host.cljs +645 -0
- package/src/polymer/society.cljs +239 -0
- package/src/polymer/stream.cljs +130 -0
- package/src/polymer/transcription/agency.cljs +69 -0
- package/src/polymer/transcription/domain.cljs +68 -0
- package/src/polymer/transcription/effector.cljs +611 -0
- package/src/polymer/transcription/planner.cljs +203 -0
- package/src/polymer/transcription/scheduler.cljs +225 -0
- package/src/polymer/transcription/service.cljs +209 -0
- package/src/polymer/transcription/state.cljs +239 -0
- package/src/polymer/tts/agency.cljs +503 -0
- package/src/polymer/tts/azure.cljs +290 -0
- package/src/polymer/tts/planner.cljs +178 -0
- package/src/polymer/tts/runtime.cljs +624 -0
- package/src/polymer/tts/scheduler.cljs +162 -0
- package/src/polymer/tts/state.cljs +191 -0
- package/src/polymer/worker.cljs +70 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
(ns polymer.transcription.planner
|
|
2
|
+
(:require [polymer.transcription.domain :as domain]
|
|
3
|
+
[polymer.transcription.state :as state]))
|
|
4
|
+
|
|
5
|
+
;; The planner turns recognition commands and provider callbacks into explicit
|
|
6
|
+
;; actions. Provider APIs are not called here; the scheduler passes accepted
|
|
7
|
+
;; provider work to the agency's private effector.
|
|
8
|
+
|
|
9
|
+
(def supported-command-types
|
|
10
|
+
#{"configure"
|
|
11
|
+
"start"
|
|
12
|
+
"stop"
|
|
13
|
+
"cancel"
|
|
14
|
+
"reset"
|
|
15
|
+
"providerStarted"
|
|
16
|
+
"providerEnded"
|
|
17
|
+
"providerPartial"
|
|
18
|
+
"partialTranscript"
|
|
19
|
+
"providerFinal"
|
|
20
|
+
"finalTranscript"
|
|
21
|
+
"providerError"
|
|
22
|
+
"providerAudioInterruption"
|
|
23
|
+
"tts.status"
|
|
24
|
+
"ttsSpeechStarted"
|
|
25
|
+
"ttsSpeechStopped"
|
|
26
|
+
"ttsSpeechEnded"
|
|
27
|
+
"agentSpeechPrepared"
|
|
28
|
+
"agentSpeechObserved"
|
|
29
|
+
"agentSpeechWord"
|
|
30
|
+
"agentSpeechEnded"})
|
|
31
|
+
|
|
32
|
+
(defn confidence
|
|
33
|
+
[command]
|
|
34
|
+
(state/clamp 0 1 (state/number-or (:confidence command) 1)))
|
|
35
|
+
|
|
36
|
+
(defn allowed-confidence?
|
|
37
|
+
[command current-state]
|
|
38
|
+
(>= (confidence command) (get-in current-state [:config :minConfidence])))
|
|
39
|
+
|
|
40
|
+
(defn retry-available?
|
|
41
|
+
[current-state]
|
|
42
|
+
(< (:retryCount current-state) (get-in current-state [:config :maxRetries])))
|
|
43
|
+
|
|
44
|
+
(defn failure-step
|
|
45
|
+
[command current-state]
|
|
46
|
+
(cond
|
|
47
|
+
(not (contains? supported-command-types (:type command)))
|
|
48
|
+
{:op "fail" :reason "unsupported-command" :commandType (:type command)}
|
|
49
|
+
|
|
50
|
+
(and (#{"providerPartial" "partialTranscript" "providerFinal" "finalTranscript"} (:type command))
|
|
51
|
+
(not (domain/transcript-text command)))
|
|
52
|
+
{:op "ignore" :reason "empty-transcript" :commandType (:type command)}
|
|
53
|
+
|
|
54
|
+
(and (#{"providerPartial" "partialTranscript" "providerFinal" "finalTranscript"} (:type command))
|
|
55
|
+
(not (allowed-confidence? command current-state)))
|
|
56
|
+
{:op "ignore" :reason "low-confidence" :commandType (:type command)}
|
|
57
|
+
|
|
58
|
+
(and (get-in current-state [:config :agentFilteringEnabled])
|
|
59
|
+
(:agentSpeaking current-state)
|
|
60
|
+
(#{"providerPartial" "partialTranscript" "providerFinal" "finalTranscript"} (:type command))
|
|
61
|
+
(or (domain/agent-source? command)
|
|
62
|
+
(domain/agent-echo? command current-state)))
|
|
63
|
+
{:op "ignore" :reason "agent-speech" :commandType (:type command)}))
|
|
64
|
+
|
|
65
|
+
(def provider-callback-types
|
|
66
|
+
#{"providerStarted" "providerEnded" "providerPartial" "providerFinal" "providerError"})
|
|
67
|
+
|
|
68
|
+
(defn stale-provider-callback?
|
|
69
|
+
[command current-state]
|
|
70
|
+
(and (contains? provider-callback-types (:type command))
|
|
71
|
+
(:sessionId command)
|
|
72
|
+
(or (not (:active current-state))
|
|
73
|
+
(not= (:sessionId command) (:sessionId current-state)))))
|
|
74
|
+
|
|
75
|
+
(defn tts-status-fact
|
|
76
|
+
[command now-ms]
|
|
77
|
+
(let [status (or (:status command)
|
|
78
|
+
(:stateStatus command)
|
|
79
|
+
(get-in command [:state :status])
|
|
80
|
+
(case (:type command)
|
|
81
|
+
"ttsSpeechStarted" "speaking"
|
|
82
|
+
("ttsSpeechStopped" "ttsSpeechEnded") "idle"
|
|
83
|
+
"idle"))]
|
|
84
|
+
{:type "transcription.ttsStatus"
|
|
85
|
+
:agency "transcription"
|
|
86
|
+
:sourceAgency "tts"
|
|
87
|
+
:status status
|
|
88
|
+
:speaking (or (:speaking command)
|
|
89
|
+
(= "speaking" status)
|
|
90
|
+
(= "ttsSpeechStarted" (:type command)))
|
|
91
|
+
:phrase (or (:phrase command) (:text command))
|
|
92
|
+
:words (:words command)
|
|
93
|
+
:ttsEventType (:type command)
|
|
94
|
+
:at now-ms}))
|
|
95
|
+
|
|
96
|
+
(defn command-steps
|
|
97
|
+
[command current-state now-ms]
|
|
98
|
+
(if (stale-provider-callback? command current-state)
|
|
99
|
+
[{:op "ignore"
|
|
100
|
+
:reason "stale-provider-callback"
|
|
101
|
+
:commandType (:type command)}]
|
|
102
|
+
(if-let [failure (failure-step command current-state)]
|
|
103
|
+
[failure]
|
|
104
|
+
(case (:type command)
|
|
105
|
+
"configure" [{:op "apply-config"}
|
|
106
|
+
{:op "configure-provider"}
|
|
107
|
+
{:op "publish-status" :status (:status current-state)}]
|
|
108
|
+
"start" (if (:active current-state)
|
|
109
|
+
[{:op "ignore" :reason "already-active" :commandType "start"}]
|
|
110
|
+
[{:op "start-session"}
|
|
111
|
+
{:op "request-provider-start"}])
|
|
112
|
+
("stop" "cancel") [{:op "request-provider-stop"
|
|
113
|
+
:method (if (= "cancel" (:type command)) "abort" "stop")
|
|
114
|
+
:reason (or (:reason command) (:type command))}
|
|
115
|
+
{:op "stop-session"
|
|
116
|
+
:reason (or (:reason command) (:type command))}]
|
|
117
|
+
"reset" [{:op "request-provider-stop"
|
|
118
|
+
:method "abort"
|
|
119
|
+
:reason "reset"}
|
|
120
|
+
{:op "reset-state"}]
|
|
121
|
+
"providerStarted" [{:op "record-provider-started"}]
|
|
122
|
+
"providerEnded" (if (= "retrying" (:status current-state))
|
|
123
|
+
[{:op "ignore"
|
|
124
|
+
:reason "provider-ended-during-retry"
|
|
125
|
+
:commandType "providerEnded"}]
|
|
126
|
+
[{:op "stop-session" :reason "provider-ended"}])
|
|
127
|
+
("providerPartial" "partialTranscript")
|
|
128
|
+
(let [interrupt? (and (:agentSpeaking current-state)
|
|
129
|
+
(get-in current-state [:config :interruptDetectionEnabled])
|
|
130
|
+
(not (domain/agent-source? command)))]
|
|
131
|
+
(cond-> []
|
|
132
|
+
interrupt?
|
|
133
|
+
(conj {:op "publish-interruption"
|
|
134
|
+
:text (domain/transcript-text command)
|
|
135
|
+
:confidence (confidence command)
|
|
136
|
+
:source (or (:source command) "provider")})
|
|
137
|
+
|
|
138
|
+
true
|
|
139
|
+
(conj {:op "publish-partial"
|
|
140
|
+
:text (domain/transcript-text command)
|
|
141
|
+
:confidence (confidence command)
|
|
142
|
+
:source (or (:source command) "provider")})))
|
|
143
|
+
("providerFinal" "finalTranscript")
|
|
144
|
+
(let [interrupt? (and (:agentSpeaking current-state)
|
|
145
|
+
(get-in current-state [:config :interruptDetectionEnabled])
|
|
146
|
+
(not (domain/agent-source? command)))]
|
|
147
|
+
(cond-> []
|
|
148
|
+
interrupt?
|
|
149
|
+
(conj {:op "publish-interruption"
|
|
150
|
+
:text (domain/transcript-text command)
|
|
151
|
+
:confidence (confidence command)
|
|
152
|
+
:source (or (:source command) "provider")})
|
|
153
|
+
|
|
154
|
+
true
|
|
155
|
+
(conj {:op "publish-final"
|
|
156
|
+
:text (domain/transcript-text command)
|
|
157
|
+
:confidence (confidence command)
|
|
158
|
+
:source (or (:source command) "provider")})))
|
|
159
|
+
"providerError"
|
|
160
|
+
(let [message (or (:message command) (:error command) "provider-error")
|
|
161
|
+
retry? (and (:active current-state)
|
|
162
|
+
(:continuous (:config current-state))
|
|
163
|
+
(retry-available? current-state))]
|
|
164
|
+
(cond-> [{:op "record-error"
|
|
165
|
+
:message message
|
|
166
|
+
:retry retry?}]
|
|
167
|
+
retry? (conj {:op "request-provider-retry"
|
|
168
|
+
:message message
|
|
169
|
+
:retryAfterMs (get-in current-state [:config :retryDelayMs])})
|
|
170
|
+
(not retry?) (conj {:op "stop-session"
|
|
171
|
+
:reason "provider-error"})))
|
|
172
|
+
"providerAudioInterruption"
|
|
173
|
+
[{:op "publish-audio-interruption"
|
|
174
|
+
:timestamp (:timestamp command)
|
|
175
|
+
:microphoneLevel (:microphoneLevel command)
|
|
176
|
+
:referenceLevel (:referenceLevel command)
|
|
177
|
+
:requiredLevel (:requiredLevel command)}]
|
|
178
|
+
("tts.status" "ttsSpeechStarted" "ttsSpeechStopped" "ttsSpeechEnded")
|
|
179
|
+
[{:op "record-tts-status"
|
|
180
|
+
:fact (tts-status-fact command now-ms)}]
|
|
181
|
+
"agentSpeechPrepared" [{:op "prepare-agent-speech"
|
|
182
|
+
:text (:text command)
|
|
183
|
+
:append false}]
|
|
184
|
+
"agentSpeechObserved" [{:op "prepare-agent-speech"
|
|
185
|
+
:text (:text command)
|
|
186
|
+
:append true}]
|
|
187
|
+
"agentSpeechWord" [{:op "record-agent-word"
|
|
188
|
+
:word (:word command)}]
|
|
189
|
+
"agentSpeechEnded" [{:op "record-tts-status"
|
|
190
|
+
:fact (tts-status-fact
|
|
191
|
+
{:type "ttsSpeechEnded"
|
|
192
|
+
:status "idle"
|
|
193
|
+
:speaking false}
|
|
194
|
+
now-ms)}]))))
|
|
195
|
+
|
|
196
|
+
(defn plan-command
|
|
197
|
+
[command current-state now-ms]
|
|
198
|
+
(let [steps (command-steps command current-state now-ms)]
|
|
199
|
+
{:agency "transcription"
|
|
200
|
+
:commandType (:type command)
|
|
201
|
+
:createdAt now-ms
|
|
202
|
+
:ok (not (#{"fail"} (:op (first steps))))
|
|
203
|
+
:steps steps}))
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
(ns polymer.transcription.scheduler
|
|
2
|
+
(:require [polymer.transcription.state :as state]))
|
|
3
|
+
|
|
4
|
+
;; The scheduler owns ordering for provider lifecycle and transcript facts. It
|
|
5
|
+
;; executes accepted steps immediately, invokes the private provider effector,
|
|
6
|
+
;; and preserves lifecycle request events as observable diagnostics.
|
|
7
|
+
|
|
8
|
+
(defn now-ms
|
|
9
|
+
[]
|
|
10
|
+
(.now js/Date))
|
|
11
|
+
|
|
12
|
+
(defn queue-entry
|
|
13
|
+
[step index plan]
|
|
14
|
+
(assoc step
|
|
15
|
+
:agency "transcription"
|
|
16
|
+
:queueIndex index
|
|
17
|
+
:commandType (:commandType plan)
|
|
18
|
+
:queuedAt (now-ms)))
|
|
19
|
+
|
|
20
|
+
(defn provider-request
|
|
21
|
+
[state-atom action extra]
|
|
22
|
+
(merge {:type "transcription.requestProvider"
|
|
23
|
+
:agency "transcription"
|
|
24
|
+
:targetAgency "transcription-provider"
|
|
25
|
+
:action action
|
|
26
|
+
:provider (get-in @state-atom [:config :provider])
|
|
27
|
+
:sessionId (:sessionId @state-atom)}
|
|
28
|
+
extra))
|
|
29
|
+
|
|
30
|
+
(defn transcript-fact
|
|
31
|
+
[state-atom kind step now]
|
|
32
|
+
{:type (str "transcription." kind)
|
|
33
|
+
:agency "transcription"
|
|
34
|
+
:targetAgency "conversation"
|
|
35
|
+
:text (:text step)
|
|
36
|
+
:confidence (:confidence step)
|
|
37
|
+
:isFinal (= kind "final")
|
|
38
|
+
:sequence (state/next-sequence @state-atom)
|
|
39
|
+
:source (:source step)
|
|
40
|
+
:sessionId (:sessionId @state-atom)
|
|
41
|
+
:at now})
|
|
42
|
+
|
|
43
|
+
(defn emit-status!
|
|
44
|
+
[emit-event state-atom status reason]
|
|
45
|
+
(emit-event (cond-> {:type "transcription.status"
|
|
46
|
+
:agency "transcription"
|
|
47
|
+
:status status
|
|
48
|
+
:sessionId (:sessionId @state-atom)
|
|
49
|
+
:at (now-ms)}
|
|
50
|
+
reason (assoc :reason reason))))
|
|
51
|
+
|
|
52
|
+
(defn create-scheduler
|
|
53
|
+
[{:keys [state-atom emit-event effector]}]
|
|
54
|
+
(let [queue (atom [])
|
|
55
|
+
disposed? (atom false)]
|
|
56
|
+
(letfn [(enqueue! [step plan]
|
|
57
|
+
(let [entry (queue-entry step (count @queue) plan)]
|
|
58
|
+
(swap! queue conj entry)
|
|
59
|
+
entry))
|
|
60
|
+
|
|
61
|
+
(execute-step! [step command plan]
|
|
62
|
+
(let [now (now-ms)]
|
|
63
|
+
(enqueue! step plan)
|
|
64
|
+
(case (:op step)
|
|
65
|
+
"apply-config"
|
|
66
|
+
(do
|
|
67
|
+
(swap! state-atom state/configure (:config command))
|
|
68
|
+
(emit-status! emit-event state-atom (:status @state-atom) "configured"))
|
|
69
|
+
|
|
70
|
+
"configure-provider"
|
|
71
|
+
((:configure effector) (:config @state-atom))
|
|
72
|
+
|
|
73
|
+
"start-session"
|
|
74
|
+
(do
|
|
75
|
+
(swap! state-atom state/start-session now)
|
|
76
|
+
(emit-status! emit-event state-atom "starting" nil))
|
|
77
|
+
|
|
78
|
+
"request-provider-start"
|
|
79
|
+
(do
|
|
80
|
+
(emit-event (provider-request state-atom "start"
|
|
81
|
+
{:config (:config @state-atom)
|
|
82
|
+
:requestedAt now}))
|
|
83
|
+
((:start effector) (:sessionId @state-atom) (:config @state-atom)))
|
|
84
|
+
|
|
85
|
+
"record-provider-started"
|
|
86
|
+
(do
|
|
87
|
+
(swap! state-atom state/provider-started now)
|
|
88
|
+
(emit-status! emit-event state-atom "listening" "provider-started"))
|
|
89
|
+
|
|
90
|
+
"stop-session"
|
|
91
|
+
(do
|
|
92
|
+
(swap! state-atom state/stop-session now (:reason step))
|
|
93
|
+
(emit-status! emit-event state-atom "idle" (:reason step)))
|
|
94
|
+
|
|
95
|
+
"request-provider-stop"
|
|
96
|
+
(do
|
|
97
|
+
(emit-event (provider-request state-atom "stop"
|
|
98
|
+
{:reason (:reason step)
|
|
99
|
+
:requestedAt now}))
|
|
100
|
+
(if (= "abort" (:method step))
|
|
101
|
+
((:abort effector))
|
|
102
|
+
((:stop effector))))
|
|
103
|
+
|
|
104
|
+
"reset-state"
|
|
105
|
+
(do
|
|
106
|
+
(swap! state-atom state/reset-state)
|
|
107
|
+
(emit-status! emit-event state-atom "idle" "reset"))
|
|
108
|
+
|
|
109
|
+
"publish-partial"
|
|
110
|
+
(let [fact (transcript-fact state-atom "partial" step now)]
|
|
111
|
+
(swap! state-atom state/record-partial fact)
|
|
112
|
+
(emit-event fact))
|
|
113
|
+
|
|
114
|
+
"publish-final"
|
|
115
|
+
(let [fact (transcript-fact state-atom "final" step now)]
|
|
116
|
+
(swap! state-atom state/record-final fact)
|
|
117
|
+
(emit-event fact))
|
|
118
|
+
|
|
119
|
+
"publish-interruption"
|
|
120
|
+
(let [fact {:type "transcription.interruption"
|
|
121
|
+
:agency "transcription"
|
|
122
|
+
:targetAgency "conversation"
|
|
123
|
+
:text (:text step)
|
|
124
|
+
:confidence (:confidence step)
|
|
125
|
+
:source (:source step)
|
|
126
|
+
:sessionId (:sessionId @state-atom)
|
|
127
|
+
:at now}]
|
|
128
|
+
(swap! state-atom state/record-interruption fact)
|
|
129
|
+
(emit-event fact))
|
|
130
|
+
|
|
131
|
+
"publish-audio-interruption"
|
|
132
|
+
(let [fact {:type "transcription.audioInterruption"
|
|
133
|
+
:agency "transcription"
|
|
134
|
+
:targetAgency "conversation"
|
|
135
|
+
:timestamp (:timestamp step)
|
|
136
|
+
:microphoneLevel (:microphoneLevel step)
|
|
137
|
+
:referenceLevel (:referenceLevel step)
|
|
138
|
+
:requiredLevel (:requiredLevel step)
|
|
139
|
+
:at now}]
|
|
140
|
+
(swap! state-atom state/record-interruption fact)
|
|
141
|
+
(emit-event fact))
|
|
142
|
+
|
|
143
|
+
"record-error"
|
|
144
|
+
(do
|
|
145
|
+
(swap! state-atom state/record-error (:message step) now (:retry step))
|
|
146
|
+
(emit-event {:type "transcription.error"
|
|
147
|
+
:agency "transcription"
|
|
148
|
+
:message (:message step)
|
|
149
|
+
:retrying (:retry step)
|
|
150
|
+
:sessionId (:sessionId @state-atom)
|
|
151
|
+
:at now}))
|
|
152
|
+
|
|
153
|
+
"request-provider-retry"
|
|
154
|
+
(do
|
|
155
|
+
(emit-event (provider-request state-atom "retry"
|
|
156
|
+
{:message (:message step)
|
|
157
|
+
:retryAfterMs (:retryAfterMs step)
|
|
158
|
+
:retryCount (:retryCount @state-atom)
|
|
159
|
+
:requestedAt now}))
|
|
160
|
+
((:retry effector)
|
|
161
|
+
(:sessionId @state-atom)
|
|
162
|
+
(:retryAfterMs step)))
|
|
163
|
+
|
|
164
|
+
"publish-status"
|
|
165
|
+
(emit-status! emit-event state-atom (:status step) (:reason step))
|
|
166
|
+
|
|
167
|
+
"ignore"
|
|
168
|
+
(let [ignored {:type "transcription.ignored"
|
|
169
|
+
:agency "transcription"
|
|
170
|
+
:reason (:reason step)
|
|
171
|
+
:commandType (:commandType step)
|
|
172
|
+
:at now}]
|
|
173
|
+
(swap! state-atom state/record-ignored ignored)
|
|
174
|
+
(emit-event ignored))
|
|
175
|
+
|
|
176
|
+
"record-tts-status"
|
|
177
|
+
(let [fact (:fact step)]
|
|
178
|
+
(swap! state-atom state/record-tts-status fact)
|
|
179
|
+
((:sync-agent-speaking effector)
|
|
180
|
+
(:agentSpeaking @state-atom))
|
|
181
|
+
(emit-event fact))
|
|
182
|
+
|
|
183
|
+
"prepare-agent-speech"
|
|
184
|
+
(do
|
|
185
|
+
(swap! state-atom state/prepare-agent-speech
|
|
186
|
+
(:text step)
|
|
187
|
+
(:append step))
|
|
188
|
+
((:sync-agent-speaking effector)
|
|
189
|
+
(:agentSpeaking @state-atom)))
|
|
190
|
+
|
|
191
|
+
"record-agent-word"
|
|
192
|
+
(swap! state-atom state/record-agent-word (:word step))
|
|
193
|
+
|
|
194
|
+
"fail"
|
|
195
|
+
(emit-event {:type "error"
|
|
196
|
+
:agency "transcription"
|
|
197
|
+
:message (:reason step)
|
|
198
|
+
:commandType (:commandType step)
|
|
199
|
+
:at now})
|
|
200
|
+
|
|
201
|
+
nil)))]
|
|
202
|
+
{:schedule
|
|
203
|
+
(fn [command plan]
|
|
204
|
+
(when-not @disposed?
|
|
205
|
+
(last (mapv #(execute-step! % command plan) (:steps plan)))))
|
|
206
|
+
|
|
207
|
+
:queue
|
|
208
|
+
(fn []
|
|
209
|
+
@queue)
|
|
210
|
+
|
|
211
|
+
:set-reference-track
|
|
212
|
+
(fn [track]
|
|
213
|
+
(when-not @disposed?
|
|
214
|
+
(let [plan {:agency "transcription"
|
|
215
|
+
:commandType "setAgentAudioReferenceTrack"
|
|
216
|
+
:createdAt (now-ms)}
|
|
217
|
+
step {:op "set-reference-track"}]
|
|
218
|
+
(enqueue! step plan)
|
|
219
|
+
((:set-reference-track effector) track))))
|
|
220
|
+
|
|
221
|
+
:dispose
|
|
222
|
+
(fn []
|
|
223
|
+
(reset! disposed? true)
|
|
224
|
+
(reset! queue [])
|
|
225
|
+
((:dispose effector)))})))
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
(ns polymer.transcription.service
|
|
2
|
+
(:require [clojure.string :as str]
|
|
3
|
+
[polymer.transcription.agency :as transcription-agency]
|
|
4
|
+
[polymer.transcription.domain :as domain]))
|
|
5
|
+
|
|
6
|
+
;; Compatibility adapter for LoomLarge's historical TranscriptionService API.
|
|
7
|
+
;; Recognition, microphone acquisition, AudioContext analysis, playback-reference
|
|
8
|
+
;; comparison, and frame monitoring all remain private to the agency effector.
|
|
9
|
+
|
|
10
|
+
(defn data-map
|
|
11
|
+
[value]
|
|
12
|
+
(cond
|
|
13
|
+
(map? value) value
|
|
14
|
+
value (js->clj value :keywordize-keys true)
|
|
15
|
+
:else {}))
|
|
16
|
+
|
|
17
|
+
(defn js-call
|
|
18
|
+
[target method-name & args]
|
|
19
|
+
(when target
|
|
20
|
+
(when-let [method (aget target method-name)]
|
|
21
|
+
(when (fn? method)
|
|
22
|
+
(.apply method target (to-array args))))))
|
|
23
|
+
|
|
24
|
+
(defn emit-set!
|
|
25
|
+
[listeners & args]
|
|
26
|
+
(doseq [listener @listeners]
|
|
27
|
+
(.apply listener nil (to-array args))))
|
|
28
|
+
|
|
29
|
+
(defn promise-resolve
|
|
30
|
+
[value]
|
|
31
|
+
(.resolve js/Promise value))
|
|
32
|
+
|
|
33
|
+
(defn compatibility-config
|
|
34
|
+
[config]
|
|
35
|
+
;; Preserve the active LoomLarge barge-in defaults explicitly at the adapter
|
|
36
|
+
;; boundary. Direct agency construction uses the same normalized defaults.
|
|
37
|
+
(merge {:agentFilteringEnabled true
|
|
38
|
+
:interruptDetectionEnabled true
|
|
39
|
+
:requireAgentReferenceForInterruption true}
|
|
40
|
+
(data-map config)))
|
|
41
|
+
|
|
42
|
+
(defn create-service
|
|
43
|
+
[config callbacks]
|
|
44
|
+
(let [agency (transcription-agency/create-transcription-agency
|
|
45
|
+
(compatibility-config config))
|
|
46
|
+
transcript-listeners (atom #{})
|
|
47
|
+
boundary-listeners (atom #{})
|
|
48
|
+
interruption-listeners (atom #{})
|
|
49
|
+
disposed? (atom false)
|
|
50
|
+
last-status (atom "idle")
|
|
51
|
+
unsubscribe (atom nil)]
|
|
52
|
+
(letfn [(dispatch! [command]
|
|
53
|
+
(js-call agency "dispatch" (clj->js command)))
|
|
54
|
+
|
|
55
|
+
(emit-transcript! [event]
|
|
56
|
+
(let [text (:text event)
|
|
57
|
+
final? (boolean (:isFinal event))]
|
|
58
|
+
(js-call callbacks "onTranscript" text final?)
|
|
59
|
+
(emit-set! transcript-listeners text final?)
|
|
60
|
+
(when final?
|
|
61
|
+
(doseq [boundary
|
|
62
|
+
(domain/boundary-events text (:at event))]
|
|
63
|
+
(let [payload (clj->js boundary)]
|
|
64
|
+
(js-call callbacks "onBoundary" payload)
|
|
65
|
+
(emit-set! boundary-listeners payload))))))
|
|
66
|
+
|
|
67
|
+
(emit-status! [event]
|
|
68
|
+
(let [status (:status event)
|
|
69
|
+
previous @last-status]
|
|
70
|
+
(reset! last-status status)
|
|
71
|
+
(when (and (= "listening" status)
|
|
72
|
+
(not= "listening" previous))
|
|
73
|
+
(js-call callbacks "onStart"))
|
|
74
|
+
(when (and (= "idle" status)
|
|
75
|
+
(not= "idle" previous))
|
|
76
|
+
(js-call callbacks "onEnd"))))
|
|
77
|
+
|
|
78
|
+
(emit-audio-interruption! [event]
|
|
79
|
+
(let [payload (clj->js
|
|
80
|
+
(select-keys
|
|
81
|
+
event
|
|
82
|
+
[:timestamp
|
|
83
|
+
:microphoneLevel
|
|
84
|
+
:referenceLevel
|
|
85
|
+
:requiredLevel]))]
|
|
86
|
+
(js-call callbacks "onInterruption" payload)
|
|
87
|
+
(emit-set! interruption-listeners payload)))
|
|
88
|
+
|
|
89
|
+
(handle-event! [value]
|
|
90
|
+
(when-not @disposed?
|
|
91
|
+
(let [event (data-map value)]
|
|
92
|
+
(case (:type event)
|
|
93
|
+
("transcription.partial" "transcription.final")
|
|
94
|
+
(emit-transcript! event)
|
|
95
|
+
|
|
96
|
+
"transcription.audioInterruption"
|
|
97
|
+
(emit-audio-interruption! event)
|
|
98
|
+
|
|
99
|
+
"transcription.error"
|
|
100
|
+
(js-call callbacks "onError"
|
|
101
|
+
(js/Error. (:message event)))
|
|
102
|
+
|
|
103
|
+
"transcription.status"
|
|
104
|
+
(emit-status! event)
|
|
105
|
+
|
|
106
|
+
nil))))
|
|
107
|
+
|
|
108
|
+
(handle-agent-speech! [value]
|
|
109
|
+
(let [event (data-map value)
|
|
110
|
+
text (or (:phrase event)
|
|
111
|
+
(when (seq (:words event))
|
|
112
|
+
(str/join " " (:words event))))]
|
|
113
|
+
(case (:type event)
|
|
114
|
+
"AGENT_SCRIPT"
|
|
115
|
+
(dispatch! {:type "agentSpeechPrepared" :text text})
|
|
116
|
+
|
|
117
|
+
"AGENT_START"
|
|
118
|
+
(dispatch! {:type "agentSpeechObserved" :text text})
|
|
119
|
+
|
|
120
|
+
("END" "AGENT_DONE" "PLAYBACK_ENDED")
|
|
121
|
+
(dispatch! {:type "agentSpeechEnded"})
|
|
122
|
+
|
|
123
|
+
"WORD"
|
|
124
|
+
(dispatch! {:type "agentSpeechWord" :word (:word event)})
|
|
125
|
+
|
|
126
|
+
nil)))]
|
|
127
|
+
(reset! unsubscribe
|
|
128
|
+
(js-call agency "subscribeEvents" handle-event!))
|
|
129
|
+
#js {:startListening
|
|
130
|
+
(fn []
|
|
131
|
+
(if @disposed?
|
|
132
|
+
(.reject js/Promise
|
|
133
|
+
(js/Error. "Transcription service is disposed"))
|
|
134
|
+
(let [result (js-call agency "start")
|
|
135
|
+
snapshot (data-map (js-call agency "snapshot"))]
|
|
136
|
+
(if (:lastError snapshot)
|
|
137
|
+
(.reject js/Promise
|
|
138
|
+
(js/Error.
|
|
139
|
+
(or (:lastError snapshot)
|
|
140
|
+
"speech-recognition-start-error")))
|
|
141
|
+
(promise-resolve result)))))
|
|
142
|
+
|
|
143
|
+
:stopListening
|
|
144
|
+
(fn []
|
|
145
|
+
(js-call agency "stop"))
|
|
146
|
+
|
|
147
|
+
:handleAgentSpeech handle-agent-speech!
|
|
148
|
+
|
|
149
|
+
:prepareAgentSpeech
|
|
150
|
+
(fn [text]
|
|
151
|
+
(dispatch! {:type "agentSpeechPrepared" :text text}))
|
|
152
|
+
|
|
153
|
+
:notifyAgentSpeech
|
|
154
|
+
(fn [text]
|
|
155
|
+
(dispatch! {:type "agentSpeechObserved" :text text}))
|
|
156
|
+
|
|
157
|
+
:notifyAgentSpeechEnd
|
|
158
|
+
(fn []
|
|
159
|
+
(dispatch! {:type "agentSpeechEnded"}))
|
|
160
|
+
|
|
161
|
+
:setAgentAudioReferenceTrack
|
|
162
|
+
(fn [track]
|
|
163
|
+
(js-call agency "setAgentAudioReferenceTrack" track))
|
|
164
|
+
|
|
165
|
+
:onBoundary
|
|
166
|
+
(fn [listener]
|
|
167
|
+
(swap! boundary-listeners conj listener)
|
|
168
|
+
(fn [] (swap! boundary-listeners disj listener)))
|
|
169
|
+
|
|
170
|
+
:onTranscript
|
|
171
|
+
(fn [listener]
|
|
172
|
+
(swap! transcript-listeners conj listener)
|
|
173
|
+
(fn [] (swap! transcript-listeners disj listener)))
|
|
174
|
+
|
|
175
|
+
:onInterruption
|
|
176
|
+
(fn [listener]
|
|
177
|
+
(swap! interruption-listeners conj listener)
|
|
178
|
+
(fn [] (swap! interruption-listeners disj listener)))
|
|
179
|
+
|
|
180
|
+
:getState
|
|
181
|
+
(fn []
|
|
182
|
+
(js-call agency "snapshot"))
|
|
183
|
+
|
|
184
|
+
:updateConfig
|
|
185
|
+
(fn [updates]
|
|
186
|
+
(dispatch! {:type "configure"
|
|
187
|
+
:config (data-map updates)}))
|
|
188
|
+
|
|
189
|
+
:dispose
|
|
190
|
+
(fn []
|
|
191
|
+
(when-not @disposed?
|
|
192
|
+
(reset! disposed? true)
|
|
193
|
+
(when-let [stop @unsubscribe]
|
|
194
|
+
(stop)
|
|
195
|
+
(reset! unsubscribe nil))
|
|
196
|
+
(reset! transcript-listeners #{})
|
|
197
|
+
(reset! boundary-listeners #{})
|
|
198
|
+
(reset! interruption-listeners #{})
|
|
199
|
+
(js-call agency "dispose")))})))
|
|
200
|
+
|
|
201
|
+
(defn TranscriptionService
|
|
202
|
+
([] (create-service nil nil))
|
|
203
|
+
([config] (create-service config nil))
|
|
204
|
+
([config callbacks] (create-service config callbacks)))
|
|
205
|
+
|
|
206
|
+
(defn createTranscriptionService
|
|
207
|
+
([] (TranscriptionService nil nil))
|
|
208
|
+
([config] (TranscriptionService config nil))
|
|
209
|
+
([config callbacks] (TranscriptionService config callbacks)))
|