Toggle navigation
Toggle navigation
This project
Loading...
Sign in
卢阳
/
front_backend_zImage
Go to a project
Toggle navigation
Projects
Groups
Snippets
Help
Toggle navigation pinning
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Builds
Commits
Authored by
ly0303521
2026-01-12 18:07:33 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
dd5dcc55687fe520d0f8715f4260644a28a68f6f
dd5dcc55
1 parent
cebb237b
添加视频的删除功能
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
80 additions
and
5 deletions
.gitignore
backend/main.py
z-image-generator/App.tsx
z-image-generator/components/DetailModal.tsx
z-image-generator/services/galleryService.ts
.gitignore
View file @
dd5dcc5
...
...
@@ -24,4 +24,5 @@ dist-ssr
*.sw?
*.pyc
backend/whitelist.txt
backend/gallery_data.json
backend/gallery_images.json
backend/gallery_videos.json
...
...
backend/main.py
View file @
dd5dcc5
...
...
@@ -148,6 +148,18 @@ class JsonStore:
self
.
_write
(
data
)
return
target_item
def
delete_item
(
self
,
item_id
:
str
)
->
bool
:
with
self
.
lock
:
data
=
self
.
_read
()
items
=
data
.
get
(
self
.
item_key
,
[])
initial_len
=
len
(
items
)
items
=
[
i
for
i
in
items
if
i
.
get
(
"id"
)
!=
item_id
]
if
len
(
items
)
<
initial_len
:
data
[
self
.
item_key
]
=
items
self
.
_write
(
data
)
return
True
return
False
image_store
=
JsonStore
(
GALLERY_IMAGES_PATH
,
item_key
=
"images"
,
max_items
=
GALLERY_MAX_ITEMS
)
video_store
=
JsonStore
(
GALLERY_VIDEOS_PATH
,
item_key
=
"videos"
,
max_items
=
GALLERY_MAX_ITEMS
)
whitelist_store
=
WhitelistStore
(
WHITELIST_PATH
)
...
...
@@ -201,6 +213,23 @@ async def add_video(video: GalleryVideo):
except
Exception
as
exc
:
raise
HTTPException
(
status_code
=
500
,
detail
=
f
"Failed to store video metadata: {exc}"
)
@app.delete
(
"/gallery/videos/{item_id}"
)
async
def
delete_video
(
item_id
:
str
,
user_id
:
str
=
Query
(
...
,
alias
=
"userId"
)):
items
=
video_store
.
list_items
()
target_item
=
next
((
i
for
i
in
items
if
i
.
get
(
"id"
)
==
item_id
),
None
)
if
not
target_item
:
raise
HTTPException
(
status_code
=
404
,
detail
=
"Video not found"
)
ADMIN_ID
=
"86427531"
if
user_id
!=
target_item
.
get
(
"authorId"
)
and
user_id
!=
ADMIN_ID
:
raise
HTTPException
(
status_code
=
403
,
detail
=
"Not authorized to delete this video"
)
if
video_store
.
delete_item
(
item_id
):
return
{
"status"
:
"ok"
,
"id"
:
item_id
}
raise
HTTPException
(
status_code
=
500
,
detail
=
"Failed to delete video"
)
@app.post
(
"/generate"
,
response_model
=
ImageGenerationResponse
)
async
def
generate_image
(
payload
:
ImageGenerationPayload
):
request_params_data
=
payload
.
model_dump
();
body
=
{
k
:
v
for
k
,
v
in
request_params_data
.
items
()
if
v
is
not
None
and
k
!=
"author_id"
}
...
...
z-image-generator/App.tsx
View file @
dd5dcc5
...
...
@@ -3,7 +3,7 @@ import { ImageItem, ImageGenerationParams, UserProfile, VideoStatus } from './ty
import { SHOWCASE_IMAGES, ADMIN_ID, VIDEO_OSS_BASE_URL } from './constants';
import { generateImage } from './services/imageService';
import { submitVideoJob, pollVideoStatus } from './services/videoService';
import { fetchGallery, fetchVideoGallery, saveVideo, toggleLike } from './services/galleryService';
import { fetchGallery, fetchVideoGallery, saveVideo, toggleLike
, deleteVideo
} from './services/galleryService';
import MasonryGrid from './components/MasonryGrid';
import InputBar from './components/InputBar';
import HistoryBar from './components/HistoryBar';
...
...
@@ -192,6 +192,21 @@ const App: React.FC = () => {
}
};
const handleDeleteVideo = async (video: ImageItem) => {
if (!currentUser) { setIsAuthModalOpen(true); return; }
if (!confirm("确定要删除这个视频吗?")) return;
try {
await deleteVideo(video.id, currentUser.employeeId);
setVideos(prev => prev.filter(v => v.id !== video.id));
setSelectedImage(null); // Close modal
} catch (e) {
console.error("Delete failed", e);
alert("删除失败");
}
};
const handleGenerateSimilar = (params: ImageGenerationParams) => {
setIncomingParams(params);
const banner = document.getElementById('similar-feedback');
...
...
@@ -269,7 +284,7 @@ const App: React.FC = () => {
<HistoryBar images={galleryMode === GalleryMode.Image ? userHistory : userVideoHistory} onSelect={setSelectedImage} />
<InputBar onGenerate={handleGenerate} isGenerating={isGenerating || isGeneratingVideo} incomingParams={incomingParams} isVideoMode={galleryMode === GalleryMode.Video} videoStatus={videoStatus} />
{selectedImage && <DetailModal image={selectedImage} onClose={() => setSelectedImage(null)} onEdit={isAdmin ? handleOpenEditModal : undefined} onGenerateSimilar={handleGenerateSimilar}/>}
{selectedImage && <DetailModal image={selectedImage} onClose={() => setSelectedImage(null)} onEdit={isAdmin ? handleOpenEditModal : undefined} onGenerateSimilar={handleGenerateSimilar}
onDelete={handleDeleteVideo} currentUser={currentUser}
/>}
<AdminModal isOpen={isAdminModalOpen} onClose={() => setIsAdminModalOpen(false)} onSave={handleSaveImage} onDelete={handleDeleteImage} initialData={editingImage} />
<WhitelistModal isOpen={isWhitelistModalOpen} onClose={() => setIsWhitelistModalOpen(false)} />
</div>
...
...
z-image-generator/components/DetailModal.tsx
View file @
dd5dcc5
import React from 'react';
import { ImageItem, ImageGenerationParams } from '../types';
import { X, Copy, Download, Edit3, Heart, Zap } from 'lucide-react';
import { X, Copy, Download, Edit3, Heart, Zap
, Trash2
} from 'lucide-react';
interface DetailModalProps {
image: ImageItem | null;
onClose: () => void;
onEdit?: (image: ImageItem) => void;
onGenerateSimilar?: (params: ImageGenerationParams) => void;
onDelete?: (image: ImageItem) => void;
currentUser?: { employeeId: string; hasAccess: boolean } | null;
}
const DetailModal: React.FC<DetailModalProps> = ({ image, onClose, onEdit, onGenerateSimilar }) => {
import { ADMIN_ID } from '../constants';
const DetailModal: React.FC<DetailModalProps> = ({ image, onClose, onEdit, onGenerateSimilar, onDelete, currentUser }) => {
if (!image) return null;
const isVideo = image.id.startsWith('vid-');
const isAdmin = currentUser?.employeeId === ADMIN_ID;
const isAuthor = currentUser?.employeeId === image.authorId;
const canDelete = onDelete && (isAdmin || isAuthor);
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
...
...
@@ -52,6 +59,15 @@ const DetailModal: React.FC<DetailModalProps> = ({ image, onClose, onEdit, onGen
<div className="flex justify-between items-start mb-6">
<h2 className="text-2xl font-bold text-gray-800 dark:text-white">参数详情</h2>
<div className="flex gap-2">
{canDelete && (
<button
onClick={() => onDelete && onDelete(image)}
className="p-2 bg-red-50 hover:bg-red-100 text-red-600 rounded-full transition-colors"
title="删除"
>
<Trash2 size={18} />
</button>
)}
{onEdit && (
<button
onClick={() => onEdit(image)}
...
...
z-image-generator/services/galleryService.ts
View file @
dd5dcc5
...
...
@@ -78,4 +78,18 @@ export const toggleLike = async (itemId: string, userId: string): Promise<ImageI
}
return
await
response
.
json
();
};
export
const
deleteVideo
=
async
(
itemId
:
string
,
userId
:
string
)
:
Promise
<
void
>
=>
{
if
(
API_BASE_URL
===
Z_IMAGE_DIRECT_BASE_URL
)
{
throw
new
Error
(
"Cannot delete videos in direct mode"
);
}
const
response
=
await
fetch
(
`
$
{
API_BASE_URL
}
/gallery/
videos
/
$
{
itemId
}?
userId
=
$
{
userId
}
`
,
{
method
:
'DELETE'
,
});
if
(
!
response
.
ok
)
{
const
errorText
=
await
response
.
text
();
throw
new
Error
(
`
Delete
failed
(
$
{
response
.
status
}):
$
{
errorText
}
`
);
}
};
\ No newline at end of file
...
...
Please
register
or
login
to post a comment