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, } }