feat: 图片上传组件层级优化

This commit is contained in:
li
2025-08-04 16:07:55 +08:00
parent c75bc792fe
commit eb7d0edb9d
10 changed files with 158 additions and 107 deletions
@@ -1,7 +1,7 @@
function getImportPath(filename) {
return `./${filename}`;
}
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["copilot_web/workflowChat-DcTitn2I.js","copilot_web/vendor-markdown-CBmlBP6m.js","copilot_web/vendor-react-Dixhfmvb.js","copilot_web/message-components-DmxYkzGi.js","copilot_web/input.js","copilot_web/assets/input-CRBDhXDN.css","copilot_web/fonts.css"].map(path => {
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["copilot_web/workflowChat-Bp01CH2d.js","copilot_web/vendor-markdown-CBmlBP6m.js","copilot_web/vendor-react-Dixhfmvb.js","copilot_web/message-components-DmxYkzGi.js","copilot_web/input.js","copilot_web/assets/input-CWoNegTl.css","copilot_web/fonts.css"].map(path => {
const apiBase = window.comfyAPI?.api?.api?.api_base;
if (apiBase) {
// 有 API base 时,使用完整路径
@@ -11,4 +11,4 @@ function getImportPath(filename) {
return `./${path}`;
}
}))))=>i.map(i=>d[i]);
import{_ as a}from"./input.js";import{j as e}from"./vendor-markdown-CBmlBP6m.js";import{r as t,R as i}from"./vendor-react-Dixhfmvb.js";import{C as n}from"./message-components-DmxYkzGi.js";const s={EXPLAIN_NODE:"copilot:explain-node",TOOLBOX_USAGE:"copilot:toolbox-usage",TOOLBOX_PARAMETERS:"copilot:toolbox-parameters",TOOLBOX_DOWNSTREAMNODES:"copilot:toolbox-downstreamnodes"},d=i.lazy(()=>a(()=>import(getImportPath("workflowChat-DcTitn2I.js")),__vite__mapDeps([0,1,2,3,4,5,6])).then(o=>({default:o.default})));function c(){const[o,r]=t.useState(!1);return t.useEffect(()=>{const l=()=>{r(!0)};return window.addEventListener(s.EXPLAIN_NODE,l),()=>window.removeEventListener(s.EXPLAIN_NODE,l)},[]),e.jsx(n,{children:e.jsx("div",{className:"h-full w-full flex flex-col",children:e.jsx(t.Suspense,{fallback:e.jsx("div",{className:"h-full w-full flex items-center justify-center",children:"Loading..."}),children:e.jsx(d,{visible:!0,triggerUsage:o,onUsageTriggered:()=>r(!1)})})})})}const O=Object.freeze(Object.defineProperty({__proto__:null,default:c},Symbol.toStringTag,{value:"Module"}));export{O as A,s as C};
import{_ as a}from"./input.js";import{j as e}from"./vendor-markdown-CBmlBP6m.js";import{r as t,R as i}from"./vendor-react-Dixhfmvb.js";import{C as n}from"./message-components-DmxYkzGi.js";const s={EXPLAIN_NODE:"copilot:explain-node",TOOLBOX_USAGE:"copilot:toolbox-usage",TOOLBOX_PARAMETERS:"copilot:toolbox-parameters",TOOLBOX_DOWNSTREAMNODES:"copilot:toolbox-downstreamnodes"},d=i.lazy(()=>a(()=>import(getImportPath("workflowChat-Bp01CH2d.js")),__vite__mapDeps([0,1,2,3,4,5,6])).then(o=>({default:o.default})));function c(){const[o,r]=t.useState(!1);return t.useEffect(()=>{const l=()=>{r(!0)};return window.addEventListener(s.EXPLAIN_NODE,l),()=>window.removeEventListener(s.EXPLAIN_NODE,l)},[]),e.jsx(n,{children:e.jsx("div",{className:"h-full w-full flex flex-col",children:e.jsx(t.Suspense,{fallback:e.jsx("div",{className:"h-full w-full flex items-center justify-center",children:"Loading..."}),children:e.jsx(d,{visible:!0,triggerUsage:o,onUsageTriggered:()=>r(!1)})})})})}const O=Object.freeze(Object.defineProperty({__proto__:null,default:c},Symbol.toStringTag,{value:"Module"}));export{O as A,s as C};
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -130,7 +130,7 @@ export namespace WorkflowChatAPI {
export async function* streamInvokeServer(
sessionId: string,
prompt: string,
images: {url: string}[] = [],
images: {url: string, name: string}[] = [],
intent: string | null = null,
ext: any | null = null,
trace_id?: string,
+87 -73
View File
@@ -6,6 +6,7 @@ import { SendIcon, ImageIcon, PlusIcon, XIcon, StopIcon } from './Icons';
import React from 'react';
import { WorkflowChatAPI } from '../../apis/workflowChatApi';
import { generateUUID } from '../../utils/uuid';
import { Portal } from './Portal';
// Debug icon component
const DebugIcon = ({ className }: { className: string }) => (
@@ -119,6 +120,11 @@ export function ChatInput({
alert(`The following files couldn't be uploaded:\n${invalidFiles.join('\n')}`);
}
if (validFiles.length > 3) {
alert('You can only upload up to 3 images');
return;
}
if (validFiles.length > 0) {
const dataTransfer = new DataTransfer();
validFiles.forEach(file => dataTransfer.items.add(file));
@@ -269,84 +275,92 @@ export function ChatInput({
{/* 上传图片模态框 */}
{showUploadModal && (
<div className="fixed inset-0 bg-[rgba(0,0,0,0.5)] flex items-center justify-center">
<div className="bg-white rounded-lg p-6 w-96 relative">
<button
onClick={() => setShowUploadModal(false)}
className="absolute top-2 right-2 bg-white border-none text-gray-500 hover:text-gray-700"
>
<XIcon className="w-5 h-5" />
</button>
<h3 className="text-lg text-gray-800 font-medium mb-4">Upload Images</h3>
<div className="border-2 border-dashed border-gray-300 rounded-lg p-8
flex flex-col items-center justify-center gap-4
hover:border-blue-500 transition-colors cursor-pointer"
onClick={() => fileInputRef.current?.click()}
>
<PlusIcon className="w-8 h-8 text-gray-400" />
<div className="text-center">
<p className="text-sm text-gray-500 mb-2">
Click to upload images or drag and drop
</p>
<p className="text-xs text-gray-400">
Supported formats: JPG, PNG, GIF, WebP
</p>
<p className="text-xs text-gray-400">
Max file size: 5MB
</p>
</div>
</div>
<input
ref={fileInputRef}
type="file"
multiple
accept={SUPPORTED_FORMATS.join(',')}
onChange={handleFileChange}
className="hidden"
/>
{/* 预览区域 */}
{uploadedImages.length > 0 && (
<div className="mt-4 grid grid-cols-3 gap-2">
{uploadedImages.map(image => (
<div key={image.id} className="relative group">
<img
src={image.preview}
alt="preview"
className="w-full h-20 object-cover rounded"
/>
{
!!image?.url && image?.url !== '' ? <button
onClick={() => onRemoveImage(image.id)}
className="absolute -top-1 -right-1 bg-white border-none text-gray-500 rounded-full p-0.5
opacity-0 group-hover:!opacity-100 transition-opacity"
>
<XIcon className="w-3 h-3" />
</button> : <span className="absolute top-0 left-0 w-full h-full bg-black/50 text-gray-500
flex items-center justify-center text-lg">
Uploading...
</span>
}
</div>
))}
</div>
)}
<div className="mt-4 flex justify-end gap-2">
<button
<Portal>
<div
id="comfyui-copilot-modal"
className="fixed inset-0 bg-[rgba(0,0,0,0.5)] flex items-center justify-center"
style={{
backgroundColor: 'rgba(0,0,0,0.5)'
}}
>
<div className="bg-white rounded-lg p-6 w-96 relative">
<button
onClick={() => setShowUploadModal(false)}
className="px-4 py-2 text-sm font-medium text-gray-700
bg-white border border-gray-300 rounded-md
hover:!bg-gray-50"
className="absolute top-2 right-2 bg-white border-none text-gray-500 hover:!text-gray-700"
>
Close
<XIcon className="w-5 h-5" />
</button>
<h3 className="text-lg text-gray-800 font-medium mb-4">Upload Images</h3>
<div className="border-2 border-dashed border-gray-300 rounded-lg p-8
flex flex-col items-center justify-center gap-4
hover:!border-blue-500 transition-colors cursor-pointer"
onClick={() => fileInputRef.current?.click()}
>
<PlusIcon className="w-8 h-8 text-gray-400" />
<div className="text-center">
<p className="text-sm text-gray-500 mb-2">
Click to upload images or drag and drop
</p>
<p className="text-xs text-gray-400">
Supported formats: JPG, PNG, GIF, WebP
</p>
<p className="text-xs text-gray-400">
Max file size: 5MB, Max 3 images
</p>
</div>
</div>
<input
ref={fileInputRef}
type="file"
multiple
accept={SUPPORTED_FORMATS.join(',')}
onChange={handleFileChange}
className="hidden"
/>
{/* 预览区域 */}
{uploadedImages.length > 0 && (
<div className="mt-4 grid grid-cols-3 gap-2">
{uploadedImages.map(image => (
<div key={image.id} className="relative group">
<img
src={image.preview}
alt="preview"
className="w-full h-20 object-cover rounded"
/>
{
!!image?.url && image?.url !== '' ? <button
onClick={() => onRemoveImage(image.id)}
className="absolute -top-1 -right-1 bg-white border-none text-gray-500 rounded-full p-0.5
opacity-0 group-hover:!opacity-100 transition-opacity"
>
<XIcon className="w-3 h-3" />
</button> : <span className="absolute top-0 left-0 w-full h-full bg-black/50 text-gray-500
flex items-center justify-center text-lg">
Uploading...
</span>
}
</div>
))}
</div>
)}
<div className="mt-4 flex justify-end gap-2">
<button
onClick={() => setShowUploadModal(false)}
className="px-4 py-2 text-sm font-medium text-gray-700
bg-white border border-gray-300 rounded-md
hover:!bg-gray-50"
>
Close
</button>
</div>
</div>
</div>
</div>
</Portal>
)}
</div>
);
+32
View File
@@ -0,0 +1,32 @@
// Copyright (C) 2025 AIDC-AI
// Licensed under the MIT License.
import React, { ReactNode, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
interface PortalProps {
children: ReactNode;
container?: HTMLElement;
}
export function Portal({ children, container = document.body }: PortalProps) {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
return () => setMounted(false);
}, []);
if (!mounted) {
return null;
}
// 创建一个包装器来应用样式
const portalContent = (
<div className="relative z-[1000]">
{children}
</div>
);
return createPortal(portalContent, container);
}
+8 -4
View File
@@ -1,4 +1,5 @@
#comfyui-copilot-plugin {
#comfyui-copilot-plugin,
#comfyui-copilot-modal {
*, ::before, ::after {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
@@ -267,6 +268,9 @@
.z-50 {
z-index: 50;
}
.z-\[1000\] {
z-index: 1000;
}
.z-\[9999\] {
z-index: 9999;
}
@@ -1538,9 +1542,9 @@
border-color: rgb(113 163 242 / var(--tw-border-opacity, 1)) !important;
}
.hover\:border-blue-500:hover {
--tw-border-opacity: 1;
border-color: rgb(59 130 246 / var(--tw-border-opacity, 1));
.hover\:\!border-blue-500:hover {
--tw-border-opacity: 1 !important;
border-color: rgb(59 130 246 / var(--tw-border-opacity, 1)) !important;
}
.hover\:\!bg-gray-100:hover {
+2 -1
View File
@@ -1,4 +1,5 @@
#comfyui-copilot-plugin {
#comfyui-copilot-plugin,
#comfyui-copilot-modal {
@tailwind base;
@tailwind components;
@tailwind utilities;