chinese-summary 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/chinese-summary.ts"],"sourcesContent":["/**\r\n * chinese-summary — 中文文本概要提取库\r\n *\r\n * TextRank + 位置加权 + TF-IDF 关键词加权 + MMR 多样性选句 + 多级压缩\r\n * 纯机器算法,无 AI 依赖,零外部依赖。\r\n * 使用字级 n-gram 绕过中文分词,位置先验提升首段/首句权重。\r\n *\r\n * Copyright (c) 2025 北京锋通科技有限公司\r\n * Authors: 郭玉峰, 吴琼\r\n * SPDX-License-Identifier: MIT\r\n *\r\n * 压缩级别(compressionLevel):\r\n * 5 — 不压缩,返回全部句子\r\n * 4 — 轻度压缩,返回约 50% 句子\r\n * 3 — 中度压缩,返回约 30% 句子(默认,等价于 sentenceCount 模式)\r\n * 2 — 高度压缩,返回约 20% 句子 + 多轮重排\r\n * 1 — 极致压缩,子句级提取,压缩为一句话\r\n *\r\n * ─── 模块结构(升级指引) ───\r\n *\r\n * 本文件按流水线顺序组织,各段落的职责和修改指引如下:\r\n *\r\n * 类型定义 — 公共接口,修改 API 时从这里开始\r\n * 默认配置 — 调参入口,新增选项需同步修改 SummaryOptions + DEFAULT_OPTIONS + sanitizeOptions\r\n * 工具函数 — clampInt/Float/safeNumber,一般不需要修改\r\n * 1. 句子分割 — 分句规则,如需支持更多标点或语言,修改此段\r\n * 2. n-gram 提取 — 字级滑动窗口,如需词级 n-gram,替换此段\r\n * 2b. TF-IDF — 关键词提取,如需换用其他关键词算法,替换此段\r\n * 3. 相似度计算 — n-gram 交集 / log 归一化,如需换相似度公式,修改此段\r\n * 4. 位置权重 — 首段/首句/段尾先验,如需新增位置规则,修改此段\r\n * 5. TextRank — 核心迭代算法,如需换用 LexRank/LSA 等,替换此段\r\n * 6. 压缩映射 — 级别→句子数,如需调整各级压缩比例,修改此段\r\n * 6b. MMR 选句 — 多样性策略,如需换用其他去冗余算法,替换此段\r\n * 7. 子句分割 — 极致压缩专用,如需调整子句切分规则,修改此段\r\n * 8. 子句 TextRank — 极致压缩专用,一般不需要修改\r\n * 9. 多轮重排 — 高度压缩专用,如需调整重排策略,修改此段\r\n * 10. 极致压缩 — 子句提取+连词处理,如需调整拼接/连词逻辑,修改此段\r\n * 11. 主接口 — extractSummary / rankSentences,新增压缩级别时修改此段\r\n */\r\n\r\n// ============================================================\r\n// 类型定义\r\n// ============================================================\r\n\r\n/** 压缩级别 1-5 */\r\nexport type CompressionLevel = 1 | 2 | 3 | 4 | 5;\r\n\r\n/** 概要提取选项 */\r\nexport interface SummaryOptions {\r\n /** 摘要句子数量,默认 3(与 compressionLevel 互斥,优先使用 compressionLevel) */\r\n sentenceCount?: number;\r\n /**\r\n * 压缩级别 1-5,默认 3\r\n *\r\n * 1 — 极致压缩:子句级提取,压缩为一句话\r\n * 2 — 高度压缩:约 20% 句子 + 多轮重排\r\n * 3 — 中度压缩:约 30% 句子(默认,等价于 sentenceCount=3)\r\n * 4 — 轻度压缩:约 50% 句子\r\n * 5 — 不压缩:返回全部句子\r\n *\r\n * 当同时指定 sentenceCount 和 compressionLevel 时,compressionLevel 优先\r\n */\r\n compressionLevel?: CompressionLevel;\r\n /** n-gram 大小,默认 2(bigram) */\r\n ngramSize?: number;\r\n /** TextRank 阻尼系数,默认 0.85 */\r\n dampingFactor?: number;\r\n /** 最大迭代次数,默认 30 */\r\n maxIterations?: number;\r\n /** 收敛阈值,默认 0.0001 */\r\n convergenceThreshold?: number;\r\n /** 位置权重:首段首句,默认 1.5 */\r\n weightFirstSentence?: number;\r\n /** 位置权重:首段其他句,默认 1.2 */\r\n weightFirstParagraph?: number;\r\n /** 位置权重:段落首句,默认 1.1 */\r\n weightParagraphStart?: number;\r\n /** 位置权重:段落末句,默认 1.05 */\r\n weightParagraphEnd?: number;\r\n /** 最小句子长度(字符数),默认 5 */\r\n minSentenceLength?: number;\r\n /** 最小子句长度(字符数),默认 3(仅 compressionLevel=1 时使用) */\r\n minClauseLength?: number;\r\n /** 极致压缩时最大子句数,默认 3(仅 compressionLevel=1 时使用) */\r\n maxClauses?: number;\r\n /**\r\n * MMR 多样性系数 λ,默认 0.7(仅 compressionLevel 2-4 时生效)\r\n *\r\n * λ 越大越偏向相关性(TextRank 得分),越小越偏向多样性\r\n * 取值范围 [0.3, 1.0],设为 1.0 则退化为纯得分排序(关闭 MMR)\r\n */\r\n mmrLambda?: number;\r\n /**\r\n * 关键词权重系数,默认 1.2(0 表示关闭关键词加权)\r\n *\r\n * 包含全文关键词的句子,先验权重乘以此系数\r\n */\r\n keywordWeight?: number;\r\n}\r\n\r\n/** 句子信息 */\r\nexport interface SentenceInfo {\r\n /** 在全文中的序号 */\r\n index: number;\r\n /** 句子文本 */\r\n text: string;\r\n /** 所在段落序号 */\r\n paragraphIndex: number;\r\n /** 段落内句子序号 */\r\n sentenceInParagraph: number;\r\n /** 是否为段落首句 */\r\n isParagraphStart: boolean;\r\n /** 是否为段落末句 */\r\n isParagraphEnd: boolean;\r\n /** 是否为首段 */\r\n isFirstParagraph: boolean;\r\n /** TextRank 得分 */\r\n score: number;\r\n}\r\n\r\n/** 子句信息(compressionLevel=1 时使用) */\r\nexport interface ClauseInfo {\r\n /** 子句文本 */\r\n text: string;\r\n /** 来源句子序号 */\r\n sourceSentenceIndex: number;\r\n /** 在来源句子中的序号 */\r\n clauseIndex: number;\r\n /** 是否为句子的主干子句(第一个子句) */\r\n isMainClause: boolean;\r\n /** TextRank 得分 */\r\n score: number;\r\n}\r\n\r\n/** 概要提取结果 */\r\nexport interface SummaryResult {\r\n /** 摘要句子(按原文顺序排列) */\r\n summary: string[];\r\n /** 所有句子及其得分 */\r\n sentences: SentenceInfo[];\r\n /** 摘要文本(句子之间用空格连接) */\r\n text: string;\r\n /** 使用的压缩级别 */\r\n compressionLevel: CompressionLevel;\r\n /** 子句信息(仅 compressionLevel=1 时有值) */\r\n clauses?: ClauseInfo[];\r\n}\r\n\r\n// ============================================================\r\n// 默认配置\r\n// ============================================================\r\n\r\nconst DEFAULT_OPTIONS: Required<SummaryOptions> = {\r\n sentenceCount: 3,\r\n compressionLevel: 3,\r\n ngramSize: 2,\r\n dampingFactor: 0.85,\r\n maxIterations: 30,\r\n convergenceThreshold: 0.0001,\r\n weightFirstSentence: 1.5,\r\n weightFirstParagraph: 1.2,\r\n weightParagraphStart: 1.1,\r\n weightParagraphEnd: 1.05,\r\n minSentenceLength: 5,\r\n minClauseLength: 3,\r\n maxClauses: 3,\r\n mmrLambda: 0.7,\r\n keywordWeight: 1.2,\r\n};\r\n\r\n// ============================================================\r\n// 工具函数\r\n// ============================================================\r\n\r\n/** 安全整数:将值限制在 [min, max] 范围内 */\r\nfunction clampInt(value: unknown, min: number, max: number, fallback: number): number {\r\n if (typeof value !== \"number\" || !Number.isFinite(value)) return fallback;\r\n return Math.max(min, Math.min(max, Math.floor(value)));\r\n}\r\n\r\n/** 安全浮点数:将值限制在 [min, max] 范围内 */\r\nfunction clampFloat(value: unknown, min: number, max: number, fallback: number): number {\r\n if (typeof value !== \"number\" || !Number.isFinite(value)) return fallback;\r\n return Math.max(min, Math.min(max, value));\r\n}\r\n\r\n/** 安全压缩级别 */\r\nfunction sanitizeCompressionLevel(value: unknown): CompressionLevel {\r\n if (typeof value === \"number\" && Number.isFinite(value)) {\r\n const clamped = Math.max(1, Math.min(5, Math.floor(value)));\r\n return clamped as CompressionLevel;\r\n }\r\n return 3;\r\n}\r\n\r\n/** 标准化选项参数,确保所有值都在合法范围内 */\r\nfunction sanitizeOptions(options: SummaryOptions): Required<SummaryOptions> {\r\n return {\r\n sentenceCount: clampInt(options.sentenceCount, 1, 100, DEFAULT_OPTIONS.sentenceCount),\r\n compressionLevel: sanitizeCompressionLevel(options.compressionLevel),\r\n ngramSize: clampInt(options.ngramSize, 1, 5, DEFAULT_OPTIONS.ngramSize),\r\n dampingFactor: clampFloat(options.dampingFactor, 0.1, 0.95, DEFAULT_OPTIONS.dampingFactor),\r\n maxIterations: clampInt(options.maxIterations, 1, 200, DEFAULT_OPTIONS.maxIterations),\r\n convergenceThreshold: clampFloat(options.convergenceThreshold, 1e-8, 1, DEFAULT_OPTIONS.convergenceThreshold),\r\n weightFirstSentence: clampFloat(options.weightFirstSentence, 0.5, 5, DEFAULT_OPTIONS.weightFirstSentence),\r\n weightFirstParagraph: clampFloat(options.weightFirstParagraph, 0.5, 5, DEFAULT_OPTIONS.weightFirstParagraph),\r\n weightParagraphStart: clampFloat(options.weightParagraphStart, 0.5, 5, DEFAULT_OPTIONS.weightParagraphStart),\r\n weightParagraphEnd: clampFloat(options.weightParagraphEnd, 0.5, 5, DEFAULT_OPTIONS.weightParagraphEnd),\r\n minSentenceLength: clampInt(options.minSentenceLength, 1, 100, DEFAULT_OPTIONS.minSentenceLength),\r\n minClauseLength: clampInt(options.minClauseLength, 1, 50, DEFAULT_OPTIONS.minClauseLength),\r\n maxClauses: clampInt(options.maxClauses, 1, 20, DEFAULT_OPTIONS.maxClauses),\r\n mmrLambda: clampFloat(options.mmrLambda, 0.3, 1.0, DEFAULT_OPTIONS.mmrLambda),\r\n keywordWeight: clampFloat(options.keywordWeight, 0, 5, DEFAULT_OPTIONS.keywordWeight),\r\n };\r\n}\r\n\r\n/** 安全数值:确保不为 NaN 或 Infinity */\r\nfunction safeNumber(value: number, fallback: number = 0): number {\r\n return Number.isFinite(value) ? value : fallback;\r\n}\r\n\r\n// ============================================================\r\n// 1. 句子分割\r\n// ============================================================\r\n\r\n/** 中文句子结束标点 */\r\nconst SENTENCE_ENDINGS = /[。!?;\\n\\r]/;\r\n/** 中文引号/括号(可能跟在句末标点后) */\r\nconst TRAILING_QUOTES = /[」』\"\"”)】》'']/;\r\n\r\n/**\r\n * 将中文文本分割为句子数组\r\n *\r\n * 规则:\r\n * - 遇到 。!?;\\n 视为句子结束\r\n * - 句末标点后的引号/括号归入前一句\r\n * - 连续换行视为段落分隔\r\n */\r\nfunction splitSentences(text: string, minLength: number): SentenceInfo[] {\r\n const sentences: SentenceInfo[] = [];\r\n\r\n // 先按段落分割(连续换行或单个换行)\r\n const paragraphs = text.split(/[\\n\\r]+/).filter((p) => p.trim().length > 0);\r\n\r\n let globalIndex = 0;\r\n\r\n for (let pi = 0; pi < paragraphs.length; pi++) {\r\n const para = paragraphs[pi].trim();\r\n if (para.length === 0) continue;\r\n\r\n // 在段落内按句末标点分割\r\n const rawSentences: string[] = [];\r\n let current = \"\";\r\n\r\n for (let i = 0; i < para.length; i++) {\r\n current += para[i];\r\n\r\n if (SENTENCE_ENDINGS.test(para[i])) {\r\n // 吞掉紧跟的引号/括号\r\n while (i + 1 < para.length && TRAILING_QUOTES.test(para[i + 1])) {\r\n i++;\r\n current += para[i];\r\n }\r\n const trimmed = current.trim();\r\n if (trimmed.length > 0) {\r\n rawSentences.push(trimmed);\r\n }\r\n current = \"\";\r\n }\r\n }\r\n\r\n // 剩余文本作为最后一个句子\r\n if (current.trim().length > 0) {\r\n rawSentences.push(current.trim());\r\n }\r\n\r\n // 过滤过短句子并记录位置信息\r\n for (let si = 0; si < rawSentences.length; si++) {\r\n const s = rawSentences[si];\r\n if (s.length < minLength) continue;\r\n\r\n sentences.push({\r\n index: globalIndex++,\r\n text: s,\r\n paragraphIndex: pi,\r\n sentenceInParagraph: si,\r\n isParagraphStart: si === 0,\r\n isParagraphEnd: si === rawSentences.length - 1,\r\n isFirstParagraph: pi === 0,\r\n score: 0,\r\n });\r\n }\r\n }\r\n\r\n return sentences;\r\n}\r\n\r\n// ============================================================\r\n// 2. 字级 n-gram 提取\r\n// ============================================================\r\n\r\n/**\r\n * 从文本中提取字级 n-gram 集合\r\n *\r\n * 优势:不需要分词器,直接按字符滑动窗口\r\n * 示例:\"今天天气好\" 的 bigram → {\"今天\", \"天天\", \"天气\", \"气好\"}\r\n */\r\nfunction extractNgrams(text: string, n: number): Set<string> {\r\n const ngrams = new Set<string>();\r\n if (!text || text.length === 0) return ngrams;\r\n\r\n // 去除标点和空白,保留中文、字母、数字\r\n const cleaned = text.replace(/[^\\u4e00-\\u9fff\\u3400-\\u4dbf\\u3007a-zA-Z0-9]/g, \"\");\r\n if (cleaned.length < n) return ngrams;\r\n\r\n for (let i = 0; i <= cleaned.length - n; i++) {\r\n ngrams.add(cleaned.substring(i, i + n));\r\n }\r\n return ngrams;\r\n}\r\n\r\n// ============================================================\r\n// 2b. TF-IDF 关键词提取(方案二)\r\n// ============================================================\r\n\r\n/** 中文停用字(高频但无实际意义的字) */\r\nconst STOP_CHARS = new Set(\"的了是在我有和就不人都一这中大为上个国也子时道说出会要没成好能对然她过生里后以到去能得着年这\".split(\"\"));\r\n\r\n/**\r\n * 从全文中提取关键词(字级 unigram 频率 + TF-IDF 简化版)\r\n *\r\n * 简化思路:\r\n * - 以句子为\"文档\",统计每个字在多少个句子中出现(DF)\r\n * - TF = 该字在全文中出现的次数\r\n * - IDF = log(总句数 / (DF + 1))\r\n * - TF-IDF = TF * IDF\r\n * - 取 Top-K 作为关键词\r\n *\r\n * 返回关键词集合\r\n */\r\nfunction extractKeywords(\r\n sentences: SentenceInfo[],\r\n topK: number\r\n): Set<string> {\r\n if (sentences.length === 0) return new Set();\r\n\r\n // 统计全文 TF 和句子级 DF\r\n const tf = new Map<string, number>();\r\n const df = new Map<string, number>();\r\n\r\n for (const sent of sentences) {\r\n const cleaned = sent.text.replace(/[^\\u4e00-\\u9fff\\u3400-\\u4dbf]/g, \"\");\r\n const seen = new Set<string>();\r\n\r\n for (const ch of cleaned) {\r\n if (STOP_CHARS.has(ch)) continue;\r\n tf.set(ch, (tf.get(ch) || 0) + 1);\r\n if (!seen.has(ch)) {\r\n df.set(ch, (df.get(ch) || 0) + 1);\r\n seen.add(ch);\r\n }\r\n }\r\n }\r\n\r\n // 计算 TF-IDF\r\n const n = sentences.length;\r\n const tfidf = new Map<string, number>();\r\n\r\n for (const [ch, freq] of tf) {\r\n const docFreq = df.get(ch) || 1;\r\n // IDF: 在越少句子中出现的字越有区分度\r\n const idf = Math.log((n + 1) / (docFreq + 1)) + 1;\r\n tfidf.set(ch, freq * idf);\r\n }\r\n\r\n // 取 Top-K\r\n const sorted = [...tfidf.entries()]\r\n .sort((a, b) => b[1] - a[1])\r\n .slice(0, topK);\r\n\r\n return new Set(sorted.map(([ch]) => ch));\r\n}\r\n\r\n/**\r\n * 计算句子的关键词命中权重\r\n *\r\n * 句子中包含的关键词越多,权重越高\r\n * weight = keywordWeight ^ (命中关键词数 / 关键词总数)\r\n */\r\nfunction keywordWeightForSentence(\r\n sentence: SentenceInfo,\r\n keywords: Set<string>,\r\n keywordWeight: number\r\n): number {\r\n if (keywords.size === 0 || keywordWeight === 0) return 1.0;\r\n\r\n const cleaned = sentence.text.replace(/[^\\u4e00-\\u9fff\\u3400-\\u4dbf]/g, \"\");\r\n let hits = 0;\r\n for (const ch of cleaned) {\r\n if (keywords.has(ch)) hits++;\r\n }\r\n\r\n if (hits === 0) return 1.0;\r\n\r\n // 使用对数平滑,避免命中数过多时权重爆炸\r\n const ratio = hits / keywords.size;\r\n return Math.pow(keywordWeight, Math.min(ratio, 1.0));\r\n}\r\n\r\n// ============================================================\r\n// 3. 句子相似度计算\r\n// ============================================================\r\n\r\n/**\r\n * 计算两个句子的 n-gram 相似度\r\n *\r\n * 公式:sim(i,j) = |ngrams(i) ∩ ngrams(j)| / (log(|ngrams(i)|) + log(|ngrams(j)|))\r\n *\r\n * 使用 log 是为了防止长句子因 n-gram 数量多而获得不公平的优势\r\n */\r\nfunction similarity(ngramsA: Set<string>, ngramsB: Set<string>): number {\r\n if (ngramsA.size === 0 || ngramsB.size === 0) return 0;\r\n\r\n let intersection = 0;\r\n for (const gram of ngramsA) {\r\n if (ngramsB.has(gram)) intersection++;\r\n }\r\n\r\n if (intersection === 0) return 0;\r\n\r\n const denom = Math.log(ngramsA.size + 1) + Math.log(ngramsB.size + 1);\r\n if (denom === 0) return 0;\r\n\r\n return safeNumber(intersection / denom);\r\n}\r\n\r\n// ============================================================\r\n// 4. 位置权重计算\r\n// ============================================================\r\n\r\n/**\r\n * 根据句子位置计算先验权重\r\n *\r\n * 规则(可叠加):\r\n * - 首段首句:weightFirstSentence\r\n * - 首段其他句:weightFirstParagraph\r\n * - 段落首句:weightParagraphStart\r\n * - 段落末句:weightParagraphEnd\r\n * - 其他:1.0\r\n */\r\nfunction positionWeight(\r\n sentence: SentenceInfo,\r\n opts: Required<SummaryOptions>\r\n): number {\r\n let weight = 1.0;\r\n\r\n if (sentence.isFirstParagraph && sentence.isParagraphStart) {\r\n weight *= opts.weightFirstSentence;\r\n } else if (sentence.isFirstParagraph) {\r\n weight *= opts.weightFirstParagraph;\r\n }\r\n\r\n if (!sentence.isFirstParagraph && sentence.isParagraphStart) {\r\n weight *= opts.weightParagraphStart;\r\n }\r\n\r\n if (sentence.isParagraphEnd) {\r\n weight *= opts.weightParagraphEnd;\r\n }\r\n\r\n return safeNumber(weight, 1.0);\r\n}\r\n\r\n// ============================================================\r\n// 5. TextRank 算法\r\n// ============================================================\r\n\r\n/**\r\n * TextRank 迭代计算\r\n *\r\n * 公式:WS(Vi) = (1-d) * init(Vi) + d * Σ(sim(Vi,Vj) / Σsim(Vj,Vk)) * WS(Vj)\r\n *\r\n * 其中 init(Vi) = 位置先验权重 × 关键词权重,d 为阻尼系数\r\n */\r\nfunction textRank(\r\n sentences: SentenceInfo[],\r\n ngramSets: Set<string>[],\r\n opts: Required<SummaryOptions>,\r\n keywords?: Set<string>\r\n): void {\r\n const n = sentences.length;\r\n if (n === 0) return;\r\n if (n === 1) {\r\n let score = positionWeight(sentences[0], opts);\r\n if (keywords && opts.keywordWeight > 0) {\r\n score *= keywordWeightForSentence(sentences[0], keywords, opts.keywordWeight);\r\n }\r\n sentences[0].score = safeNumber(score, 1.0);\r\n return;\r\n }\r\n\r\n // 构建相似度矩阵\r\n const simMatrix: number[][] = Array.from({ length: n }, () => new Array(n).fill(0));\r\n const simSums: number[] = new Array(n).fill(0);\r\n\r\n for (let i = 0; i < n; i++) {\r\n for (let j = i + 1; j < n; j++) {\r\n const sim = similarity(ngramSets[i], ngramSets[j]);\r\n simMatrix[i][j] = sim;\r\n simMatrix[j][i] = sim;\r\n simSums[i] += sim;\r\n simSums[j] += sim;\r\n }\r\n }\r\n\r\n // 初始化分数 = 位置先验权重 × 关键词权重\r\n const initScores = sentences.map((s) => {\r\n let score = positionWeight(s, opts);\r\n if (keywords && opts.keywordWeight > 0) {\r\n score *= keywordWeightForSentence(s, keywords, opts.keywordWeight);\r\n }\r\n return safeNumber(score, 1.0);\r\n });\r\n const scores = [...initScores];\r\n\r\n // 迭代\r\n for (let iter = 0; iter < opts.maxIterations; iter++) {\r\n const newScores = new Array(n).fill(0);\r\n let maxDiff = 0;\r\n\r\n for (let i = 0; i < n; i++) {\r\n let rankSum = 0;\r\n for (let j = 0; j < n; j++) {\r\n if (i === j || simSums[j] === 0) continue;\r\n rankSum += (simMatrix[i][j] / simSums[j]) * scores[j];\r\n }\r\n\r\n newScores[i] = (1 - opts.dampingFactor) * initScores[i] + opts.dampingFactor * rankSum;\r\n // 数值安全\r\n newScores[i] = safeNumber(newScores[i], initScores[i]);\r\n\r\n const diff = Math.abs(newScores[i] - scores[i]);\r\n if (diff > maxDiff) maxDiff = diff;\r\n }\r\n\r\n // 更新分数\r\n for (let i = 0; i < n; i++) {\r\n scores[i] = newScores[i];\r\n }\r\n\r\n // 收敛判断\r\n if (maxDiff < opts.convergenceThreshold) break;\r\n }\r\n\r\n // 写回得分\r\n for (let i = 0; i < n; i++) {\r\n sentences[i].score = safeNumber(scores[i], initScores[i]);\r\n }\r\n}\r\n\r\n// ============================================================\r\n// 6. 压缩级别 → 句子数量映射\r\n// ============================================================\r\n\r\n/**\r\n * 根据压缩级别计算应提取的句子数量\r\n *\r\n * 级别 5: 100% 句子\r\n * 级别 4: ~50% 句子\r\n * 级别 3: ~30% 句子(最少 3 句)\r\n * 级别 2: ~20% 句子(最少 2 句)\r\n * 级别 1: 1 句(后续走子句逻辑)\r\n */\r\nfunction sentenceCountForLevel(\r\n totalSentences: number,\r\n level: CompressionLevel\r\n): number {\r\n if (level === 5) return totalSentences;\r\n if (level === 4) return Math.max(3, Math.ceil(totalSentences * 0.5));\r\n if (level === 3) return Math.max(3, Math.ceil(totalSentences * 0.3));\r\n if (level === 2) return Math.max(2, Math.ceil(totalSentences * 0.2));\r\n return 1; // level 1\r\n}\r\n\r\n// ============================================================\r\n// 6b. MMR 多样性选句(方案一)\r\n// ============================================================\r\n\r\n/**\r\n * 使用 MMR(Maximal Marginal Relevance)策略选句\r\n *\r\n * 每次选择句子时,综合考虑:\r\n * - 相关性:TextRank 得分高\r\n * - 多样性:与已选句子不重复\r\n *\r\n * 公式:MMR(s) = λ * score(s) - (1-λ) * max_sim(s, 已选句子集)\r\n *\r\n * λ=1.0 退化为纯得分排序,λ=0.5 最大多样性\r\n */\r\nfunction selectByMMR(\r\n sentences: SentenceInfo[],\r\n ngramSets: Set<string>[],\r\n targetCount: number,\r\n lambda: number\r\n): SentenceInfo[] {\r\n if (sentences.length <= targetCount) return [...sentences];\r\n\r\n // 归一化得分到 [0, 1]\r\n const scores = sentences.map((s) => s.score);\r\n const maxScore = Math.max(...scores);\r\n const minScore = Math.min(...scores);\r\n const scoreRange = maxScore - minScore || 1;\r\n\r\n const normalizedScores = scores.map((s) => (s - minScore) / scoreRange);\r\n\r\n // 已选句子索引集合\r\n const selected: number[] = [];\r\n const remaining = new Set(sentences.map((_, i) => i));\r\n\r\n // 第一句:选得分最高的\r\n let bestIdx = -1;\r\n let bestScore = -Infinity;\r\n for (let i = 0; i < sentences.length; i++) {\r\n if (normalizedScores[i] > bestScore) {\r\n bestScore = normalizedScores[i];\r\n bestIdx = i;\r\n }\r\n }\r\n selected.push(bestIdx);\r\n remaining.delete(bestIdx);\r\n\r\n // 后续句子:用 MMR 选择\r\n while (selected.length < targetCount && remaining.size > 0) {\r\n let bestMMR = -Infinity;\r\n let bestCandidate = -1;\r\n\r\n for (const idx of remaining) {\r\n // 相关性项\r\n const relevance = lambda * normalizedScores[idx];\r\n\r\n // 多样性惩罚项:与已选句子的最大相似度\r\n let maxSim = 0;\r\n for (const selIdx of selected) {\r\n const sim = similarity(ngramSets[idx], ngramSets[selIdx]);\r\n if (sim > maxSim) maxSim = sim;\r\n }\r\n const diversity = (1 - lambda) * maxSim;\r\n\r\n const mmr = relevance - diversity;\r\n if (mmr > bestMMR) {\r\n bestMMR = mmr;\r\n bestCandidate = idx;\r\n }\r\n }\r\n\r\n if (bestCandidate === -1) break;\r\n selected.push(bestCandidate);\r\n remaining.delete(bestCandidate);\r\n }\r\n\r\n return selected.map((idx) => sentences[idx]);\r\n}\r\n\r\n// ============================================================\r\n// 7. 子句分割(compressionLevel=1 专用)\r\n// ============================================================\r\n\r\n/** 中文子句分隔标点 */\r\nconst CLAUSE_SEPARATORS = /[,、,;;::]/;\r\n\r\n/**\r\n * 将句子拆分为子句\r\n *\r\n * 规则:\r\n * - 按逗号、顿号、分号、冒号分割\r\n * - 保留分隔符在前一个子句末尾(保持可读性)\r\n * - 过滤过短的子句\r\n */\r\nfunction splitClauses(\r\n sentence: string,\r\n minLength: number\r\n): string[] {\r\n if (!sentence || sentence.length === 0) return [];\r\n\r\n const clauses: string[] = [];\r\n let current = \"\";\r\n\r\n for (let i = 0; i < sentence.length; i++) {\r\n current += sentence[i];\r\n\r\n if (CLAUSE_SEPARATORS.test(sentence[i])) {\r\n const trimmed = current.trim();\r\n if (trimmed.length >= minLength) {\r\n clauses.push(trimmed);\r\n }\r\n current = \"\";\r\n }\r\n }\r\n\r\n // 剩余文本\r\n const remaining = current.trim();\r\n if (remaining.length >= minLength) {\r\n clauses.push(remaining);\r\n }\r\n\r\n return clauses;\r\n}\r\n\r\n// ============================================================\r\n// 8. 子句级 TextRank(compressionLevel=1 专用)\r\n// ============================================================\r\n\r\n/** 主干子句加权系数 */\r\nconst MAIN_CLAUSE_BOOST = 1.3;\r\n\r\n/**\r\n * 对子句列表运行 TextRank\r\n *\r\n * 与句子级 TextRank 类似,但:\r\n * - 主干子句(每句第一个子句)获得额外先验权重\r\n * - 初始分数来自源句子得分\r\n * - 使用 n-gram 相似度\r\n */\r\nfunction clauseTextRank(\r\n clauses: ClauseInfo[],\r\n ngramSets: Set<string>[],\r\n opts: Required<SummaryOptions>\r\n): void {\r\n const n = clauses.length;\r\n if (n === 0) return;\r\n if (n === 1) {\r\n clauses[0].score = safeNumber(clauses[0].score, 1.0);\r\n return;\r\n }\r\n\r\n // 构建相似度矩阵\r\n const simMatrix: number[][] = Array.from({ length: n }, () => new Array(n).fill(0));\r\n const simSums: number[] = new Array(n).fill(0);\r\n\r\n for (let i = 0; i < n; i++) {\r\n for (let j = i + 1; j < n; j++) {\r\n const sim = similarity(ngramSets[i], ngramSets[j]);\r\n simMatrix[i][j] = sim;\r\n simMatrix[j][i] = sim;\r\n simSums[i] += sim;\r\n simSums[j] += sim;\r\n }\r\n }\r\n\r\n // 初始分数:来自源句子的得分作为先验,主干子句加权\r\n const initScores = clauses.map((c) => {\r\n const base = c.score || 1.0;\r\n return c.isMainClause ? base * MAIN_CLAUSE_BOOST : base;\r\n });\r\n const scores = [...initScores];\r\n\r\n // 迭代\r\n for (let iter = 0; iter < opts.maxIterations; iter++) {\r\n const newScores = new Array(n).fill(0);\r\n let maxDiff = 0;\r\n\r\n for (let i = 0; i < n; i++) {\r\n let rankSum = 0;\r\n for (let j = 0; j < n; j++) {\r\n if (i === j || simSums[j] === 0) continue;\r\n rankSum += (simMatrix[i][j] / simSums[j]) * scores[j];\r\n }\r\n\r\n newScores[i] = (1 - opts.dampingFactor) * initScores[i] + opts.dampingFactor * rankSum;\r\n newScores[i] = safeNumber(newScores[i], initScores[i]);\r\n\r\n const diff = Math.abs(newScores[i] - scores[i]);\r\n if (diff > maxDiff) maxDiff = diff;\r\n }\r\n\r\n for (let i = 0; i < n; i++) {\r\n scores[i] = newScores[i];\r\n }\r\n\r\n if (maxDiff < opts.convergenceThreshold) break;\r\n }\r\n\r\n for (let i = 0; i < n; i++) {\r\n clauses[i].score = safeNumber(scores[i], initScores[i]);\r\n }\r\n}\r\n\r\n// ============================================================\r\n// 9. 多轮重排(compressionLevel=2 专用)\r\n// ============================================================\r\n\r\n/**\r\n * 多轮 TextRank 重排\r\n *\r\n * 第一轮提取 Top-N 句子,将这些句子视为新文档,\r\n * 重新计算 n-gram 相似度和 TextRank,再取 Top-M 句子。\r\n * 这样在精选句子间重新排序,能更精准地找到核心句。\r\n */\r\nfunction rerankSentences(\r\n sentences: SentenceInfo[],\r\n targetCount: number,\r\n opts: Required<SummaryOptions>,\r\n keywords?: Set<string>\r\n): SentenceInfo[] {\r\n // 第一轮:在全部句子上运行 TextRank\r\n const ngramSets = sentences.map((s) => extractNgrams(s.text, opts.ngramSize));\r\n textRank(sentences, ngramSets, opts, keywords);\r\n\r\n // 取 Top-N(N 为 targetCount 的 2 倍,至少 5 句)\r\n const firstPassCount = Math.max(5, targetCount * 2);\r\n const firstPass = [...sentences]\r\n .sort((a, b) => b.score - a.score)\r\n .slice(0, Math.min(firstPassCount, sentences.length));\r\n\r\n // 如果句子太少,不需要第二轮\r\n if (firstPass.length <= targetCount) {\r\n return firstPass;\r\n }\r\n\r\n // 第二轮:在精选句子上重新运行 TextRank\r\n const secondPassSentences: SentenceInfo[] = firstPass.map((s, i) => ({\r\n ...s,\r\n index: i,\r\n }));\r\n\r\n const secondNgramSets = secondPassSentences.map((s) =>\r\n extractNgrams(s.text, opts.ngramSize)\r\n );\r\n textRank(secondPassSentences, secondNgramSets, opts);\r\n\r\n // 按第二轮得分排序,取 Top-M\r\n const result = [...secondPassSentences]\r\n .sort((a, b) => b.score - a.score)\r\n .slice(0, targetCount);\r\n\r\n // 恢复原始 index\r\n return result.map((s) => {\r\n const original = firstPass.find((f) => f.text === s.text);\r\n return original || s;\r\n });\r\n}\r\n\r\n// ============================================================\r\n// 10. 极致压缩:子句级提取(compressionLevel=1 专用)\r\n// ============================================================\r\n\r\n/** 句末标点,用于判断子句是否已自带句末标点 */\r\nconst SENTENCE_FINAL_PUNCT = /[。!?;!?;]$/;\r\n\r\n/**\r\n * 智能拼接子句,处理标点去重\r\n *\r\n * 规则:\r\n * - 子句末尾如果已有逗号/分号等子句分隔符,不再追加逗号\r\n * - 子句末尾如果是句末标点(。!?;),用空格连接\r\n * - 否则用逗号连接\r\n */\r\nfunction joinClauses(clauses: string[]): string {\r\n if (clauses.length === 0) return \"\";\r\n if (clauses.length === 1) return clauses[0];\r\n\r\n let result = clauses[0];\r\n\r\n for (let i = 1; i < clauses.length; i++) {\r\n const prev = result;\r\n const curr = clauses[i];\r\n const lastChar = prev[prev.length - 1];\r\n\r\n // 前一个子句以句末标点结尾 -> 空格连接\r\n if (SENTENCE_FINAL_PUNCT.test(prev)) {\r\n result = result + \" \" + curr;\r\n }\r\n // 前一个子句以子句分隔符(逗号等)结尾 -> 直接连接,不再加逗号\r\n else if (CLAUSE_SEPARATORS.test(lastChar)) {\r\n result = result + curr;\r\n }\r\n // 正常情况 -> 用逗号连接\r\n else {\r\n result = result + \",\" + curr;\r\n }\r\n }\r\n\r\n return result;\r\n}\r\n\r\n// ============================================================\r\n// 10b. 子句连词处理(方案三)\r\n// ============================================================\r\n\r\n/**\r\n * 中文连词/转折词表\r\n *\r\n * 这些词在子句开头时,脱离上下文会导致语义不完整\r\n * 例如:\"也标志着...\" → 读者不知道\"也\"指代什么\r\n */\r\nconst CLAUSE_CONJUNCTIONS = [\r\n // 转折\r\n \"但\", \"但是\", \"然而\", \"不过\", \"可是\", \"却\", \"反而\", \"只是\",\r\n // 递进/并列\r\n \"也\", \"而且\", \"并且\", \"此外\", \"另外\", \"同时\", \"既\", \"又\",\r\n // 因果\r\n \"因此\", \"所以\", \"于是\", \"从而\", \"进而\", \"故\",\r\n // 让步\r\n \"虽然\", \"尽管\", \"固然\", \"即使\", \"就算\",\r\n // 条件\r\n \"如果\", \"假如\", \"只要\", \"只有\", \"除非\",\r\n // 总结\r\n \"总之\", \"综上\", \"由此可见\", \"总的来说\",\r\n];\r\n\r\n/** 连词最大长度(用于匹配) */\r\nconst MAX_CONJ_LEN = Math.max(...CLAUSE_CONJUNCTIONS.map((c) => c.length));\r\n\r\n/**\r\n * 处理子句开头的连词\r\n *\r\n * 策略:\r\n * 1. 如果子句以连词开头,尝试向前合并同句的前一个子句(如果前一个子句也被选中)\r\n * 2. 如果无法合并,则剥离连词\r\n *\r\n * @param orderedClauses 已按原文顺序排列的子句列表\r\n * @param allClauses 所有子句(用于查找前一个子句)\r\n * @returns 处理后的子句文本数组\r\n */\r\nfunction processConjunctions(\r\n orderedClauses: ClauseInfo[],\r\n allClauses: ClauseInfo[]\r\n): string[] {\r\n // 建立快速查找:sourceSentenceIndex + clauseIndex -> ClauseInfo\r\n const clauseMap = new Map<string, ClauseInfo>();\r\n for (const c of allClauses) {\r\n clauseMap.set(c.sourceSentenceIndex + \":\" + c.clauseIndex, c);\r\n }\r\n\r\n // 建立选中子句集合\r\n const selectedSet = new Set(orderedClauses.map((c) => c.sourceSentenceIndex + \":\" + c.clauseIndex));\r\n\r\n const results: string[] = [];\r\n\r\n for (const clause of orderedClauses) {\r\n let text = clause.text;\r\n\r\n // 检测子句开头是否为连词\r\n const matchedConj = matchConjunction(text);\r\n\r\n if (matchedConj) {\r\n // 尝试向前合并:查找同句的前一个子句\r\n const prevKey = clause.sourceSentenceIndex + \":\" + (clause.clauseIndex - 1);\r\n const prevClause = clauseMap.get(prevKey);\r\n\r\n if (prevClause && selectedSet.has(prevKey)) {\r\n // 前一个子句也被选中,不需要处理(它们会自然拼接)\r\n // 但需要确保连词不会导致拼接后语义怪异\r\n // 这里不做额外处理,让 joinClauses 处理拼接\r\n } else {\r\n // 前一个子句未被选中,剥离连词\r\n text = text.substring(matchedConj.length);\r\n // 清理连词后的前导标点(如 \"然而,\" → 剥离 \"然而\" 后剩 \",数据...\")\r\n text = text.replace(/^[,、,;;::\\s]+/, \"\");\r\n }\r\n }\r\n\r\n if (text.length > 0) {\r\n results.push(text);\r\n }\r\n }\r\n\r\n return results;\r\n}\r\n\r\n/**\r\n * 匹配文本开头的连词\r\n *\r\n * 从长到短匹配,避免 \"但是\" 被误匹配为 \"但\"\r\n * 返回匹配到的连词,未匹配返回 null\r\n */\r\nfunction matchConjunction(text: string): string | null {\r\n for (let len = MAX_CONJ_LEN; len >= 1; len--) {\r\n if (len > text.length) continue;\r\n const prefix = text.substring(0, len);\r\n if (CLAUSE_CONJUNCTIONS.includes(prefix)) {\r\n return prefix;\r\n }\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * 极致压缩:先句子级提取 Top-N,再拆子句,子句级 TextRank,拼接 Top 子句\r\n *\r\n * 流程:\r\n * 1. 句子级 TextRank,取 Top-5 句\r\n * 2. 将这 5 句拆分为子句,标记主干子句\r\n * 3. 子句级 TextRank(以源句子得分为先验,主干子句加权)\r\n * 4. 取 Top-K 子句,按原文顺序排列\r\n * 5. 连词处理:剥离或合并脱离上下文的连词\r\n * 6. 拼接为一句话(智能标点处理)\r\n */\r\nfunction extractClauseSummary(\r\n sentences: SentenceInfo[],\r\n opts: Required<SummaryOptions>,\r\n keywords?: Set<string>\r\n): { summary: string[]; clauses: ClauseInfo[] } {\r\n // Step 1: 句子级 TextRank,取 Top-5\r\n const ngramSets = sentences.map((s) => extractNgrams(s.text, opts.ngramSize));\r\n textRank(sentences, ngramSets, opts, keywords);\r\n\r\n const topSentenceCount = Math.min(5, sentences.length);\r\n const topSentences = [...sentences]\r\n .sort((a, b) => b.score - a.score)\r\n .slice(0, topSentenceCount);\r\n\r\n // Step 2: 拆分子句,标记主干子句\r\n const allClauses: ClauseInfo[] = [];\r\n for (const sent of topSentences) {\r\n const rawClauses = splitClauses(sent.text, opts.minClauseLength);\r\n for (let ci = 0; ci < rawClauses.length; ci++) {\r\n allClauses.push({\r\n text: rawClauses[ci],\r\n sourceSentenceIndex: sent.index,\r\n clauseIndex: ci,\r\n isMainClause: ci === 0, // 第一个子句是主干子句\r\n score: sent.score,\r\n });\r\n }\r\n }\r\n\r\n if (allClauses.length === 0) {\r\n // 子句拆分失败,退回到取得分最高的 1 句\r\n const best = topSentences[0];\r\n return {\r\n summary: [best.text],\r\n clauses: [{\r\n text: best.text,\r\n sourceSentenceIndex: best.index,\r\n clauseIndex: 0,\r\n isMainClause: true,\r\n score: best.score,\r\n }],\r\n };\r\n }\r\n\r\n // Step 3: 子句级 TextRank\r\n const clauseNgramSets = allClauses.map((c) =>\r\n extractNgrams(c.text, opts.ngramSize)\r\n );\r\n clauseTextRank(allClauses, clauseNgramSets, opts);\r\n\r\n // Step 4: 取 Top-K 子句\r\n const maxClauses = Math.min(opts.maxClauses, allClauses.length);\r\n const topClauses = [...allClauses]\r\n .sort((a, b) => b.score - a.score)\r\n .slice(0, maxClauses);\r\n\r\n // Step 5: 按源句子在原文中的顺序排列子句(保持可读性)\r\n const orderedClauses = [...topClauses].sort((a, b) => {\r\n if (a.sourceSentenceIndex !== b.sourceSentenceIndex) {\r\n return a.sourceSentenceIndex - b.sourceSentenceIndex;\r\n }\r\n return a.clauseIndex - b.clauseIndex;\r\n });\r\n\r\n // Step 6: 连词处理(剥离脱离上下文的连词)\r\n const processedTexts = processConjunctions(orderedClauses, allClauses);\r\n\r\n // Step 7: 拼接为一句话(智能标点处理)\r\n const summaryText = joinClauses(processedTexts);\r\n\r\n return {\r\n summary: [summaryText],\r\n clauses: allClauses,\r\n };\r\n}\r\n\r\n// ============================================================\r\n// 11. 主接口\r\n// ============================================================\r\n\r\n/**\r\n * 提取中文文本概要\r\n *\r\n * @param text 原始中文文本\r\n * @param options 配置选项\r\n * @returns 概要提取结果\r\n *\r\n * @example\r\n * ```ts\r\n * import { extractSummary } from 'chinese-summary';\r\n *\r\n * // 使用 sentenceCount(兼容旧接口)\r\n * const result1 = extractSummary(text, { sentenceCount: 2 });\r\n *\r\n * // 使用 compressionLevel(推荐)\r\n * const result2 = extractSummary(text, { compressionLevel: 1 }); // 极致压缩\r\n * const result3 = extractSummary(text, { compressionLevel: 3 }); // 中度压缩\r\n * ```\r\n */\r\nexport function extractSummary(\r\n text: string,\r\n options: SummaryOptions = {}\r\n): SummaryResult {\r\n // ─── 输入校验 ───\r\n if (text === null || text === undefined) {\r\n return { summary: [], sentences: [], text: \"\", compressionLevel: 3 };\r\n }\r\n if (typeof text !== \"string\") {\r\n try {\r\n text = String(text);\r\n } catch {\r\n return { summary: [], sentences: [], text: \"\", compressionLevel: 3 };\r\n }\r\n }\r\n // 去除 BOM 和零宽字符\r\n text = text.replace(/[\\uFEFF\\u200B\\u200C\\u200D\\u00AD]/g, \"\");\r\n // 统一换行符\r\n text = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\r\n\r\n if (text.trim().length === 0) {\r\n return { summary: [], sentences: [], text: \"\", compressionLevel: 3 };\r\n }\r\n\r\n // ─── 参数校验 ───\r\n const opts = sanitizeOptions(options);\r\n const level = opts.compressionLevel;\r\n\r\n // Step 1: 句子分割\r\n const sentences = splitSentences(text, opts.minSentenceLength);\r\n\r\n if (sentences.length === 0) {\r\n return { summary: [], sentences: [], text: \"\", compressionLevel: level };\r\n }\r\n\r\n // Step 1b: 提取全文关键词(方案二:TF-IDF 关键词加权)\r\n const keywords = opts.keywordWeight > 0\r\n ? extractKeywords(sentences, Math.min(20, Math.max(5, Math.ceil(sentences.length * 0.5))))\r\n : undefined;\r\n\r\n // ─── 级别 5:不压缩 ───\r\n if (level === 5) {\r\n // 仍然计算得分(用于调试)\r\n const ngramSets = sentences.map((s) => extractNgrams(s.text, opts.ngramSize));\r\n textRank(sentences, ngramSets, opts, keywords);\r\n\r\n const summary = sentences.map((s) => s.text);\r\n return { summary, sentences, text: summary.join(\" \"), compressionLevel: level };\r\n }\r\n\r\n // ─── 级别 1:极致压缩(子句级) ───\r\n if (level === 1) {\r\n if (sentences.length <= 1) {\r\n const summary = sentences.map((s) => s.text);\r\n return { summary, sentences, text: summary.join(\" \"), compressionLevel: level };\r\n }\r\n\r\n const { summary, clauses } = extractClauseSummary(sentences, opts, keywords);\r\n return { summary, sentences, text: summary.join(\" \"), compressionLevel: level, clauses };\r\n }\r\n\r\n // ─── 级别 2/3/4:句子级提取 ───\r\n let targetCount: number;\r\n\r\n if (options.sentenceCount !== undefined && options.compressionLevel === undefined) {\r\n // 旧接口:用户指定了 sentenceCount 但未指定 compressionLevel\r\n targetCount = opts.sentenceCount;\r\n } else {\r\n targetCount = sentenceCountForLevel(sentences.length, level);\r\n }\r\n\r\n // 如果句子数少于目标数,全部返回\r\n if (sentences.length <= targetCount) {\r\n const summary = sentences.map((s) => s.text);\r\n return { summary, sentences, text: summary.join(\" \"), compressionLevel: level };\r\n }\r\n\r\n // ─── 级别 2:多轮重排 + MMR ───\r\n if (level === 2) {\r\n const topSentences = rerankSentences(sentences, targetCount, opts, keywords);\r\n\r\n const selectedIndices = new Set(topSentences.map((s) => s.index));\r\n const summary = sentences\r\n .filter((s) => selectedIndices.has(s.index))\r\n .map((s) => s.text);\r\n\r\n // 也给所有句子打分(用于调试)\r\n const ngramSets = sentences.map((s) => extractNgrams(s.text, opts.ngramSize));\r\n textRank(sentences, ngramSets, opts, keywords);\r\n\r\n return { summary, sentences, text: summary.join(\" \"), compressionLevel: level };\r\n }\r\n\r\n // ─── 级别 3/4:TextRank + 关键词加权 + MMR 选句 ───\r\n const ngramSets = sentences.map((s) => extractNgrams(s.text, opts.ngramSize));\r\n textRank(sentences, ngramSets, opts, keywords);\r\n\r\n // 使用 MMR 选句(mmrLambda=1.0 时退化为纯得分排序)\r\n const topN = selectByMMR(sentences, ngramSets, targetCount, opts.mmrLambda);\r\n\r\n const selectedIndices = new Set(topN.map((s) => s.index));\r\n const summary = sentences\r\n .filter((s) => selectedIndices.has(s.index))\r\n .map((s) => s.text);\r\n\r\n return { summary, sentences, text: summary.join(\" \"), compressionLevel: level };\r\n}\r\n\r\n/**\r\n * 仅获取句子得分(不提取摘要),用于调试和分析\r\n */\r\nexport function rankSentences(\r\n text: string,\r\n options: SummaryOptions = {}\r\n): SentenceInfo[] {\r\n // 输入校验\r\n if (text === null || text === undefined || typeof text !== \"string\") {\r\n return [];\r\n }\r\n if (text.trim().length === 0) return [];\r\n\r\n const opts = sanitizeOptions(options);\r\n const sentences = splitSentences(text, opts.minSentenceLength);\r\n if (sentences.length === 0) return [];\r\n\r\n const keywords = opts.keywordWeight > 0\r\n ? extractKeywords(sentences, Math.min(20, Math.max(5, Math.ceil(sentences.length * 0.5))))\r\n : undefined;\r\n\r\n const ngramSets = sentences.map((s) => extractNgrams(s.text, opts.ngramSize));\r\n textRank(sentences, ngramSets, opts, keywords);\r\n\r\n return [...sentences].sort((a, b) => b.score - a.score);\r\n}\r\n"],"mappings":"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","names":["ngramSets","summary","selectedIndices"]}
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "chinese-summary",
3
+ "version": "1.0.0",
4
+ "description": "中文文本概要提取库(TextRank + 位置加权 + TF-IDF + MMR)",
5
+ "author": "郭玉峰, 吴琼 <gyfinjava@163.com> (北京锋通科技有限公司)",
6
+ "license": "MIT",
7
+ "keywords": [
8
+ "chinese",
9
+ "summary",
10
+ "textrank",
11
+ "nlp",
12
+ "extractive-summarization",
13
+ "中文摘要",
14
+ "文本摘要",
15
+ "自动摘要"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/cn-dev/chinese-summary.git"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/cn-dev/chinese-summary/issues"
23
+ },
24
+ "homepage": "https://github.com/cn-dev/chinese-summary#readme",
25
+ "main": "dist/chinese-summary.cjs",
26
+ "module": "dist/chinese-summary.mjs",
27
+ "types": "dist/chinese-summary.d.ts",
28
+ "browser": "dist/chinese-summary.iife.js",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/chinese-summary.d.ts",
32
+ "browser": "./dist/chinese-summary.iife.js",
33
+ "import": "./dist/chinese-summary.mjs",
34
+ "require": "./dist/chinese-summary.cjs"
35
+ }
36
+ },
37
+ "files": [
38
+ "dist"
39
+ ],
40
+ "scripts": {
41
+ "build": "tsup",
42
+ "test": "npx tsx test/test.ts",
43
+ "test:long": "npx tsx test/test-long.ts",
44
+ "test:robust": "npx tsx test/test-robust.ts"
45
+ },
46
+ "devDependencies": {
47
+ "tsup": "^8.0.0",
48
+ "tsx": "^4.22.4",
49
+ "typescript": "^5.4.0"
50
+ }
51
+ }