@anthusai/papyrus 5.43.0-next.7 → 5.43.0-next.8
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/bin/papyrus-infra.mjs +21 -1
- package/components/monitoring-overview-view.tsx +4 -2
- package/components/newsroom-ops-overview.tsx +6 -1
- package/components/newsroom-references-view.tsx +2 -4
- package/components/notification-center.tsx +345 -63
- package/components/proposals-panel.tsx +21 -24
- package/components/proposals-view.tsx +4 -2
- package/components/reference-review-card.tsx +80 -63
- package/infra/amplify-app-shell.js +18 -2
- package/infra/amplify-app-shell.ts +18 -2
- package/infra/chat-stack.js +222 -0
- package/infra/chat-stack.ts +250 -0
- package/infra/chat.test.js +142 -0
- package/infra/chat.test.ts +151 -0
- package/infra/index.js +1 -0
- package/infra/site-config.js +58 -1
- package/infra/site-config.ts +78 -1
- package/lib/monitoring-overview.ts +2 -0
- package/lib/newsroom-organizer-nav.ts +1 -0
- package/lib/proposals-client.ts +29 -0
- package/lib/review-client.ts +38 -1
- package/lib/review-drafts.ts +231 -0
- package/lib/review-fixture.ts +36 -2
- package/lib/review-requests.ts +64 -24
- package/package.json +3 -1
- package/routes.manifest.json +1 -1
package/bin/papyrus-infra.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* papyrus-infra synth --site infra/site.json [--out cdk.out] [--account A] [--region R]
|
|
4
4
|
* papyrus-infra synth --account-stack github-oidc [--out cdk.out] [--account A] [--region R]
|
|
5
5
|
* papyrus-infra print-buildspec --site infra/site.json --app cms-production|cms-staging|reader
|
|
6
|
+
* papyrus-infra synth --site infra/site.json --part chat [--chat-lambdas DIR] [--out cdk.out] (the private chat data stack)
|
|
6
7
|
* papyrus-infra deploy --site infra/site.json [--only-branch main] [--only-app-id ID] [--out cdk.out] [--account A] [--region R]
|
|
7
8
|
* Synthesizes the app-shell stack for a publication from its own site.json,
|
|
8
9
|
* prints one of the generated Amplify build specs, or deploys the stack from
|
|
@@ -16,6 +17,7 @@ import { resolveCdkEnvironment } from "../src/cdk-environment.mjs";
|
|
|
16
17
|
|
|
17
18
|
const USAGE = [
|
|
18
19
|
"Usage: papyrus-infra synth --site infra/site.json [--out cdk.out] [--account A] [--region R]",
|
|
20
|
+
" papyrus-infra synth --site infra/site.json --part chat [--chat-lambdas DIR] [--out cdk.out] [--account A] [--region R]",
|
|
19
21
|
" papyrus-infra synth --account-stack github-oidc [--out cdk.out] [--account A] [--region R]",
|
|
20
22
|
" papyrus-infra print-buildspec --site infra/site.json --app cms-production|cms-staging|reader",
|
|
21
23
|
" papyrus-infra deploy-edge --stack NAME --template FILE [--adoption-template FILE --adopt-resources FILE] [--parameter KEY=VALUE]... [--delete-function NAME] [--only-branch main] [--only-app-id ID] [--region R]",
|
|
@@ -186,8 +188,9 @@ if (command === "deploy") {
|
|
|
186
188
|
}
|
|
187
189
|
|
|
188
190
|
let AmplifyAppShellStack;
|
|
191
|
+
let PapyrusChatStack;
|
|
189
192
|
try {
|
|
190
|
-
({ AmplifyAppShellStack } = await import("../infra/index.js"));
|
|
193
|
+
({ AmplifyAppShellStack, PapyrusChatStack } = await import("../infra/index.js"));
|
|
191
194
|
} catch (error) {
|
|
192
195
|
console.error(`papyrus-infra: ${error instanceof Error ? error.message : error}`);
|
|
193
196
|
process.exit(1);
|
|
@@ -195,6 +198,23 @@ try {
|
|
|
195
198
|
|
|
196
199
|
const { App } = createRequire(path.join(process.cwd(), "noop.js"))("aws-cdk-lib");
|
|
197
200
|
const app = new App({ outdir: path.resolve(opt("out", "cdk.out")) });
|
|
201
|
+
if (opt("part", "shell") === "chat") {
|
|
202
|
+
// The site's private newsroom chat data stack (docs/private-newsroom-chat-per-site.md); deployed before the app shell enables chat.
|
|
203
|
+
if (!config.chat?.enabled) {
|
|
204
|
+
console.error(`papyrus-infra: site ${config.siteId} has no enabled chat block`);
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
const lambdaAssetDirectory = opt("chat-lambdas");
|
|
208
|
+
const chatStack = new PapyrusChatStack(app, {
|
|
209
|
+
config,
|
|
210
|
+
env: resolveEnvironmentOrExit(),
|
|
211
|
+
lambdaAssetDirectory: lambdaAssetDirectory ? path.resolve(lambdaAssetDirectory) : undefined,
|
|
212
|
+
description: `Private Papyrus newsroom chat data plane for ${config.siteId}.`,
|
|
213
|
+
});
|
|
214
|
+
app.synth();
|
|
215
|
+
console.log(`synthesized ${chatStack.stackName} -> ${path.resolve(opt("out", "cdk.out"))}`);
|
|
216
|
+
process.exit(0);
|
|
217
|
+
}
|
|
198
218
|
new AmplifyAppShellStack(app, `AmplifyAppShell-${config.siteId}`, config, {
|
|
199
219
|
env: resolveEnvironmentOrExit(),
|
|
200
220
|
stackName: resolveStackName(config),
|
|
@@ -139,7 +139,7 @@ export function EmailSubmissions({ items, detail, onOpen, onResolveArticleLink,
|
|
|
139
139
|
if (!isCurrentEmailSubmissionAction(activeReceiptId.current, requestReceiptId)) return;
|
|
140
140
|
if (!("url" in result)) {
|
|
141
141
|
const reason = "reason" in result && typeof result.reason === "string"
|
|
142
|
-
&& /^(?:redirect_[a-z_]+|resolution_record_invalid|resolution_not_recorded)$/.test(result.reason)
|
|
142
|
+
&& /^(?:redirect_[a-z_]+|resolution_record_invalid|resolution_not_recorded|invalid_url|not_public|dns|timeout|connection|tls|loop|http_4xx|http_5xx|blocked|too_large|unreadable|unsafe_destination|no_query|search_unavailable|search_error|no_match)$/.test(result.reason)
|
|
143
143
|
? ` (${result.reason})` : "";
|
|
144
144
|
setNoticeByReceipt((current) => ({ ...current, [requestReceiptId]: `The article destination could not be resolved safely${reason}. The original link remains private and unchanged.` }));
|
|
145
145
|
return;
|
|
@@ -147,7 +147,9 @@ export function EmailSubmissions({ items, detail, onOpen, onResolveArticleLink,
|
|
|
147
147
|
const candidate = detail && "submission" in detail ? detail.candidateLinks[index] : null;
|
|
148
148
|
setResolvedByReceipt((current) => ({ ...current, [requestReceiptId]: { ...current[requestReceiptId], [index]: { url: result.url, title: result.title || candidate?.title || "Article" } } }));
|
|
149
149
|
setSelectedByReceipt((current) => ({ ...current, [requestReceiptId]: { ...current[requestReceiptId], [index]: true } }));
|
|
150
|
-
setNoticeByReceipt((current) => ({ ...current, [requestReceiptId]:
|
|
150
|
+
setNoticeByReceipt((current) => ({ ...current, [requestReceiptId]: result.foundVia === "search"
|
|
151
|
+
? `The newsletter link could not be followed, so this article was found by searching the headline (match ${Math.round((result.search?.matchScore ?? 0) * 100)}%). Check that it is the right article before importing it.`
|
|
152
|
+
: "Resolved. This request may be recorded as a click by the newsletter sender." }));
|
|
151
153
|
} catch {
|
|
152
154
|
if (isCurrentEmailSubmissionAction(activeReceiptId.current, requestReceiptId)) {
|
|
153
155
|
setNoticeByReceipt((current) => ({ ...current, [requestReceiptId]: "The article destination could not be resolved. Try again later." }));
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import { newsroomInboxRowClassName } from "../lib/newsroom-list-selection";
|
|
25
25
|
import type { RelevanceDecisionView } from "../lib/relevance-decisions";
|
|
26
26
|
import { NotificationCenter } from "./notification-center";
|
|
27
|
+
import { CyclotronRow, predictionFromDecision } from "./reference-review-card";
|
|
27
28
|
|
|
28
29
|
type NewsroomOpsOverviewProps = {
|
|
29
30
|
assignments: AssignmentRecord[];
|
|
@@ -105,9 +106,13 @@ export function NewsroomOpsOverview({
|
|
|
105
106
|
href={href}
|
|
106
107
|
key={lineageId}
|
|
107
108
|
>
|
|
108
|
-
<div className="min-w-0 flex-1">
|
|
109
|
+
<div className="min-w-0 flex-1 space-y-1">
|
|
109
110
|
<p className="truncate text-sm font-medium text-foreground">{referenceDisplayTitle(reference)}</p>
|
|
110
111
|
<p className="text-xs text-muted-foreground">Review source intake · {formatReferenceListDate(reference)}</p>
|
|
112
|
+
<div data-needs-attention-suggestion={lineageId}>
|
|
113
|
+
<CyclotronRow prediction={decisions.get(lineageId) ? predictionFromDecision(decisions.get(lineageId)!) : { state: "none" }} />
|
|
114
|
+
</div>
|
|
115
|
+
<p className="text-xs font-medium text-foreground">Open it to Accept or Reject in the review queue</p>
|
|
111
116
|
</div>
|
|
112
117
|
<Badge className="mt-0.5 shrink-0" variant="outline">Pending</Badge>
|
|
113
118
|
</Link>
|
|
@@ -44,6 +44,7 @@ import { newsroomListRowClassName } from "../lib/newsroom-list-selection";
|
|
|
44
44
|
import { ReferenceSourcePreview } from "./reference-source-preview";
|
|
45
45
|
import {
|
|
46
46
|
ReviewCard,
|
|
47
|
+
predictionFromDecision,
|
|
47
48
|
ReviewQueueBar,
|
|
48
49
|
useQueueGestures,
|
|
49
50
|
type CardPrediction,
|
|
@@ -269,10 +270,7 @@ function summaryOf(reference: ReferenceRecord): string | null {
|
|
|
269
270
|
}
|
|
270
271
|
|
|
271
272
|
function cardPrediction(decision: RelevanceDecisionView | undefined, predictionState: PredictionState): CardPrediction {
|
|
272
|
-
if (decision)
|
|
273
|
-
const positive = decision.positiveLabel ?? "include";
|
|
274
|
-
return { state: "ready", choice: decision.label === positive ? "accept" : "reject", confidence: decision.confidence };
|
|
275
|
-
}
|
|
273
|
+
if (decision) return predictionFromDecision(decision);
|
|
276
274
|
if (predictionState === "loading") return { state: "loading" };
|
|
277
275
|
if (predictionState === "error") return { state: "error" };
|
|
278
276
|
return { state: "none" };
|