Workspaces in Workspaces all around

This commit is contained in:
2026-02-19 01:49:15 +00:00
parent 72bca6cd42
commit dd8ab84df5
84 changed files with 3047 additions and 749 deletions
@@ -1,5 +1,7 @@
import { useState } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { X } from 'lucide-react';
import { toast } from 'sonner';
import { Trash2, X } from 'lucide-react';
import { useClient } from 'hooks/useClient';
import { usePanelChannel } from 'hooks/usePanelChannel';
import { CapabilityChat } from '../CapabilityPage';
@@ -10,6 +12,7 @@ export const AutomationEditChat = () => {
const client = useClient();
const qc = useQueryClient();
const [selection, setSelection] = usePanelChannel<AutomationSelection>('automation:selected-capability', null);
const [chatKey, setChatKey] = useState(0);
const { data: detail } = useQuery<CapabilityDetail>({
queryKey: [selection?.queryKey, selection?.dirName],
@@ -22,30 +25,48 @@ export const AutomationEditChat = () => {
setSelection({ ...selection, editing: false });
};
const deleteChat = async () => {
if (!selection) return;
try {
await client.delete(`${selection.endpoint}/${selection.dirName}/chat`);
await qc.invalidateQueries({ queryKey: [selection.queryKey, selection.dirName] });
setChatKey((k) => k + 1);
} catch {
toast.error('Failed to delete chat');
}
};
if (!selection || !detail?.filePath) {
return (
<div className="h-full flex items-center justify-center">
<p className="text-sm text-duck-dark/40">Select a resource and click edit to chat</p>
<p className="text-sm text-duck-dark/40 dark:text-foreground/40">Select a resource and click edit to chat</p>
</div>
);
}
return (
<div className="h-full flex flex-col">
<div className="shrink-0 px-4 py-1.5 border-b border-duck-dark/10 bg-white/60 flex items-center gap-2">
<span className="text-xs font-medium text-duck-dark/50 flex-1">{detail.name ?? 'Chat'}</span>
<button onClick={closeChat} className="p-1 rounded hover:bg-duck-dark/10 cursor-pointer transition-colors">
<X className="h-3.5 w-3.5 text-duck-dark/50" />
<div className="shrink-0 px-4 py-1.5 border-b border-duck-dark/10 dark:border-foreground/10 bg-background/60 flex items-center gap-2">
<span className="text-xs font-medium text-duck-dark/50 dark:text-foreground/50 flex-1">{detail.name ?? 'Chat'}</span>
<button
onClick={deleteChat}
className="p-1 rounded hover:bg-red-500/10 cursor-pointer transition-colors"
title="Delete chat history"
>
<Trash2 className="h-3.5 w-3.5 text-duck-dark/50 dark:text-foreground/50 hover:text-red-500" />
</button>
<button onClick={closeChat} className="p-1 rounded hover:bg-duck-dark/10 dark:hover:bg-foreground/10 cursor-pointer transition-colors">
<X className="h-3.5 w-3.5 text-duck-dark/50 dark:text-foreground/50" />
</button>
</div>
<CapabilityChat
kind={selection.kind.toLowerCase()}
key={detail.filePath}
key={`${detail.filePath}-${chatKey}`}
endpoint={selection.endpoint}
dirName={selection.dirName}
filePath={detail.filePath}
resourceDir={detail.filePath.replace(/\/[^/]+$/, '')}
chatSessionId={detail.chatSessionId}
chatSessionId={chatKey > 0 ? null : detail.chatSessionId}
isNew={selection.isNew}
description={selection.description}
onResponseEnd={() => qc.invalidateQueries({ queryKey: [selection.queryKey, selection.dirName] })}
@@ -1,10 +1,10 @@
import { useState } from 'react';
import { useState, useMemo } from 'react';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import rehypeRaw from 'rehype-raw';
import { toast } from 'sonner';
import { ArrowLeft, Pencil, Trash2 } from 'lucide-react';
import { ArrowLeft, Pencil, Play, Trash2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
import { useClient } from 'hooks/useClient';
@@ -13,6 +13,53 @@ import { Card } from '@/components/Card';
import { FrontmatterBlock } from '../CapabilityPage';
import type { CapabilityDetail } from '../CapabilityPage';
import type { AutomationSelection } from './AutomationRightPanel';
import { TaskRunnerModal } from '../Files/Screen/TaskRunnerModal';
type SelectOption = { value: string; label: string };
type TaskInput = {
name: string;
description: string;
type: 'string' | 'number' | 'boolean' | 'select';
required: boolean;
default: string;
options: SelectOption[];
};
function parseInputs(rawYaml: string): TaskInput[] {
// Match the inputs block: everything indented after "inputs:" until next top-level key or end
const inputsMatch = rawYaml.match(/^inputs:\s*\n((?:[ \t]+.*\n?)*)/m);
if (!inputsMatch) return [];
const block = inputsMatch[1]!;
// Split on list items that have a "name:" field (e.g. " - name: country")
const items = block.split(/(?=[ \t]+-\s*name\s*:)/);
return items
.filter((item) => /name\s*:/.test(item))
.map((item) => {
const name = item.match(/name\s*:\s*(.+)/)?.[1]?.trim() ?? '';
const description = item.match(/description\s*:\s*(.+)/)?.[1]?.trim() ?? '';
const rawType = item.match(/type\s*:\s*(.+)/)?.[1]?.trim() ?? 'string';
const type = (['string', 'number', 'boolean', 'select'] as const).includes(rawType as 'string')
? (rawType as TaskInput['type'])
: 'string';
const required = item.match(/required\s*:\s*(.+)/)?.[1]?.trim() === 'true';
const defaultFromField = item.match(/default\s*:\s*(.+)/)?.[1]?.trim();
const defaultFromDesc = description.match(/[Dd]efaults?\s+to\s+(\S+?)\.?\s*$/)?.[1];
const options: SelectOption[] = [];
const optionsMatch = item.match(/options\s*:\s*\n((?:[ \t]+.*\n?)*)/);
if (optionsMatch) {
const optionEntries = optionsMatch[1]!.split(/(?=[ \t]*- \s*value\s*:)/);
for (const entry of optionEntries) {
const value = entry.match(/value\s*:\s*(.+)/)?.[1]?.trim() ?? '';
const label = entry.match(/label\s*:\s*(.+)/)?.[1]?.trim() ?? value;
if (value) options.push({ value, label });
}
}
return { name, description, type, required, default: defaultFromField ?? defaultFromDesc ?? '', options };
});
}
type CapabilityDetailViewProps = {
kind: string;
@@ -28,12 +75,45 @@ export const CapabilityDetailView = ({ kind, endpoint, queryKey, dirName, editin
const qc = useQueryClient();
const [selection, setSelection] = usePanelChannel<AutomationSelection>('automation:selected-capability', null);
const [deleteConfirm, setDeleteConfirm] = useState(false);
const [showInputForm, setShowInputForm] = useState(false);
const [runPrompt, setRunPrompt] = useState<string | null>(null);
const [inputValues, setInputValues] = useState<Record<string, string>>({});
const { data: detail } = useQuery<CapabilityDetail>({
queryKey: [queryKey, dirName],
queryFn: () => client.get<CapabilityDetail>(`${endpoint}/${dirName}`),
});
const taskInputs = useMemo(() => (detail?.rawFrontmatter ? parseInputs(detail.rawFrontmatter) : []), [detail?.rawFrontmatter]);
const handleRunClick = () => {
if (taskInputs.length > 0) {
const defaults: Record<string, string> = {};
for (const input of taskInputs) {
if (input.type === 'boolean') {
defaults[input.name] = input.default === 'true' ? 'true' : 'false';
} else if (input.type === 'select') {
defaults[input.name] = input.default || (input.options[0]?.value ?? '');
} else {
defaults[input.name] = input.default;
}
}
setInputValues(defaults);
setShowInputForm(true);
} else {
setRunPrompt(`Read the task instructions at ${detail!.filePath} and execute them`);
}
};
const handleInputSubmit = () => {
const parts = Object.entries(inputValues)
.filter(([, v]) => v !== '')
.map(([k, v]) => `- ${k}: ${v}`);
const suffix = parts.length > 0 ? `\n\nInputs:\n${parts.join('\n')}` : '';
setRunPrompt(`Read the task instructions at ${detail!.filePath} and execute them${suffix}`);
setShowInputForm(false);
};
const toggleEditing = () => {
if (!selection) return;
setSelection({ ...selection, editing: !editing });
@@ -53,24 +133,33 @@ export const CapabilityDetailView = ({ kind, endpoint, queryKey, dirName, editin
return (
<>
<Card className="h-full overflow-hidden flex flex-col min-h-0">
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 bg-white/60 flex items-center gap-2">
<button onClick={() => setSelection({ kind, endpoint, queryKey, dirName: '' })} className="p-1 -ml-1 rounded hover:bg-duck-dark/10 cursor-pointer">
<ArrowLeft className="h-4 w-4 text-duck-dark/60" />
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 dark:border-foreground/10 bg-background/60 flex items-center gap-2">
<button onClick={() => setSelection({ kind, endpoint, queryKey, dirName: '' })} className="p-1 -ml-1 rounded hover:bg-duck-dark/10 dark:hover:bg-foreground/10 cursor-pointer">
<ArrowLeft className="h-4 w-4 text-duck-dark/60 dark:text-foreground/60" />
</button>
<span className="text-sm font-medium text-duck-dark/70 flex-1">{detail?.name ?? `Loading...`}</span>
<span className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70 flex-1">{detail?.name ?? `Loading...`}</span>
{detail && (
<>
{kind === 'Task' && (
<button
onClick={handleRunClick}
className="p-1 rounded hover:bg-duck-dark/10 dark:hover:bg-foreground/10 cursor-pointer transition-colors"
title="Run task"
>
<Play className="h-3.5 w-3.5 text-duck-teal" />
</button>
)}
<button
onClick={toggleEditing}
className={`p-1 rounded hover:bg-duck-dark/10 cursor-pointer transition-colors ${editing ? 'bg-duck-teal/10' : ''}`}
className={`p-1 rounded hover:bg-duck-dark/10 dark:hover:bg-foreground/10 cursor-pointer transition-colors ${editing ? 'bg-duck-teal/10' : ''}`}
>
<Pencil className={`h-3.5 w-3.5 ${editing ? 'text-duck-teal' : 'text-duck-dark/50'}`} />
<Pencil className={`h-3.5 w-3.5 ${editing ? 'text-duck-teal' : 'text-duck-dark/50 dark:text-foreground/50'}`} />
</button>
<button
onClick={() => setDeleteConfirm(true)}
className="p-1 rounded hover:bg-red-50 cursor-pointer transition-colors"
>
<Trash2 className="h-3.5 w-3.5 text-duck-dark/50 hover:text-red-500" />
<Trash2 className="h-3.5 w-3.5 text-duck-dark/50 dark:text-foreground/50 hover:text-red-500" />
</button>
</>
)}
@@ -84,7 +173,7 @@ export const CapabilityDetailView = ({ kind, endpoint, queryKey, dirName, editin
</ReactMarkdown>
</article>
) : detail ? (
<p className="text-sm text-duck-dark/40 text-center mt-12">Empty file</p>
<p className="text-sm text-duck-dark/40 dark:text-foreground/40 text-center mt-12">Empty file</p>
) : null}
</div>
</Card>
@@ -107,6 +196,89 @@ export const CapabilityDetailView = ({ kind, endpoint, queryKey, dirName, editin
</div>
</DialogContent>
</Dialog>
<Dialog open={showInputForm} onOpenChange={setShowInputForm}>
<DialogContent className="sm:max-w-lg z-[700]">
<DialogHeader>
<DialogTitle>Run {detail?.name}</DialogTitle>
<DialogDescription>Configure inputs before running.</DialogDescription>
</DialogHeader>
<form
onSubmit={(ev) => {
ev.preventDefault();
handleInputSubmit();
}}
className="flex flex-col gap-3 mt-1"
>
{taskInputs.map((input) => (
<div key={input.name} className="flex flex-col gap-1">
{input.type === 'boolean' ? (
<label className="flex items-center gap-2 text-sm font-medium text-foreground/70 cursor-pointer">
<input
type="checkbox"
checked={inputValues[input.name] === 'true'}
onChange={(ev) => setInputValues((prev) => ({ ...prev, [input.name]: ev.target.checked ? 'true' : 'false' }))}
className="h-4 w-4 rounded border-foreground/20 accent-duck-teal"
/>
{input.name}
{input.required && <span className="text-destructive ml-0.5">*</span>}
</label>
) : (
<>
<label className="text-sm font-medium text-foreground/70">
{input.name}
{input.required && <span className="text-destructive ml-0.5">*</span>}
</label>
{input.type === 'select' ? (
<select
value={inputValues[input.name] ?? ''}
onChange={(ev) => setInputValues((prev) => ({ ...prev, [input.name]: ev.target.value }))}
required={input.required}
className="rounded border border-foreground/20 bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
>
{input.options.map((opt) => (
<option key={opt.value} value={opt.value}>{opt.label}</option>
))}
</select>
) : (
<input
type={input.type === 'number' ? 'number' : 'text'}
value={inputValues[input.name] ?? ''}
onChange={(ev) => setInputValues((prev) => ({ ...prev, [input.name]: ev.target.value }))}
placeholder={input.default || undefined}
required={input.required}
className="rounded border border-foreground/20 bg-background px-3 py-2 text-sm text-foreground placeholder:text-foreground/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
/>
)}
</>
)}
{input.description && (
<span className="text-xs text-foreground/40">{input.description}</span>
)}
</div>
))}
<div className="flex justify-end gap-2 mt-2">
<Button type="button" variant="outline" onClick={() => setShowInputForm(false)} className="cursor-pointer">
Cancel
</Button>
<Button type="submit" className="bg-duck-teal text-duck-yellow hover:bg-duck-teal/90 cursor-pointer">
Run
</Button>
</div>
</form>
</DialogContent>
</Dialog>
{runPrompt !== null && detail && (
<TaskRunnerModal
open
onOpenChange={(open) => {
if (!open) setRunPrompt(null);
}}
task={{ dirName, name: detail.name, description: detail.description ?? '', scope: detail.scope ?? 'user', triggers: [], filePath: detail.filePath }}
promptOverride={runPrompt}
/>
)}
</>
);
};
@@ -35,23 +35,23 @@ export const CapabilityList = ({ kind, endpoint, queryKey }: CapabilityListProps
return (
<div className="h-full flex flex-col">
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 bg-white/60 flex items-center gap-2">
<span className="text-sm font-medium text-duck-dark/70 flex-1">{kind}s</span>
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 dark:border-foreground/10 bg-background/60 flex items-center gap-2">
<span className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70 flex-1">{kind}s</span>
<button
onClick={() => setSelection({ kind, endpoint, queryKey, dirName: '', creating: true })}
className="p-1 rounded hover:bg-duck-dark/10 cursor-pointer transition-colors"
className="p-1 rounded hover:bg-duck-dark/10 dark:hover:bg-foreground/10 cursor-pointer transition-colors"
>
<Plus className="h-3.5 w-3.5 text-duck-dark/50" />
<Plus className="h-3.5 w-3.5 text-duck-dark/50 dark:text-foreground/50" />
</button>
</div>
<div className="px-4 pt-3">
<div className="relative">
<Search className="absolute left-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-duck-dark/30" />
<Search className="absolute left-2 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-duck-dark/30 dark:text-foreground/30" />
<input
value={search}
onChange={(ev) => setSearch(ev.target.value)}
placeholder="Search..."
className="w-full rounded border border-duck-dark/15 bg-white/80 pl-7 pr-2 py-1.5 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
className="w-full rounded border border-duck-dark/15 dark:border-foreground/15 bg-background/80 pl-7 pr-2 py-1.5 text-sm text-duck-dark dark:text-foreground placeholder:text-duck-dark/30 dark:placeholder:text-foreground/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
/>
</div>
</div>
@@ -63,18 +63,18 @@ export const CapabilityList = ({ kind, endpoint, queryKey }: CapabilityListProps
onClick={() => setSelection({ kind, endpoint, queryKey, dirName: c.dirName })}
className={`w-full text-left px-3 py-2 rounded-lg text-sm cursor-pointer transition-colors ${
selection?.queryKey === queryKey && selection.dirName === c.dirName
? 'bg-duck-teal/10 text-duck-dark'
: 'text-duck-dark/60 hover:bg-duck-dark/5 hover:text-duck-dark'
? 'bg-duck-teal/10 text-duck-dark dark:text-foreground'
: 'text-duck-dark/60 dark:text-foreground/60 hover:bg-duck-dark/5 dark:hover:bg-foreground/5 hover:text-duck-dark dark:hover:text-foreground'
}`}
>
<span className="font-medium">{c.name}</span>
{c.description && (
<p className="text-xs text-duck-dark/40 mt-0.5 line-clamp-1">{c.description}</p>
<p className="text-xs text-duck-dark/40 dark:text-foreground/40 mt-0.5 line-clamp-1">{c.description}</p>
)}
</button>
))}
{filtered.length === 0 && (
<p className="text-xs text-duck-dark/40 px-3 py-4 text-center">
<p className="text-xs text-duck-dark/40 dark:text-foreground/40 px-3 py-4 text-center">
{items.length === 0 ? `No ${kind.toLowerCase()}s yet` : 'No matches'}
</p>
)}
@@ -43,18 +43,18 @@ export const NewCron = ({ selection }: NewCronProps) => {
return (
<div className="h-full flex flex-col">
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 bg-white/60 flex items-center gap-2">
<span className="text-sm font-medium text-duck-dark/70 flex-1">New Cron</span>
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 dark:border-foreground/10 bg-background/60 flex items-center gap-2">
<span className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70 flex-1">New Cron</span>
<button
onClick={() => setSelection(null)}
className="p-1 rounded hover:bg-duck-dark/10 cursor-pointer transition-colors"
className="p-1 rounded hover:bg-duck-dark/10 dark:hover:bg-foreground/10 cursor-pointer transition-colors"
>
<X className="h-3.5 w-3.5 text-duck-dark/50" />
<X className="h-3.5 w-3.5 text-duck-dark/50 dark:text-foreground/50" />
</button>
</div>
<div className="flex-1 overflow-y-auto p-6 flex flex-col gap-4">
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-duck-dark/70">Name</label>
<label className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70">Name</label>
<input
value={name}
onChange={(ev) => setName(ev.target.value)}
@@ -66,17 +66,17 @@ export const NewCron = ({ selection }: NewCronProps) => {
}}
placeholder="Cron name..."
autoFocus
className="rounded border border-duck-dark/20 bg-white px-3 py-2 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
className="rounded border border-duck-dark/20 dark:border-foreground/20 bg-background px-3 py-2 text-sm text-duck-dark dark:text-foreground placeholder:text-duck-dark/30 dark:placeholder:text-foreground/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
/>
</div>
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-duck-dark/70">Description</label>
<label className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70">Description</label>
<textarea
value={description}
onChange={(ev) => setDescription(ev.target.value)}
placeholder="Describe what this cron job schedules..."
rows={4}
className="rounded border border-duck-dark/20 bg-white px-3 py-2 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30 resize-none"
className="rounded border border-duck-dark/20 dark:border-foreground/20 bg-background px-3 py-2 text-sm text-duck-dark dark:text-foreground placeholder:text-duck-dark/30 dark:placeholder:text-foreground/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30 resize-none"
/>
</div>
<div className="flex justify-end">
@@ -43,18 +43,18 @@ export const NewPipeline = ({ selection }: NewPipelineProps) => {
return (
<div className="h-full flex flex-col">
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 bg-white/60 flex items-center gap-2">
<span className="text-sm font-medium text-duck-dark/70 flex-1">New Pipeline</span>
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 dark:border-foreground/10 bg-background/60 flex items-center gap-2">
<span className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70 flex-1">New Pipeline</span>
<button
onClick={() => setSelection(null)}
className="p-1 rounded hover:bg-duck-dark/10 cursor-pointer transition-colors"
className="p-1 rounded hover:bg-duck-dark/10 dark:hover:bg-foreground/10 cursor-pointer transition-colors"
>
<X className="h-3.5 w-3.5 text-duck-dark/50" />
<X className="h-3.5 w-3.5 text-duck-dark/50 dark:text-foreground/50" />
</button>
</div>
<div className="flex-1 overflow-y-auto p-6 flex flex-col gap-4">
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-duck-dark/70">Name</label>
<label className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70">Name</label>
<input
value={name}
onChange={(ev) => setName(ev.target.value)}
@@ -66,17 +66,17 @@ export const NewPipeline = ({ selection }: NewPipelineProps) => {
}}
placeholder="Pipeline name..."
autoFocus
className="rounded border border-duck-dark/20 bg-white px-3 py-2 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
className="rounded border border-duck-dark/20 dark:border-foreground/20 bg-background px-3 py-2 text-sm text-duck-dark dark:text-foreground placeholder:text-duck-dark/30 dark:placeholder:text-foreground/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
/>
</div>
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-duck-dark/70">Description</label>
<label className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70">Description</label>
<textarea
value={description}
onChange={(ev) => setDescription(ev.target.value)}
placeholder="Describe what this pipeline orchestrates..."
rows={4}
className="rounded border border-duck-dark/20 bg-white px-3 py-2 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30 resize-none"
className="rounded border border-duck-dark/20 dark:border-foreground/20 bg-background px-3 py-2 text-sm text-duck-dark dark:text-foreground placeholder:text-duck-dark/30 dark:placeholder:text-foreground/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30 resize-none"
/>
</div>
<div className="flex justify-end">
@@ -43,18 +43,18 @@ export const NewProcess = ({ selection }: NewProcessProps) => {
return (
<div className="h-full flex flex-col">
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 bg-white/60 flex items-center gap-2">
<span className="text-sm font-medium text-duck-dark/70 flex-1">New Process</span>
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 dark:border-foreground/10 bg-background/60 flex items-center gap-2">
<span className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70 flex-1">New Process</span>
<button
onClick={() => setSelection(null)}
className="p-1 rounded hover:bg-duck-dark/10 cursor-pointer transition-colors"
className="p-1 rounded hover:bg-duck-dark/10 dark:hover:bg-foreground/10 cursor-pointer transition-colors"
>
<X className="h-3.5 w-3.5 text-duck-dark/50" />
<X className="h-3.5 w-3.5 text-duck-dark/50 dark:text-foreground/50" />
</button>
</div>
<div className="flex-1 overflow-y-auto p-6 flex flex-col gap-4">
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-duck-dark/70">Name</label>
<label className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70">Name</label>
<input
value={name}
onChange={(ev) => setName(ev.target.value)}
@@ -66,17 +66,17 @@ export const NewProcess = ({ selection }: NewProcessProps) => {
}}
placeholder="Process name..."
autoFocus
className="rounded border border-duck-dark/20 bg-white px-3 py-2 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
className="rounded border border-duck-dark/20 dark:border-foreground/20 bg-background px-3 py-2 text-sm text-duck-dark dark:text-foreground placeholder:text-duck-dark/30 dark:placeholder:text-foreground/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
/>
</div>
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-duck-dark/70">Description</label>
<label className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70">Description</label>
<textarea
value={description}
onChange={(ev) => setDescription(ev.target.value)}
placeholder="Describe what this process manages..."
rows={4}
className="rounded border border-duck-dark/20 bg-white px-3 py-2 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30 resize-none"
className="rounded border border-duck-dark/20 dark:border-foreground/20 bg-background px-3 py-2 text-sm text-duck-dark dark:text-foreground placeholder:text-duck-dark/30 dark:placeholder:text-foreground/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30 resize-none"
/>
</div>
<div className="flex justify-end">
@@ -43,18 +43,18 @@ export const NewService = ({ selection }: NewServiceProps) => {
return (
<div className="h-full flex flex-col">
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 bg-white/60 flex items-center gap-2">
<span className="text-sm font-medium text-duck-dark/70 flex-1">New Service</span>
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 dark:border-foreground/10 bg-background/60 flex items-center gap-2">
<span className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70 flex-1">New Service</span>
<button
onClick={() => setSelection(null)}
className="p-1 rounded hover:bg-duck-dark/10 cursor-pointer transition-colors"
className="p-1 rounded hover:bg-duck-dark/10 dark:hover:bg-foreground/10 cursor-pointer transition-colors"
>
<X className="h-3.5 w-3.5 text-duck-dark/50" />
<X className="h-3.5 w-3.5 text-duck-dark/50 dark:text-foreground/50" />
</button>
</div>
<div className="flex-1 overflow-y-auto p-6 flex flex-col gap-4">
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-duck-dark/70">Name</label>
<label className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70">Name</label>
<input
value={name}
onChange={(ev) => setName(ev.target.value)}
@@ -66,17 +66,17 @@ export const NewService = ({ selection }: NewServiceProps) => {
}}
placeholder="Service name..."
autoFocus
className="rounded border border-duck-dark/20 bg-white px-3 py-2 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
className="rounded border border-duck-dark/20 dark:border-foreground/20 bg-background px-3 py-2 text-sm text-duck-dark dark:text-foreground placeholder:text-duck-dark/30 dark:placeholder:text-foreground/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
/>
</div>
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-duck-dark/70">Description</label>
<label className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70">Description</label>
<textarea
value={description}
onChange={(ev) => setDescription(ev.target.value)}
placeholder="Describe what this service provides..."
rows={4}
className="rounded border border-duck-dark/20 bg-white px-3 py-2 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30 resize-none"
className="rounded border border-duck-dark/20 dark:border-foreground/20 bg-background px-3 py-2 text-sm text-duck-dark dark:text-foreground placeholder:text-duck-dark/30 dark:placeholder:text-foreground/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30 resize-none"
/>
</div>
<div className="flex justify-end">
@@ -43,18 +43,18 @@ export const NewSkill = ({ selection }: NewSkillProps) => {
return (
<div className="h-full flex flex-col">
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 bg-white/60 flex items-center gap-2">
<span className="text-sm font-medium text-duck-dark/70 flex-1">New Skill</span>
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 dark:border-foreground/10 bg-background/60 flex items-center gap-2">
<span className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70 flex-1">New Skill</span>
<button
onClick={() => setSelection(null)}
className="p-1 rounded hover:bg-duck-dark/10 cursor-pointer transition-colors"
className="p-1 rounded hover:bg-duck-dark/10 dark:hover:bg-foreground/10 cursor-pointer transition-colors"
>
<X className="h-3.5 w-3.5 text-duck-dark/50" />
<X className="h-3.5 w-3.5 text-duck-dark/50 dark:text-foreground/50" />
</button>
</div>
<div className="flex-1 overflow-y-auto p-6 flex flex-col gap-4">
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-duck-dark/70">Name</label>
<label className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70">Name</label>
<input
value={name}
onChange={(ev) => setName(ev.target.value)}
@@ -66,17 +66,17 @@ export const NewSkill = ({ selection }: NewSkillProps) => {
}}
placeholder="Skill name..."
autoFocus
className="rounded border border-duck-dark/20 bg-white px-3 py-2 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
className="rounded border border-duck-dark/20 dark:border-foreground/20 bg-background px-3 py-2 text-sm text-duck-dark dark:text-foreground placeholder:text-duck-dark/30 dark:placeholder:text-foreground/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
/>
</div>
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-duck-dark/70">Description</label>
<label className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70">Description</label>
<textarea
value={description}
onChange={(ev) => setDescription(ev.target.value)}
placeholder="Describe what this skill does..."
rows={4}
className="rounded border border-duck-dark/20 bg-white px-3 py-2 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30 resize-none"
className="rounded border border-duck-dark/20 dark:border-foreground/20 bg-background px-3 py-2 text-sm text-duck-dark dark:text-foreground placeholder:text-duck-dark/30 dark:placeholder:text-foreground/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30 resize-none"
/>
</div>
<div className="flex justify-end">
@@ -43,18 +43,18 @@ export const NewTask = ({ selection }: NewTaskProps) => {
return (
<div className="h-full flex flex-col">
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 bg-white/60 flex items-center gap-2">
<span className="text-sm font-medium text-duck-dark/70 flex-1">New Task</span>
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 dark:border-foreground/10 bg-background/60 flex items-center gap-2">
<span className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70 flex-1">New Task</span>
<button
onClick={() => setSelection(null)}
className="p-1 rounded hover:bg-duck-dark/10 cursor-pointer transition-colors"
className="p-1 rounded hover:bg-duck-dark/10 dark:hover:bg-foreground/10 cursor-pointer transition-colors"
>
<X className="h-3.5 w-3.5 text-duck-dark/50" />
<X className="h-3.5 w-3.5 text-duck-dark/50 dark:text-foreground/50" />
</button>
</div>
<div className="flex-1 overflow-y-auto p-6 flex flex-col gap-4">
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-duck-dark/70">Name</label>
<label className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70">Name</label>
<input
value={name}
onChange={(ev) => setName(ev.target.value)}
@@ -66,17 +66,17 @@ export const NewTask = ({ selection }: NewTaskProps) => {
}}
placeholder="Task name..."
autoFocus
className="rounded border border-duck-dark/20 bg-white px-3 py-2 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
className="rounded border border-duck-dark/20 dark:border-foreground/20 bg-background px-3 py-2 text-sm text-duck-dark dark:text-foreground placeholder:text-duck-dark/30 dark:placeholder:text-foreground/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
/>
</div>
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-duck-dark/70">Description</label>
<label className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70">Description</label>
<textarea
value={description}
onChange={(ev) => setDescription(ev.target.value)}
placeholder="Describe what this task automates..."
rows={4}
className="rounded border border-duck-dark/20 bg-white px-3 py-2 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30 resize-none"
className="rounded border border-duck-dark/20 dark:border-foreground/20 bg-background px-3 py-2 text-sm text-duck-dark dark:text-foreground placeholder:text-duck-dark/30 dark:placeholder:text-foreground/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30 resize-none"
/>
</div>
<div className="flex justify-end">
@@ -43,18 +43,18 @@ export const NewWorkflow = ({ selection }: NewWorkflowProps) => {
return (
<div className="h-full flex flex-col">
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 bg-white/60 flex items-center gap-2">
<span className="text-sm font-medium text-duck-dark/70 flex-1">New Workflow</span>
<div className="shrink-0 px-4 py-2 border-b border-duck-dark/10 dark:border-foreground/10 bg-background/60 flex items-center gap-2">
<span className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70 flex-1">New Workflow</span>
<button
onClick={() => setSelection(null)}
className="p-1 rounded hover:bg-duck-dark/10 cursor-pointer transition-colors"
className="p-1 rounded hover:bg-duck-dark/10 dark:hover:bg-foreground/10 cursor-pointer transition-colors"
>
<X className="h-3.5 w-3.5 text-duck-dark/50" />
<X className="h-3.5 w-3.5 text-duck-dark/50 dark:text-foreground/50" />
</button>
</div>
<div className="flex-1 overflow-y-auto p-6 flex flex-col gap-4">
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-duck-dark/70">Name</label>
<label className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70">Name</label>
<input
value={name}
onChange={(ev) => setName(ev.target.value)}
@@ -66,17 +66,17 @@ export const NewWorkflow = ({ selection }: NewWorkflowProps) => {
}}
placeholder="Workflow name..."
autoFocus
className="rounded border border-duck-dark/20 bg-white px-3 py-2 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
className="rounded border border-duck-dark/20 dark:border-foreground/20 bg-background px-3 py-2 text-sm text-duck-dark dark:text-foreground placeholder:text-duck-dark/30 dark:placeholder:text-foreground/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30"
/>
</div>
<div className="flex flex-col gap-1.5">
<label className="text-sm font-medium text-duck-dark/70">Description</label>
<label className="text-sm font-medium text-duck-dark/70 dark:text-foreground/70">Description</label>
<textarea
value={description}
onChange={(ev) => setDescription(ev.target.value)}
placeholder="Describe what this workflow automates..."
rows={4}
className="rounded border border-duck-dark/20 bg-white px-3 py-2 text-sm text-duck-dark placeholder:text-duck-dark/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30 resize-none"
className="rounded border border-duck-dark/20 dark:border-foreground/20 bg-background px-3 py-2 text-sm text-duck-dark dark:text-foreground placeholder:text-duck-dark/30 dark:placeholder:text-foreground/30 focus:outline-none focus:ring-1 focus:ring-duck-teal/30 resize-none"
/>
</div>
<div className="flex justify-end">