mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-27 16:26:16 +08:00
Feat/provider icon color (#385)
* feat(ui): add color prop support to ProviderIcon component * feat(health): add stream check core functionality Add new stream-based health check module to replace model_test: - Add stream_check command layer with single and batch provider checks - Add stream_check DAO layer for config and log persistence - Add stream_check service layer with retry mechanism and health status evaluation - Add frontend HealthStatusIndicator component - Add frontend useStreamCheck hook This provides more comprehensive health checking capabilities. * refactor(health): replace model_test with stream_check Replace model_test module with stream_check across the codebase: - Remove model_test command and service modules - Update command registry in lib.rs to use stream_check commands - Update module exports in commands/mod.rs and services/mod.rs - Remove frontend useModelTest hook - Update stream_check command implementation This refactoring provides clearer naming and better separation of concerns. * refactor(db): clean up unused database tables and optimize schema Remove deprecated and unused database tables: - Remove proxy_usage table (replaced by proxy_request_logs) - Remove usage_daily_stats table (aggregation done on-the-fly) - Rename model_test_logs to stream_check_logs with updated schema - Remove related DAO methods for proxy_usage - Update usage_stats service to use proxy_request_logs only - Refactor usage_script to work with new schema This simplifies the database schema and removes redundant data storage. * refactor(ui): update frontend components for stream check Update frontend components to use stream check API: - Refactor ModelTestConfigPanel to use stream check config - Update API layer to use stream_check commands - Add HealthStatus type and StreamCheckResult interface - Update ProviderList to use new health check integration - Update AutoFailoverConfigPanel with stream check references - Improve UI layout and configuration options This completes the frontend migration from model_test to stream_check. * feat(health): add configurable test models and reasoning effort support Enhance stream check service with configurable test models: - Add claude_model, codex_model, gemini_model to StreamCheckConfig - Support reasoning effort syntax (model@level or model#level) - Parse and apply reasoning_effort for OpenAI-compatible models - Remove hardcoded model names from check functions - Add unit tests for model parsing logic - Remove obsolete model_test source files This allows users to customize which models are used for health checks.
This commit is contained in:
@@ -8,5 +8,6 @@ pub mod providers;
|
||||
pub mod proxy;
|
||||
pub mod settings;
|
||||
pub mod skills;
|
||||
pub mod stream_check;
|
||||
|
||||
// 所有 DAO 方法都通过 Database impl 提供,无需单独导出
|
||||
|
||||
@@ -210,83 +210,6 @@ impl Database {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ==================== Proxy Usage (可选) ====================
|
||||
|
||||
/// 记录代理使用统计
|
||||
#[allow(dead_code)]
|
||||
pub async fn record_proxy_usage(&self, record: &ProxyUsageRecord) -> Result<(), AppError> {
|
||||
let conn = lock_conn!(self.conn);
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO proxy_usage
|
||||
(provider_id, app_type, endpoint, request_tokens, response_tokens,
|
||||
status_code, latency_ms, error, timestamp)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)",
|
||||
rusqlite::params![
|
||||
&record.provider_id,
|
||||
&record.app_type,
|
||||
&record.endpoint,
|
||||
record.request_tokens,
|
||||
record.response_tokens,
|
||||
record.status_code as i64,
|
||||
record.latency_ms as i64,
|
||||
&record.error,
|
||||
&record.timestamp,
|
||||
],
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 查询最近的使用统计
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_recent_usage(
|
||||
&self,
|
||||
provider_id: &str,
|
||||
app_type: &str,
|
||||
limit: usize,
|
||||
) -> Result<Vec<ProxyUsageRecord>, AppError> {
|
||||
let conn = lock_conn!(self.conn);
|
||||
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT provider_id, app_type, endpoint, request_tokens, response_tokens,
|
||||
status_code, latency_ms, error, timestamp
|
||||
FROM proxy_usage
|
||||
WHERE provider_id = ?1 AND app_type = ?2
|
||||
ORDER BY timestamp DESC
|
||||
LIMIT ?3",
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
let rows = stmt
|
||||
.query_map(
|
||||
rusqlite::params![provider_id, app_type, limit as i64],
|
||||
|row| {
|
||||
Ok(ProxyUsageRecord {
|
||||
provider_id: row.get(0)?,
|
||||
app_type: row.get(1)?,
|
||||
endpoint: row.get(2)?,
|
||||
request_tokens: row.get(3)?,
|
||||
response_tokens: row.get(4)?,
|
||||
status_code: row.get::<_, i64>(5)? as u16,
|
||||
latency_ms: row.get::<_, i64>(6)? as u64,
|
||||
error: row.get(7)?,
|
||||
timestamp: row.get(8)?,
|
||||
})
|
||||
},
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
let mut records = Vec::new();
|
||||
for row in rows {
|
||||
records.push(row.map_err(|e| AppError::Database(e.to_string()))?);
|
||||
}
|
||||
|
||||
Ok(records)
|
||||
}
|
||||
|
||||
// ==================== Circuit Breaker Config ====================
|
||||
|
||||
/// 获取熔断器配置
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
//! 流式健康检查日志 DAO
|
||||
|
||||
use crate::database::{lock_conn, Database};
|
||||
use crate::error::AppError;
|
||||
use crate::services::stream_check::{StreamCheckConfig, StreamCheckResult};
|
||||
|
||||
impl Database {
|
||||
/// 保存流式检查日志
|
||||
pub fn save_stream_check_log(
|
||||
&self,
|
||||
provider_id: &str,
|
||||
provider_name: &str,
|
||||
app_type: &str,
|
||||
result: &StreamCheckResult,
|
||||
) -> Result<i64, AppError> {
|
||||
let conn = lock_conn!(self.conn);
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO stream_check_logs
|
||||
(provider_id, provider_name, app_type, status, success, message,
|
||||
response_time_ms, http_status, model_used, retry_count, tested_at)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11)",
|
||||
rusqlite::params![
|
||||
provider_id,
|
||||
provider_name,
|
||||
app_type,
|
||||
format!("{:?}", result.status).to_lowercase(),
|
||||
result.success,
|
||||
result.message,
|
||||
result.response_time_ms.map(|t| t as i64),
|
||||
result.http_status.map(|s| s as i64),
|
||||
result.model_used,
|
||||
result.retry_count as i64,
|
||||
result.tested_at,
|
||||
],
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
Ok(conn.last_insert_rowid())
|
||||
}
|
||||
|
||||
/// 获取流式检查配置
|
||||
pub fn get_stream_check_config(&self) -> Result<StreamCheckConfig, AppError> {
|
||||
match self.get_setting("stream_check_config")? {
|
||||
Some(json) => serde_json::from_str(&json)
|
||||
.map_err(|e| AppError::Message(format!("解析配置失败: {e}"))),
|
||||
None => Ok(StreamCheckConfig::default()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 保存流式检查配置
|
||||
pub fn save_stream_check_config(&self, config: &StreamCheckConfig) -> Result<(), AppError> {
|
||||
let json = serde_json::to_string(config)
|
||||
.map_err(|e| AppError::Message(format!("序列化配置失败: {e}")))?;
|
||||
self.set_setting("stream_check_config", &json)
|
||||
}
|
||||
}
|
||||
@@ -173,40 +173,7 @@ impl Database {
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
// 10. Proxy Usage 表 (代理使用统计,可选)
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS proxy_usage (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
provider_id TEXT NOT NULL,
|
||||
app_type TEXT NOT NULL,
|
||||
endpoint TEXT NOT NULL,
|
||||
request_tokens INTEGER,
|
||||
response_tokens INTEGER,
|
||||
status_code INTEGER NOT NULL,
|
||||
latency_ms INTEGER NOT NULL,
|
||||
error TEXT,
|
||||
timestamp TEXT NOT NULL
|
||||
)",
|
||||
[],
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
// 为 proxy_usage 创建索引
|
||||
conn.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_proxy_usage_timestamp
|
||||
ON proxy_usage(timestamp)",
|
||||
[],
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
conn.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_proxy_usage_provider
|
||||
ON proxy_usage(provider_id, app_type)",
|
||||
[],
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
// 11. Proxy Request Logs 表 (详细请求日志)
|
||||
// 10. Proxy Request Logs 表 (详细请求日志)
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS proxy_request_logs (
|
||||
request_id TEXT PRIMARY KEY,
|
||||
@@ -272,7 +239,7 @@ impl Database {
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
// 12. Model Pricing 表 (模型定价)
|
||||
// 11. Model Pricing 表 (模型定价)
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS model_pricing (
|
||||
model_id TEXT PRIMARY KEY,
|
||||
@@ -286,38 +253,20 @@ impl Database {
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
// 13. Usage Daily Stats 表 (每日聚合统计)
|
||||
// 12. Stream Check Logs 表 (流式健康检查日志)
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS usage_daily_stats (
|
||||
date TEXT NOT NULL,
|
||||
provider_id TEXT NOT NULL,
|
||||
app_type TEXT NOT NULL,
|
||||
model TEXT NOT NULL,
|
||||
request_count INTEGER NOT NULL DEFAULT 0,
|
||||
total_input_tokens INTEGER NOT NULL DEFAULT 0,
|
||||
total_output_tokens INTEGER NOT NULL DEFAULT 0,
|
||||
total_cost_usd TEXT NOT NULL DEFAULT '0',
|
||||
success_count INTEGER NOT NULL DEFAULT 0,
|
||||
error_count INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (date, provider_id, app_type, model)
|
||||
)",
|
||||
[],
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
// 14. Model Test Logs 表 (模型测试日志,独立于代理使用统计)
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS model_test_logs (
|
||||
"CREATE TABLE IF NOT EXISTS stream_check_logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
provider_id TEXT NOT NULL,
|
||||
provider_name TEXT NOT NULL,
|
||||
app_type TEXT NOT NULL,
|
||||
model TEXT NOT NULL,
|
||||
prompt TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
success INTEGER NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
response_time_ms INTEGER,
|
||||
http_status INTEGER,
|
||||
model_used TEXT,
|
||||
retry_count INTEGER DEFAULT 0,
|
||||
tested_at INTEGER NOT NULL
|
||||
)",
|
||||
[],
|
||||
@@ -325,20 +274,13 @@ impl Database {
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
conn.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_model_test_logs_provider
|
||||
ON model_test_logs(provider_id, app_type)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_stream_check_logs_provider
|
||||
ON stream_check_logs(app_type, provider_id, tested_at DESC)",
|
||||
[],
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
conn.execute(
|
||||
"CREATE INDEX IF NOT EXISTS idx_model_test_logs_tested_at
|
||||
ON model_test_logs(tested_at DESC)",
|
||||
[],
|
||||
)
|
||||
.map_err(|e| AppError::Database(e.to_string()))?;
|
||||
|
||||
// 15. Circuit Breaker Config 表 (熔断器配置)
|
||||
// 13. Circuit Breaker Config 表 (熔断器配置)
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS circuit_breaker_config (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
@@ -574,24 +516,6 @@ impl Database {
|
||||
[],
|
||||
)?;
|
||||
|
||||
// usage_daily_stats 表
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS usage_daily_stats (
|
||||
date TEXT NOT NULL,
|
||||
provider_id TEXT NOT NULL,
|
||||
app_type TEXT NOT NULL,
|
||||
model TEXT NOT NULL,
|
||||
request_count INTEGER NOT NULL DEFAULT 0,
|
||||
total_input_tokens INTEGER NOT NULL DEFAULT 0,
|
||||
total_output_tokens INTEGER NOT NULL DEFAULT 0,
|
||||
total_cost_usd TEXT NOT NULL DEFAULT '0',
|
||||
success_count INTEGER NOT NULL DEFAULT 0,
|
||||
error_count INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (date, provider_id, app_type, model)
|
||||
)",
|
||||
[],
|
||||
)?;
|
||||
|
||||
// 清空并重新插入模型定价
|
||||
conn.execute("DELETE FROM model_pricing", [])
|
||||
.map_err(|e| AppError::Database(format!("清空模型定价失败: {e}")))?;
|
||||
|
||||
Reference in New Issue
Block a user