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(""))
+ }
+}