diff --git a/crates/zclaw-saas/src/relay/key_pool.rs b/crates/zclaw-saas/src/relay/key_pool.rs index b679fca..7942f69 100644 --- a/crates/zclaw-saas/src/relay/key_pool.rs +++ b/crates/zclaw-saas/src/relay/key_pool.rs @@ -28,7 +28,7 @@ fn get_cache() -> &'static DashMap { KEY_SELECTION_CACHE.get_or_init(DashMap::new) } -/// Invalidate cached selection for a provider (called on usage record and 429 marking) +/// Invalidate cached selection for a provider (called on 429 marking) fn invalidate_cache(provider_id: &str) { let cache = get_cache(); cache.remove(provider_id); @@ -86,7 +86,7 @@ pub async fn select_best_key(db: &PgPool, provider_id: &str, enc_key: &[u8; 32]) COALESCE(SUM(uw.token_count), 0) FROM provider_keys pk LEFT JOIN key_usage_window uw ON pk.id = uw.key_id - AND uw.window_minute >= (NOW() - INTERVAL '1 minute')::TEXT + AND uw.window_minute >= to_char(NOW() - INTERVAL '1 minute', 'YYYY-MM-DDTHH24:MI') WHERE pk.provider_id = $1 AND pk.is_active = TRUE AND (pk.cooldown_until IS NULL OR pk.cooldown_until::timestamptz <= $2) GROUP BY pk.id, pk.key_value, pk.priority, pk.max_rpm, pk.max_tpm @@ -200,7 +200,7 @@ pub async fn record_key_usage( // 3. 清理过期的滑动窗口行(保留最近 2 分钟即可) let _ = sqlx::query( - "DELETE FROM key_usage_window WHERE window_minute < (NOW() - INTERVAL '2 minutes')::TEXT" + "DELETE FROM key_usage_window WHERE window_minute < to_char(NOW() - INTERVAL '2 minutes', 'YYYY-MM-DDTHH24:MI')" ) .execute(db).await; // 忽略错误,非关键操作 diff --git a/crates/zclaw-saas/src/relay/service.rs b/crates/zclaw-saas/src/relay/service.rs index fcfa28e..bf0f92f 100644 --- a/crates/zclaw-saas/src/relay/service.rs +++ b/crates/zclaw-saas/src/relay/service.rs @@ -651,7 +651,7 @@ pub async fn sort_candidates_by_quota( SUM(COALESCE(pk.max_rpm, 999999) - COALESCE(uw.request_count, 0)) AS remaining_rpm FROM provider_keys pk LEFT JOIN key_usage_window uw ON pk.id = uw.key_id - AND uw.window_minute = to_char(date_trunc('minute', NOW()), 'YYYY-MM-DDTHH24:MI') + AND uw.window_minute >= to_char(NOW() - INTERVAL '1 minute', 'YYYY-MM-DDTHH24:MI') WHERE pk.provider_id = ANY($1) AND pk.is_active = TRUE AND (pk.cooldown_until IS NULL OR pk.cooldown_until::timestamptz <= NOW()) diff --git a/desktop/src/components/ChatArea.tsx b/desktop/src/components/ChatArea.tsx index a977c7c..a0cfc33 100644 --- a/desktop/src/components/ChatArea.tsx +++ b/desktop/src/components/ChatArea.tsx @@ -31,6 +31,7 @@ import { ReasoningBlock } from './ai/ReasoningBlock'; import { StreamingText } from './ai/StreamingText'; import { ChatMode } from './ai/ChatMode'; import { ModelSelector } from './ai/ModelSelector'; +import { isTauriRuntime } from '../lib/tauri-gateway'; import { SuggestionChips } from './ai/SuggestionChips'; import { PipelineResultPreview } from './pipeline/PipelineResultPreview'; import { PresentationContainer } from './presentation/PresentationContainer'; @@ -562,7 +563,7 @@ export function ChatArea({ compact, onOpenDetail }: { compact?: boolean; onOpenD }
- {models.length > 0 && ( + {models.length > 0 && (!isTauriRuntime() || isLoggedIn) && ( ({ id: m.id, name: m.name, provider: m.provider }))} currentModel={currentModel} diff --git a/desktop/src/lib/saas-relay-client.ts b/desktop/src/lib/saas-relay-client.ts index 3642714..a9360fc 100644 --- a/desktop/src/lib/saas-relay-client.ts +++ b/desktop/src/lib/saas-relay-client.ts @@ -134,7 +134,7 @@ export function createSaaSRelayGatewayClient( if (opts?.plan_mode) body['plan_mode'] = true; if (opts?.subagent_enabled) body['subagent_enabled'] = true; - const response = await saasClient.chatCompletion(body); + const response = await saasClient.chatCompletion(body, abortController.signal); if (!response.ok) { const errText = await response.text().catch(() => ''); @@ -160,6 +160,9 @@ export function createSaaSRelayGatewayClient( buffer += decoder.decode(value, { stream: true }); + // Normalize CRLF to LF for SSE spec compliance + buffer = buffer.replace(/\r\n/g, '\n'); + // Optimized SSE parsing: split by double-newline (event boundaries) let boundary: number; while ((boundary = buffer.indexOf('\n\n')) !== -1) {