From 6264403b9e617a653d342cabc8ba9921d39a47e3 Mon Sep 17 00:00:00 2001 From: liwenhao2 Date: Tue, 15 Sep 2026 10:09:12 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E5=8F=A5=E4=B8=8D=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20=E5=8D=95=E5=8F=A5=E6=89=B9=E6=94=B9?= =?UTF-8?q?=E7=9B=B4=E5=87=BA=E6=A8=A1=E5=BC=8F(=E4=B8=8D=E5=A5=97JSON)?= =?UTF-8?q?=E3=80=81token=E9=A2=84=E7=AE=97=E6=8C=89=E5=8E=9F=E6=96=87?= =?UTF-8?q?=E9=95=BF=E5=BA=A6=E5=8A=A8=E6=80=81=E5=8C=96(=E5=8E=9F160/?= =?UTF-8?q?=E5=8F=A5=E6=88=AA=E6=96=AD=E9=95=BF=E5=8F=A5=E8=87=B4=E6=95=B4?= =?UTF-8?q?=E6=89=B9=E9=9D=99=E9=BB=98=E4=B8=A2=E5=BC=83)=E3=80=81?= =?UTF-8?q?=E7=BC=96=E5=8F=B7=E5=AF=B9=E5=AE=B9=E9=94=99=E6=89=AB=E6=8F=8F?= =?UTF-8?q?=E5=99=A8(=E6=88=AA=E6=96=ADJSON=E4=B9=9F=E8=83=BD=E6=95=91?= =?UTF-8?q?=E6=9D=A1=E7=9B=AE)=E3=80=81=E6=AE=B5=E6=9C=AB=E5=AF=B9?= =?UTF-8?q?=E6=9C=AA=E8=AF=91=E5=8F=A5=E5=85=9C=E5=BA=95=E8=A1=A5=E7=BF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/k2fsa/sherpa/onnx/MainActivity.kt | 76 +++++++++++++++---- 1 file changed, 61 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/k2fsa/sherpa/onnx/MainActivity.kt b/app/src/main/java/com/k2fsa/sherpa/onnx/MainActivity.kt index 7541d08..09dd300 100644 --- a/app/src/main/java/com/k2fsa/sherpa/onnx/MainActivity.kt +++ b/app/src/main/java/com/k2fsa/sherpa/onnx/MainActivity.kt @@ -1919,8 +1919,12 @@ class MainActivity : AppCompatActivity() { transSegTimer?.let { uiHandler.removeCallbacks(it) } transSegTimer = null flushInitialBatch() - val refs = seg.sentences.filter { it.text.isNotBlank() } - if (translateDisabled || refs.size < 2) return + val all = seg.sentences.filter { it.text.isNotBlank() } + if (translateDisabled) return + // 单句段:初译成功就不用重翻;失败/缺失则兜底补这一句 + val refs = if (all.size < 2) + all.filter { it.trans.isNullOrBlank() } else all + if (refs.isEmpty()) return if (SummarizePipeline.detectLanguage(refs.joinToString("")) == uiLanguage() ) return @@ -1950,16 +1954,38 @@ class MainActivity : AppCompatActivity() { } } - private fun parseNumberedJson(out: String?): Map? { + // 容错提取 "编号":"译文" 对:顺序扫描,不要求 JSON 完整闭合—— + // 生成被 maxNewTokens 截断时,已完成的条目仍能救出 + private fun extractNumberedPairs(out: String?): Map? { if (out.isNullOrBlank()) return null - val a = out.indexOf('{'); val b = out.lastIndexOf('}') - if (a < 0 || b <= a) return null - return try { - val o = org.json.JSONObject(out.substring(a, b + 1)) - val m = HashMap() - o.keys().forEach { k -> m[k] = o.optString(k) } - m - } catch (t: Throwable) { null } + val m = HashMap() + var i = 0 + while (i < out.length) { + if (out[i] != '"') { i++; continue } + val keyEnd = out.indexOf(':', i + 1) + if (keyEnd < 0) break + val key = out.substring(i + 1, keyEnd).trim() + if (key.isEmpty() || key.any { c -> !c.isDigit() }) { i++; continue } + var j = keyEnd + 1 + while (j < out.length && out[j] != '"') j++ + if (j >= out.length) break + val sb = StringBuilder() + j++ + while (j < out.length) { + val c = out[j] + if (c == '\\' && j + 1 < out.length) { + val n = out[j + 1] + sb.append(if (n == 'n') '\n' else if (n == 'r') '\r' else n) + j += 2 + continue + } + if (c == '"') break + sb.append(c); j++ + } + m[key] = sb.toString() + i = j + 1 + } + return if (m.isEmpty()) null else m } private fun ensureTranslateEngine(): LlmEngine? { @@ -2021,12 +2047,32 @@ class MainActivity : AppCompatActivity() { val targetName = when (uiLanguage()) { "ja" -> "日本語"; "en" -> "English"; else -> "简体中文" } - val numbered = items.mapIndexed { i, (_, t) -> "${i + 1}. $t" } - .joinToString("\n") translateEngine?.let { runCatching { it.release() } } translateEngine = null val engine = ensureTranslateEngine() ?: run { translateDisabled = true; return null } + // 动态预算:按原文总长给输出额度(译文常比原文长,原 160/句 会把 + // 长句截断成残缺 JSON,整批被静默丢弃——"第一句不翻"的直接死因) + val budget = (items.sumOf { it.second.length } * 2 + 96) + .coerceAtMost(1200) + // 单句 = 直出模式:不套 JSON 包装,无格式可坏,截断也仍是有效译文 + if (items.size == 1) { + val one = try { + engine.generate( + systemPrompt = "你是专业翻译。把给出的句子翻译成" + targetName + + "。只输出译文本身,不要解释、不要引号、不要抄写原文。", + userPrompt = items[0].second, + maxNewTokens = budget, temperature = 0f, + ) + } catch (t: Throwable) { + Log.w(TAG, "TRANSLATE single failed: ${t.message}"); null + } + val tr = one?.trim()?.lines()?.firstOrNull { it.isNotBlank() }?.trim() + return if (tr.isNullOrEmpty()) null + else mapOf(items[0].first to tr) + } + val numbered = items.mapIndexed { i, (_, t) -> "${i + 1}. $t" } + .joinToString("\n") val out = try { engine.generate( systemPrompt = "你是专业翻译。逐句翻译下面编号句子为" + targetName + @@ -2036,13 +2082,13 @@ class MainActivity : AppCompatActivity() { "严禁把原句原样作为译文输出;确实无法翻译的句子," + "值输出空字符串。", userPrompt = "【句子】\n$numbered", - maxNewTokens = (160 * items.size).coerceAtMost(900), + maxNewTokens = budget, temperature = 0f, ) } catch (t: Throwable) { Log.w(TAG, "TRANSLATE batch failed: ${t.message}"); null } - val byIdx = parseNumberedJson(out) ?: return null + val byIdx = extractNumberedPairs(out) ?: return null val res = HashMap() items.forEachIndexed { i, (key, _) -> byIdx[(i + 1).toString()]?.let { res[key] = it }