Download a model onto the machine that will serve it
The Models tab was about this backend's own disk, which is the wrong disk for every session that runs anywhere else: llama.cpp reads the file where it runs. So the models of a machine live under that machine's llama.cpp provider now, beside the settings deciding how each is loaded, and the download that produces one happens there. A download is a detached `curl` on that machine, started by a script this server writes and never spoken to again. Its state is a file beside the partial, so nothing about it is held here: it survives the app closing, this backend restarting and a second device watching, and the progress is `wc -c` of the partial against the size HuggingFace published rather than anything remembered. A run whose process is gone is reported failed, since `kill -0` is asked at each listing, and there is no "finished" state -- a download that finished is a model, in the list beside the ones still going. Resuming is guarded by the published sha256, which is also checked before the file takes its real name. Two other things the same screens wanted: A provider is drawn as a card rather than as a line of text, bordered against the machine card it sits in -- the tint it had was one step along the surface ladder and rendered as one flat block -- with room to tap and no chevron. Nothing in a raw block wraps any more; the block scrolls sideways instead, one offset for all its lines, so a diff or a column-aligned test run still reads as one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8c323fc7a9
commit
81c30dcda1
14 files changed
+1224
-1073
No files matched your search
@@ -1,5 +1,6 @@
|
||||
package com.example.aiapp
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -26,8 +27,11 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -246,32 +250,50 @@ private fun MachineCard(
|
||||
machine.providers.forEach { provider ->
|
||||
// A card of its own rather than a line of text: a provider is where the
|
||||
// settings that belong to *this machine* live -- how each of its models is
|
||||
// loaded, and the server holding them -- and those had nowhere to be until
|
||||
// one llama-server came to serve every session on a machine.
|
||||
// loaded, the models themselves, and the server holding them -- and those had
|
||||
// nowhere to be until one llama-server came to serve every session on a
|
||||
// machine. Sized by its own padding rather than by whatever control happened
|
||||
// to be on its row, like the tool call cards it is built after.
|
||||
Card(
|
||||
Modifier.fillMaxWidth().padding(vertical = 2.dp).clickable {
|
||||
onProvider(provider)
|
||||
},
|
||||
colors =
|
||||
CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||
),
|
||||
Modifier.fillMaxWidth()
|
||||
.padding(vertical = 4.dp)
|
||||
.clickable { onProvider(provider) }
|
||||
.semantics { contentDescription = "Open ${provider.name}" },
|
||||
// A border, and the machine card's own surface kept underneath it.
|
||||
// The tint that was here before is one step along the surface ladder
|
||||
// from the card it sits in, and two adjacent surfaces render as one flat
|
||||
// block: these read as lines of text in a box rather than as things to
|
||||
// open. One cue, and a visible one.
|
||||
colors = CardDefaults.cardColors(containerColor = Color.Transparent),
|
||||
border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp),
|
||||
modifier = Modifier.fillMaxWidth().padding(12.dp),
|
||||
) {
|
||||
Text(provider.name, style = MaterialTheme.typography.bodyMedium)
|
||||
Spacer(Modifier.weight(1f))
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(provider.name, style = MaterialTheme.typography.titleSmall)
|
||||
// What was actually found, which is the honest second line and
|
||||
// the one thing here nobody can change. No arrow: a card that
|
||||
// lifts off the one behind it already reads as something to open,
|
||||
// and the chevron was the only thing making these look like rows
|
||||
// of a list.
|
||||
provider.command?.let {
|
||||
Text(
|
||||
it,
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
// A program is identified by its name, which is the tail
|
||||
// of its path.
|
||||
overflow = TextOverflow.StartEllipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (provider.kind == "claude_cli") {
|
||||
TextButton(onClick = { onSignIn(provider) }) { Text("Sign in") }
|
||||
}
|
||||
Chevron(
|
||||
Pointing.Right,
|
||||
Modifier.padding(start = 4.dp).semantics {
|
||||
contentDescription = "Settings for ${provider.name}"
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user