Passwort vergessen?

...
Bereit

Was soll die KI bauen?

Beschreibe ein Objekt oder eine Szene — die KI generiert den passenden Roblox-Code. Max. 2 Aufgaben pro Prompt, .

Studio Plugin verbinden

------

Code wird geladen…

🏠 Erstelle ein Haus 🌊 Füge Wasser hinzu 💡 Erzeuge ein Licht-System 🌲 Platziere Bäume 🚗 Spawne ein Fahrzeug

Enter zum Senden  ·  Shift+Enter für neue Zeile  ·  Max. 2 Aufgaben pro Prompt

Anfrage bereits gestellt

Deine Löschanfrage vom ${date} wird bearbeitet.
Wir melden uns per E-Mail.

`; } } catch {} document.getElementById('deleteRequestModal').style.display = 'flex'; } function closeDeleteRequest() { document.getElementById('deleteRequestModal').style.display = 'none'; // Reset content document.getElementById('deleteRequestMsg').value = ''; } async function submitDeleteRequest() { if (!sessionToken) return; const btn = document.getElementById('deleteRequestBtn'); btn.disabled = true; btn.textContent = '…'; try { const message = document.getElementById('deleteRequestMsg').value.trim(); const res = await fetch(`${WORKER}/api/delete-request`, { method: 'POST', headers: { 'X-Session-Token': sessionToken, 'Content-Type': 'application/json' }, body: JSON.stringify({ message }), }); const data = await res.json(); if (res.ok) { document.getElementById('deleteRequestContent').innerHTML = `

Anfrage eingereicht

Wir bearbeiten deine Löschanfrage innerhalb von 30 Tagen und benachrichtigen dich per E-Mail.

`; } else { alert(data.error || 'Fehler beim Einreichen der Anfrage.'); btn.disabled = false; btn.textContent = 'Löschanfrage stellen'; } } catch { alert('Verbindungsfehler.'); btn.disabled = false; btn.textContent = 'Löschanfrage stellen'; } } let lastSend = 0; async function sendPrompt() { if (Date.now() - lastSend < 800) return; if (!sessionToken) { appendMessage('error', '⚠ Bitte zuerst einloggen.'); return; } if (!studioConnected) { appendMessage('error', '⚠ Bitte zuerst das Studio Plugin verbinden.'); return; } if (currentChatFull) { appendMessage('error', '⚠ Dieser Chat ist voll. Erstelle über "💬 Chats" einen neuen Chat.'); return; } // Auto-create a chat session if none active if (!currentChatId) { if (chatList.length >= 10) { openChatList(); return; } try { const r = await fetch(`${WORKER}/api/chat/new`, { method: 'POST', headers: { 'X-Session-Token': sessionToken, 'Content-Type': 'application/json' }, }); const d = await r.json(); if (!r.ok) { if (d.limitReached) { openChatList(); return; } appendMessage('error', '⚠ ' + (d.error || 'Chat konnte nicht erstellt werden.')); return; } currentChatId = d.chatId; currentChatFull = false; chatList.unshift({ id: d.chatId, title: 'Neuer Chat', updated_at: Date.now() }); } catch { appendMessage('error', '⚠ Verbindungsfehler beim Chat-Start.'); return; } } const cleaned = prompt.value.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g,'').trim(); if (!cleaned || cleaned.length > 2000) return; lastSend = Date.now(); const text = cleaned; prompt.value = ''; prompt.style.height = 'auto'; sendBtn.disabled = true; // Plugin liveness is verified passively via heartbeat (requires new worker deploy) // Skip explicit check here to avoid false positives with old worker // Always show user message first — regardless of mode appendMessage('user', text); hideWelcome(); // Route to Deep Research if enabled if (deepMode) { await sendDeepResearch(text); return; } setStatus('working'); // Create streaming AI bubble const msgDiv = document.createElement('div'); msgDiv.className = 'msg ai'; const label = document.createElement('div'); label.className = 'msg-label'; const tag = document.createElement('span'); tag.className = 'tag'; tag.textContent = 'KI'; label.appendChild(tag); const bubble = document.createElement('div'); bubble.className = 'bubble'; bubble.style.whiteSpace = 'pre-wrap'; // Show immediate loading text so user knows something is happening bubble.textContent = '⏳ Verbinde mit KI…'; bubble.style.color = 'var(--text-dim)'; msgDiv.appendChild(label); msgDiv.appendChild(bubble); chat.appendChild(msgDiv); chat.scrollTo({ top: chat.scrollHeight, behavior: 'smooth' }); // Streaming cursor const cursor = document.createElement('span'); cursor.style.cssText = 'display:inline-block;width:2px;height:14px;background:var(--accent);margin-left:2px;animation:pulse 0.8s infinite;vertical-align:text-bottom;'; bubble.appendChild(cursor); try { const res = await fetch(`${WORKER}/api/generate/stream`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', 'X-Session-Token': sessionToken, }, body: JSON.stringify({ prompt: cleaned, chatId: currentChatId }), }); if (!res.ok) { cursor.remove(); const data = await res.json().catch(() => ({})); if (res.status === 401) { clearSession(); bubble.textContent = '⚠ Sitzung abgelaufen. Bitte neu einloggen.'; setTimeout(() => location.reload(), 1500); } else if (data.chatFull) { const lim = data.limit || 20; bubble.textContent = `⚠ Dieser Chat ist voll (${lim} Nachrichten). Erstelle über "💬 Chats" einen neuen Chat.`; currentChatFull = true; updateInputState(); } else { bubble.textContent = '⚠ ' + (data.error || 'Server-Fehler ' + res.status); } msgDiv.className = 'msg error'; setStatus('error'); sendBtn.disabled = false; return; } const reader = res.body.getReader(); const dec = new TextDecoder(); let buf = ''; let display = ''; // text shown in bubble (excludes JSON) // Connection established — update loading text bubble.textContent = '⏳ KI denkt…'; bubble.appendChild(cursor); while (true) { const { done, value } = await reader.read(); if (done) break; buf += dec.decode(value, { stream: true }); const lines = buf.split('\n'); buf = lines.pop(); for (const line of lines) { if (!line.startsWith('data: ')) continue; const raw = line.slice(6).trim(); if (!raw) continue; try { const evt = JSON.parse(raw); if (evt.type === 'chunk') { // Stream raw text — hide JSON parts, show only the message display += evt.text; // Extract message (before ```json block) for display const msgPart = display.split('```')[0].trim(); cursor.remove(); bubble.style.color = ''; // Reset loading color bubble.textContent = msgPart || '⏳ KI denkt…'; bubble.appendChild(cursor); chat.scrollTo({ top: chat.scrollHeight, behavior: 'smooth' }); } else if (evt.type === 'done') { cursor.remove(); // Sync chatId if server auto-created one (old frontend without auto-create) if (evt.chatId && !currentChatId) { currentChatId = evt.chatId; chatList.unshift({ id: evt.chatId, title: 'Neuer Chat', updated_at: Date.now() }); document.getElementById('newChatBtn').style.display = 'block'; } // Show only the AI message — strip raw JSON block if present let finalMsg = evt.fullText?.split('```')[0]?.trim() || 'Fertig!'; if (evt.commandCount > 0) { finalMsg += `\n\n✅ ${evt.commandCount} Command(s) ins Plugin gesendet`; } if (evt.blocked > 0) { finalMsg += `\n⚠ ${evt.blocked} Script(s) blockiert`; } bubble.style.color = ''; bubble.textContent = finalMsg; // Token badge addTokenBadge(msgDiv, evt.tokens?.total, evt.balanceRemaining, evt.commandCount); // Command summary panel if (evt.commandCount > 0 && evt.fullText) { addCommandSummary(msgDiv, evt.fullText); } setStatus('ready'); loadBillingStatus(); } else if (evt.type === 'error') { cursor.remove(); bubble.textContent = '⚠ ' + (evt.message || 'KI-Fehler'); msgDiv.className = 'msg error'; setStatus('error'); } } catch { /* skip malformed event */ } } } } catch (err) { cursor.remove(); bubble.textContent = '⚠ Verbindungsfehler: ' + err.message; msgDiv.className = 'msg error'; setStatus('error'); } sendBtn.disabled = false; } prompt.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); if (!sendBtn.disabled) sendPrompt(); } }); sendBtn.addEventListener('click', sendPrompt); // ── Referral code from URL / team invite ────────────────────────── (function() { const params = new URLSearchParams(window.location.search); const ref = params.get('ref'); if (ref) { document.getElementById('authRef').value = ref.toUpperCase(); switchTab('register'); window.history.replaceState({}, '', window.location.pathname); } const joinToken = params.get('joinTeam'); if (joinToken) { sessionStorage.setItem('pendingJoinToken', joinToken); window.history.replaceState({}, '', window.location.pathname); } })(); // ── Team & Referral functions ───────────────────────────────────── async function showTeam() { document.getElementById('teamModal').style.display = 'flex'; await renderTeamContent(); } function closeTeam() { document.getElementById('teamModal').style.display = 'none'; } async function renderTeamContent() { const el = document.getElementById('teamContent'); el.textContent = 'Lade…'; if (!sessionToken) { el.textContent = 'Nicht eingeloggt.'; return; } const [teamRes, refRes] = await Promise.all([ fetch(`${WORKER}/api/team`, { headers: { 'X-Session-Token': sessionToken } }), fetch(`${WORKER}/api/referral`, { headers: { 'X-Session-Token': sessionToken } }), ]); const teamData = teamRes.ok ? await teamRes.json() : {}; const refData = refRes.ok ? await refRes.json() : {}; const team = teamData.team; let html = `
Dein Referral-Link
${safeText(refData.referralUrl || '–')}
${refData.count || 0} Einladungen · ${(refData.earnings || 0).toLocaleString()} Tokens verdient
Einladender & Eingeladener: je 5.000 Tokens · 5% Provision bei Käufen
`; if (!team) { // Check if user has paid team tier const hasPaidTeam = teamData.hasPaidTeam; if (!hasPaidTeam) { html += `
👥 Team-Plan erforderlich
Um ein Team zu erstellen brauchst du den Team-Plan (€24,99/Monat).
`; } else { html += `
Team-Plan aktiv — erstelle dein Team:
`; } } else { const isOwner = team.myRole === 'owner'; html += `
${team.name} ${isOwner ? 'Owner' : 'Member'} · TEAM
500.000 Tokens/Tag geteilt · ${team.members.length}/10 Mitglieder
${team.members.map(m => `
${safeText(m.email)}
${m.team_role === 'owner' ? 'Owner' : 'Member'} ${isOwner && m.team_role !== 'owner' ? `` : ''}
`).join('')} ${isOwner ? `
` : ''} ${!isOwner ? `` : ''}
`; } el.innerHTML = html; } // ── Datenlöschung ───────────────────────────────────────────────── async function showDeletionRequest() { closeBilling(); // Check existing status const res = await fetch(`${WORKER}/api/deletion-request`, { headers: { 'X-Session-Token': sessionToken }, }); const data = await res.json(); const req = data.request; let content = ''; if (req && req.status === 'pending') { content = `
Anfrage ausstehend
Deine Löschanfrage wird bearbeitet (innerhalb von 30 Tagen).
`; } else if (req && req.status === 'approved') { content = `
Anfrage genehmigt
Dein Account wird gelöscht.
`; } else { content = `

Hiermit beantragst du die Löschung deines Accounts und aller gespeicherten Daten gemäß DSGVO Art. 17. Du erhältst eine Bestätigung per E-Mail. Die Bearbeitung dauert bis zu 30 Tage.

${req && req.status === 'rejected' ? `
Vorherige Anfrage abgelehnt${req.admin_note ? ': ' + req.admin_note : ''}. Du kannst erneut anfragen.
` : ''} `; } // Show in a simple modal const overlay = document.createElement('div'); overlay.id = 'deletionOverlay'; overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);z-index:300;display:flex;align-items:center;justify-content:center;padding:16px;'; overlay.innerHTML = `
🗑 Datenlöschung
${content}
`; document.body.appendChild(overlay); } async function submitDeletionRequest() { const message = document.getElementById('deletionMsg')?.value?.trim() || ''; if (!confirm('Bist du sicher? Dein Account und alle Daten werden unwiderruflich gelöscht.')) return; const res = await fetch(`${WORKER}/api/deletion-request`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Session-Token': sessionToken }, body: JSON.stringify({ message }), }); const data = await res.json(); document.getElementById('deletionOverlay')?.remove(); if (res.ok) { appendMessage('ai', '✅ Deine Löschanfrage wurde eingereicht. Du erhältst eine Bestätigung per E-Mail. Bearbeitungszeit: bis zu 30 Tage.'); } else { appendMessage('error', '⚠ ' + (data.error || 'Fehler beim Einreichen.')); } } async function doCreateTeam() { const name = document.getElementById('newTeamName')?.value?.trim(); if (!name) return; const res = await fetch(`${WORKER}/api/team/create`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Session-Token': sessionToken }, body: JSON.stringify({ name }), }); const data = await res.json(); if (data.ok) { await renderTeamContent(); loadBillingStatus(); } else { const m = document.getElementById('teamMsg'); if (m) m.textContent = data.error || 'Fehler.'; } } function copyReferralUrl(btn) { const url = btn.dataset.url; if (!url) return; navigator.clipboard.writeText(url).catch(() => {}); btn.textContent = '✓'; } async function doCreateInvite() { const res = await fetch(`${WORKER}/api/team/invite`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Session-Token': sessionToken }, }); const data = await res.json(); const el = document.getElementById('inviteResult'); if (!el) return; if (data.inviteUrl) { navigator.clipboard.writeText(data.inviteUrl).catch(() => {}); el.textContent = '✓ Kopiert: ' + data.inviteUrl; } else { el.textContent = data.error || 'Fehler.'; el.style.color = 'var(--danger)'; } } async function doLeaveTeam() { if (!confirm('Team wirklich verlassen?')) return; const res = await fetch(`${WORKER}/api/team/leave`, { method: 'POST', headers: { 'X-Session-Token': sessionToken } }); const data = await res.json(); if (data.ok) { await renderTeamContent(); loadBillingStatus(); } } async function doRemoveMember(userId) { if (!confirm('Mitglied entfernen?')) return; const res = await fetch(`${WORKER}/api/team/remove`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Session-Token': sessionToken }, body: JSON.stringify({ userId }), }); const data = await res.json(); if (data.ok) await renderTeamContent(); } async function checkPendingTeamJoin() { const token = sessionStorage.getItem('pendingJoinToken'); if (!token || !sessionToken) return; sessionStorage.removeItem('pendingJoinToken'); const res = await fetch(`${WORKER}/api/team/join`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Session-Token': sessionToken }, body: JSON.stringify({ token }), }); const data = await res.json(); if (data.ok) { appendMessage('ai', `✅ Du bist dem Team "${data.teamName}" beigetreten!`); loadBillingStatus(); } } setStatus('ready');
Datenschutz & Impressum