Commit Graph
863 Commits
Author SHA1 Message Date
Cameron CordesandClaude Opus 5.5 a8ba13a03f e2e README: a port change needs a reset
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 09:40:28 -05:00
Cameron CordesandClaude Opus 5.5 3988d1f3cb e2e server: default port 8093
8090 is held by a long-running service on the dev box, so the e2e server
(and the app's drive-app / Detox, which read E2E_PORT too) move to 8093.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 09:35:15 -05:00
cameron 27e5b78ffc Merge pull request 'List favorites newest first' (#213) from fix/favorites-newest-first into master
Reviewed-on: #213
2026-11-02 14:34:53 +00:00
Cameron CordesandClaude Opus 5.5 ee921e7a73 List favorites newest first
GET /image/favorites had no ORDER BY, so SQLite returned rows in index
order (by path). Order by id descending so the app's Favorites view
(FileViewer #73) shows what was just added on top.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 08:49:33 -05:00
cameron 4d9e62d05d Merge pull request 'Enhance: list every photo's open jobs (jobs manager)' (#211) from feat/enhance-jobs-list into master
Reviewed-on: #211
2026-11-02 13:28:51 +00:00
Cameron CordesandClaude Opus 5.5 37a2386458 List every photo's open Enhance jobs for a jobs manager
GET /enhance/jobs without a path now answers the jobs manager (FVR #99):
every photo's queued / running / unavailable jobs, plus ready / failed /
expired ones touched in the last 3 days, newest first, discarded and
approved hidden, at most 100. DB only; the poller keeps rows current.
An optional status=a,b filter applies to both forms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 22:14:27 -05:00
cameron f121457c43 Merge pull request 'Clip counts on listings, so clients find photos with only clips' (#210) from feat/clip-counts into master
Reviewed-on: #210
2026-11-02 00:54:23 +00:00
Cameron CordesandClaude Opus 5.5 3d1db90e3c Clip counts on listings, so clients find photos with only clips
Derived clips (#208) aren't counted as versions, so a photo whose only
derived file is a clip looked like a plain photo, and clients never fetched
/image/versions for it. Summaries now carry clip_count: /photos adds the
parallel array photo_clip_counts, records (memories, search, /photos/exif)
carry clip_count. All skipped when zero, so other responses are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 19:49:21 -05:00
cameron 19cbc9d59c Merge pull request 'Derived clips: link MP4 versions, list them apart, video Enhance output' (#209) from feat/animate-video-versions into master
Reviewed-on: #209
2026-11-02 00:39:36 +00:00
Cameron CordesandClaude Opus 5.5 deb3d051ac Derived clips: link MP4 versions, list them apart, video Enhance output
Groundwork for Enhance "Animate" (#208): a video made from a photo.

- Version linking reads the XMP link from MP4 / MOV too (top-level uuid box
  with the XMP UUID, mdat seeked over), at ingest and on Enhance approve.
- New kind `animated`, a derived clip (CLIP_KINDS): hidden behind the photo
  like a version, but not counted in photo_version_counts / version_count,
  never a display choice (400 clip_not_displayable), and listed as `clips`
  in GET /image/versions. Approve returns the clip entry.
- Templates whose imageapi:save is ImageApiSaveVideoVersion report
  output "video" in /enhance/templates (no enhance.toml change). Previews
  already relay ComfyUI's content type (video/mp4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 19:38:56 -05:00
cameron 31c1b14d61 Merge pull request 'Enhance: generate 32-bit seeds' (#205) from fix/enhance-seed-u32 into master
Reviewed-on: #205
2026-11-01 23:25:20 +00:00
Cameron CordesandClaude Opus 5.5 096ddfe24e Enhance: generate 32-bit seeds
SeedVR2VideoUpscaler (the new Sharpen / Rescue templates) rejects seeds
above 2^32 - 1 at validation, and the 52-bit seeds made every job fail with
"Value ... bigger than max of 4294967295: seed". 32 bits is still plenty of
variety for the Fibo templates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 18:24:36 -05:00
cameron ce101a3b2c Merge pull request 'Every error response in the { error, code, details? } format (step 2)' (#204) from feat/error-contract-step2 into master
Reviewed-on: #204
2026-11-01 18:37:08 +00:00
Cameron CordesandClaude Opus 5.5 431c3a7492 Every error response in the { error, code, details? } format (step 2)
Moves every remaining hand-built error response onto ApiError: ~300
sites across faces, image, video, files, memories, versions,
duplicates, favorites, libraries, auth, clip/unified search, prefs,
reels, tts, insight chat, persona chat, knowledge and personas.

- 500s: the body was the error's text (anyhow / diesel / {:?}); now
  ApiError::internal logs it and sends a generic message.
- Each 4xx/502/503 has a stable code and a message written for users;
  statuses are unchanged. Shared helpers per module (faces_unavailable,
  person_not_found, unknown_library, insight_not_found, ...).
- Pre-format top-level fields kept where clients read them: upload 409
  duplicate_of / content_hash / library_name.
- PersonaChatError converts into ApiError in one place.
- Multipart upload errors use the format (error::multipart_config).
- Guard test: no non-test code builds HttpResponse::<error>() by hand.

Closes #202

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 13:29:13 -05:00
cameron 1836f1b9ca Merge pull request 'One error response format: { error, code, details? } (step 1)' (#203) from feat/error-contract into master
Reviewed-on: #203
2026-11-01 18:05:54 +00:00
cameron 5ea6d88d0c Merge pull request 'Tag writes: bulk endpoint, and fix remove/re-add/duplicate bugs' (#201) from feat/bulk-tags into master
Reviewed-on: #201
2026-11-01 18:05:45 +00:00
Cameron CordesandClaude Opus 5.5 13c244765f One error response format: { error, code, details? } (step 1)
- error::ApiError (ResponseError): status + stable snake_case code +
  display-safe message + optional details. From<anyhow>/From<diesel> and
  into_http_internal_err become a 500 "internal" whose cause is logged,
  never sent.
- JSON body, query and path parse failures, the Claims 401 and unknown
  routes now answer in the format instead of actix's plain text.
- tags handlers return ApiResult with codes (invalid_tag_name,
  tag_not_found, tag_name_taken, no_files, too_many_files,
  nothing_to_change, tag_added_and_removed). The rename 409 keeps
  existing_tag at the top level for older clients.
- Enhance's error_response builds through ApiError; its codes are
  unchanged, and an internal error no longer sends its message.
- duplicates: eight 500s sent format!("{:?}", e) to the client.

Refs #202

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 13:03:06 -05:00
Cameron CordesandClaude Opus 5.5 96a2858a50 Tag writes: POST /image/tags/bulk, and fix remove/re-add/duplicate bugs
- POST /image/tags/bulk { file_names, add: [names], remove: [ids] }: one
  transaction over many photos; names match case-insensitively and are
  created when missing; 400 if a tag is both added and removed.
- Adding a tag a photo already has is a no-op (was a 500 on
  UNIQUE(rel_path, tag_id)).
- Adding a tag no photo carries reuses it (the lookup only saw tags in
  use, so create_tag hit idx_tags_name_nocase: 500). update_tags could
  likewise skip such ids silently.
- Removing a tag matches the name case-insensitively and deletes it from
  every path sharing the photo's content, as reads union them; it used to
  answer 200 and leave the tag on the other copy.
- update_tags returns errors instead of panicking with the TagDao lock
  held, which poisoned it for every later tag request.

Closes #200

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 12:49:31 -05:00
cameron 9f013d7b78 Merge pull request 'Add GET /enhance/prompts (recent Enhance prompts)' (#199) from feat/enhance-recent-prompts into master
Reviewed-on: #199
2026-10-31 17:17:27 +00:00
Cameron CordesandClaude Opus 5.5 cdc339021b Add GET /enhance/prompts: recent distinct Enhance prompts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 12:43:27 -04:00
cameron c6d04589d1 Merge pull request 'Photo versions follow-ups (#194)' (#198) from fix/194-version-followups into master
Reviewed-on: #198
2026-10-31 01:57:07 +00:00
Cameron CordesandClaude Opus 5.5 6c5cdfe306 Fix test warning (#194)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:29:25 -04:00
Cameron CordesandClaude Opus 5.5 aba6467cde Validate original_hash in link_versions --reviewed; document follow-ups (#194)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:27:41 -04:00
Cameron CordesandClaude Opus 5.5 51600f900d Redirect manual date edits on a version path to its original (#194)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:26:37 -04:00
Cameron CordesandClaude Opus 5.5 0e1fdd43e5 Hide folders holding only hidden versions from /photos dirs (#194)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:24:22 -04:00
Cameron CordesandClaude Opus 5.5 aefc4bc411 Scope canonical_rel_path and merge version-path annotations at link time (#194)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:22:52 -04:00
Cameron CordesandClaude Opus 5.5 008dd0e7f4 Page the version name sweep with a resume cursor (#194)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:20:44 -04:00
cameron 9191257fbb Merge pull request 'Enhance: expose ComfyUI run time as run_ms' (#197) from feat/enhance-run-ms into master
Reviewed-on: #197
2026-10-30 23:09:34 +00:00
Cameron CordesandClaude Opus 5.5 47ab6b3c00 Expose ComfyUI run time on Enhance jobs as run_ms
Parse execution_start vs the end event from /history, store it when the
poller moves a job to ready/failed (never overwritten with null), and
return it as "run_ms" on every job endpoint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:08:48 -04:00
cameron c7d810b812 Merge pull request 'Enhance on demand: ComfyUI templates, jobs, approve as version (#192)' (#196) from feat/192-enhance into master
Reviewed-on: #196
2026-10-30 19:41:42 +00:00
Cameron CordesandClaude Opus 5.5 97d9301a71 feat(enhance): discard, tests against a mock ComfyUI, docs (#192)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:41:05 +00:00
Cameron CordesandClaude Opus 5.5 3a21761cfe feat(enhance): config, templates, ComfyUI client, jobs, endpoints (#192)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:41:05 +00:00
Cameron CordesandClaude Opus 5.5 21920e8608 refactor(watcher): extract register_new_files for targeted ingest
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 19:41:05 +00:00
cameron 7b1d252e75 Merge pull request 'link_versions: match ComfyUI outputs outside aiedits/; keep real names out of code' (#195) from fix/backfill-comfy-outside-aiedits into master
Reviewed-on: #195
2026-10-30 19:34:25 +00:00
Cameron CordesandClaude Opus 5.5 ccead2d7c0 chore: keep real names out of code and tests
The daily-summary cleaner hard-coded two contacts' names in its opening
phrases; a pattern now strips "Today, <Name> and I discussed" for any
contact (with a test). Video path tests use a neutral folder name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:06:21 -04:00
Cameron CordesandClaude Opus 5.5 b7ad2dae16 fix(versions): match ComfyUI outputs by loader outside aiedits/ too
link_versions only ran the ComfyUI LoadImage match on PNGs inside an
aiedits/ folder, so early outputs saved to a sibling folder (e.g.
edited/) or beside their original were never proposed. Run it for any
PNG whose prompt names a loaded image (still review-only), and search
the output's own folder before its parent when it isn't in aiedits/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:06:21 -04:00
cameron 4105430681 Merge pull request 'Photo versions: link AI restorations/edits to their original (#191)' (#193) from feat/191-photo-versions into master
Reviewed-on: #193
2026-10-30 18:36:01 +00:00
Cameron CordesandClaude Opus 5.5 9f50843479 fix(versions): review findings on PR #193
- A version no longer hides behind an original that duplicates resolve
  demoted (visible_version_clause, hidden_version_rows).
- Recording a link drops the version's faces, CLIP data and clip failures
  (covers link-at-ingest and link_versions --apply).
- The display file must live in the original's library; otherwise the
  original shows, and PUT /image/versions/display answers 400.
- link_new_files looks up hashes in chunks of 500 and logs errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:27:54 -04:00
Cameron CordesandClaude Opus 5.5 6135034d09 feat(versions): e2e fixtures, check_versions, docs
Fixtures gain an XMP-linked version (renamed file), a name-linked version
and an edit with a reference photo; e2e.sh counts with include_versions and
runs check_versions.py. CLAUDE.md documents the feature.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:53:21 -04:00
Cameron CordesandClaude Opus 5.5 fa3554c411 feat(versions): link_versions backfill binary
Dry run by default: prints a TSV of proposed links. XMP (PNG DerivedFrom,
incl. files already ingested) and file-name links are exact and apply
without review; ComfyUI prompt LoadImage inputs, Upscayl-style names and
nearest pHash are heuristic and apply only from a reviewed TSV, checked
against current hashes. Unresolvable cases are listed, not guessed. --apply
takes a WAL-safe VACUUM INTO snapshot first and aborts if it fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 11:39:35 -04:00
Cameron CordesandClaude Opus 5.5 8b7c517a17 test(versions): ingest linking, listings, endpoints, tag binding, lifecycle
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 11:33:47 -04:00
Cameron CordesandClaude Opus 5.5 080a75c336 feat(versions): link at ingest, hide in listings, version endpoints
XMP (PNG iTXt DerivedFrom, native chunk reader) then file-name convention
links at ingest; unlinked name candidates are retried each tick. Versions
are hidden from /photos (SQL for the recursive listing, hidden-row set for
the walk and tag branches), /memories, reels, /photos/exif, the GPS summary,
unified search and the media gauges while their original is reachable in the
view; include_versions=true lists them. /photos gains the parallel arrays
photo_display_paths / photo_version_counts, memories / search hits /
/photos/exif rows gain display_path / version_count, all skipped when empty.
GET /image/versions and PUT /image/versions/display (library optional).
Tags and favorites on a version path bind to the original; faces, CLIP and
duplicate grouping skip version hashes; insight generation redirects to the
original. Orphan GC and the back-ref refresh cover the new tables.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 11:28:34 -04:00
Cameron CordesandClaude Opus 5.5 044240037a feat(versions): schema and VersionDao for hash-keyed photo versions
photo_versions links a version's content_hash to its primary original's;
photo_version_references keeps provenance-only reference photos;
photo_version_display holds the global display choice. VersionDao covers
linking (chains flatten to the root), the display choice, per-original
summaries for /photos, the hidden-row set for non-SQL listings, and the
copy of the original's date/GPS/camera onto version rows. Also the SQL
predicate listings use to hide a version while its original is reachable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 11:16:32 -04:00
cameron e7aaa961ad Merge pull request 'Move LLM config to llm.toml; discover llama-swap models from metadata' (#190) from feat/llm-config-file into master
Reviewed-on: #190
2026-10-30 13:13:03 +00:00
Cameron CordesandClaude Opus 5.5 edd4609447 docs: mark each key's default in llm.example.toml; gitignore llm.toml
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 20:38:54 -04:00
Cameron CordesandClaude Opus 5.5 09b9e87396 docs: document llm.toml and llama-swap metadata discovery
README, CLAUDE.md and .env.example now point at llm.toml for LLM settings
(OPENROUTER_API_KEY stays in env) and describe the legacy env override
window, the discovered picker and the startup cross-check. ADR-0003 records
the move and amends ADR-0002's deployment-config home; CONTEXT.md's
Deployment config / Hard default entries follow. Source comments and the
"set LLAMA_SWAP_URL" error strings name the llm.toml keys.

Also fixes README's AGENTIC_MAX_ITERATIONS default (10 -> 12, what the
code uses).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 17:40:42 -04:00
Cameron CordesandClaude Opus 5.5 d68d9b09f3 feat(ai): warn at startup when llm.toml disagrees with llama-swap
After AppState is built, main checks the config against llama-swap's
/v1/models in the background and logs one warning per problem: a role
model (chat, vision, embedding, unified_search, tts) that isn't served or
lacks what the role needs (vision, kind: embed / tts), an embed model
whose metadata dim differs from embedding.dim, picker include/exclude ids
llama-swap doesn't serve, and models with no metadata. Warn-only: llama-swap
may still be starting. Chat-side roles are only checked under
backend = "llamacpp"; TTS always is.

Against the deployed .env this reports exactly the three stale
LLAMA_SWAP_ALLOWED_MODELS ids.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 17:37:05 -04:00
Cameron CordesandClaude Opus 5.5 e10fc26ca7 feat(ai): discover the llama-swap picker and vision from model metadata
/insights/models now lists llama-swap's models live from /v1/models
instead of LLAMA_SWAP_ALLOWED_MODELS: every kind=chat model, with
setParamsByID aliases (:sub / :fast) hidden, filtered by
[llamacpp.picker] exclude and the optional include allowlist. Ids the
include list names but llama-swap no longer serves drop out instead of
showing as dead picker entries.

has_vision and context_window come from each model's config.yaml metadata
(read from meta.llamaswap, where the deployed llama-swap merges it, or from
meta as upstream documents). A model without metadata is text-only chat.
A live served window still wins over the configured ctx.

Agentic insights on llama-swap now ask the chat model's capabilities:
text-only models (e.g. qwen3.8-27b-code, --no-mmproj) get the local vision
model's description inlined, like hybrid, instead of an image they can't
read. If llama-swap can't be asked, images are sent as before.

Drops AppState.llamacpp_allowed_models.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 17:34:28 -04:00
Cameron CordesandClaude Opus 5.5 aa3536afee refactor(ai): read LLM settings from LlmConfig instead of env
Every LLM env reader now goes through ai::config::llm_config():
build_ollama / build_llamacpp / build_openrouter take config sections
(OPENROUTER_API_KEY is still read from env), and the backend switch,
embedding dim + prefixes, Ollama / llama.cpp / TTS timeouts, TTS voice and
ref length, agentic caps, chat turn timeout and the unified-search model
read their sections. LocalLlm::from_env -> from_config; the
test_daily_summary and populate_knowledge bins take Ollama defaults from
the config. env_max_iterations / env_default_num_ctx are renamed to
max_chat_iterations / default_num_ctx.

The picker still uses picker.include (= LLAMA_SWAP_ALLOWED_MODELS) until
discovery lands. Legacy env vars keep working through the config's
override layer; checked against the deployed .env, which resolves to the
same values. The LLM_BACKEND parsing tests move to ai::config.

llm.example.toml now leaves the embed prefixes empty: copying the
nomic prefixes onto a corpus embedded without them breaks search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 17:30:24 -04:00
Cameron CordesandClaude Opus 5.5 5b8a99a2b2 feat(ai): LlmConfig loaded from llm.toml with legacy env overrides
Adds ai::config: one serde struct for every LLM setting (backend, llama.cpp,
Ollama, OpenRouter, embedding, TTS, agentic caps), resolved as defaults ->
llm.toml (LLM_CONFIG_PATH, default ./llm.toml) -> the old env vars. Env
overrides are kept for one release and listed in a single startup
deprecation warning; LLAMA_SWAP_ALLOWED_MODELS maps to picker.include so
current deploys keep their picker. Unknown keys, zero timeouts and
backend=llamacpp without a URL fail startup. OPENROUTER_API_KEY stays
env-only.

llm.example.toml documents the schema and is parsed by a test. main loads
the config right after tracing init; readers switch over next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 17:26:32 -04:00