dsh-fish-pet 0.2.5 → 0.2.6
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/CHANGELOG.md +10 -0
- package/README.md +44 -28
- package/assets/asr-listener.ps1 +1 -1
- package/assets/fish-pet.ps1 +161 -18
- package/assets/tts_worker.py +12 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.6] - 2026-10-31
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- 回复长度限制 60 字 → **30 字**(越短开口越快)
|
|
7
|
+
- 常驻 TTS 进程:单句合成 4 秒 → **2 秒**
|
|
8
|
+
- 分段流水线:先合成第一句就开播,后面边播边合成,开口等待再减半
|
|
9
|
+
- 中英历史隔离:中文模式只带中文历史、英文模式只带英文历史,避免串味
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- 中文提问偶尔用英文回答(历史残留英文带跑)
|
|
3
13
|
## [0.2.5] - 2026-10-31
|
|
4
14
|
|
|
5
15
|
### Added
|
package/README.md
CHANGED
|
@@ -139,34 +139,50 @@ powershell -ExecutionPolicy Bypass -File tools\encrypt-keys.ps1
|
|
|
139
139
|
|
|
140
140
|
之后鱼和耳朵会优先读 `deepseek.key.enc` / `siliconflow.key.enc`,明文文件会被自动删除。
|
|
141
141
|
|
|
142
|
-
## 配置文件
|
|
143
|
-
|
|
144
|
-
首次启动会生成 `%USERPROFILE%\.dsh\fish-pet\config.json`,可以改:
|
|
145
|
-
|
|
146
|
-
```json
|
|
147
|
-
{
|
|
148
|
-
"persona": {
|
|
149
|
-
"name": "大肥鱼",
|
|
150
|
-
"
|
|
151
|
-
"
|
|
152
|
-
"
|
|
153
|
-
"
|
|
154
|
-
"
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
"
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
-
|
|
168
|
-
|
|
169
|
-
|
|
142
|
+
## 配置文件
|
|
143
|
+
|
|
144
|
+
首次启动会生成 `%USERPROFILE%\.dsh\fish-pet\config.json`,可以改:
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"persona": {
|
|
149
|
+
"name": "大肥鱼",
|
|
150
|
+
"nameEn": "Mia",
|
|
151
|
+
"dad": "光光",
|
|
152
|
+
"mom": "丽丽",
|
|
153
|
+
"birthday": "2026年10月30日",
|
|
154
|
+
"birthplace": "中国广西北海",
|
|
155
|
+
"likes": "白饭、被摸头、主人夸它",
|
|
156
|
+
"dislikes": "被说胖、被冷落、吵",
|
|
157
|
+
"lang": "auto",
|
|
158
|
+
"extra": "你说话简短、俏皮、微微傲娇,称呼用户为\"主人\",每次回复不超过30个字,不要用表情符号。"
|
|
159
|
+
},
|
|
160
|
+
"bubble": { "opacity": 0.15 },
|
|
161
|
+
"voice": { "silenceGate": 600, "silenceMs": 900, "boost": 1, "engine": "cloud", "localConf": 0.55 },
|
|
162
|
+
"tts": {
|
|
163
|
+
"engine": "edge",
|
|
164
|
+
"voice": "zh-CN-XiaoxiaoNeural",
|
|
165
|
+
"rate": "+12%",
|
|
166
|
+
"pitch": "+10Hz",
|
|
167
|
+
"voiceEn": "en-US-AnaNeural",
|
|
168
|
+
"rateEn": "+8%",
|
|
169
|
+
"pitchEn": "+15Hz"
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
- `persona.name`:中文名(说中文时自称)
|
|
175
|
+
- `persona.nameEn`:**英文名**(说英文时自称,默认 `Mia`/米娅)
|
|
176
|
+
- `persona.dad` / `mom` / `birthday` / `birthplace`:家人、生日、出生地
|
|
177
|
+
- `persona.likes` / `dislikes`:喜好、厌恶
|
|
178
|
+
- `persona.lang`:语言模式,`zh`(只用中文)/ `en`(只用英文)/ `auto`(跟着你说,默认)
|
|
179
|
+
- `persona.extra`:说话风格与长度限制(默认 30 字以内,回复越短开口越快)
|
|
180
|
+
- `bubble.opacity`:气泡文字透明度(0 = 全透明,1 = 不透明)
|
|
181
|
+
- `voice.silenceGate`:说话要多响才算"开始说话"(越小越灵敏,默认 600)
|
|
182
|
+
- `voice.silenceMs`:说完后等多久算"讲完了"(英文建议 900~1100,太小会把句子切断)
|
|
183
|
+
- `voice.boost`:麦克风增益倍数
|
|
184
|
+
- `voice.engine`:语音识别引擎,`cloud`(硅基流动 Qwen3-ASR,最准)/ `local`(Windows 自带,免费但吃字)/ `auto`(本地优先、云端兜底)
|
|
185
|
+
- `voice.localConf`:`auto` 模式下本地识别置信度门槛,低于它就转云端
|
|
170
186
|
|
|
171
187
|
### 语音朗读(TTS)
|
|
172
188
|
|
package/assets/asr-listener.ps1
CHANGED
package/assets/fish-pet.ps1
CHANGED
|
@@ -49,7 +49,7 @@ $StatePath = Join-Path $DataDir 'state.json'
|
|
|
49
49
|
$ConfigPath = Join-Path $DataDir 'config.json'
|
|
50
50
|
function Load-Config {
|
|
51
51
|
$def = [ordered]@{
|
|
52
|
-
persona = [ordered]@{ name = '大肥鱼'; nameEn = 'Mia'; dad = '光光'; mom = '丽丽'; birthday = '2026年10月30日'; birthplace = '中国广西北海'; likes = '白饭、被摸头、主人夸它'; dislikes = '被说胖、被冷落、吵'; ownerBirthday = ''; lang = 'auto'; extra = '你说话简短、俏皮、微微傲娇,用中文,称呼用户为"主人",每次回复不超过
|
|
52
|
+
persona = [ordered]@{ name = '大肥鱼'; nameEn = 'Mia'; dad = '光光'; mom = '丽丽'; birthday = '2026年10月30日'; birthplace = '中国广西北海'; likes = '白饭、被摸头、主人夸它'; dislikes = '被说胖、被冷落、吵'; ownerBirthday = ''; lang = 'auto'; extra = '你说话简短、俏皮、微微傲娇,用中文,称呼用户为"主人",每次回复不超过30个字,不要用表情符号。' }
|
|
53
53
|
bubble = [ordered]@{ opacity = 0.15 }
|
|
54
54
|
voice = [ordered]@{ silenceGate = 1200; boost = 1; engine = 'cloud'; localConf = 0.55 }
|
|
55
55
|
tts = [ordered]@{ engine = 'edge'; voice = 'zh-CN-XiaoxiaoNeural'; rate = '+12%'; pitch = '+10Hz'; voiceEn = 'en-US-AnaNeural'; rateEn = '+8%'; pitchEn = '+15Hz' }
|
|
@@ -1137,38 +1137,135 @@ function Play-TtsFile([string]$mp3, [string]$voice) {
|
|
|
1137
1137
|
if ($null -eq $script:mp) {
|
|
1138
1138
|
Add-Type -AssemblyName PresentationCore
|
|
1139
1139
|
$script:mp = New-Object System.Windows.Media.MediaPlayer
|
|
1140
|
-
$script:mp.Add_MediaEnded({
|
|
1140
|
+
$script:mp.Add_MediaEnded({ Complete-TtsSegment })
|
|
1141
1141
|
$script:mp.Add_MediaOpened({
|
|
1142
1142
|
try { if ($script:mp.NaturalDuration.HasTimeSpan) { $script:speakEndAt = (Get-Date).AddSeconds($script:mp.NaturalDuration.TimeSpan.TotalSeconds + 0.8) } } catch { }
|
|
1143
1143
|
})
|
|
1144
1144
|
}
|
|
1145
1145
|
$script:mp.Open([uri]$mp3)
|
|
1146
|
-
# 用时长估算"说到什么时候",避免 MediaEnded 提前误触发导致自己听见自己
|
|
1147
|
-
$dur = 6.0
|
|
1148
|
-
try { if ($script:mp.NaturalDuration.HasTimeSpan) { $dur = $script:mp.NaturalDuration.TimeSpan.TotalSeconds } } catch { }
|
|
1149
|
-
if ($dur -lt 0.5) { $dur = [Math]::Max(3.0, $text.Length * 0.22) }
|
|
1150
|
-
$script:speakEndAt = (Get-Date).AddSeconds($dur + 0.8)
|
|
1151
1146
|
$script:mp.Play()
|
|
1152
1147
|
Write-Err ('TTS-EDGE voice=' + $voice + ' file=' + (Get-Item $mp3).Length)
|
|
1153
1148
|
try { Get-ChildItem $env:TEMP -Filter 'fish-tts-*.mp3' -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -lt (Get-Date).AddMinutes(-10) } | Remove-Item -Force -ErrorAction SilentlyContinue } catch { }
|
|
1154
1149
|
} catch { Write-Err ('TTS-PLAY-ERR ' + $_.Exception.Message) }
|
|
1155
1150
|
}
|
|
1156
1151
|
|
|
1152
|
+
# 把回复拆成 1~3 段,边合成边播,缩短"开口等待"
|
|
1153
|
+
function Split-TtsSegments([string]$text) {
|
|
1154
|
+
if (-not $text) { return @() }
|
|
1155
|
+
$parts = @($text -split '(?<=[。!?!?;;…\.\r\n])' | Where-Object { $_.Trim() })
|
|
1156
|
+
if ($parts.Count -le 1) { return @($text) }
|
|
1157
|
+
# 第一段尽量短(先开口),后面合并成最多 2 段
|
|
1158
|
+
$first = $parts[0].Trim()
|
|
1159
|
+
$rest = ''
|
|
1160
|
+
for ($i = 1; $i -lt $parts.Count; $i++) { $rest = $rest + $parts[$i] }
|
|
1161
|
+
$rest = $rest.Trim()
|
|
1162
|
+
if (-not $rest) { return @($text) }
|
|
1163
|
+
if ($rest.Length -le 26) { return ,@($first, $rest) }
|
|
1164
|
+
# 剩余拆成两半
|
|
1165
|
+
$mid = [Math]::Floor($rest.Length / 2)
|
|
1166
|
+
$cut = $rest.LastIndexOfAny([char[]]@(',', ',', ';', '、', ' ', '。', '.'), $mid)
|
|
1167
|
+
if ($cut -lt 8) { $cut = $mid }
|
|
1168
|
+
$a = $rest.Substring(0, $cut + 1).Trim()
|
|
1169
|
+
$b = $rest.Substring($cut + 1).Trim()
|
|
1170
|
+
if (-not $b) { return ,@($first, $a) }
|
|
1171
|
+
return ,@($first, $a, $b)
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
function Start-TtsPipeline([string]$text, [string]$voice, [string]$rate, [string]$pitch) {
|
|
1175
|
+
$segs = Split-TtsSegments $text
|
|
1176
|
+
$script:ttsSegs = @($segs)
|
|
1177
|
+
$script:ttsSegVoice = $voice
|
|
1178
|
+
$script:ttsSegRate = $rate
|
|
1179
|
+
$script:ttsSegPitch = $pitch
|
|
1180
|
+
$script:ttsSegFiles = @()
|
|
1181
|
+
$script:ttsSegDone = @()
|
|
1182
|
+
$script:ttsSegPlayed = @()
|
|
1183
|
+
$script:ttsSegReq = @()
|
|
1184
|
+
for ($i = 0; $i -lt $script:ttsSegs.Count; $i++) {
|
|
1185
|
+
$script:ttsSegFiles += (Join-Path $env:TEMP ('fish-tts-' + [guid]::NewGuid().ToString('N') + '.mp3'))
|
|
1186
|
+
$script:ttsSegDone += $false
|
|
1187
|
+
$script:ttsSegPlayed += $false
|
|
1188
|
+
$script:ttsSegReq += ''
|
|
1189
|
+
}
|
|
1190
|
+
$script:ttsSegPlaying = -1
|
|
1191
|
+
Write-Err ('TTS-PIPE segs=' + $script:ttsSegs.Count + ' len=' + $text.Length)
|
|
1192
|
+
$script:ttsTimer.Start()
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
function Request-TtsSegment([int]$idx) {
|
|
1196
|
+
if ($idx -lt 0 -or $idx -ge $script:ttsSegs.Count) { return }
|
|
1197
|
+
if ($script:ttsSegReq[$idx]) { return }
|
|
1198
|
+
try {
|
|
1199
|
+
$reqId = '{0:D2}{1}' -f $idx, ([guid]::NewGuid().ToString('N').Substring(0, 8))
|
|
1200
|
+
$reqFile = Join-Path $env:TEMP ('fish-tts-req-' + $reqId + '.json')
|
|
1201
|
+
$reqObj = @{
|
|
1202
|
+
id = $reqId
|
|
1203
|
+
text = $script:ttsSegs[$idx]
|
|
1204
|
+
voice = $script:ttsSegVoice
|
|
1205
|
+
rate = $script:ttsSegRate
|
|
1206
|
+
pitch = $script:ttsSegPitch
|
|
1207
|
+
out = $script:ttsSegFiles[$idx]
|
|
1208
|
+
status = $script:ttsStatusFile
|
|
1209
|
+
} | ConvertTo-Json -Compress
|
|
1210
|
+
[IO.File]::WriteAllText($reqFile, $reqObj, (New-Object Text.UTF8Encoding($false)))
|
|
1211
|
+
$script:ttsProc.StandardInput.WriteLine('@' + $reqFile)
|
|
1212
|
+
$script:ttsProc.StandardInput.Flush()
|
|
1213
|
+
$script:ttsSegReq[$idx] = $reqId
|
|
1214
|
+
Write-Err ('TTS-REQ idx=' + $idx + ' id=' + $reqId)
|
|
1215
|
+
} catch { Write-Err ('TTS-REQ-ERR ' + $_.Exception.Message) }
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
function Complete-TtsSegment {
|
|
1219
|
+
try {
|
|
1220
|
+
$cur = $script:ttsSegPlaying
|
|
1221
|
+
if ($cur -ge 0 -and $cur -lt $script:ttsSegPlayed.Count) { $script:ttsSegPlayed[$cur] = $true }
|
|
1222
|
+
$script:ttsSegPlaying = -1
|
|
1223
|
+
$script:speakEndAt = (Get-Date)
|
|
1224
|
+
} catch { }
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
# 启动常驻 TTS 进程(复用连接,比每次启动 python 快一倍)
|
|
1228
|
+
function Start-TtsWorker {
|
|
1229
|
+
try {
|
|
1230
|
+
if ($script:ttsProc -and -not $script:ttsProc.HasExited) { return $true }
|
|
1231
|
+
$workerPath = Join-Path (Split-Path -Parent $PSCommandPath) 'tts_worker.py'
|
|
1232
|
+
if (-not (Test-Path $workerPath)) { Write-Err 'TTS-WORKER-MISSING'; return $false }
|
|
1233
|
+
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
|
1234
|
+
$psi.FileName = 'python'
|
|
1235
|
+
$psi.Arguments = '"' + $workerPath + '"'
|
|
1236
|
+
$psi.UseShellExecute = $false
|
|
1237
|
+
$psi.RedirectStandardInput = $true
|
|
1238
|
+
$psi.CreateNoWindow = $true
|
|
1239
|
+
$script:ttsProc = [System.Diagnostics.Process]::Start($psi)
|
|
1240
|
+
Start-Sleep -Milliseconds 350
|
|
1241
|
+
if ($script:ttsProc.HasExited) { Write-Err 'TTS-WORKER-DIED'; return $false }
|
|
1242
|
+
$script:ttsStatusFile = Join-Path $env:TEMP 'fish-tts-status.txt'
|
|
1243
|
+
Write-Err ('TTS-WORKER-START pid=' + $script:ttsProc.Id)
|
|
1244
|
+
return $true
|
|
1245
|
+
} catch { Write-Err ('TTS-WORKER-ERR ' + $_.Exception.Message); return $false }
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1157
1248
|
function Speak-Text([string]$text) {
|
|
1158
1249
|
if (-not $script:speakOn) { return }
|
|
1159
1250
|
$talkFile = Join-Path $DataDir 'asr.talking'
|
|
1160
1251
|
try { [IO.File]::WriteAllText($talkFile, '1') } catch { }
|
|
1161
1252
|
if ($script:ttsEngine -eq 'edge') {
|
|
1162
1253
|
try {
|
|
1163
|
-
$mp3 = Join-Path $env:TEMP ('fish-tts-' + [guid]::NewGuid().ToString('N') + '.mp3')
|
|
1164
|
-
# 英文回复用英文音色,中文用中文音色
|
|
1165
1254
|
$spkLang = Get-TextLang $text
|
|
1166
1255
|
if ($spkLang -eq 'en') { $vUse = $script:ttsVoiceEn; $rUse = $script:ttsRateEn; $pUse = $script:ttsPitchEn } else { $vUse = $script:ttsVoice; $rUse = $script:ttsRate; $pUse = $script:ttsPitch }
|
|
1167
|
-
|
|
1256
|
+
if (Start-TtsWorker) {
|
|
1257
|
+
$script:ttsMode = 'worker'
|
|
1258
|
+
$script:ttsVoiceUsed = $vUse
|
|
1259
|
+
$script:ttsText = $text
|
|
1260
|
+
Start-TtsPipeline $text $vUse $rUse $pUse
|
|
1261
|
+
return
|
|
1262
|
+
}
|
|
1263
|
+
# 常驻进程起不来:退回后台线程整段合成
|
|
1168
1264
|
if ($null -eq $script:ttsPool) {
|
|
1169
1265
|
$script:ttsPool = [runspacefactory]::CreateRunspacePool(1, 2)
|
|
1170
1266
|
$script:ttsPool.Open()
|
|
1171
1267
|
}
|
|
1268
|
+
$mp3 = Join-Path $env:TEMP ('fish-tts-' + [guid]::NewGuid().ToString('N') + '.mp3')
|
|
1172
1269
|
$ps = [PowerShell]::Create()
|
|
1173
1270
|
$ps.RunspacePool = $script:ttsPool
|
|
1174
1271
|
[void]$ps.AddScript({
|
|
@@ -1176,22 +1273,65 @@ function Speak-Text([string]$text) {
|
|
|
1176
1273
|
& $exe -m edge_tts --voice $voice ('--rate=' + $rate) ('--pitch=' + $pitch) '--text' $txt '--write-media' $out 2>$null | Out-Null
|
|
1177
1274
|
}).AddArgument('python').AddArgument($vUse).AddArgument($rUse).AddArgument($pUse).AddArgument($text).AddArgument($mp3)
|
|
1178
1275
|
$script:ttsPS = $ps
|
|
1276
|
+
$script:ttsHandle = $ps.BeginInvoke()
|
|
1277
|
+
$script:ttsMode = 'thread'
|
|
1179
1278
|
$script:ttsMp3 = $mp3
|
|
1180
|
-
$script:ttsText = $text
|
|
1181
1279
|
$script:ttsVoiceUsed = $vUse
|
|
1182
|
-
$script:
|
|
1280
|
+
$script:ttsText = $text
|
|
1183
1281
|
$script:ttsTimer.Start()
|
|
1184
|
-
Write-Err ('TTS-EDGE-START len=' + $text.Length + ' voice=' + $vUse)
|
|
1282
|
+
Write-Err ('TTS-EDGE-START len=' + $text.Length + ' voice=' + $vUse + ' mode=thread')
|
|
1185
1283
|
return
|
|
1186
1284
|
} catch { Write-Err ('TTS-EDGE-ERR ' + $_.Exception.Message) }
|
|
1187
1285
|
}
|
|
1188
1286
|
Speak-Sapi $text
|
|
1189
1287
|
}
|
|
1190
1288
|
|
|
1191
|
-
#
|
|
1289
|
+
# 流水线调度:边合成边播
|
|
1192
1290
|
$script:ttsTimer = New-Object System.Windows.Forms.Timer
|
|
1193
|
-
$script:ttsTimer.Interval =
|
|
1291
|
+
$script:ttsTimer.Interval = 90
|
|
1194
1292
|
$script:ttsTimer.Add_Tick({
|
|
1293
|
+
# ---- 常驻进程 + 分段流水线 ----
|
|
1294
|
+
if ($script:ttsMode -eq 'worker' -and $script:ttsSegs.Count -gt 0) {
|
|
1295
|
+
# 1) 扫状态文件,标记完成的分段
|
|
1296
|
+
for ($i = 0; $i -lt $script:ttsSegs.Count; $i++) {
|
|
1297
|
+
if ($script:ttsSegDone[$i] -or -not $script:ttsSegReq[$i]) { continue }
|
|
1298
|
+
$rid = $script:ttsSegReq[$i]
|
|
1299
|
+
try {
|
|
1300
|
+
$hit = Get-Content $script:ttsStatusFile -ErrorAction SilentlyContinue | Where-Object { $_ -match ('^' + $rid + ' ') } | Select-Object -Last 1
|
|
1301
|
+
} catch { $hit = $null }
|
|
1302
|
+
if ($hit) {
|
|
1303
|
+
if ($hit -like ('*' + $rid + ' OK*')) { $script:ttsSegDone[$i] = $true }
|
|
1304
|
+
else { Write-Err ('TTS-SEG-FAIL idx=' + $i); $script:ttsSegDone[$i] = $true }
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
# 2) 没有在播 → 找下一段
|
|
1308
|
+
if ($script:ttsSegPlaying -lt 0) {
|
|
1309
|
+
$next = -1
|
|
1310
|
+
for ($i = 0; $i -lt $script:ttsSegs.Count; $i++) { if (-not $script:ttsSegPlayed[$i]) { $next = $i; break } }
|
|
1311
|
+
if ($next -lt 0) {
|
|
1312
|
+
$script:ttsTimer.Stop()
|
|
1313
|
+
$script:ttsSegs = @()
|
|
1314
|
+
return
|
|
1315
|
+
}
|
|
1316
|
+
if ($script:ttsSegDone[$next]) {
|
|
1317
|
+
$file = $script:ttsSegFiles[$next]
|
|
1318
|
+
if ((Test-Path $file) -and (Get-Item $file).Length -gt 800) {
|
|
1319
|
+
$script:ttsSegPlaying = $next
|
|
1320
|
+
Play-TtsFile $file $script:ttsVoiceUsed
|
|
1321
|
+
} else {
|
|
1322
|
+
$script:ttsSegPlayed[$next] = $true
|
|
1323
|
+
}
|
|
1324
|
+
} elseif (-not $script:ttsSegReq[$next]) {
|
|
1325
|
+
Request-TtsSegment $next
|
|
1326
|
+
}
|
|
1327
|
+
} else {
|
|
1328
|
+
# 3) 正在播:预取下一段
|
|
1329
|
+
$nx = $script:ttsSegPlaying + 1
|
|
1330
|
+
if ($nx -lt $script:ttsSegs.Count -and -not $script:ttsSegDone[$nx] -and -not $script:ttsSegReq[$nx]) { Request-TtsSegment $nx }
|
|
1331
|
+
}
|
|
1332
|
+
return
|
|
1333
|
+
}
|
|
1334
|
+
# ---- 后台线程整段模式 ----
|
|
1195
1335
|
if ($null -eq $script:ttsHandle) { $script:ttsTimer.Stop(); return }
|
|
1196
1336
|
if (-not $script:ttsHandle.IsCompleted) { return }
|
|
1197
1337
|
$script:ttsTimer.Stop()
|
|
@@ -1382,7 +1522,7 @@ function Start-Chat([string]$text) {
|
|
|
1382
1522
|
$replyLang = Get-ReplyLang $text
|
|
1383
1523
|
if ($replyLang -eq 'en') {
|
|
1384
1524
|
$sys = 'YOU MUST REPLY IN ENGLISH ONLY, NO MATTER WHAT. You are a round, chubby blue fish living on the master''s Windows desktop. Your name is "' + $pn.nameEn + '" (in Chinese it is written 大肥鱼, said as Da Fei Yu). Your dad is Guangguang, your mom is Lili, your birthday is October 30 2026, you were born in Beihai, Guangxi, China. Answer questions about family/birthday/birthplace with these facts and never invent them. In English your name is always this one, never any other name; if earlier messages called you something different, correct them politely.'
|
|
1385
|
-
$sys = $sys + ' Reply ONLY in English, max
|
|
1525
|
+
$sys = $sys + ' Reply ONLY in English, max 20 words, playful and a little tsundere, call the user "master", no emoji.'
|
|
1386
1526
|
if ($pn.likes) { $sys = $sys + ' You like: ' + $pn.likes + ' .' }
|
|
1387
1527
|
if ($pn.dislikes) { $sys = $sys + ' You dislike: ' + $pn.dislikes + ' .' }
|
|
1388
1528
|
} else {
|
|
@@ -1424,10 +1564,13 @@ function Start-Chat([string]$text) {
|
|
|
1424
1564
|
if ($histEn.Count -gt 4) { $histEn = $histEn[($histEn.Count - 4)..($histEn.Count - 1)] }
|
|
1425
1565
|
foreach ($m in $histEn) { $msgs += $m }
|
|
1426
1566
|
} else {
|
|
1427
|
-
|
|
1567
|
+
# 中文模式只带中文历史,避免被上一段英文带跑
|
|
1568
|
+
$histZh = @($script:chatHistory | Where-Object { (Get-TextLang ([string]$_.content)) -eq 'zh' })
|
|
1569
|
+
if ($histZh.Count -gt 10) { $histZh = $histZh[($histZh.Count - 10)..($histZh.Count - 1)] }
|
|
1570
|
+
foreach ($m in $histZh) { $msgs += $m }
|
|
1428
1571
|
}
|
|
1429
1572
|
$msgs += @{ role = 'user'; content = $text }
|
|
1430
|
-
$body = @{ model = 'deepseek-flash'; messages = $msgs; max_tokens =
|
|
1573
|
+
$body = @{ model = 'deepseek-flash'; messages = $msgs; max_tokens = 160; temperature = 0.9; thinking = @{ type = 'disabled' } } | ConvertTo-Json -Depth 6
|
|
1431
1574
|
if ($null -eq $script:http) {
|
|
1432
1575
|
$script:http = New-Object System.Net.Http.HttpClient
|
|
1433
1576
|
$script:http.Timeout = [TimeSpan]::FromSeconds(40)
|
package/assets/tts_worker.py
CHANGED
|
@@ -42,10 +42,18 @@ async def main():
|
|
|
42
42
|
line = line.strip()
|
|
43
43
|
if not line:
|
|
44
44
|
continue
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
# 支持"@文件路径"形式:避免 Windows PowerShell 写 stdin 时的编码问题
|
|
46
|
+
if line.startswith("@"):
|
|
47
|
+
try:
|
|
48
|
+
with open(line[1:], "r", encoding="utf-8") as fh:
|
|
49
|
+
req = json.loads(fh.read())
|
|
50
|
+
except Exception:
|
|
51
|
+
continue
|
|
52
|
+
else:
|
|
53
|
+
try:
|
|
54
|
+
req = json.loads(line)
|
|
55
|
+
except Exception:
|
|
56
|
+
continue
|
|
49
57
|
if req.get("cmd") == "quit":
|
|
50
58
|
break
|
|
51
59
|
try:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-fish-pet",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Desktop blue fish pet for DeepSeek Harness: per-pixel transparent WinForms window, text chat, and cloud speech-to-text voice conversation.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|