69 Commits
Author SHA1 Message Date
Tautvydas Derzinskas 88949d9d8c feat: let users choose what their API token can read over the REST API
The API token read every GET endpoint. It now reads only the scopes its user allows
(search, models, dashboard, activity, collections, tags, categories, authors, images,
model files, imports, everything else), set beside the MCP tools in a sectioned Token
permissions dialog. Existing and new tokens get every scope, so integrations such as
Homarr and holm keep working; a refused request names the scope it needs.
2026-10-21 17:57:28 +02:00
Tautvydas Derzinskas 0d0eeaf7a8 Merge pull request #111 from Ruivalim/feat/mcp-endpoint
feat: MCP endpoint for AI assistants, with tools chosen per API token
2026-10-21 16:57:17 +02:00
Tautvydas Derzinskas dec1b0dbe3 feat: admin MCP access, token permissions dialog, and review fixes
Follow-up to the review of the MCP endpoint, after merging develop.

Backend
- Instance-wide MCP access an admin sets (Off / Read only / Read and
  change, default Read and change), at GET/POST /settings/mcp and logged
  as mcp_access_changed. Off turns every token away at /api/mcp; Read
  only offers and answers only the tools that read, refuses turning a
  write tool on, and keeps a token's earlier write choices so they come
  back with Read and change. GET /profile/api-token carries mcp_access
  and only the tools it offers.
- Model links come from the public URL an admin saved, not the
  PUBLIC_URL env var it's only seeded from.
- An import refuses a category that isn't the user's own, for POST
  /import and import_model alike.
- add_to_collection inserts and logs only the models it actually adds.
- get_import_status refuses a task the web app started instead of
  failing on its result.
- The migration is now 20261030120000_api_token_mcp_tools, after every
  migration on develop and on open branches.
- Tests for each access level, the category check, a web app task id
  and the logged count.

Frontend
- The token's MCP tools move off the profile into a Token permissions
  dialog behind a cog beside the token, with MCP as its first section
  so the REST API's permissions can join it later.
- Each tool is a row: a small CSS tile picturing what it does (moving
  only while its row is hovered or focused, still for reduced motion,
  greyed when denied), its name and description, and a Deny / Allow
  Segmented control. Tools are grouped by whether they read or change
  the library.
- Admin Auth page: an AI assistants (MCP) section to set the access.
- The connect command gets a copy button and is hidden while MCP is off.
- Regenerating says the token's tools go back to the read-only ones.
- Save errors go through toasts.
- Spanish and French strings for every MCP key, and the new keys in all
  four languages.

Docs
- mcp.md and api-token.md: the Token permissions dialog, the admin
  setting, the regenerate reset, and what an assistant reading scraped
  descriptions could be talked into.
2026-10-21 08:47:49 +02:00
Tautvydas Derzinskas 12a6375832 Merge branch 'develop' into feat/mcp-endpoint 2026-10-21 08:47:09 +02:00
Tautvydas Derzinskas f913710cc4 feat: check models for print problems when they are opened
Closes #37. The first time a model is opened, each of its STL, OBJ and 3MF
files is checked in the render worker for holes, non-manifold edges, flipped
or inside-out faces, loose fragments, missing 3MF parts, damaged files and an
unlikely size. The rules follow ADMesh and Blender's 3D Print Toolbox.

The model page shows a scan overlay while it runs, then a tick or a
severity-coloured warning beside 3D Preview that opens a report with an
animated tile per problem. Results are saved per plate and rechecked only
when QUALITY_CHECK_VERSION changes.

Admins can switch checks off as a whole or per problem under Rendering;
severity and switched-off problems apply when results are read, so neither
needs a recheck. Checks are on by default.
2026-10-21 07:41:24 +02:00
Rui Valim a36a99639a feat(backend): MCP tools that import models and file them into collections
import_model imports a link through the same checks, log and
notification as + Add > Import, optionally into a collection, and hands
an import still running after 25 seconds over to get_import_status.
create_collection and add_to_collection make and fill collections. None
deletes or overwrites anything.

They're off for every token until its user turns them on. import_model
skips the captcha, as Thingport Grab does: an assistant has nowhere to
show one, and the user chose to give it the tool.
2026-10-20 14:54:41 -03:00
Rui Valim 8567f195c0 feat(backend): MCP endpoint at /api/mcp, with tools chosen per API token
An AI assistant connects over Streamable HTTP (stateless) with the user's
API token and gets read-only tools: search_models, list_models,
get_model, list_collections, get_collection, list_tags,
list_categories, get_author and library_stats. Each runs as the token's
user through the same services as the REST API.

Every MCP message is a POST, which requireAuth refuses to an API token,
so /api/mcp checks the token itself and is mounted before the routers
that apply requireAuth to every path. The token stays read-only over
REST; over MCP it only reaches the tools its user ticked, stored per
token in User.apiTokenMcpTools. Existing tokens get the read-only tools;
a tool that writes would be off for every token until its user turns it
on.

get_model reads through printOutById rather than GET /print/:id, so an
assistant looking a model up doesn't count as a view or land in
Browsing History.
2026-10-20 14:23:37 -03:00
Tautvydas Derzinskas 0d505b716a feat: keep a model's pictures and supporting files beside its model files
Gallery and description images, plate thumbnails and renders now live in an
Images folder next to the model's files (Print.assetsPath), and supporting and
prepared files sit beside the plates, named through the storage template. They
move with the model when the storage structure changes, and the storage browser
lists them by kind.

Models from before keep working: reads fall back to the old shared folders, and
the first start runs the storage move to bring their files over, then clears
what's left in those folders.
2026-10-20 15:09:19 +02:00
Tautvydas Derzinskas 926c5d345b feat: add render colour, graphite accent and hover slideshow toggle
Appearance gains a render colour that tints generated renders, the dashboard lists, tag tiles and the 3D preview.
It follows the accent until picked apart, and offers the accents plus an untinted light grey. A graphite accent
joins the palette, light grey in the dark theme so it stays visible. The hover slideshow on model cards can be
turned off, and the language picker moves to the top.

The dashboard's own Thumbnails option is gone: its lists and the tag tiles now lead with pictures or renders as
the model cards do, tags from a mosaic of the models' renders. The one-colour dashboard icon is no longer made.
2026-10-20 13:32:53 +02:00
Tautvydas Derzinskas 908ae80b5b feat: tint generated model renders with the accent
Model cards can lead with the model's own pictures (default) or with our render, a choice saved per user in Appearance. The server draws each model once in grey shading on transparent, from the first file it can draw, simplifying oversized meshes and viewing flat models from above; the browser tints it with the accent, so every theme and accent shows at once. The model page's 3D preview offers the accent palette and opens in the chosen accent.
2026-10-20 11:03:54 +02:00
Tautvydas Derzinskas d26f730bee feat: let users pick an accent color
Appearance gains an accent picker: Thingport green plus colors from provider brands (Thingiverse, Printables, Thangs, MyMiniFactory, Cults3D). The choice is stored per user beside the theme and recolors the app, the logo and its seasonal overlays at once.
2026-10-20 10:16:55 +02:00
Tautvydas Derzinskas 930b9b48db feat: add Cults3D provider
Imports Cults3D models, anyone's likes and the user's own printlists, and
syncs them. Metadata comes from Cults3D's GraphQL API with each user's own
nickname and API key. The API never hands out files, and the site's pages are
behind a Cloudflare check only a browser passes, so Thingport Grab sends the
browser's _session_id and cf_clearance cookies with its User-Agent; with
them the server orders a free model for nothing, as its Download button
does, and follows the download page to the signed file link (CloudFront, or
Cults3D's Scaleway bucket for older models). Paid models download once
bought. A Cloudflare block defers a sync until the next visit with Grab.
2026-10-20 05:08:08 +02:00
Tautvydas Derzinskas 2591714c0c feat: add Thangs provider
Imports Thangs models, likes and collections, and syncs them. Metadata is
public; files need the user's own sign-in, which Thingport Grab sends and
Thingport renews from the refresh token. Paid models download once owned;
print-only ones are refused before any download. Free accounts' daily
download limit stops a batch and defers the rest of a sync to its next check.
2026-10-19 23:36:07 +02:00
Tautvydas Derzinskas 4037151e4a feat: enable NextPrint provider 2026-10-19 18:18:26 +02:00
Tautvydas Derzinskas 772b1acfef feat: enable Creality Cloud provider 2026-10-19 14:13:45 +02:00
Tautvydas Derzinskas 5bf3457e8c feat(providers): enable MakerOnline provider 2026-10-19 07:41:05 +02:00
Tautvydas Derzinskas ac5bf253c7 feat(providers): enable Snapmaker Space provider 2026-10-19 00:57:12 +02:00
Tautvydas Derzinskas a7a91902a0 feat: add MyMiniFactory as a provider
Import, collection import and synced collections from MyMiniFactory, alongside MakerWorld,
Printables and Thingiverse.

- Backend: API v2 client with an instance-wide API key for metadata, collections and
  authors; files from MyMiniFactory's own download links (free models need no login).
  Paid models, and free ones whose files are archives, download as the user from their
  MyMiniFactory library through their session (PHPSESSID), fetching the model files
  MyMiniFactory unpacked from each archive. Paid models are left out of collection
  listings and syncs.
- One migration: MyMiniFactory category ids, username and session columns, plus a data
  migration giving existing users' starter categories the new category ids. Category
  matching now prefers the most specific site category.
- Frontend: provider everywhere the others appear (badges, Configuration and
  Administration providers, sync Browse tab, user menu, link detection, System Health,
  category ids), shared instance-key form for Thingiverse and MyMiniFactory, en and lt.
- Thingport Grab: new provider sites are allowed once from the popup (optional
  all-sites access, content script registered at runtime), so adding one never makes
  the browser disable the extension; captures the signed-in MyMiniFactory username and
  session; paid-model notice.
- Docs, website, store assets and screenshots updated.
2026-10-18 18:19:43 +02:00
Tim MooreandClaude Opus 5.5 b30b0cefd8 feat: replace the AI Thinking toggle with a per-connection reasoning effort
The Thinking switch had two states: send nothing, or send
`reasoning_effort: "none"`. That suits qwen3, which stops thinking on
"none", but not gpt-oss, which cannot stop reasoning, ignores "none" and
only takes low/medium/high. With Thinking off, gpt-oss:20b reasoned more
than with it on and ran out of the 1024-token categorisation budget three
times as often, and there was no way to ask it for "low".

Each AI connection now has a reasoning effort: the provider's default
(send nothing), none, low, medium or high. A migration keeps every
existing connection sending exactly what it sent before: Thinking on
becomes the provider default, off becomes "none". A provider that answers
400 naming reasoning_effort is still asked again without it, whichever
value was set.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 16:16:20 +11:00
Tautvydas Derzinskas 9a2238691d feat: administration settings remake + OIDC support (#49)
* feat: expand storage usage reporting with disk breakdown

* feat: add providers admin view

* feat(storage): add background reorganization and admin browser

Adds background-driven storage reorganization for moving model files
when the storage template changes, with an admin interface to preview
changes, check impact, monitor progress, and browse the storage tree
built from plate records rather than filesystem walks.

* feat: new auth admin settings page + OIDC support

* test: fix failing test
2026-10-17 05:07:24 +02:00
Tautvydas Derzinskas 9d41c4dee3 feat(admin): manage user roles and delete users, with an instance owner
Admins can now make members admins and delete members from the Users page.
The owner, the first admin, can also demote and delete other admins; nobody
can demote or delete the owner, or themselves. A migration adds the owner
flag and gives it to each existing instance's longest-standing admin, and
the first sign-up of a fresh instance takes an advisory lock so concurrent
sign-ups can't make two owners.

Deleting a user removes their models with every file, their tag mosaics and
storage folder, then the account. Roles are now read from the database on
each request, so a demotion or deletion takes effect at once. Both actions
are logged under the acting admin.
2026-10-15 12:47:06 +02:00
Tautvydas Derzinskas 97f17e4fa3 feat(AI): add improved descriptions 2026-10-14 20:29:45 +02:00
Tautvydas Derzinskas 56f173a20b feat: ability to link multiple AIs 2026-10-14 15:20:56 +02:00
Tautvydas Derzinskas 6993a4c423 feat(sync): pause syncs while a provider isn't connected, and hand back models blocked by it 2026-10-12 21:15:38 +02:00
Tautvydas Derzinskas b619084be7 feat: browse private collections functionality 2026-10-12 12:54:26 +02:00
Tautvydas Derzinskas 1131700ec5 feat(collections): turn sync on from Thingport, picking the provider's collection
Every collection's Sync configuration now works before it's synced: a
Browse tab lists the user's own collections on MakerWorld (through
their cookie, private ones included), Printables and Thingiverse (by a
username asked once), and takes any collection's link. Each provider's
collections are drawn in its own colours and manner.

On Printables and Thingiverse the collection's current models are
imported by the sync's first check. MakerWorld rate-limits bulk
imports, so there only models added from now on come in. Settings can
be picked before turning sync on.
2026-10-12 05:31:01 +02:00
Tautvydas Derzinskas 9334438a52 feat(sync): notify on enable, change defaults to daily and designer profiles 2026-10-12 04:08:24 +02:00
Tautvydas Derzinskas 1408b09c59 feat(notifications): reinvent notifications 2026-10-11 23:20:21 +02:00
Tautvydas Derzinskas 163d73f2a7 feat: add setting to customize dashboard model thumbnail appearance 2026-10-10 23:22:02 +03:00
Tautvydas Derzinskas c71154a83e feat(dashboard): add clock, weather, and featured-model widgets 2026-10-10 23:13:56 +03:00
Tautvydas Derzinskas 2813237e03 feat: extend dashboard settings with layouts 2026-10-10 21:50:29 +03: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
Tautvydas Derzinskas ef9152df6c feat(collection-sync): allow syncing collections with provider collections
Implements #10
2026-10-09 00:34:44 +02:00
Tautvydas Derzinskas 469641de18 feat(api-tokens): add per-user API tokens with generation and revocation 2026-10-08 08:10:54 +02:00
Tautvydas Derzinskas 6533781a21 feat: add users activity page 2026-10-07 20:51:44 +02:00
Tautvydas Derzinskas 3d1c851ad7 feat(dashboard): make dashboard panes toggleable 2026-10-07 18:13:24 +02:00
Tautvydas Derzinskas 97f0eea4c2 feat: support Markdown with embedded images in model descriptions 2026-10-07 00:31:34 +02:00
Tautvydas Derzinskas c52554fe7e refactor: replace re-import with narrower fill-gaps 2026-10-06 14:46:30 +02:00
Tautvydas Derzinskas 67a31cc940 feat(extension): improve loading messaging + make queue admin-only 2026-10-05 20:39:46 +02:00
Tautvydas Derzinskas dd4d2592ea fix: count queued links as models and let imported images replace 3D renders
- A links import with a profile scope counted print profiles against the
  number of links ("Imported 11 of 3 links"). Each link now counts as one
  model, and the profile totals go in the notification body.
- Opening a model while its import was still downloading images rendered a
  3D thumbnail that then stuck as the card image and first gallery image.
  Renders are now flagged, imported images replace them, and a late render
  no longer overwrites an imported cover. "Update from source" treats a
  model with only renders as having no images.
2026-10-04 20:40:06 +02: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
Tautvydas Derzinskas 93136629e4 refactor: rename storage token to support categories and folders 2026-10-02 12:49:36 +02:00
Tautvydas Derzinskas 33400fa128 feat(categories): add toggle between categories and folders 2026-10-02 12:27:03 +02:00
Tautvydas Derzinskas 3a05fae555 feat(auth): add password reset flow 2026-10-01 21:46:19 +02:00
Tautvydas Derzinskas e111dc27d7 chore: update licence and perform comment clean up 2026-09-28 22:34:47 +02:00
Tautvydas Derzinskas 101aa03f9c feat: import all designer or all MakerWorld print profiles of a model
The import dialog shows a "Print profiles" choice for a MakerWorld model link:
the profile from the link (the default, a plain import as before), every
profile its designer uploaded, or every profile including community ones. A
profile is the designer's when its instanceCreator is the design's
designCreator. The latter two run as a background job (new PROFILES job type,
POST /import/makerworld-profiles) that imports one profile at a time, paced
like a collection import: the link's or default profile creates the model,
each later one is added to it as another file, and it stops early on a
MakerWorld CAPTCHA or rejected login.

Adding a profile whose download the extension already resolved now fetches
just the file instead of the MakerWorld model page first.
2026-09-28 01:10:07 +02:00
Tautvydas Derzinskas 32710f25e7 fix: make storage template collection and creator tokens reflect real data
{collection} read a free-text Print.collection field nothing ever set, so it
always rendered "Uncollected". It now uses the model's real collections: the
collection's name, "Multiple collections" when it's in several, or
"Uncollected" when it's in none. Adding or removing a model, and renaming or
deleting a collection, moves the files when the template uses {collection}.

{creator} now resolves to the author the model page shows, falling back to
the uploader's display name for their own uploads, and an author reset moves
the files. {name} (always the same as {model}) is rewritten to {model}.

The unused Print.collection column is dropped, and the admin Storage
Structure page explains each token.
2026-09-27 20:54:06 +02:00
Tautvydas Derzinskas 91ffbf5441 feat: allow disabling registrations + invitation system 2026-09-27 19:30:34 +02:00
Tautvydas Derzinskas 303ab918ea fix: Makerworld import issues + allowing import different profiles 2026-09-24 19:15:17 +02:00
Tautvydas Derzinskas 711ece0f62 feat: allow toggling off author previews per user 2026-09-21 21:36:25 +02:00