refactor: reduces images size to around 2.5gb
This commit is contained in:
parent
ba5d51ad76
commit
501a155ad0
11 changed files with 248 additions and 158 deletions
|
|
@ -1,6 +1,39 @@
|
|||
.git
|
||||
.gitignore
|
||||
|
||||
node_modules
|
||||
**/node_modules
|
||||
**/.next
|
||||
**/.next-build
|
||||
**/.venv
|
||||
**/.pytest_cache
|
||||
**/__pycache__
|
||||
**/*.pyc
|
||||
**/debug
|
||||
**/fastembed_cache
|
||||
**/chroma
|
||||
.cache
|
||||
app_data
|
||||
presentation-export
|
||||
servers/fastapi/tests
|
||||
servers/fastapi/presenton_backend.egg-info
|
||||
servers/fastapi/.python-version
|
||||
servers/fastapi/placeholder
|
||||
servers/nextjs/cypress
|
||||
servers/nextjs/cypress.config.ts
|
||||
servers/nextjs/README.md
|
||||
servers/nextjs/tsconfig.tsbuildinfo
|
||||
**/*.cy.ts
|
||||
**/*.cy.tsx
|
||||
|
||||
# Keep only the LiteParse runner from the Electron tree.
|
||||
electron/*
|
||||
!electron/resources
|
||||
electron/resources/*
|
||||
!electron/resources/document-extraction
|
||||
electron/resources/document-extraction/*
|
||||
!electron/resources/document-extraction/liteparse_runner.mjs
|
||||
|
||||
servers/fastapi/tmp
|
||||
servers/fastapi/debug
|
||||
servers/fastapi/.venv
|
||||
|
|
@ -8,4 +41,4 @@ servers/fastapi/.venv
|
|||
servers/nextjs/node_modules
|
||||
servers/nextjs/.next
|
||||
|
||||
container.db
|
||||
container.db
|
||||
|
|
|
|||
130
Dockerfile
130
Dockerfile
|
|
@ -1,60 +1,108 @@
|
|||
# syntax=docker/dockerfile:1.4
|
||||
FROM python:3.11-slim-trixie
|
||||
# syntax=docker/dockerfile:1.7
|
||||
|
||||
FROM python:3.11-slim-trixie AS fastapi-builder
|
||||
|
||||
WORKDIR /app/servers/fastapi
|
||||
|
||||
ENV UV_COMPILE_BYTECODE=1 \
|
||||
UV_LINK_MODE=copy
|
||||
|
||||
RUN python -m venv --without-pip /opt/venv \
|
||||
&& pip install --no-cache-dir uv
|
||||
|
||||
COPY servers/fastapi/pyproject.toml servers/fastapi/uv.lock ./
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv export --frozen --no-dev --no-emit-project -o /tmp/requirements.txt \
|
||||
&& uv pip install --python /opt/venv/bin/python -r /tmp/requirements.txt
|
||||
|
||||
COPY servers/fastapi /app/servers/fastapi
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv pip install --python /opt/venv/bin/python --no-deps .
|
||||
RUN --mount=type=cache,target=/root/.cache \
|
||||
/opt/venv/bin/python scripts/warm_fastembed_cache.py
|
||||
|
||||
|
||||
FROM node:20-bookworm-slim AS nextjs-builder
|
||||
|
||||
WORKDIR /app/servers/nextjs
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED=1 \
|
||||
PUPPETEER_SKIP_DOWNLOAD=true
|
||||
|
||||
COPY servers/nextjs/package.json servers/nextjs/package-lock.json ./
|
||||
RUN --mount=type=cache,target=/root/.npm \
|
||||
npm ci
|
||||
|
||||
COPY servers/nextjs /app/servers/nextjs
|
||||
RUN npm run build \
|
||||
&& rm -rf .next-build/cache
|
||||
|
||||
|
||||
FROM node:20-bookworm-slim AS assets-builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
ca-certificates unzip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY package.json /app/
|
||||
|
||||
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 scripts/sync-presentation-export.cjs /app/scripts/sync-presentation-export.cjs
|
||||
RUN node /app/scripts/sync-presentation-export.cjs --force \
|
||||
&& chmod +x /app/presentation-export/py/convert-linux-x64
|
||||
|
||||
|
||||
FROM python:3.11-slim-trixie AS runtime
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ARG INSTALL_CHROMIUM=true
|
||||
ARG INSTALL_TESSERACT=true
|
||||
ARG INSTALL_LIBREOFFICE=true
|
||||
|
||||
# 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 \
|
||||
PATH="/root/.local/bin:${PATH}" \
|
||||
EXPORT_PACKAGE_ROOT=/app/presentation-export \
|
||||
EXPORT_RUNTIME_DIR=/app/presentation-export \
|
||||
BUILT_PYTHON_MODULE_PATH=/app/presentation-export/py/convert-linux-x64 \
|
||||
PRESENTON_APP_ROOT=/app
|
||||
PRESENTON_APP_ROOT=/app \
|
||||
PATH="/opt/venv/bin:${PATH}" \
|
||||
NODE_ENV=production \
|
||||
START_OLLAMA=false
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
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/*
|
||||
RUN set -eux; \
|
||||
packages="ca-certificates curl nginx fontconfig imagemagick zstd"; \
|
||||
if [ "$INSTALL_LIBREOFFICE" = "true" ]; then packages="$packages libreoffice"; fi; \
|
||||
if [ "$INSTALL_CHROMIUM" = "true" ]; then packages="$packages chromium"; fi; \
|
||||
if [ "$INSTALL_TESSERACT" = "true" ]; then packages="$packages tesseract-ocr tesseract-ocr-eng"; fi; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends $packages; \
|
||||
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -; \
|
||||
apt-get install -y --no-install-recommends nodejs; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
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/scripts /app/servers/fastapi /app/servers/nextjs
|
||||
|
||||
COPY package.json package-lock.json /app/
|
||||
RUN npm --prefix /app install --omit=dev
|
||||
COPY --from=fastapi-builder /opt/venv /opt/venv
|
||||
COPY --from=fastapi-builder /app/servers/fastapi /app/servers/fastapi
|
||||
|
||||
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 --from=assets-builder /app/package.json /app/package.json
|
||||
COPY --from=assets-builder /app/document-extraction-liteparse /app/document-extraction-liteparse
|
||||
COPY --from=assets-builder /app/presentation-export /app/presentation-export
|
||||
COPY --from=assets-builder /app/scripts/sync-presentation-export.cjs /app/scripts/sync-presentation-export.cjs
|
||||
|
||||
COPY scripts/sync-presentation-export.cjs /app/scripts/sync-presentation-export.cjs
|
||||
RUN node /app/scripts/sync-presentation-export.cjs --force \
|
||||
&& chmod +x /app/presentation-export/py/convert-linux-x64
|
||||
COPY --from=nextjs-builder /app/servers/nextjs/.next-build/standalone/ /app/servers/nextjs/
|
||||
COPY --from=nextjs-builder /app/servers/nextjs/public /app/servers/nextjs/public
|
||||
COPY --from=nextjs-builder /app/servers/nextjs/.next-build/static /app/servers/nextjs/.next-build/static
|
||||
|
||||
RUN curl -fsSL https://ollama.com/install.sh | sh
|
||||
|
||||
COPY servers/fastapi /app/servers/fastapi
|
||||
WORKDIR /app/servers/fastapi
|
||||
RUN --mount=type=cache,target=/root/.cache/uv \
|
||||
uv export --frozen --no-dev --no-emit-project -o /tmp/requirements.txt \
|
||||
&& uv pip install --system -r /tmp/requirements.txt \
|
||||
&& uv pip install --system --no-deps .
|
||||
|
||||
WORKDIR /app/servers/nextjs
|
||||
COPY servers/nextjs/package.json servers/nextjs/package-lock.json ./
|
||||
RUN npm install
|
||||
COPY servers/nextjs/ /app/servers/nextjs/
|
||||
RUN npm run build
|
||||
|
||||
WORKDIR /app
|
||||
COPY start.js LICENSE NOTICE ./
|
||||
COPY nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ services:
|
|||
- ANTHROPIC_MODEL=${ANTHROPIC_MODEL}
|
||||
- OLLAMA_URL=${OLLAMA_URL}
|
||||
- OLLAMA_MODEL=${OLLAMA_MODEL}
|
||||
- START_OLLAMA=${START_OLLAMA:-false}
|
||||
- CUSTOM_LLM_URL=${CUSTOM_LLM_URL}
|
||||
- CUSTOM_LLM_API_KEY=${CUSTOM_LLM_API_KEY}
|
||||
- CUSTOM_MODEL=${CUSTOM_MODEL}
|
||||
|
|
@ -77,6 +78,7 @@ services:
|
|||
- ANTHROPIC_MODEL=${ANTHROPIC_MODEL}
|
||||
- OLLAMA_URL=${OLLAMA_URL}
|
||||
- OLLAMA_MODEL=${OLLAMA_MODEL}
|
||||
- START_OLLAMA=${START_OLLAMA:-false}
|
||||
- CUSTOM_LLM_URL=${CUSTOM_LLM_URL}
|
||||
- CUSTOM_LLM_API_KEY=${CUSTOM_LLM_API_KEY}
|
||||
- CUSTOM_MODEL=${CUSTOM_MODEL}
|
||||
|
|
@ -115,8 +117,8 @@ services:
|
|||
- presenton_document_extraction_liteparse:/app/document-extraction-liteparse
|
||||
- ./app_data:/app_data
|
||||
environment:
|
||||
# Dockerfile.dev does not install ollama; use a host daemon via OLLAMA_URL or omit.
|
||||
- START_EMBEDDED_OLLAMA=false
|
||||
# Ollama is not baked into the image; set START_OLLAMA=true for runtime install, or use OLLAMA_URL.
|
||||
- START_OLLAMA=${START_OLLAMA:-false}
|
||||
- MIGRATE_DATABASE_ON_STARTUP=true
|
||||
- CAN_CHANGE_KEYS=${CAN_CHANGE_KEYS}
|
||||
- LLM=${LLM}
|
||||
|
|
@ -172,7 +174,7 @@ services:
|
|||
- presenton_document_extraction_liteparse:/app/document-extraction-liteparse
|
||||
- ./app_data:/app_data
|
||||
environment:
|
||||
- START_EMBEDDED_OLLAMA=false
|
||||
- START_OLLAMA=${START_OLLAMA:-false}
|
||||
- MIGRATE_DATABASE_ON_STARTUP=true
|
||||
- CAN_CHANGE_KEYS=${CAN_CHANGE_KEYS}
|
||||
- LLM=${LLM}
|
||||
|
|
|
|||
|
|
@ -14,20 +14,17 @@ dependencies = [
|
|||
"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",
|
||||
"mem0ai[nlp]>=0.1.115",
|
||||
"mem0ai>=0.1.115",
|
||||
"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",
|
||||
]
|
||||
|
||||
|
|
|
|||
23
servers/fastapi/scripts/warm_fastembed_cache.py
Normal file
23
servers/fastapi/scripts/warm_fastembed_cache.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
from pathlib import Path
|
||||
import sys
|
||||
|
||||
|
||||
FASTAPI_ROOT = Path(__file__).resolve().parents[1]
|
||||
if str(FASTAPI_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(FASTAPI_ROOT))
|
||||
|
||||
|
||||
from services.icon_finder_service import ICON_FINDER_SERVICE
|
||||
|
||||
|
||||
def main() -> None:
|
||||
if not ICON_FINDER_SERVICE.ensure_initialized():
|
||||
raise RuntimeError("Failed to prepare fastembed cache for icon search")
|
||||
|
||||
print(
|
||||
f"Fastembed cache prepared at {ICON_FINDER_SERVICE.cache_directory}"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -112,11 +112,13 @@ class IconFinderService:
|
|||
self._initialization_failed = True
|
||||
# Keep vectorstore as None so search_icons returns empty results
|
||||
|
||||
async def search_icons(self, query: str, k: int = 1):
|
||||
def ensure_initialized(self) -> bool:
|
||||
if not self._initialized and not self._initialization_failed:
|
||||
self._initialize_icons_collection()
|
||||
|
||||
if not self.vectorstore or self._initialization_failed:
|
||||
return self.vectorstore is not None and not self._initialization_failed
|
||||
|
||||
async def search_icons(self, query: str, k: int = 1):
|
||||
if not self.ensure_initialized():
|
||||
# Return empty list if vectorstore failed to initialize
|
||||
return []
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,10 @@ class Mem0PresentationMemoryService:
|
|||
"history_db_path": self._history_db_path,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _is_nonfatal_mem0_error(exc: BaseException) -> bool:
|
||||
return isinstance(exc, (Exception, SystemExit))
|
||||
|
||||
async def _get_client(self):
|
||||
if not self._enabled:
|
||||
return None
|
||||
|
|
@ -121,7 +125,9 @@ class Mem0PresentationMemoryService:
|
|||
self._qdrant_path,
|
||||
self._history_db_path,
|
||||
)
|
||||
except Exception:
|
||||
except BaseException as exc:
|
||||
if not self._is_nonfatal_mem0_error(exc):
|
||||
raise
|
||||
LOGGER.exception("Failed to initialize Mem0 OSS Memory")
|
||||
self._client = None
|
||||
|
||||
|
|
@ -147,7 +153,9 @@ class Mem0PresentationMemoryService:
|
|||
|
||||
try:
|
||||
await asyncio.to_thread(_add)
|
||||
except Exception:
|
||||
except BaseException as exc:
|
||||
if not self._is_nonfatal_mem0_error(exc):
|
||||
raise
|
||||
LOGGER.exception(
|
||||
"Failed to add mem0 memory for presentation_id=%s", presentation_id
|
||||
)
|
||||
|
|
@ -257,7 +265,9 @@ class Mem0PresentationMemoryService:
|
|||
|
||||
try:
|
||||
response = await asyncio.to_thread(_search)
|
||||
except Exception:
|
||||
except BaseException as exc:
|
||||
if not self._is_nonfatal_mem0_error(exc):
|
||||
raise
|
||||
LOGGER.exception(
|
||||
"Failed to search mem0 context for presentation_id=%s", presentation_id
|
||||
)
|
||||
|
|
|
|||
80
servers/fastapi/uv.lock
generated
80
servers/fastapi/uv.lock
generated
|
|
@ -341,48 +341,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chromadb"
|
||||
version = "1.0.15"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "bcrypt" },
|
||||
{ name = "build" },
|
||||
{ name = "grpcio" },
|
||||
{ name = "httpx" },
|
||||
{ name = "importlib-resources" },
|
||||
{ name = "jsonschema" },
|
||||
{ name = "kubernetes" },
|
||||
{ name = "mmh3" },
|
||||
{ name = "numpy" },
|
||||
{ name = "onnxruntime" },
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "opentelemetry-exporter-otlp-proto-grpc" },
|
||||
{ name = "opentelemetry-sdk" },
|
||||
{ name = "orjson" },
|
||||
{ name = "overrides" },
|
||||
{ name = "posthog" },
|
||||
{ name = "pybase64" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "pypika" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "rich" },
|
||||
{ name = "tenacity" },
|
||||
{ name = "tokenizers" },
|
||||
{ name = "tqdm" },
|
||||
{ name = "typer" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "uvicorn", extra = ["standard"] },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ad/e2/0653b2e539db5512d2200c759f1bc7f9ef5609fe47f3c7d24b82f62dc00f/chromadb-1.0.15.tar.gz", hash = "sha256:3e910da3f5414e2204f89c7beca1650847f2bf3bd71f11a2e40aad1eb31050aa", size = 1218840, upload-time = "2025-07-02T17:07:09.875Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/85/5a/866c6f0c2160cbc8dca0cf77b2fb391dcf435b32a58743da1bc1a08dc442/chromadb-1.0.15-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:51791553014297798b53df4e043e9c30f4e8bd157647971a6bb02b04bfa65f82", size = 18838820, upload-time = "2025-07-02T17:07:07.632Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/18/ff9b58ab5d334f5ecff7fdbacd6761bac467176708fa4d2500ae7c048af0/chromadb-1.0.15-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:48015803c0631c3a817befc276436dc084bb628c37fd4214047212afb2056291", size = 18057131, upload-time = "2025-07-02T17:07:05.15Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/49/74e34cc5aeeb25aff2c0ede6790b3671e14c1b91574dd8f98d266a4c5aad/chromadb-1.0.15-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b73cd6fb32fcdd91c577cca16ea6112b691d72b441bb3f2140426d1e79e453a", size = 18595284, upload-time = "2025-07-02T17:06:59.102Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/33/190df917a057067e37f8b48d082d769bed8b3c0c507edefc7b6c6bb577d0/chromadb-1.0.15-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:479f1b401af9e7c20f50642ffb3376abbfd78e2b5b170429f7c79eff52e367db", size = 19526626, upload-time = "2025-07-02T17:07:02.163Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/30/6890da607358993f87a01e80bcce916b4d91515ce865f07dc06845cb472f/chromadb-1.0.15-cp39-abi3-win_amd64.whl", hash = "sha256:e0cb3b93fdc42b1786f151d413ef36299f30f783a30ce08bf0bfb12e552b4190", size = 19520490, upload-time = "2025-07-02T17:07:11.559Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "click"
|
||||
version = "8.2.1"
|
||||
|
|
@ -1761,20 +1719,17 @@ dependencies = [
|
|||
{ name = "alembic" },
|
||||
{ name = "anthropic" },
|
||||
{ name = "asyncpg" },
|
||||
{ name = "chromadb" },
|
||||
{ name = "dirtyjson" },
|
||||
{ name = "fastapi", extra = ["standard"] },
|
||||
{ name = "fastembed-vectorstore" },
|
||||
{ name = "fastmcp" },
|
||||
{ name = "google-genai" },
|
||||
{ name = "mem0ai", extra = ["nlp"] },
|
||||
{ name = "mem0ai" },
|
||||
{ name = "nltk" },
|
||||
{ name = "openai" },
|
||||
{ name = "pathvalidate" },
|
||||
{ name = "pdfplumber" },
|
||||
{ name = "pytest" },
|
||||
{ name = "python-pptx" },
|
||||
{ name = "redis" },
|
||||
{ name = "sqlmodel" },
|
||||
]
|
||||
|
||||
|
|
@ -1786,20 +1741,17 @@ requires-dist = [
|
|||
{ name = "alembic", specifier = ">=1.14.0" },
|
||||
{ name = "anthropic", specifier = ">=0.60.0" },
|
||||
{ name = "asyncpg", specifier = ">=0.30.0" },
|
||||
{ name = "chromadb", specifier = ">=1.0.15" },
|
||||
{ name = "dirtyjson", specifier = ">=1.0.8" },
|
||||
{ name = "fastapi", extras = ["standard"], specifier = ">=0.116.1" },
|
||||
{ name = "fastembed-vectorstore", specifier = ">=0.5.2" },
|
||||
{ name = "fastmcp", specifier = ">=2.11.0" },
|
||||
{ name = "google-genai", specifier = ">=1.28.0" },
|
||||
{ name = "mem0ai", extras = ["nlp"], specifier = ">=0.1.115" },
|
||||
{ name = "mem0ai", specifier = ">=0.1.115" },
|
||||
{ name = "nltk", specifier = ">=3.9.1" },
|
||||
{ name = "openai", specifier = ">=1.98.0" },
|
||||
{ name = "pathvalidate", specifier = ">=3.3.1" },
|
||||
{ name = "pdfplumber", specifier = ">=0.11.7" },
|
||||
{ name = "pytest", specifier = ">=8.4.1" },
|
||||
{ name = "python-pptx", specifier = ">=1.0.2" },
|
||||
{ name = "redis", specifier = ">=6.2.0" },
|
||||
{ name = "sqlmodel", specifier = ">=0.0.24" },
|
||||
]
|
||||
|
||||
|
|
@ -2084,22 +2036,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "iniconfig" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pluggy" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/08/ba/45911d754e8eba3d5a841a5ce61a65a685ff1798421ac054f85aa8747dfb/pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c", size = 1517714, upload-time = "2025-06-18T05:48:06.109Z" }
|
||||
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-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
|
|
@ -2199,18 +2135,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/68/69/77d1a971c4b933e8c79403e99bcbb790463da5e48333cc4fd5d412c63c98/qdrant_client-1.17.1-py3-none-any.whl", hash = "sha256:6cda4064adfeaf211c751f3fbc00edbbdb499850918c7aff4855a9a759d56cbd", size = 389947, upload-time = "2026-03-13T17:13:43.156Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redis"
|
||||
version = "6.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "async-timeout", marker = "python_full_version < '3.11.3'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ea/9a/0551e01ba52b944f97480721656578c8a7c46b51b99d66814f85fe3a4f3e/redis-6.2.0.tar.gz", hash = "sha256:e821f129b75dde6cb99dd35e5c76e8c49512a5a0d8dfdc560b2fbd44b85ca977", size = 4639129, upload-time = "2025-05-28T05:01:18.91Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/13/67/e60968d3b0e077495a8fee89cf3f2373db98e528288a48f1ee44967f6e8c/redis-6.2.0-py3-none-any.whl", hash = "sha256:c8ddf316ee0aab65f04a11229e94a64b2618451dab7a67cb2f77eb799d872d5e", size = 278659, upload-time = "2025-05-28T05:01:16.955Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "referencing"
|
||||
version = "0.36.2"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
const nextConfig = {
|
||||
reactStrictMode: false,
|
||||
distDir: ".next-build",
|
||||
output: "standalone",
|
||||
|
||||
|
||||
// Rewrites for development - proxy font requests to FastAPI backend
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@
|
|||
".next-build/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
"node_modules",
|
||||
"**/*.cy.ts",
|
||||
"**/*.cy.tsx"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
96
start.js
96
start.js
|
|
@ -10,6 +10,7 @@ const __dirname = dirname(__filename);
|
|||
|
||||
const fastapiDir = join(__dirname, "servers/fastapi");
|
||||
const nextjsDir = join(__dirname, "servers/nextjs");
|
||||
const nextjsStandaloneServer = join(nextjsDir, "server.js");
|
||||
const exportSyncScript = join(__dirname, "scripts/sync-presentation-export.cjs");
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
|
@ -56,28 +57,60 @@ const setupNodeModules = () => {
|
|||
});
|
||||
};
|
||||
|
||||
const runNodeScript = (scriptPath, scriptArgs) => {
|
||||
const runCommand = (command, commandArgs, options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const scriptProcess = spawn(process.execPath, [scriptPath, ...scriptArgs], {
|
||||
cwd: __dirname,
|
||||
stdio: "inherit",
|
||||
env: process.env,
|
||||
const child = spawn(command, commandArgs, {
|
||||
cwd: options.cwd || __dirname,
|
||||
stdio: options.stdio || "inherit",
|
||||
env: options.env || process.env,
|
||||
});
|
||||
|
||||
scriptProcess.on("error", (err) => {
|
||||
child.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
scriptProcess.on("exit", (code) => {
|
||||
child.on("exit", (code) => {
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Script failed with exit code: ${code}`));
|
||||
reject(new Error(`${command} exited with code: ${code}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const runNodeScript = (scriptPath, scriptArgs) => {
|
||||
return runCommand(process.execPath, [scriptPath, ...scriptArgs], {
|
||||
cwd: __dirname,
|
||||
});
|
||||
};
|
||||
|
||||
const isTruthyEnv = (value) => {
|
||||
if (value == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !["", "0", "false", "no", "off"].includes(
|
||||
String(value).trim().toLowerCase()
|
||||
);
|
||||
};
|
||||
|
||||
const isOllamaInstalled = () =>
|
||||
existsSync("/usr/bin/ollama") || existsSync("/usr/local/bin/ollama");
|
||||
|
||||
const shouldStartOllama = () => isTruthyEnv(process.env.START_OLLAMA);
|
||||
|
||||
const ensureOllamaRuntime = async () => {
|
||||
if (!shouldStartOllama() || isOllamaInstalled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("START_OLLAMA=true; installing Ollama runtime...");
|
||||
await runCommand("sh", ["-c", "curl -fsSL https://ollama.com/install.sh | sh"], {
|
||||
cwd: "/",
|
||||
});
|
||||
};
|
||||
|
||||
const ensurePresentationExportRuntime = async () => {
|
||||
if (process.env.ENSURE_PRESENTATION_EXPORT_RUNTIME === "false") {
|
||||
return;
|
||||
|
|
@ -195,21 +228,32 @@ const startServers = async () => {
|
|||
console.error("App MCP process failed to start:", err);
|
||||
});
|
||||
|
||||
const useStandaloneNextjs = !isDev && existsSync(nextjsStandaloneServer);
|
||||
|
||||
const nextjsProcess = spawn(
|
||||
"npm",
|
||||
[
|
||||
"run",
|
||||
isDev ? "dev" : "start",
|
||||
"--",
|
||||
"-H",
|
||||
"127.0.0.1",
|
||||
"-p",
|
||||
nextjsPort.toString(),
|
||||
],
|
||||
useStandaloneNextjs ? process.execPath : "npm",
|
||||
useStandaloneNextjs
|
||||
? [nextjsStandaloneServer]
|
||||
: [
|
||||
"run",
|
||||
isDev ? "dev" : "start",
|
||||
"--",
|
||||
"-H",
|
||||
"127.0.0.1",
|
||||
"-p",
|
||||
nextjsPort.toString(),
|
||||
],
|
||||
{
|
||||
cwd: nextjsDir,
|
||||
stdio: "inherit",
|
||||
env: process.env,
|
||||
env:
|
||||
useStandaloneNextjs
|
||||
? {
|
||||
...process.env,
|
||||
HOSTNAME: "127.0.0.1",
|
||||
PORT: nextjsPort.toString(),
|
||||
}
|
||||
: process.env,
|
||||
}
|
||||
);
|
||||
|
||||
|
|
@ -217,16 +261,15 @@ const startServers = async () => {
|
|||
console.error("Next.js process failed to start:", err);
|
||||
});
|
||||
|
||||
const startEmbeddedOllama =
|
||||
process.env.START_EMBEDDED_OLLAMA !== "false" &&
|
||||
process.env.START_EMBEDDED_OLLAMA !== "0";
|
||||
const shouldStartOllamaRuntime = shouldStartOllama();
|
||||
const ollamaInstalled = isOllamaInstalled();
|
||||
|
||||
const exitPromises = [
|
||||
new Promise((resolve) => fastApiProcess.on("exit", resolve)),
|
||||
new Promise((resolve) => nextjsProcess.on("exit", resolve)),
|
||||
];
|
||||
|
||||
if (startEmbeddedOllama) {
|
||||
if (shouldStartOllamaRuntime && ollamaInstalled) {
|
||||
const ollamaProcess = spawn("ollama", ["serve"], {
|
||||
cwd: "/",
|
||||
stdio: "inherit",
|
||||
|
|
@ -236,9 +279,13 @@ const startServers = async () => {
|
|||
console.error("Ollama process failed to start:", err);
|
||||
});
|
||||
exitPromises.push(new Promise((resolve) => ollamaProcess.on("exit", resolve)));
|
||||
} else if (shouldStartOllamaRuntime) {
|
||||
console.log(
|
||||
"Ollama requested, but the binary is not installed. Set START_OLLAMA=true to install it at startup, or set OLLAMA_URL to a remote daemon."
|
||||
);
|
||||
} else {
|
||||
console.log(
|
||||
"Embedded Ollama disabled (START_EMBEDDED_OLLAMA=false); use OLLAMA_URL for a remote daemon if needed."
|
||||
"Ollama disabled (START_OLLAMA=false); use OLLAMA_URL for a remote daemon if needed."
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -271,6 +318,7 @@ const startNginx = () => {
|
|||
|
||||
const main = async () => {
|
||||
await ensurePresentationExportRuntime();
|
||||
await ensureOllamaRuntime();
|
||||
|
||||
if (isDev) {
|
||||
await setupNodeModules();
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue