diff --git a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.kt b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.kt index 921d142f6..a4efb2d35 100644 --- a/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.kt +++ b/app/src/main/java/helium314/keyboard/latin/inputlogic/InputLogic.kt @@ -62,6 +62,7 @@ import helium314.keyboard.latin.utils.RecapitalizeMode import helium314.keyboard.latin.utils.RecapitalizeStatus import helium314.keyboard.latin.utils.ScriptUtils import helium314.keyboard.latin.utils.StatsUtils +import helium314.keyboard.latin.utils.CustomAIKeyText import helium314.keyboard.latin.utils.TextExpanderUtils import helium314.keyboard.latin.utils.TextPlacement import helium314.keyboard.latin.utils.TextRange @@ -2935,7 +2936,7 @@ class InputLogic( return } - prompt += systemInstruction + prompt = CustomAIKeyText.withInstructions(prompt, systemInstruction) var textToProcess: String val hasSelection = mConnection.hasSelection() @@ -3000,7 +3001,10 @@ class InputLogic( Log.w(TAG, "Target editor changed during custom AI operation, ignoring result") return@customAsync } - if (!hasSelection && !shouldAppend) { + if (shouldAppend) { + val textBefore = mConnection.getTextBeforeCursor(2, 0) ?: "" + mLatinIME.onTextInput(CustomAIKeyText.separatorBefore(textBefore) + resultText.trim()) + } else if (!hasSelection) { mConnection.replaceEntireText(resultText) } else { mLatinIME.onTextInput(resultText) diff --git a/app/src/main/java/helium314/keyboard/latin/utils/CustomAIKeyText.kt b/app/src/main/java/helium314/keyboard/latin/utils/CustomAIKeyText.kt new file mode 100644 index 000000000..8e4ff8889 --- /dev/null +++ b/app/src/main/java/helium314/keyboard/latin/utils/CustomAIKeyText.kt @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0-only +package helium314.keyboard.latin.utils + +/** Text handling of the custom AI keys that does not need an input connection */ +object CustomAIKeyText { + /** + * Instructions of the selected keywords go before the user's prompt, so a prompt that ends + * with an answer start (e.g. "Answer:") or embeds the text with `{text}` keeps its ending. + */ + fun withInstructions(prompt: String, instructions: String): String { + val trimmed = instructions.trim() + return if (trimmed.isEmpty()) prompt else "$trimmed\n\n$prompt" + } + + /** Line breaks to put before a result appended after [textBefore], so it starts in a new paragraph */ + fun separatorBefore(textBefore: CharSequence): String = when { + textBefore.isBlank() -> "" + textBefore.endsWith("\n\n") -> "" + textBefore.endsWith("\n") -> "\n" + else -> "\n\n" + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a94615445..a3b33ad96 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2173,7 +2173,7 @@ New dictionary: Changes the tone (e.g., professional, casual). Generates new text completely ignoring selected text. Returns only the modified text, without conversational fillers. - Appends the result to the end of the original text. + Appends the result to the end of the original text, separated by an empty line. Includes AI\'s reasoning process along with result. diff --git a/app/src/test/java/helium314/keyboard/latin/utils/CustomAIKeyTextTest.kt b/app/src/test/java/helium314/keyboard/latin/utils/CustomAIKeyTextTest.kt new file mode 100644 index 000000000..3a462b61f --- /dev/null +++ b/app/src/test/java/helium314/keyboard/latin/utils/CustomAIKeyTextTest.kt @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-3.0-only +package helium314.keyboard.latin.utils + +import org.junit.Assert.assertEquals +import org.junit.Test + +class CustomAIKeyTextTest { + + @Test + fun instructionsGoBeforeThePrompt() { + val prompt = "Message: {text}\nAnswer:" + assertEquals("Output ONLY the result.\n\n$prompt", CustomAIKeyText.withInstructions(prompt, " Output ONLY the result.")) + } + + @Test + fun noInstructionsKeepThePrompt() { + assertEquals("Summarize.", CustomAIKeyText.withInstructions("Summarize.", "")) + } + + @Test + fun appendedResultStartsANewParagraph() { + assertEquals("\n\n", CustomAIKeyText.separatorBefore("Best regards, Sam")) + assertEquals("\n", CustomAIKeyText.separatorBefore("Best regards, Sam\n")) + assertEquals("", CustomAIKeyText.separatorBefore("Best regards, Sam\n\n")) + assertEquals("", CustomAIKeyText.separatorBefore("")) + } +}