feat(import): import an existing Windows-app installation - #125
Conversation
|
Docs PDF for Rendered from this PR's docs. Kept up to date on each push; expires after 14 days. |
|
Reproduced and fixed in 6ea3968 — thanks, that was a real bug and your install had exactly the row that finds it. What happened. The legacy It's been latent in What changed.
Guide updated for all of it. Worth a re-test against your install when you get a chance — I'd expect the 15 models, the images and your endpoint settings to come across now, with a warning naming the OpenAI model. One thing I could not verify from here: whether your install's other 14 models hold anything else this app refuses. If the import still stops, the completion box will now name the model and the reason rather than showing a bare error, so that message would be the useful thing to paste. |
sjseth
left a comment
There was a problem hiding this comment.
Ok, your latest changes have mostly resolved the import issue. I was able to pull all the models and settings for the convnext models, and validated that they were editable, functional once pulled in however the openai model did not come over correctly.
In windows the model is a loose term for a specific OpenAI API configuration with its own headstamps so there isn't really a model file behind the scenes but just a configuration.
One may have several openai models with different prompts, headstamp configurations (223, 9mm, etc) and different providers (openai, anthropic, local llm)
Recommend adding openai as a model type in the create model option. The OpenAI API config is at the model level and not a global config
The Windows app treats "OpenAI API" as a Training Mode peer of the ConvNeXt sizes: several such models can coexist -- different cartridges, prompts, headstamp lists, providers -- and the endpoint/key/model/prompt live on the model. Here, classifying over HTTP was only ever AI Config mode (the absence of an active model) with one app-level config, so an imported OpenAI model could only become a retrainable ConvNeXt shell (PR #125 review). "openai" is now a mode a model row can carry: - classifier: an active openai-mode model routes to api_client with its OWN ai_model_config -- the app-level api_cfg is deliberately ignored, the mirror image of the removed silent HTTP fallback. uses_local_inference and checkpoint_problem both wave it through, so the torch gate never fires and Start is not refused for a checkpoint it does not need. - schema: the models.model_mode CHECK admits 'openai' (SCHEMA_VERSION 6). Widening a CHECK is a table rebuild; it runs outside the migration ladder because it needs PRAGMA foreign_keys toggled, a silent no-op inside the transaction every ladder step runs in -- with FKs on, DROP TABLE models would cascade-delete every headstamp. Structurally guarded, idempotent, foreign_key_check before commit. - UI: the mode pair gains its third state -- an openai model leaves AI Config live, editing THAT model's settings (AiSection.retarget binds the server form to the model row, or the app config, repopulating only when the target changes so a half-typed endpoint survives unrelated mode/changed events); Train gets an explainer naming the mode. Create/ Edit Model offers "openai"; is_trainable is False for it, whatever the ownership. - import: legacy ModelMode 2 maps straight to "openai" -- config, headstamps and all -- and the shell fallback plus its warning are gone. A row first imported as a shell self-heals on re-import: the blank local config no longer shadows the endpoint the legacy install actually holds. SUPPORTED_MODEL_MODES deliberately keeps only the ConvNeXt backbones (the Train page assigns a mode straight into training_config.model_name); MODEL_MODES = backbones + "openai" is what ModelRepo accepts. Refs #98, requested in PR #125 review Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Found by the real-install validation run on PR #125: images are skipped by name and size on a re-run, but the checkpoint copy was unconditional -- 198 MB of I/O per re-import for a file that had not changed. The same size-proxy rule now applies, and the skipped branch still makes sure the model row points at the file, for a half-finished earlier run that copied but never recorded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@sjseth Implemented as you recommended — What that means here:
Side by side (left: your Windows shots; right: this branch):
The model library with an OpenAI model active, and Train's explainer for it:
Validated end-to-end against the real 1.3.8 install on this machine, which now contains a genuine OpenAI-mode model ("OpenAPI Test", and it is the install's One detail from that install worth knowing: every model's Happy to hear whether this matches what you had in mind — a re-test of the import on your side would exercise the filled-config path mine can't. |
The Models table, the editor, the status bar and Train's active label all
showed the stored snake_case identifiers ("convnext_small", "openai").
Reviewer feedback on PR #125: the OpenAI mode should read "OpenAI".
`models.MODEL_MODE_LABELS` / `model_mode_label` now carry the user-facing
spellings -- the Windows app's Training Mode names ("ConvNeXt-Tiny",
"OpenAI") -- applied at every surface that prints a mode, while storage
keeps the identifiers (they are also the torchvision backbone names the
trainer passes through). The editor combo shows the label and carries the
identifier as item data, and its field is renamed "Training mode" (the
Windows app's term): "Model type" collided with the library table's Type
column, which means ownership.
`_normalize_model_mode` already accepts the labels back (hyphen/case
tolerant), so a label leaking into a manifest still round-trips.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A Windows install that has been in use for a while holds years of models the user has no interest in carrying forward, and a flat "Models / Training images / Headstamps" triple could only answer all-or-nothing (sjseth on #125: "I actually only wanted to import a couple models... they may have a lot of junk in the old system"). The picker is now a tree: one branch per model, and under each its images, headstamps and trained checkpoint. The tree is also what makes the inheritance honest. Those three hang off a model row, so they are its children rather than its siblings -- untick the model and the whole branch goes with it, which is a structure the user can see instead of a rule the dialog has to explain. There is deliberately no leaf for the model's own row: it is the branch. Parts with nothing behind them get no row at all rather than a disabled one, so check propagation never has to reason about a child the user cannot reach. `ImportOptions.per_model` carries the answer, with `selection_for()` the single place that resolves it. `None` means no per-model choice was made and every surveyed model takes the app-level flags -- what every existing caller gets, so nothing about the old behaviour moves. Model ids cannot collide: `ModelRepo.create` allocates the rowid and the legacy id never reaches the `Model` object. The *name* can, so a newly-created row now goes through `model_io.unique_model_name` -- the same resolution the ZIP import has always applied, made public for this caller. An update keeps the local name, so re-running stays idempotent rather than growing `(2)`s. `survey(root, db=...)` resolves the same question early so each row can say `new model here` or `updates '<name>'` before the user commits. The ML.NET warning moves onto `LegacyModel.warning`, so the survey reports on the whole install -- which is what informs the pick -- while the import reports only on the models actually taken. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Thanks — both good calls. Pushed in e2fe3fb: the picker is now a tree, one branch per model.
Same staged 5-model install in both shots. Picking modelsEvery model is its own tick, with Training images, Headstamps and slot assignments and Trained model file underneath it. Select all / Select none are there so two out of fifteen is two clicks rather than thirteen, and the line under the tree totals what you've chosen before you commit to the wait. Each model's row carries what you need to judge it without expanding anything — cartridge, image count, headstamp count, checkpoint size. On the implicit inheritanceAgreed, and the tree is how it's enforced rather than a rule the dialog has to explain: images, headstamps and the checkpoint are the model's branch, so unticking the model takes them with it. There's deliberately no leaf for the model's own row — it's the branch, so "images without the manifest" isn't expressible. A part the model has nothing for gets no row at all rather than a disabled one. Here's a partial pick — two models declined outright, a third kept but without its 9 MB checkpoint, and the parents showing partial ticks: On the ID conflictYou were right that there's a conflict, but it turned out not to be the id. The name was the real one. The ZIP import path has always resolved a colliding name with To make it visible instead of a surprise, each model's row now states its fate up front: new model here, or updates '' naming the row it would refresh. The one thing I couldn't build: logsThere's nothing in a legacy install to populate that node. The layout is Also fixed while in thereThe ML.NET warning was install-wide, so declining an ML.NET model would still have told you it "was imported without a checkpoint" — a report on work that never happened. Warnings now hang off the model: the survey reports on the whole install (which is what informs your pick), the import reports only on what you actually took. Those models also say Docs updated in the same commit. Full suite green (1487 passed), ruff and ty clean. |
Anyone moving off the WinForms app ("AI Brass Sorter") had to rebuild their
setup by hand, one model-ZIP export at a time. This reads that installation
directly and copies across whatever the user ticks.
The legacy app keeps everything in its own install directory --
Data/ConfigDB.sjdb.json (the whole database as one BOM-prefixed JSON
document), Data/Settings.json, training/images/<id>/ and
training/models/<id>.zip -- and nothing usable in the registry, so discovery
is a Program Files probe plus pointing at the folder yourself.
Two findings shape the implementation:
- Legacy `Models` rows are the same PascalCase shape as an export ZIP's
`ModelInfo`, so they go straight to `model_io.model_from_export_dict`
rather than being re-parsed. ModelType 1/2 lands as ReadOnly /
CommunityManaged, so a community model stays non-trainable exactly as a
download here would.
- `training/models/<id>.zip` is a torch.save archive, not a ZIP of anything,
and copies to `<id>.pth` verbatim. The legacy ML.NET pipeline writes its
models beside it under the same extension, so the checkpoint is classified
by looking inside before it is copied; an ML.NET-only model is imported as
a shell to retrain into rather than skipped, since the images are the
expensive part.
Never destructive to the source, and idempotent: a community-UID match or
the per-root imported-models map updates a row in place, so slot assignments
and sorting templates survive and already-copied images are skipped.
Reached two ways: a once-ever first-run offer when an installation is
actually found, and Settings -> Import from Windows for a custom folder or a
second machine's data later.
Refs #98
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
CodeQL flagged `"ML.NET" in w` (py/incomplete-url-substring-sanitization, high): `.NET` parses as a TLD, so the check reads as a hostname allowlist applied to an unparsed URL. It is a false positive -- `warnings` is a list of user-facing strings -- but the shape it objects to was also the weaker test. The message moves to a `MLNET_WARNING` constant the module owns, and the test asserts the exact formatted string instead of sniffing for a fragment of it, which is a real coupling rather than a substring that would still pass if the sentence changed meaning. The UI summary test's stand-in warning loses the product-name-shaped literal for the same reason. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
Legacy `ModelMode` 2 mapped to a literal "openai", which is not a mode `ModelRepo` accepts, so one such row raised and rolled the whole transaction back: 15 models and 7,134 images imported as nothing, behind "Unsupported model_mode: 'openai'". `import_model` clamped this, so the new winforms path is the first caller to hit it. Classifying over HTTP is AI Config mode here, which is the absence of an active model — there is no model-row spelling of it. So the row comes across as a retrainable shell, a warning names it and points at the AI Config tick, and that tick now prefers the OpenAI-mode model's settings over whichever model happens to be declared first (the legacy app writes the blob on every model, mostly blank). Each model also gets its own SAVEPOINT, so a row this app refuses costs that row and not the install. Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
The ModelMode map and the per-model SAVEPOINT are both load-bearing now: this module is the only caller of model_from_export_dict without a clamp. Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
The Windows app treats "OpenAI API" as a Training Mode peer of the ConvNeXt sizes: several such models can coexist -- different cartridges, prompts, headstamp lists, providers -- and the endpoint/key/model/prompt live on the model. Here, classifying over HTTP was only ever AI Config mode (the absence of an active model) with one app-level config, so an imported OpenAI model could only become a retrainable ConvNeXt shell (PR #125 review). "openai" is now a mode a model row can carry: - classifier: an active openai-mode model routes to api_client with its OWN ai_model_config -- the app-level api_cfg is deliberately ignored, the mirror image of the removed silent HTTP fallback. uses_local_inference and checkpoint_problem both wave it through, so the torch gate never fires and Start is not refused for a checkpoint it does not need. - schema: the models.model_mode CHECK admits 'openai' (SCHEMA_VERSION 6). Widening a CHECK is a table rebuild; it runs outside the migration ladder because it needs PRAGMA foreign_keys toggled, a silent no-op inside the transaction every ladder step runs in -- with FKs on, DROP TABLE models would cascade-delete every headstamp. Structurally guarded, idempotent, foreign_key_check before commit. - UI: the mode pair gains its third state -- an openai model leaves AI Config live, editing THAT model's settings (AiSection.retarget binds the server form to the model row, or the app config, repopulating only when the target changes so a half-typed endpoint survives unrelated mode/changed events); Train gets an explainer naming the mode. Create/ Edit Model offers "openai"; is_trainable is False for it, whatever the ownership. - import: legacy ModelMode 2 maps straight to "openai" -- config, headstamps and all -- and the shell fallback plus its warning are gone. A row first imported as a shell self-heals on re-import: the blank local config no longer shadows the endpoint the legacy install actually holds. SUPPORTED_MODEL_MODES deliberately keeps only the ConvNeXt backbones (the Train page assigns a mode straight into training_config.model_name); MODEL_MODES = backbones + "openai" is what ModelRepo accepts. Refs #98, requested in PR #125 review Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
Found by the real-install validation run on PR #125: images are skipped by name and size on a re-run, but the checkpoint copy was unconditional -- 198 MB of I/O per re-import for a file that had not changed. The same size-proxy rule now applies, and the skipped branch still makes sure the model row points at the file, for a half-finished earlier run that copied but never recorded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
The Models table, the editor, the status bar and Train's active label all
showed the stored snake_case identifiers ("convnext_small", "openai").
Reviewer feedback on PR #125: the OpenAI mode should read "OpenAI".
`models.MODEL_MODE_LABELS` / `model_mode_label` now carry the user-facing
spellings -- the Windows app's Training Mode names ("ConvNeXt-Tiny",
"OpenAI") -- applied at every surface that prints a mode, while storage
keeps the identifiers (they are also the torchvision backbone names the
trainer passes through). The editor combo shows the label and carries the
identifier as item data, and its field is renamed "Training mode" (the
Windows app's term): "Model type" collided with the library table's Type
column, which means ownership.
`_normalize_model_mode` already accepts the labels back (hyphen/case
tolerant), so a label leaking into a manifest still round-trips.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
A Windows install that has been in use for a while holds years of models the user has no interest in carrying forward, and a flat "Models / Training images / Headstamps" triple could only answer all-or-nothing (sjseth on #125: "I actually only wanted to import a couple models... they may have a lot of junk in the old system"). The picker is now a tree: one branch per model, and under each its images, headstamps and trained checkpoint. The tree is also what makes the inheritance honest. Those three hang off a model row, so they are its children rather than its siblings -- untick the model and the whole branch goes with it, which is a structure the user can see instead of a rule the dialog has to explain. There is deliberately no leaf for the model's own row: it is the branch. Parts with nothing behind them get no row at all rather than a disabled one, so check propagation never has to reason about a child the user cannot reach. `ImportOptions.per_model` carries the answer, with `selection_for()` the single place that resolves it. `None` means no per-model choice was made and every surveyed model takes the app-level flags -- what every existing caller gets, so nothing about the old behaviour moves. Model ids cannot collide: `ModelRepo.create` allocates the rowid and the legacy id never reaches the `Model` object. The *name* can, so a newly-created row now goes through `model_io.unique_model_name` -- the same resolution the ZIP import has always applied, made public for this caller. An update keeps the local name, so re-running stays idempotent rather than growing `(2)`s. `survey(root, db=...)` resolves the same question early so each row can say `new model here` or `updates '<name>'` before the user commits. The ML.NET warning moves onto `LegacyModel.warning`, so the survey reports on the whole install -- which is what informs the pick -- while the import reports only on the models actually taken. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
Its docstring still described only the two things it returned before #77 added `torch_floor_problem` to the tail — a checkpoint that is present can now still be unloadable by the installed torch, and the summary line said otherwise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Jimisola Laursen <jimisola@jimisola.com>
86a444d to
17b368a
Compare








Note
Status. The first review validated the ConvNeXt import path against a
real installation. Its one finding — OpenAI-mode models imported as
retrainable shells because this app had no per-model OpenAI mode — is now
implemented as requested:
openaiis a first-class model mode with theAPI config at model level. A scripted end-to-end run against the real 1.3.8 installation on this
machine — including a genuine OpenAI-mode model — passed 56/56 checks;
details at the bottom.
Refs #98 — plus the follow-up work requested in the
first review.
What this does
Reads an existing WinForms ("AI Brass Sorter") installation and copies across
whatever the user ticks, so moving off the Windows app isn't a rebuild-by-hand.
Two ways in, one dialog:
is actually found and this app still looks unused. A user who never ran the
Windows app never sees anything.
location, for someone who declined the offer, or for a second machine's data
later.
The dialog, with the per-item counts a survey of the install produced:
OpenAI models are now first-class (from the review)
The review's screenshots pinned down the Windows model: "OpenAI API" is a
Training Mode, a peer of the ConvNeXt sizes; several OpenAI models can
coexist (different cartridges, prompts, headstamp lists, providers) and the
endpoint/key/model/prompt live on the model. This app now mirrors that:
model_mode = "openai"is a mode a row can carry — offered inCreate/Edit Model, accepted by the repo and the schema (widening the
models.model_modeCHECK on an existing database is a proper tablerebuild, foreign-keys-off, since the children cascade on
models).ai_model_config— never the app-level AI Config (deliberately: leakingthe app-level endpoint in would be the mirror image of the silent HTTP
fallback this app already removed). No PyTorch is needed or ever prompted
for;
checkpoint_problemwaves it through — there is no checkpoint tomiss.
openai model leaves AI Config live, editing that model's settings — a
caption above the form says whose settings they are, and the form rebinds
only when the target actually changes, so a half-typed endpoint survives
unrelated refreshes. Train shows an explainer naming the mode. Headstamps
are the model's own, in the
headstampstable like any model's.ModelMode2 maps straight toopenai— config,headstamps and all. The shell fallback and its warning are gone, and a row
first imported as a shell by the previous revision self-heals on
re-import.
Side by side with the Windows app (left: review screenshots; right: this PR):
The model library with an openai model active, and Train's explainer for it:
(The blank checked Train button in that last shot is a pre-existing sidebar
inking defect — reproduced on
mainwith a community model — trackedseparately, not introduced here.)
One deliberate boundary:
SUPPORTED_MODEL_MODESkeeps only the trainableConvNeXt backbones (the Train page assigns a mode straight into
training_config.model_name);MODEL_MODES= backbones +openaiis whatthe repo and the editor accept.
Answering the issue's open questions
1. Where does the Windows app store its data? In its own install
directory —
%ProgramFiles%\SJSeth\AI Brass Sorter— not in a per-user folder:ConfigDB.sjdb.json's top-level keys are the tables:Models,Headstamps,Cartridges,HeadStampParents,HeadStampParentLinks,SlotConfigs,SortingTemplates,CommunityNotes, plus aDefaultsblob holding theapp-level settings (serial port, slot quantity,
InitSettings, theIP_*image-processing values,
DefaultModelId, camera selection).2. Copy or reference the images? Copy. The source installation is never
written to, so it keeps working. Re-copies are skipped by name and size, so a
second run is cheap rather than doubling disk again.
3. Re-import behaviour. Update in place. A community UID match wins first
(the same shared model is the same model, wherever it came from); failing that,
a per-install-root map of legacy-id → local-id recorded by an earlier run. Slot
assignments and sorting templates survive because the row keeps its id — the
same property
import_modelalready relies on.4. ML.NET-only models. Imported as a shell, not skipped. The images are the
expensive thing to recreate and they come across; the model just has no
checkpoint, which the existing
NoLocalCheckpointErrorpath already explains,and Train is where the user picks it up. The dialog says which models this hit.
5. Is first-run the right moment? Both — offered once at first run, and
permanently available in Settings. Declining costs nothing.
The registry question
Nothing there is worth importing; the filesystem is the whole story. What
exists on a 1.3.8 install:
HKCU\Software\AICaseSorterHKCU\Software\SJSeth\AI Brass Sorter\{A83B627C-…}DesktopFolder— an MSI installer artifactHKLM\…\Uninstall\{C8B0E86E-…}DisplayName,DisplayVersion— andInstallLocationis blankSo the registry can't even be used to locate a non-default install, which is
why discovery is a
%ProgramFiles%probe plus "choose the folder yourself".The MSI component database does contain the install path, but only spread
across ~45,000 per-file component keys — not something to depend on.
Two format findings worth reviewing
Legacy
Modelsrows are already the shape we parse. They use the samePascalCase spelling as an export ZIP's
ModelInfo, so they go straight tomodel_io.model_from_export_dict— enum-intModelMode/ModelTypemappingincluded — rather than getting a second parser.
ModelType1/2 therefore landsas
ReadOnly/CommunityManaged, so a community model stays non-trainable hereexactly as a download would.
training/models/<id>.zipis not a ZIP of anything — it's a rawtorch.savearchive (which is itself a zip container), so it copies straight to
<id>.pth. That matters because the older ML.NET pipeline writes its modelsbeside it under the same extension (
TransformerChain/wrapping a frozenTensorFlow graph). The checkpoint is classified by looking inside before it is
copied —
*/data.pkl= torch,TransformerChain/= ML.NET — so an ML.NET modelcan never be installed as a PyTorch checkpoint.
Deliberately narrow
Defaults.IP_*maps toimage_proc.linescanonly. Those values belong tothe legacy line-scan pipeline; there is no Hough stage over there, so writing
its numbers into our Hough parameters would silently detune a working crop.
Hough and
strategyare left alone.moniker and a device name; we store an index. Mapping one to the other is a
guess that would point the app at the wrong camera.
only adopts the legacy
DefaultModelIdwhen nothing is active yet.InitSettingskeys we don't recognise are dropped — an unknown keywould be written straight to the board on connect.
Still-open questions for @sjseth
Things one installation couldn't show me:
SlotConfigs[].ParentConfig— what is in it? It is[]in every row onthis machine, even with parent classifications defined, so I can't confirm
the entry shape. The parser accepts a bare string,
{"Name": …}and a{"Parent": {"Name": …}}wrapper. Which is right?PackageMode: truerows exist butare all empty here. I'm assuming
Configcarries the same{"Headstamp": {"Name": …}}entries as standard mode. Correct?SortingTemplatesis empty on this install, so templates are notimported at all right now. Is the row shape stable enough to add, and does
SlotConfigs[].TemplateIdreference it?readable? Right now a non-default install is only reachable by the user
pointing at the folder.
ModelMode0 and 1 (DeepLearning/ResNet50 and Inception) currentlyfall back to
convnext_tinyinmodel_io. For an import that fallback isalso what the model row will claim it is. Is there a better answer than
"treat it as an untrained shell"?
training/images/emulation/(56DATA__<ticks>.jpgfiles) andtraining/images/tuning/— I've assumed these are app scratch space, notuser data, and skipped them. Right call?
Testing
tests/unit/data/test_winforms_import.py— 32 tests: discovery, surveycounts, ML.NET and OpenAI-mode handling, ownership, per-model SAVEPOINT
isolation, headstamps/parents/slots, package-mode slots, idempotent
re-import, each settings block, source-tree immutability, the first-run gate.
tests/unit/ui/test_winforms_import_dialog.py— 13 tests for the dialog.the HTTP client with its own config;
uses_local_inferenceFalse;checkpoint_problemNone), the schema rebuild against a hand-writtenpre-openai database (proving the headstamp cascade does not fire),
rebuild idempotence,
MODEL_MODESacceptance, the AI Config page'sper-model targeting (bind/save/retain-unsaved-edits/rebind), the mode
pair's third state, Train's openai explainer, and the editor offering and
persisting
openai.1470 passed),ruffandtyclean, CI green.Real-installation validation
A scripted run against the actual
C:\Program Files\SJSeth\AI Brass Sorter(1.3.8) on this machine, importing into a throwaway data root — the install
itself is only ever read. All 56 checks passed:
image-processing detected.
their 26 links, 1 slot assignment, 1 torch checkpoint (198,823,697 bytes,
byte-identical at the destination). The genuine OpenAI-mode model
("OpenAPI Test", legacy
ModelMode2) landed asStandard/openai,not trainable, and — being the legacy install's
DefaultModelId— becamethe active model here, with
uses_local_inferenceFalse andcheckpoint_problemNone. The community model kept its UID and stayedReadOnly/convnext_small.assignments intact, library size unchanged. (The run also caught that
checkpoints were re-copied unconditionally — fixed in 428764b with the
same size-skip rule the images use.)
Data\andtraining\snapshotted before and after two full runs — 0 added, 0 removed,
0 changed.
landed exactly; Hough untouched.
ai_config_importedwas correctly False— every
AIModelConfigblob in this install isnull(the OpenAI APIConfiguration screen was never saved), and the importer reported that
honestly instead of claiming success.
🤖 Generated with Claude Code