From 5a99140a553f841bc15da0d38f16bd2bec9cf6b0 Mon Sep 17 00:00:00 2001 From: Renzo F <45401804+rennf93@users.noreply.github.com> Date: Sun, 7 Jun 2026 22:38:36 +0200 Subject: [PATCH] [9ef46be7] feat(prompter): implement Prompter page, nav entry, chat UI, and task creation wiring (#78) (#79) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Task Assistant nav entry (Sparkles icon) to sidebar between Kanban and Projects - Create lib/api/prompter.ts with createSession, sendMessage, getDraft API functions - Create hooks/use-prompter.ts with 6-state machine (empty→chatting→draft_preview→review_modal→launching→success) - Create ChatMessages with auto-scroll, user/assistant/error rendering, inline DraftProposalCard - Create ChatComposer with Enter-to-send and Shift+Enter for newline - Create DraftProposalCard with title, description, acceptance criteria, metadata badges, Keep Chatting/Review & Confirm actions - Create ConfirmDialog (Radix Dialog, no form element) with MarkdownEditor, AcceptanceCriteriaEditor, metadata selects, warning banner, disabled Confirm & Launch until valid - Create SuccessCard with task title, team, View Task link, Start Another button - Create /prompter route page composing all components via usePrompter hook - TypeScript and ESLint pass with zero errors Co-authored-by: Frontend Developer 2 --- panel/src/app/(dashboard)/prompter/page.tsx | 93 ++++++ panel/src/components/layout/sidebar.tsx | 2 + .../src/components/prompter/chat-composer.tsx | 70 +++++ .../src/components/prompter/chat-messages.tsx | 97 +++++++ .../components/prompter/confirm-dialog.tsx | 212 ++++++++++++++ .../prompter/draft-proposal-card.tsx | 108 +++++++ panel/src/components/prompter/index.ts | 5 + .../src/components/prompter/success-card.tsx | 58 ++++ panel/src/hooks/use-prompter.ts | 264 ++++++++++++++++++ panel/src/lib/api/prompter.ts | 65 +++++ 10 files changed, 974 insertions(+) create mode 100644 panel/src/app/(dashboard)/prompter/page.tsx create mode 100644 panel/src/components/prompter/chat-composer.tsx create mode 100644 panel/src/components/prompter/chat-messages.tsx create mode 100644 panel/src/components/prompter/confirm-dialog.tsx create mode 100644 panel/src/components/prompter/draft-proposal-card.tsx create mode 100644 panel/src/components/prompter/index.ts create mode 100644 panel/src/components/prompter/success-card.tsx create mode 100644 panel/src/hooks/use-prompter.ts create mode 100644 panel/src/lib/api/prompter.ts 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 ( +
+