export type SeedreamCreationMode = "basic" | "interactive" | "layers"; export type SeedreamPoint = { x: number; y: number; }; export type SeedreamBox = { left: number; top: number; right: number; bottom: number; }; export type SeedreamAnnotation = { id: string; index: number; kind: "point" | "box" | "stroke"; sourceKey?: string; point?: SeedreamPoint; box?: SeedreamBox; points?: SeedreamPoint[]; }; export type SeedreamAnnotationReference = { token: string; label: string; caption: "画布标注"; }; export function seedreamAnnotationToken(index: number, kind: SeedreamAnnotation["kind"] = "box") { return kind === "stroke" ? `@标记${index}` : `@标注${index}`; } export function seedreamAnnotationReferences(annotations: SeedreamAnnotation[], query = ""): SeedreamAnnotationReference[] { const normalizedQuery = query.replace(/^@+/, "").trim().toLowerCase(); return [...annotations] .sort((a, b) => a.index - b.index) .map((annotation) => { const token = seedreamAnnotationToken(annotation.index, annotation.kind); const label = annotation.kind === "box" ? `框选区域 ${annotation.index}` : annotation.kind === "point" ? `点选位置 ${annotation.index}` : `蓝色标记 ${annotation.index}`; return { token, label, caption: "画布标注" as const }; }) .filter((reference) => !normalizedQuery || [reference.token, reference.token.slice(1), reference.label, reference.caption] .some((value) => value.toLowerCase().includes(normalizedQuery))); } export function seedreamAnnotationTag(annotation: SeedreamAnnotation, imageIndex?: number) { const prefix = imageIndex && imageIndex > 0 ? `图${imageIndex}` : ""; if (annotation.kind === "point" && annotation.point) { return `${prefix}${coordinate(annotation.point.x)} ${coordinate(annotation.point.y)}`; } if (annotation.kind === "box" && annotation.box) { return `${prefix}${coordinate(annotation.box.left)} ${coordinate(annotation.box.top)} ${coordinate(annotation.box.right)} ${coordinate(annotation.box.bottom)}`; } if (annotation.kind === "stroke" && annotation.points?.length) { return `${prefix || "图片"}中的蓝色标记区域`; } return ""; } export function missingSeedreamAnnotationTokens(prompt: string, annotations: SeedreamAnnotation[]) { const available = new Set(annotations.map((annotation) => seedreamAnnotationToken(annotation.index, annotation.kind))); const missing = new Set(); for (const match of prompt.matchAll(/@(标注|标记)(\d+)/g)) { const token = `@${match[1]}${match[2]}`; if (!available.has(token)) missing.add(token); } return [...missing].sort((a, b) => annotationTokenIndex(a) - annotationTokenIndex(b)); } export function buildSeedreamInteractivePrompt(prompt: string, annotations: SeedreamAnnotation[], sourceKeys: string[] = []) { const annotationByIndex = new Map(annotations.map((annotation) => [annotation.index, annotation])); const imageIndexBySource = new Map(sourceKeys.map((key, index) => [key, index + 1])); const referenced = new Set(); const transformed = normalizeSeedreamImageTokens(prompt).replace(/@(标注|标记)(\d+)/g, (token, kind: string, rawIndex: string) => { const index = Number(rawIndex); const annotation = annotationByIndex.get(index); if (!annotation || seedreamAnnotationToken(index, annotation.kind) !== token) return token; referenced.add(index); return seedreamAnnotationTag(annotation, imageIndexFor(annotation, imageIndexBySource)); }).trim(); const unreferenced = annotations .filter((annotation) => !referenced.has(annotation.index)) .map((annotation) => `${annotation.kind === "stroke" ? "标记" : "标注"}${annotation.index} ${seedreamAnnotationTag(annotation, imageIndexFor(annotation, imageIndexBySource))}`) .filter((value) => !value.endsWith(" ")); if (!unreferenced.length) return transformed; return `${transformed}\n编辑位置:${unreferenced.join(";")}。`.trim(); } export function buildSeedreamLayerPrompt(prompt: string, annotations: SeedreamAnnotation[]) { const annotationByIndex = new Map(annotations.map((annotation) => [annotation.index, annotation])); const referenced = new Set(); const transformed = normalizeSeedreamImageTokens(prompt, true).replace(/@(标注|标记)(\d+)/g, (token, _kind: string, rawIndex: string) => { const index = Number(rawIndex); const annotation = annotationByIndex.get(index); if (!annotation || seedreamAnnotationToken(index, annotation.kind) !== token) return token; referenced.add(index); return seedreamAnnotationTag(annotation); }).trim(); const unreferenced = annotations .filter((annotation) => !referenced.has(annotation.index)) .map((annotation) => seedreamAnnotationTag(annotation)) .filter(Boolean); if (!unreferenced.length) return transformed; const instruction = `需拆分区域:${unreferenced.join(";")}。`; return transformed ? `${transformed}\n${instruction}` : instruction; } export function normalizeSeedreamImageTokens(prompt: string, forceFirstImage = false) { return prompt.replace(/@(图片|图)(\d+)/g, (_token, _kind: string, rawIndex: string) => `图${forceFirstImage ? 1 : Number(rawIndex)}`); } export function seedreamCreationModeLabel(mode: SeedreamCreationMode) { if (mode === "interactive") return "交互编辑"; if (mode === "layers") return "图层拆分"; return "基础生图"; } function coordinate(value: number) { return Math.max(0, Math.min(1000, Math.round(value))); } function imageIndexFor(annotation: SeedreamAnnotation, imageIndexBySource: Map) { if (annotation.sourceKey) return imageIndexBySource.get(annotation.sourceKey) || 1; return 1; } function annotationTokenIndex(token: string) { return Number(token.match(/\d+$/)?.[0] || 0); }