diff --git a/panel/src/app/(dashboard)/prompter/page.tsx b/panel/src/app/(dashboard)/prompter/page.tsx new file mode 100644 index 00000000..a9641a45 --- /dev/null +++ b/panel/src/app/(dashboard)/prompter/page.tsx @@ -0,0 +1,93 @@ +"use client"; + +import { Sparkles } from "lucide-react"; +import { usePrompter } from "@/hooks/use-prompter"; +import { + ChatMessages, + ChatComposer, + ConfirmDialog, + SuccessCard, +} from "@/components/prompter"; + +export default function PrompterPage() { + const { + state, + messages, + isSending, + editableDraft, + createdTaskId, + createdTaskTitle, + createdTaskTeam, + send, + openReview, + closeReview, + keepChatting, + updateDraft, + isValidForLaunch, + launchTask, + startAnother, + isLaunching, + } = usePrompter(); + + const isComposerDisabled = + state === "launching" || state === "success"; + + return ( +
+ {/* Page header */} +
+ +
+

Task Assistant

+

+ Describe your idea and I'll help you create a structured task +

+
+
+ + {/* Chat area */} +
+ {/* Success overlay in chat area */} + {state === "success" && + createdTaskId && + createdTaskTitle && + createdTaskTeam ? ( +
+
+ +
+
+ ) : ( + + )} + + {/* Composer */} + +
+ + {/* Confirmation dialog (portal) */} + +
+ ); +} diff --git a/panel/src/components/layout/sidebar.tsx b/panel/src/components/layout/sidebar.tsx index 96f8d6d6..bc5ff1c9 100644 --- a/panel/src/components/layout/sidebar.tsx +++ b/panel/src/components/layout/sidebar.tsx @@ -21,6 +21,7 @@ import { GitBranch, Database, Cpu, + Sparkles, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; @@ -33,6 +34,7 @@ const navItems = [ // Work Management { title: "Tasks", href: "/tasks", icon: ListTodo }, { title: "Kanban", href: "/kanban", icon: Kanban }, + { title: "Task Assistant", href: "/prompter", icon: Sparkles }, // Development { title: "Projects", href: "/projects", icon: FolderGit2 }, diff --git a/panel/src/components/prompter/chat-composer.tsx b/panel/src/components/prompter/chat-composer.tsx new file mode 100644 index 00000000..6eb36556 --- /dev/null +++ b/panel/src/components/prompter/chat-composer.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { useState, useRef, KeyboardEvent } from "react"; +import { Send, Loader2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; + +interface ChatComposerProps { + onSend: (text: string) => Promise | void; + disabled?: boolean; + isSending?: boolean; + placeholder?: string; +} + +export function ChatComposer({ + onSend, + disabled = false, + isSending = false, + placeholder = "Describe the task you want to create… (Enter to send, Shift+Enter for newline)", +}: ChatComposerProps) { + const [value, setValue] = useState(""); + const textareaRef = useRef(null); + + const isDisabled = disabled || isSending || !value.trim(); + + const handleSend = async () => { + const text = value.trim(); + if (!text || isSending || disabled) return; + setValue(""); + await onSend(text); + // Refocus after send + textareaRef.current?.focus(); + }; + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Enter" && !e.shiftKey) { + e.preventDefault(); + handleSend(); + } + // Shift+Enter falls through to default (inserts newline) + }; + + return ( +
+