From c9cc5b4f6fd84dbdb64a60f7c66f68dfb1b41083 Mon Sep 17 00:00:00 2001 From: matevip Date: Mon, 13 Jul 2026 18:00:54 +0800 Subject: [PATCH] feat(chat): glass-themed preview for uploaded & AI-generated docx/xlsx/pdf (#513) --- .../vip/mate/channel/web/ChatController.java | 74 +- .../preview/OfficePreviewService.java | 194 +++++ .../web/ChatControllerPreviewRouteTest.java | 130 ++++ .../preview/OfficePreviewServiceTest.java | 90 +++ mateclaw-ui/package.json | 3 + mateclaw-ui/pnpm-lock.yaml | 712 ++++++++++++++++++ mateclaw-ui/src/App.vue | 5 + .../src/components/chat/MessageBubble.vue | 30 +- .../components/chat/preview/DocxPreview.vue | 69 ++ .../chat/preview/FilePreviewDialog.vue | 301 ++++++++ .../components/chat/preview/HtmlPreview.vue | 64 ++ .../components/chat/preview/PdfPreview.vue | 136 ++++ .../chat/preview/PreviewSpinner.vue | 40 + .../components/chat/preview/SheetPreview.vue | 195 +++++ .../components/chat/preview/TextPreview.vue | 84 +++ .../preview/__tests__/previewKind.test.ts | 68 ++ .../src/components/chat/preview/previewBus.ts | 18 + .../components/chat/preview/previewKind.ts | 75 ++ .../composables/useGlobalFileDownloadClick.ts | 22 +- mateclaw-ui/src/i18n/locales/en-US.ts | 11 + mateclaw-ui/src/i18n/locales/zh-CN.ts | 11 + mateclaw-ui/src/types/components.d.ts | 3 +- 22 files changed, 2315 insertions(+), 20 deletions(-) create mode 100644 mateclaw-server/src/main/java/vip/mate/tool/document/preview/OfficePreviewService.java create mode 100644 mateclaw-server/src/test/java/vip/mate/channel/web/ChatControllerPreviewRouteTest.java create mode 100644 mateclaw-server/src/test/java/vip/mate/tool/document/preview/OfficePreviewServiceTest.java create mode 100644 mateclaw-ui/src/components/chat/preview/DocxPreview.vue create mode 100644 mateclaw-ui/src/components/chat/preview/FilePreviewDialog.vue create mode 100644 mateclaw-ui/src/components/chat/preview/HtmlPreview.vue create mode 100644 mateclaw-ui/src/components/chat/preview/PdfPreview.vue create mode 100644 mateclaw-ui/src/components/chat/preview/PreviewSpinner.vue create mode 100644 mateclaw-ui/src/components/chat/preview/SheetPreview.vue create mode 100644 mateclaw-ui/src/components/chat/preview/TextPreview.vue create mode 100644 mateclaw-ui/src/components/chat/preview/__tests__/previewKind.test.ts create mode 100644 mateclaw-ui/src/components/chat/preview/previewBus.ts create mode 100644 mateclaw-ui/src/components/chat/preview/previewKind.ts diff --git a/mateclaw-server/src/main/java/vip/mate/channel/web/ChatController.java b/mateclaw-server/src/main/java/vip/mate/channel/web/ChatController.java index e83e95bd..746117ae 100644 --- a/mateclaw-server/src/main/java/vip/mate/channel/web/ChatController.java +++ b/mateclaw-server/src/main/java/vip/mate/channel/web/ChatController.java @@ -8,6 +8,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.core.Authentication; @@ -65,6 +66,7 @@ public class ChatController { private final ConversationCompletionPublisher completionPublisher; private final vip.mate.memory.identity.MemoryOwnerResolver memoryOwnerResolver; private final vip.mate.workspace.core.service.ChatUploadLocationResolver uploadLocationResolver; + private final vip.mate.tool.document.preview.OfficePreviewService officePreviewService; // 使用虚拟线程池处理 SSE(Java 17+ 兼容,Java 21 可用 Executors.newVirtualThreadPerTaskExecutor()) private final ExecutorService sseExecutor = Executors.newCachedThreadPool(); @@ -1142,21 +1144,7 @@ public class ChatController { return ResponseEntity.status(403).build(); } - // Check every candidate root (workspace-scoped dir + legacy default dir) - // so attachments written before the workspace-aware relocation, and the - // current workspace-scoped ones, are both servable. Each candidate keeps - // its own startsWith traversal guard. - Path filePath = null; - // Sanitized-then-raw candidate dirs so both new writes (sanitized) and - // legacy Linux uploads (raw ':' dir) resolve. - for (Path conversationDir : uploadLocationResolver.resolveCandidateConversationDirs(conversationId)) { - Path normDir = conversationDir.normalize(); - Path candidate = normDir.resolve(storedName).normalize(); - if (Files.exists(candidate) && candidate.startsWith(normDir)) { - filePath = candidate; - break; - } - } + Path filePath = resolveUploadedFile(conversationId, storedName); if (filePath == null) { return ResponseEntity.notFound().build(); } @@ -1183,6 +1171,62 @@ public class ChatController { .body(resource); } + @Operation(summary = "生成聊天附件的 PDF 预览(office 格式,soffice 转换)") + @GetMapping("/files/{conversationId}/{storedName:.+}/preview") + public ResponseEntity previewUploadedFile( + @PathVariable String conversationId, + @PathVariable String storedName, + Authentication auth) { + + // Same ownership gate as the raw file endpoint. + String username = auth != null ? auth.getName() : "anonymous"; + if (!conversationService.isConversationOwner(conversationId, username)) { + return ResponseEntity.status(403).build(); + } + + // 415: the client asked to preview a format this endpoint won't convert. + if (!officePreviewService.isConvertible(storedName)) { + return ResponseEntity.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE).build(); + } + // 501: no soffice on this host — the UI degrades to a download link. + if (!officePreviewService.isAvailable()) { + return ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED).build(); + } + + Path filePath = resolveUploadedFile(conversationId, storedName); + if (filePath == null) { + return ResponseEntity.notFound().build(); + } + + try { + byte[] pdf = officePreviewService.renderPdf(filePath); + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_PDF) + .header(HttpHeaders.CONTENT_DISPOSITION, "inline") + .body(pdf); + } catch (IOException e) { + log.warn("[ChatController] office preview conversion failed for {}: {}", storedName, e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + + /** + * Resolve an uploaded attachment to its on-disk path, probing every + * candidate conversation dir (workspace-scoped + legacy default, sanitized + + * raw id) with a per-candidate path-traversal guard. Returns {@code null} + * when no candidate holds the file. + */ + private Path resolveUploadedFile(String conversationId, String storedName) { + for (Path conversationDir : uploadLocationResolver.resolveCandidateConversationDirs(conversationId)) { + Path normDir = conversationDir.normalize(); + Path candidate = normDir.resolve(storedName).normalize(); + if (Files.exists(candidate) && candidate.startsWith(normDir)) { + return candidate; + } + } + return null; + } + /** * Build the {@link vip.mate.agent.context.ChatOrigin} that drives per-owner * memory isolation for a web request. When {@code endUserId} is supplied diff --git a/mateclaw-server/src/main/java/vip/mate/tool/document/preview/OfficePreviewService.java b/mateclaw-server/src/main/java/vip/mate/tool/document/preview/OfficePreviewService.java new file mode 100644 index 00000000..c214443e --- /dev/null +++ b/mateclaw-server/src/main/java/vip/mate/tool/document/preview/OfficePreviewService.java @@ -0,0 +1,194 @@ +package vip.mate.tool.document.preview; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import vip.mate.tool.document.pdf.PdfProperties; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +/** + * Converts office documents (pptx / ppt / doc / xls / odt / ods / odp / rtf / …) + * to PDF for in-browser preview, using a {@code soffice --convert-to pdf} + * subprocess. Formats that the frontend can render directly (pdf / docx / xlsx / + * html / text) never reach this service — it is the fallback path for the ones + * no client-side library covers. + * + *

Reuses the LibreOffice availability contract from {@link PdfProperties}: + * when {@code soffice} is absent or disabled, {@link #isAvailable()} returns + * false and the controller answers {@code 501}, letting the UI degrade to a + * download link. No LibreOffice install is required for the rest of preview to + * work. + * + *

Converted PDFs are cached next to the source under a hidden + * {@code .preview/} directory, keyed by the source file's last-modified time, + * so repeated opens of the same attachment convert only once. The cache lives + * inside the conversation directory and is removed wholesale when the + * conversation's attachments are cleaned up. + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class OfficePreviewService { + + /** Hidden sub-directory (per conversation dir) holding converted preview PDFs. */ + public static final String PREVIEW_DIR = ".preview"; + + private static final long CONVERT_TIMEOUT_SECONDS = 90; + + /** + * Extensions this service will convert. Kept in sync with the frontend + * {@code OFFICE_CONVERT_EXTS} set in {@code previewKind.ts}. Formats the + * browser renders natively (pdf/docx/xlsx/csv/html/text) are intentionally + * excluded — they never hit this endpoint. + */ + private static final Set CONVERTIBLE_EXTS = Set.of( + "ppt", "pptx", "doc", "xls", "odt", "ods", "odp", "rtf", "wps"); + + private final PdfProperties properties; + + /** Whether a usable {@code soffice} binary is present (probed each call, cheap). */ + public boolean isAvailable() { + if (!properties.libreoffice().enabled()) return false; + try { + ProcessBuilder pb = new ProcessBuilder(properties.libreoffice().binary(), "--version"); + pb.redirectErrorStream(true); + Process p = pb.start(); + p.getInputStream().readAllBytes(); + boolean finished = p.waitFor(5, TimeUnit.SECONDS); + if (!finished) { + p.destroyForcibly(); + return false; + } + return p.exitValue() == 0; + } catch (Exception e) { + log.debug("[OfficePreview] soffice probe failed: {}", e.getMessage()); + return false; + } + } + + /** Whether {@code filename}'s extension is one this service can convert. */ + public boolean isConvertible(String filename) { + return CONVERTIBLE_EXTS.contains(extensionOf(filename)); + } + + /** + * Return the preview PDF bytes for {@code source}, converting via soffice on + * a cache miss. Callers must have already verified {@link #isConvertible} + * and {@link #isAvailable}. + * + * @param source an existing, readable office document + * @return converted PDF bytes + * @throws IOException conversion failed or produced no output + */ + public byte[] renderPdf(Path source) throws IOException { + Path cached = cachePathFor(source); + if (isCacheFresh(cached, source)) { + return Files.readAllBytes(cached); + } + byte[] pdf = convert(source); + writeCache(cached, pdf); + return pdf; + } + + // ==================== internals ==================== + + private Path cachePathFor(Path source) { + Path dir = source.getParent().resolve(PREVIEW_DIR); + return dir.resolve(source.getFileName().toString() + ".pdf"); + } + + private boolean isCacheFresh(Path cached, Path source) { + try { + if (!Files.isRegularFile(cached)) return false; + // Fresh only when the cached PDF is at least as new as the source, + // so a re-uploaded/overwritten source invalidates the stale preview. + return Files.getLastModifiedTime(cached).toMillis() + >= Files.getLastModifiedTime(source).toMillis(); + } catch (IOException e) { + return false; + } + } + + private void writeCache(Path cached, byte[] pdf) { + try { + Files.createDirectories(cached.getParent()); + Files.write(cached, pdf); + } catch (IOException e) { + // Non-fatal: a failed cache write just means the next open reconverts. + log.debug("[OfficePreview] failed to cache preview {}: {}", cached, e.getMessage()); + } + } + + private byte[] convert(Path source) throws IOException { + Path tempDir = Files.createTempDirectory("mc_preview_"); + try { + ProcessBuilder pb = new ProcessBuilder( + properties.libreoffice().binary(), + "--headless", + "--convert-to", "pdf", + "--outdir", tempDir.toString(), + source.toString()); + pb.redirectErrorStream(true); + Process p = pb.start(); + byte[] stderr = p.getInputStream().readAllBytes(); + boolean finished; + try { + finished = p.waitFor(CONVERT_TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (InterruptedException e) { + p.destroyForcibly(); + Thread.currentThread().interrupt(); + throw new IOException("soffice conversion interrupted", e); + } + if (!finished) { + p.destroyForcibly(); + throw new IOException("soffice conversion timed out after " + CONVERT_TIMEOUT_SECONDS + "s"); + } + if (p.exitValue() != 0) { + throw new IOException("soffice exit " + p.exitValue() + ": " + new String(stderr).strip()); + } + // soffice names the output after the input basename, extension swapped to .pdf. + String base = stripExtension(source.getFileName().toString()); + Path pdfFile = tempDir.resolve(base + ".pdf"); + if (!Files.isRegularFile(pdfFile)) { + throw new IOException("soffice produced no PDF (stderr: " + new String(stderr).strip() + ")"); + } + return Files.readAllBytes(pdfFile); + } finally { + cleanup(tempDir); + } + } + + private void cleanup(Path tempDir) { + try (var stream = Files.walk(tempDir)) { + List entries = stream.sorted(Comparator.reverseOrder()).toList(); + for (Path entry : entries) { + try { + Files.deleteIfExists(entry); + } catch (IOException ignored) { + // Best-effort; the OS reclaims java.io.tmpdir on reboot. + } + } + } catch (IOException ignored) { + // ditto + } + } + + private static String extensionOf(String filename) { + if (filename == null) return ""; + int idx = filename.lastIndexOf('.'); + return idx >= 0 ? filename.substring(idx + 1).toLowerCase(Locale.ROOT) : ""; + } + + private static String stripExtension(String filename) { + int idx = filename.lastIndexOf('.'); + return idx >= 0 ? filename.substring(0, idx) : filename; + } +} diff --git a/mateclaw-server/src/test/java/vip/mate/channel/web/ChatControllerPreviewRouteTest.java b/mateclaw-server/src/test/java/vip/mate/channel/web/ChatControllerPreviewRouteTest.java new file mode 100644 index 00000000..6b3d8fb9 --- /dev/null +++ b/mateclaw-server/src/test/java/vip/mate/channel/web/ChatControllerPreviewRouteTest.java @@ -0,0 +1,130 @@ +package vip.mate.channel.web; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import vip.mate.agent.AgentService; +import vip.mate.approval.ApprovalWorkflowService; +import vip.mate.memory.identity.MemoryOwnerResolver; +import vip.mate.tool.document.preview.OfficePreviewService; +import vip.mate.workspace.conversation.ConversationService; +import vip.mate.workspace.core.service.ChatUploadLocationResolver; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Route + gating tests for {@code GET /files/{conversationId}/{storedName}/preview}. + * + *

Verifies the preview route is registered as its own handler (not swallowed + * by the raw {@code /files/{conversationId}/{storedName:.+}} mapping) and that + * every gate — ownership, convertibility, converter availability, file presence + * — maps to the intended status code. Standalone MockMvc so no Spring context + * or running server is needed. + */ +class ChatControllerPreviewRouteTest { + + private ConversationService conversationService; + private ChatUploadLocationResolver uploadLocationResolver; + private OfficePreviewService officePreviewService; + private MockMvc mockMvc; + + private static final String CONV = "wecom:someone"; + private static final String STORED = "1777_deck.pptx"; + private static final String URL = "/api/v1/chat/files/" + CONV + "/" + STORED + "/preview"; + + @BeforeEach + void setUp() { + conversationService = mock(ConversationService.class); + uploadLocationResolver = mock(ChatUploadLocationResolver.class); + officePreviewService = mock(OfficePreviewService.class); + + ChatController controller = new ChatController( + mock(AgentService.class), + conversationService, + mock(ApprovalWorkflowService.class), + mock(ChatStreamTracker.class), + new ObjectMapper(), + mock(vip.mate.memory.event.ConversationCompletionPublisher.class), + mock(MemoryOwnerResolver.class), + uploadLocationResolver, + officePreviewService); + + mockMvc = MockMvcBuilders.standaloneSetup(controller).build(); + } + + private UsernamePasswordAuthenticationToken admin() { + return new UsernamePasswordAuthenticationToken("admin", "n/a", List.of()); + } + + @Test + void notOwner_returns403() throws Exception { + when(conversationService.isConversationOwner(eq(CONV), anyString())).thenReturn(false); + mockMvc.perform(MockMvcRequestBuilders.get(URL).principal(admin())) + .andExpect(status().isForbidden()); + } + + @Test + void nonConvertibleFormat_returns415() throws Exception { + when(conversationService.isConversationOwner(eq(CONV), anyString())).thenReturn(true); + when(officePreviewService.isConvertible(STORED)).thenReturn(false); + mockMvc.perform(MockMvcRequestBuilders.get(URL).principal(admin())) + .andExpect(status().isUnsupportedMediaType()); + } + + @Test + void converterUnavailable_returns501() throws Exception { + when(conversationService.isConversationOwner(eq(CONV), anyString())).thenReturn(true); + when(officePreviewService.isConvertible(STORED)).thenReturn(true); + when(officePreviewService.isAvailable()).thenReturn(false); + mockMvc.perform(MockMvcRequestBuilders.get(URL).principal(admin())) + .andExpect(status().isNotImplemented()); + } + + @Test + void fileMissing_returns404() throws Exception { + when(conversationService.isConversationOwner(eq(CONV), anyString())).thenReturn(true); + when(officePreviewService.isConvertible(STORED)).thenReturn(true); + when(officePreviewService.isAvailable()).thenReturn(true); + when(uploadLocationResolver.resolveCandidateConversationDirs(CONV)).thenReturn(List.of()); + mockMvc.perform(MockMvcRequestBuilders.get(URL).principal(admin())) + .andExpect(status().isNotFound()); + } + + @Test + void convertibleAndPresent_returns200Pdf() throws Exception { + Path dir = Files.createTempDirectory("mc_preview_route_"); + try { + Path src = dir.resolve(STORED); + Files.write(src, new byte[]{1, 2, 3}); + + when(conversationService.isConversationOwner(eq(CONV), anyString())).thenReturn(true); + when(officePreviewService.isConvertible(STORED)).thenReturn(true); + when(officePreviewService.isAvailable()).thenReturn(true); + when(uploadLocationResolver.resolveCandidateConversationDirs(CONV)).thenReturn(List.of(dir)); + byte[] pdf = "%PDF-1.4 fake".getBytes(); + when(officePreviewService.renderPdf(any(Path.class))).thenReturn(pdf); + + mockMvc.perform(MockMvcRequestBuilders.get(URL).principal(admin())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_PDF)); + } finally { + Files.deleteIfExists(dir.resolve(STORED)); + Files.deleteIfExists(dir); + } + } +} diff --git a/mateclaw-server/src/test/java/vip/mate/tool/document/preview/OfficePreviewServiceTest.java b/mateclaw-server/src/test/java/vip/mate/tool/document/preview/OfficePreviewServiceTest.java new file mode 100644 index 00000000..3c8d5bdf --- /dev/null +++ b/mateclaw-server/src/test/java/vip/mate/tool/document/preview/OfficePreviewServiceTest.java @@ -0,0 +1,90 @@ +package vip.mate.tool.document.preview; + +import org.junit.jupiter.api.Test; +import vip.mate.tool.document.pdf.PdfProperties; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link OfficePreviewService} covering the pure decision logic + * (convertible-extension gate, availability gate, cache freshness) without + * requiring a real {@code soffice} install on the build host. + */ +class OfficePreviewServiceTest { + + private OfficePreviewService serviceWith(boolean enabled, String binary) { + PdfProperties props = new PdfProperties(null, null, new PdfProperties.Libreoffice(enabled, binary)); + return new OfficePreviewService(props); + } + + @Test + void isConvertible_acceptsOfficeExtensions_caseInsensitive() { + OfficePreviewService svc = serviceWith(true, "soffice"); + assertThat(svc.isConvertible("deck.pptx")).isTrue(); + assertThat(svc.isConvertible("REPORT.PPT")).isTrue(); + assertThat(svc.isConvertible("legacy.doc")).isTrue(); + assertThat(svc.isConvertible("book.xls")).isTrue(); + assertThat(svc.isConvertible("notes.odt")).isTrue(); + } + + @Test + void isConvertible_rejectsFrontendRenderedAndUnknownFormats() { + OfficePreviewService svc = serviceWith(true, "soffice"); + // These are handled client-side and must never hit the converter. + assertThat(svc.isConvertible("a.pdf")).isFalse(); + assertThat(svc.isConvertible("a.docx")).isFalse(); + assertThat(svc.isConvertible("a.xlsx")).isFalse(); + assertThat(svc.isConvertible("a.html")).isFalse(); + assertThat(svc.isConvertible("a.png")).isFalse(); + assertThat(svc.isConvertible("noext")).isFalse(); + } + + @Test + void isAvailable_falseWhenDisabled() { + // Disabled short-circuits before any process spawn. + OfficePreviewService svc = serviceWith(false, "soffice"); + assertThat(svc.isAvailable()).isFalse(); + } + + @Test + void isAvailable_falseWhenBinaryMissing() { + // A binary that cannot be exec'd probes to unavailable, not an exception. + OfficePreviewService svc = serviceWith(true, "definitely-not-a-real-soffice-binary-xyz"); + assertThat(svc.isAvailable()).isFalse(); + } + + @Test + void renderPdf_servesFreshCacheWithoutInvokingConverter() throws Exception { + // A cached PDF newer than the source must be returned verbatim, proving + // the cache short-circuits before any soffice call (host has none). + Path dir = Files.createTempDirectory("mc_preview_test_"); + try { + Path source = dir.resolve("deck.pptx"); + Files.write(source, new byte[]{1, 2, 3}); + + Path cacheDir = dir.resolve(OfficePreviewService.PREVIEW_DIR); + Files.createDirectories(cacheDir); + Path cached = cacheDir.resolve("deck.pptx.pdf"); + byte[] cachedPdf = "%PDF-1.4 cached".getBytes(); + Files.write(cached, cachedPdf); + // Ensure the cache is at least as new as the source. + Files.setLastModifiedTime(cached, Files.getLastModifiedTime(source)); + + OfficePreviewService svc = serviceWith(true, "definitely-not-a-real-soffice-binary-xyz"); + assertThat(svc.renderPdf(source)).isEqualTo(cachedPdf); + } finally { + deleteRecursively(dir); + } + } + + private static void deleteRecursively(Path dir) throws Exception { + try (var walk = Files.walk(dir)) { + walk.sorted(java.util.Comparator.reverseOrder()).forEach(p -> { + try { Files.deleteIfExists(p); } catch (Exception ignored) { } + }); + } + } +} diff --git a/mateclaw-ui/package.json b/mateclaw-ui/package.json index c4b3aea1..45f8c54a 100644 --- a/mateclaw-ui/package.json +++ b/mateclaw-ui/package.json @@ -27,15 +27,18 @@ "cytoscape-cose-bilkent": "^4.1.0", "dagre": "^0.8.5", "dayjs": "^1.11.13", + "docx-preview": "^0.4.0", "dompurify": "^3.3.3", "echarts": "^6.0.0", "element-plus": "^2.9.1", + "exceljs": "^4.4.0", "highlight.js": "^11.11.1", "katex": "^0.16.45", "marked": "^15.0.6", "marked-highlight": "^2.2.3", "mermaid": "^11.14.0", "monaco-editor": "^0.55.1", + "pdfjs-dist": "^6.1.200", "pinia": "^3.0.1", "pixelarticons": "2.1.0", "vue": "^3.5.13", diff --git a/mateclaw-ui/pnpm-lock.yaml b/mateclaw-ui/pnpm-lock.yaml index 974a5750..26a63ef8 100644 --- a/mateclaw-ui/pnpm-lock.yaml +++ b/mateclaw-ui/pnpm-lock.yaml @@ -47,6 +47,9 @@ importers: dayjs: specifier: ^1.11.13 version: 1.11.20 + docx-preview: + specifier: ^0.4.0 + version: 0.4.0 dompurify: specifier: ^3.3.3 version: 3.3.3 @@ -56,6 +59,9 @@ importers: element-plus: specifier: ^2.9.1 version: 2.13.6(typescript@5.7.3)(vue@3.5.31(typescript@5.7.3)) + exceljs: + specifier: ^4.4.0 + version: 4.4.0 highlight.js: specifier: ^11.11.1 version: 11.11.1 @@ -74,6 +80,9 @@ importers: monaco-editor: specifier: ^0.55.1 version: 0.55.1 + pdfjs-dist: + specifier: ^6.1.200 + version: 6.1.200 pinia: specifier: ^3.0.1 version: 3.0.4(typescript@5.7.3)(vue@3.5.31(typescript@5.7.3)) @@ -382,6 +391,12 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fast-csv/format@4.3.5': + resolution: {integrity: sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A==} + + '@fast-csv/parse@4.3.6': + resolution: {integrity: sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA==} + '@floating-ui/core@1.7.5': resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} @@ -474,6 +489,81 @@ packages: peerDependencies: three: '>= 0.159.0' + '@napi-rs/canvas-android-arm64@1.0.2': + resolution: {integrity: sha512-IMXKVQod0ol4vt3gmClUfXz4JAgHYESGPCUqmH3lQxBoL0K/2greJaQE1HVBVxWWFKfLc4OLZVdxg7kXVyXv+g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/canvas-darwin-arm64@1.0.2': + resolution: {integrity: sha512-Sc8tPi6cF+5lqOzCCKFALJHhDiRwyMzTPYm3bbhdXsOunU0lQO5f05ucyOzN2r55I23Hg5bsjH63uSCvWp3EgQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/canvas-darwin-x64@1.0.2': + resolution: {integrity: sha512-niDXZ9LhKB1zLrUdYB64RHQFDGz9rr0eGx061qtJJU3U20EMMIx28ADF5fVYbhtOgkWQrBjFicfaye1yM0U62A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/canvas-linux-arm-gnueabihf@1.0.2': + resolution: {integrity: sha512-sgatQL9JxGRH/Amzcvu0P3t8Am3duou74CisfuJ41Dwt8cWy723z/9KZ8LlgmxfypEwEZxSTNFJtU8d281lmhQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/canvas-linux-arm64-gnu@1.0.2': + resolution: {integrity: sha512-dgKuX0peF3xwY6ZF5QxGS4wbfDqpoFAJYXiLSp+guZKARQUKMkRqZSDrXKj7nfrec3UCMzC0PFCPte0ES98AiA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@napi-rs/canvas-linux-arm64-musl@1.0.2': + resolution: {integrity: sha512-qwROoDIC9upfvDoRLuPn2aNg9CGW1x0Ygr4k2Or+8paA9d0qBLwk87U+g8KQpoOviKoPoiwl97kvBYuYD7qZoA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@napi-rs/canvas-linux-riscv64-gnu@1.0.2': + resolution: {integrity: sha512-fXRjnPihdnbO6qy1QQOgxAonb68A0TCEG7rj1x7v7rxNElsE8EVIKIEUTvyDtU+sthYSbX+8e7g3oZiLGnOmxw==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@napi-rs/canvas-linux-x64-gnu@1.0.2': + resolution: {integrity: sha512-nPR97DXhbWIAy7yazF3jc06kEPMqYMLmPzFOVNlwKPfIoSChnI+x7dc0hTLaihz3jxrjL6j4BbA7earxfx4X3g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@napi-rs/canvas-linux-x64-musl@1.0.2': + resolution: {integrity: sha512-l7zZY5+jL5qnBZtDz7CoBtY6p7EkHu422g/0zWwrOrzIwWyWxZFRfZZORY1UG7YApymPLx+UbOkN206xXn/c1Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@napi-rs/canvas-win32-arm64-msvc@1.0.2': + resolution: {integrity: sha512-yE0koHCFF4PIbMc2o2SEALhnipz7WBISh5glLvQiomtIoCcW0np3H4Lw93ceJAfJttTTeIIWFbwH84F7EVzjMQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/canvas-win32-x64-msvc@1.0.2': + resolution: {integrity: sha512-okU8/t2foV6C31n0GtvEMbfD5rOFc70+/6xUNME9Guld29sgSOIGUEDScAWFlcP3k5TYQRl9TNkwJEEjh15w8A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/canvas@1.0.2': + resolution: {integrity: sha512-EYEqlMYaCbpZDz+IgDH5xp9MTd3ui4dmGqbQYryhMLnSRxrhHKq5KQWHHKxFUcEP4Hp8/BWgvqXocX4j7iSbOQ==} + engines: {node: '>= 10'} + '@rolldown/pluginutils@1.0.0-rc.2': resolution: {integrity: sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==} @@ -835,6 +925,9 @@ packages: '@types/lodash@4.17.24': resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + '@types/node@14.18.63': + resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} + '@types/node@25.9.1': resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} @@ -1022,6 +1115,18 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + archiver-utils@2.1.0: + resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} + engines: {node: '>= 6'} + + archiver-utils@3.0.4: + resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==} + engines: {node: '>= 10'} + + archiver@5.3.2: + resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==} + engines: {node: '>= 10'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1032,6 +1137,9 @@ packages: async-validator@4.2.5: resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -1048,25 +1156,58 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.10.13: resolution: {integrity: sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==} engines: {node: '>=6.0.0'} hasBin: true + big-integer@1.6.52: + resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} + engines: {node: '>=0.6'} + + binary@0.3.0: + resolution: {integrity: sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==} + birpc@2.9.0: resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + bluebird@3.4.7: + resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} + boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} brace-expansion@1.1.13: resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==} + brace-expansion@2.1.2: + resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==} + browserslist@4.28.2: resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + buffer-indexof-polyfill@1.0.2: + resolution: {integrity: sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A==} + engines: {node: '>=0.10'} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + buffers@0.1.1: + resolution: {integrity: sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==} + engines: {node: '>=0.2.0'} + bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} @@ -1086,6 +1227,9 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chainsaw@0.1.0: + resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1126,6 +1270,10 @@ packages: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} + compress-commons@4.1.2: + resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==} + engines: {node: '>= 10'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -1142,12 +1290,24 @@ packages: resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} engines: {node: '>=18'} + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cose-base@1.0.3: resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} cose-base@2.2.0: resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@4.0.3: + resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} + engines: {node: '>= 10'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1357,6 +1517,9 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + docx-preview@0.4.0: + resolution: {integrity: sha512-OdKtE/uj3M4RfGarLkGjahUzRg8/kBp0Sraj1r1NAY1tp/sTpHOBqDrzVf9onMBt9vxP6SdQ6bpLCUCsFwjgcA==} + dompurify@3.2.7: resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} @@ -1367,6 +1530,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + echarts@6.0.0: resolution: {integrity: sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==} @@ -1381,6 +1547,9 @@ packages: emoji-regex@10.6.0: resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + enhanced-resolve@5.20.1: resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} engines: {node: '>=10.13.0'} @@ -1483,6 +1652,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + exceljs@4.4.0: + resolution: {integrity: sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg==} + engines: {node: '>=8.3.0'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} @@ -1490,6 +1663,10 @@ packages: exsolve@1.1.0: resolution: {integrity: sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==} + fast-csv@4.3.6: + resolution: {integrity: sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw==} + engines: {node: '>=10.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1539,11 +1716,22 @@ packages: fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + fstream@1.0.12: + resolution: {integrity: sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==} + engines: {node: '>=0.6'} + deprecated: This package is no longer supported. + function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -1567,6 +1755,10 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + globals@13.24.0: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} @@ -1619,6 +1811,9 @@ packages: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1634,6 +1829,13 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + internmap@1.0.1: resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} @@ -1674,6 +1876,9 @@ packages: resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} engines: {node: '>=16'} + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -1694,6 +1899,9 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + katex@0.16.45: resolution: {integrity: sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==} hasBin: true @@ -1714,6 +1922,10 @@ packages: layout-base@2.0.1: resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1795,6 +2007,9 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + listenercount@1.0.1: + resolution: {integrity: sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==} + lit-element@4.2.2: resolution: {integrity: sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==} @@ -1822,9 +2037,49 @@ packages: lodash: '*' lodash-es: '*' + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + lodash.difference@4.5.0: + resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} + + lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + + lodash.flatten@4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + + lodash.groupby@4.6.0: + resolution: {integrity: sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isequal@4.5.0: + resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lodash.isfunction@3.0.9: + resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==} + + lodash.isnil@4.0.0: + resolution: {integrity: sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isundefined@3.0.1: + resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.union@4.6.0: + resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} @@ -1872,9 +2127,20 @@ packages: minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} @@ -1898,6 +2164,10 @@ packages: node-releases@2.0.37: resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==} + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + normalize-wheel-es@1.2.0: resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==} @@ -1907,6 +2177,9 @@ packages: obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + open@11.0.0: resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} engines: {node: '>=20'} @@ -1926,6 +2199,9 @@ packages: package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -1940,6 +2216,10 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1947,6 +2227,10 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pdfjs-dist@6.1.200: + resolution: {integrity: sha512-o8MolyzirkkLrcdsae/HEOiIcXWI7DS5zGpvqW8xTC2YUsW30rltFw2bDGvw/fskUdEMrQm2br68jzDS5BH2vw==} + engines: {node: '>=22.13.0 || >=24'} + perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} @@ -2003,6 +2287,9 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + promise-worker-transferable@1.0.4: resolution: {integrity: sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==} @@ -2021,6 +2308,16 @@ packages: resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} engines: {node: '>=0.10.0'} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + readdirp@5.0.0: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} @@ -2032,6 +2329,11 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rimraf@2.7.1: + resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + robust-predicates@3.0.3: resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} @@ -2063,14 +2365,27 @@ packages: rw@1.3.3: resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + saxes@5.0.1: + resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} + engines: {node: '>=10'} + semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} hasBin: true + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -2107,6 +2422,12 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@7.2.0: resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} @@ -2133,6 +2454,10 @@ packages: resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} engines: {node: '>=6'} + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + three@0.182.0: resolution: {integrity: sha512-GbHabT+Irv+ihI1/f5kIIsZ+Ef9Sl5A1Y7imvS5RQjWgtTPfPnZ43JmlYI7NtCRDK9zir20lQpfg8/9Yd02OvQ==} @@ -2155,6 +2480,13 @@ packages: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tmp@0.2.7: + resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} + engines: {node: '>=14.14'} + + traverse@0.3.9: + resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} + ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} engines: {node: '>=6.10'} @@ -2228,6 +2560,9 @@ packages: webpack: optional: true + unzipper@0.10.14: + resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -2244,6 +2579,10 @@ packages: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + vite@7.3.1: resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2415,6 +2754,9 @@ packages: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@8.21.0: resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} @@ -2435,6 +2777,9 @@ packages: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} @@ -2451,6 +2796,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zip-stream@4.1.1: + resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} + engines: {node: '>= 10'} + zrender@6.0.0: resolution: {integrity: sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==} @@ -2621,6 +2970,25 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@fast-csv/format@4.3.5': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.isboolean: 3.0.3 + lodash.isequal: 4.5.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + + '@fast-csv/parse@4.3.6': + dependencies: + '@types/node': 14.18.63 + lodash.escaperegexp: 4.1.2 + lodash.groupby: 4.6.0 + lodash.isfunction: 3.0.9 + lodash.isnil: 4.0.0 + lodash.isundefined: 3.0.1 + lodash.uniq: 4.5.0 + '@floating-ui/core@1.7.5': dependencies: '@floating-ui/utils': 0.2.11 @@ -2714,6 +3082,54 @@ snapshots: promise-worker-transferable: 1.0.4 three: 0.182.0 + '@napi-rs/canvas-android-arm64@1.0.2': + optional: true + + '@napi-rs/canvas-darwin-arm64@1.0.2': + optional: true + + '@napi-rs/canvas-darwin-x64@1.0.2': + optional: true + + '@napi-rs/canvas-linux-arm-gnueabihf@1.0.2': + optional: true + + '@napi-rs/canvas-linux-arm64-gnu@1.0.2': + optional: true + + '@napi-rs/canvas-linux-arm64-musl@1.0.2': + optional: true + + '@napi-rs/canvas-linux-riscv64-gnu@1.0.2': + optional: true + + '@napi-rs/canvas-linux-x64-gnu@1.0.2': + optional: true + + '@napi-rs/canvas-linux-x64-musl@1.0.2': + optional: true + + '@napi-rs/canvas-win32-arm64-msvc@1.0.2': + optional: true + + '@napi-rs/canvas-win32-x64-msvc@1.0.2': + optional: true + + '@napi-rs/canvas@1.0.2': + optionalDependencies: + '@napi-rs/canvas-android-arm64': 1.0.2 + '@napi-rs/canvas-darwin-arm64': 1.0.2 + '@napi-rs/canvas-darwin-x64': 1.0.2 + '@napi-rs/canvas-linux-arm-gnueabihf': 1.0.2 + '@napi-rs/canvas-linux-arm64-gnu': 1.0.2 + '@napi-rs/canvas-linux-arm64-musl': 1.0.2 + '@napi-rs/canvas-linux-riscv64-gnu': 1.0.2 + '@napi-rs/canvas-linux-x64-gnu': 1.0.2 + '@napi-rs/canvas-linux-x64-musl': 1.0.2 + '@napi-rs/canvas-win32-arm64-msvc': 1.0.2 + '@napi-rs/canvas-win32-x64-msvc': 1.0.2 + optional: true + '@rolldown/pluginutils@1.0.0-rc.2': {} '@rollup/rollup-android-arm-eabi@4.60.1': @@ -3003,6 +3419,8 @@ snapshots: '@types/lodash@4.17.24': {} + '@types/node@14.18.63': {} + '@types/node@25.9.1': dependencies: undici-types: 7.24.6 @@ -3257,12 +3675,50 @@ snapshots: ansi-styles@6.2.3: {} + archiver-utils@2.1.0: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 2.3.8 + + archiver-utils@3.0.4: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + + archiver@5.3.2: + dependencies: + archiver-utils: 2.1.0 + async: 3.2.6 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 2.2.0 + zip-stream: 4.1.1 + argparse@2.0.1: {} assertion-error@2.0.1: {} async-validator@4.2.5: {} + async@3.2.6: {} + asynckit@0.4.0: {} autoprefixer@10.4.27(postcss@8.5.8): @@ -3284,10 +3740,27 @@ snapshots: balanced-match@1.0.2: {} + base64-js@1.5.1: {} + baseline-browser-mapping@2.10.13: {} + big-integer@1.6.52: {} + + binary@0.3.0: + dependencies: + buffers: 0.1.1 + chainsaw: 0.1.0 + birpc@2.9.0: {} + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + bluebird@3.4.7: {} + boolbase@1.0.0: {} brace-expansion@1.1.13: @@ -3295,6 +3768,10 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@2.1.2: + dependencies: + balanced-match: 1.0.2 + browserslist@4.28.2: dependencies: baseline-browser-mapping: 2.10.13 @@ -3303,6 +3780,17 @@ snapshots: node-releases: 2.0.37 update-browserslist-db: 1.2.3(browserslist@4.28.2) + buffer-crc32@0.2.13: {} + + buffer-indexof-polyfill@1.0.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buffers@0.1.1: {} + bundle-name@4.1.0: dependencies: run-applescript: 7.1.0 @@ -3318,6 +3806,10 @@ snapshots: chai@6.2.2: {} + chainsaw@0.1.0: + dependencies: + traverse: 0.3.9 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -3360,6 +3852,13 @@ snapshots: commander@8.3.0: {} + compress-commons@4.1.2: + dependencies: + buffer-crc32: 0.2.13 + crc32-stream: 4.0.3 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + concat-map@0.0.1: {} confbox@0.1.8: {} @@ -3372,6 +3871,8 @@ snapshots: dependencies: is-what: 5.5.0 + core-util-is@1.0.3: {} + cose-base@1.0.3: dependencies: layout-base: 1.0.2 @@ -3380,6 +3881,13 @@ snapshots: dependencies: layout-base: 2.0.1 + crc-32@1.2.2: {} + + crc32-stream@4.0.3: + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -3604,6 +4112,10 @@ snapshots: detect-libc@2.1.2: {} + docx-preview@0.4.0: + dependencies: + jszip: 3.10.1 + dompurify@3.2.7: optionalDependencies: '@types/trusted-types': 2.0.7 @@ -3618,6 +4130,10 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + duplexer2@0.1.4: + dependencies: + readable-stream: 2.3.8 + echarts@6.0.0: dependencies: tslib: 2.3.0 @@ -3648,6 +4164,10 @@ snapshots: emoji-regex@10.6.0: {} + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + enhanced-resolve@5.20.1: dependencies: graceful-fs: 4.2.11 @@ -3804,10 +4324,27 @@ snapshots: esutils@2.0.3: {} + exceljs@4.4.0: + dependencies: + archiver: 5.3.2 + dayjs: 1.11.20 + fast-csv: 4.3.6 + jszip: 3.10.1 + readable-stream: 3.6.2 + saxes: 5.0.1 + tmp: 0.2.7 + unzipper: 0.10.14 + uuid: 8.3.2 + expect-type@1.3.0: {} exsolve@1.1.0: {} + fast-csv@4.3.6: + dependencies: + '@fast-csv/format': 4.3.5 + '@fast-csv/parse': 4.3.6 + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} @@ -3846,9 +4383,20 @@ snapshots: fraction.js@5.3.4: {} + fs-constants@1.0.0: {} + + fs.realpath@1.0.0: {} + fsevents@2.3.3: optional: true + fstream@1.0.12: + dependencies: + graceful-fs: 4.2.11 + inherits: 2.0.4 + mkdirp: 0.5.6 + rimraf: 2.7.1 + function-bind@1.1.2: {} get-caller-file@2.0.5: {} @@ -3877,6 +4425,15 @@ snapshots: dependencies: is-glob: 4.0.3 + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + globals@13.24.0: dependencies: type-fest: 0.20.2 @@ -3925,6 +4482,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 + ieee754@1.2.1: {} + ignore@5.3.2: {} immediate@3.0.6: {} @@ -3936,6 +4495,13 @@ snapshots: imurmurhash@0.1.4: {} + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + internmap@1.0.1: {} internmap@2.0.3: {} @@ -3962,6 +4528,8 @@ snapshots: dependencies: is-inside-container: 1.0.0 + isarray@1.0.0: {} + isexe@2.0.0: {} jiti@2.6.1: {} @@ -3976,6 +4544,13 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + katex@0.16.45: dependencies: commander: 8.3.0 @@ -3999,6 +4574,10 @@ snapshots: layout-base@2.0.1: {} + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -4057,6 +4636,8 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + listenercount@1.0.1: {} + lit-element@4.2.2: dependencies: '@lit-labs/ssr-dom-shim': 1.5.1 @@ -4091,8 +4672,34 @@ snapshots: lodash: 4.18.1 lodash-es: 4.18.1 + lodash.defaults@4.2.0: {} + + lodash.difference@4.5.0: {} + + lodash.escaperegexp@4.1.2: {} + + lodash.flatten@4.4.0: {} + + lodash.groupby@4.6.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isequal@4.5.0: {} + + lodash.isfunction@3.0.9: {} + + lodash.isnil@4.0.0: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isundefined@3.0.1: {} + lodash.merge@4.6.2: {} + lodash.union@4.6.0: {} + + lodash.uniq@4.5.0: {} + lodash@4.18.1: {} magic-string@0.30.21: @@ -4147,8 +4754,18 @@ snapshots: dependencies: brace-expansion: 1.1.13 + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.2 + + minimist@1.2.8: {} + mitt@3.0.1: {} + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + mlly@1.8.2: dependencies: acorn: 8.16.0 @@ -4171,6 +4788,8 @@ snapshots: node-releases@2.0.37: {} + normalize-path@3.0.0: {} + normalize-wheel-es@1.2.0: {} nth-check@2.1.1: @@ -4179,6 +4798,10 @@ snapshots: obug@2.1.1: {} + once@1.4.0: + dependencies: + wrappy: 1.0.2 + open@11.0.0: dependencies: default-browser: 5.5.0 @@ -4207,6 +4830,8 @@ snapshots: package-manager-detector@1.6.0: {} + pako@1.0.11: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -4217,10 +4842,16 @@ snapshots: path-exists@4.0.0: {} + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} pathe@2.0.3: {} + pdfjs-dist@6.1.200: + optionalDependencies: + '@napi-rs/canvas': 1.0.2 + perfect-debounce@1.0.0: {} picocolors@1.1.1: {} @@ -4274,6 +4905,8 @@ snapshots: prelude-ls@1.2.1: {} + process-nextick-args@2.0.1: {} + promise-worker-transferable@1.0.4: dependencies: is-promise: 2.2.2 @@ -4287,12 +4920,36 @@ snapshots: react@19.2.5: {} + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.9 + readdirp@5.0.0: {} resolve-from@4.0.0: {} rfdc@1.4.1: {} + rimraf@2.7.1: + dependencies: + glob: 7.2.3 + robust-predicates@3.0.3: {} rollup-plugin-visualizer@7.0.1(rollup@4.60.1): @@ -4346,10 +5003,20 @@ snapshots: rw@1.3.3: {} + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + safer-buffer@2.1.2: {} + saxes@5.0.1: + dependencies: + xmlchars: 2.2.0 + semver@7.7.4: {} + setimmediate@1.0.5: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -4376,6 +5043,14 @@ snapshots: get-east-asian-width: 1.6.0 strip-ansi: 7.2.0 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 @@ -4396,6 +5071,14 @@ snapshots: tapable@2.3.2: {} + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + three@0.182.0: {} tinybench@2.9.0: {} @@ -4414,6 +5097,10 @@ snapshots: tinyrainbow@3.1.0: {} + tmp@0.2.7: {} + + traverse@0.3.9: {} + ts-dedent@2.2.0: {} tslib@2.3.0: {} @@ -4468,6 +5155,19 @@ snapshots: rollup: 4.60.1 vite: 7.3.1(@types/node@25.9.1)(jiti@2.6.1)(lightningcss@1.32.0) + unzipper@0.10.14: + dependencies: + big-integer: 1.6.52 + binary: 0.3.0 + bluebird: 3.4.7 + buffer-indexof-polyfill: 1.0.2 + duplexer2: 0.1.4 + fstream: 1.0.12 + graceful-fs: 4.2.11 + listenercount: 1.0.1 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 @@ -4482,6 +5182,8 @@ snapshots: uuid@11.1.0: {} + uuid@8.3.2: {} + vite@7.3.1(@types/node@25.9.1)(jiti@2.6.1)(lightningcss@1.32.0): dependencies: esbuild: 0.27.5 @@ -4609,6 +5311,8 @@ snapshots: string-width: 7.2.0 strip-ansi: 7.2.0 + wrappy@1.0.2: {} + ws@8.21.0: {} wsl-utils@0.3.1: @@ -4618,6 +5322,8 @@ snapshots: xml-name-validator@4.0.0: {} + xmlchars@2.2.0: {} + y18n@5.0.8: {} yargs-parser@22.0.0: {} @@ -4633,6 +5339,12 @@ snapshots: yocto-queue@0.1.0: {} + zip-stream@4.1.1: + dependencies: + archiver-utils: 3.0.4 + compress-commons: 4.1.2 + readable-stream: 3.6.2 + zrender@6.0.0: dependencies: tslib: 2.3.0 diff --git a/mateclaw-ui/src/App.vue b/mateclaw-ui/src/App.vue index d294bad5..7e645364 100644 --- a/mateclaw-ui/src/App.vue +++ b/mateclaw-ui/src/App.vue @@ -4,6 +4,10 @@ + + @@ -18,6 +22,7 @@ import { useSystemSettingsStore } from '@/stores/useSystemSettingsStore' import { useGlobalWikilinkClick } from '@/composables/useGlobalWikilinkClick' import { useGlobalFileDownloadClick } from '@/composables/useGlobalFileDownloadClick' import McConfirmHost from '@/components/common/McConfirmHost.vue' +import FilePreviewDialog from '@/components/chat/preview/FilePreviewDialog.vue' // Initialize theme — applies .dark class to immediately useThemeStore() diff --git a/mateclaw-ui/src/components/chat/MessageBubble.vue b/mateclaw-ui/src/components/chat/MessageBubble.vue index e56b1258..15448f51 100644 --- a/mateclaw-ui/src/components/chat/MessageBubble.vue +++ b/mateclaw-ui/src/components/chat/MessageBubble.vue @@ -365,11 +365,16 @@ :key="attachment.storedName" class="message-attachment" type="button" - @click="downloadFile(attachment)" + @click="openFileAttachment(attachment)" > {{ attachment.name }} {{ formatFileSize(attachment.size) }} + @@ -539,6 +544,7 @@ import { CloseBold, CopyDocument, Document, + Download, InfoFilled, Loading, Microphone, @@ -557,6 +563,8 @@ import { useToolLabel } from '@/composables/useToolLabel' import { http } from '@/api' import { copyToClipboard } from '@/utils/clipboard' import TypingCursor from './TypingCursor.vue' +import { previewKindOf } from './preview/previewKind' +import { openFilePreview } from './preview/previewBus' import BrowserTimeline from './BrowserTimeline.vue' import ToolCallSegment from './ToolCallSegment.vue' import ThinkingSegment from './ThinkingSegment.vue' @@ -575,6 +583,16 @@ const { t, locale } = useI18n() const { getToolLabel } = useToolLabel() const { blobUrls, loadAllImages, loadAllVideos, loadAllAudios, loadAllModels, downloadFile, openImage, getDisplayUrl, revokeAll } = useAuthenticatedAttachment() +// Document attachments: preview in the global dialog when the format is +// supported, otherwise fall back to the legacy download behavior. +function openFileAttachment(attachment: ChatAttachment) { + if (previewKindOf(attachment)) { + openFilePreview(attachment) + } else { + void downloadFile(attachment) + } +} + interface Props { message: Message isLast?: boolean @@ -2451,6 +2469,16 @@ watch(isGenerating, (generating) => { opacity: 0.76; } +.message-attachment__download { + flex-shrink: 0; + opacity: 0.6; + transition: opacity 0.15s; +} + +.message-attachment__download:hover { + opacity: 1; +} + /* ==================== Markdown 样式 ==================== */ .markdown-body :deep(p) { margin: 0 0 10px; diff --git a/mateclaw-ui/src/components/chat/preview/DocxPreview.vue b/mateclaw-ui/src/components/chat/preview/DocxPreview.vue new file mode 100644 index 00000000..79ee41d2 --- /dev/null +++ b/mateclaw-ui/src/components/chat/preview/DocxPreview.vue @@ -0,0 +1,69 @@ + + + + + diff --git a/mateclaw-ui/src/components/chat/preview/FilePreviewDialog.vue b/mateclaw-ui/src/components/chat/preview/FilePreviewDialog.vue new file mode 100644 index 00000000..f82c31fa --- /dev/null +++ b/mateclaw-ui/src/components/chat/preview/FilePreviewDialog.vue @@ -0,0 +1,301 @@ + + + + + + + diff --git a/mateclaw-ui/src/components/chat/preview/HtmlPreview.vue b/mateclaw-ui/src/components/chat/preview/HtmlPreview.vue new file mode 100644 index 00000000..cc3e75cf --- /dev/null +++ b/mateclaw-ui/src/components/chat/preview/HtmlPreview.vue @@ -0,0 +1,64 @@ +