Commit graph

5 commits

Author SHA1 Message Date
Daniel
83206e907c fix: a second deck reused the first deck's pictures
Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 48s
Forgejo Docker Build / Root app tests (push) Successful in 51s
Forgejo Android APK / Build signed APK (push) Successful in 2m16s
Forgejo Docker Build / Build Docker image (push) Successful in 20s
Forgejo Docker Build / Deploy to the host (push) Failing after 0s
The image job key was the request-body hash plus the figure's position — slide
index for a deck, reply index for a document. Two generations from the same form
produce the same body hash, so figure 4 of the second generation collided with
figure 4 of the first on the unique (owner, workflow, idempotency_key). The
constraint handed back the existing job, and the new deck displayed the old
deck's artwork. The decks are not even the same length, so the reused picture
could land on a slide about something else entirely.

Keyed on what is being drawn now: the body hash stays, so submitting the
identical request twice still dedupes rather than billing twice, and a hash of
the prompt (plus layout and shape) is what makes two different pictures two
different jobs. Same fix in deckBuild and resourceImages.

Also split fileLog out of logger. logger requires the database at module load,
so importing it to record a diagnostic pulls in a connection pool — wrong on its
own terms, and it hung the whole test suite when imageTool started logging its
refusals: a unit test that never touches a database inherited an open pool
handle and never exited. logger.file now delegates to fileLog, so there is still
one implementation of where a line goes and how it is redacted.

With that in place, every image-tool refusal is recorded durably. There are five
of them, they want five different fixes, and until now none of them left any
trace once the container was replaced.

Verified against a mutation: restoring the index-based key fails two of the four
collision tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-12 08:26:09 +02:00
Daniel
022869f01d fix: mobile layout regression, workspace menu in the assistant rail, prompt as principles
Mobile
body.assistant-workspace .assistant-layout is both later in the file and more
specific than the .assistant-layout rule inside @media (max-width:640px), so
phones were inheriting height: calc(100vh - 64px) — an offset for the app header
that workspace mode already hides — plus grid rules on a flex container. The
topbar then overlapped the content. Added a matching-specificity mobile override
restoring 100dvh and the flex layout.

Workspace menu in the rail
The saved-chats rail gains a collapsible Workspace section listing the app's own
tabs, so the assistant sidebar carries both its chats and the workspace
navigation. It is built from the real .tab-btn elements rather than a second copy
of the menu, so tabs added, renamed or hidden in index.html follow automatically
and admin-only tabs stay hidden.

Prompt
Rewritten as principles instead of an enumerated rulebook: "respond to the user's
latest message, not to an earlier one", "if it carries no question, ask what they
would like you to look up", "never repeat a previous answer", and for the tool
"use it when the latest message asks for a picture, or for a change to one you
just made, and not otherwise". No example words in any language remain — the
model reads the message as written. 1032 -> 788 characters.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkfrkQwA4YGrGw9LZSpeAq
2026-09-09 22:24:10 +02:00
Daniel
a95236e363 refactor: the model decides about acknowledgements and images, not route heuristics
The previous fix put a hand-maintained multilingual word list in the route. That
does not generalise — the bug report itself was "Окей", and the next one would be
a language not on the list. The model already has the tool, the system prompt and
the whole conversation, so the policy belongs where it reads it.

Removed from src/routes/clinicalAssistant.js:
- GREETING_RE and its short-circuit (an ASCII keyword list that decided, before
  the model ever saw the message, that a greeting deserved a canned reply).
- The IMAGE_NOUN / IMAGE_REPEAT vocabulary lists and the answer-repetition
  backstop added earlier today.

Moved into the prompt and the tool definition:
- DEFAULT_BEHAVIOR now says that a greeting, acknowledgement or thanks in ANY
  language gets a one-sentence request for a clinical question, and that a
  previous turn's answer must never be repeated to justify a second image.
- The generate_image description says to call it ONLY when the user's latest
  message asks for a picture or a change to one just made, and that an
  acknowledgement of an existing image is not such a request.

Both are admin-editable (clinical_assistant.system_behavior), so this can now be
tuned without a deploy.

dispatchImageRequestFallback and IMAGE_REQUEST_PATTERN stay: that is the
compatibility path for a serving model that writes the image prompt as text
instead of calling the tool. It reads the USER's message only, never the model's
answer, so it cannot replay a previous turn — it was not the cause of this bug.

The DEFAULT_BEHAVIOR byte-hash lock in prompt-administration.test.js is updated
deliberately, which is what that guard is for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkfrkQwA4YGrGw9LZSpeAq
2026-09-09 19:06:28 +02:00
Daniel
73955dd5f9 feat: model selection in chat (composer pill + popup image model), deterministic image dispatch when the model writes text instead of calling the tool, markdown-rendered email, OWUI stacked composer, left mobile drawer, Go back in topbar, DeepL removed, image history inside the Create image popup 2026-09-09 02:55:28 +02:00
Daniel
6be2d1375a feat: integrate durable image jobs/private assets into current core 2026-09-07 16:53:42 +02:00