Skip to content

Commit fce97a0

Browse files
committed
style: Chat UI modify
1 parent 27d1dda commit fce97a0

4 files changed

Lines changed: 66 additions & 19 deletions

File tree

README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@ Mine StableDiffusion is a **native, offline-first AI art generation app** that b
4141
- **🎨 Modern UI** - Beautiful Compose Multiplatform interface
4242
- **📱 True Multiplatform** - Shared codebase for Android & iOS & Desktop
4343
- **🔧 Model Flexibility** - Support for FLUX, SDXL, SD3, and many more
44-
- **⚡ Hardware Accelerated** - Vulkan 1.2+ (Android/Linux/Windows) & Metal (macOS)
45-
- **🏷️ Embedded Metadata** - Automatically saves generation parameters (prompts, seeds, etc.) directly into exported PNGs
44+
- **⚡ Hardware Accelerated** - Vulkan 1.2+ (Android/Linux/Windows) & Metal (macOS/iOS)
45+
- **🧩 Extensions & Plugins** - Full support for external LoRA models (`.safetensors`) offering stylized output
46+
- **🛠 Power Features** - Batch Image Generation, Flash Attention, Multi-step Samplers, Direct Convolution
47+
- **🏷️ Embedded Metadata** - Automatically saves generation parameters (prompts, seeds, dimensions, models, loras) directly into exported PNGs
4648

4749
---
4850

@@ -99,14 +101,20 @@ _Best for high-detail 1024x1024+ generation. Requires more VRAM and time._
99101

100102
## 🌟 Key Features
101103

102-
### Text-to-Image Generation
103-
Generate stunning images from text descriptions with various models
104+
### Text-to-Image / Batch Generation
105+
Generate single or multiple stunning images from text descriptions with various models synchronously.
104106

105107
```
106108
Input: "A serene mountain landscape at sunset, digital art"
107-
Output: High-quality AI-generated image
109+
Output: High-quality AI-generated image(s) with progress indicator
108110
```
109111

112+
### 🧩 LoRA Integration
113+
Effortlessly stylize generations by utilizing multiple `.safetensors` LoRA files with completely customizable strength dials directly from the sleek UI settings.
114+
115+
### 📊 Transparent Sampler Algorithms
116+
Choose the perfect algorithm for your workflow directly on device (e.g. Euler, Euler a, DPM++ 2M, DDIM, LCM, TCD, and more) to master the noise-canceling mechanics.
117+
110118
### ⚙️ Advanced Settings Guide
111119

112120
The **Advanced Settings** page provides fine-grained control over the inference engine. Below is a summary of each toggle and its impact:

composeApp/src/commonMain/kotlin/org/onion/diffusion/ui/screen/HomeScreen.kt

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.Box
1616
import androidx.compose.foundation.layout.Column
1717
import androidx.compose.foundation.layout.PaddingValues
1818
import androidx.compose.foundation.layout.Row
19+
import androidx.compose.foundation.layout.aspectRatio
1920
import androidx.compose.foundation.layout.fillMaxHeight
2021
import androidx.compose.foundation.layout.fillMaxSize
2122
import androidx.compose.foundation.layout.fillMaxWidth
@@ -336,7 +337,7 @@ private fun ChatMessagesList(chatMessages: List<ChatMessage>,snackbarHostState:
336337
.padding(top = 70.dp, bottom = 90.dp),
337338
verticalArrangement = Arrangement.spacedBy(12.dp)
338339
) {
339-
items(chatMessages) { message ->
340+
items(chatMessages, key = { it.id }) { message ->
340341
Box(
341342
modifier = Modifier
342343
.fillMaxWidth()
@@ -373,7 +374,8 @@ private fun ChatMessagesList(chatMessages: List<ChatMessage>,snackbarHostState:
373374
coroutineScope.launch {
374375
snackbarHostState.showSnackbar(getString(Res.string.text_copied))
375376
}
376-
}
377+
},
378+
metadata = message.metadata
377379
)
378380
}
379381
}
@@ -568,7 +570,8 @@ fun ChatBubble(
568570
isUser: Boolean,
569571
onSaveImage: ((ByteArray) -> Unit)? = null,
570572
onRegenerate: (() -> Unit)? = null,
571-
onCopyText: ((String) -> Unit)? = null
573+
onCopyText: ((String) -> Unit)? = null,
574+
metadata: Map<String, String>? = null
572575
) {
573576
Box(
574577
modifier = Modifier.fillMaxWidth()
@@ -600,7 +603,8 @@ fun ChatBubble(
600603
isUser = isUser,
601604
onSaveImage = onSaveImage,
602605
onRegenerate = onRegenerate,
603-
onCopyText = onCopyText
606+
onCopyText = onCopyText,
607+
metadata = metadata
604608
)
605609
}
606610
}
@@ -660,12 +664,13 @@ private fun ChatBubbleMessage(
660664
isUser: Boolean,
661665
onSaveImage: ((ByteArray) -> Unit)? = null,
662666
onRegenerate: (() -> Unit)? = null,
663-
onCopyText: ((String) -> Unit)? = null
667+
onCopyText: ((String) -> Unit)? = null,
668+
metadata: Map<String, String>? = null
664669
) {
665670
if (isUser) {
666671
UserMessage(message = message, image = image, onCopyText = onCopyText)
667672
} else {
668-
AiMessage(message = message, image = image, onSaveImage = onSaveImage, onRegenerate = onRegenerate, onCopyText = onCopyText)
673+
AiMessage(message = message, image = image, onSaveImage = onSaveImage, onRegenerate = onRegenerate, onCopyText = onCopyText, metadata = metadata)
669674
}
670675
}
671676

@@ -716,7 +721,8 @@ private fun AiMessage(
716721
image: ByteArray? = null,
717722
onSaveImage: ((ByteArray) -> Unit)? = null,
718723
onRegenerate: (() -> Unit)? = null,
719-
onCopyText: ((String) -> Unit)? = null
724+
onCopyText: ((String) -> Unit)? = null,
725+
metadata: Map<String, String>? = null
720726
) {
721727
Column(modifier = Modifier.fillMaxWidth()) {
722728
// 当消息为空且无图片时,显示创意加载动画
@@ -726,15 +732,28 @@ private fun AiMessage(
726732
// 正常显示逻辑
727733
if (image != null) {
728734
Box(modifier = Modifier.wrapContentSize()) {
735+
// Try to pre-calculate aspect ratio to prevent layout shifts
736+
val widthStr = metadata?.get("width")
737+
val heightStr = metadata?.get("height")
738+
val imgModifier = Modifier
739+
.padding(bottom = 4.dp)
740+
.clip(RoundedCornerShape(8.dp))
741+
.let { m ->
742+
if (widthStr != null && heightStr != null && widthStr.toFloatOrNull() != null && heightStr.toFloatOrNull() != null) {
743+
val w = widthStr.toFloat()
744+
val h = heightStr.toFloat()
745+
m.aspectRatio(w / h)
746+
} else {
747+
m.wrapContentSize()
748+
}
749+
}
750+
729751
AsyncImage(
730752
model = image,
731753
contentDescription = stringResource(Res.string.ai_image),
732754
alignment = Alignment.Center,
733-
contentScale = ContentScale.Inside,
734-
modifier = Modifier
735-
.clip(RoundedCornerShape(8.dp))
736-
.wrapContentSize()
737-
.padding(bottom = 4.dp)
755+
contentScale = ContentScale.Fit,
756+
modifier = imgModifier
738757
)
739758
// Premium Action Bar Overlay
740759
Row(

composeApp/src/commonMain/kotlin/org/onion/diffusion/viewmodel/ChatViewModel.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,8 @@ class ChatViewModel : ViewModel() {
299299
"cfg_scale" to cfgScale.value.toString(),
300300
"seed" to Clock.System.now().toEpochMilliseconds().toString(), // Note: verify if we should use the same seed as generation
301301
"model" to diffusionModelPath.value.substringAfterLast("/"),
302+
"width" to imageWidth.value.toString(),
303+
"height" to imageHeight.value.toString(),
302304
"loras" to enabledLoras.joinToString(",") { "${it.name}:${it.strength}" }
303305
)
304306

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,27 @@
11
package com.onion.model
22

3+
import kotlin.random.Random
4+
35
data class ChatMessage(
46
val message: String,
57
val isUser: Boolean,
68
val image: ByteArray? = null,
79
val videoFrames: List<ByteArray>? = null,
8-
val metadata: Map<String, String>? = null
9-
)
10+
val metadata: Map<String, String>? = null,
11+
val id: Long = Random.nextLong()
12+
) {
13+
override fun equals(other: Any?): Boolean {
14+
if (this === other) return true
15+
if (other == null || this::class != other::class) return false
16+
17+
other as ChatMessage
18+
19+
if (id != other.id) return false
20+
21+
return true
22+
}
23+
24+
override fun hashCode(): Int {
25+
return id.hashCode()
26+
}
27+
}

0 commit comments

Comments
 (0)