@anthusai/papyrus 5.57.0 → 5.59.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/message-actions.js +68 -0
- package/amplify/data/message-actions.ts +70 -0
- package/amplify/data/resolvers/delete-own-message.js +30 -0
- package/amplify/data/resolvers/edit-message.js +33 -0
- package/amplify/data/resolvers/moderate-message.js +32 -0
- package/amplify/data/resolvers/post-message.js +68 -0
- package/amplify/data/resolvers/rename-thread.js +28 -0
- package/amplify/data/resource.js +28 -3
- package/amplify/data/resource.ts +28 -3
- package/amplify/data/schema.js +34 -5
- package/amplify/data/schema.ts +34 -5
- package/amplify/functions/category-action/handler.ts +51 -125
- package/amplify/functions/shared/reference-curation-metadata.ts +21 -29
- package/components/conversation-context.tsx +29 -2
- package/components/conversation-provider.tsx +5 -1
- package/components/conversation-thread.tsx +154 -25
- package/components/conversations-memory-source.ts +83 -0
- package/components/conversations-network-source.ts +52 -0
- package/components/conversations-page.tsx +14 -0
- package/components/conversations-source.ts +36 -0
- package/components/conversations-view.tsx +680 -0
- package/components/decision-control.tsx +307 -0
- package/components/list-detail-layout.tsx +44 -0
- package/components/needs-me-bodies.tsx +18 -17
- package/components/needs-me-feed.tsx +16 -32
- package/components/news-desk-taxonomy-client.ts +216 -62
- package/components/newsroom-article-editor.tsx +10 -4
- package/components/newsroom-organizer.css +34 -1
- package/components/newsroom-references-view.tsx +41 -100
- package/components/notification-cards.tsx +9 -8
- package/components/notification-center.tsx +5 -7
- package/components/proposals-view.tsx +4 -4
- package/components/reference-conversation.tsx +5 -0
- package/components/reference-feed.tsx +129 -127
- package/components/reference-review-card.tsx +9 -135
- package/components/references-page-loading.tsx +12 -6
- package/components/story-reporting-trail-client.ts +4 -3
- package/components/story-reporting-trail.tsx +8 -0
- package/components/topic-steering-workspace.tsx +44 -786
- package/components/use-conversation.ts +36 -2
- package/components/use-feed-preferences.ts +33 -0
- package/components/use-reference-ratings.ts +11 -7
- package/lib/category-repository.ts +14 -38
- package/lib/console-chat-client.ts +8 -178
- package/lib/conversation-feed.ts +96 -0
- package/lib/conversation.ts +73 -2
- package/lib/cyclotron-suggestions.ts +18 -36
- package/lib/local-preview-seed.ts +16 -27
- package/lib/needs-me-items.ts +4 -4
- package/lib/needs-me-ranking.ts +9 -22
- package/lib/newsroom-board.ts +10 -1
- package/lib/newsroom-demo-dashboard.ts +0 -7
- package/lib/newsroom-organizer-nav.ts +2 -2
- package/lib/post-message.ts +54 -0
- package/lib/proposals-admin.ts +2 -2
- package/lib/reference-feed-keys.ts +69 -0
- package/lib/reference-feed-ranking.ts +77 -160
- package/lib/reference-policy.ts +83 -11
- package/lib/reference-review-details.ts +21 -57
- package/lib/relevance-decisions.ts +6 -2
- package/lib/review-drafts.ts +2 -2
- package/lib/review-fixture.ts +1 -1
- package/lib/review-requests.ts +4 -4
- package/lib/semantic-graph.ts +1 -1
- package/lib/story-reporting-trail.ts +28 -2
- package/package.json +12 -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,13 +83,20 @@
|
|
|
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",
|
|
90
96
|
"./components/editorial-findings-panel": "./components/editorial-findings-panel.tsx",
|
|
91
97
|
"./components/editorial-preview-popover": "./components/editorial-preview-popover.tsx",
|
|
92
98
|
"./components/item-stream-view": "./components/item-stream-view.tsx",
|
|
99
|
+
"./components/list-detail-layout": "./components/list-detail-layout.tsx",
|
|
93
100
|
"./components/markdown-context": "./components/markdown-context.tsx",
|
|
94
101
|
"./components/markus-body-editor": "./components/markus-body-editor.tsx",
|
|
95
102
|
"./components/markus-ir-preview": "./components/markus-ir-preview.tsx",
|
|
@@ -164,7 +171,6 @@
|
|
|
164
171
|
"./components/reference-stories": "./components/reference-stories.tsx",
|
|
165
172
|
"./components/references-page-loading": "./components/references-page-loading.tsx",
|
|
166
173
|
"./components/references-prefetch": "./components/references-prefetch.ts",
|
|
167
|
-
"./components/review-rating-grid": "./components/review-rating-grid.tsx",
|
|
168
174
|
"./components/settings-page": "./components/settings-page.tsx",
|
|
169
175
|
"./components/signup-entries-panel": "./components/signup-entries-panel.tsx",
|
|
170
176
|
"./components/staging-banner": "./components/staging-banner.tsx",
|
|
@@ -195,6 +201,7 @@
|
|
|
195
201
|
"./components/use-console-visibility": "./components/use-console-visibility.ts",
|
|
196
202
|
"./components/use-conversation": "./components/use-conversation.ts",
|
|
197
203
|
"./components/use-editorial-checks": "./components/use-editorial-checks.ts",
|
|
204
|
+
"./components/use-feed-preferences": "./components/use-feed-preferences.ts",
|
|
198
205
|
"./components/use-media-query": "./components/use-media-query.ts",
|
|
199
206
|
"./components/use-near-viewport": "./components/use-near-viewport.ts",
|
|
200
207
|
"./components/use-paged-content-item-versions": "./components/use-paged-content-item-versions.ts",
|
|
@@ -234,6 +241,7 @@
|
|
|
234
241
|
"./lib/content-repository": "./lib/content-repository.ts",
|
|
235
242
|
"./lib/content-source-context": "./lib/content-source-context.ts",
|
|
236
243
|
"./lib/content-types": "./lib/content-types.ts",
|
|
244
|
+
"./lib/conversation-feed": "./lib/conversation-feed.ts",
|
|
237
245
|
"./lib/conversation": "./lib/conversation.ts",
|
|
238
246
|
"./lib/curation-client": "./lib/curation-client.ts",
|
|
239
247
|
"./lib/curation-delegation-helpers": "./lib/curation-delegation-helpers.ts",
|
|
@@ -321,6 +329,7 @@
|
|
|
321
329
|
"./lib/papyrus-web-locations": "./lib/papyrus-web-locations.ts",
|
|
322
330
|
"./lib/pilobol-sample": "./lib/pilobol-sample.ts",
|
|
323
331
|
"./lib/plugin-settings": "./lib/plugin-settings.ts",
|
|
332
|
+
"./lib/post-message": "./lib/post-message.ts",
|
|
324
333
|
"./lib/presentation-footer": "./lib/presentation-footer.ts",
|
|
325
334
|
"./lib/pretext-layout": "./lib/pretext-layout.ts",
|
|
326
335
|
"./lib/priming-proof-report": "./lib/priming-proof-report.ts",
|
|
@@ -348,6 +357,7 @@
|
|
|
348
357
|
"./lib/reference-capture": "./lib/reference-capture.ts",
|
|
349
358
|
"./lib/reference-display": "./lib/reference-display.ts",
|
|
350
359
|
"./lib/reference-feed-cache": "./lib/reference-feed-cache.ts",
|
|
360
|
+
"./lib/reference-feed-keys": "./lib/reference-feed-keys.ts",
|
|
351
361
|
"./lib/reference-feed-ranking": "./lib/reference-feed-ranking.ts",
|
|
352
362
|
"./lib/reference-policy": "./lib/reference-policy.ts",
|
|
353
363
|
"./lib/reference-preview": "./lib/reference-preview.ts",
|
|
@@ -510,7 +520,7 @@
|
|
|
510
520
|
"optional": true
|
|
511
521
|
}
|
|
512
522
|
},
|
|
513
|
-
"version": "5.
|
|
523
|
+
"version": "5.59.0",
|
|
514
524
|
"dependencies": {
|
|
515
525
|
"@aws-amplify/adapter-nextjs": "^1.7.3",
|
|
516
526
|
"@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
|
-
}
|