Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ let onnxRuntimeMacOSPath = "\(packageDir)/sdk/runanywhere-swift/Binaries/onnxrun
// ./scripts/build-swift.sh --set-remote (sets useLocalBinaries = false)
//
// =============================================================================
let useLocalBinaries = false // Toggle: true for local dev, false for release
let useLocalBinaries = true // Toggle: true for local dev, false for release

// Version for remote XCFrameworks (used when testLocal = false)
// Updated automatically by CI/CD during releases
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {

private var generationJob: Job? = null

private val generationPrefs by lazy {
getApplication<Application>().getSharedPreferences("generation_settings", android.content.Context.MODE_PRIVATE)
}
Comment on lines +83 to +85
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for all occurrences of "generation_settings" and related SharedPreferences usage
echo "=== Searching for 'generation_settings' string ==="
rg -n "generation_settings" --type=kt

echo -e "\n=== Searching for SharedPreferences writes in SettingsViewModel ==="
fd -t f "SettingsViewModel.kt" --exec rg -l "SharedPreferences\|putFloat\|putInt\|putString" {} \;

echo -e "\n=== Checking if SettingsViewModel exists and what SharedPreferences keys it uses ==="
fd -t f "SettingsViewModel.kt" -x cat -n {}

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 46453


🏁 Script executed:

#!/bin/bash
# Find ChatViewModel.kt
echo "=== Finding ChatViewModel.kt ==="
fd "ChatViewModel" --type f

echo -e "\n=== Reading ChatViewModel.kt around lines 83-85 ==="
fd "ChatViewModel.kt" --type f -x cat -n {} | head -100

echo -e "\n=== Search for all uses of 'generation_settings' with correct syntax ==="
rg "generation_settings" -t kotlin --no-heading

echo -e "\n=== Check if ChatViewModel imports or uses SettingsViewModel constants ==="
fd "ChatViewModel.kt" --type f -x rg "PREFS_NAME\|generation_settings" -n {}

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 5890


🏁 Script executed:

#!/bin/bash
# Find what ChatViewModel does with generationPrefs - look for getGenerationOptions
echo "=== Find getGenerationOptions method in ChatViewModel ==="
fd "ChatViewModel.kt" --type f -x sed -n '850,900p' {}

echo -e "\n=== Check if ChatViewModel could use SettingsViewModel.getGenerationSettings() ==="
fd "ChatViewModel.kt" --type f -x grep -n "getGenerationSettings\|generationPrefs" {}

echo -e "\n=== Check full context of generation prefs usage ==="
fd "ChatViewModel.kt" --type f -x sed -n '83,400p' {} | head -200

Repository: RunanywhereAI/runanywhere-sdks

Length of output: 10463


Extract SharedPreferences file name to a shared constant.

The preference file name "generation_settings" is hardcoded as a string literal. If the name defined in SettingsViewModel changes, ChatViewModel will silently use stale preferences. Extract this into a constants file (e.g., PreferencesConstants.kt) and import it in both view models, or make SettingsViewModel.PREFS_NAME public so ChatViewModel can reference it directly.

🤖 Prompt for AI Agents
In
`@examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/chat/ChatViewModel.kt`
around lines 83 - 85, Replace the hardcoded preference file name
"generation_settings" used when initializing generationPrefs with a shared
constant so both view models reference the same identifier; either create a
PreferencesConstants.kt with a public val (e.g., PREFS_NAME) and import it into
ChatViewModel (used in generationPrefs) and SettingsViewModel, or make
SettingsViewModel.PREFS_NAME public and reference that constant from
ChatViewModel.getSharedPreferences to avoid drift.


init {
// Always start with a new conversation for a fresh chat experience
val conversation = conversationStore.createConversation()
Expand Down Expand Up @@ -342,7 +346,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {

try {
// Use SDK streaming generation - returns Flow<String>
RunAnywhere.generateStream(prompt).collect { token ->
RunAnywhere.generateStream(prompt, getGenerationOptions()).collect { token ->
fullResponse += token
totalTokensReceived++

Expand Down Expand Up @@ -466,7 +470,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {

try {
// RunAnywhere.generate() returns LLMGenerationResult
val result = RunAnywhere.generate(prompt)
val result = RunAnywhere.generate(prompt, getGenerationOptions())
val response = result.text
val endTime = System.currentTimeMillis()

Expand Down Expand Up @@ -846,6 +850,24 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
_uiState.value = _uiState.value.copy(error = null)
}

/**
* Get generation options from SharedPreferences
*/
private fun getGenerationOptions(): com.runanywhere.sdk.public.extensions.LLM.LLMGenerationOptions {
val temperature = generationPrefs.getFloat("defaultTemperature", 0.7f)
val maxTokens = generationPrefs.getInt("defaultMaxTokens", 1000)
val systemPromptValue = generationPrefs.getString("defaultSystemPrompt", "")
val systemPrompt = if (systemPromptValue.isNullOrEmpty()) null else systemPromptValue

Log.i(TAG, "[PARAMS] App getGenerationOptions: temperature=$temperature, maxTokens=$maxTokens, systemPrompt=${systemPrompt ?: "nil"}")

return com.runanywhere.sdk.public.extensions.LLM.LLMGenerationOptions(
maxTokens = maxTokens,
temperature = temperature,
systemPrompt = systemPrompt
)
}

/**
* Format a ToolValue map to JSON string for display.
* Uses kotlinx.serialization for proper JSON escaping of special characters.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

package com.runanywhere.runanywhereai.presentation.settings

import android.app.Application
import android.content.Intent
import android.net.Uri
import android.text.format.Formatter
Expand Down Expand Up @@ -31,13 +32,12 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.runanywhere.runanywhereai.ui.theme.AppColors
import com.runanywhere.runanywhereai.ui.theme.AppTypography
import com.runanywhere.runanywhereai.ui.theme.Dimensions
import android.app.Application

/**
* Settings screen
* Settings & Storage Screen
*
* Section order: Generation Settings, API Configuration, Storage Overview, Downloaded Models,
* Storage Management, Logging Configuration, About.
* Section order: API Configuration, Generation Settings, Tool Calling,
* Storage Overview, Downloaded Models, Storage Management, Logging Configuration, About.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
Expand Down Expand Up @@ -72,54 +72,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = viewModel()) {
)
}

// 1. Generation Settings
SettingsSection(title = "Generation Settings") {
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
Text(
text = "Temperature: ${"%.2f".format(uiState.temperature)}",
style = AppTypography.caption,
color = AppColors.textSecondary,
)
Slider(
value = uiState.temperature,
onValueChange = { viewModel.updateTemperature(it) },
valueRange = 0f..2f,
steps = 19,
modifier = Modifier.fillMaxWidth(),
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = "Max Tokens: ${uiState.maxTokens}",
style = MaterialTheme.typography.bodyMedium,
)
Row(verticalAlignment = Alignment.CenterVertically) {
OutlinedButton(
onClick = { viewModel.updateMaxTokens((uiState.maxTokens - 500).coerceAtLeast(500)) },
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp),
modifier = Modifier.height(32.dp),
) { Text("-", style = AppTypography.caption) }
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "${uiState.maxTokens}",
style = AppTypography.caption,
modifier = Modifier.widthIn(min = 48.dp),
)
Spacer(modifier = Modifier.width(8.dp))
OutlinedButton(
onClick = { viewModel.updateMaxTokens((uiState.maxTokens + 500).coerceAtMost(20000)) },
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp),
modifier = Modifier.height(32.dp),
) { Text("+", style = AppTypography.caption) }
}
}
}
}

// 2. API Configuration (Testing)
// 1. API Configuration (Testing)
SettingsSection(title = "API Configuration (Testing)") {
Row(
modifier = Modifier
Expand Down Expand Up @@ -182,10 +135,99 @@ fun SettingsScreen(viewModel: SettingsViewModel = viewModel()) {
)
}

// Tool Calling Section
// 2. Generation Settings Section
SettingsSection(title = "Generation Settings") {
// Temperature Slider
Column(modifier = Modifier.fillMaxWidth()) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = "Temperature",
style = MaterialTheme.typography.bodyLarge,
)
Text(
text = String.format("%.1f", uiState.temperature),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
Slider(
value = uiState.temperature,
onValueChange = { viewModel.updateTemperature(it) },
valueRange = 0f..2f,
steps = 19, // 0.1 increments from 0.0 to 2.0
modifier = Modifier.fillMaxWidth(),
)
}

HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))

// Max Tokens Slider
Column(modifier = Modifier.fillMaxWidth()) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = "Max Tokens",
style = MaterialTheme.typography.bodyLarge,
)
Text(
text = uiState.maxTokens.toString(),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
Slider(
value = uiState.maxTokens.toFloat(),
onValueChange = { viewModel.updateMaxTokens(it.toInt()) },
valueRange = 50f..4096f,
steps = 80, // 50-token increments
modifier = Modifier.fillMaxWidth(),
)
Comment on lines +185 to +191
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Max Tokens slider steps don't produce clean 50-token increments.

With valueRange = 50f..4096f and steps = 80, the Slider creates 81 intervals of size (4096 - 50) / 81 ≈ 49.95, so the snap points won't land on round numbers. Users will see values like 150, 250, 349, 449… after toInt().

If clean 50-token increments are desired, consider changing the range to 50f..4050f with steps = 79, or use a different max that's reachable in exact 50-step increments from 50.

Suggested fix (clean 50-token steps)
                 Slider(
                     value = uiState.maxTokens.toFloat(),
                     onValueChange = { viewModel.updateMaxTokens(it.toInt()) },
-                    valueRange = 50f..4096f,
-                    steps = 80, // 50-token increments
+                    valueRange = 50f..4050f,
+                    steps = 79, // 50-token increments: 50, 100, 150, ..., 4050
                     modifier = Modifier.fillMaxWidth(),
                 )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Slider(
value = uiState.maxTokens.toFloat(),
onValueChange = { viewModel.updateMaxTokens(it.toInt()) },
valueRange = 50f..4096f,
steps = 80, // 50-token increments
modifier = Modifier.fillMaxWidth(),
)
Slider(
value = uiState.maxTokens.toFloat(),
onValueChange = { viewModel.updateMaxTokens(it.toInt()) },
valueRange = 50f..4050f,
steps = 79, // 50-token increments: 50, 100, 150, ..., 4050
modifier = Modifier.fillMaxWidth(),
)
🤖 Prompt for AI Agents
In
`@examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/settings/SettingsScreen.kt`
around lines 185 - 191, The Slider configuration for max tokens (the Slider
composable using uiState.maxTokens and viewModel.updateMaxTokens) currently uses
valueRange = 50f..4096f and steps = 80 which yields non‑integer 50-token
increments; adjust the range and steps so the interval divides evenly (for
example change valueRange to 50f..4050f and steps to 79 or pick a max that is 50
* N + 50 from 50) so the slider snap points map to exact 50-token multiples and
onValueChange.toInt() produces clean values.

}

HorizontalDivider(modifier = Modifier.padding(vertical = 4.dp))

// System Prompt TextField
OutlinedTextField(
value = uiState.systemPrompt,
onValueChange = { viewModel.updateSystemPrompt(it) },
label = { Text("System Prompt") },
placeholder = { Text("Enter system prompt (optional)") },
modifier = Modifier.fillMaxWidth(),
maxLines = 3,
textStyle = MaterialTheme.typography.bodyMedium,
)

Spacer(modifier = Modifier.height(8.dp))

// Save Button
OutlinedButton(
onClick = { viewModel.saveGenerationSettings() },
colors = ButtonDefaults.outlinedButtonColors(
contentColor = AppColors.primaryAccent,
),
) {
Text("Save Settings")
}

Spacer(modifier = Modifier.height(8.dp))
Text(
text = "These settings affect LLM text generation.",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}

// 3. Tool Calling Section
ToolSettingsSection()

// 3. Storage Overview - iOS Label(systemImage: "externaldrive") etc.
// 4. Storage Overview
SettingsSection(
title = "Storage Overview",
trailing = {
Expand Down Expand Up @@ -223,7 +265,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = viewModel()) {
)
}

// 4. Downloaded Models
// 5. Downloaded Models
SettingsSection(title = "Downloaded Models") {
if (uiState.downloadedModels.isEmpty()) {
Text(
Expand All @@ -245,7 +287,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = viewModel()) {
}
}

// 5. Storage Management - iOS trash icon, red/orange
// 6. Storage Management
SettingsSection(title = "Storage Management") {
StorageManagementButton(
title = "Clear Cache",
Expand All @@ -264,7 +306,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = viewModel()) {
)
}

// 6. Logging Configuration - iOS Toggle "Log Analytics Locally"
// 7. Logging Configuration
SettingsSection(title = "Logging Configuration") {
Row(
modifier = Modifier.fillMaxWidth(),
Expand All @@ -288,7 +330,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = viewModel()) {
)
}

// 7. About - iOS Label "RunAnywhere SDK" systemImage "cube", "Documentation" systemImage "book"
// 8. About
SettingsSection(title = "About") {
Row(
modifier = Modifier.padding(vertical = 8.dp),
Expand Down Expand Up @@ -394,7 +436,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = viewModel()) {
)
}

// Restart Required Dialog - iOS exact message
// Restart Required Dialog
if (uiState.showRestartDialog) {
AlertDialog(
onDismissRequest = { viewModel.dismissRestartDialog() },
Expand Down Expand Up @@ -712,8 +754,7 @@ private fun ApiConfigurationDialog(

/**
* Tool Calling Settings Section
*
* Allows users to:
* * Allows users to:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor KDoc typo: extra *.

Line 757 reads * * Allows users to: — the leading * is duplicated.

- * * Allows users to:
+ * Allows users to:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* * Allows users to:
* Allows users to:
🤖 Prompt for AI Agents
In
`@examples/android/RunAnywhereAI/app/src/main/java/com/runanywhere/runanywhereai/presentation/settings/SettingsScreen.kt`
at line 757, In the KDoc for SettingsScreen (SettingsScreen.kt) there is a
duplicated asterisk in the list description ("* * Allows users to:"); edit the
KDoc comment so the line reads a single leading list marker ("* Allows users
to:") to remove the extra `* ` and keep the list formatting consistent.

* - Enable/disable tool calling
* - Register demo tools (weather, time, calculator)
* - Clear all registered tools
Expand Down Expand Up @@ -859,4 +900,4 @@ fun ToolSettingsSection() {
)
}
}
}
}
Loading
Loading