refactor: use stepper

This commit is contained in:
Mauricio Siu
2025-01-18 23:49:47 -06:00
parent e68465f9e6
commit e7329a727f
5 changed files with 454 additions and 358 deletions

View File

@@ -24,23 +24,12 @@ const examples = [
]; ];
export const StepOne = ({ nextStep, setTemplateInfo, templateInfo }: any) => { export const StepOne = ({ nextStep, setTemplateInfo, templateInfo }: any) => {
const [userInput, setUserInput] = useState(templateInfo.userInput);
// Get servers from the API // Get servers from the API
const { data: servers } = api.server.withSSHKey.useQuery(); const { data: servers } = api.server.withSSHKey.useQuery();
const handleNext = () => {
setTemplateInfo({
...templateInfo,
userInput,
});
nextStep();
};
const handleExampleClick = (example: string) => { const handleExampleClick = (example: string) => {
setUserInput(example); setTemplateInfo({ ...templateInfo, userInput: example });
}; };
return ( return (
<div className="flex flex-col h-full gap-4"> <div className="flex flex-col h-full gap-4">
<div className=""> <div className="">
@@ -51,8 +40,10 @@ export const StepOne = ({ nextStep, setTemplateInfo, templateInfo }: any) => {
<Textarea <Textarea
id="user-needs" id="user-needs"
placeholder="Describe the type of template you need, its purpose, and any specific features you'd like to include." placeholder="Describe the type of template you need, its purpose, and any specific features you'd like to include."
value={userInput} value={templateInfo?.userInput}
onChange={(e) => setUserInput(e.target.value)} onChange={(e) =>
setTemplateInfo({ ...templateInfo, userInput: e.target.value })
}
className="min-h-[100px]" className="min-h-[100px]"
/> />
</div> </div>
@@ -106,13 +97,6 @@ export const StepOne = ({ nextStep, setTemplateInfo, templateInfo }: any) => {
</div> </div>
</div> </div>
</div> </div>
<div className="">
<div className="flex justify-end">
<Button onClick={handleNext} disabled={!userInput.trim()}>
Next
</Button>
</div>
</div>
</div> </div>
); );
}; };

View File

@@ -1,13 +1,8 @@
import { CodeEditor } from "@/components/shared/code-editor"; import { CodeEditor } from "@/components/shared/code-editor";
import { Button } from "@/components/ui/button";
import ReactMarkdown from "react-markdown"; import ReactMarkdown from "react-markdown";
import type { StepProps } from "./step-two"; import type { StepProps } from "./step-two";
export const StepThree = ({ export const StepThree = ({ templateInfo }: StepProps) => {
prevStep,
templateInfo,
onSubmit,
}: StepProps & { onSubmit: () => void }) => {
return ( return (
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
<div className="flex-grow"> <div className="flex-grow">
@@ -93,14 +88,6 @@ export const StepThree = ({
</div> </div>
</div> </div>
</div> </div>
<div className="pt-6">
<div className="flex justify-between">
<Button onClick={prevStep} variant="outline">
Back
</Button>
<Button onClick={onSubmit}>Create</Button>
</div>
</div>
</div> </div>
); );
}; };

View File

@@ -1,3 +1,4 @@
import { AlertBlock } from "@/components/shared/alert-block";
import { CodeEditor } from "@/components/shared/code-editor"; import { CodeEditor } from "@/components/shared/code-editor";
import { import {
Accordion, Accordion,
@@ -16,54 +17,45 @@ import { useEffect, useState } from "react";
import ReactMarkdown from "react-markdown"; import ReactMarkdown from "react-markdown";
import { toast } from "sonner"; import { toast } from "sonner";
import type { TemplateInfo } from "./template-generator"; import type { TemplateInfo } from "./template-generator";
import { AlertBlock } from "@/components/shared/alert-block";
export interface StepProps { export interface StepProps {
nextStep: () => void; stepper?: any;
prevStep: () => void;
templateInfo: TemplateInfo; templateInfo: TemplateInfo;
setTemplateInfo: React.Dispatch<React.SetStateAction<TemplateInfo>>; setTemplateInfo: React.Dispatch<React.SetStateAction<TemplateInfo>>;
} }
export const StepTwo = ({ export const StepTwo = ({
nextStep, stepper,
prevStep,
templateInfo, templateInfo,
setTemplateInfo, setTemplateInfo,
}: StepProps) => { }: StepProps) => {
const [suggestions, setSuggestions] = useState<TemplateInfo["details"][]>(); const suggestions = templateInfo.suggestions || [];
const [selectedVariant, setSelectedVariant] = const selectedVariant = templateInfo.details;
useState<TemplateInfo["details"]>();
const [showValues, setShowValues] = useState<Record<string, boolean>>({}); const [showValues, setShowValues] = useState<Record<string, boolean>>({});
const { mutateAsync, isLoading, error, isError } = const { mutateAsync, isLoading, error, isError } =
api.ai.suggest.useMutation(); api.ai.suggest.useMutation();
useEffect(() => { useEffect(() => {
if (suggestions?.length > 0) {
return;
}
mutateAsync({ mutateAsync({
aiId: templateInfo.aiId, aiId: templateInfo.aiId,
serverId: templateInfo.server?.serverId || "", serverId: templateInfo.server?.serverId || "",
input: templateInfo.userInput, input: templateInfo.userInput,
}) })
.then((data) => { .then((data) => {
console.log(data); setTemplateInfo({
setSuggestions(data); ...templateInfo,
suggestions: data,
});
}) })
.catch((error) => { .catch((error) => {
toast.error("Error generating suggestions"); toast.error("Error generating suggestions");
}); });
}, [templateInfo.userInput]); }, [templateInfo.userInput]);
const handleNext = () => {
if (selectedVariant) {
setTemplateInfo({
...templateInfo,
details: selectedVariant,
});
}
nextStep();
};
const toggleShowValue = (name: string) => { const toggleShowValue = (name: string) => {
setShowValues((prev) => ({ ...prev, [name]: !prev[name] })); setShowValues((prev) => ({ ...prev, [name]: !prev[name] }));
}; };
@@ -82,9 +74,14 @@ export const StepTwo = ({
[field]: value, [field]: value,
}; };
setSelectedVariant({ setTemplateInfo({
...selectedVariant, ...templateInfo,
envVariables: updatedEnvVariables, ...(templateInfo.details && {
details: {
...templateInfo.details,
envVariables: updatedEnvVariables,
},
}),
}); });
}; };
@@ -95,9 +92,14 @@ export const StepTwo = ({
(_, i) => i !== index, (_, i) => i !== index,
); );
setSelectedVariant({ setTemplateInfo({
...selectedVariant, ...templateInfo,
envVariables: updatedEnvVariables, ...(templateInfo.details && {
details: {
...templateInfo.details,
envVariables: updatedEnvVariables,
},
}),
}); });
}; };
@@ -114,9 +116,14 @@ export const StepTwo = ({
[field]: value, [field]: value,
}; };
setSelectedVariant({ setTemplateInfo({
...selectedVariant, ...templateInfo,
domains: updatedDomains, ...(templateInfo.details && {
details: {
...templateInfo.details,
domains: updatedDomains,
},
}),
}); });
}; };
@@ -127,30 +134,49 @@ export const StepTwo = ({
(_, i) => i !== index, (_, i) => i !== index,
); );
setSelectedVariant({ setTemplateInfo({
...selectedVariant, ...templateInfo,
domains: updatedDomains, ...(templateInfo.details && {
details: {
...templateInfo.details,
domains: updatedDomains,
},
}),
}); });
}; };
const addEnvVariable = () => { const addEnvVariable = () => {
if (!selectedVariant) return; if (!selectedVariant) return;
setSelectedVariant({ setTemplateInfo({
...selectedVariant, ...templateInfo,
envVariables: [...selectedVariant.envVariables, { name: "", value: "" }],
...(templateInfo.details && {
details: {
...templateInfo.details,
envVariables: [
...selectedVariant.envVariables,
{ name: "", value: "" },
],
},
}),
}); });
}; };
const addDomain = () => { const addDomain = () => {
if (!selectedVariant) return; if (!selectedVariant) return;
setSelectedVariant({ setTemplateInfo({
...selectedVariant, ...templateInfo,
domains: [ ...(templateInfo.details && {
...selectedVariant.domains, details: {
{ host: "", port: 80, serviceName: "" }, ...templateInfo.details,
], domains: [
...selectedVariant.domains,
{ host: "", port: 80, serviceName: "" },
],
},
}),
}); });
}; };
if (isError) { if (isError) {
@@ -161,15 +187,6 @@ export const StepTwo = ({
<AlertBlock type="error"> <AlertBlock type="error">
{error?.message || "Error generating suggestions"} {error?.message || "Error generating suggestions"}
</AlertBlock> </AlertBlock>
<Button
onClick={() =>
selectedVariant ? setSelectedVariant(undefined) : prevStep()
}
variant="outline"
>
{selectedVariant ? "Change Variant" : "Back"}
</Button>
</div> </div>
); );
} }
@@ -197,10 +214,13 @@ export const StepTwo = ({
<div className="space-y-4"> <div className="space-y-4">
<div>Based on your input, we suggest the following variants:</div> <div>Based on your input, we suggest the following variants:</div>
<RadioGroup <RadioGroup
value={selectedVariant} // value={selectedVariant?.}
onValueChange={(value) => { onValueChange={(value) => {
const element = suggestions?.find((s) => s?.id === value); const element = suggestions?.find((s) => s?.id === value);
setSelectedVariant(element); setTemplateInfo({
...templateInfo,
details: element,
});
}} }}
className="space-y-4" className="space-y-4"
> >
@@ -254,9 +274,14 @@ export const StepTwo = ({
value={selectedVariant?.dockerCompose} value={selectedVariant?.dockerCompose}
className="font-mono" className="font-mono"
onChange={(value) => { onChange={(value) => {
setSelectedVariant({ setTemplateInfo({
...selectedVariant, ...templateInfo,
dockerCompose: value, ...(templateInfo?.details && {
details: {
...templateInfo.details,
dockerCompose: value,
},
}),
}); });
}} }}
/> />
@@ -416,17 +441,17 @@ export const StepTwo = ({
</div> </div>
<div className=""> <div className="">
<div className="flex justify-between"> <div className="flex justify-between">
<Button {selectedVariant && (
onClick={() => <Button
selectedVariant ? setSelectedVariant(undefined) : prevStep() onClick={() => {
} const { details, ...rest } = templateInfo;
variant="outline" setTemplateInfo(rest);
> }}
{selectedVariant ? "Change Variant" : "Back"} variant="outline"
</Button> >
<Button onClick={handleNext} disabled={!selectedVariant}> Change Variant
Next </Button>
</Button> )}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -3,6 +3,7 @@ import {
Dialog, Dialog,
DialogContent, DialogContent,
DialogDescription, DialogDescription,
DialogFooter,
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
@@ -18,11 +19,14 @@ import {
import { api } from "@/utils/api"; import { api } from "@/utils/api";
import { Bot } from "lucide-react"; import { Bot } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
import { useState } from "react"; import React, { useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { StepOne } from "./step-one"; import { StepOne } from "./step-one";
import { StepThree } from "./step-three"; import { StepThree } from "./step-three";
import { StepTwo } from "./step-two"; import { StepTwo } from "./step-two";
import { defineStepper } from "@stepperize/react";
import { Separator } from "@/components/ui/separator";
import { Button } from "@/components/ui/button";
interface EnvVariable { interface EnvVariable {
name: string; name: string;
@@ -34,17 +38,20 @@ interface Domain {
port: number; port: number;
serviceName: string; serviceName: string;
} }
interface Details {
name: string;
id: string;
description: string;
dockerCompose: string;
envVariables: EnvVariable[];
shortDescription: string;
domains: Domain[];
}
export interface TemplateInfo { export interface TemplateInfo {
userInput: string; userInput: string;
details?: { details?: Details | null;
name: string; suggestions?: Details[];
id: string;
description: string;
dockerCompose: string;
envVariables: EnvVariable[];
shortDescription: string;
domains: Domain[];
};
server?: { server?: {
serverId: string; serverId: string;
name: string; name: string;
@@ -56,17 +63,25 @@ const defaultTemplateInfo: TemplateInfo = {
aiId: "", aiId: "",
userInput: "", userInput: "",
server: undefined, server: undefined,
details: { details: null,
id: "", suggestions: [],
name: "",
description: "",
dockerCompose: "",
envVariables: [],
shortDescription: "",
domains: [],
},
}; };
export const { useStepper, steps, Scoped } = defineStepper(
{
id: "needs",
title: "Describe your needs",
},
{
id: "variant",
title: "Choose a Variant",
},
{
id: "review",
title: "Review and Finalize",
},
);
interface Props { interface Props {
projectId: string; projectId: string;
projectName?: string; projectName?: string;
@@ -74,32 +89,58 @@ interface Props {
export const TemplateGenerator = ({ projectId }: Props) => { export const TemplateGenerator = ({ projectId }: Props) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [step, setStep] = useState(1); const stepper = useStepper();
const { data: aiSettings } = api.ai.getAll.useQuery(); const { data: aiSettings } = api.ai.getAll.useQuery();
const { mutateAsync } = api.ai.deploy.useMutation(); const { mutateAsync } = api.ai.deploy.useMutation();
const [templateInfo, setTemplateInfo] = const [templateInfo, setTemplateInfo] =
useState<TemplateInfo>(defaultTemplateInfo); useState<TemplateInfo>(defaultTemplateInfo);
const utils = api.useUtils(); const utils = api.useUtils();
const totalSteps = 3;
const nextStep = () => setStep((prev) => Math.min(prev + 1, totalSteps));
const prevStep = () => setStep((prev) => Math.max(prev - 1, 1));
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen);
if (!newOpen) {
setStep(1);
setTemplateInfo(defaultTemplateInfo);
}
};
const haveAtleasOneProviderEnabled = aiSettings?.some( const haveAtleasOneProviderEnabled = aiSettings?.some(
(ai) => ai.isEnabled === true, (ai) => ai.isEnabled === true,
); );
const isDisabled = () => {
if (stepper.current.id === "needs") {
return !templateInfo.aiId || !templateInfo.userInput;
}
if (stepper.current.id === "variant") {
return !templateInfo?.details?.id;
}
return false;
};
const onSubmit = async () => {
await mutateAsync({
projectId,
id: templateInfo.details?.id || "",
name: templateInfo?.details?.name || "",
description: templateInfo?.details?.shortDescription || "",
dockerCompose: templateInfo?.details?.dockerCompose || "",
envVariables: (templateInfo?.details?.envVariables || [])
.map((env: any) => `${env.name}=${env.value}`)
.join("\n"),
domains: templateInfo?.details?.domains || [],
...(templateInfo.server?.serverId && {
serverId: templateInfo.server?.serverId || "",
}),
})
.then(async () => {
toast.success("Compose Created");
setOpen(false);
await utils.project.one.invalidate({
projectId,
});
})
.catch(() => {
toast.error("Error creating the compose");
});
};
return ( return (
<Dialog open={open} onOpenChange={handleOpenChange}> <Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger asChild className="w-full"> <DialogTrigger asChild className="w-full">
<DropdownMenuItem <DropdownMenuItem
className="w-full cursor-pointer space-x-3" className="w-full cursor-pointer space-x-3"
@@ -116,114 +157,173 @@ export const TemplateGenerator = ({ projectId }: Props) => {
Create a custom template based on your needs Create a custom template based on your needs
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div className="mt-4 "> <div className="grid gap-4">
{step === 1 && ( <div className="flex justify-between">
<> <h2 className="text-lg font-semibold">Steps</h2>
{!haveAtleasOneProviderEnabled && ( <div className="flex items-center gap-2">
<AlertBlock type="warning"> <span className="text-sm text-muted-foreground">
<div className="flex flex-col w-full"> Step {stepper.current.index + 1} of {steps.length}
<span>AI features are not enabled</span> </span>
<span> <div />
To use AI-powered template generation, please{" "} </div>
<Link </div>
href="/dashboard/settings/ai" <Scoped>
className="font-medium underline underline-offset-4" <nav aria-label="Checkout Steps" className="group my-4">
> <ol
enable AI in your settings className="flex items-center justify-between gap-2"
</Link> aria-orientation="horizontal"
. >
</span> {stepper.all.map((step, index, array) => (
</div> <React.Fragment key={step.id}>
</AlertBlock> <li className="flex items-center gap-4 flex-shrink-0">
)} <Button
type="button"
{haveAtleasOneProviderEnabled && role="tab"
aiSettings && variant={
aiSettings?.length > 0 && ( index <= stepper.current.index ? "secondary" : "ghost"
<div className="space-y-4">
<div className="flex flex-col gap-2">
<label
htmlFor="user-needs"
className="text-sm font-medium"
>
Select AI Provider
</label>
<Select
value={templateInfo.aiId}
onValueChange={(value) =>
setTemplateInfo((prev) => ({
...prev,
aiId: value,
}))
} }
aria-current={
stepper.current.id === step.id ? "step" : undefined
}
aria-posinset={index + 1}
aria-setsize={steps.length}
aria-selected={stepper.current.id === step.id}
className="flex size-10 items-center justify-center rounded-full border-2 border-border"
> >
<SelectTrigger> {index + 1}
<SelectValue placeholder="Select an AI provider" /> </Button>
</SelectTrigger> <span className="text-sm font-medium">{step.title}</span>
<SelectContent> </li>
{aiSettings.map((ai) => ( {index < array.length - 1 && (
<SelectItem key={ai.aiId} value={ai.aiId}> <Separator
{ai.name} ({ai.model}) className={`flex-1 ${
</SelectItem> index < stepper.current.index
))} ? "bg-primary"
</SelectContent> : "bg-muted"
</Select> }`}
</div>
{templateInfo.aiId && (
<StepOne
nextStep={nextStep}
setTemplateInfo={setTemplateInfo}
templateInfo={templateInfo}
/> />
)} )}
</div> </React.Fragment>
)} ))}
</> </ol>
)} </nav>
{step === 2 && ( {stepper.switch({
<StepTwo needs: () => (
nextStep={nextStep} <>
prevStep={prevStep} {!haveAtleasOneProviderEnabled && (
templateInfo={templateInfo} <AlertBlock type="warning">
setTemplateInfo={setTemplateInfo} <div className="flex flex-col w-full">
/> <span>AI features are not enabled</span>
)} <span>
{step === 3 && ( To use AI-powered template generation, please{" "}
<StepThree <Link
prevStep={prevStep} href="/dashboard/settings/ai"
templateInfo={templateInfo} className="font-medium underline underline-offset-4"
nextStep={nextStep} >
setTemplateInfo={setTemplateInfo} enable AI in your settings
onSubmit={async () => { </Link>
console.log("Submitting template:", templateInfo); .
await mutateAsync({ </span>
projectId, </div>
id: templateInfo.details?.id || "", </AlertBlock>
name: templateInfo?.details?.name || "", )}
description: templateInfo?.details?.shortDescription || "",
dockerCompose: templateInfo?.details?.dockerCompose || "", {haveAtleasOneProviderEnabled &&
envVariables: (templateInfo?.details?.envVariables || []) aiSettings &&
.map((env: any) => `${env.name}=${env.value}`) aiSettings?.length > 0 && (
.join("\n"), <div className="space-y-4">
domains: templateInfo?.details?.domains || [], <div className="flex flex-col gap-2">
...(templateInfo.server?.serverId && { <label
serverId: templateInfo.server?.serverId || "", htmlFor="user-needs"
}), className="text-sm font-medium"
}) >
.then(async () => { Select AI Provider
toast.success("Compose Created"); </label>
setOpen(false); <Select
await utils.project.one.invalidate({ value={templateInfo.aiId}
projectId, onValueChange={(value) =>
}); setTemplateInfo((prev) => ({
}) ...prev,
.catch(() => { aiId: value,
toast.error("Error creating the compose"); }))
}); }
}} >
/> <SelectTrigger>
)} <SelectValue placeholder="Select an AI provider" />
</SelectTrigger>
<SelectContent>
{aiSettings.map((ai) => (
<SelectItem key={ai.aiId} value={ai.aiId}>
{ai.name} ({ai.model})
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{templateInfo.aiId && (
<StepOne
setTemplateInfo={setTemplateInfo}
templateInfo={templateInfo}
/>
)}
</div>
)}
</>
),
variant: () => (
<StepTwo
templateInfo={templateInfo}
setTemplateInfo={setTemplateInfo}
/>
),
review: () => (
<StepThree
templateInfo={templateInfo}
setTemplateInfo={setTemplateInfo}
/>
),
})}
</Scoped>
</div> </div>
<DialogFooter>
<div className="flex items-center justify-between w-full">
<div className="flex items-center gap-2 w-full justify-end">
<Button
onClick={stepper.prev}
disabled={stepper.isFirst}
variant="secondary"
>
Back
</Button>
<Button
disabled={isDisabled()}
onClick={async () => {
if (stepper.current.id === "needs") {
setTemplateInfo((prev) => ({
...prev,
suggestions: [],
details: null,
}));
}
if (stepper.isLast) {
await onSubmit();
return;
}
stepper.next();
// if (stepper.isLast) {
// // setIsOpen(false);
// // push("/dashboard/projects");
// } else {
// stepper.next();
// }
}}
>
{stepper.isLast ? "Create" : "Next"}
</Button>
</div>
</div>
</DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
); );

View File

@@ -1,152 +1,152 @@
import { slugify } from "@/lib/slug"; import { slugify } from "@/lib/slug";
import { import {
adminProcedure, adminProcedure,
createTRPCRouter, createTRPCRouter,
protectedProcedure, protectedProcedure,
} from "@/server/api/trpc"; } from "@/server/api/trpc";
import { generatePassword } from "@/templates/utils"; import { generatePassword } from "@/templates/utils";
import { IS_CLOUD } from "@dokploy/server/constants"; import { IS_CLOUD } from "@dokploy/server/constants";
import { import {
apiCreateAi, apiCreateAi,
apiUpdateAi, apiUpdateAi,
deploySuggestionSchema, deploySuggestionSchema,
} from "@dokploy/server/db/schema/ai"; } from "@dokploy/server/db/schema/ai";
import { createDomain } from "@dokploy/server/index"; import { createDomain } from "@dokploy/server/index";
import { import {
deleteAiSettings, deleteAiSettings,
getAiSettingById, getAiSettingById,
getAiSettingsByAdminId, getAiSettingsByAdminId,
saveAiSettings, saveAiSettings,
suggestVariants, suggestVariants,
} from "@dokploy/server/services/ai"; } from "@dokploy/server/services/ai";
import { createComposeByTemplate } from "@dokploy/server/services/compose"; import { createComposeByTemplate } from "@dokploy/server/services/compose";
import { findProjectById } from "@dokploy/server/services/project"; import { findProjectById } from "@dokploy/server/services/project";
import { import {
addNewService, addNewService,
checkServiceAccess, checkServiceAccess,
} from "@dokploy/server/services/user"; } from "@dokploy/server/services/user";
import { TRPCError } from "@trpc/server"; import { TRPCError } from "@trpc/server";
import { z } from "zod"; import { z } from "zod";
export const aiRouter = createTRPCRouter({ export const aiRouter = createTRPCRouter({
one: protectedProcedure one: protectedProcedure
.input(z.object({ aiId: z.string() })) .input(z.object({ aiId: z.string() }))
.query(async ({ ctx, input }) => { .query(async ({ ctx, input }) => {
const aiSetting = await getAiSettingById(input.aiId); const aiSetting = await getAiSettingById(input.aiId);
if (aiSetting.adminId !== ctx.user.adminId) { if (aiSetting.adminId !== ctx.user.adminId) {
throw new TRPCError({ throw new TRPCError({
code: "UNAUTHORIZED", code: "UNAUTHORIZED",
message: "You don't have access to this AI configuration", message: "You don't have access to this AI configuration",
}); });
} }
return aiSetting; return aiSetting;
}), }),
create: adminProcedure.input(apiCreateAi).mutation(async ({ ctx, input }) => { create: adminProcedure.input(apiCreateAi).mutation(async ({ ctx, input }) => {
return await saveAiSettings(ctx.user.adminId, input); return await saveAiSettings(ctx.user.adminId, input);
}), }),
update: protectedProcedure update: protectedProcedure
.input(apiUpdateAi) .input(apiUpdateAi)
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
return await saveAiSettings(ctx.user.adminId, input); return await saveAiSettings(ctx.user.adminId, input);
}), }),
getAll: adminProcedure.query(async ({ ctx }) => { getAll: adminProcedure.query(async ({ ctx }) => {
return await getAiSettingsByAdminId(ctx.user.adminId); return await getAiSettingsByAdminId(ctx.user.adminId);
}), }),
get: protectedProcedure get: protectedProcedure
.input(z.object({ aiId: z.string() })) .input(z.object({ aiId: z.string() }))
.query(async ({ ctx, input }) => { .query(async ({ ctx, input }) => {
const aiSetting = await getAiSettingById(input.aiId); const aiSetting = await getAiSettingById(input.aiId);
if (aiSetting.adminId !== ctx.user.authId) { if (aiSetting.adminId !== ctx.user.authId) {
throw new TRPCError({ throw new TRPCError({
code: "UNAUTHORIZED", code: "UNAUTHORIZED",
message: "You don't have access to this AI configuration", message: "You don't have access to this AI configuration",
}); });
} }
return aiSetting; return aiSetting;
}), }),
delete: protectedProcedure delete: protectedProcedure
.input(z.object({ aiId: z.string() })) .input(z.object({ aiId: z.string() }))
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
const aiSetting = await getAiSettingById(input.aiId); const aiSetting = await getAiSettingById(input.aiId);
if (aiSetting.adminId !== ctx.user.adminId) { if (aiSetting.adminId !== ctx.user.adminId) {
throw new TRPCError({ throw new TRPCError({
code: "UNAUTHORIZED", code: "UNAUTHORIZED",
message: "You don't have access to this AI configuration", message: "You don't have access to this AI configuration",
}); });
} }
return await deleteAiSettings(input.aiId); return await deleteAiSettings(input.aiId);
}), }),
suggest: protectedProcedure suggest: protectedProcedure
.input( .input(
z.object({ z.object({
aiId: z.string(), aiId: z.string(),
input: z.string(), input: z.string(),
serverId: z.string().optional(), serverId: z.string().optional(),
}) }),
) )
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
try { try {
return await suggestVariants({ return await suggestVariants({
...input, ...input,
adminId: ctx.user.adminId, adminId: ctx.user.adminId,
}); });
} catch (error) { } catch (error) {
throw new TRPCError({ throw new TRPCError({
code: "BAD_REQUEST", code: "BAD_REQUEST",
message: error instanceof Error ? error?.message : `Error: ${error}`, message: error instanceof Error ? error?.message : `Error: ${error}`,
}); });
} }
}), }),
deploy: protectedProcedure deploy: protectedProcedure
.input(deploySuggestionSchema) .input(deploySuggestionSchema)
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
if (ctx.user.rol === "user") { if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.adminId, input.projectId, "create"); await checkServiceAccess(ctx.user.adminId, input.projectId, "create");
} }
if (IS_CLOUD && !input.serverId) { if (IS_CLOUD && !input.serverId) {
throw new TRPCError({ throw new TRPCError({
code: "UNAUTHORIZED", code: "UNAUTHORIZED",
message: "You need to use a server to create a compose", message: "You need to use a server to create a compose",
}); });
} }
const project = await findProjectById(input.projectId); const project = await findProjectById(input.projectId);
const projectName = slugify(`${project.name} ${input.id}`); const projectName = slugify(`${project.name} ${input.id}`);
console.log(input); console.log(input);
const compose = await createComposeByTemplate({ const compose = await createComposeByTemplate({
...input, ...input,
composeFile: input.dockerCompose, composeFile: input.dockerCompose,
env: input.envVariables, env: input.envVariables,
serverId: input.serverId, serverId: input.serverId,
name: input.name, name: input.name,
sourceType: "raw", sourceType: "raw",
appName: `${projectName}-${generatePassword(6)}`, appName: `${projectName}-${generatePassword(6)}`,
}); });
if (input.domains && input.domains?.length > 0) { if (input.domains && input.domains?.length > 0) {
for (const domain of input.domains) { for (const domain of input.domains) {
await createDomain({ await createDomain({
...domain, ...domain,
domainType: "compose", domainType: "compose",
certificateType: "none", certificateType: "none",
composeId: compose.composeId, composeId: compose.composeId,
}); });
} }
} }
if (ctx.user.rol === "user") { if (ctx.user.rol === "user") {
await addNewService(ctx.user.authId, compose.composeId); await addNewService(ctx.user.authId, compose.composeId);
} }
return null; return null;
}), }),
}); });