import { slugify } from "@/lib/slug"; import { adminProcedure, createTRPCRouter, protectedProcedure, } from "@/server/api/trpc"; import { generatePassword } from "@/templates/utils"; import { IS_CLOUD } from "@dokploy/server/constants"; import { apiCreateAi, apiUpdateAi, deploySuggestionSchema, } from "@dokploy/server/db/schema/ai"; import { createDomain, createMount } from "@dokploy/server/index"; import { deleteAiSettings, getAiSettingById, getAiSettingsByOrganizationId, saveAiSettings, suggestVariants, } from "@dokploy/server/services/ai"; import { createComposeByTemplate } from "@dokploy/server/services/compose"; import { findProjectById } from "@dokploy/server/services/project"; import { addNewService, checkServiceAccess, } from "@dokploy/server/services/user"; import { getProviderHeaders, type Model, } from "@dokploy/server/utils/ai/select-ai-provider"; import { TRPCError } from "@trpc/server"; import { z } from "zod"; export const aiRouter = createTRPCRouter({ one: protectedProcedure .input(z.object({ aiId: z.string() })) .query(async ({ ctx, input }) => { const aiSetting = await getAiSettingById(input.aiId); if (aiSetting.organizationId !== ctx.session.activeOrganizationId) { throw new TRPCError({ code: "UNAUTHORIZED", message: "You don't have access to this AI configuration", }); } return aiSetting; }), getModels: protectedProcedure .input(z.object({ apiUrl: z.string().min(1), apiKey: z.string().min(1) })) .query(async ({ input }) => { try { const headers = getProviderHeaders(input.apiUrl, input.apiKey); const response = await fetch(`${input.apiUrl}/models`, { headers }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Failed to fetch models: ${errorText}`); } const res = await response.json(); if (Array.isArray(res)) { return res.map((model) => ({ id: model.id || model.name, object: "model", created: Date.now(), owned_by: "provider", })); } if (res.models) { return res.models.map((model: any) => ({ id: model.id || model.name, object: "model", created: Date.now(), owned_by: "provider", })) as Model[]; } if (res.data) { return res.data as Model[]; } const possibleModels = (Object.values(res).find(Array.isArray) as any[]) || []; return possibleModels.map((model) => ({ id: model.id || model.name, object: "model", created: Date.now(), owned_by: "provider", })) as Model[]; } catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error?.message : `Error: ${error}`, }); } }), create: adminProcedure.input(apiCreateAi).mutation(async ({ ctx, input }) => { return await saveAiSettings(ctx.session.activeOrganizationId, input); }), update: protectedProcedure .input(apiUpdateAi) .mutation(async ({ ctx, input }) => { return await saveAiSettings(ctx.session.activeOrganizationId, input); }), getAll: adminProcedure.query(async ({ ctx }) => { return await getAiSettingsByOrganizationId( ctx.session.activeOrganizationId, ); }), get: protectedProcedure .input(z.object({ aiId: z.string() })) .query(async ({ ctx, input }) => { const aiSetting = await getAiSettingById(input.aiId); if (aiSetting.organizationId !== ctx.session.activeOrganizationId) { throw new TRPCError({ code: "UNAUTHORIZED", message: "You don't have access to this AI configuration", }); } return aiSetting; }), delete: protectedProcedure .input(z.object({ aiId: z.string() })) .mutation(async ({ ctx, input }) => { const aiSetting = await getAiSettingById(input.aiId); if (aiSetting.organizationId !== ctx.session.activeOrganizationId) { throw new TRPCError({ code: "UNAUTHORIZED", message: "You don't have access to this AI configuration", }); } return await deleteAiSettings(input.aiId); }), suggest: protectedProcedure .input( z.object({ aiId: z.string(), input: z.string(), serverId: z.string().optional(), }), ) .mutation(async ({ ctx, input }) => { try { return await suggestVariants({ ...input, organizationId: ctx.session.activeOrganizationId, }); } catch (error) { throw new TRPCError({ code: "BAD_REQUEST", message: error instanceof Error ? error?.message : `Error: ${error}`, }); } }), deploy: protectedProcedure .input(deploySuggestionSchema) .mutation(async ({ ctx, input }) => { if (ctx.user.rol === "member") { await checkServiceAccess( ctx.session.activeOrganizationId, input.projectId, "create", ); } if (IS_CLOUD && !input.serverId) { throw new TRPCError({ code: "UNAUTHORIZED", message: "You need to use a server to create a compose", }); } const project = await findProjectById(input.projectId); const projectName = slugify(`${project.name} ${input.id}`); const compose = await createComposeByTemplate({ ...input, composeFile: input.dockerCompose, env: input.envVariables, serverId: input.serverId, name: input.name, sourceType: "raw", appName: `${projectName}-${generatePassword(6)}`, isolatedDeployment: true, }); if (input.domains && input.domains?.length > 0) { for (const domain of input.domains) { await createDomain({ ...domain, domainType: "compose", certificateType: "none", composeId: compose.composeId, }); } } if (input.configFiles && input.configFiles?.length > 0) { for (const mount of input.configFiles) { await createMount({ filePath: mount.filePath, mountPath: "", content: mount.content, serviceId: compose.composeId, serviceType: "compose", type: "file", }); } } if (ctx.user.rol === "member") { await addNewService( ctx.session.activeOrganizationId, ctx.user.ownerId, compose.composeId, ); } return null; }), });