Skip to content

Commit d9285f6

Browse files
Merge remote-tracking branch 'origin/main' into smonga/genie_support
2 parents feb6379 + 7ed42a7 commit d9285f6

35 files changed

Lines changed: 1611 additions & 1235 deletions

examples/android/RunAnywhereAI/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
<activity
4848
android:name=".MainActivity"
4949
android:exported="true"
50-
android:windowSoftInputMode="adjustResize"
50+
android:windowSoftInputMode="adjustNothing"
5151
android:theme="@style/Theme.RunAnywhereAI">
5252
<intent-filter>
5353
<action android:name="android.intent.action.MAIN" />

examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/RunAnywhereApplication.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,12 @@ class RunAnywhereApplication : Application() {
208208
isSDKInitialized = RunAnywhere.isInitialized
209209

210210
// Update observable state for Compose UI
211+
val error = initializationError
211212
if (isSDKInitialized) {
212213
_initializationState.value = SDKInitializationState.Ready
213214
Timber.i("🎉 App is ready to use!")
214-
} else if (initializationError != null) {
215-
_initializationState.value = SDKInitializationState.Error(initializationError!!)
215+
} else if (error != null) {
216+
_initializationState.value = SDKInitializationState.Error(error)
216217
} else {
217218
// SDK reported not initialized but no error - treat as ready for offline mode
218219
_initializationState.value = SDKInitializationState.Ready

examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/data/ConversationStore.kt

Lines changed: 43 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@ import timber.log.Timber
66
import com.runanywhere.runanywhereai.domain.models.ChatMessage
77
import com.runanywhere.runanywhereai.domain.models.Conversation
88
import com.runanywhere.runanywhereai.domain.models.MessageRole
9+
import kotlinx.coroutines.CoroutineScope
10+
import kotlinx.coroutines.Dispatchers
11+
import kotlinx.coroutines.SupervisorJob
912
import kotlinx.coroutines.flow.MutableStateFlow
1013
import kotlinx.coroutines.flow.StateFlow
1114
import kotlinx.coroutines.flow.asStateFlow
15+
import kotlinx.coroutines.flow.update
16+
import kotlinx.coroutines.launch
1217
import kotlinx.serialization.decodeFromString
1318
import kotlinx.serialization.encodeToString
1419
import kotlinx.serialization.json.Json
@@ -42,6 +47,7 @@ class ConversationStore private constructor(context: Context) {
4247
val currentConversation: StateFlow<Conversation?> = _currentConversation.asStateFlow()
4348

4449
private val conversationsDirectory: File
50+
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
4551
private val json =
4652
Json {
4753
prettyPrint = true
@@ -53,7 +59,7 @@ class ConversationStore private constructor(context: Context) {
5359
if (!conversationsDirectory.exists()) {
5460
conversationsDirectory.mkdirs()
5561
}
56-
loadConversations()
62+
ioScope.launch { loadConversations() }
5763
}
5864

5965
// MARK: - Public Methods
@@ -74,9 +80,7 @@ class ConversationStore private constructor(context: Context) {
7480
performanceSummary = null,
7581
)
7682

77-
val updated = _conversations.value.toMutableList()
78-
updated.add(0, conversation)
79-
_conversations.value = updated
83+
_conversations.update { list -> listOf(conversation) + list }
8084
_currentConversation.value = conversation
8185

8286
saveConversation(conversation)
@@ -88,31 +92,36 @@ class ConversationStore private constructor(context: Context) {
8892
* If not present (by id), adds it at the front so it appears in history.
8993
*/
9094
fun ensureConversationInList(conversation: Conversation) {
91-
val index = _conversations.value.indexOfFirst { it.id == conversation.id }
92-
if (index == -1) {
93-
val list = _conversations.value.toMutableList()
94-
list.add(0, conversation)
95-
_conversations.value = list
96-
saveConversation(conversation)
95+
var wasAdded = false
96+
_conversations.update { list ->
97+
if (list.any { it.id == conversation.id }) {
98+
list
99+
} else {
100+
wasAdded = true
101+
listOf(conversation) + list
102+
}
97103
}
104+
if (wasAdded) saveConversation(conversation)
98105
}
99106

100107
/**
101108
* Update an existing conversation
102109
*/
103110
fun updateConversation(conversation: Conversation) {
104111
val updated = conversation.copy(updatedAt = System.currentTimeMillis())
105-
106-
val index = _conversations.value.indexOfFirst { it.id == conversation.id }
107-
if (index != -1) {
108-
val list = _conversations.value.toMutableList()
109-
list[index] = updated
110-
_conversations.value = list
111-
112+
var found = false
113+
_conversations.update { list ->
114+
list.map {
115+
if (it.id == conversation.id) {
116+
found = true
117+
updated
118+
} else it
119+
}
120+
}
121+
if (found) {
112122
if (_currentConversation.value?.id == conversation.id) {
113123
_currentConversation.value = updated
114124
}
115-
116125
saveConversation(updated)
117126
}
118127
}
@@ -121,16 +130,18 @@ class ConversationStore private constructor(context: Context) {
121130
* Delete a conversation
122131
*/
123132
fun deleteConversation(conversation: Conversation) {
124-
_conversations.value = _conversations.value.filter { it.id != conversation.id }
133+
_conversations.update { list -> list.filter { it.id != conversation.id } }
125134

126135
if (_currentConversation.value?.id == conversation.id) {
127136
_currentConversation.value = _conversations.value.firstOrNull()
128137
}
129138

130-
// Delete file
131-
val file = conversationFileURL(conversation.id)
132-
if (file.exists()) {
133-
file.delete()
139+
// Delete file off main thread
140+
ioScope.launch {
141+
val file = conversationFileURL(conversation.id)
142+
if (file.exists()) {
143+
file.delete()
144+
}
134145
}
135146
}
136147

@@ -176,9 +187,7 @@ class ConversationStore private constructor(context: Context) {
176187
try {
177188
val jsonString = file.readText()
178189
val loaded = json.decodeFromString<Conversation>(jsonString)
179-
val list = _conversations.value.toMutableList()
180-
list.add(loaded)
181-
_conversations.value = list
190+
_conversations.update { list -> list + loaded }
182191
_currentConversation.value = loaded
183192
return loaded
184193
} catch (e: Exception) {
@@ -246,12 +255,14 @@ class ConversationStore private constructor(context: Context) {
246255
* Save a conversation to disk
247256
*/
248257
private fun saveConversation(conversation: Conversation) {
249-
try {
250-
val file = conversationFileURL(conversation.id)
251-
val jsonString = json.encodeToString(conversation)
252-
file.writeText(jsonString)
253-
} catch (e: Exception) {
254-
Timber.e(e, "Failed to save conversation")
258+
ioScope.launch {
259+
try {
260+
val file = conversationFileURL(conversation.id)
261+
val jsonString = json.encodeToString(conversation)
262+
file.writeText(jsonString)
263+
} catch (e: Exception) {
264+
Timber.e(e, "Failed to save conversation")
265+
}
255266
}
256267
}
257268

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.runanywhere.runanywhereai.data
2+
3+
/**
4+
* Example prompts for each LoRA adapter, keyed by adapter filename.
5+
* These are shown in the active LoRA card so users can quickly test the adapter.
6+
*/
7+
object LoraExamplePrompts {
8+
9+
private val promptsByFilename: Map<String, List<String>> = mapOf(
10+
"code-assistant-Q8_0.gguf" to listOf(
11+
"Write a Python function to reverse a linked list",
12+
"Explain the difference between a stack and a queue with code examples",
13+
),
14+
"reasoning-logic-Q8_0.gguf" to listOf(
15+
"If all roses are flowers and some flowers fade quickly, can we conclude some roses fade quickly?",
16+
"A farmer has 17 sheep. All but 9 die. How many are left?",
17+
),
18+
"medical-qa-Q8_0.gguf" to listOf(
19+
"What are the common symptoms of vitamin D deficiency?",
20+
"Explain the difference between Type 1 and Type 2 diabetes",
21+
),
22+
"creative-writing-Q8_0.gguf" to listOf(
23+
"Write a short story about a robot discovering emotions for the first time",
24+
"Describe a sunset over the ocean using vivid sensory language",
25+
),
26+
)
27+
28+
/**
29+
* Get example prompts for a loaded adapter by its file path.
30+
* Extracts the filename from the path and looks up prompts.
31+
*/
32+
fun forAdapterPath(path: String): List<String> {
33+
val filename = path.substringAfterLast("/")
34+
return promptsByFilename[filename] ?: emptyList()
35+
}
36+
}

examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/data/ModelList.kt

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,15 @@ object ModelList {
3434
AppModel(id = "qwen2.5-0.5b-instruct-q6_k", name = "Qwen 2.5 0.5B Instruct Q6_K",
3535
url = "https://huggingface.co/Triangle104/Qwen2.5-0.5B-Instruct-Q6_K-GGUF/resolve/main/qwen2.5-0.5b-instruct-q6_k.gguf",
3636
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
37-
memoryRequirement = 600_000_000),
37+
memoryRequirement = 600_000_000, supportsLoraAdapters = true),
3838
AppModel(id = "lfm2-350m-q4_k_m", name = "LiquidAI LFM2 350M Q4_K_M",
3939
url = "https://huggingface.co/LiquidAI/LFM2-350M-GGUF/resolve/main/LFM2-350M-Q4_K_M.gguf",
4040
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
41-
memoryRequirement = 250_000_000, supportsLoraAdapters = true),
41+
memoryRequirement = 250_000_000),
4242
AppModel(id = "lfm2-350m-q8_0", name = "LiquidAI LFM2 350M Q8_0",
4343
url = "https://huggingface.co/LiquidAI/LFM2-350M-GGUF/resolve/main/LFM2-350M-Q8_0.gguf",
4444
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
45-
memoryRequirement = 400_000_000, supportsLoraAdapters = true),
45+
memoryRequirement = 400_000_000),
4646
AppModel(id = "lfm2-1.2b-tool-q4_k_m", name = "LiquidAI LFM2 1.2B Tool Q4_K_M",
4747
url = "https://huggingface.co/LiquidAI/LFM2-1.2B-Tool-GGUF/resolve/main/LFM2-1.2B-Tool-Q4_K_M.gguf",
4848
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
@@ -83,46 +83,46 @@ object ModelList {
8383
)),
8484
)
8585

86-
// LoRA Adapters (from Void2377/Qwen on HuggingFace — real standalone LoRA GGUF files)
86+
// LoRA Adapters
8787
private val loraAdapters = listOf(
8888
LoraAdapterCatalogEntry(
89-
id = "chat-assistant-lora",
90-
name = "Chat Assistant",
91-
description = "Enhances conversational chat ability",
92-
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/chat_assistant-lora-Q8_0.gguf",
93-
filename = "chat_assistant-lora-Q8_0.gguf",
94-
compatibleModelIds = listOf("lfm2-350m-q4_k_m", "lfm2-350m-q8_0"),
95-
fileSize = 690_176,
89+
id = "code-assistant-lora",
90+
name = "Code Assistant",
91+
description = "Enhances code generation and programming assistance",
92+
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/code-assistant-Q8_0.gguf",
93+
filename = "code-assistant-Q8_0.gguf",
94+
compatibleModelIds = listOf("qwen2.5-0.5b-instruct-q6_k"),
95+
fileSize = 765_952,
9696
defaultScale = 1.0f,
9797
),
9898
LoraAdapterCatalogEntry(
99-
id = "summarizer-lora",
100-
name = "Summarizer",
101-
description = "Specialized for text summarization tasks",
102-
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/summarizer-lora-Q8_0.gguf",
103-
filename = "summarizer-lora-Q8_0.gguf",
104-
compatibleModelIds = listOf("lfm2-350m-q4_k_m", "lfm2-350m-q8_0"),
105-
fileSize = 690_176,
99+
id = "reasoning-logic-lora",
100+
name = "Reasoning Logic",
101+
description = "Improves logical reasoning and step-by-step problem solving",
102+
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/reasoning-logic-Q8_0.gguf",
103+
filename = "reasoning-logic-Q8_0.gguf",
104+
compatibleModelIds = listOf("qwen2.5-0.5b-instruct-q6_k"),
105+
fileSize = 765_952,
106106
defaultScale = 1.0f,
107107
),
108108
LoraAdapterCatalogEntry(
109-
id = "translator-lora",
110-
name = "Translator",
111-
description = "Improves translation between languages",
112-
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/translator-lora-Q8_0.gguf",
113-
filename = "translator-lora-Q8_0.gguf",
114-
compatibleModelIds = listOf("lfm2-350m-q4_k_m", "lfm2-350m-q8_0"),
115-
fileSize = 690_176,
109+
id = "medical-qa-lora",
110+
name = "Medical QA",
111+
description = "Enhances medical question answering and health-related responses",
112+
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/medical-qa-Q8_0.gguf",
113+
filename = "medical-qa-Q8_0.gguf",
114+
compatibleModelIds = listOf("qwen2.5-0.5b-instruct-q6_k"),
115+
fileSize = 765_952,
116116
defaultScale = 1.0f,
117117
),
118118
LoraAdapterCatalogEntry(
119-
id = "sentiment-lora",
120-
name = "Sentiment Analysis",
121-
description = "Fine-tuned for sentiment analysis tasks",
122-
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/sentiment-lora-Q8_0.gguf",
123-
filename = "sentiment-lora-Q8_0.gguf",
124-
compatibleModelIds = listOf("lfm2-350m-q4_k_m", "lfm2-350m-q8_0"),
125-
fileSize = 690_176,
119+
id = "creative-writing-lora",
120+
name = "Creative Writing",
121+
description = "Improves creative writing, storytelling, and literary style",
122+
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/creative-writing-Q8_0.gguf",
123+
filename = "creative-writing-Q8_0.gguf",
124+
compatibleModelIds = listOf("qwen2.5-0.5b-instruct-q6_k"),
125+
fileSize = 765_952,
126126
defaultScale = 1.0f,
127127
),
128128
)

0 commit comments

Comments
 (0)