fix(desktop): DeerFlow UI — ChatArea refactor + ai-elements + dead CSS cleanup

ChatArea retry button uses setInput instead of direct sendToGateway,
fix bootstrap spinner stuck for non-logged-in users,
remove dead CSS (aurora-title/sidebar-open/quick-action-chips),
add ai components (ReasoningBlock/StreamingText/ChatMode/ModelSelector/TaskProgress),
add ClassroomPlayer + ResizableChatLayout + artifact panel

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
iven
2026-04-02 19:24:44 +08:00
parent d40c4605b2
commit 28299807b6
70 changed files with 4938 additions and 618 deletions

View File

@@ -67,14 +67,17 @@ async fn verify_api_token(state: &AppState, raw_token: &str, client_ip: Option<S
}
}
// 异步更新 last_used_at(不阻塞请求)
let db = state.db.clone();
tokio::spawn(async move {
let now = chrono::Utc::now().to_rfc3339();
let _ = sqlx::query("UPDATE api_tokens SET last_used_at = $1 WHERE token_hash = $2")
.bind(&now).bind(&token_hash)
.execute(&db).await;
});
// 异步更新 last_used_at — 通过 Worker 通道派发,受 SpawnLimiter 门控
// 替换原来的 tokio::spawn(DB UPDATE),消除每请求无限制 spawn
{
use crate::workers::update_last_used::UpdateLastUsedArgs;
let args = UpdateLastUsedArgs {
token_hash: token_hash.to_string(),
};
if let Err(e) = state.worker_dispatcher.dispatch("update_last_used", args).await {
tracing::debug!("Failed to dispatch update_last_used: {}", e);
}
}
Ok(AuthContext {
account_id,
@@ -84,23 +87,43 @@ async fn verify_api_token(state: &AppState, raw_token: &str, client_ip: Option<S
})
}
/// 从请求中提取客户端 IP
fn extract_client_ip(req: &Request) -> Option<String> {
// 优先从 ConnectInfo 获取
if let Some(ConnectInfo(addr)) = req.extensions().get::<ConnectInfo<SocketAddr>>() {
return Some(addr.ip().to_string());
/// 从请求中提取客户端 IP(安全版:仅对 trusted_proxies 解析 XFF
fn extract_client_ip(req: &Request, trusted_proxies: &[String]) -> Option<String> {
// 优先从 ConnectInfo 获取直接连接 IP
let connect_ip = req.extensions()
.get::<ConnectInfo<SocketAddr>>()
.map(|ConnectInfo(addr)| addr.ip().to_string());
// 仅当直接连接 IP 在 trusted_proxies 中时,才信任 XFF/X-Real-IP
if let Some(ref ip) = connect_ip {
if trusted_proxies.iter().any(|p| p == ip) {
// 受信代理 → 从 XFF 取真实客户端 IP
if let Some(forwarded) = req.headers()
.get("x-forwarded-for")
.and_then(|v| v.to_str().ok())
{
if let Some(client) = forwarded.split(',').next() {
let trimmed = client.trim();
if !trimmed.is_empty() {
return Some(trimmed.to_string());
}
}
}
// 尝试 X-Real-IP
if let Some(real_ip) = req.headers()
.get("x-real-ip")
.and_then(|v| v.to_str().ok())
{
let trimmed = real_ip.trim();
if !trimmed.is_empty() {
return Some(trimmed.to_string());
}
}
}
}
// 回退到 X-Forwarded-For / X-Real-IP
if let Some(forwarded) = req.headers()
.get("x-forwarded-for")
.and_then(|v| v.to_str().ok())
{
return Some(forwarded.split(',').next()?.trim().to_string());
}
req.headers()
.get("x-real-ip")
.and_then(|v| v.to_str().ok())
.map(|s| s.to_string())
// 非受信来源或无代理头 → 返回直接连接 IP
connect_ip
}
/// 认证中间件: 从 JWT Cookie / Authorization Header / API Token 提取身份
@@ -110,7 +133,10 @@ pub async fn auth_middleware(
mut req: Request,
next: Next,
) -> Response {
let client_ip = extract_client_ip(&req);
let client_ip = {
let config = state.config.read().await;
extract_client_ip(&req, &config.server.trusted_proxies)
};
let auth_header = req.headers()
.get(header::AUTHORIZATION)
.and_then(|v| v.to_str().ok());