import { useState, useRef, useCallback, useEffect } from 'react'; import { ConfigProvider, Layout, theme, Typography, Space, Tabs, Slider, Radio } from 'antd'; import { ApartmentOutlined, ThunderboltOutlined, FilterOutlined } from '@ant-design/icons'; import { ThemeToggle } from './components/ThemeToggle'; import { InputPanel } from './components/InputPanel'; import { TransformationInputPanel } from './components/TransformationInputPanel'; import { MindmapPanel } from './components/MindmapPanel'; import { TransformationPanel } from './components/TransformationPanel'; import { DeduplicationPanel } from './components/DeduplicationPanel'; import { useAttribute } from './hooks/useAttribute'; import { getModels } from './services/api'; import type { MindmapDAGRef } from './components/MindmapDAG'; import type { TransformationDAGRef } from './components/TransformationDAG'; import type { CategoryMode, ExpertSource, ExpertTransformationDAGResult, DeduplicationMethod } from './types'; const { Header, Sider, Content } = Layout; const { Title } = Typography; interface VisualSettings { nodeSpacing: number; fontSize: number; } function App() { const [isDark, setIsDark] = useState(true); const [activeTab, setActiveTab] = useState('attribute'); const { loading, progress, error, currentResult, history, analyze, loadFromHistory } = useAttribute(); const [visualSettings, setVisualSettings] = useState({ nodeSpacing: 32, fontSize: 14, }); const mindmapRef = useRef(null); const transformationRef = useRef(null); // Transformation Agent settings const [transformModel, setTransformModel] = useState(''); const [transformTemperature, setTransformTemperature] = useState(0.95); const [expertConfig, setExpertConfig] = useState<{ expert_count: number; keywords_per_expert: number; custom_experts?: string[]; }>({ expert_count: 3, keywords_per_expert: 1, custom_experts: undefined, }); const [customExpertsInput, setCustomExpertsInput] = useState(''); const [expertSource, setExpertSource] = useState('llm'); const [expertLanguage, setExpertLanguage] = useState<'en' | 'zh'>('en'); const [shouldStartTransform, setShouldStartTransform] = useState(false); const [transformLoading, setTransformLoading] = useState(false); const [transformationResult, setTransformationResult] = useState(null); // Deduplication settings const [deduplicationThreshold, setDeduplicationThreshold] = useState(0.85); const [deduplicationMethod, setDeduplicationMethod] = useState('embedding'); // Available models from API const [availableModels, setAvailableModels] = useState([]); // Fetch models on mount useEffect(() => { async function fetchModels() { try { const response = await getModels(); setAvailableModels(response.models); // Set default model for transformation if not set if (response.models.length > 0 && !transformModel) { const defaultModel = response.models.find((m) => m.includes('qwen3')) || response.models[0]; setTransformModel(defaultModel); } } catch (err) { console.error('Failed to fetch models:', err); } } fetchModels(); }, []); const handleAnalyze = async ( query: string, model?: string, temperature?: number, chainCount?: number, categoryMode?: CategoryMode, customCategories?: string[], suggestedCategoryCount?: number ) => { await analyze(query, model, temperature, chainCount, categoryMode, customCategories, suggestedCategoryCount); }; const handleResetView = useCallback(() => { mindmapRef.current?.resetView(); }, []); const handleTransform = useCallback(() => { setShouldStartTransform(true); }, []); return (
Novelty Seeking
Attribute Agent ), children: (
), }, { key: 'transformation', label: ( Transformation Agent ), children: (
setShouldStartTransform(false)} onLoadingChange={setTransformLoading} onResultsChange={setTransformationResult} />
), }, { key: 'deduplication', label: ( Deduplication ), children: (
), }, ]} />
{activeTab === 'attribute' && ( )} {activeTab === 'transformation' && ( )} {activeTab === 'deduplication' && (
Deduplication Settings {/* Method Selection */}
Method setDeduplicationMethod(e.target.value)} buttonStyle="solid" style={{ width: '100%' }} > Embedding LLM Judge {deduplicationMethod === 'embedding' ? 'Fast vector similarity comparison' : 'Accurate but slower pairwise LLM comparison'}
{/* Threshold Slider - Only for Embedding method */} {deduplicationMethod === 'embedding' && (
Similarity Threshold Higher = stricter matching, fewer groups `${((val ?? 0) * 100).toFixed(0)}%` }} /> Current: {(deduplicationThreshold * 100).toFixed(0)}% similarity required
)} {/* LLM Warning */} {deduplicationMethod === 'llm' && ( Note: LLM method requires N*(N-1)/2 comparisons. May take longer for many descriptions. )}
)}
); } export default App;