首句不翻译修复: 单句批改直出模式(不套JSON)、token预算按原文长度动态化(原160/句截断长句致整批静默丢弃)、编号对容错扫描器(截断JSON也能救条目)、段末对未译句兜底补翻
This commit is contained in:
parent
da45878c54
commit
6264403b9e
|
|
@ -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<String, String>? {
|
||||
// 容错提取 "编号":"译文" 对:顺序扫描,不要求 JSON 完整闭合——
|
||||
// 生成被 maxNewTokens 截断时,已完成的条目仍能救出
|
||||
private fun extractNumberedPairs(out: String?): Map<String, String>? {
|
||||
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<String, String>()
|
||||
o.keys().forEach { k -> m[k] = o.optString(k) }
|
||||
m
|
||||
} catch (t: Throwable) { null }
|
||||
val m = HashMap<String, String>()
|
||||
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<String, String>()
|
||||
items.forEachIndexed { i, (key, _) ->
|
||||
byIdx[(i + 1).toString()]?.let { res[key] = it }
|
||||
|
|
|
|||
Loading…
Reference in New Issue