Skip to content

Commit 478f336

Browse files
committed
feat(generate10): enforce fallback
validation and scaffold Generate10 UI
1 parent d86fb9c commit 478f336

30 files changed

+157
-29
lines changed
4.75 KB
Binary file not shown.

api/main.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@
2222
from api.nodes.guide_node import GuideNode
2323
from api.nodes.pdf_builder_node import PdfBuilderNode
2424
from api.nodes.new_pipeline.pipeline import Generate10Pipeline
25+
from api.nodes.new_pipeline.web_fetch_node import WebFetchNode
26+
from api.nodes.new_pipeline.local_fetch_node import LocalFetchNode
27+
from api.nodes.new_pipeline.clean_node import CleanNode
28+
from api.nodes.new_pipeline.keyphrase_node import KeyphraseNode
29+
from api.nodes.new_pipeline.framework_select_node import FrameworkSelectNode
30+
from api.nodes.new_pipeline.prompt_draft_node import PromptDraftNode
31+
from api.nodes.new_pipeline.deduplicate_node import DeduplicateNode
32+
from api.nodes.new_pipeline.business_anchor_guard import BusinessAnchorGuard
33+
from api.nodes.new_pipeline.quota_enforce_node import QuotaEnforceNode
34+
from api.nodes.new_pipeline.explanation_node import ExplanationNode
35+
from api.nodes.assets_node import AssetsNode
2536

2637
import logging
2738
app = FastAPI(title="Prompt Bootstrapper API")
@@ -76,6 +87,78 @@ async def generate10(request: Request):
7687
except NotImplementedError as e:
7788
logger.exception("10-prompt pipeline not yet implemented")
7889
raise HTTPException(status_code=501, detail=str(e))
90+
except ValueError as e:
91+
# Fallback validation errors
92+
raise HTTPException(status_code=422, detail=str(e))
7993
except Exception as e:
8094
logger.exception("Unhandled error in /generate10 endpoint")
95+
raise HTTPException(status_code=500, detail=str(e))
96+
@app.post("/generate10/json")
97+
async def generate10_json(request: Request):
98+
data = await request.json()
99+
url = data.get('url')
100+
raw_text = data.get('text')
101+
if not url and not raw_text:
102+
raise HTTPException(status_code=400, detail="Missing 'url' or 'text' in request body")
103+
try:
104+
# Determine input text: either user-supplied or fetched+cleaned
105+
# Determine input text: from user or fetched+cleaned
106+
if raw_text:
107+
text = raw_text
108+
else:
109+
# Step 1: fetch raw HTML and fallback
110+
html = WebFetchNode(url)
111+
if not html or len(html) < 500:
112+
html = LocalFetchNode(url)
113+
# Validate content length after fallback
114+
if not html or len(html) < 500:
115+
raise ValueError("Fetched content too short (<500 characters); please provide raw text or a richer URL.")
116+
# Step 2: clean text
117+
text = CleanNode(html)
118+
# Step 3: extract keyphrases
119+
keyphrases = KeyphraseNode(text)
120+
# Step 4: framework plan
121+
plan = FrameworkSelectNode(keyphrases)
122+
# Step 5: draft prompts
123+
raw_prompts = PromptDraftNode(text, plan)
124+
# Step 6: dedupe
125+
unique_prompts = DeduplicateNode(raw_prompts)
126+
# Step 7: anchor
127+
anchored = BusinessAnchorGuard(unique_prompts, keyphrases)
128+
# Step 8: enforce quota
129+
final_prompts = QuotaEnforceNode(anchored, plan)
130+
# Step 9: explanations
131+
tips = ExplanationNode(final_prompts)
132+
# Step 10: assets for branding
133+
assets = AssetsNode(url)
134+
return JSONResponse(content={
135+
"prompts": final_prompts,
136+
"tips": tips,
137+
"logo_url": assets.get('logo_url'),
138+
"palette": assets.get('palette', [])
139+
})
140+
except Exception as e:
141+
logger.exception("Error in /generate10/json endpoint")
142+
# Distinguish client vs server
143+
status = 422 if isinstance(e, ValueError) else 500
144+
raise HTTPException(status_code=status, detail=str(e))
145+
146+
@app.post("/generate10/pdf")
147+
async def generate10_pdf(request: Request):
148+
data = await request.json()
149+
prompts = data.get("prompts")
150+
tips = data.get("tips")
151+
logo_url = data.get("logo_url")
152+
palette = data.get("palette", [])
153+
if not (isinstance(prompts, list) and isinstance(tips, list) and len(prompts) == len(tips)):
154+
raise HTTPException(status_code=400, detail="Invalid prompts or tips payload")
155+
try:
156+
pdf_bytes = PdfBuilderNode(logo_url, palette, prompts, tips)
157+
return StreamingResponse(
158+
io.BytesIO(pdf_bytes),
159+
media_type="application/pdf",
160+
headers={"Content-Disposition": "attachment; filename=\"prompts10.pdf\""},
161+
)
162+
except Exception as e:
163+
logger.exception("Error in /generate10/pdf endpoint")
81164
raise HTTPException(status_code=500, detail=str(e))

api/nodes/new_pipeline/business_anchor_guard.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
def BusinessAnchorGuard(prompts: list[str], keyphrases: list[str]) -> list[str]:
55
"""
66
Keep prompts that mention at least one scraped key-phrase.
7-
If no key-phrases were extracted, pass all prompts through.
7+
If no key-phrases were extracted, return an empty list.
88
"""
9-
if not keyphrases: # nothing to anchor against
10-
return prompts
9+
if not keyphrases:
10+
return []
1111

1212
lowered_phrases = [kp.lower() for kp in keyphrases]
1313
anchored = [

api/nodes/new_pipeline/explanation_node.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,18 @@ def ExplanationNode(prompts: list[str]) -> list[str]:
3030
except Exception:
3131
content = resp["choices"][0]["message"]["content"]
3232
# Clean up possible markdown or code fences
33+
# Clean raw content
3334
raw = content.strip()
35+
# Strip code fences
3436
if raw.startswith("```"):
3537
parts = raw.split('```')
3638
if len(parts) >= 3:
3739
raw = parts[1].strip()
40+
# Remove any leading non-JSON prefix (e.g., 'json')
41+
first_bracket = raw.find('[')
42+
last_bracket = raw.rfind(']')
43+
if first_bracket != -1 and last_bracket != -1:
44+
raw = raw[first_bracket:last_bracket+1]
3845
# Parse JSON output
3946
import logging
4047
logger = logging.getLogger(__name__)

api/nodes/new_pipeline/pipeline.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,13 @@ def Generate10Pipeline(url: str) -> bytes:
2424
Returns raw PDF bytes.
2525
"""
2626
# Step 1: fetch
27+
# Step 1: fetch via OpenAI web tool, else fallback
2728
html = WebFetchNode(url)
2829
if not html or len(html) < 500:
2930
html = LocalFetchNode(url)
31+
# If still too short, abort with error for fallback flow
32+
if not html or len(html) < 500:
33+
raise ValueError("Fetched content too short (<500 characters); please provide raw text or a richer URL.")
3034
# Step 2: clean
3135
text = CleanNode(html)
3236
# Step 3: keyphrases

api/nodes/new_pipeline/prompt_draft_node.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,16 @@ def PromptDraftNode(text: str, framework_plan: dict) -> list[str]:
4949
)
5050

5151
raw = resp.choices[0].message.content.strip()
52+
# Strip code fences
5253
if raw.startswith("```"):
53-
raw = raw.split("```", 2)[1].strip() # strip accidental fences
54-
54+
parts = raw.split("```")
55+
if len(parts) >= 3:
56+
raw = parts[1].strip()
57+
# Remove any leading non-JSON prefix
58+
first_bracket = raw.find('[')
59+
last_bracket = raw.rfind(']')
60+
if first_bracket != -1 and last_bracket != -1:
61+
raw = raw[first_bracket:last_bracket+1]
5562
try:
5663
prompts = json.loads(raw)
5764
except json.JSONDecodeError:

backlog-2025-05-11.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
2. **FastAPI endpoints**
3838
- [x] `POST /generate {url}` → run legacy DAG, stream PDF in response.
3939
- [x] `GET /healthz` → 200 OK.
40-
- [x] `POST /generate10 {url}` → run new 10-prompt DAG, validate prompt count=10 else 422, **stream PDF** in response.
40+
- [x] `POST /generate10 {url}` → run new 10-prompt DAG, validate prompt count=10 (error → HTTP 500), **stream PDF** in response.
4141

4242
3. **Unit & integration tests**
4343
- [x] **Unit** — all new pipeline nodes covered by unit tests.
@@ -53,6 +53,10 @@
5353
- [ ] `/Generate10` page – URL form → POST `/generate10`, loading spinner, PDF download trigger.
5454
- [ ] Use Tailwind + `fyne-core.css`, apply palette[0] to header styling.
5555
- [ ] Playwright e2e: generate prompts for `example.com` via `/generate10` endpoint.
56+
57+
## API Tests
58+
- [x] `POST /generate10/json` unit & integration tests covering success, missing URL, insufficient prompts, and server errors.
59+
- [x] `POST /generate10/pdf` unit tests covering valid prompts/tips payload, invalid payload, and server errors.
5660

5761
---
5862

prompts.pdf

-931 KB
Binary file not shown.

pytest-of-arthurlee/pytest-1/test_save_artifact_node_createcurrent

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)