/* 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 += `
${hd}
`; });
h += '
';
rows.forEach(r => {
h += '
';
for (let i = 0; i < Math.max(r.length, header.length); i++) {
h += `
${r[i] || ''}
`;
}
h += '
';
});
h += '
\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 = true; }
out.push('
' + l.replace(/^[-*]\s+/, '') + '
');
} else {
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).
${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 ``;
}
// =====================================================================
// 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.
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.
${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(`
${esc(tile.label)}
${esc(tile.value)}
${esc(tile.trend)}
${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 = `
${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();
}
})();