mirror of
https://github.com/google-ai-edge/gallery.git
synced 2025-07-06 06:30:30 -04:00
Re-enable image pickers when config is changed, and other UI bug fixes.
This commit is contained in:
parent
0f5142e67e
commit
9544b8ddcc
7 changed files with 374 additions and 380 deletions
|
@ -30,7 +30,7 @@ android {
|
|||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "0.9.1"
|
||||
versionName = "0.9.2"
|
||||
|
||||
// Needed for HuggingFace auth workflows.
|
||||
manifestPlaceholders["appAuthRedirectScheme"] = "com.google.aiedge.gallery.oauth"
|
||||
|
|
|
@ -23,6 +23,8 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
import androidx.compose.foundation.text.TextAutoSize
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.rounded.Refresh
|
||||
|
@ -44,6 +46,7 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.google.aiedge.gallery.data.AppBarAction
|
||||
|
@ -70,6 +73,7 @@ fun GalleryTopAppBar(
|
|||
scrollBehavior: TopAppBarScrollBehavior? = null,
|
||||
subtitle: String = "",
|
||||
) {
|
||||
val titleColor = MaterialTheme.colorScheme.primary
|
||||
CenterAlignedTopAppBar(
|
||||
title = {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
|
@ -85,9 +89,16 @@ fun GalleryTopAppBar(
|
|||
tint = Color.Unspecified,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
title,
|
||||
style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.SemiBold)
|
||||
BasicText(
|
||||
text = title,
|
||||
maxLines = 1,
|
||||
color = { titleColor },
|
||||
style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.SemiBold),
|
||||
autoSize = TextAutoSize.StepBased(
|
||||
minFontSize = 14.sp,
|
||||
maxFontSize = 22.sp,
|
||||
stepSize = 1.sp
|
||||
)
|
||||
)
|
||||
}
|
||||
if (subtitle.isNotEmpty()) {
|
||||
|
|
|
@ -16,9 +16,10 @@
|
|||
|
||||
package com.google.aiedge.gallery.ui.common.chat
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
import androidx.compose.animation.SharedTransitionLayout
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
@ -143,6 +144,18 @@ fun ChatPanel(
|
|||
val scope = rememberCoroutineScope()
|
||||
val haptic = LocalHapticFeedback.current
|
||||
var selectedImageMessage by remember { mutableStateOf<ChatMessageImage?>(null) }
|
||||
val hasImageMessageToLastConfigChange = remember(messages) {
|
||||
var foundImageMessage = false
|
||||
for (message in messages.reversed()) {
|
||||
if (message is ChatMessageConfigValuesChange) {
|
||||
break
|
||||
}
|
||||
if (message is ChatMessageImage) {
|
||||
foundImageMessage = true
|
||||
}
|
||||
}
|
||||
foundImageMessage
|
||||
}
|
||||
|
||||
var curMessage by remember { mutableStateOf("") } // Correct state
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
@ -222,8 +235,6 @@ fun ChatPanel(
|
|||
showErrorDialog = modelInitializationStatus?.status == ModelInitializationStatusType.ERROR
|
||||
}
|
||||
|
||||
SharedTransitionLayout(modifier = Modifier.fillMaxSize()) {
|
||||
AnimatedContent(targetState = selectedImageMessage) { targetSelectedImageMessage ->
|
||||
Column(
|
||||
modifier = modifier.imePadding()
|
||||
) {
|
||||
|
@ -293,7 +304,8 @@ fun ChatPanel(
|
|||
is ChatMessageConfigValuesChange -> MessageBodyConfigUpdate(message = message)
|
||||
|
||||
// Prompt templates.
|
||||
is ChatMessagePromptTemplates -> MessageBodyPromptTemplates(message = message,
|
||||
is ChatMessagePromptTemplates -> MessageBodyPromptTemplates(
|
||||
message = message,
|
||||
task = task,
|
||||
onPromptClicked = { template ->
|
||||
onSendMessage(
|
||||
|
@ -308,8 +320,7 @@ fun ChatPanel(
|
|||
var messageBubbleModifier = Modifier
|
||||
.clip(
|
||||
MessageBubbleShape(
|
||||
radius = bubbleBorderRadius,
|
||||
hardCornerAtLeftOrRight = hardCornerAtLeftOrRight
|
||||
radius = bubbleBorderRadius, hardCornerAtLeftOrRight = hardCornerAtLeftOrRight
|
||||
)
|
||||
)
|
||||
.background(backgroundColor)
|
||||
|
@ -333,24 +344,9 @@ fun ChatPanel(
|
|||
|
||||
// Image
|
||||
is ChatMessageImage -> {
|
||||
if (targetSelectedImageMessage != message) {
|
||||
MessageBodyImage(
|
||||
message = message,
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
MessageBodyImage(message = message, modifier = Modifier.clickable {
|
||||
selectedImageMessage = message
|
||||
}
|
||||
.sharedElement(
|
||||
sharedContentState = rememberSharedContentState(key = "selected_image"),
|
||||
animatedVisibilityScope = this@AnimatedContent,
|
||||
clipInOverlayDuringTransition = OverlayClip(
|
||||
MessageBubbleShape(
|
||||
radius = bubbleBorderRadius
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Image with history (for image gen)
|
||||
|
@ -376,6 +372,7 @@ fun ChatPanel(
|
|||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
if (message.side == ChatSide.AGENT) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
@ -413,10 +410,8 @@ fun ChatPanel(
|
|||
}
|
||||
// Remove the stats message.
|
||||
else {
|
||||
val curMessageIndex =
|
||||
viewModel.getMessageIndex(
|
||||
model = selectedModel,
|
||||
message = message
|
||||
val curMessageIndex = viewModel.getMessageIndex(
|
||||
model = selectedModel, message = message
|
||||
)
|
||||
viewModel.removeMessageAt(
|
||||
model = selectedModel, index = curMessageIndex + 1
|
||||
|
@ -488,14 +483,13 @@ fun ChatPanel(
|
|||
ChatInputType.TEXT -> {
|
||||
// val isLlmTask = task.type == TaskType.LLM_CHAT
|
||||
// val notLlmStartScreen = !(messages.size == 1 && messages[0] is ChatMessagePromptTemplates)
|
||||
val hasImageMessage = messages.any { it is ChatMessageImage }
|
||||
MessageInputText(
|
||||
modelManagerViewModel = modelManagerViewModel,
|
||||
curMessage = curMessage,
|
||||
inProgress = uiState.inProgress,
|
||||
isResettingSession = uiState.isResettingSession,
|
||||
modelPreparing = uiState.preparing,
|
||||
hasImageMessage = hasImageMessage,
|
||||
hasImageMessage = hasImageMessageToLastConfigChange,
|
||||
modelInitializing = modelInitializationStatus?.status == ModelInitializationStatusType.INITIALIZING,
|
||||
textFieldPlaceHolderRes = task.textInputPlaceHolderRes,
|
||||
onValueChanged = { curMessage = it },
|
||||
|
@ -545,32 +539,27 @@ fun ChatPanel(
|
|||
}
|
||||
|
||||
// A full-screen image viewer.
|
||||
if (targetSelectedImageMessage != null) {
|
||||
val curSelectedImageMessage = selectedImageMessage
|
||||
AnimatedVisibility(
|
||||
visible = curSelectedImageMessage != null,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
if (curSelectedImageMessage == null) return@AnimatedVisibility
|
||||
|
||||
ZoomableBox(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black.copy(alpha = 0.9f))
|
||||
.sharedElement(
|
||||
rememberSharedContentState(key = "bounds"),
|
||||
animatedVisibilityScope = this,
|
||||
)
|
||||
.skipToLookaheadSize(),
|
||||
) {
|
||||
// Image.
|
||||
Image(
|
||||
bitmap = targetSelectedImageMessage.imageBitMap,
|
||||
bitmap = curSelectedImageMessage.imageBitMap,
|
||||
contentDescription = "",
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer(
|
||||
scaleX = scale,
|
||||
scaleY = scale,
|
||||
translationX = offsetX,
|
||||
translationY = offsetY
|
||||
)
|
||||
.sharedElement(
|
||||
sharedContentState = rememberSharedContentState(key = "selected_image"),
|
||||
animatedVisibilityScope = this@AnimatedContent,
|
||||
scaleX = scale, scaleY = scale, translationX = offsetX, translationY = offsetY
|
||||
),
|
||||
contentScale = ContentScale.Fit,
|
||||
)
|
||||
|
@ -579,22 +568,16 @@ fun ChatPanel(
|
|||
IconButton(
|
||||
onClick = {
|
||||
selectedImageMessage = null
|
||||
},
|
||||
colors = IconButtonDefaults.iconButtonColors(
|
||||
}, colors = IconButtonDefaults.iconButtonColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceVariant,
|
||||
),
|
||||
modifier = Modifier.offset(x = (-8).dp, y = 8.dp)
|
||||
), modifier = Modifier.offset(x = (-8).dp, y = 8.dp)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Rounded.Close,
|
||||
contentDescription = "",
|
||||
tint = MaterialTheme.colorScheme.primary
|
||||
Icons.Rounded.Close, contentDescription = "", tint = MaterialTheme.colorScheme.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error dialog.
|
||||
if (showErrorDialog) {
|
||||
|
@ -635,7 +618,7 @@ fun ChatPanel(
|
|||
}
|
||||
}
|
||||
|
||||
// Benchmark config dialog.
|
||||
// Benchmark config dialog.
|
||||
if (showBenchmarkConfigsDialog) {
|
||||
BenchmarkConfigDialog(onDismissed = { showBenchmarkConfigsDialog = false },
|
||||
messageToBenchmark = benchmarkMessage.value,
|
||||
|
@ -644,7 +627,7 @@ fun ChatPanel(
|
|||
})
|
||||
}
|
||||
|
||||
// Sheet to show when a message is long-pressed.
|
||||
// Sheet to show when a message is long-pressed.
|
||||
if (showMessageLongPressedSheet) {
|
||||
val message = longPressedMessage.value
|
||||
if (message != null && message is ChatMessageText) {
|
||||
|
@ -700,8 +683,7 @@ fun ZoomableBox(
|
|||
var offsetX by remember { mutableFloatStateOf(0f) }
|
||||
var offsetY by remember { mutableFloatStateOf(0f) }
|
||||
var size by remember { mutableStateOf(IntSize.Zero) }
|
||||
Box(
|
||||
modifier = modifier
|
||||
Box(modifier = modifier
|
||||
.clip(RectangleShape)
|
||||
.onSizeChanged { size = it }
|
||||
.pointerInput(Unit) {
|
||||
|
@ -714,8 +696,7 @@ fun ZoomableBox(
|
|||
val minY = -maxY
|
||||
offsetY = maxOf(minY, minOf(maxY, offsetY + pan.y))
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.TopEnd
|
||||
}, contentAlignment = Alignment.TopEnd
|
||||
) {
|
||||
val scope = ZoomableBoxScopeImpl(scale, offsetX, offsetY)
|
||||
scope.content()
|
||||
|
@ -729,9 +710,7 @@ interface ZoomableBoxScope {
|
|||
}
|
||||
|
||||
private data class ZoomableBoxScopeImpl(
|
||||
override val scale: Float,
|
||||
override val offsetX: Float,
|
||||
override val offsetY: Float
|
||||
override val scale: Float, override val offsetX: Float, override val offsetY: Float
|
||||
) : ZoomableBoxScope
|
||||
|
||||
@Preview(showBackground = true)
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package com.google.aiedge.gallery.ui.home
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
|
@ -354,7 +353,7 @@ private fun TaskList(
|
|||
val linkColor = MaterialTheme.customColors.linkColor
|
||||
|
||||
val introText = buildAnnotatedString {
|
||||
append("Welcome to AI Edge Gallery! Explore a world of \namazing on-device models from ")
|
||||
append("Welcome to Google AI Edge Gallery! Explore a world of \namazing on-device models from ")
|
||||
withLink(
|
||||
link = LinkAnnotation.Url(
|
||||
url = "https://huggingface.co/litert-community", // Replace with the actual URL
|
||||
|
|
|
@ -42,6 +42,7 @@ object LlmChatModelHelper {
|
|||
fun initialize(
|
||||
context: Context, model: Model, onDone: (String) -> Unit
|
||||
) {
|
||||
// Prepare options.
|
||||
val maxTokens =
|
||||
model.getIntConfigValue(key = ConfigKey.MAX_TOKENS, defaultValue = DEFAULT_MAX_TOKEN)
|
||||
val topK = model.getIntConfigValue(key = ConfigKey.TOPK, defaultValue = DEFAULT_TOPK)
|
||||
|
@ -62,7 +63,7 @@ object LlmChatModelHelper {
|
|||
.setMaxNumImages(if (model.llmSupportImage) 1 else 0)
|
||||
.build()
|
||||
|
||||
// Create an instance of the LLM Inference task
|
||||
// Create an instance of the LLM Inference task and session.
|
||||
try {
|
||||
val llmInference = LlmInference.createFromOptions(context, options)
|
||||
|
||||
|
@ -145,6 +146,9 @@ object LlmChatModelHelper {
|
|||
}
|
||||
|
||||
// Start async inference.
|
||||
//
|
||||
// For a model that supports image modality, we need to add the text query chunk before adding
|
||||
// image.
|
||||
val session = instance.session
|
||||
session.addQueryChunk(input)
|
||||
if (image != null) {
|
||||
|
|
|
@ -205,7 +205,8 @@ fun GalleryTheme(
|
|||
content: @Composable () -> Unit
|
||||
) {
|
||||
val themeOverride = ThemeSettings.themeOverride
|
||||
val darkTheme: Boolean = isSystemInDarkTheme() || themeOverride.value == THEME_DARK
|
||||
val darkTheme: Boolean =
|
||||
(isSystemInDarkTheme() || themeOverride.value == THEME_DARK) && themeOverride.value != THEME_LIGHT
|
||||
|
||||
StatusBarColorController(useDarkTheme = darkTheme)
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
-->
|
||||
|
||||
<resources>
|
||||
<string name="app_name">AI Edge Gallery</string>
|
||||
<string name="app_name">Google AI Edge Gallery</string>
|
||||
<string name="model_manager">Model Manager</string>
|
||||
<string name="downloaded_size">%1$s downloaded</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue