feat: 全栈功能增强 - 扩展 provider/pool 管理、完善调度与数据层、重构前端 Pool 页面

后端:
- 扩展 pool_admin payloads 和 provider query models,增强 endpoint key 管理
- 完善 scheduler-core 候选排序与请求候选逻辑
- 增强 usage-runtime 写入、provider-transport 网络层与 OAuth 刷新
- 改进 AI pipeline 响应转换与流式处理
- 扩展 global_models/provider_catalog 数据层查询能力
- 增强 video-tasks-core 多 provider 支持
- 新增大量集成测试覆盖 pool/keys/provider_query/frontdoor

前端:
- 重构 PoolManagement 页面,拆分状态管理/对话框逻辑到独立模块
- 新增 poolAdvancedDialog/poolSchedulingDialog/poolManagementState/poolMobilePresentation 工具函数及测试
- 改进 Dialog 组件与 provider tabs 显示

部署:
- 更新 Rust CI workflow 和 Dockerfile 构建配置

Closes #275
Co-authored-by: AAEE86 <ppk0227@hotmail.com>
This commit is contained in:
fawney19
2026-04-09 13:51:50 +08:00
parent 4fc95adfb9
commit b0b40c16ff
97 changed files with 5816 additions and 1881 deletions

View File

@@ -8,5 +8,5 @@ pub use gemini_chat::convert_openai_chat_response_to_gemini_chat;
pub use openai_cli::convert_openai_chat_response_to_openai_cli;
pub use shared::{
build_openai_cli_response, build_openai_cli_response_with_content,
build_openai_cli_response_with_reasoning,
build_openai_cli_response_with_reasoning, OpenAiCliResponseUsage,
};

View File

@@ -1,6 +1,8 @@
use serde_json::{json, Value};
use super::shared::{build_openai_cli_response_with_content, canonicalize_tool_arguments};
use super::shared::{
build_openai_cli_response_with_content, canonicalize_tool_arguments, OpenAiCliResponseUsage,
};
pub fn convert_openai_chat_response_to_openai_cli(
body_json: &Value,
@@ -143,8 +145,10 @@ pub fn convert_openai_chat_response_to_openai_cli(
message_content,
reasoning_summaries,
function_calls,
prompt_tokens,
output_tokens,
total_tokens,
OpenAiCliResponseUsage {
prompt_tokens,
output_tokens,
total_tokens,
},
))
}

View File

@@ -1,5 +1,12 @@
use serde_json::{json, Map, Value};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OpenAiCliResponseUsage {
pub prompt_tokens: u64,
pub output_tokens: u64,
pub total_tokens: u64,
}
pub fn build_openai_cli_response(
response_id: &str,
model: &str,
@@ -24,9 +31,11 @@ pub fn build_openai_cli_response(
content,
Vec::new(),
function_calls,
prompt_tokens,
output_tokens,
total_tokens,
OpenAiCliResponseUsage {
prompt_tokens,
output_tokens,
total_tokens,
},
)
}
@@ -36,9 +45,7 @@ pub fn build_openai_cli_response_with_reasoning(
text: &str,
reasoning_summaries: Vec<String>,
function_calls: Vec<Value>,
prompt_tokens: u64,
output_tokens: u64,
total_tokens: u64,
usage: OpenAiCliResponseUsage,
) -> Value {
let content = if text.is_empty() {
Vec::new()
@@ -55,9 +62,7 @@ pub fn build_openai_cli_response_with_reasoning(
content,
reasoning_summaries,
function_calls,
prompt_tokens,
output_tokens,
total_tokens,
usage,
)
}
@@ -67,9 +72,7 @@ pub fn build_openai_cli_response_with_content(
content: Vec<Value>,
reasoning_summaries: Vec<String>,
function_calls: Vec<Value>,
prompt_tokens: u64,
output_tokens: u64,
total_tokens: u64,
usage: OpenAiCliResponseUsage,
) -> Value {
let mut output = Vec::new();
for (index, summary) in reasoning_summaries.into_iter().enumerate() {
@@ -104,9 +107,9 @@ pub fn build_openai_cli_response_with_content(
"model": model,
"output": output,
"usage": {
"input_tokens": prompt_tokens,
"output_tokens": output_tokens,
"total_tokens": total_tokens,
"input_tokens": usage.prompt_tokens,
"output_tokens": usage.output_tokens,
"total_tokens": usage.total_tokens,
}
})
}

View File

@@ -1,6 +1,8 @@
use serde_json::{json, Value};
use super::super::from_openai_chat::build_openai_cli_response_with_reasoning;
use super::super::from_openai_chat::{
build_openai_cli_response_with_reasoning, OpenAiCliResponseUsage,
};
use super::shared::{build_generated_tool_call_id, canonicalize_tool_arguments};
pub fn convert_claude_cli_response_to_openai_cli(
@@ -76,8 +78,10 @@ pub fn convert_claude_cli_response_to_openai_cli(
&text,
reasoning_summaries,
function_calls,
prompt_tokens,
output_tokens,
total_tokens,
OpenAiCliResponseUsage {
prompt_tokens,
output_tokens,
total_tokens,
},
))
}

View File

@@ -1,6 +1,8 @@
use serde_json::{json, Value};
use super::super::from_openai_chat::build_openai_cli_response_with_content;
use super::super::from_openai_chat::{
build_openai_cli_response_with_content, OpenAiCliResponseUsage,
};
use super::shared::{
build_generated_tool_call_id, canonicalize_tool_arguments, extract_gemini_image_url,
};
@@ -99,8 +101,10 @@ pub fn convert_gemini_cli_response_to_openai_cli(
message_content,
reasoning_summaries,
function_calls,
prompt_tokens,
output_tokens,
total_tokens,
OpenAiCliResponseUsage {
prompt_tokens,
output_tokens,
total_tokens,
},
))
}

View File

@@ -187,12 +187,12 @@ impl ClaudeProviderState {
tool_state.call_id = block
.get("id")
.and_then(Value::as_str)
.unwrap_or_else(|| tool_state.call_id.as_str())
.unwrap_or(tool_state.call_id.as_str())
.to_string();
tool_state.name = block
.get("name")
.and_then(Value::as_str)
.unwrap_or_else(|| tool_state.name.as_str())
.unwrap_or(tool_state.name.as_str())
.to_string();
if !tool_state.started_emitted {
out.push(CanonicalStreamFrame {

View File

@@ -125,12 +125,12 @@ impl GeminiProviderState {
tool_state.call_id = function_call
.get("id")
.and_then(Value::as_str)
.unwrap_or_else(|| tool_state.call_id.as_str())
.unwrap_or(tool_state.call_id.as_str())
.to_string();
tool_state.name = function_call
.get("name")
.and_then(Value::as_str)
.unwrap_or_else(|| tool_state.name.as_str())
.unwrap_or(tool_state.name.as_str())
.to_string();
if !tool_state.started_emitted {
out.push(CanonicalStreamFrame {

View File

@@ -367,12 +367,12 @@ impl OpenAICliProviderState {
.get("call_id")
.or_else(|| item.get("id"))
.and_then(Value::as_str)
.unwrap_or_else(|| state.call_id.as_str())
.unwrap_or(state.call_id.as_str())
.to_string();
state.name = item
.get("name")
.and_then(Value::as_str)
.unwrap_or_else(|| state.name.as_str())
.unwrap_or(state.name.as_str())
.to_string();
if !state.started_emitted {
out.push(CanonicalStreamFrame {
@@ -525,12 +525,12 @@ impl OpenAICliProviderState {
.get("call_id")
.or_else(|| item.get("id"))
.and_then(Value::as_str)
.unwrap_or_else(|| state.call_id.as_str())
.unwrap_or(state.call_id.as_str())
.to_string();
state.name = item
.get("name")
.and_then(Value::as_str)
.unwrap_or_else(|| state.name.as_str())
.unwrap_or(state.name.as_str())
.to_string();
if !state.started_emitted {
out.push(CanonicalStreamFrame {
@@ -662,7 +662,7 @@ impl OpenAIChatClientEmitter {
return Ok(Vec::new());
}
self.started = true;
Ok(encode_json_sse(
encode_json_sse(
None,
&build_openai_chat_role_chunk(
self.response_id
@@ -670,7 +670,7 @@ impl OpenAIChatClientEmitter {
.unwrap_or("chatcmpl-local-stream"),
self.model.as_deref().unwrap_or("unknown"),
),
)?)
)
}
pub fn emit(&mut self, frame: CanonicalStreamFrame) -> Result<Vec<u8>, PipelineFinalizeError> {