feat: 建立 DOCX 共享协议与准备服务

This commit is contained in:
SkyJourney
2026-07-30 11:04:18 +08:00
parent fb3ddada39
commit f7d4efeafc
10 changed files with 512 additions and 3 deletions
+149
View File
@@ -0,0 +1,149 @@
import { z } from "zod";
import { exportConfigSchema } from "./export-config.js";
export const DOCX_PANDOC_VERSION = "3.9.0.2";
export const DOCX_MIME_TYPE =
"application/vnd.openxmlformats-officedocument.wordprocessingml.document";
export const DOCX_FILE_EXTENSION = ".docx";
export const MAXIMUM_DOCX_MARKDOWN_LENGTH = 1_500_000;
export const MAXIMUM_DOCX_FILE_NAME_LENGTH = 500;
export const MAXIMUM_DOCX_RESOURCE_COUNT = 50;
export const documentExportFormatSchema = z.enum(["pdf", "docx"]);
export type DocumentExportFormat = z.infer<
typeof documentExportFormatSchema
>;
export const docxSourceResourceSchema = z.object({
path: z.string().min(1).max(1_000),
contentType: z.string().min(1).max(100).optional(),
data: z.string().min(1)
});
export type DocxSourceResource = z.infer<
typeof docxSourceResourceSchema
>;
export const docxExportRequestSchema = z.object({
markdown: z.string().max(MAXIMUM_DOCX_MARKDOWN_LENGTH),
fileName: z
.string()
.max(MAXIMUM_DOCX_FILE_NAME_LENGTH)
.default("文档.md"),
language: z.string().max(50).default("zh-CN"),
resources: z
.array(docxSourceResourceSchema)
.max(MAXIMUM_DOCX_RESOURCE_COUNT)
.default([]),
exportConfig: exportConfigSchema
});
export type DocxExportRequest = z.infer<
typeof docxExportRequestSchema
>;
export type DocxExportRequestInput = z.input<
typeof docxExportRequestSchema
>;
export const docxRuntimeStatusSchema = z.enum([
"available",
"not-found",
"version-mismatch",
"not-executable",
"probe-timeout"
]);
export type DocxRuntimeStatus = z.infer<
typeof docxRuntimeStatusSchema
>;
const availableDocxCapabilitySchema = z.object({
format: z.literal("docx"),
status: z.literal("available"),
expectedVersion: z.literal(DOCX_PANDOC_VERSION),
detectedVersion: z.literal(DOCX_PANDOC_VERSION)
});
const unavailableDocxCapabilitySchema = z.object({
format: z.literal("docx"),
status: docxRuntimeStatusSchema.exclude(["available"]),
expectedVersion: z.literal(DOCX_PANDOC_VERSION),
detectedVersion: z.string().max(100).optional(),
message: z.string().max(500)
});
export const docxCapabilitySchema = z.discriminatedUnion("status", [
availableDocxCapabilitySchema,
unavailableDocxCapabilitySchema
]);
export type DocxCapability = z.infer<typeof docxCapabilitySchema>;
export const docxExportErrorCodeSchema = z.enum([
"INVALID_DOCX_REQUEST",
"INVALID_EXPORT_CONFIG",
"INVALID_FILE_NAME",
"INVALID_MARKDOWN",
"MARKDOWN_TOO_LARGE",
"INVALID_IMAGE_RESOURCES",
"IMAGE_RESOURCES_TOO_LARGE",
"THEME_NOT_FOUND",
"DOCX_RUNTIME_NOT_FOUND",
"DOCX_RUNTIME_VERSION_MISMATCH",
"DOCX_RUNTIME_NOT_EXECUTABLE",
"DOCX_RUNTIME_PROBE_TIMEOUT",
"DOCX_QUEUE_FULL",
"DOCX_RENDER_TIMEOUT",
"DOCX_GENERATION_FAILED",
"DOCX_OUTPUT_INVALID"
]);
export type DocxExportErrorCode = z.infer<
typeof docxExportErrorCodeSchema
>;
export const docxExportErrorResponseSchema = z.object({
error: docxExportErrorCodeSchema,
message: z.string().min(1).max(1_000),
retryable: z.boolean().default(false)
});
export type DocxExportErrorResponse = z.infer<
typeof docxExportErrorResponseSchema
>;
export interface DocxGenerationTimings {
probeMs: number;
prepareMs: number;
mediaMs: number;
referenceMs: number;
pandocMs: number;
validationMs: number;
totalMs: number;
}
export interface DocxExportDiagnostics {
warnings: string[];
echartsErrors: string[];
mermaidErrors: string[];
}
export interface DocxExportResult {
docx: Uint8Array;
fileName: string;
diagnostics: DocxExportDiagnostics;
timings: DocxGenerationTimings;
}
export function createDocxFileName(fileName: unknown) {
const source =
typeof fileName === "string"
? fileName.split(/[\\/]/).at(-1) ?? ""
: "";
const stem = source
.replace(/\.(?:md|markdown|docx)$/i, "")
.replace(/[<>:"/\\|?*\u0000-\u001f]/g, "_")
.replace(/[.\s]+$/g, "")
.trim();
return `${stem || "文档"}${DOCX_FILE_EXTENSION}`;
}
+1
View File
@@ -1,4 +1,5 @@
export * from "./document.js";
export * from "./docx.js";
export * from "./document-profile.js";
export * from "./document-link.js";
export * from "./export-config.js";