@docyrus/ui-pro-ai-assistant 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +26161 -0
- package/dist/index.js.map +1 -0
- package/package.json +155 -0
- package/src/components/assistant-animations.tsx +29 -0
- package/src/components/assistant-dialogs.tsx +235 -0
- package/src/components/code-view.tsx +278 -0
- package/src/components/create-agent-task.tsx +104 -0
- package/src/components/create-new-work-version.tsx +30 -0
- package/src/components/extract-web.tsx +160 -0
- package/src/components/forward-to-agent.tsx +90 -0
- package/src/components/generate-chart.tsx +101 -0
- package/src/components/generative-action-button.tsx +122 -0
- package/src/components/generative-tool.tsx +685 -0
- package/src/components/generative-ui-object.tsx +210 -0
- package/src/components/input-area.tsx +1209 -0
- package/src/components/json-schema-layout.tsx +326 -0
- package/src/components/list-item-card.tsx +92 -0
- package/src/components/mermaid-diagram.tsx +192 -0
- package/src/components/preview-image.tsx +47 -0
- package/src/components/request-approval.tsx +48 -0
- package/src/components/request-user-input.tsx +270 -0
- package/src/components/search-web.tsx +319 -0
- package/src/components/sheet-command.tsx +88 -0
- package/src/components/shell-canvas.tsx +122 -0
- package/src/components/show-advanced-data-table.tsx +352 -0
- package/src/components/show-generated-content-options.tsx +93 -0
- package/src/components/show-people-cards.tsx +180 -0
- package/src/components/subagent-tool.tsx +180 -0
- package/src/components/text-editor-tool.tsx +328 -0
- package/src/components/work-canvas.tsx +88 -0
- package/src/components/work-card.tsx +42 -0
- package/src/declarations.d.ts +1 -0
- package/src/docy-assistant.tsx +1962 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/use-assistant-api.ts +507 -0
- package/src/hooks/use-deployment-data.ts +162 -0
- package/src/hooks/use-project-state.ts +347 -0
- package/src/hooks/use-session-state.ts +207 -0
- package/src/hooks/use-speech-recognition.ts +137 -0
- package/src/hooks/use-ui-state.ts +185 -0
- package/src/hooks/use-works-state.ts +146 -0
- package/src/i18n/context.tsx +253 -0
- package/src/i18n/index.ts +19 -0
- package/src/i18n/locales/de.json +198 -0
- package/src/i18n/locales/el.json +198 -0
- package/src/i18n/locales/en.json +226 -0
- package/src/i18n/locales/es.json +198 -0
- package/src/i18n/locales/fr.json +198 -0
- package/src/i18n/locales/it.json +198 -0
- package/src/i18n/locales/pt.json +198 -0
- package/src/i18n/locales/sl.json +198 -0
- package/src/i18n/locales/tr.json +211 -0
- package/src/i18n/types.ts +23 -0
- package/src/i18n/use-translation.ts +17 -0
- package/src/index.ts +18 -0
- package/src/internal/plate-editor/editor/auth-context.ts +11 -0
- package/src/internal/plate-editor/editor/editor-base-kit.tsx +39 -0
- package/src/internal/plate-editor/editor/editor-kit.tsx +89 -0
- package/src/internal/plate-editor/editor/plate-editor.tsx +75 -0
- package/src/internal/plate-editor/editor/plate-types.ts +126 -0
- package/src/internal/plate-editor/editor/plugins/ai-kit.tsx +172 -0
- package/src/internal/plate-editor/editor/plugins/autoformat-kit.tsx +211 -0
- package/src/internal/plate-editor/editor/plugins/basic-blocks-base-kit.tsx +26 -0
- package/src/internal/plate-editor/editor/plugins/basic-blocks-kit.tsx +51 -0
- package/src/internal/plate-editor/editor/plugins/basic-marks-base-kit.tsx +24 -0
- package/src/internal/plate-editor/editor/plugins/basic-marks-kit.tsx +38 -0
- package/src/internal/plate-editor/editor/plugins/basic-nodes-kit.tsx +6 -0
- package/src/internal/plate-editor/editor/plugins/block-menu-kit.tsx +14 -0
- package/src/internal/plate-editor/editor/plugins/block-placeholder-kit.tsx +17 -0
- package/src/internal/plate-editor/editor/plugins/block-selection-kit.tsx +31 -0
- package/src/internal/plate-editor/editor/plugins/callout-base-kit.tsx +5 -0
- package/src/internal/plate-editor/editor/plugins/callout-kit.tsx +7 -0
- package/src/internal/plate-editor/editor/plugins/code-block-base-kit.tsx +23 -0
- package/src/internal/plate-editor/editor/plugins/code-block-kit.tsx +26 -0
- package/src/internal/plate-editor/editor/plugins/column-base-kit.tsx +8 -0
- package/src/internal/plate-editor/editor/plugins/column-kit.tsx +7 -0
- package/src/internal/plate-editor/editor/plugins/comment-base-kit.tsx +5 -0
- package/src/internal/plate-editor/editor/plugins/comment-kit.tsx +174 -0
- package/src/internal/plate-editor/editor/plugins/copilot-kit.tsx +68 -0
- package/src/internal/plate-editor/editor/plugins/cursor-overlay-kit.tsx +13 -0
- package/src/internal/plate-editor/editor/plugins/date-base-kit.tsx +5 -0
- package/src/internal/plate-editor/editor/plugins/date-kit.tsx +7 -0
- package/src/internal/plate-editor/editor/plugins/discussion-kit.tsx +36 -0
- package/src/internal/plate-editor/editor/plugins/dnd-kit.tsx +27 -0
- package/src/internal/plate-editor/editor/plugins/docx-export-kit.tsx +43 -0
- package/src/internal/plate-editor/editor/plugins/docx-kit.tsx +6 -0
- package/src/internal/plate-editor/editor/plugins/emoji-kit.tsx +15 -0
- package/src/internal/plate-editor/editor/plugins/exit-break-kit.tsx +12 -0
- package/src/internal/plate-editor/editor/plugins/floating-toolbar-kit.tsx +19 -0
- package/src/internal/plate-editor/editor/plugins/font-base-kit.tsx +36 -0
- package/src/internal/plate-editor/editor/plugins/font-kit.tsx +47 -0
- package/src/internal/plate-editor/editor/plugins/indent-base-kit.tsx +19 -0
- package/src/internal/plate-editor/editor/plugins/indent-kit.tsx +22 -0
- package/src/internal/plate-editor/editor/plugins/link-base-kit.tsx +5 -0
- package/src/internal/plate-editor/editor/plugins/link-kit.tsx +35 -0
- package/src/internal/plate-editor/editor/plugins/list-base-kit.tsx +24 -0
- package/src/internal/plate-editor/editor/plugins/list-kit.tsx +27 -0
- package/src/internal/plate-editor/editor/plugins/markdown-kit.tsx +18 -0
- package/src/internal/plate-editor/editor/plugins/math-base-kit.tsx +8 -0
- package/src/internal/plate-editor/editor/plugins/math-kit.tsx +10 -0
- package/src/internal/plate-editor/editor/plugins/media-base-kit.tsx +37 -0
- package/src/internal/plate-editor/editor/plugins/media-kit.tsx +53 -0
- package/src/internal/plate-editor/editor/plugins/mention-base-kit.tsx +5 -0
- package/src/internal/plate-editor/editor/plugins/mention-kit.tsx +36 -0
- package/src/internal/plate-editor/editor/plugins/slash-kit.tsx +17 -0
- package/src/internal/plate-editor/editor/plugins/suggestion-base-kit.tsx +5 -0
- package/src/internal/plate-editor/editor/plugins/suggestion-kit.tsx +95 -0
- package/src/internal/plate-editor/editor/plugins/table-base-kit.tsx +20 -0
- package/src/internal/plate-editor/editor/plugins/table-kit.tsx +22 -0
- package/src/internal/plate-editor/editor/plugins/toc-base-kit.tsx +5 -0
- package/src/internal/plate-editor/editor/plugins/toc-kit.tsx +14 -0
- package/src/internal/plate-editor/editor/plugins/toggle-base-kit.tsx +5 -0
- package/src/internal/plate-editor/editor/plugins/toggle-kit.tsx +9 -0
- package/src/internal/plate-editor/editor/transforms.ts +165 -0
- package/src/internal/plate-editor/editor/use-chat.ts +152 -0
- package/src/internal/plate-editor/hooks/index.ts +3 -0
- package/src/internal/plate-editor/hooks/use-copy-to-clipboard.ts +31 -0
- package/src/internal/plate-editor/hooks/use-is-touch-device.ts +26 -0
- package/src/internal/plate-editor/hooks/use-lock-scroll.ts +21 -0
- package/src/internal/plate-editor/hooks/use-media-query.ts +44 -0
- package/src/internal/plate-editor/hooks/use-mounted.ts +18 -0
- package/src/internal/plate-editor/hooks/use-on-click-outside.ts +114 -0
- package/src/internal/plate-editor/hooks/use-upload-file.ts +81 -0
- package/src/internal/plate-editor/i18n/context.tsx +58 -0
- package/src/internal/plate-editor/i18n/index.ts +3 -0
- package/src/internal/plate-editor/i18n/locales/de.json +57 -0
- package/src/internal/plate-editor/i18n/locales/el.json +57 -0
- package/src/internal/plate-editor/i18n/locales/en.json +57 -0
- package/src/internal/plate-editor/i18n/locales/es.json +57 -0
- package/src/internal/plate-editor/i18n/locales/fr.json +57 -0
- package/src/internal/plate-editor/i18n/locales/it.json +57 -0
- package/src/internal/plate-editor/i18n/locales/pt.json +57 -0
- package/src/internal/plate-editor/i18n/locales/sl.json +57 -0
- package/src/internal/plate-editor/i18n/locales/tr.json +57 -0
- package/src/internal/plate-editor/i18n/types.ts +59 -0
- package/src/internal/plate-editor/i18n/use-translation.ts +22 -0
- package/src/internal/plate-editor/index.ts +39 -0
- package/src/internal/plate-editor/lib/ai-output-converter.ts +153 -0
- package/src/internal/plate-editor/lib/download-file.ts +17 -0
- package/src/internal/plate-editor/plate-ui/ai-chat-editor.tsx +26 -0
- package/src/internal/plate-editor/plate-ui/ai-menu.tsx +828 -0
- package/src/internal/plate-editor/plate-ui/ai-node.tsx +41 -0
- package/src/internal/plate-editor/plate-ui/ai-toolbar-button.tsx +27 -0
- package/src/internal/plate-editor/plate-ui/alert-dialog.tsx +147 -0
- package/src/internal/plate-editor/plate-ui/align-toolbar-button.tsx +90 -0
- package/src/internal/plate-editor/plate-ui/avatar.tsx +3 -0
- package/src/internal/plate-editor/plate-ui/block-context-menu.tsx +106 -0
- package/src/internal/plate-editor/plate-ui/block-discussion.tsx +364 -0
- package/src/internal/plate-editor/plate-ui/block-draggable.tsx +556 -0
- package/src/internal/plate-editor/plate-ui/block-list-static.tsx +78 -0
- package/src/internal/plate-editor/plate-ui/block-list.tsx +85 -0
- package/src/internal/plate-editor/plate-ui/block-menu.tsx +557 -0
- package/src/internal/plate-editor/plate-ui/block-selection.tsx +47 -0
- package/src/internal/plate-editor/plate-ui/block-suggestion.tsx +469 -0
- package/src/internal/plate-editor/plate-ui/blockquote-node-static.tsx +10 -0
- package/src/internal/plate-editor/plate-ui/blockquote-node.tsx +11 -0
- package/src/internal/plate-editor/plate-ui/button.tsx +190 -0
- package/src/internal/plate-editor/plate-ui/calendar.tsx +3 -0
- package/src/internal/plate-editor/plate-ui/callout-node-static.tsx +76 -0
- package/src/internal/plate-editor/plate-ui/callout-node.tsx +54 -0
- package/src/internal/plate-editor/plate-ui/caption.tsx +48 -0
- package/src/internal/plate-editor/plate-ui/checkbox.tsx +3 -0
- package/src/internal/plate-editor/plate-ui/code-block-node-static.tsx +172 -0
- package/src/internal/plate-editor/plate-ui/code-block-node.tsx +228 -0
- package/src/internal/plate-editor/plate-ui/code-node-static.tsx +11 -0
- package/src/internal/plate-editor/plate-ui/code-node.tsx +12 -0
- package/src/internal/plate-editor/plate-ui/column-node-static.tsx +65 -0
- package/src/internal/plate-editor/plate-ui/column-node.tsx +24 -0
- package/src/internal/plate-editor/plate-ui/command.tsx +204 -0
- package/src/internal/plate-editor/plate-ui/comment-node-static.tsx +12 -0
- package/src/internal/plate-editor/plate-ui/comment-node.tsx +45 -0
- package/src/internal/plate-editor/plate-ui/comment-toolbar-button.tsx +24 -0
- package/src/internal/plate-editor/plate-ui/comment.tsx +619 -0
- package/src/internal/plate-editor/plate-ui/cursor-overlay.tsx +85 -0
- package/src/internal/plate-editor/plate-ui/date-node-static.tsx +43 -0
- package/src/internal/plate-editor/plate-ui/date-node.tsx +56 -0
- package/src/internal/plate-editor/plate-ui/dialog.tsx +426 -0
- package/src/internal/plate-editor/plate-ui/dropdown-menu.tsx +266 -0
- package/src/internal/plate-editor/plate-ui/editor-static.tsx +40 -0
- package/src/internal/plate-editor/plate-ui/editor.tsx +148 -0
- package/src/internal/plate-editor/plate-ui/emoji-node.tsx +48 -0
- package/src/internal/plate-editor/plate-ui/emoji-toolbar-button.tsx +626 -0
- package/src/internal/plate-editor/plate-ui/equation-node-static.tsx +155 -0
- package/src/internal/plate-editor/plate-ui/equation-node.tsx +226 -0
- package/src/internal/plate-editor/plate-ui/equation-toolbar-button.tsx +26 -0
- package/src/internal/plate-editor/plate-ui/export-toolbar-button.tsx +208 -0
- package/src/internal/plate-editor/plate-ui/fixed-toolbar-buttons.tsx +157 -0
- package/src/internal/plate-editor/plate-ui/fixed-toolbar.tsx +27 -0
- package/src/internal/plate-editor/plate-ui/floating-discussion.tsx +1129 -0
- package/src/internal/plate-editor/plate-ui/floating-toolbar-buttons.tsx +129 -0
- package/src/internal/plate-editor/plate-ui/floating-toolbar.tsx +99 -0
- package/src/internal/plate-editor/plate-ui/font-color-toolbar-button.tsx +211 -0
- package/src/internal/plate-editor/plate-ui/font-size-toolbar-button.tsx +154 -0
- package/src/internal/plate-editor/plate-ui/ghost-text.tsx +20 -0
- package/src/internal/plate-editor/plate-ui/heading-node-static.tsx +52 -0
- package/src/internal/plate-editor/plate-ui/heading-node.tsx +56 -0
- package/src/internal/plate-editor/plate-ui/highlight-node-static.tsx +9 -0
- package/src/internal/plate-editor/plate-ui/highlight-node.tsx +11 -0
- package/src/internal/plate-editor/plate-ui/history-toolbar-button.tsx +52 -0
- package/src/internal/plate-editor/plate-ui/hover-card.tsx +7 -0
- package/src/internal/plate-editor/plate-ui/hr-node-static.tsx +18 -0
- package/src/internal/plate-editor/plate-ui/hr-node.tsx +28 -0
- package/src/internal/plate-editor/plate-ui/import-toolbar-button.tsx +124 -0
- package/src/internal/plate-editor/plate-ui/indent-toolbar-button.tsx +34 -0
- package/src/internal/plate-editor/plate-ui/inline-combobox.tsx +409 -0
- package/src/internal/plate-editor/plate-ui/input.tsx +39 -0
- package/src/internal/plate-editor/plate-ui/insert-toolbar-button.tsx +260 -0
- package/src/internal/plate-editor/plate-ui/label.tsx +1 -0
- package/src/internal/plate-editor/plate-ui/line-height-toolbar-button.tsx +71 -0
- package/src/internal/plate-editor/plate-ui/link-node-static.tsx +15 -0
- package/src/internal/plate-editor/plate-ui/link-node.tsx +33 -0
- package/src/internal/plate-editor/plate-ui/link-toolbar-button.tsx +30 -0
- package/src/internal/plate-editor/plate-ui/link-toolbar.tsx +149 -0
- package/src/internal/plate-editor/plate-ui/list-toolbar-button.tsx +179 -0
- package/src/internal/plate-editor/plate-ui/mark-toolbar-button.tsx +36 -0
- package/src/internal/plate-editor/plate-ui/media-audio-node-static.tsx +21 -0
- package/src/internal/plate-editor/plate-ui/media-audio-node.tsx +32 -0
- package/src/internal/plate-editor/plate-ui/media-embed-node.tsx +103 -0
- package/src/internal/plate-editor/plate-ui/media-file-node-static.tsx +30 -0
- package/src/internal/plate-editor/plate-ui/media-file-node.tsx +52 -0
- package/src/internal/plate-editor/plate-ui/media-image-node-static.tsx +37 -0
- package/src/internal/plate-editor/plate-ui/media-image-node.tsx +183 -0
- package/src/internal/plate-editor/plate-ui/media-placeholder-node.tsx +441 -0
- package/src/internal/plate-editor/plate-ui/media-preview-dialog.tsx +127 -0
- package/src/internal/plate-editor/plate-ui/media-toolbar-button.tsx +229 -0
- package/src/internal/plate-editor/plate-ui/media-toolbar.tsx +216 -0
- package/src/internal/plate-editor/plate-ui/media-upload-toast.tsx +73 -0
- package/src/internal/plate-editor/plate-ui/media-video-node-static.tsx +35 -0
- package/src/internal/plate-editor/plate-ui/media-video-node.tsx +119 -0
- package/src/internal/plate-editor/plate-ui/mention-node-static.tsx +46 -0
- package/src/internal/plate-editor/plate-ui/mention-node.tsx +79 -0
- package/src/internal/plate-editor/plate-ui/menu.tsx +532 -0
- package/src/internal/plate-editor/plate-ui/mode-toolbar-button.tsx +126 -0
- package/src/internal/plate-editor/plate-ui/more-toolbar-button.tsx +34 -0
- package/src/internal/plate-editor/plate-ui/paragraph-node-static.tsx +15 -0
- package/src/internal/plate-editor/plate-ui/paragraph-node.tsx +16 -0
- package/src/internal/plate-editor/plate-ui/popover.tsx +77 -0
- package/src/internal/plate-editor/plate-ui/progress.tsx +1 -0
- package/src/internal/plate-editor/plate-ui/remote-cursor-overlay.tsx +81 -0
- package/src/internal/plate-editor/plate-ui/resize-handle.tsx +88 -0
- package/src/internal/plate-editor/plate-ui/separator.tsx +43 -0
- package/src/internal/plate-editor/plate-ui/slash-node.tsx +435 -0
- package/src/internal/plate-editor/plate-ui/spinner.tsx +1 -0
- package/src/internal/plate-editor/plate-ui/suggestion-node-static.tsx +35 -0
- package/src/internal/plate-editor/plate-ui/suggestion-node.tsx +168 -0
- package/src/internal/plate-editor/plate-ui/suggestion-toolbar-button.tsx +24 -0
- package/src/internal/plate-editor/plate-ui/table-node-static.tsx +85 -0
- package/src/internal/plate-editor/plate-ui/table-node.tsx +285 -0
- package/src/internal/plate-editor/plate-ui/table-toolbar-button.tsx +254 -0
- package/src/internal/plate-editor/plate-ui/tabs.tsx +3 -0
- package/src/internal/plate-editor/plate-ui/textarea.tsx +58 -0
- package/src/internal/plate-editor/plate-ui/toc-node-static.tsx +142 -0
- package/src/internal/plate-editor/plate-ui/toc-node.tsx +57 -0
- package/src/internal/plate-editor/plate-ui/toc-sidebar.tsx +50 -0
- package/src/internal/plate-editor/plate-ui/toggle-node-static.tsx +18 -0
- package/src/internal/plate-editor/plate-ui/toggle-node.tsx +33 -0
- package/src/internal/plate-editor/plate-ui/toggle-toolbar-button.tsx +26 -0
- package/src/internal/plate-editor/plate-ui/toggle.tsx +3 -0
- package/src/internal/plate-editor/plate-ui/toolbar.tsx +380 -0
- package/src/internal/plate-editor/plate-ui/tooltip.tsx +149 -0
- package/src/internal/plate-editor/plate-ui/turn-into-toolbar-button.tsx +177 -0
- package/src/internal/plate-editor/types/index.ts +22 -0
- package/src/internal/plate-editor/vite.ts +284 -0
- package/src/internal/sheets/components/univer-sheets.tsx +1104 -0
- package/src/internal/sheets/i18n/context.tsx +183 -0
- package/src/internal/sheets/i18n/index.ts +19 -0
- package/src/internal/sheets/i18n/locales/de.json +21 -0
- package/src/internal/sheets/i18n/locales/el.json +21 -0
- package/src/internal/sheets/i18n/locales/en.json +21 -0
- package/src/internal/sheets/i18n/locales/es.json +21 -0
- package/src/internal/sheets/i18n/locales/fr.json +21 -0
- package/src/internal/sheets/i18n/locales/it.json +21 -0
- package/src/internal/sheets/i18n/locales/pt.json +21 -0
- package/src/internal/sheets/i18n/locales/sl.json +21 -0
- package/src/internal/sheets/i18n/locales/tr.json +21 -0
- package/src/internal/sheets/i18n/types.ts +23 -0
- package/src/internal/sheets/i18n/use-translation.ts +17 -0
- package/src/internal/sheets/index.ts +14 -0
- package/src/internal/sheets/types/css.d.ts +11 -0
- package/src/internal/sheets/types/index.ts +260 -0
- package/src/internal/sheets/xlsx.ts +1169 -0
- package/src/lib/api-client.ts +77 -0
- package/src/lib/assistant-api-actions.ts +549 -0
- package/src/lib/assistant-config.tsx +71 -0
- package/src/lib/class-utils.ts +40 -0
- package/src/lib/index.ts +7 -0
- package/src/lib/message-utils.ts +131 -0
- package/src/tools/tools-schema.json +512 -0
- package/src/types/index.ts +235 -0
- package/src/views/assistant-view.tsx +1137 -0
- package/src/views/canvas-app.tsx +839 -0
- package/src/views/canvas-code.tsx +93 -0
- package/src/views/canvas-deep-research.tsx +44 -0
- package/src/views/canvas-image.tsx +25 -0
- package/src/views/canvas-record-view.tsx +285 -0
- package/src/views/canvas-spreadsheet.tsx +125 -0
- package/src/views/canvas-text.tsx +52 -0
- package/src/views/canvas.tsx +274 -0
- package/src/views/chat-panel.tsx +149 -0
- package/src/views/index.ts +20 -0
- package/src/views/memories-panel.tsx +365 -0
- package/src/views/message-list.tsx +370 -0
- package/src/views/project-detail.tsx +257 -0
- package/src/views/projects-panel.tsx +131 -0
- package/src/views/sessions-list.tsx +98 -0
- package/src/views/sidebar-content.tsx +256 -0
- package/src/views/work-detail.tsx +98 -0
- package/src/vite.ts +284 -0
|
@@ -0,0 +1,1169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-compatible XLSX ↔ Univer Converter
|
|
3
|
+
*
|
|
4
|
+
* Uses SheetJS (xlsx) for browser-side Excel file processing.
|
|
5
|
+
* Works in both browser and Cloudflare Workers (no Node.js dependencies).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { type CellObject } from 'xlsx';
|
|
9
|
+
|
|
10
|
+
import * as XLSX from 'xlsx';
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
type AIWorkbookOutput,
|
|
14
|
+
type IBorderData,
|
|
15
|
+
type ICellData,
|
|
16
|
+
type ICellDataMatrix,
|
|
17
|
+
type IColumnData,
|
|
18
|
+
type IMergeRange,
|
|
19
|
+
type IRowData,
|
|
20
|
+
type IStyleData,
|
|
21
|
+
type IWorkbookData,
|
|
22
|
+
type IWorksheetData
|
|
23
|
+
} from './types';
|
|
24
|
+
|
|
25
|
+
/*
|
|
26
|
+
* ============================================================================
|
|
27
|
+
* Utility Functions
|
|
28
|
+
* ============================================================================
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
function generateId(): string {
|
|
32
|
+
return Math.random().toString(36).substring(2, 15);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function colIndexToLetter(colIndex: number): string {
|
|
36
|
+
let result = '';
|
|
37
|
+
let index = colIndex + 1;
|
|
38
|
+
|
|
39
|
+
while (index > 0) {
|
|
40
|
+
const remainder = (index - 1) % 26;
|
|
41
|
+
|
|
42
|
+
result = String.fromCharCode(65 + remainder) + result;
|
|
43
|
+
index = Math.floor((index - 1) / 26);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function letterToColIndex(letter: string): number {
|
|
50
|
+
let col = 0;
|
|
51
|
+
|
|
52
|
+
for (let i = 0; i < letter.length; i++) {
|
|
53
|
+
col = col * 26 + (letter.charCodeAt(i) - 64);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return col - 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function cellRefToRowCol(ref: string): { row: number; col: number } {
|
|
60
|
+
const match = ref.match(/^([A-Z]+)(\d+)$/);
|
|
61
|
+
|
|
62
|
+
if (!match) return { row: 0, col: 0 };
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
row: parseInt(match[2], 10) - 1,
|
|
66
|
+
col: letterToColIndex(match[1])
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function rangeToMerge(range: string): IMergeRange | null {
|
|
71
|
+
const [start, end] = range.split(':');
|
|
72
|
+
|
|
73
|
+
if (!start) return null;
|
|
74
|
+
|
|
75
|
+
const startPos = cellRefToRowCol(start);
|
|
76
|
+
const endPos = end ? cellRefToRowCol(end) : startPos;
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
startRow: startPos.row,
|
|
80
|
+
startColumn: startPos.col,
|
|
81
|
+
endRow: endPos.row,
|
|
82
|
+
endColumn: endPos.col
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// SheetJS color type
|
|
87
|
+
interface XLSXColor {
|
|
88
|
+
rgb?: string;
|
|
89
|
+
theme?: number;
|
|
90
|
+
tint?: number;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// SheetJS style to hex color
|
|
94
|
+
function styleColorToHex(color: XLSXColor | undefined): string | undefined {
|
|
95
|
+
if (!color) return undefined;
|
|
96
|
+
|
|
97
|
+
if (color.rgb) {
|
|
98
|
+
// RGB format: RRGGBB
|
|
99
|
+
return `#${color.rgb}`;
|
|
100
|
+
}
|
|
101
|
+
if (color.theme !== undefined) {
|
|
102
|
+
// Theme colors - return a default, actual theme resolution needs workbook context
|
|
103
|
+
const themeColors = [
|
|
104
|
+
'#000000',
|
|
105
|
+
'#FFFFFF',
|
|
106
|
+
'#1F4E79',
|
|
107
|
+
'#2E75B6',
|
|
108
|
+
'#5B9BD5',
|
|
109
|
+
'#ED7D31',
|
|
110
|
+
'#70AD47',
|
|
111
|
+
'#FFC000'
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
return themeColors[color.theme] || '#000000';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function convertBorderStyle(style: string | undefined): number {
|
|
121
|
+
const styleMap: Record<string, number> = {
|
|
122
|
+
thin: 1,
|
|
123
|
+
medium: 2,
|
|
124
|
+
thick: 3,
|
|
125
|
+
dotted: 4,
|
|
126
|
+
dashed: 5,
|
|
127
|
+
double: 6,
|
|
128
|
+
hair: 7,
|
|
129
|
+
mediumDashed: 8,
|
|
130
|
+
dashDot: 9,
|
|
131
|
+
mediumDashDot: 10,
|
|
132
|
+
dashDotDot: 11,
|
|
133
|
+
mediumDashDotDot: 12,
|
|
134
|
+
slantDashDot: 13
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return styleMap[style || 'thin'] || 1;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function convertHAlign(align: string | undefined): 1 | 2 | 3 | undefined {
|
|
141
|
+
const map: Record<string, 1 | 2 | 3> = { left: 1, center: 2, right: 3 };
|
|
142
|
+
|
|
143
|
+
return align ? map[align] : undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function convertVAlign(align: string | undefined): 1 | 2 | 3 | undefined {
|
|
147
|
+
const map: Record<string, 1 | 2 | 3> = { top: 1, center: 2, bottom: 3 };
|
|
148
|
+
|
|
149
|
+
return align ? map[align] : undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/*
|
|
153
|
+
* ============================================================================
|
|
154
|
+
* XLSX → Univer Converter
|
|
155
|
+
* ============================================================================
|
|
156
|
+
*/
|
|
157
|
+
|
|
158
|
+
export interface XlsxToUniverOptions {
|
|
159
|
+
workbookId?: string;
|
|
160
|
+
workbookName?: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function xlsxToUniver(data: ArrayBuffer | Uint8Array, options: XlsxToUniverOptions = {}): IWorkbookData {
|
|
164
|
+
const workbook = XLSX.read(data, {
|
|
165
|
+
type: 'array',
|
|
166
|
+
cellStyles: true,
|
|
167
|
+
cellFormula: true,
|
|
168
|
+
cellDates: true,
|
|
169
|
+
cellNF: true
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const workbookId = options.workbookId || generateId();
|
|
173
|
+
const workbookName = options.workbookName || workbook.Props?.Title || 'Workbook';
|
|
174
|
+
|
|
175
|
+
const sheetOrder: string[] = [];
|
|
176
|
+
const sheets: Record<string, IWorksheetData> = {};
|
|
177
|
+
const styles: Record<string, IStyleData> = {};
|
|
178
|
+
const styleCache = new Map<string, string>();
|
|
179
|
+
|
|
180
|
+
for (const sheetName of workbook.SheetNames) {
|
|
181
|
+
const ws = workbook.Sheets[sheetName];
|
|
182
|
+
|
|
183
|
+
if (!ws) continue;
|
|
184
|
+
|
|
185
|
+
const sheetId = generateId();
|
|
186
|
+
|
|
187
|
+
sheetOrder.push(sheetId);
|
|
188
|
+
|
|
189
|
+
// Get sheet range
|
|
190
|
+
const range = ws['!ref'] ? XLSX.utils.decode_range(ws['!ref']) : { s: { r: 0, c: 0 }, e: { r: 999, c: 25 } };
|
|
191
|
+
const rowCount = Math.max(range.e.r + 1, 1000);
|
|
192
|
+
const columnCount = Math.max(range.e.c + 1, 26);
|
|
193
|
+
|
|
194
|
+
const cellData: ICellDataMatrix = {};
|
|
195
|
+
const rowData: Record<number, IRowData> = {};
|
|
196
|
+
const columnData: Record<number, IColumnData> = {};
|
|
197
|
+
const mergeData: IMergeRange[] = [];
|
|
198
|
+
|
|
199
|
+
// Process cells
|
|
200
|
+
for (let { r } = range.s; r <= range.e.r; r++) {
|
|
201
|
+
for (let { c } = range.s; c <= range.e.c; c++) {
|
|
202
|
+
const cellAddress = XLSX.utils.encode_cell({ r, c });
|
|
203
|
+
const cell = ws[cellAddress] as CellObject | undefined;
|
|
204
|
+
|
|
205
|
+
if (!cell) continue;
|
|
206
|
+
|
|
207
|
+
if (!cellData[r]) {
|
|
208
|
+
cellData[r] = {};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const cellObj: ICellData = {};
|
|
212
|
+
|
|
213
|
+
// Value and type
|
|
214
|
+
if (cell.f) {
|
|
215
|
+
// Formula
|
|
216
|
+
cellObj.f = cell.f;
|
|
217
|
+
if (cell.v !== undefined) {
|
|
218
|
+
cellObj.v = cell.v as string | number | boolean;
|
|
219
|
+
}
|
|
220
|
+
} else if (cell.v !== undefined) {
|
|
221
|
+
cellObj.v = cell.v as string | number | boolean;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Cell type
|
|
225
|
+
switch (cell.t) {
|
|
226
|
+
case 's':
|
|
227
|
+
cellObj.t = 1;
|
|
228
|
+
break; // STRING
|
|
229
|
+
|
|
230
|
+
case 'n':
|
|
231
|
+
cellObj.t = 2;
|
|
232
|
+
break; // NUMBER
|
|
233
|
+
|
|
234
|
+
case 'b':
|
|
235
|
+
cellObj.t = 3;
|
|
236
|
+
break; // BOOLEAN
|
|
237
|
+
|
|
238
|
+
case 'd':
|
|
239
|
+
cellObj.t = 2;
|
|
240
|
+
break; // DATE (stored as number)
|
|
241
|
+
|
|
242
|
+
default:
|
|
243
|
+
cellObj.t = 1;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Style
|
|
247
|
+
if (cell.s) {
|
|
248
|
+
const style: IStyleData = {};
|
|
249
|
+
let hasStyle = false;
|
|
250
|
+
|
|
251
|
+
// Font
|
|
252
|
+
if (cell.s.font) {
|
|
253
|
+
const { font } = cell.s;
|
|
254
|
+
|
|
255
|
+
if (font.name) {
|
|
256
|
+
style.ff = font.name;
|
|
257
|
+
hasStyle = true;
|
|
258
|
+
}
|
|
259
|
+
if (font.sz) {
|
|
260
|
+
style.fs = font.sz;
|
|
261
|
+
hasStyle = true;
|
|
262
|
+
}
|
|
263
|
+
if (font.bold) {
|
|
264
|
+
style.bl = 1;
|
|
265
|
+
hasStyle = true;
|
|
266
|
+
}
|
|
267
|
+
if (font.italic) {
|
|
268
|
+
style.it = 1;
|
|
269
|
+
hasStyle = true;
|
|
270
|
+
}
|
|
271
|
+
if (font.underline) {
|
|
272
|
+
style.ul = { s: 1 };
|
|
273
|
+
hasStyle = true;
|
|
274
|
+
}
|
|
275
|
+
if (font.strike) {
|
|
276
|
+
style.st = { s: 1 };
|
|
277
|
+
hasStyle = true;
|
|
278
|
+
}
|
|
279
|
+
if (font.color) {
|
|
280
|
+
const rgb = styleColorToHex(font.color);
|
|
281
|
+
|
|
282
|
+
if (rgb) {
|
|
283
|
+
style.cl = { rgb };
|
|
284
|
+
hasStyle = true;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Fill
|
|
290
|
+
if (cell.s.fill?.fgColor) {
|
|
291
|
+
const rgb = styleColorToHex(cell.s.fill.fgColor);
|
|
292
|
+
|
|
293
|
+
if (rgb) {
|
|
294
|
+
style.bg = { rgb };
|
|
295
|
+
hasStyle = true;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Alignment
|
|
300
|
+
if (cell.s.alignment) {
|
|
301
|
+
const align = cell.s.alignment;
|
|
302
|
+
const ht = convertHAlign(align.horizontal);
|
|
303
|
+
const vt = convertVAlign(align.vertical);
|
|
304
|
+
|
|
305
|
+
if (ht) {
|
|
306
|
+
style.ht = ht;
|
|
307
|
+
hasStyle = true;
|
|
308
|
+
}
|
|
309
|
+
if (vt) {
|
|
310
|
+
style.vt = vt;
|
|
311
|
+
hasStyle = true;
|
|
312
|
+
}
|
|
313
|
+
if (align.wrapText) {
|
|
314
|
+
style.tb = 2;
|
|
315
|
+
hasStyle = true;
|
|
316
|
+
}
|
|
317
|
+
if (align.textRotation) {
|
|
318
|
+
style.tr = { a: align.textRotation };
|
|
319
|
+
hasStyle = true;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Border
|
|
324
|
+
if (cell.s.border) {
|
|
325
|
+
const bd: IBorderData = {};
|
|
326
|
+
let hasBorder = false;
|
|
327
|
+
|
|
328
|
+
for (const side of [
|
|
329
|
+
'top',
|
|
330
|
+
'bottom',
|
|
331
|
+
'left',
|
|
332
|
+
'right'
|
|
333
|
+
] as const) {
|
|
334
|
+
const b = cell.s.border[side];
|
|
335
|
+
|
|
336
|
+
if (b?.style) {
|
|
337
|
+
const key = side === 'top' ? 't' : side === 'bottom' ? 'b' : side === 'left' ? 'l' : 'r';
|
|
338
|
+
|
|
339
|
+
bd[key] = {
|
|
340
|
+
s: convertBorderStyle(b.style),
|
|
341
|
+
cl: { rgb: styleColorToHex(b.color) || '#000000' }
|
|
342
|
+
};
|
|
343
|
+
hasBorder = true;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (hasBorder) {
|
|
348
|
+
style.bd = bd;
|
|
349
|
+
hasStyle = true;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Number format
|
|
354
|
+
if (cell.z && cell.z !== 'General') {
|
|
355
|
+
style.n = { pattern: String(cell.z) };
|
|
356
|
+
hasStyle = true;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (hasStyle) {
|
|
360
|
+
const styleJson = JSON.stringify(style);
|
|
361
|
+
let styleId = styleCache.get(styleJson);
|
|
362
|
+
|
|
363
|
+
if (!styleId) {
|
|
364
|
+
styleId = generateId();
|
|
365
|
+
styleCache.set(styleJson, styleId);
|
|
366
|
+
styles[styleId] = style;
|
|
367
|
+
}
|
|
368
|
+
cellObj.s = styleId;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Hyperlink
|
|
373
|
+
if (cell.l) {
|
|
374
|
+
cellObj.custom = { hyperlink: cell.l.Target };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (Object.keys(cellObj).length > 0) {
|
|
378
|
+
cellData[r][c] = cellObj;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Row heights
|
|
384
|
+
if (ws['!rows']) {
|
|
385
|
+
ws['!rows'].forEach((row, idx) => {
|
|
386
|
+
if (row) {
|
|
387
|
+
const rd: IRowData = {};
|
|
388
|
+
|
|
389
|
+
if (row.hpt) rd.h = row.hpt;
|
|
390
|
+
if (row.hidden) rd.hd = 1;
|
|
391
|
+
if (Object.keys(rd).length > 0) {
|
|
392
|
+
rowData[idx] = rd;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Column widths
|
|
399
|
+
if (ws['!cols']) {
|
|
400
|
+
ws['!cols'].forEach((col, idx) => {
|
|
401
|
+
if (col) {
|
|
402
|
+
const cd: IColumnData = {};
|
|
403
|
+
|
|
404
|
+
if (col.wpx) cd.w = col.wpx;
|
|
405
|
+
else if (col.wch) cd.w = col.wch * 7; // Approximate pixels
|
|
406
|
+
if (col.hidden) cd.hd = 1;
|
|
407
|
+
if (Object.keys(cd).length > 0) {
|
|
408
|
+
columnData[idx] = cd;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Merged cells
|
|
415
|
+
if (ws['!merges']) {
|
|
416
|
+
for (const merge of ws['!merges']) {
|
|
417
|
+
mergeData.push({
|
|
418
|
+
startRow: merge.s.r,
|
|
419
|
+
startColumn: merge.s.c,
|
|
420
|
+
endRow: merge.e.r,
|
|
421
|
+
endColumn: merge.e.c
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Build sheet
|
|
427
|
+
const sheetData: IWorksheetData = {
|
|
428
|
+
id: sheetId,
|
|
429
|
+
name: sheetName,
|
|
430
|
+
rowCount,
|
|
431
|
+
columnCount,
|
|
432
|
+
defaultColumnWidth: 88,
|
|
433
|
+
defaultRowHeight: 24,
|
|
434
|
+
cellData
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
if (Object.keys(rowData).length > 0) sheetData.rowData = rowData;
|
|
438
|
+
if (Object.keys(columnData).length > 0) sheetData.columnData = columnData;
|
|
439
|
+
if (mergeData.length > 0) sheetData.mergeData = mergeData;
|
|
440
|
+
|
|
441
|
+
// Freeze panes
|
|
442
|
+
if (ws['!freeze']) {
|
|
443
|
+
const freeze = ws['!freeze'];
|
|
444
|
+
|
|
445
|
+
sheetData.freeze = {
|
|
446
|
+
xSplit: freeze.xSplit || 0,
|
|
447
|
+
ySplit: freeze.ySplit || 0,
|
|
448
|
+
startRow: freeze.ySplit || 0,
|
|
449
|
+
startColumn: freeze.xSplit || 0
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Auto filter
|
|
454
|
+
if (ws['!autofilter']) {
|
|
455
|
+
const filterRef = ws['!autofilter'].ref;
|
|
456
|
+
const filterRange = rangeToMerge(filterRef);
|
|
457
|
+
|
|
458
|
+
if (filterRange) {
|
|
459
|
+
sheetData.autoFilter = { ref: filterRange };
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Data validations (if available)
|
|
464
|
+
if ((ws as any)['!dataValidations']) {
|
|
465
|
+
const dvs = (ws as any)['!dataValidations'] as any[];
|
|
466
|
+
|
|
467
|
+
sheetData.dataValidations = dvs.map(dv => ({
|
|
468
|
+
uid: generateId(),
|
|
469
|
+
type: dv.type === 'list' ? 'list' : 'custom',
|
|
470
|
+
ranges: dv.sqref ? (dv.sqref.split(' ').map(rangeToMerge).filter(Boolean) as IMergeRange[]) : [],
|
|
471
|
+
formula1: dv.formula1,
|
|
472
|
+
formula2: dv.formula2,
|
|
473
|
+
allowBlank: dv.allowBlank,
|
|
474
|
+
showErrorMessage: dv.showErrorMessage,
|
|
475
|
+
showInputMessage: dv.showInputMessage,
|
|
476
|
+
promptTitle: dv.promptTitle,
|
|
477
|
+
prompt: dv.prompt,
|
|
478
|
+
errorTitle: dv.errorTitle,
|
|
479
|
+
error: dv.error
|
|
480
|
+
}));
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
sheets[sheetId] = sheetData;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return {
|
|
487
|
+
id: workbookId,
|
|
488
|
+
name: workbookName,
|
|
489
|
+
locale: 'EN_US',
|
|
490
|
+
sheetOrder,
|
|
491
|
+
sheets,
|
|
492
|
+
styles: Object.keys(styles).length > 0 ? styles : undefined
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/*
|
|
497
|
+
* ============================================================================
|
|
498
|
+
* Univer → XLSX Converter
|
|
499
|
+
* ============================================================================
|
|
500
|
+
*/
|
|
501
|
+
|
|
502
|
+
export function univerToXlsx(data: IWorkbookData): ArrayBuffer {
|
|
503
|
+
const workbook = XLSX.utils.book_new();
|
|
504
|
+
|
|
505
|
+
// Set workbook properties
|
|
506
|
+
workbook.Props = {
|
|
507
|
+
Title: data.name,
|
|
508
|
+
CreatedDate: new Date()
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
for (const sheetId of data.sheetOrder) {
|
|
512
|
+
const sheetData = data.sheets[sheetId];
|
|
513
|
+
|
|
514
|
+
if (!sheetData) continue;
|
|
515
|
+
|
|
516
|
+
// Build AOA (Array of Arrays) for better compatibility
|
|
517
|
+
const aoa: (string | number | boolean | null | undefined)[][] = [];
|
|
518
|
+
|
|
519
|
+
// Find actual data bounds
|
|
520
|
+
let maxRow = 0;
|
|
521
|
+
let maxCol = 0;
|
|
522
|
+
|
|
523
|
+
if (sheetData.cellData) {
|
|
524
|
+
for (const rowStr of Object.keys(sheetData.cellData)) {
|
|
525
|
+
const r = parseInt(rowStr, 10);
|
|
526
|
+
|
|
527
|
+
maxRow = Math.max(maxRow, r);
|
|
528
|
+
const rowCells = sheetData.cellData[r];
|
|
529
|
+
|
|
530
|
+
for (const colStr of Object.keys(rowCells)) {
|
|
531
|
+
const c = parseInt(colStr, 10);
|
|
532
|
+
|
|
533
|
+
maxCol = Math.max(maxCol, c);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Initialize AOA with proper dimensions
|
|
539
|
+
for (let r = 0; r <= maxRow; r++) {
|
|
540
|
+
aoa[r] = new Array(maxCol + 1).fill(null);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Fill AOA with values
|
|
544
|
+
if (sheetData.cellData) {
|
|
545
|
+
for (const rowStr of Object.keys(sheetData.cellData)) {
|
|
546
|
+
const r = parseInt(rowStr, 10);
|
|
547
|
+
const rowCells = sheetData.cellData[r];
|
|
548
|
+
|
|
549
|
+
for (const colStr of Object.keys(rowCells)) {
|
|
550
|
+
const c = parseInt(colStr, 10);
|
|
551
|
+
const cellData = rowCells[c];
|
|
552
|
+
|
|
553
|
+
// Get value (prefer calculated value over formula for simple export)
|
|
554
|
+
if (cellData.v !== undefined && cellData.v !== null) {
|
|
555
|
+
aoa[r][c] = cellData.v;
|
|
556
|
+
} else if (cellData.f) {
|
|
557
|
+
// For formulas without cached value, store the formula result placeholder
|
|
558
|
+
aoa[r][c] = null;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Create worksheet from AOA
|
|
565
|
+
const ws = XLSX.utils.aoa_to_sheet(aoa);
|
|
566
|
+
|
|
567
|
+
// Now add formulas and number formats to cells
|
|
568
|
+
if (sheetData.cellData) {
|
|
569
|
+
for (const rowStr of Object.keys(sheetData.cellData)) {
|
|
570
|
+
const r = parseInt(rowStr, 10);
|
|
571
|
+
const rowCells = sheetData.cellData[r];
|
|
572
|
+
|
|
573
|
+
for (const colStr of Object.keys(rowCells)) {
|
|
574
|
+
const c = parseInt(colStr, 10);
|
|
575
|
+
const cellData = rowCells[c];
|
|
576
|
+
const cellAddress = XLSX.utils.encode_cell({ r, c });
|
|
577
|
+
|
|
578
|
+
// Ensure cell exists
|
|
579
|
+
if (!ws[cellAddress]) {
|
|
580
|
+
ws[cellAddress] = { t: 's', v: '' };
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const cell = ws[cellAddress] as CellObject;
|
|
584
|
+
|
|
585
|
+
// Add formula
|
|
586
|
+
if (cellData.f) {
|
|
587
|
+
cell.f = cellData.f;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Add number format from style
|
|
591
|
+
const styleData
|
|
592
|
+
= typeof cellData.s === 'string' ? data.styles?.[cellData.s] : (cellData.s as IStyleData | undefined);
|
|
593
|
+
|
|
594
|
+
if (styleData?.n?.pattern) {
|
|
595
|
+
// Convert Univer pattern to Excel format if needed
|
|
596
|
+
cell.z = convertNumberFormatPattern(styleData.n.pattern);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Add hyperlink
|
|
600
|
+
if (cellData.custom?.hyperlink) {
|
|
601
|
+
cell.l = { Target: cellData.custom.hyperlink as string };
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Row heights
|
|
608
|
+
if (sheetData.rowData) {
|
|
609
|
+
ws['!rows'] = [];
|
|
610
|
+
for (const rowStr of Object.keys(sheetData.rowData)) {
|
|
611
|
+
const r = parseInt(rowStr, 10);
|
|
612
|
+
const rd = sheetData.rowData[r];
|
|
613
|
+
|
|
614
|
+
if (rd.h || rd.hd) {
|
|
615
|
+
ws['!rows'][r] = {};
|
|
616
|
+
if (rd.h) ws['!rows'][r].hpt = rd.h;
|
|
617
|
+
if (rd.hd === 1) ws['!rows'][r].hidden = true;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Column widths
|
|
623
|
+
if (sheetData.columnData) {
|
|
624
|
+
ws['!cols'] = [];
|
|
625
|
+
for (const colStr of Object.keys(sheetData.columnData)) {
|
|
626
|
+
const c = parseInt(colStr, 10);
|
|
627
|
+
const cd = sheetData.columnData[c];
|
|
628
|
+
|
|
629
|
+
if (cd.w || cd.hd) {
|
|
630
|
+
ws['!cols'][c] = {};
|
|
631
|
+
if (cd.w) ws['!cols'][c].wpx = cd.w;
|
|
632
|
+
if (cd.hd === 1) ws['!cols'][c].hidden = true;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// Merged cells
|
|
638
|
+
if (sheetData.mergeData && sheetData.mergeData.length > 0) {
|
|
639
|
+
ws['!merges'] = sheetData.mergeData.map(m => ({
|
|
640
|
+
s: { r: m.startRow, c: m.startColumn },
|
|
641
|
+
e: { r: m.endRow, c: m.endColumn }
|
|
642
|
+
}));
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Auto filter
|
|
646
|
+
if (sheetData.autoFilter) {
|
|
647
|
+
const { ref } = sheetData.autoFilter;
|
|
648
|
+
|
|
649
|
+
ws['!autofilter'] = {
|
|
650
|
+
ref: `${colIndexToLetter(ref.startColumn)}${ref.startRow + 1}:${colIndexToLetter(ref.endColumn)}${ref.endRow + 1}`
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
XLSX.utils.book_append_sheet(workbook, ws, sheetData.name);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Generate buffer - Note: cellStyles requires SheetJS Pro, so we don't use it
|
|
658
|
+
const buffer = XLSX.write(workbook, {
|
|
659
|
+
type: 'array',
|
|
660
|
+
bookType: 'xlsx',
|
|
661
|
+
compression: true
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
return buffer;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Convert Univer number format pattern to Excel-compatible format
|
|
669
|
+
*/
|
|
670
|
+
function convertNumberFormatPattern(pattern: string): string {
|
|
671
|
+
/*
|
|
672
|
+
* Most patterns should work directly, but some need conversion
|
|
673
|
+
* Remove any locale-specific prefixes like [$-804]
|
|
674
|
+
*/
|
|
675
|
+
let result = pattern;
|
|
676
|
+
|
|
677
|
+
// Remove Chinese locale prefix if present
|
|
678
|
+
result = result.replace(/\[\$-[0-9A-Fa-f]+\]/g, '');
|
|
679
|
+
|
|
680
|
+
// Convert common Univer patterns to Excel patterns
|
|
681
|
+
const patternMap: Record<string, string> = {
|
|
682
|
+
// Date patterns - ensure Excel compatibility
|
|
683
|
+
'yyyy/m/d': 'yyyy/m/d',
|
|
684
|
+
'yyyy-mm-dd': 'yyyy-mm-dd',
|
|
685
|
+
'dd/mm/yyyy': 'dd/mm/yyyy',
|
|
686
|
+
'dd.mm.yyyy': 'dd.mm.yyyy',
|
|
687
|
+
'm/d/yyyy': 'm/d/yyyy',
|
|
688
|
+
// Time patterns
|
|
689
|
+
'h:mm:ss': 'h:mm:ss',
|
|
690
|
+
'hh:mm:ss': 'hh:mm:ss',
|
|
691
|
+
'h:mm': 'h:mm',
|
|
692
|
+
'hh:mm': 'hh:mm',
|
|
693
|
+
// Percentage
|
|
694
|
+
'0%': '0%',
|
|
695
|
+
'0.00%': '0.00%'
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
return patternMap[result.toLowerCase()] || result;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/*
|
|
702
|
+
* ============================================================================
|
|
703
|
+
* CSV Converters
|
|
704
|
+
* ============================================================================
|
|
705
|
+
*/
|
|
706
|
+
|
|
707
|
+
export function csvToUniver(
|
|
708
|
+
csvContent: string,
|
|
709
|
+
options: {
|
|
710
|
+
delimiter?: string;
|
|
711
|
+
sheetName?: string;
|
|
712
|
+
workbookName?: string;
|
|
713
|
+
firstRowIsHeader?: boolean;
|
|
714
|
+
} = {}
|
|
715
|
+
): IWorkbookData {
|
|
716
|
+
const {
|
|
717
|
+
delimiter = ',', sheetName = 'Sheet1', workbookName = 'CSV Import', firstRowIsHeader = false
|
|
718
|
+
} = options;
|
|
719
|
+
|
|
720
|
+
const workbook = XLSX.read(csvContent, {
|
|
721
|
+
type: 'string',
|
|
722
|
+
FS: delimiter
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
// Convert using xlsx parser, then transform
|
|
726
|
+
const ws = workbook.Sheets[workbook.SheetNames[0]];
|
|
727
|
+
|
|
728
|
+
if (!ws) {
|
|
729
|
+
return {
|
|
730
|
+
id: generateId(),
|
|
731
|
+
name: workbookName,
|
|
732
|
+
locale: 'EN_US',
|
|
733
|
+
sheetOrder: [generateId()],
|
|
734
|
+
sheets: {
|
|
735
|
+
[generateId()]: {
|
|
736
|
+
id: generateId(),
|
|
737
|
+
name: sheetName,
|
|
738
|
+
rowCount: 1000,
|
|
739
|
+
columnCount: 26,
|
|
740
|
+
cellData: {}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// Use xlsxToUniver with proper array buffer
|
|
747
|
+
const buffer = XLSX.write(workbook, { type: 'array', bookType: 'xlsx' });
|
|
748
|
+
const result = xlsxToUniver(buffer, { workbookName });
|
|
749
|
+
|
|
750
|
+
// Apply header styling if needed
|
|
751
|
+
if (firstRowIsHeader && result.sheetOrder[0]) {
|
|
752
|
+
const sheet = result.sheets[result.sheetOrder[0]];
|
|
753
|
+
|
|
754
|
+
if (sheet.cellData[0]) {
|
|
755
|
+
const headerStyle: IStyleData = {
|
|
756
|
+
bl: 1,
|
|
757
|
+
bg: { rgb: '#F3F4F6' }
|
|
758
|
+
};
|
|
759
|
+
const styleId = generateId();
|
|
760
|
+
|
|
761
|
+
if (!result.styles) result.styles = {};
|
|
762
|
+
result.styles[styleId] = headerStyle;
|
|
763
|
+
|
|
764
|
+
for (const colKey of Object.keys(sheet.cellData[0])) {
|
|
765
|
+
const cell = sheet.cellData[0][parseInt(colKey, 10)];
|
|
766
|
+
|
|
767
|
+
if (cell) {
|
|
768
|
+
cell.s = styleId;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
// Add freeze
|
|
773
|
+
sheet.freeze = {
|
|
774
|
+
xSplit: 0,
|
|
775
|
+
ySplit: 1,
|
|
776
|
+
startRow: 1,
|
|
777
|
+
startColumn: 0
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
return result;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
export function univerToCsv(data: IWorkbookData, sheetId?: string): string {
|
|
786
|
+
const targetSheetId = sheetId || data.sheetOrder[0];
|
|
787
|
+
const sheetData = data.sheets[targetSheetId];
|
|
788
|
+
|
|
789
|
+
if (!sheetData?.cellData) {
|
|
790
|
+
return '';
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const rows: string[][] = [];
|
|
794
|
+
|
|
795
|
+
// Find bounds
|
|
796
|
+
let maxRow = 0;
|
|
797
|
+
let maxCol = 0;
|
|
798
|
+
|
|
799
|
+
for (const rowStr of Object.keys(sheetData.cellData)) {
|
|
800
|
+
const r = parseInt(rowStr, 10);
|
|
801
|
+
|
|
802
|
+
maxRow = Math.max(maxRow, r);
|
|
803
|
+
for (const colStr of Object.keys(sheetData.cellData[r])) {
|
|
804
|
+
maxCol = Math.max(maxCol, parseInt(colStr, 10));
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
for (let r = 0; r <= maxRow; r++) {
|
|
809
|
+
const row: string[] = [];
|
|
810
|
+
|
|
811
|
+
for (let c = 0; c <= maxCol; c++) {
|
|
812
|
+
const cell = sheetData.cellData[r]?.[c];
|
|
813
|
+
let value = '';
|
|
814
|
+
|
|
815
|
+
if (cell) {
|
|
816
|
+
if (cell.p?.body?.dataStream) {
|
|
817
|
+
value = cell.p.body.dataStream.replace(/\r\n$/, '');
|
|
818
|
+
} else if (cell.v !== undefined) {
|
|
819
|
+
value = String(cell.v);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Escape for CSV
|
|
824
|
+
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
|
|
825
|
+
value = `"${value.replace(/"/g, '""')}"`;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
row.push(value);
|
|
829
|
+
}
|
|
830
|
+
rows.push(row);
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
return rows.map(r => r.join(',')).join('\n');
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/*
|
|
837
|
+
* ============================================================================
|
|
838
|
+
* File Helper Functions (Browser)
|
|
839
|
+
* ============================================================================
|
|
840
|
+
*/
|
|
841
|
+
|
|
842
|
+
export async function fileToUniver(file: File): Promise<IWorkbookData> {
|
|
843
|
+
const buffer = await file.arrayBuffer();
|
|
844
|
+
const ext = file.name.split('.').pop()?.toLowerCase();
|
|
845
|
+
|
|
846
|
+
if (ext === 'csv') {
|
|
847
|
+
const text = new TextDecoder().decode(buffer);
|
|
848
|
+
|
|
849
|
+
return csvToUniver(text, {
|
|
850
|
+
workbookName: file.name.replace(/\.[^.]+$/, '')
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
return xlsxToUniver(buffer, {
|
|
855
|
+
workbookName: file.name.replace(/\.[^.]+$/, '')
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
export function univerToBlob(data: IWorkbookData, format: 'xlsx' | 'csv' = 'xlsx'): Blob {
|
|
860
|
+
if (format === 'csv') {
|
|
861
|
+
const csv = univerToCsv(data);
|
|
862
|
+
|
|
863
|
+
return new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const buffer = univerToXlsx(data);
|
|
867
|
+
|
|
868
|
+
return new Blob([buffer], {
|
|
869
|
+
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
export function downloadWorkbook(data: IWorkbookData, filename: string, format: 'xlsx' | 'csv' = 'xlsx'): void {
|
|
874
|
+
const blob = univerToBlob(data, format);
|
|
875
|
+
const url = URL.createObjectURL(blob);
|
|
876
|
+
const link = document.createElement('a');
|
|
877
|
+
|
|
878
|
+
link.href = url;
|
|
879
|
+
link.download = filename.endsWith(`.${format}`) ? filename : `${filename}.${format}`;
|
|
880
|
+
document.body.appendChild(link);
|
|
881
|
+
link.click();
|
|
882
|
+
document.body.removeChild(link);
|
|
883
|
+
URL.revokeObjectURL(url);
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/*
|
|
887
|
+
* ============================================================================
|
|
888
|
+
* AI Output → Univer Native Format Converter
|
|
889
|
+
* ============================================================================
|
|
890
|
+
*/
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Convert AI output (array-based format) to Univer native format (sparse matrix)
|
|
894
|
+
*
|
|
895
|
+
* @param aiOutput - The AI-generated workbook data in array-based format
|
|
896
|
+
* @returns Univer-compatible IWorkbookData in sparse matrix format
|
|
897
|
+
*/
|
|
898
|
+
export function aiOutputToUniver(aiOutput: AIWorkbookOutput): IWorkbookData {
|
|
899
|
+
// Support both 'workbook' (new) and 'workbookData' (legacy) keys
|
|
900
|
+
const workbookData = aiOutput.workbook || aiOutput.workbookData;
|
|
901
|
+
|
|
902
|
+
if (!workbookData) {
|
|
903
|
+
throw new Error('Missing workbook data in AI output');
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// Validate sheets array exists and is not empty
|
|
907
|
+
if (!workbookData.sheets || !Array.isArray(workbookData.sheets) || workbookData.sheets.length === 0) {
|
|
908
|
+
console.warn('[aiOutputToUniver] Missing or empty sheets array, creating default sheet');
|
|
909
|
+
|
|
910
|
+
// Create a minimal valid workbook with empty sheet
|
|
911
|
+
return {
|
|
912
|
+
id: workbookData.id || 'workbook-1',
|
|
913
|
+
name: workbookData.name || 'Untitled Workbook',
|
|
914
|
+
sheetOrder: ['sheet-1'],
|
|
915
|
+
sheets: {
|
|
916
|
+
'sheet-1': {
|
|
917
|
+
id: 'sheet-1',
|
|
918
|
+
name: 'Sheet1',
|
|
919
|
+
rowCount: 100,
|
|
920
|
+
columnCount: 20,
|
|
921
|
+
cellData: {}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
// Convert sheets array to object map
|
|
928
|
+
const sheets: Record<string, IWorksheetData> = {};
|
|
929
|
+
|
|
930
|
+
for (const sheetEntry of workbookData.sheets) {
|
|
931
|
+
// Skip null/undefined entries (malformed AI output)
|
|
932
|
+
if (!sheetEntry || !sheetEntry.sheet) {
|
|
933
|
+
console.warn('[aiOutputToUniver] Skipping invalid sheet entry:', sheetEntry);
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
const aiSheet = sheetEntry.sheet;
|
|
937
|
+
|
|
938
|
+
// Convert 2D array cellData to sparse matrix
|
|
939
|
+
const cellData: ICellDataMatrix = {};
|
|
940
|
+
|
|
941
|
+
if (aiSheet.cellData) {
|
|
942
|
+
for (let rowIndex = 0; rowIndex < aiSheet.cellData.length; rowIndex++) {
|
|
943
|
+
const row = aiSheet.cellData[rowIndex];
|
|
944
|
+
|
|
945
|
+
if (!row) continue;
|
|
946
|
+
|
|
947
|
+
for (let colIndex = 0; colIndex < row.length; colIndex++) {
|
|
948
|
+
const cell = row[colIndex];
|
|
949
|
+
|
|
950
|
+
if (!cell) continue;
|
|
951
|
+
|
|
952
|
+
// Initialize row if needed
|
|
953
|
+
if (!cellData[rowIndex]) {
|
|
954
|
+
cellData[rowIndex] = {};
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// Copy cell data, removing null values
|
|
958
|
+
const cleanCell: ICellData = {};
|
|
959
|
+
|
|
960
|
+
if (cell.v !== undefined && cell.v !== null) cleanCell.v = cell.v;
|
|
961
|
+
if (cell.t !== undefined && cell.t !== null) cleanCell.t = cell.t;
|
|
962
|
+
if (cell.f !== undefined && cell.f !== null) cleanCell.f = cell.f;
|
|
963
|
+
if (cell.s !== undefined && cell.s !== null) cleanCell.s = cell.s;
|
|
964
|
+
|
|
965
|
+
if (Object.keys(cleanCell).length > 0) {
|
|
966
|
+
cellData[rowIndex][colIndex] = cleanCell;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Convert rowData array to object map
|
|
973
|
+
let rowData: Record<number, IRowData> | undefined;
|
|
974
|
+
|
|
975
|
+
if (aiSheet.rowData && aiSheet.rowData.length > 0) {
|
|
976
|
+
rowData = {};
|
|
977
|
+
for (const rd of aiSheet.rowData) {
|
|
978
|
+
const rowEntry: IRowData = {};
|
|
979
|
+
|
|
980
|
+
if (rd.h !== undefined && rd.h !== null) rowEntry.h = rd.h;
|
|
981
|
+
if (rd.hd !== undefined && rd.hd !== null) rowEntry.hd = rd.hd;
|
|
982
|
+
if (Object.keys(rowEntry).length > 0) {
|
|
983
|
+
rowData[rd.row] = rowEntry;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
// Convert columnData array to object map
|
|
989
|
+
let columnData: Record<number, IColumnData> | undefined;
|
|
990
|
+
|
|
991
|
+
if (aiSheet.columnData && aiSheet.columnData.length > 0) {
|
|
992
|
+
columnData = {};
|
|
993
|
+
for (const cd of aiSheet.columnData) {
|
|
994
|
+
const colEntry: IColumnData = {};
|
|
995
|
+
|
|
996
|
+
if (cd.w !== undefined && cd.w !== null) colEntry.w = cd.w;
|
|
997
|
+
if (cd.hd !== undefined && cd.hd !== null) colEntry.hd = cd.hd;
|
|
998
|
+
if (Object.keys(colEntry).length > 0) {
|
|
999
|
+
columnData[cd.col] = colEntry;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// Build sheet data
|
|
1005
|
+
const sheetData: IWorksheetData = {
|
|
1006
|
+
id: aiSheet.id,
|
|
1007
|
+
name: aiSheet.name,
|
|
1008
|
+
rowCount: aiSheet.rowCount,
|
|
1009
|
+
columnCount: aiSheet.columnCount,
|
|
1010
|
+
cellData
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
// Optional properties
|
|
1014
|
+
if (aiSheet.tabColor) sheetData.tabColor = aiSheet.tabColor;
|
|
1015
|
+
if (aiSheet.hidden !== undefined && aiSheet.hidden !== null) sheetData.hidden = aiSheet.hidden;
|
|
1016
|
+
if (aiSheet.defaultColumnWidth !== undefined && aiSheet.defaultColumnWidth !== null) {
|
|
1017
|
+
sheetData.defaultColumnWidth = aiSheet.defaultColumnWidth;
|
|
1018
|
+
}
|
|
1019
|
+
if (aiSheet.defaultRowHeight !== undefined && aiSheet.defaultRowHeight !== null) {
|
|
1020
|
+
sheetData.defaultRowHeight = aiSheet.defaultRowHeight;
|
|
1021
|
+
}
|
|
1022
|
+
if (aiSheet.showGridlines !== undefined && aiSheet.showGridlines !== null) {
|
|
1023
|
+
sheetData.showGridlines = aiSheet.showGridlines;
|
|
1024
|
+
}
|
|
1025
|
+
if (aiSheet.freeze) sheetData.freeze = aiSheet.freeze;
|
|
1026
|
+
if (rowData && Object.keys(rowData).length > 0) sheetData.rowData = rowData;
|
|
1027
|
+
if (columnData && Object.keys(columnData).length > 0) sheetData.columnData = columnData;
|
|
1028
|
+
if (aiSheet.mergeData && aiSheet.mergeData.length > 0) sheetData.mergeData = aiSheet.mergeData;
|
|
1029
|
+
if (aiSheet.dataValidations && aiSheet.dataValidations.length > 0) {
|
|
1030
|
+
sheetData.dataValidations = aiSheet.dataValidations;
|
|
1031
|
+
}
|
|
1032
|
+
if (aiSheet.conditionalFormattingRules && aiSheet.conditionalFormattingRules.length > 0) {
|
|
1033
|
+
sheetData.conditionalFormattingRules = aiSheet.conditionalFormattingRules;
|
|
1034
|
+
}
|
|
1035
|
+
if (aiSheet.autoFilter) sheetData.autoFilter = aiSheet.autoFilter;
|
|
1036
|
+
|
|
1037
|
+
sheets[sheetEntry.id] = sheetData;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Ensure we have at least one valid sheet after filtering
|
|
1041
|
+
if (Object.keys(sheets).length === 0) {
|
|
1042
|
+
console.warn('[aiOutputToUniver] No valid sheets after filtering, creating default sheet');
|
|
1043
|
+
sheets['sheet-1'] = {
|
|
1044
|
+
id: 'sheet-1',
|
|
1045
|
+
name: 'Sheet1',
|
|
1046
|
+
rowCount: 100,
|
|
1047
|
+
columnCount: 20,
|
|
1048
|
+
cellData: {}
|
|
1049
|
+
};
|
|
1050
|
+
// Fix sheetOrder if empty
|
|
1051
|
+
if (!workbookData.sheetOrder || workbookData.sheetOrder.length === 0) {
|
|
1052
|
+
workbookData.sheetOrder = ['sheet-1'];
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
// Convert styles array to object map (filter out null entries from malformed AI output)
|
|
1057
|
+
let styles: Record<string, IStyleData> | undefined;
|
|
1058
|
+
|
|
1059
|
+
if (workbookData.styles && Array.isArray(workbookData.styles) && workbookData.styles.length > 0) {
|
|
1060
|
+
styles = {};
|
|
1061
|
+
for (const styleEntry of workbookData.styles) {
|
|
1062
|
+
// Skip null/undefined entries (malformed AI output often has [null, null, ...])
|
|
1063
|
+
if (!styleEntry || !styleEntry.id || !styleEntry.style) {
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
styles[styleEntry.id] = styleEntry.style;
|
|
1067
|
+
}
|
|
1068
|
+
// If all entries were null, set styles to undefined
|
|
1069
|
+
if (Object.keys(styles).length === 0) {
|
|
1070
|
+
styles = undefined;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// Ensure sheetOrder is valid and contains actual sheet IDs
|
|
1075
|
+
const validSheetIds = Object.keys(sheets);
|
|
1076
|
+
let { sheetOrder } = workbookData;
|
|
1077
|
+
|
|
1078
|
+
if (!sheetOrder || !Array.isArray(sheetOrder) || sheetOrder.length === 0) {
|
|
1079
|
+
sheetOrder = validSheetIds;
|
|
1080
|
+
} else {
|
|
1081
|
+
// Filter sheetOrder to only include valid sheet IDs
|
|
1082
|
+
sheetOrder = sheetOrder.filter(id => validSheetIds.includes(id));
|
|
1083
|
+
if (sheetOrder.length === 0) {
|
|
1084
|
+
sheetOrder = validSheetIds;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// Build workbook
|
|
1089
|
+
const workbook: IWorkbookData = {
|
|
1090
|
+
id: workbookData.id || 'workbook-1',
|
|
1091
|
+
name: workbookData.name || 'Untitled Workbook',
|
|
1092
|
+
sheetOrder,
|
|
1093
|
+
sheets
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
if (workbookData.locale) workbook.locale = workbookData.locale;
|
|
1097
|
+
if (styles && Object.keys(styles).length > 0) workbook.styles = styles;
|
|
1098
|
+
|
|
1099
|
+
return workbook;
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
/**
|
|
1103
|
+
* Validate AI output structure before conversion
|
|
1104
|
+
*
|
|
1105
|
+
* @param data - Raw parsed JSON from AI
|
|
1106
|
+
* @returns Validation result with errors if any
|
|
1107
|
+
*/
|
|
1108
|
+
export function validateAIOutput(data: unknown): {
|
|
1109
|
+
valid: boolean;
|
|
1110
|
+
errors: string[];
|
|
1111
|
+
} {
|
|
1112
|
+
const errors: string[] = [];
|
|
1113
|
+
|
|
1114
|
+
if (!data || typeof data !== 'object') {
|
|
1115
|
+
errors.push('Response must be an object');
|
|
1116
|
+
|
|
1117
|
+
return { valid: false, errors };
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
const obj = data as Record<string, unknown>;
|
|
1121
|
+
|
|
1122
|
+
// Check required top-level fields
|
|
1123
|
+
if (typeof obj.title !== 'string') errors.push('Missing or invalid "title"');
|
|
1124
|
+
if (typeof obj.description !== 'string') errors.push('Missing or invalid "description"');
|
|
1125
|
+
|
|
1126
|
+
// Support both 'workbook' (new) and 'workbookData' (legacy) keys
|
|
1127
|
+
const workbookObj = obj.workbook || obj.workbookData;
|
|
1128
|
+
|
|
1129
|
+
if (!workbookObj || typeof workbookObj !== 'object') {
|
|
1130
|
+
errors.push('Missing or invalid "workbook" (or "workbookData")');
|
|
1131
|
+
|
|
1132
|
+
return { valid: false, errors };
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
const wb = workbookObj as Record<string, unknown>;
|
|
1136
|
+
|
|
1137
|
+
// Check workbook fields
|
|
1138
|
+
if (typeof wb.id !== 'string') errors.push('workbook.id must be a string');
|
|
1139
|
+
if (typeof wb.name !== 'string') errors.push('workbook.name must be a string');
|
|
1140
|
+
if (!Array.isArray(wb.sheetOrder)) errors.push('workbook.sheetOrder must be an array');
|
|
1141
|
+
if (!Array.isArray(wb.sheets)) errors.push('workbook.sheets must be an array');
|
|
1142
|
+
|
|
1143
|
+
// Check sheets
|
|
1144
|
+
if (Array.isArray(wb.sheets)) {
|
|
1145
|
+
for (let i = 0; i < wb.sheets.length; i++) {
|
|
1146
|
+
const entry = wb.sheets[i] as Record<string, unknown>;
|
|
1147
|
+
|
|
1148
|
+
if (!entry || typeof entry !== 'object') {
|
|
1149
|
+
errors.push(`sheets[${i}] must be an object`);
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
if (typeof entry.id !== 'string') errors.push(`sheets[${i}].id must be a string`);
|
|
1153
|
+
if (!entry.sheet || typeof entry.sheet !== 'object') {
|
|
1154
|
+
errors.push(`sheets[${i}].sheet must be an object`);
|
|
1155
|
+
continue;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
const sheet = entry.sheet as Record<string, unknown>;
|
|
1159
|
+
|
|
1160
|
+
if (typeof sheet.id !== 'string') errors.push(`sheets[${i}].sheet.id must be a string`);
|
|
1161
|
+
if (typeof sheet.name !== 'string') errors.push(`sheets[${i}].sheet.name must be a string`);
|
|
1162
|
+
if (typeof sheet.rowCount !== 'number') errors.push(`sheets[${i}].sheet.rowCount must be a number`);
|
|
1163
|
+
if (typeof sheet.columnCount !== 'number') errors.push(`sheets[${i}].sheet.columnCount must be a number`);
|
|
1164
|
+
if (!Array.isArray(sheet.cellData)) errors.push(`sheets[${i}].sheet.cellData must be a 2D array`);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
return { valid: errors.length === 0, errors };
|
|
1169
|
+
}
|