papilot 0.7.0 → 0.8.1
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/README.md +53 -100
- package/README.zh-CN.md +52 -97
- package/bin/doctor.mjs +55 -6
- package/bin/installation.d.mts +2 -0
- package/bin/installation.mjs +7 -1
- package/dist/assets/{_virtual_papilot-licenses-BWIsfrQh.js → _virtual_papilot-licenses-CMfCtEYQ.js} +54 -3
- package/dist/assets/index-BlLyIqT9.js +265 -0
- package/dist/assets/index-DVZtyZvu.css +1 -0
- package/dist/assets/latex-BarqJFtQ.svg +1 -0
- package/dist/assets/{math-BsEl3u6P.js → math-Bu3xEyrC.js} +1 -1
- package/dist/assets/pdf-view-PbkCuTZ-.js +598 -0
- package/dist/index.html +2 -2
- package/examples/papilot-paper/main.tex +1 -1
- package/examples/papilot-paper/preview/main.pdf +0 -0
- package/examples/papilot-paper/preview/main.synctex.gz +0 -0
- package/examples/papilot-paper/sections/02-using.tex +3 -1
- package/examples/papilot-paper/sections/09-agents.tex +8 -5
- package/package.json +18 -8
- package/skills/ai-review.md +18 -0
- package/skills/latex-warnings.md +1 -1
- package/src/conversation.ts +6 -0
- package/src/server/acp.ts +387 -0
- package/src/server/agents.ts +220 -43
- package/src/server/ai-review-routes.ts +72 -0
- package/src/server/ai-review.ts +38 -0
- package/src/server/app.ts +56 -14
- package/src/server/build-problems.ts +329 -0
- package/src/server/changes.ts +25 -2
- package/src/server/codex.ts +2 -1
- package/src/server/cursor.ts +153 -0
- package/src/server/desktop-demo.ts +38 -0
- package/src/server/latex-installation.ts +43 -0
- package/src/server/manuscript.ts +183 -23
- package/src/server/paths.ts +1 -1
- package/src/server/reconcile.ts +20 -0
- package/src/server/references.ts +70 -0
- package/src/server/review.ts +6 -5
- package/src/server/skills.ts +5 -8
- package/src/server/start.ts +19 -4
- package/src/server/store.ts +20 -2
- package/src/server/update-install.ts +53 -2
- package/src/server/update-worker.ts +6 -6
- package/src/server/updates.ts +49 -14
- package/src/server/workspace.ts +236 -41
- package/src/shared.ts +88 -9
- package/src/skill-icons.ts +18 -0
- package/src/skill-metadata.ts +45 -0
- package/vendor/latex-review/dist/web/assets/index-CXdnUjct.js +19 -0
- package/vendor/latex-review/dist/web/assets/index-DEz-LsGl.css +1 -0
- package/vendor/latex-review/dist/web/index.html +2 -2
- package/vendor/latex-review/src/core/bib.ts +692 -0
- package/vendor/latex-review/src/core/changes.ts +39 -0
- package/vendor/latex-review/src/core/current-commit.ts +104 -0
- package/vendor/latex-review/src/core/delivery.ts +62 -16
- package/vendor/latex-review/src/core/git.ts +2 -1
- package/vendor/latex-review/src/core/publish.ts +7 -1
- package/vendor/latex-review/src/core/reconcile.ts +101 -0
- package/vendor/latex-review/src/core/review.ts +6 -2
- package/vendor/latex-review/src/core/store.ts +1 -0
- package/vendor/latex-review/src/core/tasks.ts +1 -1
- package/vendor/latex-review/src/core/verify.ts +784 -0
- package/vendor/latex-review/src/core/view.ts +204 -0
- package/vendor/latex-review/src/render/round.ts +11 -0
- package/vendor/latex-review/src/server.ts +77 -1
- package/vendor/latex-review/src/types.ts +130 -2
- package/vendor/latex-review/src/web/effect-preferences.ts +10 -0
- package/vendor/latex-review/src/web/references.tsx +499 -0
- package/vendor/latex-review/src/web/review.tsx +263 -133
- package/vendor/latex-review/src/web/style.css +285 -0
- package/vendor/latex-review/src/web/zh-CN.ts +79 -2
- package/dist/assets/index-CIJEYlP7.js +0 -252
- package/dist/assets/index-hOJEruFc.css +0 -1
- package/dist/assets/pdf-view-Cz9_vK5U.js +0 -598
- package/vendor/latex-review/dist/web/assets/index-Be9CtgcZ.css +0 -1
- package/vendor/latex-review/dist/web/assets/index-TvMWVSaw.js +0 -19
|
@@ -18,8 +18,16 @@ import type {
|
|
|
18
18
|
} from "../types.ts";
|
|
19
19
|
import { PdfStrip } from "./PdfStrip.tsx";
|
|
20
20
|
import { ChangeMap } from "./ChangeMap.tsx";
|
|
21
|
+
import { EFFECTS_EVENT, EFFECTS_STORAGE, readExpandedEffects } from "./effect-preferences.ts";
|
|
21
22
|
import { STATUS } from "./overview.tsx";
|
|
22
|
-
import { locale, t, tn, withLanguage } from "./i18n.ts";
|
|
23
|
+
import { language, locale, t, tn, withLanguage } from "./i18n.ts";
|
|
24
|
+
import {
|
|
25
|
+
ConfirmReferences,
|
|
26
|
+
ReferenceBanner,
|
|
27
|
+
ReferenceEntries,
|
|
28
|
+
risks,
|
|
29
|
+
useReferenceChecks,
|
|
30
|
+
} from "./references.tsx";
|
|
23
31
|
import { bindingLabel, readShortcuts, resolveShortcuts, shortcutAction, SHORTCUT_EVENT, SHORTCUT_STORAGE } from "./shortcuts.ts";
|
|
24
32
|
import {
|
|
25
33
|
STATE,
|
|
@@ -137,6 +145,13 @@ export function ReviewApp({
|
|
|
137
145
|
Record<string, PlaceView[] | "loading" | { error: string }>
|
|
138
146
|
>({}),
|
|
139
147
|
[composer, setComposer] = useState<Composer | null>(null),
|
|
148
|
+
// Accepting bibliography entries that are not verified waits for this.
|
|
149
|
+
[confirm, setConfirm] = useState<{
|
|
150
|
+
kind: Kind;
|
|
151
|
+
chosen: string[];
|
|
152
|
+
body: string;
|
|
153
|
+
items: { label: string; reasons: string[] }[];
|
|
154
|
+
} | null>(null),
|
|
140
155
|
[draft, setDraft] = useState(""),
|
|
141
156
|
[help, setHelp] = useState(false),
|
|
142
157
|
// A note can reveal a decided change without opening the history controls.
|
|
@@ -152,6 +167,18 @@ export function ReviewApp({
|
|
|
152
167
|
scrollUntil = useRef(0),
|
|
153
168
|
snapshot = useRef({ state, detail, busy, composer, current });
|
|
154
169
|
const [shortcuts, setShortcuts] = useState(readShortcuts);
|
|
170
|
+
const [expandedEffects, setExpandedEffects] = useState(readExpandedEffects);
|
|
171
|
+
// Per-edit disclosure is temporary and scoped to its round. Changing the
|
|
172
|
+
// global preference resets those overrides without reloading the review.
|
|
173
|
+
const [effectOverrides, setEffectOverrides] = useState<Record<string, boolean>>({});
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
const changed = (event: Event) => setExpandedEffects((event as CustomEvent).detail === true);
|
|
176
|
+
const stored = (event: StorageEvent) => { if (event.key === EFFECTS_STORAGE || event.key === null) setExpandedEffects(readExpandedEffects()); };
|
|
177
|
+
window.addEventListener(EFFECTS_EVENT, changed);
|
|
178
|
+
window.addEventListener('storage', stored);
|
|
179
|
+
return () => { window.removeEventListener(EFFECTS_EVENT, changed); window.removeEventListener('storage', stored); };
|
|
180
|
+
}, []);
|
|
181
|
+
useEffect(() => { setEffectOverrides({}); }, [expandedEffects]);
|
|
155
182
|
useEffect(() => {
|
|
156
183
|
const changed = (event: Event) => setShortcuts(resolveShortcuts((event as CustomEvent).detail));
|
|
157
184
|
const stored = (event: StorageEvent) => { if (event.key === SHORTCUT_STORAGE || event.key === null) setShortcuts(readShortcuts()); };
|
|
@@ -191,6 +218,13 @@ export function ReviewApp({
|
|
|
191
218
|
undecided = Object.keys(decisions).filter(
|
|
192
219
|
(id) => decisions[id] === "pending",
|
|
193
220
|
),
|
|
221
|
+
references = useReferenceChecks(detail, api),
|
|
222
|
+
hazards = useMemo(
|
|
223
|
+
() => risks(detail?.references, references.checks, references.pending, references.confirmed),
|
|
224
|
+
[detail, references.checks, references.pending, references.confirmed],
|
|
225
|
+
),
|
|
226
|
+
// Batch acceptance leaves the changes whose entries need a look.
|
|
227
|
+
batch = undecided.filter((id) => !hazards.has(id)),
|
|
194
228
|
latest = Boolean(
|
|
195
229
|
detail && state && detail.round === state.latest && !state.closed,
|
|
196
230
|
),
|
|
@@ -198,6 +232,9 @@ export function ReviewApp({
|
|
|
198
232
|
label = (kind: string) => t((changes ? CHANGE_ACTION : ACTION)[kind] ?? kind),
|
|
199
233
|
stateLabel = (value: string) =>
|
|
200
234
|
t((changes ? CHANGE_STATE : STATE)[value] ?? value);
|
|
235
|
+
// Keyboard handlers read the newest checks, not those of their render.
|
|
236
|
+
const hazardsNow = useRef(hazards);
|
|
237
|
+
hazardsNow.current = hazards;
|
|
201
238
|
// What each button acts on: undecided changes, or in changes mode the
|
|
202
239
|
// staged ones for Unstage and every change still on disk for Revert.
|
|
203
240
|
const targets = (units: string[], kind: Kind) =>
|
|
@@ -281,11 +318,20 @@ export function ReviewApp({
|
|
|
281
318
|
const article = paper.current?.querySelector<HTMLElement>(
|
|
282
319
|
`[data-stop="${index}"]`,
|
|
283
320
|
);
|
|
284
|
-
article
|
|
321
|
+
if (article) reveal(article, "smooth");
|
|
285
322
|
article?.focus({ preventScroll: true });
|
|
286
323
|
});
|
|
287
324
|
}
|
|
288
325
|
}
|
|
326
|
+
function reveal(article: HTMLElement, behavior: ScrollBehavior = "instant") {
|
|
327
|
+
const root = paper.current;
|
|
328
|
+
if (!root) return;
|
|
329
|
+
// scrollIntoView also scrolls ancestors across the embedding iframe.
|
|
330
|
+
// Navigation belongs to the paper, independently of the host's Chat.
|
|
331
|
+
const padding = parseFloat(getComputedStyle(root).scrollPaddingTop) || 0;
|
|
332
|
+
const margin = parseFloat(getComputedStyle(article).scrollMarginTop) || 0;
|
|
333
|
+
root.scrollTo({ top: root.scrollTop + article.getBoundingClientRect().top - root.getBoundingClientRect().top - padding - margin, behavior });
|
|
334
|
+
}
|
|
289
335
|
useEffect(() => {
|
|
290
336
|
let cancelled = false,
|
|
291
337
|
timer: ReturnType<typeof setTimeout>;
|
|
@@ -369,11 +415,10 @@ export function ReviewApp({
|
|
|
369
415
|
}, [task, pinned]);
|
|
370
416
|
useEffect(() => {
|
|
371
417
|
if (detail && current >= 0)
|
|
372
|
-
requestAnimationFrame(() =>
|
|
373
|
-
paper.current
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
);
|
|
418
|
+
requestAnimationFrame(() => {
|
|
419
|
+
const article = paper.current?.querySelector<HTMLElement>(`[data-stop="${current}"]`);
|
|
420
|
+
if (article) reveal(article);
|
|
421
|
+
});
|
|
377
422
|
}, [detail?.round]);
|
|
378
423
|
useEffect(() => {
|
|
379
424
|
if (state) document.title = `${t(state.title)} · LaTeX Review`;
|
|
@@ -408,6 +453,7 @@ export function ReviewApp({
|
|
|
408
453
|
kind: Kind,
|
|
409
454
|
chosen = targets(stops[current]?.units ?? [], kind),
|
|
410
455
|
body = "",
|
|
456
|
+
confirmed = false,
|
|
411
457
|
) {
|
|
412
458
|
if (
|
|
413
459
|
!chosen.length ||
|
|
@@ -417,6 +463,13 @@ export function ReviewApp({
|
|
|
417
463
|
blocked(kind)
|
|
418
464
|
)
|
|
419
465
|
return;
|
|
466
|
+
if (kind === "accept" && !confirmed) {
|
|
467
|
+
const items = chosen.flatMap((id) => hazardsNow.current.get(id) ?? []);
|
|
468
|
+
if (items.length) {
|
|
469
|
+
setConfirm({ kind, chosen, body, items });
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
420
473
|
epoch.current++;
|
|
421
474
|
setBusy(true);
|
|
422
475
|
snapshot.current.busy = true;
|
|
@@ -530,7 +583,8 @@ export function ReviewApp({
|
|
|
530
583
|
typing =
|
|
531
584
|
target.matches("input,textarea,select") || target.isContentEditable;
|
|
532
585
|
if (event.key === "Escape") {
|
|
533
|
-
if (
|
|
586
|
+
if (confirm) setConfirm(null);
|
|
587
|
+
else if (help) setHelp(false);
|
|
534
588
|
else if (composer) closeComposer();
|
|
535
589
|
return;
|
|
536
590
|
}
|
|
@@ -543,7 +597,7 @@ export function ReviewApp({
|
|
|
543
597
|
void submitComposer();
|
|
544
598
|
return;
|
|
545
599
|
}
|
|
546
|
-
if (typing || help || window.getSelection()?.toString()) return;
|
|
600
|
+
if (typing || help || confirm || window.getSelection()?.toString()) return;
|
|
547
601
|
if ((event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey && event.key.toLowerCase() === "z") {
|
|
548
602
|
event.preventDefault();
|
|
549
603
|
void undo();
|
|
@@ -568,7 +622,7 @@ export function ReviewApp({
|
|
|
568
622
|
};
|
|
569
623
|
window.addEventListener("keydown", listener);
|
|
570
624
|
return () => window.removeEventListener("keydown", listener);
|
|
571
|
-
}, [state, detail, current, busy, composer, draft, help, history, shortcuts]);
|
|
625
|
+
}, [state, detail, current, busy, composer, draft, help, confirm, history, shortcuts]);
|
|
572
626
|
function onScroll() {
|
|
573
627
|
if (
|
|
574
628
|
!paper.current ||
|
|
@@ -698,7 +752,10 @@ export function ReviewApp({
|
|
|
698
752
|
clean: boolean,
|
|
699
753
|
): ReactNode {
|
|
700
754
|
const { total, shown } = unit.effects!,
|
|
701
|
-
|
|
755
|
+
key = `${detail!.round}:${unit.id}`,
|
|
756
|
+
open = effectOverrides[key] ?? (expandedEffects || unit.visual === "unmapped" || unit.visual === "identical"),
|
|
757
|
+
bodyId = `effects-${detail!.round}-${unit.id}`,
|
|
758
|
+
loaded = more[key],
|
|
702
759
|
places = [...shown, ...(Array.isArray(loaded) ? loaded : [])],
|
|
703
760
|
strips = (items: Strip[], side: "before" | "after") =>
|
|
704
761
|
items.length > 0 && (
|
|
@@ -720,47 +777,57 @@ export function ReviewApp({
|
|
|
720
777
|
return (
|
|
721
778
|
<section className="effects" key={unit.id}>
|
|
722
779
|
<p className="effects-head">
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
<p className="place-where">
|
|
728
|
-
{t("p. {pages}", { pages: place.page })}
|
|
729
|
-
{place.section ? ` · ${place.section}` : ""}
|
|
730
|
-
{place.with
|
|
731
|
-
? ` · ${t("with {changes}", {
|
|
732
|
-
changes: place.with
|
|
733
|
-
.map((id) =>
|
|
734
|
-
firstStop.has(id)
|
|
735
|
-
? t("Change {n}", { n: firstStop.get(id)! + 1 })
|
|
736
|
-
: (units.get(id)?.file ?? id),
|
|
737
|
-
)
|
|
738
|
-
.join(", "),
|
|
739
|
-
})}`
|
|
740
|
-
: ""}
|
|
741
|
-
</p>
|
|
742
|
-
{strips(place.before, "before")}
|
|
743
|
-
{strips(place.after, "after")}
|
|
744
|
-
</div>
|
|
745
|
-
))}
|
|
746
|
-
{total > places.length && (
|
|
747
|
-
<button
|
|
748
|
-
className="link more-places"
|
|
749
|
-
disabled={loaded === "loading"}
|
|
750
|
-
onClick={() => void loadPlaces(unit.id)}
|
|
751
|
-
>
|
|
752
|
-
{loaded === "loading"
|
|
753
|
-
? t("Loading…")
|
|
754
|
-
: tn(
|
|
755
|
-
total - places.length,
|
|
756
|
-
"Show {n} more",
|
|
757
|
-
"Show {n} more",
|
|
758
|
-
)}
|
|
780
|
+
<button className="link effects-toggle" aria-expanded={open} aria-controls={bodyId}
|
|
781
|
+
onClick={() => setEffectOverrides((values) => ({ ...values, [key]: !open }))}>
|
|
782
|
+
<span aria-hidden="true">{open ? "▾" : "▸"}</span>{" "}
|
|
783
|
+
{tn(total, "Affects {n} place", "Affects {n} places")}
|
|
759
784
|
</button>
|
|
760
|
-
|
|
761
|
-
{
|
|
762
|
-
|
|
763
|
-
|
|
785
|
+
</p>
|
|
786
|
+
<div id={bodyId} hidden={!open}>
|
|
787
|
+
{open && (
|
|
788
|
+
<>
|
|
789
|
+
{places.map((place, i) => (
|
|
790
|
+
<div className="place" key={i}>
|
|
791
|
+
<p className="place-where">
|
|
792
|
+
{t("p. {pages}", { pages: place.page })}
|
|
793
|
+
{place.section ? ` · ${place.section}` : ""}
|
|
794
|
+
{place.with
|
|
795
|
+
? ` · ${t("with {changes}", {
|
|
796
|
+
changes: place.with
|
|
797
|
+
.map((id) =>
|
|
798
|
+
firstStop.has(id)
|
|
799
|
+
? t("Change {n}", { n: firstStop.get(id)! + 1 })
|
|
800
|
+
: (units.get(id)?.file ?? id),
|
|
801
|
+
)
|
|
802
|
+
.join(", "),
|
|
803
|
+
})}`
|
|
804
|
+
: ""}
|
|
805
|
+
</p>
|
|
806
|
+
{strips(place.before, "before")}
|
|
807
|
+
{strips(place.after, "after")}
|
|
808
|
+
</div>
|
|
809
|
+
))}
|
|
810
|
+
{total > places.length && (
|
|
811
|
+
<button
|
|
812
|
+
className="link more-places"
|
|
813
|
+
disabled={loaded === "loading"}
|
|
814
|
+
onClick={() => void loadPlaces(unit.id)}
|
|
815
|
+
>
|
|
816
|
+
{loaded === "loading"
|
|
817
|
+
? t("Loading…")
|
|
818
|
+
: tn(
|
|
819
|
+
total - places.length,
|
|
820
|
+
"Show {n} more",
|
|
821
|
+
"Show {n} more",
|
|
822
|
+
)}
|
|
823
|
+
</button>
|
|
824
|
+
)}
|
|
825
|
+
{loaded && typeof loaded === "object" && "error" in loaded && (
|
|
826
|
+
<p className="caption">{loaded.error}</p>
|
|
827
|
+
)}
|
|
828
|
+
</>
|
|
829
|
+
)}
|
|
830
|
+
</div>
|
|
764
831
|
</section>
|
|
765
832
|
);
|
|
766
833
|
}
|
|
@@ -770,6 +837,10 @@ export function ReviewApp({
|
|
|
770
837
|
inline: boolean,
|
|
771
838
|
): ReactNode {
|
|
772
839
|
const known = block.units.map((id) => units.get(id)!).filter(Boolean),
|
|
840
|
+
// A .bib edit is shown entry by entry, every field, rather than as
|
|
841
|
+
// the bibliography's typeset lines alone.
|
|
842
|
+
bib = detail!.references?.entries.filter((e) => (e.units ?? [e.unit]).some((id) => block.units.includes(id))) ?? [],
|
|
843
|
+
uncited = detail!.references?.missing.filter((m) => m.unit && block.units.includes(m.unit)) ?? [],
|
|
773
844
|
status = stopState(block.units, decisions),
|
|
774
845
|
reviewed = !pendingUnits(stops[index], decisions).length,
|
|
775
846
|
expanded = browsing || index === linkedChange,
|
|
@@ -785,15 +856,17 @@ export function ReviewApp({
|
|
|
785
856
|
(status === "commented" ? "after" : undefined),
|
|
786
857
|
source =
|
|
787
858
|
openSource.has(index) ||
|
|
788
|
-
|
|
789
|
-
|
|
859
|
+
(!bib.length &&
|
|
860
|
+
(block.identical || (!block.before.length && !block.after.length))),
|
|
790
861
|
remarks = [
|
|
791
862
|
...new Set(
|
|
792
863
|
block.units.map((id) => state?.comments[id]).filter(Boolean),
|
|
793
864
|
),
|
|
794
865
|
],
|
|
795
866
|
location = describeLocation(known, t),
|
|
796
|
-
kindLabel =
|
|
867
|
+
kindLabel = bib.length
|
|
868
|
+
? t("Bibliography")
|
|
869
|
+
: block.reflow
|
|
797
870
|
? t("Reflow")
|
|
798
871
|
: [
|
|
799
872
|
...new Set(
|
|
@@ -905,7 +978,7 @@ export function ReviewApp({
|
|
|
905
978
|
block.before.length > 0 &&
|
|
906
979
|
images(block.before, "before")}
|
|
907
980
|
{block.after.length > 0 && images(block.after, "after")}
|
|
908
|
-
{block.identical && (
|
|
981
|
+
{block.identical && !bib.length && (
|
|
909
982
|
<p className="caption">
|
|
910
983
|
{t("No visible change in the PDF; see the LaTeX source.")}
|
|
911
984
|
</p>
|
|
@@ -925,6 +998,25 @@ export function ReviewApp({
|
|
|
925
998
|
))}
|
|
926
999
|
</div>
|
|
927
1000
|
)}
|
|
1001
|
+
{uncited.length > 0 && (
|
|
1002
|
+
<p className="reference-problem reference-uncited">
|
|
1003
|
+
{uncited
|
|
1004
|
+
.map((m) => t("Cites {key}, which no bibliography entry defines.", { key: m.key }))
|
|
1005
|
+
.join(language() === "zh-CN" ? "" : " ")}
|
|
1006
|
+
</p>
|
|
1007
|
+
)}
|
|
1008
|
+
{bib.length > 0 && (
|
|
1009
|
+
<ReferenceEntries
|
|
1010
|
+
entries={bib}
|
|
1011
|
+
checks={references.checks}
|
|
1012
|
+
pending={references.pending > 0}
|
|
1013
|
+
onRetry={references.retry}
|
|
1014
|
+
confirmed={references.confirmed}
|
|
1015
|
+
onConfirm={(id, on) =>
|
|
1016
|
+
void references.confirm(id, on).catch((e) => setToast((e as Error).message))
|
|
1017
|
+
}
|
|
1018
|
+
/>
|
|
1019
|
+
)}
|
|
928
1020
|
{source && (
|
|
929
1021
|
<div className="source">
|
|
930
1022
|
{known.map((unit) => (
|
|
@@ -1036,7 +1128,7 @@ export function ReviewApp({
|
|
|
1036
1128
|
{/* Batch decisions cover only undecided changes; accepted,
|
|
1037
1129
|
rejected and commented changes keep their state. A single
|
|
1038
1130
|
undecided change is decided from its own bar. */}
|
|
1039
|
-
{count.pending > 1 && (
|
|
1131
|
+
{count.pending > 1 && batch.length > 0 && (
|
|
1040
1132
|
<button
|
|
1041
1133
|
className="ghost accept accept-all"
|
|
1042
1134
|
disabled={
|
|
@@ -1047,30 +1139,41 @@ export function ReviewApp({
|
|
|
1047
1139
|
}
|
|
1048
1140
|
title={
|
|
1049
1141
|
state?.decisionLock ||
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1142
|
+
[
|
|
1143
|
+
changes
|
|
1144
|
+
? tn(
|
|
1145
|
+
batch.length,
|
|
1146
|
+
"Stage the {n} unstaged change.",
|
|
1147
|
+
"Stage the {n} unstaged changes.",
|
|
1148
|
+
)
|
|
1149
|
+
: tn(
|
|
1150
|
+
batch.length,
|
|
1151
|
+
"Accept {n} undecided change",
|
|
1152
|
+
"Accept {n} undecided changes",
|
|
1153
|
+
),
|
|
1154
|
+
undecided.length > batch.length
|
|
1155
|
+
? tn(
|
|
1156
|
+
undecided.length - batch.length,
|
|
1157
|
+
"Leaves {n} change with bibliography entries to check.",
|
|
1158
|
+
"Leaves {n} changes with bibliography entries to check.",
|
|
1159
|
+
)
|
|
1160
|
+
: "",
|
|
1161
|
+
]
|
|
1162
|
+
.filter(Boolean)
|
|
1163
|
+
.join(" ")
|
|
1061
1164
|
}
|
|
1062
1165
|
aria-label={
|
|
1063
1166
|
changes
|
|
1064
|
-
? t("Stage all ({n})", { n:
|
|
1065
|
-
: t("Accept all ({n})", { n:
|
|
1167
|
+
? t("Stage all ({n})", { n: batch.length })
|
|
1168
|
+
: t("Accept all ({n})", { n: batch.length })
|
|
1066
1169
|
}
|
|
1067
1170
|
aria-describedby={state?.decisionLock ? "review-lock" : undefined}
|
|
1068
|
-
onClick={() => void decide("accept",
|
|
1171
|
+
onClick={() => void decide("accept", batch)}
|
|
1069
1172
|
>
|
|
1070
1173
|
<span className="batch-label">
|
|
1071
1174
|
{changes ? t("Stage all") : t("Accept all")}
|
|
1072
1175
|
</span>
|
|
1073
|
-
<span className="batch-count">{
|
|
1176
|
+
<span className="batch-count">{batch.length}</span>
|
|
1074
1177
|
</button>
|
|
1075
1178
|
)}
|
|
1076
1179
|
{count.pending > 1 && !changes && (
|
|
@@ -1223,6 +1326,70 @@ export function ReviewApp({
|
|
|
1223
1326
|
)}
|
|
1224
1327
|
<div className="review-body">
|
|
1225
1328
|
<main id="paper" ref={paper} tabIndex={-1} onScroll={onScroll}>
|
|
1329
|
+
{changes && detail && !count.pending && (count.accepted > 0 || !stops.length) && (
|
|
1330
|
+
<div className="review-complete" role="status" tabIndex={-1}>
|
|
1331
|
+
<strong>
|
|
1332
|
+
{count.accepted
|
|
1333
|
+
? t("All changes are staged")
|
|
1334
|
+
: t("No uncommitted changes")}
|
|
1335
|
+
</strong>
|
|
1336
|
+
<span>
|
|
1337
|
+
{count.accepted
|
|
1338
|
+
? t("Commit them when you are ready.")
|
|
1339
|
+
: t("The PDF matches the last commit.")}
|
|
1340
|
+
</span>
|
|
1341
|
+
<div className="review-complete-actions" />
|
|
1342
|
+
</div>
|
|
1343
|
+
)}
|
|
1344
|
+
{!changes && detail && !history && !count.pending && (
|
|
1345
|
+
<div className="review-complete" role="status" tabIndex={-1}>
|
|
1346
|
+
<strong>
|
|
1347
|
+
{t(
|
|
1348
|
+
state?.workspace?.status === "waiting_input"
|
|
1349
|
+
? "Agent needs your input"
|
|
1350
|
+
: state?.workspace?.status === "queued"
|
|
1351
|
+
? "Feedback queued"
|
|
1352
|
+
: state?.workspace?.status === "running"
|
|
1353
|
+
? "Feedback sent · Agent is working"
|
|
1354
|
+
: state?.workspace?.status === "building" ||
|
|
1355
|
+
state?.state === "building"
|
|
1356
|
+
? "Preparing the next review…"
|
|
1357
|
+
: state?.workspace?.merging ? "Merge review" : "All changes reviewed",
|
|
1358
|
+
)}
|
|
1359
|
+
</strong>
|
|
1360
|
+
<span>
|
|
1361
|
+
{t(
|
|
1362
|
+
state?.workspace?.status === "waiting_input"
|
|
1363
|
+
? "Open Chat to answer the agent's question."
|
|
1364
|
+
: ["running", "queued", "building"].includes(
|
|
1365
|
+
state?.workspace?.status || "",
|
|
1366
|
+
) || state?.state === "building"
|
|
1367
|
+
? "New changes will appear here when ready."
|
|
1368
|
+
: state?.workspace?.merging
|
|
1369
|
+
? "Your manuscript is unchanged until you save the merged result."
|
|
1370
|
+
: state?.workspace?.comments
|
|
1371
|
+
? "Your comments are saved. Send feedback so the agent can revise."
|
|
1372
|
+
: state?.workspace?.deferred
|
|
1373
|
+
? state.workspace.accepted
|
|
1374
|
+
? "Choose how to save the accepted changes to your manuscript."
|
|
1375
|
+
: state.workspace.destination === "branch"
|
|
1376
|
+
? "Choose how to save the accepted changes to your manuscript."
|
|
1377
|
+
: (state.workspace.destination === "workspace" || state.workspace.destination === "commit")
|
|
1378
|
+
? "Accepted changes are in your manuscript."
|
|
1379
|
+
: "There are no accepted changes to save."
|
|
1380
|
+
: state?.workspace?.accepted
|
|
1381
|
+
? "Accepted changes are in your manuscript."
|
|
1382
|
+
: state?.workspace?.unsent && !count.accepted
|
|
1383
|
+
? "Your decisions are saved. Send them to the agent to continue."
|
|
1384
|
+
: count.commented && !state?.workspace
|
|
1385
|
+
? "Your comments are saved for the agent."
|
|
1386
|
+
: "There are no new changes to review. Previous decisions are available in History.",
|
|
1387
|
+
)}
|
|
1388
|
+
</span>
|
|
1389
|
+
{/* A host such as Papilot puts its next step here, for example a commit. */}
|
|
1390
|
+
<div className="review-complete-actions" />
|
|
1391
|
+
</div>
|
|
1392
|
+
)}
|
|
1226
1393
|
{(state?.commentLock || state?.decisionLock) && (
|
|
1227
1394
|
<div className="banner" id="review-lock" role="status">
|
|
1228
1395
|
{t((state.commentLock || state.decisionLock)!)}
|
|
@@ -1281,68 +1448,19 @@ export function ReviewApp({
|
|
|
1281
1448
|
</details>
|
|
1282
1449
|
</div>
|
|
1283
1450
|
) : null}
|
|
1284
|
-
{
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
{
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
</div>
|
|
1298
|
-
)}
|
|
1299
|
-
{!changes && detail && !history && !count.pending && (
|
|
1300
|
-
<div className="review-complete" role="status">
|
|
1301
|
-
<strong>
|
|
1302
|
-
{t(
|
|
1303
|
-
state?.workspace?.status === "waiting_input"
|
|
1304
|
-
? "Agent needs your input"
|
|
1305
|
-
: state?.workspace?.status === "queued"
|
|
1306
|
-
? "Feedback queued"
|
|
1307
|
-
: state?.workspace?.status === "running"
|
|
1308
|
-
? "Feedback sent · Agent is working"
|
|
1309
|
-
: state?.workspace?.status === "building" ||
|
|
1310
|
-
state?.state === "building"
|
|
1311
|
-
? "Preparing the next review…"
|
|
1312
|
-
: "All changes reviewed",
|
|
1313
|
-
)}
|
|
1314
|
-
</strong>
|
|
1315
|
-
<span>
|
|
1316
|
-
{t(
|
|
1317
|
-
state?.workspace?.status === "waiting_input"
|
|
1318
|
-
? "Open Chat to answer the agent's question."
|
|
1319
|
-
: ["running", "queued", "building"].includes(
|
|
1320
|
-
state?.workspace?.status || "",
|
|
1321
|
-
) || state?.state === "building"
|
|
1322
|
-
? "New changes will appear here when ready."
|
|
1323
|
-
: state?.workspace?.comments
|
|
1324
|
-
? "Your comments are saved. Send feedback so the agent can revise."
|
|
1325
|
-
: state?.workspace?.deferred
|
|
1326
|
-
? state.workspace.accepted
|
|
1327
|
-
? "Accepted changes are saved in this task. Choose where to save them below."
|
|
1328
|
-
: state.workspace.destination === "branch"
|
|
1329
|
-
? "Accepted changes are committed to the task branch."
|
|
1330
|
-
: state.workspace.destination === "workspace"
|
|
1331
|
-
? "Accepted changes are in your manuscript."
|
|
1332
|
-
: "There are no accepted changes to save."
|
|
1333
|
-
: state?.workspace?.accepted
|
|
1334
|
-
? "Accepted changes are in your manuscript."
|
|
1335
|
-
: state?.workspace?.unsent && !count.accepted
|
|
1336
|
-
? "Your decisions are saved. Send them to the agent to continue."
|
|
1337
|
-
: count.commented && !state?.workspace
|
|
1338
|
-
? "Your comments are saved for the agent."
|
|
1339
|
-
: "There are no new changes to review. Previous decisions are available in History.",
|
|
1340
|
-
)}
|
|
1341
|
-
</span>
|
|
1342
|
-
{/* A host such as Papilot puts its next step here, for example a commit. */}
|
|
1343
|
-
<div className="review-complete-actions" />
|
|
1344
|
-
</div>
|
|
1345
|
-
)}
|
|
1451
|
+
{detail?.references &&
|
|
1452
|
+
(detail.references.entries.length > 0 ||
|
|
1453
|
+
detail.references.missing.length > 0) && (
|
|
1454
|
+
<ReferenceBanner
|
|
1455
|
+
references={detail.references}
|
|
1456
|
+
checks={references.checks}
|
|
1457
|
+
pending={references.pending}
|
|
1458
|
+
confirmed={references.confirmed}
|
|
1459
|
+
changeOf={(unit) => (unit ? (firstStop.get(unit) ?? -1) : -1)}
|
|
1460
|
+
onSelect={(index) => select(index)}
|
|
1461
|
+
onRetry={references.retry}
|
|
1462
|
+
/>
|
|
1463
|
+
)}
|
|
1346
1464
|
<div className="document">
|
|
1347
1465
|
{detail ? (
|
|
1348
1466
|
<>
|
|
@@ -1463,6 +1581,18 @@ export function ReviewApp({
|
|
|
1463
1581
|
{toast}
|
|
1464
1582
|
</div>
|
|
1465
1583
|
)}
|
|
1584
|
+
{confirm && (
|
|
1585
|
+
<ConfirmReferences
|
|
1586
|
+
items={confirm.items}
|
|
1587
|
+
staging={changes}
|
|
1588
|
+
onCancel={() => setConfirm(null)}
|
|
1589
|
+
onConfirm={() => {
|
|
1590
|
+
const chosen = confirm;
|
|
1591
|
+
setConfirm(null);
|
|
1592
|
+
void decide(chosen.kind, chosen.chosen, chosen.body, true);
|
|
1593
|
+
}}
|
|
1594
|
+
/>
|
|
1595
|
+
)}
|
|
1466
1596
|
{help && (
|
|
1467
1597
|
<div className="help-backdrop" onClick={() => setHelp(false)}>
|
|
1468
1598
|
<dialog
|