@goodandready/dsh-tts 0.4.6 → 0.4.7
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 +21 -2
- package/README.ru.md +10 -1
- package/README.zh.md +23 -4
- package/lib/client-src/20-player.js +33 -0
- package/lib/client-src/30-locales.js +205 -195
- package/lib/client-src/40-editors.js +2 -1
- package/lib/client-src/50-settings.js +1 -0
- package/lib/client-src/60-card-dock.js +11 -1
- package/lib/client.js +253 -198
- package/lib/index.js +8 -2
- package/lib/text.js +28 -7
- package/package.json +1 -1
package/lib/text.js
CHANGED
|
@@ -16,11 +16,14 @@ export function blocksToText(content) {
|
|
|
16
16
|
export const SPEECH_PHRASES = {
|
|
17
17
|
en: { codeBlock: 'code block, {n} lines', table: 'table, {n} rows', summaryIntro: 'Summary of the reply.' },
|
|
18
18
|
ru: { codeBlock: 'блок кода, {n} строк', table: 'таблица, {n} строк', summaryIntro: 'Пересказ ответа.' },
|
|
19
|
+
zh: { codeBlock: '代码块,共 {n} 行', table: '表格,共 {n} 行', summaryIntro: '回复摘要。' },
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
export function speechPhrases(language) {
|
|
22
23
|
const lang = String(language || '').toLowerCase()
|
|
23
|
-
|
|
24
|
+
if (lang.startsWith('zh')) return SPEECH_PHRASES.zh
|
|
25
|
+
if (lang.startsWith('ru')) return SPEECH_PHRASES.ru
|
|
26
|
+
return SPEECH_PHRASES.en
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
// Narration filters run on top of general scrubbing, before pronunciation.
|
|
@@ -43,8 +46,10 @@ export function applyNarrationFilters(text, cfg) {
|
|
|
43
46
|
// Rough language heuristic: whichever script has more letters wins.
|
|
44
47
|
export function detectLang(text) {
|
|
45
48
|
const s = String(text || '')
|
|
49
|
+
const cjk = (s.match(/[\u4E00-\u9FFF]/g) || []).length
|
|
46
50
|
const cyr = (s.match(/[\u0400-\u04FF]/g) || []).length
|
|
47
51
|
const lat = (s.match(/[A-Za-z]/g) || []).length
|
|
52
|
+
if (cjk >= cyr && cjk >= lat && cjk > 0) return 'zh'
|
|
48
53
|
return cyr > lat ? 'ru' : 'en'
|
|
49
54
|
}
|
|
50
55
|
|
|
@@ -165,7 +170,14 @@ export function protectAbbreviations(text) {
|
|
|
165
170
|
const rx = new RegExp('(?<=^|[\\s"«(])' + escaped + '(?=[\\s"»)\\]]|$)', 'gi')
|
|
166
171
|
res = res.replace(rx, (m) => m.replace(/\./g, '\u2024'))
|
|
167
172
|
}
|
|
173
|
+
// Decimal numbers and versions: e.g. 3.14, v0.4.6, 192.168.1.111
|
|
168
174
|
res = res.replace(/(?<=[vV]?\d+)\.(?=\d+)/g, '\u2024')
|
|
175
|
+
// List numbering at line/word starts: 1. item, 2. item
|
|
176
|
+
res = res.replace(/(?<=^|[\s\n])(\d+)\.(?=\s+[A-Za-zА-Яа-я\u4E00-\u9FFF])/g, '$1\u2024')
|
|
177
|
+
// Common file extensions: e.g. package.json, index.js, script.py
|
|
178
|
+
res = res.replace(/(?<=[a-zA-Z0-9_-])\.(?:js|mjs|cjs|ts|tsx|jsx|json|ya?ml|md|py|sh|tgz|tar|gz|zip|html?|css|rs|go|c|cpp|h|txt)(?=[ \t\n"«'\)\]\},;\.\?!]|$)/gi, (m) => m.replace(/\./g, '\u2024'))
|
|
179
|
+
// Hostnames and domains: e.g. goodandready.app, github.com
|
|
180
|
+
res = res.replace(/(?<=[a-zA-Z0-9])\.(?:com|org|net|io|dev|app|ru|cn|local)(?=[ \t\n/:"«'\)\]\},;\.\?!]|$)/gi, (m) => m.replace(/\./g, '\u2024'))
|
|
169
181
|
return res
|
|
170
182
|
}
|
|
171
183
|
|
|
@@ -186,20 +198,29 @@ export function restoreAbbreviations(text) {
|
|
|
186
198
|
*/
|
|
187
199
|
export function splitSentences(text, maxChars) {
|
|
188
200
|
const limit = Number(maxChars) > 0 ? Number(maxChars) : 320
|
|
189
|
-
const min = Math.min(60, Math.floor(limit / 3))
|
|
190
201
|
const raw = String(text || '').trim()
|
|
191
202
|
if (!raw) return []
|
|
192
203
|
|
|
204
|
+
const isCjk = /[\u4E00-\u9FFF]/.test(raw)
|
|
205
|
+
// CJK characters carry much more meaning per character than Latin/Cyrillic words.
|
|
206
|
+
const min = isCjk ? Math.min(10, Math.floor(limit / 5)) : Math.min(60, Math.floor(limit / 3))
|
|
207
|
+
|
|
193
208
|
const source = protectAbbreviations(raw)
|
|
194
209
|
const pieces = []
|
|
195
210
|
let current = ''
|
|
196
|
-
// Boundaries are period, question, exclamation, ellipsis, and newline.
|
|
197
|
-
for (const part of source.split(/(?<=[
|
|
211
|
+
// Boundaries are period, question, exclamation, ellipsis, Chinese full-width punctuation, and newline.
|
|
212
|
+
for (const part of source.split(/(?<=[.!?…\u3002\uFF01\uFF1F])\s*|\n+/)) {
|
|
198
213
|
const chunk = String(part || '').trim()
|
|
199
214
|
if (!chunk) continue
|
|
200
|
-
if (!current) {
|
|
201
|
-
|
|
202
|
-
|
|
215
|
+
if (!current) {
|
|
216
|
+
current = chunk
|
|
217
|
+
} else if (current.length < min) {
|
|
218
|
+
current += (isCjk ? '' : ' ') + chunk
|
|
219
|
+
} else {
|
|
220
|
+
pieces.push(current)
|
|
221
|
+
current = chunk
|
|
222
|
+
}
|
|
223
|
+
// Oversized pieces are split on words or characters: providers choke on walls of text.
|
|
203
224
|
while (current.length > limit) {
|
|
204
225
|
let cut = current.lastIndexOf(' ', limit)
|
|
205
226
|
if (cut < min) cut = limit
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-tts",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"description": "Text-to-speech for DeepSeek Harness: speak agent replies in the Web UI with a provider fallback chain (OpenAI, ElevenLabs, Google, Azure, Groq, Deepgram, OpenRouter, Edge, Piper, eSpeak).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|