From a3a6a1acd2b8e3d7734f6b3d1e46ffb85333f8af Mon Sep 17 00:00:00 2001 From: sudipnext Date: Thu, 16 Apr 2026 13:33:21 +0545 Subject: [PATCH] Refactor code structure and remove redundant changes in multiple sections --- .gitignore | 7 +- Dockerfile | 25 +- Dockerfile.dev | 25 +- docker-compose.yml | 9 + electron/servers/fastapi/constants/llm.py | 2 +- .../(dashboard)/settings/SettingCodex.tsx | 14 +- .../servers/nextjs/components/CodexConfig.tsx | 14 +- package.json | 7 +- presentation-export/export-version.json | 3 + scripts/sync-presentation-export.cjs | 273 ++++++ .../fastapi/api/v1/ppt/endpoints/images.py | 20 +- servers/fastapi/api/v1/ppt/router.py | 2 + servers/fastapi/constants/documents.py | 94 +- servers/fastapi/constants/llm.py | 2 +- servers/fastapi/models/presentation_layout.py | 40 +- servers/fastapi/models/sql/image_asset.py | 58 -- .../presenton_backend.egg-info/PKG-INFO | 25 + .../presenton_backend.egg-info/SOURCES.txt | 155 ++++ .../dependency_links.txt | 1 + .../presenton_backend.egg-info/requires.txt | 20 + .../presenton_backend.egg-info/top_level.txt | 7 + servers/fastapi/pyproject.toml | 4 - servers/fastapi/server.py | 6 +- servers/fastapi/services/docling_service.py | 33 - .../services/document_conversion_service.py | 235 +++++ servers/fastapi/services/documents_loader.py | 162 +++- .../fastapi/services/export_task_service.py | 242 +++++ .../services/image_generation_service.py | 20 +- servers/fastapi/services/liteparse_service.py | 309 +++++++ servers/fastapi/templates/example.py | 98 +++ .../fastapi/templates/get_layout_by_name.py | 18 + servers/fastapi/templates/handler.py | 707 +++++++++++++++ .../fastapi/templates/presentation_layout.py | 40 + servers/fastapi/templates/prompts.py | 220 +++++ servers/fastapi/templates/providers.py | 425 +++++++++ servers/fastapi/templates/router.py | 65 ++ servers/fastapi/utils/get_env.py | 4 - servers/fastapi/utils/get_layout_by_name.py | 21 +- servers/fastapi/utils/ocr_language.py | 126 +++ servers/fastapi/utils/process_slides.py | 9 +- servers/fastapi/uv.lock | 831 ++---------------- .../(dashboard)/settings/ImageProvider.tsx | 6 +- .../(dashboard)/settings/SettingCodex.tsx | 14 +- .../(dashboard)/settings/SettingPage.tsx | 129 ++- .../(dashboard)/settings/SettingSideBar.tsx | 54 +- .../(dashboard)/settings/TextProvider.tsx | 263 +++--- .../(dashboard)/settings/loading.tsx | 159 ++-- servers/nextjs/app/api/export-as-pdf/route.ts | 73 +- servers/nextjs/components/CodexConfig.tsx | 14 +- servers/nextjs/lib/run-bundled-pdf-export.ts | 139 +++ servers/nextjs/tsconfig.tsbuildinfo | 2 +- 51 files changed, 3928 insertions(+), 1303 deletions(-) create mode 100644 presentation-export/export-version.json create mode 100644 scripts/sync-presentation-export.cjs create mode 100644 servers/fastapi/presenton_backend.egg-info/PKG-INFO create mode 100644 servers/fastapi/presenton_backend.egg-info/SOURCES.txt create mode 100644 servers/fastapi/presenton_backend.egg-info/dependency_links.txt create mode 100644 servers/fastapi/presenton_backend.egg-info/requires.txt create mode 100644 servers/fastapi/presenton_backend.egg-info/top_level.txt delete mode 100644 servers/fastapi/services/docling_service.py create mode 100644 servers/fastapi/services/document_conversion_service.py create mode 100644 servers/fastapi/services/export_task_service.py create mode 100644 servers/fastapi/services/liteparse_service.py create mode 100644 servers/fastapi/templates/example.py create mode 100644 servers/fastapi/templates/get_layout_by_name.py create mode 100644 servers/fastapi/templates/handler.py create mode 100644 servers/fastapi/templates/presentation_layout.py create mode 100644 servers/fastapi/templates/prompts.py create mode 100644 servers/fastapi/templates/providers.py create mode 100644 servers/fastapi/templates/router.py create mode 100644 servers/fastapi/utils/ocr_language.py create mode 100644 servers/nextjs/lib/run-bundled-pdf-export.ts diff --git a/.gitignore b/.gitignore index 0ec246cf..71c9e7ab 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,9 @@ container.db .cursor .agents skills-lock.json -.codex/ \ No newline at end of file +.codex/ + +# presentation-export runtime (downloaded via scripts/sync-presentation-export.cjs or Docker build) +presentation-export/index.js +presentation-export/py/ +.cache/presentation-export/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 8f228620..a99092f3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,20 +3,22 @@ FROM python:3.11-slim-bookworm WORKDIR /app -# Docling + CPU torch: declared in pyproject.toml; lockfile uses PyTorch CPU index. -# UV_EXTRA_INDEX_URL mirrors the old `pip install docling --extra-index-url .../cpu`. +# LiteParse uses Node + @llamaindex/liteparse (same runner as Electron); OCR uses Tesseract. ENV APP_DATA_DIRECTORY=/app_data \ TEMP_DIRECTORY=/tmp/presenton \ PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium \ UV_SYSTEM_PYTHON=1 \ UV_COMPILE_BYTECODE=1 \ UV_LINK_MODE=copy \ - UV_EXTRA_INDEX_URL=https://download.pytorch.org/whl/cpu \ - PATH="/root/.local/bin:${PATH}" + PATH="/root/.local/bin:${PATH}" \ + EXPORT_PACKAGE_ROOT=/app/presentation-export \ + BUILT_PYTHON_MODULE_PATH=/app/presentation-export/py/convert-linux-x64 \ + PRESENTON_APP_ROOT=/app RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates curl \ + ca-certificates curl unzip \ nginx libreoffice fontconfig chromium imagemagick zstd \ + tesseract-ocr tesseract-ocr-eng \ && curl -LsSf https://astral.sh/uv/install.sh | sh \ && rm -rf /var/lib/apt/lists/* @@ -24,6 +26,19 @@ RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y nodejs \ && rm -rf /var/lib/apt/lists/* +RUN mkdir -p /app/document-extraction-liteparse \ + && npm --prefix /app/document-extraction-liteparse init -y \ + && npm --prefix /app/document-extraction-liteparse install @llamaindex/liteparse@1.4.0 --omit=dev +COPY electron/resources/document-extraction/liteparse_runner.mjs /app/document-extraction-liteparse/liteparse_runner.mjs + +# PDF/PPTX export runtime: version pin in presentation-export/export-version.json (or build-arg). +COPY presentation-export/export-version.json /app/presentation-export/export-version.json +COPY scripts/sync-presentation-export.cjs /app/scripts/sync-presentation-export.cjs +ARG EXPORT_RUNTIME_VERSION +RUN export EXPORT_RUNTIME_VERSION="${EXPORT_RUNTIME_VERSION:-}" \ + && node /app/scripts/sync-presentation-export.cjs --force \ + && chmod +x /app/presentation-export/py/convert-linux-x64 + RUN curl -fsSL https://ollama.com/install.sh | sh COPY servers/fastapi /app/servers/fastapi diff --git a/Dockerfile.dev b/Dockerfile.dev index e704c0e2..74503219 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -3,21 +3,22 @@ FROM python:3.11-slim-bookworm WORKDIR /app -# Docling is in pyproject.toml; uv.lock pins torch to this index (same as former: -# pip install docling --extra-index-url https://download.pytorch.org/whl/cpu -# UV_EXTRA_INDEX_URL keeps CPU wheels available if the lock is refreshed in Docker.) +# LiteParse (Node + @llamaindex/liteparse) for document extraction; OCR via Tesseract. ENV APP_DATA_DIRECTORY=/app_data \ TEMP_DIRECTORY=/tmp/presenton \ PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium \ UV_SYSTEM_PYTHON=1 \ UV_COMPILE_BYTECODE=1 \ UV_LINK_MODE=copy \ - UV_EXTRA_INDEX_URL=https://download.pytorch.org/whl/cpu \ - PATH="/root/.local/bin:${PATH}" + PATH="/root/.local/bin:${PATH}" \ + EXPORT_PACKAGE_ROOT=/app/presentation-export \ + BUILT_PYTHON_MODULE_PATH=/app/presentation-export/py/convert-linux-x64 \ + PRESENTON_APP_ROOT=/app RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates curl \ + ca-certificates curl unzip \ nginx libreoffice fontconfig chromium imagemagick zstd \ + tesseract-ocr tesseract-ocr-eng \ && curl -LsSf https://astral.sh/uv/install.sh | sh \ && rm -rf /var/lib/apt/lists/* @@ -25,6 +26,18 @@ RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ && apt-get install -y nodejs \ && rm -rf /var/lib/apt/lists/* +RUN mkdir -p /app/document-extraction-liteparse \ + && npm --prefix /app/document-extraction-liteparse init -y \ + && npm --prefix /app/document-extraction-liteparse install @llamaindex/liteparse@1.4.0 --omit=dev +COPY electron/resources/document-extraction/liteparse_runner.mjs /app/document-extraction-liteparse/liteparse_runner.mjs + +COPY presentation-export/export-version.json /app/presentation-export/export-version.json +COPY scripts/sync-presentation-export.cjs /app/scripts/sync-presentation-export.cjs +ARG EXPORT_RUNTIME_VERSION +RUN export EXPORT_RUNTIME_VERSION="${EXPORT_RUNTIME_VERSION:-}" \ + && node /app/scripts/sync-presentation-export.cjs --force \ + && chmod +x /app/presentation-export/py/convert-linux-x64 + # Bind mount `.:/app` hides any .venv under servers/fastapi at runtime — install deps into # system site-packages (same interpreter `start.js` uses as `python`). COPY servers/fastapi /app/servers/fastapi diff --git a/docker-compose.yml b/docker-compose.yml index 707a52fe..11b08bc6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,9 @@ services: build: context: . dockerfile: Dockerfile + args: + # Optional: override presentation-export release (else presentation-export/export-version.json) + EXPORT_RUNTIME_VERSION: ${EXPORT_RUNTIME_VERSION:-} ports: # You can replace 5000 with any other port number of your choice to run Presenton on a different port number. - "5000:80" @@ -42,6 +45,8 @@ services: build: context: . dockerfile: Dockerfile + args: + EXPORT_RUNTIME_VERSION: ${EXPORT_RUNTIME_VERSION:-} deploy: resources: reservations: @@ -86,6 +91,8 @@ services: build: context: . dockerfile: Dockerfile.dev + args: + EXPORT_RUNTIME_VERSION: ${EXPORT_RUNTIME_VERSION:-} ports: - "5000:80" # Required for Codex OAuth callback (OpenAI redirects browser directly to localhost:1455) @@ -125,6 +132,8 @@ services: build: context: . dockerfile: Dockerfile.dev + args: + EXPORT_RUNTIME_VERSION: ${EXPORT_RUNTIME_VERSION:-} deploy: resources: reservations: diff --git a/electron/servers/fastapi/constants/llm.py b/electron/servers/fastapi/constants/llm.py index 21eacb73..3f663f6c 100644 --- a/electron/servers/fastapi/constants/llm.py +++ b/electron/servers/fastapi/constants/llm.py @@ -4,4 +4,4 @@ OPENAI_URL = "https://api.openai.com/v1" DEFAULT_OPENAI_MODEL = "gpt-4.1" DEFAULT_GOOGLE_MODEL = "models/gemini-2.5-flash" DEFAULT_ANTHROPIC_MODEL = "claude-sonnet-4-20250514" -DEFAULT_CODEX_MODEL = "gpt-5.2-codex" +DEFAULT_CODEX_MODEL = "gpt-5.1-codex-mini" diff --git a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingCodex.tsx b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingCodex.tsx index 8b4e8b9a..756e4317 100644 --- a/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingCodex.tsx +++ b/electron/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/SettingCodex.tsx @@ -46,16 +46,16 @@ interface CodexModel { } const CHATGPT_MODELS: CodexModel[] = [ - { id: "gpt-5.1", name: "GPT-5.1" }, - { id: "gpt-5.1-codex-max", name: "GPT-5.1 Codex Max" }, - { id: "gpt-5.2", name: "GPT-5.2" }, - { id: "gpt-5.2-codex", name: "GPT-5.2 Codex" }, - { id: "gpt-5.3-codex", name: "GPT-5.3 Codex" }, - { id: "gpt-5.4-mini", name: "GPT-5.4 Mini" }, { id: "gpt-5.4", name: "GPT-5.4" }, + { id: "gpt-5.2-codex", name: "GPT-5.2-Codex" }, + { id: "gpt-5.1-codex-max", name: "GPT-5.1-Codex-Max" }, + { id: "gpt-5.4-mini", name: "GPT-5.4-Mini" }, + { id: "gpt-5.3-codex", name: "GPT-5.3-Codex" }, + { id: "gpt-5.2", name: "GPT-5.2" }, + { id: "gpt-5.1-codex-mini", name: "GPT-5.1-Codex-Mini" }, ]; -const DEFAULT_CODEX_MODEL = "gpt-5.4-mini"; +const DEFAULT_CODEX_MODEL = "gpt-5.1-codex-mini"; export default function CodexConfig({ codexModel, diff --git a/electron/servers/nextjs/components/CodexConfig.tsx b/electron/servers/nextjs/components/CodexConfig.tsx index 3f6453d2..da56a161 100644 --- a/electron/servers/nextjs/components/CodexConfig.tsx +++ b/electron/servers/nextjs/components/CodexConfig.tsx @@ -33,16 +33,16 @@ interface CodexModel { } export const CHATGPT_MODELS: CodexModel[] = [ - { id: "gpt-5.1", name: "GPT-5.1" }, - { id: "gpt-5.1-codex-max", name: "GPT-5.1 Codex Max" }, - { id: "gpt-5.2", name: "GPT-5.2" }, - { id: "gpt-5.2-codex", name: "GPT-5.2 Codex" }, - { id: "gpt-5.3-codex", name: "GPT-5.3 Codex" }, - { id: "gpt-5.4 mini", name: "GPT-5.4 Mini" }, { id: "gpt-5.4", name: "GPT-5.4" }, + { id: "gpt-5.2-codex", name: "GPT-5.2-Codex" }, + { id: "gpt-5.1-codex-max", name: "GPT-5.1-Codex-Max" }, + { id: "gpt-5.4-mini", name: "GPT-5.4-Mini" }, + { id: "gpt-5.3-codex", name: "GPT-5.3-Codex" }, + { id: "gpt-5.2", name: "GPT-5.2" }, + { id: "gpt-5.1-codex-mini", name: "GPT-5.1-Codex-Mini" }, ]; -export const DEFAULT_CODEX_MODEL = "gpt-5.4-mini"; +export const DEFAULT_CODEX_MODEL = "gpt-5.1-codex-mini"; export default function CodexConfig({ codexModel, diff --git a/package.json b/package.json index 5e6f4d5d..69902d5f 100644 --- a/package.json +++ b/package.json @@ -2,5 +2,10 @@ "name": "presenton", "version": "1.0.0", "type": "module", - "description": "Open-source AI presentation generator" + "description": "Open-source AI presentation generator", + "scripts": { + "sync:presentation-export": "node scripts/sync-presentation-export.cjs", + "sync:presentation-export:force": "node scripts/sync-presentation-export.cjs --force", + "check:presentation-export": "node scripts/sync-presentation-export.cjs --check-only" + } } diff --git a/presentation-export/export-version.json b/presentation-export/export-version.json new file mode 100644 index 00000000..2cbe162a --- /dev/null +++ b/presentation-export/export-version.json @@ -0,0 +1,3 @@ +{ + "exportVersion": "v0.2.0" +} diff --git a/scripts/sync-presentation-export.cjs b/scripts/sync-presentation-export.cjs new file mode 100644 index 00000000..4933e8ea --- /dev/null +++ b/scripts/sync-presentation-export.cjs @@ -0,0 +1,273 @@ +/** + * Download presenton-export release (Linux x64) into repo-root `presentation-export/`. + * Same release host as Electron (`electron/sync_export_runtime.js`); Docker uses this at build time. + * + * Version resolution (first match): + * 1. EXPORT_RUNTIME_VERSION env + * 2. presentation-export/export-version.json → exportVersion + * + * CLI: --force re-download even if valid runtime already exists + * --check-only verify index.js + converter exist and exit 0/1 + */ +const fs = require("fs"); +const path = require("path"); +const https = require("https"); +const http = require("http"); +const { execFileSync } = require("child_process"); + +const repoRoot = path.join(__dirname, ".."); +const targetRoot = path.join(repoRoot, "presentation-export"); +const targetPyDir = path.join(targetRoot, "py"); +const targetIndex = path.join(targetRoot, "index.js"); +const versionFile = path.join(targetRoot, "export-version.json"); +const cacheDir = path.join(repoRoot, ".cache", "presentation-export"); +const exportRepoBase = + "https://github.com/presenton/presenton-export/releases/download"; +const linuxAssetName = "export-Linux-X64.zip"; + +const cliArgs = new Set(process.argv.slice(2)); +const forceDownload = cliArgs.has("--force"); +const checkOnly = cliArgs.has("--check-only"); + +function ensureDir(dirPath) { + fs.mkdirSync(dirPath, { recursive: true }); +} + +function readPinnedVersion() { + if (!fs.existsSync(versionFile)) { + throw new Error( + `Missing ${path.relative(repoRoot, versionFile)}. Create it with { "exportVersion": "vX.Y.Z" }.` + ); + } + const raw = JSON.parse(fs.readFileSync(versionFile, "utf8")); + const v = (raw.exportVersion || "").trim(); + if (!v) { + throw new Error(`${versionFile} must set "exportVersion" (e.g. "v0.2.0").`); + } + return v; +} + +async function getTargetVersion() { + const fromEnv = (process.env.EXPORT_RUNTIME_VERSION || "").trim(); + if (fromEnv) { + return fromEnv === "latest" ? await resolveLatestTag() : fromEnv; + } + const pinned = readPinnedVersion(); + if (pinned === "latest") { + return await resolveLatestTag(); + } + return pinned; +} + +function requestJson(url, redirects = 5) { + return new Promise((resolve, reject) => { + const client = url.startsWith("https:") ? https : http; + const req = client.get( + url, + { + headers: { + "User-Agent": "presenton-presentation-export-sync", + Accept: "application/vnd.github+json", + }, + }, + (res) => { + if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) { + if (redirects <= 0) { + reject(new Error(`Too many redirects for JSON request: ${url}`)); + return; + } + requestJson(res.headers.location, redirects - 1).then(resolve).catch(reject); + return; + } + if (res.statusCode < 200 || res.statusCode >= 300) { + reject(new Error(`Failed to fetch ${url}. HTTP ${res.statusCode}`)); + return; + } + let payload = ""; + res.setEncoding("utf8"); + res.on("data", (chunk) => { + payload += chunk; + }); + res.on("end", () => { + try { + resolve(JSON.parse(payload)); + } catch (e) { + reject(new Error(`Invalid JSON from ${url}: ${e.message}`)); + } + }); + } + ); + req.on("error", reject); + }); +} + +async function resolveLatestTag() { + const apiUrl = + "https://api.github.com/repos/presenton/presenton-export/releases/latest"; + const latest = await requestJson(apiUrl); + if (!latest.tag_name) { + throw new Error(`Could not resolve latest tag from ${apiUrl}`); + } + return latest.tag_name; +} + +function chmodIfPossible(filePath) { + if (process.platform !== "win32") { + fs.chmodSync(filePath, 0o755); + } +} + +function getConverterCandidates() { + return [ + path.join(targetPyDir, "convert-linux-x64"), + path.join(targetPyDir, "convert-linux-amd64"), + ]; +} + +function validateExistingRuntime() { + if (!fs.existsSync(targetIndex)) { + return { ok: false, reason: `Missing runtime bundle: ${targetIndex}` }; + } + const candidates = getConverterCandidates(); + const converterPath = candidates.find((c) => fs.existsSync(c)); + if (!converterPath) { + return { + ok: false, + reason: `No Linux converter binary under ${targetPyDir}.`, + }; + } + chmodIfPossible(converterPath); + return { ok: true, converterPath }; +} + +function downloadFile(url, outputPath, redirects = 5) { + return new Promise((resolve, reject) => { + const client = url.startsWith("https:") ? https : http; + const req = client.get( + url, + { + headers: { + "User-Agent": "presenton-presentation-export-sync", + Accept: "application/octet-stream", + }, + }, + (res) => { + if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) { + if (redirects <= 0) { + reject(new Error(`Too many redirects while downloading ${url}`)); + return; + } + downloadFile(res.headers.location, outputPath, redirects - 1) + .then(resolve) + .catch(reject); + return; + } + if (res.statusCode < 200 || res.statusCode >= 300) { + reject(new Error(`Failed to download ${url}. HTTP ${res.statusCode}`)); + return; + } + ensureDir(path.dirname(outputPath)); + const fileStream = fs.createWriteStream(outputPath); + res.pipe(fileStream); + fileStream.on("finish", () => { + fileStream.close(resolve); + }); + fileStream.on("error", reject); + } + ); + req.on("error", reject); + }); +} + +function unzipArchive(zipPath, destDir) { + ensureDir(destDir); + execFileSync("unzip", ["-o", zipPath, "-d", destDir], { stdio: "inherit" }); +} + +function resolveExtractedRoot(extractDir) { + const directIndex = path.join(extractDir, "index.js"); + const directPy = path.join(extractDir, "py"); + if (fs.existsSync(directIndex) && fs.existsSync(directPy)) { + return extractDir; + } + const children = fs.readdirSync(extractDir, { withFileTypes: true }); + for (const entry of children) { + if (!entry.isDirectory()) continue; + const candidate = path.join(extractDir, entry.name); + const candidateIndex = path.join(candidate, "index.js"); + const candidatePy = path.join(candidate, "py"); + if (fs.existsSync(candidateIndex) && fs.existsSync(candidatePy)) { + return candidate; + } + } + throw new Error(`Unable to locate export runtime root under ${extractDir}`); +} + +async function downloadAndInstallRuntime() { + const tag = await getTargetVersion(); + const downloadUrl = `${exportRepoBase}/${tag}/${linuxAssetName}`; + + const versionPinBackup = fs.existsSync(versionFile) + ? fs.readFileSync(versionFile, "utf8") + : JSON.stringify({ exportVersion: tag }, null, 2) + "\n"; + + ensureDir(cacheDir); + const zipPath = path.join(cacheDir, linuxAssetName); + const extractDir = path.join(cacheDir, `extract-${Date.now()}`); + + console.log(`[presentation-export] Downloading ${downloadUrl}`); + await downloadFile(downloadUrl, zipPath); + + console.log(`[presentation-export] Extracting ${zipPath}`); + unzipArchive(zipPath, extractDir); + + const sourceRoot = resolveExtractedRoot(extractDir); + fs.rmSync(targetRoot, { recursive: true, force: true }); + ensureDir(targetRoot); + fs.cpSync(sourceRoot, targetRoot, { recursive: true, force: true }); + + ensureDir(path.dirname(versionFile)); + fs.writeFileSync(versionFile, versionPinBackup, "utf8"); + + fs.rmSync(extractDir, { recursive: true, force: true }); + + return { tag, downloadUrl }; +} + +async function main() { + const existing = validateExistingRuntime(); + + if (checkOnly) { + if (!existing.ok) { + throw new Error(existing.reason); + } + console.log("[presentation-export] OK"); + console.log(` - ${targetIndex}`); + console.log(` - ${existing.converterPath}`); + return; + } + + if (existing.ok && !forceDownload) { + console.log("[presentation-export] Using existing runtime:"); + console.log(` - ${targetIndex}`); + console.log(` - ${existing.converterPath}`); + return; + } + + const { tag, downloadUrl } = await downloadAndInstallRuntime(); + const installed = validateExistingRuntime(); + if (!installed.ok) { + throw new Error(installed.reason); + } + + console.log("[presentation-export] Synced successfully:"); + console.log(` - release: ${tag}`); + console.log(` - url: ${downloadUrl}`); + console.log(` - ${targetIndex}`); + console.log(` - ${installed.converterPath}`); +} + +main().catch((err) => { + console.error(`[presentation-export] ${err.message}`); + process.exit(1); +}); diff --git a/servers/fastapi/api/v1/ppt/endpoints/images.py b/servers/fastapi/api/v1/ppt/endpoints/images.py index 525b5f36..4b504584 100644 --- a/servers/fastapi/api/v1/ppt/endpoints/images.py +++ b/servers/fastapi/api/v1/ppt/endpoints/images.py @@ -102,7 +102,7 @@ async def generate_image( sql_session.add(image) await sql_session.commit() - return image.file_url + return image.path @IMAGES_ROUTER.get("/generated", response_model=List[ImageAsset]) @@ -113,12 +113,7 @@ async def get_generated_images(sql_session: AsyncSession = Depends(get_async_ses .where(ImageAsset.is_uploaded == False) .order_by(ImageAsset.created_at.desc()) ) - images = list(images_result) - for image in images: - # Ensure path exposed to the frontend is a web-safe URL - if hasattr(image, "file_url"): - image.path = image.file_url # type: ignore[attr-defined] - return images + return list(images_result) except Exception as e: raise HTTPException( status_code=500, detail=f"Failed to retrieve generated images: {str(e)}" @@ -145,10 +140,6 @@ async def upload_image( # Refresh to ensure all defaults are loaded await sql_session.refresh(image_asset) - # Expose a web-safe URL in the path field for the frontend - if hasattr(image_asset, "file_url"): - image_asset.path = image_asset.file_url # type: ignore[attr-defined] - return image_asset except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to upload image: {str(e)}") @@ -162,12 +153,7 @@ async def get_uploaded_images(sql_session: AsyncSession = Depends(get_async_sess .where(ImageAsset.is_uploaded == True) .order_by(ImageAsset.created_at.desc()) ) - images = list(images_result) - for image in images: - # Ensure path exposed to the frontend is a web-safe URL - if hasattr(image, "file_url"): - image.path = image.file_url # type: ignore[attr-defined] - return images + return list(images_result) except Exception as e: raise HTTPException( status_code=500, detail=f"Failed to retrieve uploaded images: {str(e)}" diff --git a/servers/fastapi/api/v1/ppt/router.py b/servers/fastapi/api/v1/ppt/router.py index fad0cf99..42b2812b 100644 --- a/servers/fastapi/api/v1/ppt/router.py +++ b/servers/fastapi/api/v1/ppt/router.py @@ -18,6 +18,7 @@ from api.v1.ppt.endpoints.slide import SLIDE_ROUTER from api.v1.ppt.endpoints.pptx_slides import PPTX_FONTS_ROUTER from api.v1.ppt.endpoints.theme import THEMES_ROUTER from api.v1.ppt.endpoints.theme_generate import THEME_ROUTER +from templates.router import TEMPLATE_ROUTER API_V1_PPT_ROUTER = APIRouter(prefix="/api/v1/ppt") @@ -43,3 +44,4 @@ API_V1_PPT_ROUTER.include_router(CODEX_AUTH_ROUTER) API_V1_PPT_ROUTER.include_router(PPTX_FONTS_ROUTER) API_V1_PPT_ROUTER.include_router(THEMES_ROUTER) API_V1_PPT_ROUTER.include_router(THEME_ROUTER) +API_V1_PPT_ROUTER.include_router(TEMPLATE_ROUTER) diff --git a/servers/fastapi/constants/documents.py b/servers/fastapi/constants/documents.py index c4f00ec3..0b87a29b 100644 --- a/servers/fastapi/constants/documents.py +++ b/servers/fastapi/constants/documents.py @@ -1,22 +1,90 @@ +PDF_EXTENSIONS = [".pdf"] +TEXT_EXTENSIONS = [".txt"] + +WORD_EXTENSIONS = [".doc", ".docx", ".docm", ".odt", ".rtf"] +POWERPOINT_EXTENSIONS = [".ppt", ".pptx", ".pptm", ".odp"] +SPREADSHEET_EXTENSIONS = [".xls", ".xlsx", ".xlsm", ".ods", ".csv", ".tsv"] + +JPEG_EXTENSIONS = [".jpg", ".jpeg"] +PNG_EXTENSIONS = [".png"] +GIF_EXTENSIONS = [".gif"] +BMP_EXTENSIONS = [".bmp"] +TIFF_EXTENSIONS = [".tiff", ".tif"] +WEBP_EXTENSIONS = [".webp"] +SVG_EXTENSIONS = [".svg"] +IMAGE_EXTENSIONS = ( + JPEG_EXTENSIONS + + PNG_EXTENSIONS + + GIF_EXTENSIONS + + BMP_EXTENSIONS + + TIFF_EXTENSIONS + + WEBP_EXTENSIONS + + SVG_EXTENSIONS +) + +OFFICE_EXTENSIONS = WORD_EXTENSIONS + POWERPOINT_EXTENSIONS + SPREADSHEET_EXTENSIONS + PDF_MIME_TYPES = ["application/pdf"] -TEXT_MIME_TYPES = ["text/plain"] -POWERPOINT_TYPES = [ - "application/vnd.openxmlformats-officedocument.presentationml.presentation" -] -# Alias used by font/PPTX validation helpers shared with the Electron server tree. -PPTX_MIME_TYPES = POWERPOINT_TYPES -WORD_TYPES = [ +TEXT_MIME_TYPES = ["text/plain", "text/markdown"] + +WORD_MIME_TYPES = [ "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/vnd.ms-word.document.macroenabled.12", + "application/vnd.oasis.opendocument.text", + "application/rtf", + "text/rtf", ] -SPREADSHEET_TYPES = ["text/csv", "application/csv"] +POWERPOINT_MIME_TYPES = [ + "application/vnd.ms-powerpoint", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "application/vnd.ms-powerpoint.presentation.macroenabled.12", + "application/vnd.oasis.opendocument.presentation", +] -PNG_MIME_TYPES = ["image/png"] -JPEG_MIME_TYPES = ["image/jpeg"] -WEBP_MIME_TYPES = ["image/webp"] +SPREADSHEET_MIME_TYPES = [ + "application/vnd.ms-excel", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.ms-excel.sheet.macroenabled.12", + "application/vnd.oasis.opendocument.spreadsheet", + "text/csv", + "application/csv", + "text/tab-separated-values", + "text/tsv", +] +IMAGE_MIME_TYPES = [ + "image/jpeg", + "image/png", + "image/gif", + "image/bmp", + "image/tiff", + "image/webp", + "image/svg+xml", +] -UPLOAD_ACCEPTED_FILE_TYPES = ( - PDF_MIME_TYPES + TEXT_MIME_TYPES + POWERPOINT_TYPES + WORD_TYPES +UPLOAD_ACCEPTED_MIME_TYPES = ( + PDF_MIME_TYPES + + TEXT_MIME_TYPES + + WORD_MIME_TYPES + + POWERPOINT_MIME_TYPES + + SPREADSHEET_MIME_TYPES + + IMAGE_MIME_TYPES ) + +UPLOAD_ACCEPTED_EXTENSIONS = ( + PDF_EXTENSIONS + TEXT_EXTENSIONS + OFFICE_EXTENSIONS + IMAGE_EXTENSIONS +) + +# Includes both MIME types and extensions because some clients upload legacy +# office files with generic content-type values. +UPLOAD_ACCEPTED_FILE_TYPES = UPLOAD_ACCEPTED_MIME_TYPES + UPLOAD_ACCEPTED_EXTENSIONS + +# Kept for endpoints that strictly require modern .pptx files. +PPTX_MIME_TYPES = ["application/vnd.openxmlformats-officedocument.presentationml.presentation"] + +# Backward compatibility aliases used across existing modules. +POWERPOINT_TYPES = PPTX_MIME_TYPES +WORD_TYPES = WORD_MIME_TYPES +SPREADSHEET_TYPES = SPREADSHEET_MIME_TYPES diff --git a/servers/fastapi/constants/llm.py b/servers/fastapi/constants/llm.py index 21eacb73..3f663f6c 100644 --- a/servers/fastapi/constants/llm.py +++ b/servers/fastapi/constants/llm.py @@ -4,4 +4,4 @@ OPENAI_URL = "https://api.openai.com/v1" DEFAULT_OPENAI_MODEL = "gpt-4.1" DEFAULT_GOOGLE_MODEL = "models/gemini-2.5-flash" DEFAULT_ANTHROPIC_MODEL = "claude-sonnet-4-20250514" -DEFAULT_CODEX_MODEL = "gpt-5.2-codex" +DEFAULT_CODEX_MODEL = "gpt-5.1-codex-mini" diff --git a/servers/fastapi/models/presentation_layout.py b/servers/fastapi/models/presentation_layout.py index 784e41fc..5de22c72 100644 --- a/servers/fastapi/models/presentation_layout.py +++ b/servers/fastapi/models/presentation_layout.py @@ -1,39 +1,5 @@ -from typing import List, Optional -from fastapi import HTTPException -from pydantic import BaseModel, Field +"""Re-export layout models defined in `templates.presentation_layout`.""" -from models.presentation_structure_model import PresentationStructureModel +from templates.presentation_layout import PresentationLayoutModel, SlideLayoutModel - -class SlideLayoutModel(BaseModel): - id: str - name: Optional[str] = None - description: Optional[str] = None - json_schema: dict - - -class PresentationLayoutModel(BaseModel): - name: str - ordered: bool = Field(default=False) - slides: List[SlideLayoutModel] - - def get_slide_layout_index(self, slide_layout_id: str) -> int: - for index, slide in enumerate(self.slides): - if slide.id == slide_layout_id: - return index - raise HTTPException( - status_code=404, detail=f"Slide layout {slide_layout_id} not found" - ) - - def to_presentation_structure(self): - return PresentationStructureModel( - slides=[index for index in range(len(self.slides))] - ) - - def to_string(self): - message = f"## Presentation Layout\n\n" - for index, slide in enumerate(self.slides): - message += f"### Slide Layout: {index}: \n" - message += f"- Name: {slide.name or slide.json_schema.get('title')} \n" - message += f"- Description: {slide.description} \n\n" - return message +__all__ = ["PresentationLayoutModel", "SlideLayoutModel"] diff --git a/servers/fastapi/models/sql/image_asset.py b/servers/fastapi/models/sql/image_asset.py index 8639f6ce..3efc99c0 100644 --- a/servers/fastapi/models/sql/image_asset.py +++ b/servers/fastapi/models/sql/image_asset.py @@ -1,27 +1,11 @@ from datetime import datetime from typing import Optional -import os import uuid from sqlalchemy import JSON, Column, DateTime from sqlmodel import Field, SQLModel from utils.datetime_utils import get_current_utc_datetime -from utils.get_env import get_app_data_directory_env, get_next_public_fast_api_env -from utils.path_helpers import get_resource_path - - -def _with_fastapi_origin(path: str) -> str: - """Prefix relative web paths with FastAPI origin when available.""" - if path.startswith("http://") or path.startswith("https://"): - return path - - fastapi_origin = (get_next_public_fast_api_env() or "").strip() - if not fastapi_origin: - return path - - normalized_path = path if path.startswith("/") else f"/{path}" - return f"{fastapi_origin.rstrip('/')}{normalized_path}" class ImageAsset(SQLModel, table=True): @@ -34,45 +18,3 @@ class ImageAsset(SQLModel, table=True): is_uploaded: bool = Field(default=False) path: str extras: Optional[dict] = Field(sa_column=Column(JSON), default=None) - - @property - def file_url(self) -> str: - """ - Returns a web path suitable for FastAPI static serving. - - HTTP(S) URLs are returned as-is. - - Files under APP_DATA are exposed under /app_data. - - Files under the packaged static directory are exposed under /static. - """ - path = self.path - - # Already an absolute web URL - if path.startswith("http://") or path.startswith("https://"): - return path - - # Already a web path under known mounts - if path.startswith("/app_data/") or path.startswith("/static/"): - return _with_fastapi_origin(path) - - # Normalize filesystem path - real_path = os.path.realpath(path) - - # Map APP_DATA files to /app_data/... - app_data_dir = get_app_data_directory_env() - if app_data_dir: - app_data_dir_real = os.path.realpath(app_data_dir) - if real_path.startswith(app_data_dir_real): - rel = os.path.relpath(real_path, app_data_dir_real) - rel_web = rel.replace(os.sep, "/") - return _with_fastapi_origin(f"/app_data/{rel_web}") - - # Map packaged static assets to /static/... - static_root = get_resource_path("static") - static_root_real = os.path.realpath(static_root) - if real_path.startswith(static_root_real): - rel = os.path.relpath(real_path, static_root_real) - rel_web = rel.replace(os.sep, "/") - return _with_fastapi_origin(f"/static/{rel_web}") - - # Fallback: return the original path (may be absolute or relative); - # frontend can decide how to handle unusual cases. - return path diff --git a/servers/fastapi/presenton_backend.egg-info/PKG-INFO b/servers/fastapi/presenton_backend.egg-info/PKG-INFO new file mode 100644 index 00000000..434857bf --- /dev/null +++ b/servers/fastapi/presenton_backend.egg-info/PKG-INFO @@ -0,0 +1,25 @@ +Metadata-Version: 2.4 +Name: presenton-backend +Version: 0.1.0 +Summary: Add your description here +Requires-Python: <3.12,>=3.11 +Requires-Dist: alembic>=1.14.0 +Requires-Dist: aiohttp>=3.12.15 +Requires-Dist: aiomysql>=0.2.0 +Requires-Dist: aiosqlite>=0.21.0 +Requires-Dist: anthropic>=0.60.0 +Requires-Dist: asyncpg>=0.30.0 +Requires-Dist: chromadb>=1.0.15 +Requires-Dist: dirtyjson>=1.0.8 +Requires-Dist: fastapi[standard]>=0.116.1 +Requires-Dist: fastembed-vectorstore>=0.5.2 +Requires-Dist: fastmcp>=2.11.0 +Requires-Dist: google-genai>=1.28.0 +Requires-Dist: nltk>=3.9.1 +Requires-Dist: openai>=1.98.0 +Requires-Dist: pathvalidate>=3.3.1 +Requires-Dist: pdfplumber>=0.11.7 +Requires-Dist: pytest>=8.4.1 +Requires-Dist: python-pptx>=1.0.2 +Requires-Dist: redis>=6.2.0 +Requires-Dist: sqlmodel>=0.0.24 diff --git a/servers/fastapi/presenton_backend.egg-info/SOURCES.txt b/servers/fastapi/presenton_backend.egg-info/SOURCES.txt new file mode 100644 index 00000000..fba03e0b --- /dev/null +++ b/servers/fastapi/presenton_backend.egg-info/SOURCES.txt @@ -0,0 +1,155 @@ +pyproject.toml +api/__init__.py +api/lifespan.py +api/main.py +api/middlewares.py +api/v1/mock/router.py +api/v1/ppt/background_tasks.py +api/v1/ppt/router.py +api/v1/ppt/endpoints/__init__.py +api/v1/ppt/endpoints/anthropic.py +api/v1/ppt/endpoints/codex_auth.py +api/v1/ppt/endpoints/files.py +api/v1/ppt/endpoints/fonts.py +api/v1/ppt/endpoints/google.py +api/v1/ppt/endpoints/icons.py +api/v1/ppt/endpoints/images.py +api/v1/ppt/endpoints/layouts.py +api/v1/ppt/endpoints/ollama.py +api/v1/ppt/endpoints/openai.py +api/v1/ppt/endpoints/outlines.py +api/v1/ppt/endpoints/pdf_slides.py +api/v1/ppt/endpoints/pptx_slides.py +api/v1/ppt/endpoints/presentation.py +api/v1/ppt/endpoints/prompts.py +api/v1/ppt/endpoints/slide.py +api/v1/ppt/endpoints/slide_to_html.py +api/v1/ppt/endpoints/theme.py +api/v1/ppt/endpoints/theme_generate.py +api/v1/webhook/router.py +constants/__init__.py +constants/documents.py +constants/llm.py +constants/presentation.py +constants/supported_ollama_models.py +enums/__init__.py +enums/image_provider.py +enums/llm_call_type.py +enums/llm_provider.py +enums/tone.py +enums/verbosity.py +enums/webhook_event.py +models/__init__.py +models/api_error_model.py +models/decomposed_file_info.py +models/document_chunk.py +models/generate_presentation_request.py +models/image_prompt.py +models/json_path_guide.py +models/llm_message.py +models/llm_tool_call.py +models/llm_tools.py +models/ollama_model_metadata.py +models/ollama_model_status.py +models/pptx_models.py +models/presentation_and_path.py +models/presentation_from_template.py +models/presentation_layout.py +models/presentation_outline_model.py +models/presentation_structure_model.py +models/presentation_with_slides.py +models/slide_layout_index.py +models/sse_response.py +models/theme_data.py +models/user_config.py +models/sql/async_presentation_generation_status.py +models/sql/image_asset.py +models/sql/key_value.py +models/sql/ollama_pull_status.py +models/sql/presentation.py +models/sql/presentation_layout_code.py +models/sql/slide.py +models/sql/template.py +models/sql/template_create_info.py +models/sql/webhook_subscription.py +presenton_backend.egg-info/PKG-INFO +presenton_backend.egg-info/SOURCES.txt +presenton_backend.egg-info/dependency_links.txt +presenton_backend.egg-info/requires.txt +presenton_backend.egg-info/top_level.txt +services/__init__.py +services/codex_llm.py +services/concurrent_service.py +services/database.py +services/document_conversion_service.py +services/documents_loader.py +services/export_task_service.py +services/html_to_text_runs_service.py +services/icon_finder_service.py +services/image_generation_service.py +services/liteparse_service.py +services/llm_client.py +services/llm_tool_calls_handler.py +services/pptx_presentation_creator.py +services/score_based_chunker.py +services/temp_file_service.py +services/webhook_service.py +templates/__init__.py +templates/example.py +templates/font_utils.py +templates/get_layout_by_name.py +templates/handler.py +templates/presentation_layout.py +templates/preview.py +templates/prompts.py +templates/providers.py +templates/router.py +tests/test_gemini_schema_support.py +tests/test_image_generation.py +tests/test_mcp_server.py +tests/test_openai_schema_support.py +tests/test_pptx_creator.py +tests/test_pptx_slides_processing.py +tests/test_presentation_generation_api.py +tests/test_slide_to_html.py +utils/__init__.py +utils/asset_directory_utils.py +utils/async_iterator.py +utils/available_models.py +utils/datetime_utils.py +utils/db_utils.py +utils/dict_utils.py +utils/download_helpers.py +utils/dummy_functions.py +utils/error_handling.py +utils/export_utils.py +utils/file_utils.py +utils/get_dynamic_models.py +utils/get_env.py +utils/get_layout_by_name.py +utils/image_provider.py +utils/image_utils.py +utils/llm_client_error_handler.py +utils/llm_provider.py +utils/model_availability.py +utils/ocr_language.py +utils/ollama.py +utils/outline_utils.py +utils/parsers.py +utils/path_helpers.py +utils/ppt_utils.py +utils/process_slides.py +utils/schema_utils.py +utils/set_env.py +utils/theme_utils.py +utils/user_config.py +utils/validators.py +utils/llm_calls/edit_slide.py +utils/llm_calls/edit_slide_html.py +utils/llm_calls/generate_presentation_outlines.py +utils/llm_calls/generate_presentation_structure.py +utils/llm_calls/generate_slide_content.py +utils/llm_calls/select_slide_type_on_edit.py +utils/oauth/__init__.py +utils/oauth/openai_codex.py +utils/oauth/pkce.py \ No newline at end of file diff --git a/servers/fastapi/presenton_backend.egg-info/dependency_links.txt b/servers/fastapi/presenton_backend.egg-info/dependency_links.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/servers/fastapi/presenton_backend.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/servers/fastapi/presenton_backend.egg-info/requires.txt b/servers/fastapi/presenton_backend.egg-info/requires.txt new file mode 100644 index 00000000..e7bfb20e --- /dev/null +++ b/servers/fastapi/presenton_backend.egg-info/requires.txt @@ -0,0 +1,20 @@ +alembic>=1.14.0 +aiohttp>=3.12.15 +aiomysql>=0.2.0 +aiosqlite>=0.21.0 +anthropic>=0.60.0 +asyncpg>=0.30.0 +chromadb>=1.0.15 +dirtyjson>=1.0.8 +fastapi[standard]>=0.116.1 +fastembed-vectorstore>=0.5.2 +fastmcp>=2.11.0 +google-genai>=1.28.0 +nltk>=3.9.1 +openai>=1.98.0 +pathvalidate>=3.3.1 +pdfplumber>=0.11.7 +pytest>=8.4.1 +python-pptx>=1.0.2 +redis>=6.2.0 +sqlmodel>=0.0.24 diff --git a/servers/fastapi/presenton_backend.egg-info/top_level.txt b/servers/fastapi/presenton_backend.egg-info/top_level.txt new file mode 100644 index 00000000..4d22b399 --- /dev/null +++ b/servers/fastapi/presenton_backend.egg-info/top_level.txt @@ -0,0 +1,7 @@ +api +constants +enums +models +services +templates +utils diff --git a/servers/fastapi/pyproject.toml b/servers/fastapi/pyproject.toml index 9f6ce80b..9b34530d 100644 --- a/servers/fastapi/pyproject.toml +++ b/servers/fastapi/pyproject.toml @@ -16,7 +16,6 @@ dependencies = [ "asyncpg>=0.30.0", "chromadb>=1.0.15", "dirtyjson>=1.0.8", - "docling>=2.43.0", "fastapi[standard]>=0.116.1", "fastembed-vectorstore>=0.5.2", "fastmcp>=2.11.0", @@ -34,9 +33,6 @@ dependencies = [ [tool.uv] index-strategy = "unsafe-best-match" -[[tool.uv.index]] -url = "https://download.pytorch.org/whl/cpu" - [tool.setuptools.packages.find] where = ["."] include = ["api*", "enums*", "models*", "services*", "constants*", "utils*", "templates*"] diff --git a/servers/fastapi/server.py b/servers/fastapi/server.py index 597dff90..e5020889 100644 --- a/servers/fastapi/server.py +++ b/servers/fastapi/server.py @@ -14,11 +14,13 @@ if __name__ == "__main__": args = parser.parse_args() reload = args.reload == "true" host = "127.0.0.1" - os.environ["FASTAPI_PUBLIC_URL"] = f"http://{host}:{args.port}" + + # PPTX-to-HTML export and other in-process callers resolve `/app_data` assets here. + os.environ.setdefault("FASTAPI_PUBLIC_URL", f"http://{host}:{args.port}") uvicorn.run( "api.main:app", - host="127.0.0.1", + host=host, port=args.port, log_level="info", reload=reload, diff --git a/servers/fastapi/services/docling_service.py b/servers/fastapi/services/docling_service.py deleted file mode 100644 index f6ae203e..00000000 --- a/servers/fastapi/services/docling_service.py +++ /dev/null @@ -1,33 +0,0 @@ -from docling.document_converter import ( - DocumentConverter, - PdfFormatOption, - PowerpointFormatOption, - WordFormatOption, -) -from docling.datamodel.pipeline_options import PdfPipelineOptions -from docling.datamodel.base_models import InputFormat - - -class DoclingService: - def __init__(self): - self.pipeline_options = PdfPipelineOptions() - self.pipeline_options.do_ocr = False - - self.converter = DocumentConverter( - allowed_formats=[InputFormat.PPTX, InputFormat.PDF, InputFormat.DOCX], - format_options={ - InputFormat.DOCX: WordFormatOption( - pipeline_options=self.pipeline_options, - ), - InputFormat.PPTX: PowerpointFormatOption( - pipeline_options=self.pipeline_options, - ), - InputFormat.PDF: PdfFormatOption( - pipeline_options=self.pipeline_options, - ), - }, - ) - - def parse_to_markdown(self, file_path: str) -> str: - result = self.converter.convert(file_path) - return result.document.export_to_markdown() diff --git a/servers/fastapi/services/document_conversion_service.py b/servers/fastapi/services/document_conversion_service.py new file mode 100644 index 00000000..8de7ec7f --- /dev/null +++ b/servers/fastapi/services/document_conversion_service.py @@ -0,0 +1,235 @@ +import os +import subprocess +import logging +from pathlib import Path +from typing import Dict, List + + +class DocumentConversionError(Exception): + pass + + +LOGGER = logging.getLogger(__name__) +_LOG_SNIPPET_LIMIT = 600 + + +def _snippet(value: str, limit: int = _LOG_SNIPPET_LIMIT) -> str: + text = (value or "").strip() + if not text: + return "" + if len(text) <= limit: + return text + return f"{text[:limit]}... [truncated {len(text) - limit} chars]" + + +def _command_str(parts: list[str]) -> str: + return " ".join(repr(part) for part in parts) + + +def _windows_hidden_subprocess_kwargs() -> Dict[str, object]: + if os.name != "nt": + return {} + + startupinfo = subprocess.STARTUPINFO() + startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW + return { + "creationflags": getattr(subprocess, "CREATE_NO_WINDOW", 0), + "startupinfo": startupinfo, + } + + +class DocumentConversionService: + def __init__(self): + self.soffice_binary = self._resolve_soffice_binary() + self.imagemagick_binary = self._resolve_imagemagick_binary() + + @staticmethod + def _resolve_soffice_binary() -> str: + configured = (os.getenv("SOFFICE_PATH") or "").strip() + if configured: + return configured + return "soffice.exe" if os.name == "nt" else "soffice" + + @staticmethod + def _can_execute(command: str, args: List[str]) -> bool: + try: + result = subprocess.run( + [command, *args], + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + timeout=10, + check=False, + **_windows_hidden_subprocess_kwargs(), + ) + return result.returncode == 0 + except Exception: + return False + + def _resolve_imagemagick_binary(self) -> str: + configured = (os.getenv("IMAGEMAGICK_BINARY") or "").strip() + if configured: + return configured + + for candidate in ["magick", "convert"]: + if self._can_execute(candidate, ["-version"]): + return candidate + + return "magick" if os.name == "nt" else "convert" + + def convert_office_to_pdf( + self, + file_path: str, + output_dir: str, + timeout_seconds: int = 180, + ) -> str: + Path(output_dir).mkdir(parents=True, exist_ok=True) + + existing_pdfs = { + p.name for p in Path(output_dir).glob("*.pdf") if p.is_file() + } + + try: + command = [ + self.soffice_binary, + "--headless", + "--convert-to", + "pdf", + "--outdir", + output_dir, + file_path, + ] + LOGGER.info( + "[DocumentConversion] LibreOffice conversion start input=%s output_dir=%s", + file_path, + output_dir, + ) + subprocess.run( + command, + check=True, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + timeout=timeout_seconds, + **_windows_hidden_subprocess_kwargs(), + ) + LOGGER.info( + "[DocumentConversion] LibreOffice conversion complete input=%s", + file_path, + ) + except subprocess.TimeoutExpired as exc: + LOGGER.error( + "[DocumentConversion] LibreOffice timed out command=%s", + _command_str(exc.cmd if isinstance(exc.cmd, list) else [str(exc.cmd)]), + ) + raise DocumentConversionError( + f"LibreOffice conversion timed out for {os.path.basename(file_path)}" + ) from exc + except subprocess.CalledProcessError as exc: + stderr = (exc.stderr or "").strip() + stdout = (exc.stdout or "").strip() + details = stderr or stdout or str(exc) + LOGGER.error( + "[DocumentConversion] LibreOffice failed code=%s command=%s stderr=%s stdout=%s", + exc.returncode, + _command_str(exc.cmd if isinstance(exc.cmd, list) else [str(exc.cmd)]), + _snippet(stderr), + _snippet(stdout), + ) + raise DocumentConversionError( + f"LibreOffice conversion failed for {os.path.basename(file_path)}: {details} " + f"(stderr={_snippet(stderr)}; stdout={_snippet(stdout)})" + ) from exc + except Exception as exc: + LOGGER.exception("[DocumentConversion] LibreOffice conversion unexpected error") + raise DocumentConversionError( + f"LibreOffice conversion failed for {os.path.basename(file_path)}: {exc}" + ) from exc + + expected_pdf = Path(output_dir) / f"{Path(file_path).stem}.pdf" + if expected_pdf.is_file(): + return str(expected_pdf) + + generated_pdfs = [ + p + for p in Path(output_dir).glob("*.pdf") + if p.is_file() and p.name not in existing_pdfs + ] + if generated_pdfs: + newest = max(generated_pdfs, key=lambda p: p.stat().st_mtime) + return str(newest) + + raise DocumentConversionError( + f"LibreOffice did not create a PDF for {os.path.basename(file_path)}" + ) + + def convert_image_to_png( + self, + file_path: str, + output_dir: str, + timeout_seconds: int = 120, + ) -> str: + Path(output_dir).mkdir(parents=True, exist_ok=True) + + output_path = Path(output_dir) / f"{Path(file_path).stem}_converted.png" + + command = [self.imagemagick_binary, file_path, str(output_path)] + + try: + LOGGER.info( + "[DocumentConversion] ImageMagick conversion start input=%s output=%s command=%s", + file_path, + output_path, + _command_str(command), + ) + subprocess.run( + command, + check=True, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + timeout=timeout_seconds, + **_windows_hidden_subprocess_kwargs(), + ) + LOGGER.info( + "[DocumentConversion] ImageMagick conversion complete output=%s", + output_path, + ) + except subprocess.TimeoutExpired as exc: + LOGGER.error( + "[DocumentConversion] ImageMagick timed out command=%s", + _command_str(exc.cmd if isinstance(exc.cmd, list) else [str(exc.cmd)]), + ) + raise DocumentConversionError( + f"ImageMagick conversion timed out for {os.path.basename(file_path)}" + ) from exc + except subprocess.CalledProcessError as exc: + stderr = (exc.stderr or "").strip() + stdout = (exc.stdout or "").strip() + details = stderr or stdout or str(exc) + LOGGER.error( + "[DocumentConversion] ImageMagick failed code=%s command=%s stderr=%s stdout=%s", + exc.returncode, + _command_str(exc.cmd if isinstance(exc.cmd, list) else [str(exc.cmd)]), + _snippet(stderr), + _snippet(stdout), + ) + raise DocumentConversionError( + f"ImageMagick conversion failed for {os.path.basename(file_path)}: {details} " + f"(stderr={_snippet(stderr)}; stdout={_snippet(stdout)})" + ) from exc + except Exception as exc: + LOGGER.exception("[DocumentConversion] ImageMagick conversion unexpected error") + raise DocumentConversionError( + f"ImageMagick conversion failed for {os.path.basename(file_path)}: {exc}" + ) from exc + + if not output_path.is_file(): + raise DocumentConversionError( + f"ImageMagick did not create a PNG for {os.path.basename(file_path)}" + ) + + return str(output_path) diff --git a/servers/fastapi/services/documents_loader.py b/servers/fastapi/services/documents_loader.py index 548d19e1..63f84a0b 100644 --- a/servers/fastapi/services/documents_loader.py +++ b/servers/fastapi/services/documents_loader.py @@ -1,24 +1,52 @@ -import mimetypes -from fastapi import HTTPException -import os, asyncio -from typing import List, Optional, Tuple +import asyncio +import logging +import os +import tempfile +from pathlib import Path +from typing import Any, List, Optional, Tuple + import pdfplumber +from fastapi import HTTPException from constants.documents import ( - PDF_MIME_TYPES, - POWERPOINT_TYPES, - TEXT_MIME_TYPES, - WORD_TYPES, + IMAGE_EXTENSIONS, + OFFICE_EXTENSIONS, + PDF_EXTENSIONS, + TEXT_EXTENSIONS, ) -from services.docling_service import DoclingService +from services.document_conversion_service import ( + DocumentConversionError, + DocumentConversionService, +) +from services.liteparse_service import LiteParseError, LiteParseService +from utils.ocr_language import presentation_language_to_ocr_code + +# Optional fallback converter (primarily useful on Windows) +try: + from services.lightweight_document_service import DocumentService as DocumentServiceCls +except Exception: + DocumentServiceCls = None + +LOGGER = logging.getLogger(__name__) class DocumentsLoader: + DECOMPOSE_TIMEOUT_SECONDS = 600 - def __init__(self, file_paths: List[str]): + def __init__( + self, + file_paths: List[str], + presentation_language: Optional[str] = None, + ): self._file_paths = file_paths - - self.docling_service = DoclingService() + self._ocr_language = presentation_language_to_ocr_code(presentation_language) + self.liteparse_service = LiteParseService( + timeout_seconds=self.DECOMPOSE_TIMEOUT_SECONDS + ) + self.document_conversion_service = DocumentConversionService() + self.document_service: Any = ( + DocumentServiceCls() if DocumentServiceCls is not None else None + ) self._documents: List[str] = [] self._images: List[List[str]] = [] @@ -40,7 +68,7 @@ class DocumentsLoader: """If load_images is True, temp_dir must be provided""" documents: List[str] = [] - images: List[str] = [] + images: List[List[str]] = [] for file_path in self._file_paths: if not os.path.exists(file_path): @@ -49,19 +77,35 @@ class DocumentsLoader: ) document = "" - imgs = [] + imgs: List[str] = [] - mime_type = mimetypes.guess_type(file_path)[0] - if mime_type in PDF_MIME_TYPES: + extension = Path(file_path).suffix.lower() + LOGGER.info( + "[DocumentsLoader] Processing file=%s extension=%s", + file_path, + extension, + ) + + if extension in PDF_EXTENSIONS: document, imgs = await self.load_pdf( file_path, load_text, load_images, temp_dir ) - elif mime_type in TEXT_MIME_TYPES: + elif extension in TEXT_EXTENSIONS: document = await self.load_text(file_path) - elif mime_type in POWERPOINT_TYPES: - document = self.load_powerpoint(file_path) - elif mime_type in WORD_TYPES: - document = self.load_msword(file_path) + elif extension in OFFICE_EXTENSIONS: + document = await asyncio.to_thread( + self.load_office_document, + file_path, + temp_dir, + ) + elif extension in IMAGE_EXTENSIONS: + document = await asyncio.to_thread( + self.load_image, + file_path, + temp_dir, + ) + else: + document = await asyncio.to_thread(self._parse_with_liteparse, file_path) documents.append(document) images.append(imgs) @@ -76,26 +120,88 @@ class DocumentsLoader: load_images: bool, temp_dir: Optional[str] = None, ) -> Tuple[str, List[str]]: - image_paths = [] + image_paths: List[str] = [] document: str = "" if load_text: - document = self.docling_service.parse_to_markdown(file_path) + document = await asyncio.to_thread(self._parse_with_liteparse, file_path) if load_images: + if temp_dir is None: + raise HTTPException( + status_code=400, + detail="temp_dir is required when load_images is true", + ) image_paths = await self.get_page_images_from_pdf_async(file_path, temp_dir) return document, image_paths async def load_text(self, file_path: str) -> str: - with open(file_path, "r") as file: + with open(file_path, "r", encoding="utf-8") as file: return await asyncio.to_thread(file.read) - def load_msword(self, file_path: str) -> str: - return self.docling_service.parse_to_markdown(file_path) + def load_office_document(self, file_path: str, temp_dir: Optional[str] = None) -> str: + if temp_dir: + converted_path = self.document_conversion_service.convert_office_to_pdf( + file_path, + temp_dir, + timeout_seconds=self.DECOMPOSE_TIMEOUT_SECONDS, + ) + return self._parse_with_liteparse(converted_path) - def load_powerpoint(self, file_path: str) -> str: - return self.docling_service.parse_to_markdown(file_path) + with tempfile.TemporaryDirectory(prefix="office-convert-") as conversion_dir: + converted_path = self.document_conversion_service.convert_office_to_pdf( + file_path, + conversion_dir, + timeout_seconds=self.DECOMPOSE_TIMEOUT_SECONDS, + ) + return self._parse_with_liteparse(converted_path) + + def load_image(self, file_path: str, temp_dir: Optional[str] = None) -> str: + if temp_dir: + converted_path = self.document_conversion_service.convert_image_to_png( + file_path, + temp_dir, + timeout_seconds=self.DECOMPOSE_TIMEOUT_SECONDS, + ) + return self._parse_with_liteparse(converted_path) + + with tempfile.TemporaryDirectory(prefix="image-convert-") as conversion_dir: + converted_path = self.document_conversion_service.convert_image_to_png( + file_path, + conversion_dir, + timeout_seconds=self.DECOMPOSE_TIMEOUT_SECONDS, + ) + return self._parse_with_liteparse(converted_path) + + def _parse_with_liteparse(self, file_path: str) -> str: + try: + LOGGER.info("[DocumentsLoader] LiteParse start file=%s", file_path) + return self.liteparse_service.parse_to_markdown( + file_path, + ocr_enabled=True, + ocr_language=self._ocr_language, + ) + except (LiteParseError, DocumentConversionError) as exc: + LOGGER.warning( + "[DocumentsLoader] Primary parse failed file=%s error=%s", + file_path, + exc, + ) + if self.document_service is not None: + try: + LOGGER.info("[DocumentsLoader] Trying fallback parser file=%s", file_path) + return self.document_service.parse_to_markdown(file_path) + except Exception: + LOGGER.exception( + "[DocumentsLoader] Fallback parser failed file=%s", + file_path, + ) + pass + raise HTTPException( + status_code=500, + detail=f"Failed to parse document {os.path.basename(file_path)}: {exc}", + ) from exc @classmethod def get_page_images_from_pdf(cls, file_path: str, temp_dir: str) -> List[str]: diff --git a/servers/fastapi/services/export_task_service.py b/servers/fastapi/services/export_task_service.py new file mode 100644 index 00000000..c975a4d3 --- /dev/null +++ b/servers/fastapi/services/export_task_service.py @@ -0,0 +1,242 @@ +import asyncio +import json +import os +import shutil +import subprocess +import tempfile +import uuid +from typing import Mapping + +from fastapi import HTTPException +from pydantic import BaseModel + +from services.liteparse_service import _snippet, _subprocess_text_kwargs +from utils.asset_directory_utils import resolve_app_path_to_filesystem +from utils.get_env import get_app_data_directory_env, get_temp_directory_env + + +class PptxToHtmlDocument(BaseModel): + slides: list[str] + font_css: str = "" + width: float + height: float + images_dir: str + fonts_dir: str + + +class ExportTaskService: + def __init__(self, timeout_seconds: int = 300): + self.timeout_seconds = timeout_seconds + self.node_binary = os.getenv("LITEPARSE_NODE_BINARY", "node") + self.export_dir = self._resolve_export_dir() + self.entrypoint_path = os.path.join(self.export_dir, "index.js") + self.converter_path = self._resolve_converter_path(self.export_dir) + + @staticmethod + def _resolve_export_dir() -> str: + configured = (os.getenv("EXPORT_RUNTIME_DIR") or "").strip() + if configured: + return configured + + cwd = os.path.abspath(".") + service_dir = os.path.dirname(__file__) + candidates = [ + os.path.abspath(os.path.join(cwd, "..", "..", "resources", "export")), + os.path.abspath(os.path.join(cwd, "..", "export")), + os.path.abspath( + os.path.join(service_dir, "..", "..", "..", "resources", "export") + ), + os.path.abspath(os.path.join(service_dir, "..", "..", "export")), + os.path.abspath( + os.path.join(cwd, "..", "..", "electron", "resources", "export") + ), + os.path.abspath( + os.path.join( + service_dir, "..", "..", "..", "..", "electron", "resources", "export" + ) + ), + ] + + for candidate in candidates: + if os.path.isfile(os.path.join(candidate, "index.js")): + return candidate + + return candidates[0] + + @staticmethod + def _resolve_converter_path(export_dir: str) -> str: + py_dir = os.path.join(export_dir, "py") + extension = ".exe" if os.name == "nt" else "" + platform_name = sys_platform() + arch_name = sys_arch() + candidates = [ + os.path.join(py_dir, f"convert-{platform_name}-{arch_name}{extension}"), + os.path.join(py_dir, f"convert-{platform_name}{extension}"), + os.path.join(py_dir, f"convert{extension}"), + os.path.join(py_dir, "convert"), + ] + for candidate in candidates: + if candidate and os.path.isfile(candidate): + return candidate + return candidates[1] + + def _build_node_env(self) -> Mapping[str, str]: + env = os.environ.copy() + binary_name = os.path.basename(self.node_binary).lower() + if binary_name not in {"node", "node.exe"}: + env.setdefault("ELECTRON_RUN_AS_NODE", "1") + + app_data_directory = get_app_data_directory_env() + if not app_data_directory: + raise HTTPException( + status_code=500, + detail="APP_DATA_DIRECTORY must be set for PPTX-to-HTML export", + ) + env["APP_DATA_DIRECTORY"] = app_data_directory + + temp_directory = get_temp_directory_env() or os.path.join( + tempfile.gettempdir(), "presenton" + ) + os.makedirs(temp_directory, exist_ok=True) + env["TEMP_DIRECTORY"] = temp_directory + + fastapi_public_url = (os.getenv("FASTAPI_PUBLIC_URL") or "").strip() + if not fastapi_public_url: + raise HTTPException( + status_code=500, + detail="FASTAPI_PUBLIC_URL must be set for PPTX-to-HTML export", + ) + env["ASSETS_BASE_URL"] = f"{fastapi_public_url.rstrip('/')}/app_data" + env["BUILT_PYTHON_MODULE_PATH"] = self.converter_path + + return env + + def _ensure_runtime_ready(self) -> None: + if not os.path.isfile(self.entrypoint_path): + raise HTTPException( + status_code=500, + detail=f"Export runtime not found at {self.entrypoint_path}", + ) + if not os.path.isfile(self.converter_path): + raise HTTPException( + status_code=500, + detail=f"Export converter binary not found at {self.converter_path}", + ) + + @staticmethod + def _resolve_output_path(response_data: dict) -> str: + path_value = response_data.get("path") + if isinstance(path_value, str): + resolved = resolve_app_path_to_filesystem(path_value) or path_value + if os.path.isfile(resolved): + return resolved + + url_value = response_data.get("url") + if isinstance(url_value, str): + resolved = resolve_app_path_to_filesystem(url_value) + if resolved and os.path.isfile(resolved): + return resolved + + raise HTTPException( + status_code=500, + detail="PPTX-to-HTML task completed without a valid output path", + ) + + async def convert_pptx_to_html( + self, pptx_path: str, get_fonts: bool = False + ) -> PptxToHtmlDocument: + self._ensure_runtime_ready() + if not os.path.isfile(pptx_path): + raise HTTPException(status_code=400, detail=f"PPTX not found: {pptx_path}") + + temp_root = get_temp_directory_env() or os.path.join(tempfile.gettempdir(), "presenton") + os.makedirs(temp_root, exist_ok=True) + temp_dir = tempfile.mkdtemp(prefix="export-task-", dir=temp_root) + task_path = os.path.join(temp_dir, "export_task.json") + response_path = os.path.join(temp_dir, "export_task.response.json") + + try: + with open(task_path, "w", encoding="utf-8") as task_file: + json.dump( + { + "type": "pptx-to-html", + "pptx_path": pptx_path, + "get_fonts": get_fonts, + }, + task_file, + ) + + result = await asyncio.to_thread( + subprocess.run, + [self.node_binary, self.entrypoint_path, task_path], + cwd=self.export_dir, + capture_output=True, + timeout=self.timeout_seconds, + env=dict(self._build_node_env()), + **_subprocess_text_kwargs(), + ) + + if result.returncode != 0: + raise HTTPException( + status_code=500, + detail=( + "PPTX-to-HTML export task failed. " + f"stderr={_snippet(result.stderr)} stdout={_snippet(result.stdout)}" + ), + ) + + if not os.path.isfile(response_path): + raise HTTPException( + status_code=500, + detail="PPTX-to-HTML export task did not produce a response file", + ) + + with open(response_path, "r", encoding="utf-8") as response_file: + response_data = json.load(response_file) + + output_path = self._resolve_output_path(response_data) + with open(output_path, "r", encoding="utf-8") as output_file: + output_data = json.load(output_file) + + return PptxToHtmlDocument(**output_data) + except subprocess.TimeoutExpired as exc: + raise HTTPException( + status_code=500, + detail=f"PPTX-to-HTML export timed out after {self.timeout_seconds} seconds", + ) from exc + except json.JSONDecodeError as exc: + raise HTTPException( + status_code=500, + detail="PPTX-to-HTML export produced invalid JSON output", + ) from exc + except OSError as exc: + raise HTTPException( + status_code=500, + detail=f"Failed to run PPTX-to-HTML export task: {exc}", + ) from exc + finally: + shutil.rmtree(temp_dir, ignore_errors=True) + + +def sys_platform() -> str: + if os.name == "nt": + return "win32" + return os.sys.platform + + +def sys_arch() -> str: + machine = (os.environ.get("PROCESSOR_ARCHITECTURE") or "").lower() + if not machine and hasattr(os, "uname"): + machine = os.uname().machine.lower() + + arch_map = { + "x86_64": "x64", + "amd64": "x64", + "x64": "x64", + "aarch64": "arm64", + "arm64": "arm64", + } + return arch_map.get(machine, machine or "x64") + + +EXPORT_TASK_SERVICE = ExportTaskService() diff --git a/servers/fastapi/services/image_generation_service.py b/servers/fastapi/services/image_generation_service.py index 00d4ecef..6b47d426 100644 --- a/servers/fastapi/services/image_generation_service.py +++ b/servers/fastapi/services/image_generation_service.py @@ -12,7 +12,6 @@ from models.sql.image_asset import ImageAsset from utils.get_env import ( get_dall_e_3_quality_env, get_gpt_image_1_5_quality_env, - get_next_public_fast_api_env, get_pexels_api_key_env, ) from utils.get_env import get_pixabay_api_key_env @@ -60,17 +59,6 @@ class ImageGenerationService: def is_stock_provider_selected(self): return is_pixels_selected() or is_pixabay_selected() - def _to_frontend_url(self, path: str) -> str: - if path.startswith("http://") or path.startswith("https://"): - return path - - fastapi_origin = (get_next_public_fast_api_env() or "").strip() - if not fastapi_origin: - return path - - normalized_path = path if path.startswith("/") else f"/{path}" - return f"{fastapi_origin.rstrip('/')}{normalized_path}" - async def generate_image(self, prompt: ImagePrompt) -> str | ImageAsset: """ Generates an image based on the provided prompt. @@ -81,11 +69,11 @@ class ImageGenerationService: """ if self.is_image_generation_disabled: print("Image generation is disabled. Using placeholder image.") - return self._to_frontend_url("/static/images/placeholder.jpg") + return "/static/images/placeholder.jpg" if not self.image_gen_func: print("No image generation function found. Using placeholder image.") - return self._to_frontend_url("/static/images/placeholder.jpg") + return "/static/images/placeholder.jpg" image_prompt = prompt.get_image_prompt( with_theme=not self.is_stock_provider_selected() @@ -114,12 +102,12 @@ class ImageGenerationService: elif image_path.startswith("/app_data/") or image_path.startswith( "/static/" ): - return self._to_frontend_url(image_path) + return image_path raise Exception(f"Image not found at {image_path}") except Exception as e: print(f"Error generating image: {e}") - return self._to_frontend_url("/static/images/placeholder.jpg") + return "/static/images/placeholder.jpg" async def generate_image_openai( self, prompt: str, output_directory: str, model: str, quality: str diff --git a/servers/fastapi/services/liteparse_service.py b/servers/fastapi/services/liteparse_service.py new file mode 100644 index 00000000..dca0835d --- /dev/null +++ b/servers/fastapi/services/liteparse_service.py @@ -0,0 +1,309 @@ +import json +import logging +import os +import subprocess +from typing import Any, Dict, Mapping, Tuple + + +class LiteParseError(Exception): + pass + + +LOGGER = logging.getLogger(__name__) +_LOG_SNIPPET_LIMIT = 600 + + +def _snippet(value: str, limit: int = _LOG_SNIPPET_LIMIT) -> str: + text = (value or "").strip() + if not text: + return "" + if len(text) <= limit: + return text + return f"{text[:limit]}... [truncated {len(text) - limit} chars]" + + +def _command_str(parts: list[str]) -> str: + return " ".join(json.dumps(part) for part in parts) + + +def _subprocess_text_kwargs() -> Mapping[str, object]: + """Decode subprocess output consistently across platforms. + + Windows defaults to a locale-dependent code page (often cp1252), which can + crash while decoding UTF-8 output from Node tools. Use UTF-8 and replace + undecodable bytes to keep parsing resilient. + """ + return {"text": True, "encoding": "utf-8", "errors": "replace"} + + +class LiteParseService: + def __init__(self, timeout_seconds: int = 180): + self.timeout_seconds = timeout_seconds + self.node_binary = os.getenv("LITEPARSE_NODE_BINARY", "node") + self.runner_path = os.getenv("LITEPARSE_RUNNER_PATH", self._resolve_runner_path()) + self.runner_dir = os.path.dirname(self.runner_path) + self._npm_project_root = self._resolve_npm_project_root() + + def _build_node_env(self) -> Dict[str, str]: + """Build environment for Node subprocesses. + + When the configured runtime binary is not the canonical `node` executable + (for example Electron's app binary), force Node-compatible mode. + """ + env = os.environ.copy() + binary_name = os.path.basename(self.node_binary).lower() + if binary_name not in {"node", "node.exe"}: + env.setdefault("ELECTRON_RUN_AS_NODE", "1") + + # LiteParse checks ImageMagick availability with `which magick`. + # On macOS app launches, PATH often excludes Homebrew bins, even when + # IMAGEMAGICK_BINARY is configured to an absolute executable path. + path_entries = [p for p in (env.get("PATH") or "").split(os.pathsep) if p] + additional_entries = [] + + imagemagick_binary = (env.get("IMAGEMAGICK_BINARY") or "").strip() + if imagemagick_binary: + magick_dir = os.path.dirname(imagemagick_binary) + if magick_dir: + additional_entries.append(magick_dir) + + soffice_binary = (env.get("SOFFICE_PATH") or "").strip() + if soffice_binary: + soffice_dir = os.path.dirname(soffice_binary) + if soffice_dir: + additional_entries.append(soffice_dir) + + if os.name != "nt": + additional_entries.extend([ + "/opt/homebrew/bin", + "/usr/local/bin", + "/opt/local/bin", + "/usr/bin", + "/bin", + ]) + + deduped_additional_entries = [] + for entry in additional_entries: + normalized = entry.strip() + if not normalized or not os.path.isdir(normalized): + continue + if normalized in path_entries or normalized in deduped_additional_entries: + continue + deduped_additional_entries.append(normalized) + + if deduped_additional_entries: + env["PATH"] = os.pathsep.join(deduped_additional_entries + path_entries) + + return env + + def _resolve_npm_project_root(self) -> str: + """Directory whose node_modules contains @llamaindex/liteparse (runner dir or Electron app root).""" + local_nm = os.path.join( + self.runner_dir, "node_modules", "@llamaindex", "liteparse" + ) + if os.path.isdir(local_nm): + return self.runner_dir + electron_nm = os.path.abspath( + os.path.join(self.runner_dir, "..", "..", "node_modules", "@llamaindex", "liteparse") + ) + if os.path.isdir(electron_nm): + return os.path.abspath(os.path.join(self.runner_dir, "..", "..")) + return os.path.abspath(os.path.join(self.runner_dir, "..", "..")) + + @staticmethod + def _resolve_runner_path() -> str: + cwd = os.path.abspath(".") + candidates = [ + # electron/servers/fastapi → electron/resources/... + os.path.abspath( + os.path.join( + cwd, "..", "..", "resources", "document-extraction", "liteparse_runner.mjs" + ) + ), + # servers/fastapi (repo root layout) → electron/resources/... + os.path.abspath( + os.path.join( + cwd, + "..", + "..", + "electron", + "resources", + "document-extraction", + "liteparse_runner.mjs", + ) + ), + # PyInstaller bundle layout + os.path.abspath( + os.path.join( + cwd, "..", "..", "app", "resources", "document-extraction", "liteparse_runner.mjs" + ) + ), + # Docker / explicit layout + "/app/document-extraction-liteparse/liteparse_runner.mjs", + ] + for path in candidates: + if os.path.isfile(path): + return path + return candidates[0] + + def check_runtime_ready(self) -> Tuple[bool, str]: + if not os.path.isfile(self.runner_path): + return False, f"LiteParse runner not found at: {self.runner_path}" + + try: + subprocess.run( + [self.node_binary, "--version"], + cwd=self.runner_dir, + check=True, + capture_output=True, + timeout=10, + env=self._build_node_env(), + **_subprocess_text_kwargs(), + ) + except Exception as exc: + return False, f"Node.js runtime is unavailable: {exc}" + + liteparse_dir = os.path.join( + self._npm_project_root, "node_modules", "@llamaindex", "liteparse" + ) + if not os.path.isdir(liteparse_dir): + return ( + False, + f"LiteParse npm package missing at {liteparse_dir}. Run npm install in the Electron app directory.", + ) + + # @llamaindex/liteparse is ESM-only; require.resolve() fails. Use dynamic import. + try: + subprocess.run( + [ + self.node_binary, + "--input-type=module", + "-e", + "import '@llamaindex/liteparse'", + ], + cwd=self._npm_project_root, + check=True, + capture_output=True, + timeout=20, + env=self._build_node_env(), + **_subprocess_text_kwargs(), + ) + except Exception as exc: + return False, f"LiteParse dependency is unavailable: {exc}" + + return True, "ok" + + def parse_to_markdown( + self, + file_path: str, + ocr_enabled: bool = True, + ocr_language: str = "eng", + ) -> str: + result = self.parse( + file_path=file_path, + ocr_enabled=ocr_enabled, + ocr_language=ocr_language, + ) + return str(result.get("text") or "") + + def parse( + self, + file_path: str, + ocr_enabled: bool = True, + ocr_language: str = "eng", + ) -> Dict[str, Any]: + is_ready, reason = self.check_runtime_ready() + if not is_ready: + raise LiteParseError(reason) + + command = [ + self.node_binary, + self.runner_path, + "--file", + file_path, + "--ocr-enabled", + "true" if ocr_enabled else "false", + "--ocr-language", + ocr_language, + ] + ocr_server = (os.getenv("LITEPARSE_OCR_SERVER_URL") or "").strip() + if ocr_server: + command.extend(["--ocr-server-url", ocr_server]) + tessdata = (os.getenv("LITEPARSE_TESSDATA_PATH") or "").strip() + if tessdata: + command.extend(["--tessdata-path", tessdata]) + + LOGGER.info( + "[LiteParse] Parsing file=%s ocr_enabled=%s ocr_language=%s", + file_path, + ocr_enabled, + ocr_language, + ) + + process = subprocess.run( + command, + cwd=self._npm_project_root, + capture_output=True, + timeout=self.timeout_seconds, + env=self._build_node_env(), + **_subprocess_text_kwargs(), + ) + LOGGER.info( + "[LiteParse] Command finished returncode=%s command=%s", + process.returncode, + _command_str(command), + ) + + payload: Dict[str, Any] + try: + payload = self._decode_runner_output(process.stdout) + except LiteParseError as exc: + raise LiteParseError( + f"{exc}; returncode={process.returncode}; " + f"stderr={_snippet(process.stderr)}; stdout={_snippet(process.stdout)}" + ) from exc + + if process.returncode != 0: + message = payload.get("error") or process.stderr.strip() or "Unknown error" + LOGGER.error( + "[LiteParse] Parse failed returncode=%s stderr=%s stdout=%s", + process.returncode, + _snippet(process.stderr), + _snippet(process.stdout), + ) + raise LiteParseError(message) + + if not payload.get("ok"): + LOGGER.error( + "[LiteParse] Runner returned not-ok payload=%s", + _snippet(json.dumps(payload)), + ) + raise LiteParseError(payload.get("error") or "LiteParse parse failed") + + return payload + + @staticmethod + def _decode_runner_output(stdout: str) -> Dict[str, Any]: + raw = (stdout or "").lstrip("\ufeff").strip() + if not raw: + raise LiteParseError("LiteParse runner returned empty output") + + # Prefer the last line that parses as JSON (handles stray log lines before our payload). + lines = [line.strip() for line in raw.splitlines() if line.strip()] + for line in reversed(lines): + try: + parsed = json.loads(line) + if isinstance(parsed, dict): + return parsed + except json.JSONDecodeError: + continue + + # Single blob without newlines (entire stdout is one JSON object). + try: + parsed = json.loads(raw) + if isinstance(parsed, dict): + return parsed + except json.JSONDecodeError: + pass + + raise LiteParseError("LiteParse runner returned invalid JSON output") diff --git a/servers/fastapi/templates/example.py b/servers/fastapi/templates/example.py new file mode 100644 index 00000000..1658e56c --- /dev/null +++ b/servers/fastapi/templates/example.py @@ -0,0 +1,98 @@ +from typing import Any + +from templates.presentation_layout import PresentationLayoutModel + +PLACEHOLDER_IMAGE_URL = "/static/images/replaceable_template_image.png" +PLACEHOLDER_ICON_URL = "/static/icons/placeholder.svg" + + +def build_schema_example(schema: dict) -> Any: + if not isinstance(schema, dict): + return None + + if "default" in schema: + return schema["default"] + + for key in ("anyOf", "oneOf", "allOf"): + options = schema.get(key) + if isinstance(options, list): + for option in options: + example = build_schema_example(option) + if example is not None: + return example + + enum_values = schema.get("enum") + if enum_values: + return enum_values[0] + + schema_type = schema.get("type") + if schema_type == "object": + properties = schema.get("properties", {}) + result = {} + for field_name, field_schema in properties.items(): + result[field_name] = build_schema_example(field_schema) + return result + + if schema_type == "array": + items_schema = schema.get("items", {}) + if "default" in schema: + return schema["default"] + item_example = build_schema_example(items_schema) + return [] if item_example is None else [item_example] + + if schema_type == "string": + schema_description = (schema.get("description") or "").lower() + if "icon" in schema_description: + return PLACEHOLDER_ICON_URL + if "image" in schema_description or "url" in schema_description: + return PLACEHOLDER_IMAGE_URL + return "Sample text" + + if schema_type == "integer": + return schema.get("minimum", 1) + + if schema_type == "number": + return schema.get("minimum", 1) + + if schema_type == "boolean": + return False + + return None + + +def replace_special_placeholders(value: Any) -> Any: + if isinstance(value, dict): + result = {} + for key, child in value.items(): + if key == "__image_url__": + result[key] = PLACEHOLDER_IMAGE_URL + elif key == "__icon_url__": + result[key] = PLACEHOLDER_ICON_URL + else: + result[key] = replace_special_placeholders(child) + return result + + if isinstance(value, list): + return [replace_special_placeholders(item) for item in value] + + if value == "__image_url__": + return PLACEHOLDER_IMAGE_URL + if value == "__icon_url__": + return PLACEHOLDER_ICON_URL + return value + + +def build_template_example( + template_id: str, layout: PresentationLayoutModel +) -> dict[str, Any]: + slides = [] + for slide in layout.slides: + example_content = replace_special_placeholders( + build_schema_example(slide.json_schema) + ) + slides.append({"layout": slide.id, "content": example_content}) + + return { + "template": template_id, + "slides": slides, + } diff --git a/servers/fastapi/templates/get_layout_by_name.py b/servers/fastapi/templates/get_layout_by_name.py new file mode 100644 index 00000000..f69251ff --- /dev/null +++ b/servers/fastapi/templates/get_layout_by_name.py @@ -0,0 +1,18 @@ +import aiohttp +from fastapi import HTTPException + +from templates.presentation_layout import PresentationLayoutModel + + +async def get_layout_by_name(layout_name: str) -> PresentationLayoutModel: + url = f"http://localhost/api/template?group={layout_name}" + async with aiohttp.ClientSession() as session: + async with session.get(url) as response: + if response.status != 200: + error_text = await response.text() + raise HTTPException( + status_code=404, + detail=f"Template '{layout_name}' not found: {error_text}", + ) + layout_json = await response.json() + return PresentationLayoutModel(**layout_json) diff --git a/servers/fastapi/templates/handler.py b/servers/fastapi/templates/handler.py new file mode 100644 index 00000000..d6cf9dc4 --- /dev/null +++ b/servers/fastapi/templates/handler.py @@ -0,0 +1,707 @@ +import os +import random +import re +import uuid +from datetime import datetime +from typing import Any, List, Optional + +import aiohttp +from fastapi import Body, Depends, File, Form, HTTPException, Path, Query, UploadFile +from pydantic import BaseModel +from sqlalchemy import func +from sqlalchemy.ext.asyncio import AsyncSession +from sqlmodel import delete, select + +from constants.presentation import DEFAULT_TEMPLATES +from models.sql.presentation_layout_code import PresentationLayoutCodeModel +from models.sql.template import TemplateModel +from models.sql.template_create_info import TemplateCreateInfoModel +from services.database import get_async_session +from services.export_task_service import EXPORT_TASK_SERVICE +from templates.example import build_template_example +from templates.get_layout_by_name import get_layout_by_name +from templates.presentation_layout import PresentationLayoutModel +from templates.preview import ( + FontsUploadAndSlidesPreviewResponse, + upload_fonts_and_slides_preview_handler, +) +from templates.prompts import ( + SLIDE_LAYOUT_CREATION_SYSTEM_PROMPT, + SLIDE_LAYOUT_EDIT_SECTION_SYSTEM_PROMPT, + SLIDE_LAYOUT_EDIT_SYSTEM_PROMPT, +) +from templates.providers import edit_slide_layout_code, generate_slide_layout_code +from utils.asset_directory_utils import ( + resolve_app_path_to_filesystem, + resolve_image_path_to_filesystem, +) + + +class TemplateDetail(BaseModel): + id: str + name: str + total_layouts: Optional[int] = None + + +class TemplateLayoutData(BaseModel): + template: uuid.UUID + layout_id: str + layout_name: str + layout_code: str + fonts: Optional[Any] = None + + +class TemplateData(BaseModel): + id: uuid.UUID + init_id: Optional[uuid.UUID] = None + name: str + description: Optional[str] = None + created_at: datetime + + +class GetTemplateLayoutsResponse(BaseModel): + layouts: list[TemplateLayoutData] + template: Optional[TemplateData] = None + fonts: Optional[Any] = None + + +class TemplateExample(BaseModel): + template: str + slides: List[dict] + + +class CreateTemplateInitRequest(BaseModel): + pptx_url: str + slide_image_urls: List[str] + fonts: dict = {} + + +class CreateSlideLayoutRequest(BaseModel): + id: uuid.UUID + index: int + + +class CreateSlideLayoutResponse(BaseModel): + react_component: str + + +class EditSlideLayoutRequest(BaseModel): + react_component: str + prompt: str + + +class EditSlideLayoutResponse(CreateSlideLayoutResponse): + pass + + +class EditSlideLayoutSectionRequest(BaseModel): + react_component: str + section: str + prompt: str + + +class EditSlideLayoutSectionResponse(CreateSlideLayoutResponse): + pass + + +class SaveTemplateLayoutData(BaseModel): + layout_id: str + layout_name: str + layout_code: str + + +class SaveTemplateRequest(BaseModel): + template_info_id: uuid.UUID + name: str + description: Optional[str] = None + layouts: List[SaveTemplateLayoutData] + + +class SaveTemplateResponse(BaseModel): + id: uuid.UUID + name: str + description: Optional[str] = None + created_at: datetime + + +class CloneTemplateRequest(BaseModel): + id: str + name: str + description: Optional[str] = None + + +class UpdateTemplateRequest(BaseModel): + id: uuid.UUID + layouts: List[SaveTemplateLayoutData] + + +class SaveSlideLayoutRequest(BaseModel): + template_id: uuid.UUID + layout_id: str + layout_code: str + + +class CloneSlideLayoutRequest(BaseModel): + template_id: str + layout_id: str + layout_name: Optional[str] = None + + +def _strip_code_fences(value: str) -> str: + return ( + value.replace("```tsx", "") + .replace("```typescript", "") + .replace("```ts", "") + .replace("```", "") + .strip() + ) + + +def _normalize_layout_code_for_create(code: str) -> str: + normalized = _strip_code_fences(code) + normalized = ( + normalized.replace("image_url", "__image_url__") + .replace("icon_url", "__icon_url__") + .replace("image_prompt", "__image_prompt__") + .replace("icon_query", "__icon_query__") + ) + + first_import_match = re.search(r"(?m)^\s*import\b", normalized) + if first_import_match: + normalized = normalized[first_import_match.start() :] + + first_export_match = re.search(r"(?m)^\s*export\b", normalized) + if first_export_match: + normalized = normalized[: first_export_match.start()] + + normalized = re.sub( + r"(?ms)^\s*(?:import|export)\b.*?;(?:\r?\n|$)", + "", + normalized, + ) + normalized = re.sub( + r"(?m)^\s*(?:import|export)\b.*(?:\r?\n|$)", + "", + normalized, + ) + normalized = normalized.strip() + normalized = re.sub( + r'(layoutId\s*=\s*["\'])([^"\']+)(["\'])', + lambda match: ( + match.group(0) + if re.search(r"-\d{4}$", match.group(2)) + else f"{match.group(1)}{match.group(2)}-{random.randint(1000, 9999)}{match.group(3)}" + ), + normalized, + ) + return normalized + + +def _update_layout_id_in_code(code: str) -> tuple[str, str]: + match = re.search(r'(layoutId\s*=\s*["\'])([^"\']+)(["\'])', code) + if not match: + raise HTTPException(status_code=400, detail="layoutId not found in layout code") + + current_id = match.group(2) + suffix = f"{random.randint(1000, 9999)}" + new_id = re.sub(r"-\d{4}$", f"-{suffix}", current_id) + if new_id == current_id: + new_id = f"{current_id}-{suffix}" + + new_code = re.sub( + r'(layoutId\s*=\s*["\'])([^"\']+)(["\'])', + f"\\1{new_id}\\3", + code, + count=1, + ) + return new_code, new_id + + +async def _download_image_bytes(image_url: str) -> bytes: + async with aiohttp.ClientSession() as session: + async with session.get(image_url) as response: + if response.status != 200: + raise HTTPException( + status_code=400, + detail=f"Failed to download slide image: {image_url}", + ) + return await response.read() + + +async def _read_image_bytes_and_media_type(image_url: str) -> tuple[bytes, str]: + actual_image_path = resolve_image_path_to_filesystem(image_url) + if actual_image_path and os.path.isfile(actual_image_path): + with open(actual_image_path, "rb") as image_file: + image_bytes = image_file.read() + file_extension = os.path.splitext(actual_image_path)[1].lower() + else: + image_bytes = await _download_image_bytes(image_url) + file_extension = os.path.splitext(image_url)[1].lower() + + media_type_map = { + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".gif": "image/gif", + ".webp": "image/webp", + } + return image_bytes, media_type_map.get(file_extension, "image/png") + + +async def get_all_templates( + include_defaults: bool = Query( + default=True, description="Whether to include default templates" + ), + sql_session: AsyncSession = Depends(get_async_session), +): + result = await sql_session.execute( + select( + TemplateModel.id, + TemplateModel.name, + func.count(PresentationLayoutCodeModel.id).label("total_layouts"), + ) + .join( + PresentationLayoutCodeModel, + PresentationLayoutCodeModel.presentation == TemplateModel.id, + ) + .group_by(TemplateModel.id, TemplateModel.name) + ) + rows = result.all() + + templates: list[TemplateDetail] = [] + if include_defaults: + templates.extend( + TemplateDetail(id=template, name=template) for template in DEFAULT_TEMPLATES + ) + + templates.extend( + TemplateDetail( + id=f"custom-{template_id}", + name=template_name, + total_layouts=total_layouts, + ) + for template_id, template_name, total_layouts in rows + ) + return templates + + +async def get_layouts( + template_id: str = Path(..., description="The id of the template"), + session: AsyncSession = Depends(get_async_session), +): + if not template_id or not template_id.strip(): + raise HTTPException(status_code=400, detail="Template ID cannot be empty") + + try: + cleaned_template_id = template_id.replace("custom-", "") + template_id_uuid = uuid.UUID(cleaned_template_id) + except Exception as exc: + raise HTTPException(status_code=400, detail="Invalid custom template ID") from exc + + result = await session.execute( + select(PresentationLayoutCodeModel).where( + PresentationLayoutCodeModel.presentation == template_id_uuid + ) + ) + layouts_db = result.scalars().all() + if not layouts_db: + raise HTTPException( + status_code=404, + detail=f"No layouts found for template ID: {template_id}", + ) + + template_meta = await session.get(TemplateModel, template_id_uuid) + template = None + if template_meta: + template = TemplateData( + id=template_id_uuid, + init_id=None, + name=template_meta.name, + description=template_meta.description, + created_at=template_meta.created_at, + ) + + layouts = [ + TemplateLayoutData( + template=template_id_uuid, + layout_id=layout.layout_id, + layout_name=layout.layout_name, + layout_code=layout.layout_code, + fonts=layout.fonts, + ) + for layout in layouts_db + ] + return GetTemplateLayoutsResponse( + layouts=layouts, + template=template, + fonts=layouts[0].fonts if layouts else None, + ) + + +async def get_template_by_id( + id: str = Path( + ..., + description=f"The id of the template, must be one of {', '.join(DEFAULT_TEMPLATES)} or your custom template", + ), + sql_session: AsyncSession = Depends(get_async_session), +): + if id.startswith("custom-"): + try: + template_id = uuid.UUID(id.replace("custom-", "")) + except Exception as exc: + raise HTTPException( + status_code=400, + detail="Template not found. Please use a valid template.", + ) from exc + + template = await sql_session.get(TemplateModel, template_id) + if not template: + raise HTTPException( + status_code=400, + detail="Template not found. Please use a valid template.", + ) + + return await get_layout_by_name(id) + + +async def get_template_example( + id: str = Path( + ..., + description=f"The id of the template, must be one of {', '.join(DEFAULT_TEMPLATES)} or your custom template", + ), + sql_session: AsyncSession = Depends(get_async_session), +): + template = await get_template_by_id(id=id, sql_session=sql_session) + return TemplateExample(**build_template_example(id, template)) + + +async def upload_fonts_and_slides_preview( + pptx_file: UploadFile = File(..., description="PPTX file to preview"), + font_files: Optional[List[UploadFile]] = File( + default=None, description="Font files to upload" + ), + original_font_names: Optional[List[str]] = Form(default=None), +): + return await upload_fonts_and_slides_preview_handler( + pptx_file=pptx_file, + font_files=font_files, + original_font_names=original_font_names, + max_slides=25, + ) + + +async def init_create_template( + request: CreateTemplateInitRequest, + sql_session: AsyncSession = Depends(get_async_session), +): + if not request.slide_image_urls: + raise HTTPException( + status_code=400, detail="At least one slide image is required" + ) + + pptx_path = resolve_app_path_to_filesystem(request.pptx_url) + if not pptx_path or not os.path.isfile(pptx_path): + raise HTTPException(status_code=400, detail="PPTX file not found") + + pptx_document = await EXPORT_TASK_SERVICE.convert_pptx_to_html( + pptx_path, get_fonts=False + ) + if not pptx_document.slides: + raise HTTPException( + status_code=500, + detail="PPTX-to-HTML export returned no slides", + ) + + if len(pptx_document.slides) < len(request.slide_image_urls): + raise HTTPException( + status_code=400, + detail=( + "PPTX-to-HTML export returned fewer slides than the preview images. " + f"Expected at least {len(request.slide_image_urls)}, got {len(pptx_document.slides)}." + ), + ) + + slide_htmls = pptx_document.slides[: len(request.slide_image_urls)] + template_create_info = TemplateCreateInfoModel( + fonts=request.fonts or {}, + pptx_url=request.pptx_url, + slide_image_urls=request.slide_image_urls, + slide_htmls=slide_htmls, + ) + sql_session.add(template_create_info) + await sql_session.commit() + await sql_session.refresh(template_create_info) + return template_create_info.id + + +async def create_slide_layout( + request: CreateSlideLayoutRequest = Body(...), + sql_session: AsyncSession = Depends(get_async_session), +): + template_info = await sql_session.get(TemplateCreateInfoModel, request.id) + if not template_info: + raise HTTPException(status_code=400, detail="Template not found") + + total_slides = len(template_info.slide_htmls) + if request.index < 0 or request.index >= total_slides: + raise HTTPException(status_code=400, detail="Invalid slide index") + + slide_html = template_info.slide_htmls[request.index] + slide_image_url = template_info.slide_image_urls[request.index] + image_bytes, media_type = await _read_image_bytes_and_media_type(slide_image_url) + + fonts_text = "" + if template_info.fonts: + font_names = [font.replace(" ", "_") for font in template_info.fonts.keys()] + fonts_text = "#PROVIDED FONTS\n- " + "\n- ".join(font_names) + + user_text = f"{fonts_text}\n\n#SLIDE HTML REFERENCE\n{slide_html}" + react_component = await generate_slide_layout_code( + system_prompt=SLIDE_LAYOUT_CREATION_SYSTEM_PROMPT, + user_text=user_text, + image_bytes=image_bytes, + media_type=media_type, + ) + normalized_react_component = _normalize_layout_code_for_create(react_component) + + return CreateSlideLayoutResponse(react_component=normalized_react_component) + + +async def edit_slide_layout( + request: EditSlideLayoutRequest, +): + user_text = f"#Prompt\n{request.prompt}\n\n#TSX code\n{request.react_component}" + react_component = await edit_slide_layout_code( + system_prompt=SLIDE_LAYOUT_EDIT_SYSTEM_PROMPT, + user_text=user_text, + ) + return EditSlideLayoutResponse(react_component=_strip_code_fences(react_component)) + + +async def edit_slide_layout_section( + request: EditSlideLayoutSectionRequest, +): + user_text = ( + f"#Prompt\n{request.prompt}\n\n" + f"#Section to make changes around\n{request.section}\n\n" + f"#TSX code\n{request.react_component}" + ) + react_component = await edit_slide_layout_code( + system_prompt=SLIDE_LAYOUT_EDIT_SECTION_SYSTEM_PROMPT, + user_text=user_text, + ) + return EditSlideLayoutSectionResponse( + react_component=_strip_code_fences(react_component) + ) + + +async def save_template( + request: SaveTemplateRequest, + sql_session: AsyncSession = Depends(get_async_session), +): + if not request.layouts: + raise HTTPException(status_code=400, detail="Layouts are required") + + template_info = await sql_session.get(TemplateCreateInfoModel, request.template_info_id) + if not template_info: + raise HTTPException(status_code=400, detail="Template info not found") + + template = TemplateModel( + id=uuid.uuid4(), + name=request.name, + description=request.description, + ) + sql_session.add(template) + + sql_session.add_all( + [ + PresentationLayoutCodeModel( + presentation=template.id, + layout_id=layout.layout_id, + layout_name=layout.layout_name, + layout_code=layout.layout_code, + fonts=template_info.fonts, + ) + for layout in request.layouts + ] + ) + await sql_session.commit() + await sql_session.refresh(template) + + return SaveTemplateResponse( + id=template.id, + name=template.name, + description=template.description, + created_at=template.created_at, + ) + + +async def clone_template( + request: CloneTemplateRequest = Body(...), + sql_session: AsyncSession = Depends(get_async_session), +): + if not request.id or not request.id.strip(): + raise HTTPException(status_code=400, detail="Template ID cannot be empty") + + try: + template_id_uuid = uuid.UUID(request.id.replace("custom-", "")) + except Exception as exc: + raise HTTPException(status_code=400, detail="Invalid custom template ID") from exc + + template = await sql_session.get(TemplateModel, template_id_uuid) + if not template: + raise HTTPException( + status_code=400, + detail="Template not found. Please use a valid template.", + ) + + result = await sql_session.execute( + select(PresentationLayoutCodeModel).where( + PresentationLayoutCodeModel.presentation == template_id_uuid + ) + ) + layouts_db = result.scalars().all() + if not layouts_db: + raise HTTPException(status_code=400, detail="No layouts found for template") + + new_template = TemplateModel( + id=uuid.uuid4(), + name=request.name, + description=template.description + if request.description is None + else request.description, + ) + sql_session.add(new_template) + + sql_session.add_all( + [ + PresentationLayoutCodeModel( + presentation=new_template.id, + layout_id=layout.layout_id, + layout_name=layout.layout_name, + layout_code=layout.layout_code, + fonts=layout.fonts, + ) + for layout in layouts_db + ] + ) + await sql_session.commit() + await sql_session.refresh(new_template) + + return SaveTemplateResponse( + id=new_template.id, + name=new_template.name, + description=new_template.description, + created_at=new_template.created_at, + ) + + +async def update_template( + request: UpdateTemplateRequest, + sql_session: AsyncSession = Depends(get_async_session), +): + if not request.layouts: + raise HTTPException(status_code=400, detail="Layouts are required") + + template = await sql_session.get(TemplateModel, request.id) + if not template: + raise HTTPException(status_code=400, detail="Template not found") + + existing_layout = await sql_session.scalar( + select(PresentationLayoutCodeModel).where( + PresentationLayoutCodeModel.presentation == request.id + ) + ) + fonts = existing_layout.fonts if existing_layout else None + + await sql_session.execute( + delete(PresentationLayoutCodeModel).where( + PresentationLayoutCodeModel.presentation == request.id + ) + ) + sql_session.add_all( + [ + PresentationLayoutCodeModel( + presentation=template.id, + layout_id=layout.layout_id, + layout_name=layout.layout_name, + layout_code=layout.layout_code, + fonts=fonts, + ) + for layout in request.layouts + ] + ) + await sql_session.commit() + + return SaveTemplateResponse( + id=template.id, + name=template.name, + description=template.description, + created_at=template.created_at, + ) + + +async def save_slide_layout( + request: SaveSlideLayoutRequest, + sql_session: AsyncSession = Depends(get_async_session), +): + template = await sql_session.get(TemplateModel, request.template_id) + if not template: + raise HTTPException(status_code=400, detail="Template not found") + + layout = await sql_session.scalar( + select(PresentationLayoutCodeModel).where( + PresentationLayoutCodeModel.presentation == request.template_id, + PresentationLayoutCodeModel.layout_id == request.layout_id, + ) + ) + if not layout: + raise HTTPException(status_code=400, detail="Layout not found") + + layout.layout_code = request.layout_code + sql_session.add(layout) + await sql_session.commit() + + +async def clone_slide_layout( + request: CloneSlideLayoutRequest = Body(...), + sql_session: AsyncSession = Depends(get_async_session), +): + if not request.template_id or not request.template_id.strip(): + raise HTTPException(status_code=400, detail="Template ID cannot be empty") + + try: + template_id_uuid = uuid.UUID(request.template_id.replace("custom-", "")) + except Exception as exc: + raise HTTPException(status_code=400, detail="Invalid custom template ID") from exc + + template = await sql_session.get(TemplateModel, template_id_uuid) + if not template: + raise HTTPException(status_code=400, detail="Template not found") + + layout = await sql_session.scalar( + select(PresentationLayoutCodeModel).where( + PresentationLayoutCodeModel.presentation == template_id_uuid, + PresentationLayoutCodeModel.layout_id == request.layout_id, + ) + ) + if not layout: + raise HTTPException(status_code=400, detail="Layout not found") + + new_layout_code, new_layout_id = _update_layout_id_in_code(layout.layout_code) + new_layout = PresentationLayoutCodeModel( + presentation=template_id_uuid, + layout_id=new_layout_id, + layout_name=request.layout_name or layout.layout_name, + layout_code=new_layout_code, + fonts=layout.fonts, + ) + sql_session.add(new_layout) + await sql_session.commit() + await sql_session.refresh(new_layout) + + return SaveTemplateLayoutData( + layout_id=new_layout.layout_id, + layout_name=new_layout.layout_name, + layout_code=new_layout.layout_code, + ) diff --git a/servers/fastapi/templates/presentation_layout.py b/servers/fastapi/templates/presentation_layout.py new file mode 100644 index 00000000..2bf9a4cb --- /dev/null +++ b/servers/fastapi/templates/presentation_layout.py @@ -0,0 +1,40 @@ +from typing import List, Optional + +from fastapi import HTTPException +from pydantic import BaseModel, Field + +from models.presentation_structure_model import PresentationStructureModel + + +class SlideLayoutModel(BaseModel): + id: str + name: Optional[str] = None + description: Optional[str] = None + json_schema: dict + + +class PresentationLayoutModel(BaseModel): + name: str + ordered: bool = Field(default=False) + slides: List[SlideLayoutModel] + + def get_slide_layout_index(self, slide_layout_id: str) -> int: + for index, slide in enumerate(self.slides): + if slide.id == slide_layout_id: + return index + raise HTTPException( + status_code=404, detail=f"Slide layout {slide_layout_id} not found" + ) + + def to_presentation_structure(self) -> PresentationStructureModel: + return PresentationStructureModel( + slides=[index for index in range(len(self.slides))] + ) + + def to_string(self) -> str: + message = "## Presentation Layout\n\n" + for index, slide in enumerate(self.slides): + message += f"### Slide Layout: {index}\n" + message += f"- Name: {slide.name or slide.json_schema.get('title')}\n" + message += f"- Description: {slide.description}\n\n" + return message diff --git a/servers/fastapi/templates/prompts.py b/servers/fastapi/templates/prompts.py new file mode 100644 index 00000000..e901e11f --- /dev/null +++ b/servers/fastapi/templates/prompts.py @@ -0,0 +1,220 @@ +SLIDE_LAYOUT_CREATION_SYSTEM_PROMPT = """ +You need to generate a Zod schema and a TSX React component and provide it as output. +Provide reusable TSX code which can be used as template to generate new slides with different content. + +# Steps: +1. Analyze the slide image to understand the visual hierarchy. +3. Classify elements into decorative and content elements. +4. Group content elements into logical sections like Header, Body, BulletPoints, etc. +5. Generate a Zod schema for the content elements. +6. Generate id, name and description for the layout. +6. Generate a TSX React component using the Zod schema and the HTML reference. + +# Decorative Elements: +- Arrows, Lines, Shapes, etc. +- Images with Grid patterns, background patterns, gradients, solid colors, etc. +- Background of infographics like funnel, timeline, etc. +- Company name, logos, etc. +- Images covering the entire slide. +- Images containing company name, logos, etc. + +# Decorative Elements Rules: +- Use them exactly as they are in the HTML reference. +- Do not change decorative images and icons urls. +- Images containing company name, logos, etc should be identified as decorative elements. + +# Content Elements: +- Title, Description, BulletPoints, etc. +- Graphs, Charts, etc. +- Images and Icons representing textual content like title, description, bullet points, etc. +- Meaningful Images and Icons. +- Icons in infographics that represent the data. + +# Content Elements Rules: +- Properly identify between images and icons elements. +- Image content: + - Image field should be 'z.object({"image_url": z.string(), "image_prompt": z.string().max(100)})' + - Replace actual image url with '/static/images/replaceable_template_image.png' +- Icon content: + - Icon field should be 'z.object({"icon_url": z.string(), "icon_query": z.string().max(30)})' + - Replace actual icon url with '/static/icons/placeholder.svg' + - Add color styling to the icon to match the color in the image. +- Make sure the urls are correct. + +# Layout Rules: +- The layout should be fixed 1280px width and 720px height. +- Adjust the positions and sizes of elements to fit the layout. +- Try to keep the positions and sizes of elements as close to HTML reference as possible. + +# Flexible Positioning and Sizes Rules: +- Must not use 'absolute' positioning for elements. +- Must use 'flex', 'grid', 'margin', 'padding', 'gap', 'basis', 'justify', 'align', etc for positioning of elements. +- For variable length lists, wrap list into a container and center it. +- Don't use specific sizes (height, width) for elements if not necessary. + +# Schema Field Name and Description Rules: +- Must not use content specific words. +- Only use words based on what content types are present in the slide image. +- Use words like 'title', description', 'heading', 'image', 'graph', 'table', 'bullet points', etc. +- Must not use words like 'budget', 'market', 'revenue', 'sales', 'growth', 'workflow', 'channel', 'plannedValue', 'actualValue', etc. + +# Layout ID, Name and Description Rules: +- Must only use slide structure to derive layout id, name and description. +- Informations like: Type of content, position of content, etc. should be used. +- layoutId example: title-description-right-image. +- layoutName example: Title Description Image. +- layoutDescription example: A slide with a title, description, and an image on right. + +# Zod Schema Rules: +- "describe" must be added for every fields. +- Add `.default(...)` to every top-level field directly inside the initial `z.object({ ... })` shape. +- Must not put a single `default` on the whole object like `const Schema = z.object({ ... }).default({ ... })`. +- Top level fields are those not nested inside other fields. +- Don't mention string type in schema like "url()", "email()", etc. +- Table must be object with "columns" and "rows" fields. +- "columns" must be an array of strings. +- "rows" must be an array of arrays of strings. +- Graph must be object with "categories" and "series" fields. +- "categories" must be an array of strings. +- "series" must be an array of objects with {"name": string, "data": array of numbers}. +- Must not use z.record() anywhere in the schema. + +# String and Array Field Rules: +- Every string field must include `.max(...)`; every array field must include `.max(...)`. +- For strings, set `max` to the exact character count of the text content it represents. +- For arrays, set `max` to the exact item count of the array content it represents. +- Choose a `max` that keeps the longest allowed content from overflowing its container. + +# Table Rules: +- Construct "tr -> th" by iterating over the "columns" field. +- Construct "tr -> td" by iterating over the "rows" field. +- Make sure table height and width adjusts to fit the content. + +# Grahps, Charts, etc Rules: +- Identify if graphs, charts, etc are present in the slide image. +- Identify the type of graph, chart, etc. +- If present, generate a zod schema for the graph, chart, etc. +- Generate TSX code for the graph, chart, etc. even if it is not present in the HTML reference. +- Use graph schema and image to generate the TSX code. +- Use Recharts library for graphs. + +# Fonts Rules: +- Check for "PROVIDED FONTS". +- Must use fonts only from "PROVIDED FONTS". +- Add "font-[\"font-name\"]" to every text element in the slide. + +# Page Number Rules: +- Identify if the slide contains page number from provided HTML reference and image. +- If page number is present, add a "page: z.number().min(1).meta({ description: "Page number" })" field in the schema. + +# React Component Rules: +- React component must be named dynamicSlideLayout. +- dynamicSlideLayout must take "{ data }: { data: Partial> }" as props. +- Wrap the code inside these classes: "relative w-full rounded-sm max-w-[1280px] shadow-lg max-h-[720px] aspect-video bg-white z-20 mx-auto overflow-hidden". +- Make sure camelCase is used for all styles. For e.g. "letter-spacing" should be "letterSpacing". +- Schema.parse must not be used in the code. +- Use 'const {field1, field2, ...} = data;' to access the data. +- field1 or field2 or ... can be undefined, so use optional chaining to access them. +- Don't use "min-height" on cards and instead make its height grow/shrink to fit the content. +- Make sure cards/items are centered vertically and horizontally in the available space. +- Make sure no element is scrollable. +- Don't add any animations, transitions, or effects. +- Make sure no content elements are overflowing the slide boundaries. + +# Import and Export Rules: +- All import statements must be defined at the top. +- Export using 'export {Schema, layoutId, layoutName, layoutDescription, dynamicSlideLayout}' statement at the bottom. +- There must be only one 'export' statement in the whole TSX code. + +# Output Code Rules: +- Code should be in following order: + - Zod Schema (Schema) + - Layout ID, Name and Description (layoutId, layoutName, layoutDescription) + - React Component (dynamicSlideLayout) +- Give just one valid TSX code as output. +- Don't add comments in the code. +- Make sure the generated code is valid TSX code. +- Give only code as output and nothing else. (no json, no markdown, no text, no explanation) + +- Go through generated code and make sure all rules are followed. +- Think as long as you can and iterate as many times as necessary to make sure all rules are followed. +""" + +SLIDE_LAYOUT_EDIT_SYSTEM_PROMPT = """ +You need to edit the given TSX code of the slide layout code according to the prompt and provide it as output. + +# Steps +1. Analyze the TSX code to understand the slide layout. +2. Analyze the prompt to understand the changes to be made. +3. Edit the TSX code according to the prompt. +4. Provide the updated TSX code as output. + +# Rules +- Make sure the changes does not break the existing code. +- Make sure to follow the pattern of the existing code. +- Make sure there are no unused schema fields after the changes are made. + +# Icons and Images Rules +Follow these rules if new icons/images are asked: +- Image field should be 'z.object({"image_url": z.string(), "image_prompt": z.string().max(100)})' +- Use this as default image url: '/static/images/replaceable_template_image.png' +- Icon field should be 'z.object({"icon_url": z.string(), "icon_query": z.string().max(30)})' +- Use this as default icon url: '/static/icons/placeholder.svg' + +# Schema Rules +- "describe" must be added for every fields. +- "default" must be added in top level fields of schema. +- Top level fields are those not nested inside other fields. +- Must set max for every string and array fields. +- Must set max to a number that will not cause overflow on max content. + +# Graphs And Table Rules +Follow these rules if new graphs/tables are asked: +1. Schema Rules +- Table must be object with "columns" and "rows" fields. +- "columns" must be an array of strings. +- "rows" must be an array of arrays of strings. +- Graph must be object with "categories" and "series" fields. +- "categories" must be an array of strings. +- "series" must be an array of objects with {"name": string, "data": array of numbers}. +2. React Component Rules +- Use recharts library for graphs. + +# Common Prompts +1. Fix the slide +- Check if text/cards/items is overflowing the slide boundaries or text/cards/items are overlapping. +- If yes, fix by moving the element to a better position or resizing the element. + +# Output Rules +- Make sure the schema and react component are valid. +- No matter what prompt is given, don't break the code. +- Provide only the updated TSX code as output and nothing else. (no json, no markdown, no text, no explanation) +""" + +SLIDE_LAYOUT_EDIT_SECTION_SYSTEM_PROMPT = """ +You need to edit the given TSX code of the slide layout code according to the prompt and provide it as output. + +# Steps +1. Analyze the TSX code to understand the slide layout. +2. Analyze the prompt to understand the changes to be made. +3. Edit the TSX code according to the prompt. +4. Provide the updated TSX code as output. + +# Rules +- Changes should be made only around the mentioned "section to make changes around". +- Make sure the changes does not break the existing code. +- Make sure to follow the pattern of the existing code. +- Make sure there are no unused schema fields after the changes are made. + +# Icons and Images Rules +Follow these rules if new icons/images are asked: +- Image field should be 'z.object({"image_url": z.string(), "image_prompt": z.string().max(100)})' +- Use this as default image url: '/static/images/replaceable_template_image.png' +- Icon field should be 'z.object({"icon_url": z.string(), "icon_query": z.string().max(30)})' +- Use this as default icon url: '/static/icons/placeholder.svg' + +# Output Rules +- Make sure the schema and react component are valid. +- No matter what prompt is given, don't break the code. +- Provide only the updated TSX code as output and nothing else. (no json, no markdown, no text, no explanation) +""" diff --git a/servers/fastapi/templates/providers.py b/servers/fastapi/templates/providers.py new file mode 100644 index 00000000..9e3a0ba8 --- /dev/null +++ b/servers/fastapi/templates/providers.py @@ -0,0 +1,425 @@ +import asyncio +import base64 +from dataclasses import dataclass +import time +from typing import Any, Awaitable, Callable, Optional + +from anthropic import AsyncAnthropic +from fastapi import HTTPException +from google import genai +from google.genai import types as google_types +from openai import AsyncOpenAI + +from enums.llm_provider import LLMProvider +from utils.get_env import ( + get_anthropic_api_key_env, + get_codex_access_token_env, + get_codex_account_id_env, + get_codex_refresh_token_env, + get_codex_token_expires_env, + get_google_api_key_env, + get_openai_api_key_env, +) +from utils.llm_provider import get_llm_provider, get_model +from utils.set_env import ( + set_codex_access_token_env, + set_codex_account_id_env, + set_codex_refresh_token_env, + set_codex_token_expires_env, +) + +MAX_ATTEMPTS_PER_PROVIDER = 4 + + +@dataclass(frozen=True) +class TemplateProviderSpec: + provider: LLMProvider + model: str + + +@dataclass(frozen=True) +class PlainLLMProvider: + name: str + call: Callable[[], Awaitable[str]] + +def get_template_provider_spec() -> TemplateProviderSpec: + provider = get_llm_provider() + if provider == LLMProvider.OPENAI: + return TemplateProviderSpec(provider=provider, model=get_model()) + if provider == LLMProvider.CODEX: + return TemplateProviderSpec(provider=provider, model=get_model()) + if provider == LLMProvider.GOOGLE: + return TemplateProviderSpec(provider=provider, model=get_model()) + if provider == LLMProvider.ANTHROPIC: + return TemplateProviderSpec(provider=provider, model=get_model()) + + raise HTTPException( + status_code=400, + detail="Template generation only supports OpenAI, Codex, Google, or Anthropic.", + ) + + +async def run_plain_provider_buckets(*, providers: list[PlainLLMProvider]) -> str: + last_exception: Optional[Exception] = None + + for provider in providers: + for attempt in range(1, MAX_ATTEMPTS_PER_PROVIDER + 1): + try: + response_text = await provider.call() + if response_text: + return response_text + raise ValueError("No output from template generation provider") + except Exception as exc: + last_exception = exc + + if isinstance(last_exception, HTTPException): + raise last_exception + raise HTTPException(status_code=500, detail="Failed to generate template output") + + +def _read_openai_response_text(response) -> str: + output_text = getattr(response, "output_text", None) + if output_text: + return output_text + text = getattr(response, "text", None) + if text: + return text + return "" + + +def _get_openai_client() -> AsyncOpenAI: + api_key = get_openai_api_key_env() + if not api_key: + raise HTTPException(status_code=400, detail="OPENAI_API_KEY is not set") + return AsyncOpenAI(api_key=api_key, timeout=120.0) + + +def _get_codex_headers() -> dict: + access_token = get_codex_access_token_env() + if not access_token: + raise HTTPException( + status_code=400, + detail="Codex OAuth access token is not set. Please authenticate via /api/v1/ppt/codex/auth/initiate", + ) + + expires_str = get_codex_token_expires_env() + if expires_str: + try: + expires_ms = int(expires_str) + now_ms = int(time.time() * 1000) + if now_ms >= expires_ms - 60_000: + refresh_token = get_codex_refresh_token_env() + if refresh_token: + from utils.oauth.openai_codex import ( + TokenSuccess, + get_account_id, + refresh_access_token, + ) + + result = refresh_access_token(refresh_token) + if isinstance(result, TokenSuccess): + set_codex_access_token_env(result.access) + set_codex_refresh_token_env(result.refresh) + set_codex_token_expires_env(str(result.expires)) + account_id = get_account_id(result.access) + if account_id: + set_codex_account_id_env(account_id) + access_token = result.access + except (TypeError, ValueError): + pass + + account_id = get_codex_account_id_env() or "" + return { + "Authorization": f"Bearer {access_token}", + "chatgpt-account-id": account_id, + "OpenAI-Beta": "responses=experimental", + "originator": "pi", + } + + +def _get_codex_client() -> AsyncOpenAI: + headers = _get_codex_headers() + access_token = (headers.get("Authorization") or "").replace("Bearer ", "").strip() + default_headers = { + key: value + for key, value in headers.items() + if key.lower() not in {"authorization", "content-type", "accept"} + } + return AsyncOpenAI( + base_url="https://chatgpt.com/backend-api/codex", + api_key=access_token or "codex", + default_headers=default_headers, + timeout=120.0, + ) + + +def _get_google_client() -> genai.Client: + api_key = get_google_api_key_env() + if not api_key: + raise HTTPException(status_code=400, detail="GOOGLE_API_KEY is not set") + return genai.Client(api_key=api_key) + + +def _get_anthropic_client() -> AsyncAnthropic: + api_key = get_anthropic_api_key_env() + if not api_key: + raise HTTPException(status_code=400, detail="ANTHROPIC_API_KEY is not set") + return AsyncAnthropic(api_key=api_key) + + +async def _call_openai_like( + *, + client: AsyncOpenAI, + model: str, + system_prompt: str, + user_text: str, + image_bytes: Optional[bytes] = None, + media_type: str = "image/png", +) -> str: + content = [{"type": "input_text", "text": user_text}] + if image_bytes: + content.insert( + 0, + { + "type": "input_image", + "image_url": f"data:{media_type};base64,{base64.b64encode(image_bytes).decode('utf-8')}", + }, + ) + + response = await client.responses.create( + model=model, + instructions=system_prompt, + input=[{"role": "user", "content": content}], + text={"verbosity": "medium"}, + store=False, + ) + output_text = _read_openai_response_text(response) + if not output_text: + raise HTTPException(status_code=500, detail="No output from template provider") + return output_text + + +def _response_event_to_dict(event: Any) -> dict: + if isinstance(event, dict): + return event + if hasattr(event, "model_dump"): + return event.model_dump() + return { + "type": getattr(event, "type", None), + "delta": getattr(event, "delta", None), + "text": getattr(event, "text", None), + "item": getattr(event, "item", None), + "response": getattr(event, "response", None), + "error": getattr(event, "error", None), + "message": getattr(event, "message", None), + } + + +async def _call_codex( + *, + model: str, + system_prompt: str, + user_text: str, + image_bytes: Optional[bytes] = None, + media_type: str = "image/png", +) -> str: + client = _get_codex_client() + content = [{"type": "input_text", "text": user_text}] + if image_bytes: + content.insert( + 0, + { + "type": "input_image", + "image_url": f"data:{media_type};base64,{base64.b64encode(image_bytes).decode('utf-8')}", + }, + ) + + stream = await client.responses.create( + model=model, + instructions=system_prompt, + input=[{"role": "user", "content": content}], + text={"verbosity": "medium"}, + store=False, + stream=True, + ) + + text_parts: list[str] = [] + + async for event in stream: + payload = _response_event_to_dict(event) + event_type = payload.get("type") or "" + + if event_type == "response.output_text.delta": + delta = payload.get("delta") or "" + if delta: + text_parts.append(delta) + continue + + if event_type == "response.output_text.done": + text = payload.get("text") or "" + if text and not text_parts: + text_parts.append(text) + continue + + if event_type in ("response.error", "response.failed", "error"): + error_detail = payload.get("message") or payload.get("error") or str(payload) + raise HTTPException(status_code=502, detail=f"Codex error: {error_detail}"[:400]) + + output_text = "".join(text_parts).strip() + if not output_text: + raise HTTPException(status_code=500, detail="No output from template provider") + return output_text + + +async def _call_google( + *, + model: str, + system_prompt: str, + user_text: str, + image_bytes: Optional[bytes] = None, + media_type: str = "image/png", +) -> str: + client = _get_google_client() + parts = [google_types.Part.from_text(text=user_text)] + if image_bytes: + parts.append(google_types.Part.from_bytes(data=image_bytes, mime_type=media_type)) + + response = await asyncio.to_thread( + client.models.generate_content, + model=model, + contents=[google_types.Content(role="user", parts=parts)], + config=google_types.GenerateContentConfig( + system_instruction=system_prompt, + response_mime_type="text/plain", + ), + ) + output_text = getattr(response, "text", None) or "" + if not output_text: + raise HTTPException(status_code=500, detail="No output from template provider") + return output_text + + +async def _call_anthropic( + *, + model: str, + system_prompt: str, + user_text: str, + image_bytes: Optional[bytes] = None, + media_type: str = "image/png", +) -> str: + client = _get_anthropic_client() + content = [{"type": "text", "text": user_text}] + if image_bytes: + content.append( + { + "type": "image", + "source": { + "type": "base64", + "media_type": media_type, + "data": base64.b64encode(image_bytes).decode("utf-8"), + }, + } + ) + + response = await client.messages.create( + model=model, + max_tokens=8192, + system=system_prompt, + messages=[{"role": "user", "content": content}], + ) + output_text = "".join( + block.text for block in response.content if getattr(block, "type", None) == "text" + ) + if not output_text: + raise HTTPException(status_code=500, detail="No output from template provider") + return output_text + + +def _build_provider_call( + *, + spec: Optional[TemplateProviderSpec] = None, + system_prompt: str, + user_text: str, + image_bytes: Optional[bytes] = None, + media_type: str = "image/png", +) -> PlainLLMProvider: + spec = spec or get_template_provider_spec() + + if spec.provider == LLMProvider.OPENAI: + return PlainLLMProvider( + name="OpenAI", + call=lambda: _call_openai_like( + client=_get_openai_client(), + model=spec.model, + system_prompt=system_prompt, + user_text=user_text, + image_bytes=image_bytes, + media_type=media_type, + ), + ) + if spec.provider == LLMProvider.CODEX: + return PlainLLMProvider( + name="Codex", + call=lambda: _call_codex( + model=spec.model, + system_prompt=system_prompt, + user_text=user_text, + image_bytes=image_bytes, + media_type=media_type, + ), + ) + if spec.provider == LLMProvider.GOOGLE: + return PlainLLMProvider( + name="Google", + call=lambda: _call_google( + model=spec.model, + system_prompt=system_prompt, + user_text=user_text, + image_bytes=image_bytes, + media_type=media_type, + ), + ) + if spec.provider == LLMProvider.ANTHROPIC: + return PlainLLMProvider( + name="Anthropic", + call=lambda: _call_anthropic( + model=spec.model, + system_prompt=system_prompt, + user_text=user_text, + image_bytes=image_bytes, + media_type=media_type, + ), + ) + + raise HTTPException( + status_code=400, + detail="Template generation only supports OpenAI, Codex, Google, or Anthropic.", + ) + + +async def generate_slide_layout_code( + *, + system_prompt: str, + user_text: str, + image_bytes: bytes, + media_type: str = "image/png", +) -> str: + provider = _build_provider_call( + system_prompt=system_prompt, + user_text=user_text, + image_bytes=image_bytes, + media_type=media_type, + ) + return await run_plain_provider_buckets(providers=[provider]) + + +async def edit_slide_layout_code( + *, + system_prompt: str, + user_text: str, +) -> str: + provider = _build_provider_call( + system_prompt=system_prompt, + user_text=user_text, + ) + return await run_plain_provider_buckets(providers=[provider]) diff --git a/servers/fastapi/templates/router.py b/servers/fastapi/templates/router.py new file mode 100644 index 00000000..3d303e4f --- /dev/null +++ b/servers/fastapi/templates/router.py @@ -0,0 +1,65 @@ +import uuid + +from fastapi import APIRouter + +from templates.handler import ( + CreateSlideLayoutResponse, + EditSlideLayoutResponse, + EditSlideLayoutSectionResponse, + FontsUploadAndSlidesPreviewResponse, + GetTemplateLayoutsResponse, + PresentationLayoutModel, + SaveTemplateLayoutData, + SaveTemplateResponse, + TemplateDetail, + TemplateExample, + clone_slide_layout, + clone_template, + create_slide_layout, + edit_slide_layout, + edit_slide_layout_section, + get_all_templates, + get_layouts, + get_template_by_id, + get_template_example, + init_create_template, + save_slide_layout, + save_template, + update_template, + upload_fonts_and_slides_preview, +) + +TEMPLATE_ROUTER = APIRouter(prefix="/template", tags=["Template"]) + +TEMPLATE_ROUTER.get("/all", response_model=list[TemplateDetail])(get_all_templates) +TEMPLATE_ROUTER.get( + "/{template_id}/layouts", response_model=GetTemplateLayoutsResponse +)(get_layouts) +TEMPLATE_ROUTER.get("/{id}", response_model=PresentationLayoutModel)(get_template_by_id) +TEMPLATE_ROUTER.get("/{id}/example", response_model=TemplateExample)( + get_template_example +) +TEMPLATE_ROUTER.post( + "/fonts-upload-and-slides-preview", + response_model=FontsUploadAndSlidesPreviewResponse, +)(upload_fonts_and_slides_preview) +TEMPLATE_ROUTER.post("/create/init", response_model=uuid.UUID)(init_create_template) +TEMPLATE_ROUTER.post("/slide-layout/create", response_model=CreateSlideLayoutResponse)( + create_slide_layout +) +TEMPLATE_ROUTER.post("/create/slide-layout", response_model=CreateSlideLayoutResponse)( + create_slide_layout +) +TEMPLATE_ROUTER.post("/slide-layout/edit", response_model=EditSlideLayoutResponse)( + edit_slide_layout +) +TEMPLATE_ROUTER.post( + "/slide-layout/edit-section", response_model=EditSlideLayoutSectionResponse +)(edit_slide_layout_section) +TEMPLATE_ROUTER.post("/save", response_model=SaveTemplateResponse)(save_template) +TEMPLATE_ROUTER.post("/clone", response_model=SaveTemplateResponse)(clone_template) +TEMPLATE_ROUTER.put("/update", response_model=SaveTemplateResponse)(update_template) +TEMPLATE_ROUTER.post("/slide-layout/save", status_code=200)(save_slide_layout) +TEMPLATE_ROUTER.post("/slide-layout/clone", response_model=SaveTemplateLayoutData)( + clone_slide_layout +) diff --git a/servers/fastapi/utils/get_env.py b/servers/fastapi/utils/get_env.py index 8a128cdd..84d9b749 100644 --- a/servers/fastapi/utils/get_env.py +++ b/servers/fastapi/utils/get_env.py @@ -156,10 +156,6 @@ def get_migrate_database_on_startup_env(): return os.getenv("MIGRATE_DATABASE_ON_STARTUP") -def get_next_public_fast_api_env(): - return os.getenv("FASTAPI_PUBLIC_URL") - - def get_sentry_dsn_env(): return os.getenv("SENTRY_DSN") diff --git a/servers/fastapi/utils/get_layout_by_name.py b/servers/fastapi/utils/get_layout_by_name.py index ec68dd6e..07140b45 100644 --- a/servers/fastapi/utils/get_layout_by_name.py +++ b/servers/fastapi/utils/get_layout_by_name.py @@ -1,18 +1,5 @@ -import aiohttp -from fastapi import HTTPException -from models.presentation_layout import PresentationLayoutModel -from typing import List +"""Re-export for callers that import from `utils.get_layout_by_name`.""" -async def get_layout_by_name(layout_name: str) -> PresentationLayoutModel: - url = f"http://localhost/api/template?group={layout_name}" - async with aiohttp.ClientSession() as session: - async with session.get(url) as response: - if response.status != 200: - error_text = await response.text() - raise HTTPException( - status_code=404, - detail=f"Template '{layout_name}' not found: {error_text}" - ) - layout_json = await response.json() - # Parse the JSON into your Pydantic model - return PresentationLayoutModel(**layout_json) +from templates.get_layout_by_name import get_layout_by_name + +__all__ = ["get_layout_by_name"] diff --git a/servers/fastapi/utils/ocr_language.py b/servers/fastapi/utils/ocr_language.py new file mode 100644 index 00000000..aa988f27 --- /dev/null +++ b/servers/fastapi/utils/ocr_language.py @@ -0,0 +1,126 @@ +""" +Map presentation UI language strings (LanguageType enum values from Next.js) to +Tesseract / LiteParse OCR language codes (ISO 639-3 where applicable). + +Keep keys in sync with: +electron/servers/nextjs/app/(presentation-generator)/upload/type.ts → LanguageType +""" + +from __future__ import annotations + +import re +from typing import Optional + +# Values must match `LanguageType` string literals in the upload UI. +PRESENTATION_LANGUAGE_TO_TESSERACT: dict[str, str] = { + "English": "eng", + "Spanish (Español)": "spa", + "French (Français)": "fra", + "German (Deutsch)": "deu", + "Portuguese (Português)": "por", + "Italian (Italiano)": "ita", + "Dutch (Nederlands)": "nld", + "Russian (Русский)": "rus", + "Chinese (Simplified - 中文, 汉语)": "chi_sim", + "Chinese (Traditional - 中文, 漢語)": "chi_tra", + "Japanese (日本語)": "jpn", + "Korean (한국어)": "kor", + "Arabic (العربية)": "ara", + "Hindi (हिन्दी)": "hin", + "Bengali (বাংলা)": "ben", + "Polish (Polski)": "pol", + "Czech (Čeština)": "ces", + "Slovak (Slovenčina)": "slk", + "Hungarian (Magyar)": "hun", + "Romanian (Română)": "ron", + "Bulgarian (Български)": "bul", + "Greek (Ελληνικά)": "ell", + "Serbian (Српски / Srpski)": "srp", + "Croatian (Hrvatski)": "hrv", + "Bosnian (Bosanski)": "bos", + "Slovenian (Slovenščina)": "slv", + "Finnish (Suomi)": "fin", + "Swedish (Svenska)": "swe", + "Danish (Dansk)": "dan", + "Norwegian (Norsk)": "nor", + "Icelandic (Íslenska)": "isl", + "Lithuanian (Lietuvių)": "lit", + "Latvian (Latviešu)": "lav", + "Estonian (Eesti)": "est", + "Maltese (Malti)": "mlt", + "Welsh (Cymraeg)": "cym", + "Irish (Gaeilge)": "gle", + "Scottish Gaelic (Gàidhlig)": "gla", + "Ukrainian (Українська)": "ukr", + "Hebrew (עברית)": "heb", + "Persian/Farsi (فارسی)": "fas", + "Turkish (Türkçe)": "tur", + "Kurdish (Kurdî / کوردی)": "kmr", + "Pashto (پښتو)": "pus", + "Dari (دری)": "prs", + "Uzbek (Oʻzbek)": "uzb", + "Kazakh (Қазақша)": "kaz", + "Tajik (Тоҷикӣ)": "tgk", + "Turkmen (Türkmençe)": "tuk", + "Azerbaijani (Azərbaycan dili)": "aze", + "Urdu (اردو)": "urd", + "Tamil (தமிழ்)": "tam", + "Telugu (తెలుగు)": "tel", + "Marathi (मराठी)": "mar", + "Punjabi (ਪੰਜਾਬੀ / پنجابی)": "pan", + "Gujarati (ગુજરાતી)": "guj", + "Malayalam (മലയാളം)": "mal", + "Kannada (ಕನ್ನಡ)": "kan", + "Odia (ଓଡ଼ିଆ)": "ori", + "Sinhala (සිංහල)": "sin", + "Nepali (नेपाली)": "nep", + "Thai (ไทย)": "tha", + "Vietnamese (Tiếng Việt)": "vie", + "Lao (ລາວ)": "lao", + "Khmer (ភាសាខ្មែរ)": "khm", + "Burmese (မြန်မာစာ)": "mya", + "Tagalog/Filipino (Tagalog/Filipino)": "tgl", + "Javanese (Basa Jawa)": "jav", + "Sundanese (Basa Sunda)": "sun", + "Malay (Bahasa Melayu)": "msa", + "Mongolian (Монгол)": "mon", + "Swahili (Kiswahili)": "swa", + "Hausa (Hausa)": "hau", + "Yoruba (Yorùbá)": "yor", + "Igbo (Igbo)": "ibo", + "Amharic (አማርኛ)": "amh", + "Zulu (isiZulu)": "zul", + "Xhosa (isiXhosa)": "xho", + "Shona (ChiShona)": "sna", + "Somali (Soomaaliga)": "som", + "Basque (Euskara)": "eus", + "Catalan (Català)": "cat", + "Galician (Galego)": "glg", + "Quechua (Runasimi)": "que", + "Nahuatl (Nāhuatl)": "nah", + "Hawaiian (ʻŌlelo Hawaiʻi)": "haw", + "Maori (Te Reo Māori)": "mri", + # No dedicated Tahitian traineddata in default Tesseract bundles. + "Tahitian (Reo Tahiti)": "eng", + "Samoan (Gagana Samoa)": "smo", +} + +_LOWER_MAP = {k.lower(): v for k, v in PRESENTATION_LANGUAGE_TO_TESSERACT.items()} + +_OCR_CODE_RE = re.compile(r"^[a-zA-Z0-9_,+]+$") + + +def presentation_language_to_ocr_code(language: Optional[str]) -> str: + """Resolve UI language label to a Tesseract language code; default English.""" + if language is None: + return "eng" + s = str(language).strip() + if not s: + return "eng" + if s in PRESENTATION_LANGUAGE_TO_TESSERACT: + code = PRESENTATION_LANGUAGE_TO_TESSERACT[s] + else: + code = _LOWER_MAP.get(s.lower(), "eng") + if not _OCR_CODE_RE.fullmatch(code): + return "eng" + return code diff --git a/servers/fastapi/utils/process_slides.py b/servers/fastapi/utils/process_slides.py index a15ad59c..616d4efb 100644 --- a/servers/fastapi/utils/process_slides.py +++ b/servers/fastapi/utils/process_slides.py @@ -1,14 +1,11 @@ import asyncio -import os -from typing import List, Optional, Tuple +from typing import List, Optional from models.image_prompt import ImagePrompt from models.sql.image_asset import ImageAsset from models.sql.slide import SlideModel from services.icon_finder_service import ICON_FINDER_SERVICE from services.image_generation_service import ImageGenerationService -from utils.asset_directory_utils import get_images_directory from utils.dict_utils import get_dict_at_path, get_dict_paths_with_key, set_dict_at_path -from utils.path_helpers import get_resource_path async def process_slide_and_fetch_assets( @@ -59,7 +56,7 @@ async def process_slide_and_fetch_assets( image_dict = get_dict_at_path(slide.content, asset_path) if isinstance(result, ImageAsset): return_assets.append(result) - image_dict["__image_url__"] = result.file_url + image_dict["__image_url__"] = result.path else: image_dict["__image_url__"] = result set_dict_at_path(slide.content, asset_path, image_dict) @@ -172,7 +169,7 @@ async def process_old_and_new_slides_and_fetch_assets( fetched_image = new_images[i] if isinstance(fetched_image, ImageAsset): new_assets.append(fetched_image) - image_url = fetched_image.file_url + image_url = fetched_image.path else: image_url = fetched_image new_image_dicts[i]["__image_url__"] = image_url diff --git a/servers/fastapi/uv.lock b/servers/fastapi/uv.lock index 41c8977a..31ac21e5 100644 --- a/servers/fastapi/uv.lock +++ b/servers/fastapi/uv.lock @@ -7,25 +7,6 @@ resolution-markers = [ "sys_platform == 'darwin'", ] -[[package]] -name = "accelerate" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "huggingface-hub" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "psutil" }, - { name = "pyyaml" }, - { name = "safetensors" }, - { name = "torch", version = "2.7.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c4/25/969456a95a90ed38f73f68d0f0915bdf1d76145d05054c59ad587b171150/accelerate-1.9.0.tar.gz", hash = "sha256:0e8c61f81af7bf37195b6175a545ed292617dd90563c88f49020aea5b6a0b47f", size = 383234, upload-time = "2025-07-16T16:24:54.526Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/1c/a17fb513aeb684fb83bef5f395910f53103ab30308bbdd77fd66d6698c46/accelerate-1.9.0-py3-none-any.whl", hash = "sha256:c24739a97ade1d54af4549a65f8b6b046adc87e2b3e4d6c66516e32c53d5a8f1", size = 367073, upload-time = "2025-07-16T16:24:52.957Z" }, -] - [[package]] name = "aiohappyeyeballs" version = "2.6.1" @@ -256,19 +237,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/63/13/47bba97924ebe86a62ef83dc75b7c8a881d53c535f83e2c54c4bd701e05c/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57967b7a28d855313a963aaea51bf6df89f833db4320da458e5b3c5ab6d4c938", size = 280110, upload-time = "2025-02-28T01:24:05.896Z" }, ] -[[package]] -name = "beautifulsoup4" -version = "4.13.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "soupsieve" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d8/e4/0c4c39e18fd76d6a628d4dd8da40543d136ce2d1752bd6eeeab0791f4d6b/beautifulsoup4-4.13.4.tar.gz", hash = "sha256:dbb3c4e1ceae6aefebdaf2423247260cd062430a410e38c66f2baa50a8437195", size = 621067, upload-time = "2025-04-15T17:05:13.836Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/50/cd/30110dc0ffcf3b131156077b90e9f60ed75711223f306da4db08eff8403b/beautifulsoup4-4.13.4-py3-none-any.whl", hash = "sha256:9bbbb14bfde9d79f38b8cd5f8c7c85f4b8f2523190ebed90e950a8dea4cb1c4b", size = 187285, upload-time = "2025-04-15T17:05:12.221Z" }, -] - [[package]] name = "build" version = "1.3.0" @@ -403,9 +371,10 @@ wheels = [ [[package]] name = "colorama" version = "0.4.6" -source = { registry = "https://download.pytorch.org/whl/cpu" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ - { url = "https://download.pytorch.org/whl/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] [[package]] @@ -476,15 +445,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/e5/a7b6db64f08cfe065e531ec6b508fa7dac704fab70d05adb5bc0c2c1d1b6/cyclopts-3.22.5-py3-none-any.whl", hash = "sha256:92efb4a094d9812718d7efe0bffa319a19cb661f230dbf24406c18cd8809fb82", size = 84994, upload-time = "2025-07-31T18:18:35.939Z" }, ] -[[package]] -name = "dill" -version = "0.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/12/80/630b4b88364e9a8c8c5797f4602d0f76ef820909ee32f0bacb9f90654042/dill-0.4.0.tar.gz", hash = "sha256:0633f1d2df477324f53a895b02c901fb961bdbf65a17122586ea7019292cbcf0", size = 186976, upload-time = "2025-04-16T00:41:48.867Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/50/3d/9373ad9c56321fdab5b41197068e1d8c25883b3fea29dd361f9b55116869/dill-0.4.0-py3-none-any.whl", hash = "sha256:44f54bf6412c2c8464c14e8243eb163690a9800dbe2c367330883b19c7561049", size = 119668, upload-time = "2025-04-16T00:41:47.671Z" }, -] - [[package]] name = "dirtyjson" version = "1.0.8" @@ -512,117 +472,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/68/1b/e0a87d256e40e8c888847551b20a017a6b98139178505dc7ffb96f04e954/dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86", size = 313632, upload-time = "2024-10-05T20:14:57.687Z" }, ] -[[package]] -name = "docling" -version = "2.43.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "accelerate" }, - { name = "beautifulsoup4" }, - { name = "certifi" }, - { name = "docling-core", extra = ["chunking"] }, - { name = "docling-ibm-models" }, - { name = "docling-parse" }, - { name = "easyocr" }, - { name = "filetype" }, - { name = "huggingface-hub" }, - { name = "lxml" }, - { name = "marko" }, - { name = "openpyxl" }, - { name = "pandas" }, - { name = "pillow" }, - { name = "pluggy" }, - { name = "pydantic" }, - { name = "pydantic-settings" }, - { name = "pylatexenc" }, - { name = "pypdfium2" }, - { name = "python-docx" }, - { name = "python-pptx" }, - { name = "requests" }, - { name = "rtree" }, - { name = "scipy" }, - { name = "tqdm" }, - { name = "typer" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/7a25f8a95ca9858c2af3523cb405f0acee5b9c7f1ee136a9b88e9e9679e4/docling-2.43.0.tar.gz", hash = "sha256:f9236eee44163f7bc0c5ca2880edeee462b57c1ef813f932e1ba47cb35c855c3", size = 174227, upload-time = "2025-07-28T09:46:56.217Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/b6/c808ba27473f9a8261e19940f0506980851e4b1a7a6832587bfe82379d67/docling-2.43.0-py3-none-any.whl", hash = "sha256:3f549a2f4a0d84206bffb4dbffa723727cbbcf992ab379478e41791b93bd36f3", size = 195149, upload-time = "2025-07-28T09:46:54.856Z" }, -] - -[[package]] -name = "docling-core" -version = "2.44.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "jsonref" }, - { name = "jsonschema" }, - { name = "latex2mathml" }, - { name = "pandas" }, - { name = "pillow" }, - { name = "pydantic" }, - { name = "pyyaml" }, - { name = "tabulate" }, - { name = "typer" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/69/eb/60faf42b760105e97960c4fde7d42210f70f1e72cd0a2b8ae898630a8cf4/docling_core-2.44.1.tar.gz", hash = "sha256:6c7753ec002ef44c8fef2f28b49cf8ee170419e491303227b527a5756a3c9553", size = 157890, upload-time = "2025-07-30T11:05:55.86Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/10/01c33540ac31587167e6ae6cbaa03e464c43296a784628619a5c3146ce83/docling_core-2.44.1-py3-none-any.whl", hash = "sha256:429b19c4e56d3e9af63a8369724552a3880a6c43295edd63a37827bb2a68f820", size = 162643, upload-time = "2025-07-30T11:05:52.776Z" }, -] - -[package.optional-dependencies] -chunking = [ - { name = "semchunk" }, - { name = "transformers" }, -] - -[[package]] -name = "docling-ibm-models" -version = "3.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "accelerate" }, - { name = "docling-core" }, - { name = "huggingface-hub" }, - { name = "jsonlines" }, - { name = "numpy" }, - { name = "opencv-python-headless" }, - { name = "pillow" }, - { name = "pydantic" }, - { name = "rtree" }, - { name = "safetensors", extra = ["torch"] }, - { name = "torch", version = "2.7.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, - { name = "torchvision", version = "0.22.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, - { name = "torchvision", version = "0.22.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, - { name = "tqdm" }, - { name = "transformers" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/15/4e/8fdc66af4390d4cfa6ecfe27a03bdbacde96f80dda4baff7f01f5dae8b04/docling_ibm_models-3.9.0.tar.gz", hash = "sha256:e3f866371df86a85abc2ae88fa05a9e56e3ae3b5e6512bec9cc5b6e12096af50", size = 86575, upload-time = "2025-07-23T14:18:29.579Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/74/9ff640777c35b7a5347e53d55fe8103ee621bd1c72e820456673f59f1184/docling_ibm_models-3.9.0-py3-none-any.whl", hash = "sha256:cde63a13314c72c969a355cd4dfea8aa253d14ff8fb7fd4bc15d6e2c9d161c4d", size = 86602, upload-time = "2025-07-23T14:18:28.354Z" }, -] - -[[package]] -name = "docling-parse" -version = "4.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "docling-core" }, - { name = "pillow" }, - { name = "pydantic" }, - { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "tabulate" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/24/fff30a36af50a720813b1bdbeaee140136ff0fcdfad041ec8127c3115b4f/docling_parse-4.1.0.tar.gz", hash = "sha256:6c2f52c5438ff6158ad2e6d2064b35786f01ce7f1b235c7c882b71ab221549c6", size = 39407179, upload-time = "2025-06-24T11:21:49.233Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/32/8755b295c9850b75f3ee64274ddcbce67c4afbd8263b5136c073483c997c/docling_parse-4.1.0-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:66a6773981702ba052a0f766f868ee98526899ad802bd03dbf50b1209fda8082", size = 14710838, upload-time = "2025-06-24T11:20:58.155Z" }, - { url = "https://files.pythonhosted.org/packages/d9/ac/051d61783b58dda5e33884dc25f4bda38025fcae7f0f94a159373895947e/docling_parse-4.1.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:78515424b90fcd305f8ea9ab243719c3030c9ce764cef44be1b8cf0d8fc4a5a5", size = 14589300, upload-time = "2025-06-24T11:21:00.479Z" }, - { url = "https://files.pythonhosted.org/packages/57/7a/a665f853ff801879598738beb9a5fc3142aa50b1f81fa46d8e1f92d1a4b2/docling_parse-4.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e568bb9d8188bffc72fe10a78712c73a5a6002980b3602d58969dc14e0d7ff1", size = 15027042, upload-time = "2025-06-24T11:21:02.614Z" }, - { url = "https://files.pythonhosted.org/packages/26/d3/04f9816b8eea9e7fa2665bcca511c27ee1e2a223a24ce39bb0cd9eefc7f2/docling_parse-4.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9cfc436cfbc635b65fe4bb5a3157872944c98b95851b71269456614c35d5bf5", size = 15106766, upload-time = "2025-06-24T11:21:04.992Z" }, - { url = "https://files.pythonhosted.org/packages/b3/51/67365adea9afcd1a923e86e5ebecf10e192e12532486e3677adb72c41be1/docling_parse-4.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:2495b5ebf7669770715c290d5f2ef47a849bc2801e8bb78e71f92ea49322b3b3", size = 15896344, upload-time = "2025-06-24T11:21:06.888Z" }, -] - [[package]] name = "docstring-parser" version = "0.17.0" @@ -650,30 +499,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922, upload-time = "2025-05-17T13:52:36.463Z" }, ] -[[package]] -name = "easyocr" -version = "1.7.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ninja" }, - { name = "numpy" }, - { name = "opencv-python-headless" }, - { name = "pillow" }, - { name = "pyclipper" }, - { name = "python-bidi" }, - { name = "pyyaml" }, - { name = "scikit-image" }, - { name = "scipy" }, - { name = "shapely" }, - { name = "torch", version = "2.7.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, - { name = "torchvision", version = "0.22.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, - { name = "torchvision", version = "0.22.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/84/4a2cab0e6adde6a85e7ba543862e5fc0250c51f3ac721a078a55cdcff250/easyocr-1.7.2-py3-none-any.whl", hash = "sha256:5be12f9b0e595d443c9c3d10b0542074b50f0ec2d98b141a109cd961fd1c177c", size = 2870178, upload-time = "2024-09-24T11:34:43.554Z" }, -] - [[package]] name = "email-validator" version = "2.2.0" @@ -687,15 +512,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d7/ee/bf0adb559ad3c786f12bcbc9296b3f5675f529199bef03e2df281fa1fadb/email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631", size = 33521, upload-time = "2024-06-20T11:30:28.248Z" }, ] -[[package]] -name = "et-xmlfile" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234, upload-time = "2024-10-25T17:25:40.039Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059, upload-time = "2024-10-25T17:25:39.051Z" }, -] - [[package]] name = "exceptiongroup" version = "1.3.0" @@ -829,19 +645,10 @@ wheels = [ [[package]] name = "filelock" version = "3.18.0" -source = { registry = "https://download.pytorch.org/whl/cpu" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de" }, -] - -[[package]] -name = "filetype" -version = "1.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020, upload-time = "2022-11-02T17:34:04.141Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970, upload-time = "2022-11-02T17:34:01.425Z" }, + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, ] [[package]] @@ -882,10 +689,10 @@ wheels = [ [[package]] name = "fsspec" version = "2025.7.0" -source = { registry = "https://download.pytorch.org/whl/cpu" } -sdist = { url = "https://files.pythonhosted.org/packages/8b/02/0835e6ab9cfc03916fe3f78c0956cfcdb6ff2669ffa6651065d5ebf7fc98/fsspec-2025.7.0.tar.gz", hash = "sha256:786120687ffa54b8283d942929540d8bc5ccfa820deb555a2b5d0ed2b737bf58" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/02/0835e6ab9cfc03916fe3f78c0956cfcdb6ff2669ffa6651065d5ebf7fc98/fsspec-2025.7.0.tar.gz", hash = "sha256:786120687ffa54b8283d942929540d8bc5ccfa820deb555a2b5d0ed2b737bf58", size = 304432, upload-time = "2025-07-15T16:05:21.19Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/e0/014d5d9d7a4564cf1c40b5039bc882db69fd881111e03ab3657ac0b218e2/fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21" }, + { url = "https://files.pythonhosted.org/packages/2f/e0/014d5d9d7a4564cf1c40b5039bc882db69fd881111e03ab3657ac0b218e2/fsspec-2025.7.0-py3-none-any.whl", hash = "sha256:8b012e39f63c7d5f10474de957f3ab793b47b45ae7d39f2fb735f8bbe25c0e21", size = 199597, upload-time = "2025-07-15T16:05:19.529Z" }, ] [[package]] @@ -1083,19 +890,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, ] -[[package]] -name = "imageio" -version = "2.37.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "pillow" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0c/47/57e897fb7094afb2d26e8b2e4af9a45c7cf1a405acdeeca001fdf2c98501/imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996", size = 389963, upload-time = "2025-01-20T02:42:37.089Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed", size = 315796, upload-time = "2025-01-20T02:42:34.931Z" }, -] - [[package]] name = "importlib-metadata" version = "8.7.0" @@ -1138,12 +932,13 @@ wheels = [ [[package]] name = "jinja2" version = "3.1.6" -source = { registry = "https://download.pytorch.org/whl/cpu" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } wheels = [ - { url = "https://download.pytorch.org/whl/jinja2-3.1.6-py3-none-any.whl" }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] [[package]] @@ -1175,27 +970,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7d/4f/1195bbac8e0c2acc5f740661631d8d750dc38d4a32b23ee5df3cde6f4e0d/joblib-1.5.1-py3-none-any.whl", hash = "sha256:4719a31f054c7d766948dcd83e9613686b27114f190f717cec7eaa2084f8a74a", size = 307746, upload-time = "2025-05-23T12:04:35.124Z" }, ] -[[package]] -name = "jsonlines" -version = "3.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2a/c8/efdb87403dae07cf20faf75449eae41898b71d6a8d4ebaf9c80d5be215f5/jsonlines-3.1.0.tar.gz", hash = "sha256:2579cb488d96f815b0eb81629e3e6b0332da0962a18fa3532958f7ba14a5c37f", size = 8510, upload-time = "2022-07-01T16:38:05.48Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/32/290ca20eb3a2b97ffa6ba1791fcafacb3cd2f41f539c96eb54cfc3cfcf47/jsonlines-3.1.0-py3-none-any.whl", hash = "sha256:632f5e38f93dfcb1ac8c4e09780b92af3a55f38f26e7c47ae85109d420b6ad39", size = 8592, upload-time = "2022-07-01T16:38:02.082Z" }, -] - -[[package]] -name = "jsonref" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/0d/c1f3277e90ccdb50d33ed5ba1ec5b3f0a242ed8c1b1a85d3afeb68464dca/jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552", size = 8814, upload-time = "2023-01-16T16:10:04.455Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/ec/e1db9922bceb168197a558a2b8c03a7963f1afe93517ddd3cf99f202f996/jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9", size = 9425, upload-time = "2023-01-16T16:10:02.255Z" }, -] - [[package]] name = "jsonschema" version = "4.25.0" @@ -1260,27 +1034,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/89/43/d9bebfc3db7dea6ec80df5cb2aad8d274dd18ec2edd6c4f21f32c237cbbb/kubernetes-33.1.0-py2.py3-none-any.whl", hash = "sha256:544de42b24b64287f7e0aa9513c93cb503f7f40eea39b20f66810011a86eabc5", size = 1941335, upload-time = "2025-06-09T21:57:56.327Z" }, ] -[[package]] -name = "latex2mathml" -version = "3.78.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/33/ad2c3929494ad160f5130ea132ca298627a6c81c70be6bedd1bc806b5b01/latex2mathml-3.78.0.tar.gz", hash = "sha256:712193aa4c6ade1a8e0145dac7bc1f9aafbd54f93046a2356a7e1c05fa0f8b31", size = 73737, upload-time = "2025-05-03T16:51:53.563Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/fd/aba08bb9e527168efad57985d7db9a853eb2384b1efa5ca5f3a3794c9cef/latex2mathml-3.78.0-py3-none-any.whl", hash = "sha256:1aeca3dc027b3006ad7b301b7f4a15ffbb4c1451e3dc8c3389e97b37b497e1d6", size = 73673, upload-time = "2025-05-03T16:51:51.991Z" }, -] - -[[package]] -name = "lazy-loader" -version = "0.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "packaging" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431, upload-time = "2024-04-05T13:03:12.261Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" }, -] - [[package]] name = "lazy-object-proxy" version = "1.11.0" @@ -1354,15 +1107,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, ] -[[package]] -name = "marko" -version = "2.1.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/dc/c8cadbd83de1b38d95a48568b445a5553005ebdd32e00a333ca940113db4/marko-2.1.4.tar.gz", hash = "sha256:dd7d66f3706732bf8f994790e674649a4fd0a6c67f16b80246f30de8e16a1eac", size = 142795, upload-time = "2025-06-13T03:25:50.857Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/66/49e3691d14898fb6e34ccb337c7677dfb7e18269ed170f12e4b85315eae6/marko-2.1.4-py3-none-any.whl", hash = "sha256:81c2b9f570ca485bc356678d9ba1a1b3eb78b4a315d01f3ded25442fdc796990", size = 42186, upload-time = "2025-06-13T03:25:49.858Z" }, -] - [[package]] name = "markupsafe" version = "3.0.1" @@ -1445,32 +1189,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278, upload-time = "2025-04-22T14:17:40.49Z" }, ] -[[package]] -name = "mpire" -version = "2.10.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pygments" }, - { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3a/93/80ac75c20ce54c785648b4ed363c88f148bf22637e10c9863db4fbe73e74/mpire-2.10.2.tar.gz", hash = "sha256:f66a321e93fadff34585a4bfa05e95bd946cf714b442f51c529038eb45773d97", size = 271270, upload-time = "2024-05-07T14:00:31.815Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/14/1db1729ad6db4999c3a16c47937d601fcb909aaa4224f5eca5a2f145a605/mpire-2.10.2-py3-none-any.whl", hash = "sha256:d627707f7a8d02aa4c7f7d59de399dec5290945ddf7fbd36cbb1d6ebb37a51fb", size = 272756, upload-time = "2024-05-07T14:00:29.633Z" }, -] - -[package.optional-dependencies] -dill = [ - { name = "multiprocess" }, -] - [[package]] name = "mpmath" version = "1.3.0" -source = { registry = "https://download.pytorch.org/whl/cpu" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c" }, + { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, ] [[package]] @@ -1500,57 +1225,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d8/30/9aec301e9772b098c1f5c0ca0279237c9766d94b97802e9888010c64b0ed/multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a", size = 12313, upload-time = "2025-06-30T15:53:45.437Z" }, ] -[[package]] -name = "multiprocess" -version = "0.70.18" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "dill" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/72/fd/2ae3826f5be24c6ed87266bc4e59c46ea5b059a103f3d7e7eb76a52aeecb/multiprocess-0.70.18.tar.gz", hash = "sha256:f9597128e6b3e67b23956da07cf3d2e5cba79e2f4e0fba8d7903636663ec6d0d", size = 1798503, upload-time = "2025-04-17T03:11:27.742Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/55/4d/9af0d1279c84618bcd35bf5fd7e371657358c7b0a523e54a9cffb87461f8/multiprocess-0.70.18-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8b8940ae30139e04b076da6c5b83e9398585ebdf0f2ad3250673fef5b2ff06d6", size = 144695, upload-time = "2025-04-17T03:11:09.161Z" }, - { url = "https://files.pythonhosted.org/packages/17/bf/87323e79dd0562474fad3373c21c66bc6c3c9963b68eb2a209deb4c8575e/multiprocess-0.70.18-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0929ba95831adb938edbd5fb801ac45e705ecad9d100b3e653946b7716cb6bd3", size = 144742, upload-time = "2025-04-17T03:11:10.072Z" }, - { url = "https://files.pythonhosted.org/packages/dd/74/cb8c831e58dc6d5cf450b17c7db87f14294a1df52eb391da948b5e0a0b94/multiprocess-0.70.18-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4d77f8e4bfe6c6e2e661925bbf9aed4d5ade9a1c6502d5dfc10129b9d1141797", size = 144745, upload-time = "2025-04-17T03:11:11.453Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d8/0cba6cf51a1a31f20471fbc823a716170c73012ddc4fb85d706630ed6e8f/multiprocess-0.70.18-py310-none-any.whl", hash = "sha256:60c194974c31784019c1f459d984e8f33ee48f10fcf42c309ba97b30d9bd53ea", size = 134948, upload-time = "2025-04-17T03:11:20.223Z" }, - { url = "https://files.pythonhosted.org/packages/4b/88/9039f2fed1012ef584751d4ceff9ab4a51e5ae264898f0b7cbf44340a859/multiprocess-0.70.18-py311-none-any.whl", hash = "sha256:5aa6eef98e691281b3ad923be2832bf1c55dd2c859acd73e5ec53a66aae06a1d", size = 144462, upload-time = "2025-04-17T03:11:21.657Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c3/ca84c19bd14cdfc21c388fdcebf08b86a7a470ebc9f5c3c084fc2dbc50f7/multiprocess-0.70.18-py38-none-any.whl", hash = "sha256:dbf705e52a154fe5e90fb17b38f02556169557c2dd8bb084f2e06c2784d8279b", size = 132636, upload-time = "2025-04-17T03:11:24.936Z" }, - { url = "https://files.pythonhosted.org/packages/6c/28/dd72947e59a6a8c856448a5e74da6201cb5502ddff644fbc790e4bd40b9a/multiprocess-0.70.18-py39-none-any.whl", hash = "sha256:e78ca805a72b1b810c690b6b4cc32579eba34f403094bbbae962b7b5bf9dfcb8", size = 133478, upload-time = "2025-04-17T03:11:26.253Z" }, -] - -[[package]] -name = "networkx" -version = "3.5" -source = { registry = "https://download.pytorch.org/whl/cpu" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec" }, -] - -[[package]] -name = "ninja" -version = "1.11.1.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/d4/6b0324541018561c5e73e617bd16f20a4fc17d1179bb3b3520b6ca8beb7b/ninja-1.11.1.4.tar.gz", hash = "sha256:6aa39f6e894e0452e5b297327db00019383ae55d5d9c57c73b04f13bf79d438a", size = 201256, upload-time = "2025-03-22T06:46:43.46Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/b1/3a61b348936b62a386465b1937cd778fa3a5748582e26d832dbab844ff27/ninja-1.11.1.4-py3-none-macosx_10_9_universal2.whl", hash = "sha256:b33923c8da88e8da20b6053e38deb433f53656441614207e01d283ad02c5e8e7", size = 279071, upload-time = "2025-03-22T06:46:17.806Z" }, - { url = "https://files.pythonhosted.org/packages/12/42/4c94fdad51fcf1f039a156e97de9e4d564c2a8cc0303782d36f9bd893a4b/ninja-1.11.1.4-py3-none-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:cede0af00b58e27b31f2482ba83292a8e9171cdb9acc2c867a3b6e40b3353e43", size = 472026, upload-time = "2025-03-22T06:46:19.974Z" }, - { url = "https://files.pythonhosted.org/packages/eb/7a/455d2877fe6cf99886849c7f9755d897df32eaf3a0fba47b56e615f880f7/ninja-1.11.1.4-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:096487995473320de7f65d622c3f1d16c3ad174797602218ca8c967f51ec38a0", size = 422814, upload-time = "2025-03-22T06:46:21.235Z" }, - { url = "https://files.pythonhosted.org/packages/e3/ad/fb6cca942528e25e8e0ab0f0cf98fe007319bf05cf69d726c564b815c4af/ninja-1.11.1.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3090d4488fadf6047d0d7a1db0c9643a8d391f0d94729554dbb89b5bdc769d7", size = 156965, upload-time = "2025-03-22T06:46:23.45Z" }, - { url = "https://files.pythonhosted.org/packages/a8/e7/d94a1b60031b115dd88526834b3da69eaacdc3c1a6769773ca8e2b1386b5/ninja-1.11.1.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecce44a00325a93631792974659cf253a815cc6da4ec96f89742925dfc295a0d", size = 179937, upload-time = "2025-03-22T06:46:24.728Z" }, - { url = "https://files.pythonhosted.org/packages/08/cc/e9316a28235409e9363794fc3d0b3083e48dd80d441006de66421e55f364/ninja-1.11.1.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c29bb66d2aa46a2409ab369ea804c730faec7652e8c22c1e428cc09216543e5", size = 157020, upload-time = "2025-03-22T06:46:26.046Z" }, - { url = "https://files.pythonhosted.org/packages/e3/30/389b22300541aa5f2e9dad322c4de2f84be4e32aa4e8babd9160d620b5f1/ninja-1.11.1.4-py3-none-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:055f386fb550c2c9d6157e45e20a84d29c47968876b9c5794ae2aec46f952306", size = 130389, upload-time = "2025-03-22T06:46:27.174Z" }, - { url = "https://files.pythonhosted.org/packages/a9/10/e27f35cb92813aabbb7ae771b1685b45be1cc8a0798ce7d4bfd08d142b93/ninja-1.11.1.4-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:f6186d7607bb090c3be1e10c8a56b690be238f953616626f5032238c66e56867", size = 372435, upload-time = "2025-03-22T06:46:28.637Z" }, - { url = "https://files.pythonhosted.org/packages/c2/26/e3559619756739aae124c6abf7fe41f7e546ab1209cfbffb13137bff2d2e/ninja-1.11.1.4-py3-none-musllinux_1_1_i686.whl", hash = "sha256:cf4453679d15babc04ba023d68d091bb613091b67101c88f85d2171c6621c6eb", size = 419300, upload-time = "2025-03-22T06:46:30.392Z" }, - { url = "https://files.pythonhosted.org/packages/35/46/809e4e9572570991b8e6f88f3583807d017371ab4cb09171cbc72a7eb3e4/ninja-1.11.1.4-py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:d4a6f159b08b0ac4aca5ee1572e3e402f969139e71d85d37c0e2872129098749", size = 420239, upload-time = "2025-03-22T06:46:32.442Z" }, - { url = "https://files.pythonhosted.org/packages/e6/64/5cb5710d15f844edf02ada577f8eddfdcd116f47eec15850f3371a3a4b33/ninja-1.11.1.4-py3-none-musllinux_1_1_s390x.whl", hash = "sha256:c3b96bd875f3ef1db782470e9e41d7508905a0986571f219d20ffed238befa15", size = 415986, upload-time = "2025-03-22T06:46:33.821Z" }, - { url = "https://files.pythonhosted.org/packages/95/b2/0e9ab1d926f423b12b09925f78afcc5e48b3c22e7121be3ddf6c35bf06a3/ninja-1.11.1.4-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:cf554e73f72c04deb04d0cf51f5fdb1903d9c9ca3d2344249c8ce3bd616ebc02", size = 379657, upload-time = "2025-03-22T06:46:36.166Z" }, - { url = "https://files.pythonhosted.org/packages/c8/3e/fd6d330d0434168e7fe070d414b57dd99c4c133faa69c05b42a3cbdc6c13/ninja-1.11.1.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:cfdd09776436a1ff3c4a2558d3fc50a689fb9d7f1bdbc3e6f7b8c2991341ddb3", size = 454466, upload-time = "2025-03-22T06:46:37.413Z" }, - { url = "https://files.pythonhosted.org/packages/e6/df/a25f3ad0b1c59d1b90564096e4fd89a6ca30d562b1e942f23880c3000b89/ninja-1.11.1.4-py3-none-win32.whl", hash = "sha256:2ab67a41c90bea5ec4b795bab084bc0b3b3bb69d3cd21ca0294fc0fc15a111eb", size = 255931, upload-time = "2025-03-22T06:46:39.171Z" }, - { url = "https://files.pythonhosted.org/packages/5b/10/9b8fe9ac004847490cc7b54896124c01ce2d87d95dc60aabd0b8591addff/ninja-1.11.1.4-py3-none-win_amd64.whl", hash = "sha256:4617b3c12ff64b611a7d93fd9e378275512bb36eff8babff7c83f5116b4f8d66", size = 296461, upload-time = "2025-03-22T06:46:40.532Z" }, - { url = "https://files.pythonhosted.org/packages/b9/58/612a17593c2d117f96c7f6b7f1e6570246bddc4b1e808519403a1417f217/ninja-1.11.1.4-py3-none-win_arm64.whl", hash = "sha256:5713cf50c5be50084a8693308a63ecf9e55c3132a78a41ab1363a28b6caaaee1", size = 271441, upload-time = "2025-03-22T06:46:42.147Z" }, -] - [[package]] name = "nltk" version = "3.9.1" @@ -1569,27 +1243,27 @@ wheels = [ [[package]] name = "numpy" version = "2.3.2" -source = { registry = "https://download.pytorch.org/whl/cpu" } -sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/96/26/1320083986108998bd487e2931eed2aeedf914b6e8905431487543ec911d/numpy-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9" }, - { url = "https://files.pythonhosted.org/packages/c4/2b/792b341463fa93fc7e55abbdbe87dac316c5b8cb5e94fb7a59fb6fa0cda5/numpy-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168" }, - { url = "https://files.pythonhosted.org/packages/b7/13/e792d7209261afb0c9f4759ffef6135b35c77c6349a151f488f531d13595/numpy-2.3.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b" }, - { url = "https://files.pythonhosted.org/packages/49/ce/055274fcba4107c022b2113a213c7287346563f48d62e8d2a5176ad93217/numpy-2.3.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8" }, - { url = "https://files.pythonhosted.org/packages/17/f2/e4d72e6bc5ff01e2ab613dc198d560714971900c03674b41947e38606502/numpy-2.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d" }, - { url = "https://files.pythonhosted.org/packages/c8/b0/fbeee3000a51ebf7222016e2939b5c5ecf8000a19555d04a18f1e02521b8/numpy-2.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3" }, - { url = "https://files.pythonhosted.org/packages/a9/ec/2f6c45c3484cc159621ea8fc000ac5a86f1575f090cac78ac27193ce82cd/numpy-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f" }, - { url = "https://files.pythonhosted.org/packages/b5/01/dd67cf511850bd7aefd6347aaae0956ed415abea741ae107834aae7d6d4e/numpy-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097" }, - { url = "https://files.pythonhosted.org/packages/a7/17/2cf60fd3e6a61d006778735edf67a222787a8c1a7842aed43ef96d777446/numpy-2.3.2-cp311-cp311-win32.whl", hash = "sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220" }, - { url = "https://files.pythonhosted.org/packages/d5/03/0eade211c504bda872a594f045f98ddcc6caef2b7c63610946845e304d3f/numpy-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170" }, - { url = "https://files.pythonhosted.org/packages/13/32/2c7979d39dafb2a25087e12310fc7f3b9d3c7d960df4f4bc97955ae0ce1d/numpy-2.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89" }, - { url = "https://files.pythonhosted.org/packages/cf/ea/50ebc91d28b275b23b7128ef25c3d08152bc4068f42742867e07a870a42a/numpy-2.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15" }, - { url = "https://files.pythonhosted.org/packages/9f/57/cdd5eac00dd5f137277355c318a955c0d8fb8aa486020c22afd305f8b88f/numpy-2.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec" }, - { url = "https://files.pythonhosted.org/packages/83/85/27280c7f34fcd305c2209c0cdca4d70775e4859a9eaa92f850087f8dea50/numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712" }, - { url = "https://files.pythonhosted.org/packages/48/b4/6500b24d278e15dd796f43824e69939d00981d37d9779e32499e823aa0aa/numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c" }, - { url = "https://files.pythonhosted.org/packages/9b/c9/142c1e03f199d202da8e980c2496213509291b6024fd2735ad28ae7065c7/numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296" }, - { url = "https://files.pythonhosted.org/packages/8b/95/8023e87cbea31a750a6c00ff9427d65ebc5fef104a136bfa69f76266d614/numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981" }, - { url = "https://files.pythonhosted.org/packages/78/e3/6690b3f85a05506733c7e90b577e4762517404ea78bab2ca3a5cb1aeb78d/numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619" }, + { url = "https://files.pythonhosted.org/packages/96/26/1320083986108998bd487e2931eed2aeedf914b6e8905431487543ec911d/numpy-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9", size = 21259016, upload-time = "2025-07-24T20:24:35.214Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2b/792b341463fa93fc7e55abbdbe87dac316c5b8cb5e94fb7a59fb6fa0cda5/numpy-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168", size = 14451158, upload-time = "2025-07-24T20:24:58.397Z" }, + { url = "https://files.pythonhosted.org/packages/b7/13/e792d7209261afb0c9f4759ffef6135b35c77c6349a151f488f531d13595/numpy-2.3.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b", size = 5379817, upload-time = "2025-07-24T20:25:07.746Z" }, + { url = "https://files.pythonhosted.org/packages/49/ce/055274fcba4107c022b2113a213c7287346563f48d62e8d2a5176ad93217/numpy-2.3.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8", size = 6913606, upload-time = "2025-07-24T20:25:18.84Z" }, + { url = "https://files.pythonhosted.org/packages/17/f2/e4d72e6bc5ff01e2ab613dc198d560714971900c03674b41947e38606502/numpy-2.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d", size = 14589652, upload-time = "2025-07-24T20:25:40.356Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b0/fbeee3000a51ebf7222016e2939b5c5ecf8000a19555d04a18f1e02521b8/numpy-2.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3", size = 16938816, upload-time = "2025-07-24T20:26:05.721Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ec/2f6c45c3484cc159621ea8fc000ac5a86f1575f090cac78ac27193ce82cd/numpy-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f", size = 16370512, upload-time = "2025-07-24T20:26:30.545Z" }, + { url = "https://files.pythonhosted.org/packages/b5/01/dd67cf511850bd7aefd6347aaae0956ed415abea741ae107834aae7d6d4e/numpy-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097", size = 18884947, upload-time = "2025-07-24T20:26:58.24Z" }, + { url = "https://files.pythonhosted.org/packages/a7/17/2cf60fd3e6a61d006778735edf67a222787a8c1a7842aed43ef96d777446/numpy-2.3.2-cp311-cp311-win32.whl", hash = "sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220", size = 6599494, upload-time = "2025-07-24T20:27:09.786Z" }, + { url = "https://files.pythonhosted.org/packages/d5/03/0eade211c504bda872a594f045f98ddcc6caef2b7c63610946845e304d3f/numpy-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170", size = 13087889, upload-time = "2025-07-24T20:27:29.558Z" }, + { url = "https://files.pythonhosted.org/packages/13/32/2c7979d39dafb2a25087e12310fc7f3b9d3c7d960df4f4bc97955ae0ce1d/numpy-2.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89", size = 10459560, upload-time = "2025-07-24T20:27:46.803Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ea/50ebc91d28b275b23b7128ef25c3d08152bc4068f42742867e07a870a42a/numpy-2.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15", size = 21130338, upload-time = "2025-07-24T20:57:54.37Z" }, + { url = "https://files.pythonhosted.org/packages/9f/57/cdd5eac00dd5f137277355c318a955c0d8fb8aa486020c22afd305f8b88f/numpy-2.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec", size = 14375776, upload-time = "2025-07-24T20:58:16.303Z" }, + { url = "https://files.pythonhosted.org/packages/83/85/27280c7f34fcd305c2209c0cdca4d70775e4859a9eaa92f850087f8dea50/numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712", size = 5304882, upload-time = "2025-07-24T20:58:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/b4/6500b24d278e15dd796f43824e69939d00981d37d9779e32499e823aa0aa/numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c", size = 6818405, upload-time = "2025-07-24T20:58:37.341Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c9/142c1e03f199d202da8e980c2496213509291b6024fd2735ad28ae7065c7/numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296", size = 14419651, upload-time = "2025-07-24T20:58:59.048Z" }, + { url = "https://files.pythonhosted.org/packages/8b/95/8023e87cbea31a750a6c00ff9427d65ebc5fef104a136bfa69f76266d614/numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981", size = 16760166, upload-time = "2025-07-24T21:28:56.38Z" }, + { url = "https://files.pythonhosted.org/packages/78/e3/6690b3f85a05506733c7e90b577e4762517404ea78bab2ca3a5cb1aeb78d/numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619", size = 12977811, upload-time = "2025-07-24T21:29:18.234Z" }, ] [[package]] @@ -1700,35 +1374,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/27/dd/b3fd642260cb17532f66cc1e8250f3507d1e580483e209dc1e9d13bd980d/openapi_spec_validator-0.7.2-py3-none-any.whl", hash = "sha256:4bbdc0894ec85f1d1bea1d6d9c8b2c3c8d7ccaa13577ef40da9c006c9fd0eb60", size = 39713, upload-time = "2025-06-07T14:48:54.077Z" }, ] -[[package]] -name = "opencv-python-headless" -version = "4.11.0.86" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/36/2f/5b2b3ba52c864848885ba988f24b7f105052f68da9ab0e693cc7c25b0b30/opencv-python-headless-4.11.0.86.tar.gz", hash = "sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798", size = 95177929, upload-time = "2025-01-16T13:53:40.22Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/53/2c50afa0b1e05ecdb4603818e85f7d174e683d874ef63a6abe3ac92220c8/opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:48128188ade4a7e517237c8e1e11a9cdf5c282761473383e77beb875bb1e61ca", size = 37326460, upload-time = "2025-01-16T13:52:57.015Z" }, - { url = "https://files.pythonhosted.org/packages/3b/43/68555327df94bb9b59a1fd645f63fafb0762515344d2046698762fc19d58/opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:a66c1b286a9de872c343ee7c3553b084244299714ebb50fbdcd76f07ebbe6c81", size = 56723330, upload-time = "2025-01-16T13:55:45.731Z" }, - { url = "https://files.pythonhosted.org/packages/45/be/1438ce43ebe65317344a87e4b150865c5585f4c0db880a34cdae5ac46881/opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6efabcaa9df731f29e5ea9051776715b1bdd1845d7c9530065c7951d2a2899eb", size = 29487060, upload-time = "2025-01-16T13:51:59.625Z" }, - { url = "https://files.pythonhosted.org/packages/dd/5c/c139a7876099916879609372bfa513b7f1257f7f1a908b0bdc1c2328241b/opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e0a27c19dd1f40ddff94976cfe43066fbbe9dfbb2ec1907d66c19caef42a57b", size = 49969856, upload-time = "2025-01-16T13:53:29.654Z" }, - { url = "https://files.pythonhosted.org/packages/95/dd/ed1191c9dc91abcc9f752b499b7928aacabf10567bb2c2535944d848af18/opencv_python_headless-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:f447d8acbb0b6f2808da71fddd29c1cdd448d2bc98f72d9bb78a7a898fc9621b", size = 29324425, upload-time = "2025-01-16T13:52:49.048Z" }, - { url = "https://files.pythonhosted.org/packages/86/8a/69176a64335aed183529207ba8bc3d329c2999d852b4f3818027203f50e6/opencv_python_headless-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:6c304df9caa7a6a5710b91709dd4786bf20a74d57672b3c31f7033cc638174ca", size = 39402386, upload-time = "2025-01-16T13:52:56.418Z" }, -] - -[[package]] -name = "openpyxl" -version = "3.1.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "et-xmlfile" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464, upload-time = "2024-06-28T14:03:44.161Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910, upload-time = "2024-06-28T14:03:41.161Z" }, -] - [[package]] name = "opentelemetry-api" version = "1.36.0" @@ -1852,27 +1497,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] -[[package]] -name = "pandas" -version = "2.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "python-dateutil" }, - { name = "pytz" }, - { name = "tzdata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d1/6f/75aa71f8a14267117adeeed5d21b204770189c0a0025acbdc03c337b28fc/pandas-2.3.1.tar.gz", hash = "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2", size = 4487493, upload-time = "2025-07-07T19:20:04.079Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/1c/ccf70029e927e473a4476c00e0d5b32e623bff27f0402d0a92b7fc29bb9f/pandas-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2b0540963d83431f5ce8870ea02a7430adca100cec8a050f0811f8e31035541b", size = 11566608, upload-time = "2025-07-07T19:18:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/ec/d3/3c37cb724d76a841f14b8f5fe57e5e3645207cc67370e4f84717e8bb7657/pandas-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fe7317f578c6a153912bd2292f02e40c1d8f253e93c599e82620c7f69755c74f", size = 10823181, upload-time = "2025-07-07T19:18:36.151Z" }, - { url = "https://files.pythonhosted.org/packages/8a/4c/367c98854a1251940edf54a4df0826dcacfb987f9068abf3e3064081a382/pandas-2.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6723a27ad7b244c0c79d8e7007092d7c8f0f11305770e2f4cd778b3ad5f9f85", size = 11793570, upload-time = "2025-07-07T19:18:38.385Z" }, - { url = "https://files.pythonhosted.org/packages/07/5f/63760ff107bcf5146eee41b38b3985f9055e710a72fdd637b791dea3495c/pandas-2.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3462c3735fe19f2638f2c3a40bd94ec2dc5ba13abbb032dd2fa1f540a075509d", size = 12378887, upload-time = "2025-07-07T19:18:41.284Z" }, - { url = "https://files.pythonhosted.org/packages/15/53/f31a9b4dfe73fe4711c3a609bd8e60238022f48eacedc257cd13ae9327a7/pandas-2.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:98bcc8b5bf7afed22cc753a28bc4d9e26e078e777066bc53fac7904ddef9a678", size = 13230957, upload-time = "2025-07-07T19:18:44.187Z" }, - { url = "https://files.pythonhosted.org/packages/e0/94/6fce6bf85b5056d065e0a7933cba2616dcb48596f7ba3c6341ec4bcc529d/pandas-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4d544806b485ddf29e52d75b1f559142514e60ef58a832f74fb38e48d757b299", size = 13883883, upload-time = "2025-07-07T19:18:46.498Z" }, - { url = "https://files.pythonhosted.org/packages/c8/7b/bdcb1ed8fccb63d04bdb7635161d0ec26596d92c9d7a6cce964e7876b6c1/pandas-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:b3cd4273d3cb3707b6fffd217204c52ed92859533e31dc03b7c5008aa933aaab", size = 11340212, upload-time = "2025-07-07T19:18:49.293Z" }, -] - [[package]] name = "parse" version = "1.20.2" @@ -1930,27 +1554,27 @@ wheels = [ [[package]] name = "pillow" version = "11.3.0" -source = { registry = "https://download.pytorch.org/whl/cpu" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523" } +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722" }, - { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288" }, - { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d" }, - { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494" }, - { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58" }, - { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f" }, - { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e" }, - { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94" }, - { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0" }, - { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac" }, - { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd" }, - { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6" }, - { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438" }, - { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3" }, - { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c" }, - { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361" }, - { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7" }, - { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8" }, + { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" }, + { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" }, + { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" }, + { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" }, + { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" }, + { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" }, + { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" }, + { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" }, + { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" }, + { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" }, + { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" }, + { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" }, ] [[package]] @@ -1981,7 +1605,7 @@ wheels = [ [[package]] name = "presenton-backend" version = "0.1.0" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "aiohttp" }, { name = "aiomysql" }, @@ -1991,7 +1615,6 @@ dependencies = [ { name = "asyncpg" }, { name = "chromadb" }, { name = "dirtyjson" }, - { name = "docling" }, { name = "fastapi", extra = ["standard"] }, { name = "fastembed-vectorstore" }, { name = "fastmcp" }, @@ -2016,7 +1639,6 @@ requires-dist = [ { name = "asyncpg", specifier = ">=0.30.0" }, { name = "chromadb", specifier = ">=1.0.15" }, { name = "dirtyjson", specifier = ">=1.0.8" }, - { name = "docling", specifier = ">=2.43.0" }, { name = "fastapi", extras = ["standard"], specifier = ">=0.116.1" }, { name = "fastembed-vectorstore", specifier = ">=0.5.2" }, { name = "fastmcp", specifier = ">=2.11.0" }, @@ -2070,21 +1692,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/af/ab3c51ab7507a7325e98ffe691d9495ee3d3aa5f589afad65ec920d39821/protobuf-6.31.1-py3-none-any.whl", hash = "sha256:720a6c7e6b77288b85063569baae8536671b39f15cc22037ec7045658d80489e", size = 168724, upload-time = "2025-05-28T19:25:53.926Z" }, ] -[[package]] -name = "psutil" -version = "7.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2a/80/336820c1ad9286a4ded7e845b2eccfcb27851ab8ac6abece774a6ff4d3de/psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456", size = 497003, upload-time = "2025-02-13T21:54:07.946Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/e6/2d26234410f8b8abdbf891c9da62bee396583f713fb9f3325a4760875d22/psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25", size = 238051, upload-time = "2025-02-13T21:54:12.36Z" }, - { url = "https://files.pythonhosted.org/packages/04/8b/30f930733afe425e3cbfc0e1468a30a18942350c1a8816acfade80c005c4/psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da", size = 239535, upload-time = "2025-02-13T21:54:16.07Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ed/d362e84620dd22876b55389248e522338ed1bf134a5edd3b8231d7207f6d/psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91", size = 275004, upload-time = "2025-02-13T21:54:18.662Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b9/b0eb3f3cbcb734d930fdf839431606844a825b23eaf9a6ab371edac8162c/psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34", size = 277986, upload-time = "2025-02-13T21:54:21.811Z" }, - { url = "https://files.pythonhosted.org/packages/eb/a2/709e0fe2f093556c17fbafda93ac032257242cabcc7ff3369e2cb76a97aa/psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993", size = 279544, upload-time = "2025-02-13T21:54:24.68Z" }, - { url = "https://files.pythonhosted.org/packages/50/e6/eecf58810b9d12e6427369784efe814a1eec0f492084ce8eb8f4d89d6d61/psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99", size = 241053, upload-time = "2025-02-13T21:54:34.31Z" }, - { url = "https://files.pythonhosted.org/packages/50/1b/6921afe68c74868b4c9fa424dad3be35b095e16687989ebbb50ce4fceb7c/psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553", size = 244885, upload-time = "2025-02-13T21:54:37.486Z" }, -] - [[package]] name = "py-rust-stemmers" version = "0.1.5" @@ -2162,20 +1769,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/01/0f/fab7ed5bf4926523c3b39f7621cea3e0da43f539fbc2270e042f1afccb79/pybase64-1.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bb082c1114f046e59fcbc4f2be13edc93b36d7b54b58605820605be948f8fdf6", size = 36131, upload-time = "2025-07-27T13:08:13.777Z" }, ] -[[package]] -name = "pyclipper" -version = "1.3.0.post6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4a/b2/550fe500e49c464d73fabcb8cb04d47e4885d6ca4cfc1f5b0a125a95b19a/pyclipper-1.3.0.post6.tar.gz", hash = "sha256:42bff0102fa7a7f2abdd795a2594654d62b786d0c6cd67b72d469114fdeb608c", size = 165909, upload-time = "2024-10-18T12:23:09.069Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/50/a9/66ca5f252dcac93ca076698591b838ba17f9729591edf4b74fef7fbe1414/pyclipper-1.3.0.post6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4247e7c44b34c87acbf38f99d48fb1acaf5da4a2cf4dcd601a9b24d431be4ef", size = 270930, upload-time = "2024-10-18T12:22:06.066Z" }, - { url = "https://files.pythonhosted.org/packages/59/fe/2ab5818b3504e179086e54a37ecc245525d069267b8c31b18ec3d0830cbf/pyclipper-1.3.0.post6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:851b3e58106c62a5534a1201295fe20c21714dee2eda68081b37ddb0367e6caa", size = 143411, upload-time = "2024-10-18T12:22:07.598Z" }, - { url = "https://files.pythonhosted.org/packages/09/f7/b58794f643e033a6d14da7c70f517315c3072f3c5fccdf4232fa8c8090c1/pyclipper-1.3.0.post6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16cc1705a915896d2aff52131c427df02265631279eac849ebda766432714cc0", size = 951754, upload-time = "2024-10-18T12:22:08.966Z" }, - { url = "https://files.pythonhosted.org/packages/c1/77/846a21957cd4ed266c36705ee340beaa923eb57d2bba013cfd7a5c417cfd/pyclipper-1.3.0.post6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace1f0753cf71c5c5f6488b8feef5dd0fa8b976ad86b24bb51f708f513df4aac", size = 969608, upload-time = "2024-10-18T12:22:10.321Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2b/580703daa6606d160caf596522d4cfdf62ae619b062a7ce6f905821a57e8/pyclipper-1.3.0.post6-cp311-cp311-win32.whl", hash = "sha256:dbc828641667142751b1127fd5c4291663490cf05689c85be4c5bcc89aaa236a", size = 100227, upload-time = "2024-10-18T12:22:11.991Z" }, - { url = "https://files.pythonhosted.org/packages/17/4b/a4cda18e8556d913ff75052585eb0d658500596b5f97fe8401d05123d47b/pyclipper-1.3.0.post6-cp311-cp311-win_amd64.whl", hash = "sha256:1c03f1ae43b18ee07730c3c774cc3cf88a10c12a4b097239b33365ec24a0a14a", size = 110442, upload-time = "2024-10-18T12:22:13.121Z" }, -] - [[package]] name = "pycparser" version = "2.22" @@ -2262,12 +1855,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] -[[package]] -name = "pylatexenc" -version = "2.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5d/ab/34ec41718af73c00119d0351b7a2531d2ebddb51833a36448fc7b862be60/pylatexenc-2.10.tar.gz", hash = "sha256:3dd8fd84eb46dc30bee1e23eaab8d8fb5a7f507347b23e5f38ad9675c84f40d3", size = 162597, upload-time = "2021-04-06T07:56:07.854Z" } - [[package]] name = "pymysql" version = "1.1.1" @@ -2343,28 +1930,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] -[[package]] -name = "python-bidi" -version = "0.6.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c4/de/1822200711beaadb2f334fa25f59ad9c2627de423c103dde7e81aedbc8e2/python_bidi-0.6.6.tar.gz", hash = "sha256:07db4c7da502593bd6e39c07b3a38733704070de0cbf92a7b7277b7be8867dd9", size = 45102, upload-time = "2025-02-18T21:43:05.598Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/03/b10c5c320fa5f3bc3d7736b2268179cc7f4dca4d054cdf2c932532d6b11a/python_bidi-0.6.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:da4949496e563b51f53ff34aad5a9f4c3aaf06f4180cf3bcb42bec649486c8f1", size = 269512, upload-time = "2025-02-18T21:42:03.267Z" }, - { url = "https://files.pythonhosted.org/packages/91/d8/8f6bd8f4662e8340e1aabb3b9a01fb1de24e8d1ce4f38b160f5cac2524f4/python_bidi-0.6.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c48a755ca8ba3f2b242d6795d4a60e83ca580cc4fa270a3aaa8af05d93b7ba7f", size = 264042, upload-time = "2025-02-18T21:41:50.298Z" }, - { url = "https://files.pythonhosted.org/packages/51/9f/2c831510ab8afb03b5ec4b15271dc547a2e8643563a7bcc712cd43b29d26/python_bidi-0.6.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76a1cd320993ba3e91a567e97f057a03f2c6b493096b3fff8b5630f51a38e7eb", size = 290963, upload-time = "2025-02-18T21:40:35.243Z" }, - { url = "https://files.pythonhosted.org/packages/95/45/17a76e7052d4d4bc1549ac2061f1fdebbaa9b7448ce81e774b7f77dc70b2/python_bidi-0.6.6-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e8bf3e396f9ebe8f4f81e92fa4c98c50160d60c58964b89c8ff4ee0c482befaa", size = 298639, upload-time = "2025-02-18T21:40:49.357Z" }, - { url = "https://files.pythonhosted.org/packages/00/11/fb5857168dcc50a2ebb2a5d8771a64b7fc66c19c9586b6f2a4d8a76db2e8/python_bidi-0.6.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2a49b506ed21f762ebf332de6de689bc4912e24dcc3b85f120b34e5f01e541a", size = 351898, upload-time = "2025-02-18T21:41:00.939Z" }, - { url = "https://files.pythonhosted.org/packages/18/e7/d25b3e767e204b9e236e7cb042bf709fd5a985cfede8c990da3bbca862a3/python_bidi-0.6.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3428331e7ce0d58c15b5a57e18a43a12e28f8733086066e6fd75b0ded80e1cae", size = 331117, upload-time = "2025-02-18T21:41:14.819Z" }, - { url = "https://files.pythonhosted.org/packages/75/50/248decd41096b4954c3887fc7fae864b8e1e90d28d1b4ce5a28c087c3d8d/python_bidi-0.6.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35adfb9fed3e72b9043a5c00b6ab69e4b33d53d2d8f8b9f60d4df700f77bc2c0", size = 292950, upload-time = "2025-02-18T21:41:38.53Z" }, - { url = "https://files.pythonhosted.org/packages/0b/d8/6ae7827fbba1403882930d4da8cbab28ab6b86b61a381c991074fb5003d1/python_bidi-0.6.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:589c5b24a8c4b5e07a1e97654020734bf16ed01a4353911ab663a37aaf1c281d", size = 307909, upload-time = "2025-02-18T21:41:28.221Z" }, - { url = "https://files.pythonhosted.org/packages/4c/a3/5b369c5da7b08b36907dcce7a78c730370ad6899459282f5e703ec1964c6/python_bidi-0.6.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:994534e47260d712c3b3291a6ab55b46cdbfd78a879ef95d14b27bceebfd4049", size = 465552, upload-time = "2025-02-18T21:42:16.157Z" }, - { url = "https://files.pythonhosted.org/packages/82/07/7779668967c0f17a107a916ec7891507b7bcdc9c7ee4d2c4b6a80ba1ac5e/python_bidi-0.6.6-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:00622f54a80826a918b22a2d6d5481bb3f669147e17bac85c81136b6ffbe7c06", size = 557371, upload-time = "2025-02-18T21:42:28.392Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e5/3154ac009a167bf0811195f12cf5e896c77a29243522b4b0697985881bc4/python_bidi-0.6.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:965e6f2182e7b9352f2d79221f6c49502a307a9778d7d87d82dc36bb1ffecbab", size = 485458, upload-time = "2025-02-18T21:42:41.465Z" }, - { url = "https://files.pythonhosted.org/packages/fd/db/88af6f0048d8ec7281b44b5599a3d2afa18fac5dd22eb72526f28f4ea647/python_bidi-0.6.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:53d7d3a550d176df99dd0bb0cc2da16b40634f11c8b9f5715777441d679c0a62", size = 459588, upload-time = "2025-02-18T21:42:53.483Z" }, - { url = "https://files.pythonhosted.org/packages/bb/d2/77b649c8b32c2b88e2facf5a42fb51dfdcc9e13db411c8bc84831ad64893/python_bidi-0.6.6-cp311-cp311-win32.whl", hash = "sha256:b271cd05cb40f47eb4600de79a8e47f8579d81ce35f5650b39b7860d018c3ece", size = 155683, upload-time = "2025-02-18T21:43:15.74Z" }, - { url = "https://files.pythonhosted.org/packages/95/41/d4dbc72b96e2eea3aeb9292707459372c8682ef039cd19fcac7e09d513ef/python_bidi-0.6.6-cp311-cp311-win_amd64.whl", hash = "sha256:4ff1eba0ff87e04bd35d7e164203ad6e5ce19f0bac0bdf673134c0b78d919608", size = 160587, upload-time = "2025-02-18T21:43:07.872Z" }, -] - [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -2377,19 +1942,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] -[[package]] -name = "python-docx" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "lxml" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a9/f7/eddfe33871520adab45aaa1a71f0402a2252050c14c7e3009446c8f4701c/python_docx-1.2.0.tar.gz", hash = "sha256:7bc9d7b7d8a69c9c02ca09216118c86552704edc23bac179283f2e38f86220ce", size = 5723256, upload-time = "2025-06-16T20:46:27.921Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/00/1e03a4989fa5795da308cd774f05b704ace555a70f9bf9d3be057b680bcf/python_docx-1.2.0-py3-none-any.whl", hash = "sha256:3fd478f3250fbbbfd3b94fe1e985955737c145627498896a8a6bf81f4baf66c7", size = 252987, upload-time = "2025-06-16T20:46:22.506Z" }, -] - [[package]] name = "python-dotenv" version = "1.1.1" @@ -2423,15 +1975,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/4f/00be2196329ebbff56ce564aa94efb0fbc828d00de250b1980de1a34ab49/python_pptx-1.0.2-py3-none-any.whl", hash = "sha256:160838e0b8565a8b1f67947675886e9fea18aa5e795db7ae531606d68e785cba", size = 472788, upload-time = "2024-08-07T17:33:28.192Z" }, ] -[[package]] -name = "pytz" -version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, -] - [[package]] name = "pywin32" version = "311" @@ -2664,108 +2207,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, ] -[[package]] -name = "rtree" -version = "1.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/b8/0091f020acafcb034daa5b062f0626f6a73c7e0d64826af23861390a9585/rtree-1.4.0.tar.gz", hash = "sha256:9d97c7c5dcf25f6c0599c76d9933368c6a8d7238f2c1d00e76f1a69369ca82a0", size = 50789, upload-time = "2025-03-05T23:31:45.962Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/4c/8d54d6dc5ff8ba8ced1fad9378f89f9dd60addcc4cf0e525ee0e67b1769f/rtree-1.4.0-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:4d1bebc418101480aabf41767e772dd2155d3b27b1376cccbd93e4509485e091", size = 482755, upload-time = "2025-03-05T23:31:29.884Z" }, - { url = "https://files.pythonhosted.org/packages/20/29/045e700d2135e9a67896086c831fde80fd4105971b443d5727a4093fcbf1/rtree-1.4.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:997f8c38d5dffa3949ea8adb4c8b291ea5cd4ef5ee69455d642dd171baf9991d", size = 439796, upload-time = "2025-03-05T23:31:31.517Z" }, - { url = "https://files.pythonhosted.org/packages/3d/fc/c3bd8cd67b10a12a6b9e2d06796779128c3e6968922dbf29fcd53af68d81/rtree-1.4.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0133d9c54ab3ffe874ba6d411dbe0254765c5e68d92da5b91362c370f16fd997", size = 497549, upload-time = "2025-03-05T23:31:33.722Z" }, - { url = "https://files.pythonhosted.org/packages/a0/dd/49dc9ab037d0cb288ed40f8b7f498f69d44243e4745e241c05d5e457ea8b/rtree-1.4.0-py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:d3b7bf1fe6463139377995ebe22a01a7005d134707f43672a3c09305e12f5f43", size = 568787, upload-time = "2025-03-05T23:31:35.478Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e7/57737dff73ce789bdadd916d48ac12e977d8578176e1e890b1b8d89b9dbf/rtree-1.4.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:27e4a6d617d63dcb82fcd4c2856134b8a3741bd1af3b1a0d98e886054f394da5", size = 541090, upload-time = "2025-03-05T23:31:37.712Z" }, - { url = "https://files.pythonhosted.org/packages/8e/8f/1f3f716c4e8388670cfd5d0a3578e2354a1e6a3403648e234e1540e3e3bd/rtree-1.4.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5258e826064eab82439760201e9421ce6d4340789d6d080c1b49367ddd03f61f", size = 1454194, upload-time = "2025-03-05T23:31:39.851Z" }, - { url = "https://files.pythonhosted.org/packages/22/ec/b42052b10e63a1c5d5d61ce234332f689736053644ba1756f7a632ea7659/rtree-1.4.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:20d5b3f9cf8bbbcc9fec42ab837c603c5dd86103ef29134300c8da2495c1248b", size = 1692814, upload-time = "2025-03-05T23:31:41.617Z" }, - { url = "https://files.pythonhosted.org/packages/c5/5b/a9920e9a2dc43b066ff13b7fde2e7bffcca315cfa43ae6f4cc15970e39eb/rtree-1.4.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a67bee1233370a4c72c0969a96d2a1df1ba404ddd9f146849c53ab420eab361b", size = 1554860, upload-time = "2025-03-05T23:31:43.091Z" }, - { url = "https://files.pythonhosted.org/packages/ce/c2/362f2cc36a7a57b47380061c23fc109c7222c1a544ffd24cda289ba19673/rtree-1.4.0-py3-none-win_amd64.whl", hash = "sha256:ba83efc7b7563905b1bfdfc14490c4bfb59e92e5e6156bdeb6ec5df5117252f4", size = 385221, upload-time = "2025-03-05T23:31:44.537Z" }, -] - -[[package]] -name = "safetensors" -version = "0.5.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/71/7e/2d5d6ee7b40c0682315367ec7475693d110f512922d582fef1bd4a63adc3/safetensors-0.5.3.tar.gz", hash = "sha256:b6b0d6ecacec39a4fdd99cc19f4576f5219ce858e6fd8dbe7609df0b8dc56965", size = 67210, upload-time = "2025-02-26T09:15:13.155Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/18/ae/88f6c49dbd0cc4da0e08610019a3c78a7d390879a919411a410a1876d03a/safetensors-0.5.3-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bd20eb133db8ed15b40110b7c00c6df51655a2998132193de2f75f72d99c7073", size = 436917, upload-time = "2025-02-26T09:15:03.702Z" }, - { url = "https://files.pythonhosted.org/packages/b8/3b/11f1b4a2f5d2ab7da34ecc062b0bc301f2be024d110a6466726bec8c055c/safetensors-0.5.3-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:21d01c14ff6c415c485616b8b0bf961c46b3b343ca59110d38d744e577f9cce7", size = 418419, upload-time = "2025-02-26T09:15:01.765Z" }, - { url = "https://files.pythonhosted.org/packages/5d/9a/add3e6fef267658075c5a41573c26d42d80c935cdc992384dfae435feaef/safetensors-0.5.3-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11bce6164887cd491ca75c2326a113ba934be596e22b28b1742ce27b1d076467", size = 459493, upload-time = "2025-02-26T09:14:51.812Z" }, - { url = "https://files.pythonhosted.org/packages/df/5c/bf2cae92222513cc23b3ff85c4a1bb2811a2c3583ac0f8e8d502751de934/safetensors-0.5.3-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a243be3590bc3301c821da7a18d87224ef35cbd3e5f5727e4e0728b8172411e", size = 472400, upload-time = "2025-02-26T09:14:53.549Z" }, - { url = "https://files.pythonhosted.org/packages/58/11/7456afb740bd45782d0f4c8e8e1bb9e572f1bf82899fb6ace58af47b4282/safetensors-0.5.3-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8bd84b12b1670a6f8e50f01e28156422a2bc07fb16fc4e98bded13039d688a0d", size = 522891, upload-time = "2025-02-26T09:14:55.717Z" }, - { url = "https://files.pythonhosted.org/packages/57/3d/fe73a9d2ace487e7285f6e157afee2383bd1ddb911b7cb44a55cf812eae3/safetensors-0.5.3-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:391ac8cab7c829452175f871fcaf414aa1e292b5448bd02620f675a7f3e7abb9", size = 537694, upload-time = "2025-02-26T09:14:57.036Z" }, - { url = "https://files.pythonhosted.org/packages/a6/f8/dae3421624fcc87a89d42e1898a798bc7ff72c61f38973a65d60df8f124c/safetensors-0.5.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cead1fa41fc54b1e61089fa57452e8834f798cb1dc7a09ba3524f1eb08e0317a", size = 471642, upload-time = "2025-02-26T09:15:00.544Z" }, - { url = "https://files.pythonhosted.org/packages/ce/20/1fbe16f9b815f6c5a672f5b760951e20e17e43f67f231428f871909a37f6/safetensors-0.5.3-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1077f3e94182d72618357b04b5ced540ceb71c8a813d3319f1aba448e68a770d", size = 502241, upload-time = "2025-02-26T09:14:58.303Z" }, - { url = "https://files.pythonhosted.org/packages/5f/18/8e108846b506487aa4629fe4116b27db65c3dde922de2c8e0cc1133f3f29/safetensors-0.5.3-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:799021e78287bac619c7b3f3606730a22da4cda27759ddf55d37c8db7511c74b", size = 638001, upload-time = "2025-02-26T09:15:05.79Z" }, - { url = "https://files.pythonhosted.org/packages/82/5a/c116111d8291af6c8c8a8b40628fe833b9db97d8141c2a82359d14d9e078/safetensors-0.5.3-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:df26da01aaac504334644e1b7642fa000bfec820e7cef83aeac4e355e03195ff", size = 734013, upload-time = "2025-02-26T09:15:07.892Z" }, - { url = "https://files.pythonhosted.org/packages/7d/ff/41fcc4d3b7de837963622e8610d998710705bbde9a8a17221d85e5d0baad/safetensors-0.5.3-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:32c3ef2d7af8b9f52ff685ed0bc43913cdcde135089ae322ee576de93eae5135", size = 670687, upload-time = "2025-02-26T09:15:09.979Z" }, - { url = "https://files.pythonhosted.org/packages/40/ad/2b113098e69c985a3d8fbda4b902778eae4a35b7d5188859b4a63d30c161/safetensors-0.5.3-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:37f1521be045e56fc2b54c606d4455573e717b2d887c579ee1dbba5f868ece04", size = 643147, upload-time = "2025-02-26T09:15:11.185Z" }, - { url = "https://files.pythonhosted.org/packages/0a/0c/95aeb51d4246bd9a3242d3d8349c1112b4ee7611a4b40f0c5c93b05f001d/safetensors-0.5.3-cp38-abi3-win32.whl", hash = "sha256:cfc0ec0846dcf6763b0ed3d1846ff36008c6e7290683b61616c4b040f6a54ace", size = 296677, upload-time = "2025-02-26T09:15:16.554Z" }, - { url = "https://files.pythonhosted.org/packages/69/e2/b011c38e5394c4c18fb5500778a55ec43ad6106126e74723ffaee246f56e/safetensors-0.5.3-cp38-abi3-win_amd64.whl", hash = "sha256:836cbbc320b47e80acd40e44c8682db0e8ad7123209f69b093def21ec7cafd11", size = 308878, upload-time = "2025-02-26T09:15:14.99Z" }, -] - -[package.optional-dependencies] -torch = [ - { name = "numpy" }, - { name = "torch", version = "2.7.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform != 'darwin'" }, -] - -[[package]] -name = "scikit-image" -version = "0.25.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "imageio" }, - { name = "lazy-loader" }, - { name = "networkx" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "pillow" }, - { name = "scipy" }, - { name = "tifffile" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c7/a8/3c0f256012b93dd2cb6fda9245e9f4bff7dc0486880b248005f15ea2255e/scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde", size = 22693594, upload-time = "2025-02-18T18:05:24.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/97/3051c68b782ee3f1fb7f8f5bb7d535cf8cb92e8aae18fa9c1cdf7e15150d/scikit_image-0.25.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f4bac9196fb80d37567316581c6060763b0f4893d3aca34a9ede3825bc035b17", size = 14003057, upload-time = "2025-02-18T18:04:30.395Z" }, - { url = "https://files.pythonhosted.org/packages/19/23/257fc696c562639826065514d551b7b9b969520bd902c3a8e2fcff5b9e17/scikit_image-0.25.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:d989d64ff92e0c6c0f2018c7495a5b20e2451839299a018e0e5108b2680f71e0", size = 13180335, upload-time = "2025-02-18T18:04:33.449Z" }, - { url = "https://files.pythonhosted.org/packages/ef/14/0c4a02cb27ca8b1e836886b9ec7c9149de03053650e9e2ed0625f248dd92/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2cfc96b27afe9a05bc92f8c6235321d3a66499995675b27415e0d0c76625173", size = 14144783, upload-time = "2025-02-18T18:04:36.594Z" }, - { url = "https://files.pythonhosted.org/packages/dd/9b/9fb556463a34d9842491d72a421942c8baff4281025859c84fcdb5e7e602/scikit_image-0.25.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24cc986e1f4187a12aa319f777b36008764e856e5013666a4a83f8df083c2641", size = 14785376, upload-time = "2025-02-18T18:04:39.856Z" }, - { url = "https://files.pythonhosted.org/packages/de/ec/b57c500ee85885df5f2188f8bb70398481393a69de44a00d6f1d055f103c/scikit_image-0.25.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4f6b61fc2db6340696afe3db6b26e0356911529f5f6aee8c322aa5157490c9b", size = 12791698, upload-time = "2025-02-18T18:04:42.868Z" }, -] - -[[package]] -name = "scipy" -version = "1.16.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f5/4a/b927028464795439faec8eaf0b03b011005c487bb2d07409f28bf30879c4/scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3", size = 30580861, upload-time = "2025-07-27T16:33:30.834Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/91/812adc6f74409b461e3a5fa97f4f74c769016919203138a3bf6fc24ba4c5/scipy-1.16.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:c033fa32bab91dc98ca59d0cf23bb876454e2bb02cbe592d5023138778f70030", size = 36552519, upload-time = "2025-07-27T16:26:29.658Z" }, - { url = "https://files.pythonhosted.org/packages/47/18/8e355edcf3b71418d9e9f9acd2708cc3a6c27e8f98fde0ac34b8a0b45407/scipy-1.16.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6e5c2f74e5df33479b5cd4e97a9104c511518fbd979aa9b8f6aec18b2e9ecae7", size = 28638010, upload-time = "2025-07-27T16:26:38.196Z" }, - { url = "https://files.pythonhosted.org/packages/d9/eb/e931853058607bdfbc11b86df19ae7a08686121c203483f62f1ecae5989c/scipy-1.16.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0a55ffe0ba0f59666e90951971a884d1ff6f4ec3275a48f472cfb64175570f77", size = 20909790, upload-time = "2025-07-27T16:26:43.93Z" }, - { url = "https://files.pythonhosted.org/packages/45/0c/be83a271d6e96750cd0be2e000f35ff18880a46f05ce8b5d3465dc0f7a2a/scipy-1.16.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f8a5d6cd147acecc2603fbd382fed6c46f474cccfcf69ea32582e033fb54dcfe", size = 23513352, upload-time = "2025-07-27T16:26:50.017Z" }, - { url = "https://files.pythonhosted.org/packages/7c/bf/fe6eb47e74f762f933cca962db7f2c7183acfdc4483bd1c3813cfe83e538/scipy-1.16.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb18899127278058bcc09e7b9966d41a5a43740b5bb8dcba401bd983f82e885b", size = 33534643, upload-time = "2025-07-27T16:26:57.503Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ba/63f402e74875486b87ec6506a4f93f6d8a0d94d10467280f3d9d7837ce3a/scipy-1.16.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adccd93a2fa937a27aae826d33e3bfa5edf9aa672376a4852d23a7cd67a2e5b7", size = 35376776, upload-time = "2025-07-27T16:27:06.639Z" }, - { url = "https://files.pythonhosted.org/packages/c3/b4/04eb9d39ec26a1b939689102da23d505ea16cdae3dbb18ffc53d1f831044/scipy-1.16.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:18aca1646a29ee9a0625a1be5637fa798d4d81fdf426481f06d69af828f16958", size = 35698906, upload-time = "2025-07-27T16:27:14.943Z" }, - { url = "https://files.pythonhosted.org/packages/04/d6/bb5468da53321baeb001f6e4e0d9049eadd175a4a497709939128556e3ec/scipy-1.16.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d85495cef541729a70cdddbbf3e6b903421bc1af3e8e3a9a72a06751f33b7c39", size = 38129275, upload-time = "2025-07-27T16:27:23.873Z" }, - { url = "https://files.pythonhosted.org/packages/c4/94/994369978509f227cba7dfb9e623254d0d5559506fe994aef4bea3ed469c/scipy-1.16.1-cp311-cp311-win_amd64.whl", hash = "sha256:226652fca853008119c03a8ce71ffe1b3f6d2844cc1686e8f9806edafae68596", size = 38644572, upload-time = "2025-07-27T16:27:32.637Z" }, -] - -[[package]] -name = "semchunk" -version = "2.2.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mpire", extra = ["dill"] }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/62/96/c418c322730b385e81d4ab462e68dd48bb2dbda4d8efa17cad2ca468d9ac/semchunk-2.2.2.tar.gz", hash = "sha256:940e89896e64eeb01de97ba60f51c8c7b96c6a3951dfcf574f25ce2146752f52", size = 12271, upload-time = "2024-12-17T22:54:30.332Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/84/94ca7896c7df20032bcb09973e9a4d14c222507c0aadf22e89fa76bb0a04/semchunk-2.2.2-py3-none-any.whl", hash = "sha256:94ca19020c013c073abdfd06d79a7c13637b91738335f3b8cdb5655ee7cc94d2", size = 10271, upload-time = "2024-12-17T22:54:27.689Z" }, -] - [[package]] name = "sentry-sdk" version = "2.34.1" @@ -2779,25 +2220,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2d/3e/bb34de65a5787f76848a533afbb6610e01fbcdd59e76d8679c254e02255c/sentry_sdk-2.34.1-py2.py3-none-any.whl", hash = "sha256:b7a072e1cdc5abc48101d5146e1ae680fa81fe886d8d95aaa25a0b450c818d32", size = 357743, upload-time = "2025-07-30T11:13:36.145Z" }, ] -[[package]] -name = "shapely" -version = "2.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ca/3c/2da625233f4e605155926566c0e7ea8dda361877f48e8b1655e53456f252/shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772", size = 315422, upload-time = "2025-05-19T11:04:41.265Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/19/97/2df985b1e03f90c503796ad5ecd3d9ed305123b64d4ccb54616b30295b29/shapely-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587a1aa72bc858fab9b8c20427b5f6027b7cbc92743b8e2c73b9de55aa71c7a7", size = 1819368, upload-time = "2025-05-19T11:03:55.937Z" }, - { url = "https://files.pythonhosted.org/packages/56/17/504518860370f0a28908b18864f43d72f03581e2b6680540ca668f07aa42/shapely-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9fa5c53b0791a4b998f9ad84aad456c988600757a96b0a05e14bba10cebaaaea", size = 1625362, upload-time = "2025-05-19T11:03:57.06Z" }, - { url = "https://files.pythonhosted.org/packages/36/a1/9677337d729b79fce1ef3296aac6b8ef4743419086f669e8a8070eff8f40/shapely-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aabecd038841ab5310d23495253f01c2a82a3aedae5ab9ca489be214aa458aa7", size = 2999005, upload-time = "2025-05-19T11:03:58.692Z" }, - { url = "https://files.pythonhosted.org/packages/a2/17/e09357274699c6e012bbb5a8ea14765a4d5860bb658df1931c9f90d53bd3/shapely-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586f6aee1edec04e16227517a866df3e9a2e43c1f635efc32978bb3dc9c63753", size = 3108489, upload-time = "2025-05-19T11:04:00.059Z" }, - { url = "https://files.pythonhosted.org/packages/17/5d/93a6c37c4b4e9955ad40834f42b17260ca74ecf36df2e81bb14d12221b90/shapely-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b9878b9e37ad26c72aada8de0c9cfe418d9e2ff36992a1693b7f65a075b28647", size = 3945727, upload-time = "2025-05-19T11:04:01.786Z" }, - { url = "https://files.pythonhosted.org/packages/a3/1a/ad696648f16fd82dd6bfcca0b3b8fbafa7aacc13431c7fc4c9b49e481681/shapely-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9a531c48f289ba355e37b134e98e28c557ff13965d4653a5228d0f42a09aed0", size = 4109311, upload-time = "2025-05-19T11:04:03.134Z" }, - { url = "https://files.pythonhosted.org/packages/d4/38/150dd245beab179ec0d4472bf6799bf18f21b1efbef59ac87de3377dbf1c/shapely-2.1.1-cp311-cp311-win32.whl", hash = "sha256:4866de2673a971820c75c0167b1f1cd8fb76f2d641101c23d3ca021ad0449bab", size = 1522982, upload-time = "2025-05-19T11:04:05.217Z" }, - { url = "https://files.pythonhosted.org/packages/93/5b/842022c00fbb051083c1c85430f3bb55565b7fd2d775f4f398c0ba8052ce/shapely-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:20a9d79958b3d6c70d8a886b250047ea32ff40489d7abb47d01498c704557a93", size = 1703872, upload-time = "2025-05-19T11:04:06.791Z" }, -] - [[package]] name = "shellingham" version = "1.5.4" @@ -2825,15 +2247,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, ] -[[package]] -name = "soupsieve" -version = "2.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3f/f4/4a80cd6ef364b2e8b65b15816a843c0980f7a5a2b4dc701fc574952aa19f/soupsieve-2.7.tar.gz", hash = "sha256:ad282f9b6926286d2ead4750552c8a6142bc4c783fd66b0293547c8fe6ae126a", size = 103418, upload-time = "2025-04-20T18:50:08.518Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/9c/0e6afc12c269578be5c0c1c9f4b49a8d32770a080260c333ac04cc1c832d/soupsieve-2.7-py3-none-any.whl", hash = "sha256:6e60cc5c1ffaf1cebcc12e8188320b72071e922c2e897f737cadce79ad5d30c4", size = 36677, upload-time = "2025-04-20T18:50:07.196Z" }, -] - [[package]] name = "sqlalchemy" version = "2.0.42" @@ -2896,22 +2309,13 @@ wheels = [ [[package]] name = "sympy" version = "1.14.0" -source = { registry = "https://download.pytorch.org/whl/cpu" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mpmath" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517" } +sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5" }, -] - -[[package]] -name = "tabulate" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, + { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, ] [[package]] @@ -2923,18 +2327,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/3f/8ba87d9e287b9d385a02a7114ddcef61b26f86411e121c9003eb509a1773/tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687", size = 28165, upload-time = "2024-07-05T07:25:29.591Z" }, ] -[[package]] -name = "tifffile" -version = "2025.6.11" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/11/9e/636e3e433c24da41dd639e0520db60750dbf5e938d023b83af8097382ea3/tifffile-2025.6.11.tar.gz", hash = "sha256:0ece4c2e7a10656957d568a093b07513c0728d30c1bd8cc12725901fffdb7143", size = 370125, upload-time = "2025-06-12T04:49:38.839Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/d8/1ba8f32bfc9cb69e37edeca93738e883f478fbe84ae401f72c0d8d507841/tifffile-2025.6.11-py3-none-any.whl", hash = "sha256:32effb78b10b3a283eb92d4ebf844ae7e93e151458b0412f38518b4e6d2d7542", size = 230800, upload-time = "2025-06-12T04:49:37.458Z" }, -] - [[package]] name = "tokenizers" version = "0.21.4" @@ -2960,83 +2352,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/41/f2/fd673d979185f5dcbac4be7d09461cbb99751554ffb6718d0013af8604cb/tokenizers-0.21.4-cp39-abi3-win_amd64.whl", hash = "sha256:475d807a5c3eb72c59ad9b5fcdb254f6e17f53dfcbb9903233b0dfa9c943b597", size = 2507568, upload-time = "2025-07-28T15:48:55.456Z" }, ] -[[package]] -name = "torch" -version = "2.7.1" -source = { registry = "https://download.pytorch.org/whl/cpu" } -resolution-markers = [ - "sys_platform == 'darwin'", -] -dependencies = [ - { name = "filelock", marker = "sys_platform == 'darwin'" }, - { name = "fsspec", marker = "sys_platform == 'darwin'" }, - { name = "jinja2", marker = "sys_platform == 'darwin'" }, - { name = "networkx", marker = "sys_platform == 'darwin'" }, - { name = "sympy", marker = "sys_platform == 'darwin'" }, - { name = "typing-extensions", marker = "sys_platform == 'darwin'" }, -] -wheels = [ - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.7.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:68a352c7f435abb5cb47e2c032dcd1012772ae2bacb6fc8b83b0c1b11874ab3a" }, -] - -[[package]] -name = "torch" -version = "2.7.1+cpu" -source = { registry = "https://download.pytorch.org/whl/cpu" } -resolution-markers = [ - "platform_machine == 'aarch64' and sys_platform == 'linux'", - "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')", -] -dependencies = [ - { name = "filelock", marker = "sys_platform != 'darwin'" }, - { name = "fsspec", marker = "sys_platform != 'darwin'" }, - { name = "jinja2", marker = "sys_platform != 'darwin'" }, - { name = "networkx", marker = "sys_platform != 'darwin'" }, - { name = "sympy", marker = "sys_platform != 'darwin'" }, - { name = "typing-extensions", marker = "sys_platform != 'darwin'" }, -] -wheels = [ - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.7.1%2Bcpu-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5fe6045b8f426bf2d0426e4fe009f1667a954ec2aeb82f1bd0bf60c6d7a85445" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.7.1%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a1684793e352f03fa14f78857e55d65de4ada8405ded1da2bf4f452179c4b779" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torch-2.7.1%2Bcpu-cp311-cp311-win_amd64.whl", hash = "sha256:7b977eccbc85ae2bd19d6998de7b1f1f4bd3c04eaffd3015deb7934389783399" }, -] - -[[package]] -name = "torchvision" -version = "0.22.1" -source = { registry = "https://download.pytorch.org/whl/cpu" } -resolution-markers = [ - "platform_machine == 'aarch64' and sys_platform == 'linux'", - "sys_platform == 'darwin'", -] -dependencies = [ - { name = "numpy", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, - { name = "pillow", marker = "(platform_machine == 'aarch64' and sys_platform == 'linux') or sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "sys_platform == 'darwin'" }, - { name = "torch", version = "2.7.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "platform_machine == 'aarch64' and sys_platform == 'linux'" }, -] -wheels = [ - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.22.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4addf626e2b57fc22fd6d329cf1346d474497672e6af8383b7b5b636fba94a53" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.22.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:8b4a53a6067d63adba0c52f2b8dd2290db649d642021674ee43c0c922f0c6a69" }, -] - -[[package]] -name = "torchvision" -version = "0.22.1+cpu" -source = { registry = "https://download.pytorch.org/whl/cpu" } -resolution-markers = [ - "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')", -] -dependencies = [ - { name = "numpy", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, - { name = "pillow", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, - { name = "torch", version = "2.7.1+cpu", source = { registry = "https://download.pytorch.org/whl/cpu" }, marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" }, -] -wheels = [ - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.22.1%2Bcpu-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:4e0cbc165a472605d0c13da68ae22e84b17a6b815d5e600834777823e1bcb658" }, - { url = "https://download-r2.pytorch.org/whl/cpu/torchvision-0.22.1%2Bcpu-cp311-cp311-win_amd64.whl", hash = "sha256:9482adee074f60a45fd69892f7488281aadfda7836948c94b0a9b0caf55d1d67" }, -] - [[package]] name = "tqdm" version = "4.67.1" @@ -3049,27 +2364,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, ] -[[package]] -name = "transformers" -version = "4.54.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "filelock" }, - { name = "huggingface-hub" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "pyyaml" }, - { name = "regex" }, - { name = "requests" }, - { name = "safetensors" }, - { name = "tokenizers" }, - { name = "tqdm" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/21/6c/4caeb57926f91d943f309b062e22ad1eb24a9f530421c5a65c1d89378a7a/transformers-4.54.1.tar.gz", hash = "sha256:b2551bb97903f13bd90c9467d0a144d41ca4d142defc044a99502bb77c5c1052", size = 9514288, upload-time = "2025-07-29T15:57:22.826Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/18/eb7578f84ef5a080d4e5ca9bc4f7c68e7aa9c1e464f1b3d3001e4c642fce/transformers-4.54.1-py3-none-any.whl", hash = "sha256:c89965a4f62a0d07009d45927a9c6372848a02ab9ead9c318c3d082708bab529", size = 11176397, upload-time = "2025-07-29T15:57:19.692Z" }, -] - [[package]] name = "typer" version = "0.16.0" @@ -3106,15 +2400,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552, upload-time = "2025-05-21T18:55:22.152Z" }, ] -[[package]] -name = "tzdata" -version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, -] - [[package]] name = "urllib3" version = "2.5.0" diff --git a/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx b/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx index 31bc11ea..1ad7983a 100644 --- a/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx +++ b/servers/nextjs/app/(presentation-generator)/(dashboard)/settings/ImageProvider.tsx @@ -60,7 +60,7 @@ const ImageProvider = ({ llmConfig, setLlmConfig }: { llmConfig: LLMConfig, setL DALL·E 3 Image Quality
- input_field_changed(value, "DALL_E_3_QUALITY")}> @@ -84,7 +84,7 @@ const ImageProvider = ({ llmConfig, setLlmConfig }: { llmConfig: LLMConfig, setL