Skip to content

Commit b630925

Browse files
committed
feat: wire AI-generated pipelines into YAML preview and wizard context
1 parent 154355c commit b630925

6 files changed

Lines changed: 256 additions & 68 deletions

File tree

client/src/pages/ConfigurePage.tsx

Lines changed: 103 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect, useState } from "react";
22
import { useRepoStore } from "../store/useRepoStore";
33
import { usePipelineStore } from "../store/usePipelineStore";
4+
import { useWizardStore } from "../store/useWizardStore";
45
import { api } from "../lib/api";
56

67
type ChatMessage = {
@@ -30,8 +31,17 @@ export default function ConfigurePage() {
3031
editedYaml,
3132
setEditedYaml,
3233
getEffectiveYaml,
34+
hydrateFromWizard,
3335
} = usePipelineStore();
3436

37+
const {
38+
repoInfo,
39+
pipelineInfo,
40+
setRepoInfo,
41+
setPipelineInfo,
42+
setLastToolCalled,
43+
} = useWizardStore();
44+
3545
const yaml = getEffectiveYaml();
3646
const busy = status === "loading";
3747

@@ -57,6 +67,13 @@ export default function ConfigurePage() {
5767
alert("Pick a repo + branch on the Connect page first.");
5868
return;
5969
}
70+
console.log("[ConfigurePage] Generate clicked with inputs:", {
71+
repo,
72+
branch,
73+
template,
74+
stages,
75+
options,
76+
});
6077
await regenerate({ repo, branch });
6178
};
6279

@@ -92,40 +109,92 @@ export default function ConfigurePage() {
92109
setChatInput("");
93110
setChatLoading(true);
94111

95-
try {
96-
const res = await api.askYamlWizard({
97-
repoUrl: repo, // backend expects "repoUrl"
98-
provider: "aws", // or whatever provider you use
99-
branch: branch, // backend expects "branch"
100-
message: trimmed, // optional, for your agent logic
101-
yaml, // optional, current YAML for context
102-
});
103-
104-
const text =
105-
(res as any)?.reply ??
106-
(res as any)?.message ??
107-
(res as any)?.content ??
108-
JSON.stringify(res, null, 2);
109-
110-
const assistantMessage: ChatMessage = {
111-
role: "assistant",
112-
content: text,
113-
};
114-
115-
setChatMessages((prev) => [...prev, assistantMessage]);
116-
} catch (e: any) {
117-
console.error("[ConfigurePage] AI wizard error:", e);
118-
const assistantMessage: ChatMessage = {
119-
role: "assistant",
120-
content:
121-
"Sorry, I ran into an issue talking to the AI backend.\n\n" +
122-
`Error: ${e?.message ?? "Unknown error"}`,
123-
};
124-
setChatMessages((prev) => [...prev, assistantMessage]);
125-
} finally {
126-
setChatLoading(false);
127-
}
128-
};
112+
try {
113+
const res = await api.askYamlWizard({
114+
repoUrl: repo, // backend expects "repoUrl"
115+
provider: "aws", // or whatever provider you use
116+
branch: branch, // backend expects "branch"
117+
message: trimmed, // optional, for your agent logic
118+
yaml, // optional, current YAML for context
119+
});
120+
121+
// ---- Update wizard context memory ----
122+
if ((res as any)?.tool_called) {
123+
setLastToolCalled((res as any).tool_called);
124+
}
125+
126+
// If repo info is available from selection or tool output, store it
127+
if (repo) {
128+
setRepoInfo({
129+
fullName: repo,
130+
});
131+
}
132+
133+
// If a pipeline was generated, hydrate pipeline store + wizard context
134+
if ((res as any)?.tool_called === "pipeline_generator") {
135+
const generatedYaml =
136+
(res as any)?.generated_yaml ??
137+
(res as any)?.tool_output?.data?.generated_yaml;
138+
139+
const pipelineName =
140+
(res as any)?.pipeline_metadata?.data?.pipeline_name ??
141+
(res as any)?.pipeline_metadata?.pipeline_name;
142+
143+
if (generatedYaml) {
144+
hydrateFromWizard({
145+
repo,
146+
generatedYaml,
147+
pipelineName,
148+
});
149+
}
150+
151+
setPipelineInfo({
152+
pipelineName,
153+
branch,
154+
provider: "aws",
155+
stages,
156+
});
157+
}
158+
159+
let text: string;
160+
161+
if ((res as any)?.reply) {
162+
text = (res as any).reply;
163+
} else if ((res as any)?.message) {
164+
text = (res as any).message;
165+
} else if (
166+
(res as any)?.tool_called === "repo_reader" &&
167+
Array.isArray((res as any)?.tool_output?.data?.data?.repositories)
168+
) {
169+
const count =
170+
(res as any).tool_output.data.data.repositories.length;
171+
text = `I found ${count} repositories. You can select one from the list to continue.`;
172+
} else if (repoInfo?.fullName) {
173+
text = `I’m looking at ${repoInfo.fullName}. What would you like to change about the pipeline?`;
174+
} else {
175+
text =
176+
"I couldn’t map that request to an action yet. You can ask me to modify the pipeline, deploy settings, or AWS role.";
177+
}
178+
179+
const assistantMessage: ChatMessage = {
180+
role: "assistant",
181+
content: text,
182+
};
183+
184+
setChatMessages((prev) => [...prev, assistantMessage]);
185+
} catch (e: any) {
186+
console.error("[ConfigurePage] AI wizard error:", e);
187+
const assistantMessage: ChatMessage = {
188+
role: "assistant",
189+
content:
190+
"Sorry, I ran into an issue talking to the AI backend.\n\n" +
191+
`Error: ${e?.message ?? "Unknown error"}`,
192+
};
193+
setChatMessages((prev) => [...prev, assistantMessage]);
194+
} finally {
195+
setChatLoading(false);
196+
}
197+
};
129198

130199
const handleChatKeyDown: React.KeyboardEventHandler<HTMLTextAreaElement> = (
131200
e

client/src/store/usePipelineStore.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ type PipelineActions = {
5252
setEditedYaml(y: string): void;
5353
resetYaml(): void;
5454
resetAll(): void;
55+
56+
hydrateFromWizard(payload: {
57+
repo: string;
58+
generatedYaml: string;
59+
pipelineName?: string;
60+
}): void;
5561
};
5662

5763
const initial: PipelineState = {
@@ -213,6 +219,26 @@ export const usePipelineStore = create<PipelineState & PipelineActions>()(
213219
});
214220
},
215221

222+
hydrateFromWizard({ repo, generatedYaml, pipelineName }) {
223+
set({
224+
result: {
225+
...(get().result ?? {}),
226+
generated_yaml: generatedYaml,
227+
yaml: generatedYaml,
228+
pipeline_name: pipelineName ?? "ci.yml",
229+
},
230+
repoFullName: repo,
231+
status: "success",
232+
editing: false,
233+
editedYaml: undefined,
234+
});
235+
236+
console.log(
237+
"[usePipelineStore] Hydrated YAML from wizard:",
238+
generatedYaml.slice(0, 80)
239+
);
240+
},
241+
216242
setEditing: (b) => set({ editing: b }),
217243
setEditedYaml: (y) => set({ editedYaml: y }),
218244
resetYaml: () => set({ editedYaml: undefined }),

client/src/store/useWizardStore.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { create } from "zustand";
2+
3+
type RepoInfo = {
4+
fullName: string;
5+
defaultBranch?: string;
6+
language?: string | null;
7+
visibility?: "public" | "private";
8+
};
9+
10+
type PipelineInfo = {
11+
pipelineName?: string;
12+
branch?: string;
13+
provider?: string;
14+
stages?: string[];
15+
};
16+
17+
type WizardContextState = {
18+
// context
19+
lastToolCalled?: string;
20+
repoInfo?: RepoInfo;
21+
pipelineInfo?: PipelineInfo;
22+
23+
// setters
24+
setLastToolCalled: (tool?: string) => void;
25+
setRepoInfo: (info?: RepoInfo) => void;
26+
setPipelineInfo: (info?: PipelineInfo) => void;
27+
28+
// reset (optional but useful)
29+
resetWizardContext: () => void;
30+
};
31+
32+
export const useWizardStore = create<WizardContextState>((set) => ({
33+
lastToolCalled: undefined,
34+
repoInfo: undefined,
35+
pipelineInfo: undefined,
36+
37+
setLastToolCalled: (tool) =>
38+
set(() => ({
39+
lastToolCalled: tool,
40+
})),
41+
42+
setRepoInfo: (info) =>
43+
set(() => ({
44+
repoInfo: info,
45+
})),
46+
47+
setPipelineInfo: (info) =>
48+
set(() => ({
49+
pipelineInfo: info,
50+
})),
51+
52+
resetWizardContext: () =>
53+
set(() => ({
54+
lastToolCalled: undefined,
55+
repoInfo: undefined,
56+
pipelineInfo: undefined,
57+
})),
58+
}));

server/tools/oidc_adapter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const oidc_adapter = {
66

77
// ✅ Input schema for validation
88
input_schema: z.object({
9-
provider: z.enum(["aws", "jenkins"]),
9+
provider: z.enum(["aws", "jenkins", "gcp"]),
1010
}),
1111

1212
// ✅ Mock handler (replace with real API calls later)

server/tools/pipeline_generator.js

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,33 @@ export const pipeline_generator = {
1919
template: z.enum(['node_app', 'python_app', 'container_service']),
2020
options: z
2121
.object({
22-
run_tests: z.boolean().default(true),
23-
include_trivy_scan: z.boolean().default(false),
22+
// legacy flags
23+
run_tests: z.boolean().optional(),
24+
include_trivy_scan: z.boolean().optional(),
2425
artifact_name: z.string().optional(),
26+
27+
// frontend-driven config
28+
nodeVersion: z.string().optional(),
29+
installCmd: z.string().optional(),
30+
testCmd: z.string().optional(),
31+
buildCmd: z.string().optional(),
32+
awsRoleArn: z.string().optional(),
33+
stages: z.array(z.enum(['build', 'test', 'deploy'])).optional(),
2534
})
2635
.optional(),
2736
}),
2837

2938
// Real handler (queries github_adapter for repo info and generates pipeline config)
3039
handler: async ({ repo, branch = 'main', provider = 'aws', template, options }) => {
40+
const normalized = {
41+
nodeVersion: options?.nodeVersion,
42+
installCmd: options?.installCmd,
43+
testCmd: options?.testCmd,
44+
buildCmd: options?.buildCmd,
45+
awsRoleArn: options?.awsRoleArn,
46+
stages: options?.stages,
47+
};
48+
3149
const sessionToken = process.env.MCP_SESSION_TOKEN;
3250
let decoded = {};
3351
let userId = null;
@@ -226,10 +244,17 @@ export const pipeline_generator = {
226244
const selectedProvider = provider || inferredProvider;
227245
const selectedTemplate = template || inferredTemplate;
228246

247+
console.log('🧪 pipeline_generator normalized options:', normalized);
248+
229249
const pipelineName = `${selectedProvider}-${selectedTemplate}-ci.yml`;
230250

231251
const generated_yaml = `
232252
name: CI/CD Pipeline for ${repo}
253+
254+
permissions:
255+
id-token: write
256+
contents: read
257+
233258
on:
234259
push:
235260
branches:
@@ -239,26 +264,34 @@ jobs:
239264
runs-on: ubuntu-latest
240265
steps:
241266
- uses: actions/checkout@v4
242-
- name: Setup ${selectedTemplate === 'node_app' ? 'Node.js' : 'Python'}
243-
uses: actions/setup-${
244-
selectedTemplate === 'node_app' ? 'node' : 'python'
245-
}@v4
267+
- name: Setup Node.js
268+
uses: actions/setup-node@v4
269+
with:
270+
node-version: ${normalized.nodeVersion ?? '20'}
246271
- name: Install Dependencies
247272
run: ${
248-
selectedTemplate === 'node_app'
273+
normalized.installCmd ??
274+
(selectedTemplate === 'node_app'
249275
? 'npm ci'
250-
: 'pip install -r requirements.txt'
276+
: 'pip install -r requirements.txt')
251277
}
252278
- name: Run Tests
253-
run: ${selectedTemplate === 'node_app' ? 'npm test' : 'pytest'}
279+
run: ${
280+
normalized.testCmd ??
281+
(selectedTemplate === 'node_app' ? 'npm test' : 'pytest')
282+
}
254283
deploy:
255284
needs: build
256285
runs-on: ubuntu-latest
257286
steps:
258-
- name: Configure ${selectedProvider.toUpperCase()}
259-
run: echo "Configuring ${selectedProvider.toUpperCase()} OIDC..."
287+
- uses: actions/checkout@v4
288+
- name: Configure AWS (OIDC)
289+
uses: aws-actions/configure-aws-credentials@v4
290+
with:
291+
role-to-assume: ${normalized.awsRoleArn ?? 'REPLACE_ME'}
292+
aws-region: us-east-1
260293
- name: Deploy Application
261-
run: echo "Deploying ${repo} to ${selectedProvider.toUpperCase()}..."
294+
run: echo "Deploying ${repo} to AWS..."
262295
`;
263296

264297
return {
@@ -270,7 +303,7 @@ jobs:
270303
provider: selectedProvider,
271304
template: selectedTemplate,
272305
options: options || {},
273-
stages: ['build', 'test', 'deploy'],
306+
stages: normalized.stages ?? ['build', 'test', 'deploy'],
274307
generated_yaml,
275308
repo_info: repoInfo,
276309
created_at: new Date().toISOString(),

0 commit comments

Comments
 (0)