fix(im_llm): 代理绕过Docker内部hostname
This commit is contained in:
1 parent
96aabc31f6
commit
9a43ee244d
24 files changed
+898
-129
No files matched your search
@@ -90,7 +90,7 @@ const App: React.FC = () => {
|
||||
localStorage.removeItem('aiflow_token');
|
||||
localStorage.removeItem('aiflow_user');
|
||||
};
|
||||
const isAdmin = user?.role === 'admin';
|
||||
const isAdmin = user?.role === 'admin' || user?.role === 'super_admin';
|
||||
|
||||
if (loading) {
|
||||
return <div className="loading-screen">加载中...</div>;
|
||||
@@ -130,7 +130,7 @@ const App: React.FC = () => {
|
||||
</Route>
|
||||
|
||||
{/* Admin Routes */}
|
||||
<Route path="/admin" element={<AdminLayout />}>
|
||||
<Route path="/admin" element={isAdmin ? <AdminLayout /> : <Navigate to="/login" replace />}>
|
||||
<Route index element={<AdminDashboard />} />
|
||||
<Route path="users" element={<AdminUsers />} />
|
||||
<Route path="models" element={<AdminModels />} />
|
||||
|
||||
@@ -194,13 +194,7 @@ const AdminLayout: React.FC = () => {
|
||||
const location = useLocation();
|
||||
|
||||
// 后台必须登录且为管理员
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem('aiflow_token');
|
||||
if (!token || !user || user.role !== 'admin') {
|
||||
logout();
|
||||
navigate('/login', { replace: true });
|
||||
}
|
||||
}, [user, logout, navigate]);
|
||||
// Auth guard moved to App.tsx route level (no race condition)
|
||||
|
||||
const navCrumb = (() => {
|
||||
|
||||
|
||||
@@ -109,12 +109,6 @@ const FrontLayout: React.FC = () => {
|
||||
<span className="nav-icon"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="3"/><path d="M12 2v4m0 12v4m10-10h-4M6 12H2"/><path d="m4.93 4.93 2.83 2.83m8.48 8.48 2.83 2.83M4.93 19.07l2.83-2.83m8.48-8.48 2.83-2.83"/></svg></span>
|
||||
{!collapsed && <span className="nav-label">Agent 编排</span>}
|
||||
</NavLink>
|
||||
<NavLink to="/" className={({ isActive }) => `nav-item nav-home-link`} end>
|
||||
<span className="nav-item-icon">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
||||
</span>
|
||||
{!collapsed && <span className="nav-item-label">返回首页</span>}
|
||||
</NavLink>
|
||||
{navGroups.map(group => (
|
||||
<div className="nav-section" key={group.title}>
|
||||
{!collapsed && <div className="nav-group-title">{group.title}</div>}
|
||||
@@ -161,7 +155,7 @@ const FrontLayout: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{user.role === 'admin' && (
|
||||
{user.role === 'admin' || user.role === 'super_admin' && (
|
||||
<NavLink
|
||||
to="/admin"
|
||||
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
|
||||
|
||||
@@ -26,6 +26,10 @@ const capabilityLabels: Record<string, string> = {
|
||||
embedding: '嵌入向量', rerank: '重排序', video: '视频生成',
|
||||
};
|
||||
|
||||
// 锁定项:文本对话(text) 与 推理思考(thinking) 强制使用平台默认对话模型,
|
||||
// 防止被误配到失效模型导致 chat 不可用(见全栈体检修复记录)。
|
||||
const LOCKED_CAP_DEFAULTS: Record<string, string> = { text: 'aaae3233-4fd9-4b3e-ab51-cd8a87c82e0b', thinking: 'aaae3233-4fd9-4b3e-ab51-cd8a87c82e0b' };
|
||||
|
||||
// Test result type
|
||||
interface TestResult {
|
||||
modelId: string;
|
||||
@@ -527,6 +531,8 @@ const Models: React.FC = () => {
|
||||
);
|
||||
const cur = capabilityDefaults[cap.id] || '';
|
||||
const saving = savingCaps.has(cap.id);
|
||||
const lockedModelId = LOCKED_CAP_DEFAULTS[cap.id];
|
||||
const isLocked = Boolean(lockedModelId);
|
||||
const dotColor = cap.color || capabilityColors[cap.id] || '#6B7280';
|
||||
return (
|
||||
<div key={cap.id} style={{ border: '1px solid #EEF0F4', borderRadius: '8px', padding: '12px 14px', background: '#FBFCFE' }}>
|
||||
@@ -537,8 +543,8 @@ const Models: React.FC = () => {
|
||||
</div>
|
||||
<div style={{ fontSize: '12px', color: '#8a8a8a', marginBottom: '10px', minHeight: '32px' }}>{cap.desc}</div>
|
||||
<select
|
||||
value={cur}
|
||||
disabled={saving}
|
||||
value={isLocked ? (lockedModelId as string) : cur}
|
||||
disabled={saving || isLocked}
|
||||
onChange={e => handleSetCapabilityDefault(cap.id, e.target.value || null)}
|
||||
style={{ width: '100%', padding: '7px 10px', border: '1px solid #D1D5DB', borderRadius: '6px', fontSize: '13px', boxSizing: 'border-box', background: '#fff', color: '#333' }}
|
||||
>
|
||||
@@ -549,6 +555,9 @@ const Models: React.FC = () => {
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{isLocked && (
|
||||
<div style={{ fontSize: '11px', color: '#16A34A', marginTop: '6px' }}>🔒 已锁定为平台默认模型,不可更改</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -85,14 +85,14 @@ const LoginPage: React.FC = () => {
|
||||
createdAt: userData.created_at || new Date().toISOString(),
|
||||
membership: userData.membership_level > 0 ? {
|
||||
planId: userData.membership_level >= 3 ? 'mp3' : (userData.membership_level >= 2 ? 'mp2' : 'mp1'),
|
||||
planName: userData.role === 'admin' ? '管理员' : (userData.membership_level >= 3 ? '企业版' : (userData.membership_level >= 2 ? '专业版' : '免费版')),
|
||||
planName: userData.role === 'admin' || userData.role === 'super_admin' ? '管理员' : (userData.membership_level >= 3 ? '企业版' : (userData.membership_level >= 2 ? '专业版' : '免费版')),
|
||||
level: userData.membership_level,
|
||||
expireAt: userData.membership_expire_at || '',
|
||||
autoRenew: userData.auto_renew || false,
|
||||
} : undefined,
|
||||
};
|
||||
login(userObj);
|
||||
navigate(userData.role === 'admin' ? '/admin' : '/chat');
|
||||
navigate(userData.role === 'admin' || userData.role === 'super_admin' ? '/admin' : '/chat');
|
||||
} catch (e: any) {
|
||||
// 处理后端返回的精确错误信息,区分用户不存在和密码错误
|
||||
const status = e.status || e.response?.status || 0;
|
||||
|
||||
Reference in new issue
Block a user