@researai/deepscientist 1.5.15 → 1.5.17

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 (202) hide show
  1. package/README.md +385 -104
  2. package/bin/ds.js +1241 -110
  3. package/docs/en/00_QUICK_START.md +100 -19
  4. package/docs/en/01_SETTINGS_REFERENCE.md +34 -1
  5. package/docs/en/02_START_RESEARCH_GUIDE.md +7 -0
  6. package/docs/en/05_TUI_GUIDE.md +6 -0
  7. package/docs/en/06_RUNTIME_AND_CANVAS.md +4 -3
  8. package/docs/en/09_DOCTOR.md +25 -8
  9. package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
  10. package/docs/en/15_CODEX_PROVIDER_SETUP.md +37 -11
  11. package/docs/en/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
  12. package/docs/en/19_LOCAL_BROWSER_AUTH.md +70 -0
  13. package/docs/en/20_WORKSPACE_MODES_GUIDE.md +250 -0
  14. package/docs/en/21_LOCAL_MODEL_BACKENDS_GUIDE.md +283 -0
  15. package/docs/en/91_DEVELOPMENT.md +237 -0
  16. package/docs/en/README.md +24 -2
  17. package/docs/zh/00_QUICK_START.md +89 -19
  18. package/docs/zh/01_SETTINGS_REFERENCE.md +34 -1
  19. package/docs/zh/02_START_RESEARCH_GUIDE.md +7 -0
  20. package/docs/zh/05_TUI_GUIDE.md +6 -0
  21. package/docs/zh/09_DOCTOR.md +26 -9
  22. package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
  23. package/docs/zh/15_CODEX_PROVIDER_SETUP.md +37 -11
  24. package/docs/zh/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
  25. package/docs/zh/19_LOCAL_BROWSER_AUTH.md +68 -0
  26. package/docs/zh/20_WORKSPACE_MODES_GUIDE.md +251 -0
  27. package/docs/zh/21_LOCAL_MODEL_BACKENDS_GUIDE.md +281 -0
  28. package/docs/zh/README.md +24 -2
  29. package/install.sh +46 -4
  30. package/package.json +2 -1
  31. package/pyproject.toml +1 -1
  32. package/src/deepscientist/__init__.py +1 -1
  33. package/src/deepscientist/acp/envelope.py +6 -0
  34. package/src/deepscientist/artifact/service.py +647 -22
  35. package/src/deepscientist/bash_exec/service.py +234 -9
  36. package/src/deepscientist/bridges/connectors.py +8 -2
  37. package/src/deepscientist/cli.py +115 -19
  38. package/src/deepscientist/codex_cli_compat.py +367 -22
  39. package/src/deepscientist/config/models.py +2 -1
  40. package/src/deepscientist/config/service.py +183 -13
  41. package/src/deepscientist/daemon/api/handlers.py +255 -31
  42. package/src/deepscientist/daemon/api/router.py +9 -0
  43. package/src/deepscientist/daemon/app.py +1146 -105
  44. package/src/deepscientist/diagnostics/__init__.py +6 -0
  45. package/src/deepscientist/diagnostics/runner_failures.py +130 -0
  46. package/src/deepscientist/doctor.py +207 -3
  47. package/src/deepscientist/gitops/__init__.py +10 -1
  48. package/src/deepscientist/gitops/diff.py +129 -0
  49. package/src/deepscientist/gitops/service.py +4 -1
  50. package/src/deepscientist/mcp/server.py +39 -0
  51. package/src/deepscientist/prompts/builder.py +275 -34
  52. package/src/deepscientist/quest/layout.py +15 -2
  53. package/src/deepscientist/quest/service.py +707 -55
  54. package/src/deepscientist/quest/stage_views.py +6 -1
  55. package/src/deepscientist/runners/codex.py +143 -43
  56. package/src/deepscientist/shared.py +19 -0
  57. package/src/deepscientist/skills/__init__.py +2 -2
  58. package/src/deepscientist/skills/installer.py +196 -5
  59. package/src/deepscientist/skills/registry.py +66 -0
  60. package/src/prompts/connectors/qq.md +18 -8
  61. package/src/prompts/connectors/weixin.md +16 -6
  62. package/src/prompts/contracts/shared_interaction.md +14 -2
  63. package/src/prompts/system.md +23 -5
  64. package/src/prompts/system_copilot.md +56 -0
  65. package/src/skills/analysis-campaign/SKILL.md +1 -0
  66. package/src/skills/baseline/SKILL.md +8 -0
  67. package/src/skills/decision/SKILL.md +8 -0
  68. package/src/skills/experiment/SKILL.md +8 -0
  69. package/src/skills/figure-polish/SKILL.md +1 -0
  70. package/src/skills/finalize/SKILL.md +1 -0
  71. package/src/skills/idea/SKILL.md +1 -0
  72. package/src/skills/intake-audit/SKILL.md +8 -0
  73. package/src/skills/mentor/SKILL.md +217 -0
  74. package/src/skills/mentor/references/correction-rules.md +210 -0
  75. package/src/skills/mentor/references/knowledge-profile.md +91 -0
  76. package/src/skills/mentor/references/persona-profile.md +138 -0
  77. package/src/skills/mentor/references/taste-profile.md +128 -0
  78. package/src/skills/mentor/references/thought-style-profile.md +138 -0
  79. package/src/skills/mentor/references/work-profile.md +289 -0
  80. package/src/skills/mentor/references/workflow-profile.md +240 -0
  81. package/src/skills/optimize/SKILL.md +1 -0
  82. package/src/skills/rebuttal/SKILL.md +1 -0
  83. package/src/skills/review/SKILL.md +1 -0
  84. package/src/skills/scout/SKILL.md +8 -0
  85. package/src/skills/write/SKILL.md +1 -0
  86. package/src/tui/dist/app/AppContainer.js +19 -11
  87. package/src/tui/dist/index.js +4 -1
  88. package/src/tui/dist/lib/api.js +33 -3
  89. package/src/tui/package.json +1 -1
  90. package/src/ui/dist/assets/AiManusChatView-Bv-Z8YpU.js +204 -0
  91. package/src/ui/dist/assets/AnalysisPlugin-BCKAfjba.js +1 -0
  92. package/src/ui/dist/assets/CliPlugin-BCKcpc35.js +109 -0
  93. package/src/ui/dist/assets/CodeEditorPlugin-DbOfSJ8K.js +2 -0
  94. package/src/ui/dist/assets/CodeViewerPlugin-CbaFRrUU.js +270 -0
  95. package/src/ui/dist/assets/DocViewerPlugin-DAjLVeQD.js +7 -0
  96. package/src/ui/dist/assets/GitCommitViewerPlugin-CIUqbUDO.js +1 -0
  97. package/src/ui/dist/assets/GitDiffViewerPlugin-CQACjoAA.js +6 -0
  98. package/src/ui/dist/assets/GitSnapshotViewer-0r4nLPke.js +30 -0
  99. package/src/ui/dist/assets/ImageViewerPlugin-nBOmI2v_.js +26 -0
  100. package/src/ui/dist/assets/LabCopilotPanel-BHxOxF4z.js +14 -0
  101. package/src/ui/dist/assets/LabPlugin-BKoZGs95.js +22 -0
  102. package/src/ui/dist/assets/LatexPlugin-ZwtV8pIp.js +25 -0
  103. package/src/ui/dist/assets/MarkdownViewerPlugin-DKqVfKyW.js +128 -0
  104. package/src/ui/dist/assets/MarketplacePlugin-BwxStZ9D.js +13 -0
  105. package/src/ui/dist/assets/NotebookEditor-BEQhaQbt.js +81 -0
  106. package/src/ui/dist/assets/{NotebookEditor-CccQYZjX.css → NotebookEditor-BHH8rdGj.css} +1 -1
  107. package/src/ui/dist/assets/NotebookEditor-BOr3x3Ej.css +1 -0
  108. package/src/ui/dist/assets/NotebookEditor-DB9N_T9q.js +361 -0
  109. package/src/ui/dist/assets/PdfLoader-Cy5jtWrr.css +1 -0
  110. package/src/ui/dist/assets/PdfLoader-eWBONbQP.js +16 -0
  111. package/src/ui/dist/assets/PdfMarkdownPlugin-D22YOZL3.js +1 -0
  112. package/src/ui/dist/assets/PdfViewerPlugin-c-RK9DLM.js +17 -0
  113. package/src/ui/dist/assets/PdfViewerPlugin-nwwE-fjJ.css +1 -0
  114. package/src/ui/dist/assets/SearchPlugin-CxF9ytAx.js +16 -0
  115. package/src/ui/dist/assets/SearchPlugin-DA4en4hK.css +1 -0
  116. package/src/ui/dist/assets/TextViewerPlugin-C5xqeeUH.js +54 -0
  117. package/src/ui/dist/assets/VNCViewer-BoLGLnHz.js +11 -0
  118. package/src/ui/dist/assets/bot-DREQOxzP.js +6 -0
  119. package/src/ui/dist/assets/browser-CTB2jwNe.js +8 -0
  120. package/src/ui/dist/assets/chevron-up-C9Qpx4DE.js +6 -0
  121. package/src/ui/dist/assets/code-WlFHE7z_.js +6 -0
  122. package/src/ui/dist/assets/file-content-BZMz3RYp.js +1 -0
  123. package/src/ui/dist/assets/file-diff-panel-CQhw0jS2.js +1 -0
  124. package/src/ui/dist/assets/file-jump-queue-DA-SdG__.js +1 -0
  125. package/src/ui/dist/assets/file-socket-CfQPKQKj.js +1 -0
  126. package/src/ui/dist/assets/git-commit-horizontal-DxZ8DCZh.js +6 -0
  127. package/src/ui/dist/assets/image-Bgl4VIyx.js +6 -0
  128. package/src/ui/dist/assets/index-BpV6lusQ.css +33 -0
  129. package/src/ui/dist/assets/index-CBNVuWcP.js +2496 -0
  130. package/src/ui/dist/assets/index-CwNu1aH4.js +11 -0
  131. package/src/ui/dist/assets/index-DrUnlf6K.js +1 -0
  132. package/src/ui/dist/assets/index-NW-h8VzN.js +1 -0
  133. package/src/ui/dist/assets/monaco-CiHMMNH_.js +1 -0
  134. package/src/ui/dist/assets/pdf-effect-queue-J8OnM0jE.js +6 -0
  135. package/src/ui/dist/assets/plugin-monaco-C8UgLomw.js +19 -0
  136. package/src/ui/dist/assets/plugin-notebook-HbW2K-1c.js +169 -0
  137. package/src/ui/dist/assets/plugin-pdf-CR8hgQBV.js +357 -0
  138. package/src/ui/dist/assets/plugin-terminal-MXFIPun8.js +227 -0
  139. package/src/ui/dist/assets/popover-CLc0pPP8.js +1 -0
  140. package/src/ui/dist/assets/project-sync-C9IdzdZW.js +1 -0
  141. package/src/ui/dist/assets/select-Cs2PmzwL.js +11 -0
  142. package/src/ui/dist/assets/sigma-ClKcHAXm.js +6 -0
  143. package/src/ui/dist/assets/trash-DwpbFr3w.js +11 -0
  144. package/src/ui/dist/assets/useCliAccess-NQ8m0Let.js +1 -0
  145. package/src/ui/dist/assets/useFileDiffOverlay-FuhcnKiw.js +1 -0
  146. package/src/ui/dist/assets/wrap-text-BC-Hltpd.js +11 -0
  147. package/src/ui/dist/assets/zoom-out-E_gaeAxL.js +11 -0
  148. package/src/ui/dist/index.html +5 -2
  149. package/src/ui/dist/assets/AiManusChatView-DDjbFnbt.js +0 -26597
  150. package/src/ui/dist/assets/AnalysisPlugin-Yb5IdmaU.js +0 -123
  151. package/src/ui/dist/assets/CliPlugin-e64sreyu.js +0 -31037
  152. package/src/ui/dist/assets/CodeEditorPlugin-C4D2TIkU.js +0 -427
  153. package/src/ui/dist/assets/CodeViewerPlugin-BVoNZIvC.js +0 -905
  154. package/src/ui/dist/assets/DocViewerPlugin-CLChbllo.js +0 -278
  155. package/src/ui/dist/assets/GitDiffViewerPlugin-C4xeFyFQ.js +0 -2661
  156. package/src/ui/dist/assets/ImageViewerPlugin-OiMUAcLi.js +0 -500
  157. package/src/ui/dist/assets/LabCopilotPanel-BjD2ThQF.js +0 -4104
  158. package/src/ui/dist/assets/LabPlugin-DQPg-NrB.js +0 -2677
  159. package/src/ui/dist/assets/LatexPlugin-CI05XAV9.js +0 -1792
  160. package/src/ui/dist/assets/MarkdownViewerPlugin-DpeBLYZf.js +0 -308
  161. package/src/ui/dist/assets/MarketplacePlugin-DolE58Q2.js +0 -413
  162. package/src/ui/dist/assets/NotebookEditor-7Qm2rSWD.js +0 -4214
  163. package/src/ui/dist/assets/NotebookEditor-C1kWaxKi.js +0 -84873
  164. package/src/ui/dist/assets/NotebookEditor-C3VQ7ylN.css +0 -1405
  165. package/src/ui/dist/assets/PdfLoader-BfOHw8Zw.js +0 -25468
  166. package/src/ui/dist/assets/PdfLoader-C-Y707R3.css +0 -49
  167. package/src/ui/dist/assets/PdfMarkdownPlugin-BulDREv1.js +0 -409
  168. package/src/ui/dist/assets/PdfViewerPlugin-C-daaOaL.js +0 -3095
  169. package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +0 -3627
  170. package/src/ui/dist/assets/SearchPlugin-CjpaiJ3A.js +0 -741
  171. package/src/ui/dist/assets/SearchPlugin-DDMrGDkh.css +0 -379
  172. package/src/ui/dist/assets/TextViewerPlugin-BxIyqPQC.js +0 -472
  173. package/src/ui/dist/assets/VNCViewer-HAg9mF7M.js +0 -18821
  174. package/src/ui/dist/assets/awareness-C0NPR2Dj.js +0 -292
  175. package/src/ui/dist/assets/bot-0DYntytV.js +0 -21
  176. package/src/ui/dist/assets/browser-BAcuE0Xj.js +0 -2895
  177. package/src/ui/dist/assets/code-B20Slj_w.js +0 -17
  178. package/src/ui/dist/assets/file-content-DT24KFma.js +0 -377
  179. package/src/ui/dist/assets/file-diff-panel-DK13YPql.js +0 -92
  180. package/src/ui/dist/assets/file-jump-queue-r5XKgJEV.js +0 -16
  181. package/src/ui/dist/assets/file-socket-B4T2o4nR.js +0 -58
  182. package/src/ui/dist/assets/function-B5QZkkHC.js +0 -1895
  183. package/src/ui/dist/assets/image-DSeR_sDS.js +0 -18
  184. package/src/ui/dist/assets/index-BrFje2Uk.js +0 -120
  185. package/src/ui/dist/assets/index-BwRJaoTl.js +0 -25
  186. package/src/ui/dist/assets/index-D_E4281X.js +0 -221322
  187. package/src/ui/dist/assets/index-DnYB3xb1.js +0 -159
  188. package/src/ui/dist/assets/index-G7AcWcMu.css +0 -12594
  189. package/src/ui/dist/assets/monaco-LExaAN3Y.js +0 -623
  190. package/src/ui/dist/assets/pdf-effect-queue-BJk5okWJ.js +0 -47
  191. package/src/ui/dist/assets/pdf_viewer-e0g1is2C.js +0 -8206
  192. package/src/ui/dist/assets/popover-D3Gg_FoV.js +0 -476
  193. package/src/ui/dist/assets/project-sync-C_ygLlVU.js +0 -297
  194. package/src/ui/dist/assets/select-CpAK6uWm.js +0 -1690
  195. package/src/ui/dist/assets/sigma-DEccaSgk.js +0 -22
  196. package/src/ui/dist/assets/square-check-big-uUfyVsbD.js +0 -17
  197. package/src/ui/dist/assets/trash-CXvwwSe8.js +0 -32
  198. package/src/ui/dist/assets/useCliAccess-Bnop4mgR.js +0 -957
  199. package/src/ui/dist/assets/useFileDiffOverlay-B8eUAX0I.js +0 -53
  200. package/src/ui/dist/assets/wrap-text-9vbOBpkW.js +0 -35
  201. package/src/ui/dist/assets/yjs-DncrqiZ8.js +0 -11243
  202. package/src/ui/dist/assets/zoom-out-BgVMmOW4.js +0 -34
@@ -1,3095 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/index-D_E4281X.js","assets/index-G7AcWcMu.css"])))=>i.map(i=>d[i]);
2
- import { w as createLucideIcon, r as reactExports, u as useI18n, j as jsxRuntimeExports, a3 as Download, I as InfoTriangleIcon, b as cn, k as FileText, l as Search, X, m as ChevronDown, Q as create, t as apiClient, aT as useQueryClient, aN as useQuery, bR as useMutation, bS as Card, aO as Button, bT as DropdownMenu, bU as DropdownMenuTrigger, bV as DropdownMenuContent, bW as DropdownMenuLabel, bX as DropdownMenuItem, bY as SegmentedControl, br as CircleHelp, bZ as Textarea, i as isCliFileId, b_ as isQuestNodeId, b$ as getQuestNodeProjectId, n as useTabsStore, f as useFileTreeStore, a as useWorkspaceSurfaceStore, o as useToast, p as useArxivStore, z as useAuthStore, s as generateBibTeX, c as copyToClipboard, W as toFilesResourcePath, v as BUILTIN_PLUGINS, c0 as getFile, c1 as listProjectMembers, _ as __vitePreload, A as ArxivInfoModal, c2 as createFileObjectUrl, N as Sparkles, T as TriangleAlert, c3 as DropdownMenuRadioGroup, c4 as DropdownMenuRadioItem, c5 as MarkdownPreview } from './index-D_E4281X.js';
3
- import { a as acquireFileSocket } from './file-socket-B4T2o4nR.js';
4
- import { M as MessageSquare, i as isPdfEffectHandled, m as markPdfEffectHandled, c as consumePdfEffects } from './pdf-effect-queue-BJk5okWJ.js';
5
- import { Z as ZOOM_LEVELS, R as Rnd, g as getPageFromElement, a as PdfLoader, e as PDF_CMAP_PACKED, b as PDF_CMAP_URL, c as PDF_WORKER_SRC, d as PdfHighlighter } from './PdfLoader-BfOHw8Zw.js';
6
- import { Z as ZoomOut, a as ZoomIn } from './zoom-out-BgVMmOW4.js';
7
- import { C as ChevronUp } from './index-BwRJaoTl.js';
8
- import { S as SquareCheckBig } from './square-check-big-uUfyVsbD.js';
9
-
10
- /**
11
- * @license lucide-react v0.511.0 - ISC
12
- *
13
- * This source code is licensed under the ISC license.
14
- * See the LICENSE file in the root directory of this source tree.
15
- */
16
-
17
-
18
- const __iconNode$1 = [
19
- ["rect", { width: "18", height: "18", x: "3", y: "3", rx: "2", key: "afitv7" }],
20
- ["path", { d: "M15 3v18", key: "14nvp0" }]
21
- ];
22
- const PanelRight = createLucideIcon("panel-right", __iconNode$1);
23
-
24
- /**
25
- * @license lucide-react v0.511.0 - ISC
26
- *
27
- * This source code is licensed under the ISC license.
28
- * See the LICENSE file in the root directory of this source tree.
29
- */
30
-
31
-
32
- const __iconNode = [
33
- ["path", { d: "M16 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V8Z", key: "qazsjp" }],
34
- ["path", { d: "M15 3v4a2 2 0 0 0 2 2h4", key: "40519r" }]
35
- ];
36
- const StickyNote = createLucideIcon("sticky-note", __iconNode);
37
-
38
- const ZoomControls = reactExports.memo(function ZoomControls2({
39
- scale,
40
- onScaleChange
41
- }) {
42
- const { t } = useI18n("pdf_viewer");
43
- const handleZoomOut = () => {
44
- const currentIndex = ZOOM_LEVELS.findIndex((z) => z >= scale);
45
- if (currentIndex > 0) {
46
- onScaleChange(ZOOM_LEVELS[currentIndex - 1]);
47
- }
48
- };
49
- const handleZoomIn = () => {
50
- const currentIndex = ZOOM_LEVELS.findIndex((z) => z >= scale);
51
- if (currentIndex < ZOOM_LEVELS.length - 1) {
52
- onScaleChange(ZOOM_LEVELS[currentIndex + 1]);
53
- }
54
- };
55
- const handleSelectChange = (e) => {
56
- onScaleChange(parseFloat(e.target.value));
57
- };
58
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
59
- /* @__PURE__ */ jsxRuntimeExports.jsx(
60
- "button",
61
- {
62
- onClick: handleZoomOut,
63
- disabled: scale <= ZOOM_LEVELS[0],
64
- className: cn(
65
- "p-1.5 rounded hover:bg-muted transition-colors",
66
- "disabled:opacity-50 disabled:cursor-not-allowed"
67
- ),
68
- title: t("zoom_out"),
69
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomOut, { className: "w-4 h-4" })
70
- }
71
- ),
72
- /* @__PURE__ */ jsxRuntimeExports.jsx(
73
- "select",
74
- {
75
- value: scale,
76
- onChange: handleSelectChange,
77
- className: cn(
78
- "w-20 px-2 py-1 text-sm rounded",
79
- "bg-muted/50 border border-border",
80
- "focus:outline-none focus:ring-2 focus:ring-primary/50"
81
- ),
82
- children: ZOOM_LEVELS.map((z) => /* @__PURE__ */ jsxRuntimeExports.jsxs("option", { value: z, children: [
83
- Math.round(z * 100),
84
- "%"
85
- ] }, z))
86
- }
87
- ),
88
- /* @__PURE__ */ jsxRuntimeExports.jsx(
89
- "button",
90
- {
91
- onClick: handleZoomIn,
92
- disabled: scale >= ZOOM_LEVELS[ZOOM_LEVELS.length - 1],
93
- className: cn(
94
- "p-1.5 rounded hover:bg-muted transition-colors",
95
- "disabled:opacity-50 disabled:cursor-not-allowed"
96
- ),
97
- title: t("zoom_in"),
98
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIn, { className: "w-4 h-4" })
99
- }
100
- )
101
- ] });
102
- });
103
- const PageNavigation = reactExports.memo(function PageNavigation2({
104
- currentPage,
105
- totalPages,
106
- onPageChange
107
- }) {
108
- const { t } = useI18n("pdf_viewer");
109
- const [inputValue, setInputValue] = reactExports.useState(String(currentPage));
110
- const inputRef = reactExports.useRef(null);
111
- reactExports.useEffect(() => {
112
- setInputValue(String(currentPage));
113
- }, [currentPage]);
114
- const handlePrevPage = () => {
115
- if (currentPage > 1) {
116
- onPageChange(currentPage - 1);
117
- }
118
- };
119
- const handleNextPage = () => {
120
- if (currentPage < totalPages) {
121
- onPageChange(currentPage + 1);
122
- }
123
- };
124
- const handleInputChange = (e) => {
125
- setInputValue(e.target.value);
126
- };
127
- const handleInputBlur = () => {
128
- const page = parseInt(inputValue, 10);
129
- if (!isNaN(page) && page >= 1 && page <= totalPages) {
130
- onPageChange(page);
131
- } else {
132
- setInputValue(String(currentPage));
133
- }
134
- };
135
- const handleInputKeyDown = (e) => {
136
- if (e.key === "Enter") {
137
- handleInputBlur();
138
- inputRef.current?.blur();
139
- } else if (e.key === "Escape") {
140
- setInputValue(String(currentPage));
141
- inputRef.current?.blur();
142
- }
143
- };
144
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
145
- /* @__PURE__ */ jsxRuntimeExports.jsx(
146
- "button",
147
- {
148
- onClick: handlePrevPage,
149
- disabled: currentPage <= 1,
150
- className: cn(
151
- "p-1.5 rounded hover:bg-muted transition-colors",
152
- "disabled:opacity-50 disabled:cursor-not-allowed"
153
- ),
154
- title: t("previous_page"),
155
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronUp, { className: "w-4 h-4" })
156
- }
157
- ),
158
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1 text-sm whitespace-nowrap", children: [
159
- /* @__PURE__ */ jsxRuntimeExports.jsx(
160
- "input",
161
- {
162
- ref: inputRef,
163
- type: "text",
164
- value: inputValue,
165
- onChange: handleInputChange,
166
- onBlur: handleInputBlur,
167
- onKeyDown: handleInputKeyDown,
168
- className: cn(
169
- "w-12 px-2 py-1 text-center rounded",
170
- "bg-muted/50 border border-border",
171
- "focus:outline-none focus:ring-2 focus:ring-primary/50"
172
- )
173
- }
174
- ),
175
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "shrink-0 whitespace-nowrap text-muted-foreground", children: [
176
- "/ ",
177
- totalPages
178
- ] })
179
- ] }),
180
- /* @__PURE__ */ jsxRuntimeExports.jsx(
181
- "button",
182
- {
183
- onClick: handleNextPage,
184
- disabled: currentPage >= totalPages,
185
- className: cn(
186
- "p-1.5 rounded hover:bg-muted transition-colors",
187
- "disabled:opacity-50 disabled:cursor-not-allowed"
188
- ),
189
- title: t("next_page"),
190
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(ChevronDown, { className: "w-4 h-4" })
191
- }
192
- )
193
- ] });
194
- });
195
- const SearchBox = reactExports.memo(function SearchBox2({
196
- value,
197
- onChange,
198
- placeholder
199
- }) {
200
- const { t } = useI18n("pdf_viewer");
201
- const inputRef = reactExports.useRef(null);
202
- const handleClear = () => {
203
- onChange("");
204
- inputRef.current?.focus();
205
- };
206
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "relative flex items-center", children: [
207
- /* @__PURE__ */ jsxRuntimeExports.jsx(Search, { className: "absolute left-2 w-4 h-4 text-muted-foreground pointer-events-none" }),
208
- /* @__PURE__ */ jsxRuntimeExports.jsx(
209
- "input",
210
- {
211
- ref: inputRef,
212
- type: "text",
213
- value,
214
- onChange: (e) => onChange(e.target.value),
215
- placeholder: placeholder || t("search_placeholder"),
216
- className: cn(
217
- "w-48 pl-8 pr-8 py-1.5 text-sm rounded",
218
- "bg-muted/50 border border-border",
219
- "placeholder:text-muted-foreground/60",
220
- "focus:outline-none focus:ring-2 focus:ring-primary/50"
221
- )
222
- }
223
- ),
224
- value && /* @__PURE__ */ jsxRuntimeExports.jsx(
225
- "button",
226
- {
227
- onClick: handleClear,
228
- className: "absolute right-2 p-0.5 rounded hover:bg-muted/80 transition-colors",
229
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "w-3 h-3 text-muted-foreground" })
230
- }
231
- )
232
- ] });
233
- });
234
- function Separator() {
235
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "w-px h-6 bg-border mx-2" });
236
- }
237
- const Toolbar = reactExports.memo(function Toolbar2({
238
- scale,
239
- onScaleChange,
240
- currentPage,
241
- totalPages,
242
- onPageChange,
243
- sidebarVisible,
244
- onSidebarToggle,
245
- searchQuery = "",
246
- onSearchChange,
247
- onDownload,
248
- onInfo,
249
- infoDisabled = false,
250
- markdownActive,
251
- onMarkdownToggle,
252
- markdownLabel,
253
- markdownTitle,
254
- reviewOpinionActive,
255
- onReviewOpinionToggle,
256
- reviewOpinionLabel,
257
- reviewOpinionTitle
258
- }) {
259
- const { t } = useI18n("pdf_viewer");
260
- const isMarkdownActive = Boolean(markdownActive);
261
- const canToggleMarkdown = typeof onMarkdownToggle === "function";
262
- const markdownButtonLabel = markdownLabel?.trim() || t("markdown");
263
- const markdownButtonTitle = markdownTitle?.trim() || t("open_markdown");
264
- const isReviewOpinionActive = Boolean(reviewOpinionActive);
265
- const canToggleReviewOpinion = typeof onReviewOpinionToggle === "function";
266
- const reviewOpinionButtonLabel = reviewOpinionLabel?.trim() || t("review_opinion");
267
- const reviewOpinionButtonTitle = reviewOpinionTitle?.trim() || t("open_review_opinion");
268
- const canShowInfo = typeof onInfo === "function";
269
- const hasLeftActions = Boolean(onSearchChange || onDownload || canShowInfo);
270
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
271
- "div",
272
- {
273
- className: cn(
274
- "flex flex-wrap items-center gap-3 px-4 py-2",
275
- "bg-muted/30 border-b border-border"
276
- ),
277
- children: [
278
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex min-w-0 flex-1 flex-wrap items-center gap-2", children: [
279
- onSearchChange ? /* @__PURE__ */ jsxRuntimeExports.jsx(
280
- SearchBox,
281
- {
282
- value: searchQuery,
283
- onChange: onSearchChange,
284
- placeholder: t("search_pdf_placeholder")
285
- }
286
- ) : null,
287
- onDownload ? /* @__PURE__ */ jsxRuntimeExports.jsx(
288
- "button",
289
- {
290
- onClick: onDownload,
291
- className: "p-1.5 rounded hover:bg-muted transition-colors",
292
- title: t("download_pdf"),
293
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "w-4 h-4" })
294
- }
295
- ) : null,
296
- canShowInfo ? /* @__PURE__ */ jsxRuntimeExports.jsx(
297
- "button",
298
- {
299
- onClick: onInfo,
300
- disabled: infoDisabled,
301
- className: cn(
302
- "p-1.5 rounded transition-colors",
303
- "hover:bg-muted",
304
- infoDisabled && "opacity-50 cursor-not-allowed"
305
- ),
306
- title: t("paper_info"),
307
- "aria-label": t("paper_info"),
308
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(InfoTriangleIcon, { className: "h-4 w-4" })
309
- }
310
- ) : null,
311
- hasLeftActions ? /* @__PURE__ */ jsxRuntimeExports.jsx(Separator, {}) : null,
312
- /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomControls, { scale, onScaleChange }),
313
- /* @__PURE__ */ jsxRuntimeExports.jsx(Separator, {}),
314
- /* @__PURE__ */ jsxRuntimeExports.jsx(
315
- PageNavigation,
316
- {
317
- currentPage,
318
- totalPages,
319
- onPageChange
320
- }
321
- )
322
- ] }),
323
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ml-auto flex items-center justify-end gap-2", children: [
324
- canToggleMarkdown ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
325
- "button",
326
- {
327
- type: "button",
328
- onClick: onMarkdownToggle,
329
- className: cn(
330
- "flex items-center gap-2 px-3 py-1.5 rounded-full border transition-colors text-sm",
331
- "bg-background hover:bg-muted/60 border-border",
332
- isMarkdownActive && "bg-primary/10 text-primary border-primary/20"
333
- ),
334
- title: markdownButtonTitle,
335
- children: [
336
- /* @__PURE__ */ jsxRuntimeExports.jsx(FileText, { className: "w-4 h-4" }),
337
- markdownButtonLabel
338
- ]
339
- }
340
- ) : null,
341
- canToggleReviewOpinion ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
342
- "button",
343
- {
344
- type: "button",
345
- onClick: onReviewOpinionToggle,
346
- className: cn(
347
- "flex items-center gap-2 px-3 py-1.5 rounded-full border transition-colors text-sm",
348
- "bg-background hover:bg-muted/60 border-border",
349
- isReviewOpinionActive && "bg-primary/10 text-primary border-primary/20"
350
- ),
351
- title: reviewOpinionButtonTitle,
352
- children: [
353
- /* @__PURE__ */ jsxRuntimeExports.jsx(MessageSquare, { className: "w-4 h-4" }),
354
- reviewOpinionButtonLabel
355
- ]
356
- }
357
- ) : null,
358
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
359
- "button",
360
- {
361
- type: "button",
362
- onClick: onSidebarToggle,
363
- className: cn(
364
- "flex items-center gap-2 px-3 py-1.5 rounded-full border transition-colors text-sm",
365
- "bg-background hover:bg-muted/60 border-border",
366
- sidebarVisible && "bg-primary/10 text-primary border-primary/20"
367
- ),
368
- title: sidebarVisible ? t("hide_annotations") : t("show_annotations"),
369
- children: [
370
- /* @__PURE__ */ jsxRuntimeExports.jsx(PanelRight, { className: "w-4 h-4" }),
371
- sidebarVisible ? t("hide") : t("show")
372
- ]
373
- }
374
- )
375
- ] })
376
- ]
377
- }
378
- );
379
- });
380
-
381
- const initialState = {
382
- scale: 1,
383
- fitMode: "custom",
384
- currentPage: 1,
385
- totalPages: 0,
386
- scrollTop: 0,
387
- searchQuery: "",
388
- searchResults: [],
389
- currentSearchIndex: -1,
390
- // Default: keep annotations sidebar hidden until the user creates one.
391
- sidebarVisible: false,
392
- selectedAnnotationId: null
393
- };
394
- const useViewerState = create((set, get) => ({
395
- // Initial state
396
- ...initialState,
397
- // ============================================================
398
- // Scale Actions
399
- // ============================================================
400
- setScale: (scale) => {
401
- const clampedScale = Math.max(
402
- ZOOM_LEVELS[0],
403
- Math.min(ZOOM_LEVELS[ZOOM_LEVELS.length - 1], scale)
404
- );
405
- set({ scale: clampedScale, fitMode: "custom" });
406
- },
407
- setFitMode: (mode) => {
408
- set({ fitMode: mode });
409
- },
410
- // ============================================================
411
- // Page Navigation Actions
412
- // ============================================================
413
- setCurrentPage: (page) => {
414
- const { totalPages } = get();
415
- const clampedPage = Math.max(1, Math.min(page, totalPages || 1));
416
- set({ currentPage: clampedPage });
417
- },
418
- setTotalPages: (total) => {
419
- set({ totalPages: total });
420
- },
421
- setScrollTop: (scrollTop) => {
422
- set({ scrollTop });
423
- },
424
- scrollToPage: (page) => {
425
- const { totalPages, setCurrentPage } = get();
426
- const clampedPage = Math.max(1, Math.min(page, totalPages || 1));
427
- setCurrentPage(clampedPage);
428
- },
429
- // ============================================================
430
- // Search Actions
431
- // ============================================================
432
- setSearchQuery: (query) => {
433
- set({ searchQuery: query, currentSearchIndex: -1 });
434
- },
435
- setSearchResults: (results) => {
436
- set({
437
- searchResults: results,
438
- currentSearchIndex: results.length > 0 ? 0 : -1
439
- });
440
- },
441
- nextSearchResult: () => {
442
- const { searchResults, currentSearchIndex } = get();
443
- if (searchResults.length === 0) return;
444
- const nextIndex = (currentSearchIndex + 1) % searchResults.length;
445
- set({ currentSearchIndex: nextIndex });
446
- },
447
- prevSearchResult: () => {
448
- const { searchResults, currentSearchIndex } = get();
449
- if (searchResults.length === 0) return;
450
- const prevIndex = currentSearchIndex <= 0 ? searchResults.length - 1 : currentSearchIndex - 1;
451
- set({ currentSearchIndex: prevIndex });
452
- },
453
- // ============================================================
454
- // Sidebar Actions
455
- // ============================================================
456
- toggleSidebar: () => {
457
- set((state) => ({ sidebarVisible: !state.sidebarVisible }));
458
- },
459
- // ============================================================
460
- // Annotation Actions
461
- // ============================================================
462
- selectAnnotation: (id) => {
463
- set({ selectedAnnotationId: id });
464
- },
465
- scrollToAnnotation: (id) => {
466
- set({ selectedAnnotationId: id });
467
- },
468
- // ============================================================
469
- // Reset
470
- // ============================================================
471
- reset: () => {
472
- set(initialState);
473
- }
474
- }));
475
-
476
- function toAnnotation(raw) {
477
- const position = raw.position ? {
478
- ...raw.position,
479
- boundingRect: {
480
- ...raw.position.boundingRect,
481
- width: raw.position.boundingRect?.width ?? 100,
482
- height: raw.position.boundingRect?.height ?? 100
483
- },
484
- rects: Array.isArray(raw.position.rects) ? raw.position.rects.map((r) => ({
485
- ...r,
486
- width: r?.width ?? 100,
487
- height: r?.height ?? 100
488
- })) : []
489
- } : void 0;
490
- const content = raw.content;
491
- return {
492
- id: String(raw.id),
493
- position,
494
- content,
495
- comment: String(raw.comment ?? ""),
496
- kind: raw.kind === "question" || raw.kind === "task" ? raw.kind : "note",
497
- color: String(raw.color ?? "#F1E9D0"),
498
- tags: Array.isArray(raw.tags) ? raw.tags.map(String) : [],
499
- createdBy: String(raw.created_by ?? raw.createdBy ?? ""),
500
- author: raw.author ? {
501
- id: String(raw.author.id),
502
- handle: String(raw.author.handle ?? "user"),
503
- color: String(raw.author.color ?? "#F1E9D0")
504
- } : null,
505
- createdAt: String(raw.created_at ?? raw.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()),
506
- updatedAt: String(raw.updated_at ?? raw.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()),
507
- fileId: String(raw.file_id ?? raw.fileId ?? ""),
508
- projectId: String(raw.project_id ?? raw.projectId ?? "")
509
- };
510
- }
511
- function toCreatePayload(data) {
512
- return {
513
- file_id: data.fileId,
514
- position: data.position,
515
- content: data.content,
516
- comment: data.comment ?? "",
517
- kind: data.kind ?? "note",
518
- // backwards-compatible; server enforces author color
519
- color: data.color,
520
- tags: data.tags ?? []
521
- };
522
- }
523
- function toUpdatePayload(data) {
524
- return {
525
- comment: data.comment,
526
- kind: data.kind,
527
- position: data.position,
528
- content: data.content,
529
- // ignored by server
530
- color: data.color,
531
- tags: data.tags
532
- };
533
- }
534
- const ENDPOINTS = {
535
- list: (fileId) => `/api/v1/annotations/file/${fileId}`,
536
- create: () => `/api/v1/annotations/`,
537
- get: (id) => `/api/v1/annotations/${id}`,
538
- update: (id) => `/api/v1/annotations/${id}`,
539
- delete: (id) => `/api/v1/annotations/${id}`,
540
- search: (projectId) => `/api/v1/annotations/project/${projectId}`
541
- };
542
- const annotationsApi = {
543
- /**
544
- * List annotations for a file
545
- *
546
- * @param fileId - File ID
547
- * @returns List of annotations
548
- */
549
- async listAnnotations(fileId) {
550
- const response = await apiClient.get(
551
- ENDPOINTS.list(fileId)
552
- );
553
- if (Array.isArray(response.data)) {
554
- return response.data.map(toAnnotation);
555
- }
556
- return (response.data.items || []).map(toAnnotation);
557
- },
558
- /**
559
- * Create a new annotation
560
- *
561
- * @param data - Annotation data
562
- * @returns Created annotation
563
- */
564
- async createAnnotation(data) {
565
- const response = await apiClient.post(
566
- ENDPOINTS.create(),
567
- toCreatePayload(data)
568
- );
569
- return toAnnotation(response.data);
570
- },
571
- /**
572
- * Get a single annotation by ID
573
- *
574
- * @param id - Annotation ID
575
- * @returns Annotation data
576
- */
577
- async getAnnotation(id) {
578
- const response = await apiClient.get(ENDPOINTS.get(id));
579
- return toAnnotation(response.data);
580
- },
581
- /**
582
- * Update an annotation
583
- *
584
- * @param id - Annotation ID
585
- * @param data - Update data
586
- * @returns Updated annotation
587
- */
588
- async updateAnnotation(id, data) {
589
- const response = await apiClient.patch(
590
- ENDPOINTS.update(id),
591
- toUpdatePayload(data)
592
- );
593
- return toAnnotation(response.data);
594
- },
595
- /**
596
- * Delete an annotation
597
- *
598
- * @param id - Annotation ID
599
- */
600
- async deleteAnnotation(id) {
601
- await apiClient.delete(ENDPOINTS.delete(id));
602
- },
603
- /**
604
- * Search annotations in a project
605
- *
606
- * @param projectId - Project ID
607
- * @param query - Search query
608
- * @param options - Search options
609
- * @returns Matching annotations
610
- */
611
- async searchAnnotations(projectId, query, options) {
612
- const params = new URLSearchParams();
613
- if (query) params.set("q", query);
614
- if (options?.color) params.set("color", options.color);
615
- if (options?.tag) params.set("tag", options.tag);
616
- if (options?.page) params.set("page", String(options.page));
617
- if (options?.limit) params.set("limit", String(options.limit));
618
- const url = `${ENDPOINTS.search(projectId)}${params.toString() ? `?${params.toString()}` : ""}`;
619
- const response = await apiClient.get(url);
620
- if (Array.isArray(response.data)) {
621
- return response.data.map(toAnnotation);
622
- }
623
- return (response.data.items || []).map(toAnnotation);
624
- }
625
- };
626
-
627
- const annotationKeys = {
628
- all: ["annotations"],
629
- lists: () => [...annotationKeys.all, "list"],
630
- list: (fileId) => [...annotationKeys.lists(), fileId],
631
- details: () => [...annotationKeys.all, "detail"],
632
- detail: (id) => [...annotationKeys.details(), id]
633
- };
634
- function useAnnotations(fileId) {
635
- const queryClient = useQueryClient();
636
- const queryKey = annotationKeys.list(fileId);
637
- const {
638
- data: annotations = [],
639
- isLoading,
640
- error,
641
- refetch,
642
- isFetching
643
- } = useQuery({
644
- queryKey,
645
- queryFn: () => annotationsApi.listAnnotations(fileId),
646
- staleTime: 30 * 1e3,
647
- // 30 seconds cache
648
- enabled: !!fileId
649
- });
650
- const createMutation = useMutation({
651
- mutationFn: (data) => annotationsApi.createAnnotation(data),
652
- onSuccess: (created) => {
653
- queryClient.setQueryData(queryKey, (old = []) => [
654
- created,
655
- ...old.filter((a) => a.id !== created.id)
656
- ]);
657
- },
658
- onSettled: () => queryClient.invalidateQueries({ queryKey })
659
- });
660
- const updateMutation = useMutation({
661
- mutationFn: ({ id, updates }) => annotationsApi.updateAnnotation(id, updates),
662
- // Optimistic update
663
- onMutate: async ({ id, updates }) => {
664
- await queryClient.cancelQueries({ queryKey });
665
- const previousAnnotations = queryClient.getQueryData(queryKey);
666
- queryClient.setQueryData(
667
- queryKey,
668
- (old = []) => old.map(
669
- (annotation) => annotation.id === id ? {
670
- ...annotation,
671
- ...updates,
672
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
673
- } : annotation
674
- )
675
- );
676
- return { previousAnnotations };
677
- },
678
- onError: (_err, _variables, context) => {
679
- if (context?.previousAnnotations) {
680
- queryClient.setQueryData(queryKey, context.previousAnnotations);
681
- }
682
- },
683
- onSuccess: (updated) => {
684
- queryClient.setQueryData(
685
- queryKey,
686
- (old = []) => old.map((a) => a.id === updated.id ? updated : a)
687
- );
688
- },
689
- onSettled: () => {
690
- queryClient.invalidateQueries({ queryKey });
691
- }
692
- });
693
- const deleteMutation = useMutation({
694
- mutationFn: (id) => annotationsApi.deleteAnnotation(id),
695
- // Optimistic update
696
- onMutate: async (id) => {
697
- await queryClient.cancelQueries({ queryKey });
698
- const previousAnnotations = queryClient.getQueryData(queryKey);
699
- queryClient.setQueryData(
700
- queryKey,
701
- (old = []) => old.filter((annotation) => annotation.id !== id)
702
- );
703
- return { previousAnnotations };
704
- },
705
- onError: (_err, _id, context) => {
706
- if (context?.previousAnnotations) {
707
- queryClient.setQueryData(queryKey, context.previousAnnotations);
708
- }
709
- },
710
- onSettled: () => {
711
- queryClient.invalidateQueries({ queryKey });
712
- }
713
- });
714
- const createAnnotation = async (data) => {
715
- return createMutation.mutateAsync(data);
716
- };
717
- const updateAnnotation = async (id, updates) => {
718
- return updateMutation.mutateAsync({ id, updates });
719
- };
720
- const deleteAnnotation = async (id) => {
721
- return deleteMutation.mutateAsync(id);
722
- };
723
- const refreshAnnotations = () => {
724
- return refetch();
725
- };
726
- const getAnnotationById = (id) => {
727
- return annotations.find((a) => a.id === id);
728
- };
729
- const getAnnotationsByPage = (pageNumber) => {
730
- return annotations.filter((a) => a.position.pageNumber === pageNumber);
731
- };
732
- const getAnnotationsByTag = (tag) => {
733
- return annotations.filter((a) => a.tags.includes(tag));
734
- };
735
- const getAnnotationsByColor = (color) => {
736
- return annotations.filter((a) => a.color === color);
737
- };
738
- const getAllTags = () => {
739
- const tags = /* @__PURE__ */ new Set();
740
- annotations.forEach((a) => a.tags.forEach((t) => tags.add(t)));
741
- return Array.from(tags).sort();
742
- };
743
- return {
744
- // Data
745
- annotations,
746
- isLoading,
747
- isFetching,
748
- error,
749
- // CRUD operations
750
- createAnnotation,
751
- updateAnnotation,
752
- deleteAnnotation,
753
- // Refresh
754
- refreshAnnotations,
755
- // Query helpers
756
- getAnnotationById,
757
- getAnnotationsByPage,
758
- getAnnotationsByTag,
759
- getAnnotationsByColor,
760
- getAllTags,
761
- // Mutation states
762
- isCreating: createMutation.isPending,
763
- isUpdating: updateMutation.isPending,
764
- isDeleting: deleteMutation.isPending,
765
- createError: createMutation.error,
766
- updateError: updateMutation.error,
767
- deleteError: deleteMutation.error
768
- };
769
- }
770
-
771
- const tip = "_tip_14ms1_1";
772
- const tipNote = "_tipNote_14ms1_38";
773
- const tipQuestion = "_tipQuestion_14ms1_42";
774
- const tipTask = "_tipTask_14ms1_46";
775
- const styles$2 = {
776
- tip: tip,
777
- tipNote: tipNote,
778
- tipQuestion: tipQuestion,
779
- tipTask: tipTask};
780
-
781
- function Tip({
782
- onConfirm,
783
- onCancel,
784
- onOpen,
785
- onUpdate,
786
- onAskCopilot,
787
- authorColor,
788
- authorHandle,
789
- showAuthorHandle,
790
- onToggleAuthorHandle
791
- }) {
792
- const { t } = useI18n("pdf_viewer");
793
- const [kind, setKind] = reactExports.useState("note");
794
- const [text, setText] = reactExports.useState("");
795
- reactExports.useEffect(() => {
796
- onOpen();
797
- onUpdate?.();
798
- }, []);
799
- reactExports.useEffect(() => {
800
- onUpdate?.();
801
- }, [kind, onUpdate]);
802
- const placeholder = reactExports.useMemo(() => {
803
- switch (kind) {
804
- case "question":
805
- return t("type_question");
806
- case "task":
807
- return t("type_task");
808
- default:
809
- return t("add_note_optional");
810
- }
811
- }, [kind, t]);
812
- const handleSave = () => {
813
- onConfirm({ text: text.trim(), emoji: kind });
814
- };
815
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
816
- Card,
817
- {
818
- className: cn(
819
- "w-80 border border-border bg-popover text-popover-foreground shadow-soft-lg",
820
- "backdrop-blur supports-[backdrop-filter]:bg-popover/95",
821
- styles$2.tip,
822
- kind === "note" && styles$2.tipNote,
823
- kind === "question" && styles$2.tipQuestion,
824
- kind === "task" && styles$2.tipTask
825
- ),
826
- children: [
827
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between px-3 py-2 border-b", children: [
828
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-2 text-sm font-medium", children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground", children: t("add_annotation") }) }),
829
- /* @__PURE__ */ jsxRuntimeExports.jsx(
830
- Button,
831
- {
832
- type: "button",
833
- variant: "ghost",
834
- size: "icon",
835
- className: "h-7 w-7",
836
- onClick: onCancel,
837
- "aria-label": t("close"),
838
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(X, { className: "h-4 w-4" })
839
- }
840
- )
841
- ] }),
842
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "px-3 py-3 space-y-3", children: [
843
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenu, { children: [
844
- /* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
845
- "button",
846
- {
847
- type: "button",
848
- className: "flex items-center gap-2 rounded-md px-2 py-1 text-xs text-muted-foreground hover:text-foreground hover:bg-muted/40 transition-colors",
849
- title: authorHandle || t("author"),
850
- children: [
851
- /* @__PURE__ */ jsxRuntimeExports.jsx(
852
- "span",
853
- {
854
- className: "h-2 w-2 rounded-full border border-border",
855
- style: { backgroundColor: authorColor || "#F1E9D0" }
856
- }
857
- ),
858
- showAuthorHandle ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px]", children: authorHandle || "user" }) : /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px]", children: t("author") })
859
- ]
860
- }
861
- ) }),
862
- /* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenuContent, { align: "start", children: [
863
- /* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuLabel, { children: t("author") }),
864
- /* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuItem, { disabled: true, children: authorHandle || "user" }),
865
- onToggleAuthorHandle ? /* @__PURE__ */ jsxRuntimeExports.jsx(
866
- DropdownMenuItem,
867
- {
868
- onSelect: (e) => {
869
- e.preventDefault();
870
- onToggleAuthorHandle();
871
- },
872
- children: showAuthorHandle ? t("hide_name") : t("show_name")
873
- }
874
- ) : null
875
- ] })
876
- ] }) }),
877
- /* @__PURE__ */ jsxRuntimeExports.jsx(
878
- SegmentedControl,
879
- {
880
- value: kind,
881
- onValueChange: setKind,
882
- size: "sm",
883
- ariaLabel: t("type_label"),
884
- items: [
885
- {
886
- value: "note",
887
- label: t("type_note"),
888
- icon: /* @__PURE__ */ jsxRuntimeExports.jsx(StickyNote, { className: "h-4 w-4" })
889
- },
890
- {
891
- value: "question",
892
- label: "Q",
893
- icon: /* @__PURE__ */ jsxRuntimeExports.jsx(CircleHelp, { className: "h-4 w-4" })
894
- },
895
- {
896
- value: "task",
897
- label: t("type_task"),
898
- icon: /* @__PURE__ */ jsxRuntimeExports.jsx(SquareCheckBig, { className: "h-4 w-4" })
899
- }
900
- ],
901
- className: "w-full justify-between"
902
- }
903
- ),
904
- /* @__PURE__ */ jsxRuntimeExports.jsx(
905
- Textarea,
906
- {
907
- autoFocus: true,
908
- value: text,
909
- onChange: (e) => setText(e.target.value),
910
- placeholder,
911
- className: "min-h-[92px] resize-none",
912
- onKeyDown: (e) => {
913
- if (e.key === "Escape") {
914
- e.preventDefault();
915
- onCancel?.();
916
- return;
917
- }
918
- if ((e.metaKey || e.ctrlKey) && e.key === "Enter") {
919
- e.preventDefault();
920
- handleSave();
921
- }
922
- }
923
- }
924
- ),
925
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
926
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: t("save_shortcut") }),
927
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
928
- onAskCopilot ? /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { type: "button", variant: "outline", onClick: onAskCopilot, className: "h-8", children: t("ask_copilot") }) : null,
929
- /* @__PURE__ */ jsxRuntimeExports.jsx(Button, { type: "button", onClick: handleSave, className: "h-8", children: t("save") })
930
- ] })
931
- ] })
932
- ] })
933
- ]
934
- }
935
- );
936
- }
937
-
938
- const highlight = "_highlight_dilkk_1";
939
- const parts = "_parts_dilkk_12";
940
- const part$1 = "_part_dilkk_12";
941
- const partFocused = "_partFocused_dilkk_30";
942
- const scrolledTo$1 = "_scrolledTo_dilkk_38";
943
- const partNote = "_partNote_dilkk_77";
944
- const partQuestion = "_partQuestion_dilkk_82";
945
- const partTask = "_partTask_dilkk_88";
946
- const hintChip = "_hintChip_dilkk_94";
947
- const hintChipFocused = "_hintChipFocused_dilkk_139";
948
- const hintPrimaryLine = "_hintPrimaryLine_dilkk_146";
949
- const hintSecondaryLine = "_hintSecondaryLine_dilkk_159";
950
- const styles$1 = {
951
- highlight: highlight,
952
- parts: parts,
953
- part: part$1,
954
- partFocused: partFocused,
955
- scrolledTo: scrolledTo$1,
956
- partNote: partNote,
957
- partQuestion: partQuestion,
958
- partTask: partTask,
959
- hintChip: hintChip,
960
- hintChipFocused: hintChipFocused,
961
- hintPrimaryLine: hintPrimaryLine,
962
- hintSecondaryLine: hintSecondaryLine};
963
-
964
- function clamp01(value) {
965
- return Math.max(0, Math.min(1, value));
966
- }
967
- function hexToRgb(hex) {
968
- const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
969
- if (!m) return null;
970
- const int = Number.parseInt(m[1], 16);
971
- return { r: int >> 16 & 255, g: int >> 8 & 255, b: int & 255 };
972
- }
973
- function rgba(hex, alpha) {
974
- const rgb = hexToRgb(hex);
975
- const a = clamp01(alpha);
976
- if (!rgb) return `rgba(241, 233, 208, ${a})`;
977
- return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${a})`;
978
- }
979
- function Highlight({
980
- position,
981
- onClick,
982
- onMouseOver,
983
- onMouseOut,
984
- comment,
985
- color,
986
- isScrolledTo,
987
- hidden = false,
988
- emphasized = false,
989
- onHoverStart,
990
- onHoverEnd,
991
- showOverlayHint = false,
992
- overlayHintText = "View details",
993
- overlayHintTitle,
994
- overlayHintBorderColor,
995
- overlayHintTextColor,
996
- overlayHintBackgroundColor,
997
- overlayHintOffsetY = 0,
998
- overlayHintTopPx,
999
- overlayHintLeftPx,
1000
- overlayHintWidthPx,
1001
- overlayHintPageHeightPx,
1002
- onOverlayHintClick
1003
- }) {
1004
- if (hidden) return null;
1005
- const { rects } = position;
1006
- const normalizedHintText = String(overlayHintText || "").trim() || "Annotation";
1007
- const [primaryLine, secondaryLine] = normalizedHintText.split("\n").filter(Boolean);
1008
- const kind = comment?.emoji === "question" || comment?.emoji === "task" ? comment.emoji : "note";
1009
- const kindClass = kind === "question" ? styles$1.partQuestion : kind === "task" ? styles$1.partTask : styles$1.partNote;
1010
- const alpha = kind === "question" ? 0.1 : kind === "task" ? 0.08 : 0.14;
1011
- const firstRect = rects[0];
1012
- const hintTop = firstRect ? Math.max(0, Number(firstRect.top || 0) + Number(firstRect.height || 0) * 0.5 - 9) : 0;
1013
- const HINT_HEIGHT_ESTIMATE = 88;
1014
- const hasLayoutTop = typeof overlayHintTopPx === "number" && Number.isFinite(overlayHintTopPx);
1015
- const rawHintTop = hasLayoutTop ? Math.max(0, overlayHintTopPx) : Math.max(0, hintTop + overlayHintOffsetY);
1016
- const boundedHintTop = hasLayoutTop || !(typeof overlayHintPageHeightPx === "number" && Number.isFinite(overlayHintPageHeightPx)) ? rawHintTop : Math.max(0, Math.min(rawHintTop, overlayHintPageHeightPx - HINT_HEIGHT_ESTIMATE));
1017
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1018
- "div",
1019
- {
1020
- className: `Highlight ${styles$1.highlight} ${isScrolledTo ? styles$1.scrolledTo : ""}`,
1021
- children: [
1022
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: `Highlight__parts ${styles$1.parts}`, children: rects.map((rect, index) => /* @__PURE__ */ jsxRuntimeExports.jsx(
1023
- "div",
1024
- {
1025
- onMouseOver: () => {
1026
- onMouseOver?.();
1027
- onHoverStart?.();
1028
- },
1029
- onMouseOut: () => {
1030
- onMouseOut?.();
1031
- onHoverEnd?.();
1032
- },
1033
- onClick,
1034
- "data-ds-highlight-target": "true",
1035
- style: {
1036
- ...rect,
1037
- backgroundColor: rgba(color || "#F1E9D0", alpha)
1038
- },
1039
- className: cn(
1040
- "Highlight__part",
1041
- styles$1.part,
1042
- kindClass,
1043
- emphasized ? styles$1.partFocused : null
1044
- )
1045
- },
1046
- index
1047
- )) }),
1048
- showOverlayHint && firstRect ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
1049
- "button",
1050
- {
1051
- type: "button",
1052
- "data-ds-overlay-hint": "true",
1053
- onClick: (event) => {
1054
- event.preventDefault();
1055
- event.stopPropagation();
1056
- if (onOverlayHintClick) {
1057
- onOverlayHintClick();
1058
- return;
1059
- }
1060
- onClick?.();
1061
- },
1062
- onMouseEnter: () => onHoverStart?.(),
1063
- onMouseLeave: () => onHoverEnd?.(),
1064
- className: cn(styles$1.hintChip, emphasized ? styles$1.hintChipFocused : null),
1065
- style: {
1066
- top: boundedHintTop,
1067
- ...typeof overlayHintLeftPx === "number" && Number.isFinite(overlayHintLeftPx) ? { left: overlayHintLeftPx } : {},
1068
- ...typeof overlayHintWidthPx === "number" && Number.isFinite(overlayHintWidthPx) ? { width: overlayHintWidthPx, maxWidth: overlayHintWidthPx } : {},
1069
- ...overlayHintBorderColor ? { borderColor: overlayHintBorderColor } : {},
1070
- ...overlayHintTextColor ? { color: overlayHintTextColor } : {},
1071
- ...overlayHintBackgroundColor ? { background: overlayHintBackgroundColor } : {}
1072
- },
1073
- "aria-label": overlayHintTitle || normalizedHintText,
1074
- children: [
1075
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: styles$1.hintPrimaryLine, children: primaryLine || normalizedHintText }),
1076
- secondaryLine ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: styles$1.hintSecondaryLine, children: secondaryLine }) : null
1077
- ]
1078
- }
1079
- ) : null
1080
- ]
1081
- }
1082
- );
1083
- }
1084
-
1085
- const areaHighlight = "_areaHighlight_7ldrr_1";
1086
- const part = "_part_7ldrr_8";
1087
- const scrolledTo = "_scrolledTo_7ldrr_20";
1088
- const focused = "_focused_7ldrr_28";
1089
- const styles = {
1090
- areaHighlight: areaHighlight,
1091
- part: part,
1092
- scrolledTo: scrolledTo,
1093
- focused: focused
1094
- };
1095
-
1096
- function AreaHighlight({
1097
- highlight,
1098
- onChange,
1099
- isScrolledTo,
1100
- onSelect,
1101
- hidden = false,
1102
- emphasized = false,
1103
- onHoverStart,
1104
- onHoverEnd,
1105
- ...otherProps
1106
- }) {
1107
- if (hidden) return null;
1108
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
1109
- "div",
1110
- {
1111
- className: `${styles.areaHighlight} ${isScrolledTo ? styles.scrolledTo : ""} ${emphasized ? styles.focused : ""}`,
1112
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1113
- Rnd,
1114
- {
1115
- className: styles.part,
1116
- "data-ds-highlight-target": "true",
1117
- onDragStop: (_, data) => {
1118
- const boundingRect = {
1119
- ...highlight.position.boundingRect,
1120
- top: data.y,
1121
- left: data.x
1122
- };
1123
- onChange(boundingRect);
1124
- },
1125
- onResizeStop: (_mouseEvent, _direction, ref, _delta, position) => {
1126
- const boundingRect = {
1127
- top: position.y,
1128
- left: position.x,
1129
- width: ref.offsetWidth,
1130
- height: ref.offsetHeight,
1131
- pageNumber: getPageFromElement(ref)?.number || -1
1132
- };
1133
- onChange(boundingRect);
1134
- },
1135
- position: {
1136
- x: highlight.position.boundingRect.left,
1137
- y: highlight.position.boundingRect.top
1138
- },
1139
- size: {
1140
- width: highlight.position.boundingRect.width,
1141
- height: highlight.position.boundingRect.height
1142
- },
1143
- onClick: (event) => {
1144
- event.stopPropagation();
1145
- event.preventDefault();
1146
- onSelect?.();
1147
- },
1148
- onMouseEnter: () => onHoverStart?.(),
1149
- onMouseLeave: () => onHoverEnd?.(),
1150
- ...otherProps
1151
- }
1152
- )
1153
- }
1154
- );
1155
- }
1156
-
1157
- function asNumber(value) {
1158
- if (typeof value === "number" && Number.isFinite(value)) return value;
1159
- if (typeof value === "string") {
1160
- const parsed = Number(value);
1161
- if (Number.isFinite(parsed)) return parsed;
1162
- }
1163
- return null;
1164
- }
1165
- function asNonEmptyString(value) {
1166
- if (typeof value !== "string") return null;
1167
- const trimmed = value.trim();
1168
- return trimmed.length > 0 ? trimmed : null;
1169
- }
1170
- function normalizeReviewObjectTypeToken(value) {
1171
- if (!value) return null;
1172
- const token = value.trim().toLowerCase();
1173
- if (!token) return null;
1174
- if (token === "evidence") return "suggestion";
1175
- if (token === "issue" || token === "suggestion" || token === "verification") return token;
1176
- if (token === "needs_verification" || token === "needs verification" || token === "verify" || token === "uncertain") {
1177
- return "verification";
1178
- }
1179
- return null;
1180
- }
1181
- function parseRectLike(raw, fallbackPageNumber) {
1182
- if (!raw || typeof raw !== "object") return null;
1183
- const row = raw;
1184
- const x1 = asNumber(row.x1);
1185
- const y1 = asNumber(row.y1);
1186
- const x2 = asNumber(row.x2);
1187
- const y2 = asNumber(row.y2);
1188
- if (x1 === null || y1 === null || x2 === null || y2 === null) return null;
1189
- const widthRaw = asNumber(row.width);
1190
- const heightRaw = asNumber(row.height);
1191
- const width = widthRaw && widthRaw > 0 ? widthRaw : 100;
1192
- const height = heightRaw && heightRaw > 0 ? heightRaw : 100;
1193
- const pageNumberRaw = asNumber(row.pageNumber);
1194
- const pageNumber = pageNumberRaw && pageNumberRaw > 0 ? Math.trunc(pageNumberRaw) : fallbackPageNumber;
1195
- return {
1196
- x1: Math.min(x1, x2),
1197
- y1: Math.min(y1, y2),
1198
- x2: Math.max(x1, x2),
1199
- y2: Math.max(y1, y2),
1200
- width,
1201
- height,
1202
- ...pageNumber ? { pageNumber } : {}
1203
- };
1204
- }
1205
- function mapReadOnlyAnnotationToHighlight(row) {
1206
- const id = asNonEmptyString(row.annotation_id) || asNonEmptyString(row.annotationId) || asNonEmptyString(row.id);
1207
- if (!id) return null;
1208
- const pageNumberRaw = asNumber(row.page_number) || asNumber(row.pageNumber) || asNumber(row.position?.pageNumber);
1209
- const pageNumber = pageNumberRaw && pageNumberRaw > 0 ? Math.trunc(pageNumberRaw) : null;
1210
- if (!pageNumber) return null;
1211
- const rawRects = Array.isArray(row.rects) ? row.rects : [];
1212
- const rects = rawRects.map((item) => parseRectLike(item, pageNumber)).filter((item) => Boolean(item));
1213
- const boundingRect = parseRectLike(row.bounding_rect, pageNumber) || parseRectLike(row.boundingRect, pageNumber) || parseRectLike(row.position?.boundingRect, pageNumber) || null;
1214
- const fallbackBoundingRect = rects.length ? rects.reduce(
1215
- (acc, rect) => ({
1216
- x1: Math.min(acc.x1, rect.x1),
1217
- y1: Math.min(acc.y1, rect.y1),
1218
- x2: Math.max(acc.x2, rect.x2),
1219
- y2: Math.max(acc.y2, rect.y2),
1220
- width: acc.width || rect.width || 100,
1221
- height: acc.height || rect.height || 100,
1222
- pageNumber
1223
- }),
1224
- { ...rects[0], pageNumber }
1225
- ) : null;
1226
- const resolvedBoundingRect = boundingRect || fallbackBoundingRect;
1227
- if (!resolvedBoundingRect) return null;
1228
- const color = asNonEmptyString(row.color) || "#F1E9D0";
1229
- const objectType = normalizeReviewObjectTypeToken(asNonEmptyString(row.object_type));
1230
- const severity = asNonEmptyString(row.severity);
1231
- const reviewItemId = asNonEmptyString(row.review_item_id);
1232
- const displayText = asNonEmptyString(row.display_text) || asNonEmptyString(row.content_text) || asNonEmptyString(row.content?.text) || "";
1233
- const commentText = asNonEmptyString(row.comment) || asNonEmptyString(row.comment_data?.text) || displayText;
1234
- const previewSummary = asNonEmptyString(row.summary) || asNonEmptyString(row.content?.summary) || asNonEmptyString(row.comment_data?.summary) || null;
1235
- const incomingTags = Array.isArray(row.tags) ? row.tags.map((tag) => asNonEmptyString(tag)).filter((tag) => Boolean(tag)) : [];
1236
- const mergedTagSet = new Set(incomingTags.map((tag) => tag.toLowerCase()));
1237
- mergedTagSet.add("review_annotation");
1238
- if (objectType && (objectType === "issue" || objectType === "suggestion" || objectType === "verification")) {
1239
- mergedTagSet.add(`review_object:${objectType}`);
1240
- }
1241
- if (severity) {
1242
- mergedTagSet.add(`severity:${severity.toLowerCase()}`);
1243
- }
1244
- if (reviewItemId) {
1245
- mergedTagSet.add(`review_item:${reviewItemId}`);
1246
- }
1247
- return {
1248
- id,
1249
- position: {
1250
- pageNumber,
1251
- rects: rects.length ? rects : [resolvedBoundingRect],
1252
- boundingRect: resolvedBoundingRect
1253
- },
1254
- content: { text: displayText },
1255
- comment: { text: commentText, emoji: "note" },
1256
- color,
1257
- tags: Array.from(mergedTagSet),
1258
- previewSummary,
1259
- author: null
1260
- };
1261
- }
1262
- function normalizeKind(value) {
1263
- if (value === "question" || value === "task") return value;
1264
- return "note";
1265
- }
1266
- const KIND_META = {
1267
- note: {
1268
- label: "Note",
1269
- dotClass: "bg-amber-400",
1270
- iconClass: "text-amber-600",
1271
- Icon: StickyNote
1272
- },
1273
- question: {
1274
- label: "Q",
1275
- dotClass: "bg-indigo-400",
1276
- iconClass: "text-indigo-600",
1277
- Icon: CircleHelp
1278
- },
1279
- task: {
1280
- label: "Task",
1281
- dotClass: "bg-emerald-400",
1282
- iconClass: "text-emerald-600",
1283
- Icon: SquareCheckBig
1284
- }
1285
- };
1286
- const REVIEW_OBJECT_META = {
1287
- issue: {
1288
- label: "Issue",
1289
- dotClass: "bg-rose-500",
1290
- iconClass: "text-rose-600",
1291
- Icon: TriangleAlert
1292
- },
1293
- suggestion: {
1294
- label: "Suggestion",
1295
- dotClass: "bg-emerald-500",
1296
- iconClass: "text-emerald-600",
1297
- Icon: Sparkles
1298
- },
1299
- verification: {
1300
- label: "Needs Verification",
1301
- dotClass: "bg-amber-500",
1302
- iconClass: "text-amber-600",
1303
- Icon: CircleHelp
1304
- }
1305
- };
1306
- function extractReviewObjectType(tags) {
1307
- let hasReviewAnnotationTag = false;
1308
- for (const tag of tags || []) {
1309
- const normalized = String(tag || "").trim().toLowerCase();
1310
- if (normalized === "review_annotation") {
1311
- hasReviewAnnotationTag = true;
1312
- continue;
1313
- }
1314
- if (normalized === "review_object:issue") return "issue";
1315
- if (normalized === "review_object:suggestion") return "suggestion";
1316
- if (normalized === "review_object:verification" || normalized === "review_object:needs_verification") {
1317
- return "verification";
1318
- }
1319
- if (normalized === "review_object:evidence") return "suggestion";
1320
- }
1321
- if (!hasReviewAnnotationTag) return null;
1322
- for (const tag of tags || []) {
1323
- const normalized = String(tag || "").trim().toLowerCase();
1324
- if (normalized === "ds_kind:question") return "issue";
1325
- if (normalized === "ds_kind:note" || normalized === "ds_kind:task") return "suggestion";
1326
- }
1327
- return null;
1328
- }
1329
- function extractAnnotationKindFromTags(tags) {
1330
- for (const tag of tags || []) {
1331
- const normalized = String(tag || "").trim().toLowerCase();
1332
- if (normalized === "ds_kind:question") return "question";
1333
- if (normalized === "ds_kind:task") return "task";
1334
- if (normalized === "ds_kind:note") return "note";
1335
- }
1336
- return null;
1337
- }
1338
- function extractReviewItemId(tags) {
1339
- for (const tag of tags || []) {
1340
- const token = String(tag || "").trim();
1341
- if (!token) continue;
1342
- if (!token.toLowerCase().startsWith("review_item:")) continue;
1343
- const value = token.slice("review_item:".length).trim();
1344
- if (value) return value;
1345
- }
1346
- return null;
1347
- }
1348
- function resolveHighlightMeta(highlight) {
1349
- const kind = normalizeKind(highlight.comment?.emoji || extractAnnotationKindFromTags(highlight.tags) || void 0);
1350
- const reviewObjectType = extractReviewObjectType(highlight.tags);
1351
- if (reviewObjectType) {
1352
- return {
1353
- kind,
1354
- reviewObjectType,
1355
- ...REVIEW_OBJECT_META[reviewObjectType]
1356
- };
1357
- }
1358
- return {
1359
- kind,
1360
- reviewObjectType: null,
1361
- ...KIND_META[kind]
1362
- };
1363
- }
1364
- function getAnnotationKindLabel(kind, t) {
1365
- if (kind === "question") return t("type_question");
1366
- if (kind === "task") return t("type_task");
1367
- return t("type_note");
1368
- }
1369
- function getReviewObjectTypeLabel(value, t) {
1370
- if (value === "issue") return t("review_type_issue");
1371
- if (value === "verification") return t("review_type_verification");
1372
- return t("review_type_suggestion");
1373
- }
1374
- function getHighlightMetaLabel(meta, t) {
1375
- if (meta.reviewObjectType) {
1376
- return getReviewObjectTypeLabel(meta.reviewObjectType, t);
1377
- }
1378
- return getAnnotationKindLabel(meta.kind, t);
1379
- }
1380
- function overlayHintTheme(meta) {
1381
- return {
1382
- borderColor: "rgba(140, 118, 72, 0.95)",
1383
- textColor: "rgba(15, 15, 15, 0.96)",
1384
- backgroundColor: "rgba(255, 255, 255, 0.98)"
1385
- };
1386
- }
1387
- function compactWords(text, maxWords) {
1388
- const normalized = String(text || "").normalize("NFKC").replace(/\uFFFD/g, " ").replace(/\s+/g, " ").trim();
1389
- if (!normalized) return "";
1390
- if (maxWords <= 0) return "";
1391
- const words = normalized.split(" ");
1392
- if (words.length <= maxWords) return normalized;
1393
- return `${words.slice(0, maxWords).join(" ")}…`;
1394
- }
1395
- const MARKDOWN_CODE_BLOCK_TOKEN = "__DS_ANNOTATION_MD_CODE_BLOCK__";
1396
- const MARKDOWN_INLINE_CODE_TOKEN = "__DS_ANNOTATION_MD_INLINE_CODE__";
1397
- const FENCED_CODE_REGEX = /(^|\n)(```|~~~)[\s\S]*?\n\2[^\n]*($|\n)/g;
1398
- const INLINE_CODE_REGEX = /`[^`\n]*`/g;
1399
- const BLOCK_MATH_BRACKET_REGEX = /\\\[([\s\S]+?)\\\]/g;
1400
- const INLINE_MATH_PAREN_REGEX = /\\\((.+?)\\\)/g;
1401
- function protectSegments(input, regex, token) {
1402
- const segments = [];
1403
- const text = input.replace(regex, (match) => {
1404
- const index = segments.length;
1405
- segments.push(match);
1406
- return `${token}${index}__`;
1407
- });
1408
- return { text, segments };
1409
- }
1410
- function restoreSegments(input, segments, token) {
1411
- let text = input;
1412
- segments.forEach((segment, index) => {
1413
- text = text.replace(`${token}${index}__`, segment);
1414
- });
1415
- return text;
1416
- }
1417
- function normalizeAnnotationCommentMarkdown(markdown) {
1418
- if (!markdown) return markdown;
1419
- const { text: withoutFenced, segments: fencedBlocks } = protectSegments(
1420
- markdown,
1421
- FENCED_CODE_REGEX,
1422
- MARKDOWN_CODE_BLOCK_TOKEN
1423
- );
1424
- const { text: withoutInline, segments: inlineBlocks } = protectSegments(
1425
- withoutFenced,
1426
- INLINE_CODE_REGEX,
1427
- MARKDOWN_INLINE_CODE_TOKEN
1428
- );
1429
- let transformed = withoutInline;
1430
- transformed = transformed.replace(BLOCK_MATH_BRACKET_REGEX, (_match, latex) => {
1431
- const value = String(latex || "").trim();
1432
- return value ? `$$
1433
- ${value}
1434
- $$` : "";
1435
- });
1436
- transformed = transformed.replace(INLINE_MATH_PAREN_REGEX, (_match, latex) => {
1437
- const value = String(latex || "").trim();
1438
- return value ? `$${value}$` : "";
1439
- });
1440
- const restoredInline = restoreSegments(
1441
- transformed,
1442
- inlineBlocks,
1443
- MARKDOWN_INLINE_CODE_TOKEN
1444
- );
1445
- return restoreSegments(restoredInline, fencedBlocks, MARKDOWN_CODE_BLOCK_TOKEN);
1446
- }
1447
- const MARKDOWN_EXTENSIONS = [".md", ".markdown", ".mdx"];
1448
- function isMarkdownFileName(name) {
1449
- const lower = name.toLowerCase();
1450
- return MARKDOWN_EXTENSIONS.some((ext) => lower.endsWith(ext));
1451
- }
1452
- function normalizeExcerptText(value) {
1453
- return String(value || "").replace(/[\u2010-\u2015]/g, "-").replace(/\s+/g, " ").trim();
1454
- }
1455
- function locateMarkdownExcerpt(markdown, selectedText) {
1456
- const normalizedMarkdown = normalizeExcerptText(markdown);
1457
- const normalizedSelection = normalizeExcerptText(selectedText);
1458
- if (!normalizedMarkdown || !normalizedSelection || normalizedSelection.length < 12) {
1459
- return null;
1460
- }
1461
- const exactIndex = normalizedMarkdown.indexOf(normalizedSelection);
1462
- if (exactIndex >= 0) {
1463
- const start2 = Math.max(0, exactIndex - 160);
1464
- const end2 = Math.min(normalizedMarkdown.length, exactIndex + normalizedSelection.length + 160);
1465
- return normalizedMarkdown.slice(start2, end2).trim();
1466
- }
1467
- const words = normalizedSelection.split(" ").filter(Boolean).slice(0, 8);
1468
- if (words.length < 3) return null;
1469
- const pattern = words.map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join(".*?");
1470
- const match = normalizedMarkdown.match(new RegExp(pattern, "i"));
1471
- if (!match || typeof match.index !== "number") return null;
1472
- const start = Math.max(0, match.index - 160);
1473
- const end = Math.min(normalizedMarkdown.length, match.index + match[0].length + 160);
1474
- return normalizedMarkdown.slice(start, end).trim();
1475
- }
1476
- function PdfSpinner() {
1477
- const { t } = useI18n("pdf_viewer");
1478
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex h-full items-center justify-center bg-muted/20", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center gap-3 text-muted-foreground", children: [
1479
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-8 w-8 rounded-full border-2 border-primary/60 border-t-transparent animate-spin" }),
1480
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-sm", children: t("loading_pdf") })
1481
- ] }) });
1482
- }
1483
- function AnnotationSidebarItem({
1484
- highlight,
1485
- selected,
1486
- followed,
1487
- flash,
1488
- onSelect,
1489
- onDelete,
1490
- onUpdateKind,
1491
- onUpdateComment,
1492
- readOnly,
1493
- registerItemRef
1494
- }) {
1495
- const { t } = useI18n("pdf_viewer");
1496
- const meta = resolveHighlightMeta(highlight);
1497
- const kind = meta.kind;
1498
- const metaLabel = getHighlightMetaLabel(meta, t);
1499
- const reviewItemId = extractReviewItemId(highlight.tags);
1500
- const displayIdRaw = reviewItemId || String(highlight.id || "").slice(0, 8);
1501
- const displayId = displayIdRaw.length > 10 ? `${displayIdRaw.slice(0, 10)}…` : displayIdRaw;
1502
- const authorColor = highlight.author?.color || highlight.color || "#F1E9D0";
1503
- const authorHandle = highlight.author?.handle || "user";
1504
- const isAI = highlight.tags?.some((tag) => tag.toLowerCase() === "ai");
1505
- const rawContentPreview = highlight.content?.text ? String(highlight.content.text).trim() : highlight.content?.image ? t("area_highlight") : "";
1506
- const compactContentPreview = highlight.content?.text ? compactWords(rawContentPreview, 20) : rawContentPreview;
1507
- const itemRef = reactExports.useRef(null);
1508
- const [isEditing, setIsEditing] = reactExports.useState(false);
1509
- const [draft, setDraft] = reactExports.useState(highlight.comment?.text || "");
1510
- reactExports.useEffect(() => {
1511
- if (!isEditing) setDraft(highlight.comment?.text || "");
1512
- }, [highlight.comment?.text, isEditing]);
1513
- reactExports.useEffect(() => {
1514
- if (!flash) return;
1515
- itemRef.current?.scrollIntoView({ block: "nearest", behavior: "smooth" });
1516
- }, [flash]);
1517
- reactExports.useEffect(() => {
1518
- if (!selected) return;
1519
- itemRef.current?.scrollIntoView({ block: "nearest", behavior: "smooth" });
1520
- }, [selected]);
1521
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1522
- "div",
1523
- {
1524
- ref: (node) => {
1525
- itemRef.current = node;
1526
- registerItemRef?.(highlight.id, node);
1527
- },
1528
- className: cn(
1529
- "group relative p-3 rounded-lg border transition-all cursor-pointer",
1530
- selected ? "border-primary bg-primary/5" : followed ? "border-border bg-muted/30 hover:bg-muted/45" : "border-border/60 hover:bg-muted/40",
1531
- flash ? "ring-2 ring-primary/30 bg-primary/10 shadow-soft-card" : null
1532
- ),
1533
- onClick: onSelect,
1534
- children: [
1535
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1536
- "span",
1537
- {
1538
- className: cn("absolute left-2 top-2 h-2 w-2 rounded-full", meta.dotClass),
1539
- "aria-hidden": true
1540
- }
1541
- ),
1542
- /* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenu, { children: [
1543
- /* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1544
- "button",
1545
- {
1546
- type: "button",
1547
- className: "absolute right-2 top-2 h-5 w-5 rounded-md border border-border bg-background/70 backdrop-blur flex items-center justify-center shadow-soft-sm",
1548
- onClick: (e) => e.stopPropagation(),
1549
- title: authorHandle,
1550
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1551
- "span",
1552
- {
1553
- className: "h-2.5 w-2.5 rounded-full border border-border",
1554
- style: { backgroundColor: authorColor }
1555
- }
1556
- )
1557
- }
1558
- ) }),
1559
- /* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenuContent, { align: "end", children: [
1560
- /* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuLabel, { children: t("author") }),
1561
- /* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuItem, { disabled: true, children: authorHandle })
1562
- ] })
1563
- ] }),
1564
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-3", children: [
1565
- readOnly ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1566
- "div",
1567
- {
1568
- className: cn(
1569
- "mt-0.5 h-8 w-8 rounded-lg border border-border bg-background",
1570
- "flex items-center justify-center flex-shrink-0 shadow-soft-sm"
1571
- ),
1572
- title: t("type_label"),
1573
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1574
- meta.Icon,
1575
- {
1576
- className: cn(
1577
- "h-4 w-4",
1578
- meta.iconClass
1579
- )
1580
- }
1581
- )
1582
- }
1583
- ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenu, { children: [
1584
- /* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1585
- "button",
1586
- {
1587
- type: "button",
1588
- className: cn(
1589
- "mt-0.5 h-8 w-8 rounded-lg border border-border bg-background",
1590
- "flex items-center justify-center flex-shrink-0 shadow-soft-sm"
1591
- ),
1592
- title: t("change_type"),
1593
- onClick: (e) => e.stopPropagation(),
1594
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1595
- meta.Icon,
1596
- {
1597
- className: cn(
1598
- "h-4 w-4",
1599
- meta.iconClass
1600
- )
1601
- }
1602
- )
1603
- }
1604
- ) }),
1605
- /* @__PURE__ */ jsxRuntimeExports.jsxs(DropdownMenuContent, { align: "start", children: [
1606
- /* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuLabel, { children: t("type_label") }),
1607
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
1608
- DropdownMenuRadioGroup,
1609
- {
1610
- value: kind,
1611
- onValueChange: (v) => onUpdateKind(normalizeKind(v)),
1612
- children: [
1613
- /* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuRadioItem, { value: "note", children: t("type_note") }),
1614
- /* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuRadioItem, { value: "question", children: t("type_question") }),
1615
- /* @__PURE__ */ jsxRuntimeExports.jsx(DropdownMenuRadioItem, { value: "task", children: t("type_task") })
1616
- ]
1617
- }
1618
- )
1619
- ] })
1620
- ] }),
1621
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 min-w-0", children: [
1622
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
1623
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] font-medium text-foreground/90", children: metaLabel }),
1624
- isAI ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "inline-flex items-center gap-1 rounded-full border border-border bg-muted/40 px-2 py-0.5 text-[9px] uppercase tracking-wide text-foreground/70", children: [
1625
- /* @__PURE__ */ jsxRuntimeExports.jsx(Sparkles, { className: "h-3 w-3" }),
1626
- "AI"
1627
- ] }) : null,
1628
- typeof highlight.position?.pageNumber === "number" ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[9px] text-muted-foreground/70", children: t("page", { page: highlight.position.pageNumber }) }) : null,
1629
- displayId ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
1630
- "span",
1631
- {
1632
- className: "text-[9px] text-muted-foreground/60",
1633
- title: `ID ${displayIdRaw}`,
1634
- children: [
1635
- "ID ",
1636
- displayId
1637
- ]
1638
- }
1639
- ) : null
1640
- ] }),
1641
- !isEditing ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 rounded-md border border-border/60 bg-background/70 px-2.5 py-2", children: highlight.comment?.text?.trim() ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1642
- MarkdownPreview,
1643
- {
1644
- content: normalizeAnnotationCommentMarkdown(highlight.comment.text),
1645
- rewriteDocsImages: false,
1646
- rewriteDocsLinks: false,
1647
- className: cn(
1648
- "review-annotation-markdown text-[11px] leading-5 text-foreground/85 break-words",
1649
- "[&_p]:my-1 [&_ul]:my-1 [&_ol]:my-1 [&_li]:my-0.5",
1650
- "[&_pre]:my-1 [&_pre]:max-w-full [&_pre]:overflow-x-auto",
1651
- "[&_pre]:whitespace-pre-wrap [&_code]:break-words",
1652
- "[&_.katex]:[word-break:normal] [&_.katex]:[overflow-wrap:normal]",
1653
- "[&_.katex]:whitespace-nowrap [&_.katex-display]:whitespace-normal",
1654
- "[&_.katex-display]:my-1 [&_.katex-display]:overflow-x-auto",
1655
- "[&_.katex-display]:overflow-y-hidden"
1656
- )
1657
- }
1658
- ) : /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "review-annotation-text-fallback text-[11px] text-foreground/80 whitespace-pre-wrap break-words", children: t("no_comment_yet") }) }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
1659
- Textarea,
1660
- {
1661
- value: draft,
1662
- onChange: (e) => setDraft(e.target.value),
1663
- onClick: (e) => e.stopPropagation(),
1664
- className: "mt-2 min-h-[84px] text-[11px] leading-5",
1665
- placeholder: t("add_comment_placeholder")
1666
- }
1667
- ),
1668
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-2 flex items-start justify-between gap-2", children: [
1669
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1670
- "p",
1671
- {
1672
- className: "review-annotation-text-fallback text-[9px] text-muted-foreground/60 whitespace-pre-wrap break-words",
1673
- title: rawContentPreview || void 0,
1674
- children: compactContentPreview
1675
- }
1676
- ),
1677
- readOnly ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[9px] text-muted-foreground", children: t("read_only") }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-1", children: [
1678
- !isEditing ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1679
- Button,
1680
- {
1681
- type: "button",
1682
- variant: "ghost",
1683
- size: "sm",
1684
- className: "h-7 px-2 text-[10px]",
1685
- onClick: (e) => {
1686
- e.stopPropagation();
1687
- setIsEditing(true);
1688
- },
1689
- children: "Edit"
1690
- }
1691
- ) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1692
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1693
- Button,
1694
- {
1695
- type: "button",
1696
- variant: "secondary",
1697
- size: "sm",
1698
- className: "h-7 px-2 text-[10px]",
1699
- onClick: (e) => {
1700
- e.stopPropagation();
1701
- setIsEditing(false);
1702
- setDraft(highlight.comment?.text || "");
1703
- },
1704
- children: "Cancel"
1705
- }
1706
- ),
1707
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1708
- Button,
1709
- {
1710
- type: "button",
1711
- size: "sm",
1712
- className: "h-7 px-2 text-[10px]",
1713
- onClick: (e) => {
1714
- e.stopPropagation();
1715
- onUpdateComment(draft.trim());
1716
- setIsEditing(false);
1717
- },
1718
- children: "Save"
1719
- }
1720
- )
1721
- ] }),
1722
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1723
- Button,
1724
- {
1725
- type: "button",
1726
- variant: "ghost",
1727
- size: "sm",
1728
- className: "h-7 px-2 text-[10px] text-muted-foreground hover:text-foreground",
1729
- onClick: (e) => {
1730
- e.stopPropagation();
1731
- onDelete();
1732
- },
1733
- children: t("delete")
1734
- }
1735
- )
1736
- ] })
1737
- ] })
1738
- ] })
1739
- ] })
1740
- ]
1741
- }
1742
- );
1743
- }
1744
- function HighlightsSidebar({
1745
- highlights,
1746
- selectedId,
1747
- currentPage,
1748
- flashId,
1749
- onSelect,
1750
- onDelete,
1751
- onUpdateKind,
1752
- onUpdateComment,
1753
- showOverlayHints,
1754
- readOnly
1755
- }) {
1756
- const { t } = useI18n("pdf_viewer");
1757
- const [objectFilter, setObjectFilter] = reactExports.useState("all");
1758
- const itemRefs = reactExports.useRef(/* @__PURE__ */ new Map());
1759
- const previousSelectedIdRef = reactExports.useRef(selectedId);
1760
- const suppressFollowScrollOnceRef = reactExports.useRef(false);
1761
- const sortHighlightsByPageOrder = reactExports.useCallback((left, right) => {
1762
- const leftPage = Number(left.position?.pageNumber || 0);
1763
- const rightPage = Number(right.position?.pageNumber || 0);
1764
- const normalizedLeftPage = Number.isFinite(leftPage) && leftPage > 0 ? leftPage : Number.MAX_SAFE_INTEGER;
1765
- const normalizedRightPage = Number.isFinite(rightPage) && rightPage > 0 ? rightPage : Number.MAX_SAFE_INTEGER;
1766
- if (normalizedLeftPage !== normalizedRightPage) {
1767
- return normalizedLeftPage - normalizedRightPage;
1768
- }
1769
- const leftTop = Number(
1770
- left.position?.boundingRect?.y1 ?? left.position?.rects?.[0]?.y1 ?? Number.MAX_SAFE_INTEGER
1771
- );
1772
- const rightTop = Number(
1773
- right.position?.boundingRect?.y1 ?? right.position?.rects?.[0]?.y1 ?? Number.MAX_SAFE_INTEGER
1774
- );
1775
- const normalizedLeftTop = Number.isFinite(leftTop) ? leftTop : Number.MAX_SAFE_INTEGER;
1776
- const normalizedRightTop = Number.isFinite(rightTop) ? rightTop : Number.MAX_SAFE_INTEGER;
1777
- if (normalizedLeftTop !== normalizedRightTop) {
1778
- return normalizedLeftTop - normalizedRightTop;
1779
- }
1780
- const leftX = Number(
1781
- left.position?.boundingRect?.x1 ?? left.position?.rects?.[0]?.x1 ?? Number.MAX_SAFE_INTEGER
1782
- );
1783
- const rightX = Number(
1784
- right.position?.boundingRect?.x1 ?? right.position?.rects?.[0]?.x1 ?? Number.MAX_SAFE_INTEGER
1785
- );
1786
- const normalizedLeftX = Number.isFinite(leftX) ? leftX : Number.MAX_SAFE_INTEGER;
1787
- const normalizedRightX = Number.isFinite(rightX) ? rightX : Number.MAX_SAFE_INTEGER;
1788
- if (normalizedLeftX !== normalizedRightX) {
1789
- return normalizedLeftX - normalizedRightX;
1790
- }
1791
- return left.id.localeCompare(right.id);
1792
- }, []);
1793
- const counts = reactExports.useMemo(() => {
1794
- const base = {
1795
- all: highlights.length,
1796
- issue: 0,
1797
- suggestion: 0,
1798
- verification: 0,
1799
- note: 0,
1800
- question: 0,
1801
- task: 0
1802
- };
1803
- for (const highlight of highlights) {
1804
- const objectType = extractReviewObjectType(highlight.tags);
1805
- if (objectType) {
1806
- base[objectType] += 1;
1807
- continue;
1808
- }
1809
- const kind = normalizeKind(
1810
- highlight.comment?.emoji || extractAnnotationKindFromTags(highlight.tags) || void 0
1811
- );
1812
- base[kind] += 1;
1813
- }
1814
- return base;
1815
- }, [highlights]);
1816
- const filterItems = reactExports.useMemo(() => {
1817
- const rows = [
1818
- { key: "all", label: t("filter_all"), count: counts.all }
1819
- ];
1820
- const reviewFilterRows = [
1821
- { key: "issue", label: t("review_type_issue"), count: counts.issue },
1822
- { key: "suggestion", label: t("review_type_suggestion"), count: counts.suggestion },
1823
- { key: "verification", label: t("review_type_verification"), count: counts.verification }
1824
- ];
1825
- const kindFilterRows = [
1826
- { key: "note", label: t("type_note"), count: counts.note },
1827
- { key: "question", label: t("type_question"), count: counts.question },
1828
- { key: "task", label: t("type_task"), count: counts.task }
1829
- ];
1830
- const hasReviewRows = reviewFilterRows.some((row) => row.count > 0);
1831
- const hasKindRows = kindFilterRows.some((row) => row.count > 0);
1832
- if (hasReviewRows) {
1833
- rows.push(...reviewFilterRows.filter((row) => row.count > 0));
1834
- }
1835
- if (hasKindRows) {
1836
- rows.push(...kindFilterRows.filter((row) => row.count > 0));
1837
- }
1838
- return rows;
1839
- }, [counts, t]);
1840
- reactExports.useEffect(() => {
1841
- if (objectFilter === "all") return;
1842
- if (filterItems.some((item) => item.key === objectFilter)) return;
1843
- setObjectFilter("all");
1844
- }, [filterItems, objectFilter]);
1845
- reactExports.useEffect(() => {
1846
- if (previousSelectedIdRef.current && !selectedId) {
1847
- suppressFollowScrollOnceRef.current = true;
1848
- }
1849
- previousSelectedIdRef.current = selectedId;
1850
- }, [selectedId]);
1851
- const filteredHighlights = reactExports.useMemo(() => {
1852
- const base = highlights.filter((highlight) => {
1853
- if (objectFilter === "all") return true;
1854
- const reviewObjectType = extractReviewObjectType(highlight.tags);
1855
- if (objectFilter === "issue" || objectFilter === "suggestion" || objectFilter === "verification") {
1856
- return reviewObjectType === objectFilter;
1857
- }
1858
- if (reviewObjectType) return false;
1859
- const kind = normalizeKind(
1860
- highlight.comment?.emoji || extractAnnotationKindFromTags(highlight.tags) || void 0
1861
- );
1862
- return kind === objectFilter;
1863
- });
1864
- return [...base].sort(sortHighlightsByPageOrder);
1865
- }, [highlights, objectFilter, sortHighlightsByPageOrder]);
1866
- const followTargetId = reactExports.useMemo(() => {
1867
- if (selectedId) return null;
1868
- if (filteredHighlights.length === 0) return null;
1869
- const rows = filteredHighlights.map((item) => ({
1870
- id: item.id,
1871
- page: Number(item.position?.pageNumber || 0)
1872
- })).filter((item) => Number.isFinite(item.page) && item.page > 0);
1873
- if (rows.length === 0) return null;
1874
- const exact = rows.find((item) => item.page === currentPage);
1875
- if (exact) return exact.id;
1876
- let best = rows[0];
1877
- let bestDistance = Math.abs(rows[0].page - currentPage);
1878
- for (let index = 1; index < rows.length; index += 1) {
1879
- const candidate = rows[index];
1880
- const distance = Math.abs(candidate.page - currentPage);
1881
- if (distance < bestDistance) {
1882
- best = candidate;
1883
- bestDistance = distance;
1884
- continue;
1885
- }
1886
- if (distance === bestDistance && candidate.page < best.page) {
1887
- best = candidate;
1888
- }
1889
- }
1890
- return best.id;
1891
- }, [currentPage, filteredHighlights, selectedId]);
1892
- reactExports.useEffect(() => {
1893
- if (!followTargetId) return;
1894
- if (suppressFollowScrollOnceRef.current) {
1895
- suppressFollowScrollOnceRef.current = false;
1896
- return;
1897
- }
1898
- const node = itemRefs.current.get(followTargetId);
1899
- if (!node) return;
1900
- node.scrollIntoView({ block: "nearest", behavior: "smooth" });
1901
- }, [followTargetId]);
1902
- const registerItemRef = reactExports.useCallback((id, node) => {
1903
- if (!id) return;
1904
- if (!node) {
1905
- itemRefs.current.delete(id);
1906
- return;
1907
- }
1908
- itemRefs.current.set(id, node);
1909
- }, []);
1910
- if (highlights.length === 0) {
1911
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col items-center justify-center h-full p-4 text-center", children: [
1912
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-4xl mb-2 opacity-30", children: "📝" }),
1913
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-xs text-muted-foreground", children: t("no_annotations") }),
1914
- /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[11px] text-muted-foreground/60 mt-1", children: readOnly ? t("read_only_annotation_desc") : t("select_text_to_highlight") })
1915
- ] });
1916
- }
1917
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col h-full", children: [
1918
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "p-3 border-b space-y-2", children: [
1919
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between", children: [
1920
- /* @__PURE__ */ jsxRuntimeExports.jsxs("h3", { className: "font-medium text-xs", children: [
1921
- t("annotations"),
1922
- " (",
1923
- filteredHighlights.length,
1924
- "/",
1925
- highlights.length,
1926
- ")"
1927
- ] }),
1928
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1929
- "span",
1930
- {
1931
- className: cn(
1932
- "inline-flex items-center rounded-full border px-2 py-0.5 text-[10px] font-medium tracking-wide",
1933
- showOverlayHints ? "border-black/10 bg-white text-foreground/70" : "border-border bg-background text-muted-foreground"
1934
- ),
1935
- title: t("all_markers_shown"),
1936
- children: t("markers_on")
1937
- }
1938
- )
1939
- ] }),
1940
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center gap-1", children: filterItems.map((item) => {
1941
- const active = objectFilter === item.key;
1942
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
1943
- "button",
1944
- {
1945
- type: "button",
1946
- onClick: () => setObjectFilter(item.key),
1947
- className: cn(
1948
- "inline-flex min-w-0 flex-1 items-center justify-center rounded-md border px-2 py-1 text-[10px] transition-colors",
1949
- active ? "border-primary/40 bg-primary/10 text-foreground" : "border-border/70 bg-background text-muted-foreground hover:text-foreground"
1950
- ),
1951
- title: t("filter_annotations_title", { label: item.label }),
1952
- children: [
1953
- item.label,
1954
- " (",
1955
- item.count,
1956
- ")"
1957
- ]
1958
- },
1959
- item.key
1960
- );
1961
- }) })
1962
- ] }),
1963
- filteredHighlights.length === 0 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex flex-1 items-center justify-center px-4 text-center text-xs text-muted-foreground", children: "No annotations for current filter." }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-auto p-2 space-y-2", children: filteredHighlights.map((h) => /* @__PURE__ */ jsxRuntimeExports.jsx(
1964
- AnnotationSidebarItem,
1965
- {
1966
- highlight: h,
1967
- selected: selectedId === h.id,
1968
- followed: Boolean(followTargetId && followTargetId === h.id),
1969
- flash: Boolean(flashId && flashId === h.id),
1970
- onSelect: () => onSelect(h.id),
1971
- onDelete: () => onDelete(h.id),
1972
- onUpdateKind: (k) => onUpdateKind(h.id, k),
1973
- onUpdateComment: (c) => onUpdateComment(h.id, c),
1974
- readOnly,
1975
- registerItemRef
1976
- },
1977
- h.id
1978
- )) })
1979
- ] });
1980
- }
1981
- function PdfHighlighterSurface({
1982
- pdfDocument,
1983
- scale,
1984
- highlights,
1985
- selectedId,
1986
- onSelect,
1987
- onAdd,
1988
- onUpdate,
1989
- onScrollRef,
1990
- onPageChange,
1991
- pdfHighlighterRef,
1992
- authorColor,
1993
- authorHandle,
1994
- showOverlayHints,
1995
- tipPlacementMode,
1996
- readOnly,
1997
- onAskCopilot
1998
- }) {
1999
- const { t } = useI18n("pdf_viewer");
2000
- const { setTotalPages } = useViewerState();
2001
- const [hoveredHighlightId, setHoveredHighlightId] = reactExports.useState(null);
2002
- const pdfScaleValue = scale === 1 ? "page-width" : `page-width:${scale}`;
2003
- const activeFocusHighlightId = hoveredHighlightId || selectedId || null;
2004
- reactExports.useEffect(() => {
2005
- setTotalPages(pdfDocument.numPages);
2006
- }, [pdfDocument.numPages, setTotalPages]);
2007
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
2008
- PdfHighlighter,
2009
- {
2010
- ref: pdfHighlighterRef,
2011
- pdfDocument,
2012
- pdfScaleValue,
2013
- tipPlacementMode,
2014
- onPageChange,
2015
- enableAreaSelection: (event) => readOnly ? false : event.altKey,
2016
- onScrollChange: () => {
2017
- },
2018
- scrollRef: onScrollRef,
2019
- highlights,
2020
- onSelectionFinished: (position, content, hideTipAndSelection, transformSelection) => readOnly ? null : /* @__PURE__ */ jsxRuntimeExports.jsx(
2021
- Tip,
2022
- {
2023
- onOpen: transformSelection,
2024
- onCancel: hideTipAndSelection,
2025
- onConfirm: (comment) => {
2026
- onAdd({ content, position, comment });
2027
- hideTipAndSelection();
2028
- },
2029
- onAskCopilot: () => {
2030
- onAskCopilot?.(content, position);
2031
- hideTipAndSelection();
2032
- },
2033
- authorColor,
2034
- authorHandle,
2035
- showAuthorHandle: false
2036
- }
2037
- ),
2038
- highlightTransform: (highlight, _index, _setTip, _hideTip, viewportToScaled, screenshot, isScrolledTo, pageMetrics) => {
2039
- const isGhost = Boolean(highlight.__dsGhost);
2040
- const ghostOpacity = highlight.__dsGhostOpacity ?? 1;
2041
- const ghostMode = highlight.__dsGhostMode;
2042
- const isTextHighlight = !highlight.content?.image;
2043
- const highlightMeta = resolveHighlightMeta({
2044
- comment: highlight.comment,
2045
- tags: highlight.tags
2046
- });
2047
- const safeMetaLabel = String(getHighlightMetaLabel(highlightMeta, t) || t("annotation_label"));
2048
- const reviewItemId = extractReviewItemId(highlight.tags);
2049
- const previewSummary = asNonEmptyString(
2050
- highlight.previewSummary
2051
- );
2052
- const firstLine = reviewItemId ? `#${reviewItemId} · ${safeMetaLabel}` : `P${highlight.position.pageNumber} · ${safeMetaLabel}`;
2053
- const secondLine = previewSummary || compactWords(highlight.comment?.text || "", 8);
2054
- const compactHintText = reviewItemId ? secondLine ? `${firstLine}
2055
- ${secondLine}` : firstLine : secondLine ? `${firstLine}
2056
- ${secondLine}` : firstLine;
2057
- const compactHintTitle = `${safeMetaLabel} · ${t("page", {
2058
- page: highlight.position.pageNumber
2059
- })} · ${t("open_details_hint")}`;
2060
- const highlightTheme = overlayHintTheme();
2061
- const isFocused = activeFocusHighlightId === highlight.id;
2062
- const hideThisHighlight = Boolean(activeFocusHighlightId && !isFocused);
2063
- const component = isTextHighlight ? /* @__PURE__ */ jsxRuntimeExports.jsx(
2064
- Highlight,
2065
- {
2066
- isScrolledTo: isScrolledTo || highlight.id === selectedId,
2067
- position: highlight.position,
2068
- comment: highlight.comment,
2069
- color: highlight.color,
2070
- hidden: hideThisHighlight,
2071
- emphasized: isFocused,
2072
- onHoverStart: isGhost ? void 0 : () => setHoveredHighlightId(highlight.id),
2073
- onHoverEnd: isGhost ? void 0 : () => setHoveredHighlightId(
2074
- (current) => current === highlight.id ? null : current
2075
- ),
2076
- onClick: isGhost ? void 0 : () => onSelect(highlight.id, {
2077
- allowToggleClose: false,
2078
- skipAutoScroll: true
2079
- }),
2080
- showOverlayHint: !isGhost && showOverlayHints && !hideThisHighlight,
2081
- overlayHintText: compactHintText,
2082
- overlayHintTitle: compactHintTitle,
2083
- overlayHintBorderColor: highlightTheme.borderColor,
2084
- overlayHintTextColor: highlightTheme.textColor,
2085
- overlayHintBackgroundColor: highlightTheme.backgroundColor,
2086
- overlayHintTopPx: pageMetrics.overlayHintTopById?.[highlight.id],
2087
- overlayHintLeftPx: pageMetrics.overlayHintLeftPx,
2088
- overlayHintWidthPx: pageMetrics.overlayHintWidthPx,
2089
- overlayHintPageHeightPx: pageMetrics.height,
2090
- onOverlayHintClick: isGhost ? void 0 : () => {
2091
- onSelect(highlight.id, {
2092
- skipAutoScroll: true
2093
- });
2094
- }
2095
- }
2096
- ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
2097
- AreaHighlight,
2098
- {
2099
- isScrolledTo: isScrolledTo || highlight.id === selectedId,
2100
- highlight,
2101
- hidden: hideThisHighlight,
2102
- emphasized: isFocused,
2103
- onHoverStart: isGhost ? void 0 : () => setHoveredHighlightId(highlight.id),
2104
- onHoverEnd: isGhost ? void 0 : () => setHoveredHighlightId(
2105
- (current) => current === highlight.id ? null : current
2106
- ),
2107
- onSelect: isGhost ? void 0 : () => onSelect(highlight.id, {
2108
- allowToggleClose: false,
2109
- skipAutoScroll: true
2110
- }),
2111
- onChange: (boundingRect) => {
2112
- if (readOnly) return;
2113
- onUpdate(
2114
- highlight.id,
2115
- { boundingRect: viewportToScaled(boundingRect) },
2116
- { image: screenshot(boundingRect) }
2117
- );
2118
- }
2119
- }
2120
- );
2121
- if (isGhost) {
2122
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
2123
- "div",
2124
- {
2125
- className: cn(
2126
- "pointer-events-none",
2127
- ghostMode === "annotate" ? "ds-pdf-ghost-annotate" : "ds-pdf-ghost-guide"
2128
- ),
2129
- style: { opacity: ghostOpacity, transition: "opacity 0.8s ease" },
2130
- children: component
2131
- },
2132
- `ghost-${_index}`
2133
- );
2134
- }
2135
- return component;
2136
- }
2137
- }
2138
- );
2139
- }
2140
- function PdfViewerPlugin({
2141
- context,
2142
- tabId,
2143
- setDirty,
2144
- setTitle
2145
- }) {
2146
- const { t } = useI18n("pdf_viewer");
2147
- const rawFileId = context.resourceId ?? (typeof context.customData?.fileId === "string" ? context.customData.fileId : null);
2148
- const fileId = rawFileId ? String(rawFileId) : "";
2149
- const isCliFile = isCliFileId(fileId);
2150
- const reviewRouteId = typeof context.customData?.reviewRouteId === "string" ? context.customData.reviewRouteId.trim() : "";
2151
- const rebuttalRouteId = typeof context.customData?.rebuttalRouteId === "string" ? context.customData.rebuttalRouteId.trim() : "";
2152
- const isReviewWorkspace = reviewRouteId.length > 0;
2153
- const isRebuttalWorkspace = rebuttalRouteId.length > 0;
2154
- const isResizableAnnotationWorkspace = isReviewWorkspace || isRebuttalWorkspace;
2155
- const annotationRouteId = isReviewWorkspace ? reviewRouteId : rebuttalRouteId;
2156
- const isSharedAnnotationWorkspace = annotationRouteId.startsWith("shared-");
2157
- const REVIEW_SIDEBAR_WIDTH_SCALE = isSharedAnnotationWorkspace ? 0.8 : 1;
2158
- const REVIEW_SIDEBAR_WIDTH_STORAGE_KEY = isRebuttalWorkspace ? isSharedAnnotationWorkspace ? "ds:rebuttal:pdf:annotation-sidebar-width:shared:v1" : "ds:rebuttal:pdf:annotation-sidebar-width:v1" : isSharedAnnotationWorkspace ? "ds:review:pdf:annotation-sidebar-width:shared:v1" : "ds:review:pdf:annotation-sidebar-width:v4";
2159
- const REVIEW_SIDEBAR_MIN_WIDTH = Math.round(336 * REVIEW_SIDEBAR_WIDTH_SCALE);
2160
- const REVIEW_SIDEBAR_MAX_WIDTH = Math.round(816 * REVIEW_SIDEBAR_WIDTH_SCALE);
2161
- const REVIEW_SIDEBAR_DEFAULT_WIDTH = Math.round(673 * REVIEW_SIDEBAR_WIDTH_SCALE);
2162
- const isReadOnlyMode = Boolean(
2163
- typeof context.customData?.readOnlyMode === "boolean" ? context.customData.readOnlyMode : false
2164
- );
2165
- const readOnlyHighlights = reactExports.useMemo(() => {
2166
- if (!isReadOnlyMode) return [];
2167
- const raw = context.customData?.readOnlyAnnotations;
2168
- if (!Array.isArray(raw)) return [];
2169
- return raw.map((item) => mapReadOnlyAnnotationToHighlight(item)).filter((item) => Boolean(item));
2170
- }, [context.customData?.readOnlyAnnotations, isReadOnlyMode]);
2171
- const readOnlySidebarInitRef = reactExports.useRef(false);
2172
- const fileName = context.resourceName || "Untitled.pdf";
2173
- const externalPdfUrl = typeof context.customData?.externalPdfUrl === "string" ? context.customData.externalPdfUrl.trim() : "";
2174
- const hasExternalPdfUrl = externalPdfUrl.length > 0;
2175
- const apiBaseUrl = (apiClient.defaults.baseURL || "").replace(/\/$/, "");
2176
- const isQuestFile = isQuestNodeId(fileId);
2177
- const questProjectIdFromFileId = reactExports.useMemo(
2178
- () => fileId && isQuestFile ? getQuestNodeProjectId(fileId) : null,
2179
- [fileId, isQuestFile]
2180
- );
2181
- const projectIdFromContext = reactExports.useMemo(() => {
2182
- const candidate = typeof context.customData?.projectId === "string" ? context.customData.projectId.trim() : "";
2183
- return candidate || null;
2184
- }, [context.customData]);
2185
- const [cliPdfUrl, setCliPdfUrl] = reactExports.useState(null);
2186
- const [cliPdfLoading, setCliPdfLoading] = reactExports.useState(false);
2187
- const [cliPdfError, setCliPdfError] = reactExports.useState(null);
2188
- const [questPdfUrl, setQuestPdfUrl] = reactExports.useState(null);
2189
- const [questPdfLoading, setQuestPdfLoading] = reactExports.useState(false);
2190
- const [questPdfError, setQuestPdfError] = reactExports.useState(null);
2191
- const pdfUrl = hasExternalPdfUrl ? externalPdfUrl : isCliFile ? cliPdfUrl || "" : isQuestFile ? questPdfUrl || "" : fileId ? `${apiBaseUrl}/api/v1/files/${fileId}/content` : (
2192
- // Demo: Use a sample PDF for testing
2193
- "https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf"
2194
- );
2195
- const token = typeof window !== "undefined" ? localStorage.getItem("ds_access_token") : null;
2196
- const httpHeaders = token && (hasExternalPdfUrl || !isCliFile && !isQuestFile && fileId) ? { Authorization: `Bearer ${token}` } : void 0;
2197
- const {
2198
- scale,
2199
- setScale,
2200
- currentPage,
2201
- setCurrentPage,
2202
- totalPages,
2203
- sidebarVisible,
2204
- toggleSidebar,
2205
- selectedAnnotationId,
2206
- selectAnnotation
2207
- } = useViewerState();
2208
- const updateTabPlugin = useTabsStore((state) => state.updateTabPlugin);
2209
- const findNode = useFileTreeStore((state) => state.findNode);
2210
- const updateWorkspaceTabState = useWorkspaceSurfaceStore((state) => state.updateTabState);
2211
- const addWorkspaceReference = useWorkspaceSurfaceStore((state) => state.addReference);
2212
- const updateWorkspaceReference = useWorkspaceSurfaceStore((state) => state.updateReference);
2213
- const openMarkdownViewFromHost = typeof context.customData?.onOpenMarkdownView === "function" ? context.customData.onOpenMarkdownView : null;
2214
- const openReviewOpinionViewFromHost = typeof context.customData?.onOpenReviewOpinionView === "function" ? context.customData.onOpenReviewOpinionView : null;
2215
- const reviewOpinionAvailableFromHost = Boolean(
2216
- typeof context.customData?.reviewOpinionAvailable === "boolean" ? context.customData.reviewOpinionAvailable : false
2217
- );
2218
- const reviewOpinionModeFromHost = typeof context.customData?.reviewViewMode === "string" ? context.customData.reviewViewMode : "";
2219
- const reviewOpinionActiveFromHost = reviewOpinionModeFromHost === "ai_review";
2220
- const reviewOpinionLabelFromHost = typeof context.customData?.reviewOpinionLabel === "string" ? context.customData.reviewOpinionLabel : void 0;
2221
- const reviewOpinionTitleFromHost = typeof context.customData?.reviewOpinionTitle === "string" ? context.customData.reviewOpinionTitle : void 0;
2222
- const queryClient = useQueryClient();
2223
- const { addToast } = useToast();
2224
- const arxivItems = useArxivStore((s) => s.items);
2225
- const arxivErrors = useArxivStore((s) => s.errors);
2226
- const arxivFromContext = reactExports.useMemo(() => {
2227
- const customData = context.customData;
2228
- return customData?.arxiv ?? null;
2229
- }, [context.customData]);
2230
- const isArxivPdf = Boolean(arxivFromContext) || Boolean(fileId && arxivItems.some((item) => item.fileId === fileId));
2231
- const annotationsFileId = isCliFile || !fileId || isReadOnlyMode ? "" : fileId;
2232
- const { annotations, createAnnotation, updateAnnotation, deleteAnnotation } = useAnnotations(annotationsFileId);
2233
- const currentUser = useAuthStore((s) => s.user);
2234
- const [projectId, setProjectId] = reactExports.useState(null);
2235
- const [authorColor, setAuthorColor] = reactExports.useState("#F1E9D0");
2236
- const [infoOpen, setInfoOpen] = reactExports.useState(false);
2237
- const markdownCacheRef = reactExports.useRef({});
2238
- const markdownLoadPromiseRef = reactExports.useRef({});
2239
- const showOverlayHints = true;
2240
- const [aiHighlights, setAiHighlights] = reactExports.useState([]);
2241
- const aiHighlightTimers = reactExports.useRef(/* @__PURE__ */ new Map());
2242
- const pendingAnnotationIdRef = reactExports.useRef(null);
2243
- const [flashAnnotationId, setFlashAnnotationId] = reactExports.useState(null);
2244
- const [reviewSidebarWidth, setReviewSidebarWidth] = reactExports.useState(() => {
2245
- if (typeof window === "undefined") return REVIEW_SIDEBAR_DEFAULT_WIDTH;
2246
- const raw = window.localStorage.getItem(REVIEW_SIDEBAR_WIDTH_STORAGE_KEY);
2247
- const parsed = raw ? Number(raw) : NaN;
2248
- if (!Number.isFinite(parsed)) return REVIEW_SIDEBAR_DEFAULT_WIDTH;
2249
- return Math.max(REVIEW_SIDEBAR_MIN_WIDTH, Math.min(REVIEW_SIDEBAR_MAX_WIDTH, parsed));
2250
- });
2251
- const reviewSidebarWidthRef = reactExports.useRef(reviewSidebarWidth);
2252
- const flashTimerRef = reactExports.useRef(null);
2253
- const sidebarVisibleRef = reactExports.useRef(sidebarVisible);
2254
- const toggleSidebarRef = reactExports.useRef(toggleSidebar);
2255
- const scrollViewerTo = reactExports.useRef(() => {
2256
- });
2257
- const pdfHighlighterRef = reactExports.useRef(null);
2258
- const socketRef = reactExports.useRef(null);
2259
- const joinedRef = reactExports.useRef(false);
2260
- const shouldRedirectToNotebook = isMarkdownFileName(fileName);
2261
- reactExports.useEffect(() => {
2262
- reviewSidebarWidthRef.current = reviewSidebarWidth;
2263
- }, [reviewSidebarWidth]);
2264
- reactExports.useEffect(() => {
2265
- if (!isResizableAnnotationWorkspace || typeof window === "undefined") return;
2266
- window.localStorage.setItem(REVIEW_SIDEBAR_WIDTH_STORAGE_KEY, String(reviewSidebarWidth));
2267
- }, [REVIEW_SIDEBAR_WIDTH_STORAGE_KEY, isResizableAnnotationWorkspace, reviewSidebarWidth]);
2268
- const clampReviewSidebarWidth = reactExports.useCallback((rawWidth) => {
2269
- const viewportMax = typeof window !== "undefined" ? Math.max(REVIEW_SIDEBAR_MIN_WIDTH, Math.min(REVIEW_SIDEBAR_MAX_WIDTH, Math.floor(window.innerWidth * 0.75))) : REVIEW_SIDEBAR_MAX_WIDTH;
2270
- return Math.max(REVIEW_SIDEBAR_MIN_WIDTH, Math.min(viewportMax, rawWidth));
2271
- }, [REVIEW_SIDEBAR_MAX_WIDTH, REVIEW_SIDEBAR_MIN_WIDTH]);
2272
- const handleReviewSidebarResizePointerDown = reactExports.useCallback(
2273
- (event) => {
2274
- if (!isResizableAnnotationWorkspace) return;
2275
- if (event.button !== 0) return;
2276
- event.preventDefault();
2277
- event.stopPropagation();
2278
- const startX = event.clientX;
2279
- const startWidth = reviewSidebarWidthRef.current;
2280
- document.body.style.cursor = "col-resize";
2281
- document.body.style.userSelect = "none";
2282
- const handleMouseMove = (moveEvent) => {
2283
- const delta = startX - moveEvent.clientX;
2284
- setReviewSidebarWidth(clampReviewSidebarWidth(startWidth + delta));
2285
- };
2286
- const handleMouseUp = () => {
2287
- document.body.style.cursor = "";
2288
- document.body.style.userSelect = "";
2289
- document.removeEventListener("mousemove", handleMouseMove);
2290
- document.removeEventListener("mouseup", handleMouseUp);
2291
- };
2292
- document.addEventListener("mousemove", handleMouseMove);
2293
- document.addEventListener("mouseup", handleMouseUp);
2294
- },
2295
- [clampReviewSidebarWidth, isResizableAnnotationWorkspace]
2296
- );
2297
- const arxivPaper = reactExports.useMemo(() => {
2298
- if (arxivFromContext) return arxivFromContext;
2299
- if (!fileId) return null;
2300
- return arxivItems.find((item) => item.fileId === fileId) || null;
2301
- }, [arxivFromContext, arxivItems, fileId]);
2302
- const arxivError = arxivPaper ? arxivErrors[arxivPaper.arxivId] : void 0;
2303
- const supportsMarkdownView = Boolean(
2304
- fileId && !isCliFile && (!isArxivPdf || arxivPaper?.overview?.trim() || arxivPaper?.abstract?.trim())
2305
- );
2306
- const markdownButtonLabel = isArxivPdf ? t("summary") : t("markdown");
2307
- const markdownButtonTitle = isArxivPdf ? t("open_summary") : t("open_markdown");
2308
- reactExports.useEffect(() => {
2309
- sidebarVisibleRef.current = sidebarVisible;
2310
- }, [sidebarVisible]);
2311
- reactExports.useEffect(() => {
2312
- toggleSidebarRef.current = toggleSidebar;
2313
- }, [toggleSidebar]);
2314
- const handleCopyBibtex = reactExports.useCallback(async () => {
2315
- if (!arxivPaper) return;
2316
- const bibtex = generateBibTeX(arxivPaper);
2317
- const success = await copyToClipboard(bibtex);
2318
- addToast({
2319
- type: success ? "success" : "error",
2320
- title: success ? t("toast_bibtex_copied_title") : t("toast_copy_failed_title"),
2321
- description: success ? arxivPaper.title || arxivPaper.arxivId : t("try_again"),
2322
- duration: 1800
2323
- });
2324
- }, [addToast, arxivPaper, t]);
2325
- const handleOpenArxiv = reactExports.useCallback(() => {
2326
- if (!arxivPaper?.arxivId) return;
2327
- window.open(`https://arxiv.org/abs/${arxivPaper.arxivId}`, "_blank", "noopener,noreferrer");
2328
- }, [arxivPaper]);
2329
- const resolveWorkspaceResourcePath = reactExports.useCallback(() => {
2330
- const pathHint = String(context.resourcePath || "").trim();
2331
- if (pathHint.startsWith("/FILES")) return pathHint;
2332
- if (fileId) {
2333
- const node = findNode(fileId);
2334
- if (node?.path) return toFilesResourcePath(node.path);
2335
- }
2336
- if (pathHint) return toFilesResourcePath(pathHint);
2337
- return void 0;
2338
- }, [context.resourcePath, fileId, findNode]);
2339
- const loadPdfMarkdown = reactExports.useCallback(async () => {
2340
- if (!fileId || isCliFile || isArxivPdf) return null;
2341
- const cached = markdownCacheRef.current[fileId];
2342
- if (cached) return cached;
2343
- const inflight = markdownLoadPromiseRef.current[fileId];
2344
- if (inflight) return inflight;
2345
- const request = apiClient.get(`/api/v1/pdf/markdown/${fileId}`, {
2346
- responseType: "text",
2347
- transformResponse: [(value) => value]
2348
- }).then((response) => {
2349
- const markdown = typeof response.data === "string" ? response.data : String(response.data || "");
2350
- if (markdown) {
2351
- markdownCacheRef.current[fileId] = markdown;
2352
- return markdown;
2353
- }
2354
- return null;
2355
- }).finally(() => {
2356
- delete markdownLoadPromiseRef.current[fileId];
2357
- });
2358
- markdownLoadPromiseRef.current[fileId] = request;
2359
- return request;
2360
- }, [fileId, isArxivPdf, isCliFile]);
2361
- const enrichReferenceWithMarkdown = reactExports.useCallback(
2362
- async (referenceId, selectedText) => {
2363
- if (!fileId || isCliFile || isArxivPdf) return;
2364
- try {
2365
- const markdown = await loadPdfMarkdown();
2366
- const excerpt = locateMarkdownExcerpt(markdown || "", selectedText);
2367
- updateWorkspaceReference(referenceId, {
2368
- markdownExcerpt: excerpt || void 0,
2369
- excerptStatus: excerpt ? "ready" : "error"
2370
- });
2371
- } catch {
2372
- updateWorkspaceReference(referenceId, {
2373
- excerptStatus: "error"
2374
- });
2375
- }
2376
- },
2377
- [fileId, isArxivPdf, isCliFile, loadPdfMarkdown, updateWorkspaceReference]
2378
- );
2379
- reactExports.useEffect(() => {
2380
- if (!fileId || isCliFile || isArxivPdf) return;
2381
- void loadPdfMarkdown().catch(() => void 0);
2382
- }, [fileId, isArxivPdf, isCliFile, loadPdfMarkdown]);
2383
- const handleAskCopilotFromSelection = reactExports.useCallback(
2384
- (content, position) => {
2385
- const selectedText = String(content?.text || "").trim();
2386
- if (!selectedText) return;
2387
- const referenceId = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" ? crypto.randomUUID() : `pdf-ref-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
2388
- const fallbackRect = position.boundingRect ? [
2389
- {
2390
- left: position.boundingRect.x1,
2391
- top: position.boundingRect.y1,
2392
- width: position.boundingRect.width,
2393
- height: position.boundingRect.height,
2394
- pageNumber: position.boundingRect.pageNumber
2395
- }
2396
- ] : [];
2397
- addWorkspaceReference({
2398
- id: referenceId,
2399
- kind: "pdf_text",
2400
- tabId,
2401
- fileId: fileId || void 0,
2402
- resourceId: fileId || void 0,
2403
- resourcePath: resolveWorkspaceResourcePath(),
2404
- resourceName: fileName,
2405
- pageNumber: position.pageNumber,
2406
- selectedText,
2407
- markdownExcerpt: void 0,
2408
- excerptStatus: fileId && !isCliFile && !isArxivPdf ? "loading" : "idle",
2409
- rects: position.rects?.map((rect) => ({
2410
- left: rect.x1,
2411
- top: rect.y1,
2412
- width: rect.width,
2413
- height: rect.height,
2414
- pageNumber: rect.pageNumber
2415
- })) || fallbackRect,
2416
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
2417
- });
2418
- window.dispatchEvent(new CustomEvent("ds:copilot:focus", { detail: { focus: true } }));
2419
- if (fileId && !isCliFile && !isArxivPdf) {
2420
- void enrichReferenceWithMarkdown(referenceId, selectedText);
2421
- }
2422
- },
2423
- [
2424
- addWorkspaceReference,
2425
- enrichReferenceWithMarkdown,
2426
- fileId,
2427
- fileName,
2428
- isArxivPdf,
2429
- isCliFile,
2430
- resolveWorkspaceResourcePath,
2431
- tabId
2432
- ]
2433
- );
2434
- reactExports.useEffect(() => {
2435
- if (!shouldRedirectToNotebook) return;
2436
- updateTabPlugin(tabId, BUILTIN_PLUGINS.NOTEBOOK, {
2437
- ...context,
2438
- resourceId: fileId,
2439
- resourceName: fileName,
2440
- mimeType: context.mimeType ?? "text/markdown",
2441
- customData: {
2442
- ...context.customData || {},
2443
- docKind: "markdown"
2444
- }
2445
- });
2446
- }, [context, fileId, fileName, shouldRedirectToNotebook, tabId, updateTabPlugin]);
2447
- reactExports.useEffect(() => {
2448
- setTitle(fileName);
2449
- }, [fileName, setTitle]);
2450
- reactExports.useEffect(() => {
2451
- setDirty(false);
2452
- }, [setDirty]);
2453
- reactExports.useEffect(() => {
2454
- updateWorkspaceTabState(tabId, {
2455
- contentKind: "pdf",
2456
- documentMode: "pdf",
2457
- pageNumber: currentPage,
2458
- isReadOnly: isReadOnlyMode
2459
- });
2460
- }, [currentPage, isReadOnlyMode, tabId, updateWorkspaceTabState]);
2461
- reactExports.useEffect(() => {
2462
- const resolvedProjectId = projectIdFromContext || questProjectIdFromFileId;
2463
- if (resolvedProjectId) {
2464
- setProjectId(resolvedProjectId);
2465
- return;
2466
- }
2467
- if (!fileId || isCliFile) return;
2468
- getFile(fileId).then((f) => {
2469
- if (f?.project_id) setProjectId(String(f.project_id));
2470
- }).catch(() => {
2471
- });
2472
- }, [fileId, hasExternalPdfUrl, isCliFile, projectIdFromContext, questProjectIdFromFileId]);
2473
- reactExports.useEffect(() => {
2474
- if (!fileId || !isCliFile) {
2475
- setCliPdfUrl(null);
2476
- setCliPdfLoading(false);
2477
- setCliPdfError(null);
2478
- return;
2479
- }
2480
- let cancelled = false;
2481
- let objectUrl = null;
2482
- setCliPdfLoading(true);
2483
- setCliPdfError(null);
2484
- const loadCliPdf = async () => {
2485
- try {
2486
- const { createFileObjectUrl: createFileObjectUrl2 } = await __vitePreload(async () => { const { createFileObjectUrl: createFileObjectUrl2 } = await import('./index-D_E4281X.js').then(n => n.ep);return { createFileObjectUrl: createFileObjectUrl2 }},true?__vite__mapDeps([0,1]):void 0);
2487
- objectUrl = await createFileObjectUrl2(fileId);
2488
- if (cancelled) {
2489
- URL.revokeObjectURL(objectUrl);
2490
- return;
2491
- }
2492
- setCliPdfUrl(objectUrl);
2493
- } catch (err) {
2494
- if (!cancelled) {
2495
- setCliPdfError("Failed to load PDF from CLI server.");
2496
- }
2497
- } finally {
2498
- if (!cancelled) {
2499
- setCliPdfLoading(false);
2500
- }
2501
- }
2502
- };
2503
- loadCliPdf();
2504
- return () => {
2505
- cancelled = true;
2506
- if (objectUrl) {
2507
- URL.revokeObjectURL(objectUrl);
2508
- }
2509
- };
2510
- }, [fileId, isCliFile]);
2511
- reactExports.useEffect(() => {
2512
- if (!fileId || !isQuestFile || isCliFile) {
2513
- setQuestPdfUrl(null);
2514
- setQuestPdfLoading(false);
2515
- setQuestPdfError(null);
2516
- return;
2517
- }
2518
- let cancelled = false;
2519
- let objectUrl = null;
2520
- setQuestPdfLoading(true);
2521
- setQuestPdfError(null);
2522
- const loadQuestPdf = async () => {
2523
- try {
2524
- objectUrl = await createFileObjectUrl(fileId);
2525
- if (cancelled) {
2526
- URL.revokeObjectURL(objectUrl);
2527
- return;
2528
- }
2529
- setQuestPdfUrl(objectUrl);
2530
- } catch (err) {
2531
- if (!cancelled) {
2532
- setQuestPdfError("Failed to load PDF document.");
2533
- }
2534
- } finally {
2535
- if (!cancelled) {
2536
- setQuestPdfLoading(false);
2537
- }
2538
- }
2539
- };
2540
- void loadQuestPdf();
2541
- return () => {
2542
- cancelled = true;
2543
- if (objectUrl) {
2544
- URL.revokeObjectURL(objectUrl);
2545
- }
2546
- };
2547
- }, [fileId, isCliFile, isQuestFile]);
2548
- reactExports.useEffect(() => {
2549
- if (!projectId) return;
2550
- const userId = currentUser?.id;
2551
- if (!userId) return;
2552
- listProjectMembers(projectId).then((members) => {
2553
- const me = members.find((m) => m.user_id === userId);
2554
- if (me?.annotation_color) setAuthorColor(String(me.annotation_color));
2555
- }).catch(() => {
2556
- });
2557
- }, [projectId, currentUser?.id]);
2558
- reactExports.useEffect(() => {
2559
- if (!projectId || !fileId || isCliFile || isReadOnlyMode) return;
2560
- const { socket, release } = acquireFileSocket();
2561
- socketRef.current = { socket, release };
2562
- const joinIfReady = async () => {
2563
- if (joinedRef.current) return;
2564
- if (!socket.connected) return;
2565
- try {
2566
- joinedRef.current = true;
2567
- await socket.emitWithAck("file:join", { projectId, fileId, clientVersion: "1.0.0" });
2568
- } catch {
2569
- joinedRef.current = false;
2570
- }
2571
- };
2572
- const onConnect = () => void joinIfReady();
2573
- const onDisconnect = () => {
2574
- joinedRef.current = false;
2575
- };
2576
- const onAnnotationEvent = (payload) => {
2577
- if (payload?.fileId && String(payload.fileId) !== String(fileId)) return;
2578
- queryClient.invalidateQueries({ queryKey: annotationKeys.list(fileId) });
2579
- const incomingAnnotationId = typeof payload?.annotationId === "string" && payload.annotationId.trim() ? payload.annotationId.trim() : null;
2580
- if (incomingAnnotationId) {
2581
- pendingAnnotationIdRef.current = incomingAnnotationId;
2582
- if (!sidebarVisibleRef.current) {
2583
- toggleSidebarRef.current?.();
2584
- }
2585
- }
2586
- };
2587
- socket.on("connect", onConnect);
2588
- socket.on("disconnect", onDisconnect);
2589
- socket.on("annotation:created", onAnnotationEvent);
2590
- socket.on("annotation:updated", onAnnotationEvent);
2591
- socket.on("annotation:deleted", onAnnotationEvent);
2592
- if (socket.connected) void joinIfReady();
2593
- return () => {
2594
- try {
2595
- socket.off("connect", onConnect);
2596
- socket.off("disconnect", onDisconnect);
2597
- socket.off("annotation:created", onAnnotationEvent);
2598
- socket.off("annotation:updated", onAnnotationEvent);
2599
- socket.off("annotation:deleted", onAnnotationEvent);
2600
- if (joinedRef.current) {
2601
- void socket.emit("file:leave", { projectId, fileId });
2602
- }
2603
- } finally {
2604
- joinedRef.current = false;
2605
- socketRef.current = null;
2606
- release();
2607
- }
2608
- };
2609
- }, [projectId, fileId, isCliFile, isReadOnlyMode]);
2610
- const highlights = reactExports.useMemo(() => {
2611
- if (isReadOnlyMode) {
2612
- return readOnlyHighlights;
2613
- }
2614
- return annotations.map((a) => ({
2615
- id: a.id,
2616
- position: a.position,
2617
- content: a.content,
2618
- comment: { text: a.comment || "", emoji: a.kind },
2619
- color: a.color,
2620
- author: a.author ?? null,
2621
- tags: a.tags,
2622
- previewSummary: typeof a.content?.summary === "string" ? String(a.content.summary || "").trim() || null : null
2623
- }));
2624
- }, [annotations, isReadOnlyMode, readOnlyHighlights]);
2625
- const mergedHighlights = reactExports.useMemo(
2626
- () => [...highlights, ...aiHighlights],
2627
- [highlights, aiHighlights]
2628
- );
2629
- reactExports.useEffect(() => {
2630
- const pendingId = pendingAnnotationIdRef.current;
2631
- if (!pendingId) return;
2632
- const highlight = highlights.find((h) => h.id === pendingId);
2633
- if (!highlight) return;
2634
- selectAnnotation(highlight.id);
2635
- setCurrentPage(highlight.position.pageNumber);
2636
- scrollViewerTo.current(highlight);
2637
- pendingAnnotationIdRef.current = null;
2638
- setFlashAnnotationId(highlight.id);
2639
- if (flashTimerRef.current) clearTimeout(flashTimerRef.current);
2640
- flashTimerRef.current = setTimeout(() => setFlashAnnotationId(null), 2200);
2641
- }, [highlights, selectAnnotation, setCurrentPage]);
2642
- reactExports.useEffect(() => {
2643
- const highlighter = pdfHighlighterRef.current;
2644
- return () => {
2645
- highlighter?.setPinnedTipForHighlight(null);
2646
- if (flashTimerRef.current) clearTimeout(flashTimerRef.current);
2647
- };
2648
- }, []);
2649
- reactExports.useEffect(() => {
2650
- if (!isReadOnlyMode) {
2651
- readOnlySidebarInitRef.current = false;
2652
- return;
2653
- }
2654
- if (readOnlySidebarInitRef.current) return;
2655
- if (highlights.length === 0) return;
2656
- readOnlySidebarInitRef.current = true;
2657
- if (!sidebarVisible) {
2658
- toggleSidebar();
2659
- }
2660
- }, [highlights.length, isReadOnlyMode, sidebarVisible, toggleSidebar]);
2661
- const scrollToPage = reactExports.useCallback(
2662
- (page) => {
2663
- setCurrentPage(page);
2664
- pdfHighlighterRef.current?.scrollToPage(page);
2665
- },
2666
- [setCurrentPage]
2667
- );
2668
- const addGhostHighlight = reactExports.useCallback(
2669
- (detail) => {
2670
- const id = `ai-ghost-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
2671
- const rects = detail.rects.map((rect) => ({
2672
- ...rect,
2673
- width: rect.width ?? 100,
2674
- height: rect.height ?? 100,
2675
- pageNumber: rect.pageNumber ?? detail.page
2676
- }));
2677
- const boundingRect = {
2678
- ...detail.boundingRect,
2679
- width: detail.boundingRect.width ?? 100,
2680
- height: detail.boundingRect.height ?? 100,
2681
- pageNumber: detail.boundingRect.pageNumber ?? detail.page
2682
- };
2683
- const ghostHighlight = {
2684
- id,
2685
- position: {
2686
- boundingRect,
2687
- rects,
2688
- pageNumber: detail.page
2689
- },
2690
- content: { text: detail.text || "" },
2691
- comment: { text: "", emoji: "note" },
2692
- color: detail.color || "#3F5A6B",
2693
- __dsGhost: true,
2694
- __dsGhostOpacity: 1,
2695
- __dsGhostMode: detail.mode || "guide"
2696
- };
2697
- setAiHighlights((prev) => [...prev, ghostHighlight]);
2698
- const duration = detail.durationMs ?? 3e3;
2699
- const fadeTimer = setTimeout(() => {
2700
- setAiHighlights(
2701
- (prev) => prev.map((h) => h.id === id ? { ...h, __dsGhostOpacity: 0 } : h)
2702
- );
2703
- }, duration);
2704
- const removeTimer = setTimeout(() => {
2705
- setAiHighlights((prev) => prev.filter((h) => h.id !== id));
2706
- aiHighlightTimers.current.delete(id);
2707
- aiHighlightTimers.current.delete(`${id}:fade`);
2708
- }, duration + 800);
2709
- aiHighlightTimers.current.set(id, removeTimer);
2710
- aiHighlightTimers.current.set(`${id}:fade`, fadeTimer);
2711
- },
2712
- []
2713
- );
2714
- reactExports.useEffect(() => {
2715
- const timers = aiHighlightTimers.current;
2716
- return () => {
2717
- timers.forEach((timer) => clearTimeout(timer));
2718
- timers.clear();
2719
- };
2720
- }, []);
2721
- const shouldSkipEffect = reactExports.useCallback((detail) => {
2722
- const effectId = typeof detail?.__dsEffectId === "string" ? detail.__dsEffectId : null;
2723
- if (!effectId) return false;
2724
- if (isPdfEffectHandled(effectId)) return true;
2725
- markPdfEffectHandled(effectId);
2726
- return false;
2727
- }, []);
2728
- const applyNavigate = reactExports.useCallback(
2729
- (detail) => {
2730
- if (!detail || detail.fileId !== fileId) return;
2731
- if (shouldSkipEffect(detail)) return;
2732
- queryClient.invalidateQueries({ queryKey: annotationKeys.list(fileId) });
2733
- if (detail.page) {
2734
- scrollToPage(detail.page);
2735
- }
2736
- if (detail.rects && detail.boundingRect) {
2737
- addGhostHighlight({
2738
- page: detail.page || 1,
2739
- rects: detail.rects,
2740
- boundingRect: detail.boundingRect,
2741
- color: detail.color,
2742
- mode: detail.mode || "guide",
2743
- durationMs: detail.durationMs,
2744
- text: detail.text
2745
- });
2746
- }
2747
- if (detail.annotationId) {
2748
- pendingAnnotationIdRef.current = detail.annotationId;
2749
- }
2750
- if ((detail.annotationId || detail.__dsEffectId) && !sidebarVisible) {
2751
- toggleSidebar();
2752
- }
2753
- },
2754
- [addGhostHighlight, fileId, queryClient, scrollToPage, sidebarVisible, toggleSidebar, shouldSkipEffect]
2755
- );
2756
- const applyAnnotationCreated = reactExports.useCallback(
2757
- (detail) => {
2758
- if (!detail || detail.fileId !== fileId) return;
2759
- if (shouldSkipEffect(detail)) return;
2760
- queryClient.invalidateQueries({ queryKey: annotationKeys.list(fileId) });
2761
- if (detail.page) {
2762
- scrollToPage(detail.page);
2763
- }
2764
- if (detail.annotationId) {
2765
- pendingAnnotationIdRef.current = detail.annotationId;
2766
- if (!sidebarVisible) {
2767
- toggleSidebar();
2768
- }
2769
- }
2770
- },
2771
- [fileId, queryClient, scrollToPage, sidebarVisible, toggleSidebar, shouldSkipEffect]
2772
- );
2773
- const processQueuedEffects = reactExports.useCallback(() => {
2774
- if (!fileId) return;
2775
- const queued = consumePdfEffects(fileId);
2776
- if (queued.length === 0) return;
2777
- queued.forEach((effect) => {
2778
- if (effect.name === "pdf:jump") {
2779
- applyNavigate(effect.data);
2780
- } else {
2781
- applyAnnotationCreated(effect.data);
2782
- }
2783
- });
2784
- }, [applyAnnotationCreated, applyNavigate, fileId]);
2785
- reactExports.useEffect(() => {
2786
- if (!fileId) return;
2787
- processQueuedEffects();
2788
- }, [fileId, processQueuedEffects]);
2789
- reactExports.useEffect(() => {
2790
- if (typeof window === "undefined") return;
2791
- if (!fileId) return;
2792
- const handleNavigate = (event) => {
2793
- const detail = event.detail;
2794
- applyNavigate(detail);
2795
- };
2796
- const handleAnnotationCreated = (event) => {
2797
- const detail = event.detail;
2798
- applyAnnotationCreated(detail);
2799
- };
2800
- const handleQueue = (event) => {
2801
- const detail = event.detail;
2802
- if (!detail || detail.fileId !== fileId) return;
2803
- processQueuedEffects();
2804
- };
2805
- window.addEventListener("pdf:navigate", handleNavigate);
2806
- window.addEventListener("pdf:annotation_created", handleAnnotationCreated);
2807
- window.addEventListener("ds:pdf:queue", handleQueue);
2808
- return () => {
2809
- window.removeEventListener("pdf:navigate", handleNavigate);
2810
- window.removeEventListener("pdf:annotation_created", handleAnnotationCreated);
2811
- window.removeEventListener("ds:pdf:queue", handleQueue);
2812
- };
2813
- }, [applyAnnotationCreated, applyNavigate, fileId, processQueuedEffects]);
2814
- const addHighlight = reactExports.useCallback(
2815
- async (highlight) => {
2816
- if (isReadOnlyMode || isCliFile || !fileId) return;
2817
- try {
2818
- const kind = normalizeKind(highlight.comment?.emoji);
2819
- const created = await createAnnotation({
2820
- fileId,
2821
- position: highlight.position,
2822
- content: highlight.content,
2823
- comment: highlight.comment?.text || "",
2824
- kind,
2825
- tags: []
2826
- });
2827
- if (created?.id && !sidebarVisible) {
2828
- toggleSidebar();
2829
- }
2830
- } catch (e) {
2831
- console.warn("[PdfViewerPlugin] Failed to create annotation:", e);
2832
- }
2833
- },
2834
- [createAnnotation, fileId, isCliFile, isReadOnlyMode, sidebarVisible, toggleSidebar]
2835
- );
2836
- const updateHighlight = reactExports.useCallback(
2837
- async (highlightId, position, content) => {
2838
- if (isReadOnlyMode || isCliFile || !fileId) return;
2839
- try {
2840
- const existing = annotations.find((a) => a.id === highlightId);
2841
- if (!existing) return;
2842
- const nextPosition = {
2843
- ...existing.position,
2844
- ...position,
2845
- boundingRect: {
2846
- ...existing.position.boundingRect,
2847
- ...position.boundingRect
2848
- }
2849
- };
2850
- const nextContent = { ...existing.content, ...content };
2851
- await updateAnnotation(highlightId, { position: nextPosition, content: nextContent });
2852
- } catch (e) {
2853
- console.warn("[PdfViewerPlugin] Failed to update annotation position/content:", e);
2854
- }
2855
- },
2856
- [annotations, fileId, isCliFile, isReadOnlyMode, updateAnnotation]
2857
- );
2858
- const handleSelectHighlight = reactExports.useCallback(
2859
- (id, options) => {
2860
- const highlight = highlights.find((h) => h.id === id);
2861
- if (!highlight) return;
2862
- const allowToggleClose = options?.allowToggleClose !== false;
2863
- const skipAutoScroll = options?.skipAutoScroll === true;
2864
- const isToggleClose = allowToggleClose && selectedAnnotationId === id;
2865
- if (isToggleClose) {
2866
- selectAnnotation(null);
2867
- pdfHighlighterRef.current?.setPinnedTipForHighlight(null);
2868
- return;
2869
- }
2870
- if (!sidebarVisible) {
2871
- toggleSidebar();
2872
- }
2873
- selectAnnotation(id);
2874
- pdfHighlighterRef.current?.setPinnedTipForHighlight(null);
2875
- if (!skipAutoScroll) {
2876
- setCurrentPage(highlight.position.pageNumber);
2877
- scrollViewerTo.current(highlight);
2878
- }
2879
- },
2880
- [
2881
- highlights,
2882
- selectedAnnotationId,
2883
- sidebarVisible,
2884
- selectAnnotation,
2885
- setCurrentPage,
2886
- toggleSidebar
2887
- ]
2888
- );
2889
- const handlePdfBackgroundPointerDown = reactExports.useCallback(
2890
- (event) => {
2891
- const target = event.target;
2892
- if (!target) return;
2893
- if (target.closest("[data-ds-highlight-target='true']") || target.closest("[data-ds-overlay-hint='true']")) {
2894
- return;
2895
- }
2896
- if (!selectedAnnotationId) return;
2897
- selectAnnotation(null);
2898
- pdfHighlighterRef.current?.setPinnedTipForHighlight(null);
2899
- },
2900
- [selectedAnnotationId, selectAnnotation]
2901
- );
2902
- const handleDeleteHighlight = reactExports.useCallback(
2903
- async (id) => {
2904
- if (isReadOnlyMode || isCliFile || !fileId) return;
2905
- try {
2906
- await deleteAnnotation(id);
2907
- if (selectedAnnotationId === id) {
2908
- selectAnnotation(null);
2909
- }
2910
- } catch (e) {
2911
- console.warn("[PdfViewerPlugin] Failed to delete annotation:", e);
2912
- }
2913
- },
2914
- [deleteAnnotation, fileId, isCliFile, isReadOnlyMode, selectedAnnotationId, selectAnnotation]
2915
- );
2916
- const handleUpdateKind = reactExports.useCallback(
2917
- async (id, kind) => {
2918
- if (isReadOnlyMode || isCliFile || !fileId) return;
2919
- try {
2920
- await updateAnnotation(id, { kind });
2921
- } catch (e) {
2922
- console.warn("[PdfViewerPlugin] Failed to update annotation kind:", e);
2923
- }
2924
- },
2925
- [fileId, isCliFile, isReadOnlyMode, updateAnnotation]
2926
- );
2927
- const handleUpdateComment = reactExports.useCallback(
2928
- async (id, comment) => {
2929
- if (isReadOnlyMode || isCliFile || !fileId) return;
2930
- try {
2931
- await updateAnnotation(id, { comment });
2932
- } catch (e) {
2933
- console.warn("[PdfViewerPlugin] Failed to update annotation comment:", e);
2934
- }
2935
- },
2936
- [fileId, isCliFile, isReadOnlyMode, updateAnnotation]
2937
- );
2938
- const handleDownload = reactExports.useCallback(() => {
2939
- if (!fileId) {
2940
- window.open(pdfUrl, "_blank");
2941
- return;
2942
- }
2943
- (async () => {
2944
- const { downloadFileById } = await __vitePreload(async () => { const { downloadFileById } = await import('./index-D_E4281X.js').then(n => n.ep);return { downloadFileById }},true?__vite__mapDeps([0,1]):void 0);
2945
- await downloadFileById(fileId, fileName);
2946
- })();
2947
- }, [fileId, fileName, pdfUrl]);
2948
- const handleOpenMarkdown = reactExports.useCallback(() => {
2949
- if (openMarkdownViewFromHost) {
2950
- openMarkdownViewFromHost();
2951
- return;
2952
- }
2953
- if (!fileId || isCliFile) return;
2954
- updateTabPlugin(tabId, BUILTIN_PLUGINS.PDF_MARKDOWN, {
2955
- ...context,
2956
- customData: {
2957
- ...context.customData || {},
2958
- pdfView: isArxivPdf ? "summary" : "markdown"
2959
- }
2960
- });
2961
- }, [context, fileId, isArxivPdf, isCliFile, openMarkdownViewFromHost, tabId, updateTabPlugin]);
2962
- if (shouldRedirectToNotebook) {
2963
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex h-full items-center justify-center bg-muted/20", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-muted-foreground", children: "Switching to Markdown editor..." }) });
2964
- }
2965
- if (isCliFile && (cliPdfLoading || cliPdfError) && !cliPdfUrl) {
2966
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-full flex items-center justify-center bg-background", children: cliPdfError ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-muted-foreground", children: cliPdfError }) : /* @__PURE__ */ jsxRuntimeExports.jsx(PdfSpinner, {}) });
2967
- }
2968
- if (isQuestFile && (questPdfLoading || questPdfError) && !questPdfUrl) {
2969
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-full flex items-center justify-center bg-background", children: questPdfError ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm text-muted-foreground", children: questPdfError }) : /* @__PURE__ */ jsxRuntimeExports.jsx(PdfSpinner, {}) });
2970
- }
2971
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-full flex flex-col bg-background", children: [
2972
- /* @__PURE__ */ jsxRuntimeExports.jsx(
2973
- Toolbar,
2974
- {
2975
- scale,
2976
- onScaleChange: setScale,
2977
- currentPage,
2978
- totalPages,
2979
- onPageChange: scrollToPage,
2980
- sidebarVisible,
2981
- onSidebarToggle: toggleSidebar,
2982
- onDownload: handleDownload,
2983
- onInfo: arxivPaper ? () => setInfoOpen(true) : void 0,
2984
- onMarkdownToggle: openMarkdownViewFromHost || supportsMarkdownView ? handleOpenMarkdown : void 0,
2985
- markdownLabel: markdownButtonLabel,
2986
- markdownTitle: markdownButtonTitle,
2987
- reviewOpinionActive: reviewOpinionActiveFromHost,
2988
- onReviewOpinionToggle: reviewOpinionAvailableFromHost && openReviewOpinionViewFromHost ? openReviewOpinionViewFromHost : void 0,
2989
- reviewOpinionLabel: reviewOpinionLabelFromHost,
2990
- reviewOpinionTitle: reviewOpinionTitleFromHost
2991
- }
2992
- ),
2993
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex overflow-hidden bg-muted/10", children: [
2994
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-hidden flex", children: /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "relative h-full w-full", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
2995
- "div",
2996
- {
2997
- className: "relative h-full overflow-hidden border border-border bg-background shadow-soft-card",
2998
- onPointerDown: handlePdfBackgroundPointerDown,
2999
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(
3000
- PdfLoader,
3001
- {
3002
- url: pdfUrl,
3003
- httpHeaders,
3004
- workerSrc: PDF_WORKER_SRC,
3005
- cMapUrl: PDF_CMAP_URL,
3006
- cMapPacked: PDF_CMAP_PACKED,
3007
- beforeLoad: /* @__PURE__ */ jsxRuntimeExports.jsx(PdfSpinner, {}),
3008
- children: (pdfDocument) => /* @__PURE__ */ jsxRuntimeExports.jsx(
3009
- PdfHighlighterSurface,
3010
- {
3011
- pdfDocument,
3012
- scale,
3013
- highlights: mergedHighlights,
3014
- selectedId: selectedAnnotationId,
3015
- onSelect: handleSelectHighlight,
3016
- onAdd: addHighlight,
3017
- onUpdate: updateHighlight,
3018
- onScrollRef: (scrollTo) => {
3019
- scrollViewerTo.current = scrollTo;
3020
- },
3021
- onPageChange: (pageNumber) => setCurrentPage(pageNumber),
3022
- pdfHighlighterRef,
3023
- authorColor,
3024
- authorHandle: (currentUser?.email ? currentUser.email.split("@", 1)[0] : "") || "user",
3025
- showOverlayHints,
3026
- tipPlacementMode: isResizableAnnotationWorkspace ? "right" : "auto",
3027
- readOnly: isReadOnlyMode,
3028
- onAskCopilot: handleAskCopilotFromSelection
3029
- }
3030
- )
3031
- }
3032
- )
3033
- }
3034
- ) }) }),
3035
- sidebarVisible && /* @__PURE__ */ jsxRuntimeExports.jsxs(
3036
- "div",
3037
- {
3038
- className: cn(
3039
- "relative border-l bg-background/60 backdrop-blur flex-shrink-0",
3040
- isResizableAnnotationWorkspace ? "" : "w-72"
3041
- ),
3042
- style: isResizableAnnotationWorkspace ? { width: reviewSidebarWidth } : void 0,
3043
- children: [
3044
- isResizableAnnotationWorkspace ? /* @__PURE__ */ jsxRuntimeExports.jsx(
3045
- "div",
3046
- {
3047
- role: "separator",
3048
- "aria-orientation": "vertical",
3049
- "aria-label": t("resize_annotation_sidebar"),
3050
- tabIndex: 0,
3051
- className: "group absolute inset-y-0 left-0 z-20 w-3 -translate-x-1/2 cursor-ew-resize touch-none",
3052
- onPointerDown: handleReviewSidebarResizePointerDown,
3053
- children: /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "pointer-events-none absolute top-1/2 left-1/2 h-16 w-[2px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-black/20 opacity-0 transition-opacity group-hover:opacity-100" })
3054
- }
3055
- ) : null,
3056
- /* @__PURE__ */ jsxRuntimeExports.jsx(
3057
- HighlightsSidebar,
3058
- {
3059
- highlights,
3060
- selectedId: selectedAnnotationId,
3061
- currentPage,
3062
- flashId: flashAnnotationId,
3063
- onSelect: handleSelectHighlight,
3064
- onDelete: handleDeleteHighlight,
3065
- onUpdateKind: handleUpdateKind,
3066
- onUpdateComment: handleUpdateComment,
3067
- showOverlayHints,
3068
- readOnly: isReadOnlyMode
3069
- }
3070
- )
3071
- ]
3072
- }
3073
- )
3074
- ] }),
3075
- /* @__PURE__ */ jsxRuntimeExports.jsx(
3076
- ArxivInfoModal,
3077
- {
3078
- open: infoOpen,
3079
- onClose: () => setInfoOpen(false),
3080
- paper: arxivPaper,
3081
- errorCode: arxivError,
3082
- onCopyBibtex: arxivPaper ? handleCopyBibtex : void 0,
3083
- onOpenArxiv: arxivPaper ? handleOpenArxiv : void 0
3084
- }
3085
- )
3086
- ] });
3087
- }
3088
- function onPdfViewerActivate() {
3089
- console.debug("[PdfViewerPlugin] Activated");
3090
- }
3091
- function onPdfViewerDeactivate() {
3092
- console.debug("[PdfViewerPlugin] Deactivated");
3093
- }
3094
-
3095
- export { PdfViewerPlugin as default, onPdfViewerActivate, onPdfViewerDeactivate };