feat: 图片上传组件层级优化
This commit is contained in:
@@ -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};
|
||||
-1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
File diff suppressed because one or more lines are too long
+23
-23
File diff suppressed because one or more lines are too long
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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
@@ -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 {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#comfyui-copilot-plugin {
|
||||
#comfyui-copilot-plugin,
|
||||
#comfyui-copilot-modal {
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
Reference in New Issue
Block a user