@researai/deepscientist 1.5.15 → 1.5.16

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 (193) hide show
  1. package/README.md +336 -98
  2. package/bin/ds.js +691 -91
  3. package/docs/en/00_QUICK_START.md +36 -15
  4. package/docs/en/01_SETTINGS_REFERENCE.md +33 -0
  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 +11 -5
  9. package/docs/en/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
  10. package/docs/en/15_CODEX_PROVIDER_SETUP.md +25 -8
  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/README.md +18 -0
  15. package/docs/zh/00_QUICK_START.md +36 -15
  16. package/docs/zh/01_SETTINGS_REFERENCE.md +33 -0
  17. package/docs/zh/02_START_RESEARCH_GUIDE.md +7 -0
  18. package/docs/zh/05_TUI_GUIDE.md +6 -0
  19. package/docs/zh/09_DOCTOR.md +11 -5
  20. package/docs/zh/14_PROMPT_SKILLS_AND_MCP_GUIDE.md +63 -13
  21. package/docs/zh/15_CODEX_PROVIDER_SETUP.md +25 -8
  22. package/docs/zh/19_EXTERNAL_CONTROLLER_GUIDE.md +226 -0
  23. package/docs/zh/19_LOCAL_BROWSER_AUTH.md +68 -0
  24. package/docs/zh/20_WORKSPACE_MODES_GUIDE.md +251 -0
  25. package/docs/zh/README.md +18 -0
  26. package/package.json +1 -1
  27. package/pyproject.toml +1 -1
  28. package/src/deepscientist/__init__.py +1 -1
  29. package/src/deepscientist/acp/envelope.py +6 -0
  30. package/src/deepscientist/artifact/service.py +647 -22
  31. package/src/deepscientist/bash_exec/service.py +234 -9
  32. package/src/deepscientist/cli.py +115 -19
  33. package/src/deepscientist/codex_cli_compat.py +232 -0
  34. package/src/deepscientist/config/models.py +2 -1
  35. package/src/deepscientist/config/service.py +31 -9
  36. package/src/deepscientist/daemon/api/handlers.py +125 -6
  37. package/src/deepscientist/daemon/api/router.py +4 -0
  38. package/src/deepscientist/daemon/app.py +715 -98
  39. package/src/deepscientist/gitops/__init__.py +10 -1
  40. package/src/deepscientist/gitops/diff.py +129 -0
  41. package/src/deepscientist/gitops/service.py +4 -1
  42. package/src/deepscientist/mcp/server.py +39 -0
  43. package/src/deepscientist/prompts/builder.py +255 -32
  44. package/src/deepscientist/quest/layout.py +15 -2
  45. package/src/deepscientist/quest/service.py +295 -43
  46. package/src/deepscientist/quest/stage_views.py +6 -1
  47. package/src/deepscientist/runners/codex.py +86 -31
  48. package/src/deepscientist/skills/__init__.py +2 -2
  49. package/src/deepscientist/skills/installer.py +196 -5
  50. package/src/deepscientist/skills/registry.py +66 -0
  51. package/src/prompts/connectors/qq.md +18 -8
  52. package/src/prompts/connectors/weixin.md +16 -6
  53. package/src/prompts/contracts/shared_interaction.md +12 -1
  54. package/src/prompts/system.md +10 -5
  55. package/src/prompts/system_copilot.md +43 -0
  56. package/src/skills/analysis-campaign/SKILL.md +1 -0
  57. package/src/skills/baseline/SKILL.md +8 -0
  58. package/src/skills/decision/SKILL.md +8 -0
  59. package/src/skills/experiment/SKILL.md +8 -0
  60. package/src/skills/figure-polish/SKILL.md +1 -0
  61. package/src/skills/finalize/SKILL.md +1 -0
  62. package/src/skills/idea/SKILL.md +1 -0
  63. package/src/skills/intake-audit/SKILL.md +8 -0
  64. package/src/skills/mentor/SKILL.md +217 -0
  65. package/src/skills/mentor/references/correction-rules.md +210 -0
  66. package/src/skills/mentor/references/knowledge-profile.md +91 -0
  67. package/src/skills/mentor/references/persona-profile.md +138 -0
  68. package/src/skills/mentor/references/taste-profile.md +128 -0
  69. package/src/skills/mentor/references/thought-style-profile.md +138 -0
  70. package/src/skills/mentor/references/work-profile.md +289 -0
  71. package/src/skills/mentor/references/workflow-profile.md +240 -0
  72. package/src/skills/optimize/SKILL.md +1 -0
  73. package/src/skills/rebuttal/SKILL.md +1 -0
  74. package/src/skills/review/SKILL.md +1 -0
  75. package/src/skills/scout/SKILL.md +8 -0
  76. package/src/skills/write/SKILL.md +1 -0
  77. package/src/tui/dist/app/AppContainer.js +19 -11
  78. package/src/tui/dist/index.js +4 -1
  79. package/src/tui/dist/lib/api.js +33 -3
  80. package/src/tui/package.json +1 -1
  81. package/src/ui/dist/assets/AiManusChatView-COFACy7V.js +204 -0
  82. package/src/ui/dist/assets/AnalysisPlugin-DnSm0GZn.js +1 -0
  83. package/src/ui/dist/assets/CliPlugin-CvwCmDQ5.js +109 -0
  84. package/src/ui/dist/assets/CodeEditorPlugin-cOqSa0xq.js +2 -0
  85. package/src/ui/dist/assets/CodeViewerPlugin-itb0tltR.js +270 -0
  86. package/src/ui/dist/assets/DocViewerPlugin-DqKkiCI6.js +7 -0
  87. package/src/ui/dist/assets/GitCommitViewerPlugin-DVgNHBCS.js +1 -0
  88. package/src/ui/dist/assets/GitDiffViewerPlugin-DxL2ezFG.js +6 -0
  89. package/src/ui/dist/assets/GitSnapshotViewer-B_RQm1YZ.js +30 -0
  90. package/src/ui/dist/assets/ImageViewerPlugin-tHqlXY3n.js +26 -0
  91. package/src/ui/dist/assets/LabCopilotPanel-ClMbq5Yu.js +14 -0
  92. package/src/ui/dist/assets/LabPlugin-L_SuE8ow.js +22 -0
  93. package/src/ui/dist/assets/LatexPlugin-B495DTXC.js +25 -0
  94. package/src/ui/dist/assets/MarkdownViewerPlugin-DG28-61B.js +128 -0
  95. package/src/ui/dist/assets/MarketplacePlugin-BiOGT-Kj.js +13 -0
  96. package/src/ui/dist/assets/{NotebookEditor-CccQYZjX.css → NotebookEditor-BHH8rdGj.css} +1 -1
  97. package/src/ui/dist/assets/NotebookEditor-BOr3x3Ej.css +1 -0
  98. package/src/ui/dist/assets/NotebookEditor-C-4Kt1p9.js +81 -0
  99. package/src/ui/dist/assets/NotebookEditor-CVsj8h_T.js +361 -0
  100. package/src/ui/dist/assets/PdfLoader-CASDQmxJ.js +16 -0
  101. package/src/ui/dist/assets/PdfLoader-Cy5jtWrr.css +1 -0
  102. package/src/ui/dist/assets/PdfMarkdownPlugin-BFhwoKsY.js +1 -0
  103. package/src/ui/dist/assets/PdfViewerPlugin-DcOzU9vd.js +17 -0
  104. package/src/ui/dist/assets/PdfViewerPlugin-nwwE-fjJ.css +1 -0
  105. package/src/ui/dist/assets/SearchPlugin-CHj7M58O.js +16 -0
  106. package/src/ui/dist/assets/SearchPlugin-DA4en4hK.css +1 -0
  107. package/src/ui/dist/assets/TextViewerPlugin-CB4DYfWO.js +54 -0
  108. package/src/ui/dist/assets/VNCViewer-CjlbyCB3.js +11 -0
  109. package/src/ui/dist/assets/bot-CFkZY-JP.js +6 -0
  110. package/src/ui/dist/assets/browser-CTB2jwNe.js +8 -0
  111. package/src/ui/dist/assets/chevron-up-Dq5ofbht.js +6 -0
  112. package/src/ui/dist/assets/code-DLC6G24T.js +6 -0
  113. package/src/ui/dist/assets/file-content-Dv4LoZec.js +1 -0
  114. package/src/ui/dist/assets/file-diff-panel-Denq-lC3.js +1 -0
  115. package/src/ui/dist/assets/file-jump-queue-DA-SdG__.js +1 -0
  116. package/src/ui/dist/assets/file-socket-Cu4Qln7Y.js +1 -0
  117. package/src/ui/dist/assets/git-commit-horizontal-BUh6G52n.js +6 -0
  118. package/src/ui/dist/assets/image-B9HUUddG.js +6 -0
  119. package/src/ui/dist/assets/index-B2B1sg-M.js +1 -0
  120. package/src/ui/dist/assets/index-Cgla8biy.css +33 -0
  121. package/src/ui/dist/assets/index-DRyx7vAc.js +1 -0
  122. package/src/ui/dist/assets/index-Gbl53BNp.js +2496 -0
  123. package/src/ui/dist/assets/index-wQ7RIIRd.js +11 -0
  124. package/src/ui/dist/assets/monaco-CiHMMNH_.js +1 -0
  125. package/src/ui/dist/assets/pdf-effect-queue-ZtnHFCAi.js +6 -0
  126. package/src/ui/dist/assets/plugin-monaco-C8UgLomw.js +19 -0
  127. package/src/ui/dist/assets/plugin-notebook-HbW2K-1c.js +169 -0
  128. package/src/ui/dist/assets/plugin-pdf-CR8hgQBV.js +357 -0
  129. package/src/ui/dist/assets/plugin-terminal-MXFIPun8.js +227 -0
  130. package/src/ui/dist/assets/popover-DL6h35vr.js +1 -0
  131. package/src/ui/dist/assets/project-sync-CsX08Qno.js +1 -0
  132. package/src/ui/dist/assets/select-DvmXt1yY.js +11 -0
  133. package/src/ui/dist/assets/sigma-7jpXazui.js +6 -0
  134. package/src/ui/dist/assets/trash-xA7kFt8i.js +11 -0
  135. package/src/ui/dist/assets/useCliAccess-DsMwDjOp.js +1 -0
  136. package/src/ui/dist/assets/useFileDiffOverlay-FuhcnKiw.js +1 -0
  137. package/src/ui/dist/assets/wrap-text-CwMn-iqb.js +11 -0
  138. package/src/ui/dist/assets/zoom-out-R-GWEhzS.js +11 -0
  139. package/src/ui/dist/index.html +5 -2
  140. package/src/ui/dist/assets/AiManusChatView-DDjbFnbt.js +0 -26597
  141. package/src/ui/dist/assets/AnalysisPlugin-Yb5IdmaU.js +0 -123
  142. package/src/ui/dist/assets/CliPlugin-e64sreyu.js +0 -31037
  143. package/src/ui/dist/assets/CodeEditorPlugin-C4D2TIkU.js +0 -427
  144. package/src/ui/dist/assets/CodeViewerPlugin-BVoNZIvC.js +0 -905
  145. package/src/ui/dist/assets/DocViewerPlugin-CLChbllo.js +0 -278
  146. package/src/ui/dist/assets/GitDiffViewerPlugin-C4xeFyFQ.js +0 -2661
  147. package/src/ui/dist/assets/ImageViewerPlugin-OiMUAcLi.js +0 -500
  148. package/src/ui/dist/assets/LabCopilotPanel-BjD2ThQF.js +0 -4104
  149. package/src/ui/dist/assets/LabPlugin-DQPg-NrB.js +0 -2677
  150. package/src/ui/dist/assets/LatexPlugin-CI05XAV9.js +0 -1792
  151. package/src/ui/dist/assets/MarkdownViewerPlugin-DpeBLYZf.js +0 -308
  152. package/src/ui/dist/assets/MarketplacePlugin-DolE58Q2.js +0 -413
  153. package/src/ui/dist/assets/NotebookEditor-7Qm2rSWD.js +0 -4214
  154. package/src/ui/dist/assets/NotebookEditor-C1kWaxKi.js +0 -84873
  155. package/src/ui/dist/assets/NotebookEditor-C3VQ7ylN.css +0 -1405
  156. package/src/ui/dist/assets/PdfLoader-BfOHw8Zw.js +0 -25468
  157. package/src/ui/dist/assets/PdfLoader-C-Y707R3.css +0 -49
  158. package/src/ui/dist/assets/PdfMarkdownPlugin-BulDREv1.js +0 -409
  159. package/src/ui/dist/assets/PdfViewerPlugin-C-daaOaL.js +0 -3095
  160. package/src/ui/dist/assets/PdfViewerPlugin-DQ11QcSf.css +0 -3627
  161. package/src/ui/dist/assets/SearchPlugin-CjpaiJ3A.js +0 -741
  162. package/src/ui/dist/assets/SearchPlugin-DDMrGDkh.css +0 -379
  163. package/src/ui/dist/assets/TextViewerPlugin-BxIyqPQC.js +0 -472
  164. package/src/ui/dist/assets/VNCViewer-HAg9mF7M.js +0 -18821
  165. package/src/ui/dist/assets/awareness-C0NPR2Dj.js +0 -292
  166. package/src/ui/dist/assets/bot-0DYntytV.js +0 -21
  167. package/src/ui/dist/assets/browser-BAcuE0Xj.js +0 -2895
  168. package/src/ui/dist/assets/code-B20Slj_w.js +0 -17
  169. package/src/ui/dist/assets/file-content-DT24KFma.js +0 -377
  170. package/src/ui/dist/assets/file-diff-panel-DK13YPql.js +0 -92
  171. package/src/ui/dist/assets/file-jump-queue-r5XKgJEV.js +0 -16
  172. package/src/ui/dist/assets/file-socket-B4T2o4nR.js +0 -58
  173. package/src/ui/dist/assets/function-B5QZkkHC.js +0 -1895
  174. package/src/ui/dist/assets/image-DSeR_sDS.js +0 -18
  175. package/src/ui/dist/assets/index-BrFje2Uk.js +0 -120
  176. package/src/ui/dist/assets/index-BwRJaoTl.js +0 -25
  177. package/src/ui/dist/assets/index-D_E4281X.js +0 -221322
  178. package/src/ui/dist/assets/index-DnYB3xb1.js +0 -159
  179. package/src/ui/dist/assets/index-G7AcWcMu.css +0 -12594
  180. package/src/ui/dist/assets/monaco-LExaAN3Y.js +0 -623
  181. package/src/ui/dist/assets/pdf-effect-queue-BJk5okWJ.js +0 -47
  182. package/src/ui/dist/assets/pdf_viewer-e0g1is2C.js +0 -8206
  183. package/src/ui/dist/assets/popover-D3Gg_FoV.js +0 -476
  184. package/src/ui/dist/assets/project-sync-C_ygLlVU.js +0 -297
  185. package/src/ui/dist/assets/select-CpAK6uWm.js +0 -1690
  186. package/src/ui/dist/assets/sigma-DEccaSgk.js +0 -22
  187. package/src/ui/dist/assets/square-check-big-uUfyVsbD.js +0 -17
  188. package/src/ui/dist/assets/trash-CXvwwSe8.js +0 -32
  189. package/src/ui/dist/assets/useCliAccess-Bnop4mgR.js +0 -957
  190. package/src/ui/dist/assets/useFileDiffOverlay-B8eUAX0I.js +0 -53
  191. package/src/ui/dist/assets/wrap-text-9vbOBpkW.js +0 -35
  192. package/src/ui/dist/assets/yjs-DncrqiZ8.js +0 -11243
  193. package/src/ui/dist/assets/zoom-out-BgVMmOW4.js +0 -34
@@ -1,1792 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/monaco-LExaAN3Y.js","assets/index-D_E4281X.js","assets/index-G7AcWcMu.css","assets/yjs-DncrqiZ8.js","assets/function-B5QZkkHC.js","assets/awareness-C0NPR2Dj.js"])))=>i.map(i=>d[i]);
2
- import { w as createLucideIcon, z as useAuthStore, u as useI18n, a as useWorkspaceSurfaceStore, r as reactExports, f as useFileTreeStore, x as supportsSocketIo, W as toFilesResourcePath, Y as checkProjectAccess, Z as listFiles, V as getFileContent, _ as __vitePreload, U as updateFileContent, $ as compileLatex, a0 as listLatexBuilds, j as jsxRuntimeExports, b as cn, k as FileText, a1 as Link2, L as LoaderCircle, S as Save, a2 as Play, T as TriangleAlert, a3 as Download, a4 as getLatexBuild, a5 as getLatexBuildPdfBlob, a6 as getLatexBuildLogText, h as dynamic } from './index-D_E4281X.js';
3
- import { P as ProjectSyncClient } from './project-sync-C_ygLlVU.js';
4
- import { c as configureMonacoLoader } from './monaco-LExaAN3Y.js';
5
- import { P as PAGE_DIMENSIONS, a as PdfLoader, b as PDF_CMAP_URL, c as PDF_WORKER_SRC, Z as ZOOM_LEVELS, d as PdfHighlighter } from './PdfLoader-BfOHw8Zw.js';
6
- import { Z as ZoomOut, a as ZoomIn } from './zoom-out-BgVMmOW4.js';
7
-
8
- /**
9
- * @license lucide-react v0.511.0 - ISC
10
- *
11
- * This source code is licensed under the ISC license.
12
- * See the LICENSE file in the root directory of this source tree.
13
- */
14
-
15
-
16
- const __iconNode = [
17
- ["circle", { cx: "12", cy: "12", r: "4", key: "4exip2" }],
18
- ["path", { d: "M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-4 8", key: "7n84p3" }]
19
- ];
20
- const AtSign = createLucideIcon("at-sign", __iconNode);
21
-
22
- const MonacoEditor = dynamic(() => __vitePreload(() => import('./monaco-LExaAN3Y.js').then(n => n.i),true?__vite__mapDeps([0,1,2]):void 0), { });
23
- configureMonacoLoader();
24
- function useIsDarkMode() {
25
- const [isDark, setIsDark] = reactExports.useState(() => {
26
- if (typeof document === "undefined") return false;
27
- return document.documentElement.classList.contains("dark");
28
- });
29
- reactExports.useEffect(() => {
30
- const el = document.documentElement;
31
- const observer = new MutationObserver(() => {
32
- setIsDark(el.classList.contains("dark"));
33
- });
34
- observer.observe(el, { attributes: true, attributeFilter: ["class"] });
35
- return () => observer.disconnect();
36
- }, []);
37
- return isDark;
38
- }
39
- function getLatexIssueIdentity(issue) {
40
- return [
41
- issue.resourcePath || issue.resourceName || "",
42
- issue.line || 0,
43
- issue.severity,
44
- issue.message
45
- ].join("::");
46
- }
47
- const normalizeBuildErrors = (errors, logItems) => {
48
- if (Array.isArray(logItems) && logItems.length > 0) {
49
- return logItems.map((item) => ({
50
- path: item.file ?? null,
51
- line: typeof item.line === "number" ? item.line : null,
52
- message: item.message,
53
- severity: item.severity === "warning" ? "warning" : "error"
54
- }));
55
- }
56
- return Array.isArray(errors) ? errors : [];
57
- };
58
- const LATEX_COMPILER_OPTIONS = ["pdflatex", "xelatex", "lualatex"];
59
- const BIB_SNIPPETS = [
60
- {
61
- id: "article",
62
- labelKey: "bib_snippet_article",
63
- snippet: "@article{key,\n title = {},\n author = {},\n journal = {},\n year = {},\n}\n"
64
- },
65
- {
66
- id: "inproceedings",
67
- labelKey: "bib_snippet_inproceedings",
68
- snippet: "@inproceedings{key,\n title = {},\n author = {},\n booktitle = {},\n year = {},\n}\n"
69
- },
70
- {
71
- id: "misc",
72
- labelKey: "bib_snippet_misc",
73
- snippet: "@misc{key,\n title = {},\n author = {},\n year = {},\n note = {},\n}\n"
74
- }
75
- ];
76
- function normalizeCompiler(value) {
77
- if (value === "xelatex" || value === "lualatex") return value;
78
- return "pdflatex";
79
- }
80
- function normalizeLatexPath(value) {
81
- return String(value || "").trim().replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/^\/+/, "").toLowerCase();
82
- }
83
- function parseBibEntries(text, sourceFile) {
84
- const entries = [];
85
- const source = String(text || "");
86
- const entryRegex = /@([a-zA-Z]+)\s*\{\s*([^,\s]+)\s*,([\s\S]*?)\n\}/g;
87
- let match = null;
88
- while (match = entryRegex.exec(source)) {
89
- const body = match[3] || "";
90
- const title = body.match(/title\s*=\s*[\{"']([^}"']+)/i)?.[1]?.trim();
91
- const author = body.match(/author\s*=\s*[\{"']([^}"']+)/i)?.[1]?.trim();
92
- entries.push({
93
- key: match[2].trim(),
94
- title,
95
- author,
96
- sourceFile
97
- });
98
- }
99
- return entries;
100
- }
101
- function parseLatexLabels(text, sourceFile) {
102
- const entries = [];
103
- const source = String(text || "");
104
- const regex = /\\label\{([^}]+)\}/g;
105
- let match = null;
106
- while (match = regex.exec(source)) {
107
- entries.push({
108
- key: match[1].trim(),
109
- sourceFile
110
- });
111
- }
112
- return entries;
113
- }
114
- function resolveLatexFileId(files, rawPath) {
115
- const normalized = normalizeLatexPath(rawPath);
116
- if (!normalized) return null;
117
- const exact = files.find((file) => normalizeLatexPath(file.name) === normalized);
118
- if (exact) return exact.id;
119
- const basename = normalized.split("/").filter(Boolean).pop();
120
- if (!basename) return null;
121
- const byBasename = files.find((file) => normalizeLatexPath(file.name).endsWith(`/${basename}`));
122
- if (byBasename) return byBasename.id;
123
- const simpleName = files.find((file) => file.name.toLowerCase() === basename);
124
- return simpleName?.id ?? null;
125
- }
126
- function PdfSurface({ pdfDocument, zoomFactor, highlights, onPageWidth }) {
127
- reactExports.useEffect(() => {
128
- let cancelled = false;
129
- pdfDocument.getPage(1).then((page) => {
130
- if (cancelled) return;
131
- const viewport = page.getViewport({ scale: 1 });
132
- if (viewport?.width) {
133
- onPageWidth(viewport.width);
134
- }
135
- }).catch(() => {
136
- if (!cancelled) onPageWidth(PAGE_DIMENSIONS.A4_WIDTH);
137
- });
138
- return () => {
139
- cancelled = true;
140
- };
141
- }, [onPageWidth, pdfDocument]);
142
- const safeZoomFactor = Number.isFinite(zoomFactor) && zoomFactor > 0 ? zoomFactor : 1;
143
- const pdfScaleValue = Math.abs(safeZoomFactor - 1) < 1e-3 ? "page-width" : `page-width:${safeZoomFactor}`;
144
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
145
- PdfHighlighter,
146
- {
147
- pdfDocument,
148
- pdfScaleValue,
149
- highlights,
150
- highlightTransform: () => /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, {}),
151
- onScrollChange: () => {
152
- },
153
- scrollRef: () => {
154
- },
155
- onSelectionFinished: (_position, _content, _hideTipAndSelection, _transformSelection) => null,
156
- enableAreaSelection: () => false
157
- }
158
- );
159
- }
160
- function LatexPlugin({ context, tabId, setDirty, setTitle }) {
161
- const custom = context.customData ?? {};
162
- const projectId = custom.projectId ?? void 0;
163
- const latexFolderId = custom.latexFolderId ?? context.resourceId ?? void 0;
164
- const viewReadOnly = Boolean(custom.readOnly);
165
- const user = useAuthStore((s) => s.user);
166
- const { t, language } = useI18n("latex");
167
- const updateWorkspaceTabState = useWorkspaceSurfaceStore((state) => state.updateTabState);
168
- const setWorkspaceActiveIssue = useWorkspaceSurfaceStore((state) => state.setActiveIssue);
169
- const [roleWritable, setRoleWritable] = reactExports.useState(null);
170
- const isDark = useIsDarkMode();
171
- const updateFileMeta = useFileTreeStore((s) => s.updateFileMeta);
172
- const [files, setFiles] = reactExports.useState([]);
173
- const initialFileId = custom.openFileId ?? custom.mainFileId ?? null;
174
- const [activeFileId, setActiveFileId] = reactExports.useState(initialFileId);
175
- const [activeFileName, setActiveFileName] = reactExports.useState("main.tex");
176
- const [initialText, setInitialText] = reactExports.useState("");
177
- const [syncState, setSyncState] = reactExports.useState("idle");
178
- const [saveState, setSaveState] = reactExports.useState("idle");
179
- const [error, setError] = reactExports.useState(null);
180
- const [isDirty, setIsDirty] = reactExports.useState(false);
181
- const [buildId, setBuildId] = reactExports.useState(null);
182
- const [buildStatus, setBuildStatus] = reactExports.useState("idle");
183
- const [buildError, setBuildError] = reactExports.useState(null);
184
- const [buildErrors, setBuildErrors] = reactExports.useState([]);
185
- const [compiler, setCompiler] = reactExports.useState("pdflatex");
186
- const [pdfObjectUrl, setPdfObjectUrl] = reactExports.useState(null);
187
- const [logText, setLogText] = reactExports.useState(null);
188
- const [zoomScale, setZoomScale] = reactExports.useState(1);
189
- const [pdfPageWidth, setPdfPageWidth] = reactExports.useState(PAGE_DIMENSIONS.A4_WIDTH);
190
- const [pdfPaneWidth, setPdfPaneWidth] = reactExports.useState(0);
191
- const [splitRatio, setSplitRatio] = reactExports.useState(0.58);
192
- const [isResizing, setIsResizing] = reactExports.useState(false);
193
- const [isWideLayout, setIsWideLayout] = reactExports.useState(false);
194
- const [referencePanelOpen, setReferencePanelOpen] = reactExports.useState(false);
195
- const [bibPanelOpen, setBibPanelOpen] = reactExports.useState(false);
196
- const [assistQuery, setAssistQuery] = reactExports.useState("");
197
- const [citationIndex, setCitationIndex] = reactExports.useState([]);
198
- const [labelIndex, setLabelIndex] = reactExports.useState([]);
199
- const emptyHighlights = reactExports.useMemo(() => [], []);
200
- const lastSavedRef = reactExports.useRef("");
201
- const yDocRef = reactExports.useRef(null);
202
- const yTextRef = reactExports.useRef(null);
203
- const syncRef = reactExports.useRef(null);
204
- const remoteOriginRef = reactExports.useRef("");
205
- const pendingUpdatesRef = reactExports.useRef([]);
206
- const flushTimerRef = reactExports.useRef(null);
207
- const bindingCleanupRef = reactExports.useRef(null);
208
- const applyingRemoteRef = reactExports.useRef(false);
209
- const lastResetTimestampRef = reactExports.useRef(0);
210
- const forceSeedRef = reactExports.useRef(false);
211
- const [resetNonce, setResetNonce] = reactExports.useState(0);
212
- const pdfUrlRef = reactExports.useRef(null);
213
- const lastLoadedPdfBuildIdRef = reactExports.useRef(null);
214
- const splitContainerRef = reactExports.useRef(null);
215
- const pdfPaneRef = reactExports.useRef(null);
216
- const editorRef = reactExports.useRef(null);
217
- const pendingJumpRef = reactExports.useRef(null);
218
- const citationIndexRef = reactExports.useRef([]);
219
- const labelIndexRef = reactExports.useRef([]);
220
- const latexCompletionDisposablesRef = reactExports.useRef([]);
221
- const effectiveReadOnly = viewReadOnly || roleWritable === false;
222
- const socketAuthMode = "user";
223
- const canUseRealtimeSync = supportsSocketIo();
224
- const isBibFile = activeFileName.toLowerCase().endsWith(".bib");
225
- reactExports.useEffect(() => {
226
- const activeFileMeta = files.find((file) => file.id === activeFileId) ?? files.find((file) => file.name === activeFileName) ?? null;
227
- updateWorkspaceTabState(tabId, {
228
- contentKind: "latex",
229
- documentMode: "source",
230
- resourceName: activeFileMeta?.name || activeFileName || context.resourceName || "main.tex",
231
- resourcePath: activeFileMeta?.path ? toFilesResourcePath(activeFileMeta.path) : void 0,
232
- isReadOnly: effectiveReadOnly,
233
- compileState: buildStatus === "queued" || buildStatus === "running" ? "compiling" : saveState === "saving" ? "saving" : buildStatus === "error" ? "error" : "idle",
234
- diagnostics: {
235
- errors: buildErrors.filter((err) => err.severity !== "warning").length,
236
- warnings: buildErrors.filter((err) => err.severity === "warning").length
237
- }
238
- });
239
- }, [
240
- activeFileId,
241
- activeFileName,
242
- buildErrors,
243
- buildStatus,
244
- context.resourceName,
245
- effectiveReadOnly,
246
- files,
247
- saveState,
248
- tabId,
249
- updateWorkspaceTabState
250
- ]);
251
- reactExports.useEffect(() => {
252
- setTitle(context.resourceName || t("title"));
253
- }, [context.resourceName, setTitle, t]);
254
- reactExports.useEffect(() => {
255
- if (!projectId) return;
256
- if (viewReadOnly) {
257
- setRoleWritable(null);
258
- return;
259
- }
260
- let cancelled = false;
261
- (async () => {
262
- try {
263
- const access = await checkProjectAccess(projectId);
264
- if (cancelled) return;
265
- const role = String(access?.role ?? "");
266
- setRoleWritable(role === "owner" || role === "admin" || role === "editor");
267
- } catch {
268
- if (cancelled) return;
269
- setRoleWritable(null);
270
- }
271
- })();
272
- return () => {
273
- cancelled = true;
274
- };
275
- }, [projectId, viewReadOnly]);
276
- reactExports.useEffect(() => {
277
- if (!projectId || !latexFolderId) return;
278
- let cancelled = false;
279
- (async () => {
280
- try {
281
- const items = await listFiles(projectId, latexFolderId);
282
- if (cancelled) return;
283
- const candidates = items.filter((x) => x.type === "file").map((x) => ({ id: x.id, name: x.name, path: x.path || void 0 })).sort((a, b) => a.name.localeCompare(b.name));
284
- setFiles(candidates);
285
- if (!activeFileId) {
286
- const main = candidates.find((f) => f.name.toLowerCase() === "main.tex") ?? candidates.find((f) => f.name.toLowerCase().endsWith(".tex")) ?? candidates[0];
287
- if (main) {
288
- setActiveFileId(main.id);
289
- setActiveFileName(main.name);
290
- }
291
- } else {
292
- const meta = candidates.find((f) => f.id === activeFileId);
293
- if (meta) setActiveFileName(meta.name);
294
- }
295
- } catch (e) {
296
- console.error("[LatexPlugin] Failed to list files:", e);
297
- setError(e instanceof Error ? e.message : t("load_files_failed"));
298
- }
299
- })();
300
- return () => {
301
- cancelled = true;
302
- };
303
- }, [projectId, latexFolderId]);
304
- reactExports.useEffect(() => {
305
- citationIndexRef.current = citationIndex;
306
- }, [citationIndex]);
307
- reactExports.useEffect(() => {
308
- labelIndexRef.current = labelIndex;
309
- }, [labelIndex]);
310
- reactExports.useEffect(() => {
311
- return () => {
312
- latexCompletionDisposablesRef.current.forEach((disposable) => {
313
- try {
314
- disposable?.dispose?.();
315
- } catch {
316
- }
317
- });
318
- latexCompletionDisposablesRef.current = [];
319
- };
320
- }, []);
321
- reactExports.useEffect(() => {
322
- if (!projectId || files.length === 0) {
323
- setCitationIndex([]);
324
- setLabelIndex([]);
325
- return;
326
- }
327
- let cancelled = false;
328
- const candidateFiles = files.filter(
329
- (file) => file.name.toLowerCase().endsWith(".bib") || file.name.toLowerCase().endsWith(".tex")
330
- );
331
- (async () => {
332
- try {
333
- const loaded = await Promise.all(
334
- candidateFiles.map(async (file) => {
335
- try {
336
- const content = await getFileContent(file.id);
337
- return { file, content };
338
- } catch {
339
- return { file, content: "" };
340
- }
341
- })
342
- );
343
- if (cancelled) return;
344
- const nextCitationIndex = loaded.filter((item) => item.file.name.toLowerCase().endsWith(".bib")).flatMap((item) => parseBibEntries(item.content, item.file.name)).sort((a, b) => a.key.localeCompare(b.key));
345
- const nextLabelIndex = loaded.filter((item) => item.file.name.toLowerCase().endsWith(".tex")).flatMap((item) => parseLatexLabels(item.content, item.file.name)).sort((a, b) => a.key.localeCompare(b.key));
346
- setCitationIndex(nextCitationIndex);
347
- setLabelIndex(nextLabelIndex);
348
- } catch {
349
- if (!cancelled) {
350
- setCitationIndex([]);
351
- setLabelIndex([]);
352
- }
353
- }
354
- })();
355
- return () => {
356
- cancelled = true;
357
- };
358
- }, [files, projectId]);
359
- reactExports.useEffect(() => {
360
- return () => {
361
- try {
362
- bindingCleanupRef.current?.();
363
- } finally {
364
- bindingCleanupRef.current = null;
365
- }
366
- };
367
- }, [activeFileId]);
368
- reactExports.useEffect(() => {
369
- if (!projectId || !activeFileId) return;
370
- let cancelled = false;
371
- let cleanup = null;
372
- setSyncState("loading");
373
- setError(null);
374
- (async () => {
375
- const { Doc, applyUpdate, encodeStateVector, encodeStateAsUpdate, mergeUpdates } = await __vitePreload(async () => { const { Doc, applyUpdate, encodeStateVector, encodeStateAsUpdate, mergeUpdates } = await import('./yjs-DncrqiZ8.js').then(n => n.z);return { Doc, applyUpdate, encodeStateVector, encodeStateAsUpdate, mergeUpdates }},true?__vite__mapDeps([3,4]):void 0);
376
- const ydoc = new Doc();
377
- const ytext = ydoc.getText("content");
378
- yDocRef.current = ydoc;
379
- yTextRef.current = ytext;
380
- if (!canUseRealtimeSync) {
381
- const seed = await getFileContent(activeFileId);
382
- ydoc.transact(() => {
383
- const length = ytext.length || 0;
384
- if (length) ytext.delete(0, length);
385
- if (seed) ytext.insert(0, seed);
386
- }, "ds-local-seed");
387
- const textNow2 = ytext.toString();
388
- setInitialText(textNow2);
389
- lastSavedRef.current = textNow2;
390
- setIsDirty(false);
391
- setDirty(false);
392
- setSyncState("ready");
393
- cleanup = () => {
394
- try {
395
- bindingCleanupRef.current?.();
396
- } finally {
397
- bindingCleanupRef.current = null;
398
- if (yDocRef.current === ydoc) yDocRef.current = null;
399
- if (yTextRef.current === ytext) yTextRef.current = null;
400
- }
401
- };
402
- return;
403
- }
404
- const sync = new ProjectSyncClient(projectId, {
405
- authMode: socketAuthMode,
406
- docKind: "latex"
407
- });
408
- syncRef.current = sync;
409
- await sync.connect();
410
- const remoteOrigin = `ds-remote:${projectId}:${activeFileId}:${Date.now()}`;
411
- remoteOriginRef.current = remoteOrigin;
412
- const diff = await sync.loadDoc(activeFileId, encodeStateVector(ydoc));
413
- if (diff?.missing) {
414
- applyUpdate(ydoc, diff.missing, remoteOrigin);
415
- }
416
- if (forceSeedRef.current) {
417
- const seed = await getFileContent(activeFileId);
418
- ydoc.transact(() => {
419
- const length = ytext.length || 0;
420
- if (length) ytext.delete(0, length);
421
- if (seed) ytext.insert(0, seed);
422
- }, "ds-reset");
423
- if (!effectiveReadOnly) {
424
- const resetUpdate = encodeStateAsUpdate(ydoc);
425
- await sync.pushDocUpdate(activeFileId, resetUpdate);
426
- }
427
- forceSeedRef.current = false;
428
- }
429
- if (!diff) {
430
- const seed = await getFileContent(activeFileId);
431
- ydoc.transact(() => {
432
- ytext.insert(0, seed);
433
- }, "ds-seed");
434
- if (!effectiveReadOnly) {
435
- const initUpdate = encodeStateAsUpdate(ydoc);
436
- await sync.pushDocUpdate(activeFileId, initUpdate);
437
- }
438
- }
439
- const unsubscribeRemote = sync.onDocUpdate((msg) => {
440
- if (msg.docId !== activeFileId) return;
441
- applyUpdate(ydoc, msg.update, remoteOrigin);
442
- });
443
- const unsubscribeReset = sync.onDocReset((msg) => {
444
- if (msg.docId !== activeFileId) return;
445
- const ts = Number(msg.timestamp || 0);
446
- if (ts && ts === lastResetTimestampRef.current) return;
447
- lastResetTimestampRef.current = ts || Date.now();
448
- if (cancelled) return;
449
- forceSeedRef.current = true;
450
- setResetNonce((v) => v + 1);
451
- });
452
- const scheduleFlush = () => {
453
- if (flushTimerRef.current != null) {
454
- window.clearTimeout(flushTimerRef.current);
455
- }
456
- flushTimerRef.current = window.setTimeout(async () => {
457
- flushTimerRef.current = null;
458
- const pending = pendingUpdatesRef.current;
459
- if (!pending.length) return;
460
- pendingUpdatesRef.current = [];
461
- try {
462
- const merged = pending.length === 1 ? pending[0] : mergeUpdates(pending);
463
- await sync.pushDocUpdate(activeFileId, merged);
464
- } catch (e) {
465
- console.error("[LatexPlugin] Failed to push update:", e);
466
- }
467
- }, 300);
468
- };
469
- const handleLocalUpdate = (update, origin) => {
470
- if (origin === remoteOrigin) return;
471
- if (effectiveReadOnly) return;
472
- pendingUpdatesRef.current.push(update);
473
- scheduleFlush();
474
- };
475
- ydoc.on("update", handleLocalUpdate);
476
- let awareness = null;
477
- let unsubscribeAwarenessUpdate = null;
478
- let unsubscribeAwarenessCollect = null;
479
- let handleAwarenessChange = null;
480
- if (!effectiveReadOnly) {
481
- try {
482
- const { Awareness, encodeAwarenessUpdate, applyAwarenessUpdate } = await __vitePreload(async () => { const { Awareness, encodeAwarenessUpdate, applyAwarenessUpdate } = await import('./awareness-C0NPR2Dj.js');return { Awareness, encodeAwarenessUpdate, applyAwarenessUpdate }},true?__vite__mapDeps([5,4]):void 0);
483
- awareness = new Awareness(ydoc);
484
- awareness.setLocalStateField("user", {
485
- id: user?.id ?? null,
486
- name: user?.username ?? "User"
487
- });
488
- await sync.joinAwareness(activeFileId);
489
- const localAwarenessOrigin = `ds-awareness:${projectId}:${activeFileId}:${Date.now()}`;
490
- unsubscribeAwarenessUpdate = sync.onAwarenessUpdate(activeFileId, (update) => {
491
- applyAwarenessUpdate(awareness, update, localAwarenessOrigin);
492
- });
493
- unsubscribeAwarenessCollect = sync.onAwarenessCollect(activeFileId, () => {
494
- const update = encodeAwarenessUpdate(awareness, [awareness.clientID]);
495
- void sync.broadcastAwareness(activeFileId, update);
496
- });
497
- handleAwarenessChange = (changes, origin) => {
498
- if (origin === localAwarenessOrigin) return;
499
- const changedClients = [
500
- ...changes?.added ?? [],
501
- ...changes?.updated ?? [],
502
- ...changes?.removed ?? []
503
- ];
504
- const update = encodeAwarenessUpdate(awareness, changedClients);
505
- void sync.broadcastAwareness(activeFileId, update);
506
- };
507
- awareness.on("change", handleAwarenessChange);
508
- sync.requestAwarenesses(activeFileId);
509
- } catch (e) {
510
- console.warn("[LatexPlugin] Awareness init failed:", e);
511
- }
512
- }
513
- const textNow = ytext.toString();
514
- setInitialText(textNow);
515
- lastSavedRef.current = textNow;
516
- setIsDirty(false);
517
- setDirty(false);
518
- setSyncState("ready");
519
- cleanup = () => {
520
- try {
521
- try {
522
- bindingCleanupRef.current?.();
523
- } finally {
524
- bindingCleanupRef.current = null;
525
- }
526
- unsubscribeRemote?.();
527
- unsubscribeReset?.();
528
- try {
529
- ydoc.off("update", handleLocalUpdate);
530
- } catch {
531
- }
532
- if (flushTimerRef.current != null) {
533
- window.clearTimeout(flushTimerRef.current);
534
- flushTimerRef.current = null;
535
- }
536
- pendingUpdatesRef.current = [];
537
- if (unsubscribeAwarenessUpdate) unsubscribeAwarenessUpdate();
538
- if (unsubscribeAwarenessCollect) unsubscribeAwarenessCollect();
539
- if (awareness && handleAwarenessChange) {
540
- try {
541
- awareness.off("change", handleAwarenessChange);
542
- } catch {
543
- }
544
- }
545
- if (!effectiveReadOnly) {
546
- try {
547
- sync.leaveAwareness(activeFileId);
548
- } catch {
549
- }
550
- }
551
- } finally {
552
- try {
553
- sync.disconnect();
554
- } catch {
555
- }
556
- if (syncRef.current === sync) syncRef.current = null;
557
- if (yDocRef.current === ydoc) yDocRef.current = null;
558
- if (yTextRef.current === ytext) yTextRef.current = null;
559
- }
560
- };
561
- })().catch((e) => {
562
- console.error("[LatexPlugin] Sync init failed:", e);
563
- if (cancelled) return;
564
- setSyncState("error");
565
- setError(e instanceof Error ? e.message : t("collaboration_failed"));
566
- });
567
- return () => {
568
- cancelled = true;
569
- cleanup?.();
570
- };
571
- }, [activeFileId, canUseRealtimeSync, effectiveReadOnly, projectId, resetNonce, setDirty, socketAuthMode, t, user?.id, user?.username]);
572
- const jumpEditorToLine = reactExports.useCallback((line) => {
573
- const editor = editorRef.current;
574
- if (!editor) return false;
575
- const model = editor.getModel?.();
576
- if (!model) return false;
577
- const maxLine = Math.max(1, Number(model.getLineCount?.() ?? 1));
578
- const safeLine = Math.min(Math.max(1, Math.round(line || 1)), maxLine);
579
- editor.revealLineInCenter?.(safeLine);
580
- editor.setPosition?.({ lineNumber: safeLine, column: 1 });
581
- editor.setSelection?.({
582
- startLineNumber: safeLine,
583
- startColumn: 1,
584
- endLineNumber: safeLine,
585
- endColumn: Number(model.getLineMaxColumn?.(safeLine) ?? 1)
586
- });
587
- editor.focus?.();
588
- return true;
589
- }, []);
590
- const flushPendingJump = reactExports.useCallback(() => {
591
- const pending = pendingJumpRef.current;
592
- if (!pending) return;
593
- if (pending.fileId && pending.fileId !== activeFileId) return;
594
- if (jumpEditorToLine(pending.line)) {
595
- pendingJumpRef.current = null;
596
- }
597
- }, [activeFileId, jumpEditorToLine]);
598
- const insertAtCursor = reactExports.useCallback((text) => {
599
- const editor = editorRef.current;
600
- if (!editor || effectiveReadOnly) return;
601
- const selection = editor.getSelection?.();
602
- if (!selection) return;
603
- editor.executeEdits?.("ds-latex-assist", [
604
- {
605
- range: selection,
606
- text,
607
- forceMoveMarkers: true
608
- }
609
- ]);
610
- editor.focus?.();
611
- setIsDirty(true);
612
- setDirty(true);
613
- }, [effectiveReadOnly, setDirty]);
614
- const insertCitation = reactExports.useCallback(
615
- (entry, command = "\\cite") => {
616
- const editor = editorRef.current;
617
- const model = editor?.getModel?.();
618
- const position = editor?.getPosition?.();
619
- if (!editor || !model || !position) {
620
- insertAtCursor(`${command}{${entry.key}}`);
621
- return;
622
- }
623
- const linePrefix = model.getValueInRange({
624
- startLineNumber: position.lineNumber,
625
- startColumn: 1,
626
- endLineNumber: position.lineNumber,
627
- endColumn: position.column
628
- });
629
- const insideCitation = /\\(?:cite|citet|citep|autocite|parencite)\{[^}]*$/i.test(linePrefix);
630
- insertAtCursor(insideCitation ? entry.key : `${command}{${entry.key}}`);
631
- setReferencePanelOpen(false);
632
- setAssistQuery("");
633
- },
634
- [insertAtCursor]
635
- );
636
- const insertLabelReference = reactExports.useCallback(
637
- (entry, command = "\\ref") => {
638
- const editor = editorRef.current;
639
- const model = editor?.getModel?.();
640
- const position = editor?.getPosition?.();
641
- if (!editor || !model || !position) {
642
- insertAtCursor(`${command}{${entry.key}}`);
643
- return;
644
- }
645
- const linePrefix = model.getValueInRange({
646
- startLineNumber: position.lineNumber,
647
- startColumn: 1,
648
- endLineNumber: position.lineNumber,
649
- endColumn: position.column
650
- });
651
- const insideRef = /\\(?:ref|eqref)\{[^}]*$/i.test(linePrefix);
652
- insertAtCursor(insideRef ? entry.key : `${command}{${entry.key}}`);
653
- setReferencePanelOpen(false);
654
- setAssistQuery("");
655
- },
656
- [insertAtCursor]
657
- );
658
- const insertBibSnippet = reactExports.useCallback(
659
- (snippet) => {
660
- insertAtCursor(snippet);
661
- setBibPanelOpen(false);
662
- setAssistQuery("");
663
- },
664
- [insertAtCursor]
665
- );
666
- const filteredCitationIndex = reactExports.useMemo(() => {
667
- const query = assistQuery.trim().toLowerCase();
668
- if (!query) return citationIndex.slice(0, 12);
669
- return citationIndex.filter(
670
- (entry) => [entry.key, entry.title, entry.author, entry.sourceFile].some(
671
- (value) => String(value || "").toLowerCase().includes(query)
672
- )
673
- ).slice(0, 12);
674
- }, [assistQuery, citationIndex]);
675
- const filteredLabelIndex = reactExports.useMemo(() => {
676
- const query = assistQuery.trim().toLowerCase();
677
- if (!query) return labelIndex.slice(0, 10);
678
- return labelIndex.filter(
679
- (entry) => [entry.key, entry.sourceFile].some(
680
- (value) => String(value || "").toLowerCase().includes(query)
681
- )
682
- ).slice(0, 10);
683
- }, [assistQuery, labelIndex]);
684
- const showAssistPanel = referencePanelOpen || bibPanelOpen;
685
- const bindEditor = reactExports.useCallback(
686
- (editor, monaco) => {
687
- editorRef.current = editor;
688
- const ytext = yTextRef.current;
689
- const ydoc = yDocRef.current;
690
- const remoteOrigin = remoteOriginRef.current;
691
- if (!ytext || !ydoc) return;
692
- const model = editor.getModel?.();
693
- if (!model) return;
694
- const ensureLanguage = (id) => {
695
- const languages = monaco.languages.getLanguages?.() || [];
696
- if (!languages.some((item) => item.id === id)) {
697
- monaco.languages.register({ id });
698
- }
699
- };
700
- ensureLanguage("latex-ds");
701
- ensureLanguage("bibtex-ds");
702
- monaco.editor.setModelLanguage(model, isBibFile ? "bibtex-ds" : "latex-ds");
703
- latexCompletionDisposablesRef.current.forEach((disposable2) => {
704
- try {
705
- disposable2?.dispose?.();
706
- } catch {
707
- }
708
- });
709
- latexCompletionDisposablesRef.current = [
710
- monaco.languages.registerCompletionItemProvider("latex-ds", {
711
- triggerCharacters: ["\\", "{"],
712
- provideCompletionItems: (targetModel, position) => {
713
- const linePrefix = targetModel.getValueInRange({
714
- startLineNumber: position.lineNumber,
715
- startColumn: 1,
716
- endLineNumber: position.lineNumber,
717
- endColumn: position.column
718
- });
719
- const word = targetModel.getWordUntilPosition(position);
720
- const range = new monaco.Range(
721
- position.lineNumber,
722
- word.startColumn,
723
- position.lineNumber,
724
- word.endColumn
725
- );
726
- if (/\\(?:cite|citet|citep|autocite|parencite)\{[^}]*$/i.test(linePrefix)) {
727
- return {
728
- suggestions: citationIndexRef.current.slice(0, 40).map((entry) => ({
729
- label: entry.key,
730
- kind: monaco.languages.CompletionItemKind.Reference,
731
- insertText: entry.key,
732
- detail: entry.title || entry.author || entry.sourceFile,
733
- documentation: [entry.author, entry.title].filter(Boolean).join(" · "),
734
- range
735
- }))
736
- };
737
- }
738
- if (/\\(?:ref|eqref)\{[^}]*$/i.test(linePrefix)) {
739
- return {
740
- suggestions: labelIndexRef.current.slice(0, 40).map((entry) => ({
741
- label: entry.key,
742
- kind: monaco.languages.CompletionItemKind.Reference,
743
- insertText: entry.key,
744
- detail: entry.sourceFile,
745
- range
746
- }))
747
- };
748
- }
749
- return { suggestions: [] };
750
- }
751
- }),
752
- monaco.languages.registerCompletionItemProvider("bibtex-ds", {
753
- triggerCharacters: ["@"],
754
- provideCompletionItems: (_targetModel, position) => {
755
- const range = new monaco.Range(
756
- position.lineNumber,
757
- position.column,
758
- position.lineNumber,
759
- position.column
760
- );
761
- return {
762
- suggestions: BIB_SNIPPETS.map((item) => ({
763
- label: item.id,
764
- kind: monaco.languages.CompletionItemKind.Snippet,
765
- insertText: item.snippet,
766
- insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet,
767
- documentation: t(item.labelKey),
768
- range
769
- }))
770
- };
771
- }
772
- })
773
- ];
774
- bindingCleanupRef.current?.();
775
- applyingRemoteRef.current = true;
776
- try {
777
- model.setValue(ytext.toString());
778
- } finally {
779
- applyingRemoteRef.current = false;
780
- }
781
- const applyDelta = (delta) => {
782
- if (!Array.isArray(delta) || delta.length === 0) return;
783
- const edits = [];
784
- let index = 0;
785
- for (const op of delta) {
786
- const retain = typeof op?.retain === "number" ? op.retain : 0;
787
- if (retain) index += retain;
788
- const ins = typeof op?.insert === "string" ? op.insert : null;
789
- if (ins != null) {
790
- const pos = model.getPositionAt(index);
791
- edits.push({
792
- range: new monaco.Range(pos.lineNumber, pos.column, pos.lineNumber, pos.column),
793
- text: ins,
794
- forceMoveMarkers: true
795
- });
796
- index += ins.length;
797
- }
798
- const del = typeof op?.delete === "number" ? op.delete : 0;
799
- if (del) {
800
- const start = model.getPositionAt(index);
801
- const end = model.getPositionAt(index + del);
802
- edits.push({
803
- range: new monaco.Range(start.lineNumber, start.column, end.lineNumber, end.column),
804
- text: "",
805
- forceMoveMarkers: true
806
- });
807
- }
808
- }
809
- if (!edits.length) return;
810
- applyingRemoteRef.current = true;
811
- try {
812
- model.applyEdits(edits);
813
- } finally {
814
- applyingRemoteRef.current = false;
815
- }
816
- };
817
- const yObserver = (event) => {
818
- const origin = event?.transaction?.origin;
819
- if (origin !== remoteOrigin) return;
820
- applyDelta(event.delta ?? []);
821
- setIsDirty(true);
822
- };
823
- ytext.observe(yObserver);
824
- const disposable = model.onDidChangeContent((e) => {
825
- if (applyingRemoteRef.current) return;
826
- if (effectiveReadOnly) return;
827
- const changes = Array.isArray(e?.changes) ? e.changes : [];
828
- if (!changes.length) return;
829
- ydoc.transact(() => {
830
- const sorted = [...changes].sort((a, b) => (b.rangeOffset ?? 0) - (a.rangeOffset ?? 0));
831
- for (const ch of sorted) {
832
- const offset = Number(ch.rangeOffset ?? 0);
833
- const length = Number(ch.rangeLength ?? 0);
834
- const text = String(ch.text ?? "");
835
- if (length) ytext.delete(offset, length);
836
- if (text) ytext.insert(offset, text);
837
- }
838
- }, "ds-monaco");
839
- setIsDirty(true);
840
- });
841
- bindingCleanupRef.current = () => {
842
- try {
843
- disposable?.dispose?.();
844
- } catch {
845
- }
846
- try {
847
- ytext.unobserve(yObserver);
848
- } catch {
849
- }
850
- };
851
- window.requestAnimationFrame(() => {
852
- flushPendingJump();
853
- });
854
- },
855
- [effectiveReadOnly, flushPendingJump, isBibFile, setDirty, t]
856
- );
857
- reactExports.useEffect(() => {
858
- if (syncState !== "ready") return;
859
- flushPendingJump();
860
- }, [activeFileId, flushPendingJump, resetNonce, syncState]);
861
- const save = reactExports.useCallback(async () => {
862
- if (!activeFileId) return false;
863
- if (effectiveReadOnly) return false;
864
- const ytext = yTextRef.current;
865
- if (!ytext) return false;
866
- try {
867
- setSaveState("saving");
868
- const text = String(ytext.toString?.() ?? "");
869
- const res = await updateFileContent(activeFileId, text);
870
- lastSavedRef.current = text;
871
- setSaveState("idle");
872
- setIsDirty(false);
873
- setDirty(false);
874
- if (res?.updated_at) {
875
- updateFileMeta(activeFileId, {
876
- updatedAt: res.updated_at,
877
- size: typeof res.size === "number" ? res.size : void 0,
878
- mimeType: res.mime_type
879
- });
880
- }
881
- return true;
882
- } catch (e) {
883
- console.error("[LatexPlugin] Save failed:", e);
884
- setSaveState("error");
885
- window.setTimeout(() => setSaveState("idle"), 1400);
886
- return false;
887
- }
888
- }, [activeFileId, effectiveReadOnly, setDirty, updateFileMeta]);
889
- const compile = reactExports.useCallback(
890
- async (opts) => {
891
- if (!projectId || !latexFolderId) return;
892
- if (viewReadOnly) return;
893
- if (isDirty && !effectiveReadOnly) {
894
- const saved = await save();
895
- if (!saved) return;
896
- }
897
- try {
898
- setBuildError(null);
899
- setBuildErrors([]);
900
- setLogText(null);
901
- setBuildStatus("queued");
902
- const res = await compileLatex(projectId, latexFolderId, {
903
- compiler,
904
- auto: Boolean(opts?.auto),
905
- stop_on_first_error: false
906
- });
907
- setBuildId(res.build_id);
908
- setCompiler(normalizeCompiler(res.compiler));
909
- setBuildStatus(res.status ?? "queued");
910
- } catch (e) {
911
- console.error("[LatexPlugin] Compile failed:", e);
912
- setBuildError(e instanceof Error ? e.message : t("compile_request_failed"));
913
- setBuildStatus("error");
914
- }
915
- },
916
- [compiler, effectiveReadOnly, isDirty, latexFolderId, projectId, save, t, viewReadOnly]
917
- );
918
- reactExports.useEffect(() => {
919
- if (!projectId || !latexFolderId) return;
920
- const handler = (event) => {
921
- const detail = event.detail;
922
- if (!detail) return;
923
- if (detail.projectId !== projectId || detail.folderId !== latexFolderId) return;
924
- if (detail.buildId) {
925
- setBuildId(detail.buildId);
926
- }
927
- setBuildStatus(detail.status ?? "queued");
928
- setBuildError(detail.errorMessage ?? null);
929
- setBuildErrors([]);
930
- setLogText(null);
931
- };
932
- window.addEventListener("ds:latex-build", handler);
933
- return () => {
934
- window.removeEventListener("ds:latex-build", handler);
935
- };
936
- }, [latexFolderId, projectId]);
937
- reactExports.useEffect(() => {
938
- if (!projectId || !latexFolderId) return;
939
- if (buildId) return;
940
- let cancelled = false;
941
- (async () => {
942
- try {
943
- const builds = await listLatexBuilds(projectId, latexFolderId, 1);
944
- if (cancelled) return;
945
- const latest = builds?.[0];
946
- if (latest?.build_id) {
947
- setBuildId(latest.build_id);
948
- setCompiler(normalizeCompiler(latest.compiler));
949
- setBuildStatus(latest.status ?? "idle");
950
- setBuildError(latest.error_message ?? null);
951
- setBuildErrors(normalizeBuildErrors(latest.errors, latest.log_items));
952
- }
953
- } catch {
954
- }
955
- })();
956
- return () => {
957
- cancelled = true;
958
- };
959
- }, [buildId, latexFolderId, projectId]);
960
- reactExports.useEffect(() => {
961
- if (!projectId || !latexFolderId || !buildId) return;
962
- let cancelled = false;
963
- let timer = null;
964
- const poll = async () => {
965
- try {
966
- const res = await getLatexBuild(projectId, latexFolderId, buildId);
967
- if (cancelled) return;
968
- setCompiler(normalizeCompiler(res.compiler));
969
- setBuildStatus(res.status);
970
- setBuildError(res.error_message ?? null);
971
- setBuildErrors(normalizeBuildErrors(res.errors, res.log_items));
972
- if (res.status === "success" && res.pdf_ready) {
973
- if (lastLoadedPdfBuildIdRef.current !== buildId) {
974
- try {
975
- const blob = await getLatexBuildPdfBlob(projectId, latexFolderId, buildId);
976
- if (cancelled) return;
977
- const nextUrl = URL.createObjectURL(blob);
978
- if (pdfUrlRef.current) {
979
- try {
980
- URL.revokeObjectURL(pdfUrlRef.current);
981
- } catch {
982
- }
983
- }
984
- pdfUrlRef.current = nextUrl;
985
- setPdfObjectUrl(nextUrl);
986
- lastLoadedPdfBuildIdRef.current = buildId;
987
- } catch (e) {
988
- console.warn("[LatexPlugin] Failed to fetch PDF:", e);
989
- }
990
- }
991
- }
992
- if (res.status === "error" && res.log_ready && !logText) {
993
- try {
994
- const txt = await getLatexBuildLogText(projectId, latexFolderId, buildId);
995
- if (cancelled) return;
996
- setLogText(txt);
997
- } catch (e) {
998
- console.warn("[LatexPlugin] Failed to fetch log:", e);
999
- }
1000
- }
1001
- if (res.status === "queued" || res.status === "running") {
1002
- timer = window.setTimeout(poll, 1e3);
1003
- }
1004
- } catch (e) {
1005
- if (cancelled) return;
1006
- timer = window.setTimeout(poll, 1500);
1007
- }
1008
- };
1009
- poll();
1010
- return () => {
1011
- cancelled = true;
1012
- if (timer != null) window.clearTimeout(timer);
1013
- };
1014
- }, [buildId, latexFolderId, logText, projectId]);
1015
- reactExports.useEffect(() => {
1016
- return () => {
1017
- if (pdfUrlRef.current) {
1018
- try {
1019
- URL.revokeObjectURL(pdfUrlRef.current);
1020
- } catch {
1021
- }
1022
- pdfUrlRef.current = null;
1023
- }
1024
- };
1025
- }, []);
1026
- reactExports.useEffect(() => {
1027
- setDirty(isDirty);
1028
- }, [isDirty, setDirty]);
1029
- reactExports.useEffect(() => {
1030
- if (typeof window === "undefined") return;
1031
- const media = window.matchMedia("(min-width: 1024px)");
1032
- const legacyMedia = media;
1033
- const handleChange = () => setIsWideLayout(media.matches);
1034
- handleChange();
1035
- if (typeof media.addEventListener === "function") {
1036
- media.addEventListener("change", handleChange);
1037
- return () => media.removeEventListener("change", handleChange);
1038
- }
1039
- legacyMedia.addListener?.(handleChange);
1040
- return () => legacyMedia.removeListener?.(handleChange);
1041
- }, []);
1042
- reactExports.useEffect(() => {
1043
- const el = pdfPaneRef.current;
1044
- if (!el) return;
1045
- let rafId = null;
1046
- const observer = new ResizeObserver((entries) => {
1047
- const entry = entries[0];
1048
- if (!entry) return;
1049
- if (rafId != null) window.cancelAnimationFrame(rafId);
1050
- rafId = window.requestAnimationFrame(() => {
1051
- setPdfPaneWidth(entry.contentRect.width);
1052
- });
1053
- });
1054
- observer.observe(el);
1055
- return () => {
1056
- if (rafId != null) window.cancelAnimationFrame(rafId);
1057
- observer.disconnect();
1058
- };
1059
- }, []);
1060
- const pdfFileName = reactExports.useMemo(() => {
1061
- const base = activeFileName ? activeFileName.replace(/\.tex$/i, "") : "document";
1062
- return `${base}.pdf`;
1063
- }, [activeFileName]);
1064
- const warningItems = reactExports.useMemo(
1065
- () => buildErrors.filter((err) => err.severity === "warning"),
1066
- [buildErrors]
1067
- );
1068
- const compilerLabel = reactExports.useMemo(() => {
1069
- return t(`compiler_${compiler}`);
1070
- }, [compiler, t]);
1071
- const statusBadge = reactExports.useMemo(() => {
1072
- if (effectiveReadOnly) {
1073
- return {
1074
- label: t("status_read_only"),
1075
- className: "border-black/10 bg-white/60 text-muted-foreground dark:bg-white/[0.04] dark:border-white/10"
1076
- };
1077
- }
1078
- if (buildStatus === "queued" || buildStatus === "running") {
1079
- return {
1080
- label: t("status_compiling"),
1081
- className: "border-[#8FA3B8]/30 bg-[#8FA3B8]/10 text-[#52667a] dark:bg-[#8FA3B8]/12 dark:text-[#c8d4df]"
1082
- };
1083
- }
1084
- if (saveState === "saving") {
1085
- return {
1086
- label: t("status_saving"),
1087
- className: "border-[#A6B0B6]/30 bg-[#A6B0B6]/12 text-[#5c666b] dark:bg-[#A6B0B6]/12 dark:text-[#d8dde0]"
1088
- };
1089
- }
1090
- if (isDirty) {
1091
- return {
1092
- label: t("status_unsaved"),
1093
- className: "border-[#B7A59A]/30 bg-[#B7A59A]/12 text-[#7e695d] dark:bg-[#B7A59A]/12 dark:text-[#eadfd8]"
1094
- };
1095
- }
1096
- if (buildStatus === "error") {
1097
- return {
1098
- label: t("status_compile_failed"),
1099
- className: "border-red-400/30 bg-red-50/80 text-red-600 dark:bg-red-500/10 dark:text-red-200"
1100
- };
1101
- }
1102
- return {
1103
- label: t("status_saved"),
1104
- className: "border-[#9AA79A]/30 bg-[#9AA79A]/12 text-[#5f6b5f] dark:bg-[#9AA79A]/12 dark:text-[#dbe4db]"
1105
- };
1106
- }, [buildStatus, effectiveReadOnly, isDirty, saveState, t]);
1107
- const buildFocusedIssue = reactExports.useCallback(
1108
- (issue) => {
1109
- const targetFileId = resolveLatexFileId(files, issue.path) ?? activeFileId ?? files.find((file) => file.name.toLowerCase() === "main.tex")?.id ?? files[0]?.id ?? null;
1110
- const targetMeta = targetFileId ? files.find((file) => file.id === targetFileId) ?? null : null;
1111
- const resourceName = targetMeta?.name || issue.path || activeFileName || "main.tex";
1112
- const normalizedPath = targetMeta?.path ? toFilesResourcePath(targetMeta.path) : issue.path ? toFilesResourcePath(issue.path) : "";
1113
- const severity = issue.severity === "warning" ? "warning" : "error";
1114
- return {
1115
- kind: "latex_error",
1116
- tabId,
1117
- fileId: targetFileId || void 0,
1118
- resourceId: targetFileId || void 0,
1119
- resourcePath: normalizedPath && normalizedPath !== "/FILES" ? normalizedPath : void 0,
1120
- resourceName,
1121
- line: typeof issue.line === "number" ? Math.max(1, Number(issue.line || 1)) : void 0,
1122
- message: issue.message,
1123
- severity,
1124
- excerpt: issue.path || issue.line ? `${issue.path || resourceName}${issue.line ? `:${issue.line}` : ""}` : void 0,
1125
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
1126
- };
1127
- },
1128
- [activeFileId, activeFileName, files, tabId]
1129
- );
1130
- const focusBuildIssue = reactExports.useCallback(
1131
- (issue) => {
1132
- const focusedIssue = buildFocusedIssue(issue);
1133
- setWorkspaceActiveIssue(tabId, focusedIssue);
1134
- return focusedIssue;
1135
- },
1136
- [buildFocusedIssue, setWorkspaceActiveIssue, tabId]
1137
- );
1138
- reactExports.useEffect(() => {
1139
- if (buildStatus === "success") {
1140
- setWorkspaceActiveIssue(tabId, null);
1141
- return;
1142
- }
1143
- if (buildErrors.length === 0) {
1144
- if (buildStatus === "error") {
1145
- setWorkspaceActiveIssue(tabId, null);
1146
- }
1147
- return;
1148
- }
1149
- const preferredIssue = buildErrors.find((issue) => issue.severity !== "warning") ?? buildErrors[0] ?? null;
1150
- if (!preferredIssue) return;
1151
- const currentFocusedIssue = useWorkspaceSurfaceStore.getState().activeIssueByTabId[tabId];
1152
- const matchingIssue = currentFocusedIssue?.kind === "latex_error" ? buildErrors.find(
1153
- (issue) => getLatexIssueIdentity(buildFocusedIssue(issue)) === getLatexIssueIdentity(currentFocusedIssue)
1154
- ) ?? null : null;
1155
- const nextFocusedIssue = buildFocusedIssue(matchingIssue ?? preferredIssue);
1156
- if (currentFocusedIssue && getLatexIssueIdentity(currentFocusedIssue) === getLatexIssueIdentity(nextFocusedIssue)) {
1157
- return;
1158
- }
1159
- setWorkspaceActiveIssue(tabId, nextFocusedIssue);
1160
- }, [buildErrors, buildFocusedIssue, buildStatus, setWorkspaceActiveIssue, tabId]);
1161
- const handleBuildIssueClick = reactExports.useCallback(
1162
- (issue) => {
1163
- const focusedIssue = focusBuildIssue(issue);
1164
- const targetFileId = focusedIssue?.fileId ?? resolveLatexFileId(files, issue.path) ?? activeFileId ?? files.find((file) => file.name.toLowerCase() === "main.tex")?.id ?? files[0]?.id ?? null;
1165
- if (!targetFileId) return;
1166
- const targetMeta = files.find((file) => file.id === targetFileId);
1167
- pendingJumpRef.current = {
1168
- fileId: targetFileId,
1169
- line: Math.max(1, Number(issue.line || 1))
1170
- };
1171
- if (targetMeta?.name && targetMeta.name !== activeFileName) {
1172
- setActiveFileName(targetMeta.name);
1173
- }
1174
- if (targetFileId !== activeFileId) {
1175
- setActiveFileId(targetFileId);
1176
- return;
1177
- }
1178
- flushPendingJump();
1179
- },
1180
- [activeFileId, activeFileName, files, flushPendingJump, focusBuildIssue]
1181
- );
1182
- const handleAskCopilotForIssue = reactExports.useCallback(
1183
- (issue) => {
1184
- const focusedIssue = focusBuildIssue(issue);
1185
- const severityLabel = issue.severity === "warning" ? t("warning_badge") : t("error_badge");
1186
- const issueLocation = focusedIssue?.line && focusedIssue.resourceName ? `${focusedIssue.resourceName}:${focusedIssue.line}` : focusedIssue?.resourceName || issue.path || activeFileName || "main.tex";
1187
- const prompt = t("issue_action_prompt", {
1188
- severity: severityLabel,
1189
- location: issueLocation,
1190
- message: issue.message
1191
- });
1192
- window.dispatchEvent(
1193
- new CustomEvent("ds:copilot:run", {
1194
- detail: {
1195
- text: prompt,
1196
- focus: true,
1197
- submit: true
1198
- }
1199
- })
1200
- );
1201
- },
1202
- [activeFileName, focusBuildIssue, t]
1203
- );
1204
- const handleFixIssueWithAi = reactExports.useCallback(
1205
- (issue) => {
1206
- if (!latexFolderId || effectiveReadOnly) return;
1207
- const focusedIssue = focusBuildIssue(issue);
1208
- const severityLabel = issue.severity === "warning" ? t("warning_badge") : t("error_badge");
1209
- const issueLocation = focusedIssue?.line && focusedIssue.resourceName ? `${focusedIssue.resourceName}:${focusedIssue.line}` : focusedIssue?.resourceName || issue.path || activeFileName || "main.tex";
1210
- const promptText = t("issue_fix_prompt", {
1211
- severity: severityLabel,
1212
- location: issueLocation,
1213
- message: issue.message
1214
- });
1215
- window.dispatchEvent(
1216
- new CustomEvent("ds:copilot:fix-with-ai", {
1217
- detail: {
1218
- folderId: latexFolderId,
1219
- buildId,
1220
- focusedError: focusedIssue,
1221
- promptText
1222
- }
1223
- })
1224
- );
1225
- },
1226
- [activeFileName, buildId, effectiveReadOnly, focusBuildIssue, latexFolderId, t]
1227
- );
1228
- const renderBuildIssueRow = reactExports.useCallback(
1229
- (issue, idx, scope) => {
1230
- const canJump = Boolean(issue.path || issue.line) && Boolean(activeFileId || files.length);
1231
- const key = `${issue.path ?? scope}-${issue.line ?? "0"}-${idx}`;
1232
- const issueContent = /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
1233
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1234
- "span",
1235
- {
1236
- className: cn(
1237
- "px-1.5 py-0.5 rounded-full text-[10px] uppercase border",
1238
- issue.severity === "warning" ? "text-amber-700 border-amber-400/40 bg-amber-50/70 dark:bg-amber-500/10" : "text-red-600 border-red-400/40 bg-red-50/70 dark:bg-red-500/10"
1239
- ),
1240
- children: issue.severity === "warning" ? t("warning_badge") : t("error_badge")
1241
- }
1242
- ),
1243
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "text-muted-foreground font-mono", children: [
1244
- issue.path || "main.tex",
1245
- issue.line ? `:${issue.line}` : ""
1246
- ] }),
1247
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-muted-foreground break-words", children: issue.message }),
1248
- canJump ? /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "ml-auto shrink-0 text-[10px] text-muted-foreground/80", children: t("issue_hint_clickable") }) : null
1249
- ] });
1250
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2", children: [
1251
- canJump ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1252
- "button",
1253
- {
1254
- type: "button",
1255
- onClick: () => handleBuildIssueClick(issue),
1256
- className: "min-w-0 flex flex-1 items-start gap-2 rounded-lg px-2 py-1.5 text-left hover:bg-black/5 dark:hover:bg-white/[0.04]",
1257
- title: issue.line ? t("issue_jump_to_line", { line: issue.line }) : t("issue_jump_to_file", { file: issue.path || "main.tex" }),
1258
- children: issueContent
1259
- }
1260
- ) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "min-w-0 flex flex-1 items-start gap-2 px-2 py-1.5", children: issueContent }),
1261
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex shrink-0 items-center gap-1 pt-1", children: [
1262
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1263
- "button",
1264
- {
1265
- type: "button",
1266
- onClick: () => handleAskCopilotForIssue(issue),
1267
- className: "rounded-md border border-black/10 dark:border-white/10 bg-white/70 dark:bg-white/[0.05] px-2 py-1 text-[11px] font-medium text-muted-foreground hover:bg-black/5 dark:hover:bg-white/[0.08]",
1268
- title: t("issue_action_ask_copilot"),
1269
- children: t("issue_action_ask_copilot")
1270
- }
1271
- ),
1272
- !effectiveReadOnly && latexFolderId ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1273
- "button",
1274
- {
1275
- type: "button",
1276
- onClick: () => handleFixIssueWithAi(issue),
1277
- className: "rounded-md border border-black/10 dark:border-white/10 bg-white/70 dark:bg-white/[0.05] px-2 py-1 text-[11px] font-medium text-muted-foreground hover:bg-black/5 dark:hover:bg-white/[0.08]",
1278
- title: t("issue_action_fix_with_ai"),
1279
- children: t("issue_action_fix_with_ai")
1280
- }
1281
- ) : null
1282
- ] })
1283
- ] }, key);
1284
- },
1285
- [
1286
- activeFileId,
1287
- effectiveReadOnly,
1288
- files,
1289
- handleAskCopilotForIssue,
1290
- handleBuildIssueClick,
1291
- handleFixIssueWithAi,
1292
- latexFolderId,
1293
- t
1294
- ]
1295
- );
1296
- const zoomOutDisabled = !pdfObjectUrl || zoomScale <= ZOOM_LEVELS[0];
1297
- const zoomInDisabled = !pdfObjectUrl || zoomScale >= ZOOM_LEVELS[ZOOM_LEVELS.length - 1];
1298
- const handleZoomOut = () => {
1299
- const currentIndex = ZOOM_LEVELS.findIndex((z) => z >= zoomScale);
1300
- const safeIndex = currentIndex === -1 ? ZOOM_LEVELS.length - 1 : currentIndex;
1301
- if (safeIndex <= 0) return;
1302
- setZoomScale(ZOOM_LEVELS[safeIndex - 1]);
1303
- };
1304
- const handleZoomIn = () => {
1305
- const currentIndex = ZOOM_LEVELS.findIndex((z) => z >= zoomScale);
1306
- const safeIndex = currentIndex === -1 ? ZOOM_LEVELS.length - 1 : currentIndex;
1307
- if (safeIndex >= ZOOM_LEVELS.length - 1) return;
1308
- setZoomScale(ZOOM_LEVELS[safeIndex + 1]);
1309
- };
1310
- const fitScale = reactExports.useMemo(() => {
1311
- if (!pdfPaneWidth || !pdfPageWidth) return 1;
1312
- const paddedWidth = Math.max(pdfPaneWidth - 32, 120);
1313
- return Math.max(paddedWidth / pdfPageWidth, 0.2);
1314
- }, [pdfPaneWidth, pdfPageWidth]);
1315
- const renderScale = fitScale * zoomScale;
1316
- const handlePageWidth = reactExports.useCallback((width) => {
1317
- setPdfPageWidth(width || PAGE_DIMENSIONS.A4_WIDTH);
1318
- }, []);
1319
- const handleResizeStart = reactExports.useCallback(
1320
- (event) => {
1321
- if (!isWideLayout || !splitContainerRef.current) return;
1322
- if (event.button !== 0) return;
1323
- event.preventDefault();
1324
- const container = splitContainerRef.current;
1325
- const rect = container.getBoundingClientRect();
1326
- const startX = event.clientX;
1327
- const startLeft = rect.width * splitRatio;
1328
- const minLeft = 360;
1329
- const minRight = 320;
1330
- const maxLeft = Math.max(rect.width - minRight, minLeft);
1331
- const prevCursor = document.body.style.cursor;
1332
- const prevSelect = document.body.style.userSelect;
1333
- document.body.style.cursor = "col-resize";
1334
- document.body.style.userSelect = "none";
1335
- setIsResizing(true);
1336
- let rafId = null;
1337
- const onMove = (moveEvent) => {
1338
- const nextLeft = startLeft + (moveEvent.clientX - startX);
1339
- const clamped = Math.min(Math.max(nextLeft, minLeft), maxLeft);
1340
- const nextRatio = clamped / rect.width;
1341
- if (rafId != null) window.cancelAnimationFrame(rafId);
1342
- rafId = window.requestAnimationFrame(() => {
1343
- setSplitRatio(nextRatio);
1344
- });
1345
- };
1346
- const onUp = () => {
1347
- if (rafId != null) window.cancelAnimationFrame(rafId);
1348
- setIsResizing(false);
1349
- document.body.style.cursor = prevCursor;
1350
- document.body.style.userSelect = prevSelect;
1351
- window.removeEventListener("pointermove", onMove);
1352
- window.removeEventListener("pointerup", onUp);
1353
- };
1354
- window.addEventListener("pointermove", onMove);
1355
- window.addEventListener("pointerup", onUp);
1356
- },
1357
- [isWideLayout, splitRatio]
1358
- );
1359
- return /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-full flex flex-col bg-white/70 dark:bg-black/30", children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
1360
- "div",
1361
- {
1362
- ref: splitContainerRef,
1363
- className: "flex-1 min-h-0 flex flex-col lg:flex-row",
1364
- children: [
1365
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
1366
- "div",
1367
- {
1368
- className: cn(
1369
- "min-h-0 flex flex-col min-w-0",
1370
- isWideLayout ? "lg:border-r-0" : "border-b border-black/5 dark:border-white/10",
1371
- isResizing ? "transition-none" : "transition-[flex-basis] duration-200 ease-out"
1372
- ),
1373
- style: isWideLayout ? {
1374
- flexBasis: `${splitRatio * 100}%`,
1375
- flexGrow: 0,
1376
- flexShrink: 0
1377
- } : void 0,
1378
- children: [
1379
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center gap-2 px-3 py-2 border-b border-black/5 dark:border-white/10", children: [
1380
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
1381
- /* @__PURE__ */ jsxRuntimeExports.jsx(FileText, { className: "h-4 w-4 text-muted-foreground shrink-0" }),
1382
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1383
- "select",
1384
- {
1385
- className: cn(
1386
- "h-8 rounded-lg px-2 text-sm bg-white/70 border border-black/10",
1387
- "dark:bg-white/[0.04] dark:border-white/10",
1388
- "min-w-[160px] max-w-[260px] truncate",
1389
- effectiveReadOnly && "opacity-70"
1390
- ),
1391
- value: activeFileId ?? "",
1392
- onChange: (e) => {
1393
- const next = e.target.value || null;
1394
- const meta = files.find((f) => f.id === next);
1395
- if (meta) setActiveFileName(meta.name);
1396
- setActiveFileId(next);
1397
- },
1398
- disabled: files.length === 0,
1399
- "aria-label": t("file_label"),
1400
- title: activeFileName,
1401
- children: files.map((f) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: f.id, children: f.name }, f.id))
1402
- }
1403
- )
1404
- ] }),
1405
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-2", children: [
1406
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-xs text-muted-foreground", children: t("compiler_label") }),
1407
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1408
- "select",
1409
- {
1410
- className: cn(
1411
- "h-8 rounded-lg px-2 text-sm bg-white/70 border border-black/10",
1412
- "dark:bg-white/[0.04] dark:border-white/10",
1413
- "min-w-[122px]",
1414
- buildStatus === "queued" || buildStatus === "running" ? "opacity-70" : ""
1415
- ),
1416
- value: compiler,
1417
- onChange: (event) => setCompiler(normalizeCompiler(event.target.value)),
1418
- disabled: buildStatus === "queued" || buildStatus === "running",
1419
- "aria-label": t("compiler_label"),
1420
- title: compilerLabel,
1421
- children: LATEX_COMPILER_OPTIONS.map((option) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: option, children: t(`compiler_${option}`) }, option))
1422
- }
1423
- )
1424
- ] }),
1425
- !isBibFile ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
1426
- "button",
1427
- {
1428
- type: "button",
1429
- onClick: () => {
1430
- setReferencePanelOpen((current) => {
1431
- const next = !current;
1432
- if (next) setBibPanelOpen(false);
1433
- if (!next) setAssistQuery("");
1434
- return next;
1435
- });
1436
- },
1437
- className: cn(
1438
- "h-8 px-3 rounded-lg text-sm border inline-flex items-center gap-2",
1439
- "bg-white/70 border-black/10 hover:bg-white/90",
1440
- "dark:bg-white/[0.04] dark:border-white/10 dark:hover:bg-white/[0.08]",
1441
- referencePanelOpen && "border-[#8FA3B8]/28 bg-[#8FA3B8]/12 text-[#405267]"
1442
- ),
1443
- "aria-label": t("assist_references"),
1444
- title: t("assist_references"),
1445
- children: [
1446
- /* @__PURE__ */ jsxRuntimeExports.jsx(Link2, { className: "h-4 w-4" }),
1447
- t("assist_references")
1448
- ]
1449
- }
1450
- ) : null,
1451
- isBibFile ? /* @__PURE__ */ jsxRuntimeExports.jsxs(
1452
- "button",
1453
- {
1454
- type: "button",
1455
- onClick: () => {
1456
- setBibPanelOpen((current) => {
1457
- const next = !current;
1458
- if (next) setReferencePanelOpen(false);
1459
- if (!next) setAssistQuery("");
1460
- return next;
1461
- });
1462
- },
1463
- className: cn(
1464
- "h-8 px-3 rounded-lg text-sm border inline-flex items-center gap-2",
1465
- "bg-white/70 border-black/10 hover:bg-white/90",
1466
- "dark:bg-white/[0.04] dark:border-white/10 dark:hover:bg-white/[0.08]",
1467
- bibPanelOpen && "border-[#A99EBE]/28 bg-[#A99EBE]/12 text-[#564f6a]"
1468
- ),
1469
- "aria-label": t("assist_bibtex"),
1470
- title: t("assist_bibtex"),
1471
- children: [
1472
- /* @__PURE__ */ jsxRuntimeExports.jsx(AtSign, { className: "h-4 w-4" }),
1473
- t("assist_bibtex")
1474
- ]
1475
- }
1476
- ) : null,
1477
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "ml-auto flex items-center gap-2", children: [
1478
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1479
- "span",
1480
- {
1481
- className: cn(
1482
- "text-xs px-2 py-0.5 rounded-full border",
1483
- statusBadge.className
1484
- ),
1485
- children: statusBadge.label
1486
- }
1487
- ),
1488
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1489
- "button",
1490
- {
1491
- type: "button",
1492
- onClick: save,
1493
- disabled: effectiveReadOnly || saveState === "saving" || !isDirty,
1494
- className: cn(
1495
- "h-8 px-3 rounded-lg text-sm font-medium border",
1496
- "bg-white/70 border-black/10 hover:bg-white/90",
1497
- "dark:bg-white/[0.04] dark:border-white/10 dark:hover:bg-white/[0.08]",
1498
- "disabled:opacity-50 disabled:cursor-not-allowed",
1499
- isDirty && !effectiveReadOnly && "border-black/20"
1500
- ),
1501
- children: saveState === "saving" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
1502
- /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-4 w-4 animate-spin" }),
1503
- t("button_saving")
1504
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
1505
- /* @__PURE__ */ jsxRuntimeExports.jsx(Save, { className: "h-4 w-4" }),
1506
- t("button_save")
1507
- ] })
1508
- }
1509
- ),
1510
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1511
- "button",
1512
- {
1513
- type: "button",
1514
- onClick: () => void compile({ auto: false }),
1515
- disabled: viewReadOnly || saveState === "saving" || buildStatus === "queued" || buildStatus === "running",
1516
- className: cn(
1517
- "h-8 px-3 rounded-lg text-sm font-medium border",
1518
- "bg-[#8FA3B8]/14 border-[#8FA3B8]/28 text-[#405267] hover:bg-[#8FA3B8]/20",
1519
- "dark:bg-[#8FA3B8]/14 dark:border-[#8FA3B8]/22 dark:text-[#dbe6ef] dark:hover:bg-[#8FA3B8]/20",
1520
- "disabled:opacity-50 disabled:cursor-not-allowed"
1521
- ),
1522
- title: viewReadOnly ? t("compile_disabled_read_only") : isDirty && !effectiveReadOnly ? t("button_save_and_compile") : t("button_compile"),
1523
- children: buildStatus === "queued" || buildStatus === "running" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
1524
- /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-4 w-4 animate-spin" }),
1525
- t("button_compiling")
1526
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "flex items-center gap-2", children: [
1527
- /* @__PURE__ */ jsxRuntimeExports.jsx(Play, { className: "h-4 w-4" }),
1528
- isDirty && !effectiveReadOnly ? t("button_save_and_compile") : t("button_compile")
1529
- ] })
1530
- }
1531
- )
1532
- ] })
1533
- ] }),
1534
- showAssistPanel ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-b border-black/5 dark:border-white/10 bg-black/[0.02] dark:bg-white/[0.02] px-3 py-3", children: [
1535
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
1536
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
1537
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-sm font-medium text-foreground", children: referencePanelOpen ? t("assist_references_title") : t("assist_bibtex_title") }),
1538
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs text-muted-foreground", children: referencePanelOpen ? t("assist_reference_hint") : t("assist_bibtex_hint") })
1539
- ] }),
1540
- referencePanelOpen ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1541
- "input",
1542
- {
1543
- value: assistQuery,
1544
- onChange: (event) => setAssistQuery(event.target.value),
1545
- placeholder: t("assist_search_placeholder"),
1546
- className: cn(
1547
- "h-8 w-full rounded-lg border border-black/10 bg-white/80 px-3 text-sm",
1548
- "sm:w-[280px] dark:border-white/10 dark:bg-white/[0.05]"
1549
- )
1550
- }
1551
- ) : null
1552
- ] }),
1553
- referencePanelOpen ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 grid gap-3 lg:grid-cols-2", children: [
1554
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-xl border border-black/5 bg-white/70 p-3 dark:border-white/10 dark:bg-white/[0.03]", children: [
1555
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: t("assist_citations") }),
1556
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2", children: filteredCitationIndex.length > 0 ? filteredCitationIndex.map((entry) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
1557
- "button",
1558
- {
1559
- type: "button",
1560
- onClick: () => insertCitation(entry),
1561
- className: "w-full rounded-lg border border-black/5 bg-black/[0.02] px-3 py-2 text-left hover:bg-black/[0.04] dark:border-white/10 dark:bg-white/[0.03] dark:hover:bg-white/[0.05]",
1562
- title: entry.title || entry.key,
1563
- children: [
1564
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-3", children: [
1565
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs text-foreground", children: entry.key }),
1566
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-muted-foreground", children: t("assist_insert_citation") })
1567
- ] }),
1568
- entry.title ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 truncate text-xs text-muted-foreground", children: entry.title }) : null,
1569
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 truncate text-[10px] text-muted-foreground/80", children: entry.author || entry.sourceFile })
1570
- ]
1571
- },
1572
- `${entry.sourceFile}:${entry.key}`
1573
- )) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-lg border border-dashed border-black/10 px-3 py-4 text-xs text-muted-foreground dark:border-white/10", children: t("assist_empty_citations") }) })
1574
- ] }),
1575
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "rounded-xl border border-black/5 bg-white/70 p-3 dark:border-white/10 dark:bg-white/[0.03]", children: [
1576
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mb-2 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: t("assist_labels") }),
1577
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "space-y-2", children: filteredLabelIndex.length > 0 ? filteredLabelIndex.map((entry) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
1578
- "button",
1579
- {
1580
- type: "button",
1581
- onClick: () => insertLabelReference(entry),
1582
- className: "w-full rounded-lg border border-black/5 bg-black/[0.02] px-3 py-2 text-left hover:bg-black/[0.04] dark:border-white/10 dark:bg-white/[0.03] dark:hover:bg-white/[0.05]",
1583
- title: entry.key,
1584
- children: [
1585
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-3", children: [
1586
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "font-mono text-xs text-foreground", children: entry.key }),
1587
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-muted-foreground", children: t("assist_insert_reference") })
1588
- ] }),
1589
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-1 truncate text-[10px] text-muted-foreground/80", children: entry.sourceFile })
1590
- ]
1591
- },
1592
- `${entry.sourceFile}:${entry.key}`
1593
- )) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-lg border border-dashed border-black/10 px-3 py-4 text-xs text-muted-foreground dark:border-white/10", children: t("assist_empty_labels") }) })
1594
- ] })
1595
- ] }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-3 grid gap-2 md:grid-cols-3", children: BIB_SNIPPETS.length > 0 ? BIB_SNIPPETS.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
1596
- "button",
1597
- {
1598
- type: "button",
1599
- onClick: () => insertBibSnippet(item.snippet),
1600
- className: "rounded-xl border border-black/5 bg-white/70 px-3 py-3 text-left hover:bg-white/90 dark:border-white/10 dark:bg-white/[0.03] dark:hover:bg-white/[0.05]",
1601
- children: [
1602
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center justify-between gap-2", children: [
1603
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { className: "font-mono text-sm text-foreground", children: [
1604
- "@",
1605
- item.id
1606
- ] }),
1607
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[10px] text-muted-foreground", children: t("assist_insert_snippet") })
1608
- ] }),
1609
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "mt-2 text-xs text-muted-foreground", children: t(item.labelKey) })
1610
- ]
1611
- },
1612
- item.id
1613
- )) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "rounded-lg border border-dashed border-black/10 px-3 py-4 text-xs text-muted-foreground dark:border-white/10", children: t("assist_empty_bib") }) })
1614
- ] }) : null,
1615
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 min-h-0", children: syncState === "loading" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "h-full flex items-center justify-center text-muted-foreground", children: [
1616
- /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-5 w-5 animate-spin mr-2" }),
1617
- t("connecting")
1618
- ] }) : syncState === "error" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-full flex items-center justify-center text-sm text-red-600", children: error ?? t("load_files_failed") }) : /* @__PURE__ */ jsxRuntimeExports.jsx(
1619
- MonacoEditor,
1620
- {
1621
- defaultValue: initialText,
1622
- language: "plaintext",
1623
- theme: isDark ? "vs-dark" : "light",
1624
- onMount: (editor, monaco) => {
1625
- try {
1626
- bindEditor(editor, monaco);
1627
- } catch (e) {
1628
- console.error("[LatexPlugin] Failed to bind editor:", e);
1629
- }
1630
- },
1631
- options: {
1632
- readOnly: effectiveReadOnly,
1633
- minimap: { enabled: false },
1634
- wordWrap: "on",
1635
- fontSize: 13,
1636
- scrollBeyondLastLine: false
1637
- }
1638
- },
1639
- `${activeFileId ?? "latex"}:${resetNonce}`
1640
- ) }),
1641
- buildStatus === "error" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-black/5 dark:border-white/10 p-4 text-sm max-h-[35vh] overflow-auto", children: [
1642
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2 text-red-600", children: [
1643
- /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "h-4 w-4 mt-0.5" }),
1644
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
1645
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium", children: t("compile_failed_title") }),
1646
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs opacity-90 break-words", children: buildError ?? t("compile_failed_fallback") })
1647
- ] })
1648
- ] }),
1649
- buildErrors.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 space-y-2 text-xs", children: [
1650
- buildErrors.slice(0, 12).map((err, idx) => renderBuildIssueRow(err, idx, "error")),
1651
- buildErrors.length > 12 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground", children: t("more_items", { count: buildErrors.length - 12 }) }) : null
1652
- ] }) : null,
1653
- logText ? /* @__PURE__ */ jsxRuntimeExports.jsx("pre", { className: "mt-3 text-xs whitespace-pre-wrap break-words text-muted-foreground max-h-[40vh] overflow-auto rounded-lg border border-black/10 dark:border-white/10 bg-white/60 dark:bg-white/[0.03] p-3", children: logText.slice(0, 8e3) }) : null
1654
- ] }) : null,
1655
- buildStatus !== "error" && warningItems.length > 0 ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "border-t border-black/5 dark:border-white/10 p-4 text-sm max-h-[35vh] overflow-auto", children: [
1656
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-start gap-2 text-amber-700", children: [
1657
- /* @__PURE__ */ jsxRuntimeExports.jsx(TriangleAlert, { className: "h-4 w-4 mt-0.5" }),
1658
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "min-w-0", children: [
1659
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "font-medium", children: t("warnings_title") }),
1660
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-xs opacity-90 break-words", children: t("warnings_reported", {
1661
- count: warningItems.length,
1662
- suffix: language === "zh-CN" || warningItems.length === 1 ? "" : "s"
1663
- }) })
1664
- ] })
1665
- ] }),
1666
- /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "mt-3 space-y-2 text-xs", children: [
1667
- warningItems.slice(0, 12).map((err, idx) => renderBuildIssueRow(err, idx, "warning")),
1668
- warningItems.length > 12 ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-muted-foreground", children: t("more_items", { count: warningItems.length - 12 }) }) : null
1669
- ] })
1670
- ] }) : null
1671
- ]
1672
- }
1673
- ),
1674
- isWideLayout ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1675
- "div",
1676
- {
1677
- className: cn(
1678
- "hidden lg:flex w-3 relative items-stretch cursor-col-resize",
1679
- isResizing ? "bg-primary/10" : "hover:bg-black/5 dark:hover:bg-white/[0.06]"
1680
- ),
1681
- onPointerDown: handleResizeStart,
1682
- role: "separator",
1683
- "aria-orientation": "vertical",
1684
- "aria-label": t("resize_panels_aria"),
1685
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(
1686
- "div",
1687
- {
1688
- className: cn(
1689
- "absolute inset-y-0 left-1/2 w-px",
1690
- isResizing ? "bg-primary/60" : "bg-black/10 dark:bg-white/10"
1691
- )
1692
- }
1693
- )
1694
- }
1695
- ) : null,
1696
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "min-h-0 flex flex-1 flex-col min-w-0", children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: pdfPaneRef, className: "relative flex-1 min-h-0 overflow-hidden", children: [
1697
- /* @__PURE__ */ jsxRuntimeExports.jsxs(
1698
- "div",
1699
- {
1700
- className: cn(
1701
- "absolute z-10 flex flex-col gap-2",
1702
- isWideLayout ? "left-3 top-1/2 -translate-y-1/2" : "right-3 top-3",
1703
- "rounded-full border border-black/10 bg-white/80 p-1 shadow-sm",
1704
- "backdrop-blur dark:border-white/10 dark:bg-black/40"
1705
- ),
1706
- onPointerDown: (event) => event.stopPropagation(),
1707
- children: [
1708
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1709
- "button",
1710
- {
1711
- type: "button",
1712
- onClick: handleZoomOut,
1713
- disabled: zoomOutDisabled,
1714
- className: cn(
1715
- "h-7 w-7 rounded-full flex items-center justify-center",
1716
- "text-muted-foreground hover:text-foreground hover:bg-black/5",
1717
- "dark:hover:bg-white/[0.08]",
1718
- "disabled:opacity-40 disabled:cursor-not-allowed"
1719
- ),
1720
- "aria-label": t("zoom_out"),
1721
- title: t("zoom_out"),
1722
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomOut, { className: "h-4 w-4" })
1723
- }
1724
- ),
1725
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "text-[11px] font-medium text-muted-foreground text-center", children: pdfObjectUrl ? `${Math.round(renderScale * 100)}%` : "--" }),
1726
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1727
- "button",
1728
- {
1729
- type: "button",
1730
- onClick: handleZoomIn,
1731
- disabled: zoomInDisabled,
1732
- className: cn(
1733
- "h-7 w-7 rounded-full flex items-center justify-center",
1734
- "text-muted-foreground hover:text-foreground hover:bg-black/5",
1735
- "dark:hover:bg-white/[0.08]",
1736
- "disabled:opacity-40 disabled:cursor-not-allowed"
1737
- ),
1738
- "aria-label": t("zoom_in"),
1739
- title: t("zoom_in"),
1740
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIn, { className: "h-4 w-4" })
1741
- }
1742
- ),
1743
- /* @__PURE__ */ jsxRuntimeExports.jsx(
1744
- "a",
1745
- {
1746
- href: pdfObjectUrl ?? "#",
1747
- download: pdfFileName,
1748
- className: cn(
1749
- "h-7 w-7 rounded-full text-xs font-medium border flex items-center justify-center",
1750
- "bg-white/70 border-black/10 hover:bg-white/90",
1751
- "dark:bg-white/[0.04] dark:border-white/10 dark:hover:bg-white/[0.08]",
1752
- !pdfObjectUrl && "opacity-50 pointer-events-none"
1753
- ),
1754
- title: t("download_pdf"),
1755
- children: /* @__PURE__ */ jsxRuntimeExports.jsx(Download, { className: "h-3.5 w-3.5" })
1756
- }
1757
- )
1758
- ]
1759
- }
1760
- ),
1761
- pdfObjectUrl ? /* @__PURE__ */ jsxRuntimeExports.jsx(
1762
- PdfLoader,
1763
- {
1764
- url: pdfObjectUrl,
1765
- workerSrc: PDF_WORKER_SRC,
1766
- cMapUrl: PDF_CMAP_URL,
1767
- cMapPacked: true,
1768
- beforeLoad: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex items-center justify-center text-sm text-muted-foreground", children: [
1769
- /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-5 w-5 animate-spin mr-2" }),
1770
- t("loading_pdf")
1771
- ] }),
1772
- children: (pdfDocument) => /* @__PURE__ */ jsxRuntimeExports.jsx(
1773
- PdfSurface,
1774
- {
1775
- pdfDocument,
1776
- zoomFactor: zoomScale,
1777
- highlights: emptyHighlights,
1778
- onPageWidth: handlePageWidth
1779
- }
1780
- )
1781
- }
1782
- ) : buildStatus === "queued" || buildStatus === "running" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex items-center justify-center text-sm text-muted-foreground", children: [
1783
- /* @__PURE__ */ jsxRuntimeExports.jsx(LoaderCircle, { className: "h-5 w-5 animate-spin mr-2" }),
1784
- t("preview_compiling")
1785
- ] }) : buildStatus === "error" ? /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex items-center justify-center text-sm text-muted-foreground", children: t("preview_no_output") }) : /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 flex items-center justify-center text-sm text-muted-foreground", children: t("preview_empty") })
1786
- ] }) })
1787
- ]
1788
- }
1789
- ) });
1790
- }
1791
-
1792
- export { LatexPlugin as default };