feat(core/frontend): pipeline stage editor (burn-in controls) + double-submit guard & button greying
Pipeline (脚本→资产→故事板→视频→拼接): - Stage1 render real script shots + wire 确认脚本→adopt (advance stage) - Stage2 add person/scene AI-生成 buttons + clickable category tabs - Stage4 auto-poll videos to completion + per-segment upload + real frame thumbnails + download - Stage5 real timeline editor: clips undo/redo/split/copy/delete/drag-reorder/zoom, subtitle style + per-clip text editor, transition select (xfade preview), BGM upload + volume, save draft, export-with-save → shows/download final MP4 - embedded asset URLs everywhere (beat assets pagination) UX: re-entry guard in action() (no double-submit anywhere) + greyed :disabled styles for btn-aigen/chat-mode/pill-cta/tl-action so generate buttons visibly disable while generating. Also includes prior uncommitted frontend work: settings preferences/sessions/avatar, asset delete, account/team/products pages, fonts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,9 @@ import type {
|
||||
Asset,
|
||||
AuthPayload,
|
||||
BillingSummary,
|
||||
BillingTrend,
|
||||
Ledger,
|
||||
LoginSession,
|
||||
ModelConfig,
|
||||
Notification,
|
||||
NotificationList,
|
||||
@@ -14,7 +16,8 @@ import type {
|
||||
ScriptVersion,
|
||||
Team,
|
||||
TeamMember,
|
||||
User
|
||||
User,
|
||||
UserPreference
|
||||
} from "./types";
|
||||
|
||||
const API_BASE = import.meta.env.VITE_API_BASE_URL || "";
|
||||
@@ -75,6 +78,27 @@ export const api = {
|
||||
uploadAvatar(formData: FormData) {
|
||||
return request<User>("/api/auth/me/avatar/", { method: "POST", body: formData });
|
||||
},
|
||||
resetAvatar() {
|
||||
return request<User>("/api/auth/me/avatar/", { method: "DELETE" });
|
||||
},
|
||||
deleteAsset(id: string) {
|
||||
return request<void>(`/api/assets/${id}/`, { method: "DELETE" });
|
||||
},
|
||||
preferences() {
|
||||
return request<UserPreference>("/api/auth/me/preferences/");
|
||||
},
|
||||
updatePreferences(payload: Partial<UserPreference>) {
|
||||
return request<UserPreference>("/api/auth/me/preferences/", { method: "PUT", body: JSON.stringify(payload) });
|
||||
},
|
||||
loginSessions() {
|
||||
return request<LoginSession[]>("/api/auth/me/sessions/");
|
||||
},
|
||||
revokeSession(id: string) {
|
||||
return request<{ revoked: number }>(`/api/auth/me/sessions/${id}/revoke/`, { method: "POST" });
|
||||
},
|
||||
revokeOtherSessions() {
|
||||
return request<{ token: string }>("/api/auth/me/sessions/revoke-others/", { method: "POST" });
|
||||
},
|
||||
logout() {
|
||||
return request<void>("/api/auth/logout/", { method: "POST" });
|
||||
},
|
||||
@@ -123,6 +147,12 @@ export const api = {
|
||||
updateProduct(id: string, payload: Partial<Product>) {
|
||||
return request<Product>(`/api/products/${id}/`, { method: "PATCH", body: JSON.stringify(payload) });
|
||||
},
|
||||
uploadProductImage(productId: string, formData: FormData) {
|
||||
return request<Product>(`/api/products/${productId}/images/`, { method: "POST", body: formData });
|
||||
},
|
||||
deleteProductImage(productId: string, imageId: string) {
|
||||
return request<Product>(`/api/products/${productId}/images/${imageId}/`, { method: "DELETE" });
|
||||
},
|
||||
deleteProduct(id: string) {
|
||||
return request<void>(`/api/products/${id}/`, { method: "DELETE" });
|
||||
},
|
||||
@@ -132,7 +162,7 @@ export const api = {
|
||||
project(id: string) {
|
||||
return request<Project>(`/api/projects/${id}/`);
|
||||
},
|
||||
createProject(payload: { name: string; product: string }) {
|
||||
createProject(payload: { name: string; product: string; metadata?: Record<string, unknown> }) {
|
||||
return request<Project>("/api/projects/", { method: "POST", body: JSON.stringify(payload) });
|
||||
},
|
||||
deleteProject(id: string) {
|
||||
@@ -159,6 +189,12 @@ export const api = {
|
||||
generateStoryboard(projectId: string, payload: { prompt: string }) {
|
||||
return request(`/api/projects/${projectId}/generate-storyboard/`, { method: "POST", body: JSON.stringify(payload) });
|
||||
},
|
||||
pollStoryboard(projectId: string) {
|
||||
return request<{ status: "generating" | "succeeded" | "failed"; done: number; total: number; version_id: string; error?: string }>(
|
||||
`/api/projects/${projectId}/poll-storyboard/`,
|
||||
{ method: "POST" }
|
||||
);
|
||||
},
|
||||
skipStoryboard(projectId: string) {
|
||||
return request<Project>(`/api/projects/${projectId}/skip-storyboard/`, { method: "POST" });
|
||||
},
|
||||
@@ -174,6 +210,24 @@ export const api = {
|
||||
submitExport(projectId: string) {
|
||||
return request(`/api/projects/${projectId}/submit-export/`, { method: "POST" });
|
||||
},
|
||||
pollExport(projectId: string) {
|
||||
return request<import("./types").ExportPoll>(`/api/projects/${projectId}/poll-export/`, { method: "POST" });
|
||||
},
|
||||
uploadVideoSegment(projectId: string, segmentId: string, file: File) {
|
||||
const form = new FormData();
|
||||
form.append("video_segment_id", segmentId);
|
||||
form.append("file", file);
|
||||
return request<Project>(`/api/projects/${projectId}/upload-video-segment/`, { method: "POST", body: form });
|
||||
},
|
||||
uploadBgm(projectId: string, file: File, volume?: number) {
|
||||
const form = new FormData();
|
||||
form.append("file", file);
|
||||
if (volume != null) form.append("volume", String(volume));
|
||||
return request<Project>(`/api/projects/${projectId}/upload-bgm/`, { method: "POST", body: form });
|
||||
},
|
||||
saveTimeline(projectId: string, payload: import("./types").TimelineSavePayload) {
|
||||
return request<Project>(`/api/projects/${projectId}/save-timeline/`, { method: "POST", body: JSON.stringify(payload) });
|
||||
},
|
||||
assets() {
|
||||
return request<Paginated<Asset>>("/api/assets/");
|
||||
},
|
||||
@@ -186,6 +240,9 @@ export const api = {
|
||||
ledgers() {
|
||||
return request<Ledger[]>("/api/billing/ledgers/");
|
||||
},
|
||||
billingTrend(range?: "day" | "week" | "month") {
|
||||
return request<BillingTrend>(`/api/billing/trend/${range ? `?range=${range}` : ""}`);
|
||||
},
|
||||
modelConfigs() {
|
||||
return request<Paginated<ModelConfig>>("/api/ai/models/");
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user