ai-chatbot/lib/ai/canvas-agent-runtime/image-preferences.ts

54 lines
2.6 KiB
TypeScript

/** User image controls are execution constraints, separate from the planning model. */
export type AgentImageParameters = {
aspectRatio?: string;
imageSize?: string;
imageCount?: number;
n?: number;
quality?: "auto" | "low" | "medium" | "high";
size?: string;
negativePrompt?: string;
promptExtend?: boolean;
watermark?: boolean;
seed?: number;
};
export type AgentImagePreferences = { modelId?: string; parameters?: AgentImageParameters };
export function applyAgentImagePreferences<T extends Record<string, unknown>>(
input: T,
preferences?: AgentImagePreferences,
): T & AgentImageParameters & { modelId?: string } {
const parameters = Object.fromEntries(Object.entries(preferences?.parameters || {})
.filter(([, value]) => value !== undefined));
const count = preferences?.parameters?.imageCount ?? preferences?.parameters?.n;
return {
...input,
...parameters,
...(count !== undefined ? { imageCount: count } : {}),
...(preferences?.modelId ? { modelId: preferences.modelId } : {}),
};
}
export function agentProviderImageParameters(parameters: AgentImageParameters) {
return Object.fromEntries(["quality", "size", "watermark", "seed"].flatMap(key => {
const value = parameters[key as keyof AgentImageParameters];
return value !== undefined ? [[key, value]] : [];
}));
}
/** Only user controls or an explicit instruction pin a model; the planner's own
* modelId and a preset default remain eligible for automatic recovery. */
export function resolveUserImageModel(text: string, entries: Array<{ model: { id: string; modelId: string; displayName?: string | null } }>, selectedId?: string) {
if (selectedId) return selectedId;
const names = entries.flatMap(({ model }) => [model.modelId, model.displayName].filter((name): name is string => Boolean(name && name.length >= 4)).map(name => ({ id: model.id, name })))
.sort((a, b) => b.name.length - a.name.length);
const matches: Array<{ id: string; index: number; length: number }> = [];
for (const { id, name } of names) {
const escaped = name.split(/[\s_-]+/).map(part => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[\\s_-]*");
const pattern = new RegExp(`(?:使用|选择|指定|通过|用)\\s*(?:模型\\s*)?${escaped}(?![a-z0-9.-])`, "ig");
for (const match of text.matchAll(pattern)) {
if (!/(?:不要|不|别|禁止|无需)\s*$/.test(text.slice(Math.max(0, match.index! - 8), match.index!))) matches.push({ id, index: match.index!, length: name.length });
}
}
return matches.sort((a, b) => b.index - a.index || b.length - a.length)[0]?.id;
}