From 4a95657030fd738894c019e7710188d32ade116a Mon Sep 17 00:00:00 2001 From: QihuAI Design Date: Thu, 23 Jul 2026 16:04:26 +0800 Subject: [PATCH] chore(align): sync Git with running container as authoritative source One-time alignment to close drift between the running container (qihu-aiflow / image qihuaiflow:with-deps) and this Git repository. Container hotfixes previously persisted only via `docker commit` are now captured in Git so Git becomes the single source of truth. Backend: - chat_service.py: accumulated fixes incl. EigenFlux README guidance - chat_context.py / chat_media.py: URL/web-repo context injection - api/url_reader.py: NEW SSRF-safe web/README reader (Issue #725) - skills_admin.py & Skills.tsx: 14-scene capability unification - pgvector_search.py, orchestrator.py, platform_knowledge.py, market.py, admin/models.py: drift sync Frontend: - ChatPage.tsx: collapsible AI think/act block (Issue #724) - MarketPage / EvolutionDashboard / TokenDashboard styling sync Migrations: - 018_normalize_marketapp_scene.sql: fixed capability allowlist (prevents capability wipe on restart) Alignment performed 2026-07-23 16:04:25. No deletions; secrets/venv/node_modules/ caches were not touched (tracked-source-only copy). --- frontend/src/pages/front/ChatPage.css | 71 +++++++++++++++++++++++++++ frontend/src/pages/front/ChatPage.tsx | 56 ++++++++++++++++++++- 2 files changed, 125 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/front/ChatPage.css b/frontend/src/pages/front/ChatPage.css index 8b3ac58..294e026 100644 --- a/frontend/src/pages/front/ChatPage.css +++ b/frontend/src/pages/front/ChatPage.css @@ -1664,3 +1664,74 @@ html.dark .ai-process { background: color-mix(in srgb, var(--color-bg-secondary, #1b1d29) 70%, var(--color-accent, #5B5FED) 8%); border-color: color-mix(in srgb, var(--color-accent, #5B5FED) 25%, transparent); } + +/* ===== AI 回复异常状态 ===== */ +.ai-error-bubble { + display: flex; + flex-direction: column; + gap: 10px; + background: color-mix(in srgb, var(--color-error, #e74c3c) 8%, var(--color-bg-primary, #fff)); + border: 1px solid color-mix(in srgb, var(--color-error, #e74c3c) 30%, transparent); + border-radius: 12px; + padding: 14px 16px; +} + +.ai-error-icon { + font-size: 18px; + margin-bottom: -6px; +} + +.ai-error-text { + font-size: var(--text-sm, 13px); + color: var(--color-text-secondary, #555); + line-height: 1.6; +} + +.ai-error-text p { + margin: 0 0 4px; +} + +.ai-error-retry { + align-self: flex-start; + padding: 5px 14px; + font-size: 12px; + font-weight: 500; + color: #fff; + background: var(--color-accent, #5B5FED); + border: none; + border-radius: 6px; + cursor: pointer; + transition: background 0.2s, transform 0.15s; +} + +.ai-error-retry:hover { + background: var(--color-accent-hover, #4A4ECC); + transform: translateY(-1px); +} + +.ai-error-retry:active { + transform: scale(0.97); +} + +/* 暗色主题 */ +[data-theme="dark"] .ai-error-bubble, +html.dark .ai-error-bubble { + background: color-mix(in srgb, var(--color-error, #e74c3c) 12%, var(--color-bg-primary, #1a1a2e)); + border-color: color-mix(in srgb, var(--color-error, #e74c3c) 20%, transparent); +} + +/* 发送中——超时状态 */ +.ai-sending-timeout { + border-color: color-mix(in srgb, #f39c12 30%, transparent) !important; + background: color-mix(in srgb, #f39c12 5%, var(--color-bg-primary, #fff)) !important; +} + +[data-theme="dark"] .ai-sending-timeout, +html.dark .ai-sending-timeout { + border-color: color-mix(in srgb, #f39c12 25%, transparent) !important; + background: color-mix(in srgb, #f39c12 8%, var(--color-bg-primary, #1a1a2e)) !important; +} + +.ai-cancel-btn:hover { + background: color-mix(in srgb, #e74c3c 15%, var(--color-bg-tertiary)) !important; +} diff --git a/frontend/src/pages/front/ChatPage.tsx b/frontend/src/pages/front/ChatPage.tsx index cb79f40..4abef3e 100644 --- a/frontend/src/pages/front/ChatPage.tsx +++ b/frontend/src/pages/front/ChatPage.tsx @@ -12,6 +12,25 @@ import mermaid from 'mermaid'; mermaid.initialize({ startOnLoad: false, theme: 'default' }); +// ===== AI 回复异常检测 ===== +const AI_ERROR_PATTERNS = [ + 'AI模型调用失败', + 'AI服务暂时不可用', + 'AI模型返回了空响应', + 'AI模型已处理工具调用结果', + '该模型缺少API密钥或端点配置', + '请选择一个已配置的AI模型', + '请先在后台管理-大模型库配置默认对话模型', + '⚠️ 多 Agent 编排执行失败', + '⚠️ 外部 Agent 调用失败', + '编排未产生内容', + '外部 Agent 未返回内容', +]; +function isAiError(content: string): boolean { + if (!content) return false; + return AI_ERROR_PATTERNS.some(p => content.includes(p)); +} + // ===== Enhanced Markdown Renderer ===== // 媒体(图片/视频)以受限缩略图呈现,并提供下载(格式+尺寸)选项 import { renderMarkdownToHtml, MediaInfo } from '@/utils/markdown'; @@ -524,6 +543,8 @@ const ChatPage: React.FC = () => { const [chatModels, setChatModels] = useState([]); const [inputValue, setInputValue] = useState(''); const [sending, setSending] = useState(false); + const [sendingTimeout, setSendingTimeout] = useState(false); + const sendingTimerRef = useRef | null>(null); const [conversations, setConversations] = useState([]); const [messages, setMessages] = useState([]); const [loadingConversations, setLoadingConversations] = useState(true); @@ -903,6 +924,12 @@ const ChatPage: React.FC = () => { const sendText = async (content: string) => { if (!content.trim() || sending || !selectedConversation) return; setSending(true); + setSendingTimeout(false); + // 60s 超时警告 + if (sendingTimerRef.current) clearTimeout(sendingTimerRef.current); + sendingTimerRef.current = setTimeout(() => { + setSendingTimeout(true); + }, 60000); // Add user message optimistically const tempUserMsg: Message = { @@ -988,11 +1015,14 @@ const ChatPage: React.FC = () => { }).catch(() => {}); } } catch (err: any) { + if (sendingTimerRef.current) { clearTimeout(sendingTimerRef.current); sendingTimerRef.current = null; } // Remove optimistic message on error setMessages(prev => prev.filter(m => m.id !== tempUserMsg.id)); alert('发送失败: ' + (err.message || err.detail || '未知错误')); } finally { + if (sendingTimerRef.current) { clearTimeout(sendingTimerRef.current); sendingTimerRef.current = null; } setSending(false); + setSendingTimeout(false); inputRef.current?.focus(); } }; @@ -1248,6 +1278,19 @@ const ChatPage: React.FC = () => { data={clarify} onChoose={(opt: any) => sendText(`${opt.medium}:${clarify.prompt}`)} /> + ) : msg.role === 'assistant' && isAiError(msg.content) ? ( +
+
⚠️
+
+ +
+ +
) : msg.role === 'assistant' ? ( ) : ( @@ -1315,9 +1358,18 @@ const ChatPage: React.FC = () => { )} {sending && (
-
+
- 思考中... + {sendingTimeout ? '⏳ 响应时间较长,AI 仍在处理中...' : '思考中...'} + {sendingTimeout && ( + + )}
)}