- 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>
93 lines
2.4 KiB
TypeScript
93 lines
2.4 KiB
TypeScript
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';
|