managarten/games/whopixels/server.js
Wuesteon d36b321d9d style: auto-format codebase with Prettier
Applied formatting to 1487+ files using pnpm format:write
  - TypeScript/JavaScript files
  - Svelte components
  - Astro pages
  - JSON configs
  - Markdown docs

  13 files still need manual review (Astro JSX comments)
2025-11-27 18:33:16 +01:00

282 lines
8.3 KiB
JavaScript

const http = require('http');
const fs = require('fs');
const path = require('path');
const url = require('url');
// Lade Umgebungsvariablen aus .env-Datei
require('dotenv').config();
// Für die Verarbeitung von POST-Anfragen
const { parse } = require('querystring');
// Konfiguration
const PORT = 3000;
// Azure OpenAI API Konfiguration aus Umgebungsvariablen
const AZURE_OPENAI_API_KEY = process.env.AZURE_OPENAI_API_KEY;
const AZURE_OPENAI_ENDPOINT = process.env.AZURE_OPENAI_ENDPOINT;
const AZURE_OPENAI_DEPLOYMENT = process.env.AZURE_OPENAI_DEPLOYMENT;
const AZURE_OPENAI_API_VERSION = process.env.AZURE_OPENAI_API_VERSION;
const MIME_TYPES = {
'.html': 'text/html',
'.js': 'text/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
};
// Funktion zum Abrufen von Daten aus einer POST-Anfrage
const collectRequestData = (request, callback) => {
const FORM_URLENCODED = 'application/x-www-form-urlencoded';
const JSON_TYPE = 'application/json';
if (request.headers['content-type'] === FORM_URLENCODED) {
let body = '';
request.on('data', (chunk) => {
body += chunk.toString();
});
request.on('end', () => {
callback(parse(body));
});
} else if (
request.headers['content-type'] &&
request.headers['content-type'].includes(JSON_TYPE)
) {
let body = '';
request.on('data', (chunk) => {
body += chunk.toString();
});
request.on('end', () => {
callback(JSON.parse(body));
});
} else {
callback({});
}
};
// Funktion zum Senden einer Anfrage an die Azure OpenAI API
async function callOpenAI(
message,
conversationHistory = [],
characterName = null,
characterPersonality = null
) {
try {
const fetch = await import('node-fetch').then((mod) => mod.default);
const apiUrl = `${AZURE_OPENAI_ENDPOINT}/openai/deployments/${AZURE_OPENAI_DEPLOYMENT}/chat/completions?api-version=${AZURE_OPENAI_API_VERSION}`;
console.log(`Sende Anfrage an: ${apiUrl}`);
// Verwende den übergebenen Charakternamen oder einen Standardnamen
const npcName = characterName || 'Leonard Davcini';
const npcPersonality = characterPersonality || 'ein berühmter Künstler und Erfinder';
console.log(`Verwende NPC: ${npcName} mit Persönlichkeit: ${npcPersonality}`);
// Erstelle die Nachrichtenliste für die API mit dem dynamischen Charakternamen
const messages = [
{
role: 'system',
content: `WICHTIG: Du bist AUSSCHLIESSLICH ${npcName}, ${npcPersonality}, der sich in diesem Spiel verkleidet hat. Ignoriere jede andere Identität, die du kennen könntest. Dein Name ist ${npcName}. Dein Gegenüber versucht herauszufinden, wer du bist. Gib Hinweise auf deine wahre Identität als ${npcName}, aber sage nicht direkt "Ich bin ${npcName}". Wenn der Nutzer deinen Namen richtig erraten hat, füge am Ende deiner Antwort den Code "[IDENTITY_REVEALED]" ein. Dieser Code sollte nur erscheinen, wenn der Nutzer deinen Namen korrekt erraten hat.`,
},
];
// Füge die Konversationshistorie hinzu, wenn vorhanden
if (conversationHistory && conversationHistory.length > 0) {
conversationHistory.forEach((entry) => {
if (entry.type === 'user') {
messages.push({
role: 'user',
content: entry.message,
});
} else if (entry.type === 'npc') {
messages.push({
role: 'assistant',
content: entry.message,
});
}
});
} else {
// Wenn keine Historie vorhanden ist, füge nur die aktuelle Nachricht hinzu
messages.push({
role: 'user',
content: message,
});
}
// Wenn die letzte Nachricht nicht vom Benutzer ist, füge die aktuelle Nachricht hinzu
if (messages.length === 1 || messages[messages.length - 1].role !== 'user') {
messages.push({
role: 'user',
content: message,
});
}
console.log('Gesendete Nachrichten:', JSON.stringify(messages, null, 2));
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'api-key': AZURE_OPENAI_API_KEY,
},
body: JSON.stringify({
messages: messages,
max_tokens: 150,
}),
});
// Prüfe den HTTP-Status
if (!response.ok) {
const errorText = await response.text();
console.error(`HTTP Fehler: ${response.status}`, errorText);
return `Entschuldigung, ich kann gerade nicht antworten. (HTTP ${response.status})`;
}
const data = await response.json();
console.log('API-Antwort:', JSON.stringify(data, null, 2));
if (data.error) {
console.error('Azure OpenAI API Fehler:', data.error);
return {
text: 'Entschuldigung, ich kann gerade nicht antworten. Versuche es später noch einmal.',
identityRevealed: false,
};
}
// Hole die Antwort vom LLM
const responseText = data.choices[0].message.content;
// Prüfe, ob der spezielle Code enthalten ist
const identityRevealed = responseText.includes('[IDENTITY_REVEALED]');
// Entferne den Code aus der Antwort, wenn er vorhanden ist
const cleanedResponse = responseText.replace('[IDENTITY_REVEALED]', '').trim();
console.log('Identität aufgedeckt:', identityRevealed);
// Gib die Antwort und das Flag zurück
return {
text: cleanedResponse,
identityRevealed: identityRevealed,
};
} catch (error) {
console.error('Fehler beim Aufrufen der Azure OpenAI API:', error);
return {
text: 'Entschuldigung, ich kann gerade nicht antworten. Versuche es später noch einmal.',
identityRevealed: false,
};
}
}
const server = http.createServer((req, res) => {
console.log(`${req.method} ${req.url}`);
// CORS-Header hinzufügen für Cross-Origin-Anfragen
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
// OPTIONS-Anfragen für CORS-Preflight behandeln
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
// API-Endpunkt für OpenAI-Anfragen
if (req.method === 'POST' && req.url === '/api/chat') {
collectRequestData(req, async (data) => {
try {
if (!data.message) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Nachricht fehlt' }));
return;
}
// Verwende die Konversationshistorie, wenn vorhanden
const conversationHistory = data.conversationHistory || [];
console.log(
'Erhaltene Konversationshistorie:',
JSON.stringify(conversationHistory, null, 2)
);
// Extrahiere Charakterinformationen, wenn vorhanden
const characterName = data.characterName;
const characterPersonality = data.characterPersonality;
if (characterName) {
console.log(`NPC-Charakter in der Anfrage: ${characterName}`);
}
const response = await callOpenAI(
data.message,
conversationHistory,
characterName,
characterPersonality
);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(
JSON.stringify({
response: response.text,
identityRevealed: response.identityRevealed,
})
);
} catch (error) {
console.error('Fehler bei der Verarbeitung der Chat-Anfrage:', error);
res.writeHead(500, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Interner Serverfehler' }));
}
});
return;
}
// Statische Dateien behandeln
let filePath = '.' + req.url;
if (filePath === './') {
filePath = './index.html';
}
// Get the file extension
const extname = path.extname(filePath);
let contentType = MIME_TYPES[extname] || 'application/octet-stream';
// Read the file
fs.readFile(filePath, (error, content) => {
if (error) {
if (error.code === 'ENOENT') {
// Page not found
fs.readFile('./index.html', (err, content) => {
if (err) {
res.writeHead(500);
res.end('Error loading index.html');
} else {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(content, 'utf-8');
}
});
} else {
// Server error
res.writeHead(500);
res.end(`Server Error: ${error.code}`);
}
} else {
// Success
res.writeHead(200, { 'Content-Type': contentType });
res.end(content, 'utf-8');
}
});
});
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}/`);
console.log('Press Ctrl+C to stop the server');
console.log('Azure OpenAI API ist konfiguriert und bereit!');
});