@arbidocs/blocks 0.4.150 → 0.4.151

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.
Files changed (107) hide show
  1. package/dist/{ConnectedNativeDocxComparison-XQSXVGHR.js → ConnectedNativeDocxComparison-OFPIXNJB.js} +6 -6
  2. package/dist/{ConnectedStreamedWordArtifact-UZVNBKQB.js.map → ConnectedNativeDocxComparison-OFPIXNJB.js.map} +1 -1
  3. package/dist/{ConnectedNativeDocxComparison-3XQSINPQ.cjs → ConnectedNativeDocxComparison-Y5BNLNH6.cjs} +7 -7
  4. package/dist/{ConnectedNativeDocxComparison-3XQSINPQ.cjs.map → ConnectedNativeDocxComparison-Y5BNLNH6.cjs.map} +1 -1
  5. package/dist/{ConnectedNativeDocxEditor-OUWFMIPH.cjs → ConnectedNativeDocxEditor-HTEW5JAH.cjs} +7 -7
  6. package/dist/{ConnectedNativeDocxEditor-OUWFMIPH.cjs.map → ConnectedNativeDocxEditor-HTEW5JAH.cjs.map} +1 -1
  7. package/dist/{ConnectedNativeDocxEditor-IPEZDERL.js → ConnectedNativeDocxEditor-LRYU5HPA.js} +5 -5
  8. package/dist/{ConnectedNativeDocxEditor-IPEZDERL.js.map → ConnectedNativeDocxEditor-LRYU5HPA.js.map} +1 -1
  9. package/dist/{ConnectedStreamedWordArtifact-UZVNBKQB.js → ConnectedStreamedWordArtifact-QAUVUCSX.js} +6 -6
  10. package/dist/{ConnectedNativeDocxComparison-XQSXVGHR.js.map → ConnectedStreamedWordArtifact-QAUVUCSX.js.map} +1 -1
  11. package/dist/{ConnectedStreamedWordArtifact-D5LSMDO4.cjs → ConnectedStreamedWordArtifact-QW74LKHN.cjs} +7 -7
  12. package/dist/ConnectedStreamedWordArtifact-QW74LKHN.cjs.map +1 -0
  13. package/dist/agents/index.cjs +238 -238
  14. package/dist/agents/index.js +4 -4
  15. package/dist/calls/index.cjs +3 -3
  16. package/dist/calls/index.js +2 -2
  17. package/dist/{chunk-IPQ5MJ4Z.cjs → chunk-2FLWPM3B.cjs} +5 -5
  18. package/dist/{chunk-IPQ5MJ4Z.cjs.map → chunk-2FLWPM3B.cjs.map} +1 -1
  19. package/dist/{chunk-CQPFW64W.js → chunk-3PCBYOIZ.js} +3 -3
  20. package/dist/{chunk-CQPFW64W.js.map → chunk-3PCBYOIZ.js.map} +1 -1
  21. package/dist/{chunk-Q6N6MZKO.js → chunk-4236BRZK.js} +3 -3
  22. package/dist/{chunk-Q6N6MZKO.js.map → chunk-4236BRZK.js.map} +1 -1
  23. package/dist/{chunk-KLPRAQCE.cjs → chunk-6I6CVX5G.cjs} +2 -2
  24. package/dist/{chunk-KLPRAQCE.cjs.map → chunk-6I6CVX5G.cjs.map} +1 -1
  25. package/dist/{chunk-TAWPVD6C.cjs → chunk-B3FMUFAW.cjs} +3 -3
  26. package/dist/{chunk-TAWPVD6C.cjs.map → chunk-B3FMUFAW.cjs.map} +1 -1
  27. package/dist/{chunk-3E2F4AHD.js → chunk-CROTH2BS.js} +5 -5
  28. package/dist/{chunk-3E2F4AHD.js.map → chunk-CROTH2BS.js.map} +1 -1
  29. package/dist/{chunk-ILM42LE6.cjs → chunk-CU2O32Z4.cjs} +25 -25
  30. package/dist/{chunk-ILM42LE6.cjs.map → chunk-CU2O32Z4.cjs.map} +1 -1
  31. package/dist/{chunk-TL45OJQE.js → chunk-DB2OXRQC.js} +3 -3
  32. package/dist/{chunk-TL45OJQE.js.map → chunk-DB2OXRQC.js.map} +1 -1
  33. package/dist/{chunk-ZAVEMQ37.cjs → chunk-DWZXSPMB.cjs} +3 -3
  34. package/dist/{chunk-ZAVEMQ37.cjs.map → chunk-DWZXSPMB.cjs.map} +1 -1
  35. package/dist/{chunk-XRWHFMHK.js → chunk-EYJMMGXF.js} +2 -2
  36. package/dist/{chunk-XRWHFMHK.js.map → chunk-EYJMMGXF.js.map} +1 -1
  37. package/dist/{chunk-NWNAYXC7.cjs → chunk-GFOEWPAP.cjs} +3 -3
  38. package/dist/{chunk-NWNAYXC7.cjs.map → chunk-GFOEWPAP.cjs.map} +1 -1
  39. package/dist/{chunk-YIFCLNII.cjs → chunk-HALD6ZB5.cjs} +4 -4
  40. package/dist/{chunk-YIFCLNII.cjs.map → chunk-HALD6ZB5.cjs.map} +1 -1
  41. package/dist/{chunk-TC25LGIM.js → chunk-HUJF2XM2.js} +4 -4
  42. package/dist/chunk-HUJF2XM2.js.map +1 -0
  43. package/dist/{chunk-GGTUMLCT.cjs → chunk-Q4BY2CQQ.cjs} +3 -3
  44. package/dist/{chunk-GGTUMLCT.cjs.map → chunk-Q4BY2CQQ.cjs.map} +1 -1
  45. package/dist/{chunk-AXTDDWMR.js → chunk-QN47Q4N7.js} +3 -3
  46. package/dist/{chunk-AXTDDWMR.js.map → chunk-QN47Q4N7.js.map} +1 -1
  47. package/dist/{chunk-V4YZXGXR.cjs → chunk-T74Q3MCZ.cjs} +4 -4
  48. package/dist/chunk-T74Q3MCZ.cjs.map +1 -0
  49. package/dist/{chunk-3FSXWVHW.cjs → chunk-U6JQ4KMX.cjs} +3 -3
  50. package/dist/{chunk-3FSXWVHW.cjs.map → chunk-U6JQ4KMX.cjs.map} +1 -1
  51. package/dist/{chunk-ECERC6TI.js → chunk-VHJHY7TT.js} +3 -3
  52. package/dist/{chunk-ECERC6TI.js.map → chunk-VHJHY7TT.js.map} +1 -1
  53. package/dist/{chunk-UTVY63DG.js → chunk-WFA2GVZJ.js} +3 -3
  54. package/dist/{chunk-UTVY63DG.js.map → chunk-WFA2GVZJ.js.map} +1 -1
  55. package/dist/{chunk-ZO7PJMCO.js → chunk-WU34CHU5.js} +3 -3
  56. package/dist/{chunk-ZO7PJMCO.js.map → chunk-WU34CHU5.js.map} +1 -1
  57. package/dist/{chunk-HGLC566X.js → chunk-YXNNNP57.js} +3 -3
  58. package/dist/{chunk-HGLC566X.js.map → chunk-YXNNNP57.js.map} +1 -1
  59. package/dist/{chunk-EDV7CYG6.cjs → chunk-ZP6Q3KXU.cjs} +4 -4
  60. package/dist/{chunk-EDV7CYG6.cjs.map → chunk-ZP6Q3KXU.cjs.map} +1 -1
  61. package/dist/collaboration/index.cjs +1 -1
  62. package/dist/collaboration/index.js +1 -1
  63. package/dist/connect/docx.cjs +9 -9
  64. package/dist/connect/docx.js +5 -5
  65. package/dist/connect/index.cjs +292 -292
  66. package/dist/connect/index.d.ts +1 -1
  67. package/dist/connect/index.js +13 -13
  68. package/dist/connect/viewer.cjs +1249 -121
  69. package/dist/connect/viewer.cjs.map +1 -1
  70. package/dist/connect/viewer.js +1145 -21
  71. package/dist/connect/viewer.js.map +1 -1
  72. package/dist/connection/viewer/parsed/ParsedPassage.d.ts +48 -0
  73. package/dist/connection/viewer/parsed/ParsedTextView.d.ts +17 -0
  74. package/dist/connection/viewer/parsed/PassageTagPanel.d.ts +18 -0
  75. package/dist/connection/viewer/parsed/citation-line.d.ts +17 -0
  76. package/dist/connection/viewer/parsed/document-tags.d.ts +8 -0
  77. package/dist/connection/viewer/parsed/useChunkTagging.d.ts +46 -0
  78. package/dist/connection/viewer/parsed/useCitationSelection.d.ts +27 -0
  79. package/dist/connection/viewer/parsed/useDocumentLabels.d.ts +38 -0
  80. package/dist/connection/viewer/parsed/useLazyPages.d.ts +18 -0
  81. package/dist/connection/viewer/parsed/usePageScrollSync.d.ts +17 -0
  82. package/dist/connection/viewer/parsed/usePassageCopy.d.ts +25 -0
  83. package/dist/connection/viewer/support.d.ts +27 -9
  84. package/dist/facts/index.cjs +55 -55
  85. package/dist/facts/index.js +5 -5
  86. package/dist/files/TagPassagePanel.d.ts +2 -1
  87. package/dist/files/index.cjs +169 -169
  88. package/dist/files/index.js +3 -3
  89. package/dist/home/index.cjs +2 -2
  90. package/dist/home/index.js +2 -2
  91. package/dist/index.cjs +355 -355
  92. package/dist/index.js +8 -8
  93. package/dist/retrieval/index.cjs +1 -1
  94. package/dist/retrieval/index.js +1 -1
  95. package/dist/settings/index.cjs +164 -164
  96. package/dist/settings/index.js +3 -3
  97. package/dist/shell/index.cjs +2 -2
  98. package/dist/shell/index.js +2 -2
  99. package/dist/studio/index.cjs +2 -2
  100. package/dist/studio/index.js +2 -2
  101. package/dist/workflow-canvas/index.cjs +16 -16
  102. package/dist/workflow-canvas/index.js +4 -4
  103. package/node_modules/@arbidocs/coedit/package.json +1 -1
  104. package/package.json +5 -5
  105. package/dist/ConnectedStreamedWordArtifact-D5LSMDO4.cjs.map +0 -1
  106. package/dist/chunk-TC25LGIM.js.map +0 -1
  107. package/dist/chunk-V4YZXGXR.cjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  "use client";
2
- import { ViewerTitle, ViewerTabBar, ViewerImageActions, ViewerFrame, ViewerMessage, DocumentLabelBar, ViewerControlBar, ViewerZoomControl, PdfPageNav, ViewerPageCounter, summariseDocTags, isDocumentLabel, ViewerPreviewUnavailable, ViewerLoadError } from '../chunk-TC25LGIM.js';
3
- import { useDocumentViewerSupport } from '../chunk-XRWHFMHK.js';
2
+ import { HtmlPassage, ViewerTitle, ViewerTabBar, ViewerImageActions, ViewerFrame, ViewerMessage, DocumentLabelBar, ViewerControlBar, ViewerZoomControl, PdfPageNav, ViewerPageCounter, summariseDocTags, isDocumentLabel, ViewerPreviewUnavailable, ViewerLoadError, HtmlViewerRoot, CitedRunNav, HtmlViewerSurface, HtmlPage, HtmlViewerEmpty, TagPassagePanel } from '../chunk-HUJF2XM2.js';
3
+ import { useDocumentViewerSupport } from '../chunk-EYJMMGXF.js';
4
4
  import '../chunk-JGOST4IL.js';
5
5
  import '../chunk-RC6VTY2H.js';
6
6
  import { useConnectedConversations, useConnectedDocuments, useConnectedTags } from '../chunk-DN2KI4BU.js';
@@ -14,6 +14,7 @@ import '../chunk-H4D7WKDY.js';
14
14
  import { ShareControl } from '../chunk-K4BGOPGG.js';
15
15
  import '../chunk-SDOEHEJP.js';
16
16
  import '../chunk-A3XPS2EU.js';
17
+ import { buildDeepLinkUrl } from '../chunk-SV65E4QW.js';
17
18
  import '../chunk-M4S75AEJ.js';
18
19
  import '../chunk-5LXQPP2K.js';
19
20
  import '../chunk-RLWIUUQ5.js';
@@ -25,8 +26,6 @@ import { ArtifactCompareBar, ArtifactPreview, ArtifactCodeView, VersionTimeline
25
26
  import '../chunk-NS4JD2VR.js';
26
27
  import '../chunk-LOJVUGBJ.js';
27
28
  import { TabHeader } from '../chunk-4WWM3GQ5.js';
28
- import { useBlocksEnvironment } from '../chunk-TBEVGWY3.js';
29
- import '../chunk-TX2ZICS7.js';
30
29
  import '../chunk-OKERRHLH.js';
31
30
  import '../chunk-7XF5SFJ2.js';
32
31
  import { useRegisterOpenDocument, savedDocumentIdFor } from '../chunk-YBLIKTJK.js';
@@ -34,15 +33,18 @@ import '../chunk-AUS3KBHJ.js';
34
33
  import { useIsPhone } from '../chunk-SLSBJRWW.js';
35
34
  import '../chunk-NPLYHR2E.js';
36
35
  import '../chunk-7F6CBP3Z.js';
36
+ import { PassageMark, NoteMark, LabelMark, PassagePill } from '../chunk-JAANWQGB.js';
37
+ import { useBlocksEnvironment } from '../chunk-TBEVGWY3.js';
38
+ import '../chunk-TX2ZICS7.js';
37
39
  import { fmtDate } from '../chunk-OXF2IFXG.js';
38
40
  import '../chunk-VUNV25KB.js';
39
- import { lazy, useMemo, useCallback, useState, useRef, useEffect, Suspense } from 'react';
41
+ import React, { memo, lazy, useMemo, useCallback, useState, useRef, useEffect, Suspense } from 'react';
40
42
  import { MessageSquare, Mail, Languages, FileCheck2, MonitorPlay, Code, FileText, History } from 'lucide-react';
41
- import { useArbi, refreshDocuments, arbiQueryKeys, useArbiSessionState, useArbiSessionStore, ImageCropModal, DEFAULT_ASPECT_OPTIONS, useDocumentVersionList, useDocument, useParsedDocument, hasVideoTrack, useFinaliseTranslation, DocIcon, XlsIcon, HtmlIcon, VidIcon, AudIcon, ImgIcon, EmlIcon, PdfIcon } from '@arbidocs/react';
43
+ import { useArbi, refreshDocuments, arbiQueryKeys, useArbiSessionState, useArbiSessionStore, ImageCropModal, DEFAULT_ASPECT_OPTIONS, useDocumentVersionList, useDocument, useParsedDocument, hasVideoTrack, useFinaliseTranslation, DocIcon, XlsIcon, HtmlIcon, VidIcon, AudIcon, ImgIcon, EmlIcon, PdfIcon, useAssignDocTags, useUpdateDocTag, useRemoveDocTags } from '@arbidocs/react';
42
44
  import { parseVersionRef, versionLabel, findVersion, documentVersions, toArtifactVersions, artifactKindFromMode, artifactKindFromFileName, useArtifactSource } from '@arbidocs/react/artifacts';
43
45
  import { useOptionalDocumentTools, documentBytesKey, clientUpload, DocumentToolsProvider, useDocumentTools, useDocumentSignatures, useDocumentMarkup, canAnnotate, SIGNED_LOCKED_REASON, useDocumentBytes, fetchDocumentFile } from '@arbidocs/react/document-tools';
44
- import { XlsxViewer, cycleZoom, VideoViewer, AudioViewer, ImageViewer, PdfViewer } from '@arbidocs/react/viewers';
45
- import { Button, getEntityLabel, isImageFile, isVideoFile, isAudioFile, isDownloadOnlyFile, friendlyFileType } from '@arbidocs/react/ui';
46
+ import { XlsxViewer, cycleZoom, VideoViewer, AudioViewer, ImageViewer, useProcessedChunks, useScrollAnchor, usePinchZoom, PdfViewer, computeSelectionRuns, usePageIntersectionObserver } from '@arbidocs/react/viewers';
47
+ import { MarkdownRenderer, Button, getEntityLabel, isImageFile, isVideoFile, isAudioFile, isDownloadOnlyFile, friendlyFileType, getEntityColor, TagEntity } from '@arbidocs/react/ui';
46
48
  import { useQueryClient } from '@tanstack/react-query';
47
49
  import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
48
50
 
@@ -105,17 +107,17 @@ function OwnDocumentTools({ children }) {
105
107
  }
106
108
  var isTranslationDraft = (doc) => !!doc?.derived_from?.some((d) => d.role === "translation") && !doc.derived_from.some((d) => d.role === "final");
107
109
  function FinaliseTranslationButton({
108
- document,
110
+ document: document2,
109
111
  isCompact
110
112
  }) {
111
113
  const finalise = useFinaliseTranslation();
112
114
  const { openDocument } = useDocumentTools();
113
115
  const { notify } = useBlocksEnvironment();
114
- if (!document || !isTranslationDraft(document)) return null;
116
+ if (!document2 || !isTranslationDraft(document2)) return null;
115
117
  const pending = finalise.isPending;
116
118
  const onClick = async () => {
117
119
  try {
118
- const { document_ext_id: finalId } = await finalise.mutateAsync(document.external_id);
120
+ const { document_ext_id: finalId } = await finalise.mutateAsync(document2.external_id);
119
121
  notify("Saved the final PDF of this translation", "success");
120
122
  openDocument?.(finalId);
121
123
  } catch (error) {
@@ -496,6 +498,1125 @@ function useViewerVersions(docId) {
496
498
  }, [documents, plainId]);
497
499
  return { versions, translations };
498
500
  }
501
+
502
+ // src/connection/viewer/parsed/document-tags.ts
503
+ function isDocumentTag(tag) {
504
+ if (tag.kind === "search") return false;
505
+ const onDocs = (tag.doctag_count ?? 0) > 0;
506
+ const onFacts = (tag.facttag_count ?? 0) > 0;
507
+ return onDocs || !onFacts;
508
+ }
509
+ function hasChunkInCitations(citations, chunkExtId) {
510
+ return (citations ?? []).some((d) => d.chunks?.some((c) => c.chunk_id === chunkExtId));
511
+ }
512
+ function removeChunkFromCitations(citations, chunkExtId) {
513
+ if (!citations) return null;
514
+ const next = citations.map((d) => ({ ...d, chunks: (d.chunks ?? []).filter((c) => c.chunk_id !== chunkExtId) })).filter((d) => d.chunks.length > 0);
515
+ return next.length ? next : null;
516
+ }
517
+ function addChunkToCitations(citations, documentId, chunkExtId) {
518
+ const chunk = { chunk_id: chunkExtId, page: null };
519
+ const list = citations ?? [];
520
+ const idx = list.findIndex((d) => d.doc_ext_id === documentId);
521
+ if (idx === -1) return [...list, { doc_ext_id: documentId, chunks: [chunk] }];
522
+ const doc = list[idx];
523
+ if ((doc.chunks ?? []).some((c) => c.chunk_id === chunkExtId)) return list;
524
+ const next = [...list];
525
+ next[idx] = { ...doc, chunks: [...doc.chunks ?? [], chunk] };
526
+ return next;
527
+ }
528
+ function useChunkTagging({ documentId, documentDoctags }) {
529
+ const createDocTag = useAssignDocTags();
530
+ const updateDocTag = useUpdateDocTag();
531
+ const deleteDocTag = useRemoveDocTags();
532
+ const getChunkTagState = useCallback(
533
+ (chunkExtId) => {
534
+ const appliedTags = documentDoctags.filter(
535
+ (dt) => hasChunkInCitations(dt.citations, chunkExtId)
536
+ );
537
+ const isTagApplied = (tagExtId) => {
538
+ return appliedTags.some((dt) => dt.tag_ext_id === tagExtId);
539
+ };
540
+ const getDoctagForChunk = (tagExtId) => {
541
+ return appliedTags.find((dt) => dt.tag_ext_id === tagExtId);
542
+ };
543
+ return {
544
+ appliedTags,
545
+ isTagApplied,
546
+ getDoctagForChunk
547
+ };
548
+ },
549
+ [documentDoctags]
550
+ );
551
+ const toggleChunkTag = useCallback(
552
+ async (chunkExtId, tag, note) => {
553
+ const existingDoctag = documentDoctags.find((dt) => dt.tag_ext_id === tag.external_id);
554
+ const isChunkTagged = hasChunkInCitations(existingDoctag?.citations, chunkExtId);
555
+ if (isChunkTagged) {
556
+ const updatedCitations = removeChunkFromCitations(existingDoctag.citations, chunkExtId);
557
+ if (!updatedCitations) {
558
+ await deleteDocTag.mutateAsync({
559
+ tagId: tag.external_id,
560
+ docIds: [documentId]
561
+ });
562
+ } else {
563
+ await updateDocTag.mutateAsync({
564
+ tagId: tag.external_id,
565
+ docId: documentId,
566
+ citations: updatedCitations,
567
+ note: existingDoctag.note
568
+ });
569
+ }
570
+ } else {
571
+ if (existingDoctag) {
572
+ const updatedCitations = addChunkToCitations(
573
+ existingDoctag.citations,
574
+ documentId,
575
+ chunkExtId
576
+ );
577
+ await updateDocTag.mutateAsync({
578
+ tagId: tag.external_id,
579
+ docId: documentId,
580
+ citations: updatedCitations,
581
+ note: note ?? existingDoctag.note
582
+ });
583
+ } else {
584
+ const newCitations = addChunkToCitations(null, documentId, chunkExtId);
585
+ await createDocTag.mutateAsync({
586
+ tagId: tag.external_id,
587
+ docIds: [documentId],
588
+ citations: newCitations,
589
+ note
590
+ });
591
+ }
592
+ }
593
+ },
594
+ [documentId, documentDoctags, createDocTag, updateDocTag, deleteDocTag]
595
+ );
596
+ const updateChunkTagNote = useCallback(
597
+ async (_chunkExtId, tag, note) => {
598
+ const existingDoctag = documentDoctags.find((dt) => dt.tag_ext_id === tag.external_id);
599
+ if (!existingDoctag) return;
600
+ await updateDocTag.mutateAsync({
601
+ tagId: tag.external_id,
602
+ docId: documentId,
603
+ citations: existingDoctag.citations,
604
+ note
605
+ });
606
+ },
607
+ [documentId, documentDoctags, updateDocTag]
608
+ );
609
+ const getChunkTagColors = useCallback(
610
+ (chunkExtId, workspaceTags) => {
611
+ const { appliedTags } = getChunkTagState(chunkExtId);
612
+ return appliedTags.map((dt) => {
613
+ const tag = workspaceTags.find((t) => t.external_id === dt.tag_ext_id);
614
+ return tag?.external_id;
615
+ }).filter((id) => !!id);
616
+ },
617
+ [getChunkTagState]
618
+ );
619
+ const isLoading = createDocTag.isPending || updateDocTag.isPending || deleteDocTag.isPending;
620
+ return {
621
+ getChunkTagState,
622
+ toggleChunkTag,
623
+ updateChunkTagNote,
624
+ getChunkTagColors,
625
+ isLoading
626
+ };
627
+ }
628
+ var GROUP_TITLES = {
629
+ checkbox: "Label",
630
+ select: "Choice",
631
+ text: "Text",
632
+ number: "Number"
633
+ };
634
+ function PassageTagPanel({
635
+ documentId,
636
+ chunkExtId,
637
+ chunkContent,
638
+ pageNumber,
639
+ documentDoctags,
640
+ onClose,
641
+ onViewTaggedChunks
642
+ }) {
643
+ const { createTag } = useDocumentViewerSupport();
644
+ const [editingNoteTagId, setEditingNoteTagId] = useState(null);
645
+ const [noteValue, setNoteValue] = useState("");
646
+ const { data: allTags } = useConnectedTags();
647
+ const workspaceTags = useMemo(() => allTags.filter(isDocumentTag), [allTags]);
648
+ const { getChunkTagState, toggleChunkTag, updateChunkTagNote, isLoading } = useChunkTagging({
649
+ documentId,
650
+ documentDoctags
651
+ });
652
+ const chunkState = useMemo(() => getChunkTagState(chunkExtId), [getChunkTagState, chunkExtId]);
653
+ const groupedTags = useMemo(() => {
654
+ const groups2 = {
655
+ checkbox: [],
656
+ select: [],
657
+ text: [],
658
+ number: []
659
+ };
660
+ workspaceTags.forEach((tag) => {
661
+ const group = tag.kind === "label" ? "checkbox" : tag.kind === "annotation" ? tag.tag_type.type : null;
662
+ if (group && groups2[group]) {
663
+ groups2[group].push(tag);
664
+ }
665
+ });
666
+ return groups2;
667
+ }, [workspaceTags]);
668
+ const handleToggleTag = useCallback(
669
+ async (tag) => {
670
+ try {
671
+ await toggleChunkTag(chunkExtId, tag);
672
+ } catch (error) {
673
+ console.error("Failed to toggle chunk tag:", error);
674
+ }
675
+ },
676
+ [chunkExtId, toggleChunkTag]
677
+ );
678
+ const handleStartEditNote = useCallback(
679
+ (tagExtId) => {
680
+ const doctag = chunkState.getDoctagForChunk(tagExtId);
681
+ setNoteValue(doctag?.note || "");
682
+ setEditingNoteTagId(tagExtId);
683
+ },
684
+ [chunkState]
685
+ );
686
+ const handleSaveNote = useCallback(
687
+ async (tag) => {
688
+ try {
689
+ await updateChunkTagNote(chunkExtId, tag, noteValue || null);
690
+ } catch (error) {
691
+ console.error("Failed to save note:", error);
692
+ }
693
+ setEditingNoteTagId(null);
694
+ setNoteValue("");
695
+ },
696
+ [chunkExtId, noteValue, updateChunkTagNote]
697
+ );
698
+ const preview = useMemo(() => {
699
+ const maxLength = 200;
700
+ if (chunkContent.length <= maxLength) return chunkContent;
701
+ return chunkContent.slice(0, maxLength) + "...";
702
+ }, [chunkContent]);
703
+ const groups = useMemo(
704
+ () => Object.entries(groupedTags).filter(([, tags]) => tags.length > 0).map(([type, tags]) => ({
705
+ id: type,
706
+ title: GROUP_TITLES[type] ?? type,
707
+ tags: tags.map((tag) => ({
708
+ id: tag.external_id,
709
+ color: getEntityColor(tag.external_id, "medium"),
710
+ isApplied: chunkState.isTagApplied(tag.external_id),
711
+ note: chunkState.getDoctagForChunk(tag.external_id)?.note,
712
+ badge: /* @__PURE__ */ jsx(TagEntity, { tag, size: "sm", showTooltip: false })
713
+ }))
714
+ })),
715
+ [groupedTags, chunkState]
716
+ );
717
+ const tagById = useMemo(
718
+ () => new Map(workspaceTags.map((tag) => [tag.external_id, tag])),
719
+ [workspaceTags]
720
+ );
721
+ return /* @__PURE__ */ jsx(
722
+ TagPassagePanel,
723
+ {
724
+ pageNumber,
725
+ preview,
726
+ groups,
727
+ isEmpty: workspaceTags.length === 0,
728
+ isBusy: isLoading,
729
+ editingNoteTagId,
730
+ noteValue,
731
+ onNoteChange: setNoteValue,
732
+ onToggle: (id) => {
733
+ const tag = tagById.get(id);
734
+ if (tag) void handleToggleTag(tag);
735
+ },
736
+ onViewTagged: onViewTaggedChunks,
737
+ onStartNote: handleStartEditNote,
738
+ onSaveNote: (id) => {
739
+ const tag = tagById.get(id);
740
+ if (tag) void handleSaveNote(tag);
741
+ },
742
+ onCancelNote: () => {
743
+ setEditingNoteTagId(null);
744
+ setNoteValue("");
745
+ },
746
+ onCreate: createTag,
747
+ onClose
748
+ }
749
+ );
750
+ }
751
+ var ParsedPassage = memo(function ParsedPassage2({
752
+ chunk,
753
+ pageNumber,
754
+ isHighlighted,
755
+ isSelected,
756
+ isCitationHighlight,
757
+ isActiveCitation,
758
+ factCitationActive,
759
+ isCitationMode,
760
+ runIsStart,
761
+ runIsEnd,
762
+ runEndIndex,
763
+ labels,
764
+ activeTagColor,
765
+ onTagPassage,
766
+ onShowLabel,
767
+ onRemoveLabel,
768
+ onChunkClick,
769
+ formatChunkWithCitation,
770
+ getPassageDeepLink,
771
+ copyToClipboard,
772
+ onOpenNavigator
773
+ }) {
774
+ const chunkId = chunk.chunkExtId;
775
+ const formattedContent = chunkId ? formatChunkWithCitation(chunk.content, pageNumber || void 0, chunkId) : "";
776
+ const isStrong = isActiveCitation || !factCitationActive && isSelected;
777
+ const isLight = isCitationHighlight || !factCitationActive && isHighlighted;
778
+ const ownIndex = chunk.chunkIndex !== void 0 ? chunk.chunkIndex + 1 : void 0;
779
+ const runLabel = runEndIndex !== void 0 && ownIndex !== void 0 && runEndIndex > ownIndex ? `\xB6${ownIndex}\u2013${runEndIndex}` : `\xB6${ownIndex ?? ""}`;
780
+ const showLabel = !!chunkId && (!isSelected || runIsStart);
781
+ const copyPassage = (type, text, href) => copyToClipboard(text, {
782
+ type,
783
+ documentTitle: chunk.docTitle || "Document",
784
+ pageNumber: pageNumber || void 0,
785
+ passageIndex: chunk.chunkIndex,
786
+ passageContent: formattedContent,
787
+ href
788
+ });
789
+ const passageItems = chunkId ? [
790
+ {
791
+ id: "copy-passage",
792
+ label: "Copy passage with citation",
793
+ primary: true,
794
+ testId: "passage-copy",
795
+ onSelect: () => copyPassage("passage", formattedContent, getPassageDeepLink(chunkId) || void 0)
796
+ },
797
+ {
798
+ id: "copy-link",
799
+ label: "Copy passage link",
800
+ testId: "passage-copy-link",
801
+ onSelect: () => {
802
+ const link = getPassageDeepLink(chunkId);
803
+ if (link) copyPassage("link", link, link);
804
+ }
805
+ },
806
+ ...onTagPassage ? [
807
+ {
808
+ id: "label",
809
+ label: "Label this passage",
810
+ testId: "passage-label",
811
+ onSelect: () => onTagPassage(chunkId)
812
+ }
813
+ ] : [],
814
+ ...onOpenNavigator ? [
815
+ {
816
+ id: "navigator",
817
+ label: "Open in navigator",
818
+ testId: "passage-navigator",
819
+ onSelect: onOpenNavigator
820
+ }
821
+ ] : []
822
+ ] : [];
823
+ const labelItems = chunkId ? labels.flatMap((l, i) => {
824
+ const suffix = labels.length > 1 ? ` \u201C${l.name}\u201D` : " label";
825
+ return [
826
+ ...onShowLabel ? [
827
+ {
828
+ id: `show-${l.tagExtId}`,
829
+ label: `Show${suffix}`,
830
+ primary: i === 0,
831
+ testId: "label-show",
832
+ onSelect: () => onShowLabel(l.tagExtId)
833
+ }
834
+ ] : [],
835
+ ...onRemoveLabel ? [
836
+ {
837
+ id: `remove-${l.tagExtId}`,
838
+ label: `Remove${suffix}`,
839
+ destructive: true,
840
+ testId: "label-remove",
841
+ onSelect: () => onRemoveLabel(chunkId, l.tagExtId)
842
+ }
843
+ ] : []
844
+ ];
845
+ }) : [];
846
+ return /* @__PURE__ */ jsx(
847
+ HtmlPassage,
848
+ {
849
+ chunkId,
850
+ highlight: isStrong ? "strong" : isLight ? "light" : void 0,
851
+ isHit: isHighlighted,
852
+ isSelected,
853
+ runIsStart,
854
+ runIsEnd,
855
+ isClickable: isCitationMode,
856
+ onSelect: onChunkClick && chunkId ? (extend) => onChunkClick(chunkId, extend) : void 0,
857
+ isTagged: labels.length > 0,
858
+ activeTagColor,
859
+ tagMark: labels.length > 0 ? /* @__PURE__ */ jsx(
860
+ LabelMark,
861
+ {
862
+ colors: labels.map((l) => l.color),
863
+ name: `Labels: ${labels.map((l) => l.name).join(", ")}`,
864
+ menuLabel: "Label actions",
865
+ testId: `passage-tag-dots-${chunkId}`,
866
+ preview: /* @__PURE__ */ jsx("span", { className: "block font-medium", children: labels.map((l) => l.name).join(", ") }),
867
+ items: labelItems
868
+ }
869
+ ) : void 0,
870
+ runMarker: isSelected && runIsStart ? /* @__PURE__ */ jsx(
871
+ NoteMark,
872
+ {
873
+ label: runLabel,
874
+ title: "Cited by this note \u2014 click a passage to add, shift-click for a range",
875
+ testId: `note-marker-${chunkId}`
876
+ }
877
+ ) : void 0,
878
+ mark: showLabel && chunkId ? /* @__PURE__ */ jsx(
879
+ PassageMark,
880
+ {
881
+ label: runLabel,
882
+ menuLabel: "Passage actions",
883
+ testId: `passage-mark-${chunkId}`,
884
+ items: passageItems
885
+ }
886
+ ) : void 0,
887
+ children: /* @__PURE__ */ jsx(MarkdownRenderer, { children: chunk.content })
888
+ }
889
+ );
890
+ });
891
+
892
+ // src/connection/viewer/parsed/citation-line.ts
893
+ function formatCitation(data) {
894
+ const { documentTitle, documentDate, pageNumber } = data;
895
+ let citation = "";
896
+ if (documentTitle) {
897
+ citation += `${documentTitle}`;
898
+ }
899
+ if (documentDate) {
900
+ citation += documentTitle ? `, ${documentDate}` : documentDate;
901
+ }
902
+ if (pageNumber) {
903
+ citation += citation ? `, p. ${pageNumber}` : `p. ${pageNumber}`;
904
+ }
905
+ return citation;
906
+ }
907
+
908
+ // src/connection/viewer/parsed/usePassageCopy.ts
909
+ function usePassageCopy({
910
+ documentId,
911
+ documentData,
912
+ selectedWorkspaceId
913
+ }) {
914
+ const { notify, runtimeConfig } = useBlocksEnvironment();
915
+ const deploymentDomain = runtimeConfig.deploymentDomain;
916
+ const formatChunkWithCitation = useCallback(
917
+ (chunkContent, pageNumber, chunkExtId) => {
918
+ const docDate = documentData?.doc_metadata?.doc_date;
919
+ const citation = formatCitation({
920
+ documentTitle: documentData?.doc_metadata?.title || documentData?.file_name || "",
921
+ documentDate: docDate ? new Date(docDate).toISOString().split("T")[0] : void 0,
922
+ pageNumber
923
+ });
924
+ const deepLinkUrl = selectedWorkspaceId && documentId && chunkExtId ? buildDeepLinkUrl(
925
+ { workspaceId: selectedWorkspaceId, documentId, chunkId: chunkExtId },
926
+ deploymentDomain
927
+ ) : "";
928
+ let result = `"${chunkContent.trim()}"`;
929
+ if (citation) {
930
+ result += `
931
+
932
+ \u2014 ${citation}`;
933
+ }
934
+ if (deepLinkUrl) {
935
+ result += `
936
+ [ARBI Docs link](${deepLinkUrl})`;
937
+ }
938
+ return result;
939
+ },
940
+ [documentData, selectedWorkspaceId, documentId, deploymentDomain]
941
+ );
942
+ const getPassageDeepLink = useCallback(
943
+ (chunkExtId) => {
944
+ if (!selectedWorkspaceId || !documentId || !chunkExtId) {
945
+ return "";
946
+ }
947
+ return buildDeepLinkUrl(
948
+ { workspaceId: selectedWorkspaceId, documentId, chunkId: chunkExtId },
949
+ deploymentDomain
950
+ );
951
+ },
952
+ [selectedWorkspaceId, documentId, deploymentDomain]
953
+ );
954
+ const copyToClipboard = useCallback(
955
+ async (text, passageInfo) => {
956
+ try {
957
+ if (passageInfo?.href) {
958
+ const isLink = passageInfo.type === "link";
959
+ const { documentTitle, pageNumber, passageIndex, href } = passageInfo;
960
+ const pageNotation = pageNumber ? `p.${pageNumber}` : "";
961
+ const passageNotation = passageIndex !== void 0 ? `(${passageIndex})` : "";
962
+ const citation = `${documentTitle}${pageNotation ? `, ${pageNotation}${passageNotation}` : ""}`;
963
+ let html;
964
+ let plainText;
965
+ if (isLink) {
966
+ html = `<a href="${href}" target="_blank" rel="noopener noreferrer">${citation}</a>`;
967
+ plainText = href;
968
+ } else {
969
+ const linkHtml = `<p><a href="${href}" target="_blank" rel="noopener noreferrer">${citation}</a></p>`;
970
+ html = `${linkHtml}<blockquote style="margin: 8px 0; padding-left: 12px; border-left: 3px solid #ccc;">${text}</blockquote>`;
971
+ plainText = `${href}
972
+
973
+ ${text}
974
+
975
+ \u2014 ${citation}`;
976
+ }
977
+ const clipboardItem = new ClipboardItem({
978
+ "text/html": new Blob([html], { type: "text/html" }),
979
+ "text/plain": new Blob([plainText], { type: "text/plain" })
980
+ });
981
+ await navigator.clipboard.write([clipboardItem]);
982
+ const prefix = isLink ? "Link copied:" : "Passage copied:";
983
+ const notificationElement = React.createElement(
984
+ "span",
985
+ { className: "inline-flex items-center gap-2" },
986
+ prefix,
987
+ React.createElement(PassagePill, {
988
+ icon: React.createElement(HtmlIcon, { size: 16, className: "flex-shrink-0" }),
989
+ documentTitle: passageInfo.documentTitle,
990
+ pageNumber: passageInfo.pageNumber,
991
+ passageIndex: passageInfo.passageIndex,
992
+ href: passageInfo.href,
993
+ onClick: (e) => e.stopPropagation(),
994
+ showTooltip: false
995
+ })
996
+ );
997
+ notify(notificationElement, "success", {
998
+ workspaceId: selectedWorkspaceId || void 0,
999
+ // The bell entry deep-links back to the copied passage.
1000
+ href: passageInfo.href
1001
+ });
1002
+ } else {
1003
+ await navigator.clipboard.writeText(text);
1004
+ notify("Copied to clipboard", "success");
1005
+ }
1006
+ } catch (err) {
1007
+ console.error("Clipboard copy failed:", err);
1008
+ notify("Failed to copy to clipboard.", "error");
1009
+ }
1010
+ },
1011
+ [selectedWorkspaceId, notify]
1012
+ );
1013
+ return {
1014
+ formatChunkWithCitation,
1015
+ getPassageDeepLink,
1016
+ copyToClipboard
1017
+ };
1018
+ }
1019
+ function useDocumentLabels(documentId, doctags, orderedChunkIds) {
1020
+ const { data: allTags } = useConnectedTags();
1021
+ const workspaceTags = useMemo(() => allTags.filter(isDocumentTag), [allTags]);
1022
+ const documentDoctags = useMemo(() => doctags ?? [], [doctags]);
1023
+ const tagSummaries = useMemo(
1024
+ () => summariseDocTags(documentDoctags, workspaceTags),
1025
+ [documentDoctags, workspaceTags]
1026
+ );
1027
+ const labelSummaries = useMemo(
1028
+ () => summariseDocTags(documentDoctags, workspaceTags.filter(isDocumentLabel)),
1029
+ [documentDoctags, workspaceTags]
1030
+ );
1031
+ const labelsByChunk = useMemo(() => {
1032
+ const map = /* @__PURE__ */ new Map();
1033
+ for (const s of tagSummaries) {
1034
+ const label = {
1035
+ tagExtId: s.tagExtId,
1036
+ name: s.name,
1037
+ color: getEntityColor(s.tagExtId, "medium")
1038
+ };
1039
+ for (const id of s.chunkIds) {
1040
+ const list = map.get(id);
1041
+ if (list) list.push(label);
1042
+ else map.set(id, [label]);
1043
+ }
1044
+ }
1045
+ return map;
1046
+ }, [tagSummaries]);
1047
+ const { toggleChunkTag } = useChunkTagging({ documentId, documentDoctags });
1048
+ const removeLabel = useCallback(
1049
+ async (chunkExtId, tagExtId) => {
1050
+ const tag = workspaceTags.find((t) => t.external_id === tagExtId);
1051
+ if (!tag) return;
1052
+ try {
1053
+ await toggleChunkTag(chunkExtId, tag);
1054
+ } catch (error) {
1055
+ console.error("Failed to remove passage label", error);
1056
+ }
1057
+ },
1058
+ [workspaceTags, toggleChunkTag]
1059
+ );
1060
+ const [activeTagExtId, setActiveTagExtId] = useState(null);
1061
+ const [tagCursor, setTagCursor] = useState(0);
1062
+ const [taggingChunkId, setTaggingChunkId] = useState(null);
1063
+ const activeTagSummary = tagSummaries.find((t) => t.tagExtId === activeTagExtId);
1064
+ const activeTagColor = activeTagExtId ? getEntityColor(activeTagExtId, "medium") : void 0;
1065
+ const activeChunkIds = useMemo(
1066
+ () => new Set(activeTagSummary?.chunkIds ?? []),
1067
+ [activeTagSummary]
1068
+ );
1069
+ const orderedActiveIds = useMemo(
1070
+ () => orderedChunkIds.filter((id) => activeChunkIds.has(id)),
1071
+ [orderedChunkIds, activeChunkIds]
1072
+ );
1073
+ const gotoTagged = useCallback(
1074
+ (delta) => {
1075
+ if (orderedActiveIds.length === 0) return;
1076
+ const next = (tagCursor + delta + orderedActiveIds.length) % orderedActiveIds.length;
1077
+ setTagCursor(next);
1078
+ document.getElementById(orderedActiveIds[next])?.scrollIntoView({ behavior: "smooth", block: "center" });
1079
+ },
1080
+ [tagCursor, orderedActiveIds]
1081
+ );
1082
+ const selectTag = useCallback((tagExtId) => {
1083
+ setActiveTagExtId(tagExtId);
1084
+ setTagCursor(0);
1085
+ }, []);
1086
+ useEffect(() => {
1087
+ if (!activeTagExtId || orderedActiveIds.length === 0) return;
1088
+ document.getElementById(orderedActiveIds[0])?.scrollIntoView({ behavior: "smooth", block: "center" });
1089
+ }, [activeTagExtId]);
1090
+ return {
1091
+ documentDoctags,
1092
+ labelSummaries,
1093
+ labelsByChunk,
1094
+ removeLabel,
1095
+ activeTagExtId,
1096
+ activeTagColor,
1097
+ activeChunkIds,
1098
+ tagCursor,
1099
+ gotoTagged,
1100
+ selectTag,
1101
+ taggingChunkId,
1102
+ setTaggingChunkId
1103
+ };
1104
+ }
1105
+ var NONE = [];
1106
+ var noop = () => {
1107
+ };
1108
+ function useCitationSelection({
1109
+ documentId,
1110
+ containerRef,
1111
+ processedChunks,
1112
+ orderedChunkIds,
1113
+ pageOf
1114
+ }) {
1115
+ const { passageCitation } = useDocumentViewerSupport();
1116
+ const activeCitationContext = passageCitation?.active ?? null;
1117
+ const setPendingPassagesForCitation = passageCitation?.pick ?? noop;
1118
+ const setUnselectedPassagesForCitation = passageCitation?.unpick ?? noop;
1119
+ const viewingPassageId = useArbiSessionState((s) => s.viewingDocument?.passageId ?? null);
1120
+ const [selectedChunks, setSelectedChunks] = useState(/* @__PURE__ */ new Set());
1121
+ const selectedChunksRef = useRef(selectedChunks);
1122
+ selectedChunksRef.current = selectedChunks;
1123
+ const selectionAnchorRef = useRef(null);
1124
+ const factCitationChunkIds = passageCitation?.highlighted ?? NONE;
1125
+ const factCitationSet = useMemo(() => new Set(factCitationChunkIds), [factCitationChunkIds]);
1126
+ const factCitationActive = factCitationSet.size > 0;
1127
+ const factCitationSetRef = useRef(factCitationSet);
1128
+ factCitationSetRef.current = factCitationSet;
1129
+ const isCitationMode = !!activeCitationContext && activeCitationContext.documentId === documentId;
1130
+ const selectionRuns = useMemo(
1131
+ () => computeSelectionRuns(processedChunks, selectedChunks),
1132
+ [processedChunks, selectedChunks]
1133
+ );
1134
+ const runStartIds = useMemo(
1135
+ () => orderedChunkIds.filter((id) => selectionRuns.get(id)?.isStart),
1136
+ [orderedChunkIds, selectionRuns]
1137
+ );
1138
+ const [runCursor, setRunCursor] = useState(0);
1139
+ const gotoRun = useCallback(
1140
+ (delta) => {
1141
+ if (runStartIds.length === 0) return;
1142
+ const next = (runCursor + delta + runStartIds.length) % runStartIds.length;
1143
+ setRunCursor(next);
1144
+ document.getElementById(runStartIds[next])?.scrollIntoView({ behavior: "smooth", block: "center" });
1145
+ },
1146
+ [runCursor, runStartIds]
1147
+ );
1148
+ useEffect(() => {
1149
+ if (runCursor >= runStartIds.length) setRunCursor(0);
1150
+ }, [runStartIds.length, runCursor]);
1151
+ useEffect(() => {
1152
+ setSelectedChunks(/* @__PURE__ */ new Set());
1153
+ }, [activeCitationContext?.key, activeCitationContext?.documentId]);
1154
+ const handleSelectionEnd = useCallback(() => {
1155
+ if (!activeCitationContext || activeCitationContext.documentId !== documentId) return;
1156
+ const selection = window.getSelection();
1157
+ if (!selection || selection.isCollapsed || selection.rangeCount === 0) return;
1158
+ const container = containerRef.current;
1159
+ if (!container) return;
1160
+ const newSelectedChunks = /* @__PURE__ */ new Set();
1161
+ container.querySelectorAll("[data-chunk-id]").forEach((el) => {
1162
+ const chunkId = el.getAttribute("data-chunk-id");
1163
+ if (!chunkId) return;
1164
+ if (selection.containsNode(el, true)) newSelectedChunks.add(chunkId);
1165
+ });
1166
+ if (newSelectedChunks.size > 0) {
1167
+ setSelectedChunks((prev) => /* @__PURE__ */ new Set([...prev, ...newSelectedChunks]));
1168
+ setPendingPassagesForCitation(
1169
+ Array.from(newSelectedChunks).map((id) => ({ chunk_id: id, page: pageOf(id) }))
1170
+ );
1171
+ }
1172
+ selection.removeAllRanges();
1173
+ }, [activeCitationContext, documentId, containerRef, setPendingPassagesForCitation, pageOf]);
1174
+ const handleChunkClick = useCallback(
1175
+ (chunkId, extendFromAnchor) => {
1176
+ if (!activeCitationContext || activeCitationContext.documentId !== documentId) return;
1177
+ const anchorId = selectionAnchorRef.current;
1178
+ const anchorIdx = anchorId ? orderedChunkIds.indexOf(anchorId) : -1;
1179
+ const targetIdx = orderedChunkIds.indexOf(chunkId);
1180
+ if (extendFromAnchor && anchorIdx !== -1 && targetIdx !== -1) {
1181
+ const [from, to] = anchorIdx <= targetIdx ? [anchorIdx, targetIdx] : [targetIdx, anchorIdx];
1182
+ const range = orderedChunkIds.slice(from, to + 1);
1183
+ setSelectedChunks((prev) => /* @__PURE__ */ new Set([...prev, ...range]));
1184
+ setPendingPassagesForCitation(range.map((id) => ({ chunk_id: id, page: pageOf(id) })));
1185
+ return;
1186
+ }
1187
+ selectionAnchorRef.current = chunkId;
1188
+ const wasSelected = factCitationSetRef.current.size ? factCitationSetRef.current.has(chunkId) : selectedChunksRef.current.has(chunkId);
1189
+ setSelectedChunks((prev) => {
1190
+ const next = new Set(prev);
1191
+ if (wasSelected) next.delete(chunkId);
1192
+ else next.add(chunkId);
1193
+ return next;
1194
+ });
1195
+ if (wasSelected) setUnselectedPassagesForCitation([chunkId]);
1196
+ else setPendingPassagesForCitation([{ chunk_id: chunkId, page: pageOf(chunkId) }]);
1197
+ },
1198
+ [
1199
+ activeCitationContext,
1200
+ documentId,
1201
+ orderedChunkIds,
1202
+ setPendingPassagesForCitation,
1203
+ setUnselectedPassagesForCitation,
1204
+ pageOf
1205
+ ]
1206
+ );
1207
+ return {
1208
+ isCitationMode,
1209
+ selectedChunks,
1210
+ selectionRuns,
1211
+ runStartIds,
1212
+ runCursor,
1213
+ gotoRun,
1214
+ handleSelectionEnd,
1215
+ handleChunkClick,
1216
+ factCitationSet,
1217
+ factCitationActive,
1218
+ viewingPassageId
1219
+ };
1220
+ }
1221
+ function scrollToChunk(chunkId) {
1222
+ requestAnimationFrame(() => {
1223
+ const element = document.getElementById(chunkId);
1224
+ if (element) {
1225
+ element.scrollIntoView({ behavior: "auto", block: "center" });
1226
+ return;
1227
+ }
1228
+ setTimeout(() => {
1229
+ document.getElementById(chunkId)?.scrollIntoView({ behavior: "auto", block: "center" });
1230
+ }, 100);
1231
+ });
1232
+ }
1233
+ function usePageScrollSync({
1234
+ containerRef,
1235
+ processedChunksLength,
1236
+ scrollTrigger,
1237
+ onVisibilityUpdate
1238
+ }) {
1239
+ const session = useArbiSessionStore();
1240
+ const viewingId = useArbiSessionState((s) => s.viewingDocument?.id);
1241
+ const viewingDocumentPage = useArbiSessionState((s) => s.viewingDocument?.page ?? null);
1242
+ const viewingPassageId = useArbiSessionState((s) => s.viewingDocument?.passageId ?? null);
1243
+ const activeTab = useArbiSessionState((s) => s.documentViewer.tab);
1244
+ const setViewingDocumentPage = useCallback(
1245
+ (page) => {
1246
+ if (viewingId) session.setViewingDocument({ id: viewingId, page });
1247
+ },
1248
+ [session, viewingId]
1249
+ );
1250
+ const [highlighted, setHighlighted] = useState(null);
1251
+ const isChunkHighlighted = useCallback(
1252
+ (chunkId) => chunkId === highlighted,
1253
+ [highlighted]
1254
+ );
1255
+ const scrollCompleteRef = useRef(false);
1256
+ const viewingPassageIdRef = useRef(viewingPassageId);
1257
+ viewingPassageIdRef.current = viewingPassageId;
1258
+ useEffect(() => {
1259
+ if (activeTab !== "html") {
1260
+ scrollCompleteRef.current = false;
1261
+ }
1262
+ }, [activeTab]);
1263
+ useEffect(() => {
1264
+ if (activeTab !== "html" || processedChunksLength === 0) return;
1265
+ if (scrollCompleteRef.current) return;
1266
+ if (viewingPassageIdRef.current) {
1267
+ scrollCompleteRef.current = true;
1268
+ return;
1269
+ }
1270
+ requestAnimationFrame(() => {
1271
+ if (viewingDocumentPage) {
1272
+ const pageElement = document.getElementById(`page-${viewingDocumentPage}`);
1273
+ if (pageElement) {
1274
+ pageElement.scrollIntoView({ behavior: "auto", block: "start" });
1275
+ setTimeout(() => {
1276
+ scrollCompleteRef.current = true;
1277
+ }, 100);
1278
+ }
1279
+ } else {
1280
+ scrollCompleteRef.current = true;
1281
+ }
1282
+ });
1283
+ }, [activeTab, processedChunksLength]);
1284
+ const lastScrolledPassageRef = useRef(null);
1285
+ useEffect(() => {
1286
+ if (!viewingPassageId) {
1287
+ lastScrolledPassageRef.current = null;
1288
+ setHighlighted(null);
1289
+ }
1290
+ }, [viewingPassageId]);
1291
+ useEffect(() => {
1292
+ if (!viewingPassageId || activeTab !== "html" || processedChunksLength === 0) return;
1293
+ if (lastScrolledPassageRef.current === viewingPassageId) return;
1294
+ lastScrolledPassageRef.current = viewingPassageId;
1295
+ setHighlighted(viewingPassageId);
1296
+ scrollToChunk(viewingPassageId);
1297
+ }, [viewingPassageId, activeTab, processedChunksLength]);
1298
+ const viewingPageRef = useRef(viewingDocumentPage);
1299
+ viewingPageRef.current = viewingDocumentPage;
1300
+ useEffect(() => {
1301
+ const viewingDocumentPage2 = viewingPageRef.current;
1302
+ if (!scrollTrigger || processedChunksLength === 0 || !viewingDocumentPage2) return;
1303
+ scrollCompleteRef.current = false;
1304
+ requestAnimationFrame(() => {
1305
+ const pageElement = document.getElementById(`page-${viewingDocumentPage2}`);
1306
+ if (pageElement) {
1307
+ pageElement.scrollIntoView({ behavior: "smooth", block: "start" });
1308
+ }
1309
+ setTimeout(() => {
1310
+ scrollCompleteRef.current = true;
1311
+ }, 500);
1312
+ });
1313
+ }, [scrollTrigger, processedChunksLength]);
1314
+ const getPageElements = useCallback(
1315
+ () => Array.from(document.querySelectorAll('[id^="page-"]')),
1316
+ []
1317
+ );
1318
+ const getPageNumber = useCallback((element) => {
1319
+ const pageMatch = element.id.match(/page-(\d+)/);
1320
+ return pageMatch ? parseInt(pageMatch[1], 10) : 0;
1321
+ }, []);
1322
+ usePageIntersectionObserver({
1323
+ containerRef,
1324
+ scrollCompleteRef,
1325
+ isEnabled: activeTab === "html" && processedChunksLength > 0,
1326
+ getPageElements,
1327
+ getPageNumber,
1328
+ onPageChange: setViewingDocumentPage,
1329
+ onVisibilityUpdate,
1330
+ observeKey: processedChunksLength
1331
+ });
1332
+ return {
1333
+ isChunkHighlighted
1334
+ };
1335
+ }
1336
+
1337
+ // src/connection/viewer/parsed/useLazyPages.ts
1338
+ var LAZY_LOAD_BUFFER_BEFORE = 2;
1339
+ var LAZY_LOAD_BUFFER_AFTER = 5;
1340
+ var INITIAL_PAGES_TO_RENDER = 3;
1341
+ var PAGE_ASPECT_RATIO = 1.3;
1342
+ function useLazyPages({ containerRef, processedChunks, scrollTrigger }) {
1343
+ const [containerWidth, setContainerWidth] = useState(0);
1344
+ useEffect(() => {
1345
+ const el = containerRef.current;
1346
+ if (!el) return;
1347
+ const observer = new ResizeObserver((entries) => {
1348
+ setContainerWidth(entries[0]?.contentRect.width ?? 0);
1349
+ });
1350
+ observer.observe(el);
1351
+ return () => observer.disconnect();
1352
+ }, [containerRef]);
1353
+ const [renderedPages, setRenderedPages] = useState(
1354
+ () => new Set(Array.from({ length: INITIAL_PAGES_TO_RENDER }, (_, i) => i))
1355
+ );
1356
+ useEffect(() => {
1357
+ if (processedChunks.length === 0) return;
1358
+ const renderAll = () => {
1359
+ setRenderedPages((prev) => {
1360
+ if (prev.size === processedChunks.length) return prev;
1361
+ return new Set(Array.from({ length: processedChunks.length }, (_, i) => i));
1362
+ });
1363
+ };
1364
+ if (typeof window.requestIdleCallback !== "function") {
1365
+ const timer = setTimeout(renderAll, 200);
1366
+ return () => clearTimeout(timer);
1367
+ }
1368
+ const id = window.requestIdleCallback(renderAll);
1369
+ return () => window.cancelIdleCallback(id);
1370
+ }, [processedChunks.length]);
1371
+ const handleVisibilityUpdate = useCallback(
1372
+ (visibilityMap) => {
1373
+ const visiblePageNumbers = Array.from(visibilityMap.entries()).filter(([, ratio]) => ratio > 0).map(([pageNum]) => pageNum);
1374
+ if (visiblePageNumbers.length === 0) return;
1375
+ const visibleIndices = [];
1376
+ processedChunks.forEach((page, index) => {
1377
+ if (page.pageNumber !== null && visiblePageNumbers.includes(page.pageNumber)) {
1378
+ visibleIndices.push(index);
1379
+ }
1380
+ });
1381
+ if (visibleIndices.length === 0) return;
1382
+ const minIndex = Math.min(...visibleIndices);
1383
+ const maxIndex = Math.max(...visibleIndices);
1384
+ const pagesToRender = /* @__PURE__ */ new Set();
1385
+ for (let i = Math.max(0, minIndex - LAZY_LOAD_BUFFER_BEFORE); i <= Math.min(processedChunks.length - 1, maxIndex + LAZY_LOAD_BUFFER_AFTER); i++) {
1386
+ pagesToRender.add(i);
1387
+ }
1388
+ setRenderedPages((prev) => {
1389
+ for (const i of pagesToRender) if (!prev.has(i)) return /* @__PURE__ */ new Set([...prev, ...pagesToRender]);
1390
+ return prev;
1391
+ });
1392
+ },
1393
+ [processedChunks]
1394
+ );
1395
+ const viewingDocumentPage = useArbiSessionState((s) => s.viewingDocument?.page ?? null);
1396
+ const viewingPassageId = useArbiSessionState((s) => s.viewingDocument?.passageId ?? null);
1397
+ const activeTab = useArbiSessionState((s) => s.documentViewer.tab);
1398
+ useEffect(() => {
1399
+ if (!viewingDocumentPage || processedChunks.length === 0) return;
1400
+ if (!viewingPassageId && activeTab !== "html") return;
1401
+ const targetIndex = processedChunks.findIndex((p) => p.pageNumber === viewingDocumentPage);
1402
+ if (targetIndex === -1) return;
1403
+ setRenderedPages((prev) => {
1404
+ if (prev.has(targetIndex)) return prev;
1405
+ const next = new Set(prev);
1406
+ for (let i = Math.max(0, targetIndex - LAZY_LOAD_BUFFER_BEFORE); i <= Math.min(processedChunks.length - 1, targetIndex + LAZY_LOAD_BUFFER_AFTER); i++) {
1407
+ next.add(i);
1408
+ }
1409
+ return next;
1410
+ });
1411
+ }, [viewingPassageId, viewingDocumentPage, processedChunks, activeTab]);
1412
+ const tabSwitchScrolledRef = useRef(false);
1413
+ const passageScrolledRef = useRef(null);
1414
+ useEffect(() => {
1415
+ if (activeTab !== "html") tabSwitchScrolledRef.current = false;
1416
+ }, [activeTab]);
1417
+ useEffect(() => {
1418
+ if (!viewingPassageId) passageScrolledRef.current = null;
1419
+ }, [viewingPassageId]);
1420
+ useEffect(() => {
1421
+ if (activeTab !== "html" || !viewingDocumentPage) return;
1422
+ if (viewingPassageId && passageScrolledRef.current !== viewingPassageId) {
1423
+ const target = viewingPassageId;
1424
+ const deadline = performance.now() + 3e3;
1425
+ let raf = 0;
1426
+ const tick = () => {
1427
+ const el = document.getElementById(target);
1428
+ if (el) {
1429
+ passageScrolledRef.current = target;
1430
+ tabSwitchScrolledRef.current = true;
1431
+ el.scrollIntoView({ behavior: "auto", block: "center" });
1432
+ raf = requestAnimationFrame(
1433
+ () => el.scrollIntoView({ behavior: "auto", block: "center" })
1434
+ );
1435
+ return;
1436
+ }
1437
+ if (performance.now() < deadline) raf = requestAnimationFrame(tick);
1438
+ };
1439
+ raf = requestAnimationFrame(tick);
1440
+ return () => cancelAnimationFrame(raf);
1441
+ }
1442
+ if (tabSwitchScrolledRef.current) return;
1443
+ const pageElement = document.getElementById(`page-${viewingDocumentPage}`);
1444
+ if (!pageElement) return;
1445
+ tabSwitchScrolledRef.current = true;
1446
+ requestAnimationFrame(() => {
1447
+ pageElement.scrollIntoView({ behavior: "auto", block: "start" });
1448
+ });
1449
+ }, [renderedPages, activeTab, viewingDocumentPage, viewingPassageId]);
1450
+ const { isChunkHighlighted } = usePageScrollSync({
1451
+ containerRef,
1452
+ processedChunksLength: processedChunks.length,
1453
+ scrollTrigger,
1454
+ onVisibilityUpdate: handleVisibilityUpdate
1455
+ });
1456
+ return {
1457
+ renderedPages,
1458
+ isChunkHighlighted,
1459
+ placeholderHeight: containerWidth ? containerWidth * PAGE_ASPECT_RATIO : 500
1460
+ };
1461
+ }
1462
+ var EMPTY_LABELS = [];
1463
+ function ParsedTextView({
1464
+ documentId,
1465
+ stage = "final",
1466
+ onLoadSuccess,
1467
+ scrollTrigger,
1468
+ zoom = 1,
1469
+ onZoomChange
1470
+ }) {
1471
+ const { workspaceId } = useConnectedWorkspace();
1472
+ const { openInNavigator } = useDocumentViewerSupport();
1473
+ const containerRef = useRef(null);
1474
+ const { data: documentRecord } = useDocument(parseVersionRef(documentId).docId, {
1475
+ enabled: !!documentId && !!workspaceId
1476
+ });
1477
+ const documentData = documentRecord ?? void 0;
1478
+ const { data: parsedDoc, isLoading, error } = useParsedDocument(documentId, stage);
1479
+ const { processedChunks, maxPageNumber } = useProcessedChunks(
1480
+ parsedDoc?.chunks,
1481
+ parsedDoc?.footnotes || {}
1482
+ );
1483
+ const pageByChunk = useMemo(() => {
1484
+ const map = /* @__PURE__ */ new Map();
1485
+ for (const page of processedChunks) {
1486
+ for (const chunk of page.chunks) {
1487
+ if (chunk.chunkExtId) map.set(chunk.chunkExtId, page.pageNumber ?? void 0);
1488
+ }
1489
+ }
1490
+ return map;
1491
+ }, [processedChunks]);
1492
+ const pageOf = useCallback((id) => pageByChunk.get(id), [pageByChunk]);
1493
+ const orderedChunkIds = useMemo(
1494
+ () => processedChunks.flatMap(
1495
+ (page) => page.chunks.map((c) => c.chunkExtId).filter((id) => !!id)
1496
+ ),
1497
+ [processedChunks]
1498
+ );
1499
+ const totalPages = useMemo(
1500
+ () => processedChunks.filter((p) => p.pageNumber !== null).length,
1501
+ [processedChunks]
1502
+ );
1503
+ const labels = useDocumentLabels(documentId, documentData?.doctags, orderedChunkIds);
1504
+ const citation = useCitationSelection({
1505
+ documentId,
1506
+ containerRef,
1507
+ processedChunks,
1508
+ orderedChunkIds,
1509
+ pageOf
1510
+ });
1511
+ const { renderedPages, isChunkHighlighted, placeholderHeight } = useLazyPages({
1512
+ containerRef,
1513
+ processedChunks,
1514
+ scrollTrigger
1515
+ });
1516
+ useScrollAnchor(containerRef, processedChunks.length);
1517
+ usePinchZoom({ containerRef, zoom, onZoomChange });
1518
+ const { formatChunkWithCitation, getPassageDeepLink, copyToClipboard } = usePassageCopy({
1519
+ documentId,
1520
+ documentData,
1521
+ selectedWorkspaceId: workspaceId
1522
+ });
1523
+ const handleOpenNavigator = useMemo(
1524
+ () => openInNavigator ? () => openInNavigator(documentId) : void 0,
1525
+ [openInNavigator, documentId]
1526
+ );
1527
+ useEffect(() => {
1528
+ if (maxPageNumber > 0 && onLoadSuccess) {
1529
+ try {
1530
+ onLoadSuccess(maxPageNumber);
1531
+ } catch (err) {
1532
+ console.error("Error in onLoadSuccess callback:", err);
1533
+ }
1534
+ }
1535
+ }, [maxPageNumber, onLoadSuccess]);
1536
+ if (isLoading) return /* @__PURE__ */ jsx(ViewerMessage, { children: "Loading parsed document..." });
1537
+ if (error) {
1538
+ return /* @__PURE__ */ jsx(ViewerMessage, { children: /* @__PURE__ */ jsx("span", { className: "text-red-text", children: "Failed to load parsed document" }) });
1539
+ }
1540
+ const taggingChunk = labels.taggingChunkId ? processedChunks.flatMap((page) => page.chunks).find((c) => c.chunkExtId === labels.taggingChunkId) : void 0;
1541
+ return /* @__PURE__ */ jsxs(HtmlViewerRoot, { children: [
1542
+ /* @__PURE__ */ jsx(
1543
+ DocumentLabelBar,
1544
+ {
1545
+ summaries: labels.labelSummaries,
1546
+ activeTagExtId: labels.activeTagExtId,
1547
+ cursor: labels.tagCursor,
1548
+ onSelect: labels.selectTag,
1549
+ onStep: labels.gotoTagged
1550
+ }
1551
+ ),
1552
+ labels.taggingChunkId && /* @__PURE__ */ jsx(
1553
+ PassageTagPanel,
1554
+ {
1555
+ documentId,
1556
+ chunkExtId: labels.taggingChunkId,
1557
+ chunkContent: taggingChunk?.content ?? "",
1558
+ pageNumber: pageOf(labels.taggingChunkId),
1559
+ documentDoctags: labels.documentDoctags,
1560
+ onClose: () => labels.setTaggingChunkId(null),
1561
+ onViewTaggedChunks: (tagExtId) => {
1562
+ labels.selectTag(tagExtId);
1563
+ labels.setTaggingChunkId(null);
1564
+ }
1565
+ }
1566
+ ),
1567
+ !citation.factCitationActive && citation.runStartIds.length > 0 && /* @__PURE__ */ jsx(
1568
+ CitedRunNav,
1569
+ {
1570
+ position: citation.runCursor + 1,
1571
+ total: citation.runStartIds.length,
1572
+ onStep: citation.gotoRun
1573
+ }
1574
+ ),
1575
+ /* @__PURE__ */ jsx(HtmlViewerSurface, { surfaceRef: containerRef, onMouseUp: citation.handleSelectionEnd, children: processedChunks.length > 0 ? processedChunks.map((page, pageIndex) => /* @__PURE__ */ jsx(
1576
+ HtmlPage,
1577
+ {
1578
+ pageNumber: page.pageNumber,
1579
+ totalPages,
1580
+ isRendered: renderedPages.has(pageIndex),
1581
+ placeholderHeight,
1582
+ zoom,
1583
+ children: page.chunks.map((chunk, chunkIndex) => {
1584
+ const id = chunk.chunkExtId;
1585
+ const run = id ? citation.selectionRuns.get(id) : void 0;
1586
+ return /* @__PURE__ */ jsx(
1587
+ ParsedPassage,
1588
+ {
1589
+ chunk,
1590
+ pageNumber: page.pageNumber,
1591
+ isHighlighted: !!(id && isChunkHighlighted(id)),
1592
+ isSelected: !!(id && citation.selectedChunks.has(id)),
1593
+ isCitationHighlight: !!(id && citation.factCitationSet.has(id)),
1594
+ isActiveCitation: !!(id && citation.factCitationActive && citation.viewingPassageId === id && // Only while it is STILL cited: clicking it off must clear it.
1595
+ citation.factCitationSet.has(id)),
1596
+ factCitationActive: citation.factCitationActive,
1597
+ isCitationMode: citation.isCitationMode,
1598
+ runIsStart: !!run?.isStart,
1599
+ runIsEnd: !!run?.isEnd,
1600
+ runEndIndex: run?.endIndex,
1601
+ onChunkClick: citation.isCitationMode ? citation.handleChunkClick : void 0,
1602
+ formatChunkWithCitation,
1603
+ getPassageDeepLink,
1604
+ copyToClipboard,
1605
+ onOpenNavigator: handleOpenNavigator,
1606
+ labels: id ? labels.labelsByChunk.get(id) ?? EMPTY_LABELS : EMPTY_LABELS,
1607
+ activeTagColor: id && labels.activeChunkIds.has(id) ? labels.activeTagColor : void 0,
1608
+ onTagPassage: labels.setTaggingChunkId,
1609
+ onShowLabel: labels.selectTag,
1610
+ onRemoveLabel: labels.removeLabel
1611
+ },
1612
+ chunkIndex
1613
+ );
1614
+ })
1615
+ },
1616
+ pageIndex
1617
+ )) : /* @__PURE__ */ jsx(HtmlViewerEmpty, {}) })
1618
+ ] });
1619
+ }
499
1620
  var AnnotatedPdfViewer = lazy(
500
1621
  () => import('../document-tools/viewer/index.js').then((m) => ({ default: m.AnnotatedPdfViewer }))
501
1622
  );
@@ -641,17 +1762,17 @@ function ViewerPdfPane({
641
1762
  return /* @__PURE__ */ jsx(SessionPdfViewer, { ...viewerProps, file: pdfBlob });
642
1763
  }
643
1764
  var NativeDocxEditor = lazy(
644
- () => import('../ConnectedNativeDocxEditor-IPEZDERL.js').then((m) => ({
1765
+ () => import('../ConnectedNativeDocxEditor-LRYU5HPA.js').then((m) => ({
645
1766
  default: m.ConnectedNativeDocxEditor
646
1767
  }))
647
1768
  );
648
1769
  var StreamedWordArtifact = lazy(
649
- () => import('../ConnectedStreamedWordArtifact-UZVNBKQB.js').then((m) => ({
1770
+ () => import('../ConnectedStreamedWordArtifact-QAUVUCSX.js').then((m) => ({
650
1771
  default: m.ConnectedStreamedWordArtifact
651
1772
  }))
652
1773
  );
653
1774
  var NativeDocxComparison = lazy(
654
- () => import('../ConnectedNativeDocxComparison-XQSXVGHR.js').then((m) => ({
1775
+ () => import('../ConnectedNativeDocxComparison-OFPIXNJB.js').then((m) => ({
655
1776
  default: m.ConnectedNativeDocxComparison
656
1777
  }))
657
1778
  );
@@ -1209,13 +2330,16 @@ function DocumentViewerContent({
1209
2330
  },
1210
2331
  documentId
1211
2332
  ) }),
1212
- !isCaptureDraft && !isComparing && activeTab === "html" && !(isStreaming && isArtifact && !liveKind) && support.renderParsedView && /* @__PURE__ */ jsx(ViewerPane, { children: /* @__PURE__ */ jsx(ViewerControlBar, { controls: pageNav, children: support.renderParsedView({
1213
- documentId: pdfSnapshotId || documentId,
1214
- onLoadSuccess: handleLoadSuccess,
1215
- scrollTrigger,
1216
- zoom,
1217
- onZoomChange: setZoom
1218
- }) }) })
2333
+ !isCaptureDraft && !isComparing && activeTab === "html" && !(isStreaming && isArtifact && !liveKind) && /* @__PURE__ */ jsx(ViewerPane, { children: /* @__PURE__ */ jsx(ViewerControlBar, { controls: pageNav, children: /* @__PURE__ */ jsx(
2334
+ ParsedTextView,
2335
+ {
2336
+ documentId: pdfSnapshotId || documentId,
2337
+ onLoadSuccess: handleLoadSuccess,
2338
+ scrollTrigger,
2339
+ zoom,
2340
+ onZoomChange: setZoom
2341
+ }
2342
+ ) }) })
1219
2343
  ]
1220
2344
  }
1221
2345
  );