Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 application/composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ compose.desktop {
}

linux {
packageVersion = "0.38.0"
packageVersion = "0.39.0"
iconFile.set(iconsRoot.resolve("icon-linux.png"))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
<string name="download_models">Download Model</string>
<string name="suggestions">Suggestions:</string>
<string name="error_model_download">Error when downloading model.</string>
<string name="version">"Version: alpha38 - Amado, Jorge"</string>
<string name="version">"Version: alpha39 - Amado, Jorge"</string>
<string name="light_theme">Light</string>
<string name="dark_theme">Dark</string>
<string name="system_theme">System</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
<string name="download_models">Baixar modelos</string>
<string name="suggestions">Sugestões:</string>
<string name="error_model_download">Error ao baixar modelo.</string>
<string name="version">"Versão: alpha38 - Amado, Jorge"</string>
<string name="version">"Versão: alpha39 - Amado, Jorge"</string>
<string name="light_theme">Claro</string>
<string name="dark_theme">Escuro</string>
<string name="system_theme">Sistema</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
<string name="download_models">Download Model</string>
<string name="suggestions">Suggestions:</string>
<string name="error_model_download">Error when downloading model.</string>
<string name="version">Version: alpha38 - Amado, Jorge</string>
<string name="version">Version: alpha39 - Amado, Jorge</string>
<string name="light_theme">Light</string>
<string name="dark_theme">Dark</string>
<string name="system_theme">System</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@ internal fun TextEditor(
groupsBackgroundColor = Color.Transparent,
drawConfig = DrawConfigFactory.getDrawConfig(),
fontFamily = fontFamily,
generateSection = noteEditorViewModel::aiSection,
receiveExternalFile = noteEditorViewModel::receiveExternalFile,
onDocumentLinkClick = onDocumentLinkClick
onDocumentLinkClick = onDocumentLinkClick,
),
storyState = storyState,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import io.writeopia.sdk.model.story.StoryState
import io.writeopia.sdk.models.document.Document
import io.writeopia.sdk.models.files.ExternalFile
import io.writeopia.sdk.models.span.Span
import io.writeopia.sdk.models.story.StoryStep
import io.writeopia.sdk.models.story.StoryTypes
import io.writeopia.sdk.persistence.core.tracker.OnUpdateDocumentTracker
import io.writeopia.sdk.repository.DocumentRepository
Expand All @@ -53,9 +52,7 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -440,61 +437,7 @@ class NoteEditorKmpViewModel(
if (ollamaRepository == null) return

viewModelScope.launch(Dispatchers.Default) {
val text = writeopiaManager.getCurrentText()
val position = writeopiaManager.getNextPosition()

if (text != null && position != null) {
val url = ollamaRepository.getConfiguredOllamaUrl()?.trim()

if (url == null) {
writeopiaManager.changeStoryState(
Action.StoryStateChange(
storyStep = StoryStep(
type = StoryTypes.AI_ANSWER.type,
text = "Ollama is not configured or not running."
),
position = position,
)
)
} else {
val model = ollamaRepository.getOllamaSelectedModel("disconnected_user")
?: return@launch

ollamaRepository.streamReply(model, text, url)
.onStart {
writeopiaManager.addAtPosition(
storyStep = StoryStep(
type = StoryTypes.LOADING.type,
ephemeral = true
),
position = position
)
}
.onCompletion {
writeopiaManager.trackState()
}
.collect { result ->
val text = when (result) {
is ResultData.Complete -> result.data
is ResultData.Error -> "Error. Message: ${result.exception.message}"
is ResultData.Loading,
is ResultData.Idle,
is ResultData.InProgress -> ""
}

writeopiaManager.changeStoryState(
Action.StoryStateChange(
storyStep = StoryStep(
type = StoryTypes.AI_ANSWER.type,
text = text
),
position = position,
),
trackIt = false
)
}
}
}
PromptService.promptBySelection(writeopiaManager, ollamaRepository)
}
}

Expand Down Expand Up @@ -522,6 +465,26 @@ class NoteEditorKmpViewModel(
documentPrompt(ollamaRepository::streamTags)
}

override fun aiSection(position: Int) {
if (ollamaRepository == null) return

val sectionText = writeopiaManager.getStory(position)?.text ?: return

viewModelScope.launch(Dispatchers.Default) {
val prompt =
"""
Create a document section for a document.
The document is:
```
${writeopiaManager.getDocumentText()}
```

Use the language of the text. Do not add titles. Create contect for this section: $sectionText
"""
PromptService.prompt(prompt = prompt, writeopiaManager, ollamaRepository, position + 1)
}
}

override fun addPage() {
viewModelScope.launch(Dispatchers.Default) {
writeopiaManager.addLinkToDocument()
Expand Down Expand Up @@ -576,62 +539,7 @@ class NoteEditorKmpViewModel(
if (ollamaRepository == null) return

viewModelScope.launch(Dispatchers.Default) {
val text = writeopiaManager.getCurrentSelectionText()
?: writeopiaManager.getDocumentText()

val position =
writeopiaManager.positionAfterSelection() ?: writeopiaManager.lastPosition()

val url = ollamaRepository.getConfiguredOllamaUrl()?.trim()

if (url == null) {
writeopiaManager.changeStoryState(
Action.StoryStateChange(
storyStep = StoryStep(
type = StoryTypes.AI_ANSWER.type,
text = "Ollama is not configured or not running."
),
position = position,
)
)
} else {
val model = ollamaRepository.getOllamaSelectedModel("disconnected_user")
?: return@launch

promptFn(model, text, url)
.onStart {
writeopiaManager.addAtPosition(
storyStep = StoryStep(
type = StoryTypes.LOADING.type,
ephemeral = true
),
position = position
)
}
.onCompletion {
writeopiaManager.trackState()
}
.collect { result ->
val text = when (result) {
is ResultData.Complete -> result.data
is ResultData.Error -> "Error. Message: ${result.exception.message}"
is ResultData.Loading,
is ResultData.Idle,
is ResultData.InProgress -> ""
}

writeopiaManager.changeStoryState(
Action.StoryStateChange(
storyStep = StoryStep(
type = StoryTypes.AI_ANSWER.type,
text = text
),
position = position,
),
trackIt = false
)
}
}
PromptService.documentPrompt(promptFn, writeopiaManager, ollamaRepository)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ interface NoteEditorViewModel : BackstackInform, BackstackHandler {

fun aiTags()

fun aiSection(position: Int)

fun addPage()

fun copySelection()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package io.writeopia.editor.features.editor.viewmodel

import io.writeopia.OllamaRepository
import io.writeopia.common.utils.ResultData
import io.writeopia.sdk.model.action.Action
import io.writeopia.sdk.models.story.StoryStep
import io.writeopia.sdk.models.story.StoryTypes
import io.writeopia.ui.manager.WriteopiaStateManager
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart

object PromptService {

suspend fun documentPrompt(
promptFn: (String, String, String) -> Flow<ResultData<String>>,
writeopiaManager: WriteopiaStateManager,
ollamaRepository: OllamaRepository
) {
val text = writeopiaManager.getCurrentSelectionText()
?: writeopiaManager.getDocumentText()

val position =
writeopiaManager.positionAfterSelection() ?: writeopiaManager.lastPosition()

val url = ollamaRepository.getConfiguredOllamaUrl()?.trim()

if (url == null) {
writeopiaManager.changeStoryState(
Action.StoryStateChange(
storyStep = StoryStep(
type = StoryTypes.AI_ANSWER.type,
text = "Ollama is not configured or not running."
),
position = position,
)
)
} else {
val model = ollamaRepository.getOllamaSelectedModel("disconnected_user")
?: return

promptFn(model, text, url).handleStream(writeopiaManager, position)
}
}

suspend fun promptBySelection(
writeopiaManager: WriteopiaStateManager,
ollamaRepository: OllamaRepository
) {
val text = writeopiaManager.getCurrentText()

prompt(text, writeopiaManager, ollamaRepository)
}

suspend fun prompt(
prompt: String?,
writeopiaManager: WriteopiaStateManager,
ollamaRepository: OllamaRepository,
promptPosition: Int? = null
) {
val position = promptPosition ?: writeopiaManager.getNextPosition()

if (prompt != null && position != null) {
val url = ollamaRepository.getConfiguredOllamaUrl()?.trim()

if (url == null) {
writeopiaManager.changeStoryState(
Action.StoryStateChange(
storyStep = StoryStep(
type = StoryTypes.AI_ANSWER.type,
text = "Ollama is not configured or not running."
),
position = position,
)
)
} else {
val model = ollamaRepository.getOllamaSelectedModel("disconnected_user")
?: return

ollamaRepository.streamReply(model, prompt, url)
.handleStream(writeopiaManager, position)
}
}
}

private suspend fun Flow<ResultData<String>>.handleStream(
writeopiaManager: WriteopiaStateManager,
position: Int
) {
this.onStart {
writeopiaManager.addAtPosition(
storyStep = StoryStep(
type = StoryTypes.LOADING.type,
ephemeral = true
),
position = position
)
}
.onCompletion {
writeopiaManager.trackState()
}
.map { result ->
when (result) {
is ResultData.Complete -> result.data
is ResultData.Error -> "Error. Message: ${result.exception.message}"
is ResultData.Loading,
is ResultData.Idle,
is ResultData.InProgress -> ""
}
}
.collect { resultText ->
writeopiaManager.changeStoryState(
Action.StoryStateChange(
storyStep = StoryStep(
type = StoryTypes.AI_ANSWER.type,
text = resultText
),
position = position,
),
trackIt = false
)
}
}
}
Loading