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();