mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-12 04:37:44 +02:00
[aidan] ux/workflows: surface paused state on card, sidebar, and calendar; tidy run history
This commit is contained in:
@@ -34,6 +34,7 @@ export default function ScheduleCalendar({ view, density, onSelectWorkflow, refD
|
||||
const c = useClaudeTokens();
|
||||
const dispatch = useAppDispatch();
|
||||
const workflows = useAppSelector((s) => Object.values(s.workflows.items));
|
||||
const allPaused = useAppSelector((s) => s.workflows.paused);
|
||||
// Right-click menu: pinned position + the workflow whose pill was
|
||||
// clicked. Same anchor pattern as MUI's menu examples.
|
||||
const [ctxMenu, setCtxMenu] = useState<{ x: number; y: number; workflow: Workflow } | null>(null);
|
||||
@@ -236,6 +237,7 @@ export default function ScheduleCalendar({ view, density, onSelectWorkflow, refD
|
||||
sx={{ height: SLOT_H, borderLeft: `1px solid ${c.border.subtle}`, borderTop: hourIdx === 0 ? 'none' : `1px solid ${c.border.subtle}`, position: 'relative' }}>
|
||||
<EventStack
|
||||
events={evs}
|
||||
paused={allPaused}
|
||||
onSelectWorkflow={onSelectWorkflow}
|
||||
eventFontSize={EVENT_FS}
|
||||
onContextWorkflow={(wf, ev) => { ev.preventDefault(); setCtxMenu({ x: ev.clientX, y: ev.clientY, workflow: wf }); }}
|
||||
@@ -374,8 +376,9 @@ export default function ScheduleCalendar({ view, density, onSelectWorkflow, refD
|
||||
// Apple Calendar style event chip: 3px colored left-bar + faintly-tinted
|
||||
// background + readable text. One chip per cell with a "+N" badge for
|
||||
// overflow; clicking it opens a popover listing all events that hour.
|
||||
function EventStack({ events, onSelectWorkflow, eventFontSize, onContextWorkflow }: {
|
||||
function EventStack({ events, paused, onSelectWorkflow, eventFontSize, onContextWorkflow }: {
|
||||
events: { workflow: Workflow; date: Date }[];
|
||||
paused?: boolean;
|
||||
onSelectWorkflow?: (id: string) => void;
|
||||
eventFontSize: string;
|
||||
onContextWorkflow?: (workflow: Workflow, e: React.MouseEvent) => void;
|
||||
@@ -414,6 +417,7 @@ function EventStack({ events, onSelectWorkflow, eventFontSize, onContextWorkflow
|
||||
fontSize: eventFontSize, fontWeight: 500,
|
||||
overflow: 'hidden', whiteSpace: 'nowrap', textOverflow: 'ellipsis',
|
||||
cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 0.5,
|
||||
opacity: paused ? 0.45 : 1,
|
||||
'&:hover': { bgcolor: accent + '22' },
|
||||
}}>
|
||||
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>{first.workflow.title}</span>
|
||||
|
||||
@@ -47,7 +47,7 @@ import { Typewriter } from '@/app/components/feedback/Animated';
|
||||
import StopRounded from '@mui/icons-material/StopRounded';
|
||||
import PauseRounded from '@mui/icons-material/PauseRounded';
|
||||
import { StatusDot, RunSparkline, LastFiredHint, isStaleSinceLastRun, isRealTitle } from './workflowVisuals';
|
||||
import { stepsSignature } from './scheduleUtils';
|
||||
import { stepsSignature, isWorkflowSchedulable } from './scheduleUtils';
|
||||
import { store } from '@/shared/state/store';
|
||||
import { getAgentWorkTime, fmtSeconds } from '@/shared/agentWorkTime';
|
||||
|
||||
@@ -118,6 +118,7 @@ const WorkflowCard: React.FC<Props> = ({
|
||||
const expandedSessionIds = useAppSelector((s) => s.agents.expandedSessionIds);
|
||||
const defaultModel = useAppSelector((s) => s.settings.data.default_model);
|
||||
const defaultMode = useAppSelector((s) => s.settings.data.default_mode);
|
||||
const allPaused = useAppSelector((s) => s.workflows.paused);
|
||||
|
||||
const cardBoxRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
@@ -591,6 +592,15 @@ const WorkflowCard: React.FC<Props> = ({
|
||||
</Typography>
|
||||
)}
|
||||
<StatusPill view={card.view} workflow={workflow} runs={runs} />
|
||||
{workflow && isWorkflowSchedulable(workflow) && (!workflow.schedule.enabled || allPaused) && (
|
||||
<Box sx={{
|
||||
display: 'inline-flex', alignItems: 'center',
|
||||
fontSize: '0.74rem', fontWeight: 600,
|
||||
px: 0.8, py: 0.18, borderRadius: `${c.radius.md}px`,
|
||||
color: c.text.muted, bgcolor: c.bg.elevated,
|
||||
ml: 0.25,
|
||||
}}>Paused</Box>
|
||||
)}
|
||||
<Box sx={{ flex: 1 }} />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -694,20 +694,17 @@ export function HistoryList({ runs, onOpen, showWorkflow = false, workflowTitleF
|
||||
}
|
||||
return out;
|
||||
}, [filtered]);
|
||||
// Header sparkline summarising recent successes/failures so users can
|
||||
// see "lately broken" before scrolling.
|
||||
const recent = (runs || []).slice(0, 30);
|
||||
if (!runs || runs.length === 0) {
|
||||
return <Typography sx={{ fontSize: '0.88rem', color: c.text.muted, py: 1.5, textAlign: 'center' }}>No runs yet</Typography>;
|
||||
}
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mb: 0.75 }}>
|
||||
<Box sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.25 }}>
|
||||
{recent.map((r) => (
|
||||
<Box key={r.id} sx={{ width: 6, height: 6, borderRadius: '50%', bgcolor: statusColor(r.status, c) }} />
|
||||
))}
|
||||
</Box>
|
||||
{groups.length > 0 && (
|
||||
<Typography sx={{ fontSize: '0.7rem', fontWeight: 700, color: c.text.muted, letterSpacing: '0.06em' }}>
|
||||
{groups[0].key.toUpperCase()}
|
||||
</Typography>
|
||||
)}
|
||||
<Box sx={{ flex: 1 }} />
|
||||
{(['all', 'failure', 'ran_late'] as const).map((k) => (
|
||||
<Box key={k} onClick={() => setFilter(k)} role="button" sx={{
|
||||
@@ -722,11 +719,13 @@ export function HistoryList({ runs, onOpen, showWorkflow = false, workflowTitleF
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
{groups.map(({ key, runs: gRuns }) => (
|
||||
{groups.map(({ key, runs: gRuns }, gi) => (
|
||||
<Box key={key} sx={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Typography sx={{ fontSize: '0.7rem', fontWeight: 700, color: c.text.muted, letterSpacing: '0.06em', mt: 0.5, mb: 0.25 }}>
|
||||
{key.toUpperCase()}
|
||||
</Typography>
|
||||
{gi > 0 && (
|
||||
<Typography sx={{ fontSize: '0.7rem', fontWeight: 700, color: c.text.muted, letterSpacing: '0.06em', mt: 0.5, mb: 0.25 }}>
|
||||
{key.toUpperCase()}
|
||||
</Typography>
|
||||
)}
|
||||
{gRuns.map((r) => {
|
||||
const expanded = expandedId === r.id;
|
||||
const dur = runDuration(r);
|
||||
@@ -759,7 +758,7 @@ export function HistoryList({ runs, onOpen, showWorkflow = false, workflowTitleF
|
||||
<Typography sx={{ fontSize: '0.78rem', color: c.status.error, lineHeight: 1.4 }}>{r.error}</Typography>
|
||||
) : (
|
||||
<Typography sx={{ fontSize: '0.78rem', color: c.text.secondary, lineHeight: 1.4 }}>
|
||||
{r.session_id ? `Saved as session ${r.session_id.slice(0, 8)}.` : 'No session was recorded for this run.'} Click below to see the full conversation.
|
||||
{r.session_id ? 'Click below to see the full conversation.' : 'No session was recorded for this run. Click below to see the full conversation.'}
|
||||
</Typography>
|
||||
)}
|
||||
<Box sx={{ mt: 0.5, display: 'flex', justifyContent: 'flex-end' }}>
|
||||
|
||||
@@ -641,6 +641,7 @@ function SidebarSection({ title, items, onPick, scheduled, onContext, onSchedule
|
||||
}) {
|
||||
const c = useClaudeTokens();
|
||||
const dispatch = useAppDispatch();
|
||||
const allPaused = useAppSelector((s) => s.workflows.paused);
|
||||
const [open, setOpen] = useState(true);
|
||||
|
||||
const toggleEnabled = useCallback((wf: Workflow, e: React.MouseEvent) => {
|
||||
@@ -707,10 +708,10 @@ function SidebarSection({ title, items, onPick, scheduled, onContext, onSchedule
|
||||
never animates on first appearance or for already-named rows. */}
|
||||
<Typewriter value={w.title} enabled={isRealTitle(w.title)}>
|
||||
{(t) => (
|
||||
<Typography sx={{ flex: 1, fontSize: '0.82rem', color: c.text.primary, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', opacity: scheduled && !w.schedule.enabled ? 0.7 : 1 }}>{t}</Typography>
|
||||
<Typography sx={{ flex: 1, fontSize: '0.82rem', color: c.text.primary, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', opacity: scheduled && (!w.schedule.enabled || allPaused) ? 0.7 : 1 }}>{t}</Typography>
|
||||
)}
|
||||
</Typewriter>
|
||||
{scheduled && !w.schedule.enabled && (
|
||||
{scheduled && (!w.schedule.enabled || allPaused) && (
|
||||
<Box sx={{ flexShrink: 0, px: 0.6, py: 0.1, borderRadius: '3px', bgcolor: c.bg.elevated, color: c.text.muted, fontSize: '0.62rem', fontWeight: 600, lineHeight: 1.5, letterSpacing: '0.02em' }}>Paused</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user