@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.
Files changed (85) hide show
  1. package/README.md +226 -0
  2. package/dist/index.d.ts +2603 -0
  3. package/dist/index.js +1031 -0
  4. package/dist/test.cjs +1519 -0
  5. package/dist/worker/agency-worker.js +972 -0
  6. package/dist/worker/manifest.edn +1 -0
  7. package/package.json +57 -0
  8. package/shadow-cljs.edn +173 -0
  9. package/src/polymer/animation/agency.cljs +84 -0
  10. package/src/polymer/animation/domain.cljs +110 -0
  11. package/src/polymer/animation/planner.cljs +88 -0
  12. package/src/polymer/animation/runtime.cljs +137 -0
  13. package/src/polymer/animation/scheduler.cljs +178 -0
  14. package/src/polymer/animation/service.cljs +981 -0
  15. package/src/polymer/animation/state.cljs +112 -0
  16. package/src/polymer/blink/agency.cljs +131 -0
  17. package/src/polymer/blink/planner.cljs +115 -0
  18. package/src/polymer/blink/scheduler.cljs +90 -0
  19. package/src/polymer/blink/snippet.cljs +50 -0
  20. package/src/polymer/blink/state.cljs +102 -0
  21. package/src/polymer/camera_context/agency.cljs +132 -0
  22. package/src/polymer/camera_context/domain.cljs +186 -0
  23. package/src/polymer/camera_context/planner.cljs +50 -0
  24. package/src/polymer/camera_context/scheduler.cljs +113 -0
  25. package/src/polymer/camera_context/state.cljs +121 -0
  26. package/src/polymer/character.cljs +471 -0
  27. package/src/polymer/conversation/agency.cljs +83 -0
  28. package/src/polymer/conversation/domain.cljs +35 -0
  29. package/src/polymer/conversation/effector.cljs +130 -0
  30. package/src/polymer/conversation/planner.cljs +178 -0
  31. package/src/polymer/conversation/scheduler.cljs +202 -0
  32. package/src/polymer/conversation/service.cljs +329 -0
  33. package/src/polymer/conversation/state.cljs +186 -0
  34. package/src/polymer/core.cljs +172 -0
  35. package/src/polymer/eye_head/agency.cljs +75 -0
  36. package/src/polymer/eye_head/domain.cljs +133 -0
  37. package/src/polymer/eye_head/goap.cljs +106 -0
  38. package/src/polymer/eye_head/scheduler.cljs +176 -0
  39. package/src/polymer/eye_head/service.cljs +259 -0
  40. package/src/polymer/eye_head/snippet.cljs +92 -0
  41. package/src/polymer/eye_head/state.cljs +167 -0
  42. package/src/polymer/gaze/agency.cljs +85 -0
  43. package/src/polymer/gaze/domain.cljs +211 -0
  44. package/src/polymer/gaze/goap.cljs +208 -0
  45. package/src/polymer/gaze/scheduler.cljs +184 -0
  46. package/src/polymer/gaze/state.cljs +202 -0
  47. package/src/polymer/gesture/agency.cljs +185 -0
  48. package/src/polymer/gesture/goap.cljs +461 -0
  49. package/src/polymer/gesture/scheduler.cljs +122 -0
  50. package/src/polymer/gesture/snippet.cljs +198 -0
  51. package/src/polymer/gesture/state.cljs +273 -0
  52. package/src/polymer/lipsync/agency.cljs +593 -0
  53. package/src/polymer/lipsync/articulation/jaw.cljs +258 -0
  54. package/src/polymer/lipsync/articulation/snippet.cljs +605 -0
  55. package/src/polymer/lipsync/articulation/tongue.cljs +418 -0
  56. package/src/polymer/lipsync/articulation/visemes.cljs +429 -0
  57. package/src/polymer/lipsync/goap.cljs +132 -0
  58. package/src/polymer/lipsync/pronunciation/effector.cljs +51 -0
  59. package/src/polymer/lipsync/scheduler.cljs +302 -0
  60. package/src/polymer/lipsync/state.cljs +202 -0
  61. package/src/polymer/nlp/phonemes.cljs +164 -0
  62. package/src/polymer/prosodic/agency.cljs +196 -0
  63. package/src/polymer/prosodic/goap.cljs +122 -0
  64. package/src/polymer/prosodic/scheduler.cljs +99 -0
  65. package/src/polymer/prosodic/snippet.cljs +106 -0
  66. package/src/polymer/prosodic/state.cljs +139 -0
  67. package/src/polymer/runtime/embody.cljs +117 -0
  68. package/src/polymer/runtime/embody_wasm.cljs +177 -0
  69. package/src/polymer/runtime/rust_embody_host.cljs +645 -0
  70. package/src/polymer/society.cljs +239 -0
  71. package/src/polymer/stream.cljs +130 -0
  72. package/src/polymer/transcription/agency.cljs +69 -0
  73. package/src/polymer/transcription/domain.cljs +68 -0
  74. package/src/polymer/transcription/effector.cljs +611 -0
  75. package/src/polymer/transcription/planner.cljs +203 -0
  76. package/src/polymer/transcription/scheduler.cljs +225 -0
  77. package/src/polymer/transcription/service.cljs +209 -0
  78. package/src/polymer/transcription/state.cljs +239 -0
  79. package/src/polymer/tts/agency.cljs +503 -0
  80. package/src/polymer/tts/azure.cljs +290 -0
  81. package/src/polymer/tts/planner.cljs +178 -0
  82. package/src/polymer/tts/runtime.cljs +624 -0
  83. package/src/polymer/tts/scheduler.cljs +162 -0
  84. package/src/polymer/tts/state.cljs +191 -0
  85. 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)))