From 0adcdfee02e58f4e94c6adf012de5bbbe7632718 Mon Sep 17 00:00:00 2001 From: tejasM17 Date: Fri, 17 Jul 2026 10:38:42 +0530 Subject: [PATCH 1/3] compleated 20-feat --- components/editor/ai-sidebar.tsx | 337 +++++++++++++++++++ components/editor/workspace-shell.tsx | 101 +----- context/feature-specs/20-ai-sidebar-shell.md | 66 ++++ context/progress-tracker.md | 22 +- 4 files changed, 417 insertions(+), 109 deletions(-) create mode 100644 components/editor/ai-sidebar.tsx create mode 100644 context/feature-specs/20-ai-sidebar-shell.md diff --git a/components/editor/ai-sidebar.tsx b/components/editor/ai-sidebar.tsx new file mode 100644 index 0000000..6a58476 --- /dev/null +++ b/components/editor/ai-sidebar.tsx @@ -0,0 +1,337 @@ +"use client"; + +import { + useCallback, + useEffect, + useRef, + useState, + type KeyboardEvent, + type RefObject, +} from "react"; +import { + Bot, + Download, + FileText, + Send, + X, +} from "lucide-react"; + +import { Button } from "@/components/ui/button"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Textarea } from "@/components/ui/textarea"; +import { cn } from "@/lib/utils"; + +const STARTER_PROMPTS = [ + "Design an e-commerce backend", + "Create a chat app architecture", + "Build a CI/CD pipeline", +] as const; + +interface ChatMessage { + id: string; + role: "user" | "assistant"; + content: string; +} + +interface AiSidebarProps { + isOpen: boolean; + onClose: () => void; +} + +/** + * Floating AI Workspace sidebar. Open/close is controlled by the parent; + * this component owns only the internal chat UI shell (no backend / Liveblocks). + */ +export function AiSidebar({ isOpen, onClose }: AiSidebarProps) { + const [messages, setMessages] = useState([]); + const [input, setInput] = useState(""); + const textareaRef = useRef(null); + + const resizeTextarea = useCallback(() => { + const el = textareaRef.current; + if (!el) return; + el.style.height = "auto"; + const next = Math.min(Math.max(el.scrollHeight, 72), 160); + el.style.height = `${next}px`; + }, []); + + useEffect(() => { + resizeTextarea(); + }, [input, resizeTextarea]); + + const handleSend = useCallback(() => { + const trimmed = input.trim(); + if (!trimmed) return; + + setMessages((prev) => [ + ...prev, + { + id: `user-${Date.now()}`, + role: "user", + content: trimmed, + }, + ]); + setInput(""); + }, [input]); + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Enter" && !event.shiftKey) { + event.preventDefault(); + handleSend(); + } + }; + + const applyStarter = (prompt: string) => { + setInput(prompt); + textareaRef.current?.focus(); + }; + + return ( + <> + {/* Mobile backdrop for AI sidebar */} + + + + {/* Tabs */} + +
+ + + AI Architect + + + Specs + + +
+ + + + + + + + +
+ + + ); +} + +interface ArchitectTabProps { + messages: ChatMessage[]; + input: string; + textareaRef: RefObject; + onInputChange: (value: string) => void; + onKeyDown: (event: KeyboardEvent) => void; + onSend: () => void; + onStarter: (prompt: string) => void; +} + +function ArchitectTab({ + messages, + input, + textareaRef, + onInputChange, + onKeyDown, + onSend, + onStarter, +}: ArchitectTabProps) { + const isEmpty = messages.length === 0; + + return ( + <> + +
+ {isEmpty ? ( + + ) : ( + messages.map((message) => ( +
+ {message.content} +
+ )) + )} +
+
+ +
+
+