import { useEffect, useState } from 'react' import axios from 'axios' import { SyncOutlined } from '@ant-design/icons' import { Alert, Button, Card, Collapse, Form, Input, Space, Spin, Tabs, Tag, Typography, message, } from 'antd' import AppLayout from '../../components/AppLayout/AppLayout' const { Title, Text } = Typography const API_BASE_URL = (import.meta as any).env?.VITE_API_URL || '/api/v1' const PLAYGROUND_PROVIDER_STATUS_STORAGE_KEY = 'playground-provider-status' interface AIProviderStatus { provider: string api?: string | null enabled: boolean configured: boolean model?: string | null base_url?: string | null } interface AIContentBlock { type: string text?: string | null thinking?: string | null } interface SituationalAnalysisResponse { provider: string model: string content: string content_blocks: AIContentBlock[] text_blocks: string[] thinking_blocks: string[] raw_response: Record } interface PlaygroundFormValues { title: string objective: string observations?: string constraints?: string } function splitLines(value?: string) { return (value || '') .split('\n') .map((item) => item.trim()) .filter(Boolean) } function Playground() { const [form] = Form.useForm() const [messageApi, contextHolder] = message.useMessage() const [statusLoading, setStatusLoading] = useState(false) const [analyzing, setAnalyzing] = useState(false) const [providerStatus, setProviderStatus] = useState(null) const [analysis, setAnalysis] = useState(null) const [activeTab, setActiveTab] = useState<'request' | 'result'>('request') const [helpExpanded, setHelpExpanded] = useState(true) const loadProviderStatus = async (force = false) => { if (!force) { const cached = sessionStorage.getItem(PLAYGROUND_PROVIDER_STATUS_STORAGE_KEY) if (cached) { try { setProviderStatus(JSON.parse(cached) as AIProviderStatus) return } catch { sessionStorage.removeItem(PLAYGROUND_PROVIDER_STATUS_STORAGE_KEY) } } } setStatusLoading(true) try { const res = await axios.get(`${API_BASE_URL}/ai/provider/status`) setProviderStatus(res.data) sessionStorage.setItem(PLAYGROUND_PROVIDER_STATUS_STORAGE_KEY, JSON.stringify(res.data)) } catch { messageApi.error('AI Provider 状态获取失败') } finally { setStatusLoading(false) } } useEffect(() => { void loadProviderStatus() }, []) const handleAnalyze = async (values: PlaygroundFormValues) => { setAnalyzing(true) setActiveTab('result') try { const res = await axios.post( `${API_BASE_URL}/ai/situational-awareness/analyze`, { title: values.title.trim(), objective: values.objective.trim(), observations: splitLines(values.observations), constraints: splitLines(values.constraints), context: { source: 'playground', }, }, ) setAnalysis(res.data) messageApi.success('分析已完成') } catch { messageApi.error('分析失败,请检查 AI Provider 配置或稍后再试') setActiveTab('request') } finally { setAnalyzing(false) } } return ( {contextHolder}
AI Playground 在前端测试 AI 能力,但实际请求仍统一经由主后端转发到 AI Provider。
} onClick={() => void loadProviderStatus(true)} aria-label="刷新 Provider 状态" className="playground-card__icon-button" /> )} >
{providerStatus ? (
Provider {providerStatus.provider || '-'}
模型 {providerStatus.model || '-'}
API {providerStatus.api || '-'}
状态
{providerStatus.enabled ? 'enabled' : 'disabled'} {providerStatus.configured ? 'configured' : 'not configured'}
Base URL {providerStatus.base_url || '-'}
) : ( )}
setHelpExpanded(Array.isArray(keys) ? keys.includes('help') : keys === 'help')} items={[ { key: 'help', label: '测试说明', children: ( 当前链路: frontend /playground {' -> '} backend /api/v1/ai/* {' -> '} aiprovider /v1/* )} /> ), }, ]} />
setActiveTab(key as 'request' | 'result')} className="playground-tabs" items={[ { key: 'request', label: '请求', children: (
), }, { key: 'result', label: '结果', children: (
{analyzing ? (
AI 正在生成结果...
) : analysis ? (
{analysis.provider} {analysis.model}
{analysis.content}
{analysis.text_blocks.length ? (
文本块
{analysis.text_blocks.map((block, index) => (
{block}
))}
) : null}
) : ( )}
), }, ]} />
) } export default Playground