fix: 识别成功后直接添加到对话框,去掉确认弹窗
This commit is contained in:
parent
13e72c9134
commit
7db61d8533
@ -2,7 +2,7 @@
|
||||
|
||||
import React, { useState, useRef, useCallback } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
import { X, Loader2, Check, RefreshCw, Crosshair } from "lucide-react";
|
||||
import { X, Loader2, Crosshair } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useCanvasStore, type PickedObject } from "@/store/canvas";
|
||||
import { toast } from "sonner";
|
||||
@ -13,10 +13,8 @@ export function ObjectPickerOverlay() {
|
||||
const isPickerMode = useCanvasStore((state) => state.isPickerMode);
|
||||
const pickerTargetNodeId = useCanvasStore((state) => state.pickerTargetNodeId);
|
||||
const isPickerLoading = useCanvasStore((state) => state.isPickerLoading);
|
||||
const pickedObject = useCanvasStore((state) => state.pickedObject);
|
||||
const setPickerMode = useCanvasStore((state) => state.setPickerMode);
|
||||
const setPickerLoading = useCanvasStore((state) => state.setPickerLoading);
|
||||
const setPickedObject = useCanvasStore((state) => state.setPickedObject);
|
||||
const addPendingPickedObject = useCanvasStore((state) => state.addPendingPickedObject);
|
||||
const getNode = useCanvasStore((state) => state.getNode);
|
||||
|
||||
@ -27,7 +25,7 @@ export function ObjectPickerOverlay() {
|
||||
const targetNode = pickerTargetNodeId ? getNode(pickerTargetNodeId) : null;
|
||||
const imageUrl = targetNode?.type === 'image' ? (targetNode.data as any)?.src as string | undefined : undefined;
|
||||
|
||||
// 处理图片点击
|
||||
// 处理图片点击 - 识别成功后直接添加到对话框
|
||||
const handleImageClick = useCallback(async (e: React.MouseEvent<HTMLImageElement>) => {
|
||||
if (isPickerLoading || !imageUrl || !pickerTargetNodeId) return;
|
||||
|
||||
@ -43,12 +41,12 @@ export function ObjectPickerOverlay() {
|
||||
setPickerLoading(true);
|
||||
|
||||
try {
|
||||
// 直接使用图片 URL 调用 API(不再压缩为 Base64)
|
||||
// 直接使用原始图片 URL 调用 API(无任何标记)
|
||||
const response = await fetch('/api/pick-object', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
imageUrl, // 直接发送图片 URL
|
||||
imageUrl, // 原始图片 URL,无标记
|
||||
clickX,
|
||||
clickY,
|
||||
}),
|
||||
@ -61,7 +59,7 @@ export function ObjectPickerOverlay() {
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.object) {
|
||||
// 设置识别结果
|
||||
// 构建拾取结果
|
||||
const picked: PickedObject = {
|
||||
imageUrl,
|
||||
imageNodeId: pickerTargetNodeId,
|
||||
@ -72,8 +70,14 @@ export function ObjectPickerOverlay() {
|
||||
confidence: result.object.confidence,
|
||||
boundingBox: result.object.boundingBox,
|
||||
};
|
||||
setPickedObject(picked);
|
||||
toast.success(`识别成功: ${result.object.name}`);
|
||||
|
||||
// 直接添加到对话框,不询问确认
|
||||
addPendingPickedObject(picked);
|
||||
toast.success(`已添加: ${result.object.name}`);
|
||||
|
||||
// 关闭拾取模式
|
||||
setPickerMode(false);
|
||||
setClickPosition(null);
|
||||
} else {
|
||||
toast.error(result.error || '未能识别物品');
|
||||
setClickPosition(null);
|
||||
@ -85,23 +89,7 @@ export function ObjectPickerOverlay() {
|
||||
} finally {
|
||||
setPickerLoading(false);
|
||||
}
|
||||
}, [imageUrl, isPickerLoading, pickerTargetNodeId, setPickerLoading, setPickedObject]);
|
||||
|
||||
// 确认添加到对话
|
||||
const handleConfirm = useCallback(() => {
|
||||
if (pickedObject) {
|
||||
addPendingPickedObject(pickedObject);
|
||||
toast.success('已添加到对话框');
|
||||
setPickerMode(false);
|
||||
setClickPosition(null);
|
||||
}
|
||||
}, [pickedObject, addPendingPickedObject, setPickerMode]);
|
||||
|
||||
// 重新选择
|
||||
const handleRetry = useCallback(() => {
|
||||
setPickedObject(null);
|
||||
setClickPosition(null);
|
||||
}, [setPickedObject]);
|
||||
}, [imageUrl, isPickerLoading, pickerTargetNodeId, setPickerLoading, addPendingPickedObject, setPickerMode]);
|
||||
|
||||
// 关闭拾取模式
|
||||
const handleClose = useCallback(() => {
|
||||
@ -126,7 +114,7 @@ export function ObjectPickerOverlay() {
|
||||
<div className="absolute top-4 left-1/2 -translate-x-1/2 flex items-center gap-3 bg-white rounded-full px-4 py-2 shadow-lg">
|
||||
<Crosshair className="h-5 w-5 text-orange-500" />
|
||||
<span className="text-sm font-medium">
|
||||
{isPickerLoading ? '正在识别...' : pickedObject ? '识别完成' : '点击图片中的物品进行拾取'}
|
||||
{isPickerLoading ? '正在识别...' : '点击图片中的物品进行拾取'}
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
@ -140,20 +128,20 @@ export function ObjectPickerOverlay() {
|
||||
|
||||
{/* 图片容器 */}
|
||||
<div className="relative max-w-[80vw] max-h-[70vh]">
|
||||
{/* 图片 */}
|
||||
{/* 图片 - 原始图片,无任何标记 */}
|
||||
<img
|
||||
src={imageUrl}
|
||||
alt="拾取目标"
|
||||
className={cn(
|
||||
"max-w-full max-h-[70vh] object-contain rounded-lg border-4 transition-all",
|
||||
isPickerLoading ? "border-orange-400 opacity-80" : "border-orange-500",
|
||||
!pickedObject && !isPickerLoading && "cursor-crosshair"
|
||||
!isPickerLoading && "cursor-crosshair"
|
||||
)}
|
||||
onClick={!pickedObject && !isPickerLoading ? handleImageClick : undefined}
|
||||
onClick={!isPickerLoading ? handleImageClick : undefined}
|
||||
draggable={false}
|
||||
/>
|
||||
|
||||
{/* 点击位置标记(脉冲动画) */}
|
||||
{/* 点击位置标记(脉冲动画)- 仅在前端显示,不影响发送给 AI 的图片 */}
|
||||
{clickPosition && (
|
||||
<div
|
||||
className="absolute pointer-events-none"
|
||||
@ -174,22 +162,6 @@ export function ObjectPickerOverlay() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 物品名称标签(识别成功后,显示在点击位置上方) */}
|
||||
{pickedObject && clickPosition && (
|
||||
<div
|
||||
className="absolute pointer-events-none"
|
||||
style={{
|
||||
left: `${clickPosition.x * 100}%`,
|
||||
top: `${clickPosition.y * 100}%`,
|
||||
transform: 'translate(-50%, -100%)',
|
||||
}}
|
||||
>
|
||||
<div className="bg-green-500 text-white text-xs px-2 py-1 rounded whitespace-nowrap mb-2 shadow-lg">
|
||||
{pickedObject.objectName}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 加载状态 */}
|
||||
{isPickerLoading && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/20 rounded-lg">
|
||||
@ -200,44 +172,6 @@ export function ObjectPickerOverlay() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 识别结果确认框 */}
|
||||
{pickedObject && !isPickerLoading && (
|
||||
<div className="absolute bottom-8 left-1/2 -translate-x-1/2 bg-white rounded-xl shadow-2xl p-4 min-w-[280px]">
|
||||
<div className="flex items-start gap-3 mb-4">
|
||||
<div className="w-10 h-10 rounded-lg bg-green-100 flex items-center justify-center flex-shrink-0">
|
||||
<Crosshair className="h-5 w-5 text-green-600" />
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900">{pickedObject.objectName}</h4>
|
||||
<p className="text-sm text-gray-500">{pickedObject.objectPosition}</p>
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
置信度: {Math.round(pickedObject.confidence * 100)}%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
onClick={handleRetry}
|
||||
>
|
||||
<RefreshCw className="h-4 w-4 mr-1" />
|
||||
重新选择
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
className="flex-1 bg-green-600 hover:bg-green-700"
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
<Check className="h-4 w-4 mr-1" />
|
||||
添加到对话
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>,
|
||||
document.body
|
||||
);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user