95 lines
3.9 KiB
TypeScript
95 lines
3.9 KiB
TypeScript
import { useState } from 'react';
|
|
import { useQueryClient } from '@tanstack/react-query';
|
|
import { X } from 'lucide-react';
|
|
import { toast } from 'sonner';
|
|
import { Button } from '@/components/ui/button';
|
|
import { useClient } from 'hooks/useClient';
|
|
import { usePanelChannel } from 'hooks/usePanelChannel';
|
|
import type { AutomationSelection } from './AutomationRightPanel';
|
|
|
|
type NewSkillProps = {
|
|
selection: NonNullable<AutomationSelection>;
|
|
};
|
|
|
|
export const NewSkill = ({ selection }: NewSkillProps) => {
|
|
const client = useClient();
|
|
const qc = useQueryClient();
|
|
const [, setSelection] = usePanelChannel<AutomationSelection>('automation:selected-capability', null);
|
|
const [name, setName] = useState('');
|
|
const [description, setDescription] = useState('');
|
|
const [submitting, setSubmitting] = useState(false);
|
|
|
|
const handleCreate = async () => {
|
|
const trimmed = name.trim();
|
|
if (!trimmed) return;
|
|
setSubmitting(true);
|
|
try {
|
|
const res = await client.post<{ name: string; dirName: string }>(selection.endpoint, { name: trimmed });
|
|
await qc.invalidateQueries({ queryKey: [selection.queryKey] });
|
|
setSelection({
|
|
kind: selection.kind,
|
|
endpoint: selection.endpoint,
|
|
queryKey: selection.queryKey,
|
|
dirName: res.dirName,
|
|
isNew: true,
|
|
editing: true,
|
|
description: description.trim() || undefined,
|
|
});
|
|
} catch {
|
|
toast.error('Failed to create skill');
|
|
setSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="h-full flex flex-col">
|
|
<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 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>
|
|
<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 dark:text-foreground/70">Name</label>
|
|
<input
|
|
value={name}
|
|
onChange={(ev) => setName(ev.target.value)}
|
|
onKeyDown={(ev) => {
|
|
if (ev.key === 'Enter') {
|
|
ev.preventDefault();
|
|
handleCreate();
|
|
}
|
|
}}
|
|
placeholder="Skill name..."
|
|
autoFocus
|
|
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 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 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">
|
|
<Button
|
|
onClick={handleCreate}
|
|
disabled={!name.trim() || submitting}
|
|
className="bg-duck-teal text-duck-yellow hover:bg-duck-teal/90 cursor-pointer"
|
|
>
|
|
{submitting ? 'Creating...' : 'Create'}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|