Browse Source

上传修改

feature/composer
TianYun 4 weeks ago
parent
commit
e14af359eb
  1. 10
      src/components/AssetLibraryDrawer.tsx
  2. 24
      src/components/AssetLibraryUploadModal.tsx
  3. 9
      src/components/nodes/SmartImageNode.tsx
  4. 9
      src/components/nodes/SmartVideoNode.tsx
  5. 4
      src/i18n/index.tsx

10
src/components/AssetLibraryDrawer.tsx

@ -283,6 +283,7 @@ export function AssetLibraryDrawer({ open, onClose }: AssetLibraryDrawerProps) {
const [newFolderName, setNewFolderName] = useState(""); const [newFolderName, setNewFolderName] = useState("");
const [submittingFolder, setSubmittingFolder] = useState(false); const [submittingFolder, setSubmittingFolder] = useState(false);
const [uploadModalOpen, setUploadModalOpen] = useState(false); const [uploadModalOpen, setUploadModalOpen] = useState(false);
const [uploadingAsset, setUploadingAsset] = useState(false);
const [actionItemKey, setActionItemKey] = useState<string | null>(null); const [actionItemKey, setActionItemKey] = useState<string | null>(null);
const [renamingItemKey, setRenamingItemKey] = useState<string | null>(null); const [renamingItemKey, setRenamingItemKey] = useState<string | null>(null);
const [renameValue, setRenameValue] = useState(""); const [renameValue, setRenameValue] = useState("");
@ -571,10 +572,11 @@ export function AssetLibraryDrawer({ open, onClose }: AssetLibraryDrawerProps) {
}, [loadRootFolders, newFolderName, refreshFolderChildren, t]); }, [loadRootFolders, newFolderName, refreshFolderChildren, t]);
const handleOpenUploadModal = useCallback(() => { const handleOpenUploadModal = useCallback(() => {
if (uploadingAsset) return;
setAddPopoverOpen(false); setAddPopoverOpen(false);
setError(null); setError(null);
setUploadModalOpen(true); setUploadModalOpen(true);
}, []); }, [uploadingAsset]);
const handleUploadedAsset = useCallback(async (folderId: number) => { const handleUploadedAsset = useCallback(async (folderId: number) => {
await refreshFolderChildren(folderId); await refreshFolderChildren(folderId);
@ -730,10 +732,11 @@ export function AssetLibraryDrawer({ open, onClose }: AssetLibraryDrawerProps) {
<div className="nodrag nopan nowheel flex min-w-[176px] flex-col rounded-lg bg-[var(--surface-2)] p-1 text-sm text-[var(--text-secondary)] shadow-[var(--shadow-panel)]"> <div className="nodrag nopan nowheel flex min-w-[176px] flex-col rounded-lg bg-[var(--surface-2)] p-1 text-sm text-[var(--text-secondary)] shadow-[var(--shadow-panel)]">
<button <button
type="button" type="button"
className="flex h-9 items-center gap-2 rounded-md px-3 text-left transition-colors hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)]" disabled={uploadingAsset}
className="flex h-9 items-center gap-2 rounded-md px-3 text-left transition-colors hover:bg-[var(--surface-hover)] hover:text-[var(--text-primary)] disabled:cursor-wait disabled:opacity-60 disabled:hover:bg-transparent disabled:hover:text-inherit"
onClick={handleOpenUploadModal} onClick={handleOpenUploadModal}
> >
<UploadOutlined className="text-[14px]" /> {uploadingAsset ? <Spin size="small" /> : <UploadOutlined className="text-[14px]" />}
<span>{t("assetLibrary.upload")}</span> <span>{t("assetLibrary.upload")}</span>
</button> </button>
<button <button
@ -1192,6 +1195,7 @@ export function AssetLibraryDrawer({ open, onClose }: AssetLibraryDrawerProps) {
onClose={() => setUploadModalOpen(false)} onClose={() => setUploadModalOpen(false)}
onUploaded={handleUploadedAsset} onUploaded={handleUploadedAsset}
onError={setError} onError={setError}
onUploadingChange={setUploadingAsset}
/> />
<AssetLibraryFolderPickerModal <AssetLibraryFolderPickerModal
open={movingItem !== null} open={movingItem !== null}

24
src/components/AssetLibraryUploadModal.tsx

@ -5,7 +5,7 @@ import { message } from "antd";
import { useI18n } from "@/i18n"; import { useI18n } from "@/i18n";
import { authFetch } from "@/utils/authFetch"; import { authFetch } from "@/utils/authFetch";
import { uploadFileToGatewayMedia } from "@/utils/gatewayMediaUpload"; import { uploadFileToGatewayMedia, type GatewayMediaKind } from "@/utils/gatewayMediaUpload";
import { type UserFileItem, type UserFileMutationResponse } from "@/utils/userFileLibrary"; import { type UserFileItem, type UserFileMutationResponse } from "@/utils/userFileLibrary";
import { AssetLibraryFolderPickerModal } from "./AssetLibraryFolderPickerModal"; import { AssetLibraryFolderPickerModal } from "./AssetLibraryFolderPickerModal";
@ -19,6 +19,14 @@ interface AssetLibraryUploadModalProps {
onClose: () => void; onClose: () => void;
onUploaded: (folderId: number) => void | Promise<void>; onUploaded: (folderId: number) => void | Promise<void>;
onError?: (message: string) => void; onError?: (message: string) => void;
onUploadingChange?: (uploading: boolean) => void;
}
function getUploadMediaKind(file: File): GatewayMediaKind | null {
if (file.type.startsWith("image/")) return "image";
if (file.type.startsWith("video/")) return "video";
if (file.type.startsWith("audio/")) return "audio";
return null;
} }
export function AssetLibraryUploadModal({ export function AssetLibraryUploadModal({
@ -31,6 +39,7 @@ export function AssetLibraryUploadModal({
onClose, onClose,
onUploaded, onUploaded,
onError, onError,
onUploadingChange,
}: AssetLibraryUploadModalProps) { }: AssetLibraryUploadModalProps) {
const { t } = useI18n(); const { t } = useI18n();
const [selectedFolderId, setSelectedFolderId] = useState<number | null>(null); const [selectedFolderId, setSelectedFolderId] = useState<number | null>(null);
@ -45,6 +54,10 @@ export function AssetLibraryUploadModal({
setSelectedFolderId((current) => current ?? folders[0]?.id ?? null); setSelectedFolderId((current) => current ?? folders[0]?.id ?? null);
}, [folders, open]); }, [folders, open]);
useEffect(() => {
onUploadingChange?.(uploading);
}, [onUploadingChange, uploading]);
const handleCancel = useCallback(() => { const handleCancel = useCallback(() => {
if (uploading) return; if (uploading) return;
onClose(); onClose();
@ -61,14 +74,15 @@ export function AssetLibraryUploadModal({
event.target.value = ""; event.target.value = "";
const targetFolderId = uploadTargetFolderIdRef.current ?? selectedId; const targetFolderId = uploadTargetFolderIdRef.current ?? selectedId;
if (!file || targetFolderId === null) return; if (!file || targetFolderId === null) return;
if (!file.type.startsWith("image/")) { const mediaKind = getUploadMediaKind(file);
void message.error(t("assetLibrary.selectImageFile")); if (!mediaKind) {
void message.error(t("assetLibrary.selectMediaFile"));
return; return;
} }
setUploading(true); setUploading(true);
try { try {
const uploaded = await uploadFileToGatewayMedia(file, "image"); const uploaded = await uploadFileToGatewayMedia(file, mediaKind);
const response = await authFetch("/api/popi/user-file/upload", { const response = await authFetch("/api/popi/user-file/upload", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@ -100,7 +114,7 @@ export function AssetLibraryUploadModal({
<input <input
ref={inputRef} ref={inputRef}
type="file" type="file"
accept="image/*" accept="image/*,video/*,audio/*"
className="hidden" className="hidden"
onChange={handleFileChange} onChange={handleFileChange}
/> />

9
src/components/nodes/SmartImageNode.tsx

@ -1,7 +1,7 @@
"use client"; "use client";
import { useCallback, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { Node, NodeProps } from "@xyflow/react"; import { Node, NodeProps, useUpdateNodeInternals } from "@xyflow/react";
import { Spin } from "antd"; import { Spin } from "antd";
import { AssetPickerModal, PopiAssetItem } from "@/components/AssetPickerModal"; import { AssetPickerModal, PopiAssetItem } from "@/components/AssetPickerModal";
import { useI18n } from "@/i18n"; import { useI18n } from "@/i18n";
@ -308,8 +308,13 @@ function SmartImageNeutralView({
export function SmartImageNode({ id, data, selected }: NodeProps<SmartImageNodeType>) { export function SmartImageNode({ id, data, selected }: NodeProps<SmartImageNodeType>) {
const edges = useWorkflowStore((state) => state.edges); const edges = useWorkflowStore((state) => state.edges);
const providerEnvStatus = useWorkflowStore((state) => state.providerEnvStatus); const providerEnvStatus = useWorkflowStore((state) => state.providerEnvStatus);
const updateNodeInternals = useUpdateNodeInternals();
const mode = deriveSmartImageMode(id, data, edges); const mode = deriveSmartImageMode(id, data, edges);
useEffect(() => {
updateNodeInternals(id);
}, [id, mode, updateNodeInternals]);
if (mode === "generate") { if (mode === "generate") {
if (isSmartGenerateUploadEnabled(providerEnvStatus)) { if (isSmartGenerateUploadEnabled(providerEnvStatus)) {
return <SmartImageNeutralView id={id} data={data} selected={selected} addToGenerationHistory />; return <SmartImageNeutralView id={id} data={data} selected={selected} addToGenerationHistory />;

9
src/components/nodes/SmartVideoNode.tsx

@ -1,7 +1,7 @@
"use client"; "use client";
import { useCallback, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { Node, NodeProps } from "@xyflow/react"; import { Node, NodeProps, useUpdateNodeInternals } from "@xyflow/react";
import { Spin } from "antd"; import { Spin } from "antd";
import { AssetPickerModal, PopiAssetItem } from "@/components/AssetPickerModal"; import { AssetPickerModal, PopiAssetItem } from "@/components/AssetPickerModal";
import { useI18n } from "@/i18n"; import { useI18n } from "@/i18n";
@ -334,8 +334,13 @@ function SmartVideoNeutralView({
export function SmartVideoNode({ id, data, selected }: NodeProps<SmartVideoNodeType>) { export function SmartVideoNode({ id, data, selected }: NodeProps<SmartVideoNodeType>) {
const edges = useWorkflowStore((state) => state.edges); const edges = useWorkflowStore((state) => state.edges);
const providerEnvStatus = useWorkflowStore((state) => state.providerEnvStatus); const providerEnvStatus = useWorkflowStore((state) => state.providerEnvStatus);
const updateNodeInternals = useUpdateNodeInternals();
const mode = deriveSmartVideoMode(id, data, edges); const mode = deriveSmartVideoMode(id, data, edges);
useEffect(() => {
updateNodeInternals(id);
}, [id, mode, updateNodeInternals]);
if (mode === "generate") { if (mode === "generate") {
if (isSmartGenerateUploadEnabled(providerEnvStatus)) { if (isSmartGenerateUploadEnabled(providerEnvStatus)) {
return <SmartVideoNeutralView id={id} data={data} selected={selected} addToGenerationHistory />; return <SmartVideoNeutralView id={id} data={data} selected={selected} addToGenerationHistory />;

4
src/i18n/index.tsx

@ -74,6 +74,7 @@ const en = {
"assetLibrary.selectFolder": "Select folder", "assetLibrary.selectFolder": "Select folder",
"assetLibrary.noFolders": "No folders available", "assetLibrary.noFolders": "No folders available",
"assetLibrary.selectImageFile": "Please select an image file", "assetLibrary.selectImageFile": "Please select an image file",
"assetLibrary.selectMediaFile": "Please select an image, video, or audio file",
"assetLibrary.uploadFailed": "Upload failed", "assetLibrary.uploadFailed": "Upload failed",
"assetLibrary.uploadSuccess": "Uploaded", "assetLibrary.uploadSuccess": "Uploaded",
"assetLibrary.itemActions": "Item actions", "assetLibrary.itemActions": "Item actions",
@ -1207,6 +1208,7 @@ const zhCN: Dictionary = {
"assetLibrary.selectFolder": "选择文件夹", "assetLibrary.selectFolder": "选择文件夹",
"assetLibrary.noFolders": "暂无文件夹", "assetLibrary.noFolders": "暂无文件夹",
"assetLibrary.selectImageFile": "请选择图片文件", "assetLibrary.selectImageFile": "请选择图片文件",
"assetLibrary.selectMediaFile": "请选择图片、视频或音频文件",
"assetLibrary.uploadFailed": "上传失败", "assetLibrary.uploadFailed": "上传失败",
"assetLibrary.uploadSuccess": "上传成功", "assetLibrary.uploadSuccess": "上传成功",
"assetLibrary.itemActions": "素材操作", "assetLibrary.itemActions": "素材操作",
@ -2108,6 +2110,7 @@ const zhTW: Dictionary = {
"assetLibrary.selectFolder": "選擇資料夾", "assetLibrary.selectFolder": "選擇資料夾",
"assetLibrary.noFolders": "暫無資料夾", "assetLibrary.noFolders": "暫無資料夾",
"assetLibrary.selectImageFile": "請選擇圖片檔案", "assetLibrary.selectImageFile": "請選擇圖片檔案",
"assetLibrary.selectMediaFile": "請選擇圖片、影片或音訊檔案",
"assetLibrary.uploadFailed": "上傳失敗", "assetLibrary.uploadFailed": "上傳失敗",
"assetLibrary.uploadSuccess": "上傳成功", "assetLibrary.uploadSuccess": "上傳成功",
"assetLibrary.itemActions": "素材操作", "assetLibrary.itemActions": "素材操作",
@ -2796,6 +2799,7 @@ const ja: Dictionary = {
"assetLibrary.selectFolder": "フォルダを選択", "assetLibrary.selectFolder": "フォルダを選択",
"assetLibrary.noFolders": "フォルダがありません", "assetLibrary.noFolders": "フォルダがありません",
"assetLibrary.selectImageFile": "画像ファイルを選択してください", "assetLibrary.selectImageFile": "画像ファイルを選択してください",
"assetLibrary.selectMediaFile": "画像、動画、または音声ファイルを選択してください",
"assetLibrary.uploadFailed": "アップロードに失敗しました", "assetLibrary.uploadFailed": "アップロードに失敗しました",
"assetLibrary.uploadSuccess": "アップロードしました", "assetLibrary.uploadSuccess": "アップロードしました",
"assetLibrary.itemActions": "アイテム操作", "assetLibrary.itemActions": "アイテム操作",

Loading…
Cancel
Save