@@ -386,28 +386,56 @@ class ChatBotViewModel(
386386 if (existingJob?.isActive == true ) return
387387
388388 val job = screenModelScope.launch(Dispatchers .IO ) {
389- updateDownload(url) { it.copy(inProgress = true , progress = 0 , done = false , error = null ) }
389+ updateDownload(url) {
390+ it.copy(
391+ inProgress = true ,
392+ progress = 0 ,
393+ done = false ,
394+ error = null
395+ )
396+ }
390397
391398 modelDownloadOrchestrator.download(model).collect { ev ->
392399 when (ev) {
393400 is DownloadEvent .Progress -> {
394401 updateDownload(url) { it.copy(inProgress = true , progress = ev.percent) }
395402 }
403+
396404 is DownloadEvent .Completed -> {
397- updateDownload(url) { it.copy(inProgress = false , progress = 100 , done = true , error = null ) }
405+ updateDownload(url) {
406+ it.copy(
407+ inProgress = false ,
408+ progress = 100 ,
409+ done = true ,
410+ error = null
411+ )
412+ }
398413 getModelsUseCase.saveModelPath(model.name, ev.localPath)
399414
400415 _state .value = _state .value.copy(
401416 embedModels = _state .value.embedModels.map {
402- if (it.url == url) it.copy(fileName = ev.localPath, localPath = ev.localPath) else it
417+ if (it.url == url) it.copy(
418+ fileName = ev.localPath,
419+ localPath = ev.localPath
420+ ) else it
403421 },
404422 generateModels = _state .value.generateModels.map {
405- if (it.url == url) it.copy(fileName = ev.localPath, localPath = ev.localPath) else it
423+ if (it.url == url) it.copy(
424+ fileName = ev.localPath,
425+ localPath = ev.localPath
426+ ) else it
406427 },
407428 )
408429 }
430+
409431 is DownloadEvent .Failed -> {
410- updateDownload(url) { it.copy(inProgress = false , done = false , error = ev.message) }
432+ updateDownload(url) {
433+ it.copy(
434+ inProgress = false ,
435+ done = false ,
436+ error = ev.message
437+ )
438+ }
411439 }
412440 }
413441 }
@@ -626,69 +654,76 @@ class ChatBotViewModel(
626654 val collapsed = tail.replace(" \\ s+" .toRegex(), " " ).trim()
627655 val commas = collapsed.count { it == ' ,' }
628656 if (commas > 60 ) return true
629- val m = Regex (""" \b([A-Za-z0-9]{1,3})\b(?:[,\s]+\1\b){25,}""" ).find(collapsed)
657+ val m =
658+ Regex (""" \b([A-Za-z0-9]{1,3})\b(?:[,\s]+\1\b){25,}""" ).find(collapsed)
630659 return m != null
631660 }
632661
633662 val generateSettings = _state .value.generateSettings
634663
635- ChatRunner .stream(
636- system = currentSystemPrompt(),
637- contexts = emptyList(),
638- messages = chatHistory,
639- template = currentGenerateTemplate(),
640- maxTokens = generateSettings.maxTokens,
641- onDelta = { chunk ->
642- if (activeRequestId != requestId || completed) return @stream
643- if (chunk.isEmpty()) return @stream
644-
645- acc.append(chunk)
646- _conversation .value = _conversation .value.dropLast(1 ) +
647- ChatUiModel .Message (acc.toString(), ChatUiModel .Author .bot)
648- _sideEffects .trySend(ChatBotSideEffects .ScrollToBottom )
649-
650- if (looksLikeEchoOrLoop(full = acc.toString(), user = input)) {
651- val trimmed = trimLoop(acc.toString(), user = input)
664+ try {
665+ ChatRunner .stream(
666+ system = currentSystemPrompt(),
667+ contexts = emptyList(),
668+ messages = chatHistory,
669+ template = currentGenerateTemplate(),
670+ maxTokens = generateSettings.maxTokens,
671+ onDelta = { chunk ->
672+ if (activeRequestId != requestId || completed) return @stream
673+ if (chunk.isEmpty()) return @stream
674+
675+ acc.append(chunk)
652676 _conversation .value = _conversation .value.dropLast(1 ) +
653- ChatUiModel .Message (trimmed, ChatUiModel .Author .bot)
654- completed = true
655- activeRequestId = null
656- _state .value = _state .value.copy(isGenerating = false )
657- _sideEffects .trySend(ChatBotSideEffects .OnMessageLoaded )
658- return @stream
659- }
677+ ChatUiModel .Message (acc.toString(), ChatUiModel .Author .bot)
678+ _sideEffects .trySend(ChatBotSideEffects .ScrollToBottom )
660679
661- if (looksLikeBabble(acc.toString())) {
680+ if (looksLikeEchoOrLoop(full = acc.toString(), user = input)) {
681+ val trimmed = trimLoop(acc.toString(), user = input)
682+ _conversation .value = _conversation .value.dropLast(1 ) +
683+ ChatUiModel .Message (trimmed, ChatUiModel .Author .bot)
684+ completed = true
685+ activeRequestId = null
686+ _state .value = _state .value.copy(isGenerating = false )
687+ _sideEffects .trySend(ChatBotSideEffects .OnMessageLoaded )
688+ return @stream
689+ }
690+
691+ if (looksLikeBabble(acc.toString())) {
692+ completed = true
693+ activeRequestId = null
694+ val cleaned = acc.toString().trim().trimEnd(' ,' , ' ' , ' \n ' )
695+ _conversation .value = _conversation .value.dropLast(1 ) +
696+ ChatUiModel .Message (cleaned, ChatUiModel .Author .bot)
697+ _state .value = _state .value.copy(isGenerating = false )
698+ _sideEffects .trySend(ChatBotSideEffects .OnMessageLoaded )
699+ }
700+ },
701+ onComplete = { final ->
702+ if (activeRequestId != requestId || completed) return @stream
662703 completed = true
663704 activeRequestId = null
664- val cleaned = acc.toString().trim().trimEnd(' ,' , ' ' , ' \n ' )
665705 _conversation .value = _conversation .value.dropLast(1 ) +
666- ChatUiModel .Message (cleaned , ChatUiModel .Author .bot)
706+ ChatUiModel .Message (final , ChatUiModel .Author .bot)
667707 _state .value = _state .value.copy(isGenerating = false )
668708 _sideEffects .trySend(ChatBotSideEffects .OnMessageLoaded )
709+ },
710+ onError = { err ->
711+ if (activeRequestId != requestId) return @stream
712+ _conversation .value = _conversation .value.dropLast(1 ) +
713+ ChatUiModel .Message (
714+ " There is a problem with the AI: $err " ,
715+ ChatUiModel .Author .bot
716+ )
717+ activeRequestId = null
718+ _state .value = _state .value.copy(isGenerating = false )
719+ _sideEffects .trySend(ChatBotSideEffects .OnLoadError )
669720 }
670- },
671- onComplete = { final ->
672- if (activeRequestId != requestId || completed) return @stream
673- completed = true
674- activeRequestId = null
675- _conversation .value = _conversation .value.dropLast(1 ) +
676- ChatUiModel .Message (final, ChatUiModel .Author .bot)
677- _state .value = _state .value.copy(isGenerating = false )
678- _sideEffects .trySend(ChatBotSideEffects .OnMessageLoaded )
679- },
680- onError = { err ->
681- if (activeRequestId != requestId) return @stream
682- _conversation .value = _conversation .value.dropLast(1 ) +
683- ChatUiModel .Message (
684- " There is a problem with the AI: $err " ,
685- ChatUiModel .Author .bot
686- )
687- activeRequestId = null
721+ )
722+ } finally {
723+ if (activeRequestId == null ) { // stopped or finished
688724 _state .value = _state .value.copy(isGenerating = false )
689- _sideEffects .trySend(ChatBotSideEffects .OnLoadError )
690725 }
691- )
726+ }
692727 } catch (t: Throwable ) {
693728 emitBot(" There is a problem with the AI: ${t.message ? : " Unknown error" } " )
694729 activeRequestId = null
@@ -705,6 +740,15 @@ class ChatBotViewModel(
705740 LlamaBridge .nativeCancelGenerate()
706741 activeRequestId = null
707742 _state .value = _state .value.copy(isGenerating = false )
743+ val messages = _conversation .value
744+ if (messages.isNotEmpty()) {
745+ val last = messages.last()
746+ if (last.author == ChatUiModel .Author .bot && last.text.isBlank()) {
747+ _conversation .value = messages.dropLast(1 )
748+ }
749+ }
750+ _sideEffects .trySend(ChatBotSideEffects .OnMessageLoaded )
751+ _sideEffects .trySend(ChatBotSideEffects .ScrollToBottom )
708752 }
709753
710754 private fun emitBot (text : String ) {
0 commit comments