import { extractMaterialPlaceholders } from "@/lib/prompt/material-placeholders"; export type MaterialDraftKind = "image" | "video" | "audio"; const tokenBoundaryBeforePattern = /[\s,。;:、((【\[]$/; const tokenBoundaryAfterPattern = /^[\s,。;:、))】\]]/; export function detectMaterialDraftStart(previous: string, next: string, cursor: number): { text: string; index: number } | null { const index = cursor - 1; if (index < 0 || next[index] !== "@") return null; if (next.length !== previous.length + 1) return null; if (`${next.slice(0, index)}${next.slice(cursor)}` !== previous) return null; return { text: previous, index }; } export function normalizeMaterialDraftToken(input: string, prompt: string, defaultKind: MaterialDraftKind = "image"): string | null { const query = input.replace(/^@+/, "").trim().replace(/\s+/g, ""); if (!query) return nextMaterialDraftToken(defaultKind, prompt); const explicit = query.match(/^(参考视频|图片|图|视频|音频)(\d+)$/); if (explicit) { const kind = kindFromText(explicit[1]); const index = Number(explicit[2]); if (Number.isInteger(index) && index > 0) return labelForDraftKind(kind, index); return null; } const kindOnly = query.match(/^(参考视频|图片|图|视频|音频)$/); if (kindOnly) return nextMaterialDraftToken(kindFromText(kindOnly[1]), prompt); return null; } export function nextMaterialDraftToken(kind: MaterialDraftKind, prompt: string): string { const placeholders = extractMaterialPlaceholders(prompt).filter((placeholder) => placeholder.type === kind); const nextIndex = placeholders.reduce((max, placeholder) => Math.max(max, placeholder.index), 0) + 1; return labelForDraftKind(kind, nextIndex); } export function insertMaterialDraftToken(text: string, index: number, token: string): { text: string; cursor: number } { const safeIndex = Math.max(0, Math.min(index, text.length)); const before = text.slice(0, safeIndex); const after = text.slice(safeIndex); const needsLeadingSpace = before.length > 0 && !tokenBoundaryBeforePattern.test(before); const needsTrailingSpace = after.length === 0 || !tokenBoundaryAfterPattern.test(after); const insertion = `${needsLeadingSpace ? " " : ""}${token}${needsTrailingSpace ? " " : ""}`; return { text: `${before}${insertion}${after}`, cursor: before.length + insertion.length }; } export function labelForDraftKind(kind: MaterialDraftKind, index: number): string { if (kind === "video") return `@视频${index}`; if (kind === "audio") return `@音频${index}`; return `@图片${index}`; } function kindFromText(value: string): MaterialDraftKind { if (value === "视频" || value === "参考视频") return "video"; if (value === "音频") return "audio"; return "image"; }