feat(ai): add configuration files support for AI template generation

- Enhance template generation with configFiles feature
- Update StepTwo and StepThree components to display and edit configuration files
- Modify AI router and schemas to support configuration file mounting
- Refine AI service prompt to provide stricter guidelines for config file usage
This commit is contained in:
Mauricio Siu
2025-03-02 01:54:39 -06:00
parent e7db3a196c
commit a8fc27e830
6 changed files with 137 additions and 28 deletions

View File

@@ -85,6 +85,22 @@ export const StepThree = ({ templateInfo }: StepProps) => {
)} )}
</ul> </ul>
</div> </div>
<div>
<h3 className="text-sm font-semibold">Configuration Files</h3>
<ul className="list-disc pl-5">
{templateInfo?.details?.configFiles.map((file, index) => (
<li key={index}>
<strong className="text-sm font-semibold">
{file.filePath}
</strong>
:
<span className="text-sm ml-2 text-muted-foreground">
{file.content}
</span>
</li>
))}
</ul>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -24,11 +24,7 @@ export interface StepProps {
setTemplateInfo: React.Dispatch<React.SetStateAction<TemplateInfo>>; setTemplateInfo: React.Dispatch<React.SetStateAction<TemplateInfo>>;
} }
export const StepTwo = ({ export const StepTwo = ({ templateInfo, setTemplateInfo }: StepProps) => {
stepper,
templateInfo,
setTemplateInfo,
}: StepProps) => {
const suggestions = templateInfo.suggestions || []; const suggestions = templateInfo.suggestions || [];
const selectedVariant = templateInfo.details; const selectedVariant = templateInfo.details;
const [showValues, setShowValues] = useState<Record<string, boolean>>({}); const [showValues, setShowValues] = useState<Record<string, boolean>>({});
@@ -52,7 +48,9 @@ export const StepTwo = ({
}); });
}) })
.catch((error) => { .catch((error) => {
toast.error("Error generating suggestions"); toast.error("Error generating suggestions", {
description: error.message,
});
}); });
}, [templateInfo.userInput]); }, [templateInfo.userInput]);
@@ -434,6 +432,78 @@ export const StepTwo = ({
</ScrollArea> </ScrollArea>
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
<AccordionItem value="mounts">
<AccordionTrigger>Configuration Files</AccordionTrigger>
<AccordionContent>
<ScrollArea className="w-full rounded-md border">
<div className="p-4 space-y-4">
{selectedVariant?.configFiles?.length > 0 ? (
<>
<div className="text-sm text-muted-foreground mb-4">
This template requires the following
configuration files to be mounted:
</div>
{selectedVariant.configFiles.map(
(config, index) => (
<div
key={index}
className="space-y-2 border rounded-lg p-4"
>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label className="text-primary">
{config.filePath}
</Label>
<p className="text-xs text-muted-foreground">
Will be mounted as: ../files
{config.filePath}
</p>
</div>
</div>
<CodeEditor
value={config.content}
className="font-mono"
onChange={(value) => {
if (!selectedVariant?.configFiles)
return;
const updatedConfigFiles = [
...selectedVariant.configFiles,
];
updatedConfigFiles[index] = {
filePath: config.filePath,
content: value,
};
setTemplateInfo({
...templateInfo,
...(templateInfo.details && {
details: {
...templateInfo.details,
configFiles: updatedConfigFiles,
},
}),
});
}}
/>
</div>
),
)}
</>
) : (
<div className="text-center text-muted-foreground py-8">
<p>
This template doesn't require any configuration
files.
</p>
<p className="text-sm mt-2">
All necessary configurations are handled through
environment variables.
</p>
</div>
)}
</div>
</ScrollArea>
</AccordionContent>
</AccordionItem>
</Accordion> </Accordion>
</ScrollArea> </ScrollArea>
</> </>

View File

@@ -47,7 +47,14 @@ interface Details {
envVariables: EnvVariable[]; envVariables: EnvVariable[];
shortDescription: string; shortDescription: string;
domains: Domain[]; domains: Domain[];
configFiles: Mount[];
} }
interface Mount {
filePath: string;
content: string;
}
export interface TemplateInfo { export interface TemplateInfo {
userInput: string; userInput: string;
details?: Details | null; details?: Details | null;
@@ -126,6 +133,7 @@ export const TemplateGenerator = ({ projectId }: Props) => {
...(templateInfo.server?.serverId && { ...(templateInfo.server?.serverId && {
serverId: templateInfo.server?.serverId || "", serverId: templateInfo.server?.serverId || "",
}), }),
configFiles: templateInfo?.details?.configFiles || [],
}) })
.then(async () => { .then(async () => {
toast.success("Compose Created"); toast.success("Compose Created");

View File

@@ -11,7 +11,7 @@ import {
apiUpdateAi, apiUpdateAi,
deploySuggestionSchema, deploySuggestionSchema,
} from "@dokploy/server/db/schema/ai"; } from "@dokploy/server/db/schema/ai";
import { createDomain } from "@dokploy/server/index"; import { createDomain, createMount } from "@dokploy/server/index";
import { import {
deleteAiSettings, deleteAiSettings,
getAiSettingById, getAiSettingById,
@@ -126,8 +126,6 @@ export const aiRouter = createTRPCRouter({
const projectName = slugify(`${project.name} ${input.id}`); const projectName = slugify(`${project.name} ${input.id}`);
console.log(input);
const compose = await createComposeByTemplate({ const compose = await createComposeByTemplate({
...input, ...input,
composeFile: input.dockerCompose, composeFile: input.dockerCompose,
@@ -136,6 +134,7 @@ export const aiRouter = createTRPCRouter({
name: input.name, name: input.name,
sourceType: "raw", sourceType: "raw",
appName: `${projectName}-${generatePassword(6)}`, appName: `${projectName}-${generatePassword(6)}`,
isolatedDeployment: true,
}); });
if (input.domains && input.domains?.length > 0) { if (input.domains && input.domains?.length > 0) {
@@ -148,6 +147,18 @@ export const aiRouter = createTRPCRouter({
}); });
} }
} }
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") { if (ctx.user.rol === "member") {
await addNewService( await addNewService(

View File

@@ -71,4 +71,12 @@ export const deploySuggestionSchema = z.object({
}), }),
) )
.optional(), .optional(),
configFiles: z
.array(
z.object({
filePath: z.string().min(1),
content: z.string().min(1),
}),
)
.optional(),
}); });

View File

@@ -153,25 +153,21 @@ export const suggestVariants = async ({
3. Don't set container_name field in services 3. Don't set container_name field in services
4. Don't set version field in the docker compose 4. Don't set version field in the docker compose
5. Don't set ports like 'ports: 3000:3000', use 'ports: "3000"' instead 5. Don't set ports like 'ports: 3000:3000', use 'ports: "3000"' instead
6. Use dokploy-network in all services 6. If a service depends on a database or other service, INCLUDE that service in the docker-compose
7. Add dokploy-network at the end and mark it as external: true 7. Make sure all required services are defined in the docker-compose
8. If a service depends on a database or other service, INCLUDE that service in the docker-compose
9. Make sure all required services are defined in the docker-compose
Volume Mounting Rules: Volume Mounting and Configuration Rules:
1. All file mounts in volumes section MUST use "../files/" prefix 1. DO NOT create configuration files unless the service CANNOT work without them
2. NEVER use absolute paths or direct host paths 2. Most services can work with just environment variables - USE THEM FIRST
3. Format should be: "../files/folder:/container/path" 3. Ask yourself: "Can this be configured with an environment variable instead?"
4. If a service needs configuration files, they should be defined in configFiles array and referenced in volumes 4. If and ONLY IF a config file is absolutely required:
5. Example: if you define a config file with filePath: "/nginx/nginx.conf", reference it in volumes as "../files/nginx/nginx.conf:/etc/nginx/nginx.conf" - Keep it minimal with only critical settings
- Use "../files/" prefix for all mounts
- Format: "../files/folder:/container/path"
Configuration Files Rules: 5. DO NOT add configuration files for:
1. If a service needs configuration files, include them in the configFiles array - Default configurations that work out of the box
2. Each config file should have: - Settings that can be handled by environment variables
- content: The actual content of the configuration file - Proxy or routing configurations (these are handled elsewhere)
- filePath: The relative path where the file should be stored (e.g., "/nginx/nginx.conf")
3. Make sure to reference these files correctly in the docker-compose volumes section
Environment Variables Rules: Environment Variables Rules:
1. For the envVariables array, provide ACTUAL example values, not placeholders 1. For the envVariables array, provide ACTUAL example values, not placeholders
@@ -186,7 +182,7 @@ export const suggestVariants = async ({
- host: the domain name for the service in format: {service-name}-{random-3-chars-hex}-${ip ? ip.replaceAll(".", "-") : ""}.traefik.me - host: the domain name for the service in format: {service-name}-{random-3-chars-hex}-${ip ? ip.replaceAll(".", "-") : ""}.traefik.me
- port: the internal port the service runs on - port: the internal port the service runs on
- serviceName: the name of the service in the docker-compose - serviceName: the name of the service in the docker-compose
2. Make sure the service is properly configured in the docker-compose to work with the specified port 2. Make sure the service is properly configured to work with the specified port
Project details: Project details:
${suggestion?.description} ${suggestion?.description}