@anthusai/papyrus 5.56.1 → 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-context.tsx +52 -0
- package/components/conversation-provider.tsx +20 -0
- package/components/conversation-thread.tsx +188 -0
- 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/needs-me-reference-detail.tsx +2 -0
- package/components/news-desk-taxonomy-client.ts +322 -37
- package/components/newsroom-organizer.css +24 -0
- package/components/newsroom-references-view.tsx +9 -0
- package/components/notification-cards.tsx +5 -4
- package/components/reference-conversation.tsx +43 -0
- package/components/reference-feed.tsx +69 -37
- 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 +43 -784
- package/components/use-conversation.ts +84 -0
- package/components/use-reference-ratings.ts +11 -7
- package/lib/category-repository.ts +7 -0
- package/lib/conversation-feed.ts +96 -0
- package/lib/conversation.ts +154 -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 +15 -2
- package/routes.manifest.json +1 -1
- package/components/review-rating-grid.tsx +0 -190
|
@@ -44,7 +44,6 @@ import {
|
|
|
44
44
|
loadEditorReferenceReviewDetails,
|
|
45
45
|
loadEditionForumThreads,
|
|
46
46
|
loadNewsroomAssignmentPage,
|
|
47
|
-
loadNewsroomMessagePage,
|
|
48
47
|
loadNewsroomReferencePage,
|
|
49
48
|
loadNewsroomSemanticNodePage,
|
|
50
49
|
appendForumThreadMessageRecord,
|
|
@@ -68,14 +67,11 @@ import {
|
|
|
68
67
|
loadCyclotronPrimingRequests,
|
|
69
68
|
requestCyclotronReviewRate,
|
|
70
69
|
} from "./news-desk-taxonomy-client";
|
|
71
|
-
import { listConsoleThreads } from "../lib/console-chat-client";
|
|
72
70
|
import {
|
|
73
71
|
effectiveAssignmentsIndexFilters,
|
|
74
|
-
effectiveMessagesIndexFilters,
|
|
75
72
|
effectiveReferencesIndexFilters,
|
|
76
73
|
normalizeReferenceIndexOrder,
|
|
77
74
|
readAssignmentsIndexFilters,
|
|
78
|
-
readMessagesIndexFilters,
|
|
79
75
|
readReferencesIndexFilters,
|
|
80
76
|
referencesStatusFromUrl,
|
|
81
77
|
referencesStatusToUrl,
|
|
@@ -101,6 +97,8 @@ import { referencePreviewsByLineage } from "../lib/reference-preview";
|
|
|
101
97
|
import { NewsroomOpsOverview } from "./newsroom-ops-overview";
|
|
102
98
|
import { NewsroomCurationBootstrap } from "./newsroom-curation-bootstrap";
|
|
103
99
|
import { NewsroomReferencesView } from "./newsroom-references-view";
|
|
100
|
+
import { ConversationProvider } from "./conversation-provider";
|
|
101
|
+
import { ConversationsPage } from "./conversations-page";
|
|
104
102
|
import { NewsroomAssignmentsView } from "./newsroom-assignments-view";
|
|
105
103
|
import { NewsroomTopicsView } from "./newsroom-topics-view";
|
|
106
104
|
import { MarkdownContext } from "./markdown-context";
|
|
@@ -220,7 +218,6 @@ import {
|
|
|
220
218
|
import {
|
|
221
219
|
buildForumThreadUrl,
|
|
222
220
|
getForumMessageAnchorId,
|
|
223
|
-
isForumThreadId,
|
|
224
221
|
parseForumMessageAnchorFromHash,
|
|
225
222
|
pushForumThreadUrl,
|
|
226
223
|
readCurrentForumRoute,
|
|
@@ -317,20 +314,6 @@ type NewsroomCardRecord = {
|
|
|
317
314
|
templateRole?: NewsroomCardTemplateRole;
|
|
318
315
|
title: ReactNode;
|
|
319
316
|
};
|
|
320
|
-
type ConsoleThreadSummary = {
|
|
321
|
-
id: string;
|
|
322
|
-
threadKind: string;
|
|
323
|
-
status: string;
|
|
324
|
-
title: string;
|
|
325
|
-
summary?: string | null;
|
|
326
|
-
primaryAnchorKey?: string | null;
|
|
327
|
-
createdByLabel?: string | null;
|
|
328
|
-
messageCount?: number | null;
|
|
329
|
-
lastMessageAt?: string | null;
|
|
330
|
-
createdAt: string;
|
|
331
|
-
updatedAt: string;
|
|
332
|
-
newsroomFeedKey?: string | null;
|
|
333
|
-
};
|
|
334
317
|
type NewsroomDetailAction = {
|
|
335
318
|
ariaLabel?: string;
|
|
336
319
|
icon?: ReactNode;
|
|
@@ -793,17 +776,22 @@ function NewsDeskDashboard({
|
|
|
793
776
|
() => (isLocalPreviewEnabled() && !semanticRelations.some((relation) => relation.predicate === "relevance_decision_is") ? createLocalPreviewCyclotronFixtures(references) : null),
|
|
794
777
|
[references, semanticRelations],
|
|
795
778
|
);
|
|
779
|
+
// A Reference keeps its latest vote on itself; the preview fixtures overlay votes on the first few.
|
|
780
|
+
const votedReferences = useMemo(
|
|
781
|
+
() => (cyclotronPreviewFixtures ? references.map((reference) => cyclotronPreviewFixtures.votedReferences.get(reference.id) ?? reference) : references),
|
|
782
|
+
[cyclotronPreviewFixtures, references],
|
|
783
|
+
);
|
|
796
784
|
const relevanceDecisions = useMemo(
|
|
797
785
|
() => currentRelevanceDecisions(cyclotronPreviewFixtures ? [...semanticRelations, ...cyclotronPreviewFixtures.relations] : semanticRelations),
|
|
798
786
|
[cyclotronPreviewFixtures, semanticRelations],
|
|
799
787
|
);
|
|
800
788
|
const delegatedCurations = useMemo(
|
|
801
|
-
() => delegatedCurationsByReference(
|
|
802
|
-
[
|
|
789
|
+
() => delegatedCurationsByReference(votedReferences),
|
|
790
|
+
[votedReferences],
|
|
803
791
|
);
|
|
804
792
|
const humanCuratedReferenceLineageIds = useMemo(
|
|
805
|
-
() => referencesWithHumanLatestCuration(
|
|
806
|
-
[
|
|
793
|
+
() => referencesWithHumanLatestCuration(votedReferences),
|
|
794
|
+
[votedReferences],
|
|
807
795
|
);
|
|
808
796
|
const relevanceCyclotronId = useMemo(
|
|
809
797
|
() => [...relevanceDecisions.values()].find((decision) => decision.cyclotronId)?.cyclotronId ?? null,
|
|
@@ -922,8 +910,8 @@ function NewsDeskDashboard({
|
|
|
922
910
|
const referenceReviewsPending = useRef(new Map<string, Promise<void>>());
|
|
923
911
|
const [selectedReviewRead, setSelectedReviewRead] = useState<{ lineageId: string; state: "loading" | "ready" | "error"; error: string | null }>({ lineageId: "", state: "ready", error: null });
|
|
924
912
|
const [selectedReviewAttempt, setSelectedReviewAttempt] = useState(0);
|
|
925
|
-
const reviewReasons = useMemo(() => latestReferenceReviewReasons(
|
|
926
|
-
const reviewSectionIds = useMemo(() => latestReferenceReviewSections(
|
|
913
|
+
const reviewReasons = useMemo(() => latestReferenceReviewReasons(references), [references]);
|
|
914
|
+
const reviewSectionIds = useMemo(() => latestReferenceReviewSections(references), [references]);
|
|
927
915
|
|
|
928
916
|
useEffect(() => {
|
|
929
917
|
referencesRef.current = references;
|
|
@@ -1348,10 +1336,9 @@ function NewsDeskDashboard({
|
|
|
1348
1336
|
if (!canLoadPrivateCorpus || activeTab !== "references" || !selectedReviewLineageId) return;
|
|
1349
1337
|
let active = true;
|
|
1350
1338
|
setSelectedReviewRead({ lineageId: selectedReviewLineageId, state: "loading", error: null });
|
|
1351
|
-
void loadEditorReferenceReviewDetails(selectedReviewLineageId).then(({ relations
|
|
1339
|
+
void loadEditorReferenceReviewDetails(selectedReviewLineageId).then(({ relations }) => {
|
|
1352
1340
|
if (!active) return;
|
|
1353
1341
|
setSemanticRelations((current) => [...current.filter((relation) => relation.subjectLineageId !== selectedReviewLineageId || relation.subjectKind !== "reference"), ...relations]);
|
|
1354
|
-
setMessages((current) => [...current.filter((message) => !history.some((entry) => entry.id === message.id)), ...history]);
|
|
1355
1342
|
setSelectedReviewRead({ lineageId: selectedReviewLineageId, state: "ready", error: null });
|
|
1356
1343
|
}).catch((error) => {
|
|
1357
1344
|
if (active) setSelectedReviewRead({ lineageId: selectedReviewLineageId, state: "error", error: error instanceof Error ? error.message : "Reference review details could not be loaded." });
|
|
@@ -1638,59 +1625,18 @@ function NewsDeskDashboard({
|
|
|
1638
1625
|
setActionState({ id: reference.id, message: `${action} pending`, tone: "pending" });
|
|
1639
1626
|
if (dashboard.isDemo) {
|
|
1640
1627
|
const now = new Date().toISOString();
|
|
1641
|
-
const messageId = `message-demo-${reference.id}-${action}-${now.replace(/[^0-9]/g, "")}`;
|
|
1642
|
-
const relationId = `semantic-relation-demo-${messageId}`;
|
|
1643
1628
|
applyReferenceRecord(buildReviewedReferenceRecord(referencesRef.current, reference, {
|
|
1644
1629
|
actorLabel: authState.label,
|
|
1645
1630
|
nextStatus,
|
|
1646
1631
|
note: note ?? null,
|
|
1647
1632
|
now,
|
|
1648
|
-
|
|
1649
|
-
setMessages((current) => [{
|
|
1650
|
-
id: messageId,
|
|
1651
|
-
messageKind: "reference_curation",
|
|
1652
|
-
messageDomain: "commentary",
|
|
1653
|
-
status: "active",
|
|
1654
|
-
body: note?.trim() || `${authState.label} marked this reference ${nextStatus}.`,
|
|
1655
|
-
summary: `${reference.title ?? reference.externalItemId}: ${nextStatus}`,
|
|
1656
|
-
source: "newsroom",
|
|
1657
|
-
authorLabel: authState.label,
|
|
1658
|
-
createdAt: now,
|
|
1659
|
-
updatedAt: now,
|
|
1660
|
-
metadata: {
|
|
1661
|
-
referenceLineageId: reference.lineageId ?? reference.id,
|
|
1662
|
-
action,
|
|
1663
|
-
curationStatus: nextStatus,
|
|
1633
|
+
vote: {
|
|
1664
1634
|
reasonCode: action === "reject" ? reasonCode ?? null : null,
|
|
1665
|
-
|
|
1666
|
-
|
|
1635
|
+
sectionId: action === "accept" ? sectionId ?? null : null,
|
|
1636
|
+
decisionRelationId: relevance?.decisionRelationId ?? null,
|
|
1637
|
+
shareable: relevance ? relevance.shareable : null,
|
|
1667
1638
|
},
|
|
1668
|
-
}
|
|
1669
|
-
setSemanticRelations((current) => [{
|
|
1670
|
-
id: relationId,
|
|
1671
|
-
relationState: "current",
|
|
1672
|
-
predicate: "comment",
|
|
1673
|
-
relationTypeId: "semantic-relation-type-comment",
|
|
1674
|
-
relationTypeKey: "comment",
|
|
1675
|
-
relationDomain: "commentary",
|
|
1676
|
-
subjectKind: "message",
|
|
1677
|
-
subjectId: messageId,
|
|
1678
|
-
subjectLineageId: messageId,
|
|
1679
|
-
subjectVersionNumber: 1,
|
|
1680
|
-
objectKind: "reference",
|
|
1681
|
-
objectId: reference.id,
|
|
1682
|
-
objectLineageId: reference.lineageId ?? reference.id,
|
|
1683
|
-
objectVersionNumber: reference.versionNumber ?? null,
|
|
1684
|
-
subjectStateKey: `message#${messageId}#current`,
|
|
1685
|
-
objectStateKey: `reference#${reference.lineageId ?? reference.id}#current`,
|
|
1686
|
-
objectSubjectStateKey: `reference#${reference.lineageId ?? reference.id}#current#message`,
|
|
1687
|
-
predicateObjectStateKey: `comment#reference#${reference.lineageId ?? reference.id}#current`,
|
|
1688
|
-
subjectVersionKey: `message#${messageId}`,
|
|
1689
|
-
objectVersionKey: `reference#${reference.id}`,
|
|
1690
|
-
rank: 1,
|
|
1691
|
-
reviewRecommended: false,
|
|
1692
|
-
importedAt: now,
|
|
1693
|
-
}, ...current]);
|
|
1639
|
+
}));
|
|
1694
1640
|
setActionState({ id: reference.id, message: `${action} saved`, tone: "ok" });
|
|
1695
1641
|
return;
|
|
1696
1642
|
}
|
|
@@ -1716,6 +1662,12 @@ function NewsDeskDashboard({
|
|
|
1716
1662
|
nextStatus: status,
|
|
1717
1663
|
note: note?.trim() || null,
|
|
1718
1664
|
now,
|
|
1665
|
+
vote: {
|
|
1666
|
+
reasonCode: action === "reject" ? reasonCode ?? null : null,
|
|
1667
|
+
sectionId: action === "accept" ? sectionId ?? null : null,
|
|
1668
|
+
decisionRelationId: relevance?.decisionRelationId ?? null,
|
|
1669
|
+
shareable: relevance ? relevance.shareable : null,
|
|
1670
|
+
},
|
|
1719
1671
|
}));
|
|
1720
1672
|
setActionState({ id: reference.id, message: `${action} saved`, tone: "ok" });
|
|
1721
1673
|
retryReferenceReviewData();
|
|
@@ -3049,7 +3001,7 @@ function NewsDeskDashboard({
|
|
|
3049
3001
|
: formatDeskSectionHeadline(activeTab);
|
|
3050
3002
|
|
|
3051
3003
|
return (
|
|
3052
|
-
|
|
3004
|
+
<ConversationProvider authorLabel={authState.label} demo={Boolean(dashboard.isDemo)}>
|
|
3053
3005
|
<NewsroomOpsShell
|
|
3054
3006
|
activeTab={activeTab}
|
|
3055
3007
|
appTitle={SITE_BRAND.appTitle}
|
|
@@ -3216,16 +3168,7 @@ function NewsDeskDashboard({
|
|
|
3216
3168
|
</NewsroomCurationBootstrap>
|
|
3217
3169
|
) : null}
|
|
3218
3170
|
{activeTab === "messages" ? (
|
|
3219
|
-
<
|
|
3220
|
-
assignments={assignments}
|
|
3221
|
-
graph={graph}
|
|
3222
|
-
initialForumThreadId={initialSelection.forumThread}
|
|
3223
|
-
initialMessageId={initialSelection.message}
|
|
3224
|
-
isDemo={Boolean(dashboard.isDemo)}
|
|
3225
|
-
messages={messages}
|
|
3226
|
-
newsroomSections={newsroomSections}
|
|
3227
|
-
summary={summary}
|
|
3228
|
-
/>
|
|
3171
|
+
<ConversationsPage authorLabel={authState.label} demo={Boolean(dashboard.isDemo)} />
|
|
3229
3172
|
) : null}
|
|
3230
3173
|
{activeTab === "assignments" ? (
|
|
3231
3174
|
<AssignmentDeskView
|
|
@@ -3286,7 +3229,7 @@ function NewsDeskDashboard({
|
|
|
3286
3229
|
</div>
|
|
3287
3230
|
</NewsroomOpsShell>
|
|
3288
3231
|
{topBarSearchControl.dialog}
|
|
3289
|
-
|
|
3232
|
+
</ConversationProvider>
|
|
3290
3233
|
);
|
|
3291
3234
|
}
|
|
3292
3235
|
|
|
@@ -6846,574 +6789,6 @@ function NewsroomDeskSectionLede({
|
|
|
6846
6789
|
);
|
|
6847
6790
|
}
|
|
6848
6791
|
|
|
6849
|
-
function MessagesDeskView({
|
|
6850
|
-
assignments,
|
|
6851
|
-
graph,
|
|
6852
|
-
initialForumThreadId = null,
|
|
6853
|
-
initialMessageId,
|
|
6854
|
-
isDemo = false,
|
|
6855
|
-
messages,
|
|
6856
|
-
newsroomSections,
|
|
6857
|
-
summary,
|
|
6858
|
-
}: {
|
|
6859
|
-
assignments: AssignmentRecord[];
|
|
6860
|
-
graph: SemanticGraph;
|
|
6861
|
-
initialForumThreadId?: string | null;
|
|
6862
|
-
initialMessageId?: string | null;
|
|
6863
|
-
isDemo?: boolean;
|
|
6864
|
-
messages: MessageRecord[];
|
|
6865
|
-
newsroomSections: NewsroomSectionRecord[];
|
|
6866
|
-
summary?: NewsroomSummaryRecord | null;
|
|
6867
|
-
}) {
|
|
6868
|
-
const resolvedInitialForumThreadId = initialForumThreadId
|
|
6869
|
-
?? (isForumThreadId(initialMessageId) ? initialMessageId : null);
|
|
6870
|
-
const forumMessageAnchorId = useForumMessageAnchorId();
|
|
6871
|
-
const [kindFilter, setKindFilter] = useState(() => {
|
|
6872
|
-
if (resolvedInitialForumThreadId) return "__forum";
|
|
6873
|
-
if (typeof window === "undefined") return "";
|
|
6874
|
-
return readMessagesIndexFilters(new URLSearchParams(window.location.search)).kind;
|
|
6875
|
-
});
|
|
6876
|
-
const [domainFilter, setDomainFilter] = useState(() => {
|
|
6877
|
-
if (typeof window === "undefined") return "";
|
|
6878
|
-
return readMessagesIndexFilters(new URLSearchParams(window.location.search)).domain;
|
|
6879
|
-
});
|
|
6880
|
-
const [selectedMessageId, setSelectedMessageId] = useState(initialMessageId ?? "");
|
|
6881
|
-
const [isMessageDetailOpen, setIsMessageDetailOpen] = useState(Boolean(initialMessageId));
|
|
6882
|
-
const [consoleThreads, setConsoleThreads] = useState<ConsoleThreadSummary[]>([]);
|
|
6883
|
-
const [consoleThreadsError, setConsoleThreadsError] = useState<string | null>(null);
|
|
6884
|
-
const [selectedForumEditionId, setSelectedForumEditionId] = useState("");
|
|
6885
|
-
const [selectedForumSectionId, setSelectedForumSectionId] = useState("");
|
|
6886
|
-
const [forumScopeFilter, setForumScopeFilter] = useState<"all" | "edition" | "section">("all");
|
|
6887
|
-
const [forumActivityFilter, setForumActivityFilter] = useState<"active" | "all">("active");
|
|
6888
|
-
const [forumThreads, setForumThreads] = useState<ForumThreadWithMessages[]>([]);
|
|
6889
|
-
const [forumThreadsLoading, setForumThreadsLoading] = useState(false);
|
|
6890
|
-
const [forumThreadsError, setForumThreadsError] = useState<string | null>(null);
|
|
6891
|
-
const [selectedForumThreadId, setSelectedForumThreadId] = useState("");
|
|
6892
|
-
const [forumView, setForumView] = useState<ForumViewState>({ mode: "index" });
|
|
6893
|
-
const [forumComposeSummary, setForumComposeSummary] = useState("");
|
|
6894
|
-
const [forumComposeContent, setForumComposeContent] = useState("");
|
|
6895
|
-
const [forumReplyParentId, setForumReplyParentId] = useState("");
|
|
6896
|
-
const [forumComposeError, setForumComposeError] = useState<string | null>(null);
|
|
6897
|
-
const [newForumThreadDraft, setNewForumThreadDraft] = useState<ForumNewThreadDraft>(createDefaultForumNewThreadDraft(""));
|
|
6898
|
-
const [newForumThreadOpen, setNewForumThreadOpen] = useState(false);
|
|
6899
|
-
const [newForumThreadError, setNewForumThreadError] = useState<string | null>(null);
|
|
6900
|
-
const [deletingForumMessageId, setDeletingForumMessageId] = useState("");
|
|
6901
|
-
const messageKind = kindFilter === "__chat_detail" ? "console_chat_turn" : kindFilter;
|
|
6902
|
-
const isForumMode = kindFilter === "__forum";
|
|
6903
|
-
const syncMessagesIndexUrl = useCallback((nextKind: string, nextDomain: string, replace = true) => {
|
|
6904
|
-
if (isDemo || isForumMode || isMessageDetailOpen) return;
|
|
6905
|
-
syncBrowserNewsroomIndexUrl(
|
|
6906
|
-
"messages",
|
|
6907
|
-
effectiveMessagesIndexFilters({ kind: nextKind, domain: nextDomain }),
|
|
6908
|
-
{ replace },
|
|
6909
|
-
);
|
|
6910
|
-
}, [isDemo, isForumMode, isMessageDetailOpen]);
|
|
6911
|
-
useEffect(() => {
|
|
6912
|
-
if (isDemo || isForumMode || isMessageDetailOpen) return;
|
|
6913
|
-
syncMessagesIndexUrl(kindFilter, domainFilter, true);
|
|
6914
|
-
}, [domainFilter, isDemo, isForumMode, isMessageDetailOpen, kindFilter, syncMessagesIndexUrl]);
|
|
6915
|
-
const feed = useNewsroomPagedRows({
|
|
6916
|
-
initialItems: messages,
|
|
6917
|
-
enabled: !isDemo && kindFilter !== "__chat_sessions" && !isForumMode,
|
|
6918
|
-
resetKey: `messages:${kindFilter}:${domainFilter}`,
|
|
6919
|
-
loadPage: (nextToken) => loadNewsroomMessagePage({
|
|
6920
|
-
kind: messageKind,
|
|
6921
|
-
domain: domainFilter,
|
|
6922
|
-
nextToken,
|
|
6923
|
-
}),
|
|
6924
|
-
});
|
|
6925
|
-
useEffect(() => {
|
|
6926
|
-
if (isDemo || kindFilter !== "__chat_sessions") return;
|
|
6927
|
-
let active = true;
|
|
6928
|
-
void listConsoleThreads(100)
|
|
6929
|
-
.then((threads) => {
|
|
6930
|
-
if (!active) return;
|
|
6931
|
-
setConsoleThreads(threads);
|
|
6932
|
-
setConsoleThreadsError(null);
|
|
6933
|
-
})
|
|
6934
|
-
.catch((error) => {
|
|
6935
|
-
if (!active) return;
|
|
6936
|
-
setConsoleThreadsError(error instanceof Error ? error.message : "Unable to load chat sessions.");
|
|
6937
|
-
});
|
|
6938
|
-
return () => {
|
|
6939
|
-
active = false;
|
|
6940
|
-
};
|
|
6941
|
-
}, [isDemo, kindFilter]);
|
|
6942
|
-
const [messageOverviewEditions, setMessageOverviewEditions] = useState<ResolvedOverviewEdition[]>([]);
|
|
6943
|
-
useEffect(() => {
|
|
6944
|
-
if (isDemo) {
|
|
6945
|
-
setMessageOverviewEditions([]);
|
|
6946
|
-
return;
|
|
6947
|
-
}
|
|
6948
|
-
let active = true;
|
|
6949
|
-
void loadEditorOverviewEditionData()
|
|
6950
|
-
.then((data) => {
|
|
6951
|
-
if (!active) return;
|
|
6952
|
-
setMessageOverviewEditions(resolveOverviewEditions({
|
|
6953
|
-
editions: data.editions,
|
|
6954
|
-
editionSlots: data.editionSlots,
|
|
6955
|
-
assignments: data.assignments,
|
|
6956
|
-
}));
|
|
6957
|
-
})
|
|
6958
|
-
.catch(() => {
|
|
6959
|
-
if (!active) return;
|
|
6960
|
-
setMessageOverviewEditions(resolveOverviewEditions({
|
|
6961
|
-
editions: [],
|
|
6962
|
-
editionSlots: [],
|
|
6963
|
-
assignments,
|
|
6964
|
-
}));
|
|
6965
|
-
});
|
|
6966
|
-
return () => {
|
|
6967
|
-
active = false;
|
|
6968
|
-
};
|
|
6969
|
-
}, [assignments, isDemo]);
|
|
6970
|
-
const availableSections = useMemo(
|
|
6971
|
-
() => sortNewsroomSections(newsroomSections).filter((section) => section.enabled !== false),
|
|
6972
|
-
[newsroomSections],
|
|
6973
|
-
);
|
|
6974
|
-
useEffect(() => {
|
|
6975
|
-
if (!messageOverviewEditions.length) {
|
|
6976
|
-
setSelectedForumEditionId("");
|
|
6977
|
-
return;
|
|
6978
|
-
}
|
|
6979
|
-
if (selectedForumEditionId && messageOverviewEditions.some((edition) => edition.editionId === selectedForumEditionId)) {
|
|
6980
|
-
return;
|
|
6981
|
-
}
|
|
6982
|
-
const preferred = messageOverviewEditions.find((edition) => edition.isNearestUpcoming) ?? messageOverviewEditions[0];
|
|
6983
|
-
setSelectedForumEditionId(preferred.editionId);
|
|
6984
|
-
}, [messageOverviewEditions, selectedForumEditionId]);
|
|
6985
|
-
useEffect(() => {
|
|
6986
|
-
if (!availableSections.length) {
|
|
6987
|
-
setSelectedForumSectionId("");
|
|
6988
|
-
setNewForumThreadDraft((current) => ({ ...current, sectionId: "" }));
|
|
6989
|
-
return;
|
|
6990
|
-
}
|
|
6991
|
-
if (!selectedForumSectionId || !availableSections.some((section) => section.id === selectedForumSectionId)) {
|
|
6992
|
-
setSelectedForumSectionId(availableSections[0].id);
|
|
6993
|
-
}
|
|
6994
|
-
setNewForumThreadDraft((current) => (
|
|
6995
|
-
current.sectionId
|
|
6996
|
-
? current
|
|
6997
|
-
: { ...current, sectionId: availableSections[0].id }
|
|
6998
|
-
));
|
|
6999
|
-
}, [availableSections, selectedForumSectionId]);
|
|
7000
|
-
const refreshForumThreads = useCallback(async () => {
|
|
7001
|
-
if (!selectedForumEditionId) {
|
|
7002
|
-
setForumThreads([]);
|
|
7003
|
-
setForumThreadsError(null);
|
|
7004
|
-
return;
|
|
7005
|
-
}
|
|
7006
|
-
setForumThreadsLoading(true);
|
|
7007
|
-
try {
|
|
7008
|
-
const sectionIdForQuery = forumScopeFilter === "section" ? selectedForumSectionId : "";
|
|
7009
|
-
const sectionForQuery = availableSections.find((section) => section.id === sectionIdForQuery) ?? null;
|
|
7010
|
-
const result = await loadEditionForumThreads({
|
|
7011
|
-
editionId: selectedForumEditionId,
|
|
7012
|
-
sectionId: sectionIdForQuery || undefined,
|
|
7013
|
-
sectionKey: sectionForQuery?.id ?? undefined,
|
|
7014
|
-
includeMessages: true,
|
|
7015
|
-
status: forumActivityFilter === "active" ? "active" : "",
|
|
7016
|
-
});
|
|
7017
|
-
const merged = [...result.editionThreads, ...result.sectionThreads]
|
|
7018
|
-
.sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")));
|
|
7019
|
-
setForumThreads(merged);
|
|
7020
|
-
setForumThreadsError(null);
|
|
7021
|
-
setSelectedForumThreadId((current) => (current && merged.some((thread) => thread.id === current) ? current : ""));
|
|
7022
|
-
} catch (error) {
|
|
7023
|
-
setForumThreads([]);
|
|
7024
|
-
setForumThreadsError(error instanceof Error ? error.message : "Could not load edition forum threads.");
|
|
7025
|
-
} finally {
|
|
7026
|
-
setForumThreadsLoading(false);
|
|
7027
|
-
}
|
|
7028
|
-
}, [availableSections, forumActivityFilter, forumScopeFilter, selectedForumEditionId, selectedForumSectionId]);
|
|
7029
|
-
useEffect(() => {
|
|
7030
|
-
if (!isForumMode || isDemo) return;
|
|
7031
|
-
void refreshForumThreads();
|
|
7032
|
-
}, [isDemo, isForumMode, refreshForumThreads]);
|
|
7033
|
-
useEffect(() => {
|
|
7034
|
-
if (!isForumMode) return;
|
|
7035
|
-
const threadId = resolvedInitialForumThreadId ?? readCurrentForumRoute().threadId;
|
|
7036
|
-
if (!threadId || forumThreadsLoading) return;
|
|
7037
|
-
if (!forumThreads.some((thread) => thread.id === threadId)) return;
|
|
7038
|
-
if (selectedForumThreadId === threadId && forumView.mode === "thread") return;
|
|
7039
|
-
setSelectedForumThreadId(threadId);
|
|
7040
|
-
setForumView({ mode: "thread", threadId });
|
|
7041
|
-
}, [
|
|
7042
|
-
forumThreads,
|
|
7043
|
-
forumThreadsLoading,
|
|
7044
|
-
forumView.mode,
|
|
7045
|
-
isForumMode,
|
|
7046
|
-
resolvedInitialForumThreadId,
|
|
7047
|
-
selectedForumThreadId,
|
|
7048
|
-
]);
|
|
7049
|
-
|
|
7050
|
-
useEffect(() => {
|
|
7051
|
-
if (!isForumMode) return;
|
|
7052
|
-
const handlePopState = () => {
|
|
7053
|
-
const route = readCurrentForumRoute();
|
|
7054
|
-
if (route.threadId) {
|
|
7055
|
-
setSelectedForumThreadId(route.threadId);
|
|
7056
|
-
setForumView({ mode: "thread", threadId: route.threadId });
|
|
7057
|
-
return;
|
|
7058
|
-
}
|
|
7059
|
-
setSelectedForumThreadId("");
|
|
7060
|
-
setForumView({ mode: "index" });
|
|
7061
|
-
};
|
|
7062
|
-
window.addEventListener("popstate", handlePopState);
|
|
7063
|
-
return () => window.removeEventListener("popstate", handlePopState);
|
|
7064
|
-
}, [isForumMode]);
|
|
7065
|
-
const feedMessages = isDemo ? messages : feed.items;
|
|
7066
|
-
const messageKindCounts = summary?.facets?.messages?.byKind ?? summary?.messageKindCounts ?? countMessagesBy(messages, "messageKind");
|
|
7067
|
-
const messageDomainCounts = summary?.facets?.messages?.byDomain ?? summary?.messageDomainCounts ?? countMessagesBy(messages, "messageDomain");
|
|
7068
|
-
const messageKinds = useMemo(() => sortedCountOptions(messageKindCounts).map((option) => option.key), [messageKindCounts]);
|
|
7069
|
-
const messageDomains = useMemo(() => sortedCountOptions(messageDomainCounts).map((option) => option.key), [messageDomainCounts]);
|
|
7070
|
-
const chatSessionMessages = useMemo(() => consoleThreads.map(consoleThreadToMessageRecord), [consoleThreads]);
|
|
7071
|
-
const forumSectionKeyByThreadId = useMemo(() => {
|
|
7072
|
-
const map = new Map<string, string>();
|
|
7073
|
-
for (const thread of forumThreads) {
|
|
7074
|
-
const sectionKey = normalizeForumThreadSectionKey(thread);
|
|
7075
|
-
if (sectionKey) map.set(thread.id, sectionKey);
|
|
7076
|
-
}
|
|
7077
|
-
return map;
|
|
7078
|
-
}, [forumThreads]);
|
|
7079
|
-
const filteredForumThreads = useMemo(() => forumThreads.filter((thread) => {
|
|
7080
|
-
if (forumScopeFilter === "edition" && thread.scope !== "edition") return false;
|
|
7081
|
-
if (forumScopeFilter === "section" && thread.scope !== "section") return false;
|
|
7082
|
-
if (forumScopeFilter === "section" && selectedForumSectionId) {
|
|
7083
|
-
const selectedSection = availableSections.find((section) => section.id === selectedForumSectionId) ?? null;
|
|
7084
|
-
if (selectedSection) {
|
|
7085
|
-
const threadSectionKey = forumSectionKeyByThreadId.get(thread.id) ?? "";
|
|
7086
|
-
if (threadSectionKey && threadSectionKey !== selectedSection.id) return false;
|
|
7087
|
-
}
|
|
7088
|
-
}
|
|
7089
|
-
if (forumActivityFilter === "active" && thread.status !== "active") return false;
|
|
7090
|
-
return true;
|
|
7091
|
-
}), [availableSections, forumActivityFilter, forumScopeFilter, forumSectionKeyByThreadId, forumThreads, selectedForumSectionId]);
|
|
7092
|
-
const selectedForumThread = filteredForumThreads.find((thread) => thread.id === selectedForumThreadId) ?? null;
|
|
7093
|
-
const visibleMessages = useMemo(() => {
|
|
7094
|
-
if (kindFilter === "__chat_sessions") return chatSessionMessages;
|
|
7095
|
-
return feedMessages
|
|
7096
|
-
.filter((message) => !messageKind || message.messageKind === messageKind)
|
|
7097
|
-
.filter((message) => !domainFilter || message.messageDomain === domainFilter)
|
|
7098
|
-
.sort((left, right) => right.createdAt.localeCompare(left.createdAt));
|
|
7099
|
-
}, [chatSessionMessages, domainFilter, feedMessages, kindFilter, messageKind]);
|
|
7100
|
-
const selectedMessage = visibleMessages.find((message) => message.id === selectedMessageId)
|
|
7101
|
-
?? (initialMessageId ? messages.find((message) => message.id === initialMessageId) : null)
|
|
7102
|
-
?? visibleMessages[0]
|
|
7103
|
-
?? null;
|
|
7104
|
-
const selected = selectedMessage ? graph.resolve("message", selectedMessage.id) : null;
|
|
7105
|
-
const selectedForumMessage = selectedForumThread
|
|
7106
|
-
? [...(selectedForumThread.messages ?? [])]
|
|
7107
|
-
.sort((left, right) => Number(left.sequenceNumber ?? 0) - Number(right.sequenceNumber ?? 0))
|
|
7108
|
-
.at(-1) ?? null
|
|
7109
|
-
: null;
|
|
7110
|
-
const selectedEntity = isForumMode
|
|
7111
|
-
? (selectedForumMessage ? graph.resolve("message", selectedForumMessage.id) : null)
|
|
7112
|
-
: selected;
|
|
7113
|
-
const messageKnowledgeQuery = useNewsroomKnowledgeContext(selectedMessage ? {
|
|
7114
|
-
anchor: { kind: "message", id: selectedMessage.id },
|
|
7115
|
-
title: selectedMessage.summary ?? "Stored message payload",
|
|
7116
|
-
subtitle: selectedMessage.messageKind,
|
|
7117
|
-
} : null);
|
|
7118
|
-
const forumKnowledgeQuery = useNewsroomKnowledgeContext(selectedForumMessage ? {
|
|
7119
|
-
anchor: { kind: "message", id: selectedForumMessage.id },
|
|
7120
|
-
title: selectedForumThread?.title ?? selectedForumMessage.summary ?? "Forum thread",
|
|
7121
|
-
subtitle: selectedForumMessage.messageKind,
|
|
7122
|
-
} : null);
|
|
7123
|
-
const cards = visibleMessages.map((message, index) => messageToNewsroomCard(message, index));
|
|
7124
|
-
const totalMessageCount = summaryCountFromRecord(summary, "messages") || messages.length;
|
|
7125
|
-
const metrics = [
|
|
7126
|
-
{ key: "", label: "All", count: totalMessageCount },
|
|
7127
|
-
...messageDomains.slice(0, 4).map((domain) => ({ key: domain, label: domain, count: messageDomainCounts[domain] ?? 0 })),
|
|
7128
|
-
];
|
|
7129
|
-
const selectMessage = (id: string) => {
|
|
7130
|
-
setSelectedMessageId(id);
|
|
7131
|
-
setIsMessageDetailOpen(true);
|
|
7132
|
-
pushNewsroomDetailUrl("messages", id, isDemo);
|
|
7133
|
-
};
|
|
7134
|
-
const openForumThread = (threadId: string, options?: { messageId?: string | null; replace?: boolean }) => {
|
|
7135
|
-
setSelectedForumThreadId(threadId);
|
|
7136
|
-
setForumView({ mode: "thread", threadId });
|
|
7137
|
-
setIsMessageDetailOpen(true);
|
|
7138
|
-
pushForumThreadUrl(threadId, {
|
|
7139
|
-
demo: isDemo,
|
|
7140
|
-
messageId: options?.messageId,
|
|
7141
|
-
replace: options?.replace,
|
|
7142
|
-
});
|
|
7143
|
-
};
|
|
7144
|
-
|
|
7145
|
-
const closeForumThread = () => {
|
|
7146
|
-
setSelectedForumThreadId("");
|
|
7147
|
-
setForumView({ mode: "index" });
|
|
7148
|
-
pushForumThreadUrl(null, { demo: isDemo, replace: true });
|
|
7149
|
-
};
|
|
7150
|
-
const submitNewForumThread = async () => {
|
|
7151
|
-
if (!selectedForumEditionId) return;
|
|
7152
|
-
const title = newForumThreadDraft.title.trim();
|
|
7153
|
-
const content = newForumThreadDraft.content.trim();
|
|
7154
|
-
if (!title) {
|
|
7155
|
-
setNewForumThreadError("Thread title is required.");
|
|
7156
|
-
return;
|
|
7157
|
-
}
|
|
7158
|
-
if (!content) {
|
|
7159
|
-
setNewForumThreadError("Thread body is required.");
|
|
7160
|
-
return;
|
|
7161
|
-
}
|
|
7162
|
-
try {
|
|
7163
|
-
let threadId = "";
|
|
7164
|
-
if (newForumThreadDraft.scope === "section") {
|
|
7165
|
-
const sectionId = String(newForumThreadDraft.sectionId || selectedForumSectionId || "").trim();
|
|
7166
|
-
const section = availableSections.find((entry) => entry.id === sectionId) ?? null;
|
|
7167
|
-
if (!section) {
|
|
7168
|
-
setNewForumThreadError("Section is required for section threads.");
|
|
7169
|
-
return;
|
|
7170
|
-
}
|
|
7171
|
-
const created = await createSectionForumThreadRecord({
|
|
7172
|
-
editionId: selectedForumEditionId,
|
|
7173
|
-
sectionId: section.id,
|
|
7174
|
-
sectionKey: section.id,
|
|
7175
|
-
sectionTitle: section.title,
|
|
7176
|
-
title,
|
|
7177
|
-
actorLabel: "human-editor",
|
|
7178
|
-
});
|
|
7179
|
-
threadId = created.thread.id;
|
|
7180
|
-
} else {
|
|
7181
|
-
const ensured = await ensureEditionForumThreadRecord({
|
|
7182
|
-
editionId: selectedForumEditionId,
|
|
7183
|
-
actorLabel: "human-editor",
|
|
7184
|
-
});
|
|
7185
|
-
threadId = ensured.thread.id;
|
|
7186
|
-
}
|
|
7187
|
-
await appendForumThreadMessageRecord({
|
|
7188
|
-
threadId,
|
|
7189
|
-
summary: title,
|
|
7190
|
-
content,
|
|
7191
|
-
role: "human",
|
|
7192
|
-
authorLabel: "human-editor",
|
|
7193
|
-
});
|
|
7194
|
-
await refreshForumThreads();
|
|
7195
|
-
setNewForumThreadDraft(createDefaultForumNewThreadDraft(selectedForumSectionId || availableSections[0]?.id || ""));
|
|
7196
|
-
setNewForumThreadError(null);
|
|
7197
|
-
setNewForumThreadOpen(false);
|
|
7198
|
-
openForumThread(threadId, { replace: true });
|
|
7199
|
-
} catch (error) {
|
|
7200
|
-
setNewForumThreadError(error instanceof Error ? error.message : "Could not create thread.");
|
|
7201
|
-
}
|
|
7202
|
-
};
|
|
7203
|
-
const submitForumMessage = async () => {
|
|
7204
|
-
if (!selectedForumThread) return;
|
|
7205
|
-
const summary = forumComposeSummary.trim() || forumComposeContent.trim().slice(0, 120) || "Forum reply";
|
|
7206
|
-
const content = forumComposeContent.trim();
|
|
7207
|
-
if (!content) {
|
|
7208
|
-
setForumComposeError("Message content is required.");
|
|
7209
|
-
return;
|
|
7210
|
-
}
|
|
7211
|
-
setForumComposeError(null);
|
|
7212
|
-
try {
|
|
7213
|
-
await appendForumThreadMessageRecord({
|
|
7214
|
-
threadId: selectedForumThread.id,
|
|
7215
|
-
summary,
|
|
7216
|
-
content,
|
|
7217
|
-
role: "human",
|
|
7218
|
-
authorLabel: "human-editor",
|
|
7219
|
-
parentMessageId: forumReplyParentId || undefined,
|
|
7220
|
-
});
|
|
7221
|
-
setForumComposeSummary("");
|
|
7222
|
-
setForumComposeContent("");
|
|
7223
|
-
setForumReplyParentId("");
|
|
7224
|
-
await refreshForumThreads();
|
|
7225
|
-
setForumView({ mode: "thread", threadId: selectedForumThread.id });
|
|
7226
|
-
} catch (error) {
|
|
7227
|
-
setForumComposeError(error instanceof Error ? error.message : "Could not append message.");
|
|
7228
|
-
}
|
|
7229
|
-
};
|
|
7230
|
-
const deleteForumMessage = async (threadId: string, messageId: string) => {
|
|
7231
|
-
setDeletingForumMessageId(messageId);
|
|
7232
|
-
try {
|
|
7233
|
-
await deleteForumThreadMessageRecord({ threadId, messageId });
|
|
7234
|
-
if (forumReplyParentId === messageId) setForumReplyParentId("");
|
|
7235
|
-
await refreshForumThreads();
|
|
7236
|
-
setForumComposeError(null);
|
|
7237
|
-
} catch (error) {
|
|
7238
|
-
setForumComposeError(error instanceof Error ? error.message : "Could not delete message.");
|
|
7239
|
-
} finally {
|
|
7240
|
-
setDeletingForumMessageId("");
|
|
7241
|
-
}
|
|
7242
|
-
};
|
|
7243
|
-
return (
|
|
7244
|
-
<>
|
|
7245
|
-
<NewsroomListDetailShell
|
|
7246
|
-
animatedDetail
|
|
7247
|
-
sectionKey="messages"
|
|
7248
|
-
canExpandDetail={Boolean(isForumMode ? (forumView.mode === "thread" && forumView.threadId) : selectedMessage)}
|
|
7249
|
-
detailOpen={isForumMode ? (forumView.mode === "thread" && Boolean(forumView.threadId)) : isMessageDetailOpen}
|
|
7250
|
-
selectionScrollKey={(isForumMode ? (forumView.mode === "thread" ? forumView.threadId : null) : selectedMessage?.id) ?? null}
|
|
7251
|
-
utilityActions={[isForumMode ? forumKnowledgeQuery.action : messageKnowledgeQuery.action]}
|
|
7252
|
-
lede={(
|
|
7253
|
-
<section className="news-desk-lede news-desk-assignment-lede" aria-labelledby="message-management-title">
|
|
7254
|
-
<div>
|
|
7255
|
-
<h2 id="message-management-title">{formatDeskSectionHeadline("messages")}</h2>
|
|
7256
|
-
<p>{formatDeskSectionLede("messages")}</p>
|
|
7257
|
-
</div>
|
|
7258
|
-
</section>
|
|
7259
|
-
)}
|
|
7260
|
-
list={(
|
|
7261
|
-
<section className="category-steering-section category-steering-section--lead" aria-label="Messages">
|
|
7262
|
-
{isForumMode ? (
|
|
7263
|
-
<ForumThreadIndex
|
|
7264
|
-
threads={filteredForumThreads}
|
|
7265
|
-
sections={availableSections}
|
|
7266
|
-
isLoading={forumThreadsLoading}
|
|
7267
|
-
error={forumThreadsError}
|
|
7268
|
-
emptyLabel="No forum threads for this edition/filter."
|
|
7269
|
-
toolbar={(
|
|
7270
|
-
<div className="news-desk-forum-toolbar">
|
|
7271
|
-
<select
|
|
7272
|
-
aria-label="Forum edition"
|
|
7273
|
-
value={selectedForumEditionId}
|
|
7274
|
-
onChange={(event) => setSelectedForumEditionId(event.target.value)}
|
|
7275
|
-
>
|
|
7276
|
-
{messageOverviewEditions.length ? messageOverviewEditions.map((edition) => (
|
|
7277
|
-
<option key={edition.editionId} value={edition.editionId}>{edition.label}</option>
|
|
7278
|
-
)) : <option value="">No editions found</option>}
|
|
7279
|
-
</select>
|
|
7280
|
-
<select
|
|
7281
|
-
aria-label="Forum scope"
|
|
7282
|
-
value={forumScopeFilter}
|
|
7283
|
-
onChange={(event) => setForumScopeFilter((event.target.value as "all" | "edition" | "section"))}
|
|
7284
|
-
>
|
|
7285
|
-
<option value="all">All scopes</option>
|
|
7286
|
-
<option value="edition">Edition</option>
|
|
7287
|
-
<option value="section">Section</option>
|
|
7288
|
-
</select>
|
|
7289
|
-
<select
|
|
7290
|
-
aria-label="Forum section"
|
|
7291
|
-
value={selectedForumSectionId}
|
|
7292
|
-
onChange={(event) => {
|
|
7293
|
-
setSelectedForumSectionId(event.target.value);
|
|
7294
|
-
setNewForumThreadDraft((current) => ({ ...current, sectionId: event.target.value }));
|
|
7295
|
-
}}
|
|
7296
|
-
disabled={!availableSections.length}
|
|
7297
|
-
>
|
|
7298
|
-
{availableSections.length ? availableSections.map((section) => (
|
|
7299
|
-
<option key={section.id} value={section.id}>{section.title}</option>
|
|
7300
|
-
)) : <option value="">No sections</option>}
|
|
7301
|
-
</select>
|
|
7302
|
-
<select
|
|
7303
|
-
aria-label="Forum status"
|
|
7304
|
-
value={forumActivityFilter}
|
|
7305
|
-
onChange={(event) => setForumActivityFilter((event.target.value as "active" | "all"))}
|
|
7306
|
-
>
|
|
7307
|
-
<option value="active">Active</option>
|
|
7308
|
-
<option value="all">All</option>
|
|
7309
|
-
</select>
|
|
7310
|
-
<button type="button" disabled={!selectedForumEditionId} onClick={() => setNewForumThreadOpen((value) => !value)}>
|
|
7311
|
-
{newForumThreadOpen ? "Close New Thread" : "New Thread"}
|
|
7312
|
-
</button>
|
|
7313
|
-
<button type="button" onClick={() => setKindFilter("")}>Back to Messages</button>
|
|
7314
|
-
</div>
|
|
7315
|
-
)}
|
|
7316
|
-
composer={newForumThreadOpen ? (
|
|
7317
|
-
<ForumThreadComposer
|
|
7318
|
-
draft={newForumThreadDraft}
|
|
7319
|
-
sections={availableSections}
|
|
7320
|
-
error={newForumThreadError}
|
|
7321
|
-
onChange={setNewForumThreadDraft}
|
|
7322
|
-
onCancel={() => {
|
|
7323
|
-
setNewForumThreadOpen(false);
|
|
7324
|
-
setNewForumThreadError(null);
|
|
7325
|
-
}}
|
|
7326
|
-
onSubmit={() => void submitNewForumThread()}
|
|
7327
|
-
/>
|
|
7328
|
-
) : null}
|
|
7329
|
-
onOpenThread={openForumThread}
|
|
7330
|
-
/>
|
|
7331
|
-
) : (
|
|
7332
|
-
<NewsroomCardGrid
|
|
7333
|
-
cards={cards}
|
|
7334
|
-
emptyLabel="No private messages recorded"
|
|
7335
|
-
filterLabel="Message kind"
|
|
7336
|
-
filterOptions={[
|
|
7337
|
-
{ key: "__forum", label: "Forum threads", count: forumThreads.length || undefined },
|
|
7338
|
-
{ key: "", label: "All kinds", count: totalMessageCount },
|
|
7339
|
-
{ key: "__chat_sessions", label: "Chat sessions", count: consoleThreads.length || undefined },
|
|
7340
|
-
{ key: "__chat_detail", label: "Chat detail", count: messageKindCounts.console_chat_turn ?? 0 },
|
|
7341
|
-
...messageKinds.map((kind) => ({ key: kind, label: kind, count: messageKindCounts[kind] ?? 0 })),
|
|
7342
|
-
]}
|
|
7343
|
-
filterValue={kindFilter}
|
|
7344
|
-
metricValue={domainFilter}
|
|
7345
|
-
metrics={metrics}
|
|
7346
|
-
footerLabel={consoleThreadsError ?? feed.error ?? undefined}
|
|
7347
|
-
hasMore={!isDemo && kindFilter !== "__chat_sessions" && feed.hasMore}
|
|
7348
|
-
isLoadingMore={feed.isLoadingMore}
|
|
7349
|
-
onFilterChange={(value) => {
|
|
7350
|
-
setKindFilter(value);
|
|
7351
|
-
syncMessagesIndexUrl(value, domainFilter, true);
|
|
7352
|
-
}}
|
|
7353
|
-
onLoadMore={feed.loadMore}
|
|
7354
|
-
onMetricChange={(value) => {
|
|
7355
|
-
setDomainFilter(value);
|
|
7356
|
-
syncMessagesIndexUrl(kindFilter, value, true);
|
|
7357
|
-
}}
|
|
7358
|
-
onSelect={selectMessage}
|
|
7359
|
-
selectedId={selectedMessage?.id ?? null}
|
|
7360
|
-
/>
|
|
7361
|
-
)}
|
|
7362
|
-
</section>
|
|
7363
|
-
)}
|
|
7364
|
-
onCloseDetail={() => {
|
|
7365
|
-
if (isForumMode) {
|
|
7366
|
-
closeForumThread();
|
|
7367
|
-
return;
|
|
7368
|
-
}
|
|
7369
|
-
setIsMessageDetailOpen(false);
|
|
7370
|
-
syncMessagesIndexUrl(kindFilter, domainFilter, true);
|
|
7371
|
-
}}
|
|
7372
|
-
detail={isForumMode ? (
|
|
7373
|
-
(forumView.mode === "thread" && selectedForumThread ? (
|
|
7374
|
-
<ForumThreadView
|
|
7375
|
-
thread={selectedForumThread}
|
|
7376
|
-
sections={availableSections}
|
|
7377
|
-
replyParentId={forumReplyParentId}
|
|
7378
|
-
composeSummary={forumComposeSummary}
|
|
7379
|
-
composeContent={forumComposeContent}
|
|
7380
|
-
composeError={forumComposeError}
|
|
7381
|
-
focusMessageId={forumMessageAnchorId}
|
|
7382
|
-
isDemo={isDemo}
|
|
7383
|
-
onBack={closeForumThread}
|
|
7384
|
-
onReplyTarget={(messageId) => {
|
|
7385
|
-
setForumReplyParentId(messageId);
|
|
7386
|
-
pushForumThreadUrl(selectedForumThread.id, { demo: isDemo, messageId, replace: true });
|
|
7387
|
-
}}
|
|
7388
|
-
onDeleteMessage={(messageId) => {
|
|
7389
|
-
if (!selectedForumThread || deletingForumMessageId === messageId) return;
|
|
7390
|
-
void deleteForumMessage(selectedForumThread.id, messageId);
|
|
7391
|
-
}}
|
|
7392
|
-
onClearReplyTarget={() => {
|
|
7393
|
-
setForumReplyParentId("");
|
|
7394
|
-
pushForumThreadUrl(selectedForumThread.id, { demo: isDemo, replace: true });
|
|
7395
|
-
}}
|
|
7396
|
-
onSummaryChange={setForumComposeSummary}
|
|
7397
|
-
onContentChange={setForumComposeContent}
|
|
7398
|
-
onSubmit={() => void submitForumMessage()}
|
|
7399
|
-
deletingMessageId={deletingForumMessageId}
|
|
7400
|
-
/>
|
|
7401
|
-
) : forumView.mode === "thread" ? (
|
|
7402
|
-
<section className="category-steering-section">
|
|
7403
|
-
<SectionHeader title="Thread Unavailable" detail="Forum" />
|
|
7404
|
-
<EmptyRow label="That thread is not available under the current filters. Return to Threads and adjust filters." />
|
|
7405
|
-
<button type="button" onClick={closeForumThread}>Back to Threads</button>
|
|
7406
|
-
</section>
|
|
7407
|
-
) : <SemanticDetailPanel graph={graph} selected={selectedEntity} knowledgeQuery={forumKnowledgeQuery} />)
|
|
7408
|
-
) : (selectedMessage
|
|
7409
|
-
? <MessageDetailPanel graph={graph} message={selectedMessage} selected={selected} knowledgeQuery={messageKnowledgeQuery} />
|
|
7410
|
-
: <SemanticDetailPanel graph={graph} selected={selected} knowledgeQuery={messageKnowledgeQuery} />)}
|
|
7411
|
-
/>
|
|
7412
|
-
{isForumMode ? forumKnowledgeQuery.dialog : messageKnowledgeQuery.dialog}
|
|
7413
|
-
</>
|
|
7414
|
-
);
|
|
7415
|
-
}
|
|
7416
|
-
|
|
7417
6792
|
function useForumMessageAnchorId(): string | null {
|
|
7418
6793
|
const [messageId, setMessageId] = useState<string | null>(null);
|
|
7419
6794
|
useEffect(() => {
|
|
@@ -9039,11 +8414,7 @@ function SemanticDetailPanel({
|
|
|
9039
8414
|
{messages.slice(0, 4).map((message) => (
|
|
9040
8415
|
<div className="news-desk-detail-line" key={message.id}>
|
|
9041
8416
|
<span>{message.messageKind}</span>
|
|
9042
|
-
<strong>
|
|
9043
|
-
{message.messageKind === "reference_curation"
|
|
9044
|
-
? "Canonical summary is stored on the linked reference metadata attachment."
|
|
9045
|
-
: (message.summary ?? "Stored message payload")}
|
|
9046
|
-
</strong>
|
|
8417
|
+
<strong>{message.summary ?? "Stored message payload"}</strong>
|
|
9047
8418
|
</div>
|
|
9048
8419
|
))}
|
|
9049
8420
|
</div>
|
|
@@ -10452,11 +9823,7 @@ function ReferenceDetailPanel({
|
|
|
10452
9823
|
{messages.slice(0, 4).map((message) => (
|
|
10453
9824
|
<div className="news-desk-detail-line" key={message.id}>
|
|
10454
9825
|
<span>{message.messageKind}</span>
|
|
10455
|
-
<strong>
|
|
10456
|
-
{message.messageKind === "reference_curation"
|
|
10457
|
-
? (metadataSummary ?? "Canonical summary unavailable")
|
|
10458
|
-
: (message.summary ?? "Stored message payload")}
|
|
10459
|
-
</strong>
|
|
9826
|
+
<strong>{message.summary ?? "Stored message payload"}</strong>
|
|
10460
9827
|
</div>
|
|
10461
9828
|
))}
|
|
10462
9829
|
</div>
|
|
@@ -10558,25 +9925,9 @@ function MessageDetailPanel({
|
|
|
10558
9925
|
const bodyPayload = modelPayloadByRole(messagePayloadState.payloads, "message_body");
|
|
10559
9926
|
const metadataPayload = modelPayloadByRole(messagePayloadState.payloads, "metadata");
|
|
10560
9927
|
const messageMetadata = metadataRecord(metadataPayload?.json) ?? metadataRecord(message.metadata);
|
|
10561
|
-
const linkedReference = useReferenceCurationMessageReference(graph, message, messageMetadata);
|
|
10562
|
-
const referencePayloadState = useModelPayloads("reference", linkedReference?.id, ["metadata"]);
|
|
10563
|
-
const referenceMetadataPayload = modelPayloadByRole(referencePayloadState.payloads, "metadata");
|
|
10564
|
-
const referenceSubtitle = referenceMetadataSubtitle(referenceMetadataPayload, linkedReference?.metadata);
|
|
10565
|
-
const referenceSummary = linkedReference
|
|
10566
|
-
? referenceDisplaySummary(
|
|
10567
|
-
graph,
|
|
10568
|
-
linkedReference.lineageId ?? linkedReference.id,
|
|
10569
|
-
referenceMetadataPayload,
|
|
10570
|
-
linkedReference.metadata,
|
|
10571
|
-
)
|
|
10572
|
-
: null;
|
|
10573
9928
|
const headerLabel = humanizeNewsroomLabel(message.messageKind ?? "message");
|
|
10574
|
-
const headerTitle = message.
|
|
10575
|
-
|
|
10576
|
-
: message.summary ?? "Stored message payload";
|
|
10577
|
-
const detailSummary = message.messageKind === "reference_curation"
|
|
10578
|
-
? referenceSummary ?? null
|
|
10579
|
-
: message.summary ?? null;
|
|
9929
|
+
const headerTitle = message.summary ?? "Stored message payload";
|
|
9930
|
+
const detailSummary = message.summary ?? null;
|
|
10580
9931
|
const neighborGroups = selected ? graph.neighbors(selected.kind, selected.lineageId) : [];
|
|
10581
9932
|
return (
|
|
10582
9933
|
<section className="category-steering-section" aria-label="Message detail" data-news-desk-message-detail={message.id}>
|
|
@@ -10585,9 +9936,6 @@ function MessageDetailPanel({
|
|
|
10585
9936
|
<div>
|
|
10586
9937
|
<p className="story-label">{headerLabel}</p>
|
|
10587
9938
|
<strong data-news-desk-message-headline>{headerTitle}</strong>
|
|
10588
|
-
{message.messageKind === "reference_curation" && referenceSubtitle ? (
|
|
10589
|
-
<p className="news-desk-semantic-detail__subheading" data-news-desk-message-subheading>{referenceSubtitle}</p>
|
|
10590
|
-
) : null}
|
|
10591
9939
|
<span>{formatDateTime(message.createdAt)}</span>
|
|
10592
9940
|
</div>
|
|
10593
9941
|
</header>
|
|
@@ -10606,14 +9954,13 @@ function MessageDetailPanel({
|
|
|
10606
9954
|
<p className="story-label">Message Metadata</p>
|
|
10607
9955
|
{detailSummary ? (
|
|
10608
9956
|
<div className="news-desk-detail-line" data-news-desk-message-summary>
|
|
10609
|
-
<span>
|
|
9957
|
+
<span>Event summary</span>
|
|
10610
9958
|
<strong>{detailSummary}</strong>
|
|
10611
9959
|
</div>
|
|
10612
9960
|
) : null}
|
|
10613
9961
|
<div className="news-desk-detail-line"><span>Status</span><strong>{message.status}</strong></div>
|
|
10614
9962
|
<div className="news-desk-detail-line"><span>Source</span><strong>{message.source ?? "unknown"}</strong></div>
|
|
10615
9963
|
<div className="news-desk-detail-line"><span>Author</span><strong>{message.authorLabel ?? message.authorSub ?? "unknown"}</strong></div>
|
|
10616
|
-
{linkedReference ? <div className="news-desk-detail-line"><span>Reference</span><strong>{linkedReference.title ?? linkedReference.externalItemId}</strong></div> : null}
|
|
10617
9964
|
{message.importRunId ? <div className="news-desk-detail-line"><span>Import run</span><strong>{message.importRunId}</strong></div> : null}
|
|
10618
9965
|
</div>
|
|
10619
9966
|
<PayloadDetailBlock
|
|
@@ -10632,81 +9979,6 @@ function MessageDetailPanel({
|
|
|
10632
9979
|
);
|
|
10633
9980
|
}
|
|
10634
9981
|
|
|
10635
|
-
function useReferenceCurationMessageReference(
|
|
10636
|
-
graph: ReturnType<typeof createSemanticGraphSnapshot>,
|
|
10637
|
-
message: MessageRecord,
|
|
10638
|
-
metadata: Record<string, unknown> | null,
|
|
10639
|
-
): ReferenceRecord | null {
|
|
10640
|
-
const relationReferenceId = useMemo(
|
|
10641
|
-
() => referenceCurationMessageReferenceId(graph, message),
|
|
10642
|
-
[graph, message],
|
|
10643
|
-
);
|
|
10644
|
-
const graphReference = useMemo(
|
|
10645
|
-
() => resolveReferenceCurationMessageReference(graph, message, metadata),
|
|
10646
|
-
[graph, message, metadata],
|
|
10647
|
-
);
|
|
10648
|
-
const referenceId = relationReferenceId ?? normalizeMetadataString(metadata?.referenceId);
|
|
10649
|
-
const [loadedReference, setLoadedReference] = useState<ReferenceRecord | null>(null);
|
|
10650
|
-
|
|
10651
|
-
useEffect(() => {
|
|
10652
|
-
setLoadedReference(null);
|
|
10653
|
-
if (message.messageKind !== "reference_curation" || graphReference || !referenceId) return;
|
|
10654
|
-
let active = true;
|
|
10655
|
-
loadReferenceRecordById(referenceId)
|
|
10656
|
-
.then((reference) => {
|
|
10657
|
-
if (active) setLoadedReference(reference);
|
|
10658
|
-
})
|
|
10659
|
-
.catch(() => {
|
|
10660
|
-
if (active) setLoadedReference(null);
|
|
10661
|
-
});
|
|
10662
|
-
return () => {
|
|
10663
|
-
active = false;
|
|
10664
|
-
};
|
|
10665
|
-
}, [graphReference, message.id, message.messageKind, referenceId]);
|
|
10666
|
-
|
|
10667
|
-
return graphReference ?? loadedReference;
|
|
10668
|
-
}
|
|
10669
|
-
|
|
10670
|
-
function resolveReferenceCurationMessageReference(
|
|
10671
|
-
graph: ReturnType<typeof createSemanticGraphSnapshot>,
|
|
10672
|
-
message: MessageRecord,
|
|
10673
|
-
metadata: Record<string, unknown> | null,
|
|
10674
|
-
): ReferenceRecord | null {
|
|
10675
|
-
if (message.messageKind !== "reference_curation") return null;
|
|
10676
|
-
const outgoingReference = referenceCurationMessageRelation(graph, message);
|
|
10677
|
-
const graphSummary = outgoingReference
|
|
10678
|
-
? graph.resolve("reference", outgoingReference.objectLineageId) ?? graph.resolve("reference", outgoingReference.objectId)
|
|
10679
|
-
: null;
|
|
10680
|
-
if (graphSummary?.kind === "reference" && graphSummary.record) {
|
|
10681
|
-
return graphSummary.record as ReferenceRecord;
|
|
10682
|
-
}
|
|
10683
|
-
const referenceId = normalizeMetadataString(metadata?.referenceId);
|
|
10684
|
-
const referenceLineageId = normalizeMetadataString(metadata?.referenceLineageId);
|
|
10685
|
-
const fallbackSummary = (referenceId ? graph.resolve("reference", referenceId) : null)
|
|
10686
|
-
?? (referenceLineageId ? graph.resolve("reference", referenceLineageId) : null);
|
|
10687
|
-
return fallbackSummary?.kind === "reference" && fallbackSummary.record
|
|
10688
|
-
? fallbackSummary.record as ReferenceRecord
|
|
10689
|
-
: null;
|
|
10690
|
-
}
|
|
10691
|
-
|
|
10692
|
-
function referenceCurationMessageReferenceId(
|
|
10693
|
-
graph: ReturnType<typeof createSemanticGraphSnapshot>,
|
|
10694
|
-
message: MessageRecord,
|
|
10695
|
-
): string | null {
|
|
10696
|
-
const relation = referenceCurationMessageRelation(graph, message);
|
|
10697
|
-
return relation?.objectId ?? null;
|
|
10698
|
-
}
|
|
10699
|
-
|
|
10700
|
-
function referenceCurationMessageRelation(
|
|
10701
|
-
graph: ReturnType<typeof createSemanticGraphSnapshot>,
|
|
10702
|
-
message: MessageRecord,
|
|
10703
|
-
): SemanticRelationRecord | null {
|
|
10704
|
-
if (message.messageKind !== "reference_curation") return null;
|
|
10705
|
-
return graph.outgoing("message", message.id)
|
|
10706
|
-
.find((relation) => relation.objectKind === "reference" && relationTypeKey(relation) === "comment")
|
|
10707
|
-
?? null;
|
|
10708
|
-
}
|
|
10709
|
-
|
|
10710
9982
|
type ReferenceCurationDisplayState = {
|
|
10711
9983
|
effectiveDisplayedStars: number;
|
|
10712
9984
|
effectiveStatus: string;
|
|
@@ -13102,29 +12374,6 @@ function messageToNewsroomCard(message: MessageRecord, index: number): NewsroomC
|
|
|
13102
12374
|
};
|
|
13103
12375
|
}
|
|
13104
12376
|
|
|
13105
|
-
function consoleThreadToMessageRecord(thread: ConsoleThreadSummary): MessageRecord {
|
|
13106
|
-
const updatedAt = thread.updatedAt ?? thread.lastMessageAt ?? thread.createdAt;
|
|
13107
|
-
return {
|
|
13108
|
-
id: thread.id,
|
|
13109
|
-
messageKind: "console_thread",
|
|
13110
|
-
messageDomain: "conversation",
|
|
13111
|
-
status: thread.status,
|
|
13112
|
-
summary: thread.summary ?? thread.title,
|
|
13113
|
-
source: "papyrus-console",
|
|
13114
|
-
authorLabel: thread.createdByLabel ?? null,
|
|
13115
|
-
createdAt: thread.createdAt,
|
|
13116
|
-
updatedAt,
|
|
13117
|
-
newsroomFeedKey: thread.newsroomFeedKey ?? "consoleChat",
|
|
13118
|
-
metadata: {
|
|
13119
|
-
threadId: thread.id,
|
|
13120
|
-
threadKind: thread.threadKind,
|
|
13121
|
-
messageCount: thread.messageCount ?? 0,
|
|
13122
|
-
lastMessageAt: thread.lastMessageAt ?? null,
|
|
13123
|
-
primaryAnchorKey: thread.primaryAnchorKey ?? null,
|
|
13124
|
-
},
|
|
13125
|
-
};
|
|
13126
|
-
}
|
|
13127
|
-
|
|
13128
12377
|
function assignmentToNewsroomCard(assignment: AssignmentRecord, index: number, reportingDecision: ReportingPacketDecisionSummary | null = null): NewsroomCardRecord {
|
|
13129
12378
|
const analysisPlan = assignmentAnalysisReindexMetadata(assignment);
|
|
13130
12379
|
const subtitle = assignmentCardSubtitle(assignment);
|
|
@@ -15745,6 +14994,8 @@ function buildReviewedReferenceRecord(
|
|
|
15745
14994
|
nextStatus: string;
|
|
15746
14995
|
note: string | null;
|
|
15747
14996
|
now: string;
|
|
14997
|
+
/** The details of a person's vote, as the curation mutation records them on the Reference. */
|
|
14998
|
+
vote?: { reasonCode: string | null; sectionId: string | null; decisionRelationId: string | null; shareable: boolean | null };
|
|
15748
14999
|
},
|
|
15749
15000
|
): ReferenceRecord {
|
|
15750
15001
|
const current = records.find((entry) => entry.id === reference.id) ?? reference;
|
|
@@ -15755,6 +15006,14 @@ function buildReviewedReferenceRecord(
|
|
|
15755
15006
|
curationStatusUpdatedAt: options.now,
|
|
15756
15007
|
curationStatusUpdatedBy: options.actorLabel,
|
|
15757
15008
|
curationStatusReason: options.note,
|
|
15009
|
+
...(options.vote ? {
|
|
15010
|
+
curationActorKind: "human",
|
|
15011
|
+
curationReasonCode: options.vote.reasonCode,
|
|
15012
|
+
curationSectionId: options.vote.sectionId,
|
|
15013
|
+
curationShareable: options.vote.decisionRelationId ? options.vote.shareable === true : null,
|
|
15014
|
+
curationDelegation: null,
|
|
15015
|
+
curationDecisionRelationId: options.vote.decisionRelationId,
|
|
15016
|
+
} : {}),
|
|
15758
15017
|
updatedAt: options.now,
|
|
15759
15018
|
};
|
|
15760
15019
|
}
|