[aidan] feat/workflows: per-workflow color swatch

This commit is contained in:
abccodes
2026-06-22 23:57:05 -07:00
parent 3eff6d6b37
commit 7b2928f35e
4 changed files with 50 additions and 0 deletions
+4
View File
@@ -105,6 +105,8 @@ class Workflow(BaseModel):
title: str = "Untitled workflow"
description: str = ""
icon: str = ""
# User-chosen swatch (hex). None falls back to the id-hash color in the UI.
color: Optional[str] = None
system_prompt: Optional[str] = None
use_synced_prompt: bool = True
steps: list[WorkflowStep] = Field(default_factory=list)
@@ -212,6 +214,7 @@ class WorkflowCreate(BaseModel):
unsaved: bool = False
description: str = ""
icon: str = ""
color: Optional[str] = None
system_prompt: Optional[str] = None
use_synced_prompt: bool = True
steps: list[WorkflowStep] = Field(default_factory=list)
@@ -246,6 +249,7 @@ class WorkflowUpdate(BaseModel):
auto_named: Optional[bool] = None
description: Optional[str] = None
icon: Optional[str] = None
color: Optional[str] = None
system_prompt: Optional[str] = None
use_synced_prompt: Optional[bool] = None
steps: Optional[list[WorkflowStep]] = None
+1
View File
@@ -248,6 +248,7 @@ async def create_workflow(body: WorkflowCreate):
title=body.title,
description=body.description,
icon=body.icon,
color=body.color,
system_prompt=body.system_prompt,
use_synced_prompt=body.use_synced_prompt,
steps=body.steps,
@@ -0,0 +1,43 @@
import React, { useEffect, useRef, useState } from 'react';
import { useWC, WORKFLOW_PALETTE } from './uiKit';
// Small swatch button that opens a palette popover. Self-rendered (no portal)
// so it survives the canvas compositor, same reasoning as RepeatField.
const ColorSwatch: React.FC<{ value: string; onChange: (hex: string) => void; size?: number }> = ({ value, onChange, size = 14 }) => {
const WC = useWC();
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!open) return;
const onDoc = (e: MouseEvent) => { if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); };
document.addEventListener('mousedown', onDoc);
return () => document.removeEventListener('mousedown', onDoc);
}, [open]);
return (
<div ref={ref} style={{ position: 'relative', flex: 'none' }}>
<div
onClick={() => setOpen((o) => !o)}
title="Workflow color"
style={{ width: size, height: size, borderRadius: 4, cursor: 'pointer', background: value, boxShadow: `0 0 0 1px rgba(${WC.inkRGB},0.14)` }}
/>
{open && (
<div style={{ position: 'absolute', top: size + 11, left: 0, zIndex: 45, background: WC.paper, border: `1px solid ${WC.line2}`, borderRadius: WC.radius.lg, boxShadow: WC.shadow.md, padding: 9, display: 'flex', gap: 7 }}>
{WORKFLOW_PALETTE.map((hex) => {
const active = hex.toLowerCase() === value.toLowerCase();
return (
<div
key={hex}
onClick={() => { onChange(hex); setOpen(false); }}
style={{ width: 18, height: 18, borderRadius: 5, cursor: 'pointer', background: hex, boxShadow: active ? `0 0 0 2px ${WC.paper}, 0 0 0 3.5px ${hex}` : `0 0 0 1px rgba(${WC.inkRGB},0.12)` }}
/>
);
})}
</div>
)}
</div>
);
};
export default ColorSwatch;
@@ -63,6 +63,8 @@ export interface Workflow {
title: string;
description: string;
icon: string;
/** User-chosen swatch (hex). Null/undefined falls back to the id-hash color. */
color?: string | null;
system_prompt: string | null;
use_synced_prompt: boolean;
steps: WorkflowStep[];