Commit graph

10 commits

Author SHA1 Message Date
Daniel
8a6a4df121 refactor: citations are a markdown-it token, and the numbers you see are display order
The old renderer rewrote the text: it found "[n]" with regexes, renumbered
them, and swapped the result back in — which broke inside `arr[2][1]`, inside
HTML attributes, and whenever two turns disagreed about what "[3]" meant. It
also had a fallback markdown renderer of its own for when the rewrite
produced something markdown-it would not parse.

Now "[n]" is an inline rule registered on the same markdown-it instance that
renders everything else. The parser decides what is prose and what is code, a
link, or a URL, so the rule never sees "[1]" inside a code span, and it steps
aside for "[1](url)". Math is two more rules on the same parser instead of a
regex pre-pass, so "$" inside a URL is no longer math.

Identity vs display: the stored "[n]" and each card's id are the source's
identity (sourceNumber) and are never rewritten. The number a reader sees is
the order of first appearance, computed at render time from the token stream
(orderSourcesByCitation), so "one, then seven" cannot happen and a saved chat
re-opens pointing at the same cards it was saved with. Stored messages and
sources are untouched; export and the modal resolve by identity.

Translated HTML gets the same links through a TreeWalker over text nodes
(linkCitationsInHtml) rather than a regex over markup.

Deleted: renderCitationLinks, normalizeAdjacentCitationClusters, the
fallback renderer (fallbackMarkdown/renderMixedList/renderFallbackTable),
renderLatexText, CITATION_SCAN. Tests that asserted rewritten text now assert
token output; harnesses that render for real are given a parser.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-13 04:41:40 +02:00
Daniel
1f06a19007 feat: a text-only model can ask a model that can see; and the image regex is gone
Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 50s
Forgejo Docker Build / Root app tests (push) Successful in 48s
Forgejo Android APK / Build signed APK (push) Successful in 2m8s
Forgejo Docker Build / Build Docker image (push) Successful in 18s
Forgejo Docker Build / Deploy to the host (push) Failing after 0s
**The regex is gone.** The route ran a pattern over the user's message and
enqueued an image from the answer text when the model had not called the tool.
It was a compatibility path for models without tool calling and it did more harm
than good: it decided in English only, it could not see the conversation, and
"image summary" fell through it while reading as an obvious image request to the
model itself — which was measured, not assumed. A second and worse
decision-maker sitting behind the first. Whether a message deserves a picture is
now the model's call, made from the tool description, which is the only place it
ever belonged.

**Lending eyes.** The same shape, for a different capability. When someone
attaches a photograph and the chat model cannot accept image input, the
attachment was either refused by the provider or silently dropped — an answer
about a picture nobody had looked at, which is worse than a refusal.

The chat model is now offered look_at_image beside the image tool and decides
when to use it. The attachment goes to clinical_assistant.vision_model, whose
description comes back as a tool result, and the chat model answers in its own
voice with its own sources. Only the seeing is delegated; the clinical reasoning
stays with the model an administrator chose. The seeing model is told to report
and not to diagnose, because it has a picture and no context and an opinion from
it would carry weight it has not earned.

Delegation triggers only on an explicit supports_vision: false from the gateway.
An unknown is left alone — most of a roster reports nothing, and treating
silence as blindness would route good models through a detour. The capability
lookup moved to its own module, is cached for five minutes because it runs on
exactly the requests that are already slowest, and is never inferred from the
model id. liteLLMBaseUrl moved from the admin route to litellm.js, where the
other gateway helpers live.

The new setting is guarded like the slide reviewer: a model the gateway calls
text-only cannot be saved as the one that looks at images.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-12 15:43:36 +02:00
Daniel
d04a3fe53b fix: translate as HTML so tables and emphasis survive; stop repeat image generation
Translation formatting
LibreTranslate's text mode destroys markdown syntax. Verified against the live
container: table pipes come back as "←", the |---| delimiter row is translated
as prose ("Silencio."), and "**bold**" returns as "** bold**" which no longer
renders. Its html mode leaves tags — and bare [n] markers — completely intact.
Messages and the patient take home are now rendered to HTML, simplified (maths
and UI chrome flattened to text), and translated as HTML. Citation chips are
re-linked from the returned markers afterwards, which is the step the original
html path was missing. A text-mode fallback remains for builds that reject html.

Repeat image generation
Typing "Окей" or "Nice" after an image turn produced another image every time:
the model saw its own "I'll generate an educational image…" in the history and
repeated it. Recognising acknowledgements in every language is not possible, so
the rule is inverted — a short follow-up (<=3 words) that mentions nothing about
a picture does not get the image tool offered at all when the previous assistant
turn produced an image. Terse repeat requests ("again", "ещё", "another one")
still work. The worst case is that a terse question is answered in text.

In-chat images
Generated images render as a 320x240 thumbnail instead of filling the bubble,
and the image itself opens the full-resolution preview.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkfrkQwA4YGrGw9LZSpeAq
2026-09-09 18:49:58 +02:00
Daniel
4d92488f0c fix: translated answers keep their source chips; the patient take home can be translated
Translation
- Stop scrubbing markdown before sending it to LibreTranslate. The scrub
  deleted ordered-list numbering ("1. Give amoxicillin" -> "Give amoxicillin"),
  flattened tables into ambiguous whitespace and ate underscores inside
  identifiers. Raw markdown now goes to the translator unchanged.
- Render the translation through the same markdown pipeline as the original
  bubble, with the message's own sources, so [n] markers come back as the usual
  clickable .assistant-cite chips instead of escaped literal text. Headings,
  lists and tables survive with them.
- When the translator drops citation markers, surface the affected sources in a
  recovery block rather than letting the evidence disappear.
- Image cards are live nodes: they are now re-attached on every path out of a
  translation (success, failure and Show original), so a failed translation no
  longer silently removes a generating image from the message.

Patient take home
- Add a language selector to the take-home modal, reusing the existing
  /translate endpoint and offering only what the local LibreTranslate reports.
- Copy, Export and Email carry what the caregiver is actually reading; the
  original stays canonical behind "Original".

Conversation budget
- The admin field no longer prefills with the environment value, which turned
  the next Save into an accidental override and made the documented "leave
  empty to use the environment" path unreachable. The effective limit is shown
  as a placeholder instead.
- Report source 'default' honestly instead of naming an unset env var.
- The load-failure notice now lands on the <p> instead of an <input>'s
  textContent, where it rendered nothing.
- One validator for the budget everywhere: conversationLimit() replaces a
  parseInt that accepted "120000abc".

Other
- /assistant is addressed by its URL, not by ped_last_tab, so "/" no longer
  reopens the assistant; the URL follows tab changes and Back leaves it.
- Remove the dead DeepL path (it referenced an undefined DEEPL_BASES) and stop
  offering admins a provider the server silently ignores.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkfrkQwA4YGrGw9LZSpeAq
2026-09-09 18:40:23 +02:00
Daniel
094de9ebd1 fix: mobile menu — no icons, hard-truncated titles within the drawer width, ⋮ always visible without horizontal scroll; translation uses the reliable plain-text path
All checks were successful
Forgejo Android APK / Root app tests (push) Successful in 50s
Forgejo Android APK / Build signed APK (push) Successful in 2m11s
2026-09-09 12:29:37 +02:00
Daniel
ec4f1905de fix: DOMParser guard in translation simplification; translate test contract updated to html mode
All checks were successful
Forgejo Android APK / Root app tests (push) Successful in 51s
Forgejo Android APK / Build signed APK (push) Successful in 1m58s
2026-09-09 05:09:02 +02:00
Daniel
2f7d929464 fix: translation 400 — translate raw markdown as text and re-render; download buttons on previews and library items; Done state on image completion; mic mirrors the working dictation flow; send→stop icon; grouped bottom-left tools
All checks were successful
Forgejo Android APK / Root app tests (push) Successful in 46s
Forgejo Android APK / Build signed APK (push) Successful in 2m4s
2026-09-09 04:17:18 +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
09a4e02e39 feat: patient take home (copy/export/email), composer mic dictation, voice conversation mode, provider-aware translate languages 2026-09-08 20:01:46 +02:00
Daniel
8b072496e2 feat: Open WebUI-style assistant workspace — 3-column layout, markdown/math/code/tables, autosave with images, translation (LibreTranslate+DeepL), citation modal, Learning Hub moved in, handoff removed 2026-09-08 18:52:35 +02:00