Skip to content

Commit b852034

Browse files
Merge remote-tracking branch 'origin/main' into smonga/cpp_optm
2 parents 7375a6d + 7ed42a7 commit b852034

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
@@ -40,7 +40,7 @@
4040
<activity
4141
android:name=".MainActivity"
4242
android:exported="true"
43-
android:windowSoftInputMode="adjustResize"
43+
android:windowSoftInputMode="adjustNothing"
4444
android:theme="@style/Theme.RunAnywhereAI">
4545
<intent-filter>
4646
<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
@@ -32,15 +32,15 @@ object ModelList {
3232
AppModel(id = "qwen2.5-0.5b-instruct-q6_k", name = "Qwen 2.5 0.5B Instruct Q6_K",
3333
url = "https://huggingface.co/Triangle104/Qwen2.5-0.5B-Instruct-Q6_K-GGUF/resolve/main/qwen2.5-0.5b-instruct-q6_k.gguf",
3434
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
35-
memoryRequirement = 600_000_000),
35+
memoryRequirement = 600_000_000, supportsLoraAdapters = true),
3636
AppModel(id = "lfm2-350m-q4_k_m", name = "LiquidAI LFM2 350M Q4_K_M",
3737
url = "https://huggingface.co/LiquidAI/LFM2-350M-GGUF/resolve/main/LFM2-350M-Q4_K_M.gguf",
3838
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
39-
memoryRequirement = 250_000_000, supportsLoraAdapters = true),
39+
memoryRequirement = 250_000_000),
4040
AppModel(id = "lfm2-350m-q8_0", name = "LiquidAI LFM2 350M Q8_0",
4141
url = "https://huggingface.co/LiquidAI/LFM2-350M-GGUF/resolve/main/LFM2-350M-Q8_0.gguf",
4242
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
43-
memoryRequirement = 400_000_000, supportsLoraAdapters = true),
43+
memoryRequirement = 400_000_000),
4444
AppModel(id = "lfm2-1.2b-tool-q4_k_m", name = "LiquidAI LFM2 1.2B Tool Q4_K_M",
4545
url = "https://huggingface.co/LiquidAI/LFM2-1.2B-Tool-GGUF/resolve/main/LFM2-1.2B-Tool-Q4_K_M.gguf",
4646
framework = InferenceFramework.LLAMA_CPP, category = ModelCategory.LANGUAGE,
@@ -81,46 +81,46 @@ object ModelList {
8181
)),
8282
)
8383

84-
// LoRA Adapters (from Void2377/Qwen on HuggingFace — real standalone LoRA GGUF files)
84+
// LoRA Adapters
8585
private val loraAdapters = listOf(
8686
LoraAdapterCatalogEntry(
87-
id = "chat-assistant-lora",
88-
name = "Chat Assistant",
89-
description = "Enhances conversational chat ability",
90-
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/chat_assistant-lora-Q8_0.gguf",
91-
filename = "chat_assistant-lora-Q8_0.gguf",
92-
compatibleModelIds = listOf("lfm2-350m-q4_k_m", "lfm2-350m-q8_0"),
93-
fileSize = 690_176,
87+
id = "code-assistant-lora",
88+
name = "Code Assistant",
89+
description = "Enhances code generation and programming assistance",
90+
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/code-assistant-Q8_0.gguf",
91+
filename = "code-assistant-Q8_0.gguf",
92+
compatibleModelIds = listOf("qwen2.5-0.5b-instruct-q6_k"),
93+
fileSize = 765_952,
9494
defaultScale = 1.0f,
9595
),
9696
LoraAdapterCatalogEntry(
97-
id = "summarizer-lora",
98-
name = "Summarizer",
99-
description = "Specialized for text summarization tasks",
100-
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/summarizer-lora-Q8_0.gguf",
101-
filename = "summarizer-lora-Q8_0.gguf",
102-
compatibleModelIds = listOf("lfm2-350m-q4_k_m", "lfm2-350m-q8_0"),
103-
fileSize = 690_176,
97+
id = "reasoning-logic-lora",
98+
name = "Reasoning Logic",
99+
description = "Improves logical reasoning and step-by-step problem solving",
100+
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/reasoning-logic-Q8_0.gguf",
101+
filename = "reasoning-logic-Q8_0.gguf",
102+
compatibleModelIds = listOf("qwen2.5-0.5b-instruct-q6_k"),
103+
fileSize = 765_952,
104104
defaultScale = 1.0f,
105105
),
106106
LoraAdapterCatalogEntry(
107-
id = "translator-lora",
108-
name = "Translator",
109-
description = "Improves translation between languages",
110-
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/translator-lora-Q8_0.gguf",
111-
filename = "translator-lora-Q8_0.gguf",
112-
compatibleModelIds = listOf("lfm2-350m-q4_k_m", "lfm2-350m-q8_0"),
113-
fileSize = 690_176,
107+
id = "medical-qa-lora",
108+
name = "Medical QA",
109+
description = "Enhances medical question answering and health-related responses",
110+
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/medical-qa-Q8_0.gguf",
111+
filename = "medical-qa-Q8_0.gguf",
112+
compatibleModelIds = listOf("qwen2.5-0.5b-instruct-q6_k"),
113+
fileSize = 765_952,
114114
defaultScale = 1.0f,
115115
),
116116
LoraAdapterCatalogEntry(
117-
id = "sentiment-lora",
118-
name = "Sentiment Analysis",
119-
description = "Fine-tuned for sentiment analysis tasks",
120-
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/sentiment-lora-Q8_0.gguf",
121-
filename = "sentiment-lora-Q8_0.gguf",
122-
compatibleModelIds = listOf("lfm2-350m-q4_k_m", "lfm2-350m-q8_0"),
123-
fileSize = 690_176,
117+
id = "creative-writing-lora",
118+
name = "Creative Writing",
119+
description = "Improves creative writing, storytelling, and literary style",
120+
downloadUrl = "https://huggingface.co/Void2377/Qwen/resolve/main/lora/creative-writing-Q8_0.gguf",
121+
filename = "creative-writing-Q8_0.gguf",
122+
compatibleModelIds = listOf("qwen2.5-0.5b-instruct-q6_k"),
123+
fileSize = 765_952,
124124
defaultScale = 1.0f,
125125
),
126126
)

0 commit comments

Comments
 (0)