return prompt analysis without generating images
This commit is contained in:
parent
7ff6af2065
commit
1e3e81ed0b
@ -1662,6 +1662,56 @@ function buildAutoModeFallbackMessage(params: {
|
|||||||
].join("\n");
|
].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 {
|
function hasStructuredAgentSections(message?: string): boolean {
|
||||||
if (!message) return false;
|
if (!message) return false;
|
||||||
return /(?:###\s*需求理解|【\s*需求理解\s*】|###\s*图片分析|【\s*图片分析\s*】|###\s*执行计划|【\s*执行计划\s*】|###\s*优化后提示词|【\s*优化后提示词\s*】)/.test(
|
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();
|
const streamId = generateUUID();
|
||||||
await createStreamId({ streamId, chatId: id });
|
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) {
|
if (shouldRunAgentConversation && smartAutoNeedsClarification) {
|
||||||
const hasCurrentImageInput = message.parts.some((part: any) =>
|
const hasCurrentImageInput = message.parts.some((part: any) =>
|
||||||
part?.type === "file" || part?.type === "image" || part?.type === "image_url"
|
part?.type === "file" || part?.type === "image" || part?.type === "image_url"
|
||||||
|
|||||||
@ -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 {
|
function isExplicitVideoGenerationRequest(text: string): boolean {
|
||||||
const normalizedText = normalizeCompactText(text);
|
const normalizedText = normalizeCompactText(text);
|
||||||
const hasStrongVideoMarkers =
|
const hasStrongVideoMarkers =
|
||||||
@ -112,6 +132,8 @@ function applyDirectExecutionPolicy(
|
|||||||
hasAnyRequestText && isLikelyAdvisoryOrPlanningRequest(params.text);
|
hasAnyRequestText && isLikelyAdvisoryOrPlanningRequest(params.text);
|
||||||
const likelyVisionRequest = hasAnyRequestText && isLikelyVisionRequest(params.text);
|
const likelyVisionRequest = hasAnyRequestText && isLikelyVisionRequest(params.text);
|
||||||
const likelyGeneralChatTurn = hasAnyRequestText && isLikelyGeneralChatTurn(params.text);
|
const likelyGeneralChatTurn = hasAnyRequestText && isLikelyGeneralChatTurn(params.text);
|
||||||
|
const promptOptimizationOnlyRequest =
|
||||||
|
hasAnyRequestText && isExplicitPromptOptimizationRequest(params.text);
|
||||||
|
|
||||||
if (explicitVideoGenerationRequest) {
|
if (explicitVideoGenerationRequest) {
|
||||||
return {
|
return {
|
||||||
@ -130,6 +152,19 @@ function applyDirectExecutionPolicy(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!hasUsableImage) {
|
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 (!explicitImageGenerationRequest && !explicitVideoGenerationRequest) {
|
||||||
if (likelyAdvisoryRequest || likelyGeneralChatTurn || result.intent === "chat") {
|
if (likelyAdvisoryRequest || likelyGeneralChatTurn || result.intent === "chat") {
|
||||||
return {
|
return {
|
||||||
@ -174,6 +209,19 @@ function applyDirectExecutionPolicy(
|
|||||||
const keepClarification = Boolean(result.needsClarification);
|
const keepClarification = Boolean(result.needsClarification);
|
||||||
const explicitEditRequest = hasAnyRequestText && isLikelyImageEditRequest(params.text);
|
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) {
|
if (!hasAnyRequestText) {
|
||||||
return {
|
return {
|
||||||
...baseResult,
|
...baseResult,
|
||||||
@ -2102,6 +2150,14 @@ export function fallbackKeywordAnalysis(
|
|||||||
|
|
||||||
// === 图片编辑场景(需要有图片) ===
|
// === 图片编辑场景(需要有图片) ===
|
||||||
if (hasImage) {
|
if (hasImage) {
|
||||||
|
if (isExplicitPromptOptimizationRequest(text)) {
|
||||||
|
return {
|
||||||
|
intent: "vision",
|
||||||
|
confidence: 0.92,
|
||||||
|
conversationalReply: "收到,我先分析这张图并给你整理优化后的提示词,不直接生图。",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// 视频生成(图生视频/首尾帧等)
|
// 视频生成(图生视频/首尾帧等)
|
||||||
if (isExplicitVideoGenerationRequest(text)) {
|
if (isExplicitVideoGenerationRequest(text)) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user