diff --git a/apps/rowboatx/app/page.tsx b/apps/rowboatx/app/page.tsx index e69de29b..44a784c1 100644 --- a/apps/rowboatx/app/page.tsx +++ b/apps/rowboatx/app/page.tsx @@ -0,0 +1,145 @@ +"use client"; + +import { AppSidebar } from "@/components/app-sidebar"; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbPage, + BreadcrumbSeparator, +} from "@/components/ui/breadcrumb"; +import { Separator } from "@/components/ui/separator"; +import { + SidebarInset, + SidebarProvider, + SidebarTrigger, +} from "@/components/ui/sidebar"; +import { + PromptInput, + PromptInputBody, + PromptInputTextarea, + PromptInputFooter, + PromptInputTools, + PromptInputButton, + PromptInputSubmit, + PromptInputAttachments, + PromptInputAttachment, + PromptInputActionMenu, + PromptInputActionMenuTrigger, + PromptInputActionMenuContent, + PromptInputActionAddAttachments, + PromptInputHeader, + type PromptInputMessage, +} from "@/components/ai-elements/prompt-input"; +import { useState } from "react"; +import { GlobeIcon, MicIcon } from "lucide-react"; + +export default function HomePage() { + const [text, setText] = useState(""); + const [useWebSearch, setUseWebSearch] = useState(false); + const [useMicrophone, setUseMicrophone] = useState(false); + const [status, setStatus] = useState< + "submitted" | "streaming" | "ready" | "error" + >("ready"); + + const handleSubmit = (message: PromptInputMessage) => { + const hasText = Boolean(message.text); + const hasAttachments = Boolean(message.files?.length); + + if (!(hasText || hasAttachments)) { + return; + } + + setStatus("submitted"); + console.log("Message submitted:", message); + + // Reset after submission + setText(""); + setTimeout(() => setStatus("ready"), 500); + }; + + return ( + + + +
+
+ + + + + + RowboatX + + + + Chat + + + +
+
+ +
+ {/* Blank canvas - main content area */} +
+ {/* Empty space for messages */} +
+ + {/* Input area - centered and narrower */} +
+
+ + + + {(attachment) => } + + + + setText(event.target.value)} + value={text} + placeholder="Ask me anything..." + /> + + + + + + + + + + setUseMicrophone(!useMicrophone)} + variant={useMicrophone ? "default" : "ghost"} + > + + Microphone + + setUseWebSearch(!useWebSearch)} + variant={useWebSearch ? "default" : "ghost"} + > + + Search + + + + + +
+
+
+
+
+ ); +} + diff --git a/apps/rowboatx/components/app-sidebar.tsx b/apps/rowboatx/components/app-sidebar.tsx new file mode 100644 index 00000000..a6b93a55 --- /dev/null +++ b/apps/rowboatx/components/app-sidebar.tsx @@ -0,0 +1,196 @@ +"use client" + +import * as React from "react" +import { + AudioWaveform, + Bot, + Calendar, + Command, + GalleryVerticalEnd, + Play, + Plug, + Users, + Zap, +} from "lucide-react" + +import { NavMain } from "@/components/nav-main" +import { NavProjects } from "@/components/nav-projects" +import { NavUser } from "@/components/nav-user" +import { TeamSwitcher } from "@/components/team-switcher" +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarHeader, + SidebarRail, +} from "@/components/ui/sidebar" + +// This is sample data. +const data = { + user: { + name: "shadcn", + email: "m@example.com", + avatar: "/avatars/shadcn.jpg", + }, + teams: [ + { + name: "Acme Inc", + logo: GalleryVerticalEnd, + plan: "Enterprise", + }, + { + name: "Acme Corp.", + logo: AudioWaveform, + plan: "Startup", + }, + { + name: "Evil Corp.", + logo: Command, + plan: "Free", + }, + ], + navMain: [ + { + title: "Agents", + url: "#", + icon: Users, + isActive: true, + items: [ + { + title: "View All Agents", + url: "#", + }, + { + title: "Create Agent", + url: "#", + }, + { + title: "Agent Templates", + url: "#", + }, + ], + }, + { + title: "MCP", + url: "#", + icon: Plug, + items: [ + { + title: "Servers", + url: "#", + }, + { + title: "Tools", + url: "#", + }, + { + title: "Configuration", + url: "#", + }, + ], + }, + { + title: "Runs", + url: "#", + icon: Play, + items: [ + { + title: "Active Runs", + url: "#", + }, + { + title: "History", + url: "#", + }, + { + title: "Failed Runs", + url: "#", + }, + ], + }, + { + title: "Scheduled", + url: "#", + icon: Calendar, + items: [ + { + title: "View Schedule", + url: "#", + }, + { + title: "Create Schedule", + url: "#", + }, + { + title: "Recurring Tasks", + url: "#", + }, + ], + }, + { + title: "Applets", + url: "#", + icon: Zap, + items: [ + { + title: "Browse Applets", + url: "#", + }, + { + title: "Create Applet", + url: "#", + }, + { + title: "My Applets", + url: "#", + }, + ], + }, + ], + chatHistory: [ + { + name: "Building a React Dashboard", + url: "#", + }, + { + name: "API Integration Best Practices", + url: "#", + }, + { + name: "TypeScript Migration Guide", + url: "#", + }, + { + name: "Database Optimization Tips", + url: "#", + }, + { + name: "Docker Container Setup", + url: "#", + }, + { + name: "GraphQL vs REST API", + url: "#", + }, + ], +} + +export function AppSidebar({ ...props }: React.ComponentProps) { + return ( + + + + + + + + + + + + + + ) +} + + diff --git a/apps/rowboatx/components/nav-main.tsx b/apps/rowboatx/components/nav-main.tsx new file mode 100644 index 00000000..1d71af13 --- /dev/null +++ b/apps/rowboatx/components/nav-main.tsx @@ -0,0 +1,73 @@ +"use client" + +import { ChevronRight, type LucideIcon } from "lucide-react" + +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from "@/components/ui/collapsible" +import { + SidebarGroup, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + SidebarMenuSub, + SidebarMenuSubButton, + SidebarMenuSubItem, +} from "@/components/ui/sidebar" + +export function NavMain({ + items, +}: { + items: { + title: string + url: string + icon?: LucideIcon + isActive?: boolean + items?: { + title: string + url: string + }[] + }[] +}) { + return ( + + Platform + + {items.map((item) => ( + + + + + {item.icon && } + {item.title} + + + + + + {item.items?.map((subItem) => ( + + + + {subItem.title} + + + + ))} + + + + + ))} + + + ) +} diff --git a/apps/rowboatx/components/nav-projects.tsx b/apps/rowboatx/components/nav-projects.tsx new file mode 100644 index 00000000..0f0c3f26 --- /dev/null +++ b/apps/rowboatx/components/nav-projects.tsx @@ -0,0 +1,45 @@ +"use client" + +import { + MoreHorizontal, +} from "lucide-react" + +import { + SidebarGroup, + SidebarGroupLabel, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from "@/components/ui/sidebar" + +export function NavProjects({ + projects, +}: { + projects: { + name: string + url: string + }[] +}) { + return ( + + Chat History + + {projects.map((item) => ( + + + + {item.name} + + + + ))} + + + + More + + + + + ) +} diff --git a/apps/rowboatx/components/nav-user.tsx b/apps/rowboatx/components/nav-user.tsx new file mode 100644 index 00000000..3d6d9f86 --- /dev/null +++ b/apps/rowboatx/components/nav-user.tsx @@ -0,0 +1,114 @@ +"use client" + +import { + BadgeCheck, + Bell, + ChevronsUpDown, + CreditCard, + LogOut, + Sparkles, +} from "lucide-react" + +import { + Avatar, + AvatarFallback, + AvatarImage, +} from "@/components/ui/avatar" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuGroup, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from "@/components/ui/sidebar" + +export function NavUser({ + user, +}: { + user: { + name: string + email: string + avatar: string + } +}) { + const { isMobile } = useSidebar() + + return ( + + + + + + + + CN + +
+ {user.name} + {user.email} +
+ +
+
+ + +
+ + + CN + +
+ {user.name} + {user.email} +
+
+
+ + + + + Upgrade to Pro + + + + + + + Account + + + + Billing + + + + Notifications + + + + + + Log out + +
+
+
+
+ ) +} diff --git a/apps/rowboatx/components/team-switcher.tsx b/apps/rowboatx/components/team-switcher.tsx new file mode 100644 index 00000000..083e9ecb --- /dev/null +++ b/apps/rowboatx/components/team-switcher.tsx @@ -0,0 +1,91 @@ +"use client" + +import * as React from "react" +import { ChevronsUpDown, Plus } from "lucide-react" + +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + useSidebar, +} from "@/components/ui/sidebar" + +export function TeamSwitcher({ + teams, +}: { + teams: { + name: string + logo: React.ElementType + plan: string + }[] +}) { + const { isMobile } = useSidebar() + const [activeTeam, setActiveTeam] = React.useState(teams[0]) + + if (!activeTeam) { + return null + } + + return ( + + + + + +
+ +
+
+ {activeTeam.name} + {activeTeam.plan} +
+ +
+
+ + + Teams + + {teams.map((team, index) => ( + setActiveTeam(team)} + className="gap-2 p-2" + > +
+ +
+ {team.name} + ⌘{index + 1} +
+ ))} + + +
+ +
+
Add team
+
+
+
+
+
+ ) +} diff --git a/apps/rowboatx/components/ui/avatar.tsx b/apps/rowboatx/components/ui/avatar.tsx new file mode 100644 index 00000000..71e428b4 --- /dev/null +++ b/apps/rowboatx/components/ui/avatar.tsx @@ -0,0 +1,53 @@ +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +function Avatar({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarImage({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AvatarFallback({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Avatar, AvatarImage, AvatarFallback } diff --git a/apps/rowboatx/components/ui/breadcrumb.tsx b/apps/rowboatx/components/ui/breadcrumb.tsx new file mode 100644 index 00000000..eb88f321 --- /dev/null +++ b/apps/rowboatx/components/ui/breadcrumb.tsx @@ -0,0 +1,109 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { + return