From 53b07568d9d1392b7ff722e2cc1d8066e185fe0c Mon Sep 17 00:00:00 2001 From: pyr0ball Date: Sat, 4 Apr 2026 22:04:51 -0700 Subject: [PATCH] =?UTF-8?q?feat(vue):=20accumulated=20parity=20work=20?= =?UTF-8?q?=E2=80=94=20Q&A,=20Apply=20highlights,=20AppNav=20switcher,=20c?= =?UTF-8?q?loud=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit API additions (dev-api.py split across this and next commit): - /api/jobs/{job_id}/qa GET/PATCH/suggest — Interview Prep answer storage + LLM suggestions - /api/settings/ui-preference POST — persist streamlit/vue preference to user.yaml - cancel_task() added to scripts/db.py (per-task cancel for Danger Zone) Vue / UI: - AppNav: "⚡ Classic" button to switch back to Streamlit UI (writes cookie + persists to user.yaml) - ApplyWorkspace: Resume Highlights panel (collapsible skills/domains/keywords with job-match highlighting) - SettingsView: hide Data tab in cloud mode (showData guard) - ResumeProfileView: minor improvements - useApi.ts: error handling improvements Infra: - compose.cloud.yml: add api service (uvicorn dev_api running in cloud container) - docker/web/nginx.conf: proxy /api/* to api service in cloud mode - README.md: Vue SPA now listed as Free tier feature --- README.md | 2 +- compose.cloud.yml | 26 ++ docker/web/nginx.conf | 2 + scripts/db.py | 13 + web/src/components/AppNav.vue | 43 ++ web/src/components/ApplyWorkspace.vue | 391 +++++++++++++++++++ web/src/composables/useApi.ts | 7 +- web/src/views/settings/MyProfileView.vue | 2 +- web/src/views/settings/ResumeProfileView.vue | 46 ++- web/src/views/settings/SettingsView.vue | 5 +- 10 files changed, 527 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 55051ff..228ac81 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,7 @@ Re-enter the wizard any time via **Settings → Developer → Reset wizard**. | Calendar sync (Google, Apple) | Paid | | Slack notifications | Paid | | CircuitForge shared cover-letter model | Paid | -| Vue 3 SPA beta UI | Paid | +| Vue 3 SPA — full UI with onboarding wizard, job board, apply workspace, sort/filter, research modal, draft cover letter | Free | | **Voice guidelines** (custom writing style & tone) | Premium with LLM¹ ² | | Cover letter model fine-tuning (your writing, your model) | Premium | | Multi-user support | Premium | diff --git a/compose.cloud.yml b/compose.cloud.yml index c42c15f..ea3c23d 100644 --- a/compose.cloud.yml +++ b/compose.cloud.yml @@ -45,6 +45,30 @@ services: - "host.docker.internal:host-gateway" restart: unless-stopped + api: + build: + context: .. + dockerfile: peregrine/Dockerfile.cfcore + command: > + bash -c "uvicorn dev_api:app --host 0.0.0.0 --port 8601" + volumes: + - /devl/menagerie-data:/devl/menagerie-data + - ./config/llm.cloud.yaml:/app/config/llm.yaml:ro + environment: + - CLOUD_MODE=true + - CLOUD_DATA_ROOT=/devl/menagerie-data + - STAGING_DB=/devl/menagerie-data/cloud-default.db + - DIRECTUS_JWT_SECRET=${DIRECTUS_JWT_SECRET} + - CF_SERVER_SECRET=${CF_SERVER_SECRET} + - PLATFORM_DB_URL=${PLATFORM_DB_URL} + - HEIMDALL_URL=${HEIMDALL_URL:-http://cf-license:8000} + - HEIMDALL_ADMIN_TOKEN=${HEIMDALL_ADMIN_TOKEN} + - PYTHONUNBUFFERED=1 + - FORGEJO_API_TOKEN=${FORGEJO_API_TOKEN:-} + extra_hosts: + - "host.docker.internal:host-gateway" + restart: unless-stopped + web: build: context: . @@ -53,6 +77,8 @@ services: VITE_BASE_PATH: /peregrine/ ports: - "8508:80" + depends_on: + - api restart: unless-stopped searxng: diff --git a/docker/web/nginx.conf b/docker/web/nginx.conf index 35b52b8..2107e1a 100644 --- a/docker/web/nginx.conf +++ b/docker/web/nginx.conf @@ -2,6 +2,8 @@ server { listen 80; server_name _; + client_max_body_size 20m; + root /usr/share/nginx/html; index index.html; diff --git a/scripts/db.py b/scripts/db.py index 234c179..0e6bd5f 100644 --- a/scripts/db.py +++ b/scripts/db.py @@ -383,6 +383,19 @@ def mark_applied(db_path: Path = DEFAULT_DB, ids: list[int] = None) -> None: conn.close() +def cancel_task(db_path: Path = DEFAULT_DB, task_id: int = 0) -> bool: + """Cancel a single queued/running task by id. Returns True if a row was updated.""" + conn = sqlite3.connect(db_path) + count = conn.execute( + "UPDATE background_tasks SET status='failed', error='Cancelled by user'," + " finished_at=datetime('now') WHERE id=? AND status IN ('queued','running')", + (task_id,), + ).rowcount + conn.commit() + conn.close() + return count > 0 + + def kill_stuck_tasks(db_path: Path = DEFAULT_DB) -> int: """Mark all queued/running background tasks as failed. Returns count killed.""" conn = sqlite3.connect(db_path) diff --git a/web/src/components/AppNav.vue b/web/src/components/AppNav.vue index cd4af21..84d616d 100644 --- a/web/src/components/AppNav.vue +++ b/web/src/components/AppNav.vue @@ -40,6 +40,9 @@