mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-06 19:58:43 +02:00
feat: directional edges and provider icons on agent nodes
- Add ArrowClosed markers to all graph edges for clear directional flow - Replace generic Bot icon with AI provider logos (OpenAI, Anthropic, Google) on agent nodes, inferred from the agent's model id - Show model display name as subtitle on agent nodes (e.g. 'GPT-5.4', 'Claude Opus 4.5') - Thread availableModels catalog from PatternEditor through canvas to resolve friendly model names; falls back to raw model id - Add 3 new tests for arrow markers, provider icons, and model labels Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -318,6 +318,7 @@ export function PatternEditor({
|
||||
<div className="min-h-[300px] flex-1 px-5 py-3">
|
||||
<PatternGraphCanvas
|
||||
pattern={pattern}
|
||||
availableModels={availableModels}
|
||||
onGraphChange={emitGraphChange}
|
||||
onNodeSelect={setSelectedNodeId}
|
||||
selectedNodeId={selectedNodeId}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { memo } from 'react';
|
||||
import { Handle, Position, type NodeProps } from '@xyflow/react';
|
||||
import { CircleUser, Bot, Shuffle, Layers, Radio } from 'lucide-react';
|
||||
import { CircleUser, Shuffle, Layers, Radio, Bot } from 'lucide-react';
|
||||
|
||||
import type { GraphNodeData } from '@renderer/lib/patternGraph';
|
||||
import type { PatternGraphNodeKind } from '@shared/domain/pattern';
|
||||
import { ProviderIcon } from '@renderer/components/ProviderIcons';
|
||||
|
||||
const kindIcons: Record<PatternGraphNodeKind, typeof CircleUser> = {
|
||||
'user-input': CircleUser,
|
||||
'user-output': CircleUser,
|
||||
agent: Bot,
|
||||
agent: Bot, // fallback when no provider is resolved
|
||||
distributor: Shuffle,
|
||||
collector: Layers,
|
||||
orchestrator: Radio,
|
||||
@@ -24,23 +25,30 @@ const kindColors: Record<PatternGraphNodeKind, { bg: string; border: string; tex
|
||||
};
|
||||
|
||||
function GraphNodeContent({ data, selected }: { data: GraphNodeData; selected: boolean }) {
|
||||
const Icon = kindIcons[data.kind] ?? Bot;
|
||||
const colors = kindColors[data.kind] ?? kindColors.agent;
|
||||
const isAgent = data.kind === 'agent';
|
||||
|
||||
const renderIcon = () => {
|
||||
if (isAgent && data.provider) {
|
||||
return <ProviderIcon provider={data.provider} className="size-4 shrink-0" />;
|
||||
}
|
||||
const FallbackIcon = kindIcons[data.kind] ?? Bot;
|
||||
return <FallbackIcon className={`size-4 shrink-0 ${colors.text}`} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex min-w-[120px] items-center gap-2 rounded-xl border px-3 py-2 shadow-md transition ${
|
||||
colors.bg
|
||||
} ${selected ? 'ring-2 ring-indigo-500/50' : ''} ${colors.border}`}
|
||||
>
|
||||
<Icon className={`size-4 shrink-0 ${colors.text}`} />
|
||||
{renderIcon()}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className={`truncate text-[12px] font-semibold ${colors.text}`}>
|
||||
{data.label}
|
||||
</div>
|
||||
{isAgent && typeof data.order === 'number' && (
|
||||
<div className="text-[10px] text-zinc-500">#{data.order + 1}</div>
|
||||
{isAgent && data.modelLabel && (
|
||||
<div className="truncate text-[10px] text-zinc-500">{data.modelLabel}</div>
|
||||
)}
|
||||
</div>
|
||||
{data.readOnly && (
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
ReactFlow,
|
||||
Background,
|
||||
BackgroundVariant,
|
||||
MarkerType,
|
||||
useNodesState,
|
||||
useEdgesState,
|
||||
type Node,
|
||||
@@ -15,6 +16,7 @@ import '@xyflow/react/dist/style.css';
|
||||
|
||||
import type { OrchestrationMode, PatternDefinition, PatternGraph } from '@shared/domain/pattern';
|
||||
import { resolvePatternGraph } from '@shared/domain/pattern';
|
||||
import type { ModelDefinition } from '@shared/domain/models';
|
||||
import {
|
||||
addHandoffEdge,
|
||||
fromCanvasPositions,
|
||||
@@ -30,6 +32,7 @@ import { graphNodeTypes } from './GraphNodes';
|
||||
|
||||
interface PatternGraphCanvasProps {
|
||||
pattern: PatternDefinition;
|
||||
availableModels?: ReadonlyArray<ModelDefinition>;
|
||||
onGraphChange: (graph: PatternGraph) => void;
|
||||
onNodeSelect: (nodeId: string | null) => void;
|
||||
selectedNodeId: string | null;
|
||||
@@ -37,6 +40,7 @@ interface PatternGraphCanvasProps {
|
||||
|
||||
export function PatternGraphCanvas({
|
||||
pattern,
|
||||
availableModels,
|
||||
onGraphChange,
|
||||
onNodeSelect,
|
||||
selectedNodeId,
|
||||
@@ -45,7 +49,7 @@ export function PatternGraphCanvas({
|
||||
const draggingRef = useRef(false);
|
||||
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(
|
||||
toCanvasNodes(graph, pattern.agents),
|
||||
toCanvasNodes(graph, pattern.agents, availableModels),
|
||||
);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(
|
||||
toCanvasEdges(graph, pattern.mode),
|
||||
@@ -53,9 +57,9 @@ export function PatternGraphCanvas({
|
||||
|
||||
// Sync canvas when pattern changes externally
|
||||
useEffect(() => {
|
||||
setNodes(toCanvasNodes(graph, pattern.agents));
|
||||
setNodes(toCanvasNodes(graph, pattern.agents, availableModels));
|
||||
setEdges(toCanvasEdges(graph, pattern.mode));
|
||||
}, [graph, pattern.agents, pattern.mode, setNodes, setEdges]);
|
||||
}, [graph, pattern.agents, pattern.mode, availableModels, setNodes, setEdges]);
|
||||
|
||||
const handleNodesChange: OnNodesChange<Node<GraphNodeData>> = useCallback(
|
||||
(changes) => {
|
||||
@@ -162,6 +166,7 @@ export function PatternGraphCanvas({
|
||||
defaultEdgeOptions={{
|
||||
type: 'smoothstep',
|
||||
style: { stroke: '#52525b', strokeWidth: 1.5 },
|
||||
markerEnd: { type: MarkerType.ArrowClosed, width: 16, height: 16, color: '#52525b' },
|
||||
}}
|
||||
connectionLineStyle={{ stroke: '#6366f1', strokeWidth: 1.5 }}
|
||||
deleteKeyCode="Delete"
|
||||
|
||||
Reference in New Issue
Block a user