diff --git a/backend/generator.py b/backend/generator.py index e124cfc..4639601 100644 --- a/backend/generator.py +++ b/backend/generator.py @@ -365,7 +365,7 @@ FORMAT-SPEZIFIKATION: REFERENZ-BEISPIEL: {reference} -Schlage 8 Bausteine vor. Antworte AUSSCHLIESSLICH mit einem JSON-Array. Jedes Element hat: +Schlage 40 Bausteine vor. Antworte AUSSCHLIESSLICH mit einem JSON-Array. Jedes Element hat: - "title" - "description" - "purpose" @@ -375,9 +375,10 @@ Orientiere dich an der Spezifikation und Referenz. NUR das JSON-Array, kein weit """ -def _build_baustein_detail_prompt(topic: str, title: str) -> str: +def _build_baustein_detail_prompt(topic: str, title: str, instructions: str = "") -> str: spec = (TEMPLATES_DIR / "Format" / "Baustein.md").read_text(encoding="utf-8") reference = (TEMPLATES_DIR / "Referenz" / "Baustein.md").read_text(encoding="utf-8") + extra = f"\n\nZUSÄTZLICHE INFOS VOM NUTZER:\n{instructions}\n" if instructions else "" return f"""Generiere Details für den Baustein "{title}" im Kontext des Themas "{topic}". @@ -386,7 +387,7 @@ FORMAT-SPEZIFIKATION: REFERENZ-BEISPIEL: {reference} - +{extra} Antworte AUSSCHLIESSLICH mit einem JSON-Objekt mit den Feldern "description", "purpose", "examples". "examples" ist ein Array mit 1 Objekt {{"label": "...", "code": "..."}}. Orientiere dich an der Spezifikation und Referenz. Kein weiterer Text, nur das JSON. @@ -403,7 +404,7 @@ async def generate_suggestions(topic: str, html_paths: list[Path]) -> None: prompt = _build_suggestions_prompt(topic, html_paths, existing_titles) tools = "Read" if html_paths else None - returncode, stdout, stderr = await _run_claude("suggestions-" + topic, prompt, 180, tools=tools, model=MODEL_BAUSTEIN_GEN) + returncode, stdout, stderr = await _run_claude("suggestions-" + topic, prompt, 1800, tools=tools, model=MODEL_BAUSTEIN_GEN) if returncode != 0: return @@ -414,7 +415,7 @@ async def generate_suggestions(topic: str, html_paths: list[Path]) -> None: now = datetime.now(timezone.utc).isoformat() suggestions = [] - for item in items[:8]: + for item in items[:40]: suggestions.append({ "id": str(uuid.uuid4()), "topic": topic, @@ -433,9 +434,9 @@ async def generate_suggestions(topic: str, html_paths: list[Path]) -> None: _suggestions_generating.discard(topic) -async def generate_baustein_detail(baustein_id: str, topic: str, title: str) -> None: +async def generate_baustein_detail(baustein_id: str, topic: str, title: str, instructions: str = "") -> None: try: - prompt = _build_baustein_detail_prompt(topic, title) + prompt = _build_baustein_detail_prompt(topic, title, instructions) returncode, stdout, stderr = await _run_claude("baustein-" + baustein_id, prompt, 60, tools=None, model=MODEL_BAUSTEIN_GEN) if returncode != 0: diff --git a/backend/models.py b/backend/models.py index a7bbde7..d478412 100644 --- a/backend/models.py +++ b/backend/models.py @@ -35,6 +35,7 @@ class GuideResponse(BaseModel): class BausteinCreateRequest(BaseModel): topic: str = Field(min_length=1, max_length=100) title: str = Field(min_length=1, max_length=200) + instructions: str = Field(default="", max_length=2000) class BausteinReworkRequest(BaseModel): diff --git a/backend/routes.py b/backend/routes.py index 6fe99a5..8322d7a 100644 --- a/backend/routes.py +++ b/backend/routes.py @@ -135,7 +135,7 @@ async def add_baustein(req: BausteinCreateRequest): "updated_at": now, } await db_create_baustein(baustein) - asyncio.create_task(generate_baustein_detail(baustein["id"], baustein["topic"], baustein["title"])) + asyncio.create_task(generate_baustein_detail(baustein["id"], baustein["topic"], baustein["title"], req.instructions.strip())) return baustein diff --git a/frontend/src/api.js b/frontend/src/api.js index 4ffda61..07edf4b 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -49,11 +49,11 @@ export async function fetchBausteine(topic) { return res.json() } -export async function createBaustein(topic, title) { +export async function createBaustein(topic, title, instructions = '') { const res = await fetch(`${BASE}/bausteine`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ topic, title }), + body: JSON.stringify({ topic, title, instructions }), }) return res.json() } diff --git a/frontend/src/components/BausteineView.vue b/frontend/src/components/BausteineView.vue index 0c0c427..00d3dd2 100644 --- a/frontend/src/components/BausteineView.vue +++ b/frontend/src/components/BausteineView.vue @@ -20,6 +20,7 @@ const bausteine = ref([]) const suggestions = ref([]) const suggestionsLoading = ref(false) const newTitle = ref('') +const newInfo = ref('') const reworkInputs = ref({}) const reworkingIds = ref(new Set()) const reworkingSnapshots = new Map() @@ -55,8 +56,10 @@ async function checkAndGenerate() { async function handleAdd() { const title = newTitle.value.trim() if (!title) return + const info = newInfo.value.trim() newTitle.value = '' - const created = await createBaustein(props.topic, title) + newInfo.value = '' + const created = await createBaustein(props.topic, title, info) bausteine.value.push(created) reworkingSnapshots.set(created.id, created.updated_at) reworkingIds.value = new Set([...reworkingIds.value, created.id]) @@ -168,12 +171,18 @@ onUnmounted(stopPolling)
+

Neuer Baustein

- + +
@@ -272,41 +281,52 @@ onUnmounted(stopPolling) display: flex; flex-direction: column; gap: 0.5rem; + max-height: 400px; + overflow-y: auto; + overscroll-behavior: contain; } .new-card { - flex-direction: row; - align-items: center; - gap: 8px; background: #f8f9fb; border-style: dashed; } -.new-card input { - flex: 1; +.new-card h3 { + font-size: 0.95rem; + color: #1a1a1a; + margin: 0; +} + +.new-card input, +.new-card textarea { + width: 100%; padding: 8px 10px; border: 1px solid #d8dde3; border-radius: 6px; - font-size: 0.9rem; + font-size: 0.85rem; + font-family: inherit; outline: none; + resize: vertical; } -.new-card input:focus { +.new-card input:focus, +.new-card textarea:focus { border-color: #6366f1; } -.new-card button { +.new-btn { padding: 8px 12px; border: none; background: #6366f1; color: white; border-radius: 6px; - font-size: 1rem; - font-weight: 700; + font-size: 0.85rem; + font-weight: 600; cursor: pointer; + margin-top: auto; } -.new-card button:disabled { +.new-btn:disabled { opacity: 0.4; cursor: not-allowed; }