From a79ab7abe5625b37458d3cff2f2309eaa19cd156 Mon Sep 17 00:00:00 2001 From: dadong Date: Thu, 6 Mar 2025 02:10:06 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=92=84=20style:=20add=20epub=20file?= =?UTF-8?q?=20chunk=20split=20support=20(#6317)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨feat: add epub file chunk split support * ✅test: add unit test for epub chunk splitter --------- Co-authored-by: stevendong --- package.json | 2 + src/database/client/migrations.json | 11 +- .../__snapshots__/index.test.ts.snap | 238 ++++++++++++++++++ .../loaders/epub/__tests__/demo.epub | Bin 0 -> 3683 bytes .../loaders/epub/__tests__/index.test.ts | 24 ++ src/libs/langchain/loaders/epub/index.ts | 21 ++ src/libs/langchain/loaders/index.ts | 9 + src/libs/langchain/types.ts | 3 +- src/server/utils/tempFileManager.ts | 70 ++++++ 9 files changed, 374 insertions(+), 4 deletions(-) create mode 100644 src/libs/langchain/loaders/epub/__tests__/__snapshots__/index.test.ts.snap create mode 100644 src/libs/langchain/loaders/epub/__tests__/demo.epub create mode 100644 src/libs/langchain/loaders/epub/__tests__/index.test.ts create mode 100644 src/libs/langchain/loaders/epub/index.ts create mode 100644 src/server/utils/tempFileManager.ts diff --git a/package.json b/package.json index a4af1052fe095..67bd7e3190b24 100644 --- a/package.json +++ b/package.json @@ -162,10 +162,12 @@ "diff": "^7.0.0", "drizzle-orm": "^0.40.0", "drizzle-zod": "^0.5.1", + "epub2": "^3.0.2", "fast-deep-equal": "^3.1.3", "file-type": "^20.0.0", "framer-motion": "^11.16.0", "gpt-tokenizer": "^2.8.1", + "html-to-text": "^9.0.5", "i18next": "^24.2.1", "i18next-browser-languagedetector": "^8.0.2", "i18next-resources-to-backend": "^1.2.1", diff --git a/src/database/client/migrations.json b/src/database/client/migrations.json index 543549ad41233..171b636570c1f 100644 --- a/src/database/client/migrations.json +++ b/src/database/client/migrations.json @@ -223,7 +223,10 @@ "hash": "9646161fa041354714f823d726af27247bcd6e60fa3be5698c0d69f337a5700b" }, { - "sql": ["DROP TABLE \"user_budgets\";", "\nDROP TABLE \"user_subscriptions\";"], + "sql": [ + "DROP TABLE \"user_budgets\";", + "\nDROP TABLE \"user_subscriptions\";" + ], "bps": true, "folderMillis": 1729699958471, "hash": "7dad43a2a25d1aec82124a4e53f8d82f8505c3073f23606c1dc5d2a4598eacf9" @@ -295,7 +298,9 @@ "hash": "845a692ceabbfc3caf252a97d3e19a213bc0c433df2689900135f9cfded2cf49" }, { - "sql": ["ALTER TABLE \"messages\" ADD COLUMN \"reasoning\" jsonb;"], + "sql": [ + "ALTER TABLE \"messages\" ADD COLUMN \"reasoning\" jsonb;" + ], "bps": true, "folderMillis": 1737609172353, "hash": "2cb36ae4fcdd7b7064767e04bfbb36ae34518ff4bb1b39006f2dd394d1893868" @@ -309,4 +314,4 @@ "folderMillis": 1739901891891, "hash": "78d8fefd8c58938d7bc3da2295a73b35ce2e8d7cb2820f8e817acdb8dd5bebb2" } -] +] \ No newline at end of file diff --git a/src/libs/langchain/loaders/epub/__tests__/__snapshots__/index.test.ts.snap b/src/libs/langchain/loaders/epub/__tests__/__snapshots__/index.test.ts.snap new file mode 100644 index 0000000000000..ffcb40644c23d --- /dev/null +++ b/src/libs/langchain/loaders/epub/__tests__/__snapshots__/index.test.ts.snap @@ -0,0 +1,238 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`EPubLoader > should run 1`] = ` +[ + Document { + "id": undefined, + "metadata": { + "loc": { + "lines": { + "from": 1, + "to": 13, + }, + }, + "source": "", + }, + "pageContent": "HEFTY WATER + +This document serves to test Reading System support for the epub:switch +[http://idpf.org/epub/30/spec/epub30-contentdocs.html#sec-xhtml-content-switch] +element. There is also a little bit of ruby markup +[http://www.w3.org/TR/html5/the-ruby-element.html#the-ruby-element] available. + + +THE SWITCH + +Below is an instance of the epub:switch element, containing Chemical Markup +Language [http://en.wikipedia.org/wiki/Chemical_Markup_Language] (CML). The +fallback content is a chunk of plain XHTML5.", + }, + Document { + "id": undefined, + "metadata": { + "loc": { + "lines": { + "from": 9, + "to": 22, + }, + }, + "source": "", + }, + "pageContent": "THE SWITCH + +Below is an instance of the epub:switch element, containing Chemical Markup +Language [http://en.wikipedia.org/wiki/Chemical_Markup_Language] (CML). The +fallback content is a chunk of plain XHTML5. + + * If your Reading System supports epub:switch and CML, it will render the CML + formula natively, and ignore (a.k.a not display) the XHTML fallback. + * If your Reading System supports epub:switch but not CML, it will ignore (not + display) the CML formula, and render the the XHTML fallback instead. + * If your Reading System does not support epub:switch at all, then the + rendering results are somewhat unpredictable, but the most likely result is + that it will display both a failed attempt to render the CML and the XHTML + fallback.", + }, + Document { + "id": undefined, + "metadata": { + "loc": { + "lines": { + "from": 24, + "to": 43, + }, + }, + "source": "", + }, + "pageContent": "Note: the XHTML fallback is bold and enclosed in a gray dotted box with a +slightly gray background. A failed CML rendering will most likely appear above +the gray fallback box and read: +"H hydrogen O oxygen hefty H O water". + +Here the switch begins... + + +H hydrogen O oxygen hefty H O water + +2H2 + O2 ⟶ 2H2O + +... and here the switch ends. + + +THE SOURCE + +Below is a rendition of the source code of the switch element. Your Reading +System should display this correctly regardless of whether it supports the +switch element.", + }, + Document { + "id": undefined, + "metadata": { + "loc": { + "lines": { + "from": 46, + "to": 66, + }, + }, + "source": "", + }, + "pageContent": " + + + + + H + hydrogen + + + + O + oxygen + + + hefty + + + H + O + water + ", + }, + Document { + "id": undefined, + "metadata": { + "loc": { + "lines": { + "from": 57, + "to": 79, + }, + }, + "source": "", + }, + "pageContent": " oxygen + + + hefty + + + H + O + water + + + + + +

+ 2H2 + + + O2 + + 2H2O +

+
+
", + }, + Document { + "id": undefined, + "metadata": { + "loc": { + "lines": { + "from": 84, + "to": 94, + }, + }, + "source": "", + }, + "pageContent": "HEFTY RUBY WATER + +While the ruby element is mostly used in east-asian languages, it can also be +useful in other contexts. As an example, and as you can see in the source of the +CML element above, the code includes a caption element which is intended to be +displayed below the formula segments. Following this paragraph is a reworked +version of the XHTML fallback used above, using the ruby element. If your +Reading System does not support ruby markup, then the captions will appear in +parentheses on the same line as the formula segments. + +2H2(hydrogen) + O2(oxygen) ⟶(hefty) 2H2O(water)", + }, + Document { + "id": undefined, + "metadata": { + "loc": { + "lines": { + "from": 94, + "to": 111, + }, + }, + "source": "", + }, + "pageContent": "2H2(hydrogen) + O2(oxygen) ⟶(hefty) 2H2O(water) + +If your Reading System in addition to supporting ruby markup also supports the +-epub-ruby-position +[http://idpf.org/epub/30/spec/epub30-contentdocs.html#sec-css-ruby-position] +property, then the captions will appear under the formula segments instead of +over them. + +The source code for the ruby version of the XHTML fallback looks as follows: + + +

+ 2H2(hydrogen) + + + O2(oxygen) + (hefty) + 2H2O(water) +

", + }, + Document { + "id": undefined, + "metadata": { + "loc": { + "lines": { + "from": 105, + "to": 120, + }, + }, + "source": "", + }, + "pageContent": "

+ 2H2(hydrogen) + + + O2(oxygen) + (hefty) + 2H2O(water) +

+ + +... and the css declaration using the -epub-ruby-position property looks like +this: + + +p#rubyp { + -epub-ruby-position : under; +}", + }, +] +`; diff --git a/src/libs/langchain/loaders/epub/__tests__/demo.epub b/src/libs/langchain/loaders/epub/__tests__/demo.epub new file mode 100644 index 0000000000000000000000000000000000000000..11cc0a74cf7ccd23c2d2a63e942e00210cea31e1 GIT binary patch literal 3683 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 { + it('should run', async () => { + const content = fs.readFileSync(join(__dirname, `./demo.epub`)); + + const fileContent: Uint8Array = new Uint8Array(content); + + const data = await EPubLoader(fileContent); + expect(sanitizeDynamicFields(data)).toMatchSnapshot(); + }); +}); diff --git a/src/libs/langchain/loaders/epub/index.ts b/src/libs/langchain/loaders/epub/index.ts new file mode 100644 index 0000000000000..1481e49bba235 --- /dev/null +++ b/src/libs/langchain/loaders/epub/index.ts @@ -0,0 +1,21 @@ +import { EPubLoader as Loader } from '@langchain/community/document_loaders/fs/epub'; +import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter'; +import { loaderConfig } from '../config'; +import { TempFileManager } from '@/server/utils/tempFileManager'; + +export const EPubLoader = async (content: Uint8Array) => { + const tempManager = new TempFileManager(); + try { + const tempPath = await tempManager.writeTempFile(content); + const loader = new Loader(tempPath); + const documents = await loader.load(); + + const splitter = new RecursiveCharacterTextSplitter(loaderConfig); + return await splitter.splitDocuments(documents); + } catch (e) { + throw new Error(`EPubLoader error: ${(e as Error).message}`); + } finally { + tempManager.cleanup(); // 确保清理 + } + +}; diff --git a/src/libs/langchain/loaders/index.ts b/src/libs/langchain/loaders/index.ts index 50c91c8fc94cd..b4763320b99a2 100644 --- a/src/libs/langchain/loaders/index.ts +++ b/src/libs/langchain/loaders/index.ts @@ -14,6 +14,7 @@ import { MarkdownLoader } from './markdown'; import { PdfLoader } from './pdf'; import { PPTXLoader } from './pptx'; import { TextLoader } from './txt'; +import { EPubLoader } from './epub'; class LangChainError extends Error { constructor(message: string) { @@ -64,6 +65,10 @@ export class ChunkingLoader { return await CsVLoader(fileBlob); } + case 'epub': { + return await EPubLoader(content); + } + default: { throw new Error( `Unsupported file type [${type}], please check your file is supported, or create report issue here: https://github.com/lobehub/lobe-chat/discussions/3550`, @@ -100,6 +105,10 @@ export class ChunkingLoader { return 'csv'; } + if (filename.endsWith('epub')) { + return 'epub'; + } + const ext = filename.split('.').pop(); if (ext && SupportedTextSplitterLanguages.includes(ext as SupportedTextSplitterLanguage)) { diff --git a/src/libs/langchain/types.ts b/src/libs/langchain/types.ts index 0bcc746bcd9ce..5512fb9524774 100644 --- a/src/libs/langchain/types.ts +++ b/src/libs/langchain/types.ts @@ -6,4 +6,5 @@ export type LangChainLoaderType = | 'doc' | 'text' | 'latex' - | 'csv'; + | 'csv' + | 'epub'; diff --git a/src/server/utils/tempFileManager.ts b/src/server/utils/tempFileManager.ts new file mode 100644 index 0000000000000..908b457717df5 --- /dev/null +++ b/src/server/utils/tempFileManager.ts @@ -0,0 +1,70 @@ +import { mkdtempSync, rmSync , writeFileSync, existsSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { v4 as uuidv4 } from 'uuid'; + +/** + * 安全存储临时文件工具类 + */ +export class TempFileManager { + private readonly tempDir: string; + private filePaths: Set = new Set(); + + constructor() { + // 创建唯一临时目录 (跨平台安全) + this.tempDir = mkdtempSync(join(tmpdir(), 'epub-')); + // 注册退出清理钩子 + this.registerCleanupHook(); + } + + /** + * 将 Uint8Array 写入临时文件 + * @param data 文件数据 + * @param ext 文件扩展名 (默认 .epub) + * @returns 临时文件绝对路径 + */ + async writeTempFile(data: Uint8Array, ext = '.epub'): Promise { + const filePath = join(this.tempDir, `${uuidv4()}${ext}`); + + try { + writeFileSync(filePath, data); + this.filePaths.add(filePath); + return filePath; + } catch (error) { + this.cleanup(); // 写入失败时立即清理 + throw new Error(`Failed to write temp file: ${(error as Error).message}`); + } + } + + /** + * 安全清理临时资源 + */ + cleanup(): void { + if (existsSync(this.tempDir)) { + // 递归删除目录及内容 + rmSync(this.tempDir, { force: true, recursive: true }); + this.filePaths.clear(); + } + } + + /** + * 注册进程退出/异常时的自动清理 + */ + private registerCleanupHook(): void { + // 正常退出 + process.on('exit', () => this.cleanup()); + // 异常退出 + process.on('uncaughtException', (err) => { + console.error('Uncaught exception, cleaning temp files:', err); + this.cleanup(); + process.exit(1); + }); + // 信号终止 + ['SIGINT', 'SIGTERM'].forEach((signal) => { + process.on(signal, () => { + this.cleanup(); + process.exit(0); + }); + }); + } +} From 8f54b6708573df29759b5d7c87f248b255593350 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 5 Mar 2025 18:18:48 +0000 Subject: [PATCH 2/3] :bookmark: chore(release): v1.68.9 [skip ci] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### [Version 1.68.9](https://github.com/lobehub/lobe-chat/compare/v1.68.8...v1.68.9) Released on **2025-03-05** #### 💄 Styles - **misc**: Add epub file chunk split support.
Improvements and Fixes #### Styles * **misc**: Add epub file chunk split support, closes [#6317](https://github.com/lobehub/lobe-chat/issues/6317) ([a79ab7a](https://github.com/lobehub/lobe-chat/commit/a79ab7a))
[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
--- CHANGELOG.md | 25 +++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 054b2befa623f..c89d267a6cde0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,31 @@ # Changelog +### [Version 1.68.9](https://github.com/lobehub/lobe-chat/compare/v1.68.8...v1.68.9) + +Released on **2025-03-05** + +#### 💄 Styles + +- **misc**: Add epub file chunk split support. + +
+ +
+Improvements and Fixes + +#### Styles + +- **misc**: Add epub file chunk split support, closes [#6317](https://github.com/lobehub/lobe-chat/issues/6317) ([a79ab7a](https://github.com/lobehub/lobe-chat/commit/a79ab7a)) + +
+ +
+ +[![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top) + +
+ ### [Version 1.68.8](https://github.com/lobehub/lobe-chat/compare/v1.68.7...v1.68.8) Released on **2025-03-05** diff --git a/package.json b/package.json index 67bd7e3190b24..387866ab64cce 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lobehub/chat", - "version": "1.68.8", + "version": "1.68.9", "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.", "keywords": [ "framework", From 00a33bf70253dde0eecde05b000747093ac3868f Mon Sep 17 00:00:00 2001 From: lobehubbot Date: Wed, 5 Mar 2025 18:19:52 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=93=9D=20docs(bot):=20Auto=20sync=20a?= =?UTF-8?q?gents=20&=20plugin=20to=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/v1.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/changelog/v1.json b/changelog/v1.json index 563b8b42ce918..34f44da76c3fc 100644 --- a/changelog/v1.json +++ b/changelog/v1.json @@ -1,4 +1,11 @@ [ + { + "children": { + "improvements": ["Add epub file chunk split support."] + }, + "date": "2025-03-05", + "version": "1.68.9" + }, { "children": { "improvements": ["Improve openrouter models info."]