diff --git a/android-llmservice/README.md b/android-llmservice/README.md index 7929070c..918bdb79 100644 --- a/android-llmservice/README.md +++ b/android-llmservice/README.md @@ -174,8 +174,8 @@ android-llmservice/ ├── main/res/values/{strings,themes}.xml + values-*/strings.xml (13 languages) ├── main/res/xml/locales_config.xml ├── main/kotlin/net/ladenthin/android/llmservice/ - │ ├── MainActivity.kt # Compose UI + SAF picker + flag language menu + save/load - │ ├── ChatViewModel.kt # model load + streaming chat + session save/load (the logic) + │ ├── MainActivity.kt # Compose UI + SAF picker + flag menu + save/load + settings + log viewer + │ ├── ChatViewModel.kt # model load + streaming chat + sampling settings + in-app log + session (the logic) │ ├── Languages.kt # the flag/language list │ └── SessionStore.kt # private local JSON persistence (filesDir) └── androidTest/kotlin/net/ladenthin/android/llmservice/ diff --git a/android-llmservice/TODO.md b/android-llmservice/TODO.md index bd7ae60c..b077c1b8 100644 --- a/android-llmservice/TODO.md +++ b/android-llmservice/TODO.md @@ -45,6 +45,9 @@ Everything else in the brief is feasible with standard AndroidX + the existing b | Private local save / load session | ✅ | JSON in `filesDir`, app-private, nothing uploaded | | `allowBackup=false` (data stays on device) | ✅ | resolves CodeQL alert; privacy posture | | minSdk 28, arm64-v8a + x86_64, signed release `.aab`, on-device emulator UI test | ✅ | | +| Generation settings sheet (temperature, Top-K/P, Min-P, **repeat penalty**, repeat range, max tokens) | ✅ | ⚙️ in the top bar; live sliders + reset. The repeat-penalty default (1.1) fixes the degenerate repetition loop on small models | +| In-app log: always-visible one-line strip + full viewer (copy-all, save-as-txt via SAF, clear) | ✅ | 🧾 strip at the bottom; tap to open, ✕ to close | +| Draggable (horizontally scrollable) model-name title | ✅ | long names can be dragged into view — no auto-marquee wobble | --- @@ -56,7 +59,7 @@ Everything else in the brief is feasible with standard AndroidX + the existing b | Material 3 theme + brand palette (indigo `#476EAC`, lavender, charcoal, cream) | Polished, on-brand look | S–M | 🔧 | currently default `MaterialTheme`; add color scheme + shapes/elevation | | Light + dark ("technical dark" / "soft light") | Brief's hybrid direction | S | 🔧 | Compose supports; wire dynamic + manual toggle | | Smooth transitions / motion polish | "more polished than a dev demo" | S | ⬜ | Compose animations, shared-element nav | -| Optional 5th destination: Tools / Logs | Advanced users | S | ⬜ | keep out of bottom bar to avoid crowding (overflow/menu) | +| Optional 5th destination: Tools / Logs | Advanced users | S | 🔧 | **log** shipped as a bottom strip + full viewer (copy/save-txt/clear); a dedicated Tools destination is still todo | ## 2. Onboarding & device readiness @@ -76,7 +79,7 @@ Everything else in the brief is feasible with standard AndroidX + the existing b | Feature | Purpose | Effort | Status | Notes | |---|---|---|---|---| | Streaming responses + bubbles | Core chat | S | ✅ | polish to pastel user bubbles / larger AI cards | -| Top bar: model chip · "Local" · RAM indicator · offline badge · switch | Context + control | M | 🔧 | model name shown; RAM/switch/offline todo | +| Top bar: model chip · "Local" · RAM indicator · offline badge · switch | Context + control | M | 🔧 | model name shown (now horizontally draggable so long names are fully readable); RAM/switch/offline todo | | Markdown rendering (code, lists, tables, headings) | Readable answers | M | ⬜ | add a Compose Markdown renderer (e.g. compose-markdown) | | Copy / regenerate / stop / clear-chat actions | Standard chat UX | M | 🔧 | **stop** = `CancellationToken` (façade wires it; `completeSuspend`/flow cancel) | | Prompt shortcut chips (Summarize / Explain code / Translate / …) | Fast starts | S | ⬜ | localized prompt templates | @@ -131,7 +134,7 @@ See decision #1. All rows below assume a new in-app Ktor/NanoHTTPD HTTP layer wr |---|---|---|---|---| | CPU threads | Tune speed | S | ⬜ | `ModelParameters.setThreads` / `setThreadsBatch` | | Context length | Memory vs. history | S | ⬜ | `setCtxSize` | -| Temperature / Max tokens | Sampling control | S | ⬜ | `withTemperature` / `withNPredict` (+ TopK/TopP/MinP available) | +| Temperature / Max tokens / Top-K / Top-P / Min-P / **repeat penalty** + range | Sampling control | S | ✅ | shipped in the ⚙️ Generation settings sheet (live sliders + reset); `withTemperature`/`withNPredict`/`withTopK`/`withTopP`/`withMinP`/`withRepeatPenalty`/`withRepeatLastN`. The repeat-penalty default is what stops the small-model repetition loop | | GPU acceleration toggle | Speed on Adreno | M | 🔧 | `setGpuLayers`; **Adreno/OpenCL AAR only** (see §7) | | Battery saver mode | Protect battery | M | ⬜ | throttle threads / ngl on low battery | | Thermal protection | Prevent overheating | M | ⬜ | `PowerManager.getCurrentThermalStatus()` → pause/throttle | diff --git a/android-llmservice/app/src/main/kotlin/net/ladenthin/android/llmservice/ChatViewModel.kt b/android-llmservice/app/src/main/kotlin/net/ladenthin/android/llmservice/ChatViewModel.kt index db6255a1..a25e7c60 100644 --- a/android-llmservice/app/src/main/kotlin/net/ladenthin/android/llmservice/ChatViewModel.kt +++ b/android-llmservice/app/src/main/kotlin/net/ladenthin/android/llmservice/ChatViewModel.kt @@ -9,6 +9,8 @@ import android.net.Uri import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import java.io.File +import java.time.LocalTime +import java.time.temporal.ChronoUnit import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableStateFlow @@ -50,6 +52,28 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { /** A localized-at-the-UI error: the [type] selects the template, [detail] fills it in. */ data class ErrorInfo(val type: ErrorType, val detail: String) + /** + * Sampling / generation knobs surfaced in the Settings sheet. The defaults are chosen to give + * a coherent reply on small on-device models: a [repeatPenalty] > 1 with a non-zero + * [repeatLastN] is what stops the degenerate "…spezifischen spezifischen…" repetition loop that + * a bare greedy/no-penalty decode falls into (the reported SmolVLM-500M behaviour). All are + * forwarded verbatim to [InferenceParameters]; see [GenerationSettings.DEFAULT]. + */ + data class GenerationSettings( + val temperature: Float = 0.7f, + val topK: Int = 40, + val topP: Float = 0.95f, + val minP: Float = 0.05f, + val repeatPenalty: Float = 1.1f, + val repeatLastN: Int = 64, + val maxTokens: Int = 256, + ) { + companion object { + /** The shipped defaults (also the "Reset" target). */ + val DEFAULT = GenerationSettings() + } + } + /** Immutable snapshot the Compose UI renders. */ data class UiState( val modelState: ModelState = ModelState.NONE, @@ -58,11 +82,20 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { val generating: Boolean = false, val error: ErrorInfo? = null, val notice: Notice? = null, + val settings: GenerationSettings = GenerationSettings.DEFAULT, ) private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow = _uiState.asStateFlow() + /** + * Rolling in-app log (newest last), shown as a one-line strip at the bottom and in full in the + * log viewer. Kept separate from [uiState] so per-token chat updates don't re-emit the whole + * log list. Capped at [MAX_LOG_LINES]; entries are prefixed with a local wall-clock time. + */ + private val _log = MutableStateFlow>(emptyList()) + val log: StateFlow> = _log.asStateFlow() + private var model: LlamaModel? = null private var modelPath: String? = null private var generation: Job? = null @@ -75,8 +108,30 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { private var chatTemplate: String? = null private companion object { - const val MAX_TOKENS = 256 const val CONTEXT_SIZE = 2048 + const val MAX_LOG_LINES = 500 + } + + /** Appends one timestamped line to the rolling [log] (trimmed to [MAX_LOG_LINES]). */ + private fun log(line: String) { + val ts = LocalTime.now().truncatedTo(ChronoUnit.SECONDS) + _log.update { (it + "$ts $line").takeLast(MAX_LOG_LINES) } + } + + /** Replaces the sampling settings (from the Settings sheet). */ + fun updateSettings(settings: GenerationSettings) { + _uiState.update { it.copy(settings = settings) } + } + + /** Restores the shipped default sampling settings. */ + fun resetSettings() { + _uiState.update { it.copy(settings = GenerationSettings.DEFAULT) } + log("Settings reset to defaults") + } + + /** Clears the in-app log buffer. */ + fun clearLog() { + _log.value = emptyList() } /** @@ -109,6 +164,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { } private suspend fun openModel(path: String, displayName: String, template: String?) { + log("Loading model: $displayName") try { val loaded = withContext(Dispatchers.IO) { LlamaModel( @@ -125,7 +181,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { _uiState.update { it.copy(modelState = ModelState.READY, modelName = displayName, error = null) } + log("Model ready: $displayName (ctx=$CONTEXT_SIZE, CPU)") } catch (t: Throwable) { + log("Load failed: ${t.message ?: "load failed"}") _uiState.update { it.copy(modelState = ModelState.NONE, error = ErrorInfo(ErrorType.LOAD, t.message ?: "load failed")) } @@ -149,13 +207,23 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { // Append an empty assistant message that grows as tokens arrive. _uiState.update { it.copy(messages = history + Message("assistant", ""), generating = true, error = null) } + val s = _uiState.value.settings generation = viewModelScope.launch { val pairs = history.map { Pair(it.role, it.text) } + // Apply the sampling knobs. repeatPenalty (> 1) + repeatLastN are the ones that break + // the degenerate repetition loop small on-device models fall into with a bare decode. var params = InferenceParameters("") .withMessages(systemPrompt, pairs) - .withNPredict(MAX_TOKENS) + .withNPredict(s.maxTokens) + .withTemperature(s.temperature) + .withTopK(s.topK) + .withTopP(s.topP) + .withMinP(s.minP) + .withRepeatPenalty(s.repeatPenalty) + .withRepeatLastN(s.repeatLastN) chatTemplate?.let { params = params.withChatTemplate(it) } + log("Generating (temp=${s.temperature}, repeat=${s.repeatPenalty}/${s.repeatLastN}, maxTokens=${s.maxTokens})") val reply = StringBuilder() try { active.generateChatFlow(params) @@ -164,7 +232,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { reply.append(output.text) _uiState.update { state -> state.replaceLastAssistant(reply.toString()) } } + log("Reply complete (${reply.length} chars)") } catch (t: Throwable) { + log("Generation failed: ${t.message ?: "generation failed"}") _uiState.update { state -> state.replaceLastAssistant(reply.toString()) .copy(error = ErrorInfo(ErrorType.GENERATION, t.message ?: "generation failed")) @@ -185,6 +255,7 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { withContext(Dispatchers.IO) { SessionStore.save(getApplication(), pathAtSave, nameAtSave, messagesAtSave) } + log("Session saved (${messagesAtSave.size} messages)") _uiState.update { it.copy(notice = Notice.SESSION_SAVED) } } } @@ -194,9 +265,11 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) { viewModelScope.launch { val saved = withContext(Dispatchers.IO) { SessionStore.load(getApplication()) } if (saved == null) { + log("Load session: no saved chat") _uiState.update { it.copy(notice = Notice.NO_SESSION) } return@launch } + log("Session loaded (${saved.messages.size} messages)") _uiState.update { it.copy(messages = saved.messages, notice = Notice.SESSION_LOADED) } val path = saved.modelPath if (path != null && File(path).canRead() && modelPath != path) { diff --git a/android-llmservice/app/src/main/kotlin/net/ladenthin/android/llmservice/MainActivity.kt b/android-llmservice/app/src/main/kotlin/net/ladenthin/android/llmservice/MainActivity.kt index c2d18ec3..4c7682f4 100644 --- a/android-llmservice/app/src/main/kotlin/net/ladenthin/android/llmservice/MainActivity.kt +++ b/android-llmservice/app/src/main/kotlin/net/ladenthin/android/llmservice/MainActivity.kt @@ -8,23 +8,31 @@ import android.content.Context import android.net.Uri import android.os.Bundle import android.provider.OpenableColumns +import android.widget.Toast import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.setContent import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.DropdownMenu @@ -34,6 +42,7 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold +import androidx.compose.material3.Slider import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Surface @@ -48,13 +57,20 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Dialog +import androidx.compose.ui.window.DialogProperties import androidx.core.os.LocaleListCompat import androidx.lifecycle.compose.collectAsStateWithLifecycle +import java.util.Locale +import kotlin.math.roundToInt /** * Single-screen KISS demo: pick a GGUF from the file system, then chat with it fully on-device. @@ -117,10 +133,22 @@ private fun ChatScreen(viewModel: ChatViewModel) { } } + var showSettings by remember { mutableStateOf(false) } + var showLog by remember { mutableStateOf(false) } + Scaffold( topBar = { TopAppBar( - title = { Text(state.modelName ?: stringResource(R.string.app_name)) }, + title = { + // Horizontally scrollable so a long model name can be dragged into view in full, + // without an auto-scrolling marquee that would "wobble" constantly. + Text( + text = state.modelName ?: stringResource(R.string.app_name), + maxLines = 1, + softWrap = false, + modifier = Modifier.horizontalScroll(rememberScrollState()), + ) + }, actions = { IconButton( onClick = { viewModel.saveSession() }, @@ -131,10 +159,14 @@ private fun ChatScreen(viewModel: ChatViewModel) { IconButton(onClick = { viewModel.loadSession() }) { Text("📂", modifier = Modifier.testTag("loadButton")) } + IconButton(onClick = { showSettings = true }) { + Text("⚙️", modifier = Modifier.testTag("settingsButton")) + } LanguageMenu() }, ) }, + bottomBar = { LogStrip(viewModel = viewModel, onOpen = { showLog = true }) }, snackbarHost = { SnackbarHost(snackbarHostState) }, ) { padding -> Box(modifier = Modifier.fillMaxSize().padding(padding)) { @@ -151,6 +183,161 @@ private fun ChatScreen(viewModel: ChatViewModel) { } } } + + if (showSettings) { + SettingsDialog( + settings = state.settings, + onChange = viewModel::updateSettings, + onReset = viewModel::resetSettings, + onClose = { showSettings = false }, + ) + } + if (showLog) { + LogDialog(viewModel = viewModel, onClose = { showLog = false }) + } +} + +/** The always-visible one-line log strip at the very bottom; tap to open the full [LogDialog]. */ +@Composable +private fun LogStrip(viewModel: ChatViewModel, onOpen: () -> Unit) { + val log by viewModel.log.collectAsStateWithLifecycle() + val last = log.lastOrNull() ?: stringResource(R.string.log_empty) + Surface(tonalElevation = 2.dp) { + Row( + modifier = Modifier.fillMaxWidth().clickable(onClick = onOpen).testTag("logStrip") + .padding(horizontal = 12.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text("🧾", modifier = Modifier.padding(end = 8.dp)) + Text( + text = last, + style = MaterialTheme.typography.labelSmall, + maxLines = 2, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), + ) + } + } +} + +/** Full-screen log viewer with copy-all, save-as-txt (SAF) and clear; ✕ (top-right) closes it. */ +@Composable +private fun LogDialog(viewModel: ChatViewModel, onClose: () -> Unit) { + val context = LocalContext.current + val clipboard = LocalClipboardManager.current + val log by viewModel.log.collectAsStateWithLifecycle() + val text = log.joinToString("\n") + val copiedMsg = stringResource(R.string.toast_log_copied) + val savedMsg = stringResource(R.string.toast_log_saved) + + val saver = rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { uri: Uri? -> + if (uri != null) { + context.contentResolver.openOutputStream(uri)?.use { it.write(text.toByteArray(Charsets.UTF_8)) } + Toast.makeText(context, savedMsg, Toast.LENGTH_SHORT).show() + } + } + + Dialog(onDismissRequest = onClose, properties = DialogProperties(usePlatformDefaultWidth = false)) { + Surface(modifier = Modifier.fillMaxSize().padding(12.dp), shape = MaterialTheme.shapes.large) { + Column(modifier = Modifier.fillMaxSize().padding(12.dp)) { + Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { + Text( + text = stringResource(R.string.log_title), + style = MaterialTheme.typography.titleLarge, + modifier = Modifier.weight(1f), + ) + IconButton(onClick = onClose) { Text("✕", modifier = Modifier.testTag("logClose")) } + } + SelectionContainer(modifier = Modifier.weight(1f).fillMaxWidth()) { + Column(modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState())) { + Text( + text = text.ifEmpty { stringResource(R.string.log_empty) }, + style = MaterialTheme.typography.bodySmall, + ) + } + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically, + ) { + TextButton(onClick = { viewModel.clearLog() }) { Text(stringResource(R.string.log_clear)) } + TextButton( + onClick = { + clipboard.setText(AnnotatedString(text)) + Toast.makeText(context, copiedMsg, Toast.LENGTH_SHORT).show() + }, + enabled = text.isNotEmpty(), + ) { + Text(stringResource(R.string.log_copy_all)) + } + TextButton(onClick = { saver.launch("llm-service-log.txt") }, enabled = text.isNotEmpty()) { + Text(stringResource(R.string.log_save_as)) + } + } + } + } + } +} + +/** Sampling-knobs sheet. Changes apply live; "Reset defaults" restores the shipped values. */ +@Composable +private fun SettingsDialog( + settings: ChatViewModel.GenerationSettings, + onChange: (ChatViewModel.GenerationSettings) -> Unit, + onReset: () -> Unit, + onClose: () -> Unit, +) { + Dialog(onDismissRequest = onClose) { + Surface(shape = MaterialTheme.shapes.large, tonalElevation = 6.dp) { + Column(modifier = Modifier.padding(20.dp).verticalScroll(rememberScrollState())) { + Text(stringResource(R.string.settings_title), style = MaterialTheme.typography.titleLarge) + Spacer(Modifier.height(12.dp)) + FloatSetting(R.string.settings_temperature, settings.temperature, 0f..2f) { + onChange(settings.copy(temperature = it)) + } + IntSetting(R.string.settings_top_k, settings.topK, 0..100) { onChange(settings.copy(topK = it)) } + FloatSetting(R.string.settings_top_p, settings.topP, 0f..1f) { onChange(settings.copy(topP = it)) } + FloatSetting(R.string.settings_min_p, settings.minP, 0f..1f) { onChange(settings.copy(minP = it)) } + FloatSetting(R.string.settings_repeat_penalty, settings.repeatPenalty, 1f..2f) { + onChange(settings.copy(repeatPenalty = it)) + } + IntSetting(R.string.settings_repeat_last_n, settings.repeatLastN, 0..256) { + onChange(settings.copy(repeatLastN = it)) + } + IntSetting(R.string.settings_max_tokens, settings.maxTokens, 16..1024) { + onChange(settings.copy(maxTokens = it)) + } + Spacer(Modifier.height(8.dp)) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { + TextButton(onClick = onReset) { Text(stringResource(R.string.settings_reset)) } + TextButton(onClick = onClose) { Text(stringResource(R.string.action_close)) } + } + } + } + } +} + +/** A labelled float slider showing its current value (2 decimals, locale-independent). */ +@Composable +private fun FloatSetting(labelRes: Int, value: Float, range: ClosedFloatingPointRange, onChange: (Float) -> Unit) { + Column(modifier = Modifier.padding(vertical = 4.dp)) { + Text("${stringResource(labelRes)}: ${String.format(Locale.US, "%.2f", value)}", style = MaterialTheme.typography.bodyMedium) + Slider(value = value, onValueChange = onChange, valueRange = range) + } +} + +/** A labelled integer slider (rounds the slider's float to the nearest int). */ +@Composable +private fun IntSetting(labelRes: Int, value: Int, range: IntRange, onChange: (Int) -> Unit) { + Column(modifier = Modifier.padding(vertical = 4.dp)) { + Text("${stringResource(labelRes)}: $value", style = MaterialTheme.typography.bodyMedium) + Slider( + value = value.toFloat(), + onValueChange = { onChange(it.roundToInt()) }, + valueRange = range.first.toFloat()..range.last.toFloat(), + ) + } } @Composable diff --git a/android-llmservice/app/src/main/res/values-ar/strings.xml b/android-llmservice/app/src/main/res/values-ar/strings.xml index b6a0ca87..c56134e7 100644 --- a/android-llmservice/app/src/main/res/values-ar/strings.xml +++ b/android-llmservice/app/src/main/res/values-ar/strings.xml @@ -20,4 +20,26 @@ SPDX-License-Identifier: MIT تم حفظ المحادثة تم تحميل المحادثة لا توجد محادثة محفوظة + + الإعدادات + إعدادات التوليد + درجة الحرارة + Top-K + Top-P + Min-P + عقوبة التكرار + نطاق التكرار + الحد الأقصى للرموز + إعادة التعيين إلى الافتراضي + إغلاق + + + السجل + السجل + لا يوجد إخراج للسجل بعد. + نسخ الكل + حفظ باسم… + مسح + تم نسخ السجل + تم حفظ السجل diff --git a/android-llmservice/app/src/main/res/values-de/strings.xml b/android-llmservice/app/src/main/res/values-de/strings.xml index 8d7512be..f982b146 100644 --- a/android-llmservice/app/src/main/res/values-de/strings.xml +++ b/android-llmservice/app/src/main/res/values-de/strings.xml @@ -20,4 +20,26 @@ SPDX-License-Identifier: MIT Chat gespeichert Chat geladen Kein gespeicherter Chat + + Einstellungen + Generierungseinstellungen + Temperatur + Top-K + Top-P + Min-P + Wiederholungsstrafe + Wiederholungsbereich + Max. Tokens + Standard wiederherstellen + Schließen + + + Protokoll + Protokoll + Noch keine Protokollausgabe. + Alles kopieren + Speichern unter… + Leeren + Protokoll kopiert + Protokoll gespeichert diff --git a/android-llmservice/app/src/main/res/values-es/strings.xml b/android-llmservice/app/src/main/res/values-es/strings.xml index 3fb0d7ad..311c86c5 100644 --- a/android-llmservice/app/src/main/res/values-es/strings.xml +++ b/android-llmservice/app/src/main/res/values-es/strings.xml @@ -20,4 +20,26 @@ SPDX-License-Identifier: MIT Chat guardado Chat cargado No hay chat guardado + + Ajustes + Ajustes de generación + Temperatura + Top-K + Top-P + Min-P + Penalización por repetición + Rango de repetición + Tokens máximos + Restablecer valores + Cerrar + + + Registro + Registro + Aún no hay salida de registro. + Copiar todo + Guardar como… + Borrar + Registro copiado + Registro guardado diff --git a/android-llmservice/app/src/main/res/values-fr/strings.xml b/android-llmservice/app/src/main/res/values-fr/strings.xml index 4a5dbb24..645d9adc 100644 --- a/android-llmservice/app/src/main/res/values-fr/strings.xml +++ b/android-llmservice/app/src/main/res/values-fr/strings.xml @@ -20,4 +20,26 @@ SPDX-License-Identifier: MIT Discussion enregistrée Discussion chargée Aucune discussion enregistrée + + Paramètres + Paramètres de génération + Température + Top-K + Top-P + Min-P + Pénalité de répétition + Portée de répétition + Jetons max + Réinitialiser + Fermer + + + Journal + Journal + Aucune sortie de journal pour l\'instant. + Tout copier + Enregistrer sous… + Effacer + Journal copié + Journal enregistré diff --git a/android-llmservice/app/src/main/res/values-hi/strings.xml b/android-llmservice/app/src/main/res/values-hi/strings.xml index 2d40ec81..a20f8a4a 100644 --- a/android-llmservice/app/src/main/res/values-hi/strings.xml +++ b/android-llmservice/app/src/main/res/values-hi/strings.xml @@ -20,4 +20,26 @@ SPDX-License-Identifier: MIT चैट सहेजी गई चैट लोड हो गई कोई सहेजी गई चैट नहीं + + सेटिंग्स + जनरेशन सेटिंग्स + तापमान + Top-K + Top-P + Min-P + दोहराव दंड + दोहराव सीमा + अधिकतम टोकन + डिफ़ॉल्ट रीसेट करें + बंद करें + + + लॉग + लॉग + अभी तक कोई लॉग आउटपुट नहीं। + सभी कॉपी करें + इस रूप में सहेजें… + साफ़ करें + लॉग कॉपी किया गया + लॉग सहेजा गया diff --git a/android-llmservice/app/src/main/res/values-it/strings.xml b/android-llmservice/app/src/main/res/values-it/strings.xml index fe030b3d..f16ca439 100644 --- a/android-llmservice/app/src/main/res/values-it/strings.xml +++ b/android-llmservice/app/src/main/res/values-it/strings.xml @@ -20,4 +20,26 @@ SPDX-License-Identifier: MIT Chat salvata Chat caricata Nessuna chat salvata + + Impostazioni + Impostazioni di generazione + Temperatura + Top-K + Top-P + Min-P + Penalità di ripetizione + Intervallo di ripetizione + Token massimi + Ripristina predefiniti + Chiudi + + + Registro + Registro + Nessun output di registro ancora. + Copia tutto + Salva con nome… + Cancella + Registro copiato + Registro salvato diff --git a/android-llmservice/app/src/main/res/values-ja/strings.xml b/android-llmservice/app/src/main/res/values-ja/strings.xml index 932ea961..4a8da33a 100644 --- a/android-llmservice/app/src/main/res/values-ja/strings.xml +++ b/android-llmservice/app/src/main/res/values-ja/strings.xml @@ -20,4 +20,26 @@ SPDX-License-Identifier: MIT チャットを保存しました チャットを読み込みました 保存されたチャットはありません + + 設定 + 生成設定 + 温度 + Top-K + Top-P + Min-P + 繰り返しペナルティ + 繰り返し範囲 + 最大トークン数 + デフォルトに戻す + 閉じる + + + ログ + ログ + まだログ出力がありません。 + すべてコピー + 名前を付けて保存… + クリア + ログをコピーしました + ログを保存しました diff --git a/android-llmservice/app/src/main/res/values-ko/strings.xml b/android-llmservice/app/src/main/res/values-ko/strings.xml index a30d4d4e..811b1f15 100644 --- a/android-llmservice/app/src/main/res/values-ko/strings.xml +++ b/android-llmservice/app/src/main/res/values-ko/strings.xml @@ -20,4 +20,26 @@ SPDX-License-Identifier: MIT 채팅이 저장되었습니다 채팅을 불러왔습니다 저장된 채팅이 없습니다 + + 설정 + 생성 설정 + 온도 + Top-K + Top-P + Min-P + 반복 페널티 + 반복 범위 + 최대 토큰 + 기본값으로 재설정 + 닫기 + + + 로그 + 로그 + 아직 로그 출력이 없습니다. + 모두 복사 + 다른 이름으로 저장… + 지우기 + 로그 복사됨 + 로그 저장됨 diff --git a/android-llmservice/app/src/main/res/values-pt/strings.xml b/android-llmservice/app/src/main/res/values-pt/strings.xml index 8d259a08..6d415501 100644 --- a/android-llmservice/app/src/main/res/values-pt/strings.xml +++ b/android-llmservice/app/src/main/res/values-pt/strings.xml @@ -20,4 +20,26 @@ SPDX-License-Identifier: MIT Conversa salva Conversa carregada Nenhuma conversa salva + + Definições + Definições de geração + Temperatura + Top-K + Top-P + Min-P + Penalização de repetição + Intervalo de repetição + Tokens máximos + Repor predefinições + Fechar + + + Registo + Registo + Ainda sem saída de registo. + Copiar tudo + Guardar como… + Limpar + Registo copiado + Registo guardado diff --git a/android-llmservice/app/src/main/res/values-ru/strings.xml b/android-llmservice/app/src/main/res/values-ru/strings.xml index c8774e84..608a0a8f 100644 --- a/android-llmservice/app/src/main/res/values-ru/strings.xml +++ b/android-llmservice/app/src/main/res/values-ru/strings.xml @@ -20,4 +20,26 @@ SPDX-License-Identifier: MIT Чат сохранён Чат загружен Нет сохранённого чата + + Настройки + Параметры генерации + Температура + Top-K + Top-P + Min-P + Штраф за повтор + Диапазон повтора + Макс. токенов + Сбросить настройки + Закрыть + + + Журнал + Журнал + Пока нет записей журнала. + Копировать всё + Сохранить как… + Очистить + Журнал скопирован + Журнал сохранён diff --git a/android-llmservice/app/src/main/res/values-tr/strings.xml b/android-llmservice/app/src/main/res/values-tr/strings.xml index 0c0b12a3..da9a10e2 100644 --- a/android-llmservice/app/src/main/res/values-tr/strings.xml +++ b/android-llmservice/app/src/main/res/values-tr/strings.xml @@ -20,4 +20,26 @@ SPDX-License-Identifier: MIT Sohbet kaydedildi Sohbet yüklendi Kayıtlı sohbet yok + + Ayarlar + Üretim ayarları + Sıcaklık + Top-K + Top-P + Min-P + Tekrar cezası + Tekrar aralığı + Maks. jeton + Varsayılanlara sıfırla + Kapat + + + Günlük + Günlük + Henüz günlük çıktısı yok. + Tümünü kopyala + Farklı kaydet… + Temizle + Günlük kopyalandı + Günlük kaydedildi diff --git a/android-llmservice/app/src/main/res/values-zh-rCN/strings.xml b/android-llmservice/app/src/main/res/values-zh-rCN/strings.xml index e17300bc..413dced8 100644 --- a/android-llmservice/app/src/main/res/values-zh-rCN/strings.xml +++ b/android-llmservice/app/src/main/res/values-zh-rCN/strings.xml @@ -20,4 +20,26 @@ SPDX-License-Identifier: MIT 对话已保存 对话已加载 没有已保存的对话 + + 设置 + 生成设置 + 温度 + Top-K + Top-P + Min-P + 重复惩罚 + 重复范围 + 最大令牌数 + 恢复默认 + 关闭 + + + 日志 + 日志 + 暂无日志输出。 + 全部复制 + 另存为… + 清除 + 日志已复制 + 日志已保存 diff --git a/android-llmservice/app/src/main/res/values/strings.xml b/android-llmservice/app/src/main/res/values/strings.xml index b13f43fb..0284f988 100644 --- a/android-llmservice/app/src/main/res/values/strings.xml +++ b/android-llmservice/app/src/main/res/values/strings.xml @@ -23,4 +23,27 @@ SPDX-License-Identifier: MIT Chat saved Chat loaded No saved chat + + + Settings + Generation settings + Temperature + Top-K + Top-P + Min-P + Repeat penalty + Repeat range + Max tokens + Reset defaults + Close + + + Log + Log + No log output yet. + Copy all + Save as… + Clear + Log copied + Log saved