Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 55s
Forgejo Docker Build / Root app tests (push) Successful in 45s
Forgejo Android APK / Build signed APK (push) Successful in 2m5s
Forgejo Docker Build / Build Docker image (push) Successful in 19s
Forgejo Docker Build / Deploy to the host (push) Failing after 0s
**Fallbacks.** One image model meant a refusal, a rate limit or a model the gateway had since dropped ended as a missing picture. Every workflow now tries its model, then each fallback in order, stopping at the first that produces an image. Primary plus two, capped: each hop is a paid request, and a chain long enough to need a cap is long enough to surprise someone. My Resources previously had no fallback at all — only the Clinical Assistant did, and only one. That is backwards: a missing figure is most visible in a deck, where it leaves a hole in a slide. The retry rule is now a classifier that says *why*, rather than a boolean. Transient faults, a 404 for a model the gateway does not have, and a content refusal all move to the next model — a refusal because policy is a vendor decision, not a fact about the request. 401/403 stop immediately (one gateway, one set of credentials, the next model fails identically), as do 413 and any other 4xx, which are malformed everywhere. Refusals are recognised from the message: no provider sends a machine-readable reason and the status varies. Each hop re-leases the job, so a chain cannot outlive its claim and let a second worker repeat the same paid work, and the row records the model actually being paid for so a picture made by the third model is not attributed to the first. The old singular `fallback_image_model` is still read, so an existing configuration keeps working without anyone re-entering it. **Library.** Documents/Images tabs in My Resources, with a real grid: fixed aspect tiles so the rows line up whatever shape the pictures are, a source badge on the picture, two-line prompt, hover lift, shimmer skeletons while thumbnails land, and a lightbox that closes on Escape or the backdrop and restores focus. Actions are hidden on hover only behind `@media (hover:hover)` — hiding delete behind :hover would put it out of reach on touch and keyboard. Downloads go through privateImageBlob rather than a bare `<a download href>`: a mobile client's session is a bearer token an anchor cannot send, and these assets are served no-store on purpose. The gallery lives in My Resources only. Assistant images appear in it, which was the point; the assistant page does not grow a gallery of its own, and a test asserts no assistant module lists the endpoint. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
185 lines
11 KiB
HTML
185 lines
11 KiB
HTML
<div class="card">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-folder-open"></i> My Resources</h3>
|
|
<span style="font-size:12px;color:var(--g500);">Only you can see these</span>
|
|
</div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:14px;">
|
|
<p style="margin:0;font-size:13px;color:var(--g600);line-height:1.5;">
|
|
Build a teaching deck or handout on any topic — grounded in the clinical library,
|
|
the literature, or both — then revise it below and download it as PowerPoint,
|
|
Word or PDF.
|
|
</p>
|
|
<div class="admin-row">
|
|
<label for="mr-topic" class="admin-row-label">Topic</label>
|
|
<input id="mr-topic" type="text" class="admin-control" placeholder="e.g. febrile seizures in under-fives" maxlength="300">
|
|
</div>
|
|
|
|
<div class="admin-row">
|
|
<label for="mr-kind" class="admin-row-label">Format</label>
|
|
<div style="flex:1;display:flex;gap:10px;align-items:center;flex-wrap:wrap;min-width:0;">
|
|
<select id="mr-kind" class="admin-control" style="max-width:220px;">
|
|
<option value="presentation">Presentation (slides)</option>
|
|
<option value="article">Article (prose)</option>
|
|
</select>
|
|
<label style="font-size:12px;color:var(--g600);display:flex;align-items:center;gap:6px;">
|
|
Slides
|
|
<input id="mr-slide-count" type="number" min="3" max="30" value="8" style="width:70px;font-size:13px;padding:5px 8px;border:1px solid var(--g300);border-radius:6px;">
|
|
</label>
|
|
<label style="font-size:12px;color:var(--g600);display:flex;align-items:center;gap:6px;" id="mr-word-wrap" hidden>
|
|
Words
|
|
<input id="mr-word-count" type="number" min="200" max="3000" step="100" value="800" style="width:90px;font-size:13px;padding:5px 8px;border:1px solid var(--g300);border-radius:6px;">
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-row" id="mr-model-row" hidden>
|
|
<label for="mr-model" class="admin-row-label">Model</label>
|
|
<div style="flex:1;display:flex;flex-direction:column;gap:4px;min-width:0;">
|
|
<select id="mr-model" class="admin-control" style="max-width:320px;"></select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- One group rather than four rows. Each option hides itself when an
|
|
administrator has not enabled it, so nothing appears that a person
|
|
could tick and then be refused. -->
|
|
<div class="admin-row" style="align-items:flex-start;">
|
|
<strong class="admin-row-label">Draw on</strong>
|
|
<div style="flex:1;display:flex;flex-direction:column;gap:8px;min-width:0;">
|
|
<label style="display:flex;align-items:center;gap:8px;font-size:13px;">
|
|
<input type="checkbox" id="mr-use-corpus" checked>
|
|
The clinical library
|
|
</label>
|
|
<label style="display:flex;align-items:center;gap:8px;font-size:13px;" id="mr-pubmed-row" hidden>
|
|
<input type="checkbox" id="mr-pubmed">
|
|
PubMed — cite published literature
|
|
<span style="font-size:12px;color:var(--g500);">The query goes to NCBI.</span>
|
|
</label>
|
|
<label style="display:flex;align-items:center;gap:8px;font-size:13px;flex-wrap:wrap;" id="mr-web-row" hidden>
|
|
<input type="checkbox" id="mr-web-search">
|
|
The web — for anything newer than the library
|
|
<span style="font-size:12px;color:var(--g500);">The query leaves this network; keep the topic non-identifying.</span>
|
|
</label>
|
|
<label style="display:flex;align-items:center;gap:8px;font-size:13px;" id="mr-images-row" hidden>
|
|
<input type="checkbox" id="mr-with-images">
|
|
Add illustrations — say how many in Instructions, or leave it to the model
|
|
</label>
|
|
<!-- Filled in when the instructions ask for a figure the options do not
|
|
allow. Silence there is the failure worth avoiding. -->
|
|
<p id="mr-image-hint" style="margin:0;font-size:12px;color:#92400e;" hidden></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-row" style="align-items:flex-start;">
|
|
<label for="mr-refinement" class="admin-row-label">Instructions <span style="color:var(--g400);font-weight:400;">(optional)</span></label>
|
|
<textarea id="mr-refinement" class="admin-control" style="min-height:70px;resize:vertical;font-family:inherit;" placeholder="e.g. for FY1s, case-based, emphasise red flags, use 3 diagrams"></textarea>
|
|
</div>
|
|
|
|
<div style="border-top:1px solid var(--g100);padding-top:12px;display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
|
|
<button id="btn-mr-generate" class="btn-sm btn-primary" type="button"><i class="fas fa-wand-magic-sparkles"></i> Generate</button>
|
|
<span id="mr-status" role="status" style="font-size:12px;color:var(--g600);"></span>
|
|
</div>
|
|
|
|
<!-- An illustration the model asked for. It renders here rather than in an
|
|
image history, because this feature does not have one and telling
|
|
someone to look somewhere that does not exist is worse than silence. -->
|
|
<div id="mr-images" style="display:flex;flex-direction:column;gap:10px;"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modify — change something already generated rather than starting again.
|
|
The markdown is what is stored, so "redo slide 4" is a text edit; the
|
|
resource keeps its id, its downloads and its place in the library. -->
|
|
<div class="card" style="margin-top:14px;">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-pen-to-square"></i> Modify</h3>
|
|
<span style="font-size:12px;color:var(--g500);">Revise one you already have</span>
|
|
</div>
|
|
<div style="padding:16px;display:flex;flex-direction:column;gap:14px;">
|
|
<div class="admin-row">
|
|
<label for="mr-modify-target" class="admin-row-label">Resource</label>
|
|
<select id="mr-modify-target" class="admin-control" style="max-width:420px;"></select>
|
|
</div>
|
|
|
|
<!-- The same choices as generating. A modification can need material the
|
|
original never had — "add what the 2024 trial showed" is a request for
|
|
sources, not a rewording — so the options are offered again rather
|
|
than inherited from whatever was ticked at the time. -->
|
|
<div class="admin-row" style="align-items:flex-start;">
|
|
<strong class="admin-row-label">Draw on</strong>
|
|
<div style="flex:1;display:flex;flex-direction:column;gap:8px;min-width:0;">
|
|
<label style="display:flex;align-items:center;gap:8px;font-size:13px;">
|
|
<input type="checkbox" id="mr-modify-corpus" checked>
|
|
The clinical library
|
|
</label>
|
|
<label style="display:flex;align-items:center;gap:8px;font-size:13px;" id="mr-modify-pubmed-row" hidden>
|
|
<input type="checkbox" id="mr-modify-pubmed">
|
|
PubMed — cite published literature
|
|
<span style="font-size:12px;color:var(--g500);">The query goes to NCBI.</span>
|
|
</label>
|
|
<label style="display:flex;align-items:center;gap:8px;font-size:13px;flex-wrap:wrap;" id="mr-modify-web-row" hidden>
|
|
<input type="checkbox" id="mr-modify-web-search">
|
|
The web — for anything newer than the library
|
|
<span style="font-size:12px;color:var(--g500);">The query leaves this network.</span>
|
|
</label>
|
|
<label style="display:flex;align-items:center;gap:8px;font-size:13px;" id="mr-modify-images-row" hidden>
|
|
<input type="checkbox" id="mr-modify-images">
|
|
Add illustrations
|
|
</label>
|
|
<p id="mr-modify-image-hint" style="margin:0;font-size:12px;color:#92400e;" hidden></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-row" style="align-items:flex-start;">
|
|
<label for="mr-modify-instructions" class="admin-row-label">What to change</label>
|
|
<div style="flex:1;display:flex;flex-direction:column;gap:4px;min-width:0;">
|
|
<textarea id="mr-modify-instructions" class="admin-control" style="min-height:70px;resize:vertical;font-family:inherit;" placeholder="e.g. split slide 4 into two, drop the pharmacology, add a summary slide at the end"></textarea>
|
|
<p style="margin:0;font-size:12px;color:var(--g500);">
|
|
The previous version is replaced.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div style="border-top:1px solid var(--g100);padding-top:12px;display:flex;align-items:center;gap:10px;flex-wrap:wrap;">
|
|
<button id="btn-mr-modify" class="btn-sm btn-primary" type="button"><i class="fas fa-pen-to-square"></i> Apply changes</button>
|
|
<span id="mr-modify-status" role="status" style="font-size:12px;color:var(--g600);"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card" style="margin-top:14px;">
|
|
<div class="card-header">
|
|
<h3><i class="fas fa-book"></i> Library</h3>
|
|
<div style="display:flex;align-items:center;gap:6px;">
|
|
<!-- Two views of the same library. Documents is what this card has always
|
|
shown; Images is every picture this account has generated, wherever it
|
|
was made, because a figure is worth keeping after the deck it was
|
|
drawn for has been replaced. -->
|
|
<div role="tablist" aria-label="Library view" style="display:flex;gap:2px;background:var(--g100);border-radius:6px;padding:2px;">
|
|
<button id="tab-mr-docs" role="tab" aria-selected="true" aria-controls="mr-list" class="btn-sm" type="button"
|
|
style="border:0;border-radius:4px;background:var(--white);font-size:12px;padding:4px 10px;">Documents</button>
|
|
<button id="tab-mr-images" role="tab" aria-selected="false" aria-controls="mr-images-panel" class="btn-sm" type="button"
|
|
style="border:0;border-radius:4px;background:transparent;font-size:12px;padding:4px 10px;">Images</button>
|
|
</div>
|
|
<button id="btn-mr-refresh" class="btn-sm btn-ghost" type="button"><i class="fas fa-rotate"></i> Refresh</button>
|
|
</div>
|
|
</div>
|
|
<div id="mr-docs-search" style="padding:12px 16px 0;">
|
|
<input id="mr-search" type="search" class="admin-control" placeholder="Search your resources by title or topic" autocomplete="off">
|
|
</div>
|
|
<!-- Bounded rather than unlimited: a long library otherwise pushes everything
|
|
else off the page. Tall enough to show several at a glance, and it
|
|
collapses to the content when there are only a few. -->
|
|
<div id="mr-list" role="tabpanel" aria-labelledby="tab-mr-docs" style="padding:12px 16px;display:flex;flex-direction:column;gap:6px;max-height:360px;overflow-y:auto;"></div>
|
|
|
|
<!-- A grid rather than a list: a picture is recognised by looking at it, and a
|
|
row of filenames would defeat the point of keeping them. Auto-fill so it
|
|
reflows to one or two columns on a phone without a breakpoint. -->
|
|
<div id="mr-images-panel" role="tabpanel" aria-labelledby="tab-mr-images" hidden
|
|
style="padding:12px 16px;max-height:420px;overflow-y:auto;">
|
|
<p id="mr-images-empty" style="margin:0;font-size:12px;color:var(--g500);">Loading…</p>
|
|
<div id="mr-images-grid" class="img-grid"></div>
|
|
<div style="display:flex;justify-content:center;padding-top:10px;">
|
|
<button id="btn-mr-images-more" class="btn-sm btn-ghost" type="button" hidden>Load more</button>
|
|
</div>
|
|
</div>
|
|
</div>
|