feat: Add Expert Transformation Agent with multi-expert perspective system

- Backend: Add expert transformation router with 3-step SSE pipeline
  - Step 0: Generate diverse expert team (random domains)
  - Step 1: Each expert generates keywords for attributes
  - Step 2: Batch generate descriptions for expert keywords
- Backend: Add simplified prompts for reliable JSON output
- Frontend: Add TransformationPanel with React Flow visualization
- Frontend: Add TransformationInputPanel for expert configuration
  - Expert count (2-8), keywords per expert (1-3)
  - Custom expert domains support
- Frontend: Add expert keyword nodes with expert badges
- Frontend: Improve description card layout (wider cards, more spacing)
- Frontend: Add fallback for missing descriptions with visual indicators

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-03 16:26:17 +08:00
parent 1ed1dab78f
commit 534fdbbcc4
25 changed files with 3114 additions and 27 deletions

View File

@@ -1,11 +1,15 @@
import { useState, useRef, useCallback } from 'react';
import { ConfigProvider, Layout, theme, Typography, Space } from 'antd';
import { ApartmentOutlined } from '@ant-design/icons';
import { useState, useRef, useCallback, useEffect } from 'react';
import { ConfigProvider, Layout, theme, Typography, Space, Tabs } from 'antd';
import { ApartmentOutlined, ThunderboltOutlined } 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 { useAttribute } from './hooks/useAttribute';
import { getModels } from './services/api';
import type { MindmapDAGRef } from './components/MindmapDAG';
import type { TransformationDAGRef } from './components/TransformationDAG';
import type { CategoryMode } from './types';
const { Header, Sider, Content } = Layout;
@@ -18,12 +22,51 @@ interface VisualSettings {
function App() {
const [isDark, setIsDark] = useState(true);
const [activeTab, setActiveTab] = useState<string>('attribute');
const { loading, progress, error, currentResult, history, analyze, loadFromHistory } = useAttribute();
const [visualSettings, setVisualSettings] = useState<VisualSettings>({
nodeSpacing: 32,
fontSize: 14,
});
const mindmapRef = useRef<MindmapDAGRef>(null);
const transformationRef = useRef<TransformationDAGRef>(null);
// Transformation Agent settings
const [transformModel, setTransformModel] = useState<string>('');
const [transformTemperature, setTransformTemperature] = useState<number>(0.7);
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 [shouldStartTransform, setShouldStartTransform] = useState(false);
const [transformLoading, setTransformLoading] = useState(false);
// Available models from API
const [availableModels, setAvailableModels] = useState<string[]>([]);
// 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,
@@ -41,6 +84,10 @@ function App() {
mindmapRef.current?.resetView();
}, []);
const handleTransform = useCallback(() => {
setShouldStartTransform(true);
}, []);
return (
<ConfigProvider
theme={{
@@ -82,7 +129,7 @@ function App() {
backgroundClip: 'text',
}}
>
Attribute Agent
Novelty Seeking
</Title>
</Space>
<ThemeToggle isDark={isDark} onToggle={setIsDark} />
@@ -95,13 +142,58 @@ function App() {
overflow: 'hidden',
}}
>
<MindmapPanel
ref={mindmapRef}
data={currentResult}
loading={loading}
error={error}
isDark={isDark}
visualSettings={visualSettings}
<Tabs
activeKey={activeTab}
onChange={setActiveTab}
style={{ height: '100%' }}
tabBarStyle={{ marginBottom: 8 }}
items={[
{
key: 'attribute',
label: (
<span>
<ApartmentOutlined style={{ marginRight: 8 }} />
Attribute Agent
</span>
),
children: (
<div style={{ height: 'calc(100vh - 140px)' }}>
<MindmapPanel
ref={mindmapRef}
data={currentResult}
loading={loading}
error={error}
isDark={isDark}
visualSettings={visualSettings}
/>
</div>
),
},
{
key: 'transformation',
label: (
<span>
<ThunderboltOutlined style={{ marginRight: 8 }} />
Transformation Agent
</span>
),
children: (
<div style={{ height: 'calc(100vh - 140px)' }}>
<TransformationPanel
ref={transformationRef}
attributeData={currentResult}
isDark={isDark}
model={transformModel}
temperature={transformTemperature}
expertConfig={expertConfig}
shouldStartTransform={shouldStartTransform}
onTransformComplete={() => setShouldStartTransform(false)}
onLoadingChange={setTransformLoading}
/>
</div>
),
},
]}
/>
</Content>
<Sider
@@ -112,17 +204,35 @@ function App() {
overflow: 'auto',
}}
>
<InputPanel
loading={loading}
progress={progress}
history={history}
currentResult={currentResult}
onAnalyze={handleAnalyze}
onLoadHistory={loadFromHistory}
onResetView={handleResetView}
visualSettings={visualSettings}
onVisualSettingsChange={setVisualSettings}
/>
{activeTab === 'attribute' ? (
<InputPanel
loading={loading}
progress={progress}
history={history}
currentResult={currentResult}
onAnalyze={handleAnalyze}
onLoadHistory={loadFromHistory}
onResetView={handleResetView}
visualSettings={visualSettings}
onVisualSettingsChange={setVisualSettings}
/>
) : (
<TransformationInputPanel
onTransform={handleTransform}
loading={transformLoading}
hasData={!!currentResult}
isDark={isDark}
model={transformModel}
temperature={transformTemperature}
expertConfig={expertConfig}
customExpertsInput={customExpertsInput}
onModelChange={setTransformModel}
onTemperatureChange={setTransformTemperature}
onExpertConfigChange={setExpertConfig}
onCustomExpertsInputChange={setCustomExpertsInput}
availableModels={availableModels}
/>
)}
</Sider>
</Layout>
</Layout>