Systematisk gjennomgang av PG-skjema, auth-middleware, intensjoner, skrivestien og WebSocket-laget. Alle kjernetabeller matcher docs. Auth fungerer korrekt (401 for ugyldig/manglende token). Skrivestien er konsistent: direkte PG-skriving → NOTIFY → WebSocket. Fikser: - Fjern død kode: pg_writes enqueue-funksjoner (aldri kalt etter STDB-migrering) - Fjern ubrukt truncate() i tts.rs - Legg til #[allow(dead_code)] for sqlx-structs med ubrukte felt - Rett feilaktig doc-påstand i api_grensesnitt.md om jobbkø - Fjern utdatert STDB-referanse i agent_api.md - Kompilerer uten warnings Se logs/validering-23.1.md for fullstendig rapport.
137 lines
4.3 KiB
Rust
137 lines
4.3 KiB
Rust
// TTS-dispatcher — delegerer til synops-tts CLI.
|
|
//
|
|
// Maskinrommet beholder: voice_id-oppslag (payload > node metadata > env).
|
|
// Alt annet (ElevenLabs-kall, CAS-lagring, PG-skriving) gjøres av synops-tts.
|
|
// PG NOTIFY-triggere sender sanntidsoppdateringer.
|
|
//
|
|
// Jobbtype: "tts_generate"
|
|
// Payload: { "text", "voice_id"?, "language"?, "source_node_id"?, "requested_by" }
|
|
//
|
|
// Ref: docs/retninger/unix_filosofi.md, docs/proposals/ghost_host_tts.md
|
|
|
|
use sqlx::PgPool;
|
|
use uuid::Uuid;
|
|
|
|
use crate::cli_dispatch;
|
|
use crate::jobs::JobRow;
|
|
|
|
/// Synops-tts binary path.
|
|
fn tts_bin() -> String {
|
|
std::env::var("SYNOPS_TTS_BIN")
|
|
.unwrap_or_else(|_| "synops-tts".to_string())
|
|
}
|
|
|
|
/// Håndterer tts_generate-jobb.
|
|
///
|
|
/// Spawner synops-tts med --write for å gjøre alt arbeidet:
|
|
/// ElevenLabs-kall, CAS-lagring, PG-skriving, ressurslogging.
|
|
/// PG NOTIFY-triggere sender sanntidsoppdateringer til klienter.
|
|
pub async fn handle_tts_job(
|
|
job: &JobRow,
|
|
db: &PgPool,
|
|
) -> Result<serde_json::Value, String> {
|
|
let text = job.payload["text"]
|
|
.as_str()
|
|
.ok_or("Mangler 'text' i payload")?;
|
|
|
|
let source_node_id: Option<Uuid> = job.payload["source_node_id"]
|
|
.as_str()
|
|
.and_then(|s| s.parse().ok());
|
|
|
|
let requested_by: Uuid = job.payload["requested_by"]
|
|
.as_str()
|
|
.and_then(|s| s.parse().ok())
|
|
.ok_or("Mangler gyldig 'requested_by' i payload")?;
|
|
|
|
let language = job.payload["language"]
|
|
.as_str()
|
|
.unwrap_or("no");
|
|
|
|
// Bestem voice_id: payload > source-node metadata > env default
|
|
let voice_id = resolve_voice_id(job, db, source_node_id).await?;
|
|
|
|
// Bygg kommando
|
|
let bin = tts_bin();
|
|
let mut cmd = tokio::process::Command::new(&bin);
|
|
|
|
cmd.arg("--text").arg(text)
|
|
.arg("--voice").arg(&voice_id)
|
|
.arg("--language").arg(language)
|
|
.arg("--requested-by").arg(requested_by.to_string())
|
|
.arg("--write");
|
|
|
|
if let Some(source_id) = source_node_id {
|
|
cmd.arg("--source-node-id").arg(source_id.to_string());
|
|
}
|
|
|
|
// Sett miljøvariabler CLI-verktøyet trenger
|
|
cli_dispatch::set_database_url(&mut cmd)?;
|
|
cli_dispatch::forward_env(&mut cmd, "CAS_ROOT");
|
|
cli_dispatch::forward_env(&mut cmd, "ELEVENLABS_API_KEY");
|
|
cli_dispatch::forward_env(&mut cmd, "ELEVENLABS_MODEL");
|
|
cli_dispatch::forward_env(&mut cmd, "ELEVENLABS_DEFAULT_VOICE");
|
|
|
|
tracing::info!(
|
|
text_len = text.len(),
|
|
voice_id = %voice_id,
|
|
bin = %bin,
|
|
"Starter synops-tts"
|
|
);
|
|
|
|
let result = cli_dispatch::run_cli_tool(&bin, &mut cmd).await?;
|
|
|
|
// PG-skriving gjøres av synops-tts med --write.
|
|
// PG NOTIFY-triggere sender sanntidsoppdateringer til WebSocket-klienter.
|
|
|
|
tracing::info!(
|
|
cas_hash = result["cas_hash"].as_str().unwrap_or("n/a"),
|
|
media_node_id = result["media_node_id"].as_str().unwrap_or("n/a"),
|
|
"synops-tts fullført"
|
|
);
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
/// Bestem voice_id: payload-verdi > source-node metadata.voice_preference > env default.
|
|
async fn resolve_voice_id(
|
|
job: &JobRow,
|
|
db: &PgPool,
|
|
source_node_id: Option<Uuid>,
|
|
) -> Result<String, String> {
|
|
// 1. Eksplisitt i payload
|
|
if let Some(vid) = job.payload["voice_id"].as_str() {
|
|
if !vid.is_empty() {
|
|
return Ok(vid.to_string());
|
|
}
|
|
}
|
|
|
|
// 2. Source-nodens metadata.voice_preference.voice_id
|
|
if let Some(node_id) = source_node_id {
|
|
let meta: Option<serde_json::Value> = sqlx::query_scalar(
|
|
"SELECT metadata FROM nodes WHERE id = $1",
|
|
)
|
|
.bind(node_id)
|
|
.fetch_optional(db)
|
|
.await
|
|
.map_err(|e| format!("PG-feil ved henting av voice_preference: {e}"))?
|
|
.flatten();
|
|
|
|
if let Some(meta) = meta {
|
|
if let Some(vid) = meta
|
|
.get("voice_preference")
|
|
.and_then(|vp| vp.get("voice_id"))
|
|
.and_then(|v| v.as_str())
|
|
{
|
|
if !vid.is_empty() {
|
|
tracing::info!(node_id = %node_id, voice_id = %vid, "Bruker mottaker-preferanse");
|
|
return Ok(vid.to_string());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. Miljøvariabel-default
|
|
Ok(std::env::var("ELEVENLABS_DEFAULT_VOICE")
|
|
.unwrap_or_else(|_| "21m00Tcm4TlvDq8ikWAM".to_string()))
|
|
}
|
|
|