speechflow 2.1.2 → 2.2.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.
@@ -21,7 +21,8 @@
21
21
  "typopro-web": "4.2.8",
22
22
  "@fortawesome/fontawesome-free": "7.2.0",
23
23
  "patch-package": "8.0.1",
24
- "@rse/stx": "1.1.4"
24
+ "@rse/stx": "1.1.4",
25
+ "animejs": "4.3.6"
25
26
  },
26
27
  "devDependencies": {
27
28
  "vite": "7.3.1",
@@ -43,14 +44,14 @@
43
44
  "eslint-plugin-import": "2.32.0",
44
45
  "eslint-plugin-vue": "10.8.0",
45
46
 
46
- "oxlint": "1.48.0",
47
- "eslint-plugin-oxlint": "1.48.0",
47
+ "oxlint": "1.50.0",
48
+ "eslint-plugin-oxlint": "1.50.0",
48
49
 
49
50
  "htmllint": "0.8.0",
50
51
  "htmllint-cli": "0.0.7",
51
52
 
52
53
  "check-dependencies": "2.0.0",
53
- "nodemon": "3.1.11",
54
+ "nodemon": "3.1.14",
54
55
  "shx": "0.4.0",
55
56
  "stylelint": "17.3.0",
56
57
  "stylelint-config-html": "1.1.0",
@@ -61,7 +62,7 @@
61
62
  "postcss-html": "1.8.1",
62
63
  "stylus": "0.64.0",
63
64
  "typescript": "5.9.3",
64
- "vue-tsc": "3.2.4",
65
+ "vue-tsc": "3.2.5",
65
66
  "delay-cli": "3.0.0",
66
67
  "cross-env": "10.1.0",
67
68
  "serve": "14.2.5",
@@ -26,7 +26,7 @@
26
26
  class="text-col"
27
27
  v-bind:class="{ intermediate: block.lastKind === 'intermediate' }">
28
28
  <div v-bind:key="value"
29
- v-for="value of block.value"
29
+ v-for="value in block.value"
30
30
  class="text-value">
31
31
  {{ value }}
32
32
  </div>
@@ -37,6 +37,20 @@
37
37
  </div>
38
38
  </div>
39
39
  </div>
40
+ <div class="popups">
41
+ <div class="popup-cols">
42
+ <div class="popup"
43
+ v-for="(entry) in popup"
44
+ v-bind:class="{ warning: entry.level === 'warning', error: entry.level === 'error' }"
45
+ v-bind:ref="`popup-${entry.id}`"
46
+ v-bind:key="entry.id">
47
+ <span class="time">[{{ DateTime.fromMillis(entry.time).toFormat("yyyy-MM-dd HH:mm:ss") }}]&nbsp;</span>
48
+ <span class="level">{{ entry.level.toUpperCase() }}:&nbsp;</span>
49
+ <span class="node" v-show="entry.node !== ''">{{ entry.node }}:&nbsp;</span>
50
+ <span class="message">{{ entry.message }}</span>
51
+ </div>
52
+ </div>
53
+ </div>
40
54
  </div>
41
55
  </template>
42
56
 
@@ -133,6 +147,37 @@
133
147
  .text-value:last-child
134
148
  background-color: var(--color-sig-bg-3)
135
149
  color: var(--color-sig-fg-5)
150
+ .popups
151
+ position: absolute
152
+ top: 0
153
+ left: 0
154
+ z-index: 100
155
+ width: 100%
156
+ height: 100%
157
+ display: flex
158
+ flex-direction: row
159
+ justify-content: center
160
+ align-items: flex-start
161
+ .popup-cols
162
+ display: flex
163
+ flex-direction: column
164
+ justify-content: flex-start
165
+ align-items: center
166
+ width: 30vw
167
+ .popup
168
+ width: 30vw
169
+ color: var(--color-std-fg-5)
170
+ background-color: var(--color-acc-bg-3)
171
+ margin-top: 0.3vw
172
+ padding: 0.5vw
173
+ border-radius: 0.5vw
174
+ font-size: 1.0vw
175
+ .level
176
+ font-weight: bold
177
+ &.warning
178
+ background-color: var(--color-sig-bg-3)
179
+ &.error
180
+ background-color: var(--color-sig-bg-3)
136
181
  </style>
137
182
 
138
183
  <script setup lang="ts">
@@ -140,6 +185,7 @@ import { defineComponent } from "vue"
140
185
  import { DateTime } from "luxon"
141
186
  import ReconnectingWebSocket from "@opensumi/reconnecting-websocket"
142
187
  import axios from "axios"
188
+ import * as anime from "animejs"
143
189
  </script>
144
190
 
145
191
  <script lang="ts">
@@ -162,19 +208,36 @@ type Info = {
162
208
  lufsBuffer?: LufsEntry[]
163
209
  }
164
210
 
211
+ /* type of a popup entry */
212
+ type Popup = {
213
+ id: number,
214
+ time: number,
215
+ node: string,
216
+ level: string,
217
+ message: string,
218
+ removing: boolean
219
+ }
220
+
165
221
  /* type of a websocket event message */
166
- interface WebSocketEvent {
167
- response: string,
168
- args: [ string, string, string, number | string ]
222
+ type WebSocketEvent = {
223
+ response: "DASHBOARD",
224
+ node: string,
225
+ args: [ string, string, string, number | string ]
226
+ } | {
227
+ response: "HINT",
228
+ node: string,
229
+ args: [ number, string, string ]
169
230
  }
170
231
 
171
232
  /* the Vue component */
172
233
  export default defineComponent({
173
234
  name: "app",
174
235
  data: () => ({
175
- info: [] as Info[],
176
- ws: null as ReconnectingWebSocket | null,
177
- lufsTimer: null as ReturnType<typeof setInterval> | null
236
+ info: [] as Info[],
237
+ ws: null as ReconnectingWebSocket | null,
238
+ lufsTimer: null as ReturnType<typeof setInterval> | null,
239
+ popup: [] as Popup[],
240
+ popupTimer: null as ReturnType<typeof setInterval> | null
178
241
  }),
179
242
  async mounted () {
180
243
  /* determine API URLs */
@@ -196,6 +259,11 @@ export default defineComponent({
196
259
  this.updateLufsDisplayValues()
197
260
  }, 50)
198
261
 
262
+ /* start timer to remove expired popups */
263
+ this.popupTimer = setInterval(() => {
264
+ this.removeExpiredPopups()
265
+ }, 50)
266
+
199
267
  /* connect to WebSocket API for receiving dashboard information */
200
268
  this.ws = new ReconnectingWebSocket(urlWS.toString(), [], {
201
269
  reconnectionDelayGrowFactor: 1.3,
@@ -209,12 +277,34 @@ export default defineComponent({
209
277
  })
210
278
 
211
279
  /* track connection open/close */
280
+ let initially = true
281
+ let popupId = 0
212
282
  this.ws.addEventListener("open", (ev) => {
213
- this.log("INFO", "WebSocket connection established")
283
+ if (initially) {
284
+ initially = false
285
+ this.log("INFO", "WebSocket server connection: opened")
286
+ this.popup.unshift({
287
+ id: popupId++,
288
+ time: Date.now(),
289
+ node: "",
290
+ level: "info",
291
+ message: "server connection: opened",
292
+ removing: false
293
+ })
294
+ }
295
+ else {
296
+ this.log("INFO", "WebSocket server connection: re-opened")
297
+ this.popup.unshift({
298
+ id: popupId++,
299
+ time: Date.now(),
300
+ node: "",
301
+ level: "info",
302
+ message: "server connection: re-opened",
303
+ removing: false
304
+ })
305
+ }
214
306
  })
215
307
  this.ws.addEventListener("close", (ev) => {
216
- this.log("INFO", "WebSocket connection destroyed")
217
-
218
308
  /* reset meters and clear LUFS buffers */
219
309
  for (const block of this.info) {
220
310
  if (block.type === "audio" && block.lufsBuffer !== undefined) {
@@ -222,6 +312,17 @@ export default defineComponent({
222
312
  block.lufsBuffer.length = 0
223
313
  }
224
314
  }
315
+
316
+ /* drop notice */
317
+ this.log("INFO", "WebSocket server connection: closed")
318
+ this.popup.unshift({
319
+ id: popupId++,
320
+ time: Date.now(),
321
+ node: "",
322
+ level: "warning",
323
+ message: "server connection: closed",
324
+ removing: false
325
+ })
225
326
  })
226
327
 
227
328
  /* receive messages */
@@ -234,36 +335,40 @@ export default defineComponent({
234
335
  this.log("ERROR", "Failed to parse WebSocket message", { error, data: ev.data })
235
336
  return
236
337
  }
237
- if (event.response !== "DASHBOARD")
238
- return
239
-
240
- /* extract dashboard update parameters: [ type, id, kind, value ] */
241
- const [ type, id, kind, value ] = event.args
242
- for (const block of this.info) {
243
- if (block.type === type && block.id === id) {
244
- if (block.type === "audio" && block.lufsBuffer !== undefined) {
245
- /* buffer LUFS values for temporal averaging */
246
- if (kind === "final" && typeof value === "number")
247
- block.lufsBuffer.push({ value, time: Date.now() })
248
- }
249
- else {
250
- if (typeof value === "string") {
251
- const arr = block.value as string[]
252
- if (block.lastKind === "intermediate")
253
- arr[arr.length - 1] = value
254
- else {
255
- arr.push(value)
256
- block.value = arr.slice(-20)
338
+ if (event.response === "DASHBOARD") {
339
+ /* extract dashboard update parameters: [ type, id, kind, value ] */
340
+ const [ type, id, kind, value ] = event.args
341
+ for (const block of this.info) {
342
+ if (block.type === type && block.id === id) {
343
+ if (block.type === "audio" && block.lufsBuffer !== undefined) {
344
+ /* buffer LUFS values for temporal averaging */
345
+ if (kind === "final" && typeof value === "number")
346
+ block.lufsBuffer.push({ value, time: Date.now() })
347
+ }
348
+ else {
349
+ if (typeof value === "string") {
350
+ const arr = block.value as string[]
351
+ if (block.lastKind === "intermediate")
352
+ arr[arr.length - 1] = value
353
+ else {
354
+ arr.push(value)
355
+ block.value = arr.slice(-20)
356
+ }
257
357
  }
358
+ block.lastKind = kind
359
+ this.$nextTick(() => {
360
+ for (const textCol of this.$refs.textCol as HTMLDivElement[])
361
+ textCol.scrollTop = textCol.scrollHeight
362
+ })
258
363
  }
259
- block.lastKind = kind
260
- this.$nextTick(() => {
261
- for (const textCol of this.$refs.textCol as HTMLDivElement[])
262
- textCol.scrollTop = textCol.scrollHeight
263
- })
264
364
  }
265
365
  }
266
366
  }
367
+ else if (event.response === "HINT") {
368
+ const node = event.node
369
+ const [ time, level, message ] = event.args
370
+ this.popup.unshift({ id: popupId++, time, node, level, message, removing: false })
371
+ }
267
372
  })
268
373
  },
269
374
  beforeUnmount () {
@@ -271,6 +376,10 @@ export default defineComponent({
271
376
  clearInterval(this.lufsTimer)
272
377
  this.lufsTimer = null
273
378
  }
379
+ if (this.popupTimer) {
380
+ clearInterval(this.popupTimer)
381
+ this.popupTimer = null
382
+ }
274
383
  if (this.ws) {
275
384
  this.ws.close()
276
385
  this.ws = null
@@ -308,6 +417,25 @@ export default defineComponent({
308
417
  else
309
418
  block.value = -60
310
419
  }
420
+ },
421
+
422
+ /* remove expired popups */
423
+ removeExpiredPopups () {
424
+ this.popup.forEach((entry) => {
425
+ if (entry.time < Date.now() - 8000 && !entry.removing) {
426
+ const el = (this.$refs[`popup-${entry.id}`] as HTMLDivElement[])[0]
427
+ entry.removing = true
428
+ anime.animate(el, {
429
+ opacity: [ 1, 0 ],
430
+ duration: 2000,
431
+ easing: "easeOutQuad",
432
+ onComplete: () => {
433
+ const idx = this.popup.findIndex((e) => e.id === entry.id)
434
+ this.popup.splice(idx, 1)
435
+ }
436
+ })
437
+ }
438
+ })
311
439
  }
312
440
  }
313
441
  })