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