From 15f8d1af656a22fa0acc0cea8dfb5daccba97213 Mon Sep 17 00:00:00 2001 From: Jerome Picault Date: Tue, 21 Jul 2026 07:45:28 +0000 Subject: [PATCH] Remove Gradio GUI and Hugging Face deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hugging Face Gradio Spaces are no longer free, so drop the Gradio front-end and the HF publish path, keeping the NiceGUI GUI as the only front-end. The shared view logic refactor (conflens/view.py) is kept β€” app.py still uses it. Removed: - conflens/gradio_app.py, space_app.py, requirements.txt (HF Space entry/deps) - .github/workflows/sync-hf-space.yml + .github/huggingface/ (HF sync + metadata) - tests/test_gradio_app.py - pyproject: the `gradio` extra and the `conflens-gradio` console script - test.yml: revert to `uv sync` (no gradio extra); uv.lock re-locked (gradio gone) Docs (README, AGENTS, CLAUDE, ARCHITECTURE) updated back to a single NiceGUI front-end while keeping view.py as the GUI-agnostic view layer. --- .github/huggingface/space_readme_header.md | 19 - .github/workflows/sync-hf-space.yml | 58 --- .github/workflows/test.yml | 4 +- AGENTS.md | 12 +- CLAUDE.md | 17 +- README.md | 53 +-- conflens/gradio_app.py | 522 --------------------- docs/ARCHITECTURE.md | 10 +- pyproject.toml | 7 +- requirements.txt | 12 - space_app.py | 28 -- tests/test_gradio_app.py | 45 -- uv.lock | 251 +--------- 13 files changed, 39 insertions(+), 999 deletions(-) delete mode 100644 .github/huggingface/space_readme_header.md delete mode 100644 .github/workflows/sync-hf-space.yml delete mode 100644 conflens/gradio_app.py delete mode 100644 requirements.txt delete mode 100644 space_app.py delete mode 100644 tests/test_gradio_app.py diff --git a/.github/huggingface/space_readme_header.md b/.github/huggingface/space_readme_header.md deleted file mode 100644 index 5b5a11c..0000000 --- a/.github/huggingface/space_readme_header.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -title: ConfLens -emoji: πŸ”Ž -colorFrom: blue -colorTo: indigo -sdk: gradio -app_file: space_app.py -python_version: "3.12" -suggested_hardware: cpu-basic -pinned: false -license: mpl-2.0 -short_description: Browse, theme-classify and topic-model conference papers ---- - - diff --git a/.github/workflows/sync-hf-space.yml b/.github/workflows/sync-hf-space.yml deleted file mode 100644 index 8135df2..0000000 --- a/.github/workflows/sync-hf-space.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Sync to Hugging Face Space - -# Mirror main to a Hugging Face **Docker** Space on every push, keeping the Space -# in sync with this repo. The Space builds the repo's Dockerfile (which serves on -# 0.0.0.0:8080 β€” matched by `app_port: 8080` in the Space metadata). -# -# One-time setup (see README β†’ "Deploy to Hugging Face Spaces"): -# 1. Create a Gradio Space on huggingface.co. -# 2. Add a repo secret HF_TOKEN (a HF access token with write scope). -# 3. Optionally set repo variables HF_USERNAME / HF_SPACE (they default to -# "picault" and "conflens"). -# 4. Add your LLM API keys as *Space* secrets in the Space settings. -on: - push: - branches: [main] - workflow_dispatch: - -concurrency: - group: hf-sync - cancel-in-progress: true - -jobs: - sync: - runs-on: ubuntu-latest - steps: - - name: Checkout (full history) - uses: actions/checkout@v4 - with: - fetch-depth: 0 - lfs: true - - - name: Prepend Space metadata to README - run: | - set -euo pipefail - cat .github/huggingface/space_readme_header.md README.md > README.hf - mv README.hf README.md - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add README.md - git commit -m "Add Hugging Face Space metadata [skip ci]" - - - name: Push to Hugging Face Space - env: - HF_TOKEN: ${{ secrets.HF_TOKEN }} - # HF owner defaults to 'picault' (the HF username differs from the - # GitHub owner 'picaultj'); override with the HF_USERNAME repo variable. - HF_USERNAME: ${{ vars.HF_USERNAME || 'picault' }} - HF_SPACE: ${{ vars.HF_SPACE || 'conflens' }} - run: | - set -euo pipefail - if [ -z "${HF_TOKEN:-}" ]; then - echo "::error::Missing HF_TOKEN secret. Add a Hugging Face write token as a repo secret named HF_TOKEN (see README β†’ Deploy to Hugging Face Spaces)." - exit 1 - fi - echo "Syncing to https://huggingface.co/spaces/${HF_USERNAME}/${HF_SPACE}" - git push --force \ - "https://${HF_USERNAME}:${HF_TOKEN}@huggingface.co/spaces/${HF_USERNAME}/${HF_SPACE}.git" \ - HEAD:main diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7619e30..a731e23 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -23,8 +23,8 @@ jobs: with: enable-cache: true - - name: Sync dependencies (base + dev + gradio) - run: uv sync --extra gradio # so the Gradio GUI is covered by the suite + - name: Sync dependencies (base + dev) + run: uv sync - name: Tests (pytest) run: uv run pytest -q diff --git a/AGENTS.md b/AGENTS.md index 3f898ff..8f36940 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -17,19 +17,15 @@ save/load a run). Python 3.13, managed with **uv**. ```bash uv sync # base install (Anthropic, OpenAI, LiteLLM + NiceGUI) -uv sync --extra gradio # + the Gradio GUI uv sync --extra bertopic # + BERTopic topic backend (heavy) cp .env.example .env # add provider key(s); loaded automatically uv run conference-analyzer # NiceGUI GUI on http://localhost:6868 -uv run conflens-gradio # Gradio GUI on http://localhost:7860 uv run conference-analyzer --clear-cache # wipe the on-disk cache ``` -**Two front-ends, kept in parity:** `app.py` (NiceGUI) and `gradio_app.py` -(Gradio, deployed to Hugging Face). All non-UI logic lives in `view.py` + -`pipeline.py`; when you change a user-facing feature, update **both** GUIs. See -[CLAUDE.md](CLAUDE.md). +The GUI (`app.py`) is a thin **NiceGUI** presentation layer; all non-UI logic +lives in `view.py` + `pipeline.py` β€” put behaviour changes there. Docker: `docker compose up --build` (see the README). @@ -66,8 +62,8 @@ regexes over heavyweight parsers, small focused modules). | Area | Module | |------|--------| -| GUIs (feature parity) | `app.py` (NiceGUI), `gradio_app.py` (Gradio) | -| Shared view logic | `view.py` (filter/sort/highlight/compute_view/exports) | +| UI (NiceGUI) | `app.py` | +| View logic | `view.py` (filter/sort/highlight/compute_view/exports) | | Exports | `pptx_export.py`, `bibtex.py` | | Orchestration | `pipeline.py` (`AnalysisConfig`, `run_analysis`) | | Sources | `sources.py` (registry + `make_source`; `IJCAISource`, `OpenReviewSource`, `PSCCSource`, `DBLPSource`), `scraper.py` (`AnthologyScraper` β€” also serves EMNLP/NAACL) | diff --git a/CLAUDE.md b/CLAUDE.md index bbe2a6a..ef339c0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,16 +25,9 @@ repository or its GitHub artifacts: feature (the `anthropic` provider and `claude-*` model ids in `llm.py`) and stays β€” this policy is about tooling attribution, not the app's providers. -## Two front-ends (persistent β€” keep both maintained) +## Front-end (NiceGUI) -The app ships **two GUIs with feature parity**: - -- **NiceGUI** β€” `conflens/app.py` (console script `conference-analyzer`); run - locally / in Docker. -- **Gradio** β€” `conflens/gradio_app.py` (console script `conflens-gradio`); this - is the front-end deployed to Hugging Face Spaces. - -All non-UI logic (filtering, sorting, highlighting, the computed view, exports) -lives in **`conflens/view.py`** and the pipeline β€” put behaviour changes there so -both GUIs inherit them. When you add or change a user-facing feature, **update -both `app.py` and `gradio_app.py`** so they stay in parity. +The app has a single **NiceGUI** GUI in `conflens/app.py` (console script +`conference-analyzer`). All non-UI logic (filtering, sorting, highlighting, the +computed view, exports) lives in **`conflens/view.py`** and the pipeline β€” put +behaviour changes there so the UI stays a thin presentation layer. diff --git a/README.md b/README.md index 68dc9e9..3c84f09 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,7 @@ [![uv](https://img.shields.io/badge/managed%20by-uv-DE5FE9.svg?logo=uv&logoColor=white)](https://docs.astral.sh/uv/) [![Built with NiceGUI](https://img.shields.io/badge/UI-NiceGUI-2b6cb0.svg)](https://nicegui.io) -A desktop-style web app β€” with **two interchangeable front-ends**, -[NiceGUI](https://nicegui.io) (local/Docker) and [Gradio](https://gradio.app) -(deployed to Hugging Face Spaces) β€” that: +A desktop-style web app (built with [NiceGUI](https://nicegui.io)) that: 1. **Browses** papers from a chosen **source** and retrieves their abstracts: - the [ACL Anthology](https://aclanthology.org) (default; e.g. `acl-2026`), @@ -44,7 +42,7 @@ A desktop-style web app β€” with **two interchangeable front-ends**, ## Contents -- [Quick start](#quick-start) Β· [Run with Docker](#run-with-docker) Β· [Deploy to Hugging Face Spaces](#deploy-to-hugging-face-spaces) Β· [LLM providers](#llm-providers) +- [Quick start](#quick-start) Β· [Run with Docker](#run-with-docker) Β· [LLM providers](#llm-providers) - [Architecture](#architecture) Β· [How it works](#how-it-works) - [Configuration](#configuration-in-the-ui) Β· [Features](#features) - [Caching](#caching) Β· [Cost](#cost) Β· [BERTopic](#optional-bertopic) @@ -57,15 +55,10 @@ Requires Python 3.13+ and [uv](https://docs.astral.sh/uv/). ```bash uv sync # Claude, OpenAI, LiteLLM work out of the box cp .env.example .env # then fill in your provider key(s) -uv run conference-analyzer # NiceGUI GUI β†’ http://localhost:6868 +uv run conference-analyzer # or: uv run python run.py ``` -Prefer **Gradio** (same features; the front-end deployed to Hugging Face)? - -```bash -uv sync --extra gradio -uv run conflens-gradio # Gradio GUI β†’ http://localhost:7860 -``` +Then open . Keys are read from `.env` (loaded automatically) or the process environment; you can also paste a key into the app's **API key** field at runtime. @@ -99,44 +92,6 @@ docker run --rm -p 6868:6868 --env-file .env \ To also build the optional BERTopic backend into the image: `docker build --build-arg EXTRAS="--extra bertopic" -t conference-analyzer .` -### Deploy to Hugging Face Spaces - -The **Gradio** front-end is deployed to Hugging Face as a **Gradio SDK Space** -(free CPU tier). HF installs [`requirements.txt`](requirements.txt) and runs -[`space_app.py`](space_app.py), which serves `conflens.gradio_app`. A GitHub -Action mirrors `main` to the Space on every push, so it stays in sync. - -**One-time setup:** - -1. **Create the Space** β€” on , pick **Gradio β†’ - Blank**, **CPU basic** hardware (this app is CPU-only; ZeroGPU errors with - *"No @spaces.GPU function detected"*), and note its owner + name (e.g. - `your-user/conflens`). -2. **Give GitHub a token** β€” create a Hugging Face access token with **write** - scope (Settings β†’ Access Tokens) and add it to this GitHub repo as a secret - named **`HF_TOKEN`** (Settings β†’ Secrets and variables β†’ Actions). -3. **Point the action at your Space** *(optional)* β€” the workflow defaults to - owner `picault` and space `conflens` (the HF username differs from the GitHub - owner). If your Space differs, set repo **variables** `HF_USERNAME` and - `HF_SPACE`. -4. **Add your API keys as *Space* secrets** β€” in the Space's *Settings β†’ - Variables and secrets*, add whatever your provider needs (e.g. - `ANTHROPIC_API_KEY`, or `OPENAI_API_KEY` / `OPENAI_BASE_URL`; see - [`.env.example`](.env.example)). The app reads them as environment variables. - -That's it β€” push to `main` (or run the **Sync to Hugging Face Space** workflow -manually) and the Space rebuilds. The Space metadata (`sdk: gradio`, -`app_file: space_app.py`, …) lives in -[`.github/huggingface/space_readme_header.md`](.github/huggingface/space_readme_header.md); -the action prepends it to the README it pushes, so the GitHub README stays clean. - -> The Space's filesystem is ephemeral, so the on-disk cache resets on rebuild. -> For persistent caching, attach Hugging Face **persistent storage** and set -> `HOME` (or a `--cache-dir`) to point at its `/data` mount. - -> The Docker image above still runs the **NiceGUI** app (`0.0.0.0:6868`) for -> local/self-hosted use; the two front-ends share all analysis logic. - ### LLM providers The classifier and topic engine work with three providers, chosen in the UI: diff --git a/conflens/gradio_app.py b/conflens/gradio_app.py deleted file mode 100644 index d9b1cd4..0000000 --- a/conflens/gradio_app.py +++ /dev/null @@ -1,522 +0,0 @@ -"""Gradio front-end for ConfLens β€” feature-parity with the NiceGUI app. - -This is the front-end deployed to Hugging Face Spaces (Gradio SDK Spaces are -free). It shares all non-UI logic with the NiceGUI app via :mod:`conflens.view` -and :mod:`conflens.pipeline`, so keep behavioural changes in those shared modules -and mirror only the *presentation* here and in ``app.py``. -""" - -from __future__ import annotations - -import html -import os -import tempfile -import threading -import time -from typing import Optional - -import gradio as gr - -from . import view -from .cache import default_cache_dir -from .llm import DEFAULT_MODELS, MODEL_SUGGESTIONS, PROVIDERS, env_key_for -from .models import AnalysisResult -from .pipeline import AnalysisConfig, Progress, run_analysis -from .sources import SOURCES -from .view import TOPIC_COLORS - -_CACHE_DIR = default_cache_dir() - -# Sober palette (mirrors app.py). -PRIMARY = "#1f4e79" -INK = "#1a202c" -MUTED = "#64748b" -LINE = "#e2e8f0" - -_CSS = """ -.conflens mark { background:#fde68a; color:inherit; padding:0 1px; border-radius:2px; } -.conflens .ca-card { border:1px solid %s; border-radius:10px; background:#fff; - padding:14px 18px; margin-bottom:14px; } -.conflens a.ca-title { color:%s; font-weight:600; text-decoration:none; } -.conflens a.ca-title:hover { text-decoration:underline; } -.conflens .ca-badge { display:inline-block; color:#fff; border-radius:6px; - padding:1px 7px; font-size:.72rem; font-weight:600; text-decoration:none; margin-left:4px; } -.conflens .muted { color:%s; font-size:.82rem; } -""" % (LINE, PRIMARY, MUTED) - - -# --------------------------------------------------------------------------- # -# HTML rendering (presentation only; all logic comes from view.compute_view) -# --------------------------------------------------------------------------- # -def _chart_html(names: list[str], counts: list[int]) -> str: - if not names: - return "" - top = max(counts) or 1 - rows = [] - for i, (name, c) in enumerate(zip(names, counts)): - color = TOPIC_COLORS[i % len(TOPIC_COLORS)] - width = max(2, round(100 * c / top)) - rows.append( - f'
' - f'
{html.escape(name)}
' - f'
' - f'
' - f'
{c}
' - ) - return ( - '
' - "Papers per topic
%s
" % (INK, "".join(rows)) - ) - - -def _paper_html(p, kws: list[str], also_in: list[str], dup: Optional[str], also_label: str) -> str: - if kws: - title = view.highlight(p.title, kws) - else: - title = html.escape(p.title) - if p.url: - title_html = ( - f'{title}' - ) - else: - title_html = f'{title}' - - badges = "" - if dup: - badges += ( - f'near-dup' - ) - if p.confidence is not None: - badges += ( - f'{p.confidence:.0%}' - ) - if p.pdf_url: - badges += ( - f'PDF' - ) - - parts = [ - f'
', - f'
{title_html}{badges}
', - ] - if p.authors: - auth = ", ".join(p.authors[:6]) + ("…" if len(p.authors) > 6 else "") - parts.append(f'
{html.escape(auth)}
') - if also_in: - parts.append( - f'
{html.escape(also_label)}' - f'{html.escape(", ".join(also_in))}
' - ) - if p.abstract: - body = view.highlight(p.abstract, kws) if kws else html.escape(p.abstract) - parts.append( - f'
Abstract' - f'
{body}
' - "
" - ) - if p.reason: - parts.append(f'
Why: {html.escape(p.reason)}
') - parts.append("
") - return "".join(parts) - - -def _render_html(result: AnalysisResult, min_conf, query, author, sort, is_global) -> tuple[str, str]: - """Return (results_html, status_text) for the current filters.""" - if result is None: - return "", "" - kws = view.keywords(query or "") - vd = view.compute_view( - result, min_conf=float(min_conf or 0), query=query or "", - author=author or "", sort=sort or "confidence", - ) - blocks = [_chart_html(vd.names, vd.counts)] - - if is_global: - papers = vd.flat - card = [ - '
', - f'
All matching papers ({len(papers)})
', - ] - if not papers: - card.append('
No papers match the current filters.
') - for p in papers: - card.append(_paper_html( - p, kws, - [vd.topic_name[tid] for tid in p.topic_ids if tid in vd.topic_name], - view.dup_title(p, vd.all_by_id), "Topics: ", - )) - card.append("
") - blocks.append("".join(card)) - shown_papers, shown_topics = len(papers), (1 if papers else 0) - else: - shown_papers = shown_topics = 0 - for tv in vd.grouped: - t, papers = tv.topic, tv.papers - shown_papers += len(papers) - shown_topics += 1 - color = TOPIC_COLORS[t.topic_id % len(TOPIC_COLORS)] - badge = ( - f"{len(papers)} of {t.count}" if len(papers) != t.count - else f"{t.count} paper{'s' if t.count != 1 else ''}" - ) - card = [ - '
', - f'
' - f'' - f'{html.escape(t.name)}' - f'{badge}
', - ] - if t.description: - card.append( - f'
' - f'{html.escape(t.description)}
' - ) - if t.findings: - items = "".join(f"
  • {html.escape(f)}
  • " for f in t.findings) - card.append( - f'
    ' - f'
    Main findings across this topic
    ' - f'
      {items}
    ' - ) - card.append(f'
    Papers ({len(papers)})
    ') - for p in papers: - card.append(_paper_html( - p, kws, view.also_in(p, t.topic_id, vd.topic_name), - view.dup_title(p, vd.all_by_id), "Also in: ", - )) - card.append("
    ") - blocks.append("".join(card)) - if shown_topics == 0: - blocks.append('
    No papers match the current filters.
    ') - - scope = "in one list" if is_global else f"Β· {shown_topics} of {vd.total_topics} topics" - status = ( - f"Show β‰₯ {float(min_conf or 0):.2f} confidence β€” " - f"{shown_papers} of {vd.total_relevant} papers {scope}" - ) - return f'
    {"".join(blocks)}
    ', status - - -def _summary_md(result: AnalysisResult) -> str: - if result is None: - return "" - bits = [ - f"**{result.scanned}** scanned", - f"**{len(result.relevant_papers)}** relevant to *{result.theme}*", - f"**{len(result.topics)}** topics", - ] - if result.duplicate_groups: - bits.append(f"**{result.duplicate_groups}** near-duplicate groups") - link = f" Β· [source]({result.event_url})" if result.event_url else "" - return " Β· ".join(bits) + link - - -def _provider_hint(provider: str, suggestions: str) -> str: - env_var = { - "anthropic": "ANTHROPIC_API_KEY", - "openai": "OPENAI_API_KEY", - "litellm": "LITELLM_API_KEY / OPENAI_API_KEY", - }.get(provider, "") - parts = [] - if env_key_for(provider): - parts.append(f"Using `{env_var}` from the environment.") - else: - parts.append(f"No `{env_var}` found β€” set it or fill the API key field.") - if provider == "litellm": - parts.append("LiteLLM: set the LLM endpoint.") - return " ".join(parts) - - -# --------------------------------------------------------------------------- # -# Export helpers (write bytes to a temp file for DownloadButton) -# --------------------------------------------------------------------------- # -def _write_temp(data: bytes, name: str) -> str: - path = os.path.join(tempfile.mkdtemp(prefix="conflens_"), name) - with open(path, "wb") as fh: - fh.write(data) - return path - - -def _dl_json(result): - if result is None: - raise gr.Error("Run or load an analysis first.") - return _write_temp(view.json_bytes(result), "analysis.json") - - -def _dl_csv(result): - if result is None: - raise gr.Error("Run or load an analysis first.") - return _write_temp(view.csv_bytes(result), "analysis.csv") - - -def _dl_bibtex(result): - if result is None: - raise gr.Error("Run or load an analysis first.") - from .bibtex import build_bibtex - - return _write_temp(build_bibtex(result).encode("utf-8"), "papers.bib") - - -def _dl_pptx(result): - if result is None: - raise gr.Error("Run or load an analysis first.") - from .pptx_export import build_pptx - - try: - data = build_pptx(result) - except Exception as e: # missing dependency / render failure - raise gr.Error(str(e)) - return _write_temp(data, "analysis.pptx") - - -# --------------------------------------------------------------------------- # -# Analyze / load lifecycle -# --------------------------------------------------------------------------- # -def _validate(model, event, provider, llm_base_url) -> Optional[str]: - if not (model or "").strip(): - return "Please set a model before running." - if not (event or "").strip(): - return "Please set the event / target." - if provider == "litellm" and not (llm_base_url or "").strip(): - return "LiteLLM needs an LLM endpoint." - return None - - -def _populate(result: AnalysisResult): - """Outputs tuple shared by analyze() and load() to fill the results panel.""" - html_, status = _render_html(result, result.min_confidence, "", "", "confidence", False) - return ( - result, # result_state - _summary_md(result), # summary_md - html_, # results_html - status, # status_md - gr.update(choices=view.author_choices(result), value=None), # author_dd - gr.update(value=result.min_confidence), # conf_slider - gr.update(value=""), # search_box - gr.update(value="confidence"), # sort_dd - gr.update(value=False), # global_chk - gr.update(visible=True), # results_group - ) - - -def analyze( - source, base_url, event, theme, theme_def, provider, model, backend, - llm_base_url, api_key, max_papers, n_topics, min_conf, refresh, - progress=gr.Progress(), -): - err = _validate(model, event, provider, llm_base_url) - if err: - raise gr.Error(err) - cfg = AnalysisConfig( - source=source, - base_url=(base_url or "").strip(), - event=(event or "").strip(), - theme=(theme or "").strip() or "Agentic AI", - theme_definition=(theme_def or "").strip(), - provider=provider, - model=(model or "").strip(), - llm_base_url=(llm_base_url or "").strip(), - api_key=(api_key or "").strip(), - max_papers=int(max_papers or 150), - n_topics=int(n_topics or 8), - min_confidence=float(min_conf), - topic_backend=backend, - refresh=bool(refresh), - ) - prog = Progress() - box: dict = {} - - def work(): - try: - box["r"] = run_analysis(cfg, prog, _CACHE_DIR) - except Exception as e: # surface to the user - prog.error = str(e) - - th = threading.Thread(target=work, daemon=True) - th.start() - progress(0.0, desc="Starting…") - while th.is_alive(): - progress(min(prog.fraction, 0.99), desc=prog.message or "Working…") - time.sleep(0.25) - th.join() - if prog.error: - raise gr.Error(prog.error) - progress(1.0, desc="Done") - result = box.get("r") - if result is None or (not result.relevant_papers and not result.topics): - empty = AnalysisResult(theme=cfg.theme, event_url=result.event_url if result else "") - return ( - empty, _summary_md(result) if result else "", - '
    ' - "No matching papers were found for this theme.
    ", - "", gr.update(choices=[], value=None), gr.update(value=cfg.min_confidence), - gr.update(value=""), gr.update(value="confidence"), gr.update(value=False), - gr.update(visible=True), - ) - return _populate(result) - - -def load_run(file): - if not file: - return (None, "", "", "", gr.update(), gr.update(), gr.update(), - gr.update(), gr.update(), gr.update(visible=False)) - try: - with open(file, "rb") as fh: - import json - result = AnalysisResult.from_dict(json.loads(fh.read().decode("utf-8"))) - except Exception as e: - raise gr.Error(f"Could not load run: {e}") - if not result.relevant_papers and not result.topics: - raise gr.Error("That file doesn't look like a saved analysis run.") - return _populate(result) - - -def rerender(result, min_conf, query, author, sort, is_global): - if result is None: - return "", "" - return _render_html(result, min_conf, query, author, sort, is_global) - - -# --------------------------------------------------------------------------- # -# Blocks layout -# --------------------------------------------------------------------------- # -def build_demo() -> "gr.Blocks": - default_provider = "litellm" - with gr.Blocks(title="ConfLens β€” Conference Paper Analyzer") as demo: - # Inject CSS via a ") - gr.Markdown("## πŸ”Ž ConfLens β€” Conference Paper Analyzer\nBrowse Β· classify by theme Β· discover topics") - result_state = gr.State(None) - - with gr.Accordion("Configuration", open=True): - with gr.Row(): - source_dd = gr.Dropdown( - choices=[(v["label"], k) for k, v in SOURCES.items()], - value="aclanthology", label="Source", - ) - base_url_tb = gr.Textbox( - value=SOURCES["aclanthology"]["base"], - label=SOURCES["aclanthology"]["base_label"], - ) - event_tb = gr.Textbox( - value=SOURCES["aclanthology"]["target"], - label=SOURCES["aclanthology"]["target_label"], - ) - with gr.Row(): - theme_tb = gr.Textbox(value="Agentic AI", label="Theme") - provider_dd = gr.Dropdown(choices=PROVIDERS, value=default_provider, label="LLM provider") - model_tb = gr.Textbox(value=DEFAULT_MODELS[default_provider], label="Model") - backend_dd = gr.Dropdown( - choices=[("LLM topics", "llm"), ("BERTopic", "bertopic")], - value="llm", label="Topic engine", - ) - theme_def_tb = gr.Textbox( - label="Theme definition (optional)", - placeholder="Clarify what counts as this theme β€” what to include / exclude", - ) - with gr.Row(): - llm_base_url_tb = gr.Textbox( - value=os.environ.get("OPENAI_BASE_URL", ""), - label="LLM endpoint (LiteLLM / OpenAI-compatible)", - ) - api_key_tb = gr.Textbox(label="API key (optional β€” overrides env var)", type="password") - key_hint_md = gr.Markdown(_provider_hint(default_provider, "")) - with gr.Row(): - max_papers_num = gr.Number(value=150, precision=0, label="Max papers to scan") - n_topics_num = gr.Number(value=8, precision=0, label="Target number of topics") - min_conf_num = gr.Slider(0, 1, value=0.5, step=0.05, label="Minimum confidence") - with gr.Row(): - refresh_chk = gr.Checkbox(value=False, label="Refresh from source (ignore cache)") - load_file = gr.File(label="Load saved run (.json)", file_types=[".json"]) - analyze_btn = gr.Button("Analyze", variant="primary") - - with gr.Group(visible=False) as results_group: - summary_md = gr.Markdown() - with gr.Row(): - dl_pptx_btn = gr.DownloadButton("PPTX", size="sm") - dl_json_btn = gr.DownloadButton("JSON", size="sm") - dl_csv_btn = gr.DownloadButton("CSV", size="sm") - dl_bib_btn = gr.DownloadButton("BibTeX", size="sm") - with gr.Row(): - search_box = gr.Textbox( - label="Filter by keywords (comma-separated; each may contain spaces)", - scale=3, - ) - sort_dd = gr.Dropdown( - choices=[("Confidence", "confidence"), ("Title", "title"), ("Year", "year")], - value="confidence", label="Sort by", scale=1, - ) - with gr.Row(): - author_dd = gr.Dropdown(choices=[], value=None, label="Filter by author", - allow_custom_value=True, scale=2) - conf_slider = gr.Slider(0, 1, value=0.5, step=0.05, label="Show β‰₯ confidence", scale=2) - global_chk = gr.Checkbox(value=False, label="Search all topics", scale=1) - status_md = gr.Markdown() - results_html = gr.HTML() - - # -- wiring --------------------------------------------------------- # - def on_source(source): - cfg = SOURCES.get(source, {}) - return ( - gr.update(value=cfg.get("base", ""), label=cfg.get("base_label", "Base URL")), - gr.update(value=cfg.get("target", ""), label=cfg.get("target_label", "Target")), - ) - - source_dd.change(on_source, [source_dd], [base_url_tb, event_tb]) - - def on_provider(provider): - sugg = ", ".join(MODEL_SUGGESTIONS.get(provider, [])) - return gr.update(value=DEFAULT_MODELS.get(provider, ""), info=f"e.g. {sugg}"), \ - _provider_hint(provider, sugg) - - provider_dd.change(on_provider, [provider_dd], [model_tb, key_hint_md]) - - analyze_outputs = [ - result_state, summary_md, results_html, status_md, author_dd, - conf_slider, search_box, sort_dd, global_chk, results_group, - ] - analyze_btn.click( - analyze, - [source_dd, base_url_tb, event_tb, theme_tb, theme_def_tb, provider_dd, - model_tb, backend_dd, llm_base_url_tb, api_key_tb, max_papers_num, - n_topics_num, min_conf_num, refresh_chk], - analyze_outputs, - ) - load_file.change(load_run, [load_file], analyze_outputs) - - view_inputs = [result_state, conf_slider, search_box, author_dd, sort_dd, global_chk] - for ctrl in (conf_slider, search_box, author_dd, sort_dd, global_chk): - ctrl.change(rerender, view_inputs, [results_html, status_md]) - - dl_json_btn.click(_dl_json, [result_state], [dl_json_btn]) - dl_csv_btn.click(_dl_csv, [result_state], [dl_csv_btn]) - dl_bib_btn.click(_dl_bibtex, [result_state], [dl_bib_btn]) - dl_pptx_btn.click(_dl_pptx, [result_state], [dl_pptx_btn]) - - return demo - - -def main() -> None: - """Launch the Gradio app (console script: ``conflens-gradio``).""" - try: - from dotenv import load_dotenv - - load_dotenv() - except ImportError: - pass - port = int(os.environ.get("PORT", "7860")) - build_demo().launch(server_name="0.0.0.0", server_port=port) - - -if __name__ == "__main__": - main() diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 11a9e8a..4da70e5 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,9 +1,8 @@ # Architecture -ConfLens is a small, single-process web app. It has **two interchangeable -front-ends** β€” NiceGUI (`app.py`, local/Docker) and Gradio (`gradio_app.py`, -deployed to Hugging Face) β€” that share all non-UI logic via `view.py` and the -pipeline. A front end drives a +ConfLens is a small, single-process web app. A **NiceGUI** front end (`app.py`) +is a thin presentation layer over the shared view logic (`view.py`) and the +pipeline, which drives a linear pipeline β€” **browse β†’ classify β†’ topic-model β†’ summarize** β€” over a pluggable *source* (which conference) and a pluggable *LLM provider* (which model). Everything expensive is cached on disk. @@ -117,8 +116,7 @@ sequenceDiagram |--------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `cli.py` | Console entry point (`conflens`); `--clear-cache`, `--host/--port`; loads `.env`. | | `app.py` | **NiceGUI** GUI: configuration form, input validation, progress, and the interactive results view (ECharts chart, per-topic findings + papers, live re-threshold / search / sort / facet, save + load a run), exports. | -| `gradio_app.py` | **Gradio** GUI (deployed to Hugging Face) β€” same features via HTML rendering; launched by `space_app.py`. | -| `view.py` | GUI-agnostic view logic shared by both front-ends: `keywords`/`matches`/`highlight`/`sort_papers`/`compute_view` + CSV/JSON export bytes. | +| `view.py` | GUI-agnostic view logic used by `app.py`: `keywords`/`matches`/`highlight`/`sort_papers`/`compute_view` + CSV/JSON export bytes. | | `pipeline.py` | `AnalysisConfig` + `run_analysis()` orchestrating the stages with a `Progress` object (supports cooperative cancel). | | `sources.py` | Source interface + registry + `make_source()`; `IJCAISource`, `OpenReviewSource`, `PSCCSource`, `DBLPSource`, shared `_robust_get`. | | `scraper.py` | `AnthologyScraper` (ACL Anthology adapter, also serving EMNLP / NAACL) + shared HTML helpers. | diff --git a/pyproject.toml b/pyproject.toml index b68b769..c20dfae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,14 +18,9 @@ dependencies = [ # Optional embedding-based topic backend. Heavy (pulls in sentence-transformers # / torch); install with `uv sync --extra bertopic`. bertopic = ["bertopic>=0.16"] -# Gradio front-end (the one deployed to Hugging Face Spaces). The NiceGUI GUI -# ships in the base install; install this to run the Gradio GUI locally with -# `uv sync --extra gradio` then `uv run conflens-gradio`. -gradio = ["gradio>=4.44"] [project.scripts] -conference-analyzer = "conflens.cli:main" # NiceGUI GUI -conflens-gradio = "conflens.gradio_app:main" # Gradio GUI +conference-analyzer = "conflens.cli:main" [dependency-groups] # Installed by default with `uv sync`; used for tests + linting / CI. diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 210cb8a..0000000 --- a/requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -# Dependencies for the Hugging Face Gradio Space (sdk: gradio). -# -# HF installs these with pip, then runs space_app.py (which imports the conflens -# package from this repo). Deliberately excludes nicegui β€” the Space runs the -# Gradio GUI only. Keep in sync with the runtime deps in pyproject.toml -# (everything except nicegui) plus gradio. -anthropic>=0.40 -openai>=1.40 -litellm>=1.40 -python-pptx>=0.6.21 -python-dotenv>=1.0 -gradio>=4.44 diff --git a/space_app.py b/space_app.py deleted file mode 100644 index 1cbf8d5..0000000 --- a/space_app.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Hugging Face Space entry point (Gradio SDK). - -The Space's README metadata sets ``app_file: space_app.py``. HF imports this -module, finds the module-level ``demo`` and serves it. Running it directly -(``python space_app.py``) launches locally too. - -The Space runs on free **CPU** hardware, so Gradio's SSR (which spawns a Node -sidecar) is disabled β€” the app is plain server-rendered Python. Set the Space -hardware to *CPU basic*; ZeroGPU hardware would error with "No @spaces.GPU -function detected" because this app has no GPU code. -""" - -import os - -from conflens.gradio_app import build_demo - -# Disable Gradio SSR (Node proxy) β€” set before any launch, whether HF launches -# `demo` itself or this file is run directly. -os.environ.setdefault("GRADIO_SSR_MODE", "false") - -demo = build_demo() - -if __name__ == "__main__": - demo.launch( - server_name="0.0.0.0", - server_port=int(os.environ.get("PORT", "7860")), - ssr_mode=False, - ) diff --git a/tests/test_gradio_app.py b/tests/test_gradio_app.py deleted file mode 100644 index ae3ce50..0000000 --- a/tests/test_gradio_app.py +++ /dev/null @@ -1,45 +0,0 @@ -import pytest - -pytest.importorskip("gradio") # only runs when the `gradio` extra is installed - -from conflens.models import AnalysisResult, Paper, Topic # noqa: E402 - - -def _result() -> AnalysisResult: - papers = [ - Paper("p1", "Agentic Planning", "http://x/1", "http://x/1.pdf", - ["Ada Lovelace"], "tool agents", True, 0.9, "core", [0, 1]), - Paper("p2", "Graph Networks", "http://x/2", "http://x/2.pdf", - ["Marie Curie"], "gnn", True, 0.6, "core", [1]), - ] - topics = [Topic(0, "Agents", "d", ["f"], ["p1"]), Topic(1, "Graphs", "d", ["f"], ["p1", "p2"])] - return AnalysisResult("Agentic AI", "https://x", 50, papers, papers, topics, 0, 0.5) - - -def test_render_html_highlights_and_filters(): - from conflens import gradio_app as g - - html_, status = g._render_html(_result(), 0.5, "agent", "", "confidence", False) - assert "" in html_ # keyword highlighted - assert "Papers per topic" in html_ # chart rendered - assert "Agents" in html_ # topic card - assert "of 2 papers" in status - - # raising the threshold hides the 0.60 paper in global mode - h2, s2 = g._render_html(_result(), 0.7, "", "", "title", True) - assert "Graph Networks" not in h2 - assert "1 of 2 papers" in s2 - - -def test_build_demo_constructs(): - from conflens import gradio_app as g - - demo = g.build_demo() - assert demo.__class__.__name__ == "Blocks" - - -def test_summary_md(): - from conflens import gradio_app as g - - md = g._summary_md(_result()) - assert "50" in md and "Agentic AI" in md and "topics" in md diff --git a/uv.lock b/uv.lock index 4580566..a9310fd 100644 --- a/uv.lock +++ b/uv.lock @@ -179,62 +179,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] -[[package]] -name = "audioop-lts" -version = "0.2.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/38/53/946db57842a50b2da2e0c1e34bd37f36f5aadba1a929a3971c5d7841dbca/audioop_lts-0.2.2.tar.gz", hash = "sha256:64d0c62d88e67b98a1a5e71987b7aa7b5bcffc7dcee65b635823dbdd0a8dbbd0", size = 30686, upload-time = "2025-08-05T16:43:17.409Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/d4/94d277ca941de5a507b07f0b592f199c22454eeaec8f008a286b3fbbacd6/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd3d4602dc64914d462924a08c1a9816435a2155d74f325853c1f1ac3b2d9800", size = 46523, upload-time = "2025-08-05T16:42:20.836Z" }, - { url = "https://files.pythonhosted.org/packages/f8/5a/656d1c2da4b555920ce4177167bfeb8623d98765594af59702c8873f60ec/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:550c114a8df0aafe9a05442a1162dfc8fec37e9af1d625ae6060fed6e756f303", size = 27455, upload-time = "2025-08-05T16:42:22.283Z" }, - { url = "https://files.pythonhosted.org/packages/1b/83/ea581e364ce7b0d41456fb79d6ee0ad482beda61faf0cab20cbd4c63a541/audioop_lts-0.2.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:9a13dc409f2564de15dd68be65b462ba0dde01b19663720c68c1140c782d1d75", size = 26997, upload-time = "2025-08-05T16:42:23.849Z" }, - { url = "https://files.pythonhosted.org/packages/b8/3b/e8964210b5e216e5041593b7d33e97ee65967f17c282e8510d19c666dab4/audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51c916108c56aa6e426ce611946f901badac950ee2ddaf302b7ed35d9958970d", size = 85844, upload-time = "2025-08-05T16:42:25.208Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2e/0a1c52faf10d51def20531a59ce4c706cb7952323b11709e10de324d6493/audioop_lts-0.2.2-cp313-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47eba38322370347b1c47024defbd36374a211e8dd5b0dcbce7b34fdb6f8847b", size = 85056, upload-time = "2025-08-05T16:42:26.559Z" }, - { url = "https://files.pythonhosted.org/packages/75/e8/cd95eef479656cb75ab05dfece8c1f8c395d17a7c651d88f8e6e291a63ab/audioop_lts-0.2.2-cp313-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba7c3a7e5f23e215cb271516197030c32aef2e754252c4c70a50aaff7031a2c8", size = 93892, upload-time = "2025-08-05T16:42:27.902Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1e/a0c42570b74f83efa5cca34905b3eef03f7ab09fe5637015df538a7f3345/audioop_lts-0.2.2-cp313-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def246fe9e180626731b26e89816e79aae2276f825420a07b4a647abaa84becc", size = 96660, upload-time = "2025-08-05T16:42:28.9Z" }, - { url = "https://files.pythonhosted.org/packages/50/d5/8a0ae607ca07dbb34027bac8db805498ee7bfecc05fd2c148cc1ed7646e7/audioop_lts-0.2.2-cp313-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e160bf9df356d841bb6c180eeeea1834085464626dc1b68fa4e1d59070affdc3", size = 79143, upload-time = "2025-08-05T16:42:29.929Z" }, - { url = "https://files.pythonhosted.org/packages/12/17/0d28c46179e7910bfb0bb62760ccb33edb5de973052cb2230b662c14ca2e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b4cd51a57b698b2d06cb9993b7ac8dfe89a3b2878e96bc7948e9f19ff51dba6", size = 84313, upload-time = "2025-08-05T16:42:30.949Z" }, - { url = "https://files.pythonhosted.org/packages/84/ba/bd5d3806641564f2024e97ca98ea8f8811d4e01d9b9f9831474bc9e14f9e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4a53aa7c16a60a6857e6b0b165261436396ef7293f8b5c9c828a3a203147ed4a", size = 93044, upload-time = "2025-08-05T16:42:31.959Z" }, - { url = "https://files.pythonhosted.org/packages/f9/5e/435ce8d5642f1f7679540d1e73c1c42d933331c0976eb397d1717d7f01a3/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:3fc38008969796f0f689f1453722a0f463da1b8a6fbee11987830bfbb664f623", size = 78766, upload-time = "2025-08-05T16:42:33.302Z" }, - { url = "https://files.pythonhosted.org/packages/ae/3b/b909e76b606cbfd53875693ec8c156e93e15a1366a012f0b7e4fb52d3c34/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:15ab25dd3e620790f40e9ead897f91e79c0d3ce65fe193c8ed6c26cffdd24be7", size = 87640, upload-time = "2025-08-05T16:42:34.854Z" }, - { url = "https://files.pythonhosted.org/packages/30/e7/8f1603b4572d79b775f2140d7952f200f5e6c62904585d08a01f0a70393a/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:03f061a1915538fd96272bac9551841859dbb2e3bf73ebe4a23ef043766f5449", size = 86052, upload-time = "2025-08-05T16:42:35.839Z" }, - { url = "https://files.pythonhosted.org/packages/b5/96/c37846df657ccdda62ba1ae2b6534fa90e2e1b1742ca8dcf8ebd38c53801/audioop_lts-0.2.2-cp313-abi3-win32.whl", hash = "sha256:3bcddaaf6cc5935a300a8387c99f7a7fbbe212a11568ec6cf6e4bc458c048636", size = 26185, upload-time = "2025-08-05T16:42:37.04Z" }, - { url = "https://files.pythonhosted.org/packages/34/a5/9d78fdb5b844a83da8a71226c7bdae7cc638861085fff7a1d707cb4823fa/audioop_lts-0.2.2-cp313-abi3-win_amd64.whl", hash = "sha256:a2c2a947fae7d1062ef08c4e369e0ba2086049a5e598fda41122535557012e9e", size = 30503, upload-time = "2025-08-05T16:42:38.427Z" }, - { url = "https://files.pythonhosted.org/packages/34/25/20d8fde083123e90c61b51afb547bb0ea7e77bab50d98c0ab243d02a0e43/audioop_lts-0.2.2-cp313-abi3-win_arm64.whl", hash = "sha256:5f93a5db13927a37d2d09637ccca4b2b6b48c19cd9eda7b17a2e9f77edee6a6f", size = 24173, upload-time = "2025-08-05T16:42:39.704Z" }, - { url = "https://files.pythonhosted.org/packages/58/a7/0a764f77b5c4ac58dc13c01a580f5d32ae8c74c92020b961556a43e26d02/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:73f80bf4cd5d2ca7814da30a120de1f9408ee0619cc75da87d0641273d202a09", size = 47096, upload-time = "2025-08-05T16:42:40.684Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ed/ebebedde1a18848b085ad0fa54b66ceb95f1f94a3fc04f1cd1b5ccb0ed42/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:106753a83a25ee4d6f473f2be6b0966fc1c9af7e0017192f5531a3e7463dce58", size = 27748, upload-time = "2025-08-05T16:42:41.992Z" }, - { url = "https://files.pythonhosted.org/packages/cb/6e/11ca8c21af79f15dbb1c7f8017952ee8c810c438ce4e2b25638dfef2b02c/audioop_lts-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fbdd522624141e40948ab3e8cdae6e04c748d78710e9f0f8d4dae2750831de19", size = 27329, upload-time = "2025-08-05T16:42:42.987Z" }, - { url = "https://files.pythonhosted.org/packages/84/52/0022f93d56d85eec5da6b9da6a958a1ef09e80c39f2cc0a590c6af81dcbb/audioop_lts-0.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:143fad0311e8209ece30a8dbddab3b65ab419cbe8c0dde6e8828da25999be911", size = 92407, upload-time = "2025-08-05T16:42:44.336Z" }, - { url = "https://files.pythonhosted.org/packages/87/1d/48a889855e67be8718adbc7a01f3c01d5743c325453a5e81cf3717664aad/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfbbc74ec68a0fd08cfec1f4b5e8cca3d3cd7de5501b01c4b5d209995033cde9", size = 91811, upload-time = "2025-08-05T16:42:45.325Z" }, - { url = "https://files.pythonhosted.org/packages/98/a6/94b7213190e8077547ffae75e13ed05edc488653c85aa5c41472c297d295/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfcac6aa6f42397471e4943e0feb2244549db5c5d01efcd02725b96af417f3fe", size = 100470, upload-time = "2025-08-05T16:42:46.468Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e9/78450d7cb921ede0cfc33426d3a8023a3bda755883c95c868ee36db8d48d/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:752d76472d9804ac60f0078c79cdae8b956f293177acd2316cd1e15149aee132", size = 103878, upload-time = "2025-08-05T16:42:47.576Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e2/cd5439aad4f3e34ae1ee852025dc6aa8f67a82b97641e390bf7bd9891d3e/audioop_lts-0.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:83c381767e2cc10e93e40281a04852facc4cd9334550e0f392f72d1c0a9c5753", size = 84867, upload-time = "2025-08-05T16:42:49.003Z" }, - { url = "https://files.pythonhosted.org/packages/68/4b/9d853e9076c43ebba0d411e8d2aa19061083349ac695a7d082540bad64d0/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c0022283e9556e0f3643b7c3c03f05063ca72b3063291834cca43234f20c60bb", size = 90001, upload-time = "2025-08-05T16:42:50.038Z" }, - { url = "https://files.pythonhosted.org/packages/58/26/4bae7f9d2f116ed5593989d0e521d679b0d583973d203384679323d8fa85/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a2d4f1513d63c795e82948e1305f31a6d530626e5f9f2605408b300ae6095093", size = 99046, upload-time = "2025-08-05T16:42:51.111Z" }, - { url = "https://files.pythonhosted.org/packages/b2/67/a9f4fb3e250dda9e9046f8866e9fa7d52664f8985e445c6b4ad6dfb55641/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c9c8e68d8b4a56fda8c025e538e639f8c5953f5073886b596c93ec9b620055e7", size = 84788, upload-time = "2025-08-05T16:42:52.198Z" }, - { url = "https://files.pythonhosted.org/packages/70/f7/3de86562db0121956148bcb0fe5b506615e3bcf6e63c4357a612b910765a/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:96f19de485a2925314f5020e85911fb447ff5fbef56e8c7c6927851b95533a1c", size = 94472, upload-time = "2025-08-05T16:42:53.59Z" }, - { url = "https://files.pythonhosted.org/packages/f1/32/fd772bf9078ae1001207d2df1eef3da05bea611a87dd0e8217989b2848fa/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e541c3ef484852ef36545f66209444c48b28661e864ccadb29daddb6a4b8e5f5", size = 92279, upload-time = "2025-08-05T16:42:54.632Z" }, - { url = "https://files.pythonhosted.org/packages/4f/41/affea7181592ab0ab560044632571a38edaf9130b84928177823fbf3176a/audioop_lts-0.2.2-cp313-cp313t-win32.whl", hash = "sha256:d5e73fa573e273e4f2e5ff96f9043858a5e9311e94ffefd88a3186a910c70917", size = 26568, upload-time = "2025-08-05T16:42:55.627Z" }, - { url = "https://files.pythonhosted.org/packages/28/2b/0372842877016641db8fc54d5c88596b542eec2f8f6c20a36fb6612bf9ee/audioop_lts-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9191d68659eda01e448188f60364c7763a7ca6653ed3f87ebb165822153a8547", size = 30942, upload-time = "2025-08-05T16:42:56.674Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ca/baf2b9cc7e96c179bb4a54f30fcd83e6ecb340031bde68f486403f943768/audioop_lts-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c174e322bb5783c099aaf87faeb240c8d210686b04bd61dfd05a8e5a83d88969", size = 24603, upload-time = "2025-08-05T16:42:57.571Z" }, - { url = "https://files.pythonhosted.org/packages/5c/73/413b5a2804091e2c7d5def1d618e4837f1cb82464e230f827226278556b7/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f9ee9b52f5f857fbaf9d605a360884f034c92c1c23021fb90b2e39b8e64bede6", size = 47104, upload-time = "2025-08-05T16:42:58.518Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8c/daa3308dc6593944410c2c68306a5e217f5c05b70a12e70228e7dd42dc5c/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:49ee1a41738a23e98d98b937a0638357a2477bc99e61b0f768a8f654f45d9b7a", size = 27754, upload-time = "2025-08-05T16:43:00.132Z" }, - { url = "https://files.pythonhosted.org/packages/4e/86/c2e0f627168fcf61781a8f72cab06b228fe1da4b9fa4ab39cfb791b5836b/audioop_lts-0.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b00be98ccd0fc123dcfad31d50030d25fcf31488cde9e61692029cd7394733b", size = 27332, upload-time = "2025-08-05T16:43:01.666Z" }, - { url = "https://files.pythonhosted.org/packages/c7/bd/35dce665255434f54e5307de39e31912a6f902d4572da7c37582809de14f/audioop_lts-0.2.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6d2e0f9f7a69403e388894d4ca5ada5c47230716a03f2847cfc7bd1ecb589d6", size = 92396, upload-time = "2025-08-05T16:43:02.991Z" }, - { url = "https://files.pythonhosted.org/packages/2d/d2/deeb9f51def1437b3afa35aeb729d577c04bcd89394cb56f9239a9f50b6f/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9b0b8a03ef474f56d1a842af1a2e01398b8f7654009823c6d9e0ecff4d5cfbf", size = 91811, upload-time = "2025-08-05T16:43:04.096Z" }, - { url = "https://files.pythonhosted.org/packages/76/3b/09f8b35b227cee28cc8231e296a82759ed80c1a08e349811d69773c48426/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2b267b70747d82125f1a021506565bdc5609a2b24bcb4773c16d79d2bb260bbd", size = 100483, upload-time = "2025-08-05T16:43:05.085Z" }, - { url = "https://files.pythonhosted.org/packages/0b/15/05b48a935cf3b130c248bfdbdea71ce6437f5394ee8533e0edd7cfd93d5e/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0337d658f9b81f4cd0fdb1f47635070cc084871a3d4646d9de74fdf4e7c3d24a", size = 103885, upload-time = "2025-08-05T16:43:06.197Z" }, - { url = "https://files.pythonhosted.org/packages/83/80/186b7fce6d35b68d3d739f228dc31d60b3412105854edb975aa155a58339/audioop_lts-0.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:167d3b62586faef8b6b2275c3218796b12621a60e43f7e9d5845d627b9c9b80e", size = 84899, upload-time = "2025-08-05T16:43:07.291Z" }, - { url = "https://files.pythonhosted.org/packages/49/89/c78cc5ac6cb5828f17514fb12966e299c850bc885e80f8ad94e38d450886/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0d9385e96f9f6da847f4d571ce3cb15b5091140edf3db97276872647ce37efd7", size = 89998, upload-time = "2025-08-05T16:43:08.335Z" }, - { url = "https://files.pythonhosted.org/packages/4c/4b/6401888d0c010e586c2ca50fce4c903d70a6bb55928b16cfbdfd957a13da/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:48159d96962674eccdca9a3df280e864e8ac75e40a577cc97c5c42667ffabfc5", size = 99046, upload-time = "2025-08-05T16:43:09.367Z" }, - { url = "https://files.pythonhosted.org/packages/de/f8/c874ca9bb447dae0e2ef2e231f6c4c2b0c39e31ae684d2420b0f9e97ee68/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8fefe5868cd082db1186f2837d64cfbfa78b548ea0d0543e9b28935ccce81ce9", size = 84843, upload-time = "2025-08-05T16:43:10.749Z" }, - { url = "https://files.pythonhosted.org/packages/3e/c0/0323e66f3daebc13fd46b36b30c3be47e3fc4257eae44f1e77eb828c703f/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:58cf54380c3884fb49fdd37dfb7a772632b6701d28edd3e2904743c5e1773602", size = 94490, upload-time = "2025-08-05T16:43:12.131Z" }, - { url = "https://files.pythonhosted.org/packages/98/6b/acc7734ac02d95ab791c10c3f17ffa3584ccb9ac5c18fd771c638ed6d1f5/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:088327f00488cdeed296edd9215ca159f3a5a5034741465789cad403fcf4bec0", size = 92297, upload-time = "2025-08-05T16:43:13.139Z" }, - { url = "https://files.pythonhosted.org/packages/13/c3/c3dc3f564ce6877ecd2a05f8d751b9b27a8c320c2533a98b0c86349778d0/audioop_lts-0.2.2-cp314-cp314t-win32.whl", hash = "sha256:068aa17a38b4e0e7de771c62c60bbca2455924b67a8814f3b0dee92b5820c0b3", size = 27331, upload-time = "2025-08-05T16:43:14.19Z" }, - { url = "https://files.pythonhosted.org/packages/72/bb/b4608537e9ffcb86449091939d52d24a055216a36a8bf66b936af8c3e7ac/audioop_lts-0.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a5bf613e96f49712073de86f20dbdd4014ca18efd4d34ed18c75bd808337851b", size = 31697, upload-time = "2025-08-05T16:43:15.193Z" }, - { url = "https://files.pythonhosted.org/packages/f6/22/91616fe707a5c5510de2cac9b046a30defe7007ba8a0c04f9c08f27df312/audioop_lts-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:b492c3b040153e68b9fdaff5913305aaaba5bb433d8a7f73d5cf6a64ed3cc1dd", size = 25206, upload-time = "2025-08-05T16:43:16.444Z" }, -] - [[package]] name = "bertopic" version = "0.17.4" @@ -264,34 +208,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/37/e8730c3587a65eb5645d4aba2d27aae48e8003614d6aaf15dda67f702f1f/bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5", size = 32764, upload-time = "2024-02-18T19:09:04.156Z" }, ] -[[package]] -name = "brotli" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f7/16/c92ca344d646e71a43b8bb353f0a6490d7f6e06210f8554c8f874e454285/brotli-1.2.0.tar.gz", hash = "sha256:e310f77e41941c13340a95976fe66a8a95b01e783d430eeaf7a2f87e0a57dd0a", size = 7388632, upload-time = "2025-11-05T18:39:42.86Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/d4/4ad5432ac98c73096159d9ce7ffeb82d151c2ac84adcc6168e476bb54674/brotli-1.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9e5825ba2c9998375530504578fd4d5d1059d09621a02065d1b6bfc41a8e05ab", size = 861523, upload-time = "2025-11-05T18:38:34.67Z" }, - { url = "https://files.pythonhosted.org/packages/91/9f/9cc5bd03ee68a85dc4bc89114f7067c056a3c14b3d95f171918c088bf88d/brotli-1.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0cf8c3b8ba93d496b2fae778039e2f5ecc7cff99df84df337ca31d8f2252896c", size = 444289, upload-time = "2025-11-05T18:38:35.6Z" }, - { url = "https://files.pythonhosted.org/packages/2e/b6/fe84227c56a865d16a6614e2c4722864b380cb14b13f3e6bef441e73a85a/brotli-1.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8565e3cdc1808b1a34714b553b262c5de5fbda202285782173ec137fd13709f", size = 1528076, upload-time = "2025-11-05T18:38:36.639Z" }, - { url = "https://files.pythonhosted.org/packages/55/de/de4ae0aaca06c790371cf6e7ee93a024f6b4bb0568727da8c3de112e726c/brotli-1.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:26e8d3ecb0ee458a9804f47f21b74845cc823fd1bb19f02272be70774f56e2a6", size = 1626880, upload-time = "2025-11-05T18:38:37.623Z" }, - { url = "https://files.pythonhosted.org/packages/5f/16/a1b22cbea436642e071adcaf8d4b350a2ad02f5e0ad0da879a1be16188a0/brotli-1.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67a91c5187e1eec76a61625c77a6c8c785650f5b576ca732bd33ef58b0dff49c", size = 1419737, upload-time = "2025-11-05T18:38:38.729Z" }, - { url = "https://files.pythonhosted.org/packages/46/63/c968a97cbb3bdbf7f974ef5a6ab467a2879b82afbc5ffb65b8acbb744f95/brotli-1.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ecdb3b6dc36e6d6e14d3a1bdc6c1057c8cbf80db04031d566eb6080ce283a48", size = 1484440, upload-time = "2025-11-05T18:38:39.916Z" }, - { url = "https://files.pythonhosted.org/packages/06/9d/102c67ea5c9fc171f423e8399e585dabea29b5bc79b05572891e70013cdd/brotli-1.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3e1b35d56856f3ed326b140d3c6d9db91740f22e14b06e840fe4bb1923439a18", size = 1593313, upload-time = "2025-11-05T18:38:41.24Z" }, - { url = "https://files.pythonhosted.org/packages/9e/4a/9526d14fa6b87bc827ba1755a8440e214ff90de03095cacd78a64abe2b7d/brotli-1.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54a50a9dad16b32136b2241ddea9e4df159b41247b2ce6aac0b3276a66a8f1e5", size = 1487945, upload-time = "2025-11-05T18:38:42.277Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e8/3fe1ffed70cbef83c5236166acaed7bb9c766509b157854c80e2f766b38c/brotli-1.2.0-cp313-cp313-win32.whl", hash = "sha256:1b1d6a4efedd53671c793be6dd760fcf2107da3a52331ad9ea429edf0902f27a", size = 334368, upload-time = "2025-11-05T18:38:43.345Z" }, - { url = "https://files.pythonhosted.org/packages/ff/91/e739587be970a113b37b821eae8097aac5a48e5f0eca438c22e4c7dd8648/brotli-1.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:b63daa43d82f0cdabf98dee215b375b4058cce72871fd07934f179885aad16e8", size = 369116, upload-time = "2025-11-05T18:38:44.609Z" }, - { url = "https://files.pythonhosted.org/packages/17/e1/298c2ddf786bb7347a1cd71d63a347a79e5712a7c0cba9e3c3458ebd976f/brotli-1.2.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6c12dad5cd04530323e723787ff762bac749a7b256a5bece32b2243dd5c27b21", size = 863080, upload-time = "2025-11-05T18:38:45.503Z" }, - { url = "https://files.pythonhosted.org/packages/84/0c/aac98e286ba66868b2b3b50338ffbd85a35c7122e9531a73a37a29763d38/brotli-1.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:3219bd9e69868e57183316ee19c84e03e8f8b5a1d1f2667e1aa8c2f91cb061ac", size = 445453, upload-time = "2025-11-05T18:38:46.433Z" }, - { url = "https://files.pythonhosted.org/packages/ec/f1/0ca1f3f99ae300372635ab3fe2f7a79fa335fee3d874fa7f9e68575e0e62/brotli-1.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:963a08f3bebd8b75ac57661045402da15991468a621f014be54e50f53a58d19e", size = 1528168, upload-time = "2025-11-05T18:38:47.371Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a6/2ebfc8f766d46df8d3e65b880a2e220732395e6d7dc312c1e1244b0f074a/brotli-1.2.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9322b9f8656782414b37e6af884146869d46ab85158201d82bab9abbcb971dc7", size = 1627098, upload-time = "2025-11-05T18:38:48.385Z" }, - { url = "https://files.pythonhosted.org/packages/f3/2f/0976d5b097ff8a22163b10617f76b2557f15f0f39d6a0fe1f02b1a53e92b/brotli-1.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cf9cba6f5b78a2071ec6fb1e7bd39acf35071d90a81231d67e92d637776a6a63", size = 1419861, upload-time = "2025-11-05T18:38:49.372Z" }, - { url = "https://files.pythonhosted.org/packages/9c/97/d76df7176a2ce7616ff94c1fb72d307c9a30d2189fe877f3dd99af00ea5a/brotli-1.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7547369c4392b47d30a3467fe8c3330b4f2e0f7730e45e3103d7d636678a808b", size = 1484594, upload-time = "2025-11-05T18:38:50.655Z" }, - { url = "https://files.pythonhosted.org/packages/d3/93/14cf0b1216f43df5609f5b272050b0abd219e0b54ea80b47cef9867b45e7/brotli-1.2.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:fc1530af5c3c275b8524f2e24841cbe2599d74462455e9bae5109e9ff42e9361", size = 1593455, upload-time = "2025-11-05T18:38:51.624Z" }, - { url = "https://files.pythonhosted.org/packages/b3/73/3183c9e41ca755713bdf2cc1d0810df742c09484e2e1ddd693bee53877c1/brotli-1.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d2d085ded05278d1c7f65560aae97b3160aeb2ea2c0b3e26204856beccb60888", size = 1488164, upload-time = "2025-11-05T18:38:53.079Z" }, - { url = "https://files.pythonhosted.org/packages/64/6a/0c78d8f3a582859236482fd9fa86a65a60328a00983006bcf6d83b7b2253/brotli-1.2.0-cp314-cp314-win32.whl", hash = "sha256:832c115a020e463c2f67664560449a7bea26b0c1fdd690352addad6d0a08714d", size = 339280, upload-time = "2025-11-05T18:38:54.02Z" }, - { url = "https://files.pythonhosted.org/packages/f5/10/56978295c14794b2c12007b07f3e41ba26acda9257457d7085b0bb3bb90c/brotli-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:e7c0af964e0b4e3412a0ebf341ea26ec767fa0b4cf81abb5e897c9338b5ad6a3", size = 375639, upload-time = "2025-11-05T18:38:55.67Z" }, -] - [[package]] name = "certifi" version = "2026.6.17" @@ -396,9 +312,6 @@ dependencies = [ bertopic = [ { name = "bertopic" }, ] -gradio = [ - { name = "gradio" }, -] [package.dev-dependencies] dev = [ @@ -410,14 +323,13 @@ dev = [ requires-dist = [ { name = "anthropic", specifier = ">=0.40" }, { name = "bertopic", marker = "extra == 'bertopic'", specifier = ">=0.16" }, - { name = "gradio", marker = "extra == 'gradio'", specifier = ">=4.44" }, { name = "litellm", specifier = ">=1.40" }, { name = "nicegui", specifier = ">=2.0" }, { name = "openai", specifier = ">=1.40" }, { name = "python-dotenv", specifier = ">=1.0" }, { name = "python-pptx", specifier = ">=0.6.21" }, ] -provides-extras = ["bertopic", "gradio"] +provides-extras = ["bertopic"] [package.metadata.requires-dev] dev = [ @@ -430,7 +342,7 @@ name = "cuda-bindings" version = "13.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-pathfinder" }, + { name = "cuda-pathfinder", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/cc/6e/2394f8163360f8391f8f1b7e72d300a82724edb81a7b7084c799fbd4c91f/cuda_bindings-13.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9efb21c1ee64981e184b9e0ba5eb3179e5ba3d4b51665a6cb52b8ef3d01a7cbf", size = 5920504, upload-time = "2026-05-29T23:11:56.883Z" }, @@ -459,34 +371,34 @@ wheels = [ [package.optional-dependencies] cudart = [ - { name = "nvidia-cuda-runtime" }, + { name = "nvidia-cuda-runtime", marker = "sys_platform == 'linux'" }, ] cufft = [ - { name = "nvidia-cufft" }, + { name = "nvidia-cufft", marker = "sys_platform == 'linux'" }, ] cufile = [ - { name = "nvidia-cufile" }, + { name = "nvidia-cufile", marker = "sys_platform == 'linux'" }, ] cupti = [ - { name = "nvidia-cuda-cupti" }, + { name = "nvidia-cuda-cupti", marker = "sys_platform == 'linux'" }, ] curand = [ - { name = "nvidia-curand" }, + { name = "nvidia-curand", marker = "sys_platform == 'linux'" }, ] cusolver = [ - { name = "nvidia-cusolver" }, + { name = "nvidia-cusolver", marker = "sys_platform == 'linux'" }, ] cusparse = [ - { name = "nvidia-cusparse" }, + { name = "nvidia-cusparse", marker = "sys_platform == 'linux'" }, ] nvjitlink = [ - { name = "nvidia-nvjitlink" }, + { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux'" }, ] nvrtc = [ - { name = "nvidia-cuda-nvrtc" }, + { name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux'" }, ] nvtx = [ - { name = "nvidia-nvtx" }, + { name = "nvidia-nvtx", marker = "sys_platform == 'linux'" }, ] [[package]] @@ -653,70 +565,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/22/4222d7ddf3da30f363edaa98e329c2bce6c65497c9cb2810931c8b2c0fbc/fsspec-2026.6.0-py3-none-any.whl", hash = "sha256:02e0b71817df9b2169dc30a16832045764def1191b43dcff5bb85bdee212d2a1", size = 203949, upload-time = "2026-06-16T01:57:26.358Z" }, ] -[[package]] -name = "gradio" -version = "6.20.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, - { name = "audioop-lts" }, - { name = "brotli" }, - { name = "fastapi" }, - { name = "gradio-client" }, - { name = "groovy" }, - { name = "hf-gradio" }, - { name = "httpx" }, - { name = "huggingface-hub" }, - { name = "jinja2" }, - { name = "markupsafe" }, - { name = "numpy" }, - { name = "orjson" }, - { name = "packaging" }, - { name = "pandas" }, - { name = "pillow" }, - { name = "pydantic" }, - { name = "pydub" }, - { name = "python-multipart" }, - { name = "pytz" }, - { name = "pyyaml" }, - { name = "safehttpx" }, - { name = "semantic-version" }, - { name = "starlette" }, - { name = "tomlkit" }, - { name = "typer" }, - { name = "typing-extensions" }, - { name = "uvicorn" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f8/96/3618f8a189180ab3d4e5f8b258fd92e419eda9f49b6ab4dd50c6782cda33/gradio-6.20.0.tar.gz", hash = "sha256:8672866e9225a1f8297325a6742bb525a3a0ddc8aa4d75d99beb10a27ea2915f", size = 44844340, upload-time = "2026-07-07T18:53:57.402Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/f4/7dc4540836e326391a9d1ba20193d795d5a4582839b60f5c10397f5120bb/gradio-6.20.0-py3-none-any.whl", hash = "sha256:91d2ec29c37917d55b18dba87aa15bb15c4796d84dce9a9ef4ec7d87453a7fee", size = 30964820, upload-time = "2026-07-07T18:53:52.403Z" }, -] - -[[package]] -name = "gradio-client" -version = "2.5.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "fsspec" }, - { name = "httpx" }, - { name = "huggingface-hub" }, - { name = "packaging" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e8/e6/6b6029f5fe2ad7f1211105d530e34d991014c2cae463f9223033031cfc4f/gradio_client-2.5.0.tar.gz", hash = "sha256:4cde99bad62149595c30c90876ca2e405e3a13687ecf895474f3412cb476673d", size = 59013, upload-time = "2026-04-20T23:16:21.518Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/81/0a861b8e1ff42960139c6cd4c7dd591292fa09ea1ae2d87677441cba4c00/gradio_client-2.5.0-py3-none-any.whl", hash = "sha256:d43e2179c29076292a76485ad7ed2e6eaa19d14ac58283bd7f5beabfe4ca958c", size = 59952, upload-time = "2026-04-20T23:16:20.186Z" }, -] - -[[package]] -name = "groovy" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/36/bbdede67400277bef33d3ec0e6a31750da972c469f75966b4930c753218f/groovy-0.1.2.tar.gz", hash = "sha256:25c1dc09b3f9d7e292458aa762c6beb96ea037071bf5e917fc81fb78d2231083", size = 17325, upload-time = "2025-02-28T20:24:56.068Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/28/27/3d6dcadc8a3214d8522c1e7f6a19554e33659be44546d44a2f7572ac7d2a/groovy-0.1.2-py3-none-any.whl", hash = "sha256:7f7975bab18c729a257a8b1ae9dcd70b7cafb1720481beae47719af57c35fa64", size = 14090, upload-time = "2025-02-28T20:24:55.152Z" }, -] - [[package]] name = "h11" version = "0.16.0" @@ -746,19 +594,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/ae/74d1aee2a7c6c39b16a8d4fa7aa3d029d7d08d0c5461d7b55f9619b48598/hdbscan-0.8.44-cp314-cp314-win_amd64.whl", hash = "sha256:5ea248dcaca951861e811411bf3eb9954f932f3a90c8bbe5629b5ee8479e011e", size = 1969244, upload-time = "2026-06-01T18:56:29.289Z" }, ] -[[package]] -name = "hf-gradio" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "gradio-client" }, - { name = "typer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ce/86/c9694b7cfada5780e75769e60dc161a161f4dd7fc91b61db5e3a3338bef9/hf_gradio-0.4.1.tar.gz", hash = "sha256:a017d942618f0d495a58ee4563047fa04bef614c00e0cb789a9a6d0633cffa7b", size = 6560, upload-time = "2026-04-22T14:01:32.334Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/2d/afff2ee87e75d8eb85c92bb8cf0e15b05c23c2ebd8fd8dec781d8601ed7f/hf_gradio-0.4.1-py3-none-any.whl", hash = "sha256:76b8cb8be6abe62d74c1ad2d35b42f0629db89aa9e1a8d033cecfe7c856eeab3", size = 4482, upload-time = "2026-04-17T19:53:31.827Z" }, -] - [[package]] name = "hf-xet" version = "1.5.1" @@ -1442,7 +1277,7 @@ name = "nvidia-cublas" version = "13.1.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cuda-nvrtc" }, + { name = "nvidia-cuda-nvrtc", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/a7/a1/0bd24ee8c8d03adac032fd2909426a00c88f8c57961b1277ded97f91119f/nvidia_cublas-13.1.1.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:b7a210458267ac818974c53038fbec2e969d5c99f305ab15c72522fa9f001dd5", size = 542848918, upload-time = "2026-04-08T18:46:22.985Z" }, @@ -1481,7 +1316,7 @@ name = "nvidia-cudnn-cu13" version = "9.20.0.48" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas" }, + { name = "nvidia-cublas", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/56/c5/83384d846b2fd17c44bd499b36c75a45ed4f095fbbb2252294e89cea5c5c/nvidia_cudnn_cu13-9.20.0.48-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:e31454ae00094b0c55319d9d15b6fa2fc50a9e1c0f5c8c80fb75258234e731e1", size = 444574296, upload-time = "2026-03-09T19:28:27.751Z" }, @@ -1493,7 +1328,7 @@ name = "nvidia-cufft" version = "12.0.0.61" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink" }, + { name = "nvidia-nvjitlink", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, @@ -1523,9 +1358,9 @@ name = "nvidia-cusolver" version = "12.0.4.66" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-cublas" }, - { name = "nvidia-cusparse" }, - { name = "nvidia-nvjitlink" }, + { name = "nvidia-cublas", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-cusparse", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-nvjitlink", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, @@ -1537,7 +1372,7 @@ name = "nvidia-cusparse" version = "12.6.3.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "nvidia-nvjitlink" }, + { name = "nvidia-nvjitlink", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, @@ -1927,15 +1762,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f6/d2/42dd53d0a85c27606f316d3aa5d2869c4e8470a5ed6dec30e4a1abe19192/pydantic_core-2.46.4-cp314-cp314t-win_arm64.whl", hash = "sha256:4fcbe087dbc2068af7eda3aa87634eba216dbda64d1ae73c8684b621d33f6596", size = 2017325, upload-time = "2026-05-06T13:40:52.723Z" }, ] -[[package]] -name = "pydub" -version = "0.25.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/9a/e6bca0eed82db26562c73b5076539a4a08d3cffd19c3cc5913a3e61145fd/pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f", size = 38326, upload-time = "2021-03-10T02:09:54.659Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6", size = 32327, upload-time = "2021-03-10T02:09:53.503Z" }, -] - [[package]] name = "pygments" version = "2.20.0" @@ -2052,15 +1878,6 @@ asyncio-client = [ { name = "aiohttp" }, ] -[[package]] -name = "pytz" -version = "2026.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ff/46/dd499ec9038423421951e4fad73051febaa13d2df82b4064f87af8b8c0c3/pytz-2026.2.tar.gz", hash = "sha256:0e60b47b29f21574376f218fe21abc009894a2321ea16c6754f3cad6eb7cdd6a", size = 320861, upload-time = "2026-05-04T01:35:29.667Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/dd/96da98f892250475bdf2328112d7468abdd4acc7b902b6af23f4ed958ea0/pytz-2026.2-py2.py3-none-any.whl", hash = "sha256:04156e608bee23d3792fd45c94ae47fae1036688e75032eea2e3bf0323d1f126", size = 510141, upload-time = "2026-05-04T01:35:27.408Z" }, -] - [[package]] name = "pyyaml" version = "6.0.3" @@ -2330,18 +2147,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498, upload-time = "2026-06-25T17:20:35.03Z" }, ] -[[package]] -name = "safehttpx" -version = "0.1.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "httpx" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/89/d1/4282284d9cf1ee873607a46442da977fc3c985059315ab23610be31d5885/safehttpx-0.1.7.tar.gz", hash = "sha256:db201c0978c41eddb8bb480f3eee59dd67304fdd91646035e9d9a720049a9d23", size = 10385, upload-time = "2025-10-24T18:30:09.783Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/a3/0f0b7d78e2f1eb9e8e1afbff1d2bff8d60144aee17aca51c065b516743dd/safehttpx-0.1.7-py3-none-any.whl", hash = "sha256:c4f4a162db6993464d7ca3d7cc4af0ffc6515a606dfd220b9f82c6945d869cde", size = 8959, upload-time = "2025-10-24T18:30:08.733Z" }, -] - [[package]] name = "safetensors" version = "0.8.0" @@ -2440,15 +2245,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d5/19/969dc072906c84dd0a3b05dcf57ea750936087d7873549e408b35cfc3f97/scipy-1.18.0-cp314-cp314t-win_arm64.whl", hash = "sha256:368e0a705903c466aa5f08eefb39e6b1b6b2d659e7352a31fd9e2438365be0f8", size = 25279661, upload-time = "2026-06-19T15:01:40.817Z" }, ] -[[package]] -name = "semantic-version" -version = "2.10.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/31/f2289ce78b9b473d582568c234e104d2a342fd658cc288a7553d83bb8595/semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c", size = 52289, upload-time = "2022-05-26T13:35:23.454Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/23/8146aad7d88f4fcb3a6218f41a60f6c2d4e3a72de72da1825dc7c8f7877c/semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177", size = 15552, upload-time = "2022-05-26T13:35:21.206Z" }, -] - [[package]] name = "sentence-transformers" version = "5.6.0" @@ -2627,15 +2423,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, ] -[[package]] -name = "tomlkit" -version = "0.14.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" }, -] - [[package]] name = "torch" version = "2.12.1"