Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions client/src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { usePipelineStore } from "../store/usePipelineStore";

// In dev: talk to Vite dev server proxy at /api
// In prod: use the real backend URL from VITE_API_BASE (e.g. https://api.autodeploy.app)
const DEFAULT_API_BASE =
import.meta.env.MODE === "development" ? "/api" : "";

export const BASE =
import.meta.env.VITE_API_BASE || DEFAULT_API_BASE;
import { usePipelineStore } from "../store/usePipelineStore";

// SERVER_BASE is the same as BASE but without trailing /api,
// so we can call /mcp and /auth directly.
Expand Down Expand Up @@ -519,7 +518,11 @@ export const api = {
pipelineStore?.repoFullName ||
(pipelineStore as any)?.result?.repo;
const selectedBranch = branch || (pipelineStore as any)?.selectedBranch || "main";
const yaml = (pipelineStore as any)?.result?.generated_yaml;
const yaml =
fromCallerYaml ||
(pipelineStore as any)?.result?.generated_yaml ||
"";

const environment = env || (pipelineStore as any)?.environment || "dev";

const providerFinal = provider || (pipelineStore as any)?.provider || "aws";
Expand All @@ -545,6 +548,9 @@ export const api = {
};

console.log("[Deploy] Final payload:", payload);
if (!repoFullName) throw new Error("startDeploy: missing repoFullName");
if (!selectedBranch) throw new Error("startDeploy: missing branch");
if (!yaml || yaml.trim().length === 0) throw new Error("startDeploy: missing yaml");

const res = await fetch(`${SERVER_BASE}/mcp/v1/pipeline_commit`, {
method: "POST",
Expand Down
202 changes: 153 additions & 49 deletions client/src/pages/ConfigurePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,15 @@ export default function ConfigurePage() {
alert("Pick a repo + branch on the Connect page first.");
return;
}
await regenerate({ repo, branch });

await regenerate({
repo,
branch,
template,
provider,
stages,
options,
});
};

const handleOpenPr = async () => {
Expand All @@ -95,6 +103,36 @@ export default function ConfigurePage() {
const trimmed = chatInput.trim();
if (!trimmed) return;

// --- Sync AI intent with pipeline stages BEFORE sending to backend ---
// The AI is a planner, not an authority. UI state must be updated first.
const lower = trimmed.toLowerCase();

// Reset to defaults first
let nextStages: Array<"build" | "test" | "deploy"> = ["build", "test", "deploy"];

if (lower.includes("just build") || lower.includes("only build")) {
nextStages = ["build"];
} else if (
lower.includes("build and test") ||
(lower.includes("build") && lower.includes("test") && !lower.includes("deploy"))
) {
nextStages = ["build", "test"];
} else if (
lower.includes("no deploy") ||
lower.includes("without deploy")
) {
nextStages = ["build", "test"];
}

// Apply stage changes to the pipeline store
(["build", "test", "deploy"] as const).forEach((stage) => {
const shouldEnable = nextStages.includes(stage);
const isEnabled = stages.includes(stage);
if (shouldEnable !== isEnabled) {
toggleStage(stage);
}
});

if (!repo || !branch) {
alert(
"Pick a repo + branch on the Connect page first so I can give better suggestions."
Expand All @@ -114,7 +152,7 @@ export default function ConfigurePage() {
template,
provider,
branch,
stages,
stages: nextStages,
options,
};

Expand Down Expand Up @@ -159,9 +197,8 @@ export default function ConfigurePage() {
pipelineName,
branch,
provider,
stages,
// Keep a copy of the current options in wizard context so follow-up prompts
// can reference the selected provider identity (AWS role / GCP service account).
// 🔒 Never override stages from backend / metadata
stages: pipelineSnapshot.stages,
options,
} as any);
}
Expand Down Expand Up @@ -249,8 +286,8 @@ export default function ConfigurePage() {
className="rounded-md border border-white/25 bg-white px-3 py-2 text-sm text-slate-900 placeholder-slate-500"
>
<option value="node_app">Node.js app</option>
<option value="node_library">Node.js library</option>
<option value="react_vite">React/Vite app</option>
<option value="python_app">Python App</option>
<option value="container_service">Container</option>
</select>
<span className="text-xs text-slate-200">
Pick the closest match to your repo; the MCP backend refines it.
Expand Down Expand Up @@ -296,54 +333,99 @@ export default function ConfigurePage() {
</div>
</fieldset>

{/* Node version + commands */}
{/* Runtime version + commands */}
<div className="grid gap-4">
<label className="grid gap-1">
<span className="text-sm font-medium text-slate-800">Node version</span>
<input
disabled={busy}
value={options.nodeVersion}
onChange={(e) => setOption("nodeVersion", e.target.value)}
className="rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
placeholder="20"
/>
</label>
{/* Node.js version: only show for node_app AND build stage enabled */}
{template === "node_app" && stages.includes("build") && (
<label className="grid gap-1">
<span className="text-sm font-medium text-slate-800">Node.js version</span>
<input
disabled={busy}
value={options.nodeVersion}
onChange={(e) => setOption("nodeVersion", e.target.value)}
className="rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
placeholder="20"
/>
</label>
)}

<label className="grid gap-1">
<span className="text-sm font-medium text-slate-800">Install command</span>
<input
disabled={busy}
value={options.installCmd}
onChange={(e) => setOption("installCmd", e.target.value)}
className="rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
placeholder="npm ci"
/>
</label>
{/* Install command: only show if build stage enabled */}
{stages.includes("build") && (
<label className="grid gap-1">
<span className="text-sm font-medium text-slate-800">
{template === "node_app"
? "Install command (npm)"
: template === "python_app"
? "Install command (pip)"
: "Install command"}
</span>
<input
disabled={busy}
value={options.installCmd}
onChange={(e) => setOption("installCmd", e.target.value)}
className="rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
placeholder={
template === "node_app"
? "npm ci"
: template === "python_app"
? "pip install -r requirements.txt"
: ""
}
/>
</label>
)}

<label className="grid gap-1">
<span className="text-sm font-medium text-slate-800">Test command</span>
<input
disabled={busy}
value={options.testCmd}
onChange={(e) => setOption("testCmd", e.target.value)}
className="rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
placeholder="npm test"
/>
</label>
{/* Test command: only show if test stage enabled */}
{stages.includes("test") && (
<label className="grid gap-1">
<span className="text-sm font-medium text-slate-800">
{template === "node_app"
? "Test command (npm)"
: template === "python_app"
? "Test command (pytest)"
: "Test command"}
</span>
<input
disabled={busy}
value={options.testCmd}
onChange={(e) => setOption("testCmd", e.target.value)}
className="rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
placeholder={
template === "node_app"
? "npm test"
: template === "python_app"
? "pytest"
: ""
}
/>
</label>
)}

<label className="grid gap-1">
<span className="text-sm font-medium text-slate-800">Build command</span>
<input
disabled={busy}
value={options.buildCmd}
onChange={(e) => setOption("buildCmd", e.target.value)}
className="rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
placeholder="npm run build"
/>
</label>
{/* Build command: only show if build stage enabled */}
{stages.includes("build") && (
<label className="grid gap-1">
<span className="text-sm font-medium text-slate-800">
{template === "node_app"
? "Build command (npm)"
: "Build command"}
</span>
<input
disabled={busy}
value={options.buildCmd}
onChange={(e) => setOption("buildCmd", e.target.value)}
className="rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
placeholder={
template === "node_app"
? "npm run build"
: ""
}
/>
</label>
)}
</div>

{provider === "aws" && (
{provider === "aws" && stages.includes("deploy") && (
<>
<label className="grid gap-1">
<span className="text-sm font-medium">AWS Role (OIDC)</span>
<select
Expand All @@ -364,6 +446,28 @@ export default function ConfigurePage() {
the deploy job.
</span>
</label>
<label className="grid gap-1">
<span className="text-sm font-medium">AWS Role Session Name</span>
<input
disabled={busy}
value={options.awsSessionName ?? ""}
onChange={(e) => setOption("awsSessionName", e.target.value)}
className="rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
placeholder="autodeploy"
/>
</label>

<label className="grid gap-1">
<span className="text-sm font-medium">AWS Region</span>
<input
disabled={busy}
value={options.awsRegion ?? ""}
onChange={(e) => setOption("awsRegion", e.target.value)}
className="rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
placeholder="us-east-1"
/>
</label>
</>
)}

{provider === "gcp" && (
Expand Down
Loading
Loading