70eed20909
Allow overriding the LLM system prompt via the `LLM_SYSTEM_PROMPT` environment variable. This provides flexibility for customizing LLM behavior without code changes. A default prompt is used if the environment variable is unset or empty.
114 lines
3.5 KiB
Rust
114 lines
3.5 KiB
Rust
use std::sync::Arc;
|
|
|
|
use tokio::net::TcpListener;
|
|
use tower_http::limit::RequestBodyLimitLayer;
|
|
use tower_http::trace::TraceLayer;
|
|
use tracing::{info, warn};
|
|
use tracing_subscriber::layer::SubscriberExt;
|
|
use tracing_subscriber::util::SubscriberInitExt;
|
|
use tracing_subscriber::EnvFilter;
|
|
|
|
use doctate_server::analyze;
|
|
use doctate_server::config::Config;
|
|
use doctate_server::transcribe;
|
|
use doctate_server::AppState;
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
dotenvy::dotenv().ok();
|
|
let config = Arc::new(Config::from_env());
|
|
|
|
// Logging: stdout + daily rotating log files.
|
|
let env_filter = EnvFilter::try_new(&config.log_level)
|
|
.unwrap_or_else(|_| EnvFilter::new("info"));
|
|
|
|
let file_appender = tracing_appender::rolling::daily(&config.log_path, "recorder.log");
|
|
let (file_writer, _guard) = tracing_appender::non_blocking(file_appender);
|
|
|
|
tracing_subscriber::registry()
|
|
.with(env_filter)
|
|
.with(
|
|
tracing_subscriber::fmt::layer()
|
|
.with_target(false)
|
|
.with_writer(std::io::stdout),
|
|
)
|
|
.with(
|
|
tracing_subscriber::fmt::layer()
|
|
.with_target(false)
|
|
.with_ansi(false)
|
|
.with_writer(file_writer),
|
|
)
|
|
.init();
|
|
|
|
// Surface configuration gaps that silently disable features.
|
|
if !config.llm_configured() {
|
|
warn!(
|
|
"LLM not configured — 'Fall abschließen' is disabled. \
|
|
Set LLM_URL, LLM_API_KEY and LLM_MODEL in .env to enable."
|
|
);
|
|
}
|
|
|
|
{
|
|
let custom = std::env::var("LLM_SYSTEM_PROMPT")
|
|
.map(|v| !v.is_empty())
|
|
.unwrap_or(false);
|
|
info!(
|
|
prompt_source = if custom { "env" } else { "default" },
|
|
prompt_chars = config.llm_system_prompt.chars().count(),
|
|
"system prompt loaded"
|
|
);
|
|
}
|
|
|
|
// Shared HTTP client for both downstream pipelines.
|
|
let http_client = reqwest::Client::builder()
|
|
.build()
|
|
.expect("reqwest client build failed");
|
|
|
|
// Transcription pipeline: channel + worker + recovery scan.
|
|
let (transcribe_tx, transcribe_rx) = transcribe::channel();
|
|
tokio::spawn(transcribe::worker::run(
|
|
transcribe_rx,
|
|
config.clone(),
|
|
http_client.clone(),
|
|
));
|
|
{
|
|
let tx = transcribe_tx.clone();
|
|
let data_path = config.data_path.clone();
|
|
tokio::spawn(async move {
|
|
transcribe::recovery::scan_and_enqueue(&data_path, &tx).await;
|
|
});
|
|
}
|
|
|
|
// Analyze pipeline: channel + worker + recovery scan.
|
|
let (analyze_tx, analyze_rx) = analyze::channel();
|
|
tokio::spawn(analyze::worker::run(
|
|
analyze_rx,
|
|
config.clone(),
|
|
http_client.clone(),
|
|
));
|
|
{
|
|
let tx = analyze_tx.clone();
|
|
let data_path = config.data_path.clone();
|
|
tokio::spawn(async move {
|
|
analyze::recovery::scan_and_enqueue(&data_path, &tx).await;
|
|
});
|
|
}
|
|
|
|
let state = AppState {
|
|
config: config.clone(),
|
|
transcribe_tx,
|
|
analyze_tx,
|
|
session_store: doctate_server::web_session::new_store(),
|
|
};
|
|
|
|
let addr = format!("0.0.0.0:{}", config.server_port);
|
|
info!(port = config.server_port, "Server starting");
|
|
|
|
let app = doctate_server::create_router_with_state(state)
|
|
.layer(TraceLayer::new_for_http())
|
|
.layer(RequestBodyLimitLayer::new(50 * 1024 * 1024)); // 50 MB
|
|
|
|
let listener = TcpListener::bind(&addr).await.unwrap();
|
|
axum::serve(listener, app).await.unwrap();
|
|
}
|