Say a transcript's size, name a model by something, and ask before a reload
Five things asked for on the phone, and one trap behind the first of them.
A llama prompt carries `<__media__>` where a picture was, and llama.cpp
pairs each marker with a decoded image when it tokenizes -- so a marker in
words nobody attached a picture to fails the turn, and then fails every
later one, since the conversation is folded out of a transcript that holds
it for good. A model saying the marker back is enough to do it. Every
message with words in it now goes through `without_marker`.
A model's `general.name` is filled in by whatever converted the file, and
`convert_hf_to_gguf.py` fills it from the directory it converted: Prism ML's
Bonsai publishes `general.name = "Hf"`, which is unique and so passed the
label cascade and told a reader nothing. A name is now used only where it
shares a word with the repo or the file it came from; one that does not
drops to the file name.
The model settings dialog and the server card said what a save would cost in
a paragraph under the control, read after the decision if at all. Both ask
instead, in the shape the rest of that screen already uses for Stop and
Delete -- and the dialog's question is asked over the edits, so Cancel comes
back to them.
A field's label was `labelMedium` in the variant colour while every setting
beside it was body text, which on one form read as two ranks of setting.
`GET /sessions/{id}`'s `transcriptFile` now carries the file's size, and the
settings screen draws it beside what this phone has cached.
Checked on the emulator against the sandbox: the labels line up, the row
says "14 kB · 14 kB cached", and both confirmations appear over a loaded
Qwen3-0.6B. `cargo test` 275 passed, clippy and fmt clean, lint clean.
This commit is contained in:
1 parent
4b5ed6e398
commit
1aac22bfc9
10 files changed
+291
-52
No files matched your search
+94
-1
@@ -74,7 +74,10 @@ pub struct LocalModel {
|
||||
pub fn labels(models: &[LocalModel]) -> Vec<String> {
|
||||
let rungs = |model: &LocalModel| {
|
||||
[
|
||||
model.name.clone(),
|
||||
model
|
||||
.name
|
||||
.clone()
|
||||
.filter(|name| names_the_file(name, model)),
|
||||
Some(model.file.trim_end_matches(".gguf").to_string()),
|
||||
Some(model.key.clone()),
|
||||
]
|
||||
@@ -106,6 +109,35 @@ pub fn labels(models: &[LocalModel]) -> Vec<String> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Whether a model's own `general.name` is a name for *this* model, or the
|
||||
/// converter's working directory wearing the same field.
|
||||
///
|
||||
/// The field is filled in by whatever produced the file, and
|
||||
/// `convert_hf_to_gguf.py` fills it from the directory it converted -- so a
|
||||
/// model converted out of a folder called `hf` publishes `general.name = "hf"`,
|
||||
/// which is unique, passes the cascade above, and tells a reader nothing at
|
||||
/// all. Prism ML's Bonsai is the one here (reported 2026-09-21, drawn as "hf"
|
||||
/// in the model picker).
|
||||
///
|
||||
/// The test is corroboration rather than a list of words to distrust: a real
|
||||
/// name shares something with where the file came from, both being about the
|
||||
/// same model, and a directory name picked up in passing does not. One word in
|
||||
/// common is enough. A name that fails it drops to the next rung, which is the
|
||||
/// file name the reader downloaded.
|
||||
fn names_the_file(name: &str, model: &LocalModel) -> bool {
|
||||
let words = |text: &str| -> Vec<String> {
|
||||
text.split(|c: char| !c.is_ascii_alphanumeric())
|
||||
.filter(|word| word.len() > 1)
|
||||
.map(str::to_ascii_lowercase)
|
||||
.collect()
|
||||
};
|
||||
let from = words(&model.repo);
|
||||
let from_file = words(&model.file);
|
||||
words(name)
|
||||
.iter()
|
||||
.any(|word| from.contains(word) || from_file.contains(word))
|
||||
}
|
||||
|
||||
/// What a download is doing, or did.
|
||||
///
|
||||
/// Flat rather than a tagged enum carrying its message, because the phone
|
||||
@@ -702,3 +734,64 @@ pub fn urlencode(value: &str) -> String {
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn model(repo: &str, file: &str, name: Option<&str>) -> LocalModel {
|
||||
LocalModel {
|
||||
key: format!("{repo}/{file}"),
|
||||
repo: repo.to_string(),
|
||||
file: file.to_string(),
|
||||
bytes: 1,
|
||||
name: name.map(str::to_string),
|
||||
}
|
||||
}
|
||||
|
||||
/// The cascade, and the one rung that is not simply "is it unique": a name
|
||||
/// the file it came from says nothing about is the converter's working
|
||||
/// directory rather than the model's name.
|
||||
#[test]
|
||||
fn a_model_is_labelled_by_the_first_thing_that_identifies_it() {
|
||||
let models = [
|
||||
// Its own name, which is what a reader recognises.
|
||||
model(
|
||||
"Qwen/Qwen3-0.6B-GGUF",
|
||||
"Qwen3-0.6B-Q8_0.gguf",
|
||||
Some("Qwen3-0.6B"),
|
||||
),
|
||||
// Two quantisations carry one name, so both fall to the file.
|
||||
model("org/Big-GGUF", "Big-Q4_K_M.gguf", Some("Big")),
|
||||
model("org/Big-GGUF", "Big-Q8_0.gguf", Some("Big")),
|
||||
// `convert_hf_to_gguf.py` naming the directory it converted.
|
||||
model("PrismML/Bonsai-GGUF", "Bonsai-1B-TQ1_0.gguf", Some("hf")),
|
||||
// Nothing to go on but where it came from.
|
||||
model("org/Quiet-GGUF", "weights.gguf", None),
|
||||
];
|
||||
assert_eq!(
|
||||
labels(&models),
|
||||
[
|
||||
"Qwen3-0.6B",
|
||||
"Big-Q4_K_M",
|
||||
"Big-Q8_0",
|
||||
"Bonsai-1B-TQ1_0",
|
||||
"weights",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// Two repos holding a file of the same name: the file rung collides as
|
||||
/// well, and the key is what always terminates the cascade.
|
||||
#[test]
|
||||
fn a_file_name_two_repos_share_falls_through_to_the_key() {
|
||||
let models = [
|
||||
model("one/GGUF", "model.gguf", None),
|
||||
model("two/GGUF", "model.gguf", None),
|
||||
];
|
||||
assert_eq!(
|
||||
labels(&models),
|
||||
["one/GGUF/model.gguf", "two/GGUF/model.gguf"]
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user