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

@@ -0,0 +1,92 @@
import { memo, useState } from 'react';
interface ExpertKeywordNodeProps {
data: {
label: string;
expertName: string;
expertId: string;
color: string;
isDark: boolean;
};
}
export const ExpertKeywordNode = memo(({ data }: ExpertKeywordNodeProps) => {
const { label, expertName, color, isDark } = data;
const [isHovered, setIsHovered] = useState(false);
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{
position: 'relative',
display: 'flex',
flexDirection: 'column',
gap: 4,
padding: '6px 12px',
paddingRight: '36px',
marginTop: 18,
borderRadius: 6,
background: isDark
? 'linear-gradient(135deg, rgba(24, 144, 255, 0.15) 0%, rgba(255, 255, 255, 0.1) 100%)'
: 'linear-gradient(135deg, rgba(24, 144, 255, 0.1) 0%, rgba(0, 0, 0, 0.03) 100%)',
border: `2px solid ${color}`,
borderWidth: isHovered ? 3 : 2,
color: isDark ? '#fff' : '#333',
fontSize: '13px',
fontWeight: 500,
textAlign: 'center',
cursor: 'pointer',
transition: 'all 0.2s ease',
whiteSpace: 'nowrap',
userSelect: 'none',
filter: isHovered ? 'brightness(1.1)' : 'none',
boxShadow: isDark
? '0 2px 8px rgba(24, 144, 255, 0.2)'
: '0 2px 8px rgba(24, 144, 255, 0.15)',
}}
>
{/* Expert Badge - positioned above the node */}
<div
style={{
position: 'absolute',
top: -18,
left: 0,
padding: '2px 6px',
borderRadius: 3,
background: isDark
? 'rgba(24, 144, 255, 0.8)'
: 'rgba(24, 144, 255, 0.9)',
color: '#fff',
fontSize: '10px',
fontWeight: 500,
whiteSpace: 'nowrap',
}}
>
{expertName}
</div>
{/* Keyword Label */}
<div>{label}</div>
{/* NEW Badge - positioned below the node */}
<span
style={{
position: 'absolute',
bottom: -14,
right: 0,
padding: '2px 5px',
borderRadius: 3,
background: 'linear-gradient(135deg, #52c41a 0%, #73d13d 100%)',
color: '#fff',
fontSize: '9px',
fontWeight: 600,
}}
>
NEW
</span>
</div>
);
});
ExpertKeywordNode.displayName = 'ExpertKeywordNode';