mmap21-final: loadModel() in AssistantEngine, Modell-Picker, Thinking-Indikator
This commit is contained in:
parent
274e73cb4d
commit
2dbd4f895b
2 changed files with 35 additions and 21 deletions
|
|
@ -22,22 +22,7 @@ import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ODY AssistantEngine — mmap12 / ODY-RUNTIME-STATEFLOW-BRIDGE-FIX-1
|
* ODY AssistantEngine — mmap21
|
||||||
*
|
|
||||||
* Root Cause des bisherigen Bugs:
|
|
||||||
* val runtimeState: StateFlow<RuntimeState>
|
|
||||||
* get() = runtime.state
|
|
||||||
*
|
|
||||||
* collectAsState() in Compose abonniert den Flow beim ersten Compose-Lauf.
|
|
||||||
* Wenn runtime danach intern ausgetauscht wird (SimulationRuntime → LlamaCppRuntime),
|
|
||||||
* weiß Compose nicht, dass es einen anderen Flow sammeln muss.
|
|
||||||
* Ergebnis: UI lauscht auf altem SimulationRuntime.state, Engine nutzt neue Runtime.
|
|
||||||
*
|
|
||||||
* Fix:
|
|
||||||
* - Stabiler engine-owned _runtimeState MutableStateFlow — wird NIEMALS ausgetauscht
|
|
||||||
* - bindRuntime() forwarded newRuntime.state → _runtimeState via runtimeStateJob
|
|
||||||
* - runtimeStateJob wird bei jedem Runtime-Wechsel gecancelt und neu gestartet
|
|
||||||
* - Lifecycle-Cleanup in release(): runtimeStateJob canceln, Runtime freigeben
|
|
||||||
*/
|
*/
|
||||||
class AssistantEngine(private val context: Context) {
|
class AssistantEngine(private val context: Context) {
|
||||||
|
|
||||||
|
|
@ -57,8 +42,10 @@ class AssistantEngine(private val context: Context) {
|
||||||
private val _runtimeState = MutableStateFlow<RuntimeState>(RuntimeState.UNLOADED)
|
private val _runtimeState = MutableStateFlow<RuntimeState>(RuntimeState.UNLOADED)
|
||||||
val runtimeState: StateFlow<RuntimeState> = _runtimeState.asStateFlow()
|
val runtimeState: StateFlow<RuntimeState> = _runtimeState.asStateFlow()
|
||||||
|
|
||||||
|
private val _currentModelId = MutableStateFlow<String?>(null)
|
||||||
|
val currentModelId: StateFlow<String?> = _currentModelId.asStateFlow()
|
||||||
|
|
||||||
// Job der newRuntime.state → _runtimeState forwarded
|
// Job der newRuntime.state → _runtimeState forwarded
|
||||||
// Wird bei jedem Runtime-Wechsel gecancelt und neu gestartet
|
|
||||||
private var runtimeStateJob: Job? = null
|
private var runtimeStateJob: Job? = null
|
||||||
|
|
||||||
val isSimulation: Boolean
|
val isSimulation: Boolean
|
||||||
|
|
@ -95,6 +82,7 @@ class AssistantEngine(private val context: Context) {
|
||||||
contextLength = 4096
|
contextLength = 4096
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
_currentModelId.value = modelFile.nameWithoutExtension
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.i(TAG, "start: Kein Modell → SimulationRuntime aktiv")
|
Log.i(TAG, "start: Kein Modell → SimulationRuntime aktiv")
|
||||||
|
|
@ -107,6 +95,34 @@ class AssistantEngine(private val context: Context) {
|
||||||
scope.launch { runtime.unload() }
|
scope.launch { runtime.unload() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wechselt das aktive Modell zur Laufzeit.
|
||||||
|
* Entlädt die aktuelle Runtime, bindet eine neue LlamaCppRuntime und lädt das neue Modell.
|
||||||
|
*/
|
||||||
|
fun loadModel(modelFile: File) {
|
||||||
|
scope.launch {
|
||||||
|
Log.i(TAG, "loadModel: ${modelFile.name}")
|
||||||
|
try {
|
||||||
|
_currentModelId.value = null
|
||||||
|
runtime.unload()
|
||||||
|
val llamaRuntime = LlamaCppRuntime(context)
|
||||||
|
bindRuntime(llamaRuntime)
|
||||||
|
llamaRuntime.loadModel(
|
||||||
|
modelPath = modelFile.absolutePath,
|
||||||
|
config = ModelConfig(
|
||||||
|
modelPath = modelFile.absolutePath,
|
||||||
|
modelId = modelFile.nameWithoutExtension,
|
||||||
|
contextLength = 4096
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_currentModelId.value = modelFile.nameWithoutExtension
|
||||||
|
Log.i(TAG, "loadModel: ${modelFile.name} erfolgreich geladen")
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.e(TAG, "loadModel: Fehler beim Laden von ${modelFile.name}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun generate(
|
suspend fun generate(
|
||||||
request: GenerationRequest,
|
request: GenerationRequest,
|
||||||
onToken: (String) -> Unit
|
onToken: (String) -> Unit
|
||||||
|
|
@ -118,9 +134,7 @@ class AssistantEngine(private val context: Context) {
|
||||||
fun cancel() = runtime.cancel()
|
fun cancel() = runtime.cancel()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vollständiges Lifecycle-Cleanup:
|
* Vollständiges Lifecycle-Cleanup.
|
||||||
* - runtimeStateJob canceln (kein weiteres Forwarding)
|
|
||||||
* - Runtime via unload() freigeben
|
|
||||||
*/
|
*/
|
||||||
fun release() {
|
fun release() {
|
||||||
runtimeStateJob?.cancel()
|
runtimeStateJob?.cancel()
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ class ModelManager(private val context: Context) {
|
||||||
fileName: String,
|
fileName: String,
|
||||||
onProgress: (Long, Long) -> Unit = { _, _ -> },
|
onProgress: (Long, Long) -> Unit = { _, _ -> },
|
||||||
): File? {
|
): File? {
|
||||||
val safeName = fileName.replace(Regex("[^a-zA-Z0-9._\-]"), "_")
|
val safeName = fileName.replace(Regex("[^a-zA-Z0-9._ -]"), "_")
|
||||||
.let { if (it.endsWith(".gguf")) it else "$it.gguf" }
|
.let { if (it.endsWith(".gguf")) it else "$it.gguf" }
|
||||||
val partFile = File(modelsDir(), "$safeName.part")
|
val partFile = File(modelsDir(), "$safeName.part")
|
||||||
val finalFile = File(modelsDir(), safeName)
|
val finalFile = File(modelsDir(), safeName)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue