Skip to content

Add a vault upload dialog with optional ingest and serve /api/upload-vault - #14

Merged
benjsmith merged 2 commits into
mainfrom
fix/graph-editor-upload
Oct 8, 2026
Merged

benjsmith merged 2 commits into
mainfrom
fix/graph-editor-upload

Conversation

@benjsmith

@benjsmith benjsmith commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

The graph sidebar's + button in the vendored CE editor (edit.js) posts to /api/upload-vault, but the Switchbay daemon never served that path. In the built-in sidebar the button was handled separately and always sent one file straight to an ingest agent. In the embedded CE sidebar the request went through the /embed/ce proxy to CE's own viewer, which works for small files but fails with 413 above 1 MiB, the proxy's body limit.

This adds a daemon route for POST /api/upload-vault and a single Switchbay upload dialog that every + button opens.

Route. It follows CE's viewer_server.py handler: multipart form, every part with a filename is saved into <workspace>/vault/raw/, names are sanitised with CE's rules (basename only, anything outside [A-Za-z0-9._-] becomes _, empty or dot-leading names are refused), an existing file of the same name is replaced, and the reply is {"ok": true, "saved": [...]}. Files are streamed to a temporary file and moved into place, with a 50 MB per-file limit (413 with the names already saved). An optional ingest form field (true/false, default false) asks for the saved files to be ingested. They are queued as ingest runs on the existing drain queue, in one batch: through CE's deterministic local ingest when CE is installed, or through the ingest agent (one run per file) when CE is absent and the default model provider is ready. With neither, the files are still saved and the reply's ingest.message says why nothing was ingested. When ingest is requested the reply also carries ingest: {backend, queued, runs: [{file, run_id, vault_path}]}.

Reserved path. /api/upload-vault is added to the daemon-reserved API list, so the embedded CE sidebar's own upload also reaches this route instead of CE's viewer through the proxy, which removes the 1 MiB limit for uploads.

Dialog. UploadVaultDialog is a Switchbay React component in the shell, not part of edit.js. It lets you pick several files (and add or remove them before uploading), has an "Ingest automatically" checkbox, and uses the theme variables, so it follows light and dark mode. Switchbay has no global auto-ingest setting, so the checkbox starts off and remembers the last choice in localStorage (sy.uploadVault.ingest). After the upload a toast reports what was saved and whether ingest was queued. For agent ingest it opens the run in the Agents view, as the old + did.

Entry points. A new + in the Files browser toolbar opens the dialog. It is always there, with or without CE, so upload keeps working when the Graph tab is hidden. The built-in sidebar and Zen graph + buttons open the same dialog. CE's embed has no hook or message for its +, so the embedded CE sidebar's + is intercepted on the Switchbay side (a capture-phase click listener on the sidebar slot) and opens the dialog too. If CE's + ever runs its own handler, it posts a plain upload to the same reserved route. edit.js is unchanged. Dropping files onto the sidebar still stages them and starts ingest agents, as before.

Tests. tests/unit/test_upload_vault.py checks that the daemon resolves edit.js's literal request, and covers multi-file uploads, same-name replacement, CE's sanitising cases (including ../../escape.txt, /etc/passwd, .env and ..), that path traversal stays inside vault/raw/, the size cap, non-multipart and file-less requests, ingest=false, ingest=true with CE present (draining through local ingest), with CE absent and a provider ready (agent runs), and with no backend (message, no runs). frontend/src/lib/uploadVault.test.ts covers the dialog's request (ingest flag first, one file field per file), the stored checkbox preference, and the result messages. embedMount.test.ts checks that the path is reserved.

The graph sidebar's upload control (edit.js, forked from CE's viewer)
posts the chosen files to /api/upload-vault. CE's viewer_server serves
that route, but the Switchbay daemon never did, so the built-in path
answered 405.

Add the route to the daemon with CE's behaviour: multipart, every part
with a filename is saved, names are sanitised with CE's rules, files are
written to <workspace>/vault/raw/ with no ingest run, and the reply is
{ok, saved: [names]}. Each file is capped at 50 MB, the same cap as CE's
other upload route and /api/ingest/from-upload.

Keep the path on the daemon when skill embeds are proxied. Through
/embed/ce the request hits the proxy's 1 MiB body limit and any normal
PDF fails with 413; the daemon route writes to the same vault/raw/.

Signed-off-by: benjsmith <benjamincharlessmith@gmail.com>
The + buttons in the Files browser, the graph sidebar and the embedded
CE sidebar now open one Switchbay dialog: pick several files, choose
whether to ingest them, upload. POST /api/upload-vault takes an ingest
form field; when set, saved files are queued as ingest runs that drain
through CE's local ingest when CE is installed, or through the ingest
agent when a model provider is ready. With neither, files are saved and
the response says why nothing was ingested. The path is reserved for
the daemon so the embedded CE sidebar reaches it too.

Signed-off-by: benjsmith <benjamincharlessmith@gmail.com>
@benjsmith benjsmith changed the title Serve /api/upload-vault so graph sidebar uploads land in vault/raw/ Add a vault upload dialog with optional ingest and serve /api/upload-vault Oct 8, 2026
@benjsmith
benjsmith merged commit 205d3a2 into main Oct 8, 2026
7 checks passed
@benjsmith
benjsmith deleted the fix/graph-editor-upload branch October 12, 2026 06:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant