import { useEffect, useState } from 'react' import { Button, Card, Form, Input, Space, Typography } from '@arco-design/web-react' import { IconCheckCircleFill, IconCloseCircleFill, IconLaunch, IconLoading, } from '@arco-design/web-react/icon' const { Title, Text } = Typography type ConnectionStatus = 'checking' | 'online' | 'offline' export function LoginPage({ onLogin }: { onLogin: (input: { server: string; email: string; password: string }) => void }) { const [server, setServer] = useState('http://localhost:9150') const [email, setEmail] = useState('demo@senlin.ai') const [password, setPassword] = useState('password123') const [status, setStatus] = useState('checking') useEffect(() => { const controller = new AbortController() const timer = window.setTimeout(() => { void checkServerStatus(server, controller.signal, setStatus) }, 300) return () => { controller.abort() window.clearTimeout(timer) } }, [server]) return (
森林AI
私有部署 · 安全可控的智能协作平台 知识沉淀 · 团队协作 · AI 助手
登录到您的森林AI 连接私有工作台
} /> checkServerStatus(server, undefined, setStatus)} />
记住服务器地址
) } function ConnectionCard({ status, onCheck }: { status: ConnectionStatus; onCheck: () => void }) { const icon = status === 'checking' ? : status === 'online' ? : const title = status === 'checking' ? '正在检查' : status === 'online' ? '连接正常' : '连接异常' return (
{icon}
{title}
) } async function checkServerStatus( server: string, signal: AbortSignal | undefined, setStatus: (status: ConnectionStatus) => void, ) { const baseUrl = normalizeBaseUrl(server) setStatus('checking') try { const response = await fetch(`${baseUrl}/api/status`, { signal }) if (!response.ok) throw new Error(`HTTP ${response.status}`) const payload = await response.json() as { timestamp_ms?: number } if (typeof payload.timestamp_ms !== 'number') throw new Error('invalid status payload') setStatus('online') } catch (error) { if (error instanceof DOMException && error.name === 'AbortError') return setStatus('offline') } } function normalizeBaseUrl(value: string) { const trimmed = value.trim() return trimmed.endsWith('/') ? trimmed.slice(0, -1) : trimmed }