998 lines
33 KiB
JavaScript
998 lines
33 KiB
JavaScript
// IT Nexus Dev Team — Lokaler KI-Entwickler-Agent
|
||
// Start: node devteam.js
|
||
// Dann: http://localhost:4242
|
||
|
||
const http = require('http');
|
||
const https = require('https');
|
||
|
||
const API_KEY = process.env.ANTHROPIC_API_KEY || '';
|
||
const PORT = 4242;
|
||
|
||
// ─── IT Nexus Kontext für alle Agenten ────────────────────────────────────────
|
||
const IT_NEXUS_CONTEXT = `
|
||
# IT Nexus – Vollständiger Stack-Kontext
|
||
|
||
## Tech Stack
|
||
- Backend: Node.js 18 + Express 4 + SQLite (better-sqlite3, SYNCHRON - kein await!)
|
||
- Frontend: React 18 (Create React App) + Custom CSS (Glassmorphism)
|
||
- Auth: JWT (jsonwebtoken) + bcryptjs
|
||
- KI: @anthropic-ai/sdk (Claude claude-sonnet-4-6)
|
||
- Deployment: Docker Compose + nginx auf LXC CT 102 (192.168.0.194)
|
||
|
||
## Dateistruktur Backend
|
||
\`\`\`
|
||
backend/src/
|
||
server.js – Express Setup, alle app.use() Registrierungen
|
||
db/seed.js – Alle DB-Migrations (jede in try/catch, idempotent!)
|
||
config/database.js – SQLite Verbindung via better-sqlite3
|
||
middleware/
|
||
auth.js – authenticateToken (JWT prüfen)
|
||
roleCheck.js – requireAdmin, requireStaff Middleware
|
||
errorHandler.js – asyncHandler(fn) Wrapper für alle Controller
|
||
controllers/ – Business Logic, eine Datei pro Feature
|
||
models/ – SQLite Queries, eine Datei pro Entity
|
||
routes/ – Express Router, eine Datei pro Feature
|
||
services/ – Externe APIs (Graph API, Proxmox, Anthropic)
|
||
\`\`\`
|
||
|
||
## Dateistruktur Frontend
|
||
\`\`\`
|
||
frontend/src/
|
||
pages/ – Alle Seiten (NamingConvention: XxxPage.jsx)
|
||
components/common/
|
||
AppLayout.jsx – Wrapper mit Sidebar + Topbar
|
||
Sidebar.jsx – Navigation
|
||
Topbar.jsx – Header
|
||
context/AuthContext.jsx – Auth State, user Objekt
|
||
services/api.js – Axios Instance mit Interceptors (IMMER verwenden!)
|
||
\`\`\`
|
||
|
||
## Kritische Konventionen Backend
|
||
1. DB-Migration: Jedes ALTER TABLE / CREATE TABLE einzeln in try/catch in seed.js
|
||
2. Controller immer mit asyncHandler wrappen: const foo = asyncHandler(async (req, res) => {...})
|
||
3. Statische Routen VOR /:id definieren (z.B. /statistics vor /:id)
|
||
4. better-sqlite3 ist SYNCHRON: db.prepare('...').get() – KEIN await!
|
||
5. Exports am Ende: module.exports = { foo, bar }
|
||
|
||
## Kritische Konventionen Frontend
|
||
1. IMMER import api from '../../services/api' – NIEMALS axios direkt importieren
|
||
2. IMMER user.role_name – NIEMALS user.role (falsches Feld!)
|
||
3. CSS Variablen verwenden:
|
||
- Text: var(--text-primary), var(--text-secondary), var(--text-muted)
|
||
- Hintergrund: var(--bg-card), var(--bg-secondary), var(--border-color)
|
||
- Glassmorphism: backdrop-filter: blur(20px), rgba() Hintergründe
|
||
4. Rollen: super_admin, admin, support, bearbeiter, benutzer, hr_personal, buchhaltung
|
||
5. Neue Seite in AppLayout einbinden (Route in App.jsx)
|
||
|
||
## Deploy-Workflow
|
||
\`\`\`
|
||
scp datei.js root@192.168.0.194:/opt/it-nexus/backend/src/...
|
||
docker cp /opt/it-nexus/backend/src/.../datei.js fido-backend:/app/src/.../
|
||
docker restart fido-backend
|
||
# Frontend rebuild nur nötig wenn neue Seite/Komponente:
|
||
docker compose build frontend && docker compose up -d frontend
|
||
\`\`\`
|
||
|
||
## Beispiel: Neue Feature-Struktur
|
||
Backend: controller + model + route + seed.js Migration + server.js Route registrieren
|
||
Frontend: XxxPage.jsx + xxxService.js + Route in App.jsx + Link in Sidebar.jsx
|
||
`;
|
||
|
||
// ─── Agenten Definitionen ──────────────────────────────────────────────────────
|
||
const AGENTS = {
|
||
pm: {
|
||
name: 'Project Manager',
|
||
emoji: '📋',
|
||
color: '#6366f1',
|
||
system: `Du bist der Project Manager des IT Nexus Entwicklerteams. Du koordinierst alle anderen Agenten.
|
||
|
||
${IT_NEXUS_CONTEXT}
|
||
|
||
Deine Aufgabe:
|
||
1. Analysiere die Anfrage des Benutzers
|
||
2. Erstelle einen klaren Implementierungsplan
|
||
3. Bestimme welche Spezialisten benötigt werden
|
||
4. Fasse am Ende alles zusammen
|
||
|
||
Antworte auf Deutsch. Sei konkret und strukturiert. Nutze Markdown.
|
||
Erkläre kurz was getan werden muss, dann liste die benötigten Schritte auf.`
|
||
},
|
||
|
||
architect: {
|
||
name: 'Architect',
|
||
emoji: '🏗️',
|
||
color: '#f59e0b',
|
||
system: `Du bist der System-Architekt des IT Nexus Entwicklerteams.
|
||
|
||
${IT_NEXUS_CONTEXT}
|
||
|
||
Deine Aufgabe:
|
||
- Entscheide über DB-Schema (SQLite Spalten, Typen, Constraints)
|
||
- Entscheide über API-Struktur (Endpoints, HTTP-Methoden, Auth-Level)
|
||
- Entscheide über Datenfluss zwischen Frontend und Backend
|
||
- Identifiziere Abhängigkeiten zu bestehenden Features
|
||
|
||
Antworte auf Deutsch. Gib konkrete technische Entscheidungen.
|
||
Format: DB-Schema → API-Endpoints → Abhängigkeiten → Besonderheiten`
|
||
},
|
||
|
||
backend: {
|
||
name: 'Backend Dev',
|
||
emoji: '⚙️',
|
||
color: '#10b981',
|
||
system: `Du bist der Backend-Entwickler des IT Nexus Entwicklerteams. Du schreibst den kompletten Backend-Code.
|
||
|
||
${IT_NEXUS_CONTEXT}
|
||
|
||
Deine Aufgabe:
|
||
- Schreibe vollständige, lauffähige Node.js Dateien
|
||
- Folge exakt den Konventionen (asyncHandler, better-sqlite3 synchron, etc.)
|
||
- Jede Datei vollständig mit korrekten Imports und Exports
|
||
- DB-Migrations in seed.js Format
|
||
|
||
Format pro Datei:
|
||
### Datei: \`backend/src/pfad/dateiname.js\`
|
||
\`\`\`javascript
|
||
// kompletter Code
|
||
\`\`\`
|
||
|
||
Antworte auf Deutsch für Erklärungen, Code auf Englisch.`
|
||
},
|
||
|
||
frontend: {
|
||
name: 'Frontend Dev',
|
||
emoji: '🎨',
|
||
color: '#3b82f6',
|
||
system: `Du bist der Frontend-Entwickler des IT Nexus Entwicklerteams. Du schreibst den kompletten Frontend-Code.
|
||
|
||
${IT_NEXUS_CONTEXT}
|
||
|
||
Deine Aufgabe:
|
||
- Schreibe vollständige React Komponenten (.jsx Dateien)
|
||
- Glassmorphism Design (konsistent mit bestehendem IT Nexus Style)
|
||
- IMMER api.get/post aus services/api.js verwenden
|
||
- IMMER user.role_name verwenden
|
||
- CSS inline mit var(--*) Variablen
|
||
|
||
Format pro Datei:
|
||
### Datei: \`frontend/src/pfad/DateiName.jsx\`
|
||
\`\`\`jsx
|
||
// kompletter Code
|
||
\`\`\`
|
||
|
||
Antworte auf Deutsch für Erklärungen, Code auf Englisch.`
|
||
},
|
||
|
||
senior: {
|
||
name: 'Senior Dev',
|
||
emoji: '🔍',
|
||
color: '#ef4444',
|
||
system: `Du bist der Senior Developer und Code Reviewer des IT Nexus Entwicklerteams.
|
||
|
||
${IT_NEXUS_CONTEXT}
|
||
|
||
Deine Aufgabe:
|
||
- Reviewe den generierten Code auf Bugs, Sicherheitsprobleme, Konventionsverletzungen
|
||
- Prüfe ob alle Konventionen eingehalten wurden (user.role_name, api.js, asyncHandler, etc.)
|
||
- Prüfe auf fehlende Error Handling
|
||
- Prüfe auf fehlende Route-Registrierungen in server.js
|
||
- Prüfe auf fehlende Sidebar-Links
|
||
- Gib konkrete Fixes wenn nötig
|
||
|
||
Format:
|
||
✅ Was gut ist
|
||
⚠️ Was fehlt / falsch ist + Fix
|
||
📝 Deploy-Befehl am Ende
|
||
|
||
Antworte auf Deutsch.`
|
||
},
|
||
|
||
qa: {
|
||
name: 'QA Engineer',
|
||
emoji: '🧪',
|
||
color: '#8b5cf6',
|
||
system: `Du bist der QA Engineer des IT Nexus Entwicklerteams.
|
||
|
||
${IT_NEXUS_CONTEXT}
|
||
|
||
Deine Aufgabe:
|
||
- Identifiziere potenzielle Edge Cases
|
||
- Schreibe manuelle Testschritte die der Entwickler ausführen soll
|
||
- Prüfe ob alle API-Endpoints abgesichert sind (Auth)
|
||
- Prüfe Fehlerszenarien (leere Daten, falscher Input, fehlende Rechte)
|
||
- Schreibe wenn möglich einen Playwright-Test
|
||
|
||
Format:
|
||
📋 Manuelle Tests (nummeriert)
|
||
🔒 Security-Checks
|
||
⚡ Edge Cases
|
||
(Optional) Playwright Test Code
|
||
|
||
Antworte auf Deutsch.`
|
||
}
|
||
};
|
||
|
||
// ─── Anthropic API Aufruf ──────────────────────────────────────────────────────
|
||
function callClaude(systemPrompt, messages, onChunk) {
|
||
return new Promise((resolve, reject) => {
|
||
const body = JSON.stringify({
|
||
model: 'claude-sonnet-4-6',
|
||
max_tokens: 8096,
|
||
system: systemPrompt,
|
||
messages,
|
||
stream: true
|
||
});
|
||
|
||
const req = https.request({
|
||
hostname: 'api.anthropic.com',
|
||
path: '/v1/messages',
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'x-api-key': API_KEY,
|
||
'anthropic-version': '2023-06-01',
|
||
'Content-Length': Buffer.byteLength(body)
|
||
}
|
||
}, (res) => {
|
||
let full = '';
|
||
res.on('data', chunk => {
|
||
const lines = chunk.toString().split('\n');
|
||
for (const line of lines) {
|
||
if (line.startsWith('data: ')) {
|
||
try {
|
||
const d = JSON.parse(line.slice(6));
|
||
if (d.type === 'content_block_delta' && d.delta?.text) {
|
||
full += d.delta.text;
|
||
onChunk(d.delta.text);
|
||
}
|
||
} catch {}
|
||
}
|
||
}
|
||
});
|
||
res.on('end', () => resolve(full));
|
||
res.on('error', reject);
|
||
});
|
||
req.on('error', reject);
|
||
req.write(body);
|
||
req.end();
|
||
});
|
||
}
|
||
|
||
// ─── Orchestrierung ────────────────────────────────────────────────────────────
|
||
async function runTeam(task, send) {
|
||
const context = [];
|
||
|
||
const runAgent = async (agentId, userMsg) => {
|
||
const agent = AGENTS[agentId];
|
||
send({ type: 'agent_start', agent: agentId, name: agent.name, emoji: agent.emoji, color: agent.color });
|
||
|
||
const msgs = [...context, { role: 'user', content: userMsg }];
|
||
let full = '';
|
||
await callClaude(agent.system, msgs, chunk => {
|
||
full += chunk;
|
||
send({ type: 'chunk', agent: agentId, text: chunk });
|
||
});
|
||
|
||
send({ type: 'agent_done', agent: agentId });
|
||
context.push({ role: 'user', content: userMsg });
|
||
context.push({ role: 'assistant', content: full });
|
||
return full;
|
||
};
|
||
|
||
// 1. PM analysiert
|
||
const pmResult = await runAgent('pm',
|
||
`Neue Aufgabe für das IT Nexus Entwicklerteam:\n\n"${task}"\n\nErstelle einen kurzen Implementierungsplan.`
|
||
);
|
||
|
||
// 2. Architect entscheidet
|
||
await runAgent('architect',
|
||
`Aufgabe: "${task}"\n\nPM-Plan:\n${pmResult}\n\nGib deine technischen Entscheidungen für DB-Schema und API-Struktur.`
|
||
);
|
||
|
||
// 3. Backend Dev schreibt Code
|
||
const backendResult = await runAgent('backend',
|
||
`Aufgabe: "${task}"\n\nSchreibe den vollständigen Backend-Code (Controller, Model, Route, seed.js Migration). Alle Dateien komplett.`
|
||
);
|
||
|
||
// 4. Frontend Dev schreibt Code
|
||
const frontendResult = await runAgent('frontend',
|
||
`Aufgabe: "${task}"\n\nSchreibe den vollständigen Frontend-Code (Page, Service). Alle Dateien komplett.`
|
||
);
|
||
|
||
// 5. Senior Dev reviewt
|
||
await runAgent('senior',
|
||
`Reviewe diesen Code:\n\n**Backend:**\n${backendResult}\n\n**Frontend:**\n${frontendResult}\n\nFinde Bugs, Konventionsverletzungen, fehlende Teile.`
|
||
);
|
||
|
||
// 6. QA testet
|
||
await runAgent('qa',
|
||
`Aufgabe: "${task}"\n\nErstelle Testschritte und prüfe Edge Cases für die implementierte Funktion.`
|
||
);
|
||
|
||
send({ type: 'done' });
|
||
}
|
||
|
||
// ─── HTTP Server ───────────────────────────────────────────────────────────────
|
||
const HTML = `<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>IT Nexus Dev Team</title>
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body {
|
||
font-family: 'Segoe UI', system-ui, sans-serif;
|
||
background: #0a0f1e;
|
||
color: #e2e8f0;
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* Header */
|
||
.header {
|
||
background: rgba(15,23,42,0.95);
|
||
border-bottom: 1px solid rgba(99,102,241,0.3);
|
||
padding: 14px 24px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
flex-shrink: 0;
|
||
}
|
||
.header-logo {
|
||
width: 36px; height: 36px;
|
||
background: linear-gradient(135deg, #6366f1, #8b5cf6);
|
||
border-radius: 10px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 18px;
|
||
}
|
||
.header h1 { font-size: 18px; font-weight: 700; color: #fff; }
|
||
.header p { font-size: 12px; color: #64748b; margin-top: 1px; }
|
||
.status-dot {
|
||
width: 8px; height: 8px; border-radius: 50%;
|
||
background: #10b981; margin-left: auto;
|
||
box-shadow: 0 0 8px #10b981;
|
||
animation: pulse 2s infinite;
|
||
}
|
||
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.4} }
|
||
|
||
/* Layout */
|
||
.main { display: flex; flex: 1; overflow: hidden; }
|
||
|
||
/* Sidebar: Team */
|
||
.sidebar {
|
||
width: 200px;
|
||
background: rgba(15,23,42,0.8);
|
||
border-right: 1px solid rgba(255,255,255,0.06);
|
||
padding: 16px 12px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
flex-shrink: 0;
|
||
}
|
||
.sidebar-title {
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
color: #475569;
|
||
text-transform: uppercase;
|
||
letter-spacing: 1px;
|
||
margin-bottom: 4px;
|
||
padding: 0 4px;
|
||
}
|
||
.agent-card {
|
||
padding: 8px 10px;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: #94a3b8;
|
||
border: 1px solid transparent;
|
||
transition: all 0.2s;
|
||
}
|
||
.agent-card.active {
|
||
background: rgba(255,255,255,0.06);
|
||
color: #fff;
|
||
border-color: rgba(255,255,255,0.1);
|
||
}
|
||
.agent-card.thinking {
|
||
animation: agent-pulse 0.8s infinite;
|
||
}
|
||
@keyframes agent-pulse {
|
||
0%,100% { opacity: 1; }
|
||
50% { opacity: 0.5; }
|
||
}
|
||
.agent-emoji { font-size: 16px; }
|
||
.agent-status {
|
||
width: 6px; height: 6px; border-radius: 50%;
|
||
background: #1e293b;
|
||
margin-left: auto;
|
||
flex-shrink: 0;
|
||
}
|
||
.agent-status.active { background: #10b981; box-shadow: 0 0 6px #10b981; }
|
||
.agent-status.done { background: #6366f1; }
|
||
|
||
/* Chat Area */
|
||
.chat { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 16px; }
|
||
.chat::-webkit-scrollbar { width: 4px; }
|
||
.chat::-webkit-scrollbar-track { background: transparent; }
|
||
.chat::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; }
|
||
|
||
.msg { display: flex; gap: 12px; }
|
||
.msg-avatar {
|
||
width: 36px; height: 36px;
|
||
border-radius: 10px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 18px;
|
||
flex-shrink: 0;
|
||
border: 1px solid rgba(255,255,255,0.1);
|
||
}
|
||
.msg-body { flex: 1; min-width: 0; }
|
||
.msg-header {
|
||
display: flex; align-items: center; gap: 8px;
|
||
margin-bottom: 6px;
|
||
}
|
||
.msg-name { font-size: 13px; font-weight: 700; }
|
||
.msg-badge {
|
||
font-size: 10px;
|
||
padding: 2px 6px;
|
||
border-radius: 4px;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
}
|
||
.msg-content {
|
||
font-size: 13px;
|
||
line-height: 1.7;
|
||
color: #cbd5e1;
|
||
background: rgba(255,255,255,0.04);
|
||
border: 1px solid rgba(255,255,255,0.06);
|
||
border-radius: 10px;
|
||
padding: 14px 16px;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
}
|
||
|
||
/* Code Blocks */
|
||
.msg-content pre {
|
||
background: rgba(0,0,0,0.5);
|
||
border: 1px solid rgba(255,255,255,0.08);
|
||
border-radius: 8px;
|
||
padding: 14px;
|
||
margin: 10px 0;
|
||
overflow-x: auto;
|
||
font-family: 'Cascadia Code', 'Fira Code', Consolas, monospace;
|
||
font-size: 12px;
|
||
line-height: 1.6;
|
||
}
|
||
.msg-content code {
|
||
background: rgba(0,0,0,0.4);
|
||
padding: 1px 5px;
|
||
border-radius: 3px;
|
||
font-family: 'Cascadia Code', Consolas, monospace;
|
||
font-size: 12px;
|
||
color: #7dd3fc;
|
||
}
|
||
.msg-content pre code {
|
||
background: none; padding: 0; color: #e2e8f0;
|
||
}
|
||
.msg-content h1, .msg-content h2, .msg-content h3 {
|
||
color: #f1f5f9; margin: 14px 0 6px;
|
||
}
|
||
.msg-content ul, .msg-content ol { padding-left: 20px; margin: 6px 0; }
|
||
.msg-content li { margin: 3px 0; }
|
||
.msg-content strong { color: #f1f5f9; }
|
||
|
||
/* User message */
|
||
.msg.user .msg-content {
|
||
background: rgba(99,102,241,0.1);
|
||
border-color: rgba(99,102,241,0.3);
|
||
color: #e2e8f0;
|
||
}
|
||
|
||
/* Input Area */
|
||
.input-area {
|
||
border-top: 1px solid rgba(255,255,255,0.06);
|
||
background: rgba(15,23,42,0.95);
|
||
padding: 16px 20px;
|
||
flex-shrink: 0;
|
||
}
|
||
.api-key-bar {
|
||
display: flex; gap: 8px; margin-bottom: 10px; align-items: center;
|
||
}
|
||
.api-key-bar label { font-size: 11px; color: #475569; white-space: nowrap; }
|
||
.api-key-bar input {
|
||
flex: 1;
|
||
background: rgba(255,255,255,0.05);
|
||
border: 1px solid rgba(255,255,255,0.1);
|
||
border-radius: 6px;
|
||
padding: 6px 10px;
|
||
color: #94a3b8;
|
||
font-size: 12px;
|
||
font-family: monospace;
|
||
}
|
||
.input-row { display: flex; gap: 10px; }
|
||
textarea {
|
||
flex: 1;
|
||
background: rgba(255,255,255,0.05);
|
||
border: 1px solid rgba(255,255,255,0.1);
|
||
border-radius: 10px;
|
||
padding: 12px 14px;
|
||
color: #e2e8f0;
|
||
font-size: 13px;
|
||
resize: none;
|
||
font-family: inherit;
|
||
line-height: 1.5;
|
||
transition: border-color 0.2s;
|
||
}
|
||
textarea:focus { outline: none; border-color: rgba(99,102,241,0.5); }
|
||
textarea::placeholder { color: #334155; }
|
||
.send-btn {
|
||
background: linear-gradient(135deg, #6366f1, #8b5cf6);
|
||
border: none;
|
||
border-radius: 10px;
|
||
color: #fff;
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
padding: 0 20px;
|
||
cursor: pointer;
|
||
transition: opacity 0.2s;
|
||
white-space: nowrap;
|
||
}
|
||
.send-btn:hover { opacity: 0.85; }
|
||
.send-btn:disabled { opacity: 0.4; cursor: not-allowed; }
|
||
.hint { font-size: 11px; color: #334155; margin-top: 8px; }
|
||
|
||
/* Welcome */
|
||
.welcome {
|
||
margin: auto;
|
||
text-align: center;
|
||
padding: 40px;
|
||
max-width: 500px;
|
||
}
|
||
.welcome .big-emoji { font-size: 60px; margin-bottom: 16px; }
|
||
.welcome h2 { font-size: 22px; font-weight: 700; color: #f1f5f9; margin-bottom: 8px; }
|
||
.welcome p { font-size: 14px; color: #475569; line-height: 1.6; }
|
||
.examples { margin-top: 24px; display: flex; flex-direction: column; gap: 8px; }
|
||
.example-btn {
|
||
background: rgba(255,255,255,0.04);
|
||
border: 1px solid rgba(255,255,255,0.08);
|
||
border-radius: 8px;
|
||
padding: 10px 14px;
|
||
color: #94a3b8;
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
text-align: left;
|
||
transition: all 0.2s;
|
||
}
|
||
.example-btn:hover { background: rgba(255,255,255,0.08); color: #e2e8f0; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="header">
|
||
<div class="header-logo">👨💻</div>
|
||
<div>
|
||
<h1>IT Nexus Dev Team</h1>
|
||
<p>6 KI-Agenten · Lokal · Nur für dich</p>
|
||
</div>
|
||
<div class="status-dot"></div>
|
||
</div>
|
||
|
||
<div class="main">
|
||
<!-- Sidebar -->
|
||
<div class="sidebar">
|
||
<div class="sidebar-title">Dev Team</div>
|
||
<div class="agent-card" id="card-pm">
|
||
<span class="agent-emoji">📋</span>
|
||
<span>Project Manager</span>
|
||
<div class="agent-status" id="status-pm"></div>
|
||
</div>
|
||
<div class="agent-card" id="card-architect">
|
||
<span class="agent-emoji">🏗️</span>
|
||
<span>Architect</span>
|
||
<div class="agent-status" id="status-architect"></div>
|
||
</div>
|
||
<div class="agent-card" id="card-backend">
|
||
<span class="agent-emoji">⚙️</span>
|
||
<span>Backend Dev</span>
|
||
<div class="agent-status" id="status-backend"></div>
|
||
</div>
|
||
<div class="agent-card" id="card-frontend">
|
||
<span class="agent-emoji">🎨</span>
|
||
<span>Frontend Dev</span>
|
||
<div class="agent-status" id="status-frontend"></div>
|
||
</div>
|
||
<div class="agent-card" id="card-senior">
|
||
<span class="agent-emoji">🔍</span>
|
||
<span>Senior Dev</span>
|
||
<div class="agent-status" id="status-senior"></div>
|
||
</div>
|
||
<div class="agent-card" id="card-qa">
|
||
<span class="agent-emoji">🧪</span>
|
||
<span>QA Engineer</span>
|
||
<div class="agent-status" id="status-qa"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Chat -->
|
||
<div class="chat" id="chat">
|
||
<div class="welcome" id="welcome">
|
||
<div class="big-emoji">🚀</div>
|
||
<h2>Dein persönliches Dev Team</h2>
|
||
<p>Beschreibe was du bauen möchtest. Das Team analysiert, plant, schreibt Code und reviewt alles automatisch.</p>
|
||
<div class="examples">
|
||
<button class="example-btn" onclick="setExample(this)">📦 Neue Seite für Lizenzmanagement (CRUD, DB, API, React)</button>
|
||
<button class="example-btn" onclick="setExample(this)">🔔 E-Mail-Benachrichtigung wenn Agent offline ist</button>
|
||
<button class="example-btn" onclick="setExample(this)">📊 Dashboard-Widget mit Top 5 offenen Tickets</button>
|
||
<button class="example-btn" onclick="setExample(this)">🔐 Zwei-Faktor-Authentifizierung für Admin-Login</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Input -->
|
||
<div class="input-area">
|
||
<div class="api-key-bar">
|
||
<label>🔑 API Key:</label>
|
||
<input type="password" id="apiKey" placeholder="sk-ant-..." oninput="saveKey(this.value)" />
|
||
</div>
|
||
<div class="input-row">
|
||
<textarea id="taskInput" rows="2" placeholder="Was soll das Team bauen? (Enter = Senden, Shift+Enter = Neue Zeile)"></textarea>
|
||
<button class="send-btn" id="sendBtn" onclick="sendTask()">Team starten ▶</button>
|
||
</div>
|
||
<div class="hint">⚡ PM → Architect → Backend Dev → Frontend Dev → Senior Dev → QA Engineer</div>
|
||
</div>
|
||
|
||
<script>
|
||
const COLORS = {
|
||
pm: '#6366f1', architect: '#f59e0b', backend: '#10b981',
|
||
frontend: '#3b82f6', senior: '#ef4444', qa: '#8b5cf6'
|
||
};
|
||
const NAMES = {
|
||
pm: 'Project Manager', architect: 'Architect', backend: 'Backend Dev',
|
||
frontend: 'Frontend Dev', senior: 'Senior Dev', qa: 'QA Engineer'
|
||
};
|
||
const EMOJIS = {
|
||
pm: '📋', architect: '🏗️', backend: '⚙️',
|
||
frontend: '🎨', senior: '🔍', qa: '🧪'
|
||
};
|
||
|
||
// API Key aus localStorage laden
|
||
window.onload = () => {
|
||
const saved = localStorage.getItem('devteam_apikey');
|
||
if (saved) document.getElementById('apiKey').value = saved;
|
||
};
|
||
function saveKey(v) { localStorage.setItem('devteam_apikey', v); }
|
||
|
||
function setExample(btn) {
|
||
document.getElementById('taskInput').value = btn.textContent.replace(/^[^\s]+\s/, '').trim();
|
||
document.getElementById('taskInput').focus();
|
||
}
|
||
|
||
document.getElementById('taskInput').addEventListener('keydown', e => {
|
||
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendTask(); }
|
||
});
|
||
|
||
let currentMsgEl = null;
|
||
let currentRaw = '';
|
||
|
||
function addUserMsg(text) {
|
||
document.getElementById('welcome')?.remove();
|
||
const el = document.createElement('div');
|
||
el.className = 'msg user';
|
||
el.innerHTML = \`
|
||
<div class="msg-avatar" style="background:rgba(99,102,241,0.2)">👤</div>
|
||
<div class="msg-body">
|
||
<div class="msg-header">
|
||
<span class="msg-name" style="color:#6366f1">Du</span>
|
||
</div>
|
||
<div class="msg-content">\${escHtml(text)}</div>
|
||
</div>\`;
|
||
document.getElementById('chat').appendChild(el);
|
||
scrollChat();
|
||
}
|
||
|
||
function startAgentMsg(agentId) {
|
||
// Sidebar aktualisieren
|
||
document.querySelectorAll('.agent-card').forEach(c => c.classList.remove('active','thinking'));
|
||
document.querySelectorAll('.agent-status').forEach(s => { s.classList.remove('active'); });
|
||
const card = document.getElementById('card-' + agentId);
|
||
const status = document.getElementById('status-' + agentId);
|
||
if (card) { card.classList.add('active','thinking'); }
|
||
if (status) { status.classList.add('active'); }
|
||
|
||
const color = COLORS[agentId];
|
||
const name = NAMES[agentId];
|
||
const emoji = EMOJIS[agentId];
|
||
|
||
const el = document.createElement('div');
|
||
el.className = 'msg';
|
||
el.id = 'msg-' + agentId;
|
||
el.innerHTML = \`
|
||
<div class="msg-avatar" style="background:\${color}20; border-color:\${color}40">\${emoji}</div>
|
||
<div class="msg-body">
|
||
<div class="msg-header">
|
||
<span class="msg-name" style="color:\${color}">\${name}</span>
|
||
<span class="msg-badge" style="background:\${color}20;color:\${color}">schreibt...</span>
|
||
</div>
|
||
<div class="msg-content" id="content-\${agentId}"><span class="cursor">▌</span></div>
|
||
</div>\`;
|
||
document.getElementById('chat').appendChild(el);
|
||
currentMsgEl = document.getElementById('content-' + agentId);
|
||
currentRaw = '';
|
||
scrollChat();
|
||
}
|
||
|
||
function appendChunk(agentId, text) {
|
||
currentRaw += text;
|
||
if (currentMsgEl) {
|
||
currentMsgEl.innerHTML = renderMarkdown(currentRaw) + '<span class="cursor">▌</span>';
|
||
scrollChat();
|
||
}
|
||
}
|
||
|
||
function doneAgentMsg(agentId) {
|
||
if (currentMsgEl) {
|
||
currentMsgEl.innerHTML = renderMarkdown(currentRaw);
|
||
}
|
||
const card = document.getElementById('card-' + agentId);
|
||
const status = document.getElementById('status-' + agentId);
|
||
if (card) card.classList.remove('thinking');
|
||
if (status) { status.classList.remove('active'); status.classList.add('done'); }
|
||
|
||
// Badge aktualisieren
|
||
const msg = document.getElementById('msg-' + agentId);
|
||
if (msg) {
|
||
const badge = msg.querySelector('.msg-badge');
|
||
if (badge) { badge.textContent = 'fertig ✓'; badge.style.background = COLORS[agentId]+'30'; }
|
||
}
|
||
currentMsgEl = null;
|
||
}
|
||
|
||
function renderMarkdown(text) {
|
||
return text
|
||
.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
|
||
.replace(/\`\`\`(\w+)?\n([\s\S]*?)\`\`\`/g, '<pre><code>$2</code></pre>')
|
||
.replace(/\`([^\`]+)\`/g, '<code>$1</code>')
|
||
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
|
||
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
|
||
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
|
||
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||
.replace(/^- (.+)$/gm, '<li>$1</li>')
|
||
.replace(/^(\d+)\. (.+)$/gm, '<li>$1. $2</li>')
|
||
.replace(/\n\n/g, '<br><br>')
|
||
.replace(/\n/g, '<br>');
|
||
}
|
||
|
||
function escHtml(t) {
|
||
return t.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||
}
|
||
|
||
function scrollChat() {
|
||
const chat = document.getElementById('chat');
|
||
chat.scrollTop = chat.scrollHeight;
|
||
}
|
||
|
||
async function sendTask() {
|
||
const task = document.getElementById('taskInput').value.trim();
|
||
const apiKey = document.getElementById('apiKey').value.trim();
|
||
|
||
if (!task) return;
|
||
if (!apiKey) { alert('Bitte zuerst den Anthropic API Key eingeben!'); return; }
|
||
|
||
const btn = document.getElementById('sendBtn');
|
||
btn.disabled = true;
|
||
btn.textContent = '⏳ Team arbeitet...';
|
||
|
||
addUserMsg(task);
|
||
document.getElementById('taskInput').value = '';
|
||
|
||
// Alle Status zurücksetzen
|
||
document.querySelectorAll('.agent-status').forEach(s => s.classList.remove('active','done'));
|
||
document.querySelectorAll('.agent-card').forEach(c => c.classList.remove('active','thinking'));
|
||
|
||
try {
|
||
const res = await fetch('/api/task', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ task, apiKey })
|
||
});
|
||
|
||
const reader = res.body.getReader();
|
||
const decoder = new TextDecoder();
|
||
let buf = '';
|
||
|
||
while (true) {
|
||
const { done, value } = await reader.read();
|
||
if (done) break;
|
||
buf += decoder.decode(value, { stream: true });
|
||
const lines = buf.split('\n\n');
|
||
buf = lines.pop();
|
||
for (const line of lines) {
|
||
if (line.startsWith('data: ')) {
|
||
try {
|
||
const d = JSON.parse(line.slice(6));
|
||
if (d.type === 'agent_start') startAgentMsg(d.agent);
|
||
else if (d.type === 'chunk') appendChunk(d.agent, d.text);
|
||
else if (d.type === 'agent_done') doneAgentMsg(d.agent);
|
||
else if (d.type === 'done') {
|
||
document.querySelectorAll('.agent-card').forEach(c => c.classList.remove('active','thinking'));
|
||
}
|
||
} catch {}
|
||
}
|
||
}
|
||
}
|
||
} catch (e) {
|
||
const el = document.createElement('div');
|
||
el.style.cssText = 'background:rgba(239,68,68,0.1);border:1px solid rgba(239,68,68,0.3);border-radius:8px;padding:12px;color:#fca5a5;font-size:13px;';
|
||
el.textContent = '❌ Fehler: ' + e.message;
|
||
document.getElementById('chat').appendChild(el);
|
||
}
|
||
|
||
btn.disabled = false;
|
||
btn.textContent = 'Team starten ▶';
|
||
scrollChat();
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>`;
|
||
|
||
const server = http.createServer((req, res) => {
|
||
const url = new URL(req.url, `http://localhost:${PORT}`);
|
||
|
||
// HTML
|
||
if (req.method === 'GET' && url.pathname === '/') {
|
||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||
res.end(HTML);
|
||
return;
|
||
}
|
||
|
||
// Task API
|
||
if (req.method === 'POST' && url.pathname === '/api/task') {
|
||
let body = '';
|
||
req.on('data', c => body += c);
|
||
req.on('end', async () => {
|
||
try {
|
||
const { task, apiKey } = JSON.parse(body);
|
||
|
||
// API Key aus Request oder Umgebungsvariable
|
||
const key = apiKey || API_KEY;
|
||
if (!key) {
|
||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||
res.end(JSON.stringify({ error: 'Kein API Key' }));
|
||
return;
|
||
}
|
||
|
||
res.writeHead(200, {
|
||
'Content-Type': 'text/event-stream',
|
||
'Cache-Control': 'no-cache',
|
||
'Connection': 'keep-alive',
|
||
'Access-Control-Allow-Origin': '*'
|
||
});
|
||
|
||
const send = (data) => res.write(`data: ${JSON.stringify(data)}\n\n`);
|
||
|
||
// Agenten mit aktuellem Key aufrufen
|
||
const callAgent = (systemPrompt, messages) => new Promise((resolve, reject) => {
|
||
const reqBody = JSON.stringify({
|
||
model: 'claude-sonnet-4-6',
|
||
max_tokens: 8096,
|
||
system: systemPrompt,
|
||
messages,
|
||
stream: true
|
||
});
|
||
|
||
const apiReq = https.request({
|
||
hostname: 'api.anthropic.com',
|
||
path: '/v1/messages',
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'x-api-key': key,
|
||
'anthropic-version': '2023-06-01',
|
||
'Content-Length': Buffer.byteLength(reqBody)
|
||
}
|
||
}, (apiRes) => {
|
||
let full = '';
|
||
let buf = '';
|
||
apiRes.on('data', chunk => {
|
||
buf += chunk.toString();
|
||
const lines = buf.split('\n');
|
||
buf = lines.pop();
|
||
for (const line of lines) {
|
||
if (line.startsWith('data: ')) {
|
||
try {
|
||
const d = JSON.parse(line.slice(6));
|
||
if (d.type === 'content_block_delta' && d.delta?.text) {
|
||
full += d.delta.text;
|
||
send({ type: 'chunk', agent: currentAgent, text: d.delta.text });
|
||
}
|
||
} catch {}
|
||
}
|
||
}
|
||
});
|
||
apiRes.on('end', () => resolve(full));
|
||
apiRes.on('error', reject);
|
||
});
|
||
apiReq.on('error', reject);
|
||
apiReq.write(reqBody);
|
||
apiReq.end();
|
||
});
|
||
|
||
let currentAgent = '';
|
||
const context = [];
|
||
|
||
const runAgent = async (agentId, userMsg) => {
|
||
currentAgent = agentId;
|
||
send({ type: 'agent_start', agent: agentId });
|
||
const msgs = [...context, { role: 'user', content: userMsg }];
|
||
const full = await callAgent(AGENTS[agentId].system, msgs);
|
||
send({ type: 'agent_done', agent: agentId });
|
||
context.push({ role: 'user', content: userMsg });
|
||
context.push({ role: 'assistant', content: full });
|
||
return full;
|
||
};
|
||
|
||
// Team ausführen
|
||
const pmResult = await runAgent('pm',
|
||
`Neue Aufgabe:\n\n"${task}"\n\nErstelle einen kurzen klaren Implementierungsplan für das IT Nexus Entwicklerteam.`
|
||
);
|
||
|
||
await runAgent('architect',
|
||
`Aufgabe: "${task}"\n\nPM-Plan:\n${pmResult}\n\nEntscheide über DB-Schema, API-Endpoints und technische Architektur.`
|
||
);
|
||
|
||
const backendResult = await runAgent('backend',
|
||
`Aufgabe: "${task}"\n\nSchreibe jetzt den vollständigen Backend-Code. Alle Dateien komplett und lauffähig. Folge exakt den IT Nexus Konventionen.`
|
||
);
|
||
|
||
const frontendResult = await runAgent('frontend',
|
||
`Aufgabe: "${task}"\n\nSchreibe jetzt den vollständigen Frontend-Code. Alle Dateien komplett. Folge exakt den IT Nexus Konventionen.`
|
||
);
|
||
|
||
await runAgent('senior',
|
||
`Reviewe diesen Code:\n\nBACKEND:\n${backendResult}\n\nFRONTEND:\n${frontendResult}\n\nFinde alle Bugs, Konventionsverletzungen und fehlende Teile. Gib Fixes.`
|
||
);
|
||
|
||
await runAgent('qa',
|
||
`Aufgabe: "${task}"\n\nErstelle konkrete Testschritte, Security-Checks und Edge Cases.`
|
||
);
|
||
|
||
send({ type: 'done' });
|
||
res.end();
|
||
} catch (e) {
|
||
try {
|
||
res.write(`data: ${JSON.stringify({ type: 'error', text: e.message })}\n\n`);
|
||
res.end();
|
||
} catch {}
|
||
}
|
||
});
|
||
return;
|
||
}
|
||
|
||
res.writeHead(404);
|
||
res.end();
|
||
});
|
||
|
||
server.listen(PORT, '127.0.0.1', () => {
|
||
console.log('');
|
||
console.log('╔═══════════════════════════════════════╗');
|
||
console.log('║ IT Nexus Dev Team — Bereit! ║');
|
||
console.log('╠═══════════════════════════════════════╣');
|
||
console.log(`║ → http://localhost:${PORT} ║`);
|
||
console.log('║ ║');
|
||
console.log('║ 6 Agenten: PM, Architect, ║');
|
||
console.log('║ Backend, Frontend, Senior, QA ║');
|
||
console.log('╚═══════════════════════════════════════╝');
|
||
console.log('');
|
||
if (!API_KEY) {
|
||
console.log('⚠️ Kein ANTHROPIC_API_KEY gesetzt — im Browser eingeben');
|
||
console.log(' Oder: set ANTHROPIC_API_KEY=sk-ant-... && node devteam.js');
|
||
}
|
||
console.log('');
|
||
});
|