@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,471 @@
|
|
|
1
|
+
(ns polymer.character
|
|
2
|
+
(:require [polymer.animation.agency :as animation]
|
|
3
|
+
[polymer.blink.agency :as blink]
|
|
4
|
+
[polymer.camera-context.agency :as camera-context]
|
|
5
|
+
[polymer.conversation.agency :as conversation]
|
|
6
|
+
[polymer.eye-head.agency :as eye-head]
|
|
7
|
+
[polymer.gaze.agency :as gaze]
|
|
8
|
+
[polymer.gesture.agency :as gesture]
|
|
9
|
+
[polymer.lipsync.agency :as lipsync]
|
|
10
|
+
[polymer.prosodic.agency :as prosodic]
|
|
11
|
+
[polymer.society :as society]
|
|
12
|
+
[polymer.stream :as stream]
|
|
13
|
+
[polymer.transcription.agency :as transcription]
|
|
14
|
+
[polymer.tts.agency :as tts]))
|
|
15
|
+
|
|
16
|
+
;; A character is a network of Polymer agencies.
|
|
17
|
+
;;
|
|
18
|
+
;; LoomLarge may still consume streams from legacy Latticework services during
|
|
19
|
+
;; the migration, but it should not consume Polymer animation events and turn
|
|
20
|
+
;; them into animation calls. Inside Polymer, Blink emits blink intent and fast
|
|
21
|
+
;; blink facts, Camera Context emits gaze facts, Transcription emits transcript
|
|
22
|
+
;; facts, Conversation emits speech requests, TTS emits speech timing facts,
|
|
23
|
+
;; LipSync emits mouth animation intent, Gesture emits arm/hand animation intent,
|
|
24
|
+
;; Prosodic emits speech/blink expression intent, and Animation talks directly
|
|
25
|
+
;; to Embody. Hair appearance and physics are Embody runtime concerns, not a
|
|
26
|
+
;; Polymer behavioral agency.
|
|
27
|
+
|
|
28
|
+
(defn create-character-agencies [config]
|
|
29
|
+
(let [input-stream (stream/create-stream)
|
|
30
|
+
event-stream (stream/create-stream)
|
|
31
|
+
emit-input (:emit input-stream)
|
|
32
|
+
emit-event (:emit event-stream)
|
|
33
|
+
agency-society (society/normalize-agency-society
|
|
34
|
+
(when config (aget config "agencySociety")))
|
|
35
|
+
animation-agency (animation/create-animation-agency
|
|
36
|
+
(society/agency-config config agency-society "animation"))
|
|
37
|
+
blink-agency (blink/create-blink-agency
|
|
38
|
+
(society/agency-config config agency-society "blink"))
|
|
39
|
+
gaze-agency (gaze/create-gaze-agency
|
|
40
|
+
(society/agency-config config agency-society "gaze"))
|
|
41
|
+
eye-head-agency (eye-head/create-eye-head-tracking-agency
|
|
42
|
+
(society/agency-config config agency-society "eyeHeadTracking"))
|
|
43
|
+
gesture-agency (gesture/create-gesture-agency
|
|
44
|
+
(society/agency-config config agency-society "gesture"))
|
|
45
|
+
camera-context-agency (camera-context/create-camera-context-agency
|
|
46
|
+
(society/agency-config config agency-society "cameraContext"))
|
|
47
|
+
transcription-agency (transcription/create-transcription-agency
|
|
48
|
+
(society/agency-config config agency-society "transcription"))
|
|
49
|
+
conversation-config (society/agency-config
|
|
50
|
+
config agency-society "conversation")
|
|
51
|
+
;; The provider is injected once into Conversation's private effector.
|
|
52
|
+
;; The character router never invokes provider functions itself.
|
|
53
|
+
conversation-provider (when config (aget config "conversationProvider"))
|
|
54
|
+
conversation-options (if conversation-provider
|
|
55
|
+
(let [options (js/Object.assign
|
|
56
|
+
#js {}
|
|
57
|
+
(or conversation-config #js {}))]
|
|
58
|
+
(aset options "provider" conversation-provider)
|
|
59
|
+
options)
|
|
60
|
+
conversation-config)
|
|
61
|
+
conversation-agency (conversation/create-conversation-agency
|
|
62
|
+
conversation-options)
|
|
63
|
+
tts-agency (tts/create-tts-agency
|
|
64
|
+
(society/agency-config config agency-society "tts"))
|
|
65
|
+
lipsync-runtime (when config (aget config "lipSyncRuntime"))
|
|
66
|
+
lipsync-agency (lipsync/create-lipsync-agency
|
|
67
|
+
(society/agency-config config agency-society "lipSync")
|
|
68
|
+
lipsync-runtime)
|
|
69
|
+
prosodic-agency (prosodic/create-prosodic-agency
|
|
70
|
+
(society/agency-config config agency-society "prosodic"))
|
|
71
|
+
unsubscribers (atom [])
|
|
72
|
+
disposed? (atom false)]
|
|
73
|
+
(letfn [(track! [unsubscribe]
|
|
74
|
+
(swap! unsubscribers conj unsubscribe))
|
|
75
|
+
|
|
76
|
+
(agency-by-name [name]
|
|
77
|
+
(case name
|
|
78
|
+
"animation" animation-agency
|
|
79
|
+
"blink" blink-agency
|
|
80
|
+
"gaze" gaze-agency
|
|
81
|
+
"eyeHeadTracking" eye-head-agency
|
|
82
|
+
"gesture" gesture-agency
|
|
83
|
+
"cameraContext" camera-context-agency
|
|
84
|
+
"transcription" transcription-agency
|
|
85
|
+
"conversation" conversation-agency
|
|
86
|
+
"tts" tts-agency
|
|
87
|
+
"lipSync" lipsync-agency
|
|
88
|
+
"prosodic" prosodic-agency
|
|
89
|
+
nil))
|
|
90
|
+
|
|
91
|
+
(dispatch-to! [name command]
|
|
92
|
+
(when-let [target (agency-by-name name)]
|
|
93
|
+
(when-let [dispatch (aget target "dispatch")]
|
|
94
|
+
(.call dispatch target (clj->js command))
|
|
95
|
+
true)))
|
|
96
|
+
|
|
97
|
+
(route-command! [from event to command]
|
|
98
|
+
(when-let [route (society/route-context agency-society from event to)]
|
|
99
|
+
(dispatch-to! to (assoc command :society route))))
|
|
100
|
+
|
|
101
|
+
(schedule-animation! [event]
|
|
102
|
+
(route-command! (:agency event)
|
|
103
|
+
event
|
|
104
|
+
"animation"
|
|
105
|
+
{:type "scheduleSnippet"
|
|
106
|
+
:sourceAgency (:agency event)
|
|
107
|
+
:snippet (:snippet event)
|
|
108
|
+
:options (:options event)}))
|
|
109
|
+
|
|
110
|
+
(remove-animation! [event]
|
|
111
|
+
(route-command! (:agency event)
|
|
112
|
+
event
|
|
113
|
+
"animation"
|
|
114
|
+
{:type "removeSnippet"
|
|
115
|
+
:sourceAgency (:agency event)
|
|
116
|
+
:name (:name event)}))
|
|
117
|
+
|
|
118
|
+
(seek-animation! [event]
|
|
119
|
+
(route-command! (:agency event)
|
|
120
|
+
event
|
|
121
|
+
"animation"
|
|
122
|
+
{:type "seekSnippet"
|
|
123
|
+
:sourceAgency (:agency event)
|
|
124
|
+
:name (:name event)
|
|
125
|
+
:offsetSec (:offsetSec event)}))
|
|
126
|
+
|
|
127
|
+
(dispatch! [message]
|
|
128
|
+
(when-not @disposed?
|
|
129
|
+
(let [payload (js->clj message :keywordize-keys true)]
|
|
130
|
+
;; Input is still observable for tests/workers, but the write
|
|
131
|
+
;; path routes to agencies directly inside Polymer.
|
|
132
|
+
(emit-input {:type "command"
|
|
133
|
+
:agency (:agency payload)
|
|
134
|
+
:message payload})
|
|
135
|
+
(let [name (:agency payload)]
|
|
136
|
+
(cond
|
|
137
|
+
(nil? (agency-by-name name))
|
|
138
|
+
(emit-event {:type "error"
|
|
139
|
+
:agency (or name "unknown")
|
|
140
|
+
:message "Unknown Polymer agency"})
|
|
141
|
+
|
|
142
|
+
(not (society/agency-enabled? agency-society name))
|
|
143
|
+
(emit-event {:type "agencyDisabled"
|
|
144
|
+
:agency name
|
|
145
|
+
:message "Polymer agency is disabled by agencySociety"})
|
|
146
|
+
|
|
147
|
+
:else
|
|
148
|
+
(dispatch-to! name (:command payload)))))))
|
|
149
|
+
|
|
150
|
+
(snapshot! []
|
|
151
|
+
(clj->js {:blink (js->clj (.snapshot ^js blink-agency) :keywordize-keys true)
|
|
152
|
+
:gaze (js->clj (.snapshot ^js gaze-agency) :keywordize-keys true)
|
|
153
|
+
:eyeHeadTracking (js->clj (.snapshot ^js eye-head-agency) :keywordize-keys true)
|
|
154
|
+
:gesture (js->clj (.snapshot ^js gesture-agency) :keywordize-keys true)
|
|
155
|
+
:cameraContext (js->clj (.snapshot ^js camera-context-agency) :keywordize-keys true)
|
|
156
|
+
:transcription (js->clj (.snapshot ^js transcription-agency) :keywordize-keys true)
|
|
157
|
+
:conversation (js->clj (.snapshot ^js conversation-agency) :keywordize-keys true)
|
|
158
|
+
:tts (js->clj (.snapshot ^js tts-agency) :keywordize-keys true)
|
|
159
|
+
:lipSync (js->clj (.snapshot ^js lipsync-agency) :keywordize-keys true)
|
|
160
|
+
:prosodic (js->clj (.snapshot ^js prosodic-agency) :keywordize-keys true)
|
|
161
|
+
:animation (js->clj (.snapshot ^js animation-agency) :keywordize-keys true)
|
|
162
|
+
:agencySociety agency-society}))
|
|
163
|
+
|
|
164
|
+
(route-blink-event! [event]
|
|
165
|
+
(case (:type event)
|
|
166
|
+
"animation.requestScheduleSnippet"
|
|
167
|
+
(schedule-animation! event)
|
|
168
|
+
|
|
169
|
+
"signal"
|
|
170
|
+
(when (and (= "blink" (:agency event))
|
|
171
|
+
(= "blink-fast" (:signal event)))
|
|
172
|
+
(route-command! "blink"
|
|
173
|
+
event
|
|
174
|
+
"prosodic"
|
|
175
|
+
{:type "blinkFast"
|
|
176
|
+
:sourceAgency "blink"
|
|
177
|
+
:plan (:plan event)}))
|
|
178
|
+
|
|
179
|
+
nil))
|
|
180
|
+
|
|
181
|
+
(route-gaze-event! [event]
|
|
182
|
+
(case (:type event)
|
|
183
|
+
("eyeHeadTracking.requestGaze"
|
|
184
|
+
"eyeHeadTracking.requestReset"
|
|
185
|
+
"eyeHeadTracking.requestCancel")
|
|
186
|
+
(route-command! "gaze" event "eyeHeadTracking" event)
|
|
187
|
+
|
|
188
|
+
nil))
|
|
189
|
+
|
|
190
|
+
(route-eye-head-event! [event]
|
|
191
|
+
(case (:type event)
|
|
192
|
+
"animation.requestScheduleSnippet"
|
|
193
|
+
(schedule-animation! event)
|
|
194
|
+
|
|
195
|
+
"animation.requestRemoveSnippet"
|
|
196
|
+
(remove-animation! event)
|
|
197
|
+
|
|
198
|
+
nil))
|
|
199
|
+
|
|
200
|
+
(route-gesture-event! [event]
|
|
201
|
+
(case (:type event)
|
|
202
|
+
"animation.requestScheduleSnippet"
|
|
203
|
+
(schedule-animation! event)
|
|
204
|
+
|
|
205
|
+
"animation.requestRemoveSnippet"
|
|
206
|
+
(remove-animation! event)
|
|
207
|
+
|
|
208
|
+
nil))
|
|
209
|
+
|
|
210
|
+
(route-camera-context-event! [event]
|
|
211
|
+
(case (:type event)
|
|
212
|
+
"camera.fact"
|
|
213
|
+
(route-command! "cameraContext" event "gaze" event)
|
|
214
|
+
|
|
215
|
+
"camera.stale"
|
|
216
|
+
(route-command! "cameraContext" event "gaze" event)
|
|
217
|
+
|
|
218
|
+
nil))
|
|
219
|
+
|
|
220
|
+
(route-transcription-event! [event]
|
|
221
|
+
(case (:type event)
|
|
222
|
+
"transcription.final"
|
|
223
|
+
(route-command! "transcription"
|
|
224
|
+
event
|
|
225
|
+
"conversation"
|
|
226
|
+
{:type "transcript.final"
|
|
227
|
+
:text (:text event)
|
|
228
|
+
:source "transcription"
|
|
229
|
+
:confidence (:confidence event)
|
|
230
|
+
:sequence (:sequence event)
|
|
231
|
+
:sessionId (:sessionId event)
|
|
232
|
+
:at (:at event)})
|
|
233
|
+
|
|
234
|
+
"transcription.interruption"
|
|
235
|
+
(route-command! "transcription"
|
|
236
|
+
event
|
|
237
|
+
"conversation"
|
|
238
|
+
{:type "interrupt"
|
|
239
|
+
:reason "transcription-interruption"
|
|
240
|
+
:text (:text event)
|
|
241
|
+
:source "transcription"})
|
|
242
|
+
|
|
243
|
+
"transcription.audioInterruption"
|
|
244
|
+
(route-command! "transcription"
|
|
245
|
+
event
|
|
246
|
+
"conversation"
|
|
247
|
+
{:type "interrupt"
|
|
248
|
+
:reason "audio-interruption"
|
|
249
|
+
:event event
|
|
250
|
+
:source "transcription"})
|
|
251
|
+
|
|
252
|
+
nil))
|
|
253
|
+
|
|
254
|
+
(route-conversation-event! [event]
|
|
255
|
+
;; Router only: forward conversation facts/requests. Gaze and
|
|
256
|
+
;; Prosodic own local policy through their planners/schedulers.
|
|
257
|
+
(case (:type event)
|
|
258
|
+
("conversation.userUtterance"
|
|
259
|
+
"conversation.agentUtterance")
|
|
260
|
+
(let [fact (assoc event :sourceAgency "conversation")]
|
|
261
|
+
(route-command! "conversation" event "prosodic" fact)
|
|
262
|
+
(route-command! "conversation" event "gaze" fact))
|
|
263
|
+
|
|
264
|
+
"conversation.requestResponse"
|
|
265
|
+
(route-command! "conversation"
|
|
266
|
+
event
|
|
267
|
+
"prosodic"
|
|
268
|
+
(assoc event :sourceAgency "conversation"))
|
|
269
|
+
|
|
270
|
+
"tts.requestSpeak"
|
|
271
|
+
(route-command! "conversation" event "tts" (:command event))
|
|
272
|
+
|
|
273
|
+
"conversation.cancelRequested"
|
|
274
|
+
(let [cancel (assoc event :sourceAgency "conversation")]
|
|
275
|
+
(route-command! "conversation" event "prosodic" cancel)
|
|
276
|
+
(route-command! "conversation" event "gaze" cancel)
|
|
277
|
+
(route-command! "conversation"
|
|
278
|
+
event
|
|
279
|
+
"tts"
|
|
280
|
+
{:type "stop"
|
|
281
|
+
:reason (:reason event)}))
|
|
282
|
+
|
|
283
|
+
nil))
|
|
284
|
+
|
|
285
|
+
(route-tts-event! [event]
|
|
286
|
+
(case (:type event)
|
|
287
|
+
"lipSync.command"
|
|
288
|
+
(route-command! "tts" event "lipSync" (:command event))
|
|
289
|
+
|
|
290
|
+
"ttsStatusChanged"
|
|
291
|
+
(let [status (get-in event [:state :status])]
|
|
292
|
+
(route-command! "tts"
|
|
293
|
+
event
|
|
294
|
+
"conversation"
|
|
295
|
+
{:type "tts.status"
|
|
296
|
+
:status status})
|
|
297
|
+
(route-command! "tts"
|
|
298
|
+
event
|
|
299
|
+
"transcription"
|
|
300
|
+
{:type "tts.status"
|
|
301
|
+
:status status
|
|
302
|
+
:speaking (get-in event [:state :speaking])
|
|
303
|
+
:state (:state event)}))
|
|
304
|
+
|
|
305
|
+
"ttsSpeechStarted"
|
|
306
|
+
(do
|
|
307
|
+
(route-command! "tts"
|
|
308
|
+
event
|
|
309
|
+
"prosodic"
|
|
310
|
+
{:type "speechStarted"
|
|
311
|
+
:sourceAgency "tts"
|
|
312
|
+
:name (:name event)
|
|
313
|
+
:engine (:engine event)})
|
|
314
|
+
(route-command! "tts" event "transcription" event)
|
|
315
|
+
(route-command! "tts"
|
|
316
|
+
event
|
|
317
|
+
"conversation"
|
|
318
|
+
{:type "tts.status"
|
|
319
|
+
:status "speaking"}))
|
|
320
|
+
|
|
321
|
+
"ttsWordBoundary"
|
|
322
|
+
(route-command! "tts"
|
|
323
|
+
event
|
|
324
|
+
"prosodic"
|
|
325
|
+
{:type "wordBoundary"
|
|
326
|
+
:sourceAgency "tts"
|
|
327
|
+
:word (:word event)
|
|
328
|
+
:wordIndex (:wordIndex event)
|
|
329
|
+
:observedElapsedSec (:observedElapsedSec event)
|
|
330
|
+
:hostElapsedSec (:hostElapsedSec event)})
|
|
331
|
+
|
|
332
|
+
"ttsSpeechStopped"
|
|
333
|
+
(do
|
|
334
|
+
(route-command! "tts"
|
|
335
|
+
event
|
|
336
|
+
"prosodic"
|
|
337
|
+
{:type "speechStopped"
|
|
338
|
+
:sourceAgency "tts"
|
|
339
|
+
:reason (:reason event)})
|
|
340
|
+
(route-command! "tts" event "transcription" event)
|
|
341
|
+
(route-command! "tts"
|
|
342
|
+
event
|
|
343
|
+
"conversation"
|
|
344
|
+
{:type "tts.status"
|
|
345
|
+
:status "idle"}))
|
|
346
|
+
|
|
347
|
+
"ttsSpeechEnded"
|
|
348
|
+
(do
|
|
349
|
+
(route-command! "tts"
|
|
350
|
+
event
|
|
351
|
+
"prosodic"
|
|
352
|
+
{:type "speechStopped"
|
|
353
|
+
:sourceAgency "tts"
|
|
354
|
+
:reason "completed"})
|
|
355
|
+
(route-command! "tts" event "transcription" event)
|
|
356
|
+
(route-command! "tts"
|
|
357
|
+
event
|
|
358
|
+
"conversation"
|
|
359
|
+
{:type "tts.status"
|
|
360
|
+
:status "idle"}))
|
|
361
|
+
|
|
362
|
+
nil))
|
|
363
|
+
|
|
364
|
+
(route-lipsync-event! [event]
|
|
365
|
+
(case (:type event)
|
|
366
|
+
"animation.requestScheduleSnippet"
|
|
367
|
+
(schedule-animation! event)
|
|
368
|
+
|
|
369
|
+
"animation.requestRemoveSnippet"
|
|
370
|
+
(remove-animation! event)
|
|
371
|
+
|
|
372
|
+
"animation.requestSeekSnippet"
|
|
373
|
+
(seek-animation! event)
|
|
374
|
+
|
|
375
|
+
nil))
|
|
376
|
+
|
|
377
|
+
(route-prosodic-event! [event]
|
|
378
|
+
(case (:type event)
|
|
379
|
+
"animation.requestScheduleSnippet"
|
|
380
|
+
(schedule-animation! event)
|
|
381
|
+
|
|
382
|
+
"animation.requestRemoveSnippet"
|
|
383
|
+
(remove-animation! event)
|
|
384
|
+
|
|
385
|
+
nil))]
|
|
386
|
+
;; Fan-in agency events to one character-level event stream for tests,
|
|
387
|
+
;; workers, and future non-React observers. LoomLarge does not need to
|
|
388
|
+
;; consume this stream for Polymer animation playback.
|
|
389
|
+
(track! (.subscribeEvents ^js animation-agency
|
|
390
|
+
#(emit-event (js->clj % :keywordize-keys true))))
|
|
391
|
+
(track! (.subscribeEvents ^js blink-agency
|
|
392
|
+
(fn [event]
|
|
393
|
+
(let [payload (js->clj event :keywordize-keys true)]
|
|
394
|
+
(route-blink-event! payload)
|
|
395
|
+
(emit-event payload)))))
|
|
396
|
+
(track! (.subscribeEvents ^js gaze-agency
|
|
397
|
+
(fn [event]
|
|
398
|
+
(let [payload (js->clj event :keywordize-keys true)]
|
|
399
|
+
(route-gaze-event! payload)
|
|
400
|
+
(emit-event payload)))))
|
|
401
|
+
(track! (.subscribeEvents ^js eye-head-agency
|
|
402
|
+
(fn [event]
|
|
403
|
+
(let [payload (js->clj event :keywordize-keys true)]
|
|
404
|
+
(route-eye-head-event! payload)
|
|
405
|
+
(emit-event payload)))))
|
|
406
|
+
(track! (.subscribeEvents ^js gesture-agency
|
|
407
|
+
(fn [event]
|
|
408
|
+
(let [payload (js->clj event :keywordize-keys true)]
|
|
409
|
+
(route-gesture-event! payload)
|
|
410
|
+
(emit-event payload)))))
|
|
411
|
+
(track! (.subscribeEvents ^js camera-context-agency
|
|
412
|
+
(fn [event]
|
|
413
|
+
(let [payload (js->clj event :keywordize-keys true)]
|
|
414
|
+
(route-camera-context-event! payload)
|
|
415
|
+
(emit-event payload)))))
|
|
416
|
+
(track! (.subscribeEvents ^js transcription-agency
|
|
417
|
+
(fn [event]
|
|
418
|
+
(let [payload (js->clj event :keywordize-keys true)]
|
|
419
|
+
(route-transcription-event! payload)
|
|
420
|
+
(emit-event payload)))))
|
|
421
|
+
(track! (.subscribeEvents ^js conversation-agency
|
|
422
|
+
(fn [event]
|
|
423
|
+
(let [payload (js->clj event :keywordize-keys true)]
|
|
424
|
+
(route-conversation-event! payload)
|
|
425
|
+
(emit-event payload)))))
|
|
426
|
+
(track! (.subscribeEvents ^js tts-agency
|
|
427
|
+
(fn [event]
|
|
428
|
+
(let [payload (js->clj event :keywordize-keys true)]
|
|
429
|
+
(route-tts-event! payload)
|
|
430
|
+
(emit-event payload)))))
|
|
431
|
+
(track! (.subscribeEvents ^js lipsync-agency
|
|
432
|
+
(fn [event]
|
|
433
|
+
(let [payload (js->clj event :keywordize-keys true)]
|
|
434
|
+
(route-lipsync-event! payload)
|
|
435
|
+
(emit-event payload)))))
|
|
436
|
+
(track! (.subscribeEvents ^js prosodic-agency
|
|
437
|
+
(fn [event]
|
|
438
|
+
(let [payload (js->clj event :keywordize-keys true)]
|
|
439
|
+
(route-prosodic-event! payload)
|
|
440
|
+
(emit-event payload)))))
|
|
441
|
+
#js {:dispatch dispatch!
|
|
442
|
+
:input (stream/writable-port input-stream dispatch!)
|
|
443
|
+
:events (stream/readable-port event-stream)
|
|
444
|
+
:agencySociety (clj->js agency-society)
|
|
445
|
+
:snapshot snapshot!
|
|
446
|
+
:agency (fn [name]
|
|
447
|
+
(when (society/agency-enabled? agency-society name)
|
|
448
|
+
(agency-by-name name)))
|
|
449
|
+
:subscribeInput (fn [listener] ((:subscribe input-stream) listener))
|
|
450
|
+
:subscribeEvents (fn [listener] ((:subscribe event-stream) listener))
|
|
451
|
+
:subscribe (fn [listener] ((:subscribe event-stream) listener))
|
|
452
|
+
:subscribeStatus (fn [listener] ((:subscribe event-stream) listener))
|
|
453
|
+
:dispose (fn []
|
|
454
|
+
(when-not @disposed?
|
|
455
|
+
(reset! disposed? true)
|
|
456
|
+
(doseq [unsubscribe @unsubscribers]
|
|
457
|
+
(unsubscribe))
|
|
458
|
+
(reset! unsubscribers [])
|
|
459
|
+
(.dispose ^js blink-agency)
|
|
460
|
+
(.dispose ^js gaze-agency)
|
|
461
|
+
(.dispose ^js eye-head-agency)
|
|
462
|
+
(.dispose ^js gesture-agency)
|
|
463
|
+
(.dispose ^js camera-context-agency)
|
|
464
|
+
(.dispose ^js transcription-agency)
|
|
465
|
+
(.dispose ^js conversation-agency)
|
|
466
|
+
(.dispose ^js tts-agency)
|
|
467
|
+
(.dispose ^js lipsync-agency)
|
|
468
|
+
(.dispose ^js prosodic-agency)
|
|
469
|
+
(.dispose ^js animation-agency)
|
|
470
|
+
((:dispose input-stream))
|
|
471
|
+
((:dispose event-stream))))})))
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
(ns polymer.conversation.agency
|
|
2
|
+
(:require [polymer.conversation.domain :as domain]
|
|
3
|
+
[polymer.conversation.effector :as effector]
|
|
4
|
+
[polymer.conversation.planner :as planner]
|
|
5
|
+
[polymer.conversation.scheduler :as scheduler]
|
|
6
|
+
[polymer.conversation.state :as state]
|
|
7
|
+
[polymer.stream :as stream]))
|
|
8
|
+
|
|
9
|
+
;; Conversation is a peer agency in the Polymer character society. It accepts
|
|
10
|
+
;; transcript, response, and interruption facts as data; plans local turn-taking
|
|
11
|
+
;; work; schedules those actions; then emits requests/facts that TTS, Gaze,
|
|
12
|
+
;; Prosodic, and other agencies can decide how to handle.
|
|
13
|
+
|
|
14
|
+
(defn now-ms
|
|
15
|
+
[]
|
|
16
|
+
(.now js/Date))
|
|
17
|
+
|
|
18
|
+
(defn js-command
|
|
19
|
+
[type value]
|
|
20
|
+
#js {:type type :value value})
|
|
21
|
+
|
|
22
|
+
(defn create-conversation-agency
|
|
23
|
+
[config]
|
|
24
|
+
(let [provider (if (map? config)
|
|
25
|
+
(or (:provider config)
|
|
26
|
+
(:conversationProvider config))
|
|
27
|
+
(when config
|
|
28
|
+
(or (aget config "provider")
|
|
29
|
+
(aget config "conversationProvider"))))
|
|
30
|
+
config-map (domain/data-map config)
|
|
31
|
+
state-config (dissoc config-map :provider :conversationProvider)
|
|
32
|
+
input-stream (stream/create-stream)
|
|
33
|
+
event-stream (stream/create-stream)
|
|
34
|
+
state-atom (atom (state/config->state state-config))
|
|
35
|
+
disposed? (atom false)
|
|
36
|
+
emit-input (:emit input-stream)
|
|
37
|
+
emit-event (:emit event-stream)
|
|
38
|
+
scheduler-atom (atom nil)]
|
|
39
|
+
(letfn [(dispatch! [command]
|
|
40
|
+
(when-not @disposed?
|
|
41
|
+
(let [payload (domain/data-map command)
|
|
42
|
+
plan (planner/plan-command payload @state-atom (now-ms))]
|
|
43
|
+
;; Input is observable for workers/tests and for future
|
|
44
|
+
;; diagnostics. It is not host application state and should
|
|
45
|
+
;; not be mirrored into a UI on every agency message.
|
|
46
|
+
(emit-input {:type "command"
|
|
47
|
+
:agency "conversation"
|
|
48
|
+
:command payload})
|
|
49
|
+
(when-let [agency-scheduler @scheduler-atom]
|
|
50
|
+
((:schedule agency-scheduler) payload plan)))))
|
|
51
|
+
(dispatch-type! [type]
|
|
52
|
+
(dispatch! #js {:type type}))]
|
|
53
|
+
(let [agency-scheduler (scheduler/create-scheduler
|
|
54
|
+
{:state-atom state-atom
|
|
55
|
+
:emit-event emit-event
|
|
56
|
+
:dispatch-command dispatch!
|
|
57
|
+
:provider-effector
|
|
58
|
+
(effector/create-provider-effector provider)})]
|
|
59
|
+
(reset! scheduler-atom agency-scheduler)
|
|
60
|
+
#js {:dispatch dispatch!
|
|
61
|
+
:snapshot (fn [] (state/visible-state @state-atom))
|
|
62
|
+
:input (stream/writable-port input-stream dispatch!)
|
|
63
|
+
:events (stream/readable-port event-stream)
|
|
64
|
+
:subscribeInput (fn [listener] ((:subscribe input-stream) listener))
|
|
65
|
+
:subscribeEvents (fn [listener] ((:subscribe event-stream) listener))
|
|
66
|
+
:subscribe (fn [listener] ((:subscribe event-stream) listener))
|
|
67
|
+
:subscribeStatus (fn [listener] ((:subscribe event-stream) listener))
|
|
68
|
+
:start (fn [] (dispatch-type! "start"))
|
|
69
|
+
:stop (fn [] (dispatch-type! "stop"))
|
|
70
|
+
:reset (fn [] (dispatch-type! "reset"))
|
|
71
|
+
:interrupt (fn
|
|
72
|
+
([] (dispatch-type! "interrupt"))
|
|
73
|
+
([reason] (dispatch! #js {:type "interrupt" :reason reason})))
|
|
74
|
+
:setAutoRespond (fn [value]
|
|
75
|
+
(dispatch! #js {:type "configure"
|
|
76
|
+
:config #js {:autoRespond value}}))
|
|
77
|
+
:queue (fn [] (clj->js ((:queue agency-scheduler))))
|
|
78
|
+
:dispose (fn []
|
|
79
|
+
(when-not @disposed?
|
|
80
|
+
(reset! disposed? true)
|
|
81
|
+
((:dispose agency-scheduler))
|
|
82
|
+
((:dispose input-stream))
|
|
83
|
+
((:dispose event-stream))))}))))
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
(ns polymer.conversation.domain
|
|
2
|
+
(:require [clojure.string :as str]))
|
|
3
|
+
|
|
4
|
+
;; Pure Conversation transforms live here so command normalization is shared by
|
|
5
|
+
;; state, planner, and the JS-facing agency boundary.
|
|
6
|
+
|
|
7
|
+
(defn data-map
|
|
8
|
+
[value]
|
|
9
|
+
(cond
|
|
10
|
+
(map? value) value
|
|
11
|
+
value (js->clj value :keywordize-keys true)
|
|
12
|
+
:else {}))
|
|
13
|
+
|
|
14
|
+
(defn clean-text
|
|
15
|
+
[value]
|
|
16
|
+
(let [text (str/trim (or value ""))]
|
|
17
|
+
(when (pos? (count text))
|
|
18
|
+
text)))
|
|
19
|
+
|
|
20
|
+
(defn text-or
|
|
21
|
+
[value fallback]
|
|
22
|
+
(or (clean-text value) fallback))
|
|
23
|
+
|
|
24
|
+
(defn user-text
|
|
25
|
+
[command]
|
|
26
|
+
(clean-text (or (:text command)
|
|
27
|
+
(:transcript command)
|
|
28
|
+
(:utterance command))))
|
|
29
|
+
|
|
30
|
+
(defn agent-text
|
|
31
|
+
[command]
|
|
32
|
+
(clean-text (or (:agentText command)
|
|
33
|
+
(:responseText command)
|
|
34
|
+
(:text command)
|
|
35
|
+
(:utterance command))))
|