import { useState, useRef, useCallback, useEffect, useMemo } from 'react'; import { ConfigProvider, Layout, theme, Typography, Space, Tabs, Slider, Radio, Switch, Segmented } from 'antd'; import { ApartmentOutlined, ThunderboltOutlined, FilterOutlined, SwapOutlined, FileSearchOutlined, GlobalOutlined } 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 { PatentSearchPanel } from './components/PatentSearchPanel'; import { DualPathInputPanel } from './components/DualPathInputPanel'; import { DualPathMindmapPanel } from './components/DualPathMindmapPanel'; import { CrossoverPanel } from './components/CrossoverPanel'; import { useAttribute } from './hooks/useAttribute'; import { useDualPathAttribute } from './hooks/useDualPathAttribute'; import { getModels } from './services/api'; import { crossoverPairsToDAGs, type CrossoverDAGResult } from './utils/crossoverToDAG'; import { DualTransformationPanel } from './components/DualTransformationPanel'; import type { MindmapDAGRef } from './components/MindmapDAG'; import type { TransformationDAGRef } from './components/TransformationDAG'; import type { CategoryMode, ExpertSource, ExpertTransformationDAGResult, DeduplicationMethod, ExpertMode, CrossoverPair, PromptLanguage } 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 [dualPathMode, setDualPathMode] = useState(false); const [promptLanguage, setPromptLanguage] = useState('zh'); // Single path hook const { loading, progress, error, currentResult, history, analyze, loadFromHistory } = useAttribute(); // Dual path hook const dualPath = useDualPathAttribute(); const [visualSettings, setVisualSettings] = useState({ nodeSpacing: 32, fontSize: 14, }); const mindmapRef = useRef(null); const transformationRef = useRef(null); // Dual path expert mode const [expertMode, setExpertMode] = useState('shared'); const [selectedCrossoverPairs, setSelectedCrossoverPairs] = useState([]); // Convert selected crossover pairs to two separate DAGs for dual transformation const crossoverDAGs = useMemo((): CrossoverDAGResult | null => { if (selectedCrossoverPairs.length === 0) return null; if (!dualPath.pathA.result || !dualPath.pathB.result) return null; return crossoverPairsToDAGs( selectedCrossoverPairs, dualPath.pathA.result, dualPath.pathB.result ); }, [selectedCrossoverPairs, dualPath.pathA.result, dualPath.pathB.result]); // 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, lang?: PromptLanguage ) => { await analyze(query, model, temperature, chainCount, categoryMode, customCategories, suggestedCategoryCount, lang || promptLanguage); }; const handleResetView = useCallback(() => { mindmapRef.current?.resetView(); }, []); const handleTransform = useCallback(() => { setShouldStartTransform(true); }, []); // Dual path analysis handler const handleDualPathAnalyze = useCallback(async ( queryA: string, queryB: string, options?: { model?: string; temperature?: number; chainCount?: number; categoryMode?: CategoryMode; customCategories?: string[]; suggestedCategoryCount?: number; lang?: PromptLanguage; } ) => { await dualPath.analyzeParallel(queryA, queryB, { ...options, lang: options?.lang || promptLanguage }); }, [dualPath, promptLanguage]); // Handle mode switch const handleModeSwitch = useCallback((checked: boolean) => { setDualPathMode(checked); // Reset to attribute tab when switching modes setActiveTab('attribute'); }, []); return (
Novelty Seeking Single } unCheckedChildren={} /> Dual setPromptLanguage(value as PromptLanguage)} options={[ { label: '中文', value: 'zh' }, { label: 'EN', value: 'en' }, ]} />
Dual Path Attribute ), children: (
), }, { key: 'crossover', label: ( Crossover ), children: (
), }, { key: 'transformation', label: ( Transformation Agent {crossoverDAGs && ( (A:{crossoverDAGs.pathA.nodes.length} / B:{crossoverDAGs.pathB.nodes.length}) )} ), children: (
setShouldStartTransform(false)} onLoadingChange={setTransformLoading} />
), }, { key: 'patent', label: ( Patent Search ), children: (
), }, ] : [ // ===== Single Path Mode Tabs ===== { key: 'attribute', label: ( Attribute Agent ), children: (
), }, { key: 'transformation', label: ( Transformation Agent ), children: (
setShouldStartTransform(false)} onLoadingChange={setTransformLoading} onResultsChange={setTransformationResult} />
), }, { key: 'deduplication', label: ( Deduplication ), children: (
), }, { key: 'patent', label: ( Patent Search ), children: (
r.descriptions)} isDark={isDark} />
), }, ]} />
{activeTab === 'attribute' && !dualPathMode && ( loadFromHistory(item, lang || promptLanguage)} onResetView={handleResetView} visualSettings={visualSettings} onVisualSettingsChange={setVisualSettings} lang={promptLanguage} /> )} {activeTab === 'attribute' && dualPathMode && ( )} {activeTab === 'crossover' && dualPathMode && (
Crossover Settings Select attribute pairs in the main panel to create crossover combinations. {selectedCrossoverPairs.length > 0 && (
{selectedCrossoverPairs.length} pairs selected
)}
)} {activeTab === 'transformation' && ( )} {activeTab === 'patent' && (
Patent Search Info Search patents using the Lens.org API to find prior art and similar inventions. How to Use
  1. Click a generated description on the left to load it into the search box
  2. Edit the description to refine your search query
  3. Click "Search Patents" to find similar patents
  4. Results appear on the right - click to view on Lens.org
Result Interpretation Many results: Query may overlap with existing prior art - consider making it more specific. Few/no results: Potentially novel concept - good candidate for further exploration.
)} {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;