Die Seite verlangte eine technische Weichenstellung, BEVOR man gesagt hat was man will — mit Begriffen aus dem Maschinenraum (BOX / IDE: SIMPEL / IDE: GRUENDLICH). Deshalb klebten 116 Woerter Beipackzettel unter einem einzigen Eingabefeld. Zwei unabhaengige Entscheidungen, vorher in vier sich ausschliessende Knoepfe gepresst: wer — die Box allein, oder ein Repo fuer Zed nurDenken — erst nur ein Konzept (passt zu beidem, jetzt ein Haken) Die Knoepfe nennen das ERGEBNIS statt des Werkzeugs, und der erklaerende Satz steht an der Option statt in einer Legende darunter. Neu: POST /api/ideen/einordnen — das dauerwarme Hirn liest den getippten Text und waehlt vor (gemessen ~500 ms, 5 von 5 Testfaellen richtig, inkl. "Lohnt sich...?" -> nur denken). Bewusst ein VORSCHLAG mit sichtbarem Etikett: er setzt nur die Auswahl, die ohnehin dasteht. Faellt er aus, bleibt schlicht die Voreinstellung — das Tippen wird nie blockiert, es gibt keinen Ladebalken und keine Fehlermeldung. Tiefe und Ziel erscheinen nur noch beim Repo-Weg, weil sie nur dort wirken. Ein Schalter der nichts tut ist schlimmer als keiner. Leerer Zustand zeigt drei anklickbare Beispiele statt "wirf der Box eine Idee zu". Kopfzeile nennt nur noch den Zustand — "Ideen" stand dreimal uebereinander. Die API bleibt unveraendert; die Abbildung passiert in submit(). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,10 @@ class ArchivAlleIn(BaseModel):
|
||||
projekt: str = ""
|
||||
|
||||
|
||||
class EinordnenIn(BaseModel):
|
||||
text: str
|
||||
|
||||
|
||||
@router.get("/ideen")
|
||||
def list_queue() -> dict:
|
||||
return ideen.list_queue()
|
||||
@@ -119,6 +123,13 @@ def archive(body: TaskIn) -> dict:
|
||||
return res
|
||||
|
||||
|
||||
@router.post("/ideen/einordnen")
|
||||
def einordnen(body: EinordnenIn) -> dict:
|
||||
"""Einordnungs-VORSCHLAG beim Tippen. Wirft nie — scheitert es, kommt `ok: false`
|
||||
und die Oberfläche behält ihre Voreinstellung."""
|
||||
return ideen.einordnen(body.text)
|
||||
|
||||
|
||||
@router.post("/ideen/archivieren-alle")
|
||||
def archive_alle(body: ArchivAlleIn) -> dict:
|
||||
"""Fertige Karten sammelweise wegräumen. Fasst ausschließlich `done` an."""
|
||||
|
||||
@@ -22,6 +22,9 @@ import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import httpx
|
||||
from config import GATEWAY_URL, V1_UPSTREAM
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
HERMES = Path(os.environ.get("MC_HERMES_BIN", "~/.local/bin/hermes")).expanduser()
|
||||
@@ -509,6 +512,66 @@ def archive_task(task_id: str) -> dict:
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
_EINORDNEN_SYS = (
|
||||
"Du ordnest eine frisch getippte Projekt-Idee ein. Antworte AUSSCHLIESSLICH mit einem "
|
||||
"JSON-Objekt, ohne Prosa, ohne Codezaun:\n"
|
||||
'{"wer":"box"|"zed","tiefe":"schlank"|"gruendlich","nur_denken":true|false}\n\n'
|
||||
"wer=box: Aufgabe am eigenen Heimlabor des Nutzers — seine Server, seine bestehenden "
|
||||
"Dienste (Mission Control, Lucy, Rippy), Wartung, Doku, Automatisierung. Die Box baut selbst.\n"
|
||||
"wer=zed: ein NEUES eigenstaendiges Programm/Werkzeug/App, das der Nutzer danach selbst "
|
||||
"programmieren will. Er bekommt nur ein vorbereitetes Repo.\n"
|
||||
"tiefe=gruendlich: gross, vage oder mit vielen offenen Entwurfsfragen. "
|
||||
"tiefe=schlank: klein und klar umrissen.\n"
|
||||
"nur_denken=true: der Text stellt eine FRAGE oder waegt ab (\"lohnt sich\", \"waere es sinnvoll\", "
|
||||
"\"brauche ich\") statt einen Auftrag zu geben."
|
||||
)
|
||||
|
||||
|
||||
def einordnen(text: str) -> dict:
|
||||
"""Grober Einordnungs-VORSCHLAG für eine frisch getippte Idee.
|
||||
|
||||
Nur eine Vorauswahl in der Oberfläche — der Mensch sieht und überschreibt sie. Deshalb
|
||||
bewusst anspruchslos: ein einziger kurzer Aufruf ans dauerwarme Hirn (69,6 t/s gemessen),
|
||||
Schnellspur-Marker gegen das Nachdenken, hartes Timeout. Scheitert irgendetwas, kommt
|
||||
`{"ok": False}` zurück und die Oberfläche behält schlicht ihre Voreinstellung — eine
|
||||
Einordnung darf das Tippen NIE blockieren oder eine Fehlermeldung erzeugen.
|
||||
"""
|
||||
text = (text or "").strip()
|
||||
if len(text) < 12:
|
||||
return {"ok": False}
|
||||
ziel_url = (V1_UPSTREAM or GATEWAY_URL).rstrip("/")
|
||||
marker = os.environ.get("MC_NO_THINK_MARKER", "[[MC:NO_THINK]]")
|
||||
try:
|
||||
r = httpx.post(f"{ziel_url}/v1/chat/completions", timeout=12.0, json={
|
||||
"model": "hermes", "temperature": 0, "max_tokens": 60, "stream": False,
|
||||
"messages": [
|
||||
{"role": "system", "content": _EINORDNEN_SYS},
|
||||
{"role": "user", "content": f"{marker} {text[:600]}"},
|
||||
],
|
||||
})
|
||||
r.raise_for_status()
|
||||
inhalt = (r.json()["choices"][0]["message"]["content"] or "").strip()
|
||||
except Exception:
|
||||
log.debug("ideen: einordnen fehlgeschlagen", exc_info=True)
|
||||
return {"ok": False}
|
||||
|
||||
# Modelle packen JSON gern in einen Codezaun oder schreiben einen Satz davor.
|
||||
m = re.search(r"\{.*\}", inhalt, re.DOTALL)
|
||||
if not m:
|
||||
return {"ok": False}
|
||||
try:
|
||||
d = json.loads(m.group(0))
|
||||
except Exception:
|
||||
return {"ok": False}
|
||||
|
||||
wer = d.get("wer") if d.get("wer") in ("box", "zed") else None
|
||||
tiefe = d.get("tiefe") if d.get("tiefe") in ("schlank", "gruendlich") else None
|
||||
if wer is None and tiefe is None:
|
||||
return {"ok": False}
|
||||
return {"ok": True, "wer": wer or "zed", "tiefe": tiefe or "schlank",
|
||||
"nur_denken": bool(d.get("nur_denken"))}
|
||||
|
||||
|
||||
def archive_done(projekt: str = "") -> dict:
|
||||
"""Alle FERTIGEN Karten wegräumen — optional nur die eines Projekts.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user