batuta/serve/banco/
server.rs1use super::router::create_banco_router;
4use super::state::BancoState;
5use tokio::net::TcpListener;
6
7pub async fn start_server(host: &str, port: u16, state: BancoState) -> anyhow::Result<()> {
9 let addr = format!("{host}:{port}");
10 let listener = TcpListener::bind(&addr).await?;
11
12 let model_status = if let Some(info) = state.model.info() {
13 format!("{} ({})", info.model_id, format!("{:?}", info.format).to_lowercase())
14 } else {
15 "none — load via POST /api/v1/models/load or --model flag".to_string()
16 };
17
18 let tokenizer_status = {
19 #[cfg(feature = "aprender")]
20 {
21 if state.model.has_bpe_tokenizer() {
22 "BPE (proper merge rules)"
23 } else if state.model.is_loaded() {
24 "greedy (no tokenizer.json found)"
25 } else {
26 "n/a"
27 }
28 }
29 #[cfg(not(feature = "aprender"))]
30 {
31 "greedy (aprender not enabled)"
32 }
33 };
34
35 eprintln!("┌──────────────────────────────────────────────────┐");
36 eprintln!("│ Banco – Local AI Workbench │");
37 eprintln!("├──────────────────────────────────────────────────┤");
38 eprintln!("│ Listening: {addr:<36}│");
39 eprintln!("│ Privacy: {:?}", state.privacy_tier);
40 eprintln!("│ Model: {model_status}");
41 eprintln!("│ Tokenizer: {tokenizer_status}");
42 eprintln!("│ Telemetry: disabled");
43 eprintln!("├──────────────────────────────────────────────────┤");
44 eprintln!("│ Browser: http://{addr}/ (chat UI)");
45 eprintln!("│ Core: /health /api/v1/models /api/v1/system");
46 eprintln!("│ Chat: /api/v1/chat/completions (SSE)");
47 eprintln!("│ Completions:/v1/completions (text, OpenAI-compat)");
48 eprintln!("│ Data: /api/v1/tokenize /detokenize /embeddings");
49 eprintln!("│ Models: /api/v1/models/load|unload|status|:id");
50 eprintln!("│ Chat cfg: /api/v1/chat/parameters");
51 eprintln!("│ Convos: /api/v1/conversations + /search /export /import");
52 eprintln!("│ Presets: /api/v1/prompts");
53 eprintln!("│ Files: /api/v1/data/upload /files");
54 eprintln!("│ Recipes: /api/v1/data/recipes /datasets");
55 eprintln!("│ RAG: /api/v1/rag/index /status /search");
56 eprintln!("│ Eval: /api/v1/eval/perplexity /runs");
57 eprintln!("│ Training: /api/v1/train/start /runs /presets /metrics /export");
58 eprintln!("│ Merge: /api/v1/models/merge /strategies (TIES/DARE/SLERP)");
59 eprintln!("│ Experiments:/api/v1/experiments /compare");
60 eprintln!("│ Batch: /api/v1/batch");
61 eprintln!("│ Registry: /api/v1/models/pull /registry (pacha)");
62 eprintln!("│ Audio: /api/v1/audio/transcriptions (whisper-apr)");
63 eprintln!("│ MCP: /api/v1/mcp (Model Context Protocol)");
64 eprintln!("│ Tools: /api/v1/tools (calculator, code_execution, web_search)");
65 eprintln!("│ Metrics: /api/v1/metrics (Prometheus)");
66 eprintln!("│ WebSocket: /api/v1/ws (real-time events)");
67 eprintln!("│ OpenAI: /v1/models /v1/completions /v1/chat/completions /v1/embeddings");
68 eprintln!("│ Ollama: /api/generate /api/chat /api/tags /api/show /api/pull /api/delete");
69 eprintln!("├──────────────────────────────────────────────────┤");
70 let sys = state.system_info();
71 eprintln!("│ Data: {} files, {} conversations", sys.files, sys.conversations);
72 eprintln!(
73 "│ RAG: {}",
74 if sys.rag_indexed {
75 format!("{} chunks indexed", sys.rag_chunks)
76 } else {
77 "empty".to_string()
78 }
79 );
80 eprintln!("│ Storage: ~/.banco/");
81 eprintln!("│ Shutdown: Ctrl+C (graceful drain)");
82 eprintln!("└──────────────────────────────────────────────────┘");
83
84 let app = create_banco_router(state);
85
86 axum::serve(listener, app).with_graceful_shutdown(shutdown_signal()).await?;
87
88 eprintln!("[banco] Graceful shutdown complete");
89 Ok(())
90}
91
92async fn shutdown_signal() {
94 let ctrl_c = async {
95 tokio::signal::ctrl_c().await.expect("failed to install Ctrl+C handler");
96 };
97
98 #[cfg(unix)]
99 let terminate = async {
100 tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
101 .expect("failed to install signal handler")
102 .recv()
103 .await;
104 };
105
106 #[cfg(not(unix))]
107 let terminate = std::future::pending::<()>();
108
109 tokio::select! {
110 () = ctrl_c => eprintln!("\n[banco] Received Ctrl+C, shutting down..."),
111 () = terminate => eprintln!("\n[banco] Received SIGTERM, shutting down..."),
112 }
113}