return prompt analysis without generating images
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-03-10 10:22:11 +08:00
parent 7ff6af2065
commit 1e3e81ed0b
2 changed files with 233 additions and 0 deletions

View File

@ -1662,6 +1662,56 @@ function buildAutoModeFallbackMessage(params: {
].join("\n");
}
function sanitizePromptOnlyUserIntent(text: string): string {
return String(text || "")
.replace(/先?分析(?:一下)?(?:这张)?(?:图片|图像|这张图)?/g, "")
.replace(/帮我|给我|先给我|只给我|仅给我/g, "")
.replace(/优化(?:一下)?提示词|提示词优化|提示词|prompt/gi, "")
.replace(/不要生图|先别生图|不需要生图|不要生成|别生成|不直接生成/g, "")
.replace(/[,。;:,.!!??]/g, " ")
.replace(/\s+/g, " ")
.trim();
}
function buildPromptOnlyOptimizationSeed(params: {
text: string;
imageSummary?: string;
}): string {
const cleanedIntent = sanitizePromptOnlyUserIntent(params.text);
const summary = String(params.imageSummary || "").replace(/\s+/g, " ").trim();
if (cleanedIntent && summary) {
return `${cleanedIntent}。参考图关键特征:${summary}`;
}
if (summary) {
return `基于参考图生成一段可直接复现核心主体、场景、风格与光线特征的中文提示词。参考图关键特征:${summary}`;
}
if (cleanedIntent) {
return cleanedIntent;
}
return "基于当前参考图整理一段可直接使用的中文提示词。";
}
function buildPromptOnlyReply(params: {
userText: string;
imageSummary?: string;
optimizedPrompt: string;
}): string {
const demand = sanitizePromptOnlyUserIntent(params.userText) || "你希望我先分析这张图并整理可直接使用的提示词";
const summary = String(params.imageSummary || "").replace(/\s+/g, " ").trim();
return [
"【需求理解】",
`- ${demand}`,
"【图片分析】",
summary ? `- ${summary}` : "- 我已基于当前图片梳理主体、场景、风格与光线特征。",
"【优化后提示词】",
`- ${params.optimizedPrompt.replace(/\s+/g, " ").trim()}`,
"【使用建议】",
"- 本轮仅输出优化后的提示词,不直接执行生图。",
].join("\n");
}
function hasStructuredAgentSections(message?: string): boolean {
if (!message) return false;
return /(?:###\s*需求理解|【\s*需求理解\s*】|###\s*图片分析|【\s*图片分析\s*】|###\s*执行计划|【\s*执行计划\s*】|###\s*优化后提示词|【\s*优化后提示词\s*】)/.test(
@ -4042,6 +4092,133 @@ export async function POST(request: Request) {
const streamId = generateUUID();
await createStreamId({ streamId, chatId: id });
const wantsPromptOnlyFromCurrentImage =
shouldRunAgentConversation &&
!smartAutoNeedsClarification &&
requestResolvedMediaInput.currentMessageImageUrls.length > 0 &&
(
await import("@/lib/ai/smart-analyzer")
).isExplicitPromptOptimizationRequest(requestTextContent);
if (wantsPromptOnlyFromCurrentImage) {
let promptOnlyImageSummary = "";
try {
const { summarizeImageForExecutionReport } = await import("@/lib/ai/smart-analyzer");
const summaryTimeoutMs = getBudgetedTimeout({
startedAt: requestStartedAt,
preferredMs: SMART_AUTO_IMAGE_SUMMARY_TIMEOUT_MS,
minimumMs: 6_000,
});
if (summaryTimeoutMs) {
const summaryAssetIds = Array.from(
new Set([
...extractMessageImageAssetIds(message.parts as any[]),
...((contextPayload?.selectedAssetIds || []).filter(Boolean) as string[]),
]),
);
const summary = await withOperationTimeout(
summarizeImageForExecutionReport({
text: requestTextContent,
imageUrls: requestResolvedMediaInput.currentMessageImageUrls.slice(0, 4),
context: typeof autoContext === "string" ? autoContext : undefined,
preferredModelId: preferredAgentModelId,
assetIds: summaryAssetIds,
}),
"promptOnlyImageSummary",
summaryTimeoutMs,
);
if (summary?.trim()) {
promptOnlyImageSummary = summary.trim();
}
}
} catch (promptOnlySummaryError) {
console.warn("[Chat API] Prompt-only image summary failed:", promptOnlySummaryError);
}
let promptOnlyOptimizedPrompt = smartAutoOptimizedPrompt?.trim() || "";
if (!promptOnlyOptimizedPrompt) {
try {
const { optimizePromptWithLLM, optimizePrompt } = await import("@/lib/ai/prompt-optimizer");
const promptSeed = buildPromptOnlyOptimizationSeed({
text: requestTextContent,
imageSummary: promptOnlyImageSummary,
});
const optimizedResult = await optimizePromptWithLLM({
originalPrompt: promptSeed,
intent: "image_gen",
historyMessages: smartAutoHistoryMessagesFromDb,
context: typeof autoContext === "string" ? autoContext : undefined,
}).catch(() =>
optimizePrompt({
originalPrompt: promptSeed,
intent: "image_gen",
}),
);
promptOnlyOptimizedPrompt = String(optimizedResult?.optimizedPrompt || "").trim();
} catch (promptOnlyOptimizeError) {
console.warn("[Chat API] Prompt-only optimization failed:", promptOnlyOptimizeError);
}
}
if (!promptOnlyOptimizedPrompt) {
promptOnlyOptimizedPrompt = buildPromptOnlyOptimizationSeed({
text: requestTextContent,
imageSummary: promptOnlyImageSummary,
});
}
const promptOnlyMessage = buildPromptOnlyReply({
userText: requestTextContent,
imageSummary: promptOnlyImageSummary,
optimizedPrompt: promptOnlyOptimizedPrompt,
});
const promptOnlyStream = createUIMessageStream({
execute: async ({ writer }) => {
const messageId = generateUUID();
writeAssistantTextChunk(writer, promptOnlyMessage, "prompt-only:reply", messageId);
await saveMessages({
messages: [
{
id: messageId,
chatId: id,
role: "assistant",
parts: [{ type: "text", text: promptOnlyMessage }],
attachments: [],
createdAt: new Date(),
videoStorageStatus: null,
temporaryVideoUrl: null,
permanentVideoUrl: null,
archivedAt: null,
videoMetadata: null,
},
],
});
try {
await upsertConversationWorkingState({
userId: session.user.id,
chatId: id,
projectId: contextPayload?.projectId || null,
currentIntent: "vision",
lastMessageId: messageId,
lastTurnSummary: {
text: requestTextContent.slice(0, 500),
resolutionMode: "prompt_only",
confidence: 90,
optimizedPrompt: promptOnlyOptimizedPrompt.slice(0, 1000),
},
});
} catch (promptOnlyMemoryError) {
console.warn("[Chat API] Failed to persist prompt-only response state:", promptOnlyMemoryError);
}
},
});
return createSseResponse(promptOnlyStream);
}
if (shouldRunAgentConversation && smartAutoNeedsClarification) {
const hasCurrentImageInput = message.parts.some((part: any) =>
part?.type === "file" || part?.type === "image" || part?.type === "image_url"

View File

@ -46,6 +46,26 @@ function isLikelyAdvisoryOrPlanningRequest(text: string): boolean {
);
}
export function isExplicitPromptOptimizationRequest(text: string): boolean {
const normalizedText = normalizeCompactText(text);
if (!normalizedText) return false;
const asksPrompt =
/提示词|prompt|文生图提示|出图提示|描述词|咒语|关键词/.test(normalizedText);
if (!asksPrompt) return false;
const asksAnalysis =
/分析|看图|看看|识别|拆解|提炼|整理|归纳|总结|描述|写出|给我|输出|优化|改写/.test(
normalizedText,
);
const blocksExecution =
/不要生图|先别生图|不需要生图|不要生成|别生成|仅给|只给|先给我|先出提示词|先分析/.test(
normalizedText,
);
return asksAnalysis || blocksExecution;
}
function isExplicitVideoGenerationRequest(text: string): boolean {
const normalizedText = normalizeCompactText(text);
const hasStrongVideoMarkers =
@ -112,6 +132,8 @@ function applyDirectExecutionPolicy(
hasAnyRequestText && isLikelyAdvisoryOrPlanningRequest(params.text);
const likelyVisionRequest = hasAnyRequestText && isLikelyVisionRequest(params.text);
const likelyGeneralChatTurn = hasAnyRequestText && isLikelyGeneralChatTurn(params.text);
const promptOptimizationOnlyRequest =
hasAnyRequestText && isExplicitPromptOptimizationRequest(params.text);
if (explicitVideoGenerationRequest) {
return {
@ -130,6 +152,19 @@ function applyDirectExecutionPolicy(
}
if (!hasUsableImage) {
if (promptOptimizationOnlyRequest) {
return {
...result,
intent: "chat",
subIntent: undefined,
confidence: Math.max(result.confidence, 0.82),
needsClarification: false,
clarifyingQuestions: [],
conversationalReply:
result.conversationalReply || "收到,我先按你的要求整理提示词,不直接执行生成。",
};
}
if (!explicitImageGenerationRequest && !explicitVideoGenerationRequest) {
if (likelyAdvisoryRequest || likelyGeneralChatTurn || result.intent === "chat") {
return {
@ -174,6 +209,19 @@ function applyDirectExecutionPolicy(
const keepClarification = Boolean(result.needsClarification);
const explicitEditRequest = hasAnyRequestText && isLikelyImageEditRequest(params.text);
if (promptOptimizationOnlyRequest) {
return {
...baseResult,
intent: "vision",
subIntent: undefined,
confidence: Math.max(baseResult.confidence, 0.88),
needsClarification: false,
clarifyingQuestions: [],
conversationalReply:
baseResult.conversationalReply || "收到,我先分析这张图并整理可直接使用的优化提示词,不会直接生图。",
};
}
if (!hasAnyRequestText) {
return {
...baseResult,
@ -2102,6 +2150,14 @@ export function fallbackKeywordAnalysis(
// === 图片编辑场景(需要有图片) ===
if (hasImage) {
if (isExplicitPromptOptimizationRequest(text)) {
return {
intent: "vision",
confidence: 0.92,
conversationalReply: "收到,我先分析这张图并给你整理优化后的提示词,不直接生图。",
};
}
// 视频生成(图生视频/首尾帧等)
if (isExplicitVideoGenerationRequest(text)) {
return {