Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
0b493ea
ui : add the models manager
allozaur Sep 28, 2026
e164fe8
ui : group the model selector components
allozaur Sep 28, 2026
282869e
ui : rework the selector around its providers
allozaur Sep 28, 2026
211826e
ui : move reasoning effort beside the model selector
allozaur Sep 28, 2026
69e9d49
ui : drop an orphaned doc comment left by a moved method
allozaur Sep 29, 2026
e7693a8
ui : move mcp servers to the sidebar rail
allozaur Sep 28, 2026
3aab29b
ui : default to the Hub metadata and hide avatars when it is off
allozaur Oct 1, 2026
e4ccbec
ui : list downloads as their own manager section
allozaur Oct 1, 2026
39fc670
ui : refine the model pane and the manager filters
allozaur Oct 2, 2026
642df40
ui : keep hidden models out of the selector
allozaur Oct 2, 2026
22ae02a
ui : do not drop models whose context is unknown
allozaur Oct 2, 2026
45ba758
ui : rename the model org name key in the migration service
allozaur Oct 2, 2026
ccd4a65
ui : turn the hub metadata off by default
allozaur Oct 2, 2026
6394445
server : keep serving the ui under cross-origin isolation
allozaur Oct 2, 2026
26e36d8
ui : fetch the mcp server icons through the cors proxy
allozaur Oct 2, 2026
8212fc0
ui : drop the unused recent model usage stamps
allozaur Oct 2, 2026
52c1573
ui : move the models manager constants into the enums
allozaur Oct 2, 2026
eba729a
ui : read the group models setting through its key
allozaur Oct 2, 2026
c1ab9d7
ui : print context lengths as plain numbers
allozaur Oct 2, 2026
18d0067
ui : describe the models dialog and drop two leftovers
allozaur Oct 2, 2026
43f9547
ui : derive the model load state in one place
allozaur Oct 2, 2026
7c97bc6
ui : read the repo of a model id in one place
allozaur Oct 2, 2026
66b591a
ui : move the model list helpers under utils
allozaur Oct 2, 2026
2a69dfa
ui : search the manager with the selector filter
allozaur Oct 2, 2026
91a2338
ui : select the manager rows with a button
allozaur Oct 2, 2026
c84cccb
ui : decouple the grouped list from the models store
allozaur Oct 2, 2026
90826af
ui : cover the model list helpers with tests
allozaur Oct 2, 2026
a97ecf0
ui : cover the manager helpers and row semantics
allozaur Oct 2, 2026
6505bd4
ui : add stories for the models manager
allozaur Oct 2, 2026
5b5f1a0
ui : keep the manager rows as they were
allozaur Oct 2, 2026
78f12c2
ui : let the model avatar follow the family grouping
allozaur Oct 2, 2026
63ae1ec
ui : fetch the search result icons through the cors proxy
allozaur Oct 2, 2026
fa886b9
ui : describe the models manager in the readme
allozaur Oct 2, 2026
7fbc70b
ui : guard the context lookup on a repo id
allozaur Oct 2, 2026
aaa4f86
ui : do not cache a failed base model lookup
allozaur Oct 2, 2026
e657259
ui : split the hidden quants from the local ones
allozaur Oct 2, 2026
cedc377
ui : derive the router mode once in the selector hook
allozaur Oct 2, 2026
dfdfdae
ui : type the download entries once
allozaur Oct 2, 2026
b081f0f
ui : tidy the model list pieces
allozaur Oct 2, 2026
3573073
ui : map the sidebar actions to their handlers
allozaur Oct 2, 2026
6ca626c
ui : note why the model name selection skips the updating guard
allozaur Oct 2, 2026
8afce40
ui : drop a leftover download doc comment
allozaur Oct 2, 2026
8a98965
ui : focus the composer once the models manager closes onto the chat
allozaur Oct 2, 2026
fe915aa
ui : trim the model list helpers down to what the selector reads
allozaur Oct 2, 2026
8b5d646
ui : drop a stray doc comment over the hidden models loader
allozaur Oct 2, 2026
c332e94
ui : spell the loading label with ascii dots
allozaur Oct 2, 2026
e2c141b
ui : let the models manager hand the focus back
allozaur Oct 3, 2026
5076593
ui : grow a windowed list by a page
allozaur Oct 3, 2026
8cbef87
ui : keep a model with unread capabilities listed
allozaur Oct 3, 2026
b84875a
ui : keep the manager read-only on a model server
allozaur Oct 3, 2026
bb24526
ui : select a model row with a button
allozaur Oct 3, 2026
224d881
ui : drop the unused providers settings section
allozaur Oct 3, 2026
9d802a9
ui : correct the avatar org note
allozaur Oct 3, 2026
2cf7f84
ui : drop the context column on a phone
allozaur Oct 3, 2026
8bbb873
ui : stack the models toolbar on a phone
allozaur Oct 3, 2026
217bba5
ui : show row actions in a drawer on a phone
allozaur Oct 3, 2026
94cb3c1
ui : pick a model in the manager on a phone
allozaur Oct 3, 2026
ca223d7
ui : cover the table with the model pane on a phone
allozaur Oct 3, 2026
04194e4
ui : tighten the model row on a phone
allozaur Oct 3, 2026
dce6148
ui : keep the model badges whole
allozaur Oct 3, 2026
613d6de
ui : even out the dialogs on a phone
allozaur Oct 3, 2026
4e7bd7f
ui : give the model pane header room on a phone
allozaur Oct 3, 2026
01bb149
ui : close the mobile navigation behind a dialog
allozaur Oct 3, 2026
c8cdfd0
ui : give the pane header its own rows on a phone
allozaur Oct 3, 2026
d9dba7a
ui : size the close of every panel the same on a phone
allozaur Oct 3, 2026
88545ff
ui : keep the family chevron on its row on a phone
allozaur Oct 3, 2026
4853b6c
ui : pick the reasoning effort in a drawer on a phone
allozaur Oct 3, 2026
11d12ad
ui : keep the status column off the phone table
allozaur Oct 3, 2026
dd71708
ui : offer the add actions in a drawer on a phone
allozaur Oct 3, 2026
bd46b17
ui : open the context panel in a drawer on a phone
allozaur Oct 3, 2026
ed13436
ui : leave the panel close unfocused when a phone opens it
allozaur Oct 3, 2026
fbc399d
ui : drop the actions column from the phone table
allozaur Oct 3, 2026
aac3667
ui : grow the reasoning bulb on a phone
allozaur Oct 3, 2026
fba235c
ui : switch the open chat from the model pane on a phone
allozaur Oct 5, 2026
dda7c56
ui : give the phone table more room
allozaur Oct 5, 2026
9adc621
ui : float the model actions in a bar on a phone
allozaur Oct 5, 2026
c066835
ui : drop the border of the phone's full screen dialogs
allozaur Oct 5, 2026
615ed15
ui : keep the reasoning trigger transparent on a touch screen
allozaur Oct 5, 2026
799da9d
ui : draw no capability box when there is nothing in it
allozaur Oct 5, 2026
f8eeddf
ui : tighten the phone model trigger
allozaur Oct 5, 2026
4ca5a6e
test : give the switch test an open chat
allozaur Oct 5, 2026
bedd7c0
common : resolve <quant>-<sidecar> download tags and list cached side…
allozaur Sep 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
279 changes: 221 additions & 58 deletions common/download.cpp

Large diffs are not rendered by default.

141 changes: 137 additions & 4 deletions tests/test-model-resolution.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include <cstdio>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <map>
#include <thread>
#include <string>
Expand Down Expand Up @@ -139,7 +140,7 @@ static const std::vector<std::string> hole = {
"dflash-model-Q8_0.gguf",
};

// unsloth-style naming with UD quants and a suffix MTP file
// unsloth-style naming with UD quants and an uppercase infix MTP head
static const std::vector<std::string> unsloth = {
"model-UD-Q8_K_XL.gguf",
"mmproj-BF16.gguf",
Expand Down Expand Up @@ -180,6 +181,13 @@ static const std::vector<std::string> spark = {
"dspark-model-MXFP4.gguf",
};

// main weights plus a trailing-form mtp head, as in unsloth
// gemma-4-E2B-it-GGUF (Model-Q4_0-mtp.gguf)
static const std::vector<std::string> trailing = {
"model-BF16.gguf",
"model-BF16-mtp.gguf",
};

// dspark outranks dflash in the type auto-selection
static const std::vector<std::string> dspark_dflash = {
"model-Q8_0.gguf",
Expand Down Expand Up @@ -223,7 +231,7 @@ static const plan_case plan_cases[] = {
// no tag and no default match falls back to the first model in the listing
{"unsloth fallback", unsloth, "test/repo", "", true, true,
"model-UD-Q8_K_XL.gguf", {"model-UD-Q8_K_XL.gguf"},
"mmproj-BF16.gguf", "", "", "", ""},
"mmproj-BF16.gguf", "model-MTP-BF16.gguf", "", "", ""},

// explicit hf_file picks that exact file
{"flat hf_file", flat, "test/repo", "model-BF16.gguf", false, false,
Expand Down Expand Up @@ -258,10 +266,11 @@ static const plan_case plan_cases[] = {
"model-Q4_K_M.gguf", {"model-Q4_K_M.gguf"},
"", "mtp-model-Q4_0.gguf", "dflash-model-Q8_0.gguf", "", ""},

// the mtp- keyword is case sensitive, a suffix -MTP file is not discovered
// the uppercase infix MTP head is a sidecar too; it resolves at the
// nearest quant to the tag when the exact one does not exist
{"unsloth suffix mtp", unsloth, "test/repo:Q8_K_XL", "", true, false,
"model-UD-Q8_K_XL.gguf", {"model-UD-Q8_K_XL.gguf"},
"mmproj-BF16.gguf", "", "", "", ""},
"mmproj-BF16.gguf", "model-MTP-BF16.gguf", "", "", ""},

// vendor prefixes and the dot quant convention both match the tag,
// first match wins between two files at the same quant
Expand All @@ -283,6 +292,45 @@ static const plan_case plan_cases[] = {
{"spark tag sidecar", spark, "test/repo:BF16", "", true, false,
"", {},
"", "", "", "", "dspark-model-BF16.gguf"},

// a `<quant>-<sidecar>` tag resolves that sidecar alone, without a primary
{"hole quant-sidecar tag", hole, "test/repo:Q4_0-mtp", "", false, false,
"", {},
"", "mtp-model-Q4_0.gguf", "", "", ""},

{"spark quant-sidecar tag", spark, "test/repo:BF16-dspark", "", false, false,
"", {},
"", "", "", "", "dspark-model-BF16.gguf"},

// a bare sidecar tag resolves the sidecar at any quant
{"hole bare sidecar tag", hole, "test/repo:mtp", "", false, false,
"", {},
"", "mtp-model-Q4_0.gguf", "", "", ""},

// a trailing-form sidecar resolves as the sidecar alone, never as a primary
{"trailing quant-sidecar tag", trailing, "test/repo:BF16-mtp", "", false, false,
"", {},
"", "model-BF16-mtp.gguf", "", "", ""},

// the plain tag resolves the plain model; the trailing-token file is skipped
{"trailing plain tag", trailing, "test/repo:BF16", "", false, false,
"model-BF16.gguf", {"model-BF16.gguf"},
"", "", "", "", ""},

// a short-form sidecar (`mmproj-F16.gguf`) resolves by its bare quant tag
{"unsloth quant-sidecar tag", unsloth, "test/repo:BF16-mmproj", "", false, false,
"", {},
"mmproj-BF16.gguf", "", "", "", ""},

// an uppercase infix sidecar resolves through the lowercase tag
{"unsloth uppercase infix tag", unsloth, "test/repo:BF16-mtp", "", false, false,
"", {},
"", "model-MTP-BF16.gguf", "", "", ""},

// a sidecar token in the middle of the name resolves as the sidecar alone
{"subdir quant-sidecar tag", subdir, "test/repo:Q8_0-mtp", "", false, false,
"", {},
"", "model-mtp-Q8_0.gguf", "", "", ""},
};

static void check_plan(const plan_case & c) {
Expand Down Expand Up @@ -482,6 +530,90 @@ static void test_task_assembly() {
g_repos.clear();
}

//
// cache listing and removal against the isolated LLAMA_CACHE, using the
// same filename grammar the plan tests exercise above
//

static void cache_put(const std::string & repo, const std::string & path) {
namespace fs = std::filesystem;
auto local = fs::path(cached(repo, path));
fs::create_directories(local.parent_path());
{ std::ofstream(local) << "gguf"; }
auto repo_dir = local.parent_path().parent_path().parent_path();
auto refs = repo_dir / "refs";
fs::create_directories(refs);
auto ref = refs / "main";
if (!fs::exists(ref)) {
std::ofstream(ref) << COMMIT << "\n";
}
}

static bool cache_lists(const std::string & repo_tag) {
for (const auto & e : common_list_cached_models()) {
if (e.to_string() == repo_tag) {
return true;
}
}
return false;
}

static void test_cache_listing_and_remove() {
namespace fs = std::filesystem;
const std::string repo = "test/eh";

printf("test-model-resolution: cache listing and removal\n");

g_context = "cache fixture";
cache_put(repo, "gemma-4-E2B-it-BF16.gguf"); // main weights
cache_put(repo, "gemma-4-E2B-it-BF16-mtp.gguf"); // trailing-form draft head
cache_put(repo, "gemma-4-31B-it-MTP-BF16.gguf"); // uppercase infix draft head
cache_put(repo, "mmproj-gemma-4-E2B-it-BF16.gguf"); // mmproj sidecar
cache_put(repo, "mmproj-F16.gguf"); // short-form mmproj
cache_put(repo, "model-mtp-Q8_0.gguf"); // mid-name mtp sidecar

// a sidecar is listed under `<quant>-<sidecar>` in every form and case it
// can be named; only the token-less main weights stay a loadable model
REQUIRE(cache_lists("test/eh:BF16"));
REQUIRE(cache_lists("test/eh:BF16-mtp"));
REQUIRE(cache_lists("test/eh:BF16-mmproj"));
REQUIRE(cache_lists("test/eh:F16-mmproj"));
REQUIRE(cache_lists("test/eh:Q8_0-mtp"));
REQUIRE(!cache_lists("test/eh:MTP"));

// a plain quant tag removes the model files and leaves every sidecar,
// whatever form or case its name carries
g_context = "remove plain quant";
REQUIRE(common_download_remove("test/eh:BF16"));
REQUIRE(!fs::exists(cached(repo, "gemma-4-E2B-it-BF16.gguf")));
REQUIRE(fs::exists(cached(repo, "gemma-4-E2B-it-BF16-mtp.gguf")));
REQUIRE(fs::exists(cached(repo, "gemma-4-31B-it-MTP-BF16.gguf")));
REQUIRE(fs::exists(cached(repo, "mmproj-gemma-4-E2B-it-BF16.gguf")));
REQUIRE(fs::exists(cached(repo, "mmproj-F16.gguf")));
REQUIRE(fs::exists(cached(repo, "model-mtp-Q8_0.gguf")));

// a `<quant>-<sidecar>` tag removes every sidecar listed under it,
// across forms and cases
g_context = "remove quant-sidecar";
REQUIRE(common_download_remove("test/eh:BF16-mtp"));
REQUIRE(!fs::exists(cached(repo, "gemma-4-E2B-it-BF16-mtp.gguf")));
REQUIRE(!fs::exists(cached(repo, "gemma-4-31B-it-MTP-BF16.gguf")));
REQUIRE(fs::exists(cached(repo, "mmproj-gemma-4-E2B-it-BF16.gguf")));

// the short form removes by its bare quant tag
REQUIRE(common_download_remove("test/eh:F16-mmproj"));
REQUIRE(!fs::exists(cached(repo, "mmproj-F16.gguf")));

REQUIRE(common_download_remove("test/eh:Q8_0-mtp"));
REQUIRE(!fs::exists(cached(repo, "model-mtp-Q8_0.gguf")));

// a bare sidecar tag is ambiguous across quants and is rejected
g_context = "remove bare sidecar";
cache_put(repo, "mtp-Model-Q4_0.gguf");
REQUIRE(!common_download_remove("test/eh:mtp"));
REQUIRE(fs::exists(cached(repo, "mtp-Model-Q4_0.gguf")));
}

int main(void) {
// unbuffered, so a crash cannot swallow the reports already printed
setvbuf(stdout, nullptr, _IONBF, 0);
Expand Down Expand Up @@ -510,6 +642,7 @@ int main(void) {

test_plan_resolution();
test_task_assembly();
test_cache_listing_and_remove();

server.stop();
server_thread.join();
Expand Down
Loading