feat: Add expert source selector UI

- Add expert source dropdown in TransformationInputPanel
  - Options: LLM 生成, Wikidata, ConceptNet
  - Shows description for each source
- Pass expertSource through App -> TransformationPanel -> hook -> API
- Default source remains 'llm'

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-04 14:59:27 +08:00
parent 43785db595
commit 8777e27cbb
4 changed files with 48 additions and 4 deletions

View File

@@ -10,7 +10,7 @@ import { useAttribute } from './hooks/useAttribute';
import { getModels } from './services/api'; import { getModels } from './services/api';
import type { MindmapDAGRef } from './components/MindmapDAG'; import type { MindmapDAGRef } from './components/MindmapDAG';
import type { TransformationDAGRef } from './components/TransformationDAG'; import type { TransformationDAGRef } from './components/TransformationDAG';
import type { CategoryMode } from './types'; import type { CategoryMode, ExpertSource } from './types';
const { Header, Sider, Content } = Layout; const { Header, Sider, Content } = Layout;
const { Title } = Typography; const { Title } = Typography;
@@ -44,6 +44,7 @@ function App() {
custom_experts: undefined, custom_experts: undefined,
}); });
const [customExpertsInput, setCustomExpertsInput] = useState(''); const [customExpertsInput, setCustomExpertsInput] = useState('');
const [expertSource, setExpertSource] = useState<ExpertSource>('llm');
const [shouldStartTransform, setShouldStartTransform] = useState(false); const [shouldStartTransform, setShouldStartTransform] = useState(false);
const [transformLoading, setTransformLoading] = useState(false); const [transformLoading, setTransformLoading] = useState(false);
@@ -186,6 +187,7 @@ function App() {
model={transformModel} model={transformModel}
temperature={transformTemperature} temperature={transformTemperature}
expertConfig={expertConfig} expertConfig={expertConfig}
expertSource={expertSource}
shouldStartTransform={shouldStartTransform} shouldStartTransform={shouldStartTransform}
onTransformComplete={() => setShouldStartTransform(false)} onTransformComplete={() => setShouldStartTransform(false)}
onLoadingChange={setTransformLoading} onLoadingChange={setTransformLoading}
@@ -226,10 +228,12 @@ function App() {
temperature={transformTemperature} temperature={transformTemperature}
expertConfig={expertConfig} expertConfig={expertConfig}
customExpertsInput={customExpertsInput} customExpertsInput={customExpertsInput}
expertSource={expertSource}
onModelChange={setTransformModel} onModelChange={setTransformModel}
onTemperatureChange={setTransformTemperature} onTemperatureChange={setTransformTemperature}
onExpertConfigChange={setExpertConfig} onExpertConfigChange={setExpertConfig}
onCustomExpertsInputChange={setCustomExpertsInput} onCustomExpertsInputChange={setCustomExpertsInput}
onExpertSourceChange={setExpertSource}
availableModels={availableModels} availableModels={availableModels}
/> />
)} )}

View File

@@ -1,9 +1,16 @@
import { Card, Select, Slider, Typography, Space, Button, Divider } from 'antd'; import { Card, Select, Slider, Typography, Space, Button, Divider } from 'antd';
import { ThunderboltOutlined } from '@ant-design/icons'; import { ThunderboltOutlined } from '@ant-design/icons';
import { ExpertConfigPanel } from './transformation'; import { ExpertConfigPanel } from './transformation';
import type { ExpertSource } from '../types';
const { Title, Text } = Typography; const { Title, Text } = Typography;
const EXPERT_SOURCE_OPTIONS = [
{ label: 'LLM 生成', value: 'llm' as ExpertSource, description: '使用 AI 模型生成專家' },
{ label: 'Wikidata', value: 'wikidata' as ExpertSource, description: '從維基數據查詢職業' },
{ label: 'ConceptNet', value: 'conceptnet' as ExpertSource, description: '從知識圖譜查詢概念' },
];
interface TransformationInputPanelProps { interface TransformationInputPanelProps {
onTransform: () => void; onTransform: () => void;
loading: boolean; loading: boolean;
@@ -17,6 +24,7 @@ interface TransformationInputPanelProps {
custom_experts?: string[]; custom_experts?: string[];
}; };
customExpertsInput: string; customExpertsInput: string;
expertSource: ExpertSource;
onModelChange: (model: string) => void; onModelChange: (model: string) => void;
onTemperatureChange: (temperature: number) => void; onTemperatureChange: (temperature: number) => void;
onExpertConfigChange: (config: { onExpertConfigChange: (config: {
@@ -25,6 +33,7 @@ interface TransformationInputPanelProps {
custom_experts?: string[]; custom_experts?: string[];
}) => void; }) => void;
onCustomExpertsInputChange: (value: string) => void; onCustomExpertsInputChange: (value: string) => void;
onExpertSourceChange: (source: ExpertSource) => void;
availableModels: string[]; availableModels: string[];
} }
@@ -37,10 +46,12 @@ export const TransformationInputPanel: React.FC<TransformationInputPanelProps> =
temperature, temperature,
expertConfig, expertConfig,
customExpertsInput, customExpertsInput,
expertSource,
onModelChange, onModelChange,
onTemperatureChange, onTemperatureChange,
onExpertConfigChange, onExpertConfigChange,
onCustomExpertsInputChange, onCustomExpertsInputChange,
onExpertSourceChange,
availableModels, availableModels,
}) => { }) => {
return ( return (
@@ -108,6 +119,31 @@ export const TransformationInputPanel: React.FC<TransformationInputPanelProps> =
</Space> </Space>
</Card> </Card>
{/* Expert Source Selection */}
<Card
size="small"
title="專家來源"
style={{
background: isDark ? '#1f1f1f' : '#fafafa',
border: `1px solid ${isDark ? '#434343' : '#d9d9d9'}`,
}}
>
<Space direction="vertical" size="small" style={{ width: '100%' }}>
<Select
value={expertSource}
onChange={onExpertSourceChange}
style={{ width: '100%' }}
options={EXPERT_SOURCE_OPTIONS.map((opt) => ({
label: opt.label,
value: opt.value,
}))}
/>
<Text type="secondary" style={{ fontSize: 11 }}>
{EXPERT_SOURCE_OPTIONS.find((opt) => opt.value === expertSource)?.description}
</Text>
</Space>
</Card>
{/* Expert Configuration */} {/* Expert Configuration */}
<ExpertConfigPanel <ExpertConfigPanel
expertCount={expertConfig.expert_count} expertCount={expertConfig.expert_count}

View File

@@ -1,7 +1,7 @@
import { forwardRef, useMemo, useCallback, useEffect } from 'react'; import { forwardRef, useMemo, useCallback, useEffect } from 'react';
import { Empty, Spin, Button, Progress, Card, Space, Typography, Tag } from 'antd'; import { Empty, Spin, Button, Progress, Card, Space, Typography, Tag } from 'antd';
import { ReloadOutlined } from '@ant-design/icons'; import { ReloadOutlined } from '@ant-design/icons';
import type { AttributeDAG, ExpertTransformationInput } from '../types'; import type { AttributeDAG, ExpertTransformationInput, ExpertSource } from '../types';
import { TransformationDAG } from './TransformationDAG'; import { TransformationDAG } from './TransformationDAG';
import type { TransformationDAGRef } from './TransformationDAG'; import type { TransformationDAGRef } from './TransformationDAG';
import { useExpertTransformation } from '../hooks/useExpertTransformation'; import { useExpertTransformation } from '../hooks/useExpertTransformation';
@@ -18,20 +18,21 @@ interface TransformationPanelProps {
keywords_per_expert: number; keywords_per_expert: number;
custom_experts?: string[]; custom_experts?: string[];
}; };
expertSource: ExpertSource;
shouldStartTransform: boolean; shouldStartTransform: boolean;
onTransformComplete: () => void; onTransformComplete: () => void;
onLoadingChange: (loading: boolean) => void; onLoadingChange: (loading: boolean) => void;
} }
export const TransformationPanel = forwardRef<TransformationDAGRef, TransformationPanelProps>( export const TransformationPanel = forwardRef<TransformationDAGRef, TransformationPanelProps>(
({ attributeData, isDark, model, temperature, expertConfig, shouldStartTransform, onTransformComplete, onLoadingChange }, ref) => { ({ attributeData, isDark, model, temperature, expertConfig, expertSource, shouldStartTransform, onTransformComplete, onLoadingChange }, ref) => {
const { const {
loading, loading,
progress, progress,
results, results,
transformAll, transformAll,
clearResults, clearResults,
} = useExpertTransformation({ model, temperature }); } = useExpertTransformation({ model, temperature, expertSource });
// Notify parent of loading state changes // Notify parent of loading state changes
useEffect(() => { useEffect(() => {

View File

@@ -7,11 +7,13 @@ import type {
ExpertTransformationDAGResult, ExpertTransformationDAGResult,
ExpertProfile, ExpertProfile,
CategoryDefinition, CategoryDefinition,
ExpertSource,
} from '../types'; } from '../types';
interface UseExpertTransformationOptions { interface UseExpertTransformationOptions {
model?: string; model?: string;
temperature?: number; temperature?: number;
expertSource?: ExpertSource;
} }
export function useExpertTransformation(options: UseExpertTransformationOptions = {}) { export function useExpertTransformation(options: UseExpertTransformationOptions = {}) {
@@ -60,6 +62,7 @@ export function useExpertTransformation(options: UseExpertTransformationOptions
expert_count: expertConfig.expert_count, expert_count: expertConfig.expert_count,
keywords_per_expert: expertConfig.keywords_per_expert, keywords_per_expert: expertConfig.keywords_per_expert,
custom_experts: expertConfig.custom_experts, custom_experts: expertConfig.custom_experts,
expert_source: options.expertSource,
model: options.model, model: options.model,
temperature: options.temperature, temperature: options.temperature,
}, },