Files
dogfather-universe/workspace/assets/js/startcheck.js
T
DogFatherGit 4d7d9b183b Workspace: KI-Vorschlaege in Calls, Start-Check und Report
Nach der Korrektur von CPUQuota (50% -> 200%) ist die KI benutzbar:
nr_throttled steht bei 0 (vorher 1752 von 1801 Zeitfenstern).

Gemessen NACH der Korrektur:
  To-dos aus einem Protokoll : 12,7 s (vorher: Abbruch nach 45 s)
  Website waehrend der Arbeit: 0,063 s -- Basis war 0,068 s

Kein Einfluss auf die Website, wie bei der ersten Messung mit zwei
echten Kernen vorhergesagt.

DREI KNOEPFE, alle nach demselben Muster:

- Calls: "To-dos vorschlagen" liest, was im Protokoll schon steht, und
  fuellt LEERE To-do-Zeilen. Bereits Getipptes wird nie ueberschrieben.
- Start-Check: "Besser formulieren" macht aus einer Notiz einen
  Aufgabentitel. Der einfache Vorschlag (erster Satz der Notiz) bleibt
  daneben bestehen -- er funktioniert auch ohne KI.
- Report: "In Worte fassen" fasst die Zahlen in drei Saetzen zusammen.

IST DIE KI AUS, GIBT ES DEN KNOPF NICHT. Jede Seite fragt beim Laden
einmal nach. Ein Knopf, der immer eine Fehlermeldung bringt, ist
schlimmer als gar kein Knopf.

Die Absicherung steht in EINER Datei (assets/js/ki.js), nicht dreimal.
Der Knopf sagt waehrend der Arbeit "Die KI denkt ..." und der Funke
pulst -- zwoelf Sekunden ohne Rueckmeldung fuehlen sich sonst an wie
ein Fehler.

Optisch bewusst ANDERS als die normalen Knoepfe: gestrichelter Rand
statt geschliffener Kante. Ein KI-Knopf tut nichts Endgueltiges, er
schlaegt vor -- das soll man auf den ersten Blick sehen.

ZWEI VERBESSERUNGEN AUS DEM TEST:

1. Der Bereichsname wurde dem Modell mitgegeben und klebte prompt in
   der Antwort: "LIVE-Struktur auf 18 und 21 Uhr festlegen". Ohne ihn
   kommt eine echte Handlung heraus. Der Befund allein reicht -- er
   steht ja ohnehin im richtigen Bereich. Variable entfernt, kein toter
   Code.

2. Der Report gab dem Modell "ueberfaellig" ohne Umlaut vor und bekam
   es genauso zurueck. Jetzt richtig geschrieben.

Geprueft: Calls 7 s und drei brauchbare To-dos, Start-Check erzeugt
einen Titel, Report drei Saetze, 0 Konsolenfehler.
2026-08-28 13:34:57 +02:00

304 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ===================================================================
Start-Check / Erstanalyse (Konzept, Seite 5).
Bewusst ohne Speichern-Knopf: Jede Bewertung wird sofort gesichert.
Eine Erstanalyse geht man im Gespraech durch -- da will niemand am
Ende noch daran denken, unten auf einen Knopf zu druecken.
Und: Jeder Punkt mit Handlungsbedarf bietet direkt an, daraus eine
Aufgabe zu machen. "Ausgangslage verstehen UND daraus konkrete
Arbeitspakete bauen" -- der zweite Teil ist der, der zaehlt.
=================================================================== */
(() => {
'use strict';
const $ = (id) => document.getElementById(id);
const STUFEN = [
{ wert: 'ok', name: 'Passt' },
{ wert: 'mittel', name: 'Ausbaufähig' },
{ wert: 'handlung', name: 'Handlungsbedarf' },
];
let ich = null;
let daten = null;
let aktuell = null;
let kiDa = false;
const el = (tag, klasse, text) => {
const k = document.createElement(tag);
if (klasse) k.className = klasse;
if (text !== undefined) k.textContent = text;
return k;
};
const melde = (t) => { $('fehler').textContent = t || ''; };
async function hole(pfad, optionen = {}) {
const a = await fetch(pfad, { credentials: 'same-origin', ...optionen });
if (a.status === 401) { location.assign('/workspace/'); throw new Error('abgemeldet'); }
return a;
}
async function setze(feld, punkt, aenderung) {
try {
const a = await hole('/workspace/api/startcheck/' + aktuell, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ feld, punkt, ...aenderung }),
});
if (!a.ok) {
melde((await a.json().catch(() => ({}))).fehler || 'Konnte nicht gespeichert werden.');
return false;
}
melde('');
return true;
} catch { return false; }
}
/* ---------- Ein Punkt ---------------------------------------------------- */
function punktZeile(feld, p) {
const k = el('div', 'punkt');
if (p.bewertung) k.dataset.bewertung = p.bewertung;
const kopf = el('div', 'punkt__kopf');
const links = el('div');
links.append(el('h3', 'punkt__name', p.name));
links.append(el('p', 'punkt__hinweis', p.hinweis));
kopf.append(links);
/* Bewertung */
if (daten.darf_aendern) {
const wahl = el('div', 'stufen');
wahl.setAttribute('role', 'group');
wahl.setAttribute('aria-label', 'Bewertung für ' + p.name);
for (const s of STUFEN) {
const b = el('button', 'stufe-knopf', s.name);
b.type = 'button';
b.dataset.wert = s.wert;
if (p.bewertung === s.wert) b.dataset.aktiv = 'ja';
b.addEventListener('click', async () => {
/* Nochmal auf dieselbe Stufe = zurücknehmen. */
const neu = p.bewertung === s.wert ? null : s.wert;
if (await setze(feld.schluessel, p.schluessel, { bewertung: neu, notiz: p.notiz })) {
await laden();
}
});
wahl.append(b);
}
kopf.append(wahl);
} else if (p.bewertung) {
const m = el('span', 'stufe-marke', STUFEN.find((s) => s.wert === p.bewertung)?.name);
m.dataset.wert = p.bewertung;
kopf.append(m);
}
k.append(kopf);
/* Notiz erst, wenn bewertet wurde. Sechzehn offene Textfelder
machen aus einer Checkliste eine Wand -- und vor der Bewertung
hat man ohnehin nichts zu notieren. */
if (daten.darf_aendern && (p.bewertung || p.notiz)) {
const n = el('textarea', 'punkt__notiz');
n.rows = 2;
n.maxLength = 2000;
n.placeholder = 'Was ist dir aufgefallen?';
n.value = p.notiz || '';
let warte = null;
n.addEventListener('input', () => {
clearTimeout(warte);
warte = setTimeout(() => {
setze(feld.schluessel, p.schluessel, { bewertung: p.bewertung, notiz: n.value });
p.notiz = n.value;
}, 700);
});
k.append(n);
} else if (p.notiz) {
k.append(el('p', 'punkt__notiz-lesen', p.notiz));
}
/* Aus dem Befund ein Arbeitspaket machen. */
if (daten.darf_aendern && (p.bewertung === 'handlung' || p.bewertung === 'mittel')) {
k.append(aufgabenTeil(feld, p));
}
if (p.von && p.geaendert) {
k.append(el('p', 'punkt__quelle',
`${p.von} · ${p.geaendert.slice(0, 10).split('-').reverse().join('.')}`));
}
return k;
}
function aufgabenTeil(feld, p) {
const box = el('div', 'paket');
const auf = el('button', 'knopf-still', '+ Daraus eine Aufgabe machen');
auf.type = 'button';
const form = el('form', 'paket__form');
form.hidden = true;
const titel = el('input', 'paket__titel');
titel.type = 'text';
titel.maxLength = 160;
titel.placeholder = 'Was genau ist zu tun?';
const frist = el('input', 'paket__frist');
frist.type = 'date';
frist.title = 'Frist (optional)';
const ok = el('button', 'knopf knopf--klein', 'Anlegen');
ok.type = 'submit';
form.append(titel, frist, ok);
const stand = el('span', 'leise');
auf.addEventListener('click', () => {
form.hidden = false;
auf.hidden = true;
/* Ein sinnvoller Vorschlag statt eines leeren Feldes -- die Notiz
ist meistens schon die halbe Aufgabe. */
titel.value = p.notiz ? p.notiz.split(/[.\n]/)[0].trim().slice(0, 160) : p.name;
titel.focus();
titel.select();
});
/* Die KI formuliert daraus eine Handlung. Der einfache Vorschlag
oben bleibt bestehen -- er funktioniert auch ohne KI. */
if (kiDa && p.notiz) {
form.append(window.KI.knopf('Besser formulieren', async () => {
const d = await window.KI.frage('aufgabentitel',
{ befund: p.notiz, bereich: feld.name });
if (!d.titel) throw new Error('Die KI hat nichts geliefert.');
titel.value = d.titel;
titel.focus();
}));
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
ok.disabled = true;
try {
const a = await hole(`/workspace/api/startcheck/${aktuell}/aufgabe`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
feld: feld.schluessel, punkt: p.schluessel,
titel: titel.value, frist: frist.value,
}),
});
if (!a.ok) {
melde((await a.json().catch(() => ({}))).fehler || 'Ging nicht.');
return;
}
const r = await a.json();
form.hidden = true;
stand.textContent = `Im Aufgabenbrett angelegt (Priorität ${r.prioritaet}) ✓`;
setTimeout(() => { stand.textContent = ''; auf.hidden = false; }, 4000);
} catch { /* umgeleitet */ } finally { ok.disabled = false; }
});
box.append(auf, form, stand);
return box;
}
/* ---------- Zeichnen ----------------------------------------------------- */
function zeichne() {
const ziel = $('felder');
ziel.textContent = '';
ziel.setAttribute('aria-busy', 'false');
for (const f of daten.felder) {
const s = el('section', 'feld-block');
const kopf = el('div', 'feld-block__kopf');
kopf.append(el('h2', 'feld-block__titel', f.name));
const fertig = f.punkte.filter((p) => p.bewertung).length;
const z = el('span', 'feld-block__zahl', `${fertig}/${f.punkte.length}`);
if (fertig === f.punkte.length) z.dataset.fertig = 'ja';
kopf.append(z);
s.append(kopf);
const box = el('div', 'feld-block__punkte');
for (const p of f.punkte) box.append(punktZeile(f, p));
s.append(box);
ziel.append(s);
}
/* Fortschritt */
$('fortschritt').hidden = false;
$('f-zahl').textContent = `${daten.geprueft} von ${daten.gesamt}`;
const anteil = Math.round((daten.geprueft / daten.gesamt) * 100);
$('f-balken').style.width = anteil + '%';
const aussen = $('f-balken-aussen');
aussen.setAttribute('aria-valuenow', String(daten.geprueft));
aussen.setAttribute('aria-valuemax', String(daten.gesamt));
const h = $('f-hinweis');
if (daten.geprueft === 0) {
h.textContent = daten.darf_aendern
? 'Noch nichts geprüft. Am besten im Gespräch durchgehen – gespeichert wird sofort.'
: 'Die Erstanalyse wurde noch nicht ausgefüllt.';
} else if (daten.handlungsbedarf) {
h.textContent = `${daten.handlungsbedarf} ${daten.handlungsbedarf === 1
? 'Punkt braucht' : 'Punkte brauchen'} Handlung`
+ (daten.darf_aendern ? ' – daraus lassen sich direkt Aufgaben machen.' : '.');
} else if (daten.geprueft === daten.gesamt) {
h.textContent = 'Vollständig geprüft, nichts Dringendes offen.';
} else {
h.textContent = '';
}
$('fortschritt').dataset.stufe = daten.handlungsbedarf ? 'handlung'
: daten.geprueft === daten.gesamt ? 'fertig' : 'laeuft';
}
async function laden() {
try {
const a = await hole('/workspace/api/startcheck/' + aktuell);
if (a.status === 404) {
$('felder').textContent = '';
$('felder').append(el('p', 'leer-hinweis', 'Dieser Start-Check ist für dich nicht sichtbar.'));
return;
}
if (!a.ok) { melde('Start-Check konnte nicht geladen werden.'); return; }
daten = await a.json();
zeichne();
} catch { /* umgeleitet */ }
}
/* ---------- Start --------------------------------------------------------- */
(async () => {
try {
const a = await hole('/workspace/api/ich');
if (!a.ok) { location.assign('/workspace/'); return; }
ich = await a.json();
} catch { return; }
$('wer').textContent = ich.name + ' · ' + (ich.rolle_name || ich.rolle);
kiDa = await window.KI.verfuegbar();
let liste;
try { liste = await (await hole('/workspace/api/startcheck/creator')).json(); } catch { return; }
if (!liste.creator.length) {
$('felder').textContent = '';
$('felder').append(el('p', 'leer-hinweis', ich.rolle === 'scout'
? 'Dir ist noch kein Creator zugeteilt. DogFather legt fest, wen du betreust.'
: 'Es gibt noch keinen Creator.'));
return;
}
if (!liste.eigen) {
$('creator-block').hidden = false;
for (const c of liste.creator) {
const o = document.createElement('option');
o.value = String(c.id); o.textContent = c.name;
$('f-creator').append(o);
}
$('f-creator').addEventListener('change', () => {
aktuell = Number($('f-creator').value);
laden();
});
} else {
$('unterzeile').textContent =
'Die Erstanalyse zu deinem Kanal – Grundlage für deinen 90-Tage-Plan.';
}
aktuell = liste.creator[0].id;
await laden();
})();
})();