Canvas: Support Anthropic Claude based AI agents
Change-Id: Ib74c9672da9a80a4f20d63741a471c728a435b8e
diff --git a/apps/canvas/front/src/Integrations.tsx b/apps/canvas/front/src/Integrations.tsx
index cf4b586..09bc28d 100644
--- a/apps/canvas/front/src/Integrations.tsx
+++ b/apps/canvas/front/src/Integrations.tsx
@@ -1,4 +1,4 @@
-import { useProjectId, useGithubService, useStateStore, useGeminiService } from "@/lib/state";
+import { useProjectId, useGithubService, useStateStore, useGeminiService, useAnthropicService } from "@/lib/state";
import { Form, FormControl, FormField, FormItem, FormMessage } from "./components/ui/form";
import { Input } from "./components/ui/input";
import { useForm } from "react-hook-form";
@@ -17,14 +17,20 @@
geminiApiKey: z.string().min(1, "Gemini API token is required"),
});
+const anthropicSchema = z.object({
+ anthropicApiKey: z.string().min(1, "Anthropic API token is required"),
+});
+
export function Integrations() {
const { toast } = useToast();
const store = useStateStore();
const projectId = useProjectId();
const [isEditingGithub, setIsEditingGithub] = useState(false);
const [isEditingGemini, setIsEditingGemini] = useState(false);
+ const [isEditingAnthropic, setIsEditingAnthropic] = useState(false);
const githubService = useGithubService();
const geminiService = useGeminiService();
+ const anthropicService = useAnthropicService();
const [isSaving, setIsSaving] = useState(false);
const githubForm = useForm<z.infer<typeof githubSchema>>({
@@ -37,6 +43,11 @@
mode: "onChange",
});
+ const anthropicForm = useForm<z.infer<typeof anthropicSchema>>({
+ resolver: zodResolver(anthropicSchema),
+ mode: "onChange",
+ });
+
const onGithubSubmit = useCallback(
async (data: z.infer<typeof githubSchema>) => {
if (!projectId) return;
@@ -109,6 +120,40 @@
[projectId, store, geminiForm, toast, setIsEditingGemini, setIsSaving],
);
+ const onAnthropicSubmit = useCallback(
+ async (data: z.infer<typeof anthropicSchema>) => {
+ if (!projectId) return;
+ setIsSaving(true);
+ try {
+ const response = await fetch(`/api/project/${projectId}/anthropic-token`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ anthropicApiKey: data.anthropicApiKey }),
+ });
+ if (!response.ok) {
+ throw new Error("Failed to save Anthropic token");
+ }
+ await store.refreshEnv();
+ setIsEditingAnthropic(false);
+ anthropicForm.reset();
+ toast({
+ title: "Anthropic token saved successfully",
+ });
+ } catch (error) {
+ toast({
+ variant: "destructive",
+ title: "Failed to save Anthropic token",
+ description: error instanceof Error ? error.message : "Unknown error",
+ });
+ } finally {
+ setIsSaving(false);
+ }
+ },
+ [projectId, store, anthropicForm, toast, setIsEditingAnthropic, setIsSaving],
+ );
+
const handleCancelGithub = () => {
setIsEditingGithub(false);
githubForm.reset();
@@ -119,6 +164,11 @@
geminiForm.reset();
};
+ const handleCancelAnthropic = () => {
+ setIsEditingAnthropic(false);
+ anthropicForm.reset();
+ };
+
return (
<div className="px-4 py-1">
<div className="flex flex-col gap-1">
@@ -261,6 +311,67 @@
</Form>
)}
</div>
+ <div className="flex flex-col gap-1">
+ <div className="flex flex-row items-center gap-1">
+ {anthropicService ? <CircleCheck /> : <CircleX />}
+ <div>Anthropic</div>
+ </div>
+
+ {!!anthropicService && !isEditingAnthropic && (
+ <Button variant="outline" className="w-fit" onClick={() => setIsEditingAnthropic(true)}>
+ Update API Key
+ </Button>
+ )}
+
+ {(!anthropicService || isEditingAnthropic) && (
+ <div className="flex flex-row items-center gap-1 text-sm">
+ <div>
+ Follow the link to generate new API Key:{" "}
+ <a href="https://console.anthropic.com/settings/keys" target="_blank">
+ https://console.anthropic.com/settings/keys
+ </a>
+ </div>
+ </div>
+ )}
+ {(!anthropicService || isEditingAnthropic) && (
+ <Form {...anthropicForm}>
+ <form className="space-y-2" onSubmit={anthropicForm.handleSubmit(onAnthropicSubmit)}>
+ <FormField
+ control={anthropicForm.control}
+ name="anthropicApiKey"
+ render={({ field }) => (
+ <FormItem>
+ <FormControl>
+ <Input
+ type="password"
+ placeholder="Anthropic API Token"
+ className="w-1/4"
+ {...field}
+ />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <div className="flex flex-row items-center gap-1">
+ <Button type="submit" disabled={isSaving}>
+ {isSaving ? "Saving..." : "Save"}
+ </Button>
+ {!!anthropicService && (
+ <Button
+ type="button"
+ variant="outline"
+ onClick={handleCancelAnthropic}
+ disabled={isSaving}
+ >
+ Cancel
+ </Button>
+ )}
+ </div>
+ </form>
+ </Form>
+ )}
+ </div>
</div>
);
}