From c16874072c9d94a9dc896c62450fc16922970865 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 9 Nov 2025 23:27:57 +0000 Subject: [PATCH 1/4] feat: Add dev server logs tab and API endpoint Co-authored-by: andre.timo.landgraf --- src/app/api/v1/dev-servers/logs/route.ts | 91 ++++++++++ src/components/dev-server-logs.tsx | 209 +++++++++++++++++++++++ src/components/project-chat.tsx | 105 ++++++++---- src/lib/freestyle.ts | 61 +++++++ 4 files changed, 433 insertions(+), 33 deletions(-) create mode 100644 src/app/api/v1/dev-servers/logs/route.ts create mode 100644 src/components/dev-server-logs.tsx diff --git a/src/app/api/v1/dev-servers/logs/route.ts b/src/app/api/v1/dev-servers/logs/route.ts new file mode 100644 index 0000000..11e959c --- /dev/null +++ b/src/app/api/v1/dev-servers/logs/route.ts @@ -0,0 +1,91 @@ +import { NextResponse } from "next/server"; +import { stackServerApp } from "@/lib/stack/server"; +import { db } from "@/lib/db/db"; +import { projectsTable } from "@/lib/db/schema"; +import { and, eq } from "drizzle-orm"; +import { freestyleService } from "@/lib/freestyle"; + +export async function POST(request: Request) { + try { + const user = await stackServerApp.getUser(); + + if (!user) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + let body: unknown; + try { + body = await request.json(); + } catch { + return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); + } + + if (!body || typeof body !== "object") { + return NextResponse.json({ error: "Invalid request body" }, { status: 400 }); + } + + const { projectId, lines, gitRef } = body as { + projectId?: unknown; + lines?: unknown; + gitRef?: unknown; + }; + + if (typeof projectId !== "string" || projectId.length === 0) { + return NextResponse.json( + { error: "Missing or invalid projectId" }, + { status: 400 }, + ); + } + + let resolvedLines: number | null = null; + if (typeof lines === "number") { + if (!Number.isInteger(lines) || lines <= 0) { + return NextResponse.json( + { error: "lines must be a positive integer" }, + { status: 400 }, + ); + } + resolvedLines = lines; + } else if (lines !== null && lines !== undefined) { + return NextResponse.json( + { error: "lines must be a positive integer, null, or undefined" }, + { status: 400 }, + ); + } + + const resolvedGitRef = + typeof gitRef === "string" && gitRef.trim().length > 0 ? gitRef : null; + + const [project] = await db + .select({ + id: projectsTable.id, + repoId: projectsTable.repoId, + }) + .from(projectsTable) + .where( + and(eq(projectsTable.id, projectId), eq(projectsTable.userId, user.id)), + ) + .limit(1); + + if (!project) { + return NextResponse.json( + { error: "Project not found" }, + { status: 404 }, + ); + } + + const { logs } = await freestyleService.getDevServerLogs({ + repoId: project.repoId, + gitRef: resolvedGitRef, + lines: resolvedLines, + }); + + return NextResponse.json({ logs }); + } catch (error) { + console.error("[API] Error fetching dev server logs:", error); + return NextResponse.json( + { error: "Failed to fetch dev server logs" }, + { status: 500 }, + ); + } +} diff --git a/src/components/dev-server-logs.tsx b/src/components/dev-server-logs.tsx new file mode 100644 index 0000000..dad8b02 --- /dev/null +++ b/src/components/dev-server-logs.tsx @@ -0,0 +1,209 @@ +"use client"; + +import { useCallback, useEffect, useRef, useState } from "react"; +import { useDevServerData } from "@/components/dev-server-context"; +import { Button } from "@/components/ui/button"; +import { Loader2, RotateCcw } from "lucide-react"; + +interface DevServerLogsProps { + projectId: string; + isActive: boolean; + pollIntervalMs?: number; +} + +const DEFAULT_POLL_INTERVAL_MS = 10_000; + +export function DevServerLogs({ + projectId, + isActive, + pollIntervalMs = DEFAULT_POLL_INTERVAL_MS, +}: DevServerLogsProps) { + const { + devServerUrl, + devCommandRunning, + installCommandRunning, + isLoading: isDevServerStatusesLoading, + } = useDevServerData(); + + const [logs, setLogs] = useState(""); + const [isLoading, setIsLoading] = useState(true); + const [isRefreshing, setIsRefreshing] = useState(false); + const [error, setError] = useState(null); + const hasFetchedRef = useRef(false); + const abortControllerRef = useRef(null); + const logContainerRef = useRef(null); + + const fetchLogs = useCallback(async () => { + if (!projectId) { + return; + } + + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + + const controller = new AbortController(); + abortControllerRef.current = controller; + + if (!hasFetchedRef.current) { + setIsLoading(true); + } else { + setIsRefreshing(true); + } + + try { + setError(null); + + const response = await fetch("/api/v1/dev-servers/logs", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ projectId }), + cache: "no-store", + signal: controller.signal, + }); + + if (!response.ok) { + const message = await response.text(); + throw new Error(message || "Request failed"); + } + + const data: unknown = await response.json(); + if ( + !data || + typeof data !== "object" || + typeof (data as { logs?: unknown }).logs !== "string" + ) { + throw new Error("Unexpected response from server"); + } + + setLogs((data as { logs: string }).logs); + hasFetchedRef.current = true; + } catch (fetchError) { + if (controller.signal.aborted) { + return; + } + + console.error("[DevServerLogs] Failed to fetch logs:", fetchError); + setError("Unable to load dev server logs right now."); + } finally { + if (!controller.signal.aborted) { + setIsLoading(false); + setIsRefreshing(false); + } + } + }, [projectId]); + + useEffect(() => { + if (!isActive) { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + + setIsRefreshing(false); + return; + } + + fetchLogs(); + + const interval = window.setInterval(() => { + fetchLogs(); + }, pollIntervalMs); + + return () => { + window.clearInterval(interval); + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + }; + }, [fetchLogs, isActive, pollIntervalMs]); + + useEffect(() => { + if (!logContainerRef.current) { + return; + } + + logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight; + }, [logs]); + + useEffect(() => { + return () => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + }; + }, []); + + const statusMessage = (() => { + if (isDevServerStatusesLoading) { + return "Checking dev server status…"; + } + + if (!devServerUrl && !devCommandRunning && !installCommandRunning) { + return "Dev server is not running yet. Start it to see logs."; + } + + if (devCommandRunning || installCommandRunning) { + return "Dev server is starting. Logs update automatically."; + } + + return null; + })(); + + return ( +
+
+
+

Latest logs

+ {statusMessage && ( +

{statusMessage}

+ )} +
+ +
+ +
+ {isLoading && !hasFetchedRef.current ? ( +
+ Loading logs… +
+ ) : error ? ( +
+

{error}

+ +
+ ) : ( +
+ {logs.trim().length > 0 ? ( +
+                {logs}
+              
+ ) : ( +
+ No logs available yet. +
+ )} +
+ )} +
+
+ ); +} diff --git a/src/components/project-chat.tsx b/src/components/project-chat.tsx index 64831e0..9e405c2 100644 --- a/src/components/project-chat.tsx +++ b/src/components/project-chat.tsx @@ -14,6 +14,7 @@ import { useProjectData, } from "@/components/project-context"; import { useDevServerData } from "@/components/dev-server-context"; +import { DevServerLogs } from "@/components/dev-server-logs"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { @@ -37,6 +38,12 @@ import { ModelSelectorModal } from "@/components/model-selector-modal"; import { useModelSelection } from "@/lib/model-selection/hooks"; import { useEffect, useState } from "react"; import { FreestyleDevServer } from "freestyle-sandboxes/react/dev-server"; +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/components/ui/tabs"; import { requestDevServer } from "@/actions/preview-actions"; import Link from "next/link"; @@ -64,6 +71,8 @@ const ProjectChatContent = ({ validatePersonalProvider: true, }); const [isModelSelectorOpen, setIsModelSelectorOpen] = useState(false); + const [activeDevServerTab, setActiveDevServerTab] = + useState<"preview" | "logs">("preview"); // Wrap the action to include projectId const wrappedRequestDevServer = async (args: { repoId: string }) => { @@ -261,42 +270,72 @@ const ProjectChatContent = ({ )} -
- {/* Chat side */} -
- {isThreadReady && isVersionReady ? ( - - ) : ( -
- - - - - -
- -
- )} -
- {/* Preview side */} -
- {isVersionReady ? ( - - ) : ( -
-
-
- Initializing project... +
+ {/* Chat side */} +
+ {isThreadReady && isVersionReady ? ( + + ) : ( +
+ + + + + +
+ +
+ )} +
+ {/* Preview side */} +
+ {isVersionReady ? ( + + setActiveDevServerTab(value as "preview" | "logs") + } + className="flex h-full min-h-0 flex-col" + > +
+ + Dev Server + Dev Server Logs + +
+ +
+ +
+
+ + + +
+ ) : ( +
+
+
+ Initializing project... +
+
-
-
- )} + )} +
-
); diff --git a/src/lib/freestyle.ts b/src/lib/freestyle.ts index 44299e5..f9f5fd3 100644 --- a/src/lib/freestyle.ts +++ b/src/lib/freestyle.ts @@ -15,6 +15,16 @@ interface RequestDevServerParams { environmentVariables: Record; } +interface GetDevServerLogsParams { + repoId: string; + gitRef?: string | null; + lines?: number | null; +} + +interface GetDevServerLogsResponse { + logs: string; +} + interface CommitResponse { commits: Array<{ sha: string; @@ -233,6 +243,57 @@ export class FreestyleService { ); } } + + async getDevServerLogs({ + repoId, + gitRef = null, + lines = null, + }: GetDevServerLogsParams): Promise { + console.log("[Freestyle] Fetching dev server logs for repo:", repoId, { + gitRef, + lines, + }); + + try { + const response = await fetch( + `${this.apiBaseUrl}/ephemeral/v1/dev-servers/logs`, + { + method: "POST", + headers: { + Authorization: `Bearer ${this.apiKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + devServer: { + repoId, + gitRef, + }, + lines, + }), + }, + ); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error( + `Failed to fetch dev server logs: ${response.status} ${response.statusText} - ${errorText}`, + ); + } + + const data = (await response.json()) as GetDevServerLogsResponse; + + if (typeof data.logs !== "string") { + throw new Error("Malformed response from Freestyle logs API"); + } + + return data; + } catch (error) { + console.error("[Freestyle] Error fetching dev server logs:", error); + throw new Error( + `Failed to fetch dev server logs: ${error instanceof Error ? error.message : String(error)}`, + ); + } + } } export const freestyleService = new FreestyleService( From e16a8a5b4fe04742ce876fe347ca8278c73abc74 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 9 Nov 2025 23:43:41 +0000 Subject: [PATCH 2/4] feat: Add dev server restart functionality Co-authored-by: andre.timo.landgraf --- src/app/api/v1/dev-servers/restart/route.ts | 73 +++++++++++++++++++++ src/components/project-chat.tsx | 48 +++++++++++++- src/lib/freestyle.ts | 57 ++++++++++++++++ 3 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 src/app/api/v1/dev-servers/restart/route.ts diff --git a/src/app/api/v1/dev-servers/restart/route.ts b/src/app/api/v1/dev-servers/restart/route.ts new file mode 100644 index 0000000..4c340ca --- /dev/null +++ b/src/app/api/v1/dev-servers/restart/route.ts @@ -0,0 +1,73 @@ +import { NextResponse } from "next/server"; +import { stackServerApp } from "@/lib/stack/server"; +import { db } from "@/lib/db/db"; +import { projectsTable } from "@/lib/db/schema"; +import { and, eq } from "drizzle-orm"; +import { freestyleService } from "@/lib/freestyle"; + +export async function POST(request: Request) { + try { + const user = await stackServerApp.getUser(); + + if (!user) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + let body: unknown; + try { + body = await request.json(); + } catch { + return NextResponse.json({ error: "Invalid JSON body" }, { status: 400 }); + } + + if (!body || typeof body !== "object") { + return NextResponse.json({ error: "Invalid request body" }, { status: 400 }); + } + + const { projectId, gitRef } = body as { + projectId?: unknown; + gitRef?: unknown; + }; + + if (typeof projectId !== "string" || projectId.length === 0) { + return NextResponse.json( + { error: "Missing or invalid projectId" }, + { status: 400 }, + ); + } + + const resolvedGitRef = + typeof gitRef === "string" && gitRef.trim().length > 0 ? gitRef : null; + + const [project] = await db + .select({ + id: projectsTable.id, + repoId: projectsTable.repoId, + }) + .from(projectsTable) + .where( + and(eq(projectsTable.id, projectId), eq(projectsTable.userId, user.id)), + ) + .limit(1); + + if (!project) { + return NextResponse.json( + { error: "Project not found" }, + { status: 404 }, + ); + } + + const { restarted } = await freestyleService.restartDevServer({ + repoId: project.repoId, + gitRef: resolvedGitRef, + }); + + return NextResponse.json({ restarted }); + } catch (error) { + console.error("[API] Error restarting dev server:", error); + return NextResponse.json( + { error: "Failed to restart dev server" }, + { status: 500 }, + ); + } +} diff --git a/src/components/project-chat.tsx b/src/components/project-chat.tsx index 9e405c2..ae3a744 100644 --- a/src/components/project-chat.tsx +++ b/src/components/project-chat.tsx @@ -33,6 +33,7 @@ import { ArrowLeft, MoreVertical, ChevronDown, + RotateCcw, } from "lucide-react"; import { ModelSelectorModal } from "@/components/model-selector-modal"; import { useModelSelection } from "@/lib/model-selection/hooks"; @@ -63,7 +64,12 @@ const ProjectChatContent = ({ accessToken, }: ProjectChatProps) => { const { currentVersionId } = useProjectData(); - const { devServerUrl, codeServerUrl, deploymentUrl } = useDevServerData(); + const { + devServerUrl, + codeServerUrl, + deploymentUrl, + refreshUrls, + } = useDevServerData(); const [isDeploying, setIsDeploying] = useState(false); const [runtimeError, setRuntimeError] = useState(null); const { modelSelection, updateModelSelection } = useModelSelection({ @@ -71,6 +77,7 @@ const ProjectChatContent = ({ validatePersonalProvider: true, }); const [isModelSelectorOpen, setIsModelSelectorOpen] = useState(false); + const [isRestartingDevServer, setIsRestartingDevServer] = useState(false); const [activeDevServerTab, setActiveDevServerTab] = useState<"preview" | "logs">("preview"); @@ -79,6 +86,34 @@ const ProjectChatContent = ({ return await requestDevServer({ projectId }); }; + const handleRestartDevServer = async () => { + if (isRestartingDevServer) { + return; + } + + setIsRestartingDevServer(true); + try { + const response = await fetch("/api/v1/dev-servers/restart", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ projectId }), + }); + + if (!response.ok) { + const message = await response.text(); + throw new Error(message || "Failed to restart dev server"); + } + + await refreshUrls(); + } catch (error) { + console.error("[ProjectChat] Failed to restart dev server:", error); + } finally { + setIsRestartingDevServer(false); + } + }; + const handleDeploy = async () => { setIsDeploying(true); try { @@ -208,6 +243,17 @@ const ProjectChatContent = ({ View in VS Code )} + + + + {isRestartingDevServer + ? "Restarting Dev Server..." + : "Restart Dev Server"} + + Production diff --git a/src/lib/freestyle.ts b/src/lib/freestyle.ts index f9f5fd3..00d464f 100644 --- a/src/lib/freestyle.ts +++ b/src/lib/freestyle.ts @@ -25,6 +25,15 @@ interface GetDevServerLogsResponse { logs: string; } +interface RestartDevServerParams { + repoId: string; + gitRef?: string | null; +} + +interface RestartDevServerResponse { + restarted: boolean; +} + interface CommitResponse { commits: Array<{ sha: string; @@ -294,6 +303,54 @@ export class FreestyleService { ); } } + + async restartDevServer({ + repoId, + gitRef = null, + }: RestartDevServerParams): Promise { + console.log("[Freestyle] Restarting dev server for repo:", repoId, { + gitRef, + }); + + try { + const response = await fetch( + `${this.apiBaseUrl}/ephemeral/v1/dev-servers/restart`, + { + method: "POST", + headers: { + Authorization: `Bearer ${this.apiKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + devServer: { + repoId, + gitRef, + }, + }), + }, + ); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error( + `Failed to restart dev server: ${response.status} ${response.statusText} - ${errorText}`, + ); + } + + const data = (await response.json()) as RestartDevServerResponse; + + if (typeof data.restarted !== "boolean") { + throw new Error("Malformed response from Freestyle restart API"); + } + + return data; + } catch (error) { + console.error("[Freestyle] Error restarting dev server:", error); + throw new Error( + `Failed to restart dev server: ${error instanceof Error ? error.message : String(error)}`, + ); + } + } } export const freestyleService = new FreestyleService( From 8ee67c3334caa2f243c9be8e67fe878d511396c3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 10 Nov 2025 00:14:47 +0000 Subject: [PATCH 3/4] Refactor: Improve dev server logs UI and functionality This commit introduces several improvements to the dev server logs component: - **UI Enhancements:** The component now uses Shadcn UI components like `Card`, `ScrollArea`, `Alert`, and `Empty` for a more polished and organized look. - **Error Handling:** Improved error display with a dedicated `Alert` component. - **Loading States:** Clearer loading and refreshing indicators using `Spinner`. - **Empty State:** A new `Empty` component is displayed when no logs are available. - **Scrolling Behavior:** The auto-scrolling logic has been refined to correctly scroll the parent container. - **Code Structure:** The code has been refactored for better readability and maintainability. Co-authored-by: andre.timo.landgraf --- src/components/dev-server-logs.tsx | 130 ++++++++++++++++++----------- 1 file changed, 81 insertions(+), 49 deletions(-) diff --git a/src/components/dev-server-logs.tsx b/src/components/dev-server-logs.tsx index dad8b02..a5b7d86 100644 --- a/src/components/dev-server-logs.tsx +++ b/src/components/dev-server-logs.tsx @@ -3,7 +3,24 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { useDevServerData } from "@/components/dev-server-context"; import { Button } from "@/components/ui/button"; -import { Loader2, RotateCcw } from "lucide-react"; +import { + Card, + CardAction, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { + Empty, + EmptyDescription, + EmptyHeader, + EmptyTitle, +} from "@/components/ui/empty"; +import { Spinner } from "@/components/ui/spinner"; +import { RotateCcw } from "lucide-react"; interface DevServerLogsProps { projectId: string; @@ -120,11 +137,15 @@ export function DevServerLogs({ }, [fetchLogs, isActive, pollIntervalMs]); useEffect(() => { - if (!logContainerRef.current) { + const logElement = logContainerRef.current; + if (!logElement) { return; } - logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight; + const scrollParent = logElement.parentElement; + if (scrollParent) { + scrollParent.scrollTop = scrollParent.scrollHeight; + } }, [logs]); useEffect(() => { @@ -151,59 +172,70 @@ export function DevServerLogs({ return null; })(); + const isFetching = isLoading || isRefreshing; + return ( -
-
-
-

Latest logs

- {statusMessage && ( -

{statusMessage}

- )} + + +
+ Latest logs + {statusMessage && {statusMessage}}
- -
- -
+ + + + + {isLoading && !hasFetchedRef.current ? ( -
- Loading logs… +
+ + Loading logs…
) : error ? ( -
-

{error}

- +
+ + Failed to load dev server logs + +

{error}

+ +
+
+ ) : logs.trim().length === 0 ? ( + + + No logs yet + + We’ll show output from the dev server as soon as it’s available. + + + ) : ( -
- {logs.trim().length > 0 ? ( -
-                {logs}
-              
- ) : ( -
- No logs available yet. -
- )} -
+ +
+ {logs} +
+
)} -
-
+
+ ); } From 22819aa3a7f25b10caec5759b63f0b6a3f3ae321 Mon Sep 17 00:00:00 2001 From: Andre Landgraf Date: Sun, 9 Nov 2025 19:50:32 -0800 Subject: [PATCH 4/4] fmt --- src/app/api/v1/dev-servers/logs/route.ts | 10 +- src/app/api/v1/dev-servers/restart/route.ts | 10 +- src/components/project-chat.tsx | 140 +++++++++----------- 3 files changed, 76 insertions(+), 84 deletions(-) diff --git a/src/app/api/v1/dev-servers/logs/route.ts b/src/app/api/v1/dev-servers/logs/route.ts index 11e959c..7a85747 100644 --- a/src/app/api/v1/dev-servers/logs/route.ts +++ b/src/app/api/v1/dev-servers/logs/route.ts @@ -21,7 +21,10 @@ export async function POST(request: Request) { } if (!body || typeof body !== "object") { - return NextResponse.json({ error: "Invalid request body" }, { status: 400 }); + return NextResponse.json( + { error: "Invalid request body" }, + { status: 400 }, + ); } const { projectId, lines, gitRef } = body as { @@ -68,10 +71,7 @@ export async function POST(request: Request) { .limit(1); if (!project) { - return NextResponse.json( - { error: "Project not found" }, - { status: 404 }, - ); + return NextResponse.json({ error: "Project not found" }, { status: 404 }); } const { logs } = await freestyleService.getDevServerLogs({ diff --git a/src/app/api/v1/dev-servers/restart/route.ts b/src/app/api/v1/dev-servers/restart/route.ts index 4c340ca..8739aab 100644 --- a/src/app/api/v1/dev-servers/restart/route.ts +++ b/src/app/api/v1/dev-servers/restart/route.ts @@ -21,7 +21,10 @@ export async function POST(request: Request) { } if (!body || typeof body !== "object") { - return NextResponse.json({ error: "Invalid request body" }, { status: 400 }); + return NextResponse.json( + { error: "Invalid request body" }, + { status: 400 }, + ); } const { projectId, gitRef } = body as { @@ -51,10 +54,7 @@ export async function POST(request: Request) { .limit(1); if (!project) { - return NextResponse.json( - { error: "Project not found" }, - { status: 404 }, - ); + return NextResponse.json({ error: "Project not found" }, { status: 404 }); } const { restarted } = await freestyleService.restartDevServer({ diff --git a/src/components/project-chat.tsx b/src/components/project-chat.tsx index ae3a744..e7af77e 100644 --- a/src/components/project-chat.tsx +++ b/src/components/project-chat.tsx @@ -39,12 +39,7 @@ import { ModelSelectorModal } from "@/components/model-selector-modal"; import { useModelSelection } from "@/lib/model-selection/hooks"; import { useEffect, useState } from "react"; import { FreestyleDevServer } from "freestyle-sandboxes/react/dev-server"; -import { - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "@/components/ui/tabs"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { requestDevServer } from "@/actions/preview-actions"; import Link from "next/link"; @@ -64,12 +59,8 @@ const ProjectChatContent = ({ accessToken, }: ProjectChatProps) => { const { currentVersionId } = useProjectData(); - const { - devServerUrl, - codeServerUrl, - deploymentUrl, - refreshUrls, - } = useDevServerData(); + const { devServerUrl, codeServerUrl, deploymentUrl, refreshUrls } = + useDevServerData(); const [isDeploying, setIsDeploying] = useState(false); const [runtimeError, setRuntimeError] = useState(null); const { modelSelection, updateModelSelection } = useModelSelection({ @@ -78,8 +69,9 @@ const ProjectChatContent = ({ }); const [isModelSelectorOpen, setIsModelSelectorOpen] = useState(false); const [isRestartingDevServer, setIsRestartingDevServer] = useState(false); - const [activeDevServerTab, setActiveDevServerTab] = - useState<"preview" | "logs">("preview"); + const [activeDevServerTab, setActiveDevServerTab] = useState< + "preview" | "logs" + >("preview"); // Wrap the action to include projectId const wrappedRequestDevServer = async (args: { repoId: string }) => { @@ -316,72 +308,72 @@ const ProjectChatContent = ({
)} -
- {/* Chat side */} -
- {isThreadReady && isVersionReady ? ( - - ) : ( -
- - - - - -
- +
+ {/* Chat side */} +
+ {isThreadReady && isVersionReady ? ( + + ) : ( +
+ + + + + +
+ +
+ )} +
+ {/* Preview side */} +
+ {isVersionReady ? ( + + setActiveDevServerTab(value as "preview" | "logs") + } + className="flex h-full min-h-0 flex-col" + > +
+ + Dev Server + Dev Server Logs +
- )} -
- {/* Preview side */} -
- {isVersionReady ? ( - - setActiveDevServerTab(value as "preview" | "logs") - } - className="flex h-full min-h-0 flex-col" + -
- - Dev Server - Dev Server Logs - -
- -
- -
-
- - + - -
- ) : ( -
-
-
- Initializing project... -
-
+ + + + + + ) : ( +
+
+
+ Initializing project... +
+
- )} -
+
+ )}
+
);