主人 2026-10-24:「tts启动脚本在这 ...\一键启动.bat,以后启动后端可以顺便检测 一下如果TTS没起就顺带启一下」。本片只做「启动时顺带探一下」,**默认关**(主人 早前说过「开机自启先不急 后面做」),未开开关时连探活都不发。 - 新包 internal/runtime/voice/ttslaunch:探 http://127.0.0.1:9880(毫秒级超时), 已在跑什么都不做;没起则异步起脚本(cmd.exe /c 传参,不拼命令行字符串) - 防重复:in-flight 单飞 + 一次尝试后的冷却(失败也算一次,重启风暴不会变成重试风暴) - 失败只 WARN:探活超时/脚本不存在/起进程失败都只记带 source 的日志, 组合根那条路径**没有 error 通道** - 配置走面板文件 $DATA_DIR/config/voice-tts-launch.json(零新 .env 键) - 接线在 cmd/cyrene 的网关 Listen 之后,整段在后台 goroutine 里,不占启动时长 (用例以「挂载耗时 < 150ms 而探活超时 800ms」证明) - 不新开端点、不加帧族;不碰 gateway 包
75 lines
3.5 KiB
Go
75 lines
3.5 KiB
Go
package main
|
||
|
||
// 本文件把**后端启动时顺带照顾 emotionTTS**(切片 M47)接进组合根,并守住三条
|
||
// 红线(塔 2026-10 已定,主人原话见 internal/runtime/voice/ttslaunch 包注释):
|
||
//
|
||
// 1. **默认关**:面板文件 `$DATA_DIR/config/voice-tts-launch.json` 没写
|
||
// `{"enabled": true}` 时,这里**连探活都不发**、也不构造任何后台任务 ——
|
||
// 主人早前说过「开机自启先不急 后面做」。
|
||
// 2. **不占启动时长**:`Trigger` 把探活 + 起脚本整段丢进后台 goroutine 后立即
|
||
// 返回,调用点在网关 Listen 之后(见 main.run);探活超时/脚本拉起耗时都
|
||
// 落不到后端的启动路径上。
|
||
// 3. **失败只 WARN**:脚本不存在、起进程失败、探活超时都只是带 source 的日志,
|
||
// 本函数**没有 error 返回**,绝不阻塞或改坏后端启动。
|
||
//
|
||
// 零新 `.env` 键(`.env` 已冻结):开关与脚本路径都走面板文件家族
|
||
// (`runtimeconfig`,与 `voice-tts.json` 同一套读取口径)。
|
||
|
||
import (
|
||
"context"
|
||
"time"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/config/runtimeconfig"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/logger"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/voice/ttslaunch"
|
||
)
|
||
|
||
// voiceTTSLaunchSource 是本切片日志的来源标签(AGENTS §10:日志必须标来源)。
|
||
const voiceTTSLaunchSource = "cmd/cyrene/voice-tts-launch"
|
||
|
||
// startVoiceTTSLaunch 读面板配置,按需投递**一次**「探活 → 必要时拉起」。
|
||
//
|
||
// 返回 nil 表示这一级没启用(没配过 / 显式 false / 文件坏掉),调用方据此什么都
|
||
// 不做;返回非 nil 仅为了让调用方在关闭期或验收里能 `Wait()`(生产路径忽略它)。
|
||
func (a *app) startVoiceTTSLaunch(ctx context.Context) *ttslaunch.Launcher {
|
||
log := logger.WithSource(a.log, voiceTTSLaunchSource)
|
||
path := runtimeconfig.DefaultVoiceTTSLaunchPath(resolveDataDir(a.cfg))
|
||
tuning, err := runtimeconfig.LoadVoiceTTSLaunchTuning(path)
|
||
if err != nil {
|
||
// 坏文件 ⇒ **不启用**:一份读不懂的配置绝不能变成"随手拉起一个进程"。
|
||
log.Warn("voice tts launch config unreadable, emotionTTS auto-launch stays off",
|
||
"path", runtimeconfig.VoiceTTSLaunchConfigRelPath, "err", err)
|
||
return nil
|
||
}
|
||
if !tuning.EnabledOn() {
|
||
log.Debug("emotionTTS auto-launch not enabled in panel config",
|
||
"path", runtimeconfig.VoiceTTSLaunchConfigRelPath, "hint", `set {"enabled": true}`)
|
||
return nil
|
||
}
|
||
launcher := ttslaunch.New(voiceTTSLaunchConfig(tuning), ttslaunch.WithLogger(log))
|
||
cfg := launcher.Config()
|
||
log.Info("emotionTTS auto-launch enabled",
|
||
"probe_url", cfg.ProbeURL,
|
||
"script", cfg.ScriptPath,
|
||
"probe_timeout_ms", cfg.ProbeTimeout.Milliseconds(),
|
||
"cooldown_sec", int(cfg.Cooldown/time.Second),
|
||
"config", runtimeconfig.VoiceTTSLaunchConfigRelPath)
|
||
// 立即返回:探活与起脚本都在后台跑(见 ttslaunch.Launcher.Trigger)。
|
||
launcher.Trigger(ctx)
|
||
return launcher
|
||
}
|
||
|
||
// voiceTTSLaunchConfig 把面板形状映射成 launcher 的配置。
|
||
//
|
||
// 它**不**在这里裁决缺省值(脚本路径/探活地址/超时/冷却的默认值全在
|
||
// `ttslaunch.Config.Normalized` 一处):零值原样传下去,由那一处补齐。
|
||
func voiceTTSLaunchConfig(t runtimeconfig.VoiceTTSLaunchTuning) ttslaunch.Config {
|
||
return ttslaunch.Config{
|
||
Enabled: t.EnabledOn(),
|
||
ScriptPath: t.ScriptPath,
|
||
ProbeURL: t.ProbeURL,
|
||
ProbeTimeout: time.Duration(t.ProbeTimeoutMs) * time.Millisecond,
|
||
Cooldown: time.Duration(t.CooldownSec) * time.Second,
|
||
}
|
||
}
|