import { parseJsonLoose } from "../../../llm/jsonParse"; import { LlmError } from "../../../llm/types"; import type { SummaryJson } from "../../domain/types"; import { requestStructuredOutput } from "./structuredOutput"; const SENTENCE_SYSTEM = `你是 ChatPapers 论文伴读助手。用户选中论文中的一句原文,需要你解释并用于语音朗读。 必须只输出一个 JSON 对象,不要 markdown 代码块。 格式:{"translation":"...","syntaxNote":"...","contextRole":"..."} 要求: - translation:准确中文翻译,并用 30–80 字口语解释该句含义(不要念 LaTeX,公式转述为口语)。 - syntaxNote:1–2 句,拆解句法结构或关键术语(英文句为主;中文句可简述逻辑)。 - contextRole:1 句,说明该句在段落或全文论证中的作用。 - 术语翻译与全文保持一致。`; export interface SentenceLlmResult { translation: string; syntaxNote: string; contextRole: string; } function parseSentenceJson(raw: string): SentenceLlmResult { const parsed = parseJsonLoose>(raw); const translation = String(parsed.translation || parsed.zh || "").trim(); if (!translation) { throw new LlmError("provider", "单句讲解 JSON 缺少 translation 字段。"); } return { translation, syntaxNote: String(parsed.syntaxNote || parsed.syntax || "").trim(), contextRole: String(parsed.contextRole || parsed.role || parsed.context || "").trim(), }; } function fallbackSentence(selectedText: string): SentenceLlmResult { return { translation: selectedText.slice(0, 200), syntaxNote: "", contextRole: "(模型未能生成上下文说明,请重试或更换模型。)", }; } function formatSummaryBlock(summary?: SummaryJson): string { if (!summary) return "(暂无全文摘要)"; return [ `问题:${summary.problem}`, `方法:${summary.method}`, `结果:${summary.result}`, `局限:${summary.limitation}`, ].join("\n"); } export async function generateSentenceExplanation(options: { paperTitle: string; summary?: SummaryJson; selectedText: string; surroundingContext?: string; }): Promise { const user = `【论文标题】${options.paperTitle} 【全文摘要】 ${formatSummaryBlock(options.summary)} 【选区附近上下文(可能为空)】 ${options.surroundingContext?.trim() || "(无)"} 【用户选中的句子】 ${options.selectedText.trim()} 请输出 JSON。`; return requestStructuredOutput({ system: SENTENCE_SYSTEM, user, parse: parseSentenceJson, jsonMode: true, fallback: () => fallbackSentence(options.selectedText), }); }