diff --git a/components/terminal/terminal-container.tsx b/components/terminal/terminal-container.tsx index f8fd1eb..55e1ade 100644 --- a/components/terminal/terminal-container.tsx +++ b/components/terminal/terminal-container.tsx @@ -122,7 +122,6 @@ export function TerminalContainer({ project, sandbox, isVisible = true }: Termin
{/* Toolbar with tabs and operations */} void; onConfirm: () => void; - sandboxUrl: string | null | undefined; } export function AppRunnerDialog({ open, onOpenChange, onConfirm, - sandboxUrl, }: AppRunnerDialogProps) { return ( - Run Application & Keep Active? + Prepare Deployment Files?
- This will build and start your application by running: + This will invoke the deployment skill in the selected directory by running:
- pnpm build && pnpm start + claude -p "/fulling-deploy"
@@ -41,35 +39,18 @@ export function AppRunnerDialog({
- App runs continuously in the background + Generate or reuse a Dockerfile for the current project
- Remains active even if you leave this page + Create a GitHub Actions workflow for image build and push
- - Can be stopped anytime by clicking this button again - + Let the skill commit and push the generated files to GitHub
- {sandboxUrl && ( -
-
- Once running, your application will be available at: -
- - {sandboxUrl} - -
- )}
@@ -82,7 +63,7 @@ export function AppRunnerDialog({ onClick={onConfirm} className="bg-[#007fd4] hover:bg-[#0060a0] text-white" > - Confirm & Run + Confirm & Run Skill diff --git a/components/terminal/toolbar/app-runner.tsx b/components/terminal/toolbar/app-runner.tsx index 5cd929f..69d332e 100644 --- a/components/terminal/toolbar/app-runner.tsx +++ b/components/terminal/toolbar/app-runner.tsx @@ -1,7 +1,7 @@ 'use client'; import { useState } from 'react'; -import { MdPlayArrow, MdRefresh, MdStop } from 'react-icons/md'; +import { MdRefresh, MdRocketLaunch } from 'react-icons/md'; import type { Prisma } from '@prisma/client'; import { useAppRunner } from '@/hooks/use-app-runner'; @@ -17,28 +17,20 @@ interface AppRunnerProps { } export function AppRunner({ sandbox }: AppRunnerProps) { - const [showStartConfirm, setShowStartConfirm] = useState(false); + const [showRunConfirm, setShowRunConfirm] = useState(false); const [deployDirectory, setDeployDirectory] = useState('./'); const { - isStartingApp, - isStoppingApp, - isAppRunning, - startApp, - stopApp, + isRunningSkill, + runDeploySkill, } = useAppRunner(sandbox?.id, deployDirectory); - // Toggle app start/stop - const handleToggleApp = () => { - if (isAppRunning) { - stopApp(); - } else { - setShowStartConfirm(true); // Open confirmation modal - } + const handleRunSkill = () => { + setShowRunConfirm(true); }; - const handleConfirmStart = () => { - setShowStartConfirm(false); - startApp(); + const handleConfirmRun = () => { + setShowRunConfirm(false); + runDeploySkill(); }; return ( @@ -55,30 +47,20 @@ export function AppRunner({ sandbox }: AppRunnerProps) { {/* Run App Button */} @@ -87,10 +69,9 @@ export function AppRunner({ sandbox }: AppRunnerProps) { {/* Confirmation Alert Dialog */} ); diff --git a/components/terminal/toolbar/toolbar.tsx b/components/terminal/toolbar/toolbar.tsx index 048bbe2..daaac37 100644 --- a/components/terminal/toolbar/toolbar.tsx +++ b/components/terminal/toolbar/toolbar.tsx @@ -14,18 +14,9 @@ import { AppRunner } from './app-runner'; import { NetworkDialog } from './network-dialog'; import { type Tab,TerminalTabs } from './terminal-tabs'; -type Project = Prisma.ProjectGetPayload<{ - include: { - sandboxes: true; - databases: true; - }; -}>; - type Sandbox = Prisma.SandboxGetPayload; export interface TerminalToolbarProps { - /** Project data */ - project: Project; /** Sandbox data */ sandbox: Sandbox | undefined; /** Terminal tabs */ diff --git a/hooks/use-app-runner.ts b/hooks/use-app-runner.ts index 8d8ac47..e76669a 100644 --- a/hooks/use-app-runner.ts +++ b/hooks/use-app-runner.ts @@ -1,12 +1,19 @@ -import { useCallback, useEffect, useMemo,useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; import { toast } from 'sonner'; const BASE_DIR = '/home/fulling/next'; -export function useAppRunner(sandboxId: string | undefined, deployDir: string = './') { - const [isStartingApp, setIsStartingApp] = useState(false); - const [isStoppingApp, setIsStoppingApp] = useState(false); - const [isAppRunning, setIsAppRunning] = useState(false); +function buildSkillPrompt(repoUrl?: string) { + void repoUrl; + return '/fulling-deploy'; +} + +export function useAppRunner( + sandboxId: string | undefined, + deployDir: string = './', + repoUrl?: string +) { + const [isRunningSkill, setIsRunningSkill] = useState(false); // Calculate workdir based on deployDir const workdir = useMemo(() => { @@ -18,120 +25,43 @@ export function useAppRunner(sandboxId: string | undefined, deployDir: string = return `${BASE_DIR}/${relativePath}`; }, [deployDir]); - // Check app status on mount - useEffect(() => { - if (!sandboxId) return; - - const checkStatus = async () => { - try { - const response = await fetch(`/api/sandbox/${sandboxId}/app-status`); - const data = await response.json(); - setIsAppRunning(data.running); - } catch (error) { - console.error('Failed to check app status:', error); - } - }; - - checkStatus(); - }, [sandboxId]); - - // Start application in background - const startApp = useCallback(async () => { - if (!sandboxId || isStartingApp) return; - - setIsStartingApp(true); - - // Send exec command (fire and forget, don't wait for response) - fetch(`/api/sandbox/${sandboxId}/exec`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - command: 'pnpm run build && pnpm run start', - workdir, - }), - }).catch(() => { - // Ignore errors, we'll detect success via port polling - }); - - toast.info('Starting...', { - description: 'Building and starting your app. This may take a few minutes.', - }); - - // Poll for app status every 10 seconds, max 5 minutes - const maxAttempts = 30; // 30 * 10s = 5 minutes - let attempts = 0; + const runDeploySkill = useCallback(async () => { + if (!sandboxId || isRunningSkill) return; - const pollStatus = async (): Promise => { - try { - const response = await fetch(`/api/sandbox/${sandboxId}/app-status`); - const data = await response.json(); - return data.running; - } catch { - return false; - } - }; - - const poll = async () => { - while (attempts < maxAttempts) { - attempts++; - const running = await pollStatus(); - if (running) { - setIsAppRunning(true); - setIsStartingApp(false); - toast.success('App Running', { - description: 'Your app is live in the background', - }); - return; - } - // Wait 10 seconds before next check - await new Promise((resolve) => setTimeout(resolve, 10000)); - } - - // Timeout after max attempts - setIsStartingApp(false); - toast.error('Start Timeout', { - description: 'App did not start within 5 minutes. Check terminal for errors.', - }); - }; - - poll(); - }, [sandboxId, isStartingApp, workdir]); - - // Stop application - const stopApp = useCallback(async () => { - if (!sandboxId || isStoppingApp) return; - - setIsStoppingApp(true); + setIsRunningSkill(true); try { - const response = await fetch(`/api/sandbox/${sandboxId}/app-status`, { - method: 'DELETE', + const prompt = buildSkillPrompt(repoUrl); + const response = await fetch(`/api/sandbox/${sandboxId}/exec`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + command: `claude -p ${JSON.stringify(prompt)}`, + workdir, + }), }); - const result = await response.json(); - if (result.success) { - setIsAppRunning(false); - toast.success('App Stopped'); + if (response.ok && result.success) { + toast.success('Deploy Prep Started', { + description: 'The /fulling-deploy skill is running in the sandbox background.', + }); } else { - toast.error('Stop Failed', { + toast.error('Failed to Start Deploy Prep', { description: result.error || 'Unknown error', }); } } catch (error) { - console.error('Failed to stop app:', error); - toast.error('Stop Failed', { + console.error('Failed to start deploy prep skill:', error); + toast.error('Failed to Start Deploy Prep', { description: 'Network error, please try again', }); } finally { - setIsStoppingApp(false); + setIsRunningSkill(false); } - }, [sandboxId, isStoppingApp]); + }, [sandboxId, isRunningSkill, repoUrl, workdir]); return { - isStartingApp, - isStoppingApp, - isAppRunning, - startApp, - stopApp, + isRunningSkill, + runDeploySkill, }; }