@anthusai/papyrus 5.57.0 → 5.58.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/amplify/data/resource.js +25 -3
- package/amplify/data/resource.ts +25 -3
- package/amplify/data/schema.js +25 -3
- package/amplify/data/schema.ts +25 -3
- package/amplify/functions/category-action/handler.ts +51 -125
- package/amplify/functions/shared/reference-curation-metadata.ts +21 -29
- package/components/conversation-thread.tsx +27 -12
- package/components/conversations-memory-source.ts +46 -0
- package/components/conversations-network-source.ts +40 -0
- package/components/conversations-page.tsx +14 -0
- package/components/conversations-source.ts +24 -0
- package/components/conversations-view.tsx +319 -0
- package/components/decision-control.tsx +307 -0
- package/components/needs-me-bodies.tsx +4 -3
- package/components/needs-me-feed.tsx +8 -14
- package/components/news-desk-taxonomy-client.ts +83 -14
- package/components/newsroom-organizer.css +24 -0
- package/components/notification-cards.tsx +5 -4
- package/components/reference-feed.tsx +60 -42
- package/components/reference-review-card.tsx +8 -134
- package/components/story-reporting-trail-client.ts +4 -3
- package/components/story-reporting-trail.tsx +8 -0
- package/components/topic-steering-workspace.tsx +40 -782
- package/components/use-reference-ratings.ts +11 -7
- package/lib/category-repository.ts +7 -0
- package/lib/conversation-feed.ts +96 -0
- package/lib/cyclotron-suggestions.ts +18 -36
- package/lib/local-preview-seed.ts +16 -27
- package/lib/reference-feed-keys.ts +69 -0
- package/lib/reference-policy.ts +83 -11
- package/lib/reference-review-details.ts +21 -57
- package/lib/relevance-decisions.ts +6 -2
- package/lib/story-reporting-trail.ts +28 -2
- package/package.json +9 -2
- package/routes.manifest.json +1 -1
- package/components/review-rating-grid.tsx +0 -190
|
@@ -17,6 +17,15 @@ export type TrailVersion = {
|
|
|
17
17
|
updatedAt: string | null;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
/** One stage move of the story, read from the latest Item version's `metadata.stageMoves` (not from Messages). */
|
|
21
|
+
export type TrailStageMove = {
|
|
22
|
+
at: string;
|
|
23
|
+
from: string | null;
|
|
24
|
+
to: string;
|
|
25
|
+
by: string | null;
|
|
26
|
+
reason: string | null;
|
|
27
|
+
};
|
|
28
|
+
|
|
20
29
|
export type TrailAssignmentEvent = {
|
|
21
30
|
id: string;
|
|
22
31
|
eventType: string;
|
|
@@ -89,12 +98,29 @@ export type StoryReportingTrail = {
|
|
|
89
98
|
threads: TrailThread[];
|
|
90
99
|
references: TrailReference[];
|
|
91
100
|
attachments: TrailAttachment[];
|
|
101
|
+
stageMoves: TrailStageMove[];
|
|
92
102
|
};
|
|
93
103
|
|
|
94
|
-
export const EMPTY_STORY_REPORTING_TRAIL: StoryReportingTrail = { versions: [], assignments: [], threads: [], references: [], attachments: [] };
|
|
104
|
+
export const EMPTY_STORY_REPORTING_TRAIL: StoryReportingTrail = { versions: [], assignments: [], threads: [], references: [], attachments: [], stageMoves: [] };
|
|
105
|
+
|
|
106
|
+
/** The stage moves recorded on an Item version's metadata, oldest first. Anything that is not a well-formed move is skipped. */
|
|
107
|
+
export function stageMovesOf(metadata: unknown): TrailStageMove[] {
|
|
108
|
+
let parsed: unknown = metadata;
|
|
109
|
+
if (typeof metadata === "string") {
|
|
110
|
+
try { parsed = JSON.parse(metadata); } catch { return []; }
|
|
111
|
+
}
|
|
112
|
+
const moves = typeof parsed === "object" && parsed ? (parsed as Record<string, unknown>).stageMoves : null;
|
|
113
|
+
if (!Array.isArray(moves)) return [];
|
|
114
|
+
return moves.flatMap(entry => {
|
|
115
|
+
if (typeof entry !== "object" || !entry) return [];
|
|
116
|
+
const move = entry as Record<string, unknown>;
|
|
117
|
+
if (typeof move.at !== "string" || typeof move.to !== "string") return [];
|
|
118
|
+
return [{ at: move.at, from: typeof move.from === "string" ? move.from : null, to: move.to, by: typeof move.by === "string" ? move.by : null, reason: typeof move.reason === "string" ? move.reason : null }];
|
|
119
|
+
});
|
|
120
|
+
}
|
|
95
121
|
|
|
96
122
|
export function storyReportingTrailIsEmpty(trail: StoryReportingTrail): boolean {
|
|
97
|
-
return trail.assignments.length === 0 && trail.threads.length === 0 && trail.references.length === 0 && trail.attachments.length === 0 && trail.versions.length <= 1;
|
|
123
|
+
return trail.assignments.length === 0 && trail.threads.length === 0 && trail.stageMoves.length === 0 && trail.references.length === 0 && trail.attachments.length === 0 && trail.versions.length <= 1;
|
|
98
124
|
}
|
|
99
125
|
|
|
100
126
|
export type StoryFeedEntry =
|
package/package.json
CHANGED
|
@@ -83,7 +83,13 @@
|
|
|
83
83
|
"./components/conversation-context": "./components/conversation-context.tsx",
|
|
84
84
|
"./components/conversation-provider": "./components/conversation-provider.tsx",
|
|
85
85
|
"./components/conversation-thread": "./components/conversation-thread.tsx",
|
|
86
|
+
"./components/conversations-memory-source": "./components/conversations-memory-source.ts",
|
|
87
|
+
"./components/conversations-network-source": "./components/conversations-network-source.ts",
|
|
88
|
+
"./components/conversations-page": "./components/conversations-page.tsx",
|
|
89
|
+
"./components/conversations-source": "./components/conversations-source.ts",
|
|
90
|
+
"./components/conversations-view": "./components/conversations-view.tsx",
|
|
86
91
|
"./components/curation-delegation-client": "./components/curation-delegation-client.ts",
|
|
92
|
+
"./components/decision-control": "./components/decision-control.tsx",
|
|
87
93
|
"./components/dev-sandbox-editor-sign-in": "./components/dev-sandbox-editor-sign-in.tsx",
|
|
88
94
|
"./components/editorial-checks-admin-panel": "./components/editorial-checks-admin-panel.tsx",
|
|
89
95
|
"./components/editorial-finding-card": "./components/editorial-finding-card.tsx",
|
|
@@ -164,7 +170,6 @@
|
|
|
164
170
|
"./components/reference-stories": "./components/reference-stories.tsx",
|
|
165
171
|
"./components/references-page-loading": "./components/references-page-loading.tsx",
|
|
166
172
|
"./components/references-prefetch": "./components/references-prefetch.ts",
|
|
167
|
-
"./components/review-rating-grid": "./components/review-rating-grid.tsx",
|
|
168
173
|
"./components/settings-page": "./components/settings-page.tsx",
|
|
169
174
|
"./components/signup-entries-panel": "./components/signup-entries-panel.tsx",
|
|
170
175
|
"./components/staging-banner": "./components/staging-banner.tsx",
|
|
@@ -234,6 +239,7 @@
|
|
|
234
239
|
"./lib/content-repository": "./lib/content-repository.ts",
|
|
235
240
|
"./lib/content-source-context": "./lib/content-source-context.ts",
|
|
236
241
|
"./lib/content-types": "./lib/content-types.ts",
|
|
242
|
+
"./lib/conversation-feed": "./lib/conversation-feed.ts",
|
|
237
243
|
"./lib/conversation": "./lib/conversation.ts",
|
|
238
244
|
"./lib/curation-client": "./lib/curation-client.ts",
|
|
239
245
|
"./lib/curation-delegation-helpers": "./lib/curation-delegation-helpers.ts",
|
|
@@ -348,6 +354,7 @@
|
|
|
348
354
|
"./lib/reference-capture": "./lib/reference-capture.ts",
|
|
349
355
|
"./lib/reference-display": "./lib/reference-display.ts",
|
|
350
356
|
"./lib/reference-feed-cache": "./lib/reference-feed-cache.ts",
|
|
357
|
+
"./lib/reference-feed-keys": "./lib/reference-feed-keys.ts",
|
|
351
358
|
"./lib/reference-feed-ranking": "./lib/reference-feed-ranking.ts",
|
|
352
359
|
"./lib/reference-policy": "./lib/reference-policy.ts",
|
|
353
360
|
"./lib/reference-preview": "./lib/reference-preview.ts",
|
|
@@ -510,7 +517,7 @@
|
|
|
510
517
|
"optional": true
|
|
511
518
|
}
|
|
512
519
|
},
|
|
513
|
-
"version": "5.
|
|
520
|
+
"version": "5.58.0",
|
|
514
521
|
"dependencies": {
|
|
515
522
|
"@aws-amplify/adapter-nextjs": "^1.7.3",
|
|
516
523
|
"@aws-crypto/sha256-js": "5.2.0",
|
package/routes.manifest.json
CHANGED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { CheckIcon, CircleCheckIcon, CircleXIcon, Loader2Icon, XIcon } from "lucide-react";
|
|
4
|
-
import { UNRECORDED_CONFIDENCE } from "../lib/reference-feed-ranking";
|
|
5
|
-
import { cn } from "../lib/utils";
|
|
6
|
-
import type { CardPrediction, ReviewChoice } from "./reference-review-card";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* The rating area of a card: one grid, three columns (label | Accept | Reject), and the two rows share its columns exactly.
|
|
10
|
-
* Cyclotron [ Accept 96% ] [ Reject ] read only; the predicted side is highlighted with its confidence
|
|
11
|
-
* [====== confidence bar, centred at 50% ======]
|
|
12
|
-
* You [ Accept ] [ Reject ] the real buttons, each directly under its matching cell
|
|
13
|
-
* Feedback is never colour alone: before you answer the predicted cell is neutral-accent with a check or cross; after, it turns
|
|
14
|
-
* green with a CircleCheck (Match) or red with a CircleX (Miss), and the confidence bar tints the same way. Your chosen button is
|
|
15
|
-
* filled. Flat tones and rounded shapes only, no borders.
|
|
16
|
-
* The grid sizes itself to its own width (a container query), not the window's: on a narrow card the label column shrinks to its
|
|
17
|
-
* word, the padding tightens, and the confidence wraps under "Accept" instead of spilling out of its cell.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
export type RatingFeedback = { phase: "idle" | "saving" | "saved" | "error"; message?: string };
|
|
21
|
-
|
|
22
|
-
/** P(accept) from a prediction: its confidence is in the side it chose; an unrecorded confidence is a mild lean. */
|
|
23
|
-
export function predictionAcceptProbability(prediction: Extract<CardPrediction, { state: "ready" }>): number {
|
|
24
|
-
const confidence = prediction.confidence ?? UNRECORDED_CONFIDENCE;
|
|
25
|
-
return prediction.choice === "accept" ? confidence : 1 - confidence;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** How far, in percent of the whole bar, the fill reaches from the centre: |P(accept) - 0.5| * 2, over the two halves. */
|
|
29
|
-
export function confidenceFillPercent(probability: number): number {
|
|
30
|
-
return Math.min(100, Math.abs(probability - 0.5) * 2 * 100);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const WORD: Record<ReviewChoice, string> = { accept: "Accept", reject: "Reject" };
|
|
34
|
-
|
|
35
|
-
const TONE = {
|
|
36
|
-
match: "bg-emerald-600/20 text-emerald-900 dark:bg-emerald-400/20 dark:text-emerald-100",
|
|
37
|
-
miss: "bg-red-600/20 text-red-900 dark:bg-red-400/20 dark:text-red-100",
|
|
38
|
-
} as const;
|
|
39
|
-
const FILL = { match: "bg-emerald-600 dark:bg-emerald-400", miss: "bg-red-600 dark:bg-red-400", neutral: "bg-[var(--primary)]" } as const;
|
|
40
|
-
|
|
41
|
-
export function RatingGrid({
|
|
42
|
-
answer,
|
|
43
|
-
decided = answer,
|
|
44
|
-
disabled = false,
|
|
45
|
-
disabledReason = null,
|
|
46
|
-
feedback = { phase: "idle" },
|
|
47
|
-
note = null,
|
|
48
|
-
onChoose,
|
|
49
|
-
prediction,
|
|
50
|
-
showPrediction = true,
|
|
51
|
-
size = "compact",
|
|
52
|
-
statusOverlay = false,
|
|
53
|
-
surface = "footer",
|
|
54
|
-
}: {
|
|
55
|
-
/** The side the buttons show as chosen (it may be a pending choice that is still saving). */
|
|
56
|
-
answer: ReviewChoice | null;
|
|
57
|
-
/** The saved human answer, which the Match / Miss verdict compares against. */
|
|
58
|
-
decided?: ReviewChoice | null;
|
|
59
|
-
disabled?: boolean;
|
|
60
|
-
disabledReason?: string | null;
|
|
61
|
-
feedback?: RatingFeedback;
|
|
62
|
-
/** A short line shown in the status row beside the Saved tick (for example the reason). It never adds a row. */
|
|
63
|
-
note?: string | null;
|
|
64
|
-
onChoose: (choice: ReviewChoice) => void;
|
|
65
|
-
prediction: CardPrediction;
|
|
66
|
-
/** False for a card with nothing to predict (an AI draft): the Cyclotron row and the "You" label are left out and only the two buttons remain. */
|
|
67
|
-
showPrediction?: boolean;
|
|
68
|
-
size?: "compact" | "large";
|
|
69
|
-
/** Lay the Saved / note line over the bottom padding of the nearest positioned ancestor, so it adds no height under the buttons. */
|
|
70
|
-
statusOverlay?: boolean;
|
|
71
|
-
/** The tone the grid sits on, so the bar's centre notch matches it. */
|
|
72
|
-
surface?: "footer" | "card";
|
|
73
|
-
}) {
|
|
74
|
-
const ready = prediction.state === "ready" ? prediction : null;
|
|
75
|
-
const verdict = ready && decided ? (ready.choice === decided ? "match" : "miss") : null;
|
|
76
|
-
const probability = ready ? predictionAcceptProbability(ready) : 0.5;
|
|
77
|
-
const half = confidenceFillPercent(probability) / 2; // percent of the full bar width, on one side of the centre
|
|
78
|
-
const leansAccept = probability >= 0.5;
|
|
79
|
-
const confidenceText = ready && ready.confidence !== null ? `${Math.round(ready.confidence * 100)}%` : null;
|
|
80
|
-
const cell = size === "large" ? "min-h-14 text-base" : "min-h-12 text-sm";
|
|
81
|
-
const label = size === "large" ? "text-sm" : "text-xs";
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
<div
|
|
85
|
-
className={cn(
|
|
86
|
-
"@container/rating grid gap-x-2 gap-y-2",
|
|
87
|
-
showPrediction ? "grid-cols-[auto_minmax(0,1fr)_minmax(0,1fr)] @max-[17rem]/rating:gap-x-1.5" : "grid-cols-2",
|
|
88
|
-
)}
|
|
89
|
-
data-rating-grid
|
|
90
|
-
>
|
|
91
|
-
{showPrediction ? <div className="col-span-3 grid grid-cols-subgrid items-stretch gap-y-2" data-review-row="cyclotron">
|
|
92
|
-
<span className={cn("self-center font-semibold", label)}>Cyclotron</span>
|
|
93
|
-
{(["accept", "reject"] as const).map((choice) => {
|
|
94
|
-
const predicted = ready?.choice === choice;
|
|
95
|
-
const Icon = predicted ? (verdict === "match" ? CircleCheckIcon : verdict === "miss" ? CircleXIcon : choice === "accept" ? CheckIcon : XIcon) : null;
|
|
96
|
-
return (
|
|
97
|
-
<span
|
|
98
|
-
className={cn(
|
|
99
|
-
"flex min-w-0 flex-col items-center justify-center rounded-[16px] px-2 py-1.5 text-center @max-[17rem]/rating:px-1",
|
|
100
|
-
cell,
|
|
101
|
-
!predicted && "bg-[var(--feed-control)]/45 text-muted-foreground",
|
|
102
|
-
predicted && !verdict && "bg-[var(--feed-control)] font-semibold text-[var(--primary)]",
|
|
103
|
-
predicted && verdict && cn("font-semibold", TONE[verdict]),
|
|
104
|
-
)}
|
|
105
|
-
data-cyclotron-choice={choice}
|
|
106
|
-
data-predicted={predicted || undefined}
|
|
107
|
-
data-verdict={predicted && verdict ? verdict : undefined}
|
|
108
|
-
key={choice}
|
|
109
|
-
>
|
|
110
|
-
{/* Wraps rather than overflows: when the cell is narrow, the confidence moves onto its own line. */}
|
|
111
|
-
<span className="flex max-w-full flex-wrap items-center justify-center gap-x-1.5 leading-tight">
|
|
112
|
-
<span className="inline-flex items-center gap-1">
|
|
113
|
-
{Icon ? <Icon aria-hidden="true" className="size-4 shrink-0" /> : null}
|
|
114
|
-
{WORD[choice]}
|
|
115
|
-
</span>
|
|
116
|
-
{/* The confidence and the verdict share one line, so a vote never makes the cell (or the card) taller. */}
|
|
117
|
-
{predicted && (confidenceText || verdict) ? (
|
|
118
|
-
<small className="inline-flex items-center gap-1 whitespace-nowrap text-xs">
|
|
119
|
-
{confidenceText ? <span className="font-normal opacity-80" data-cyclotron-confidence>{confidenceText}</span> : null}
|
|
120
|
-
{confidenceText && verdict ? <span aria-hidden="true" className="opacity-60">·</span> : null}
|
|
121
|
-
{verdict ? <span className="font-medium" data-verdict-word>{verdict === "match" ? "Match" : "Miss"}</span> : null}
|
|
122
|
-
</small>
|
|
123
|
-
) : null}
|
|
124
|
-
</span>
|
|
125
|
-
{predicted && verdict ? <span className="sr-only">{verdict === "match" ? ", matches your answer" : ", differs from your answer"}</span> : null}
|
|
126
|
-
</span>
|
|
127
|
-
);
|
|
128
|
-
})}
|
|
129
|
-
<div className="col-span-2 col-start-2 flex flex-col justify-center" data-confidence>
|
|
130
|
-
<div
|
|
131
|
-
aria-label={ready ? `Cyclotron confidence: leans ${leansAccept ? "Accept" : "Reject"}, ${Math.round(confidenceFillPercent(probability))} percent of the way` : "No confidence"}
|
|
132
|
-
className="relative h-2.5 w-full overflow-hidden rounded-full bg-[var(--feed-control)]"
|
|
133
|
-
data-confidence-bar
|
|
134
|
-
data-confidence-direction={ready ? (leansAccept ? "accept" : "reject") : "none"}
|
|
135
|
-
role="img"
|
|
136
|
-
>
|
|
137
|
-
{ready ? (
|
|
138
|
-
<span
|
|
139
|
-
className={cn("absolute inset-y-0 rounded-full", FILL[verdict ?? "neutral"])}
|
|
140
|
-
data-confidence-fill
|
|
141
|
-
data-fill-percent={Math.round(half * 2)}
|
|
142
|
-
style={{ left: leansAccept ? `${50 - half}%` : "50%", width: `${half}%` }}
|
|
143
|
-
/>
|
|
144
|
-
) : null}
|
|
145
|
-
<span aria-hidden="true" className={cn("absolute inset-y-0 left-1/2 w-1 -translate-x-1/2", surface === "footer" ? "bg-[var(--feed-footer)]" : "bg-[var(--feed-card)]")} />
|
|
146
|
-
</div>
|
|
147
|
-
{/* Screen-reader status only: a visible line under the bar would push it off the middle between the Cyclotron and You rows. */}
|
|
148
|
-
<small className="sr-only" data-cyclotron-state={prediction.state === "ready" ? undefined : prediction.state}>
|
|
149
|
-
{prediction.state === "ready" ? "" : prediction.state === "loading" ? "Loading…" : prediction.state === "error" ? "Prediction unavailable" : "No prediction"}
|
|
150
|
-
</small>
|
|
151
|
-
</div>
|
|
152
|
-
</div> : null}
|
|
153
|
-
|
|
154
|
-
<div className={cn("grid grid-cols-subgrid items-stretch", showPrediction ? "col-span-3" : "col-span-2")} data-review-row="you">
|
|
155
|
-
{showPrediction ? <span className={cn("self-center font-semibold", label)}>You</span> : null}
|
|
156
|
-
{(["accept", "reject"] as const).map((choice) => {
|
|
157
|
-
const on = answer === choice;
|
|
158
|
-
const Icon = choice === "accept" ? CheckIcon : XIcon;
|
|
159
|
-
return (
|
|
160
|
-
<button
|
|
161
|
-
aria-disabled={disabled ? true : undefined}
|
|
162
|
-
aria-pressed={on}
|
|
163
|
-
className={cn(
|
|
164
|
-
"inline-flex min-w-0 items-center justify-center gap-1.5 rounded-[16px] px-2 font-medium outline-none transition-colors focus-visible:ring-2 focus-visible:ring-ring @max-[17rem]/rating:gap-1 @max-[17rem]/rating:px-1",
|
|
165
|
-
cell,
|
|
166
|
-
on ? "bg-[var(--primary)] font-semibold text-[var(--primary-foreground)]" : "bg-[var(--feed-control)] text-foreground hover:bg-[var(--feed-control)]/70",
|
|
167
|
-
disabled && !on && "opacity-60",
|
|
168
|
-
)}
|
|
169
|
-
data-review-choice={choice}
|
|
170
|
-
key={choice}
|
|
171
|
-
onClick={() => onChoose(choice)}
|
|
172
|
-
type="button"
|
|
173
|
-
>
|
|
174
|
-
<Icon aria-hidden="true" className={cn("size-4", !on && "opacity-60")} />
|
|
175
|
-
{WORD[choice]}
|
|
176
|
-
</button>
|
|
177
|
-
);
|
|
178
|
-
})}
|
|
179
|
-
</div>
|
|
180
|
-
|
|
181
|
-
<div className={cn("flex flex-wrap items-center gap-x-3 text-xs text-muted-foreground", statusOverlay ? "absolute inset-x-4 bottom-0 h-4 overflow-hidden leading-4" : cn("min-h-5", showPrediction ? "col-span-3" : "col-span-2"))} data-feed-saved role="status">
|
|
182
|
-
{note ? <span data-feed-reason>{note}</span> : null}
|
|
183
|
-
{feedback.phase === "saving" ? <span className="inline-flex items-center gap-1"><Loader2Icon aria-hidden="true" className="size-3.5 animate-spin" />Saving…</span> : null}
|
|
184
|
-
{feedback.phase === "saved" ? <span className="inline-flex items-center gap-1" data-review-saved><CheckIcon aria-hidden="true" className="size-3.5" />Saved</span> : null}
|
|
185
|
-
</div>
|
|
186
|
-
{disabledReason ? <p className={cn("text-xs text-muted-foreground", showPrediction ? "col-span-3" : "col-span-2")} data-review-disabled-reason>{disabledReason}</p> : null}
|
|
187
|
-
{feedback.phase === "error" ? <p className={cn("text-xs text-destructive", showPrediction ? "col-span-3" : "col-span-2")} role="alert">{feedback.message}</p> : null}
|
|
188
|
-
</div>
|
|
189
|
-
);
|
|
190
|
-
}
|