Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 47s
Forgejo Docker Build / Root app tests (push) Successful in 46s
Forgejo Android APK / Build signed APK (push) Successful in 2m6s
Forgejo Docker Build / Build Docker image (push) Successful in 19s
Forgejo Docker Build / Deploy to the host (push) Failing after 0s
A figure outlives the deck it was drawn for: the deck gets replaced, the diagram is still good. Until now a generated image could only be seen in the resource it was made for, and there was no way to find one again or to delete it. Library → Images is a grid of every finished image the account owns, across all three workflows, newest first. GET /api/generated-images is scoped by owner_id in the statement rather than filtered after, returns only finished jobs — an unfinished one is a broken frame in a gallery — and pages by keyset, because a gallery that grows while you scroll repeats or skips rows under OFFSET. Most of this already existed. Thumbnails were already rendered at 256 and 640 by sharp and already served by ?w=, with their own checksum so the client's tamper check passes on a derived copy; hydrateImage already handles auth, the account boundary and caching. The tiles ask for the 256px preview, so thirty of them cost a few kB each rather than thirty full-size downloads, and the prompt is decrypted for the caption because it is the only human-readable label an image has. Deleting needed new work. The storage interface had no remove at all, so a delete that dropped the row would have left the object and both previews in the bucket — paid for, and still readable by anything with credentials. Storage now removes all three keys, and the bytes go before the row: a row pointing at a missing object is a broken image in a gallery, while an object without its row is only wasted space, and unreachable storage refuses the delete outright rather than reporting a success that left the picture behind. THUMB_WIDTHS now has one definition, in generatedImageStorage. Two copies drift, and the drift that matters is a width that gets written and never deleted. 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" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px;"></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>
|