Canvas: Support Anthropic Claude based AI agents

Change-Id: Ib74c9672da9a80a4f20d63741a471c728a435b8e
diff --git a/apps/canvas/front/src/components/node-app.tsx b/apps/canvas/front/src/components/node-app.tsx
index fa08977..a01709f 100644
--- a/apps/canvas/front/src/components/node-app.tsx
+++ b/apps/canvas/front/src/components/node-app.tsx
@@ -6,7 +6,7 @@
 import { z } from "zod";
 import { useForm, EventType, DeepPartial } from "react-hook-form";
 import { zodResolver } from "@hookform/resolvers/zod";
-import { Form, FormControl, FormField, FormItem, FormMessage } from "./ui/form";
+import { Form, FormControl, FormField, FormItem, FormMessage, FormLabel } from "./ui/form";
 import { Button } from "./ui/button";
 import { Handle, Position, useNodes } from "@xyflow/react";
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
@@ -80,7 +80,8 @@
 });
 
 const agentSchema = z.object({
-	geminiApiKey: z.string().optional(),
+	model: z.enum(["gemini", "claude"]),
+	apiKey: z.string().optional(),
 });
 
 export function NodeAppDetails({ node, disabled, showName = true, isOverview = false }: NodeDetailsProps<ServiceNode>) {
@@ -253,19 +254,34 @@
 		resolver: zodResolver(agentSchema),
 		mode: "onChange",
 		defaultValues: {
-			geminiApiKey: data.agent?.geminiApiKey,
+			apiKey: data.model?.apiKey,
+			model: data.model?.name,
 		},
 	});
 	useEffect(() => {
-		const sub = agentForm.watch((value) => {
-			store.updateNodeData<"app">(id, {
-				agent: {
-					geminiApiKey: value.geminiApiKey,
-				},
-			});
+		const sub = agentForm.watch((value, { name }: { name?: keyof z.infer<typeof agentSchema> | undefined }) => {
+			switch (name) {
+				case "model":
+					agentForm.setValue("apiKey", "", { shouldDirty: true });
+					store.updateNodeData<"app">(id, {
+						model: {
+							name: value.model,
+							apiKey: undefined,
+						},
+					});
+					break;
+				case "apiKey":
+					store.updateNodeData<"app">(id, {
+						model: {
+							name: data.model?.name,
+							apiKey: value.apiKey,
+						},
+					});
+					break;
+			}
 		});
 		return () => sub.unsubscribe();
-	}, [id, agentForm, store]);
+	}, [id, agentForm, store, data]);
 	return (
 		<>
 			<SourceRepo node={node} disabled={disabled} />
@@ -307,16 +323,41 @@
 			{node.data.type === "sketch:latest" && (
 				<Form {...agentForm}>
 					<form className="space-y-2">
-						<Label>Gemini API Key</Label>
 						<FormField
 							control={agentForm.control}
-							name="geminiApiKey"
+							name="model"
+							render={({ field }) => (
+								<FormItem>
+									<FormLabel>AI Model</FormLabel>
+									<Select
+										onValueChange={field.onChange}
+										defaultValue={field.value}
+										disabled={disabled}
+									>
+										<FormControl>
+											<SelectTrigger>
+												<SelectValue placeholder="Select a model" />
+											</SelectTrigger>
+										</FormControl>
+										<SelectContent>
+											<SelectItem value="gemini">Gemini</SelectItem>
+											<SelectItem value="claude">Claude</SelectItem>
+										</SelectContent>
+									</Select>
+									<FormMessage />
+								</FormItem>
+							)}
+						/>
+						<Label>API Key</Label>
+						<FormField
+							control={agentForm.control}
+							name="apiKey"
 							render={({ field }) => (
 								<FormItem>
 									<FormControl>
 										<Input
 											type="password"
-											placeholder="Override Gemini API key"
+											placeholder="Override AI Model API key"
 											{...field}
 											value={field.value || ""}
 											disabled={disabled}