fix(canvas): 提示词生成 - 移除本地长度限制并交由上游判断
This commit is contained in:
@@ -6,7 +6,6 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"infinite-canvas/backend/internal/model"
|
||||
)
|
||||
@@ -355,9 +354,6 @@ func (s *Service) ValidateTaskCapability(input map[string]any) error {
|
||||
}
|
||||
|
||||
func validateVideoTask(profile *VideoCapabilityConfig, input canvasGenerationInput) error {
|
||||
if utf8.RuneCountInString(input.Prompt) > profile.References.PromptMaxChars {
|
||||
return BadAuthRequest(fmt.Sprintf("提示词超过当前模型限制(最多 %d 字)", profile.References.PromptMaxChars))
|
||||
}
|
||||
if len(input.ReferenceImages) > profile.References.MaxImages || len(input.ReferenceVideos) > profile.References.MaxVideos || len(input.ReferenceAudios) > profile.References.MaxAudios {
|
||||
return BadAuthRequest("参考素材数量超过当前模型限制")
|
||||
}
|
||||
@@ -410,9 +406,6 @@ func validateImageTask(profile *ImageCapabilityConfig, input canvasGenerationInp
|
||||
if profile == nil {
|
||||
return nil
|
||||
}
|
||||
if utf8.RuneCountInString(input.Prompt) > profile.References.PromptMaxChars {
|
||||
return BadAuthRequest(fmt.Sprintf("提示词超过当前模型限制(最多 %d 字)", profile.References.PromptMaxChars))
|
||||
}
|
||||
if len(input.ReferenceImages) > profile.References.MaxImages {
|
||||
return BadAuthRequest(fmt.Sprintf("当前图片模型最多支持 %d 张参考图", profile.References.MaxImages))
|
||||
}
|
||||
|
||||
@@ -57,9 +57,9 @@ export function CanvasConfigNodePanel({ node, isRunning, inputSummary, onConfigC
|
||||
const hasAnyInput = Boolean(inputSummary.textCount || inputSummary.imageCount || inputSummary.videoCount || inputSummary.audioCount);
|
||||
const hasComposerContent = Boolean((node.metadata?.composerContent ?? node.metadata?.prompt ?? "").trim());
|
||||
const capabilityError = imageProfile
|
||||
? imageCapabilityError(imageProfile, node.metadata?.composerContent ?? node.metadata?.prompt ?? "", inputSummary)
|
||||
? imageCapabilityError(imageProfile, inputSummary)
|
||||
: videoProfile
|
||||
? videoCapabilityError(videoProfile, config.videoSeconds, node.metadata?.composerContent ?? node.metadata?.prompt ?? "", inputSummary, node.metadata?.videoEditOperation)
|
||||
? videoCapabilityError(videoProfile, config.videoSeconds, inputSummary, node.metadata?.videoEditOperation)
|
||||
: "";
|
||||
const canGenerate = (hasComposerContent || (mode === "audio" ? inputSummary.textCount > 0 : hasAnyInput)) && !capabilityError;
|
||||
|
||||
@@ -252,17 +252,15 @@ function videoConfigPatch(key: keyof AiConfig, value: string) {
|
||||
return { [key]: value };
|
||||
}
|
||||
|
||||
function videoCapabilityError(profile: NonNullable<ReturnType<typeof modelCapabilityConfigFor>["video"]>, seconds: string, prompt: string, input: CanvasConfigNodePanelProps["inputSummary"], operation?: string) {
|
||||
function videoCapabilityError(profile: NonNullable<ReturnType<typeof modelCapabilityConfigFor>["video"]>, seconds: string, input: CanvasConfigNodePanelProps["inputSummary"], operation?: string) {
|
||||
if (!videoDurationAllowed(profile, Number(seconds))) return "当前模型不支持该视频时长";
|
||||
if (Array.from(prompt).length > profile.references.promptMaxChars) return `提示词超过模型限制(最多 ${profile.references.promptMaxChars} 字)`;
|
||||
if (input.imageCount > profile.references.maxImages || input.videoCount > profile.references.maxVideos || input.audioCount > profile.references.maxAudios) return "参考素材数量超过当前模型限制";
|
||||
const resolvedOperation = operation || (input.audioCount > 0 && input.imageCount === 0 && input.videoCount === 0 ? "audio_to_video" : input.videoCount > 0 ? "extend" : input.imageCount > 0 ? "image_to_video" : "text_to_video");
|
||||
if (!profile.operations.includes(resolvedOperation)) return "当前模型不支持该生成模式";
|
||||
return "";
|
||||
}
|
||||
|
||||
function imageCapabilityError(profile: ImageCapabilityConfig, prompt: string, input: CanvasConfigNodePanelProps["inputSummary"]) {
|
||||
if (Array.from(prompt).length > profile.references.promptMaxChars) return `提示词超过模型限制(最多 ${profile.references.promptMaxChars} 字)`;
|
||||
function imageCapabilityError(profile: ImageCapabilityConfig, input: CanvasConfigNodePanelProps["inputSummary"]) {
|
||||
if (input.imageCount > profile.references.maxImages) return `当前图片模型最多支持 ${profile.references.maxImages} 张参考图`;
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -314,28 +314,12 @@ export default function CreatePage() {
|
||||
toast.error("当前模型不支持所选视频时长,请重新选择");
|
||||
return;
|
||||
}
|
||||
if (mode === "video" && Array.from(text).length > videoProfile.references.promptMaxChars) {
|
||||
toast.error(`提示词超过当前模型限制(最多 ${videoProfile.references.promptMaxChars} 字)`);
|
||||
return;
|
||||
}
|
||||
if (mode === "image" && Array.from(text).length > imageProfile.references.promptMaxChars) {
|
||||
toast.error(`提示词超过当前模型限制(最多 ${imageProfile.references.promptMaxChars} 字)`);
|
||||
return;
|
||||
}
|
||||
const settings = { ratio, seconds, quality, videoQuality, count };
|
||||
const references = selectedCreationReferences(text, mentionReferences);
|
||||
// 后端对图片和视频使用不同的参考字段;这里先拆分,避免媒体类型在写入任务时被误判。
|
||||
const referenceImages = attachments.filter(isImageAttachment);
|
||||
const referenceVideos = attachments.filter(isVideoAttachment);
|
||||
const expandedPrompt = expandCreationPrompt(text, references, attachments);
|
||||
if (mode === "video" && Array.from(expandedPrompt).length > videoProfile.references.promptMaxChars) {
|
||||
toast.error(`引用展开后提示词超过当前模型限制(最多 ${videoProfile.references.promptMaxChars} 字)`);
|
||||
return;
|
||||
}
|
||||
if (mode === "image" && Array.from(expandedPrompt).length > imageProfile.references.promptMaxChars) {
|
||||
toast.error(`引用展开后提示词超过当前模型限制(最多 ${imageProfile.references.promptMaxChars} 字)`);
|
||||
return;
|
||||
}
|
||||
const referenceMetadata = creationReferenceMetadata(references);
|
||||
followLatestMessageRef.current = true;
|
||||
const userMessage = newMessage("user", text, { mode, model: selectedModel, attachments, references, settings });
|
||||
@@ -744,7 +728,7 @@ function CreationComposer(props: ComposerProps) {
|
||||
<input ref={props.fileInputRef} type="file" hidden accept={props.mode === "video" ? "image/*,video/*" : "image/*"} multiple onChange={props.onFileChange} />
|
||||
<Tooltip title={!referencesSupported ? "当前模型不支持参考媒体" : "从素材库选择参考内容"}><button type="button" className="creation-chat-reference is-paper" onClick={props.onOpenLibrary} disabled={props.busy || !referencesSupported} aria-label="打开素材库选择参考内容"><Plus /><span>参考内容</span></button></Tooltip>
|
||||
<div className="creation-chat-editor">
|
||||
<CanvasResourceMentionTextarea value={props.prompt} references={props.references} maxLength={props.mode === "video" ? props.videoProfile.references.promptMaxChars : props.mode === "image" ? props.imageProfile.references.promptMaxChars : undefined} mentionMenuWidth={400} sendOnEnter={false} onChange={props.setPrompt} onSubmit={props.onSubmit} containerClassName="creation-chat-mention-container" className="creation-chat-mention-editor creation-scrollbar" style={{ color: "var(--creation-text)" }} placeholder={props.variant === "empty" ? emptyPlaceholder : placeholder} aria-label="创作提示词,可使用 @ 引用当前参考内容或技能" spellCheck disabled={props.busy} />
|
||||
<CanvasResourceMentionTextarea value={props.prompt} references={props.references} mentionMenuWidth={400} sendOnEnter={false} onChange={props.setPrompt} onSubmit={props.onSubmit} containerClassName="creation-chat-mention-container" className="creation-chat-mention-editor creation-scrollbar" style={{ color: "var(--creation-text)" }} placeholder={props.variant === "empty" ? emptyPlaceholder : placeholder} aria-label="创作提示词,可使用 @ 引用当前参考内容或技能" spellCheck disabled={props.busy} />
|
||||
{props.attachments.length ? <div className="creation-chat-attachment-strip">{props.attachments.map((item) => <div key={item.id} className="creation-chat-attachment">{isVideoAttachment(item) ? <video src={item.url} poster={item.previewUrl !== item.url ? item.previewUrl : undefined} muted playsInline preload="metadata" aria-label={item.name} /> : <img src={item.previewUrl} alt={item.name} /> }<button type="button" onClick={() => props.onRemoveAttachment(item.id)} aria-label={`移除 ${item.name}`}><X /></button></div>)}</div> : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -216,8 +216,7 @@ function resolveImageRequestSize(profile: ImageCapabilityConfig, quality: string
|
||||
return value ? { parameter: request.parameter, value } : undefined;
|
||||
}
|
||||
|
||||
function validateImageCapability(profile: ImageCapabilityConfig, prompt: string, references: ReferenceImage[], mask?: ReferenceImage) {
|
||||
if (Array.from(prompt).length > profile.references.promptMaxChars) throw new Error(`提示词超过当前模型限制(最多 ${profile.references.promptMaxChars} 字)`);
|
||||
function validateImageCapability(profile: ImageCapabilityConfig, references: ReferenceImage[], mask?: ReferenceImage) {
|
||||
if (references.length > profile.references.maxImages) throw new Error(`当前图片模型最多支持 ${profile.references.maxImages} 张参考图`);
|
||||
if (mask && !profile.references.maskSupported) throw new Error("当前图片模型不支持蒙版编辑");
|
||||
if (profile.references.maxImageBytes > 0 && references.some((image) => (image.bytes || 0) > profile.references.maxImageBytes)) throw new Error("参考图片文件超过当前模型大小限制");
|
||||
@@ -811,7 +810,7 @@ export async function requestGeneration(config: AiConfig, prompt: string, option
|
||||
const selectedModel = config.model || config.imageModel;
|
||||
const requestConfig = resolveModelRequestConfig(config, selectedModel);
|
||||
const imageProfile = modelCapabilityConfigFor(config, selectedModel).image!;
|
||||
validateImageCapability(imageProfile, prompt, []);
|
||||
validateImageCapability(imageProfile, []);
|
||||
const normalizedImage = normalizeImageValue(imageProfile, config);
|
||||
const n = Number(normalizedImage.count);
|
||||
if (requestConfig.apiFormat === "gemini") {
|
||||
@@ -896,7 +895,7 @@ export async function requestEdit(config: AiConfig, prompt: string, references:
|
||||
const selectedModel = config.model || config.imageModel;
|
||||
const requestConfig = resolveModelRequestConfig(config, selectedModel);
|
||||
const imageProfile = modelCapabilityConfigFor(config, selectedModel).image!;
|
||||
validateImageCapability(imageProfile, prompt, references, mask);
|
||||
validateImageCapability(imageProfile, references, mask);
|
||||
const normalizedImage = normalizeImageValue(imageProfile, config);
|
||||
const n = Number(normalizedImage.count);
|
||||
const requestPrompt = buildImageReferencePromptText(prompt, references);
|
||||
|
||||
@@ -61,7 +61,7 @@ export async function createVideoGenerationTask(config: AiConfig, prompt: string
|
||||
const selectedModel = (config.model || config.videoModel).trim();
|
||||
const requestConfig = resolveModelRequestConfig(config, selectedModel);
|
||||
assertVideoConfig(requestConfig, requestConfig.model);
|
||||
assertVideoCapability(modelCapabilityConfigFor(config, selectedModel).video!, prompt, references, videoReferences, audioReferences, config.videoSeconds);
|
||||
assertVideoCapability(modelCapabilityConfigFor(config, selectedModel).video!, references, videoReferences, audioReferences, config.videoSeconds);
|
||||
if (requestConfig.interfaceType === "newapi-channel-2") {
|
||||
return createVideoGenerationsTask(requestConfig, selectedModel, prompt, references, videoReferences, audioReferences, options);
|
||||
}
|
||||
@@ -80,8 +80,7 @@ export async function createVideoGenerationTask(config: AiConfig, prompt: string
|
||||
return createOpenAIVideoTask(requestConfig, selectedModel, prompt, references, options);
|
||||
}
|
||||
|
||||
function assertVideoCapability(profile: NonNullable<ReturnType<typeof modelCapabilityConfigFor>["video"]>, prompt: string, references: ReferenceImage[], videoReferences: ReferenceVideo[], audioReferences: ReferenceAudio[], seconds: string) {
|
||||
if (Array.from(prompt).length > profile.references.promptMaxChars) throw new Error(`提示词超过当前模型限制(最多 ${profile.references.promptMaxChars} 字)`);
|
||||
function assertVideoCapability(profile: NonNullable<ReturnType<typeof modelCapabilityConfigFor>["video"]>, references: ReferenceImage[], videoReferences: ReferenceVideo[], audioReferences: ReferenceAudio[], seconds: string) {
|
||||
if (references.length > profile.references.maxImages || videoReferences.length > profile.references.maxVideos || audioReferences.length > profile.references.maxAudios) throw new Error("参考素材数量超过当前模型限制");
|
||||
if (!videoDurationAllowed(profile, Number(seconds))) throw new Error("视频时长不在当前模型支持范围内");
|
||||
if (profile.references.maxImageBytes > 0 && references.some((image) => (image.bytes || 0) > profile.references.maxImageBytes)) throw new Error("参考图片文件超过当前模型大小限制");
|
||||
|
||||
Reference in New Issue
Block a user