chore: save local changes

This commit is contained in:
2026-01-05 22:32:08 +08:00
parent bc281b8e0a
commit ec48709755
42 changed files with 5576 additions and 254 deletions

View File

@@ -1,17 +1,24 @@
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 { 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 } from './types';
import type { CategoryMode, ExpertSource, ExpertTransformationDAGResult, DeduplicationMethod, ExpertMode, CrossoverPair, PromptLanguage } from './types';
const { Header, Sider, Content } = Layout;
const { Title } = Typography;
@@ -24,7 +31,15 @@ interface VisualSettings {
function App() {
const [isDark, setIsDark] = useState(true);
const [activeTab, setActiveTab] = useState<string>('attribute');
const [dualPathMode, setDualPathMode] = useState(false);
const [promptLanguage, setPromptLanguage] = useState<PromptLanguage>('zh');
// Single path hook
const { loading, progress, error, currentResult, history, analyze, loadFromHistory } = useAttribute();
// Dual path hook
const dualPath = useDualPathAttribute();
const [visualSettings, setVisualSettings] = useState<VisualSettings>({
nodeSpacing: 32,
fontSize: 14,
@@ -32,6 +47,21 @@ function App() {
const mindmapRef = useRef<MindmapDAGRef>(null);
const transformationRef = useRef<TransformationDAGRef>(null);
// Dual path expert mode
const [expertMode, setExpertMode] = useState<ExpertMode>('shared');
const [selectedCrossoverPairs, setSelectedCrossoverPairs] = useState<CrossoverPair[]>([]);
// 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<string>('');
const [transformTemperature, setTransformTemperature] = useState<number>(0.95);
@@ -83,9 +113,10 @@ function App() {
chainCount?: number,
categoryMode?: CategoryMode,
customCategories?: string[],
suggestedCategoryCount?: number
suggestedCategoryCount?: number,
lang?: PromptLanguage
) => {
await analyze(query, model, temperature, chainCount, categoryMode, customCategories, suggestedCategoryCount);
await analyze(query, model, temperature, chainCount, categoryMode, customCategories, suggestedCategoryCount, lang || promptLanguage);
};
const handleResetView = useCallback(() => {
@@ -96,6 +127,30 @@ function App() {
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 (
<ConfigProvider
theme={{
@@ -140,7 +195,31 @@ function App() {
Novelty Seeking
</Title>
</Space>
<ThemeToggle isDark={isDark} onToggle={setIsDark} />
<Space align="center" size="middle">
<Space size="small">
<Typography.Text type="secondary">Single</Typography.Text>
<Switch
checked={dualPathMode}
onChange={handleModeSwitch}
checkedChildren={<SwapOutlined />}
unCheckedChildren={<ApartmentOutlined />}
/>
<Typography.Text type="secondary">Dual</Typography.Text>
</Space>
<Space size="small">
<GlobalOutlined style={{ color: isDark ? '#177ddc' : '#1890ff' }} />
<Segmented
size="small"
value={promptLanguage}
onChange={(value) => setPromptLanguage(value as PromptLanguage)}
options={[
{ label: '中文', value: 'zh' },
{ label: 'EN', value: 'en' },
]}
/>
</Space>
<ThemeToggle isDark={isDark} onToggle={setIsDark} />
</Space>
</Header>
<Layout>
<Content
@@ -155,7 +234,98 @@ function App() {
onChange={setActiveTab}
style={{ height: '100%' }}
tabBarStyle={{ marginBottom: 8 }}
items={[
items={dualPathMode ? [
// ===== Dual Path Mode Tabs =====
{
key: 'attribute',
label: (
<span>
<SwapOutlined style={{ marginRight: 8 }} />
Dual Path Attribute
</span>
),
children: (
<div style={{ height: 'calc(100vh - 140px)' }}>
<DualPathMindmapPanel
pathA={dualPath.pathA}
pathB={dualPath.pathB}
isDark={isDark}
visualSettings={visualSettings}
/>
</div>
),
},
{
key: 'crossover',
label: (
<span>
<SwapOutlined style={{ marginRight: 8 }} />
Crossover
</span>
),
children: (
<div style={{ height: 'calc(100vh - 140px)', padding: 16 }}>
<CrossoverPanel
pathAResult={dualPath.pathA.result}
pathBResult={dualPath.pathB.result}
isDark={isDark}
expertMode={expertMode}
onExpertModeChange={setExpertMode}
onCrossoverReady={setSelectedCrossoverPairs}
/>
</div>
),
},
{
key: 'transformation',
label: (
<span>
<ThunderboltOutlined style={{ marginRight: 8 }} />
Transformation Agent
{crossoverDAGs && (
<span style={{ marginLeft: 4, fontSize: 10, opacity: 0.7 }}>
(A:{crossoverDAGs.pathA.nodes.length} / B:{crossoverDAGs.pathB.nodes.length})
</span>
)}
</span>
),
children: (
<div style={{ height: 'calc(100vh - 140px)' }}>
<DualTransformationPanel
crossoverDAGA={crossoverDAGs?.pathA ?? null}
crossoverDAGB={crossoverDAGs?.pathB ?? null}
isDark={isDark}
model={transformModel}
temperature={transformTemperature}
expertConfig={expertConfig}
expertSource={expertSource}
expertLanguage={expertLanguage}
lang={promptLanguage}
shouldStartTransform={shouldStartTransform}
onTransformComplete={() => setShouldStartTransform(false)}
onLoadingChange={setTransformLoading}
/>
</div>
),
},
{
key: 'patent',
label: (
<span>
<FileSearchOutlined style={{ marginRight: 8 }} />
Patent Search
</span>
),
children: (
<div style={{ height: 'calc(100vh - 140px)' }}>
<PatentSearchPanel
isDark={isDark}
/>
</div>
),
},
] : [
// ===== Single Path Mode Tabs =====
{
key: 'attribute',
label: (
@@ -196,6 +366,7 @@ function App() {
expertConfig={expertConfig}
expertSource={expertSource}
expertLanguage={expertLanguage}
lang={promptLanguage}
shouldStartTransform={shouldStartTransform}
onTransformComplete={() => setShouldStartTransform(false)}
onLoadingChange={setTransformLoading}
@@ -221,6 +392,24 @@ function App() {
onThresholdChange={setDeduplicationThreshold}
method={deduplicationMethod}
onMethodChange={setDeduplicationMethod}
lang={promptLanguage}
/>
</div>
),
},
{
key: 'patent',
label: (
<span>
<FileSearchOutlined style={{ marginRight: 8 }} />
Patent Search
</span>
),
children: (
<div style={{ height: 'calc(100vh - 140px)' }}>
<PatentSearchPanel
descriptions={transformationResult?.results.flatMap(r => r.descriptions)}
isDark={isDark}
/>
</div>
),
@@ -236,24 +425,54 @@ function App() {
overflow: 'auto',
}}
>
{activeTab === 'attribute' && (
{activeTab === 'attribute' && !dualPathMode && (
<InputPanel
loading={loading}
progress={progress}
history={history}
currentResult={currentResult}
onAnalyze={handleAnalyze}
onLoadHistory={loadFromHistory}
onLoadHistory={(item, lang) => loadFromHistory(item, lang || promptLanguage)}
onResetView={handleResetView}
visualSettings={visualSettings}
onVisualSettingsChange={setVisualSettings}
lang={promptLanguage}
/>
)}
{activeTab === 'attribute' && dualPathMode && (
<DualPathInputPanel
onAnalyze={handleDualPathAnalyze}
loadingA={dualPath.pathA.loading}
loadingB={dualPath.pathB.loading}
progressA={dualPath.pathA.progress}
progressB={dualPath.pathB.progress}
availableModels={availableModels}
lang={promptLanguage}
/>
)}
{activeTab === 'crossover' && dualPathMode && (
<div style={{ padding: 16 }}>
<Typography.Title level={5} style={{ marginBottom: 16 }}>
<SwapOutlined style={{ marginRight: 8 }} />
Crossover Settings
</Typography.Title>
<Typography.Text type="secondary">
Select attribute pairs in the main panel to create crossover combinations.
{selectedCrossoverPairs.length > 0 && (
<div style={{ marginTop: 8 }}>
<Typography.Text strong>
{selectedCrossoverPairs.length} pairs selected
</Typography.Text>
</div>
)}
</Typography.Text>
</div>
)}
{activeTab === 'transformation' && (
<TransformationInputPanel
onTransform={handleTransform}
loading={transformLoading}
hasData={!!currentResult}
hasData={dualPathMode ? !!crossoverDAGs : !!currentResult}
isDark={isDark}
model={transformModel}
temperature={transformTemperature}