diff --git a/Cargo.toml b/Cargo.toml index c6f84c7..fe760ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ reqwest = { version = "0.12.12", default-features = false, features = ["gzip", " rkyv = { version = "0.7.45", default-features = false, features = ["alloc", "std", "bytecheck", "size_64", "validation", "std"] } serde = { version = "1.0.217", default-features = false, features = ["std", "derive"] } serde_json = { package = "sonic-rs", version = "0.3.17" } +# serde_json = "1.0.137" sha2 = { version = "0.10.8", default-features = false } sysinfo = { version = "0.33.1", default-features = false, features = ["system"] } tokio = { version = "1.43.0", features = ["rt-multi-thread", "macros", "net", "sync", "time", "fs", "signal"] } diff --git a/Dockerfile b/Dockerfile index 3bf5683..a73112a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ RUN apt-get update && \ build-essential protobuf-compiler pkg-config libssl-dev nodejs npm \ && rm -rf /var/lib/apt/lists/* COPY . . -ENV RUSTFLAGS="-C link-arg=-s -C target-cpu=native" +ENV RUSTFLAGS="-C link-arg=-s -C target-cpu=x86-64-v3" RUN cargo build --release && \ cp target/release/cursor-api /app/cursor-api @@ -18,7 +18,7 @@ RUN apt-get update && \ build-essential protobuf-compiler pkg-config libssl-dev nodejs npm \ && rm -rf /var/lib/apt/lists/* COPY . . -ENV RUSTFLAGS="-C link-arg=-s -C target-cpu=native" +ENV RUSTFLAGS="-C link-arg=-s -C target-cpu=apple-m1" RUN cargo build --release && \ cp target/release/cursor-api /app/cursor-api diff --git a/Dockerfile.cross b/Dockerfile.cross index 1e71cba..902da8b 100644 --- a/Dockerfile.cross +++ b/Dockerfile.cross @@ -1,9 +1,7 @@ # Dockerfile.cross -# 使用与你 GitHub Actions 中相同的基础镜像 -FROM rust:1.84.0-slim-bookworm +FROM --platform=linux/amd64 rust:1.84.0-slim-bookworm -# 设置工作目录 WORKDIR /app # 安装必要的软件包 diff --git a/src/chat/constant.rs b/src/chat/constant.rs index bf14e40..b003cf5 100644 --- a/src/chat/constant.rs +++ b/src/chat/constant.rs @@ -48,32 +48,32 @@ def_pub_const!(GEMINI_2_0_FLASH_EXP, "gemini-2.0-flash-exp"); def_pub_const!(DEEPSEEK_V3, "deepseek-v3"); def_pub_const!(DEEPSEEK_R1, "deepseek-r1"); -#[derive(Clone, PartialEq, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)] -pub enum ModelType { - Claude35Sonnet, - Gpt4, - Gpt4o, - Claude3Opus, - CursorFast, - CursorSmall, - Gpt35Turbo, - Gpt4Turbo202404, - Gpt4o128k, - Gemini15Flash500k, - Claude3Haiku200k, - Claude35Sonnet200k, - Claude35Sonnet20241022, - Gpt4oMini, - O1Mini, - O1Preview, - O1, - Claude35Haiku, - GeminiExp1206, - Gemini20FlashThinkingExp, - Gemini20FlashExp, - DeepseekV3, - DeepseekR1, -} +// #[derive(Clone, PartialEq, rkyv::Archive, rkyv::Deserialize, rkyv::Serialize)] +// pub enum ModelType { +// Claude35Sonnet, +// Gpt4, +// Gpt4o, +// Claude3Opus, +// CursorFast, +// CursorSmall, +// Gpt35Turbo, +// Gpt4Turbo202404, +// Gpt4o128k, +// Gemini15Flash500k, +// Claude3Haiku200k, +// Claude35Sonnet200k, +// Claude35Sonnet20241022, +// Gpt4oMini, +// O1Mini, +// O1Preview, +// O1, +// Claude35Haiku, +// GeminiExp1206, +// Gemini20FlashThinkingExp, +// Gemini20FlashExp, +// DeepseekV3, +// DeepseekR1, +// } macro_rules! create_model { ($($id:expr, $owner:expr),* $(,)?) => { @@ -95,64 +95,64 @@ macro_rules! count { (($id:expr, $owner:expr) $( ($id2:expr, $owner2:expr) )*) => (1 + count!($( ($id2, $owner2) )*)); } -impl ModelType { - pub fn as_str_name(&self) -> &'static str { - match self { - ModelType::Claude35Sonnet => CLAUDE_3_5_SONNET, - ModelType::Gpt4 => GPT_4, - ModelType::Gpt4o => GPT_4O, - ModelType::Claude3Opus => CLAUDE_3_OPUS, - ModelType::CursorFast => CURSOR_FAST, - ModelType::CursorSmall => CURSOR_SMALL, - ModelType::Gpt35Turbo => GPT_3_5_TURBO, - ModelType::Gpt4Turbo202404 => GPT_4_TURBO_2024_04_09, - ModelType::Gpt4o128k => GPT_4O_128K, - ModelType::Gemini15Flash500k => GEMINI_1_5_FLASH_500K, - ModelType::Claude3Haiku200k => CLAUDE_3_HAIKU_200K, - ModelType::Claude35Sonnet200k => CLAUDE_3_5_SONNET_200K, - ModelType::Claude35Sonnet20241022 => CLAUDE_3_5_SONNET_20241022, - ModelType::Gpt4oMini => GPT_4O_MINI, - ModelType::O1Mini => O1_MINI, - ModelType::O1Preview => O1_PREVIEW, - ModelType::O1 => O1, - ModelType::Claude35Haiku => CLAUDE_3_5_HAIKU, - ModelType::GeminiExp1206 => GEMINI_EXP_1206, - ModelType::Gemini20FlashThinkingExp => GEMINI_2_0_FLASH_THINKING_EXP, - ModelType::Gemini20FlashExp => GEMINI_2_0_FLASH_EXP, - ModelType::DeepseekV3 => DEEPSEEK_V3, - ModelType::DeepseekR1 => DEEPSEEK_R1, - } - } +// impl ModelType { +// pub fn as_str_name(&self) -> &'static str { +// match self { +// ModelType::Claude35Sonnet => CLAUDE_3_5_SONNET, +// ModelType::Gpt4 => GPT_4, +// ModelType::Gpt4o => GPT_4O, +// ModelType::Claude3Opus => CLAUDE_3_OPUS, +// ModelType::CursorFast => CURSOR_FAST, +// ModelType::CursorSmall => CURSOR_SMALL, +// ModelType::Gpt35Turbo => GPT_3_5_TURBO, +// ModelType::Gpt4Turbo202404 => GPT_4_TURBO_2024_04_09, +// ModelType::Gpt4o128k => GPT_4O_128K, +// ModelType::Gemini15Flash500k => GEMINI_1_5_FLASH_500K, +// ModelType::Claude3Haiku200k => CLAUDE_3_HAIKU_200K, +// ModelType::Claude35Sonnet200k => CLAUDE_3_5_SONNET_200K, +// ModelType::Claude35Sonnet20241022 => CLAUDE_3_5_SONNET_20241022, +// ModelType::Gpt4oMini => GPT_4O_MINI, +// ModelType::O1Mini => O1_MINI, +// ModelType::O1Preview => O1_PREVIEW, +// ModelType::O1 => O1, +// ModelType::Claude35Haiku => CLAUDE_3_5_HAIKU, +// ModelType::GeminiExp1206 => GEMINI_EXP_1206, +// ModelType::Gemini20FlashThinkingExp => GEMINI_2_0_FLASH_THINKING_EXP, +// ModelType::Gemini20FlashExp => GEMINI_2_0_FLASH_EXP, +// ModelType::DeepseekV3 => DEEPSEEK_V3, +// ModelType::DeepseekR1 => DEEPSEEK_R1, +// } +// } - pub fn from_str_name(id :&str) -> Option { - match id { - CLAUDE_3_5_SONNET => Some(ModelType::Claude35Sonnet), - GPT_4 => Some(ModelType::Gpt4), - GPT_4O => Some(ModelType::Gpt4o), - CLAUDE_3_OPUS => Some(ModelType::Claude3Opus), - CURSOR_FAST => Some(ModelType::CursorFast), - CURSOR_SMALL => Some(ModelType::CursorSmall), - GPT_3_5_TURBO => Some(ModelType::Gpt35Turbo), - GPT_4_TURBO_2024_04_09 => Some(ModelType::Gpt4Turbo202404), - GPT_4O_128K => Some(ModelType::Gpt4o128k), - GEMINI_1_5_FLASH_500K => Some(ModelType::Gemini15Flash500k), - CLAUDE_3_HAIKU_200K => Some(ModelType::Claude3Haiku200k), - CLAUDE_3_5_SONNET_200K => Some(ModelType::Claude35Sonnet200k), - CLAUDE_3_5_SONNET_20241022 => Some(ModelType::Claude35Sonnet20241022), - GPT_4O_MINI => Some(ModelType::Gpt4oMini), - O1_MINI => Some(ModelType::O1Mini), - O1_PREVIEW => Some(ModelType::O1Preview), - O1 => Some(ModelType::O1), - CLAUDE_3_5_HAIKU => Some(ModelType::Claude35Haiku), - GEMINI_EXP_1206 => Some(ModelType::GeminiExp1206), - GEMINI_2_0_FLASH_THINKING_EXP => Some(ModelType::Gemini20FlashThinkingExp), - GEMINI_2_0_FLASH_EXP => Some(ModelType::Gemini20FlashExp), - DEEPSEEK_V3 => Some(ModelType::DeepseekV3), - DEEPSEEK_R1 => Some(ModelType::DeepseekR1), - _ => None, - } - } -} +// pub fn from_str_name(id :&str) -> Option { +// match id { +// CLAUDE_3_5_SONNET => Some(ModelType::Claude35Sonnet), +// GPT_4 => Some(ModelType::Gpt4), +// GPT_4O => Some(ModelType::Gpt4o), +// CLAUDE_3_OPUS => Some(ModelType::Claude3Opus), +// CURSOR_FAST => Some(ModelType::CursorFast), +// CURSOR_SMALL => Some(ModelType::CursorSmall), +// GPT_3_5_TURBO => Some(ModelType::Gpt35Turbo), +// GPT_4_TURBO_2024_04_09 => Some(ModelType::Gpt4Turbo202404), +// GPT_4O_128K => Some(ModelType::Gpt4o128k), +// GEMINI_1_5_FLASH_500K => Some(ModelType::Gemini15Flash500k), +// CLAUDE_3_HAIKU_200K => Some(ModelType::Claude3Haiku200k), +// CLAUDE_3_5_SONNET_200K => Some(ModelType::Claude35Sonnet200k), +// CLAUDE_3_5_SONNET_20241022 => Some(ModelType::Claude35Sonnet20241022), +// GPT_4O_MINI => Some(ModelType::Gpt4oMini), +// O1_MINI => Some(ModelType::O1Mini), +// O1_PREVIEW => Some(ModelType::O1Preview), +// O1 => Some(ModelType::O1), +// CLAUDE_3_5_HAIKU => Some(ModelType::Claude35Haiku), +// GEMINI_EXP_1206 => Some(ModelType::GeminiExp1206), +// GEMINI_2_0_FLASH_THINKING_EXP => Some(ModelType::Gemini20FlashThinkingExp), +// GEMINI_2_0_FLASH_EXP => Some(ModelType::Gemini20FlashExp), +// DEEPSEEK_V3 => Some(ModelType::DeepseekV3), +// DEEPSEEK_R1 => Some(ModelType::DeepseekR1), +// _ => None, +// } +// } +// } create_model!( CLAUDE_3_5_SONNET, ANTHROPIC, diff --git a/src/chat/service.rs b/src/chat/service.rs index 426d6e6..ada0b49 100644 --- a/src/chat/service.rs +++ b/src/chat/service.rs @@ -491,8 +491,13 @@ pub async fn handle_chat( } StreamMessage::Debug(debug_prompt) => { if let Ok(mut state) = ctx.state.try_lock() { - if let Some(last_log) = state.request_logs.last_mut() { - last_log.prompt = Some(debug_prompt); + if let Some(log) = state + .request_logs + .iter_mut() + .rev() + .find(|log| log.id == ctx.current_id) + { + log.prompt = Some(debug_prompt); } } } @@ -507,7 +512,7 @@ pub async fn handle_chat( let mut stream = response.bytes_stream(); // 处理第一个chunk并获取first_result - while decoder.lock().await.has_no_first_result() { + while !decoder.lock().await.is_first_result_ready() { match stream.next().await { Some(first_chunk) => { let chunk = first_chunk.map_err(|e| { @@ -642,9 +647,8 @@ pub async fn handle_chat( let mut decoder = StreamDecoder::new(); let mut full_text = String::with_capacity(1024); let mut stream = response.bytes_stream(); - let mut all_chunks = Vec::new(); - // 收集所有的chunks + // 逐个处理chunks while let Some(chunk) = stream.next().await { let chunk = chunk.map_err(|e| { let error_message = format!("Failed to read response chunk: {}", e); @@ -653,47 +657,50 @@ pub async fn handle_chat( Json(ChatError::RequestFailed(error_message).to_json()), ) })?; - all_chunks.extend(chunk); - } - // 一次性解码所有数据 - let messages = match decoder.decode(&all_chunks, convert_web_ref) { - Ok(msgs) => msgs, - Err(StreamError::ChatError(error)) => { - let error_response = error.to_error_response(); - return Err(( - error_response.status_code(), - Json(error_response.to_common()), - )); - } - Err(e) => { - let error_response = ErrorResponse { - status: ApiStatus::Error, - code: Some(500), - error: Some(e.to_string()), - message: None, - }; - return Err((StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))); - } - }; - - // 处理所有消息 - for message in messages { - match message { - StreamMessage::Content(text) => { - if first_chunk_time.is_none() { - first_chunk_time = Some(start_time.elapsed().as_secs_f64()); - } - full_text.push_str(&text); - } - StreamMessage::Debug(debug_prompt) => { - if let Ok(mut state) = state.try_lock() { - if let Some(last_log) = state.request_logs.last_mut() { - last_log.prompt = Some(debug_prompt); + // 立即处理当前chunk + match decoder.decode(&chunk, convert_web_ref) { + Ok(messages) => { + for message in messages { + match message { + StreamMessage::Content(text) => { + if first_chunk_time.is_none() { + first_chunk_time = Some(start_time.elapsed().as_secs_f64()); + } + full_text.push_str(&text); + } + StreamMessage::Debug(debug_prompt) => { + if let Ok(mut state) = state.try_lock() { + if let Some(log) = state + .request_logs + .iter_mut() + .rev() + .find(|log| log.id == current_id) + { + log.prompt = Some(debug_prompt); + } + } + } + _ => {} } } } - _ => {} + Err(StreamError::ChatError(error)) => { + let error_response = error.to_error_response(); + return Err(( + error_response.status_code(), + Json(error_response.to_common()), + )); + } + Err(e) => { + let error_response = ErrorResponse { + status: ApiStatus::Error, + code: Some(500), + error: Some(e.to_string()), + message: None, + }; + return Err((StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))); + } } } diff --git a/src/chat/stream/decoder.rs b/src/chat/stream/decoder.rs index 936df7e..76e23d1 100644 --- a/src/chat/stream/decoder.rs +++ b/src/chat/stream/decoder.rs @@ -79,7 +79,7 @@ impl StreamDecoder { // 获取第一个结果的引用 pub fn take_first_result(&mut self) -> Option> { - if self.is_incomplete() { + if !self.buffer.is_empty() { return None; } if self.first_result.is_some() { @@ -88,12 +88,13 @@ impl StreamDecoder { self.first_result.take() } + #[cfg(test)] fn is_incomplete(&self) -> bool { !self.buffer.is_empty() } - pub fn has_no_first_result(&self) -> bool { - self.first_result.is_none() + pub fn is_first_result_ready(&self) -> bool { + self.first_result.is_some() && self.buffer.is_empty() && !self.first_result_taken } pub fn decode(&mut self, data: &[u8], convert_web_ref: bool) -> Result, StreamError> { diff --git a/src/main.rs b/src/main.rs index 2d0658a..13a85e0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,14 +39,14 @@ use tower_http::{cors::CorsLayer, limit::RequestBodyLimitLayer}; #[tokio::main] async fn main() { // 设置自定义 panic hook - std::panic::set_hook(Box::new(|info| { - // std::env::set_var("RUST_BACKTRACE", "1"); - if let Some(msg) = info.payload().downcast_ref::() { - eprintln!("{}", msg); - } else if let Some(msg) = info.payload().downcast_ref::<&str>() { - eprintln!("{}", msg); - } - })); + // std::panic::set_hook(Box::new(|info| { + // // std::env::set_var("RUST_BACKTRACE", "1"); + // if let Some(msg) = info.payload().downcast_ref::() { + // eprintln!("{}", msg); + // } else if let Some(msg) = info.payload().downcast_ref::<&str>() { + // eprintln!("{}", msg); + // } + // })); // 加载环境变量 dotenvy::dotenv().ok(); diff --git a/static/config.html b/static/config.html index 69f1299..d60cd1b 100644 --- a/static/config.html +++ b/static/config.html @@ -106,6 +106,15 @@ style="display: none;"> +
+ + +
+
@@ -173,6 +182,8 @@ document.getElementById('usage_check_models_list').value = data.data.usage_check_models?.type === 'list' ? data.data.usage_check_models?.content || '' : document.getElementById('usage_check_models_list').value; document.getElementById('enable_dynamic_key').value = parseStringFromBoolean(data.data.enable_dynamic_key, ''); + document.getElementById('include_web_references').value = + parseStringFromBoolean(data.data.include_web_references, ''); // 处理代理设置 const proxies = data.data.proxies || ''; @@ -260,6 +271,9 @@ } })() }), + ...(document.getElementById('include_web_references').value && { + include_web_references: parseBooleanFromString(document.getElementById('include_web_references').value) + }), share_token: document.getElementById('shareToken').value.trim(), };