@tita-agent/dsh-data-table 0.0.0-stage → 0.2.0
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/LICENSE +21 -0
- package/README.md +172 -2
- package/cordis.patch.yml +7 -0
- package/lib/client.js +16766 -0
- package/lib/contract-envelope.d.ts +91 -0
- package/lib/contract-envelope.js +106 -0
- package/lib/contract-layout.d.ts +130 -0
- package/lib/contract-layout.js +138 -0
- package/lib/contract-refs.d.ts +39 -0
- package/lib/contract-refs.js +9 -0
- package/lib/contract-version.d.ts +311 -0
- package/lib/contract-version.js +270 -0
- package/lib/contract-wire.d.ts +147 -0
- package/lib/contract-wire.js +99 -0
- package/lib/contracts.d.ts +12 -0
- package/lib/contracts.js +12 -0
- package/lib/types/audit.d.ts +42 -0
- package/lib/types/audit.js +39 -0
- package/lib/types/client/ProjectImportMenu.d.ts +30 -0
- package/lib/types/client/ProjectImportMenu.js +70 -0
- package/lib/types/client/SheetPanel.d.ts +33 -0
- package/lib/types/client/SheetPanel.js +1766 -0
- package/lib/types/client/SheetToggle.d.ts +18 -0
- package/lib/types/client/SheetToggle.js +26 -0
- package/lib/types/client/index.d.ts +15 -0
- package/lib/types/client/index.js +110 -0
- package/lib/types/client/persistence/changes.d.ts +15 -0
- package/lib/types/client/persistence/changes.js +51 -0
- package/lib/types/client/persistence/ids.d.ts +34 -0
- package/lib/types/client/persistence/ids.js +48 -0
- package/lib/types/client/persistence/merge.d.ts +35 -0
- package/lib/types/client/persistence/merge.js +362 -0
- package/lib/types/client/store.d.ts +61 -0
- package/lib/types/client/store.js +152 -0
- package/lib/types/client/surface-context.d.ts +27 -0
- package/lib/types/client/surface-context.js +104 -0
- package/lib/types/client/surface-control.d.ts +2 -0
- package/lib/types/client/surface-control.js +12 -0
- package/lib/types/client/transport/workbookApi.d.ts +159 -0
- package/lib/types/client/transport/workbookApi.js +246 -0
- package/lib/types/client/univer/StandaloneWorkbookPage.d.ts +5 -0
- package/lib/types/client/univer/StandaloneWorkbookPage.js +7 -0
- package/lib/types/client/univer/WorkbookCanvas.d.ts +40 -0
- package/lib/types/client/univer/WorkbookCanvas.js +87 -0
- package/lib/types/client/univer/WorkbookView.d.ts +3 -0
- package/lib/types/client/univer/WorkbookView.js +2 -0
- package/lib/types/client/univer/loader.d.ts +18 -0
- package/lib/types/client/univer/loader.js +81 -0
- package/lib/types/client/univer/standalone-entry.d.ts +1 -0
- package/lib/types/client/univer/standalone-entry.js +19 -0
- package/lib/types/client/versions/VersionChrome.d.ts +33 -0
- package/lib/types/client/versions/VersionChrome.js +21 -0
- package/lib/types/client/versions/inspect.d.ts +70 -0
- package/lib/types/client/versions/inspect.js +88 -0
- package/lib/types/client/versions/preview-decoration.d.ts +11 -0
- package/lib/types/client/versions/preview-decoration.js +54 -0
- package/lib/types/client/versions/records.d.ts +19 -0
- package/lib/types/client/versions/records.js +70 -0
- package/lib/types/client/workspace-host.d.ts +61 -0
- package/lib/types/client/workspace-host.js +124 -0
- package/lib/types/collaboration-hub.d.ts +31 -0
- package/lib/types/collaboration-hub.js +69 -0
- package/lib/types/contract/ids.d.ts +24 -0
- package/lib/types/contract/ids.js +46 -0
- package/lib/types/contract/mutations.d.ts +25 -0
- package/lib/types/contract/mutations.js +782 -0
- package/lib/types/contract/operations.d.ts +54 -0
- package/lib/types/contract/operations.js +203 -0
- package/lib/types/contract/snapshots.d.ts +176 -0
- package/lib/types/contract/snapshots.js +351 -0
- package/lib/types/contract/univer-api-catalog.d.ts +29 -0
- package/lib/types/contract/univer-api-catalog.js +264 -0
- package/lib/types/errors.d.ts +8 -0
- package/lib/types/errors.js +12 -0
- package/lib/types/http/assets-routes.d.ts +3 -0
- package/lib/types/http/assets-routes.js +169 -0
- package/lib/types/http/collaboration.d.ts +10 -0
- package/lib/types/http/collaboration.js +154 -0
- package/lib/types/http/history.d.ts +15 -0
- package/lib/types/http/history.js +134 -0
- package/lib/types/http/project-files.d.ts +79 -0
- package/lib/types/http/project-files.js +206 -0
- package/lib/types/http/project-scope.d.ts +50 -0
- package/lib/types/http/project-scope.js +52 -0
- package/lib/types/http/routes.d.ts +98 -0
- package/lib/types/http/routes.js +323 -0
- package/lib/types/http/standalone.d.ts +9 -0
- package/lib/types/http/standalone.js +31 -0
- package/lib/types/http/trunk.d.ts +10 -0
- package/lib/types/http/trunk.js +322 -0
- package/lib/types/http/worktree.d.ts +9 -0
- package/lib/types/http/worktree.js +145 -0
- package/lib/types/index.d.ts +18 -0
- package/lib/types/index.js +156 -0
- package/lib/types/invariant.d.ts +15 -0
- package/lib/types/invariant.js +15 -0
- package/lib/types/request-params.d.ts +21 -0
- package/lib/types/request-params.js +76 -0
- package/lib/types/store/paths.d.ts +68 -0
- package/lib/types/store/paths.js +148 -0
- package/lib/types/store/project.d.ts +88 -0
- package/lib/types/store/project.js +120 -0
- package/lib/types/store/read.d.ts +110 -0
- package/lib/types/store/read.js +404 -0
- package/lib/types/store/retention.d.ts +27 -0
- package/lib/types/store/retention.js +111 -0
- package/lib/types/store/write.d.ts +22 -0
- package/lib/types/store/write.js +210 -0
- package/lib/types/tools/arguments.d.ts +40 -0
- package/lib/types/tools/arguments.js +130 -0
- package/lib/types/tools/cells.d.ts +50 -0
- package/lib/types/tools/cells.js +298 -0
- package/lib/types/tools/index.d.ts +2 -0
- package/lib/types/tools/index.js +250 -0
- package/lib/types/tools/render.d.ts +47 -0
- package/lib/types/tools/render.js +290 -0
- package/lib/types/tools/univer-api.d.ts +18 -0
- package/lib/types/tools/univer-api.js +198 -0
- package/lib/types/tools/workbook-catalog.d.ts +3 -0
- package/lib/types/tools/workbook-catalog.js +83 -0
- package/lib/types/tools/workbook-edit.d.ts +192 -0
- package/lib/types/tools/workbook-edit.js +912 -0
- package/lib/types/tools/workbook-find.d.ts +11 -0
- package/lib/types/tools/workbook-find.js +89 -0
- package/lib/types/tools/workbook-history.d.ts +2 -0
- package/lib/types/tools/workbook-history.js +59 -0
- package/lib/types/tools/workbook-read.d.ts +21 -0
- package/lib/types/tools/workbook-read.js +204 -0
- package/lib/types/types.d.ts +377 -0
- package/lib/types/types.js +34 -0
- package/lib/types/univer/apply-operation.d.ts +14 -0
- package/lib/types/univer/apply-operation.js +398 -0
- package/lib/types/univer/chunk.d.ts +68 -0
- package/lib/types/univer/chunk.js +16 -0
- package/lib/types/univer/decoration.d.ts +34 -0
- package/lib/types/univer/decoration.js +88 -0
- package/lib/types/univer/diagnostics.d.ts +31 -0
- package/lib/types/univer/diagnostics.js +141 -0
- package/lib/types/univer/engine.d.ts +33 -0
- package/lib/types/univer/engine.js +338 -0
- package/lib/types/univer/entry.d.ts +8 -0
- package/lib/types/univer/entry.js +11 -0
- package/lib/types/univer/facade.d.ts +107 -0
- package/lib/types/univer/facade.js +1 -0
- package/lib/types/univer/fonts.d.ts +202 -0
- package/lib/types/univer/fonts.js +140 -0
- package/lib/types/univer/plugins/collaboration.d.ts +23 -0
- package/lib/types/univer/plugins/collaboration.js +47 -0
- package/lib/types/univer/plugins/export.d.ts +39 -0
- package/lib/types/univer/plugins/export.js +246 -0
- package/lib/types/univer/plugins/history.d.ts +24 -0
- package/lib/types/univer/plugins/history.js +50 -0
- package/lib/types/univer/plugins/version.d.ts +13 -0
- package/lib/types/univer/plugins/version.js +13 -0
- package/lib/types/version/diff.d.ts +36 -0
- package/lib/types/version/diff.js +156 -0
- package/lib/types/version/grouping.d.ts +7 -0
- package/lib/types/version/grouping.js +94 -0
- package/lib/types/version/records.d.ts +17 -0
- package/lib/types/version/records.js +102 -0
- package/lib/types/version/session.d.ts +36 -0
- package/lib/types/version/session.js +89 -0
- package/lib/types/version/summary.d.ts +24 -0
- package/lib/types/version/summary.js +111 -0
- package/lib/types/workbook-manager.d.ts +191 -0
- package/lib/types/workbook-manager.js +935 -0
- package/lib/types/xlsx-export-worker.d.ts +5 -0
- package/lib/types/xlsx-export-worker.js +24 -0
- package/lib/types/xlsx-export.d.ts +21 -0
- package/lib/types/xlsx-export.js +371 -0
- package/lib/types/xlsx-import-worker.d.ts +1 -0
- package/lib/types/xlsx-import-worker.js +19 -0
- package/lib/univer-client.js +690 -0
- package/lib/workbook-page.js +23826 -0
- package/lib/xlsx-export-worker.js +104 -0
- package/lib/xlsx-import-worker.js +46 -0
- package/package.json +106 -3
- package/src/audit.ts +58 -0
- package/src/client/ProjectImportMenu.tsx +100 -0
- package/src/client/SheetPanel.module.css +1578 -0
- package/src/client/SheetPanel.tsx +1934 -0
- package/src/client/SheetToggle.module.css +73 -0
- package/src/client/SheetToggle.tsx +65 -0
- package/src/client/contract.d.ts +19 -0
- package/src/client/index.ts +131 -0
- package/src/client/persistence/changes.ts +58 -0
- package/src/client/persistence/ids.ts +60 -0
- package/src/client/persistence/merge.ts +392 -0
- package/src/client/store.ts +188 -0
- package/src/client/surface-context.ts +123 -0
- package/src/client/surface-control.ts +13 -0
- package/src/client/transport/workbookApi.ts +355 -0
- package/src/client/univer/SheetView.module.css +110 -0
- package/src/client/univer/StandaloneWorkbookPage.module.css +8 -0
- package/src/client/univer/StandaloneWorkbookPage.tsx +15 -0
- package/src/client/univer/WorkbookCanvas.tsx +128 -0
- package/src/client/univer/WorkbookView.module.css +30 -0
- package/src/client/univer/WorkbookView.tsx +3 -0
- package/src/client/univer/loader.ts +90 -0
- package/src/client/univer/react-dom-client.d.ts +10 -0
- package/src/client/univer/standalone-entry.tsx +19 -0
- package/src/client/univer/standalone.css +12 -0
- package/src/client/univer/univer-locales.d.ts +89 -0
- package/src/client/univer/univer-modules.d.ts +26 -0
- package/src/client/versions/VersionChrome.tsx +114 -0
- package/src/client/versions/inspect.ts +138 -0
- package/src/client/versions/preview-decoration.ts +54 -0
- package/src/client/versions/records.ts +78 -0
- package/src/client/workspace-host.ts +147 -0
- package/src/collaboration-hub.ts +83 -0
- package/src/contract/ids.ts +50 -0
- package/src/contract/mutations.ts +791 -0
- package/src/contract/operations.ts +221 -0
- package/src/contract/snapshots.ts +383 -0
- package/src/contract/univer-api-catalog.ts +289 -0
- package/src/css-modules.d.ts +6 -0
- package/src/errors.ts +25 -0
- package/src/http/assets-routes.ts +177 -0
- package/src/http/collaboration.ts +144 -0
- package/src/http/history.ts +151 -0
- package/src/http/project-files.ts +227 -0
- package/src/http/project-scope.ts +77 -0
- package/src/http/routes.ts +364 -0
- package/src/http/standalone.ts +32 -0
- package/src/http/trunk.ts +322 -0
- package/src/http/worktree.ts +119 -0
- package/src/index.ts +165 -0
- package/src/invariant.ts +17 -0
- package/src/request-params.ts +92 -0
- package/src/store/paths.ts +165 -0
- package/src/store/project.ts +137 -0
- package/src/store/read.ts +454 -0
- package/src/store/retention.ts +106 -0
- package/src/store/write.ts +230 -0
- package/src/tools/arguments.ts +128 -0
- package/src/tools/cells.ts +298 -0
- package/src/tools/index.ts +274 -0
- package/src/tools/render.ts +301 -0
- package/src/tools/univer-api.ts +243 -0
- package/src/tools/workbook-catalog.ts +94 -0
- package/src/tools/workbook-edit.ts +987 -0
- package/src/tools/workbook-find.ts +80 -0
- package/src/tools/workbook-history.ts +63 -0
- package/src/tools/workbook-read.ts +210 -0
- package/src/types.ts +432 -0
- package/src/univer/apply-operation.ts +369 -0
- package/src/univer/chunk.ts +77 -0
- package/src/univer/decoration.ts +111 -0
- package/src/univer/diagnostics.ts +136 -0
- package/src/univer/engine.ts +370 -0
- package/src/univer/entry.ts +14 -0
- package/src/univer/facade.ts +91 -0
- package/src/univer/fonts.ts +148 -0
- package/src/univer/overrides.css +33 -0
- package/src/univer/plugins/collaboration.ts +47 -0
- package/src/univer/plugins/export.ts +236 -0
- package/src/univer/plugins/history.ts +49 -0
- package/src/univer/plugins/version.ts +13 -0
- package/src/version/diff.ts +169 -0
- package/src/version/grouping.ts +96 -0
- package/src/version/records.ts +113 -0
- package/src/version/session.ts +90 -0
- package/src/version/summary.ts +117 -0
- package/src/workbook-manager.ts +1111 -0
- package/src/xlsx-export-worker.ts +34 -0
- package/src/xlsx-export.ts +373 -0
- package/src/xlsx-import-worker.ts +30 -0
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a tool result reaches the model (docs/issue/0022 P3b).
|
|
3
|
+
*
|
|
4
|
+
* A tool answers with text blocks, and a large value is spilled to disk with a
|
|
5
|
+
* pointer instead of being inlined into the conversation. Both decisions are
|
|
6
|
+
* about *presentation*, so they sit together and neither tool owns them.
|
|
7
|
+
*/
|
|
8
|
+
import { randomUUID } from 'node:crypto';
|
|
9
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { formatCellReference } from '#contracts';
|
|
12
|
+
import { operationSummary } from "../contract/operations.js";
|
|
13
|
+
import { toolOutputRoot } from "../store/paths.js";
|
|
14
|
+
import { pruneToolOutput } from "../store/retention.js";
|
|
15
|
+
export function textBlock(text) {
|
|
16
|
+
return { type: 'text', text };
|
|
17
|
+
}
|
|
18
|
+
export function asRecordOrUndefined(value) {
|
|
19
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value) ? value : undefined;
|
|
20
|
+
}
|
|
21
|
+
export function renderSummaryAndValue(summary, value) {
|
|
22
|
+
const detailPath = asRecordOrUndefined(value)?.detailPath;
|
|
23
|
+
const json = JSON.stringify(value, null, 2);
|
|
24
|
+
const header = summary.trim().length === 0 ? '' : `${summary}\n\n`;
|
|
25
|
+
if (json.length <= TOOL_RESULT_INLINE_LIMIT)
|
|
26
|
+
return [textBlock(`${header}${json}`)];
|
|
27
|
+
const preview = json.slice(0, TOOL_RESULT_INLINE_LIMIT);
|
|
28
|
+
const tail = typeof detailPath === 'string'
|
|
29
|
+
? `\n…(结果过大,已截断)\n完整结果:${detailPath}(用 read 工具读取完整 JSON)`
|
|
30
|
+
: '\n…(结果过大,已截断)';
|
|
31
|
+
return [textBlock(`${header}${preview}${tail}`)];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Model-facing tool output.
|
|
35
|
+
*
|
|
36
|
+
* DSH treats `output.render` as the only content the model sees
|
|
37
|
+
* (harness `createSuccessResult`), so a constant string silently drops the
|
|
38
|
+
* whole workbook payload. These helpers keep a readable summary plus the real
|
|
39
|
+
* JSON inline, and oversized results are spilled to
|
|
40
|
+
* `<storage root>/tool-output/*.json` during `execute` and reported as
|
|
41
|
+
* `detailPath` instead of being truncated away.
|
|
42
|
+
*/
|
|
43
|
+
export const TOOL_RESULT_INLINE_LIMIT = 24_000;
|
|
44
|
+
/** Spill an oversized canonical value so the model can still page through it. */
|
|
45
|
+
export async function spillLargeToolResult(toolName, value) {
|
|
46
|
+
const record = asRecordOrUndefined(value) ?? { value };
|
|
47
|
+
if (JSON.stringify(record).length <= TOOL_RESULT_INLINE_LIMIT)
|
|
48
|
+
return record;
|
|
49
|
+
const directory = toolOutputRoot();
|
|
50
|
+
const detailPath = join(directory, `${toolName}-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}.json`);
|
|
51
|
+
await mkdir(directory, { recursive: true });
|
|
52
|
+
await writeFile(detailPath, JSON.stringify(value, null, 2), 'utf8');
|
|
53
|
+
// Keep the directory bounded. Nothing used to, so these accumulated for the
|
|
54
|
+
// life of the installation.
|
|
55
|
+
await pruneToolOutput();
|
|
56
|
+
return { ...record, detailPath };
|
|
57
|
+
}
|
|
58
|
+
/** Keep large write payloads out of the model/tool transcript. */
|
|
59
|
+
export function compactToolValue(value) {
|
|
60
|
+
if (Array.isArray(value))
|
|
61
|
+
return value.map(compactToolValue);
|
|
62
|
+
if (typeof value !== 'object' || value === null)
|
|
63
|
+
return value;
|
|
64
|
+
const record = value;
|
|
65
|
+
const result = {};
|
|
66
|
+
for (const [key, item] of Object.entries(record)) {
|
|
67
|
+
if (key === 'operation' && isOperationRecord(item))
|
|
68
|
+
result[key] = operationReceipt(item);
|
|
69
|
+
else if ((key === 'operations' || key === 'rebasedOperations') && Array.isArray(item)) {
|
|
70
|
+
result[key] = item.map(candidate => isOperationRecord(candidate) ? operationReceipt(candidate) : compactToolValue(candidate));
|
|
71
|
+
}
|
|
72
|
+
else
|
|
73
|
+
result[key] = compactToolValue(item);
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
export function asJsonValue(value) {
|
|
78
|
+
const serialized = JSON.stringify(compactToolValue(value));
|
|
79
|
+
if (serialized === undefined)
|
|
80
|
+
throw new Error('工具结果无法序列化');
|
|
81
|
+
return JSON.parse(serialized);
|
|
82
|
+
}
|
|
83
|
+
export function isOperationRecord(value) {
|
|
84
|
+
if (typeof value !== 'object' || value === null)
|
|
85
|
+
return false;
|
|
86
|
+
const operation = value;
|
|
87
|
+
return typeof operation.opId === 'string' && typeof operation.workbookId === 'string'
|
|
88
|
+
&& typeof operation.revision === 'number' && typeof operation.type === 'string'
|
|
89
|
+
&& typeof operation.timestamp === 'string' && typeof operation.payload === 'object'
|
|
90
|
+
&& operation.payload !== null;
|
|
91
|
+
}
|
|
92
|
+
export function operationReceipt(operation) {
|
|
93
|
+
const payload = operation.payload;
|
|
94
|
+
const cells = Array.isArray(payload.cells) ? payload.cells : [];
|
|
95
|
+
const range = payload.range ?? payload.merge;
|
|
96
|
+
const sheetRef = payload.sheetId ?? payload.sheetName;
|
|
97
|
+
const payloads = operation.type === 'batch' ? (payload.operations ?? []).map(step => step.payload) : [payload];
|
|
98
|
+
const sheetIds = [...new Set(payloads.map(item => item.sheetId ?? item.sheetName).filter((value) => typeof value === 'string'))];
|
|
99
|
+
const totalCellCount = operation.type === 'batch'
|
|
100
|
+
? payloads.reduce((total, item) => total + (Array.isArray(item.cells) ? item.cells.length : 0), 0)
|
|
101
|
+
: cells.length;
|
|
102
|
+
const totalRowCount = operation.type === 'batch'
|
|
103
|
+
? payloads.reduce((total, item) => total + (Array.isArray(item.rows) ? item.rows.length : 0), 0)
|
|
104
|
+
: Array.isArray(payload.rows) ? payload.rows.length : 0;
|
|
105
|
+
return {
|
|
106
|
+
opId: operation.opId,
|
|
107
|
+
workbookId: operation.workbookId,
|
|
108
|
+
revision: operation.revision,
|
|
109
|
+
parentRevision: operation.parentRevision ?? Math.max(0, operation.revision - 1),
|
|
110
|
+
...(operation.versionId === undefined ? {} : { versionId: operation.versionId }),
|
|
111
|
+
...(operation.versionStatus === undefined ? {} : { versionStatus: operation.versionStatus }),
|
|
112
|
+
...(operation.versionSummary === undefined ? {} : { versionSummary: operation.versionSummary }),
|
|
113
|
+
...(operation.versionIntent === undefined ? {} : { versionIntent: operation.versionIntent }),
|
|
114
|
+
...(operation.agentRunId === undefined ? {} : { agentRunId: operation.agentRunId }),
|
|
115
|
+
...(operation.lineage === undefined ? {} : { lineage: operation.lineage }),
|
|
116
|
+
...(operation.restoredFromRevision === undefined ? {} : { restoredFromRevision: operation.restoredFromRevision }),
|
|
117
|
+
actor: operation.actor,
|
|
118
|
+
type: operation.type,
|
|
119
|
+
timestamp: operation.timestamp,
|
|
120
|
+
summary: operationSummary(operation),
|
|
121
|
+
...(sheetRef === undefined ? {} : { sheetId: sheetRef }),
|
|
122
|
+
...(sheetIds.length > 0 && operation.type === 'batch' ? { sheetIds } : {}),
|
|
123
|
+
...(totalCellCount > 0 ? { cellCount: totalCellCount } : {}),
|
|
124
|
+
...(totalRowCount > 0 ? { rowCount: totalRowCount } : {}),
|
|
125
|
+
...(range === undefined ? {} : { range }),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
export function isNonEmptyCell(cell) {
|
|
129
|
+
const record = asRecordOrUndefined(cell);
|
|
130
|
+
if (record === undefined)
|
|
131
|
+
return false;
|
|
132
|
+
if (typeof record.f === 'string' && record.f.length > 0)
|
|
133
|
+
return true;
|
|
134
|
+
return record.v !== undefined && record.v !== null && record.v !== '';
|
|
135
|
+
}
|
|
136
|
+
/** Compact per-sheet map (`name ↔ id` + size) that Agent callers use to address sheets. */
|
|
137
|
+
export function sheetOverviews(snapshot) {
|
|
138
|
+
const root = asRecordOrUndefined(snapshot) ?? {};
|
|
139
|
+
const sheets = asRecordOrUndefined(root.sheets) ?? {};
|
|
140
|
+
const order = Array.isArray(root.sheetOrder)
|
|
141
|
+
? root.sheetOrder.filter((item) => typeof item === 'string')
|
|
142
|
+
: Object.keys(sheets);
|
|
143
|
+
const result = [];
|
|
144
|
+
for (const id of order) {
|
|
145
|
+
const sheet = asRecordOrUndefined(sheets[id]);
|
|
146
|
+
if (sheet === undefined)
|
|
147
|
+
continue;
|
|
148
|
+
let maxRow = -1;
|
|
149
|
+
let maxColumn = -1;
|
|
150
|
+
let nonEmptyCells = 0;
|
|
151
|
+
const cellData = asRecordOrUndefined(sheet.cellData);
|
|
152
|
+
if (cellData !== undefined) {
|
|
153
|
+
for (const [rowKey, line] of Object.entries(cellData)) {
|
|
154
|
+
const row = Number(rowKey);
|
|
155
|
+
const cells = asRecordOrUndefined(line);
|
|
156
|
+
if (!Number.isInteger(row) || cells === undefined)
|
|
157
|
+
continue;
|
|
158
|
+
for (const [columnKey, cell] of Object.entries(cells)) {
|
|
159
|
+
const column = Number(columnKey);
|
|
160
|
+
if (!Number.isInteger(column))
|
|
161
|
+
continue;
|
|
162
|
+
maxRow = Math.max(maxRow, row);
|
|
163
|
+
maxColumn = Math.max(maxColumn, column);
|
|
164
|
+
if (isNonEmptyCell(cell))
|
|
165
|
+
nonEmptyCells += 1;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
result.push({
|
|
170
|
+
id,
|
|
171
|
+
name: typeof sheet.name === 'string' ? sheet.name : id,
|
|
172
|
+
rowCount: typeof sheet.rowCount === 'number' ? sheet.rowCount : 0,
|
|
173
|
+
columnCount: typeof sheet.columnCount === 'number' ? sheet.columnCount : 0,
|
|
174
|
+
maxRow: maxRow < 0 ? null : maxRow,
|
|
175
|
+
maxColumn: maxColumn < 0 ? null : maxColumn,
|
|
176
|
+
nonEmptyCells,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
export function sheetMapSummary(sheets) {
|
|
182
|
+
if (sheets.length === 0)
|
|
183
|
+
return '当前工作簿没有 sheet。';
|
|
184
|
+
return sheets.map(sheet => {
|
|
185
|
+
// `== null` on purpose: callers that summarise a sheet without reading its
|
|
186
|
+
// cells leave these absent rather than null, and `workbook_catalog` was
|
|
187
|
+
// printing `rows 0-undefined × cols 0-undefined` because of it.
|
|
188
|
+
const extent = sheet.maxRow == null || sheet.maxColumn == null
|
|
189
|
+
? '暂无数据'
|
|
190
|
+
: `数据 rows 0-${sheet.maxRow} × cols 0-${sheet.maxColumn}`;
|
|
191
|
+
return `- ${sheet.name} ↔ ${sheet.id}(${extent},非空 ${sheet.nonEmptyCells})`;
|
|
192
|
+
}).join('\n');
|
|
193
|
+
}
|
|
194
|
+
/** Inline `名称(id)` list used by self-healing error messages. */
|
|
195
|
+
export function sheetReferenceList(snapshot) {
|
|
196
|
+
const sheets = sheetOverviews(snapshot);
|
|
197
|
+
if (sheets.length === 0)
|
|
198
|
+
return '(当前工作簿没有 sheet)';
|
|
199
|
+
return sheets.map(sheet => `${sheet.name}(${sheet.id})`).join(' / ');
|
|
200
|
+
}
|
|
201
|
+
export function renderCatalogResult(value) {
|
|
202
|
+
const record = asRecordOrUndefined(value) ?? {};
|
|
203
|
+
const action = typeof record.action === 'string' ? record.action : 'list';
|
|
204
|
+
if (Array.isArray(record.workbooks)) {
|
|
205
|
+
const workbooks = record.workbooks;
|
|
206
|
+
const lines = workbooks.map(item => `- ${String(item.id)}(${String(item.name)})revision=${String(item.revision ?? 0)} sheets=${String(item.sheetCount ?? 0)} updatedAt=${String(item.updatedAt ?? '')}`);
|
|
207
|
+
return renderSummaryAndValue(workbooks.length === 0 ? '没有工作簿;请用 workbook_catalog(action=create) 创建。' : `共 ${workbooks.length} 个工作簿:\n${lines.join('\n')}`, value);
|
|
208
|
+
}
|
|
209
|
+
const workbook = asRecordOrUndefined(record.workbook);
|
|
210
|
+
if (workbook !== undefined) {
|
|
211
|
+
const sheets = Array.isArray(workbook.sheets) ? workbook.sheets : [];
|
|
212
|
+
return renderSummaryAndValue(`工作簿已就绪:${String(workbook.id)}(${String(workbook.name)})revision=${String(workbook.revision ?? 0)}\n${sheetMapSummary(sheets)}`, value);
|
|
213
|
+
}
|
|
214
|
+
return renderSummaryAndValue(`工作簿目录操作已完成:action=${action}`, value);
|
|
215
|
+
}
|
|
216
|
+
export function renderReadResult(value) {
|
|
217
|
+
const record = asRecordOrUndefined(value) ?? {};
|
|
218
|
+
const parts = [`workbookId: ${String(record.workbookId ?? '')},revision: ${String(record.revision ?? 0)}`];
|
|
219
|
+
const workbook = asRecordOrUndefined(record.workbook);
|
|
220
|
+
if (workbook !== undefined) {
|
|
221
|
+
const sheets = Array.isArray(workbook.sheets) ? workbook.sheets : [];
|
|
222
|
+
parts.push(`工作簿:${String(workbook.name ?? '')},sheet 数:${sheets.length}`);
|
|
223
|
+
parts.push(sheetMapSummary(sheets));
|
|
224
|
+
}
|
|
225
|
+
const sheet = asRecordOrUndefined(record.sheet);
|
|
226
|
+
if (sheet !== undefined)
|
|
227
|
+
parts.push(`sheet:${String(sheet.name ?? '')} ↔ ${String(sheet.id ?? '')}`);
|
|
228
|
+
const range = asRecordOrUndefined(record.range);
|
|
229
|
+
if (range !== undefined && typeof range.startRow === 'number') {
|
|
230
|
+
parts.push(`range:rows ${range.startRow}-${range.endRow} × cols ${range.startColumn}-${range.endColumn}(0-based,end inclusive)`);
|
|
231
|
+
}
|
|
232
|
+
if (Array.isArray(record.values)) {
|
|
233
|
+
const first = record.values[0];
|
|
234
|
+
parts.push(`values:${record.values.length} 行 × ${Array.isArray(first) ? first.length : 0} 列`);
|
|
235
|
+
}
|
|
236
|
+
else if (Array.isArray(record.cells)) {
|
|
237
|
+
parts.push(`cells:${record.cells.length} 个`);
|
|
238
|
+
}
|
|
239
|
+
if (typeof record.detailPath === 'string')
|
|
240
|
+
parts.push(`完整结果:${record.detailPath}`);
|
|
241
|
+
return renderSummaryAndValue(parts.join('\n'), value);
|
|
242
|
+
}
|
|
243
|
+
export function renderFindResult(value) {
|
|
244
|
+
const record = asRecordOrUndefined(value) ?? {};
|
|
245
|
+
const results = Array.isArray(record.results) ? record.results : [];
|
|
246
|
+
const lines = results.slice(0, 20).map(item => `- ${formatCellReference({
|
|
247
|
+
sheet: typeof item.sheetName === 'string' ? item.sheetName : typeof item.sheetId === 'string' ? item.sheetId : '',
|
|
248
|
+
row: typeof item.row === 'number' ? item.row : 0,
|
|
249
|
+
column: typeof item.column === 'number' ? item.column : 0,
|
|
250
|
+
})} = ${JSON.stringify(item.value ?? null)}`);
|
|
251
|
+
const summary = `匹配 ${results.length} 处${record.truncated === true ? '(已截断,请缩小范围或提高 limit)' : ''}(query=${String(record.query ?? '')},scope=${String(record.scope ?? '')})`;
|
|
252
|
+
return renderSummaryAndValue(lines.length === 0 ? summary : `${summary}\n${lines.join('\n')}`, value);
|
|
253
|
+
}
|
|
254
|
+
export function renderHistoryResult(value) {
|
|
255
|
+
const record = asRecordOrUndefined(value) ?? {};
|
|
256
|
+
if (Array.isArray(record.versions)) {
|
|
257
|
+
const versions = record.versions;
|
|
258
|
+
const lines = versions.map(item => `- revision=${String(item.revision)} status=${String(item.status ?? '')} steps=${String(item.stepCount ?? '')} ${String(item.summary ?? '')}`);
|
|
259
|
+
return renderSummaryAndValue(`workbookId: ${String(record.workbookId ?? '')},revision: ${String(record.revision ?? 0)},Version ${versions.length} 个\n${lines.join('\n')}`, value);
|
|
260
|
+
}
|
|
261
|
+
return renderSummaryAndValue(`历史查询已完成:action=${String(record.action ?? '')},workbookId=${String(record.workbookId ?? '')}`, value);
|
|
262
|
+
}
|
|
263
|
+
export function renderEditResult(value) {
|
|
264
|
+
const record = asRecordOrUndefined(value) ?? {};
|
|
265
|
+
const dryRun = record.dryRun === true;
|
|
266
|
+
const lines = [
|
|
267
|
+
dryRun
|
|
268
|
+
? `编辑预检通过,尚未写入(revision 保持 ${String(record.revision ?? record.revisionBefore ?? 0)})。`
|
|
269
|
+
: `工作簿编辑已提交(revision ${String(record.revisionBefore ?? '?')} → ${String(record.revisionAfter ?? record.revision ?? '?')})。`,
|
|
270
|
+
`workbookId: ${String(record.workbookId ?? '')}`,
|
|
271
|
+
`Version ${String(record.versionCount ?? 0)} 个 / 命令 ${String(record.commandCount ?? 0)} 条,影响 sheet:${Array.isArray(record.affectedSheets) && record.affectedSheets.length > 0 ? record.affectedSheets.join('、') : '(无)'},单元格 ${String(record.affectedCellCount ?? 0)} 个`,
|
|
272
|
+
];
|
|
273
|
+
if (record.replayed === true)
|
|
274
|
+
lines.push('该 opId 之前已提交,本次为幂等重放。');
|
|
275
|
+
if (record.transformed === true) {
|
|
276
|
+
const rebased = Array.isArray(record.rebasedOperations) ? record.rebasedOperations.length : 0;
|
|
277
|
+
lines.push(`提交前检测到 ${rebased} 个并发编辑(均为单元格写入),已自动变基到最新 revision 后提交;行/列结构未变化,本计划的坐标仍然有效。`);
|
|
278
|
+
}
|
|
279
|
+
if (Array.isArray(record.warnings) && record.warnings.length > 0) {
|
|
280
|
+
lines.push('warnings:');
|
|
281
|
+
for (const warning of record.warnings)
|
|
282
|
+
lines.push(`- ${String(warning)}`);
|
|
283
|
+
}
|
|
284
|
+
if (dryRun && Array.isArray(record.versions)) {
|
|
285
|
+
for (const version of record.versions) {
|
|
286
|
+
lines.push(`- ${String(version.summary ?? '')}:${String(version.stepCount ?? 0)} 个 step`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return renderSummaryAndValue(lines.join('\n'), value);
|
|
290
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { LineageContext } from '#contracts';
|
|
2
|
+
/** Sheet column types a modelled table may declare. */
|
|
3
|
+
export declare function parseUniverApiAction(value: unknown): 'catalog' | 'search' | 'invoke';
|
|
4
|
+
export type ApiArguments = Record<string, unknown>;
|
|
5
|
+
export declare function asApiArguments(value: unknown): ApiArguments;
|
|
6
|
+
/** Execute one cataloged Facade method through the durable DSH adapter. */
|
|
7
|
+
export declare function executeUniverApi(args: {
|
|
8
|
+
workbookId: string;
|
|
9
|
+
api: string;
|
|
10
|
+
arguments: ApiArguments;
|
|
11
|
+
expectedRevision?: number;
|
|
12
|
+
opId?: string;
|
|
13
|
+
lineage?: LineageContext;
|
|
14
|
+
}): Promise<unknown>;
|
|
15
|
+
export declare function parseBoolean(value: unknown, label: string): boolean;
|
|
16
|
+
export declare function parseColor(value: unknown, label: string): string;
|
|
17
|
+
export declare function parseHorizontalAlignment(value: unknown): number;
|
|
18
|
+
export declare function parseVerticalAlignment(value: unknown): number;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { findUniverApi } from "../contract/univer-api-catalog.js";
|
|
2
|
+
import { mutateWorkbook, readWorkbook, readWorkbookRange, summarize } from "../workbook-manager.js";
|
|
3
|
+
import { normalizeValueMatrix, parseIndex, parseRange } from "../request-params.js";
|
|
4
|
+
import { parseCellDeleteShift, parseCount, parseNonEmptyString, sheetRefFromArguments, sheetRefForSnapshot, } from "./arguments.js";
|
|
5
|
+
import { dimensionRange, parseApiValues, parseFiniteNumber, parseFreeze, parseHyperlink, parseHyperlinkLabel, parseSort, parseValueRange, } from "./cells.js";
|
|
6
|
+
/** Sheet column types a modelled table may declare. */
|
|
7
|
+
const COLUMN_TYPES = ['string', 'number', 'boolean', 'date', 'currency'];
|
|
8
|
+
/** Sheet column types a modelled table may declare. */
|
|
9
|
+
export function parseUniverApiAction(value) {
|
|
10
|
+
if (value === 'catalog' || value === 'search' || value === 'invoke')
|
|
11
|
+
return value;
|
|
12
|
+
throw new Error('action 必须是 catalog、search 或 invoke');
|
|
13
|
+
}
|
|
14
|
+
export function asApiArguments(value) {
|
|
15
|
+
if (value === undefined || value === null)
|
|
16
|
+
return {};
|
|
17
|
+
if (typeof value !== 'object' || Array.isArray(value))
|
|
18
|
+
throw new Error('arguments 必须是对象');
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
/** Execute one cataloged Facade method through the durable DSH adapter. */
|
|
22
|
+
export async function executeUniverApi(args) {
|
|
23
|
+
const definition = findUniverApi(args.api);
|
|
24
|
+
if (definition === undefined)
|
|
25
|
+
throw new Error(`未知 Univer API:${args.api};请先调用 univer_api(action=catalog)`);
|
|
26
|
+
if (definition.availability !== 'implemented') {
|
|
27
|
+
throw new Error(`${args.api} 仅作为浏览器 Facade 元数据提供;持久化 workbook 请使用 workbook_edit 或已注册的 Adapter`);
|
|
28
|
+
}
|
|
29
|
+
const meta = await readWorkbook(args.workbookId);
|
|
30
|
+
if (meta === null)
|
|
31
|
+
throw new Error(`工作簿不存在:${args.workbookId}`);
|
|
32
|
+
const input = args.arguments;
|
|
33
|
+
if (definition.mode === 'read') {
|
|
34
|
+
switch (args.api) {
|
|
35
|
+
case 'workbook.getSnapshot':
|
|
36
|
+
return { ok: true, workbookId: args.workbookId, revision: meta.revision ?? 0, snapshot: meta.snapshot };
|
|
37
|
+
case 'workbook.getWorksheets': {
|
|
38
|
+
const sheets = summarize(meta).sheets;
|
|
39
|
+
return { ok: true, workbookId: args.workbookId, revision: meta.revision ?? 0, sheets, worksheets: sheets, sheetCount: sheets.length };
|
|
40
|
+
}
|
|
41
|
+
case 'worksheet.getRangeValues': {
|
|
42
|
+
const range = parseRange(input.range);
|
|
43
|
+
const sheet = sheetRefForSnapshot(meta.snapshot, input, 'arguments.sheet');
|
|
44
|
+
return { ok: true, workbookId: args.workbookId, revision: meta.revision ?? 0, ...readWorkbookRange(meta.snapshot, sheet, range) };
|
|
45
|
+
}
|
|
46
|
+
default:
|
|
47
|
+
throw new Error(`读取 API 尚未实现:${args.api}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
let type = definition.operation;
|
|
51
|
+
let payload;
|
|
52
|
+
const sheet = args.api === 'workbook.createSheet'
|
|
53
|
+
? sheetRefFromArguments(input, 'arguments.sheet')
|
|
54
|
+
: sheetRefForSnapshot(meta.snapshot, input, 'arguments.sheet');
|
|
55
|
+
switch (args.api) {
|
|
56
|
+
case 'worksheet.setValues': {
|
|
57
|
+
const matrix = input.values ?? input.data ?? input.matrix;
|
|
58
|
+
const valueMatrix = normalizeValueMatrix(matrix);
|
|
59
|
+
const range = parseValueRange(input.range, valueMatrix, 'arguments');
|
|
60
|
+
payload = { sheetId: sheet, cells: parseApiValues(valueMatrix, range) };
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case 'worksheet.clearContent':
|
|
64
|
+
payload = { sheetId: sheet, range: parseRange(input.range) };
|
|
65
|
+
break;
|
|
66
|
+
case 'worksheet.setBackgroundColor':
|
|
67
|
+
payload = { sheetId: sheet, range: parseRange(input.range), style: { bg: parseColor(input.color, 'color') } };
|
|
68
|
+
break;
|
|
69
|
+
case 'worksheet.setFontColor':
|
|
70
|
+
payload = { sheetId: sheet, range: parseRange(input.range), style: { cl: parseColor(input.color, 'color') } };
|
|
71
|
+
break;
|
|
72
|
+
case 'worksheet.setFontFamily':
|
|
73
|
+
payload = { sheetId: sheet, range: parseRange(input.range), style: { ff: parseNonEmptyString(input.family, 'family') } };
|
|
74
|
+
break;
|
|
75
|
+
case 'worksheet.setFontSize': {
|
|
76
|
+
const size = parseFiniteNumber(input.size, 'size');
|
|
77
|
+
if (size <= 0)
|
|
78
|
+
throw new Error('size 必须大于 0');
|
|
79
|
+
payload = { sheetId: sheet, range: parseRange(input.range), style: { fs: size } };
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case 'worksheet.setFontWeight':
|
|
83
|
+
payload = { sheetId: sheet, range: parseRange(input.range), style: { bl: parseBoolean(input.bold, 'bold') } };
|
|
84
|
+
break;
|
|
85
|
+
case 'worksheet.setFontStyle':
|
|
86
|
+
payload = { sheetId: sheet, range: parseRange(input.range), style: { it: parseBoolean(input.italic, 'italic') } };
|
|
87
|
+
break;
|
|
88
|
+
case 'worksheet.setHorizontalAlignment':
|
|
89
|
+
payload = { sheetId: sheet, range: parseRange(input.range), style: { ht: parseHorizontalAlignment(input.alignment) } };
|
|
90
|
+
break;
|
|
91
|
+
case 'worksheet.setVerticalAlignment':
|
|
92
|
+
payload = { sheetId: sheet, range: parseRange(input.range), style: { vt: parseVerticalAlignment(input.alignment) } };
|
|
93
|
+
break;
|
|
94
|
+
case 'worksheet.merge':
|
|
95
|
+
payload = { sheetId: sheet, range: parseRange(input.range) };
|
|
96
|
+
break;
|
|
97
|
+
case 'worksheet.breakApart':
|
|
98
|
+
payload = { sheetId: sheet, range: parseRange(input.range) };
|
|
99
|
+
break;
|
|
100
|
+
case 'worksheet.deleteCells':
|
|
101
|
+
payload = { sheetId: sheet, range: parseRange(input.range), shift: parseCellDeleteShift(input.shift) };
|
|
102
|
+
break;
|
|
103
|
+
case 'worksheet.insertRows':
|
|
104
|
+
payload = { sheetId: sheet, range: dimensionRange(parseIndex(input.index, 'index'), 'row'), count: input.count === undefined ? 1 : parseCount(input.count) };
|
|
105
|
+
break;
|
|
106
|
+
case 'worksheet.deleteRows':
|
|
107
|
+
payload = { sheetId: sheet, range: dimensionRange(parseIndex(input.index, 'index'), 'row'), count: input.count === undefined ? 1 : parseCount(input.count) };
|
|
108
|
+
break;
|
|
109
|
+
case 'worksheet.insertColumns':
|
|
110
|
+
payload = { sheetId: sheet, range: dimensionRange(parseIndex(input.index, 'index'), 'column'), count: input.count === undefined ? 1 : parseCount(input.count) };
|
|
111
|
+
break;
|
|
112
|
+
case 'worksheet.deleteColumns':
|
|
113
|
+
payload = { sheetId: sheet, range: dimensionRange(parseIndex(input.index, 'index'), 'column'), count: input.count === undefined ? 1 : parseCount(input.count) };
|
|
114
|
+
break;
|
|
115
|
+
case 'worksheet.setTextRotation':
|
|
116
|
+
payload = { sheetId: sheet, range: parseRange(input.range), style: { tr: parseFiniteNumber(input.rotation, 'rotation') } };
|
|
117
|
+
break;
|
|
118
|
+
case 'worksheet.setNumberFormat':
|
|
119
|
+
payload = { sheetId: sheet, range: parseRange(input.range), style: { n: parseNonEmptyString(input.format, 'format') } };
|
|
120
|
+
break;
|
|
121
|
+
case 'worksheet.setName':
|
|
122
|
+
payload = { sheetId: sheet, name: parseNonEmptyString(input.name, 'name') };
|
|
123
|
+
break;
|
|
124
|
+
case 'workbook.createSheet':
|
|
125
|
+
payload = { sheetId: sheet, name: parseNonEmptyString(input.name, 'name') };
|
|
126
|
+
break;
|
|
127
|
+
case 'workbook.deleteSheet':
|
|
128
|
+
payload = { sheetId: sheet };
|
|
129
|
+
break;
|
|
130
|
+
case 'worksheet.sort':
|
|
131
|
+
payload = { sheetId: sheet, range: parseRange(input.range), sort: parseSort({ column: input.column, direction: input.direction }) };
|
|
132
|
+
break;
|
|
133
|
+
case 'worksheet.setColumnWidth':
|
|
134
|
+
payload = { sheetId: sheet, range: dimensionRange(parseIndex(input.index, 'index'), 'column'), size: parseFiniteNumber(input.size, 'size') };
|
|
135
|
+
break;
|
|
136
|
+
case 'worksheet.setRowHeight':
|
|
137
|
+
payload = { sheetId: sheet, range: dimensionRange(parseIndex(input.index, 'index'), 'row'), size: parseFiniteNumber(input.size, 'size') };
|
|
138
|
+
break;
|
|
139
|
+
case 'worksheet.setHyperLink':
|
|
140
|
+
payload = { sheetId: sheet, range: parseRange(input.range), url: parseHyperlink(input.url), label: input.label === undefined ? undefined : parseHyperlinkLabel(input.label) };
|
|
141
|
+
break;
|
|
142
|
+
case 'worksheet.cancelHyperLink':
|
|
143
|
+
payload = { sheetId: sheet, range: parseRange(input.range) };
|
|
144
|
+
break;
|
|
145
|
+
case 'worksheet.setFreeze':
|
|
146
|
+
payload = { sheetId: sheet, freeze: parseFreeze({ rows: input.rows, columns: input.columns }) };
|
|
147
|
+
break;
|
|
148
|
+
default:
|
|
149
|
+
type = undefined;
|
|
150
|
+
throw new Error(`写入 API 尚未实现:${args.api}`);
|
|
151
|
+
}
|
|
152
|
+
if (type === undefined)
|
|
153
|
+
throw new Error(`API ${args.api} 没有持久化 operation 映射`);
|
|
154
|
+
const result = await mutateWorkbook({
|
|
155
|
+
workbookId: args.workbookId,
|
|
156
|
+
actor: 'agent',
|
|
157
|
+
type,
|
|
158
|
+
payload,
|
|
159
|
+
expectedRevision: args.expectedRevision,
|
|
160
|
+
opId: args.opId,
|
|
161
|
+
versionKind: 'named',
|
|
162
|
+
lineage: args.lineage,
|
|
163
|
+
});
|
|
164
|
+
return {
|
|
165
|
+
ok: true,
|
|
166
|
+
workbookId: args.workbookId,
|
|
167
|
+
revision: result.meta.revision ?? 0,
|
|
168
|
+
operation: result.operation,
|
|
169
|
+
...(args.lineage === undefined ? {} : { lineage: args.lineage }),
|
|
170
|
+
...(result.rebasedOperations === undefined ? {} : { rebasedOperations: result.rebasedOperations }),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
export function parseBoolean(value, label) {
|
|
174
|
+
if (typeof value !== 'boolean')
|
|
175
|
+
throw new Error(`${label} 必须是 boolean`);
|
|
176
|
+
return value;
|
|
177
|
+
}
|
|
178
|
+
export function parseColor(value, label) {
|
|
179
|
+
return parseNonEmptyString(value, label);
|
|
180
|
+
}
|
|
181
|
+
export function parseHorizontalAlignment(value) {
|
|
182
|
+
if (value === 'left')
|
|
183
|
+
return 1;
|
|
184
|
+
if (value === 'center')
|
|
185
|
+
return 2;
|
|
186
|
+
if (value === 'normal')
|
|
187
|
+
return 0;
|
|
188
|
+
throw new Error('alignment 必须是 left、center 或 normal');
|
|
189
|
+
}
|
|
190
|
+
export function parseVerticalAlignment(value) {
|
|
191
|
+
if (value === 'top')
|
|
192
|
+
return 1;
|
|
193
|
+
if (value === 'middle')
|
|
194
|
+
return 2;
|
|
195
|
+
if (value === 'bottom')
|
|
196
|
+
return 0;
|
|
197
|
+
throw new Error('alignment 必须是 top、middle 或 bottom');
|
|
198
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `workbook_catalog`: the directory and lifecycle of workbooks (docs/issue/0022 P3b).
|
|
3
|
+
*
|
|
4
|
+
* Creation also names the file. A workbook's id *is* its file name under
|
|
5
|
+
* `<project>/storages/data-table/`, so the naming rule lives at this one entry
|
|
6
|
+
* point: `yyyymmdd-中文名含义` (see `storageNameOf`). Before it, a Chinese display
|
|
7
|
+
* name was slugged by an ASCII-only rule and became `_wb` — a directory nobody
|
|
8
|
+
* could read.
|
|
9
|
+
*/
|
|
10
|
+
import { blankSnapshot, deleteWorkbook, listWorkbookSummaries, readWorkbook, renameWorkbook, summarize, workbookExists, writeWorkbook, } from "../workbook-manager.js";
|
|
11
|
+
import { storageNameOf } from '#contracts';
|
|
12
|
+
import { normalizeWorkbookId } from "../contract/ids.js";
|
|
13
|
+
import { now, parseNonEmptyString, workbookIdOf } from "./arguments.js";
|
|
14
|
+
export function parseCatalogAction(value) {
|
|
15
|
+
if (value === 'list' || value === 'create' || value === 'rename' || value === 'delete')
|
|
16
|
+
return value;
|
|
17
|
+
throw new Error('action 必须是 list、create、rename 或 delete');
|
|
18
|
+
}
|
|
19
|
+
export async function executeWorkbookCatalog(args) {
|
|
20
|
+
const action = parseCatalogAction(args.action);
|
|
21
|
+
if (action === 'list') {
|
|
22
|
+
const summaries = await listWorkbookSummaries();
|
|
23
|
+
// 响应按 wire 契约标注,字段变化会在编译期被发现。
|
|
24
|
+
const response = {
|
|
25
|
+
ok: true,
|
|
26
|
+
workbooks: summaries.map(summary => ({
|
|
27
|
+
id: summary.id,
|
|
28
|
+
name: summary.name,
|
|
29
|
+
revision: summary.revision ?? 0,
|
|
30
|
+
sheets: summary.sheets,
|
|
31
|
+
sheetCount: summary.sheets.length,
|
|
32
|
+
updatedAt: summary.updatedAt,
|
|
33
|
+
})),
|
|
34
|
+
};
|
|
35
|
+
return response;
|
|
36
|
+
}
|
|
37
|
+
if (action === 'create') {
|
|
38
|
+
const name = parseNonEmptyString(args.name, 'name');
|
|
39
|
+
const requestedId = typeof args.workbookId === 'string' && args.workbookId.trim().length > 0 ? args.workbookId : undefined;
|
|
40
|
+
if (args.workbookId !== undefined && requestedId === undefined) {
|
|
41
|
+
throw new Error('workbookId 必须是非空字符串');
|
|
42
|
+
}
|
|
43
|
+
// Both halves of the name rule meet here:
|
|
44
|
+
//
|
|
45
|
+
// · no id given — the display name *becomes* the file name's meaning, so the
|
|
46
|
+
// directory says what each workbook is: `20261002-销售汇总.json`;
|
|
47
|
+
// · an id given — it is the caller's own name for this workbook, and it is
|
|
48
|
+
// used as written (character set aside). It is deliberately **not**
|
|
49
|
+
// re-dated: a workbook named `20261001-销售汇总` yesterday must still be
|
|
50
|
+
// addressable by the id its creator used, today.
|
|
51
|
+
const baseId = requestedId === undefined
|
|
52
|
+
? storageNameOf(name, { fallback: '未命名工作簿' })
|
|
53
|
+
: normalizeWorkbookId(requestedId);
|
|
54
|
+
let workbookId = baseId;
|
|
55
|
+
if (requestedId !== undefined && await workbookExists(workbookId)) {
|
|
56
|
+
throw new Error('工作簿 id 已存在:' + workbookId);
|
|
57
|
+
}
|
|
58
|
+
for (let suffix = 2; requestedId === undefined && await workbookExists(workbookId); suffix += 1) {
|
|
59
|
+
workbookId = baseId + '-' + suffix;
|
|
60
|
+
}
|
|
61
|
+
await writeWorkbook({
|
|
62
|
+
id: workbookId,
|
|
63
|
+
name,
|
|
64
|
+
sheetOrder: ['sheet1'],
|
|
65
|
+
updatedAt: now(),
|
|
66
|
+
revision: 0,
|
|
67
|
+
operations: [],
|
|
68
|
+
snapshot: blankSnapshot(name),
|
|
69
|
+
});
|
|
70
|
+
return { ok: true, action, workbook: { id: workbookId, name, sheets: summarize((await readWorkbook(workbookId))).sheets, revision: 0 } };
|
|
71
|
+
}
|
|
72
|
+
const workbookId = workbookIdOf(args.workbookId);
|
|
73
|
+
if (action === 'rename') {
|
|
74
|
+
const meta = await renameWorkbook(workbookId, parseNonEmptyString(args.name, 'name'));
|
|
75
|
+
return { ok: true, action, workbook: { id: meta.id, name: meta.name, sheets: summarize(meta).sheets, revision: meta.revision ?? 0 } };
|
|
76
|
+
}
|
|
77
|
+
if (args.confirm !== true)
|
|
78
|
+
throw new Error('删除工作簿必须显式传 confirm: true');
|
|
79
|
+
if (!await workbookExists(workbookId))
|
|
80
|
+
throw new Error('工作簿不存在:' + workbookId);
|
|
81
|
+
await deleteWorkbook(workbookId);
|
|
82
|
+
return { ok: true, action, workbookId };
|
|
83
|
+
}
|