bump version to 0.4.1, sanitize API responses, and update string resources

This commit is contained in:
jonasgaudian
2026-02-13 19:01:53 +01:00
parent 99d379071b
commit 73cb3e1855
9 changed files with 79 additions and 35 deletions

View File

@@ -171,22 +171,27 @@ class ApiRequestHandler(private val apiManager: ApiManager, context: Context) {
* Handles:
* 1. Markdown code blocks (```json ... ```)
* 2. Conversational wrapping ("Here is the JSON: ...")
* 3. Raw JSON
* 3. Think blocks (<think>...</think>)
* 4. Raw JSON
*/
private fun extractJsonFromResponse(text: String): String {
val trimmed = text.trim()
// 0. First, strip any <think>...</think> blocks (may contain newlines)
val thinkRegex = Regex("<think>[\\s\\S]*?</think>")
val textWithoutThink = thinkRegex.replace(trimmed, "").trim()
// 1. Try to find content within Markdown code blocks
// This regex looks for ``` (optional json) ... content ... ```
val codeBlockRegex = Regex("```(?:json)?\\s*([\\s\\S]*?)\\s*```", RegexOption.IGNORE_CASE)
val match = codeBlockRegex.find(trimmed)
val match = codeBlockRegex.find(textWithoutThink)
if (match != null) {
return match.groupValues[1].trim()
}
// 2. If no code block is found, try to find the outermost JSON structure (Object or Array)
val firstBrace = trimmed.indexOf('{')
val firstBracket = trimmed.indexOf('[')
val firstBrace = textWithoutThink.indexOf('{')
val firstBracket = textWithoutThink.indexOf('[')
var startIndex = -1
var endIndex = -1
@@ -195,18 +200,18 @@ class ApiRequestHandler(private val apiManager: ApiManager, context: Context) {
// We pick whichever appears first
if (firstBrace != -1 && (firstBracket == -1 || firstBrace < firstBracket)) {
startIndex = firstBrace
endIndex = trimmed.lastIndexOf('}')
endIndex = textWithoutThink.lastIndexOf('}')
} else if (firstBracket != -1) {
startIndex = firstBracket
endIndex = trimmed.lastIndexOf(']')
endIndex = textWithoutThink.lastIndexOf(']')
}
if (startIndex != -1 && endIndex != -1 && endIndex > startIndex) {
return trimmed.substring(startIndex, endIndex + 1)
return textWithoutThink.substring(startIndex, endIndex + 1)
}
// 3. Fallback: return the original text (parsing will likely fail if it's not valid JSON)
return trimmed
// 3. Fallback: return the text without think blocks (parsing will likely fail if it's not valid JSON)
return textWithoutThink
}
/**
@@ -266,4 +271,4 @@ class ApiResponseException(message: String, cause: Throwable? = null) : Exceptio
/**
* Custom exception for API validation errors.
*/
class ApiValidationException(message: String, cause: Throwable? = null) : Exception(message, cause)
class ApiValidationException(message: String, cause: Throwable? = null) : Exception(message, cause)

View File

@@ -174,15 +174,19 @@ object JsonCleanUtil {
private fun isolateJsonBlock(response: String): String {
// Handle specific non-JSON tokens first
// Strip any <think>...</think> blocks (may contain newlines)
val thinkRegex = Regex("<think>[\\s\\S]*?</think>")
val cleanedResponse = thinkRegex.replace(response, "").trim()
// The rest of the function operates on the cleaned response
val markdownRegex = Regex("```json\\s*([\\s\\S]*?)\\s*```")
val markdownMatch = markdownRegex.find(response)
val markdownMatch = markdownRegex.find(cleanedResponse)
if (markdownMatch != null && markdownMatch.groupValues.size > 1) {
return markdownMatch.groupValues[1]
}
val firstBrace = response.indexOf('{')
val firstBracket = response.indexOf('[')
val firstBrace = cleanedResponse.indexOf('{')
val firstBracket = cleanedResponse.indexOf('[')
val startIndex = when {
firstBrace == -1 -> firstBracket
firstBracket == -1 -> firstBrace
@@ -190,12 +194,12 @@ object JsonCleanUtil {
}
if (startIndex == -1) return ""
val lastBrace = response.lastIndexOf('}')
val lastBracket = response.lastIndexOf(']')
val lastBrace = cleanedResponse.lastIndexOf('}')
val lastBracket = cleanedResponse.lastIndexOf(']')
val endIndex = maxOf(lastBrace, lastBracket)
if (endIndex == -1 || startIndex >= endIndex) return ""
return response.substring(startIndex, endIndex + 1)
return cleanedResponse.substring(startIndex, endIndex + 1)
}
/**
@@ -289,4 +293,4 @@ fun formatJsonForDisplay(json: String): String {
} catch (_: Exception) {
json // Fallback to raw JSON if formatting fails
}
}
}

View File

@@ -537,7 +537,7 @@ class DictionaryViewModel @Inject constructor(
showErrorMessage(
getApplication<Application>().getString(
R.string.text_failed_to_fetch_manifest,
e.message
// e.message Let's not include that part here, since it would show the server IP to the user
))
}
}