feat(preview): add 1-click start dev server button to empty state
This commit is contained in:
@@ -174,6 +174,8 @@ export default function PreviewTab() {
|
||||
bridge.registerPreviewIframe(iframeDomRef.current, iframeSrc);
|
||||
}, [bridge, iframeSrc]);
|
||||
|
||||
const [isForceStarting, setIsForceStarting] = useState(false);
|
||||
|
||||
// Determine which empty state to show.
|
||||
const emptyContent = (() => {
|
||||
if (loading && !anatomy) return <InitialLoader />;
|
||||
@@ -190,7 +192,11 @@ export default function PreviewTab() {
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (ensureStatus === "calling" || ensureStatus === "starting") {
|
||||
if (
|
||||
ensureStatus === "calling" ||
|
||||
ensureStatus === "starting" ||
|
||||
isForceStarting
|
||||
) {
|
||||
return (
|
||||
<WarmingUpState
|
||||
startedAt={undefined}
|
||||
@@ -200,7 +206,19 @@ export default function PreviewTab() {
|
||||
);
|
||||
}
|
||||
// Never had a dev server — needs the AI to start one.
|
||||
return <NotRunningState />;
|
||||
return (
|
||||
<NotRunningState
|
||||
onStart={() => {
|
||||
setIsForceStarting(true);
|
||||
fetch(
|
||||
`/api/projects/${projectId}/dev-server/ensure?forceStart=true`,
|
||||
{
|
||||
method: "POST",
|
||||
},
|
||||
).catch(() => setIsForceStarting(false));
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})();
|
||||
|
||||
return (
|
||||
@@ -421,7 +439,7 @@ function FailedState({
|
||||
);
|
||||
}
|
||||
|
||||
function NotRunningState() {
|
||||
function NotRunningState({ onStart }: { onStart: () => void }) {
|
||||
return (
|
||||
<div style={{ textAlign: "center", maxWidth: 260 }}>
|
||||
<div
|
||||
@@ -443,9 +461,25 @@ function NotRunningState() {
|
||||
</div>
|
||||
<p style={emptyTitle}>Preview not running</p>
|
||||
<p style={emptySubtext}>No dev server found on port 3000.</p>
|
||||
<p style={{ ...emptySubtext, marginTop: 4 }}>
|
||||
Ask the AI to start the dev server.
|
||||
</p>
|
||||
<button
|
||||
onClick={onStart}
|
||||
style={{
|
||||
marginTop: 16,
|
||||
background: "#18181b",
|
||||
color: "white",
|
||||
border: "none",
|
||||
padding: "8px 16px",
|
||||
borderRadius: 8,
|
||||
fontSize: "0.8rem",
|
||||
fontWeight: 500,
|
||||
cursor: "pointer",
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: 6,
|
||||
}}
|
||||
>
|
||||
⚡ Start Dev Server
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
} from "@/lib/dev-container";
|
||||
|
||||
export async function POST(
|
||||
_req: Request,
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ projectId: string }> },
|
||||
) {
|
||||
const { projectId } = await params;
|
||||
@@ -96,7 +96,10 @@ export async function POST(
|
||||
[projectId],
|
||||
);
|
||||
|
||||
if (!last) {
|
||||
const forceStart =
|
||||
new URL(request.url).searchParams.get("forceStart") === "true";
|
||||
|
||||
if (!last && !forceStart) {
|
||||
return NextResponse.json({ status: "no_history" });
|
||||
}
|
||||
|
||||
@@ -111,13 +114,12 @@ export async function POST(
|
||||
}
|
||||
|
||||
// 4. Fire restart in background — don't block the response.
|
||||
// The probe (up to 300s) runs in background; anatomy polling at 5s
|
||||
// will surface state='starting' immediately, then 'running' when ready.
|
||||
// If forceStart is true but we have no history, default to Next.js start command.
|
||||
const restartOpts = {
|
||||
projectId: project.id,
|
||||
projectSlug: project.slug,
|
||||
command: last.command,
|
||||
port: last.port,
|
||||
command: last?.command || "next dev -H 0.0.0.0 --no-turbopack",
|
||||
port: last?.port || 3000,
|
||||
workspace,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user