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.
- package/LICENSE +21 -0
- package/README.md +244 -0
- package/ROADMAP.md +171 -0
- package/example/main.tex +360 -0
- package/frontend/src/components/ui/badge.jsx +5 -0
- package/frontend/src/components/ui/button.jsx +24 -0
- package/frontend/src/components/workbench.jsx +182 -0
- package/frontend/src/lib/utils.js +6 -0
- package/frontend/src/main.jsx +19 -0
- package/frontend/src/theme.css +256 -0
- package/frontend/vite.config.js +23 -0
- package/package.json +73 -0
- package/papergod-demo.png +0 -0
- package/public/app.js +5480 -0
- package/public/brand/papergod-logo.png +0 -0
- package/public/i18n.js +95 -0
- package/public/index.html +480 -0
- package/public/pdf-sentence-mapping.js +142 -0
- package/public/react/app.js +209 -0
- package/public/react/assets/addon-fit-YJmn1quW.js +12 -0
- package/public/react/assets/addon-web-links-BWjmmSgS.js +12 -0
- package/public/react/assets/main.css +32 -0
- package/public/react/assets/xterm-BqvuqXEL.js +27 -0
- package/public/style.css +1462 -0
- package/src/cli.js +128 -0
- package/src/server/agent-adapters.js +1240 -0
- package/src/server/agent-errors.js +105 -0
- package/src/server/agent-runtime.js +81 -0
- package/src/server/agent.js +173 -0
- package/src/server/app-version.js +86 -0
- package/src/server/change-history.js +114 -0
- package/src/server/document-structure.js +174 -0
- package/src/server/index.js +1442 -0
- package/src/server/latex-structure.js +344 -0
- package/src/server/latex.js +67 -0
- package/src/server/library-engine.js +193 -0
- package/src/server/library-files.js +134 -0
- package/src/server/literature-review.js +122 -0
- package/src/server/orchestration-engine.js +662 -0
- package/src/server/paragraph-analysis.js +300 -0
- package/src/server/project-resources.js +290 -0
- package/src/server/project-store.js +808 -0
- package/src/server/prompt-manifest.js +300 -0
- package/src/server/references.js +425 -0
- package/src/server/review-panel.js +263 -0
- package/src/server/revise-workflow.js +278 -0
- package/src/server/revision-engine.js +607 -0
- package/src/server/security.js +16 -0
- package/src/server/text-extraction.js +149 -0
- package/src/server/workspace-browser.js +49 -0
- package/src/server/workspace-registry.js +143 -0
- package/src/server/workspace-terminal.js +99 -0
- package/src/server/workspace.js +223 -0
- package/src/server/zotero.js +98 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
const TOKEN_PATTERN = /[\p{L}\p{N}]+(?:['’][\p{L}\p{N}]+)*/gu;
|
|
2
|
+
|
|
3
|
+
export function textTokens(text) {
|
|
4
|
+
return [...String(text || '').matchAll(TOKEN_PATTERN)].map((match, index) => ({
|
|
5
|
+
value: match[0].toLocaleLowerCase(),
|
|
6
|
+
start: match.index,
|
|
7
|
+
end: match.index + match[0].length,
|
|
8
|
+
index,
|
|
9
|
+
}));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function alignTokenSequence(sourceTokens, pdfTokens, fromToken) {
|
|
13
|
+
const sourceLength = sourceTokens.length;
|
|
14
|
+
if (!sourceLength || fromToken >= pdfTokens.length) return null;
|
|
15
|
+
const searchLength = Math.min(
|
|
16
|
+
pdfTokens.length - fromToken,
|
|
17
|
+
Math.max(180, Math.min(1200, sourceLength * 5 + 120)),
|
|
18
|
+
);
|
|
19
|
+
const window = pdfTokens.slice(fromToken, fromToken + searchLength);
|
|
20
|
+
const windowLength = window.length;
|
|
21
|
+
let previous = new Float64Array(windowLength + 1);
|
|
22
|
+
const trace = Array.from({ length: sourceLength + 1 }, () => new Uint8Array(windowLength + 1));
|
|
23
|
+
|
|
24
|
+
for (let sourceIndex = 1; sourceIndex <= sourceLength; sourceIndex += 1) {
|
|
25
|
+
const current = new Float64Array(windowLength + 1);
|
|
26
|
+
current[0] = sourceIndex * -2;
|
|
27
|
+
trace[sourceIndex][0] = 2;
|
|
28
|
+
for (let pdfIndex = 1; pdfIndex <= windowLength; pdfIndex += 1) {
|
|
29
|
+
const equal = sourceTokens[sourceIndex - 1].value === window[pdfIndex - 1].value;
|
|
30
|
+
const diagonal = previous[pdfIndex - 1] + (equal ? 4 : -3);
|
|
31
|
+
const skipSource = previous[pdfIndex] - 2;
|
|
32
|
+
const skipPdf = current[pdfIndex - 1] - 1;
|
|
33
|
+
if (diagonal >= skipSource && diagonal >= skipPdf) {
|
|
34
|
+
current[pdfIndex] = diagonal;
|
|
35
|
+
trace[sourceIndex][pdfIndex] = 1;
|
|
36
|
+
} else if (skipSource >= skipPdf) {
|
|
37
|
+
current[pdfIndex] = skipSource;
|
|
38
|
+
trace[sourceIndex][pdfIndex] = 2;
|
|
39
|
+
} else {
|
|
40
|
+
current[pdfIndex] = skipPdf;
|
|
41
|
+
trace[sourceIndex][pdfIndex] = 3;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
previous = current;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let bestEnd = 1;
|
|
48
|
+
for (let pdfIndex = 2; pdfIndex <= windowLength; pdfIndex += 1) {
|
|
49
|
+
if (previous[pdfIndex] > previous[bestEnd]) bestEnd = pdfIndex;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const matches = [];
|
|
53
|
+
let sourceIndex = sourceLength;
|
|
54
|
+
let pdfIndex = bestEnd;
|
|
55
|
+
while (sourceIndex > 0 && pdfIndex >= 0) {
|
|
56
|
+
const direction = trace[sourceIndex][pdfIndex];
|
|
57
|
+
if (direction === 1) {
|
|
58
|
+
if (pdfIndex > 0 && sourceTokens[sourceIndex - 1].value === window[pdfIndex - 1].value) {
|
|
59
|
+
matches.push({ sourceIndex: sourceIndex - 1, pdfIndex: fromToken + pdfIndex - 1 });
|
|
60
|
+
}
|
|
61
|
+
sourceIndex -= 1;
|
|
62
|
+
pdfIndex -= 1;
|
|
63
|
+
} else if (direction === 2) {
|
|
64
|
+
sourceIndex -= 1;
|
|
65
|
+
} else if (direction === 3) {
|
|
66
|
+
pdfIndex -= 1;
|
|
67
|
+
} else {
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
matches.reverse();
|
|
72
|
+
if (!matches.length) return null;
|
|
73
|
+
|
|
74
|
+
const firstToken = matches[0].pdfIndex;
|
|
75
|
+
const lastToken = matches.at(-1).pdfIndex;
|
|
76
|
+
const coverage = matches.length / sourceLength;
|
|
77
|
+
const density = matches.length / Math.max(1, lastToken - firstToken + 1);
|
|
78
|
+
const minimumMatches = sourceLength === 1 ? 1 : sourceLength <= 3 ? sourceLength : Math.max(3, Math.ceil(sourceLength * 0.62));
|
|
79
|
+
if (matches.length < minimumMatches || coverage < (sourceLength <= 3 ? 1 : 0.62) || density < 0.38) return null;
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
firstToken,
|
|
83
|
+
lastToken,
|
|
84
|
+
matches: matches.length,
|
|
85
|
+
sourceTokens: sourceLength,
|
|
86
|
+
coverage,
|
|
87
|
+
density,
|
|
88
|
+
confidence: (coverage * 0.75) + (density * 0.25),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function extendMappedRange(pdfText, sourceText, start, end) {
|
|
93
|
+
while (start > 0 && /["'“‘(([]/u.test(pdfText[start - 1])) start -= 1;
|
|
94
|
+
const sourceTail = String(sourceText || '').trim().match(/[.!?。!?:;]["'”’))\]]*$/u)?.[0]?.[0] || '';
|
|
95
|
+
if (!sourceTail) return { start, end };
|
|
96
|
+
const terminalPattern = '.!?。!?'.includes(sourceTail) ? /[.!?。!?]/u : new RegExp(`[${sourceTail}]`, 'u');
|
|
97
|
+
const limit = Math.min(pdfText.length, end + 120);
|
|
98
|
+
let tokenCount = 0;
|
|
99
|
+
for (let cursor = end; cursor < limit; cursor += 1) {
|
|
100
|
+
if (terminalPattern.test(pdfText[cursor])) {
|
|
101
|
+
end = cursor + 1;
|
|
102
|
+
while (end < pdfText.length && /["'”’))\]]/u.test(pdfText[end])) end += 1;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
if (/\s/u.test(pdfText[cursor]) && /[\p{L}\p{N}]/u.test(pdfText[cursor + 1] || '')) {
|
|
106
|
+
tokenCount += 1;
|
|
107
|
+
if (tokenCount > 12) break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { start, end };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function buildOrderedSentenceMappings(pdfText, sentenceItems) {
|
|
114
|
+
const pdfTokens = textTokens(pdfText);
|
|
115
|
+
const mappings = [];
|
|
116
|
+
let cursor = 0;
|
|
117
|
+
for (const item of sentenceItems || []) {
|
|
118
|
+
const matchText = String(item.matchText || item.text || '');
|
|
119
|
+
const sourceTokens = textTokens(matchText);
|
|
120
|
+
const alignment = alignTokenSequence(sourceTokens, pdfTokens, cursor);
|
|
121
|
+
if (!alignment) continue;
|
|
122
|
+
const first = pdfTokens[alignment.firstToken];
|
|
123
|
+
const last = pdfTokens[alignment.lastToken];
|
|
124
|
+
const range = extendMappedRange(pdfText, matchText, first.start, last.end);
|
|
125
|
+
mappings.push({
|
|
126
|
+
...item,
|
|
127
|
+
...range,
|
|
128
|
+
confidence: alignment.confidence,
|
|
129
|
+
matchedTokens: alignment.matches,
|
|
130
|
+
sourceTokenCount: alignment.sourceTokens,
|
|
131
|
+
pdfStartToken: alignment.firstToken,
|
|
132
|
+
pdfEndToken: alignment.lastToken,
|
|
133
|
+
});
|
|
134
|
+
cursor = alignment.lastToken + 1;
|
|
135
|
+
}
|
|
136
|
+
return { mappings, pdfTokens };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function sentenceMappingAt(mappings, pdfIndex) {
|
|
140
|
+
if (!Number.isInteger(pdfIndex)) return null;
|
|
141
|
+
return (mappings || []).find((mapping) => mapping.start <= pdfIndex && pdfIndex < mapping.end) || null;
|
|
142
|
+
}
|