UI: polish model card
This commit is contained in:
parent
c12ef7a779
commit
6b74c49e6b
|
|
@ -46,8 +46,7 @@ import java.util.Locale
|
||||||
* Displays model information in a card format with core details.
|
* Displays model information in a card format with core details.
|
||||||
*
|
*
|
||||||
* This component shows essential model information like name, context length,
|
* This component shows essential model information like name, context length,
|
||||||
* architecture, and quantization in a compact card format. It can be used
|
* architecture, quantization and file size in a compact card format.
|
||||||
* in lists where only basic information is needed.
|
|
||||||
*
|
*
|
||||||
* @param model The model information to display
|
* @param model The model information to display
|
||||||
* @param onClick Action to perform when the card is clicked
|
* @param onClick Action to perform when the card is clicked
|
||||||
|
|
@ -92,52 +91,12 @@ fun ModelCardCore(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ModelCardContentCore(
|
|
||||||
model: ModelInfo,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
Column(modifier = modifier) {
|
|
||||||
// Row 1: Model full name
|
|
||||||
Text(
|
|
||||||
text = model.formattedFullName,
|
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
|
|
||||||
// Row 2: Context length, size label
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
|
||||||
) {
|
|
||||||
ModelCardContentField("Context", model.formattedContextLength)
|
|
||||||
|
|
||||||
ModelCardContentField("Params", model.formattedParamSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
// Row 3: Architecture, quantization, formatted size
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
ModelCardContentField("Architecture", model.formattedArchitecture)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
|
|
||||||
ModelCardContentField(model.formattedQuantization, model.formattedFileSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays model information in a card format with expandable details.
|
* Displays model information in a card format with expandable details.
|
||||||
*
|
*
|
||||||
* This component shows essential model information and can be expanded to show
|
* This component shows essential model information and can be expanded to show
|
||||||
* additional details such as dates, tags, and languages. The expanded state is
|
* additional details such as dates, tags, and languages.
|
||||||
* toggled by clicking on the content area of the card.
|
* The expanded state is toggled by clicking on the content area of the card.
|
||||||
*
|
*
|
||||||
* @param model The model information to display
|
* @param model The model information to display
|
||||||
* @param isSelected Optional selection state (shows checkbox when not null)
|
* @param isSelected Optional selection state (shows checkbox when not null)
|
||||||
|
|
@ -145,6 +104,7 @@ fun ModelCardContentCore(
|
||||||
* @param isExpanded Whether additional details is expanded or shrunk
|
* @param isExpanded Whether additional details is expanded or shrunk
|
||||||
* @param onExpanded Action to perform when the card is expanded or shrunk
|
* @param onExpanded Action to perform when the card is expanded or shrunk
|
||||||
*/
|
*/
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ModelCardExpandable(
|
fun ModelCardExpandable(
|
||||||
model: ModelInfo,
|
model: ModelInfo,
|
||||||
|
|
@ -187,11 +147,27 @@ fun ModelCardExpandable(
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(start = 16.dp, top = 16.dp, end = 16.dp)
|
.padding(start = 16.dp, top = 16.dp, end = 16.dp)
|
||||||
) {
|
) {
|
||||||
// Core content always visible
|
// Row 1: Model full name
|
||||||
ModelCardContentCore(model = model)
|
Text(
|
||||||
|
text = model.formattedFullName,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||||
|
// Row 2: Context length, size label
|
||||||
|
ModelCardContentContextRow(model)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// Row 3: Architecture, quantization, formatted size
|
||||||
|
ModelCardContentArchitectureRow(model)
|
||||||
|
}
|
||||||
|
|
||||||
// Expandable content
|
// Expandable content
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
visible = isExpanded,
|
visible = isExpanded,
|
||||||
|
|
@ -203,7 +179,27 @@ fun ModelCardExpandable(
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
) {
|
) {
|
||||||
ExpandableModelDetails(model = model)
|
Column(modifier = Modifier.padding(top = 12.dp)) {
|
||||||
|
// Divider between core and expanded content
|
||||||
|
HorizontalDivider(modifier = Modifier.padding(bottom = 12.dp))
|
||||||
|
|
||||||
|
// Row 4: Dates
|
||||||
|
ModelCardContentDatesRow(model)
|
||||||
|
|
||||||
|
// Row 5: Tags
|
||||||
|
model.tags?.let { tags ->
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
ModelCardContentTagsSection(tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Row 6: Languages
|
||||||
|
model.languages?.let { languages ->
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
ModelCardContentLanguagesSections(languages)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -211,84 +207,6 @@ fun ModelCardExpandable(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Displays expandable details for a model, to be used only inside ModelCardExpandable.
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
@Composable
|
|
||||||
private fun ExpandableModelDetails(model: ModelInfo) {
|
|
||||||
val dateFormatter = remember { SimpleDateFormat("MMM d, yyyy", Locale.getDefault()) }
|
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(top = 16.dp)) {
|
|
||||||
// Divider between core and expanded content
|
|
||||||
HorizontalDivider(modifier = Modifier.padding(bottom = 16.dp))
|
|
||||||
|
|
||||||
// Dates
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
|
||||||
) {
|
|
||||||
// Added date
|
|
||||||
ModelCardContentField("Added", dateFormatter.format(Date(model.dateAdded)))
|
|
||||||
|
|
||||||
// Last used date (if available)
|
|
||||||
model.dateLastUsed?.let { lastUsed ->
|
|
||||||
ModelCardContentField("Last used", dateFormatter.format(Date(lastUsed)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tags (if available)
|
|
||||||
model.tags?.let { tags ->
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
FlowRow(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
tags.forEach { tag ->
|
|
||||||
AssistChip(
|
|
||||||
onClick = { /* No action */ },
|
|
||||||
label = {
|
|
||||||
Text(
|
|
||||||
text = tag,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
fontStyle = FontStyle.Italic,
|
|
||||||
fontWeight = FontWeight.Light,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Languages (if available)
|
|
||||||
model.languages?.let { languages ->
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
FlowRow(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
languages.forEach { language ->
|
|
||||||
AssistChip(
|
|
||||||
onClick = { /* No action */ },
|
|
||||||
label = {
|
|
||||||
Text(
|
|
||||||
text = language,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
fontStyle = FontStyle.Italic,
|
|
||||||
fontWeight = FontWeight.Light,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expandable card that shows model info and system prompt
|
* Expandable card that shows model info and system prompt
|
||||||
*/
|
*/
|
||||||
|
|
@ -358,6 +276,119 @@ fun ModelCardWithSystemPrompt(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ModelCardContentCore(
|
||||||
|
model: ModelInfo,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) =
|
||||||
|
Column(modifier = modifier) {
|
||||||
|
// Row 1: Model full name
|
||||||
|
Text(
|
||||||
|
text = model.formattedFullName,
|
||||||
|
style = MaterialTheme.typography.headlineSmall, // TODO
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
|
||||||
|
// Row 2: Context length, size label
|
||||||
|
ModelCardContentContextRow(model)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// Row 3: Architecture, quantization, formatted size
|
||||||
|
ModelCardContentArchitectureRow(model)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ModelCardContentContextRow(model: ModelInfo) =
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
ModelCardContentField("Context", model.formattedContextLength)
|
||||||
|
|
||||||
|
ModelCardContentField("Params", model.formattedParamSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ModelCardContentArchitectureRow(model: ModelInfo) =
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
ModelCardContentField("Architecture", model.formattedArchitecture)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
|
ModelCardContentField(model.formattedQuantization, model.formattedFileSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ModelCardContentDatesRow(model: ModelInfo) {
|
||||||
|
val dateFormatter = remember { SimpleDateFormat("MMM d, yyyy", Locale.getDefault()) }
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
// Added date
|
||||||
|
ModelCardContentField("Added", dateFormatter.format(Date(model.dateAdded)))
|
||||||
|
|
||||||
|
// Last used date (if available)
|
||||||
|
model.dateLastUsed?.let { lastUsed ->
|
||||||
|
ModelCardContentField("Last used", dateFormatter.format(Date(lastUsed)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun ModelCardContentTagsSection(tags: List<String>) =
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
tags.forEach { tag ->
|
||||||
|
AssistChip(
|
||||||
|
onClick = { /* No action */ },
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = tag,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun ModelCardContentLanguagesSections(languages: List<String>) =
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
languages.forEach { language ->
|
||||||
|
AssistChip(
|
||||||
|
onClick = { /* No action */ },
|
||||||
|
label = {
|
||||||
|
Text(
|
||||||
|
text = language,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
|
fontWeight = FontWeight.Light,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun ModelCardContentField(name: String, value: String) =
|
private fun ModelCardContentField(name: String, value: String) =
|
||||||
Row {
|
Row {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import androidx.lifecycle.viewModelScope
|
||||||
import com.example.llama.revamp.data.model.ModelInfo
|
import com.example.llama.revamp.data.model.ModelInfo
|
||||||
import com.example.llama.revamp.data.repository.InsufficientStorageException
|
import com.example.llama.revamp.data.repository.InsufficientStorageException
|
||||||
import com.example.llama.revamp.data.repository.ModelRepository
|
import com.example.llama.revamp.data.repository.ModelRepository
|
||||||
|
import com.example.llama.revamp.util.GgufMetadataReader
|
||||||
import com.example.llama.revamp.util.getFileNameFromUri
|
import com.example.llama.revamp.util.getFileNameFromUri
|
||||||
import com.example.llama.revamp.util.getFileSizeFromUri
|
import com.example.llama.revamp.util.getFileSizeFromUri
|
||||||
import com.example.llama.revamp.viewmodel.ModelManagementState.Deletion
|
import com.example.llama.revamp.viewmodel.ModelManagementState.Deletion
|
||||||
|
|
@ -192,7 +193,14 @@ class ModelsManagementViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO-han.yin: Stub for now. Would need to investigate HuggingFace APIs
|
// TODO-han.yin: Stub for now. Would need to investigate HuggingFace APIs
|
||||||
fun importFromHuggingFace() {}
|
fun importFromHuggingFace() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
// val path = "/data/user/0/com.example.llama/files/models/Phi-4-mini-instruct-Q4_0.gguf"
|
||||||
|
val path = "/data/user/0/com.example.llama/files/models/gemma-3-4b-it-Q4_K_M.gguf"
|
||||||
|
val metadata = GgufMetadataReader().readStructuredMetadata(path)
|
||||||
|
Log.i("JOJO", "GGUF Metadata for $path:\n $metadata")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* First show confirmation instead of starting deletion immediately
|
* First show confirmation instead of starting deletion immediately
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue