speechflow 1.6.3 → 1.6.4
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/.claude/settings.local.json +3 -0
- package/CHANGELOG.md +10 -0
- package/README.md +82 -45
- package/etc/speechflow.yaml +19 -14
- package/package.json +5 -5
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js +2 -1
- package/speechflow-cli/dst/speechflow-node-a2a-compressor.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js +2 -1
- package/speechflow-cli/dst/speechflow-node-a2a-expander.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js +4 -14
- package/speechflow-cli/dst/speechflow-node-a2a-gender.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js +11 -8
- package/speechflow-cli/dst/speechflow-node-a2a-meter.js.map +1 -1
- package/speechflow-cli/dst/speechflow-node-x2x-trace.d.ts +1 -0
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js +22 -2
- package/speechflow-cli/dst/speechflow-node-x2x-trace.js.map +1 -1
- package/speechflow-cli/etc/stx.conf +6 -10
- package/speechflow-cli/package.json +13 -13
- package/speechflow-cli/src/speechflow-node-a2a-compressor.ts +2 -1
- package/speechflow-cli/src/speechflow-node-a2a-expander.ts +2 -1
- package/speechflow-cli/src/speechflow-node-a2a-meter.ts +11 -8
- package/speechflow-cli/src/speechflow-node-x2x-trace.ts +25 -2
- package/speechflow-ui-db/dst/index.js +24 -33
- package/speechflow-ui-db/package.json +12 -10
- package/speechflow-ui-db/src/app.vue +16 -2
- package/speechflow-ui-st/.claude/settings.local.json +3 -0
- package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-BoldIt.eot +0 -0
- package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-BoldIt.ttf +0 -0
- package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-BoldIt.woff +0 -0
- package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-RegularIt.eot +0 -0
- package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-RegularIt.ttf +0 -0
- package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-RegularIt.woff +0 -0
- package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-Semibold.eot +0 -0
- package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-Semibold.ttf +0 -0
- package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-Semibold.woff +0 -0
- package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-SemiboldIt.eot +0 -0
- package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-SemiboldIt.ttf +0 -0
- package/speechflow-ui-st/dst/app-font-TypoPRO-SourceSansPro-SemiboldIt.woff +0 -0
- package/speechflow-ui-st/dst/index.css +2 -2
- package/speechflow-ui-st/dst/index.js +26 -28
- package/speechflow-ui-st/package.json +11 -10
- package/speechflow-ui-st/src/app.vue +142 -48
- package/speechflow-ui-st/src/index.ts +4 -0
|
@@ -16,20 +16,20 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"vue": "3.5.21",
|
|
18
18
|
"vue3-spinners": "1.3.1",
|
|
19
|
-
"moment": "2.30.1",
|
|
20
19
|
"luxon": "3.7.2",
|
|
21
20
|
"@opensumi/reconnecting-websocket": "4.4.0",
|
|
22
|
-
"axios": "1.
|
|
21
|
+
"axios": "1.12.1",
|
|
23
22
|
"typopro-web": "4.2.7",
|
|
24
23
|
"@fortawesome/fontawesome-free": "7.0.1",
|
|
25
24
|
"patch-package": "8.0.0",
|
|
26
|
-
"@rse/stx": "1.1.
|
|
25
|
+
"@rse/stx": "1.1.1",
|
|
26
|
+
"animejs": "4.1.3"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"vite": "7.1.
|
|
30
|
-
"typescript-eslint": "8.
|
|
31
|
-
"@typescript-eslint/eslint-plugin": "8.
|
|
32
|
-
"@typescript-eslint/parser": "8.
|
|
29
|
+
"vite": "7.1.5",
|
|
30
|
+
"typescript-eslint": "8.43.0",
|
|
31
|
+
"@typescript-eslint/eslint-plugin": "8.43.0",
|
|
32
|
+
"@typescript-eslint/parser": "8.43.0",
|
|
33
33
|
"@vitejs/plugin-vue": "6.0.1",
|
|
34
34
|
"@rollup/plugin-yaml": "4.1.2",
|
|
35
35
|
"vite-plugin-node-polyfills": "0.24.0",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
"nodemon": "3.1.10",
|
|
52
52
|
"shx": "0.4.0",
|
|
53
|
-
"stylelint": "16.
|
|
53
|
+
"stylelint": "16.24.0",
|
|
54
54
|
"stylelint-config-html": "1.1.0",
|
|
55
55
|
"stylelint-config-recommended-vue": "1.6.1",
|
|
56
56
|
"stylelint-config-standard": "39.0.0",
|
|
@@ -59,12 +59,13 @@
|
|
|
59
59
|
"postcss-html": "1.8.0",
|
|
60
60
|
"stylus": "0.64.0",
|
|
61
61
|
"typescript": "5.9.2",
|
|
62
|
-
"vue-tsc": "3.0.
|
|
62
|
+
"vue-tsc": "3.0.7",
|
|
63
63
|
"delay-cli": "2.0.0",
|
|
64
64
|
"cross-env": "10.0.0",
|
|
65
65
|
"serve": "14.2.5",
|
|
66
66
|
|
|
67
|
-
"@types/luxon": "3.7.1"
|
|
67
|
+
"@types/luxon": "3.7.1",
|
|
68
|
+
"@types/animejs": "3.1.13"
|
|
68
69
|
},
|
|
69
70
|
"overrides": {
|
|
70
71
|
"@liuli-util/vite-plugin-node": { "vite": ">=6.0.0" }
|
|
@@ -8,23 +8,25 @@
|
|
|
8
8
|
|
|
9
9
|
<template>
|
|
10
10
|
<div class="app">
|
|
11
|
-
<div class="
|
|
12
|
-
<div class="
|
|
13
|
-
v-bind:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class="
|
|
17
|
-
{
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
<div class="area">
|
|
12
|
+
<div class="block" ref="block">
|
|
13
|
+
<span v-bind:key="chunk.id"
|
|
14
|
+
v-for="(chunk, idx) of text"
|
|
15
|
+
class="chunk"
|
|
16
|
+
v-bind:class="{ intermediate: chunk.kind === 'intermediate', removed: chunk.removed }"
|
|
17
|
+
v-bind:ref="`chunk-${chunk.id}`">
|
|
18
|
+
{{ chunk.text }}
|
|
19
|
+
<span class="cursor" v-if="idx === (text.length - 1) && chunk.kind === 'intermediate'">
|
|
20
|
+
<spinner-grid class="spinner-grid" size="50"/>
|
|
20
21
|
</span>
|
|
21
|
-
</
|
|
22
|
+
</span>
|
|
22
23
|
</div>
|
|
23
24
|
</div>
|
|
24
25
|
</div>
|
|
25
26
|
</template>
|
|
26
27
|
|
|
27
28
|
<style lang="stylus">
|
|
29
|
+
/* entire app */
|
|
28
30
|
.app
|
|
29
31
|
position: relative
|
|
30
32
|
width: 100vw
|
|
@@ -35,53 +37,74 @@
|
|
|
35
37
|
flex-direction: column
|
|
36
38
|
justify-content: center
|
|
37
39
|
align-items: center
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
|
|
41
|
+
/* rendering area */
|
|
42
|
+
.area
|
|
43
|
+
height: auto
|
|
44
|
+
min-height: 30vh
|
|
45
|
+
max-height: 60vh
|
|
46
|
+
width: 75vw
|
|
41
47
|
position: absolute
|
|
42
|
-
bottom:
|
|
43
|
-
|
|
48
|
+
bottom: 4vh
|
|
49
|
+
mask: linear-gradient(to bottom, transparent 0%, black 50%)
|
|
50
|
+
overflow-x: hidden
|
|
51
|
+
overflow-y: hidden
|
|
52
|
+
display: flex
|
|
53
|
+
flex-direction: column
|
|
54
|
+
align-items: center
|
|
55
|
+
justify-content: flex-end
|
|
56
|
+
|
|
57
|
+
/* content block */
|
|
58
|
+
.block
|
|
59
|
+
display: block
|
|
60
|
+
text-align: left
|
|
44
61
|
width: 100%
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
padding: 0.5vw 1.0vw
|
|
59
|
-
margin-bottom: 0.5vw
|
|
60
|
-
overflow-wrap: break-word
|
|
62
|
+
overflow-wrap: break-word
|
|
63
|
+
|
|
64
|
+
/* content chunk */
|
|
65
|
+
.chunk
|
|
66
|
+
color: #eeeeee
|
|
67
|
+
background-color: #000000b0
|
|
68
|
+
text-shadow: 0 0 0.20vw #000000
|
|
69
|
+
-webkit-text-stroke: 0.05vw #000000
|
|
70
|
+
font-size: 2.0vw
|
|
71
|
+
font-weight: 600
|
|
72
|
+
line-height: 1.4
|
|
73
|
+
padding-left: 0.20vw
|
|
74
|
+
padding-right: 0.20vw
|
|
61
75
|
.cursor
|
|
62
76
|
display: inline-block
|
|
63
77
|
margin-left: 10px
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
78
|
+
&.intermediate:last-child
|
|
79
|
+
color: #ffffff
|
|
80
|
+
&.removed
|
|
81
|
+
opacity: 0
|
|
67
82
|
</style>
|
|
68
83
|
|
|
69
84
|
<script setup lang="ts">
|
|
70
|
-
import { defineComponent }
|
|
71
|
-
import { VueSpinnerGrid }
|
|
72
|
-
import
|
|
73
|
-
import
|
|
74
|
-
import
|
|
85
|
+
import { defineComponent } from "vue"
|
|
86
|
+
import { VueSpinnerGrid } from "vue3-spinners"
|
|
87
|
+
import { DateTime, Duration } from "luxon"
|
|
88
|
+
import ReconnectingWebSocket from "@opensumi/reconnecting-websocket"
|
|
89
|
+
import * as anime from "animejs"
|
|
75
90
|
</script>
|
|
76
91
|
|
|
77
92
|
<script lang="ts">
|
|
93
|
+
type TextChunk = {
|
|
94
|
+
id: string,
|
|
95
|
+
timestamp: DateTime,
|
|
96
|
+
kind: "intermediate" | "final",
|
|
97
|
+
text: string,
|
|
98
|
+
removing: boolean,
|
|
99
|
+
removed: boolean
|
|
100
|
+
}
|
|
78
101
|
type SpeechFlowChunk = {
|
|
79
102
|
timestampStart: Duration,
|
|
80
103
|
timestampEnd: Duration,
|
|
81
104
|
kind: "intermediate" | "final",
|
|
82
105
|
type: "text",
|
|
83
106
|
payload: string,
|
|
84
|
-
meta: Map<string,
|
|
107
|
+
meta: Map<string, unknown>
|
|
85
108
|
}
|
|
86
109
|
export default defineComponent({
|
|
87
110
|
name: "app",
|
|
@@ -89,13 +112,39 @@ export default defineComponent({
|
|
|
89
112
|
"spinner-grid": VueSpinnerGrid
|
|
90
113
|
},
|
|
91
114
|
data: () => ({
|
|
92
|
-
text: [] as
|
|
93
|
-
|
|
115
|
+
text: [] as TextChunk[],
|
|
116
|
+
chunkIdCounter: 0,
|
|
117
|
+
cleanupIntervalId: null as ReturnType<typeof setInterval> | null
|
|
94
118
|
}),
|
|
95
119
|
async mounted () {
|
|
96
120
|
/* determine API URL */
|
|
97
121
|
const url = new URL("/api", document.location.href).toString()
|
|
98
122
|
|
|
123
|
+
/* optically remove outdated text chunks */
|
|
124
|
+
this.cleanupIntervalId = setInterval(() => {
|
|
125
|
+
for (const chunk of this.text) {
|
|
126
|
+
if (chunk.timestamp < DateTime.now().minus({ seconds: 20 }) && !chunk.removing && !chunk.removed) {
|
|
127
|
+
const el = this.$refs[`chunk-${chunk.id}`] as HTMLSpanElement
|
|
128
|
+
if (!el)
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
/* start removing */
|
|
132
|
+
chunk.removing = true
|
|
133
|
+
chunk.removed = false
|
|
134
|
+
anime.animate(el, {
|
|
135
|
+
opacity: [ 1, 0 ],
|
|
136
|
+
duration: 2000,
|
|
137
|
+
easing: "easeOutQuad",
|
|
138
|
+
onComplete: () => {
|
|
139
|
+
/* end removing */
|
|
140
|
+
chunk.removing = false
|
|
141
|
+
chunk.removed = true
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}, 500)
|
|
147
|
+
|
|
99
148
|
/* connect to WebSocket API for receiving dashboard information */
|
|
100
149
|
const ws = new ReconnectingWebSocket(url, [], {
|
|
101
150
|
reconnectionDelayGrowFactor: 1.3,
|
|
@@ -104,7 +153,18 @@ export default defineComponent({
|
|
|
104
153
|
connectionTimeout: 4000,
|
|
105
154
|
minUptime: 5000
|
|
106
155
|
})
|
|
156
|
+
|
|
157
|
+
/* track connection open/close */
|
|
158
|
+
ws.addEventListener("open", (ev) => {
|
|
159
|
+
this.log("INFO", "WebSocket connection established")
|
|
160
|
+
})
|
|
161
|
+
ws.addEventListener("close", (ev) => {
|
|
162
|
+
this.log("INFO", "WebSocket connection destroyed")
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
/* receive messages */
|
|
107
166
|
ws.addEventListener("message", (ev) => {
|
|
167
|
+
/* parse message */
|
|
108
168
|
let chunk: SpeechFlowChunk
|
|
109
169
|
try {
|
|
110
170
|
chunk = JSON.parse(ev.data)
|
|
@@ -113,18 +173,52 @@ export default defineComponent({
|
|
|
113
173
|
this.log("ERROR", "Failed to parse WebSocket message", { error, data: ev.data })
|
|
114
174
|
return
|
|
115
175
|
}
|
|
116
|
-
|
|
117
|
-
|
|
176
|
+
|
|
177
|
+
/* process text chunks */
|
|
178
|
+
if (this.text.length > 0 && this.text[this.text.length - 1].kind === "intermediate") {
|
|
179
|
+
/* override previous intermediate text chunk
|
|
180
|
+
with either another intermediate one or a final one */
|
|
181
|
+
const lastChunk = this.text[this.text.length - 1]
|
|
182
|
+
lastChunk.text = chunk.payload
|
|
183
|
+
lastChunk.kind = chunk.kind
|
|
184
|
+
lastChunk.timestamp = DateTime.now()
|
|
185
|
+
}
|
|
118
186
|
else {
|
|
119
|
-
|
|
120
|
-
|
|
187
|
+
/* remove content in case all chunks were removed
|
|
188
|
+
(this way new next chunk starts at the left edge again
|
|
189
|
+
and the overall memory consumption is reduced) */
|
|
190
|
+
if (this.text.every((chunk) => chunk.removed))
|
|
191
|
+
this.text = []
|
|
192
|
+
|
|
193
|
+
/* add new text chunk */
|
|
194
|
+
this.text.push({
|
|
195
|
+
id: `chunk-${this.chunkIdCounter++}`,
|
|
196
|
+
text: chunk.payload,
|
|
197
|
+
kind: chunk.kind,
|
|
198
|
+
timestamp: DateTime.now(),
|
|
199
|
+
removing: false,
|
|
200
|
+
removed: false
|
|
201
|
+
})
|
|
121
202
|
}
|
|
122
|
-
|
|
203
|
+
|
|
204
|
+
/* ensure we always scrolled the chunks inside the block to the bottom */
|
|
205
|
+
this.$nextTick(() => {
|
|
206
|
+
const block = this.$refs.block as HTMLDivElement
|
|
207
|
+
block.scrollTop = block.scrollHeight
|
|
208
|
+
})
|
|
123
209
|
})
|
|
124
210
|
},
|
|
211
|
+
beforeUnmount () {
|
|
212
|
+
/* cleanup */
|
|
213
|
+
if (this.cleanupIntervalId !== null) {
|
|
214
|
+
clearInterval(this.cleanupIntervalId)
|
|
215
|
+
this.cleanupIntervalId = null
|
|
216
|
+
}
|
|
217
|
+
},
|
|
125
218
|
methods: {
|
|
219
|
+
/* helper function for console logging */
|
|
126
220
|
log (level: string, msg: string, data: { [ key: string ]: any } | null = null) {
|
|
127
|
-
const timestamp =
|
|
221
|
+
const timestamp = DateTime.now().toFormat("yyyy-MM-dd HH:mm:ss.SSS")
|
|
128
222
|
let output = `${timestamp} [${level}]: ${msg}`
|
|
129
223
|
if (data !== null)
|
|
130
224
|
output += ` (${Object.keys(data)
|
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
/* external dependencies */
|
|
8
8
|
import * as Vue from "vue"
|
|
9
9
|
import "typopro-web/web/TypoPRO-SourceSansPro/TypoPRO-SourceSansPro-Regular.css"
|
|
10
|
+
import "typopro-web/web/TypoPRO-SourceSansPro/TypoPRO-SourceSansPro-RegularIt.css"
|
|
11
|
+
import "typopro-web/web/TypoPRO-SourceSansPro/TypoPRO-SourceSansPro-Semibold.css"
|
|
12
|
+
import "typopro-web/web/TypoPRO-SourceSansPro/TypoPRO-SourceSansPro-SemiboldIt.css"
|
|
10
13
|
import "typopro-web/web/TypoPRO-SourceSansPro/TypoPRO-SourceSansPro-Bold.css"
|
|
14
|
+
import "typopro-web/web/TypoPRO-SourceSansPro/TypoPRO-SourceSansPro-BoldIt.css"
|
|
11
15
|
import "@fortawesome/fontawesome-free/js/all.min.js"
|
|
12
16
|
import "@fortawesome/fontawesome-free/css/all.min.css"
|
|
13
17
|
|