feat: 内置阅读器、批注笔记与 AI 助手,发布 1.3.0

新增 PDF/EPUB/MOBI/AZW 内置阅读器,PDF 分段读取支持超大文件,
批注、读书与画布笔记、封面生成与本地导入。AI 助手支持三种协议、
图像上下文与安全 Markdown 渲染,上下文范围改为 选中/当前页/全文,
页面与全文无需选中文本即可发送,全文会提示可能超出模型限制。

便携版输出目录固定为 PeopleLib-windows-x64,不再随版本号变化,
避免升级后 data/ 被遗留在旧目录。

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
This commit is contained in:
lofyer
2026-08-03 12:13:02 +08:00
co-authored by factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
parent b8c8d24107
commit 3ccd044527
307 changed files with 98477 additions and 1148 deletions
+334
View File
@@ -0,0 +1,334 @@
<!DOCTYPE html>
<html lang="zh-CN" data-ui-theme="dark">
<head>
<meta charset="UTF-8" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' data: blob:; style-src 'self' 'unsafe-inline'; worker-src 'self' blob:; script-src 'self'" />
<title>PeopleLib</title>
<link rel="stylesheet" href="reader.css" />
<link rel="stylesheet" href="vendor/quill/quill.snow.css" />
<link rel="stylesheet" href="rich-note.css" />
</head>
<body>
<div class="titlebar">
<div class="titlebar-left">
<span class="brand">
<img class="brand-logo brand-logo-dark" src="../../icons/dist/dark/icon-32.png" alt="" />
<img class="brand-logo brand-logo-light" src="../../icons/dist/light/icon-32.png" alt="" />
<span>人民阅读器</span>
</span>
<span id="bookTitle" class="brand-sub">未打开书籍</span>
</div>
<div class="titlebar-spacer"></div>
<div class="titlebar-controls">
<button id="uiThemeBtn" class="win-btn ui-theme-btn" title="切换到明亮主题" aria-label="切换到明亮主题">
<svg class="toolbar-icon ui-theme-sun" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/>
</svg>
<svg class="toolbar-icon ui-theme-moon" viewBox="0 0 24 24" aria-hidden="true">
<path d="M20 15.5A8.5 8.5 0 0 1 8.5 4 8.5 8.5 0 1 0 20 15.5Z"/>
</svg>
</button>
<button id="minBtn" class="win-btn" title="最小化">&#9472;</button>
<button id="maxBtn" class="win-btn" title="最大化">&#9633;</button>
<button id="closeBtn" class="win-btn win-close" title="关闭">&#10005;</button>
</div>
</div>
<div class="doctabs">
<div id="docTabs" class="doctabs-list"></div>
<button id="addTabBtn" class="doctab-add" title="打开其它书籍">&#43;</button>
</div>
<div id="annotationToolbar" class="annotation-toolbar hidden" role="toolbar" aria-label="PDF 批注工具">
<span class="annotation-title">PDF 批注</span>
<div class="annotation-tools">
<button class="annotation-tool active" data-annotation-tool="pan" title="拖拽页面" aria-label="拖拽页面">
<svg class="toolbar-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M8 11V6a1.5 1.5 0 0 1 3 0v4-6a1.5 1.5 0 0 1 3 0v6-5a1.5 1.5 0 0 1 3 0v6-3a1.5 1.5 0 0 1 3 0v5c0 5-3 8-8 8h-1c-2 0-3.5-1-4.5-2.5L3 13.5A1.5 1.5 0 0 1 5.3 12L8 14.5Z"/></svg>
</button>
<button class="annotation-tool" data-annotation-tool="text-select" title="选择正文文本" aria-label="选择正文文本">
<svg class="toolbar-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M8 4h8M12 4v16M8 20h8"/></svg>
</button>
<button class="annotation-tool" data-annotation-tool="select" title="选择、移动或缩放批注" aria-label="选择、移动或缩放批注">
<svg class="toolbar-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m5 3 13 9-6 1.5L9 19Z"/><path d="m13 14 4 6"/></svg>
</button>
<button class="annotation-tool" data-annotation-tool="pen" title="自由画笔" aria-label="自由画笔">
<svg class="toolbar-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m4 20 4.5-1 10-10a2 2 0 0 0-3-3l-10 10Z"/><path d="m14 7 3 3M4 20l1.5-4"/></svg>
</button>
<button class="annotation-tool" data-annotation-tool="highlight" title="半透明高亮笔" aria-label="半透明高亮笔">
<svg class="toolbar-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m7 15 8-11 4 3-8 11H7Z"/><path d="m13 7 4 3M4 20h16"/></svg>
</button>
<button class="annotation-tool" data-annotation-tool="rectangle" title="绘制矩形" aria-label="绘制矩形">
<svg class="toolbar-icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="4" y="5" width="16" height="14" rx="1"/></svg>
</button>
<button class="annotation-tool" data-annotation-tool="text" title="添加文本" aria-label="添加文本">
<svg class="toolbar-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 5h14M12 5v14M8 19h8"/></svg>
</button>
<button class="annotation-tool" data-annotation-tool="eraser" title="点击删除批注对象" aria-label="点击删除批注对象">
<svg class="toolbar-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m4 15 8-10 7 6-7 8H7Z"/><path d="m9 19 7-11M12 19h8"/></svg>
</button>
</div>
<span class="annotation-divider"></span>
<label class="annotation-color" title="批注颜色">
<input id="annotationColor" type="color" value="#ff4d4f" aria-label="批注颜色" />
</label>
<label class="annotation-width" title="线条粗细">
<svg class="toolbar-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M4 12h16M4 18h16" class="stroke-widths"/></svg>
<select id="annotationWidth" class="mini-select" aria-label="线条粗细">
<option value="1">1</option>
<option value="3" selected>3</option>
<option value="5">5</option>
<option value="8">8</option>
</select>
</label>
<button id="annotationUndoBtn" class="tb-btn ghost sm annotation-icon-btn" title="撤销" aria-label="撤销" disabled>
<svg class="toolbar-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m9 7-5 5 5 5"/><path d="M5 12h8a6 6 0 0 1 6 6"/></svg>
</button>
<button id="annotationRedoBtn" class="tb-btn ghost sm annotation-icon-btn" title="重做" aria-label="重做" disabled>
<svg class="toolbar-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m15 7 5 5-5 5"/><path d="M19 12h-8a6 6 0 0 0-6 6"/></svg>
</button>
<button id="annotationClearBtn" class="tb-btn danger sm annotation-icon-btn" title="清除当前页所有批注" aria-label="清除当前页所有批注">
<svg class="toolbar-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M7 7l1 13h8l1-13M10 11v5M14 11v5"/></svg>
</button>
<span id="annotationStatus" class="annotation-status">第 1 页 · 0 项</span>
<button id="annotationCloseBtn" class="icon-btn" title="收起批注工具栏">&#10005;</button>
</div>
<div class="reader-body">
<aside id="tocPane" class="side-pane side-left">
<div class="pane-head">
<span class="pane-head-title">目录</span>
<button id="tocHideBtn" class="icon-btn" title="收起目录">&#10005;</button>
</div>
<div id="tocList" class="pane-body"></div>
</aside>
<main id="docArea" class="doc-area">
<div id="docEmpty" class="doc-empty">
<div class="doc-empty-title">没有打开的书籍</div>
<div class="doc-empty-sub">点击上方的 + 从书库中选择 PDF、EPUB、MOBI、AZW 或 AZW3 图书</div>
<button id="emptyOpenBtn" class="tb-btn">从书库打开</button>
</div>
</main>
<aside id="sidePane" class="side-pane side-right">
<div class="pane-tabs">
<button class="pane-tab active" data-pane="bookmarks">书签</button>
<button class="pane-tab" data-pane="annotations">标注</button>
<button class="pane-tab" data-pane="notes">笔记</button>
<button class="pane-tab" data-pane="ai">AI 助手</button>
<button id="sideHideBtn" class="icon-btn pane-tabs-close" title="收起面板">&#10005;</button>
</div>
<div id="pane-bookmarks" class="pane-body">
<div class="pane-toolbar">
<button id="addBookmarkBtn" class="tb-btn sm">在当前位置加书签</button>
</div>
<div id="bookmarkList" class="list"></div>
</div>
<div id="pane-annotations" class="pane-body hidden">
<div id="annotationList" class="list"></div>
</div>
<div id="pane-notes" class="pane-body hidden">
<div class="pane-toolbar note-pane-toolbar">
<button id="addNoteBtn" class="tb-btn sm">+ 新建笔记</button>
<select id="noteCollectionFilter" class="mini-select" title="按笔记本筛选">
<option value="">全部笔记本</option>
</select>
</div>
<div id="noteList" class="list"></div>
</div>
<div id="pane-ai" class="pane-body hidden ai-pane">
<div id="aiStatus" class="ai-status">正在读取模型配置…</div>
<div class="ai-scope">
<span class="ai-scope-label">上下文</span>
<select id="aiScope" class="ai-scope-select" title="决定每次提问发送多少正文,范围越大消耗越多">
<option value="selection">仅选中文本</option>
<option value="page">当前页</option>
<option value="document">全文</option>
<option value="page-image" data-requires-vision="true">当前页面(图像)</option>
<option value="region-image" data-requires-vision="true">框选区域(图像)</option>
</select>
<span id="aiCost" class="ai-cost">未选中文本</span>
</div>
<div id="aiVisualCard" class="ai-visual-card hidden">
<img id="aiVisualPreview" alt="待发送的图像上下文" />
<div class="ai-visual-body">
<strong id="aiVisualLabel">图像上下文</strong>
<span id="aiVisualMeta"></span>
<span id="aiOcrStatus">OCR:尚未识别</span>
</div>
<div class="ai-visual-actions">
<button id="aiVisualReselectBtn" class="tb-btn ghost sm">重新获取</button>
<button id="aiOcrBtn" class="tb-btn ghost sm" disabled title="OCR 引擎将在后续版本接入">OCR 识别</button>
<button id="aiVisualRemoveBtn" class="tb-btn ghost sm">移除</button>
</div>
</div>
<div class="ai-quick">
<button class="tb-btn ghost sm" data-ai-task="summarize">总结</button>
</div>
<div id="aiQuote" class="ai-quote hidden"></div>
<div id="aiOutput" class="ai-output" aria-live="polite"></div>
<div id="aiError" class="ai-error hidden"></div>
<div class="ai-out-actions">
<button id="aiStopBtn" class="tb-btn danger sm hidden">停止生成</button>
<button id="aiSaveBtn" class="tb-btn sm hidden">保存为笔记</button>
<button id="aiCopyBtn" class="tb-btn ghost sm hidden">复制</button>
</div>
<div class="ai-input">
<textarea id="aiQuestion" rows="3" maxlength="4000" placeholder="基于当前章节内容提问…"></textarea>
<button id="aiSendBtn" class="tb-btn sm">发送</button>
</div>
</div>
</aside>
</div>
<div class="statusbar">
<button id="tocToggleBtn" class="tb-btn ghost sm">目录</button>
<span id="posLabel" class="status-text"></span>
<input id="progressRange" class="progress-range" type="range" min="0" max="1000" value="0" title="拖动跳转" />
<span id="pctLabel" class="status-text dim">0%</span>
<div class="spacer"></div>
<span id="statusMsg" class="status-text dim"></span>
<div class="statusbar-group">
<button id="prevBtn" class="tb-btn ghost sm" title="上一页">&#8592;</button>
<button id="nextBtn" class="tb-btn ghost sm" title="下一页">&#8594;</button>
</div>
<div class="statusbar-group">
<button id="zoomOutBtn" class="tb-btn ghost sm" title="缩小">&#8722;</button>
<span id="zoomLabel" class="status-text dim"></span>
<button id="zoomInBtn" class="tb-btn ghost sm" title="放大">&#43;</button>
<button id="fitWidthBtn" class="tb-btn ghost sm fit-width-btn hidden" title="适应内容宽度" aria-label="适应内容宽度">
<svg class="toolbar-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 5v14M20 5v14M7 12h10M10 9l-3 3 3 3M14 9l3 3-3 3"/></svg>
</button>
</div>
<div id="pdfViewControls" class="statusbar-group pdf-view-controls hidden">
<label><span>阅读</span>
<select id="pdfViewMode" class="mini-select" title="PDF 阅读方式">
<option value="continuous">连续</option>
<option value="paged">分页</option>
</select>
</label>
<label><span>版式</span>
<select id="pdfPageLayout" class="mini-select" title="PDF 页面版式">
<option value="single">单页</option>
<option value="auto">自动</option>
</select>
</label>
</div>
<select id="themeSelect" class="mini-select" title="阅读主题">
<option value="light">浅色</option>
<option value="sepia">羊皮纸</option>
<option value="dark">深色</option>
</select>
<button id="annotationToggleBtn" class="tb-btn ghost sm annotation-toggle-btn hidden" title="PDF 批注" aria-label="PDF 批注">
<svg class="toolbar-icon" viewBox="0 0 24 24" aria-hidden="true"><path d="m4 20 4.5-1 10-10a2 2 0 0 0-3-3l-10 10Z"/><path d="m14 7 3 3M4 20l1.5-4"/></svg>
</button>
<button id="sideToggleBtn" class="tb-btn ghost sm">面板</button>
</div>
<div id="selBar" class="sel-bar hidden">
<button class="sel-btn" data-sel="translate">翻译</button>
<button class="sel-btn" data-sel="explain">解释</button>
<button class="sel-btn" data-sel="excerpt">摘录</button>
<button class="sel-btn" data-sel="note">记笔记</button>
<button class="sel-btn" data-sel="bookmark">加书签</button>
<button class="sel-btn" data-sel="copy">复制</button>
</div>
<div id="toast" class="toast hidden"></div>
<div id="pickModal" class="modal hidden">
<div class="modal-box">
<div class="modal-title">从书库打开</div>
<div id="pickList" class="pick-list"></div>
<div class="modal-actions">
<button id="pickCancelBtn" class="tb-btn ghost">取消</button>
</div>
</div>
</div>
<div id="aiConfirmModal" class="modal hidden" role="dialog" aria-modal="true" aria-labelledby="aiConfirmTitle">
<div class="modal-box ai-confirm-box">
<div id="aiConfirmTitle" class="modal-title">确认发送到模型</div>
<div class="ai-confirm-summary">
<div>
<span class="ai-confirm-label">上下文</span>
<strong id="aiConfirmScope"></strong>
</div>
<div>
<span class="ai-confirm-label">预计用量</span>
<strong id="aiConfirmCost"></strong>
</div>
</div>
<p id="aiConfirmNotice" class="ai-confirm-notice">正文将发送到你配置的模型接口,并可能产生费用。PeopleLib 不会自动发送,只有确认后才会继续。</p>
<div class="modal-actions">
<button id="aiConfirmCancelBtn" class="tb-btn ghost">取消</button>
<button id="aiConfirmSendBtn" class="tb-btn">继续发送</button>
</div>
</div>
</div>
<div id="annotationClearModal" class="modal hidden" role="dialog" aria-modal="true" aria-labelledby="annotationClearTitle">
<div class="modal-box confirm-box">
<div id="annotationClearTitle" class="modal-title">清空当前页批注?</div>
<p class="ai-confirm-notice">这会删除当前 PDF 页面上的全部批注,可以立即使用“撤销”恢复。</p>
<div class="modal-actions">
<button id="annotationClearCancelBtn" class="tb-btn ghost">取消</button>
<button id="annotationClearConfirmBtn" class="tb-btn danger">清空本页</button>
</div>
</div>
</div>
<div id="noteEditorModal" class="modal hidden" role="dialog" aria-modal="true" aria-labelledby="noteEditorTitle">
<div class="modal-box note-editor-box">
<div id="noteEditorTitle" class="modal-title">新建笔记</div>
<div id="noteTypeChooser" class="note-type-chooser hidden">
<button type="button" class="note-type-choice" data-note-type="reading">
<span class="note-type-choice-title">读书笔记</span>
<span class="note-type-choice-desc">富文本、摘录和阅读心得</span>
</button>
<button type="button" class="note-type-choice" data-note-type="canvas">
<span class="note-type-choice-title">画布笔记</span>
<span class="note-type-choice-desc">分页画布、手写工具和 PDF 底版</span>
</button>
</div>
<div id="noteEditorFields" class="note-editor-fields">
<div id="noteAssociation" class="note-editor-association"></div>
<input id="noteTitleInput" class="note-editor-input" type="text" maxlength="300" placeholder="标题(可选)" />
<div id="noteRichEditor"></div>
<blockquote id="noteQuotePreview" class="note-editor-quote hidden"></blockquote>
<div class="note-editor-row">
<label>
<span>笔记本</span>
<select id="noteCollectionInput" class="mini-select">
<option value="">未分类</option>
</select>
</label>
<label class="note-editor-tags">
<span>标签</span>
<input id="noteTagsInput" class="note-editor-input" type="text" placeholder="用逗号分隔" />
</label>
</div>
<label class="note-editor-pin"><input id="notePinnedInput" type="checkbox" /> 置顶笔记</label>
</div>
<div class="modal-actions">
<button id="noteEditorCancelBtn" class="tb-btn ghost">取消</button>
<button id="noteEditorSaveBtn" class="tb-btn">保存笔记</button>
</div>
</div>
</div>
<script src="vendor/jszip.min.js"></script>
<script src="vendor/quill/quill.js"></script>
<script src="vendor/jspdf.umd.min.js"></script>
<script src="vendor/purify.min.js"></script>
<script src="vendor/markdown-it.min.js"></script>
<script src="ai-markdown.js"></script>
<script src="rich-note.js"></script>
<script src="mixed-note.js"></script>
<script type="module" src="reader/shell.mjs"></script>
</body>
</html>