feat: 画布图片节点添加图片标注功能
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
94640002d0
commit
59a81f3c0e
@ -456,6 +456,10 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
||||
}
|
||||
}
|
||||
|
||||
// 🆕 检查是否有标注的图片
|
||||
const annotatedNodes = selectedImageNodes.filter(node => (node.data as any)?.hasAnnotations);
|
||||
const hasAnnotatedImages = annotatedNodes.length > 0;
|
||||
|
||||
// 添加文件部分(作为 file 类型的 part)
|
||||
effectiveAttachments.forEach((a) => {
|
||||
parts.push({
|
||||
@ -463,11 +467,19 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
|
||||
mediaType: a.contentType,
|
||||
name: a.name,
|
||||
url: a.url,
|
||||
hasAnnotations: hasAnnotatedImages, // 🆕 标记图片是否有标注
|
||||
});
|
||||
});
|
||||
|
||||
// 添加文本部分
|
||||
const textContent = input.trim() || (effectiveAttachments.length > 0 ? "请处理这张图片" : "");
|
||||
let textContent = input.trim() || (effectiveAttachments.length > 0 ? "请处理这张图片" : "");
|
||||
|
||||
// 🆕 如果有标注的图片,在文本中添加提示
|
||||
if (hasAnnotatedImages && textContent) {
|
||||
textContent = `【注意:图片中有标注(箭头、圆圈、文字等)标记的区域是需要重点关注或修改的部分,请根据标注进行处理】\n${textContent}`;
|
||||
console.log('[CanvasChat] 检测到标注图片,已添加标注提示');
|
||||
}
|
||||
|
||||
if (textContent) {
|
||||
parts.push({ type: "text" as const, text: textContent });
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
import React, { useState, useRef, useEffect } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { ImagePlus, Loader2, AlertCircle, MoreVertical, Scissors, Sparkles, Expand, Maximize2, Move3D, Layers, Download, Trash2, RefreshCw } from "lucide-react";
|
||||
import { ImagePlus, Loader2, AlertCircle, MoreVertical, Scissors, Sparkles, Expand, Maximize2, Move3D, Layers, Download, Trash2, RefreshCw, Pencil } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
@ -27,6 +27,7 @@ import type { CanvasNode, ImageNodeData } from "@/types/canvas";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { AngleEditDialog } from "@/components/angle-edit-dialog";
|
||||
import { SceneFusionDialog } from "@/components/scene-fusion-dialog";
|
||||
import { ImageAnnotator } from "@/components/image-annotator";
|
||||
|
||||
interface ImageNodeProps {
|
||||
node: CanvasNode;
|
||||
@ -43,6 +44,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
const [resizeWidth, setResizeWidth] = useState(1024);
|
||||
const [resizeHeight, setResizeHeight] = useState(1024);
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
const [annotatorOpen, setAnnotatorOpen] = useState(false);
|
||||
|
||||
// 文件输入的 ref
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
@ -136,6 +138,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
'resize': '尺寸调整',
|
||||
'angle': '角度编辑',
|
||||
'scene-fusion': '详情替换',
|
||||
'annotate': '图片标注',
|
||||
};
|
||||
return names[action] || action;
|
||||
};
|
||||
@ -160,6 +163,10 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
setResizeDialogOpen(true);
|
||||
return;
|
||||
}
|
||||
if (action === 'annotate') {
|
||||
setAnnotatorOpen(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const actionName = getActionName(action);
|
||||
|
||||
@ -263,6 +270,26 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
});
|
||||
};
|
||||
|
||||
// 图片标注完成回调
|
||||
const handleAnnotateComplete = (annotatedImageUrl: string) => {
|
||||
setAnnotatorOpen(false);
|
||||
|
||||
// 更新当前节点,标记为已标注图片
|
||||
// 标注后的图片会带有视觉标记(箭头、圆圈、文字等)
|
||||
// 这些标记指示了用户希望 AI 重点关注/修改的区域
|
||||
updateNode(node.id, {
|
||||
data: {
|
||||
...data,
|
||||
src: annotatedImageUrl,
|
||||
hasAnnotations: true, // 🆕 标记为已标注
|
||||
originalSrc: data.originalSrc || data.src, // 保留原始图片
|
||||
prompt: data.prompt ? `${data.prompt} (已标注)` : '已标注图片',
|
||||
},
|
||||
});
|
||||
|
||||
toast.success('标注已保存!发送对话时将重点处理标注区域');
|
||||
};
|
||||
|
||||
// 尺寸调整执行
|
||||
const handleResize = async () => {
|
||||
if (!data.src) return;
|
||||
@ -617,6 +644,18 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
<span>调整</span>
|
||||
</Button>
|
||||
|
||||
{/* 图片标注 */}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={`h-9 px-3 text-xs gap-1.5 rounded-full hover:bg-gray-100 ${data.hasAnnotations ? 'text-primary' : ''}`}
|
||||
onClick={() => handleAction('annotate')}
|
||||
title="标注图片重点区域"
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
<span>标注</span>
|
||||
</Button>
|
||||
|
||||
{/* 更多操作 */}
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@ -647,6 +686,15 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
|
||||
{/* 图片标注器 */}
|
||||
{annotatorOpen && data.src && (
|
||||
<ImageAnnotator
|
||||
imageUrl={data.src}
|
||||
onConfirm={handleAnnotateComplete}
|
||||
onCancel={() => setAnnotatorOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -57,6 +57,7 @@ const EDIT_TEMPLATES: Record<
|
||||
negative: string;
|
||||
extractors: Record<string, RegExp>;
|
||||
defaults: Record<string, string>;
|
||||
useOriginalPrompt?: boolean; // 🆕 是否使用原始提示词
|
||||
}
|
||||
> = {
|
||||
remove_background: {
|
||||
@ -178,6 +179,13 @@ const LLM_OPTIMIZER_SYSTEM_PROMPT = `你是一个专业的AI图片编辑提示
|
||||
3. **细节要求**:包含颜色、材质、光影、位置等具体参数
|
||||
4. **自然过渡**:修改区域与周围环境自然融合
|
||||
|
||||
## 图片标注处理(重要!)
|
||||
如果用户消息中提到"图片中有标注"或"标注区域",说明用户已经在图片上画了标记(箭头、圆圈、文字等)来指示需要修改的区域:
|
||||
- **优先处理标注区域**:将标注标记的区域作为主要编辑目标
|
||||
- **理解标注意图**:箭头指向的位置、圆圈圈住的区域、文字说明的内容都是用户想要修改的部分
|
||||
- **强调标注**:在提示词中明确提到"按照图片中的标注/标记指示进行修改"
|
||||
- **保护非标注区域**:标注以外的区域应保持完全不变
|
||||
|
||||
## 输出格式(严格JSON)
|
||||
{
|
||||
"optimizedPrompt": "精准的图片编辑指令",
|
||||
@ -403,7 +411,7 @@ export function optimizePrompt(params: {
|
||||
friendlyMessage = friendlyTemplate.template;
|
||||
|
||||
// 填充友好消息中的变量
|
||||
for (const [key, regex] of Object.entries(template.extractors)) {
|
||||
for (const [key, regex] of Object.entries(template.extractors) as [string, RegExp][]) {
|
||||
const match = originalPrompt.match(regex);
|
||||
let value = match ? match[1].trim() : template.defaults[key] || "";
|
||||
value = value.replace(/[,。!?,.!?]/g, "").trim();
|
||||
@ -414,7 +422,7 @@ export function optimizePrompt(params: {
|
||||
}
|
||||
|
||||
// 填充剩余的默认值
|
||||
for (const [key, defaultValue] of Object.entries(template.defaults)) {
|
||||
for (const [key, defaultValue] of Object.entries(template.defaults) as [string, string][]) {
|
||||
friendlyMessage = friendlyMessage.replace(`{${key}}`, defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,6 +40,7 @@ export interface ImageNodeData {
|
||||
// 图片编辑相关
|
||||
originalSrc?: string;
|
||||
editHistory?: ImageEditAction[];
|
||||
hasAnnotations?: boolean; // 🆕 标记图片是否有用户标注
|
||||
}
|
||||
|
||||
// 视频节点数据
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user