diff --git a/app/chat/[owner]/[repo]/page.tsx b/app/chat/[owner]/[repo]/page.tsx
new file mode 100644
index 00000000..55d71a0b
--- /dev/null
+++ b/app/chat/[owner]/[repo]/page.tsx
@@ -0,0 +1,40 @@
+import { cookies } from 'next/headers'
+import { ChatPageContent } from '@/components/chat-page-content'
+import { getServerSession } from '@/lib/session/get-server-session'
+import { getGitHubStars } from '@/lib/github-stars'
+import { getMaxSandboxDuration } from '@/lib/db/settings'
+
+interface ChatPageProps {
+ params: Promise<{
+ owner: string
+ repo: string
+ }>
+}
+
+export default async function ChatPage({ params }: ChatPageProps) {
+ const { owner, repo } = await params
+ const cookieStore = await cookies()
+ const installDependencies = cookieStore.get('install-dependencies')?.value === 'true'
+ const keepAlive = cookieStore.get('keep-alive')?.value === 'true'
+
+ const session = await getServerSession()
+
+ // Get max sandbox duration for this user (user-specific > global > env var)
+ const maxSandboxDuration = await getMaxSandboxDuration(session?.user?.id)
+ const maxDuration = parseInt(cookieStore.get('max-duration')?.value || maxSandboxDuration.toString(), 10)
+
+ const stars = await getGitHubStars()
+
+ return (
+
+ )
+}
diff --git a/components/chat-page-content.tsx b/components/chat-page-content.tsx
new file mode 100644
index 00000000..5df7f368
--- /dev/null
+++ b/components/chat-page-content.tsx
@@ -0,0 +1,353 @@
+'use client'
+
+import { useState, useEffect } from 'react'
+import { TaskForm } from '@/components/task-form'
+import { HomePageHeader } from '@/components/home-page-header'
+import { toast } from 'sonner'
+import { useRouter } from 'next/navigation'
+import { useTasks } from '@/components/app-layout'
+import { setSelectedOwner, setSelectedRepo } from '@/lib/utils/cookies'
+import type { Session } from '@/lib/session/types'
+import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog'
+import { Button } from '@/components/ui/button'
+import { redirectToSignIn } from '@/lib/session/redirect-to-sign-in'
+import { GitHubIcon } from '@/components/icons/github-icon'
+import { getEnabledAuthProviders } from '@/lib/auth/providers'
+import { useSetAtom } from 'jotai'
+import { taskPromptAtom } from '@/lib/atoms/task'
+import { HomePageMobileFooter } from '@/components/home-page-mobile-footer'
+
+interface ChatPageContentProps {
+ owner: string
+ repo: string
+ initialInstallDependencies?: boolean
+ initialMaxDuration?: number
+ initialKeepAlive?: boolean
+ maxSandboxDuration?: number
+ user?: Session['user'] | null
+ initialStars?: number
+}
+
+export function ChatPageContent({
+ owner,
+ repo,
+ initialInstallDependencies = false,
+ initialMaxDuration = 300,
+ initialKeepAlive = false,
+ maxSandboxDuration = 300,
+ user = null,
+ initialStars = 1200,
+}: ChatPageContentProps) {
+ const [isSubmitting, setIsSubmitting] = useState(false)
+ const [selectedOwner, setSelectedOwnerState] = useState(owner)
+ const [selectedRepo, setSelectedRepoState] = useState(repo)
+ const [showSignInDialog, setShowSignInDialog] = useState(false)
+ const [loadingVercel, setLoadingVercel] = useState(false)
+ const [loadingGitHub, setLoadingGitHub] = useState(false)
+ const router = useRouter()
+ const { refreshTasks, addTaskOptimistically } = useTasks()
+ const setTaskPrompt = useSetAtom(taskPromptAtom)
+
+ // Check which auth providers are enabled
+ const { github: hasGitHub, vercel: hasVercel } = getEnabledAuthProviders()
+
+ // Set cookies for the selected owner/repo on mount
+ useEffect(() => {
+ setSelectedOwner(owner)
+ setSelectedRepo(repo)
+ }, [owner, repo])
+
+ // Wrapper functions to update both state and cookies
+ const handleOwnerChange = (newOwner: string) => {
+ setSelectedOwnerState(newOwner)
+ setSelectedOwner(newOwner)
+ // Navigate to new URL if owner changes
+ if (selectedRepo) {
+ router.push(`/chat/${newOwner}/${selectedRepo}`)
+ }
+ }
+
+ const handleRepoChange = (newRepo: string) => {
+ setSelectedRepoState(newRepo)
+ setSelectedRepo(newRepo)
+ // Navigate to new URL if repo changes
+ router.push(`/chat/${selectedOwner}/${newRepo}`)
+ }
+
+ const handleTaskSubmit = async (data: {
+ prompt: string
+ repoUrl: string
+ selectedAgent: string
+ selectedModel: string
+ selectedModels?: string[]
+ installDependencies: boolean
+ maxDuration: number
+ keepAlive: boolean
+ }) => {
+ // Check if user is authenticated
+ if (!user) {
+ setShowSignInDialog(true)
+ return
+ }
+
+ // Check if user has selected a repository
+ if (!data.repoUrl) {
+ toast.error('Please select a repository', {
+ description: 'Choose a GitHub repository to work with from the header.',
+ })
+ return
+ }
+
+ // Clear the saved prompt since we're actually submitting it now
+ setTaskPrompt('')
+
+ setIsSubmitting(true)
+
+ // Check if this is multi-agent mode with multiple models selected
+ const isMultiAgent = data.selectedAgent === 'multi-agent' && data.selectedModels && data.selectedModels.length > 0
+
+ if (isMultiAgent) {
+ // Create multiple tasks, one for each selected model
+ const taskIds: string[] = []
+ const tasksData = data.selectedModels!.map((modelValue) => {
+ // Parse agent:model format
+ const [agent, model] = modelValue.split(':')
+ const { id } = addTaskOptimistically({
+ prompt: data.prompt,
+ repoUrl: data.repoUrl,
+ selectedAgent: agent,
+ selectedModel: model,
+ installDependencies: data.installDependencies,
+ maxDuration: data.maxDuration,
+ })
+ taskIds.push(id)
+ return {
+ id,
+ prompt: data.prompt,
+ repoUrl: data.repoUrl,
+ selectedAgent: agent,
+ selectedModel: model,
+ installDependencies: data.installDependencies,
+ maxDuration: data.maxDuration,
+ keepAlive: data.keepAlive,
+ }
+ })
+
+ // Navigate to the first task
+ router.push(`/tasks/${taskIds[0]}`)
+
+ try {
+ // Create all tasks in parallel
+ const responses = await Promise.all(
+ tasksData.map((taskData) =>
+ fetch('/api/tasks', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(taskData),
+ }),
+ ),
+ )
+
+ const successCount = responses.filter((r) => r.ok).length
+ const failCount = responses.length - successCount
+
+ if (successCount === responses.length) {
+ toast.success(`${successCount} tasks created successfully!`)
+ } else if (successCount > 0) {
+ toast.warning(`${successCount} tasks created, ${failCount} failed`)
+ } else {
+ toast.error('Failed to create tasks')
+ }
+
+ // Refresh sidebar to get the real task data from server
+ await refreshTasks()
+ } catch (error) {
+ console.error('Error creating tasks:', error)
+ toast.error('Failed to create tasks')
+ await refreshTasks()
+ } finally {
+ setIsSubmitting(false)
+ }
+ } else {
+ // Single task creation (original behavior)
+ const { id } = addTaskOptimistically(data)
+
+ // Navigate to the new task page immediately
+ router.push(`/tasks/${id}`)
+
+ try {
+ const response = await fetch('/api/tasks', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ ...data, id }), // Include the pre-generated ID
+ })
+
+ if (response.ok) {
+ toast.success('Task created successfully!')
+ // Refresh sidebar to get the real task data from server
+ await refreshTasks()
+ } else {
+ const error = await response.json()
+ // Show detailed message for rate limits, or generic error message
+ toast.error(error.message || error.error || 'Failed to create task')
+ // TODO: Remove the optimistic task on error
+ await refreshTasks() // For now, just refresh to remove the optimistic task
+ }
+ } catch (error) {
+ console.error('Error creating task:', error)
+ toast.error('Failed to create task')
+ // TODO: Remove the optimistic task on error
+ await refreshTasks() // For now, just refresh to remove the optimistic task
+ } finally {
+ setIsSubmitting(false)
+ }
+ }
+ }
+
+ const handleVercelSignIn = async () => {
+ setLoadingVercel(true)
+ await redirectToSignIn()
+ }
+
+ const handleGitHubSignIn = () => {
+ setLoadingGitHub(true)
+ window.location.href = '/api/auth/signin/github'
+ }
+
+ return (
+
+
+
+
+
+
+
+
+
+ {/* Mobile Footer with Stars and Deploy Button */}
+
+
+ {/* Sign In Dialog */}
+
+
+ )
+}