Spaces:
Runtime error
Runtime error
Refactor VideoTemplateEditor component: remove AI editing features and related state management, streamline template loading, and update styles for improved layout. Enhance CSS for responsive design and visual consistency across various components, including dropdowns, sync stats, and quiz elements. Remove deprecated premium badge styles and adjust layout for chat components.
66de9c8 Download backend/app/pipeline/infographic.py from firebird-technologies/NoteStack-Test: direct link, hf CLI and curl.
- Browser
- Download file 7.05 kB
-
https://huggingface.co/spaces/firebird-technologies/NoteStack-Test/resolve/main/backend/app/pipeline/infographic.py
- Command line
-
hf download hf://spaces/firebird-technologies/NoteStack-Test/backend/app/pipeline/infographic.py
-
curl -L -o infographic.py https://huggingface.co/spaces/firebird-technologies/NoteStack-Test/resolve/main/backend/app/pipeline/infographic.py
7.05 kB
| """Infographics: a short block of text written from posts' stored ideas and/or chats (and the user's own prompt), | |
| poured into a premade space theme. The model writes content only (a few hundred tokens), never the page, which is | |
| what keeps it to seconds and keeps every word spelled as written. The page is shown as HTML; the PNG is drawn by the | |
| Remotion service only when someone downloads it.""" | |
| import uuid | |
| from sqlalchemy import select | |
| from sqlalchemy.orm import Session | |
| from app.corpus import Corpus | |
| from concurrent.futures import ThreadPoolExecutor | |
| from app.infographics.design import LOOKS, STYLES, build_content, render_design | |
| from app.infographics.image import measure_fit | |
| from app.infographics.render import build_html | |
| from app.infographics.themes import theme_id | |
| from app.llm import run | |
| from app.llm.provider import fast_lm | |
| from app.llm.signatures import DesignInfographic | |
| from app.models import Artifact, Chat, Document, Job, Notebook | |
| from app.pipeline.generate import NothingToDo, extract_ideas, ideas_fresh | |
| from app.pipeline.material import chat_material, max_posts | |
| from app.pipeline.passages import notebook_docs, passages_for, workspace_docs | |
| from app.services.jobs import update_job | |
| IDEAS_BUDGET = 7_000 # characters of stored ideas the model reads | |
| CHAT_BUDGET = 9_000 # characters of chat transcript the model reads | |
| def _ideas_text(db: Session, workspace_id: uuid.UUID, docs: list[Document], job: Job | None) -> list[str]: | |
| """One block per post from the ideas already stored on it (extracted now for a post that has none).""" | |
| stale = [str(d.id) for d in docs if not ideas_fresh(d)] | |
| if stale: | |
| extract_ideas(db, job, workspace_id, stale) | |
| for d in docs: | |
| db.refresh(d) | |
| share = IDEAS_BUDGET // max(len(docs), 1) | |
| corpus = Corpus(workspace_id) | |
| out = [] | |
| for d in docs: | |
| ideas = (d.metadata_json or {}).get("ideas") if ideas_fresh(d) else None | |
| if ideas: | |
| lines = [] | |
| for i in ideas: | |
| points = "; ".join(str(p) for p in (i.get("details") or [])[:3] if isinstance(p, str)) | |
| lines.append(f"- {i.get('label', '')}: {i.get('note', '')}" + (f" ({points})" if points else "")) | |
| body = "\n".join(lines) | |
| else: # extraction failed for this post: its opening lines | |
| body = "\n".join(passages_for(corpus, [d], budget_chars=share)) | |
| out.append(f"POST {d.title}\n{body}"[:share]) | |
| return out | |
| def _chat_text(db: Session, workspace_id: uuid.UUID, chat_ids: list[uuid.UUID]) -> tuple[list[Chat], list[str]]: | |
| chats = list(db.scalars(select(Chat).where(Chat.id.in_(chat_ids), Chat.workspace_id == workspace_id) | |
| .order_by(Chat.created_at))) | |
| share = CHAT_BUDGET // max(len(chats), 1) | |
| return chats, [t[-share:] for t in chat_material(db, chats)] | |
| def write_content(db: Session, job: Job, artifact: Artifact, params: dict) -> tuple[dict, dict, str]: | |
| """The clamped content, the source it came from and a title. Raises NothingToDo when there is nothing to draw.""" | |
| ws = artifact.workspace_id | |
| chat_ids = [uuid.UUID(str(i)) for i in params.get("chat_ids") or []] | |
| picked = [uuid.UUID(str(i)) for i in params.get("document_ids") or []] | |
| nb = db.get(Notebook, artifact.notebook_id) if artifact.notebook_id else None | |
| docs: list[Document] = [] | |
| if picked: | |
| docs = workspace_docs(db, ws, picked) | |
| elif not chat_ids and nb: | |
| docs = notebook_docs(db, nb.id) | |
| docs = docs[:max_posts()] | |
| chats, chat_blocks = _chat_text(db, ws, chat_ids) if chat_ids else ([], []) | |
| if not docs and not chat_blocks: | |
| raise NothingToDo("Pick at least one post or chat for the infographic.") | |
| update_job(db, job, progress=0.1, message="Reading the key ideas") | |
| material = (_ideas_text(db, ws, docs, job) if docs else []) + chat_blocks | |
| prompt = (params.get("instructions") or "").strip() | |
| title = docs[0].title if docs else (chats[0].title or "Chat") | |
| update_job(db, job, progress=0.35, message="Designing the infographic") | |
| style = params.get("style") if params.get("style") in STYLES else "auto" | |
| content = None | |
| for _ in range(2): # one retry when the model gives too little to draw | |
| out = run.predict(DesignInfographic, db=db, workspace_id=ws, job=job, title=title, material=material, | |
| focus=prompt or "(none)", layout_style=f"{style}: {STYLES[style]}" if style != "auto" else "auto", | |
| lm=fast_lm()) | |
| content = build_content(out.get("design") or {}) | |
| if content: | |
| break | |
| if not content: | |
| raise NothingToDo("The material did not give enough to draw. Try other posts or add a prompt.") | |
| source: dict = {"kind": "mixed" if docs and chat_blocks else "posts" if docs else "chats"} | |
| if docs: | |
| source["document_ids"] = [str(d.id) for d in docs] | |
| if chat_blocks: | |
| source["chat_ids"] = [str(c.id) for c in chats] | |
| source["count"] = len(docs) + len(chats if chat_blocks else []) | |
| return content, source, title | |
| def build_infographic(db: Session, job: Job, artifact: Artifact) -> dict: | |
| """Write the text and keep it with the theme. The page is built from them whenever it is shown (so it is ready at | |
| once) and shared by link.""" | |
| params = job.params | |
| theme = theme_id(params.get("theme")) | |
| content, source, _title = write_content(db, job, artifact, params) | |
| update_job(db, job, progress=0.8, message="Fitting it to the page") | |
| content["fit"] = fit_scales(theme, content) | |
| artifact.content_json = {**(artifact.content_json or {}), "title": f"Infographic: {content['title']}"[:140], | |
| "theme": theme, "prompt": (params.get("instructions") or "").strip(), "source": source, | |
| "content": content} | |
| artifact.storage_key = None | |
| artifact.status = "ready" | |
| db.commit() | |
| return {"artifact_id": str(artifact.id)} | |
| def fit_scales(theme: str, content: dict) -> dict: | |
| """The scale at which the body fits the page, tall and wide (measured in Chrome once, kept with the content).""" | |
| look = theme if theme in LOOKS else "midnight" | |
| with ThreadPoolExecutor(max_workers=2) as pool: | |
| wide, tall = (pool.submit(measure_fit, render_design(look, content, layout, fit=1.0), layout) | |
| for layout in ("landscape", "portrait")) | |
| return {"landscape": wide.result(), "portrait": tall.result()} | |
| def infographic_html(content: dict, layout: str = "portrait") -> str | None: | |
| """The page for a finished infographic's stored content and look, tall or wide (None when it has none yet). Older | |
| infographics (fixed text in a premade theme) still draw in that theme.""" | |
| text = content.get("content") | |
| if not text: | |
| return None | |
| theme = theme_id(content.get("theme")) | |
| if "body_html" in text: | |
| return render_design(theme if theme in LOOKS else "midnight", text, layout) | |
| return build_html(theme, text, layout) if "items" in text else None | |