feat: 画布图片按原始比例 1:1 显示
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

- 图片使用 object-contain 保持原始比例,不再裁剪
- 添加 getImageDimensions 函数获取图片原始尺寸
- 图片节点大小根据原始尺寸动态设置(最大 500px,最小 150px)
- 图片间距动态调整,避免重叠
This commit is contained in:
Your Name 2026-02-02 10:57:42 +08:00
parent 52867404f7
commit d7c0e66c3e
2 changed files with 57 additions and 14 deletions

View File

@ -34,6 +34,41 @@ interface CanvasChatPanelProps {
// 建议卡片数据 - 已移除
const SUGGESTIONS: { title: string; desc: string; image: string }[] = [];
// 获取图片的原始尺寸
function getImageDimensions(url: string): Promise<{ width: number; height: number }> {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => {
// 限制最大尺寸,避免节点过大
const maxSize = 500;
let width = img.naturalWidth;
let height = img.naturalHeight;
// 如果图片太大,按比例缩小
if (width > maxSize || height > maxSize) {
const scale = Math.min(maxSize / width, maxSize / height);
width = Math.round(width * scale);
height = Math.round(height * scale);
}
// 设置最小尺寸
const minSize = 150;
if (width < minSize && height < minSize) {
const scale = minSize / Math.min(width, height);
width = Math.round(width * scale);
height = Math.round(height * scale);
}
resolve({ width, height });
};
img.onerror = () => {
// 加载失败时使用默认尺寸
resolve({ width: 300, height: 300 });
};
img.src = url;
});
}
// 从消息中提取图片 URL(只提取原始图片,避免重复)
function extractImageUrls(text: string): string[] {
const urls: string[] = [];
@ -349,7 +384,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
}
// 只处理新消息
messages.forEach(message => {
messages.forEach(async (message) => {
if (message.role === 'assistant' && !processedMessageIds.current.has(message.id)) {
const content = message.parts?.map((p: any) => p.text || '').join('') || '';
const imageUrls = extractImageUrls(content);
@ -366,16 +401,22 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
// 只添加不存在的图片
const newUrls = imageUrls.filter(url => !existingUrls.has(url));
newUrls.forEach((url, index) => {
// 计算位置(避免重叠)
const existingNodes = nodes.length;
const x = 100 + ((existingNodes + index) % 4) * 320;
const y = 100 + Math.floor((existingNodes + index) / 4) * 320;
// 异步获取每张图片的尺寸并添加到画布
for (let index = 0; index < newUrls.length; index++) {
const url = newUrls[index];
// 获取图片原始尺寸
const dimensions = await getImageDimensions(url);
// 计算位置(避免重叠,根据图片宽度动态调整间距)
const existingNodes = nodes.length + index;
const spacing = 20; // 图片间距
const x = 100 + (existingNodes % 3) * (dimensions.width + spacing);
const y = 100 + Math.floor(existingNodes / 3) * (dimensions.height + spacing);
addNode({
type: 'image',
position: { x, y },
size: { width: 300, height: 300 },
size: { width: dimensions.width, height: dimensions.height },
data: {
src: url,
status: 'completed',
@ -383,7 +424,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
},
connections: [],
});
});
}
if (newUrls.length > 0) {
toast.success(`已将 ${newUrls.length} 张图片添加到画布`);
@ -605,15 +646,17 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF
}];
});
// 立即将图片添加到画布
// 获取图片原始尺寸后添加到画布
const dimensions = await getImageDimensions(imageUrl);
const existingNodes = nodes.length;
const x = 100 + (existingNodes % 4) * 320;
const y = 100 + Math.floor(existingNodes / 4) * 320;
const spacing = 20;
const x = 100 + (existingNodes % 3) * (dimensions.width + spacing);
const y = 100 + Math.floor(existingNodes / 3) * (dimensions.height + spacing);
addNode({
type: 'image',
position: { x, y },
size: { width: 300, height: 300 },
size: { width: dimensions.width, height: dimensions.height },
data: {
src: imageUrl,
status: 'completed',

View File

@ -384,12 +384,12 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{/* 图片内容 */}
{/* 图片内容 - 使用 object-contain 保持原始比例 */}
{data.src ? (
<img
src={data.src}
alt={data.prompt || "图片"}
className="w-full h-full object-cover"
className="w-full h-full object-contain bg-muted/30"
draggable={false}
/>
) : data.status === 'generating' ? (