feat: implement web UI for provider and model configuration

- Added 'provider_configs' and 'model_configs' tables to database.
- Refactored ProviderManager to support thread-safe dynamic updates and database overrides.
- Implemented 'Models' tab in dashboard to manage model visibility, mapping, and pricing.
- Added provider configuration modal to 'Providers' tab.
- Integrated database overrides into chat completion logic (enabled state, mapping, and cost).
This commit is contained in:
2026-02-26 18:13:04 -05:00
parent c5fb2357ff
commit 3165aa1859
14 changed files with 707 additions and 103 deletions

View File

@@ -2,6 +2,7 @@ use futures::stream::Stream;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::sync::Arc;
use sqlx::Row;
use crate::logging::{RequestLogger, RequestLog};
use crate::client::ClientManager;
use crate::providers::{Provider, ProviderStreamChunk};
@@ -20,6 +21,7 @@ pub struct AggregatingStream<S> {
logger: Arc<RequestLogger>,
client_manager: Arc<ClientManager>,
model_registry: Arc<crate::models::registry::ModelRegistry>,
db_pool: crate::database::DbPool,
start_time: std::time::Instant,
has_logged: bool,
}
@@ -38,6 +40,7 @@ where
logger: Arc<RequestLogger>,
client_manager: Arc<ClientManager>,
model_registry: Arc<crate::models::registry::ModelRegistry>,
db_pool: crate::database::DbPool,
) -> Self {
Self {
inner,
@@ -51,6 +54,7 @@ where
logger,
client_manager,
model_registry,
db_pool,
start_time: std::time::Instant::now(),
has_logged: false,
}
@@ -72,6 +76,7 @@ where
let prompt_tokens = self.prompt_tokens;
let has_images = self.has_images;
let registry = self.model_registry.clone();
let pool = self.db_pool.clone();
// Estimate completion tokens (including reasoning if present)
let content_tokens = estimate_completion_tokens(&self.accumulated_content, &model);
@@ -83,10 +88,29 @@ where
let completion_tokens = content_tokens + reasoning_tokens;
let total_tokens = prompt_tokens + completion_tokens;
let cost = provider.calculate_cost(&model, prompt_tokens, completion_tokens, &registry);
// Spawn a background task to log the completion
tokio::spawn(async move {
// Check database for cost overrides
let db_cost = sqlx::query("SELECT prompt_cost_per_m, completion_cost_per_m FROM model_configs WHERE id = ?")
.bind(&model)
.fetch_optional(&pool)
.await
.unwrap_or(None);
let cost = if let Some(row) = db_cost {
let prompt_rate = row.get::<Option<f64>, _>("prompt_cost_per_m");
let completion_rate = row.get::<Option<f64>, _>("completion_cost_per_m");
if let (Some(p), Some(c)) = (prompt_rate, completion_rate) {
(prompt_tokens as f64 * p / 1_000_000.0) + (completion_tokens as f64 * c / 1_000_000.0)
} else {
provider.calculate_cost(&model, prompt_tokens, completion_tokens, &registry)
}
} else {
provider.calculate_cost(&model, prompt_tokens, completion_tokens, &registry)
};
// Log to database
logger.log_request(RequestLog {
timestamp: chrono::Utc::now(),
@@ -188,6 +212,7 @@ mod tests {
logger,
client_manager,
registry,
pool.clone(),
);
while let Some(item) = agg_stream.next().await {