agent-ecosystem/packages/agent-graph/src/constants/canvas-constants.ts
iliya 6621660376 feat(graph): add cross-team ghost nodes and task card improvements
- Cross-team messages now show ghost nodes (dashed hexagons) for external teams
- Ghost nodes have purple color, link icon, and connect to lead via message edge
- Particles flow between ghost node and lead with cross-team message labels
- Cross-team popover shows external team name
- Task click opens full KanbanTaskCard with glow effects and action buttons
- All kanban task actions wired through CustomEvent to TeamDetailView
2026-03-31 01:48:15 +03:00

249 lines
7.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Canvas rendering constants for the agent graph visualization.
* Adapted from agent-flow's canvas-constants.ts (Apache 2.0).
* Stripped of unused features (tool cards, discoveries, cost overlays, bubbles).
*/
// ─── Visibility threshold ───────────────────────────────────────────────────
export const MIN_VISIBLE_OPACITY = 0.05;
// ─── Animation speed multipliers (× deltaTime) ─────────────────────────────
export const ANIM_SPEED = {
agentFadeIn: 3,
agentScaleIn: 4,
agentFadeOut: 0.4,
agentScaleOut: 0.05,
edgeFadeIn: 4,
particleSpeed: 1.2,
maxDeltaTime: 0.1,
defaultDeltaTime: 0.016,
/** Task pill fade in/out */
taskFadeIn: 3,
taskFadeOut: 0.6,
} as const;
// ─── Camera / interaction ───────────────────────────────────────────────────
export const CAMERA = {
zoomStepDown: 0.92,
zoomStepUp: 1.08,
minZoom: 0.15,
maxZoom: 5,
velocityScale: 0.016,
} as const;
// ─── Force simulation ───────────────────────────────────────────────────────
export const FORCE = {
chargeStrength: -800,
centerStrength: 0.03,
collideRadius: 100,
linkDistance: {
'parent-child': 500,
ownership: 150,
blocking: 200,
related: 200,
message: 300,
},
linkStrength: 0.4,
alphaDecay: 0.02,
velocityDecay: 0.4,
} as const;
// ─── Node sizes ─────────────────────────────────────────────────────────────
export const NODE = {
/** Lead agent radius */
radiusLead: 32,
/** Team member radius */
radiusMember: 24,
/** Process node radius */
radiusProcess: 14,
/** Cross-team ghost node radius */
radiusCrossTeam: 20,
} as const;
// ─── Task pill dimensions ───────────────────────────────────────────────────
export const TASK_PILL = {
width: 160,
height: 36,
borderRadius: 6,
statusDotRadius: 4,
statusDotX: 12,
/** Font size for display ID */
idFontSize: 9,
/** Font size for subject text */
subjectFontSize: 7,
/** Max chars for subject before truncation */
subjectMaxChars: 18,
/** X offset for text content */
textOffsetX: 20,
} as const;
// ─── Agent drawing constants ────────────────────────────────────────────────
export const AGENT_DRAW = {
glowPadding: 20,
outerRingOffset: 3,
shadowBlur: 15,
shadowOffsetX: 3,
shadowOffsetY: 5,
labelYOffset: 8,
labelWidthMultiplier: 3,
scanlineHalfH: 4,
waitingDashSpeed: 25,
orbitParticleOffset: 12,
orbitParticleSize: 1.5,
rippleInnerOffset: 5,
rippleMaxExpand: 45,
rippleMaxAlpha: 0.4,
waitingOrbitOffset: 14,
waitingOrbitParticleSize: 2,
waitingOrbitSpeed: 0.8,
waitingBreatheSpeed: 1.2,
waitingBreatheAmp: 0.08,
sparkScale: 0.45,
sparkViewBox: 256,
subIconScale: 0.45,
} as const;
// ─── Context ring (lead node only) ─────────────────────────────────────────
export const CONTEXT_RING = {
ringOffset: 8,
ringWidth: 4,
warningThreshold: 0.8,
criticalThreshold: 0.9,
percentLabelThreshold: 0.7,
glowPadding: 4,
glowLineWidth: 2,
glowBlur: 12,
percentYOffset: 10,
} as const;
// ─── Edge/beam drawing ──────────────────────────────────────────────────────
export const BEAM = {
curvature: 0.15,
cp1: 0.33,
cp2: 0.66,
segments: 16,
parentChild: { startW: 3, endW: 1 },
ownership: { startW: 2, endW: 0.8 },
blocking: { startW: 2, endW: 1.5 },
related: { startW: 1, endW: 0.5 },
message: { startW: 1.5, endW: 0.5 },
glowExtra: { startW: 3, endW: 1, alpha: 0.08 },
idleAlpha: 0.08,
activeAlpha: 0.3,
wobble: { amp: 3, freq: 10, timeFreq: 3, trailOffset: 0.15 },
} as const;
// ─── Animation constants ────────────────────────────────────────────────────
export const ANIM = {
inertiaDecay: 0.94,
inertiaThreshold: 0.5,
dragLerp: 0.25,
autoFitLerp: 0.06,
dragThresholdPx: 5,
viewportPadding: 120,
breathe: {
activeSpeed: 2,
activeAmp: 0.03,
idleSpeed: 0.7,
idleAmp: 0.015,
},
scanline: { active: 40, normal: 15 },
orbitSpeed: 1.5,
pulseSpeed: 4,
} as const;
// ─── Visual effects ─────────────────────────────────────────────────────────
export const FX = {
spawnDuration: 0.8,
completeDuration: 1.0,
shatterDuration: 0.8,
shatterCount: 12,
shatterSpeed: { min: 30, range: 60 },
shatterSize: { min: 1, range: 2 },
trailSegments: 8,
} as const;
export const SPAWN_FX = {
ringStart: 10,
ringExpand: 60,
maxAlpha: 0.7,
flashThreshold: 0.3,
flashAlpha: 0.6,
flashBaseRadius: 20,
flashMinRadius: 5,
particleCount: 8,
particleSize: 1.5,
} as const;
export const COMPLETE_FX = {
ringStart: 20,
ringExpand: 80,
maxAlpha: 0.6,
flashThreshold: 0.2,
flashAlpha: 0.8,
flashRadius: 30,
lineWidthMax: 3,
glowInner: 5,
glowOuter: 10,
} as const;
// ─── Particle drawing ───────────────────────────────────────────────────────
export const PARTICLE_DRAW = {
glowRadius: 15,
coreHighlightScale: 0.4,
labelMinT: 0.2,
labelMaxT: 0.8,
labelFontSize: 8,
labelYOffset: -12,
/** Seconds a particle lives before fading */
lifetime: 2.0,
} as const;
// ─── Hit detection ──────────────────────────────────────────────────────────
export const HIT_DETECTION = {
/** Extra padding around nodes for easier clicking */
agentPadding: 8,
/** Task pill hit area padding */
taskPadding: 4,
} as const;
// ─── Background ─────────────────────────────────────────────────────────────
export const BACKGROUND = {
/** Number of depth particles (stars) */
starCount: 80,
/** Hex grid cell size */
hexSize: 30,
/** Hex grid max alpha */
hexAlpha: 0.08,
/** Hex grid pulse speed */
hexPulseSpeed: 0.3,
} as const;
// ─── Kanban zone layout ─────────────────────────────────────────────────────
export const KANBAN_ZONE = {
/** Column width: pill (160) + gap (20) */
columnWidth: 180,
/** Row height: pill (36) + gap (10) */
rowHeight: 46,
/** Zone starts this far below member node center */
offsetY: 70,
/** Column order: todo → wip → done → review → approved */
columns: ['todo', 'wip', 'done', 'review', 'approved'] as const,
/** Max tasks shown per column (overflow hidden) */
maxVisibleRows: 6,
} as const;