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).
This commit is contained in:
1 parent
d3d8d62b94
commit
4a95657030
2 files changed
+125
-2
No files matched your search
@@ -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<any[]>([]);
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [sending, setSending] = useState(false);
|
||||
const [sendingTimeout, setSendingTimeout] = useState(false);
|
||||
const sendingTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const [conversations, setConversations] = useState<Conversation[]>([]);
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
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) ? (
|
||||
<div className="ai-error-bubble">
|
||||
<div className="ai-error-icon">⚠️</div>
|
||||
<div className="ai-error-text">
|
||||
<MarkdownContent content={msg.content} />
|
||||
</div>
|
||||
<button className="ai-error-retry" onClick={() => {
|
||||
setMessages(prev => prev.filter(m => m.id !== msg.id));
|
||||
const msgsAfterFilter = messages.filter(m => m.id !== msg.id);
|
||||
const lastUser = [...msgsAfterFilter].reverse().find(m => m.role === 'user');
|
||||
if (lastUser) sendText(lastUser.content);
|
||||
}}>🔄 重试</button>
|
||||
</div>
|
||||
) : msg.role === 'assistant' ? (
|
||||
<MarkdownContent content={msg.content} />
|
||||
) : (
|
||||
@@ -1315,9 +1358,18 @@ const ChatPage: React.FC = () => {
|
||||
)}
|
||||
{sending && (
|
||||
<div className="message-row assistant">
|
||||
<div className="message-bubble assistant" style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<div className={`message-bubble assistant${sendingTimeout ? ' ai-sending-timeout' : ''}`} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<IconSpinner />
|
||||
<span>思考中...</span>
|
||||
<span>{sendingTimeout ? '⏳ 响应时间较长,AI 仍在处理中...' : '思考中...'}</span>
|
||||
{sendingTimeout && (
|
||||
<button className="ai-cancel-btn" onClick={() => {
|
||||
setSending(false);
|
||||
setSendingTimeout(false);
|
||||
if (sendingTimerRef.current) clearTimeout(sendingTimerRef.current);
|
||||
}} title="取消等待" style={{ marginLeft: 12, padding: '2px 8px', fontSize: 12, cursor: 'pointer', background: 'var(--color-bg-tertiary)', border: '1px solid var(--color-border)', borderRadius: 4, color: 'var(--color-text-secondary)' }}>
|
||||
取消
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user