This commit is contained in:
Team3
2026-05-29 15:49:22 +02:00
parent 5cf0822f5a
commit a826e9f6b3
5 changed files with 46 additions and 24 deletions

View File

@@ -365,7 +365,7 @@ FORMAT-SPEZIFIKATION:
REFERENZ-BEISPIEL: REFERENZ-BEISPIEL:
{reference} {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" - "title"
- "description" - "description"
- "purpose" - "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") spec = (TEMPLATES_DIR / "Format" / "Baustein.md").read_text(encoding="utf-8")
reference = (TEMPLATES_DIR / "Referenz" / "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}". return f"""Generiere Details für den Baustein "{title}" im Kontext des Themas "{topic}".
@@ -386,7 +387,7 @@ FORMAT-SPEZIFIKATION:
REFERENZ-BEISPIEL: REFERENZ-BEISPIEL:
{reference} {reference}
{extra}
Antworte AUSSCHLIESSLICH mit einem JSON-Objekt mit den Feldern "description", "purpose", "examples". Antworte AUSSCHLIESSLICH mit einem JSON-Objekt mit den Feldern "description", "purpose", "examples".
"examples" ist ein Array mit 1 Objekt {{"label": "...", "code": "..."}}. "examples" ist ein Array mit 1 Objekt {{"label": "...", "code": "..."}}.
Orientiere dich an der Spezifikation und Referenz. Kein weiterer Text, nur das JSON. 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) prompt = _build_suggestions_prompt(topic, html_paths, existing_titles)
tools = "Read" if html_paths else None 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: if returncode != 0:
return return
@@ -414,7 +415,7 @@ async def generate_suggestions(topic: str, html_paths: list[Path]) -> None:
now = datetime.now(timezone.utc).isoformat() now = datetime.now(timezone.utc).isoformat()
suggestions = [] suggestions = []
for item in items[:8]: for item in items[:40]:
suggestions.append({ suggestions.append({
"id": str(uuid.uuid4()), "id": str(uuid.uuid4()),
"topic": topic, "topic": topic,
@@ -433,9 +434,9 @@ async def generate_suggestions(topic: str, html_paths: list[Path]) -> None:
_suggestions_generating.discard(topic) _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: 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) returncode, stdout, stderr = await _run_claude("baustein-" + baustein_id, prompt, 60, tools=None, model=MODEL_BAUSTEIN_GEN)
if returncode != 0: if returncode != 0:

View File

@@ -35,6 +35,7 @@ class GuideResponse(BaseModel):
class BausteinCreateRequest(BaseModel): class BausteinCreateRequest(BaseModel):
topic: str = Field(min_length=1, max_length=100) topic: str = Field(min_length=1, max_length=100)
title: str = Field(min_length=1, max_length=200) title: str = Field(min_length=1, max_length=200)
instructions: str = Field(default="", max_length=2000)
class BausteinReworkRequest(BaseModel): class BausteinReworkRequest(BaseModel):

View File

@@ -135,7 +135,7 @@ async def add_baustein(req: BausteinCreateRequest):
"updated_at": now, "updated_at": now,
} }
await db_create_baustein(baustein) 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 return baustein

View File

@@ -49,11 +49,11 @@ export async function fetchBausteine(topic) {
return res.json() return res.json()
} }
export async function createBaustein(topic, title) { export async function createBaustein(topic, title, instructions = '') {
const res = await fetch(`${BASE}/bausteine`, { const res = await fetch(`${BASE}/bausteine`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ topic, title }), body: JSON.stringify({ topic, title, instructions }),
}) })
return res.json() return res.json()
} }

View File

@@ -20,6 +20,7 @@ const bausteine = ref([])
const suggestions = ref([]) const suggestions = ref([])
const suggestionsLoading = ref(false) const suggestionsLoading = ref(false)
const newTitle = ref('') const newTitle = ref('')
const newInfo = ref('')
const reworkInputs = ref({}) const reworkInputs = ref({})
const reworkingIds = ref(new Set()) const reworkingIds = ref(new Set())
const reworkingSnapshots = new Map() const reworkingSnapshots = new Map()
@@ -55,8 +56,10 @@ async function checkAndGenerate() {
async function handleAdd() { async function handleAdd() {
const title = newTitle.value.trim() const title = newTitle.value.trim()
if (!title) return if (!title) return
const info = newInfo.value.trim()
newTitle.value = '' newTitle.value = ''
const created = await createBaustein(props.topic, title) newInfo.value = ''
const created = await createBaustein(props.topic, title, info)
bausteine.value.push(created) bausteine.value.push(created)
reworkingSnapshots.set(created.id, created.updated_at) reworkingSnapshots.set(created.id, created.updated_at)
reworkingIds.value = new Set([...reworkingIds.value, created.id]) reworkingIds.value = new Set([...reworkingIds.value, created.id])
@@ -168,12 +171,18 @@ onUnmounted(stopPolling)
<div class="bausteine-view"> <div class="bausteine-view">
<div class="bausteine-grid"> <div class="bausteine-grid">
<div class="card new-card"> <div class="card new-card">
<h3>Neuer Baustein</h3>
<input <input
v-model="newTitle" v-model="newTitle"
placeholder="Neuer Baustein…" placeholder="Thema…"
@keyup.enter="handleAdd" @keyup.enter="handleAdd"
/> />
<button @click="handleAdd" :disabled="!newTitle.trim()">+</button> <textarea
v-model="newInfo"
placeholder="Zusätzliche Infos (optional)…"
rows="4"
></textarea>
<button class="new-btn" @click="handleAdd" :disabled="!newTitle.trim()">Generieren</button>
</div> </div>
<div v-for="b in bausteine" :key="b.id" class="card"> <div v-for="b in bausteine" :key="b.id" class="card">
@@ -272,41 +281,52 @@ onUnmounted(stopPolling)
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5rem; gap: 0.5rem;
max-height: 400px;
overflow-y: auto;
overscroll-behavior: contain;
} }
.new-card { .new-card {
flex-direction: row;
align-items: center;
gap: 8px;
background: #f8f9fb; background: #f8f9fb;
border-style: dashed; border-style: dashed;
} }
.new-card input { .new-card h3 {
flex: 1; font-size: 0.95rem;
color: #1a1a1a;
margin: 0;
}
.new-card input,
.new-card textarea {
width: 100%;
padding: 8px 10px; padding: 8px 10px;
border: 1px solid #d8dde3; border: 1px solid #d8dde3;
border-radius: 6px; border-radius: 6px;
font-size: 0.9rem; font-size: 0.85rem;
font-family: inherit;
outline: none; outline: none;
resize: vertical;
} }
.new-card input:focus { .new-card input:focus,
.new-card textarea:focus {
border-color: #6366f1; border-color: #6366f1;
} }
.new-card button { .new-btn {
padding: 8px 12px; padding: 8px 12px;
border: none; border: none;
background: #6366f1; background: #6366f1;
color: white; color: white;
border-radius: 6px; border-radius: 6px;
font-size: 1rem; font-size: 0.85rem;
font-weight: 700; font-weight: 600;
cursor: pointer; cursor: pointer;
margin-top: auto;
} }
.new-card button:disabled { .new-btn:disabled {
opacity: 0.4; opacity: 0.4;
cursor: not-allowed; cursor: not-allowed;
} }