Commit Graph
5 Commits
Author SHA1 Message Date
Rui Valim d70dcfeac2 fix(import): stop flagging models that mention robots as a MakerWorld CAPTCHA, and let a manual retry try again (#18)
* fix(import): stop flagging models that mention robots as a MakerWorld CAPTCHA, and let a manual retry try again

isCaptchaChallenge looked for "captcha" or "robot" in every top-level string of a reply,
design titles and summaries included, so a model named "Robot ..." failed as a CAPTCHA on
every retry and started the 2h cooloff for every other import. Only a captcha key or the
reply's own error, message or msg text counts now.

The cooloff also failed a retry straight away after the user solved the puzzle in the
browser, which is what the error message tells them to do. Starting or retrying a queue job
by hand, a bulk start or retry, and POST /import/jobs/:id/retry now lift it. Automatic
collection syncs still respect it.

* feat(extension): offer "Add to the queue" when MakerWorld blocks an import

A CAPTCHA or the daily download limit won't clear by clicking Import again, so the error
panel lets an admin park the link in the queue with the same collection and profile scope.

* fix(import): let a queue job cut off by a restart be started or retried

A restart marks a running job ERROR with its links still PENDING or RUNNING. The queue page
only offered Start for PAUSED jobs and Retry for jobs with FAILED links, and the API agreed,
so such a job could never run again. The same held for a job whose runner crashed.

Start now takes an ERROR job with links waiting, and a retry reruns links an interrupted
run never reached even when none of them failed.
2026-10-14 10:18:48 +02:00
Rui Valim 748879311a fix(ai): explain answers cut off by the token limit, and log AI and import runs (#16)
* fix(ai): leave room for a full answer and explain a cut-off one

max_tokens 300 cut answers off once the reason filled its 300-character
limit, and the run showed JSON.parse's position instead of the cause.
Raise it to 1024 and turn a parse failure into a readable error, naming
the token limit when the provider reports finish_reason length.

* feat(backend): add a shared logger with levels and greppable fields

One line per event with a level, a scope and key=value fields, or one
JSON object per line with LOG_FORMAT=json. LOG_LEVEL drops lines below
it. Errors log their message, status and code, plus the stack at error
level, and bearer tokens are redacted. Nothing outside it changes yet.

* feat(ai,import): log categorization and import runs through the logger

A bulk AI run kept only its last error in memory, so which models failed
and why was lost. Each provider answer it can't use now logs the model,
finish_reason, completion and reasoning tokens and the end of the answer;
each model's outcome and the run's start and totals are logged too.

Collection, Thingiverse, Printables, profile and zip imports dropped a
failed model without a trace; they now log it with its reason, plus the
job's start and totals. The links runner's console lines move to the
logger.

* fix(ai): read an answer wrapped in markdown or prose

Xiaomi MiMo ignores response_format and sent the object in bold,
`**{...}**`, which failed as not valid JSON although the answer was
complete. When the whole text doesn't parse, try the outermost braces.
2026-10-13 21:29:49 +02:00
Rui ValimandTautvydas Derzinskas 708a93822a feat: categorize models with an OpenAI-compatible AI provider (#11)
* feat: categorize models with an OpenAI-compatible AI provider

Models can now be sorted into the user's categories by any OpenAI-compatible
chat endpoint (llama.cpp, Ollama, vLLM, OpenAI, OpenRouter...), configured once
by an admin under settings: mode off/suggest/auto, confidence threshold,
categorize on import, concurrency, optional preview image.

Every print now records where its category came from (manual, site rule, AI,
or legacy for rows that predate this). AI never changes a manual choice: runs
and the import hook skip them, and a single-model request needs force. Folders
are never touched. Category changes go through one setPrintCategory path, so
the name check and file relocation stay shared, and AI writes are a
compare-and-set against what was read before the provider call, so an edit
made meanwhile wins.

Users get per-model recategorize, accept/reject of pending suggestions (a
rejected category is not offered again), and a background "recategorize all"
with scope flags, progress and cancel that stops after repeated provider
failures.

* feat: AI categorization settings, suggestions and bulk runs in the UI

Admin settings get an AI categorization section with a connection test and a
privacy note. The model menu offers "Recategorize with AI" (asking before it
overrides a manual category), the side panel shows a pending suggestion with
accept/reject and marks AI-set categories, and the models page has
"Recategorize all with AI" with scope, progress and cancel, plus a pending
suggestions list with bulk accept/reject.

* fix: keep suggestion actions from overlapping the reason

MUI's secondaryAction is absolutely positioned over a fixed padding, so the
two buttons covered a long reason. They now sit in the row, and wrap under
the text on narrow screens.

* fix: put the clear API key action inside the key field

A full-width outlined button under the field read as an empty input in an
error state. It is now a small action at the end of the field, and the
helper text says the key goes away on save once it's clicked.

* refactor: update AI categorization admin part

* refactor: move ai categorization buttons to categories manager modal

* fix: multiple smaller problems with AI categorization

---------

Co-authored-by: Tautvydas Derzinskas <tautvydasuk@gmail.com>
2026-10-10 20:59:52 +03:00
Rui Valim 96da36ae79 feat: link import queue with an admin page and send-to-queue in the extension, plus update from source (#6)
* feat: link queue with retries and update-from-source

Pasting several model links now imports them as one background queue
job (LINKS), one at a time, with an in-place retry for transient
failures and a retry button for whatever still fails. Failed links are
kept per item, so a retry reruns only those.

An imported model can also be refreshed from its source ("Update from
source" in the model menu, "Update model in Thingport" in the
extension). Only empty fields are filled, so an edit always wins and
tags are only added; missing files come in (for MakerWorld, the print
profiles the model doesn't hold yet) and an empty image slot is
filled.

* fix: show the import queue and log its execution

Feedback from testing a mixed batch: the provider chips only showed one
of the pasted links' providers (now every provider in the batch lights
up), the queue had no view while it ran, and the pod's logs said nothing
about what was importing.

The progress bar is now clickable and opens the queue as a list: one row
per link with its status (waiting/importing/imported/failed) and why it
failed. Items carry a RUNNING state so the current link is visible, and
a finished batch with failures shows up in warning colors instead of the
quiet bar. The runner logs each link and the job summary with an
[import] prefix, in the console style the rest of the backend uses.

* feat: admin import queue page with start, pause and retry

Links can now wait in a PAUSED LINKS job until someone starts them. The
new Administration > Import queue page lists every user's batch imports
and starts, pauses, retries and deletes link-list jobs as their owner,
with the owner's saved MakerWorld cookie. Start all and Retry all failed
run jobs one at a time across users, so a bulk run never bursts the
providers from one IP.

POST /import/queue parks one link in the user's paused queue with its
own collection, profile scope and title, keeping a single "N links
waiting" notification up to date. Pausing is cooperative: the runner
finishes the link it is on, then stops, and a job cut off by a restart
can be paused and started again to finish.

* feat(extension): send to queue toggle

A "Send to queue" switch under "Extension enabled" makes the single
model panel add the link to the instance's paused import queue instead
of importing it, keeping the panel's collection and print profile
choice. Choosing files out of a zip still imports directly, since it
needs the download right away.
2026-10-04 19:11:06 +01:00
Rui ValimandClaude Fable 5.1 a1a8db2f41 fix(extension): load common.js on Firefox, where importScripts is undefined (#2)
Firefox has no extension service workers: with both background.service_worker and
background.scripts present it runs background.js as a background page, where
importScripts() does not exist. The unguarded importScripts("common.js") on line 7
threw a ReferenceError before chrome.runtime.onMessage was registered, so every
popup message failed with "Could not establish connection. Receiving end does not
exist" and Save did nothing.

Guard the importScripts call (Chrome's service worker still uses it) and list
common.js in background.scripts so Firefox loads it before background.js.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-22 20:42:37 +02:00