/* Kai Cockpit — vanilla JS, no framework, no build, no CDN. * Shares XP/Level/Streak state with the Kai widget via localStorage key `kai.state.v1`. * Cockpit-specific keys: kai.cockpit.reifegrad, kai.cockpit.aiact, kai.cockpit.dashboard, kai.cockpit.chat */ (() => { 'use strict'; // ========= CONFIG ========= const API = 'https://llm.qognio.com/api/bots/ki-kennzahlen-coach/chat'; const RAW_KEY = window.__KAI_KEY__ || ''; const KEY = /^qb_[a-zA-Z0-9]{6,}$/.test(RAW_KEY) ? RAW_KEY : ''; // Shared with widget const LS_STATE = 'kai.state.v1'; // Cockpit-specific const LS_REIFE = 'kai.cockpit.reifegrad'; const LS_AIACT = 'kai.cockpit.aiact'; const LS_DASH = 'kai.cockpit.dashboard'; const LS_CHAT = 'kai.cockpit.chat'; // ========= UTIL ========= const $ = (s, r = document) => r.querySelector(s); const $$ = (s, r = document) => Array.from(r.querySelectorAll(s)); const today = () => { const d = new Date(); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`; }; const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); // ========= SHARED STATE (with widget) ========= function loadState() { try { const s = JSON.parse(localStorage.getItem(LS_STATE) || '{}'); return Object.assign({ xp: 0, totalAnswers: 0, correctAnswers: 0, currentStreak: 0, maxStreak: 0, lastActive: null, quizStreak: 0, maxQuizStreak: 0, mastery: {}, moduleCorrect: {}, moduleTotal: {}, modulePassedFlash: {}, completedQuizzes: 0, flashCardsRated: 0, badges: {}, seenWelcome: false, completedCurricula: [] }, s); } catch (e) { return { xp: 0, badges: {} }; } } function saveState() { localStorage.setItem(LS_STATE, JSON.stringify(state)); } let state = loadState(); function addXP(n, reason = '') { state.xp = (state.xp || 0) + n; saveState(); toast(`+${n} XP${reason ? ' · ' + reason : ''}`, 'info', 2200); renderXP(); } const LEVELS = [ { min: 0, title: 'AI-Trainee' }, { min: 50, title: 'AI-Analyst:in' }, { min: 200, title: 'ML-Engineer' }, { min: 500, title: 'AI-Lead' }, { min: 1250, title: 'AI-Officer' }, { min: 2500, title: 'AI-Governance-Lead' }, { min: 5000, title: 'Chief AI Officer' } ]; function levelInfo() { let cur = LEVELS[0]; for (const l of LEVELS) if ((state.xp || 0) >= l.min) cur = l; const idx = LEVELS.indexOf(cur); const next = LEVELS[idx + 1] || null; const pct = next ? Math.min(100, ((state.xp - cur.min) / (next.min - cur.min)) * 100) : 100; return { levelNum: idx + 1, title: cur.title, pct, next }; } function renderXP() { const li = levelInfo(); const lvl = $('#xp-level'); const xp = $('#xp-score'); const bar = $('#xp-bar-fill'); if (lvl) lvl.textContent = `Lvl ${li.levelNum} · ${li.title}`; if (xp) xp.textContent = `${state.xp || 0} XP`; if (bar) bar.style.width = li.pct + '%'; } // ========= BADGES ========= const COCKPIT_BADGES = { cockpit_reifegrad_done: 'Reifegrad-Profi', cockpit_ai_act_done: 'AI-Act-Navigator (3 Systeme)', cockpit_dashboard_explored: 'Dashboard-Flaneur:in', cockpit_power_user: 'Cockpit-Power-User' }; function unlockBadge(id) { if (state.badges[id]) return false; state.badges[id] = today(); saveState(); const title = COCKPIT_BADGES[id] || id; toast('🏆 Neues Abzeichen: ' + title, 'success', 4200); // Power-user check if (id !== 'cockpit_power_user') checkPowerUser(); return true; } function checkPowerUser() { if (state.badges.cockpit_reifegrad_done && state.badges.cockpit_ai_act_done && state.badges.cockpit_dashboard_explored) { unlockBadge('cockpit_power_user'); } } // ========= TOAST ========= function toast(msg, kind = '', ms = 3000) { const stack = $('#toast-stack'); if (!stack) return; const el = document.createElement('div'); el.className = 'toast ' + kind; el.textContent = msg; stack.appendChild(el); setTimeout(() => { el.style.transition = 'opacity .3s'; el.style.opacity = '0'; setTimeout(() => el.remove(), 300); }, ms); } // ========= MARKDOWN (light + GFM tables) ========= function renderMD(src) { let t = esc(src); // code fences t = t.replace(/```([\s\S]*?)```/g, (_, c) => `
${c}
`); // inline code t = t.replace(/`([^`]+)`/g, '$1'); // GFM tables — consume before line-based processing t = t.replace(/(?:^|\n)((?:\|[^\n]*\|[ \t]*\n){2,})/g, (block, content) => { const ls = content.trim().split('\n'); if (ls.length < 2) return block; const sep = ls[1]; if (!/^\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)+\|?\s*$/.test(sep)) return block; const parseRow = (ln) => ln.replace(/^\|/, '').replace(/\|\s*$/, '').split('|').map(c => c.trim()); const header = parseRow(ls[0]); const aligns = parseRow(sep).map(s => /^:-+:$/.test(s) ? 'center' : /-+:$/.test(s) ? 'right' : 'left'); const rows = ls.slice(2).map(parseRow); let h = '\n'; header.forEach((hd, i) => { h += ``; }); h += ''; rows.forEach(r => { h += ''; for (let i = 0; i < Math.max(r.length, header.length); i++) { h += ``; } h += ''; }); h += '
${hd}
${r[i] || ''}
\n'; return h; }); // bold + italic t = t.replace(/\*\*([^\*]+)\*\*/g, '$1'); t = t.replace(/\*([^\*]+)\*/g, '$1'); // headings (very lightweight) t = t.replace(/^###\s+(.*)$/gm, '$1'); t = t.replace(/^##\s+(.*)$/gm, '$1'); t = t.replace(/^#\s+(.*)$/gm, '$1'); // bullets + paragraphs (skip inside tables/pre) const lines = t.split('\n'); const out = []; let inUL = false; let inSkip = false; for (const l of lines) { // Skip line-processing while inside or
 blocks
      if (/'); inUL = false; }
        out.push(l);
        if (/<\/table>/.test(l) || /<\/pre>/.test(l)) inSkip = false;
        continue;
      }
      if (/^[-*]\s+/.test(l)) {
        if (!inUL) { out.push(''); inUL = false; }
        if (l.trim()) out.push('

' + l + '

'); } } if (inUL) out.push(''); return out.join(''); } // ===================================================================== // MODULE 1 — REIFEGRAD ASSESSMENT // ===================================================================== const DIMENSIONS = [ { id: 'strategy', title: 'Strategie', short: 'Wo steht KI in eurer Unternehmens-Strategie?', questions: [ 'Hat euer Unternehmen eine dokumentierte KI-Strategie mit Executive-Sponsorship?', 'Sind AI-Use-Cases nach Business-Value priorisiert und in einem Portfolio gelistet?', 'Gibt es ein klares Budget-Committment für AI über 12 Monate hinaus?', 'Ist AI Teil der Digital- oder Geschäftsstrategie (nicht nur IT-Plan)?', 'Wird der ROI von AI-Initiativen auf Portfolio-Ebene an den Vorstand reported?' ] }, { id: 'data', title: 'Daten', short: 'Wie solide ist euer Daten-Fundament?', questions: [ 'Existiert ein zentraler Data-Catalog mit Lineage für relevante Datensätze?', 'Wird Data-Quality (Completeness, Accuracy, Freshness) systematisch gemessen?', 'Habt ihr dokumentierte Data-Governance-Rollen (Data-Owner, Steward)?', 'Ist Master-Data für Kunden/Produkte/Mitarbeitende harmonisiert?', 'Sind Trainingsdaten versioniert und auf Bias/Verzerrungen gescreent (Art. 10 AI Act)?' ] }, { id: 'technology', title: 'Technologie', short: 'MLOps, Registry, Monitoring — wie tief ist euer Stack?', questions: [ 'Habt ihr eine MLOps-Plattform (CI/CD für ML, nicht nur Ad-hoc-Notebooks)?', 'Gibt es eine zentrale Model-Registry mit Versionierung und Approval-Flow?', 'Sind Inferenz-Pipelines auf Latenz, Throughput und Error-Rate beobachtet?', 'Läuft automatisches Drift-Monitoring (Daten- oder Modell-Drift) in Produktion?', 'Ist eure Inferenz-Infrastruktur skalierbar (Cloud/On-Prem/Hybrid) dokumentiert?' ] }, { id: 'people', title: 'Menschen', short: 'Skills, Rollen, AI-Literacy.', questions: [ 'Habt ihr einen AI-/ML-Engineer(in) oder ein Data-Science-Team (intern)?', 'Gibt es AI-Literacy-Training für Non-Tech-Mitarbeitende (Art. 4 AI Act)?', 'Existieren klare Rollen wie CAIO / AI-Risk-Officer / Data-Owner?', 'Wird externe Expertise (Consulting, Fachanwalt IT-Recht) strukturiert eingebunden?', 'Wird AI-Kompetenz in Job-Profile und Hiring-Prozesse integriert?' ] }, { id: 'process', title: 'Prozesse', short: 'Deployment, Monitoring, Incident-Response.', questions: [ 'Habt ihr standardisierte Deployment-Prozesse (Design → Train → Deploy → Retire)?', 'Existiert ein AI-Incident-Response-Plan inkl. Art. 73 AI-Act-Meldewege?', 'Gibt es ein jährliches AI-Program-Review mit Vorstand oder Geschäftsführung?', 'Wurden FRIA (Art. 27) / DSFA (Art. 35 DSGVO) für relevante Systeme durchgeführt?', 'Ist EU AI Act-Klassifikation für ALLE produktiven Systeme abgeschlossen?' ] } ]; // Scale: 0 nicht vorhanden / 1 ad-hoc / 2 geplant / 3 etabliert / 4 optimiert const LIKERT = [ { v: 0, t: 'nicht vorhanden' }, { v: 1, t: 'ad-hoc' }, { v: 2, t: 'geplant' }, { v: 3, t: 'etabliert' }, { v: 4, t: 'optimiert' } ]; const BENCHMARK = { strategy: 2.1, data: 1.8, technology: 2.3, people: 1.6, process: 1.9 }; function loadReifegrad() { try { return JSON.parse(localStorage.getItem(LS_REIFE) || '{}'); } catch (e) { return {}; } } function saveReifegrad(data) { localStorage.setItem(LS_REIFE, JSON.stringify(data)); } const reifeState = { view: 'overview', // 'overview' | 'assess' | 'result' currentDim: null, currentQ: 0, answers: loadReifegrad() // { dimId: [v,v,v,v,v] } }; function reifeScore(dimId) { const arr = reifeState.answers[dimId]; if (!arr || arr.length !== 5 || arr.some(v => v == null)) return null; return arr.reduce((s, v) => s + v, 0) / arr.length; } function reifeDimDone(dimId) { const s = reifeScore(dimId); return s != null; } function reifeCountDone() { return DIMENSIONS.filter(d => reifeDimDone(d.id)).length; } function reifeOverallScore() { const scores = DIMENSIONS.map(d => reifeScore(d.id)).filter(v => v != null); if (!scores.length) return 0; return scores.reduce((s, v) => s + v, 0) / scores.length; } function renderReifegradModule() { const host = $('#reifegrad-host'); if (reifeState.view === 'overview') return renderReifeOverview(host); if (reifeState.view === 'assess') return renderReifeAssessment(host); if (reifeState.view === 'result') return renderReifeResult(host); } function renderReifeOverview(host) { const done = reifeCountDone(); const overall = reifeOverallScore(); host.innerHTML = `
Modul 01

Reifegrad-Assessment

5-Dimensionen-Self-Assessment auf Basis von Gartner + MIT CISR + Microsoft RAI-MM. Pro Dimension 5 Fragen, 0–4 Likert. Ergibt einen Radar gegen den DACH-Mittelstand-Durchschnitt (BITKOM/Fraunhofer 2024).

${done}/5 Dimensionen beantwortet ${overall.toFixed(2)} / 4.00 Overall-Score ${done === 5 ? 'komplett' : 'in Arbeit'}
${DIMENSIONS.map(d => { const s = reifeScore(d.id); const pct = s != null ? (s / 4) * 100 : 0; const done = s != null; return ` `; }).join('')}
${done === 5 ? `` : ''} ${done > 0 ? `` : ''}
${done === 5 ? renderBadgeRow() : ''} `; // Events $$('.dim-card', host).forEach(el => el.addEventListener('click', () => { reifeState.currentDim = el.dataset.dim; reifeState.currentQ = 0; reifeState.view = 'assess'; renderReifegradModule(); })); const startBtn = $('#reife-start', host); if (startBtn) startBtn.addEventListener('click', () => { if (done === 5) { reifeState.view = 'result'; renderReifegradModule(); return; } // find first un-done dim const next = DIMENSIONS.find(d => !reifeDimDone(d.id)) || DIMENSIONS[0]; reifeState.currentDim = next.id; reifeState.currentQ = 0; reifeState.view = 'assess'; renderReifegradModule(); }); const resBtn = $('#reife-result', host); if (resBtn) resBtn.addEventListener('click', () => { reifeState.view = 'result'; renderReifegradModule(); }); const resetBtn = $('#reife-reset', host); if (resetBtn) resetBtn.addEventListener('click', () => { if (confirm('Alle Antworten löschen?')) { reifeState.answers = {}; saveReifegrad({}); renderReifegradModule(); } }); } function renderBadgeRow() { const ids = Object.keys(COCKPIT_BADGES); return `
${ids.map(id => { const earned = !!state.badges[id]; return `${esc(COCKPIT_BADGES[id])}`; }).join('')}
`; } function renderReifeAssessment(host) { const dim = DIMENSIONS.find(d => d.id === reifeState.currentDim); if (!dim) { reifeState.view = 'overview'; return renderReifegradModule(); } const qi = reifeState.currentQ; const q = dim.questions[qi]; const answers = reifeState.answers[dim.id] || Array(5).fill(null); const selected = answers[qi]; const total = dim.questions.length; const pct = ((qi) / total) * 100; host.innerHTML = `
Dimension · ${esc(dim.title)}

${esc(dim.title)}

${esc(dim.short)}

Frage ${qi + 1} / ${total}

${esc(q)}

Skala: 0 = nicht vorhanden, 4 = optimiert & firmenweit verankert

${LIKERT.map(l => ` `).join('')}
`; $('#reife-back', host).addEventListener('click', () => { reifeState.view = 'overview'; renderReifegradModule(); }); $$('.likert-btn', host).forEach(b => b.addEventListener('click', () => { const v = parseInt(b.dataset.v, 10); if (!reifeState.answers[dim.id]) reifeState.answers[dim.id] = Array(5).fill(null); const wasEmpty = reifeState.answers[dim.id][qi] == null; reifeState.answers[dim.id][qi] = v; saveReifegrad(reifeState.answers); if (wasEmpty) addXP(2, 'Antwort'); renderReifeAssessment(host); })); const prevBtn = $('#q-prev', host); if (prevBtn) prevBtn.addEventListener('click', () => { reifeState.currentQ = Math.max(0, qi - 1); renderReifeAssessment(host); }); const nextBtn = $('#q-next', host); if (nextBtn) nextBtn.addEventListener('click', () => { if (qi < total - 1) { reifeState.currentQ = qi + 1; renderReifeAssessment(host); } else { // dim completed const justCompleted = reifeDimDone(dim.id); const prevDoneCount = DIMENSIONS.filter(d => d.id !== dim.id && reifeDimDone(d.id)).length; if (justCompleted) { // +20 per dim (first completion only) handled by checking badges flag per dim const key = `reife_dim_${dim.id}`; if (!state.badges[key]) { state.badges[key] = today(); saveState(); addXP(20, `Dimension ${dim.title} komplett`); } } const allDone = reifeCountDone() === 5; if (allDone && !state.badges.cockpit_reifegrad_done) { addXP(50, 'Reifegrad-Assessment abgeschlossen'); unlockBadge('cockpit_reifegrad_done'); } reifeState.view = allDone ? 'result' : 'overview'; renderReifegradModule(); } }); $('#ask-kai-dim', host).addEventListener('click', () => { openDock(); const prompt = `Was sind die wichtigsten Hebel, um in der Dimension "${dim.title}" vom DACH-Mittelstand (Durchschnitt ${BENCHMARK[dim.id].toFixed(1)}) auf Stufe 3 (etabliert) zu kommen?`; fillDockInput(prompt); }); } function renderReifeResult(host) { const scores = DIMENSIONS.map(d => ({ dim: d, score: reifeScore(d.id) || 0 })); const overall = reifeOverallScore(); // Find 3 weakest for recommendations const weakest = [...scores].sort((a, b) => a.score - b.score).slice(0, 3); host.innerHTML = `
Ergebnis

Dein Reifegrad-Profil

Radar gegen DACH-Mittelstand (BITKOM 2024: ~Gartner-Stufe 2 Mittelwert). Die 3 schwächsten Achsen sind deine Roadmap.

${renderRadarSVG(scores)}
${overall.toFixed(2)} / 4.00
${overallLabel(overall)}
Deine Werte
DACH-Mittelstand Ø

Top-Empfehlungen

${weakest.map((w, i) => { const reco = weakestReco(w.dim.id, w.score); return `
${i + 1}

${esc(w.dim.title)} · ${w.score.toFixed(1)} / 4.0

${esc(reco)}

`; }).join('')}
${renderBadgeRow()} `; $('#reife-overview-btn', host).addEventListener('click', () => { reifeState.view = 'overview'; renderReifegradModule(); }); $('#ask-kai-overall', host).addEventListener('click', () => { openDock(); const txt = `Mein Reifegrad-Assessment ergab: ${scores.map(s => `${s.dim.title} ${s.score.toFixed(1)}`).join(', ')}. Gesamt ${overall.toFixed(2)}/4.0. Erstelle mir eine priorisierte 12-Monats-Roadmap für die schwächsten 3 Dimensionen.`; fillDockInput(txt); }); $$('[data-dim]', host).forEach(b => { if (b.classList.contains('btn-ask-kai')) { b.addEventListener('click', () => { const d = DIMENSIONS.find(dd => dd.id === b.dataset.dim); if (!d) return; openDock(); fillDockInput(`Meine Dimension "${d.title}" liegt bei ${(reifeScore(d.id) || 0).toFixed(1)} / 4.0 (DACH-Ø ${BENCHMARK[d.id]}). Was sind die 3 wichtigsten Maßnahmen in den nächsten 90 Tagen?`); }); } }); } function overallLabel(s) { if (s < 1) return 'Foundational / Experimenting — Start beim AI-Strategie-Dokument + Sponsorship.'; if (s < 2) return 'Emerging / Piloting — 3-5 Pilots mit klaren Business-Cases als Fokus.'; if (s < 2.8) return 'Operational / Scaling — der kritische Sprung. MLOps + Monitoring konsolidieren.'; if (s < 3.5) return 'Scaled — Platform + AI-Engineering-Team in Produktion.'; return 'Transformational / Optimizing — AI ist Teil des Geschäftsmodells.'; } function weakestReco(dimId, score) { const low = { strategy: 'AI-Charter in 2-3 Seiten schreiben, vom CEO unterschreiben lassen. Quartals-Review einführen. Use-Case-Intake-Template verpflichtend ab Tag 1.', data: 'Data-Catalog aktivieren (Open-Source-Stack reicht initial). Data-Quality-Scorecard für Top-5-Datensätze. Data-Owner benennen.', technology: 'MLOps auf 1 Pattern einkochen (z.B. Training + Registry + Serving). Modell-Drift-Monitoring als Muss vor jedem Go-Live.', people: 'AI-Literacy-Training für alle Mitarbeitenden (Art. 4 AI-Act-Pflicht). AI-Risk-Officer benennen — reicht initial als 20%-Rolle.', process: 'Model-Lifecycle-Standard auf 1 Seite. EU-AI-Act-Klassifikation für alle Systeme durchführen. Incident-Response-Plan einmal durchspielen.' }; const mid = { strategy: 'Portfolio-Review quartalsweise: Welcher Use-Case wirft ROI? Welcher wird retiret? Business-Case-Templates standardisieren.', data: 'Data-Lineage end-to-end (nicht nur Katalog-Eintrag). Trainingsdaten auf Bias screenen (Art. 10 AI-Act-Pflicht für High-Risk).', technology: 'Von Ad-hoc-Monitoring zu Self-Service-Observability. Model-Registry mit Approval-Workflow. Automated Retrain-Pipelines.', people: 'CAIO oder AI-Risk-Officer in Vollzeit. Hiring-Profil überarbeiten. Externe Fachanwalt-IT-Recht als Retainer.', process: 'FRIA + DSFA kombiniert durchführen. Jährlicher Program-Review mit Vorstand. ISO 42001 als Rahmen adaptieren.' }; return score < 2 ? low[dimId] : mid[dimId]; } // ========= RADAR SVG ========= function renderRadarSVG(scoresArr) { const size = 420; const cx = size / 2; const cy = size / 2; const radius = 150; const rings = 4; // 0-4 scale, 4 rings const n = scoresArr.length; const angleFor = (i) => (-Math.PI / 2) + (i * 2 * Math.PI / n); const pointAt = (i, val) => { const a = angleFor(i); const r = (val / 4) * radius; return [cx + r * Math.cos(a), cy + r * Math.sin(a)]; }; // Grid let grid = ''; for (let r = 1; r <= rings; r++) { const pts = []; for (let i = 0; i < n; i++) { const a = angleFor(i); const rr = (r / rings) * radius; pts.push([cx + rr * Math.cos(a), cy + rr * Math.sin(a)]); } grid += ``; } // Axes + labels let axes = ''; let labels = ''; for (let i = 0; i < n; i++) { const [x, y] = pointAt(i, 4); axes += ``; const a = angleFor(i); const lx = cx + (radius + 26) * Math.cos(a); const ly = cy + (radius + 26) * Math.sin(a); labels += `${esc(scoresArr[i].dim.title)}`; } // Benchmark polygon const benchPts = scoresArr.map((s, i) => pointAt(i, BENCHMARK[s.dim.id] || 2)); const bench = ``; // User polygon const userPts = scoresArr.map((s, i) => pointAt(i, s.score)); const user = ``; const dots = scoresArr.map((s, i) => { const [x, y] = pointAt(i, s.score); return ``; }).join(''); return ` ${grid} ${axes} ${bench} ${user} ${dots} ${labels} `; } // ===================================================================== // MODULE 2 — EU AI ACT CLASSIFIER // ===================================================================== // Simple decision tree — 7 yes/no questions, returns verdict object const AIACT_TREE = [ { id: 'q1', key: 'prohibited', q: 'Wird das System für Social-Scoring, Massenüberwachung, unterschwellige Manipulation oder Emotion-Recognition am Arbeitsplatz / in der Schule eingesetzt?', hint: 'Art. 5 AI Act — „Prohibited Practices" gelten seit Februar 2025.', yes: { verdict: 'prohibited' }, no: 'q2' }, { id: 'q2', key: 'safety_component', q: 'Ist das System Sicherheitskomponente eines regulierten Produkts (Medizin-Device, Maschine, Fahrzeug, kritische Infrastruktur, Spielzeug)?', hint: 'Art. 6 Abs. 1 — Weg A zu High-Risk via Anhang I.', yes: { verdict: 'high', reason: 'safety_component' }, no: 'q3' }, { id: 'q3', key: 'annex_iii', q: 'Wird das System für HR-Entscheidungen, Recruiting, Kredit-/Versicherungs-Scoring, Strafverfolgung, Bildungsbewertung, Migration oder Biometrie eingesetzt?', hint: 'Anhang III Kategorien 1, 3, 4, 5, 6, 7. Deutsche Mittelständler unterschätzen HR-AI oft.', yes: { verdict: 'high', reason: 'annex_iii' }, no: 'q4' }, { id: 'q4', key: 'narrow_task', q: 'Erfüllt das System nur eine schmale, vorbereitende Aufgabe (z. B. reine Duplikat-Erkennung, Mustererkennung ohne Profiling)?', hint: 'Art. 6 Abs. 3 — Opt-out aus High-Risk, muss dokumentiert werden.', yes: { verdict: 'limited', reason: 'narrow_task_opt_out' }, no: 'q5' }, { id: 'q5', key: 'chatbot', q: 'Ist es ein Chatbot, ein Deepfake-/Synthetic-Media-Generator oder interagiert direkt mit Endnutzer:innen als AI?', hint: 'Art. 50 — Transparenzpflicht: „Du sprichst mit einer KI."', yes: { verdict: 'limited', reason: 'chatbot' }, no: 'q6' }, { id: 'q6', key: 'pii', q: 'Werden personenbezogene Daten verarbeitet oder zum Training verwendet?', hint: 'DSGVO greift zusätzlich — Art. 10 AI Act + Art. 5 DSGVO synchron behandeln.', yes: { verdict: 'minimal', reason: 'dsgvo_pflicht' }, no: 'q7' }, { id: 'q7', key: 'gpai', q: 'Ist es ein General-Purpose-AI-Modell oder wird ein solches direkt in eurer Kontrolle trainiert (Foundation-Model mit > 10^25 FLOPs)?', hint: 'Art. 51-56 GPAI-Regeln — für die meisten Deployer nicht relevant.', yes: { verdict: 'minimal', reason: 'gpai_deployer' }, no: { verdict: 'minimal', reason: 'default' } } ]; const aiactState = { step: 0, answers: {}, verdict: null, runs: (function() { try { return JSON.parse(localStorage.getItem(LS_AIACT) || '{}').runs || []; } catch (e) { return []; } })() }; function saveAIActRuns() { localStorage.setItem(LS_AIACT, JSON.stringify({ runs: aiactState.runs })); } const VERDICTS = { prohibited: { title: 'Verboten (Prohibited Practice)', label: 'Art. 5 AI Act', cls: 'prohibited', summary: 'Dein System fällt unter Art. 5 und ist seit dem 2. Februar 2025 in der EU verboten. Weiterbetrieb ist keine Option.', obligations: [ 'Art. 5: Unverzüglicher Stopp und Abbau', 'Art. 99: Strafen bis zu 35 Mio. EUR oder 7 % weltweiter Jahresumsatz (höher)', 'Legal-Review + Information der Aufsichtsbehörde prüfen', 'Alternative Implementierung ohne Art-5-Praktik entwerfen' ] }, high: { title: 'High-Risk', label: 'Art. 6 + Anhang III', cls: 'high', summary: 'Umfangreiche Pflichten. Für Deployer + Provider unterschiedlich. Vollständige Anwendung ab 2. August 2026.', obligations: [ 'Art. 9: Risk-Management-System etablieren', 'Art. 10: Data-Governance — Training/Validation/Test dokumentieren, Bias-Screening', 'Art. 11 + Anhang IV: Technical Documentation', 'Art. 13: Transparency — User-Instructions für Deployer', 'Art. 14: Human Oversight — dokumentierte Eingriffsmöglichkeit', 'Art. 15: Accuracy, Robustness, Cybersecurity', 'Art. 26: Deployer-Pflichten (Monitoring, Logs, Information)', 'Art. 27: Fundamental Rights Impact Assessment (FRIA) — kombinierbar mit DSFA', 'Art. 43: Conformity Assessment + CE-Kennzeichnung', 'Art. 49: Registrierung in EU-Datenbank' ] }, limited: { title: 'Limited Risk', label: 'Art. 50', cls: 'limited', summary: 'Transparenzpflichten. Kein CE-Zwang, aber Hinweispflicht gegenüber Nutzer:innen.', obligations: [ 'Art. 50 Abs. 1: Kennzeichnung — "Du interagierst mit einer KI"', 'Art. 50 Abs. 2: Synthetic-Content maschinenlesbar markieren', 'Art. 50 Abs. 4: Deepfakes offenlegen', 'DSGVO Art. 13/14: Informationspflichten, wenn personenbezogene Daten verarbeitet', 'Art. 4 (AI-Literacy) — gilt für ALLE AI-Nutzungen, unabhängig von Klasse' ] }, minimal: { title: 'Minimal Risk', label: 'Freiwillig + Art. 4', cls: 'minimal', summary: 'Keine spezifischen AI-Act-Pflichten außer AI-Literacy (Art. 4). DSGVO gilt weiterhin bei personenbezogenen Daten.', obligations: [ 'Art. 4: AI-Literacy-Training für Mitarbeitende (seit Feb 2025)', 'Freiwillige Codes of Conduct empfohlen (Art. 95)', 'DSGVO Art. 5/6: Rechtmäßigkeitsgrundlage + Grundsätze prüfen', 'Art. 35 DSGVO: DSFA bei hohem Risiko für Betroffene' ] } }; function renderAIActModule() { const host = $('#aiact-host'); if (aiactState.verdict) return renderAIActVerdict(host); renderAIActQuestion(host); } function renderAIActQuestion(host) { const step = aiactState.step; const node = AIACT_TREE[step]; const totalSteps = AIACT_TREE.length; host.innerHTML = `
Modul 02

EU AI Act — Risk-Classifier

Entscheidungsbaum in einfacher Sprache. Am Ende: Risikoklasse + konkrete Pflichten mit Artikel-Bezug. Keine Rechtsberatung — für die konkrete Umsetzung mit IT-Recht-Fachanwalt sprechen.

${AIACT_TREE.map((_, i) => ``).join('')}

${esc(node.q)}

${esc(node.hint)}

${aiactState.runs.length > 0 ? `
Bereits ${aiactState.runs.length} Systeme klassifiziert. ${aiactState.runs.length >= 3 && !state.badges.cockpit_ai_act_done ? '' : ''}
` : ''} `; $$('[data-ans]', host).forEach(btn => btn.addEventListener('click', () => { const ans = btn.dataset.ans; aiactState.answers[node.key] = ans; const branch = node[ans]; if (branch && typeof branch === 'object' && branch.verdict) { aiactState.verdict = { kind: branch.verdict, reason: branch.reason || null }; // record aiactState.runs.push({ ts: Date.now(), verdict: branch.verdict, answers: { ...aiactState.answers } }); // cap stored runs at 10 aiactState.runs = aiactState.runs.slice(-10); saveAIActRuns(); addXP(30, 'AI-Act-Classifier Run'); if (aiactState.runs.length >= 3 && !state.badges.cockpit_ai_act_done) { unlockBadge('cockpit_ai_act_done'); } renderAIActModule(); } else if (typeof branch === 'string') { // find next step const nextIdx = AIACT_TREE.findIndex(n => n.id === branch); aiactState.step = nextIdx !== -1 ? nextIdx : step + 1; renderAIActModule(); } else { // default — move on aiactState.step = step + 1; renderAIActModule(); } })); $('#aiact-restart', host).addEventListener('click', () => { aiactState.step = 0; aiactState.answers = {}; aiactState.verdict = null; renderAIActModule(); }); } function renderAIActVerdict(host) { const v = VERDICTS[aiactState.verdict.kind]; const reason = aiactState.verdict.reason; host.innerHTML = `
Ergebnis

Klassifikation

Basierend auf deinen Antworten. Speichere das Ergebnis, druck's aus, zeig's dem Fachanwalt.

${esc(v.label)}

${esc(v.title)}

${esc(v.summary)}

Konkrete Pflichten & Artikel

    ${v.obligations.map(o => { const parts = o.split(':'); if (parts.length > 1) return `
  • ${esc(parts[0])}:${esc(parts.slice(1).join(':'))}
  • `; return `
  • ${esc(o)}
  • `; }).join('')}
Runs insgesamt: ${aiactState.runs.length} / für „AI-Act-Navigator"-Badge 3 verschiedene Systeme
`; $('#aiact-another', host).addEventListener('click', () => { aiactState.step = 0; aiactState.answers = {}; aiactState.verdict = null; renderAIActModule(); }); $('#aiact-ask-kai', host).addEventListener('click', () => { openDock(); const p = `Mein System wurde als "${v.title}" (${v.label}) klassifiziert. Was sind die 5 häufigsten Umsetzungsfehler in dieser Klasse, die ich vermeiden soll?`; fillDockInput(p); }); } // ===================================================================== // MODULE 3 — KPI DASHBOARD (demo) // ===================================================================== const TILES = [ // Executive { id: 'adoption', group: 'exec', label: 'Adoption Rate', value: '73%', trend: '+4% vs. Vormonat', trendKind: 'up', explain: 'Anteil der anspruchsberechtigten User:innen, die das AI-System im letzten Monat aktiv genutzt haben. Proxy für Change-Success.', series: [54, 58, 62, 64, 67, 70, 73] }, { id: 'savings', group: 'exec', label: 'Monthly Cost Savings', value: '€ 38.400', trend: '+€3.2k vs. Vormonat', trendKind: 'up', explain: 'Eingesparte operative Kosten durch AI-Automatisierung (Service-Deflection, Process-Shortening). Teil des Business-KPI-Sets (Modul 02).', series: [22000, 26500, 29000, 30500, 33000, 35200, 38400] }, { id: 'trust', group: 'exec', label: 'Trust Score', value: '4.1 / 5', trend: '+0.2 vs. Q-1', trendKind: 'up', explain: 'User-Survey NPS-ähnlich: „Vertrauen Sie den AI-Antworten?". Zielwert > 4.0 für Skalierung.', series: [3.5, 3.6, 3.7, 3.8, 3.9, 4.0, 4.1] }, // Operational { id: 'latency', group: 'ops', label: 'P95 Latency', value: '182 ms', trend: '-18ms vs. Vorwoche', trendKind: 'up', explain: 'P95-Response-Time Ende-zu-Ende. Ziel < 300ms für konversationelle UX. Hinweis: nicht der Mittelwert — der P95 zählt.', series: [210, 205, 200, 195, 190, 185, 182] }, { id: 'throughput', group: 'ops', label: 'Throughput', value: '840 req/s', trend: '+12 req/s', trendKind: 'up', explain: 'Peak-Requests pro Sekunde. Kapazitätsplanung + Kostenschätzung pro Inferenz.', series: [720, 750, 760, 785, 800, 820, 840] }, { id: 'errors', group: 'ops', label: 'Error Rate', value: '0.8%', trend: '-0.1% vs. Vorwoche', trendKind: 'up', explain: 'Fehlgeschlagene Requests (5xx + Fachlich-Invalid). Ziel < 1%. Alarm bei > 2%.', series: [1.2, 1.1, 1.0, 0.95, 0.9, 0.85, 0.8] }, { id: 'availability', group: 'ops', label: 'Availability', value: '99.92%', trend: 'SLO ≥ 99.9%', trendKind: 'neutral', explain: 'Uptime des AI-Service. Typisches SLO 99.9% (= ~43min Downtime/Monat). Tracking pro Region + Gesamt.', series: [99.85, 99.87, 99.9, 99.92, 99.91, 99.92, 99.92] }, // Technical { id: 'accuracy', group: 'tech', label: 'Model Accuracy', value: '94.3%', trend: '+0.4% vs. Baseline', trendKind: 'up', explain: 'Classification-Accuracy auf Holdout-Set. Achtung: Accuracy alleine unzuverlässig bei imbalancierten Klassen — Prüfe zusätzlich Precision/Recall.', series: [92.5, 93.0, 93.4, 93.7, 94.0, 94.2, 94.3] }, { id: 'f1', group: 'tech', label: 'F1 Score', value: '0.89', trend: '+0.02 vs. Vormonat', trendKind: 'up', explain: 'Harmonisches Mittel aus Precision und Recall. Robust gegen Klassenungleichheit.', series: [0.83, 0.85, 0.86, 0.87, 0.88, 0.885, 0.89] }, { id: 'drift', group: 'tech', label: 'Drift (PSI)', value: '0.07', trend: 'ok (< 0.1)', trendKind: 'up', explain: 'Population Stability Index. 0 = keine Drift, > 0.1 = moderate Drift, > 0.25 = signifikant → Retrain.', series: [0.03, 0.04, 0.05, 0.055, 0.06, 0.065, 0.07] }, { id: 'bias', group: 'tech', label: 'Bias (EO Gap)', value: '0.03', trend: 'EO-OK (< 0.05)', trendKind: 'up', explain: 'Equal-Opportunity-Differenz zwischen protected und unprotected group. Hinweis: Bias-Metriken immer auf Fairness-Definition (DP vs. EO vs. EOd) abstimmen.', series: [0.07, 0.06, 0.055, 0.05, 0.045, 0.04, 0.03] } ]; function loadDash() { try { return JSON.parse(localStorage.getItem(LS_DASH) || '{"seen": []}'); } catch (e) { return { seen: [] }; } } function saveDash(d) { localStorage.setItem(LS_DASH, JSON.stringify(d)); } const dashState = loadDash(); if (!Array.isArray(dashState.seen)) dashState.seen = []; function renderDashboardModule() { const host = $('#dashboard-host'); const groups = [ { key: 'exec', title: 'Executive Layer — Board-Kennzahlen', cls: 'exec' }, { key: 'ops', title: 'Operational Layer — Service-Health', cls: 'ops' }, { key: 'tech', title: 'Technical Layer — Model-Qualität', cls: 'tech' } ]; host.innerHTML = `
Modul 03

KPI-Dashboard für KI-Systeme

So könnte dein erstes Dashboard aussehen — drei Layer, Executive/Operational/Technical. Klick auf jedes Tile für Sparkline + „Was misst das?". Daten sind Demo-Werte, die Struktur ist das, was zählt.

${groups.map(g => `
${esc(g.title)}
${TILES.filter(t => t.group === g.key).map(t => { const seen = dashState.seen.includes(t.id); return ` `; }).join('')}
`).join('')}

${dashState.seen.length} / ${TILES.length} Tiles geöffnet — ${dashState.seen.length === TILES.length ? 'komplett!' : `öffne alle für das Dashboard-Flaneur:in-Abzeichen`}

`; $$('[data-tile]', host).forEach(btn => btn.addEventListener('click', () => openTileModal(btn.dataset.tile))); } function openTileModal(id) { const tile = TILES.find(t => t.id === id); if (!tile) return; // mark seen if (!dashState.seen.includes(id)) { dashState.seen.push(id); saveDash(dashState); // partial XP per new tile addXP(2, 'Tile geöffnet'); if (dashState.seen.length === TILES.length && !state.badges.cockpit_dashboard_explored) { addXP(25, 'Alle Tiles erkundet'); unlockBadge('cockpit_dashboard_explored'); } } // re-render to reflect seen state renderDashboardModule(); // open modal openModal(`
${renderSparkline(tile.series)}

${esc(tile.explain)}

`); $('#modal-ask-kai').addEventListener('click', () => { closeModal(); openDock(); fillDockInput(`Erklär mir die Kennzahl "${tile.label}" im Kontext eines AI-Systems in Produktion. Welche Alert-Schwellen sind üblich?`); }); } function renderSparkline(series) { if (!series || series.length < 2) return ''; const w = 420; const h = 80; const pad = 8; const min = Math.min(...series); const max = Math.max(...series); const range = (max - min) || 1; const xStep = (w - pad * 2) / (series.length - 1); const pts = series.map((v, i) => { const x = pad + i * xStep; const y = h - pad - ((v - min) / range) * (h - pad * 2); return [x, y]; }); const path = pts.map((p, i) => (i === 0 ? 'M' : 'L') + p[0].toFixed(1) + ' ' + p[1].toFixed(1)).join(' '); // area fill const area = path + ` L ${pts[pts.length-1][0].toFixed(1)} ${h - pad} L ${pts[0][0].toFixed(1)} ${h - pad} Z`; const last = pts[pts.length - 1]; return ` `; } // ========= MODAL ========= function openModal(innerHTML) { const root = $('#modal-root'); root.innerHTML = ``; root.classList.add('open'); root.setAttribute('aria-hidden', 'false'); $$('[data-close]', root).forEach(b => b.addEventListener('click', closeModal)); root.addEventListener('click', backdropClose); document.addEventListener('keydown', modalEscClose); // focus first focusable const first = root.querySelector('button,[href],textarea,input'); if (first) setTimeout(() => first.focus(), 50); } function backdropClose(e) { if (e.target.id === 'modal-root') closeModal(); } function modalEscClose(e) { if (e.key === 'Escape') closeModal(); } function closeModal() { const root = $('#modal-root'); root.classList.remove('open'); root.setAttribute('aria-hidden', 'true'); root.innerHTML = ''; root.removeEventListener('click', backdropClose); document.removeEventListener('keydown', modalEscClose); } // ===================================================================== // MODULE 4 — CHAT DOCK (+ FULL CHAT in module-4) // ===================================================================== function loadDockChat() { try { return JSON.parse(localStorage.getItem(LS_CHAT) || '[]'); } catch (e) { return []; } } function saveDockChat() { const trimmed = dockHistory.slice(-30); localStorage.setItem(LS_CHAT, JSON.stringify(trimmed)); } let dockHistory = loadDockChat(); let dockBusy = false; function renderDockMessages() { const box = $('#dock-box'); if (!box) return; if (dockHistory.length === 0) { box.innerHTML = `
Hi, ich bin Kai. Frag mich zu Metriken, AI Act, Reifegrad — oder klick "Frag Kai" in den Modulen.
`; return; } box.innerHTML = ''; for (const m of dockHistory) { const el = document.createElement('div'); el.className = 'dock-msg ' + (m.role === 'assistant' ? 'bot' : 'user'); if (m.role === 'assistant') el.innerHTML = renderMD(m.content); else el.textContent = m.content; box.appendChild(el); } box.scrollTop = box.scrollHeight; } function openDock() { const dock = $('#chat-dock'); dock.classList.remove('collapsed'); dock.classList.add('open'); // mobile: focus input setTimeout(() => { const ta = $('#dock-input'); if (ta) ta.focus(); }, 60); } function closeDock() { const dock = $('#chat-dock'); dock.classList.remove('open'); if (window.innerWidth > 960) dock.classList.add('collapsed'); } function fillDockInput(text) { const ta = $('#dock-input'); if (!ta) return; ta.value = text; ta.focus(); // autoresize ta.style.height = 'auto'; ta.style.height = Math.min(140, ta.scrollHeight) + 'px'; } async function sendDockChat(text) { if (!text.trim() || dockBusy) return; if (!KEY) { appendDockMsg('err', 'Kein API-Schlüssel konfiguriert. window.__KAI_KEY__ fehlt.'); return; } dockBusy = true; const sendBtn = $('#dock-send'); if (sendBtn) sendBtn.disabled = true; dockHistory.push({ role: 'user', content: text }); renderDockMessages(); saveDockChat(); const box = $('#dock-box'); const pending = document.createElement('div'); pending.className = 'dock-msg bot'; pending.innerHTML = ''; box.appendChild(pending); box.scrollTop = box.scrollHeight; try { const r = await fetch(API, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + KEY }, body: JSON.stringify({ message: text, history: dockHistory.slice(0, -1).slice(-20) }) }); let data; try { data = await r.json(); } catch (e) { throw new Error('Server-Antwort nicht lesbar'); } if (!r.ok) throw new Error(data.error || ('HTTP ' + r.status)); pending.remove(); dockHistory.push({ role: 'assistant', content: data.reply || '(leere Antwort)' }); saveDockChat(); renderDockMessages(); // award small XP for first-ever question of the session (tracked via timestamp) if (!state._lastDockQ || (Date.now() - state._lastDockQ) > 60000) { state._lastDockQ = Date.now(); saveState(); addXP(5, 'Frage an Kai'); } } catch (err) { pending.remove(); appendDockMsg('err', 'Fehler: ' + (err.message || String(err))); } finally { dockBusy = false; if (sendBtn) sendBtn.disabled = false; const ta = $('#dock-input'); if (ta) { ta.value = ''; ta.style.height = 'auto'; ta.focus(); } } } function appendDockMsg(kind, content) { const box = $('#dock-box'); if (!box) return; const el = document.createElement('div'); el.className = 'dock-msg ' + kind; el.textContent = content; box.appendChild(el); box.scrollTop = box.scrollHeight; } function resetDock() { if (!confirm('Chat-Verlauf im Dock löschen?')) return; dockHistory = []; saveDockChat(); renderDockMessages(); toast('Dock-Chat zurückgesetzt', 'info', 1800); } // Full chat module (module 4) — shows a larger empty-state + shares state with dock function renderChatFullModule() { const host = $('#chat-full-host'); host.innerHTML = `
Modul 04

Chat mit Kai

Dein ruhiger, systematischer Sparring-Coach. Isolierter Chat-Verlauf vom Widget — hier diskutierst du Cockpit-Themen. Shortcut: Ctrl+K fokussiert das Dock-Input rechts.

Quick-Starter:

Öffne das Dock (rechts oder unten), um zu chatten — oder klicke einen Starter, um eine Frage vorzubereiten.

`; $$('.chip', host).forEach(c => c.addEventListener('click', () => { openDock(); fillDockInput(c.dataset.chip); })); } // ===================================================================== // NAV + ROUTING // ===================================================================== function switchModule(modId) { $$('.module').forEach(m => m.dataset.active = (m.id === 'mod-' + modId) ? 'true' : 'false'); $$('.nav-item').forEach(n => n.setAttribute('aria-selected', (n.dataset.module === modId) ? 'true' : 'false')); // Re-render current module fresh if (modId === 'reifegrad') renderReifegradModule(); if (modId === 'aiact') renderAIActModule(); if (modId === 'dashboard') renderDashboardModule(); if (modId === 'chat') renderChatFullModule(); } function installNav() { $$('.nav-item').forEach(n => n.addEventListener('click', () => switchModule(n.dataset.module))); } function installKeyboardShortcuts() { document.addEventListener('keydown', (e) => { // Ctrl+1..4 -> switch module if ((e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey && ['1','2','3','4'].includes(e.key)) { const map = { '1': 'reifegrad', '2': 'aiact', '3': 'dashboard', '4': 'chat' }; e.preventDefault(); switchModule(map[e.key]); return; } // Ctrl+K -> focus dock if ((e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey && e.key.toLowerCase() === 'k') { e.preventDefault(); openDock(); const ta = $('#dock-input'); if (ta) ta.focus(); } }); } function installDock() { renderDockMessages(); const form = $('#dock-form'); const input = $('#dock-input'); // autoresize input.addEventListener('input', () => { input.style.height = 'auto'; input.style.height = Math.min(140, input.scrollHeight) + 'px'; }); input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendDockChat(input.value); } }); form.addEventListener('submit', (e) => { e.preventDefault(); sendDockChat(input.value); }); $('#dock-reset').addEventListener('click', resetDock); $('#dock-collapse').addEventListener('click', () => { const dock = $('#chat-dock'); if (window.innerWidth <= 960) { dock.classList.remove('open'); } else { dock.classList.add('collapsed'); } }); $('#dock-open').addEventListener('click', openDock); } // ===================================================================== // BOOT // ===================================================================== function boot() { // Ensure badges object exists if (!state.badges) { state.badges = {}; saveState(); } renderXP(); installNav(); installKeyboardShortcuts(); installDock(); // Initial module switchModule('reifegrad'); // Touch activity for streak (shared widget logic would also do this, harmless redundancy) state.lastActive = today(); saveState(); // Welcome toast if first visit if (!state.seenCockpitWelcome) { state.seenCockpitWelcome = true; saveState(); setTimeout(() => toast('Willkommen im Kai-Cockpit. Start mit dem Reifegrad-Assessment.', 'info', 3800), 600); } } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', boot); } else { boot(); } })();