papergod 0.1.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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +244 -0
  3. package/ROADMAP.md +171 -0
  4. package/example/main.tex +360 -0
  5. package/frontend/src/components/ui/badge.jsx +5 -0
  6. package/frontend/src/components/ui/button.jsx +24 -0
  7. package/frontend/src/components/workbench.jsx +182 -0
  8. package/frontend/src/lib/utils.js +6 -0
  9. package/frontend/src/main.jsx +19 -0
  10. package/frontend/src/theme.css +256 -0
  11. package/frontend/vite.config.js +23 -0
  12. package/package.json +73 -0
  13. package/papergod-demo.png +0 -0
  14. package/public/app.js +5480 -0
  15. package/public/brand/papergod-logo.png +0 -0
  16. package/public/i18n.js +95 -0
  17. package/public/index.html +480 -0
  18. package/public/pdf-sentence-mapping.js +142 -0
  19. package/public/react/app.js +209 -0
  20. package/public/react/assets/addon-fit-YJmn1quW.js +12 -0
  21. package/public/react/assets/addon-web-links-BWjmmSgS.js +12 -0
  22. package/public/react/assets/main.css +32 -0
  23. package/public/react/assets/xterm-BqvuqXEL.js +27 -0
  24. package/public/style.css +1462 -0
  25. package/src/cli.js +128 -0
  26. package/src/server/agent-adapters.js +1240 -0
  27. package/src/server/agent-errors.js +105 -0
  28. package/src/server/agent-runtime.js +81 -0
  29. package/src/server/agent.js +173 -0
  30. package/src/server/app-version.js +86 -0
  31. package/src/server/change-history.js +114 -0
  32. package/src/server/document-structure.js +174 -0
  33. package/src/server/index.js +1442 -0
  34. package/src/server/latex-structure.js +344 -0
  35. package/src/server/latex.js +67 -0
  36. package/src/server/library-engine.js +193 -0
  37. package/src/server/library-files.js +134 -0
  38. package/src/server/literature-review.js +122 -0
  39. package/src/server/orchestration-engine.js +662 -0
  40. package/src/server/paragraph-analysis.js +300 -0
  41. package/src/server/project-resources.js +290 -0
  42. package/src/server/project-store.js +808 -0
  43. package/src/server/prompt-manifest.js +300 -0
  44. package/src/server/references.js +425 -0
  45. package/src/server/review-panel.js +263 -0
  46. package/src/server/revise-workflow.js +278 -0
  47. package/src/server/revision-engine.js +607 -0
  48. package/src/server/security.js +16 -0
  49. package/src/server/text-extraction.js +149 -0
  50. package/src/server/workspace-browser.js +49 -0
  51. package/src/server/workspace-registry.js +143 -0
  52. package/src/server/workspace-terminal.js +99 -0
  53. package/src/server/workspace.js +223 -0
  54. package/src/server/zotero.js +98 -0
Binary file
package/public/i18n.js ADDED
@@ -0,0 +1,95 @@
1
+ const STORAGE_KEY = 'papergod.locale';
2
+ const DEFAULT_LOCALE = 'en';
3
+ const SUPPORTED_LOCALES = new Set(['en', 'zh-CN']);
4
+
5
+ const messages = {
6
+ en: {
7
+ 'language.label': 'Language', 'language.english': 'English', 'language.chinese': '简体中文',
8
+ 'version.product': 'Papergod', 'version.available': 'Update', 'version.releaseNotes': 'Release notes', 'version.upToDate': 'You’re up to date', 'version.updateReady': '{version} is available', 'version.installed': 'Installed', 'version.latest': 'Latest', 'version.checking': 'Checking for updates…', 'version.offline': 'Could not check for updates. Your installed version is shown above.', 'version.availableNote': 'A newer version is ready. Review what changed before updating.', 'version.currentNote': 'You’re running the latest available version.', 'version.whatsNew': 'What’s new', 'version.fixes': 'Bug fixes', 'version.notesOnRelease': 'See the complete release page for details.', 'version.noFixesListed': 'No separate bug fixes were listed for this release.', 'version.safeNote': 'Review the release details before updating.', 'version.viewUpdate': 'View update',
9
+ 'header.libraries': 'Writing libraries', 'header.focus': 'Focus annotation', 'header.review': 'Review & revise', 'header.peerReview': 'Peer review',
10
+ 'nav.outline': 'Outline', 'nav.tools': 'Tools', 'nav.paperOutline': 'Paper outline', 'nav.workspaceTools': 'Workspace tools', 'nav.openPaper': 'Open a paper',
11
+ 'tools.workspaces': 'Workspaces', 'tools.references': 'References', 'tools.terminal': 'Terminal', 'tools.openFolder': 'Open paper folder', 'tools.source': 'LaTeX source', 'tools.compile': 'Compile PDF', 'tools.changeHistory': 'Change history', 'tools.libraries': 'Writing libraries', 'tools.agentConfig': 'Agent configuration', 'tools.orchestration': 'Agent orchestration', 'tools.analysis': 'Paragraph analysis', 'tools.backendFiles': 'Backend files', 'tools.group.workspace': 'Workspace', 'tools.group.ai': 'AI assistants', 'tools.group.document': 'Document',
12
+ 'workspace.title': 'Workspaces', 'workspace.subtitle': 'Switch papers without restarting Papergod. Each folder keeps its own Agent configuration and writing data.', 'workspace.current': 'Current workspace', 'workspace.loading': 'Loading workspaces…', 'workspace.empty': 'No saved workspaces.', 'workspace.addLabel': 'Add a local folder', 'workspace.addHelp': 'Choose an existing folder, including a cloned Git repository. Empty folders receive a starter paper and writing library.', 'workspace.browse': 'Browse…', 'workspace.addSwitch': 'Add and switch', 'workspace.switch': 'Switch', 'workspace.active': 'Current', 'workspace.switching': 'Switching workspace…', 'workspace.pickerFallback': 'System picker unavailable; use the folder browser below or paste an absolute path.', 'workspace.failed': 'Could not switch workspace', 'workspace.selectFolder': 'Select this folder', 'workspace.loadingFolders': 'Loading folders…', 'workspace.emptyFolder': 'No visible subfolders.', 'workspace.gitRepo': 'Git repository',
13
+ 'terminal.title': 'Workspace Terminal', 'terminal.starting': 'Starting shell in the current workspace…', 'terminal.connecting': 'Connecting…', 'terminal.connected': 'Connected', 'terminal.exited': 'Terminal exited with code {code}', 'terminal.stop': 'Stop terminal', 'terminal.stopped': 'Terminal stopped', 'terminal.failed': 'Could not start terminal',
14
+ 'references.title': 'References', 'references.subtitle': 'One searchable citation library from local papers and Zotero Desktop.', 'references.search': 'Search title, author, year, DOI, or citekey…', 'references.writeBib': 'Update references.bib', 'references.check': 'Check citations', 'references.insertSelected': 'Insert selected', 'references.reviewButton': 'Generate review paragraph', 'references.reviewPrompt': 'What should the review paragraph cover? (optional)', 'references.folderSource': 'Literature folders', 'references.folderHelp': 'Scan BibTeX and PDF files recursively. Uncertain PDF matches remain marked for review.', 'references.addScan': 'Add & scan', 'references.noFolders': 'No folders configured.', 'references.rescan': 'Rescan folders', 'references.zoteroHelp': 'Read the local Zotero library without cloud credentials.', 'references.connectZotero': 'Connect Zotero', 'references.allZotero': 'All Zotero items', 'references.searchButton': 'Search', 'references.addSetup': 'Add bibliography setup to paper', 'references.loading': 'Loading references…',
15
+ 'editor.source': 'Source', 'editor.preview': 'PDF Preview', 'editor.save': 'Save', 'editor.compile': 'Compile',
16
+ 'ai.title': 'AI Assistant', 'ai.agentConfig': 'Agent Configuration', 'ai.configure': 'Configure', 'ai.detecting': 'Detecting local Agents…', 'ai.promptManagement': 'Prompt Management',
17
+ 'ai.intents': 'Modification intents', 'ai.intentsEmpty': 'Click PDF text to add revision comments.', 'ai.queued': '{count} queued', 'ai.tempPrompt': 'Temporary prompt', 'ai.thisRun': 'This run only', 'ai.tempPlaceholder': 'Add a one-time goal, constraint, or instruction…', 'ai.previewPrompt': 'Preview final prompt', 'ai.invoke': 'Invoke Agent',
18
+ 'activity.idle': 'Agent idle', 'activity.none': 'No Agent task has run in this session.', 'activity.context': 'Context', 'activity.agent': 'Agent', 'activity.apply': 'Apply', 'activity.compile': 'Compile', 'activity.liveOutput': 'Live CLI output will appear here.', 'activity.cancel': 'Cancel', 'activity.undo': 'Undo this revision', 'activity.preparing': 'Preparing context', 'activity.running': 'Agent running', 'activity.applying': 'Applying revision', 'activity.compiling': 'Compiling PDF', 'activity.complete': 'Revision complete', 'activity.failed': 'Agent failed', 'activity.waiting': 'Waiting for Agent output…', 'activity.cliOutput': 'CLI output', 'activity.viewCli': 'View CLI', 'activity.details': 'CLI details', 'activity.detailsTitle': 'Agent CLI details', 'activity.detailsHelp': 'Inspect the exact background command progress and provider output.',
19
+ 'pdf.word': 'Word', 'pdf.sentence': 'Sentence', 'pdf.paragraph': 'Paragraph', 'pdf.locateSource': 'Locate in LaTeX source', 'pdf.reader': 'Sentence reader', 'pdf.commentPlaceholder': 'What should the AI change?', 'pdf.cancel': 'Cancel', 'pdf.queue': 'Add to revision queue',
20
+ 'reader.title': 'Sentence reader', 'reader.close': 'Close sentence reader', 'reader.help': 'Focus on one sentence at a time. Select a word to comment on that word; leave all words unselected to comment on the whole sentence.', 'reader.scopeSentence': 'Current scope: whole sentence', 'reader.scopeWord': 'Current scope: word “{word}”', 'reader.comment': 'Revision comment', 'reader.commentPlaceholder': 'Describe how this sentence should be revised…', 'reader.previous': '← Previous sentence', 'reader.next': 'Next sentence →', 'reader.queue': 'Add to revision queue', 'reader.position': '{section} · sentence {current} of {total}',
21
+ 'agentConfig.title': 'Agent Configuration', 'agentConfig.subtitle': 'Configure local CLI Agents; unavailable adapters retain the same interface shape', 'agentConfig.provider': 'Provider', 'agentConfig.command': 'CLI command', 'agentConfig.model': 'Model override', 'agentConfig.args': 'Prefix args (one per line)', 'agentConfig.check': 'Test subscription', 'agentConfig.save': 'Save and use',
22
+ 'agent.unavailable': 'unavailable', 'agent.activeTitle': 'Active Agent: {name}', 'agent.selected': '{name} selected', 'agent.active': '{name} is now active', 'agent.mockNote': 'The built-in Mock provider runs locally without an external account.', 'agent.cliDetected': 'CLI detected.', 'agent.cliMissing': 'CLI not detected.', 'agent.authUnchecked': 'Authentication not checked.', 'agent.currentUse': 'This provider is currently in use.', 'agent.saveActivate': 'Save to make this the active provider.', 'agent.checkMock': 'Check Mock', 'agent.checkSetup': 'Test {name} subscription', 'agent.saveSettings': 'Save settings', 'agent.use': 'Use {name}', 'agent.ready': 'ready', 'agent.signin': 'sign-in needed', 'agent.notInstalled': 'not installed', 'agent.future': 'future adapter', 'agent.currently': 'Currently in use', 'agent.clickUse': 'Click to use', 'agent.activating': 'Activating {name}…', 'agent.configSaved': 'Agent configuration saved', 'agent.checking': 'Checking setup…', 'agent.checkingCommand': 'Calling {name} with a real structured subscription test…',
23
+ 'invoke.title': 'Confirm Agent Invocation', 'invoke.subtitle': 'The queued modification intents, temporary prompt, and managed context will be sent as one final prompt.', 'invoke.preparing': 'Preparing prompt…', 'invoke.cancel': 'Cancel', 'invoke.confirm': 'Confirm',
24
+ 'promptPreview.title': 'Final Agent Prompt', 'promptPreview.empty': 'Not generated yet', 'promptPreview.bodyEmpty': 'Prompt not generated yet.',
25
+ 'status.readerNoSentences': 'No readable sentences were detected', 'status.readerCommentRequired': 'Describe the revision you want first', 'status.readerQueued': 'Modification intent queued. You can continue reading.',
26
+ 'status.intentRequired': 'Describe the change you want', 'status.intentQueued': 'Modification intent queued. Add more or invoke the Agent to process the batch.',
27
+ 'history.title': 'Change history', 'history.subtitle': 'Preview and restore any of the five most recent paper versions.', 'history.refresh': 'Refresh', 'history.loading': 'Loading change history…', 'history.select': 'Select a version', 'history.selectHelp': 'Open a version to preview its compiled paper without altering the current version.', 'history.empty': 'No saved versions yet', 'history.emptyHelp': 'A version appears after an Agent or accepted revision changes the paper.', 'history.latest': 'Latest', 'history.applied': 'Applied', 'history.rolledBack': 'Rolled back', 'history.changes': '{count} changes', 'history.before': 'Before', 'history.after': 'After', 'history.reason': 'Reason', 'history.openSource': 'Open in source', 'history.rollback': 'Restore previous version', 'history.rollbackWarning': 'This restores the whole paper to the state before this revision. It is available only when no later edits would be lost.', 'history.rollbackConfirm': 'Restore the paper to the version before this revision?', 'history.restored': 'Previous version restored', 'history.currentMatch': 'This is the current paper version.', 'history.readOnly': 'Historical diff — read only', 'history.recentNotice': 'Showing the five most recent versions; complete audit data remains stored.',
28
+ 'history.restoreThis': 'Restore this version', 'history.restoreConfirm': 'Restore this historical version? Your current paper will be saved as a new recovery point.', 'history.versionRestored': 'Historical version restored; the previous paper remains recoverable.', 'history.restoreSafe': 'Historical version. Restoring it creates a new version and preserves the current paper.', 'history.pdfPreview': 'Compiled PDF preview', 'history.previewHelp': 'This isolated preview does not change the current paper.', 'history.sourceChanges': 'Source changes ({count})',
29
+ 'history.annotatedPreview': 'Annotated PDF', 'history.preparingMarks': 'Preparing revision markers…', 'history.rendering': 'Compiling and rendering this historical version…', 'history.renderFailed': 'Historical PDF could not be rendered', 'history.added': 'Added', 'history.modified': 'Modified', 'history.deleted': 'Deleted', 'history.hideMarks': 'Hide marks', 'history.showMarks': 'Show marks', 'history.previousChange': 'Previous change', 'history.nextChange': 'Next change', 'history.mappedSummary': '{mapped} of {total} changes located in the PDF', 'history.unmapped': 'Not safely located in the PDF; source diff remains available', 'history.confidence.exact': 'Exact PDF match', 'history.confidence.anchored': 'Located with surrounding text', 'history.confidence.approximate': 'Approximate location',
30
+ 'history.openPaper': 'Open a paper first', 'history.saved': '{count} AI changes saved. Review them in Tools → Change history.',
31
+ 'engine.missing': 'No LaTeX engine detected', 'engine.checkFailed': 'LaTeX engine check failed',
32
+ 'common.close': 'Close', 'common.wholeDocument': 'Whole document',
33
+ 'orch.title': 'Agent orchestration', 'orch.subtitle': 'Connect configured local Agents into reviewable multi-step workflows with approval gates. Every node output and edge summary is audited.', 'orch.new': 'New', 'orch.delete': 'Delete', 'orch.palette': 'Palette', 'orch.addAgent': '+ Agent node', 'orch.addGate': '+ Approval gate', 'orch.connect': 'Connect nodes', 'orch.deleteSelected': 'Delete selected', 'orch.clear': 'Clear results', 'orch.hint': 'Drag nodes to arrange them. Click Connect, then two nodes in order to link them.', 'orch.draft': 'Draft', 'orch.running': 'Running', 'orch.complete': 'Complete', 'orch.failed': 'Failed', 'orch.cancelled': 'Cancelled', 'orch.reset': 'Reset', 'orch.cancelRun': 'Cancel run', 'orch.run': 'Run', 'orch.inspector': 'Inspector', 'orch.noSelection': 'Select a node or edge', 'orch.selectWorkflow': 'Select a workflow', 'orch.emptyWorkflows': 'No workflows yet. Create one to start arranging Agents.', 'orch.loading': 'Loading orchestrations…', 'orch.nodeLabel': 'Label', 'orch.nodeKind': 'Type', 'orch.agentKind': 'Agent', 'orch.gateKind': 'Approval gate', 'orch.provider': 'Provider', 'orch.capability': 'Task', 'orch.capabilitySuggest': 'Suggest edits', 'orch.capabilityReview': 'Peer review', 'orch.capabilityParagraph': 'Draft paragraph', 'orch.capabilityGenerate': 'Generate paper', 'orch.prompt': 'Node prompt', 'orch.promptPlaceholder': 'What should this node do?', 'orch.inputSource': 'Input source', 'orch.inputManual': 'Manual text', 'orch.inputUpstream': 'From upstream node', 'orch.inputText': 'Input text', 'orch.inputUpstreamSelect': 'Upstream node', 'orch.reviewerRole': 'Reviewer role', 'orch.reviewerName': 'Reviewer name', 'orch.reviewerFocus': 'Reviewer focus', 'orch.apply': 'Apply', 'orch.decision': 'Decision', 'orch.decisionPending': 'Pending', 'orch.decisionApproved': 'Approved', 'orch.decisionRejected': 'Rejected', 'orch.approve': 'Approve', 'orch.reject': 'Reject', 'orch.gateNote': 'Note', 'orch.waiting': 'Waiting for approval', 'orch.output': 'Output', 'orch.outputNone': 'Not run yet', 'orch.edgeSummary': 'Input summary carried to the next node', 'orch.saved': 'Workflow saved', 'orch.created': 'Workflow created', 'orch.deleted': 'Workflow deleted', 'orch.runningStarted': 'Run started', 'orch.gateDecided': 'Gate decision recorded', 'orch.cancelledNote': 'Run cancelled', 'orch.selectFirst': 'Select the first node', 'orch.noNodes': 'Add at least one node before running', 'orch.runningBusy': 'Cannot edit while running', 'orch.edgeToSelf': 'Cannot connect a node to itself', 'orch.edgeExists': 'These nodes are already connected', 'orch.deleteConfirm': 'Delete this workflow?', 'orch.gateRejected': 'Run stopped at the rejected gate', 'orch.nodeSkipped': 'Skipped', 'orch.nodeIdle': 'Idle', 'orch.nodeQueued': 'Queued', 'orch.nodeRunning': 'Running', 'orch.nodeComplete': 'Complete', 'orch.nodeFailed': 'Failed', 'orch.manualInput': 'Manual input', 'orch.upstreamInput': 'Uses upstream output', 'orch.statusLabel': 'Status',
34
+ 'analysis.title': 'Paragraph analysis', 'analysis.subtitle': 'Sentence and paragraph lengths with standard deviation, variation index, and the formulas behind them.', 'analysis.loading': 'Analyzing…', 'analysis.units': 'Units', 'analysis.documentLevel': 'Whole document', 'analysis.sentenceChart': 'Sentence lengths', 'analysis.stats': 'Statistics', 'analysis.formulas': 'Formulas used', 'analysis.openTool': 'Analyze this paper',
35
+ 'library.extractPdf': 'Extract from PDF', 'library.extractPdfRun': 'Extract patterns', 'library.cancel': 'Cancel',
36
+ },
37
+ 'zh-CN': {
38
+ 'language.label': '语言', 'language.english': 'English', 'language.chinese': '简体中文',
39
+ 'version.product': 'Papergod', 'version.available': '有更新', 'version.releaseNotes': '版本更新', 'version.upToDate': '当前已是最新版本', 'version.updateReady': '发现新版本 {version}', 'version.installed': '当前版本', 'version.latest': '最新版本', 'version.checking': '正在检查更新……', 'version.offline': '暂时无法检查更新,上方仍会显示当前安装版本。', 'version.availableNote': '新版本已经发布,请先查看更新内容再决定是否更新。', 'version.currentNote': '你正在使用目前可用的最新版本。', 'version.whatsNew': '新增与改进', 'version.fixes': 'Bug 修复', 'version.notesOnRelease': '详细内容请查看完整版本说明。', 'version.noFixesListed': '此版本没有单独列出的 Bug 修复。', 'version.safeNote': '更新前建议先查看完整版本说明。', 'version.viewUpdate': '查看并更新',
40
+ 'header.libraries': '写作素材库', 'header.focus': '专注标注', 'header.review': '审阅与修改', 'header.peerReview': '同行评审',
41
+ 'nav.outline': '目录', 'nav.tools': '工具', 'nav.paperOutline': '论文目录', 'nav.workspaceTools': '工作区工具', 'nav.openPaper': '打开一篇论文',
42
+ 'tools.workspaces': '工作区', 'tools.references': '文献引用', 'tools.terminal': '终端', 'tools.openFolder': '打开论文文件夹', 'tools.source': 'LaTeX 源文件', 'tools.compile': '编译 PDF', 'tools.changeHistory': '更改历史', 'tools.libraries': '写作素材库', 'tools.agentConfig': 'Agent 配置', 'tools.orchestration': 'Agent 编排', 'tools.analysis': '段落分析', 'tools.backendFiles': '后台文件', 'tools.group.workspace': '工作区', 'tools.group.ai': 'AI 助手', 'tools.group.document': '文档',
43
+ 'workspace.title': '工作区', 'workspace.subtitle': '无需重启 Papergod 即可切换论文;每个文件夹分别保存 Agent 配置和写作数据。', 'workspace.current': '当前工作区', 'workspace.loading': '正在加载工作区……', 'workspace.empty': '尚无已保存的工作区。', 'workspace.addLabel': '添加本地文件夹', 'workspace.addHelp': '选择已有文件夹,也可以选择 git clone 得到的仓库。空文件夹会自动加入示例论文与写作库。', 'workspace.browse': '浏览……', 'workspace.addSwitch': '添加并切换', 'workspace.switch': '切换', 'workspace.active': '当前', 'workspace.switching': '正在切换工作区……', 'workspace.pickerFallback': '系统选择器不可用,请使用下方目录浏览器或粘贴绝对路径。', 'workspace.failed': '无法切换工作区', 'workspace.selectFolder': '选择此文件夹', 'workspace.loadingFolders': '正在加载文件夹……', 'workspace.emptyFolder': '没有可见的子文件夹。', 'workspace.gitRepo': 'Git 仓库',
44
+ 'terminal.title': '工作区终端', 'terminal.starting': '正在当前工作区启动 Shell……', 'terminal.connecting': '正在连接……', 'terminal.connected': '已连接', 'terminal.exited': '终端已退出,代码 {code}', 'terminal.stop': '停止终端', 'terminal.stopped': '终端已停止', 'terminal.failed': '无法启动终端',
45
+ 'references.title': '文献引用', 'references.subtitle': '统一搜索本地论文文件夹与 Zotero Desktop 文献。', 'references.search': '搜索标题、作者、年份、DOI 或 citekey…', 'references.writeBib': '更新 references.bib', 'references.check': '检查引用', 'references.insertSelected': '插入所选引用', 'references.reviewButton': '生成综述段落', 'references.reviewPrompt': '综述段落应覆盖什么主题?(可选)', 'references.folderSource': '文献文件夹', 'references.folderHelp': '递归扫描 BibTeX 和 PDF;不确定的 PDF 匹配会保留为待确认。', 'references.addScan': '添加并扫描', 'references.noFolders': '尚未配置文献文件夹。', 'references.rescan': '重新扫描', 'references.zoteroHelp': '无需云端密钥,直接读取本机 Zotero 文献库。', 'references.connectZotero': '连接 Zotero', 'references.allZotero': '全部 Zotero 条目', 'references.searchButton': '搜索', 'references.addSetup': '向论文添加参考文献配置', 'references.loading': '正在加载文献……',
46
+ 'editor.source': '源文件', 'editor.preview': 'PDF 预览', 'editor.save': '保存', 'editor.compile': '编译',
47
+ 'ai.title': 'AI 助手', 'ai.agentConfig': 'Agent 配置', 'ai.configure': '配置', 'ai.detecting': '正在检测本地 Agent…', 'ai.promptManagement': 'Prompt 管理',
48
+ 'ai.intents': '修改意见', 'ai.intentsEmpty': '点击 PDF 文字添加修改意见。', 'ai.queued': '已排队 {count} 条', 'ai.tempPrompt': '临时 Prompt', 'ai.thisRun': '仅本次运行', 'ai.tempPlaceholder': '添加本次运行的目标、约束或指令……', 'ai.previewPrompt': '预览最终 Prompt', 'ai.invoke': '请神',
49
+ 'activity.idle': 'Agent 空闲', 'activity.none': '本次会话尚未运行 Agent 任务。', 'activity.context': '上下文', 'activity.agent': 'Agent', 'activity.apply': '应用', 'activity.compile': '编译', 'activity.liveOutput': 'CLI 实时输出将显示在这里。', 'activity.cancel': '取消', 'activity.undo': '撤回本次修改', 'activity.preparing': '正在准备上下文', 'activity.running': 'Agent 运行中', 'activity.applying': '正在应用修改', 'activity.compiling': '正在编译 PDF', 'activity.complete': '修改完成', 'activity.failed': 'Agent 失败', 'activity.waiting': '等待 Agent 输出……', 'activity.cliOutput': 'CLI 输出', 'activity.viewCli': '查看 CLI', 'activity.details': 'CLI 详情', 'activity.detailsTitle': 'Agent CLI 详细过程', 'activity.detailsHelp': '查看后台命令的实际执行进度和服务输出。',
50
+ 'pdf.word': '词汇', 'pdf.sentence': '句子', 'pdf.paragraph': '段落', 'pdf.locateSource': '定位到 LaTeX 源码', 'pdf.reader': '逐句阅读', 'pdf.commentPlaceholder': '希望 AI 如何修改?', 'pdf.cancel': '取消', 'pdf.queue': '加入修改队列',
51
+ 'reader.title': '逐句阅读', 'reader.close': '关闭逐句阅读', 'reader.help': '一次专注一句。点击单词可只修改该词;不选择单词时,意见作用于整句。', 'reader.scopeSentence': '当前范围:整句', 'reader.scopeWord': '当前范围:单词“{word}”', 'reader.comment': '修改意见', 'reader.commentPlaceholder': '写下希望如何修改这句话……', 'reader.previous': '← 上一句', 'reader.next': '下一句 →', 'reader.queue': '加入修改队列', 'reader.position': '{section} · 第 {current} 句,共 {total} 句',
52
+ 'agentConfig.title': 'Agent 配置', 'agentConfig.subtitle': '配置本地 CLI Agent;不可用的适配器仍保留相同接口', 'agentConfig.provider': '服务', 'agentConfig.command': 'CLI 命令', 'agentConfig.model': '指定模型', 'agentConfig.args': '前置参数(每行一个)', 'agentConfig.check': '测试订阅', 'agentConfig.save': '保存并使用',
53
+ 'agent.unavailable': '不可用', 'agent.activeTitle': '当前 Agent:{name}', 'agent.selected': '已选择 {name}', 'agent.active': '{name} 已启用', 'agent.mockNote': '内置 Mock 服务在本地运行,不需要外部账户。', 'agent.cliDetected': '已检测到 CLI。', 'agent.cliMissing': '未检测到 CLI。', 'agent.authUnchecked': '尚未检查登录状态。', 'agent.currentUse': '当前正在使用此服务。', 'agent.saveActivate': '保存后将启用此服务。', 'agent.checkMock': '检查 Mock', 'agent.checkSetup': '测试 {name} 订阅', 'agent.saveSettings': '保存设置', 'agent.use': '使用 {name}', 'agent.ready': '可用', 'agent.signin': '需要登录', 'agent.notInstalled': '未安装', 'agent.future': '未来适配器', 'agent.currently': '正在使用', 'agent.clickUse': '点击使用', 'agent.activating': '正在启用 {name}…', 'agent.configSaved': 'Agent 配置已保存', 'agent.checking': '正在检查配置……', 'agent.checkingCommand': '正在通过真实结构化请求测试 {name} 订阅……',
54
+ 'invoke.title': '确认调用 Agent', 'invoke.subtitle': '队列中的修改意见、临时 Prompt 和上下文将组成一个最终 Prompt 后发送。', 'invoke.preparing': '正在准备 Prompt…', 'invoke.cancel': '取消', 'invoke.confirm': '确认',
55
+ 'promptPreview.title': '最终 Agent Prompt', 'promptPreview.empty': '尚未生成', 'promptPreview.bodyEmpty': 'Prompt 尚未生成。',
56
+ 'status.readerNoSentences': '未识别到可阅读的句子', 'status.readerCommentRequired': '请先填写修改意见', 'status.readerQueued': '修改意见已加入队列,可以继续阅读。',
57
+ 'status.intentRequired': '请描述希望如何修改', 'status.intentQueued': '修改意见已加入队列。可以继续添加,最后统一请神。',
58
+ 'history.title': '更改历史', 'history.subtitle': '预览并恢复论文最近五个版本中的任意一个。', 'history.refresh': '刷新', 'history.loading': '正在加载更改历史……', 'history.select': '选择一个版本', 'history.selectHelp': '打开版本可预览编译后的论文,不会改变当前版本。', 'history.empty': '尚无已保存版本', 'history.emptyHelp': 'Agent 或已接受的修改写入论文后,会在这里生成版本。', 'history.latest': '最新', 'history.applied': '已应用', 'history.rolledBack': '已撤回', 'history.changes': '{count} 处修改', 'history.before': '修改前', 'history.after': '修改后', 'history.reason': '原因', 'history.openSource': '在源文件中打开', 'history.rollback': '恢复上一个版本', 'history.rollbackWarning': '这会把整篇论文恢复到本次修改之前。只有不会覆盖后续编辑时才允许执行。', 'history.rollbackConfirm': '确定把论文恢复到本次修改之前吗?', 'history.restored': '已恢复上一个版本', 'history.currentMatch': '这是当前论文版本。', 'history.readOnly': '历史差异,只读', 'history.recentNotice': '仅显示最近五个版本;完整审计数据仍会保留。',
59
+ 'history.restoreThis': '恢复此版本', 'history.restoreConfirm': '确定恢复这个历史版本吗?当前论文会先保存为新的恢复点。', 'history.versionRestored': '历史版本已恢复;恢复前的论文仍可找回。', 'history.restoreSafe': '这是历史版本。恢复后会生成一个新版本,并保留当前论文。', 'history.pdfPreview': '编译后的 PDF 预览', 'history.previewHelp': '预览在隔离副本中生成,不会改变当前论文。', 'history.sourceChanges': '源码修改({count})',
60
+ 'history.annotatedPreview': '带修改标记的 PDF', 'history.preparingMarks': '正在准备修改标记……', 'history.rendering': '正在隔离编译并渲染此历史版本……', 'history.renderFailed': '无法渲染历史 PDF', 'history.added': '新增', 'history.modified': '修改', 'history.deleted': '删除', 'history.hideMarks': '隐藏标记', 'history.showMarks': '显示标记', 'history.previousChange': '上一处修改', 'history.nextChange': '下一处修改', 'history.mappedSummary': 'PDF 中已定位 {mapped}/{total} 处修改', 'history.unmapped': '无法安全定位到 PDF;仍可查看源码差异', 'history.confidence.exact': 'PDF 精确匹配', 'history.confidence.anchored': '根据上下文定位', 'history.confidence.approximate': '大致位置',
61
+ 'history.openPaper': '请先打开一篇论文', 'history.saved': '已保存 {count} 处 AI 修改。可在“工具 → 更改历史”中检查。',
62
+ 'engine.missing': '未检测到 LaTeX 编译器', 'engine.checkFailed': 'LaTeX 编译器检查失败',
63
+ 'common.close': '关闭', 'common.wholeDocument': '整篇文档',
64
+ 'orch.title': 'Agent 编排', 'orch.subtitle': '把已配置的本地 Agent 连接成带人工审批关卡、可审阅的多步工作流;每个节点的输出和每条连线的摘要都会被审计记录。', 'orch.new': '新建', 'orch.delete': '删除', 'orch.palette': '图元', 'orch.addAgent': '+ Agent 节点', 'orch.addGate': '+ 审批关卡', 'orch.connect': '连接节点', 'orch.deleteSelected': '删除选中', 'orch.clear': '清除结果', 'orch.hint': '拖动节点调整布局。点击“连接节点”后依次点击两个节点即可连线。', 'orch.draft': '草稿', 'orch.running': '运行中', 'orch.complete': '已完成', 'orch.failed': '失败', 'orch.cancelled': '已取消', 'orch.reset': '重置', 'orch.cancelRun': '取消运行', 'orch.run': '运行', 'orch.inspector': '检查器', 'orch.noSelection': '选择一个节点或连线', 'orch.selectWorkflow': '选择一个工作流', 'orch.emptyWorkflows': '还没有工作流,新建一个开始编排。', 'orch.loading': '正在加载编排……', 'orch.nodeLabel': '名称', 'orch.nodeKind': '类型', 'orch.agentKind': 'Agent', 'orch.gateKind': '审批关卡', 'orch.provider': '服务', 'orch.capability': '任务', 'orch.capabilitySuggest': '提出修改建议', 'orch.capabilityReview': '同行评审', 'orch.capabilityParagraph': '起草段落', 'orch.capabilityGenerate': '生成全文', 'orch.prompt': '节点 Prompt', 'orch.promptPlaceholder': '这个节点要做什么?', 'orch.inputSource': '输入来源', 'orch.inputManual': '手动输入', 'orch.inputUpstream': '来自上游节点', 'orch.inputText': '输入文本', 'orch.inputUpstreamSelect': '上游节点', 'orch.reviewerRole': '评审角色', 'orch.reviewerName': '评审人名称', 'orch.reviewerFocus': '评审重点', 'orch.apply': '应用', 'orch.decision': '决策', 'orch.decisionPending': '待决策', 'orch.decisionApproved': '已批准', 'orch.decisionRejected': '已拒绝', 'orch.approve': '批准', 'orch.reject': '拒绝', 'orch.gateNote': '备注', 'orch.waiting': '等待批准', 'orch.output': '输出', 'orch.outputNone': '尚未运行', 'orch.edgeSummary': '传递给下游节点的输入摘要', 'orch.saved': '工作流已保存', 'orch.created': '工作流已创建', 'orch.deleted': '工作流已删除', 'orch.runningStarted': '运行已启动', 'orch.gateDecided': '关卡决策已记录', 'orch.cancelledNote': '运行已取消', 'orch.selectFirst': '请先选择第一个节点', 'orch.noNodes': '运行前至少添加一个节点', 'orch.runningBusy': '运行中不能编辑', 'orch.edgeToSelf': '不能把节点连接到自身', 'orch.edgeExists': '这两个节点已经连过了', 'orch.deleteConfirm': '确定删除这个工作流吗?', 'orch.gateRejected': '运行在被拒绝的关卡处停止', 'orch.nodeSkipped': '已跳过', 'orch.nodeIdle': '空闲', 'orch.nodeQueued': '排队中', 'orch.nodeRunning': '运行中', 'orch.nodeComplete': '已完成', 'orch.nodeFailed': '失败', 'orch.manualInput': '手动输入', 'orch.upstreamInput': '使用上游输出', 'orch.statusLabel': '状态',
65
+ 'analysis.title': '段落分析', 'analysis.subtitle': '句子与段落长度、标准差、变异系数、变化程度指标及其公式。', 'analysis.loading': '正在分析……', 'analysis.units': '分析对象', 'analysis.documentLevel': '整篇文档', 'analysis.sentenceChart': '句子长度', 'analysis.stats': '统计指标', 'analysis.formulas': '使用的公式', 'analysis.openTool': '分析本文',
66
+ 'library.extractPdf': '从 PDF 提取', 'library.extractPdfRun': '提取句式', 'library.cancel': '取消',
67
+ },
68
+ };
69
+
70
+ export function getLocale() {
71
+ const saved = globalThis.localStorage?.getItem(STORAGE_KEY);
72
+ return SUPPORTED_LOCALES.has(saved) ? saved : DEFAULT_LOCALE;
73
+ }
74
+
75
+ export function t(key, values = {}) {
76
+ const locale = getLocale();
77
+ const template = messages[locale]?.[key] ?? messages.en[key] ?? key;
78
+ return template.replace(/\{(\w+)\}/g, (_match, name) => values[name] ?? `{${name}}`);
79
+ }
80
+
81
+ export function translateDom(root = document) {
82
+ root.querySelectorAll('[data-i18n]').forEach((element) => { element.textContent = t(element.dataset.i18n); });
83
+ root.querySelectorAll('[data-i18n-placeholder]').forEach((element) => { element.placeholder = t(element.dataset.i18nPlaceholder); });
84
+ root.querySelectorAll('[data-i18n-aria-label]').forEach((element) => { element.setAttribute('aria-label', t(element.dataset.i18nAriaLabel)); });
85
+ document.documentElement.lang = getLocale();
86
+ const selector = document.getElementById('language-select');
87
+ if (selector) selector.value = getLocale();
88
+ document.dispatchEvent(new CustomEvent('papergod:locale-changed', { detail: { locale: getLocale() } }));
89
+ }
90
+
91
+ export function setLocale(locale) {
92
+ const next = SUPPORTED_LOCALES.has(locale) ? locale : DEFAULT_LOCALE;
93
+ globalThis.localStorage?.setItem(STORAGE_KEY, next);
94
+ translateDom();
95
+ }