- Replaced the eager `markit-ai` singleton import with a lazy async factory in `markit.ts`. - Initialized and memoized a `Markit` instance on first use, with later calls reusing it. - Updated conversion helpers to receive the initialized `Markit` instance and execute conversions through a shared runner.
90 lines
2.6 KiB
TypeScript
90 lines
2.6 KiB
TypeScript
import { untilAborted } from "@oh-my-pi/pi-utils";
|
|
import type { Markit, StreamInfo } from "markit-ai";
|
|
import { ToolAbortError } from "../tools/tool-errors";
|
|
|
|
export interface MarkitConversionResult {
|
|
content: string;
|
|
ok: boolean;
|
|
error?: string;
|
|
}
|
|
|
|
let markit: () => Markit | Promise<Markit> = async () => {
|
|
const promise = import("markit-ai").then(({ Markit }) => {
|
|
const instance = new Markit();
|
|
markit = () => instance;
|
|
return instance;
|
|
});
|
|
markit = () => promise;
|
|
return promise;
|
|
};
|
|
|
|
function normalizeExtension(extension: string): string {
|
|
const trimmed = extension.trim().toLowerCase();
|
|
if (!trimmed) return ".bin";
|
|
return trimmed.startsWith(".") ? trimmed : `.${trimmed}`;
|
|
}
|
|
|
|
function normalizeError(error: unknown): string {
|
|
if (error instanceof Error && error.message.trim().length > 0) {
|
|
return error.message.trim();
|
|
}
|
|
return "Conversion failed";
|
|
}
|
|
|
|
async function runMarkitConversion<T>(task: (markit: Markit) => Promise<T>, signal?: AbortSignal): Promise<T> {
|
|
try {
|
|
const instance = await markit();
|
|
return signal ? await untilAborted(signal, () => task(instance)) : await task(instance);
|
|
} catch (error) {
|
|
if (error instanceof ToolAbortError) {
|
|
throw error;
|
|
}
|
|
if (error instanceof Error && error.name === "AbortError") {
|
|
throw new ToolAbortError();
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
function finalizeConversion(markdown?: string): MarkitConversionResult {
|
|
if (typeof markdown === "string" && markdown.length > 0) {
|
|
return { content: markdown, ok: true };
|
|
}
|
|
|
|
return { content: "", ok: false, error: "Conversion produced no output" };
|
|
}
|
|
|
|
export async function convertFileWithMarkit(filePath: string, signal?: AbortSignal): Promise<MarkitConversionResult> {
|
|
try {
|
|
const result = await runMarkitConversion(markit => markit.convertFile(filePath), signal);
|
|
return finalizeConversion(result.markdown);
|
|
} catch (error) {
|
|
if (error instanceof ToolAbortError) {
|
|
throw error;
|
|
}
|
|
return { content: "", ok: false, error: normalizeError(error) };
|
|
}
|
|
}
|
|
|
|
export async function convertBufferWithMarkit(
|
|
buffer: Uint8Array,
|
|
extension: string,
|
|
signal?: AbortSignal,
|
|
): Promise<MarkitConversionResult> {
|
|
const normalizedExtension = normalizeExtension(extension);
|
|
const streamInfo: StreamInfo = {
|
|
extension: normalizedExtension,
|
|
filename: `input${normalizedExtension}`,
|
|
};
|
|
|
|
try {
|
|
const result = await runMarkitConversion(markit => markit.convert(Buffer.from(buffer), streamInfo), signal);
|
|
return finalizeConversion(result.markdown);
|
|
} catch (error) {
|
|
if (error instanceof ToolAbortError) {
|
|
throw error;
|
|
}
|
|
return { content: "", ok: false, error: normalizeError(error) };
|
|
}
|
|
}
|