Repository navigation
Merge #164, #163, #165 and #166 - #167
Merged
Merged
Conversation
FinetunedRecord.published: the Hugging Face repository and folder a model finetuned in the browser was uploaded to, so the publish code can record it and a share link can name the model there. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
The browser's catalog listed cellmap/onnx-web-models alone. A ref of another public model repo laid out the same way (a folder holding a manifest.json and the .onnx files it names, as a model published from the app is) now resolves on the spot: <owner>/<repo>/<folder>, the repo alone when it holds one model, hf: before either, or the huggingface.co URL, as the server's resolver reads them. The repo's models join the list after cellmap's, and the repo is remembered in localStorage with a listing cached for a day, so the next visit lists it too; a repo the Hub no longer shows (401 or 404) is forgotten. A repo that does not exist or is private, a folder it lacks and a repo of several models named without its folder are refused with the reason, which Add a model and Submit show; only the main revision is read. A published model's manifest names it (published.name); every name in the list stays unique, the built-in catalog's and the finetuned models' kept and another repo's model whose name is taken followed by _2. Serving re-tunes an entry's variant with withVariant, which keeps that name. A manifest's variant source and published block are kept, so a published finetune can be finetuned again on its base's file. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
A model sent as a file had no way into the browser app: Add a model's field takes a path, which means nothing to a page. Open an .onnx file… (the Models tab, browser only) picks one: an .onnx the app downloaded, which carries its manifest in metadata_props, or any .onnx with its manifest.json beside it. The model is kept in this browser as a finetuned one is (registry and byte cache, FinetunedRecord.from_file), so it survives a reload and is served, finetuned and published the same way, named after its published name or its file and kept once however often it is opened; then it goes through Add a model's own Resolve and Run. readMetadata moves from the tests into onnx-proto.ts beside withMetadata. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
Share named every Hugging Face model by its folder, which drops the repo: a model of another repo opened elsewhere as nothing, or as a built-in folder of the same name. It is now <owner>/<repo>/<folder>, and a folder of cellmap/onnx-web-models stays as it was. A finetuned model published from this browser is named where it was published (FinetunedRecord.published), whole-network finetunes included, which links could not carry before; a last-layer finetune is rebuilt on the first model up its lineage a link can name, its Hugging Face base in any repo or an ancestor published from here. A model opened from a file stays out of links until it is published, and Share says so. Opening such a link resolves the refs through the catalog, so another repo is looked up and listed there, a model-only link included (served once a volume is opened); a published ref of a model kept here is that model, and a Hugging Face model listed under another name has its layer renamed in the view. Links made before open as they did. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
web/README.md gets a section on Add a model's Hugging Face refs and Open an .onnx file…, and its link and Share sections say how another repo's model, a published finetune and a model-only link are named; docs/source/browser.rst says the same for users; a CHANGELOG bullet under Added. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
The Hugging Face client the browser's Publish to Hugging Face uses: its sign-in (oauthLoginUrl, oauthHandleRedirect), whoAmI, createRepo and uploadFilesWithProgress. 2.17.6 is the newest release at least two weeks old (2026-10-08); its dependencies are pinned as of then in the lock. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
python-data.json carries cellmap_flow.__version__, which a model published
to Hugging Face from the browser names in its manifest ("cellmap-flow
<version> (browser)"). The freshness test asks for a new export when the
version changes.
Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
The store answers cross-origin GETs by URL alone, so a request with an Authorization header (Hugging Face's who-am-I, an upload's short-lived write token) would be answered with another token's answer, or with a write token that has expired. The worker now leaves such requests to the network, as it leaves anything but a GET. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
The routes behind the Training Status card's Publish to Hugging Face,
answered in the page (backend/routes/publish.ts), with src/publish:
- /api/huggingface/credentials: the user's token, pasted or from the Sign
in popup, checked with whoAmI and kept in the page's memory only; GET
says who is signed in and gives the popup's URL when there is an OAuth
app, POST {token} or {forget: true}.
- /api/finetune/model/<name>/publish: POST starts a publish to <user>/<model>
or a repository typed (the user's or one of their organizations'), GET
follows it, and answers where a model was published before. Done, it
gives the repository's page, the ref <owner>/<repo>/<folder>, and the
links that open it in the app, the model alone and on the open dataset.
- One commit (model.ts, hub.ts): README.md, a model card linking the public
app; <folder>/manifest.json, the record's manifest with a "published"
block (the dataset only when asked); <folder>/<file>.onnx. The repository
is made when it is not there (public unless asked), and a publish to one
that is is a new commit. Errors say what to do: not signed in, a token
refused or not allowed to write, an owner that is not the user, the Hub
out of reach, the file whose upload failed.
- Sign in (oauth.ts, oauth.html): OAuth with PKCE in a popup, scopes openid
profile contribute-repos, its verifier in the popup's sessionStorage, the
token handed to the tab over a BroadcastChannel. The client id is empty
until an OAuth app is registered, and Sign in is not offered meanwhile.
The record keeps where the model went (FinetunedRecord.published). The
routes are in cellmap_flow/schema.py's table as the browser's, their
bodies FinetunePublish and HuggingFaceCredentials; the tests run the
library against a fake Hub.
Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
…X export
The browser names a model of any Hugging Face repo by its folder, and
accepts hf: before it; the server took hf: as a cellmap-models export
and refused three segments ("not a Hugging Face repo id"). A folder of a
repo can only be an ONNX web export, so hf: with one goes where onnx:
and the bare form already went. hf:<owner>/<repo> is unchanged.
Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
Beside Download finetuned model on the Training Status card, for the same model: a button that opens a compact panel (finetune/publish.js) with who is signed in to Hugging Face, Sign in (a popup, opened in the click so no blocker stops it, when the app has an OAuth app) or a pasted access token (masked by CSS and kept from password managers as the AI panel's credentials are, emptied once the route has taken it), the repository (by default <user>/<model>), Private, and whether the card names the dataset. Publish starts the upload and follows its progress; the result is the Hugging Face page, the model's ref and the two app links, each with Copy. A model published before shows where it went when the panel opens. The popup's answer reaches the panel as HUGGINGFACE_SIGN_IN_EVENT. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
ModelManifest.published is contracts.ts's PublishedBlock, declared as the load side declares it, in place of publish/model.ts's own. A model opened from a file has no base id: its finetuned_from is what its own manifest's published block says, or none, and a model trained elsewhere (a link's, a file's) says so in its card rather than claiming paint in this browser. The test holds the uploaded variant to the entry's, source and all. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
web/README.md: what is uploaded, the routes, where the token lives, the CORS checked, how a user makes a write token, and how to register the OAuth app (name, homepage, scopes, redirect URLs, no secret) and where its client id goes. docs/source/browser.rst: the panel as the user sees it, public unless Private is ticked. ARCHITECTURE.md: src/publish. CHANGELOG. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
The browser opens models of any Hugging Face repo and from an .onnx file, and share links name them by their repo; the server takes hf:<owner>/<repo>/<folder> too. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
Publish to Hugging Face for a model finetuned in the browser: sign in or paste a token, then README, manifest and .onnx go to the user's own repo, with a link to try it in the browser. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
It went in under 0.3.0's Added, the first Added heading on its branch, where Unreleased had none yet. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
…s link waits for - The result panel lists each link under its name and Copy, wrapping, in place of fields cut off in the sidebar. - After the commit, the repository is read back: done means Hugging Face shows every file at the size sent, and the page says whether the repository is public or private (a private one's links open it for no one). The two checkboxes are kept from the browser's form restore. - A link with models and no dataset says the models run on the volume you open, rather than showing nothing. - The dataset checkbox is worded as what it adds to the card. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
…waits for a volume - A model kept here that was published stands for its copy on the Hub: its ref resolves to it, and the Hub's copy is not listed beside it as `<name>_2`. - In the browser mode, a loaded model with no dataset open is "waiting" (for a volume) rather than "starting", and the list keeps asking so it turns to running once one opens. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
…and sign in with no registered app - cellmap_flow/portable/publish.py makes the model card, the manifest's "published" block, the app links and the file list for both modes; the browser's publish route calls it through the Pyodide bridge (publish_calls.py), and the TypeScript copy in publish/model.ts is gone. It also takes a server's export: several variants, and LoRA merged in. - Sign in with Hugging Face needs no OAuth app on anyone's account: the client id is the URL of a client metadata document (CIMD) the app serves. The client id, the scopes and the link parameter names have one source, in Python, exported to the browser. - The code-for-token exchange sends the client id, which @huggingface/hub's oauthHandleRedirect leaves out and a public client must send. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
web/.well-known/oauth-cimd is the document at the URL the sign-in uses as its client id; the Pages workflow copies it beside the app and fails without it, and a test holds it to the client id and the popup's page. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
… panel - The server dashboard answers the credentials and publish routes as the browser's backend does (cellmap_flow/dashboard/routes/finetune/publish.py), uploading with huggingface_hub: the export job's files, then the card and manifest from cellmap_flow.portable.publish, in one commit, read back. - Signing in on the server: a code entered at huggingface.co (OAuth device code, the same client id as the browser's popup), the token huggingface_hub already has (HF_TOKEN or `hf auth login`), or a pasted one, which Remember on this server saves where `hf auth login` does. Every token is redacted from the log the dashboard streams. - The Publish panel shows in both modes, and publish.js follows what the credentials answer offers rather than the mode. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
The repository-name check, the owner check, and what a publish says when Hugging Face refuses its token or the repository are in cellmap_flow/portable/publish.py (repo_refusal, hub_refusal), which the server's route calls and the browser's calls through Pyodide; the fixed sentences (sign in first, a token refused, one that can only read) are exported to the browser with the client id. Only the out-of-reach and the Hub's-own-reason wording stays with each mode's HTTP client. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
- cellmap_flow/finetune/export_web.py (`cellmap_flow finetune export-web`) builds the finetune's base network as training does, folds the LoRA adapter or the full weights in (export_merged.apply_finetune), and exports it with cellmap-models' onnx_web at the served block in fp32, fp16 and int8 weights, each checked against PyTorch on a raw block of the training data normalized as the trainer normalizes it, into the catalog's manifest. start_export/export_status run it through the finetune's own launch and environment (submit.module_command, taken out of build_command). - apply_finetune takes the module load_trainable_model builds; unflattened transposed convolutions can be rebuilt as real ConvTranspose layers (lora_wrapper), which the WebGPU rewrite needs. - cellmap-models comes from Marwan's onnx-web-export-and-pruning branch, in the finetune feature only, until it is released. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
…xport and export_status Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
…ntime, where models finetune Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
…GELOG, the finetuning guide and the architecture notes Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
All four trainers stepped at the form's rate throughout. A run now starts at it and eases down along half a cosine to LEARNING_RATE_FLOOR (0.1, finetune/options.py, exported to the browser in python-data.json) of it at its last step: the "big steps, then small steps" a LICONN head trained at 1e-2 and restarted at a smaller rate got by hand. The server's AdamW has a LambdaLR (trainer/loop.py learning_rate_factor), stepped after each optimizer step, over epochs x optimizer steps an epoch; a step past that count (an OOM's halved batch) stays at the floor, and a restart's new trainer starts it again. The start line and the TensorBoard config say the schedule, and train/lr is the rate each step took. The browser's last layer steps its own Adam at the schedule's rate (schedule.ts learningRateAt); its whole network in 2D steps an Adam of its own (full/adam.ts, TF.js's arithmetic), since TF.js's optimizer keeps the rate it was made with, and in 3D net3d.ts's step takes each step's rate. Each browser run's start says the schedule too. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
Each Learning Rate field (the server's, the browser's last layer's and its whole network's) was a select of four to seven rates. It is a field that takes any number above 0 (1e-4, 0.0003), with the presets in a select beside it that types the one picked in and shows the one typed. The typed rate is saved, restored and sent as before; an empty, zero, negative or non-numeric one is refused in the form, saying why. The titles say the rate eases down to a tenth by the run's end. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
…greyed out Closing the review keeps its result waiting, and Annotate stayed disabled until it was accepted or rejected, with no word of why. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
Every run eased its rate down to a tenth of the form's, with no way back
to the one rate a run used to keep. A run's learning_rate_schedule, a
TrainingOptions setting like the others, is cosine (the default, the
ease down) or constant (every step at the form's rate, as before the
schedule): the submit and restart bodies take it, the job's command line
says it (--learning-rate-schedule), a restart may change it, and the
browser's routes read it into FinetuneOptions.learningRateSchedule. The
server's LambdaLR and the browser's three trainers (learningRateAt) take
it, and each run's start line says which ("1e-4, constant over 40
steps").
The training form has an "Ease down to a tenth over the run" box under
each Learning Rate (the server's, the browser's last layer's and its
whole network's), ticked as the page starts, saved and restored with the
form and sent with submit and restart as the rate's schedule; Restart's
confirm shows it beside the rate.
Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
Seed from Prediction, All Background and Split Objects acted on a box centred where the viewer looks, one output patch or Box's size. With Field of view, beside Box, they cover what the panel shows instead: its width and height at the zoom on screen, one voxel deep at the slice shown unless Box gives a depth along the axis the panel looks along. One slice deep, a wall painted in that slice is enough to split an object. The panel's plane is the AI annotation's (in 4panel the top-left xy panel), its zoom the one view_of reads. Its size is the viewer frame's, which only the page knows (Neuroglancer's own page is of another origin on the server) and sends as viewer_px, less Neuroglancer's top row and the side panels the state shows open, and the panel's share of that in the layout. A view of more than 2048 x 2048 voxels is refused, saying how far to zoom in, and a 3d layout is refused. One rule for both modes, portable/label_it.py field_of_view_nm, which the server's routes call and the browser's through label_it_calls.field_of_view. The buttons' titles and the log line say the box used; the setting is kept with the others; Undo is unchanged. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
…s x, y, z axes Each layout now shows the plane it is named after: the xy panel, and 4panel's top-left, the XY plane. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
Use this model, Fit to Paint and Use these settings set the row's postprocessing, and since 61b8c5f an affinity model's carried the fitted threshold too: Sigmoid, then Threshold. AffinityPostprocessor reads that uint8 0/1 as DefaultPostprocessor's 0-255 and divides by 255, so a watershed added after it got affinities of at most 0.004, below its 0.5 bias, and no objects came out ("for 2d affs how to run aff on em didn't seem to work with aff thingy"). An affinity model's row now gives the sigmoid alone, both modes; its fitted threshold is still the row's, Seed from Prediction's. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
… modes
A ThresholdPostprocessor before an AffinityPostprocessor leaves the
watershed 0s and 1s and no objects: the review now says so ("warn") and
suggests the chain without the threshold (serving/probe.py
review_postprocess).
The browser gave no postprocessing review at all ("unknown"), though it
runs AffinityPostprocessor. Its advice now reviews an affinity model's
chain as the server's does, from the output class the served model's info
gives: the same two warnings, and the chain into a segmentation offered
as "optional" once its affinities show (models.ts postprocessReview, with
probe.py's suggest_affinity_chain and suggest_postprocess_params ported
beside it and NEIGHBORHOOD_OFFSETS exported through python-data.json). Its
entry's output_class is the served info's, not a second reading of the
manifest's text.
Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
setup_53_2d and setup_54_2d give three channels (z, y, x), and the browser serves them a slice a block, where nothing has a neighbour along z. The mutex watershed builds the z channel no edge there, but counted it in the mean affinity that tells a fragment's objects from background: where the model's guess at a z neighbour said "apart", every object was pulled a third of the way to background, and two 4x4 squares at 0.88 came out as none. A channel whose offset pairs no voxel in the block is now left out of the agglomeration and of that mean, both modes (post/segment.py mutex_watershed, web/src/infer/affinity.ts mutexWatershed); a block thick enough for every offset, the server's 8 slices a chunk among them, segments as before, and the parity fixtures are unchanged. In Python this also keeps an offset longer than its block from mwatershed, which panicked on one. The tests run each mode's suggested chains on such a slice: the chain offered on the ranking's sigmoid (Sigmoid, Default, Affinity) and the one the threshold warning suggests (Sigmoid, Affinity) find the two squares, and the chain with the threshold ahead finds none. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
The browser's advice keeps where the output class was read (its network, else its manifest) and takes #165's affinity verdicts; its unknown review says which. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
In the browser app the Annotation Crops tab's button answered the number of painted chunks and drew nothing: the route was a stub. It now draws the annotated_regions layer as the server's refresh_annotated_regions_layer does: a local annotation layer of axis-aligned boxes in nm over the viewer's axes, one per painted chunk of the session's volume (the one it trains on, as the server draws its manifest's volume alone), from the corner of the chunk's first voxel, half a voxel before the centre dataset_offset_nm gives (finetune/volume.ts chunkBoxes, by volumeCornerNm), labelled painted:<chunk key>. A new layer is added hidden, as the server adds it; drawn again it keeps what the user set on it, shown or hidden included (setLayers, carriedLayer), and with nothing painted it is taken away. It answers the same count. A browser volume has no imported crops (Load Crops needs a server), so the server's crop boxes have no counterpart. The good regions' layer builder draws both layers now, so it is boxesLayer, and the run_region layer's empty one uses it as before. schema.py's AVAILABILITY gives the route to both modes (regenerated). Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
… cuts its objects there David chose to show what was scored: Use this model, Fit to Paint and Use these settings give an affinity model's layer the sigmoid and the threshold fitted to the paint again, as a binary model's, both modes (rank_routes.py, rank.ts; 6515317 gave it the sigmoid alone). Its score calls a painted pair of neighbours joined above that cut-off, so a threshold there on each channel shows the decision the score read. Its objects stay one click away, cut at the same level: the model advice's optional chain into a segmentation (Sigmoid, Default, Affinity), offered over the row's Sigmoid and Threshold, takes the threshold's place and gives AffinityPostprocessor the threshold the ranking fitted for the model as its bias, 0.5 without a ranking; so does the chain the warning of a threshold ahead of the watershed suggests. The line says the cut ("joined above 0.076 (fitted to your paint)"). The fit's threshold is kept beside its steps (Session.rank_fits, the browser's rankFits) for serving/probe.py review_postprocess and suggest_postprocess_params, and models.ts postprocessReview and postprocessParams. A suggestion's Apply already makes the Processing tab's list the steps with their params, the bias among them. The tests, in both modes: an affinity model's row is Sigmoid then Threshold at the row's threshold, and the advice over it offers the chain with that bias; on one slice of two squares whose affinities are about 0.12, the chain at the fitted 0.076 finds both, at 0.5 neither. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
Once every model's probe has answered, the server's advice stops polling (the browser's slows to every 30 s), and Fit to Paint and Use these settings change the chain with no model change: the optional affinity chain went on cutting at the 0.5 it was offered at before the ranking, where it now cuts at the threshold fitted. A chain the backend sets itself (CHAIN_CHANGED_EVENT, which main.js already reads the chain again on) asks for the advice again, both modes (model-advice.js). Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
The advice's review takes both where the output class was read and the threshold the ranking fitted. Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW
Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW # Conflicts: # cellmap_flow/dashboard/routes/model_advice.py # cellmap_flow/dashboard/routes/pipeline.py # cellmap_flow/dashboard/state.py # cellmap_flow/models/configs/base.py # docs/source/browser.rst # web/src/backend/routes/models.ts
Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW # Conflicts: # CHANGELOG.md # cellmap_flow/dashboard/state.py # cellmap_flow/dashboard/static/js/dashboard/finetune/training-form.js # cellmap_flow/dashboard/templates/_models_tab.html # cellmap_flow/finetune/job_manager/submit.py # docs/api.md # web/build/export_data.py # web/src/backend/routes/models.ts # web/src/backend/routes/rank.ts # web/src/backend/serving.ts # web/src/generated/cellmap-flow.schema.json # web/src/generated/cellmap-flow.ts
Claude-Session: https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW # Conflicts: # CHANGELOG.md # web/src/backend/routes/models.ts # web/src/backend/routes/rank.ts # web/src/backend/serving.ts # web/src/finetune/onnx-proto.ts # web/src/models/index.ts # web/src/models/metadata.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merges four PRs into main in one go, as #161 did: each under its own "Merge pull request #N" commit, in order, with the conflicts between them resolved here.
Conflicts resolved (each PR's description says the same):
HuggingFaceModelConfig.declared_input_norm, which readsinput_normfirst, theninput_normalization. Git would have kept two methods silently.add-model.js's field, with the Resolve button's idaddModelResolveBtnthatmodel-file.tspresses.rank.ts'sservedForRankingkeepswithVariantwith[...inputNorm].resolveReftakes Publish finetuned models to Hugging Face from the browser or the server, and open anyone's #163's resolve body.serving.ts'sservedVariantuseswithVariant.The final tree is the one tested as a whole for the last two days (the local
try-bothbranch, without its local thread-dump helper). Each merge commit typechecks.Before merging: retarget #165 and #166 to
main(each PR's Edit beside its title), so GitHub marks them merged with this one. Merge with a merge commit, not a squash.Checks: Python 1287 passed, vitest 581, typecheck, schema and export checks, render.
https://claude.ai/code/session_01EzWMS4pG3tm1ZtvKULfSfW