Settings as a screen with two tabs, and fields that cost one line
The settings dialog had outgrown a dialog: it scrolled inside itself, covered the session it is about, and had nowhere to put a second tab. It is a screen now, drawn over the session like the file explorer so the session under it stays composed, with the back gesture to leave it. The second tab is ProviderScreen itself -- the same composable the machines tab opens -- so a provider's settings have two ways in and one implementation. Every text field in the app goes through LabelledField: the label is a line above the box rather than a thing floating inside it, the hint says what leaving it blank means, and the padding is one line's worth. Material's outlined field spends the height of three lines to hold one, which on a form of a dozen settings is a screen and a half of scrolling. The value's own text is unchanged -- the framing was what cost. A session also gets a system prompt, which for llama.cpp is one entry in the params table and no app change: it rides in front of the conversation on every request rather than being recorded as the first thing in it, so changing it takes effect on the next message. ParamKind::Prose is new because a paragraph in a one-line box shows six words of itself.
This commit is contained in:
1 parent
386c1c4def
commit
7278a58387
17 files changed
+364
-176
No files matched your search
@@ -156,6 +156,27 @@ const THINKING_OFF: &str = "off";
|
||||
/// window: 2,181 tokens against 698 with none, measured 2026-09-19.
|
||||
const TOOLS: &str = "tools";
|
||||
|
||||
/// The session's own system prompt, in [`params`](crate::config::LLAMA_PARAMS).
|
||||
///
|
||||
/// In front of the conversation on every request rather than recorded as the
|
||||
/// first thing in it: it is a setting, and a setting that had been written
|
||||
/// into the transcript would go on saying whatever it said when the session
|
||||
/// was young.
|
||||
const SYSTEM_PROMPT: &str = "systemPrompt";
|
||||
|
||||
/// The session's system prompt as a message, or `None` where it has none.
|
||||
///
|
||||
/// Trimmed, so a field somebody emptied is the same as one never filled in --
|
||||
/// a prompt of one newline is not a prompt, and it would cost a turn's tokens
|
||||
/// to say nothing.
|
||||
fn system_message(params: &std::collections::BTreeMap<String, String>) -> Option<String> {
|
||||
params
|
||||
.get(SYSTEM_PROMPT)
|
||||
.map(|prompt| prompt.trim())
|
||||
.filter(|prompt| !prompt.is_empty())
|
||||
.map(str::to_string)
|
||||
}
|
||||
|
||||
/// How many times one message may go round the call-a-tool loop.
|
||||
///
|
||||
/// A bound rather than a budget: a small model that has decided to read the
|
||||
@@ -463,6 +484,9 @@ struct Shared {
|
||||
/// `None` for the model's own default. Live like the sampling settings,
|
||||
/// and for the same reason -- it rides on the next request.
|
||||
thinking: Mutex<Option<String>>,
|
||||
/// What this session tells the model it is, in front of every request.
|
||||
/// Live for the same reason again -- see [`SYSTEM_PROMPT`].
|
||||
system: Mutex<Option<String>>,
|
||||
/// What the loaded model's chat template actually takes, asked of the
|
||||
/// server that loaded it (see [`thinking_options`]).
|
||||
///
|
||||
@@ -560,6 +584,7 @@ impl LlamaDriver {
|
||||
tools_wanted: Mutex::new(Chosen::from(meta.params.get(TOOLS).map(String::as_str))),
|
||||
watching: AtomicBool::new(false),
|
||||
thinking: Mutex::new(chosen_thinking(&meta.params)),
|
||||
system: Mutex::new(system_message(&meta.params)),
|
||||
thinking_options: Mutex::new(None),
|
||||
vision: Mutex::new(None),
|
||||
}),
|
||||
@@ -1683,6 +1708,7 @@ impl Driver for LlamaDriver {
|
||||
*self.shared.thinking.lock().unwrap() = chosen_thinking(params);
|
||||
*self.shared.tools_wanted.lock().unwrap() =
|
||||
Chosen::from(params.get(TOOLS).map(String::as_str));
|
||||
*self.shared.system.lock().unwrap() = system_message(params);
|
||||
self.shared.note_unusable_thinking();
|
||||
}
|
||||
|
||||
@@ -2326,11 +2352,23 @@ fn generate(
|
||||
shared: &Shared,
|
||||
cancel: &Cancel,
|
||||
) -> Result<(Vec<Message>, Reply)> {
|
||||
// The session's system prompt goes in front of the conversation rather
|
||||
// than into it -- see `SYSTEM_PROMPT`. Borrowed into the request rather
|
||||
// than pushed onto `messages`, which is handed back for the next round and
|
||||
// would otherwise collect one per tool call.
|
||||
let system = shared
|
||||
.system
|
||||
.lock()
|
||||
.unwrap()
|
||||
.as_deref()
|
||||
.map(|prompt| Message::new("system", prompt));
|
||||
let asked: Vec<&Message> = system.iter().chain(messages.iter()).collect();
|
||||
|
||||
let mut body = json!({
|
||||
// Which model, because one `llama-server` is serving every model this
|
||||
// machine has loaded and this is how a request says which it means.
|
||||
"model": serves.model,
|
||||
"messages": messages,
|
||||
"messages": asked,
|
||||
"stream": true,
|
||||
"stream_options": {"include_usage": true},
|
||||
// What it has got through of the prompt, which is the wait
|
||||
|
||||
Reference in new issue
Block a user