import { useState, useEffect } from 'react'; import { Input, Button, Select, List, Typography, Space, message, Slider, Divider, Collapse, Progress, Tag, } from 'antd'; import { SearchOutlined, HistoryOutlined, DownloadOutlined, ExpandAltOutlined, ShrinkOutlined, LoadingOutlined, CheckCircleOutlined, } from '@ant-design/icons'; import type { HistoryItem, AttributeNode, StreamProgress } from '../types'; import { getModels } from '../services/api'; const { TextArea } = Input; const { Text } = Typography; interface VisualSettings { nodeSpacing: number; fontSize: number; } interface InputPanelProps { loading: boolean; progress: StreamProgress; history: HistoryItem[]; currentResult: AttributeNode | null; onAnalyze: (query: string, model?: string, temperature?: number, chainCount?: number) => Promise; onLoadHistory: (item: HistoryItem) => void; onExpandAll?: () => void; onCollapseAll?: () => void; visualSettings: VisualSettings; onVisualSettingsChange: (settings: VisualSettings) => void; } export function InputPanel({ loading, progress, history, currentResult, onAnalyze, onLoadHistory, onExpandAll, onCollapseAll, visualSettings, onVisualSettingsChange, }: InputPanelProps) { const [query, setQuery] = useState(''); const [models, setModels] = useState([]); const [selectedModel, setSelectedModel] = useState(); const [loadingModels, setLoadingModels] = useState(false); const [temperature, setTemperature] = useState(0.7); const [chainCount, setChainCount] = useState(5); useEffect(() => { async function fetchModels() { setLoadingModels(true); try { const response = await getModels(); setModels(response.models); if (response.models.length > 0 && !selectedModel) { const defaultModel = response.models.find((m) => m.includes('qwen3')) || response.models[0]; setSelectedModel(defaultModel); } } catch { message.error('Failed to fetch models'); } finally { setLoadingModels(false); } } fetchModels(); }, []); const handleAnalyze = async () => { if (!query.trim()) { message.warning('Please enter a query'); return; } try { await onAnalyze(query.trim(), selectedModel, temperature, chainCount); setQuery(''); } catch { message.error('Analysis failed'); } }; const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleAnalyze(); } }; const handleExportJSON = () => { if (!currentResult) { message.warning('No data to export'); return; } const json = JSON.stringify(currentResult, null, 2); const blob = new Blob([json], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${currentResult.name || 'mindmap'}.json`; a.click(); URL.revokeObjectURL(url); }; const handleExportMarkdown = () => { if (!currentResult) { message.warning('No data to export'); return; } const nodeToMarkdown = (node: AttributeNode, level: number = 0): string => { const indent = ' '.repeat(level); let md = `${indent}- ${node.name}\n`; if (node.children) { node.children.forEach((child) => { md += nodeToMarkdown(child, level + 1); }); } return md; }; const markdown = `# ${currentResult.name}\n\n${currentResult.children?.map((c) => nodeToMarkdown(c, 0)).join('') || ''}`; const blob = new Blob([markdown], { type: 'text/markdown' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${currentResult.name || 'mindmap'}.md`; a.click(); URL.revokeObjectURL(url); }; const handleExportSVG = () => { const svg = document.querySelector('.mindmap-svg'); if (!svg) { message.warning('No mindmap to export'); return; } const svgData = new XMLSerializer().serializeToString(svg); const blob = new Blob([svgData], { type: 'image/svg+xml' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${currentResult?.name || 'mindmap'}.svg`; a.click(); URL.revokeObjectURL(url); }; const handleExportPNG = () => { const svg = document.querySelector('.mindmap-svg') as SVGSVGElement; if (!svg) { message.warning('No mindmap to export'); return; } const svgData = new XMLSerializer().serializeToString(svg); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const img = new Image(); img.onload = () => { canvas.width = svg.clientWidth * 2; canvas.height = svg.clientHeight * 2; ctx?.scale(2, 2); ctx?.drawImage(img, 0, 0); const pngUrl = canvas.toDataURL('image/png'); const a = document.createElement('a'); a.href = pngUrl; a.download = `${currentResult?.name || 'mindmap'}.png`; a.click(); }; img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgData))); }; const renderProgressIndicator = () => { if (progress.step === 'idle' || progress.step === 'done') return null; const percent = progress.step === 'step1' ? 10 : progress.step === 'chains' ? 10 + (progress.currentChainIndex / progress.totalChains) * 90 : 100; return (
{progress.step === 'error' ? ( Error ) : ( )} {progress.message} {progress.completedChains.length > 0 && (
Completed chains:
{progress.completedChains.map((chain, i) => (
{chain.material} → {chain.function} → {chain.usage} → {chain.user}
))}
)}
); }; const collapseItems = [ { key: 'llm', label: 'LLM Parameters', children: (
Temperature: {temperature}
Chain Count: {chainCount}
), }, { key: 'visual', label: 'Visual Settings', children: (
Node Spacing: {visualSettings.nodeSpacing} onVisualSettingsChange({ ...visualSettings, nodeSpacing: v })} />
Font Size: {visualSettings.fontSize}px onVisualSettingsChange({ ...visualSettings, fontSize: v })} />
), }, { key: 'export', label: 'Export', children: ( ), }, ]; return (
Model