Skip to content

Engine: local mode + all-in-one Docker image + BYOK + UI + source endpoint + ingest race fix#45

Merged
hallelx2 merged 10 commits into
mainfrom
halleluyaholudele/hal-186-engine-zero-config-local-mode
Jun 19, 2026
Merged

Engine: local mode + all-in-one Docker image + BYOK + UI + source endpoint + ingest race fix#45
hallelx2 merged 10 commits into
mainfrom
halleluyaholudele/hal-186-engine-zero-config-local-mode

Conversation

@hallelx2

@hallelx2 hallelx2 commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Consolidates the local/self-hosted slice onto main.

What's in here

  • All-in-one Docker image (Dockerfile.allinone + entrypoint): engine + bundled Postgres + the local web UI in one container; published to Docker Hub + GHCR by docker-allinone.yml. (HAL-185)
  • BYOK: engine boots without a key in local mode and accepts per-request credentials via X-LLM-* headers; the bundled UI has a settings panel to configure the key from the dashboard. (HAL-188)
  • GET /v1/documents/{id}/source: streams original bytes (for the UI's PDF page preview).
  • Ingest source-write race fix: Local.Put fsyncs + source fetch retries on ErrNotFound — kills intermittent parse: fetch source: storage: object not found. (Closes HAL-319)
  • GLM base_url doc fix: documents the required /v1 suffix for Anthropic-compatible gateways. (Closes HAL-318)
  • Bundled local viewer (localapp/): upload, structure map, treewalk Q&A with citations, sections-read panel, PDF page preview, favicon.

Verification

  • go build ./..., go vet ./... clean; all 12 test packages pass.
  • Published image pulled + booted: engine + UI + a real cited query work in-container (3M FY2018 capex → $1,577M).

Closes HAL-185
Closes HAL-318
Closes HAL-319

Summary by Sourcery

Add local BYOK support, a bundled local web UI, and an all-in-one Docker image while hardening ingest and improving Anthropic gateway configuration.

New Features:

  • Allow the engine to run in local mode without a shared LLM key and accept per-request credentials via X-LLM-* headers for treewalk answering.
  • Expose a GET /v1/documents/{id}/source endpoint to stream original document bytes for client-side previews.
  • Bundle a static local viewer web UI that supports upload, document browsing, treewalk Q&A with citations, section inspection, and PDF page previews.
  • Provide a Python-based local viewer proxy that serves the UI and forwards API calls to the engine to avoid CORS issues.
  • Introduce an all-in-one Docker deployment that runs Postgres, the engine in local mode, and the bundled viewer UI in a single container.

Bug Fixes:

  • Harden ingest against races where a background parse job sees a freshly written source object as missing by adding fsync on local writes and retrying ErrNotFound reads.

Enhancements:

  • Support building per-request LLM clients from caller-supplied credentials while inheriting server defaults, and keep treewalk endpoints available even without a server-wide key.
  • Improve Anthropic-compatible gateway configuration docs and comments to clarify the required /v1 suffix in base URLs.

Build:

  • Add a docker-allinone GitHub Actions workflow to build and publish the combined engine+Postgres+UI image to Docker Hub and GHCR.

Deployment:

  • Add an all-in-one container entrypoint that boots Postgres, the engine, and the local viewer UI together and warns when no LLM provider key is configured.

Documentation:

  • Document the local viewer, its rationale, and how to run it against a local engine instance.

hallelx2 added 10 commits June 18, 2026 00:40
…7654

Adds a local mode that moves the base config to zero-setup defaults BEFORE
the file/env layers (which still override): listen on :7654, a localhost
Postgres URL matching the bundled/dev database, local file storage, and the
Postgres-backed river queue (no Redis needed). The engine then boots with
no required configuration — closing the 'database.url is required' gap that
otherwise blocks a bare run.

- --local flag sets VLE_LOCAL_MODE=true so CLI + Docker (env) share one path.
- Adds VLE_STORAGE_LOCAL_ROOT binding for the image's data volume.
- cmd/engine is already unauthenticated (single tenant), so local-mode auth
  needs no extra wiring; documented as dev/local only.
- Documented in config.example.yaml; tests cover defaults, truthy forms,
  env-override precedence, and the non-local missing-DB-URL failure.

Foundation for the all-in-one image (HAL-185) + local dashboard (HAL-188).
Closes HAL-186.
…mode=disable)

Per CodeRabbit review on #42 — the example referenced the DSN without the
sslmode param that applyLocalDefaults actually injects.
…319)

The ingest worker could race the just-written source object (Local.Put did
no fsync; River picks the job up within microseconds), failing with
'parse: fetch source: storage: object not found' and marking the doc failed.
Local.Put now fsyncs before returning and the source fetch retries on
ErrNotFound with short backoff.
…HAL-188, HAL-185)

New GET /v1/documents/{id}/source streams the original bytes (for PDF page
previews in clients). Dockerfile.allinone bundles the engine + Postgres + the
local web UI into one image; docker-allinone.yml publishes it to Docker Hub +
GHCR.
… settings (HAL-188)

The engine now boots without a provider key in local mode and accepts per-request
credentials (X-LLM-Api-Key / X-LLM-Provider / X-LLM-Base-Url / X-LLM-Model),
building a per-request client that drives both the treewalk loop and citation
span extraction. The bundled UI gains a settings modal that stores the key in the
browser and sends it as headers — so a docker-run user configures their key from
the dashboard, not only via env.
@sourcery-ai

sourcery-ai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds BYOK support for local mode, a new /v1/documents/{id}/source endpoint, an ingest source-read race fix, GLM base_url doc clarification, and a bundled local UI plus all-in-one Docker image (engine + Postgres + viewer) with CI publishing workflow.

Sequence diagram for BYOK treewalk answer flow

sequenceDiagram
    actor User
    participant LocalUI as Local_UI
    participant Viewer as Viewer_serve_py
    participant Engine as Engine_HTTP
    participant Deps as Deps_handleAnswerTreeWalk
    participant LLMFactory as Deps.BuildLLM

    User->>LocalUI: Submit question
    LocalUI->>Viewer: POST /engine/v1/answer/treewalk
    Note right of LocalUI: Adds X-LLM-Api-Key<br/>X-LLM-Provider<br/>X-LLM-Base-Url<br/>X-LLM-Model
    Viewer->>Engine: POST /v1/answer/treewalk

    Engine->>Deps: handleAnswerTreeWalk
    Deps->>Deps: resolveLLM(request)
    alt X-LLM-Api-Key present and BuildLLM set
        Deps->>LLMFactory: BuildLLM(provider, apiKey, baseURL, model)
        LLMFactory-->>Deps: llmgate.Client (per-request)
    else no per-request key
        Deps-->>Deps: use shared d.LLM
    end

    Deps->>Deps: set perReq.LLM and dReq.LLM
    Deps->>Engine: serveAnswerTreeWalkStream or answerTreeWalk
    Engine-->>Viewer: treewalk answer + citations
    Viewer-->>LocalUI: answer, citations, pages_read
    LocalUI-->>User: Render cited answer + preview
Loading

Sequence diagram for ingest source-read race retry

sequenceDiagram
    participant Pipeline as Pipeline.parse
    participant Storage as storage.Storage

    Pipeline->>Pipeline: getSourceWithRetry(ctx, storage, key)
    loop up to 6 attempts
        Pipeline->>Storage: Get(ctx, key)
        alt Storage.Get succeeds
            Storage-->>Pipeline: ReadCloser, Metadata
            Pipeline-->>Pipeline: return rc, meta
        else Storage.Get returns ErrNotFound
            Storage-->>Pipeline: ErrNotFound
            Pipeline->>Pipeline: backoff 150ms * (attempt+1)
        else other error
            Storage-->>Pipeline: error
            Pipeline-->>Pipeline: return error
        end
    end
    Pipeline->>Pipeline: parsers.Parse(ctx, contentType, filename, rc)
Loading

Sequence diagram for GET /v1/documents/{id}/source

sequenceDiagram
    actor User
    participant LocalUI as Local_UI
    participant Viewer as Viewer_serve_py
    participant Engine as Engine_HTTP
    participant Deps as Deps_handleGetSource
    participant DB as DB
    participant Store as Storage

    User->>LocalUI: Open PDF preview
    LocalUI->>Viewer: GET /engine/v1/documents/{id}/source
    Viewer->>Engine: GET /v1/documents/{id}/source

    Engine->>Deps: handleGetSource
    Deps->>DB: GetDocument(ctx, id, standaloneOrgID, "")
    alt document not found
        DB-->>Deps: db.ErrNotFound
        Deps-->>Engine: 404 document not found
    else document found
        DB-->>Deps: Document{SourceRef, ContentType}
        alt SourceRef is empty
            Deps-->>Engine: 404 document has no stored source
        else SourceRef set
            Deps->>Store: Get(ctx, doc.SourceRef)
            alt object missing
                Store-->>Deps: storage.ErrNotFound
                Deps-->>Engine: 404 source object not found
            else success
                Store-->>Deps: ReadCloser, Metadata
                Deps-->>Engine: 200 stream bytes<br/>Content-Type, Content-Length, inline
            end
        end
    end
    Engine-->>Viewer: HTTP response
    Viewer-->>LocalUI: HTTP response
    LocalUI-->>User: Render PDF page canvas
Loading

Flow diagram for engine LLM initialization with local BYOK mode

flowchart LR
    A[Start engine] --> B["buildLLM(cfg.LLM)"]
    B -->|ok| C[llmClient set]
    B -->|error| D{LocalModeEnabled<br/>and llmKeyMissing}
    D -->|yes| E[Log warning<br/>no provider key]
    E --> F[llmClient = nil<br/>TreeWalkStrategy built<br/>with nil client]
    D -->|no| G[return init llm error]

    F --> H[Deps.BuildLLM = buildLLMFrom]
    C --> H
    H --> I[Server accepts<br/>X-LLM-* headers<br/>for per-request BYOK]
Loading

File-Level Changes

Change Details Files
Allow engine to start in local mode without a server-wide LLM key and support per-request BYOK credentials for treewalk answering.
  • Treat missing provider key as non-fatal in local mode and log a warning instead of failing startup.
  • Always build the treewalk strategy when enabled and record whether a server key exists.
  • Add a BuildLLM factory dependency so handlers can construct per-request llm clients from headers.
  • Implement llmKeyMissing and buildLLMFrom helpers to inherit server defaults when building BYOK clients.
  • Extend treewalk handler to resolve LLM from X-LLM-* headers, validate credentials, and run navigation + citation extraction with the per-request client.
cmd/engine/main.go
internal/api/server.go
internal/api/treewalk.go
Expose original uploaded document bytes over HTTP for use by the local UI (e.g., PDF preview).
  • Add GET /v1/documents/{id}/source route to the API router.
  • Implement handleGetSource to fetch the document, resolve SourceRef, stream bytes from storage, and set appropriate content headers and caching.
  • Handle not-found cases for missing document, missing SourceRef, or missing storage object with 404 responses.
internal/api/server.go
Harden ingest pipeline against races between source upload and background parsing, especially with local filesystem storage.
  • Change ingest parse stage to read source via a helper that retries when the object is briefly not found.
  • Implement getSourceWithRetry with bounded retries and backoff on storage.ErrNotFound while propagating other errors immediately.
  • Update local storage Put implementation to fsync the file before returning and close cleanly on errors, reducing visibility races on some filesystems.
pkg/ingest/ingest.go
pkg/storage/local.go
Clarify Anthropic-compatible GLM base_url requirements and environment overrides, ensuring the /v1 segment is present.
  • Update AnthropicBlock.BaseURL documentation to show GLM example with /v1 suffix and explain messages endpoint behavior when missing.
  • Adjust internal and external config env-override comments to reference the /v1 requirement and how the client constructs ${base}/messages.
pkg/config/config.go
internal/config/config.go
Introduce a bundled local web UI and Python proxy, plus an all-in-one Docker image and CI workflow to run engine, Postgres, and UI together.
  • Add a standalone SPA (index.html) implementing upload, document list, tree view, treewalk Q&A with citations, sections panel, and PDF preview via the new source endpoint, including BYOK settings stored in the browser and forwarded via X-LLM-* headers.
  • Add a lightweight Python HTTP server (serve.py) that serves the SPA and reverse-proxies /engine/* requests to the engine, handling headers and errors.
  • Document the local viewer usage and configuration in localapp/README.md.
  • Add all-in-one entrypoint script that starts Postgres, waits for readiness, runs the viewer, warns if no LLM key is configured, and launches the engine in local mode.
  • Add GitHub Actions workflow to build and publish the all-in-one Docker image to Docker Hub and GHCR with appropriate tags, labels, and caching.
  • Introduce Dockerfile.allinone and related gitattributes entries to build the combined image.
localapp/index.html
localapp/serve.py
localapp/README.md
deploy/allinone/entrypoint.sh
.github/workflows/docker-allinone.yml
Dockerfile.allinone
.gitattributes

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9ce30fe8-f2be-4f5a-9d61-d05af56c7cc1

📥 Commits

Reviewing files that changed from the base of the PR and between 7a21b11 and 079d2aa.

📒 Files selected for processing (14)
  • .gitattributes
  • .github/workflows/docker-allinone.yml
  • Dockerfile.allinone
  • cmd/engine/main.go
  • deploy/allinone/entrypoint.sh
  • internal/api/server.go
  • internal/api/treewalk.go
  • internal/config/config.go
  • localapp/README.md
  • localapp/index.html
  • localapp/serve.py
  • pkg/config/config.go
  • pkg/ingest/ingest.go
  • pkg/storage/local.go
 _______________________________________________
< Crouching Tiger, Hidden NullPointerException. >
 -----------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch halleluyaholudele/hal-186-engine-zero-config-local-mode

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can use OpenGrep to find security vulnerabilities and bugs across 17+ programming languages.

OpenGrep is compatible with Semgrep configurations. Add an opengrep.yml or semgrep.yml configuration file to your project to enable OpenGrep analysis.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • In buildLLMFrom, the baseURL argument is only honored for the anthropic driver and silently ignored for openai/gemini, which makes the X-LLM-Base-Url header misleading for those providers; either wire baseURL through to those clients (if supported) or document/guard that only anthropic supports a custom base URL.
  • buildLLM and buildLLMFrom now contain very similar provider/model wiring logic; consider factoring out a shared helper so new providers or config fields can be added in one place without the two paths diverging over time.
  • The local viewer’s health status string is hardcoded to show :7654 even though ENGINE_URL in serve.py can point elsewhere; using ENGINE_URL (or the proxied origin) to populate this text would avoid confusing users when the engine listens on a non-default host/port.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In buildLLMFrom, the baseURL argument is only honored for the anthropic driver and silently ignored for openai/gemini, which makes the X-LLM-Base-Url header misleading for those providers; either wire baseURL through to those clients (if supported) or document/guard that only anthropic supports a custom base URL.
- buildLLM and buildLLMFrom now contain very similar provider/model wiring logic; consider factoring out a shared helper so new providers or config fields can be added in one place without the two paths diverging over time.
- The local viewer’s health status string is hardcoded to show `:7654` even though ENGINE_URL in serve.py can point elsewhere; using ENGINE_URL (or the proxied origin) to populate this text would avoid confusing users when the engine listens on a non-default host/port.

## Individual Comments

### Comment 1
<location path="cmd/engine/main.go" line_range="437-429" />
<code_context>
+	return false
+}
+
+func buildLLMFrom(c config.LLMConfig, provider, apiKey, baseURL, model string) (llmgate.Client, error) {
+	if provider == "" {
+		provider = c.Driver
+	}
+	switch provider {
+	case "anthropic":
+		if model == "" {
+			model = c.Anthropic.Model
+		}
+		if baseURL == "" {
+			baseURL = c.Anthropic.BaseURL
+		}
+		return anthropic.New(anthropic.Config{
+			APIKey:         apiKey,
+			Model:          model,
+			ReasoningModel: c.Anthropic.ReasoningModel,
+			BaseURL:        baseURL,
+		})
+	case "openai":
+		if model == "" {
+			model = c.OpenAI.Model
</code_context>
<issue_to_address>
**suggestion (bug_risk):** OpenAI and Gemini branches ignore the `baseURL` argument, which may surprise callers of `BuildLLM`.

`BuildLLM` passes `baseURL` to `buildLLMFrom`, but the `openai` and `gemini` branches ignore it. Calls that set `X-LLM-Base-Url` for these providers will have no effect. Please either plumb `baseURL` into the OpenAI/Gemini client configs (as with Anthropic) or make it explicit in the API/docs that this parameter only applies to Anthropic.

Suggested implementation:

```golang
	case "openai":
		if model == "" {
			model = c.OpenAI.Model
		}
		if baseURL == "" {
			baseURL = c.OpenAI.BaseURL
		}
		return openai.New(openai.Config{
			APIKey:         apiKey,
			Model:          model,
			ReasoningModel: c.OpenAI.ReasoningModel,
			BaseURL:        baseURL,
		})

```

```golang
	case "gemini":
		if model == "" {
			model = c.Gemini.Model
		}
		if baseURL == "" {
			baseURL = c.Gemini.BaseURL
		}

```

To fully wire `baseURL` for the Gemini provider, ensure the `gemini` case returns a client configured with the `BaseURL` field, mirroring the Anthropic/OpenAI patterns. For example, the body should look like:

```go
	case "gemini":
		if model == "" {
			model = c.Gemini.Model
		}
		if baseURL == "" {
			baseURL = c.Gemini.BaseURL
		}
		return gemini.New(gemini.Config{
			APIKey:         apiKey,
			Model:          model,
			ReasoningModel: c.Gemini.ReasoningModel,
			BaseURL:        baseURL,
		})
```

This assumes `openai.Config` and `gemini.Config` both support a `BaseURL` field; if they use a different field name, adjust accordingly.
</issue_to_address>

### Comment 2
<location path="localapp/index.html" line_range="464-469" />
<code_context>
+function cleanSnip(t){ if(!t) return ""; return t.replace(/\s+/g," ").trim().slice(0,200)+(t.length>200?"…":""); }
+
+// pdf preview
+async function loadPdf(docId, page){
+  const wrap=document.getElementById("pvWrap");
+  const srcUrl=E(`/v1/documents/${docId}/source`);
+  const openLink=document.getElementById("pvOpen"); if(openLink) openLink.href=srcUrl;
+  const fallback=()=>{ wrap.innerHTML=`<div class="pvmsg">Inline preview didn’t load — <a href="${srcUrl}" target="_blank" rel="noopener">open the source ↗</a>.</div>`; };
+  if(!window.pdfjsLib){ fallback(); return; }
+  try{
+    // disableRange/Stream: the local proxy serves the whole body with a 200
</code_context>
<issue_to_address>
**issue:** The PDF preview is attempted for any document type, which can cause confusing errors for non-PDF uploads.

Since `loadPdf`/`pdfjsLib.getDocument` is always called, non-PDF docs (DOCX/HTML/MD/TXT) will hit `/source` with a non-PDF body and cause pdf.js failures or generic error messages. You already have `content_type` on the document, so consider only invoking the inline PDF viewer when `content_type === 'application/pdf'` (or similar), and otherwise skip pdf.js and just show the “open source” link to avoid these spurious errors.
</issue_to_address>

### Comment 3
<location path="localapp/index.html" line_range="266" />
<code_context>
+const LS="vl_llm_settings";
+const DEFAULTS={provider:"anthropic",baseUrl:"https://api.z.ai/api/anthropic/v1",model:"glm-4.6",apiKey:""};
+function getSettings(){ try{ return {...DEFAULTS, ...JSON.parse(localStorage.getItem(LS)||"{}")}; }catch{ return {...DEFAULTS}; } }
+function saveSettings(s){ localStorage.setItem(LS, JSON.stringify(s)); refreshKeyStatus(); }
+function llmHeaders(){ const s=getSettings(); const h={}; if(s.apiKey){ h["X-LLM-Api-Key"]=s.apiKey;
+  if(s.provider) h["X-LLM-Provider"]=s.provider; if(s.baseUrl) h["X-LLM-Base-Url"]=s.baseUrl; if(s.model) h["X-LLM-Model"]=s.model; } return h; }
</code_context>
<issue_to_address>
**🚨 issue (security):** Storing raw API keys in `localStorage` increases the blast radius of any XSS in the viewer.

Because `localStorage` is readable by any script on this origin, any present or future XSS in this viewer could leak the BYOK key. If this UI is intended to be used beyond a tightly controlled local/dev context, consider keeping the key only in memory for the session, or otherwise constraining where this viewer can run and documenting the risk explicitly.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread cmd/engine/main.go
switch c.Driver {
case "anthropic":
return c.Anthropic.APIKey == ""
case "openai":

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): OpenAI and Gemini branches ignore the baseURL argument, which may surprise callers of BuildLLM.

BuildLLM passes baseURL to buildLLMFrom, but the openai and gemini branches ignore it. Calls that set X-LLM-Base-Url for these providers will have no effect. Please either plumb baseURL into the OpenAI/Gemini client configs (as with Anthropic) or make it explicit in the API/docs that this parameter only applies to Anthropic.

Suggested implementation:

	case "openai":
		if model == "" {
			model = c.OpenAI.Model
		}
		if baseURL == "" {
			baseURL = c.OpenAI.BaseURL
		}
		return openai.New(openai.Config{
			APIKey:         apiKey,
			Model:          model,
			ReasoningModel: c.OpenAI.ReasoningModel,
			BaseURL:        baseURL,
		})
	case "gemini":
		if model == "" {
			model = c.Gemini.Model
		}
		if baseURL == "" {
			baseURL = c.Gemini.BaseURL
		}

To fully wire baseURL for the Gemini provider, ensure the gemini case returns a client configured with the BaseURL field, mirroring the Anthropic/OpenAI patterns. For example, the body should look like:

	case "gemini":
		if model == "" {
			model = c.Gemini.Model
		}
		if baseURL == "" {
			baseURL = c.Gemini.BaseURL
		}
		return gemini.New(gemini.Config{
			APIKey:         apiKey,
			Model:          model,
			ReasoningModel: c.Gemini.ReasoningModel,
			BaseURL:        baseURL,
		})

This assumes openai.Config and gemini.Config both support a BaseURL field; if they use a different field name, adjust accordingly.

Comment thread localapp/index.html
Comment on lines +464 to +469
async function loadPdf(docId, page){
const wrap=document.getElementById("pvWrap");
const srcUrl=E(`/v1/documents/${docId}/source`);
const openLink=document.getElementById("pvOpen"); if(openLink) openLink.href=srcUrl;
const fallback=()=>{ wrap.innerHTML=`<div class="pvmsg">Inline preview didn’t load — <a href="${srcUrl}" target="_blank" rel="noopener">open the source ↗</a>.</div>`; };
if(!window.pdfjsLib){ fallback(); return; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: The PDF preview is attempted for any document type, which can cause confusing errors for non-PDF uploads.

Since loadPdf/pdfjsLib.getDocument is always called, non-PDF docs (DOCX/HTML/MD/TXT) will hit /source with a non-PDF body and cause pdf.js failures or generic error messages. You already have content_type on the document, so consider only invoking the inline PDF viewer when content_type === 'application/pdf' (or similar), and otherwise skip pdf.js and just show the “open source” link to avoid these spurious errors.

Comment thread localapp/index.html
const LS="vl_llm_settings";
const DEFAULTS={provider:"anthropic",baseUrl:"https://api.z.ai/api/anthropic/v1",model:"glm-4.6",apiKey:""};
function getSettings(){ try{ return {...DEFAULTS, ...JSON.parse(localStorage.getItem(LS)||"{}")}; }catch{ return {...DEFAULTS}; } }
function saveSettings(s){ localStorage.setItem(LS, JSON.stringify(s)); refreshKeyStatus(); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): Storing raw API keys in localStorage increases the blast radius of any XSS in the viewer.

Because localStorage is readable by any script on this origin, any present or future XSS in this viewer could leak the BYOK key. If this UI is intended to be used beyond a tightly controlled local/dev context, consider keeping the key only in memory for the session, or otherwise constraining where this viewer can run and documenting the risk explicitly.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

@hallelx2 hallelx2 merged commit 678a7a3 into main Jun 19, 2026
8 checks passed
@hallelx2 hallelx2 deleted the halleluyaholudele/hal-186-engine-zero-config-local-mode branch June 19, 2026 14:32
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