Spaces:
Paused
Paused
File size: 7,061 Bytes
5a81b95 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | import fs from 'fs/promises';
import path from 'path';
import 'dotenv/config'; // Loader variabler fra .env
interface AIResponse {
code: string;
modelUsed: string;
}
export class EvolutionService {
// Stier til mapperne
private liveDir = path.resolve(__dirname, '../../../../apps/matrix-frontend/src/widgets/generated');
private stagingDir = path.resolve(__dirname, '../../../../apps/matrix-frontend/src/widgets/staging');
// SYSTEM PROMPT: WidgeTDC Design Systemet (Opdateret med strengere regler)
private systemPrompt = `
Du er "The Architect", en elite React-udvikler for WidgeTDC platformen.
Din opgave er at generere en moderne, responsiv Widget baseret på brugerens prompt.
DESIGN REGLER (SKAL OVERHOLDES):
1. **Glassmorphism:** Brug mørke baggrunde med transparens (f.eks. 'bg-[#051e3c]/90', 'border-white/10', 'backdrop-blur-md').
2. **Icons:** Brug KUN 'lucide-react' til ikoner.
3. **Layout:** Widgetten skal fylde 'h-full w-full' og håndtere overflow pænt.
4. **Interactivity:** Brug React hooks (useState, useEffect) til at gøre den levende.
5. **No Placeholders:** Brug ALDRIG 'Lorem Ipsum' eller 'Content here'. Generer realistisk mock-data hvis nødvendigt.
6. **Robustness:** Inkluder altid fejlhåndtering i komponenten.
TEKNISKE KRAV:
1. Output KUN ren kode. Ingen markdown blokke (\`\`\`), ingen forklaringer før/efter.
2. Komponenten skal eksporteres som 'default'.
3. Importer React korrekt.
`;
// --- HOVEDMETODE: GENERER TIL STAGING ---
async generateWidget(userPrompt: string): Promise<{ success: boolean; filename?: string; code?: string; error?: string; model?: string }> {
try {
console.log(`[ARCHITECT] 🧠 Modtog ordre: "${userPrompt.substring(0, 50)}..."`);
const componentName = 'DynamicWidget_' + Date.now();
const filename = `${componentName}.tsx`;
// 1. KALD AI (Smart routing)
const aiResult = await this.callSmartLLM(userPrompt, componentName);
let rawCode = aiResult.code;
// 2. RENS KODEN (Fjern markdown hvis AI'en glemte reglerne)
rawCode = rawCode.replace(/```tsx/g, '').replace(/```typescript/g, '').replace(/```/g, '').trim();
// Sikkerhedsnet: Tjek at koden ser valid ud
if (!rawCode.includes('export default')) {
throw new Error("AI genererede ugyldig kode (manglede export default)");
}
// 3. GEM TIL STAGING (Klar til review)
await fs.mkdir(this.stagingDir, { recursive: true });
await fs.writeFile(path.join(this.stagingDir, filename), rawCode);
console.log(`[ARCHITECT] ✨ Kode genereret med ${aiResult.modelUsed}: ${filename}`);
return { success: true, filename, code: rawCode, model: aiResult.modelUsed };
} catch (error: any) {
console.error("[ARCHITECT] ❌ Critical Failure:", error);
return { success: false, error: error.message };
}
}
// --- INTELLIGENT LLM ROUTER ---
private async callSmartLLM(userPrompt: string, componentName: string): Promise<AIResponse> {
const anthropicKey = process.env.ANTHROPIC_API_KEY;
const deepseekKey = process.env.DEEPSEEK_API_KEY || process.env.OPENAI_API_KEY;
// Debug: Tjek om nøgler findes (uden at logge dem)
if (!anthropicKey && !deepseekKey) {
console.error("[ARCHITECT] INGEN API NØGLER FUNDET I .env!");
throw new Error("Ingen API nøgler konfigureret. Tjek .env filen i backend.");
}
const fullPrompt = `Lav en React komponent kaldet '${componentName}'.\nFunktionalitet: ${userPrompt}`;
// OPTION A: ANTHROPIC (Claude 3.5 Sonnet - Foretrukket til kode)
if (anthropicKey) {
try {
console.log("[ARCHITECT] Forbinder til Anthropic (Claude 3.5 Sonnet)...");
const response = await fetch('[https://api.anthropic.com/v1/messages](https://api.anthropic.com/v1/messages)', {
method: 'POST',
headers: {
'x-api-key': anthropicKey,
'anthropic-version': '2023-06-01',
'content-type': 'application/json'
},
body: JSON.stringify({
model: 'claude-3-5-sonnet-20240620',
max_tokens: 4000,
system: this.systemPrompt,
messages: [{ role: 'user', content: fullPrompt }]
})
});
if (!response.ok) {
const errText = await response.text();
throw new Error(`Anthropic API Error: ${errText}`);
}
const data: any = await response.json();
return { code: data.content[0].text, modelUsed: 'Claude 3.5 Sonnet' };
} catch (err: any) {
console.warn("[ARCHITECT] Anthropic fejlede, forsøger fallback...", err.message);
// Fortsæt til fallback hvis muligt...
}
}
// OPTION B: DEEPSEEK / OPENAI (Fallback)
if (deepseekKey) {
console.log("[ARCHITECT] Forbinder til DeepSeek/OpenAI Compatible...");
const baseURL = process.env.DEEPSEEK_BASE_URL || '[https://api.deepseek.com/v1](https://api.deepseek.com/v1)';
const response = await fetch(`${baseURL}/chat/completions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${deepseekKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: process.env.DEEPSEEK_MODEL || 'deepseek-coder',
messages: [
{ role: 'system', content: this.systemPrompt },
{ role: 'user', content: fullPrompt }
],
temperature: 0.2
})
});
if (!response.ok) {
const errText = await response.text();
throw new Error(`DeepSeek/OpenAI API Error: ${errText}`);
}
const data: any = await response.json();
return { code: data.choices[0].message.content, modelUsed: 'DeepSeek/OpenAI' };
}
throw new Error("Alle AI providers fejlede. Tjek internetforbindelse og API status.");
}
// --- DEPLOYMENT LOGIK ---
async promoteWidget(filename: string): Promise<{ success: boolean; error?: string }> {
try {
const sourcePath = path.join(this.stagingDir, filename);
const destPath = path.join(this.liveDir, filename);
// Tjek om staging filen eksisterer
try {
await fs.access(sourcePath);
} catch {
throw new Error(`Kan ikke finde filen ${filename} i staging. Måske er den allerede deployet?`);
}
// Flyt filen
await fs.rename(sourcePath, destPath);
console.log(`[ARCHITECT] 🚀 DEPLOYED TO PRODUCTION: ${filename}`);
return { success: true };
} catch (error: any) {
console.error("[ARCHITECT] Deployment fejl:", error);
return { success: false, error: error.message };
}
}
async discardWidget(filename: string) {
try {
await fs.unlink(path.join(this.stagingDir, filename));
console.log(`[ARCHITECT] 🗑️ Kasseret: ${filename}`);
return { success: true };
} catch (error) {
return { success: false };
}
}
}
export const evolutionService = new EvolutionService(); |