mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-07 02:21:23 +00:00
Merge branch 'feature/mvp' into bflat-fy-dynamic-storyteller-fakeyou
This commit is contained in:
89 files changed
+1565
-881
No files matched your search
Regular → Executable
File mode changed.
@@ -496,8 +496,8 @@ class ApiConfig {
|
||||
return `${this.getApiOrigin()}/v1/mocap/mocapnet/create`;
|
||||
}
|
||||
|
||||
// =============== Storyteller Filter ===============
|
||||
enqueueStorytellerFilter(): string {
|
||||
// =============== Video Workflow ===============
|
||||
enqueueVideoWorkflow(): string {
|
||||
return `${this.getApiOrigin()}/v1/workflow/comfy/create`;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ export interface UserInfo {
|
||||
display_name: string,
|
||||
email_gravatar_hash: string,
|
||||
|
||||
// Rollout feature flags
|
||||
can_access_studio: boolean,
|
||||
|
||||
// Usage
|
||||
can_use_tts: boolean,
|
||||
can_use_w2l: boolean,
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
export type {
|
||||
EnqueueStorytellerFilterRequest, EnqueueStorytellerFilterResponse,
|
||||
} from './EnqueueStorytellerFilter';
|
||||
|
||||
export {
|
||||
EnqueueStorytellerFilter
|
||||
} from './EnqueueStorytellerFilter';
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
import { ApiConfig } from "@storyteller/components";
|
||||
|
||||
export interface EnqueueStorytellerFilterRequest {
|
||||
export interface EnqueueVideoWorkflowRequest {
|
||||
"uuid_idempotency_token": string,
|
||||
|
||||
"maybe_sd_model": string,
|
||||
@@ -29,16 +29,16 @@ export interface EnqueueStorytellerFilterRequest {
|
||||
}
|
||||
}
|
||||
|
||||
export interface EnqueueStorytellerFilterResponse {
|
||||
export interface EnqueueVideoWorkflowResponse {
|
||||
success: boolean,
|
||||
inference_job_token?: string,
|
||||
}
|
||||
|
||||
export async function EnqueueStorytellerFilter(request: EnqueueStorytellerFilterRequest) : Promise<EnqueueStorytellerFilterResponse>
|
||||
export async function EnqueueVideoWorkflow(request: EnqueueVideoWorkflowRequest) : Promise<EnqueueVideoWorkflowResponse>
|
||||
{
|
||||
// const endpoint = "/v1/workflow/comfy/create";
|
||||
|
||||
const endpoint = new ApiConfig().enqueueStorytellerFilter();
|
||||
const endpoint = new ApiConfig().enqueueVideoWorkflow();
|
||||
|
||||
return await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
@@ -0,0 +1,7 @@
|
||||
export type {
|
||||
EnqueueVideoWorkflowRequest, EnqueueVideoWorkflowResponse,
|
||||
} from './EnqueueVideoWorkflow';
|
||||
|
||||
export {
|
||||
EnqueueVideoWorkflow
|
||||
} from './EnqueueVideoWorkflow';
|
||||
@@ -12,7 +12,7 @@ export enum FrontendInferenceJobType {
|
||||
VideoMotionCapture,
|
||||
ConvertFbxtoGltf,
|
||||
EngineComposition,
|
||||
StorytellerFilter,
|
||||
VideoWorkflow,
|
||||
}
|
||||
|
||||
// NB: Many of these fields are optional despite the response payload containing them
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { GetSessionInfo, GetSessionInfoIsOk, SessionInfoSuccessResponse } from '../api/session/GetSessionInfo'
|
||||
import { StudioRolloutHostnameAllowed } from '../utils/StudioRolloutHostnameAllowed';
|
||||
|
||||
// A lot of the APIs return null or leave values absent.
|
||||
// I need to pick a single strategy and stick with it.
|
||||
@@ -44,6 +45,14 @@ export class SessionWrapper {
|
||||
return this.sessionStateResponse?.logged_in || false;
|
||||
}
|
||||
|
||||
// Feature flag rollout for Storyteller Studio
|
||||
// This depends on the domain name and the backend.
|
||||
public canAccessStudio() : boolean {
|
||||
const hostnameAllowed = StudioRolloutHostnameAllowed();
|
||||
const userAllowed = this.sessionStateResponse?.user?.can_access_studio || false;
|
||||
return hostnameAllowed && userAllowed;
|
||||
}
|
||||
|
||||
// Username is all lowercase
|
||||
public getUsername() : string | undefined {
|
||||
return this.sessionStateResponse?.user?.username;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
// Check if hostname is allowed to display studio features.
|
||||
// This should allow more frequently landing against production.
|
||||
export function StudioRolloutHostnameAllowed() : boolean {
|
||||
const allowed = StudioRolloutHostnameCheck();
|
||||
return allowed;
|
||||
}
|
||||
|
||||
function StudioRolloutHostnameCheck() : boolean {
|
||||
// Do not allow "production" or "staging" domains to use studio yet.
|
||||
// Other domains (local development and feature branches) are allowed.
|
||||
switch (window.location.hostname) {
|
||||
case 'fakeyou.com':
|
||||
case 'storyteller.ai':
|
||||
return false;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
"videoFaceAnimator": "ফেস অ্যানিমেটর",
|
||||
"videoMotionCapture": "ভিডিও মোশন ক্যাপচার",
|
||||
"videoTitle": "ভিডিও জেনারেশন",
|
||||
"videoW2l": "ভিডিও লিপ সিঙ্ক"
|
||||
"videoW2l": "ভিডিও লিপ সিঙ্ক",
|
||||
"videoWorkflow": "ভিডিও ওয়ার্কফ্লো"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"button": {
|
||||
"proceed": "ওয়ার্কফ্লো কন্ট্রোলে এগিয়ে যান",
|
||||
"process": "কর্মপ্রবাহের প্রক্রিয়া",
|
||||
"select": "নির্বাচন করুন",
|
||||
"upload": "ভিডিও আপলোড কর"
|
||||
},
|
||||
"headings": {
|
||||
"title": "ভিডিও ওয়ার্কফ্লো"
|
||||
},
|
||||
"tabTitle": {
|
||||
"library": "মিডিয়া থেকে বেছে নিন",
|
||||
"original": "আসল",
|
||||
"preview": "পূর্বরূপ",
|
||||
"upload": "আপলোড করুন"
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
"videoFaceAnimator": "Gesichtsanimator",
|
||||
"videoMotionCapture": "Video-Bewegungserfassung",
|
||||
"videoTitle": "Videogenerierung",
|
||||
"videoW2l": "Video-Lippensynchronisation"
|
||||
"videoW2l": "Video-Lippensynchronisation",
|
||||
"videoWorkflow": "Video-Workflow"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"button": {
|
||||
"proceed": "Fahren Sie mit den Workflow-Kontrollen fort",
|
||||
"process": "Vom Prozess zum Workflow",
|
||||
"select": "Wählen",
|
||||
"upload": "Video hochladen"
|
||||
},
|
||||
"headings": {
|
||||
"title": "Video-Workflow"
|
||||
},
|
||||
"tabTitle": {
|
||||
"library": "Wählen Sie aus Medien",
|
||||
"original": "Original",
|
||||
"preview": "Vorschau",
|
||||
"upload": "Hochladen"
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
"videoFaceAnimator": "Face Animator",
|
||||
"videoMotionCapture": "Video Motion Capture",
|
||||
"videoTitle": "Video Generation",
|
||||
"videoW2l": "Video Lip Sync"
|
||||
"videoW2l": "Video Lip Sync",
|
||||
"videoWorkflow": "Video Workflow"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"button": {
|
||||
"proceed": "Proceed to Workflow Controls",
|
||||
"process": "Process to Workflow",
|
||||
"select": "Select",
|
||||
"upload": "Upload Video"
|
||||
},
|
||||
"headings": {
|
||||
"title": "Video Workflow"
|
||||
},
|
||||
"tabTitle": {
|
||||
"library": "Choose from media",
|
||||
"original": "Original",
|
||||
"preview": "Preview",
|
||||
"upload": "Upload"
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
"videoFaceAnimator": "Animador de rostros",
|
||||
"videoMotionCapture": "Captura de movimiento de vídeo",
|
||||
"videoTitle": "Generación de vídeo",
|
||||
"videoW2l": "Sincronización de labios de vídeo"
|
||||
"videoW2l": "Sincronización de labios de vídeo",
|
||||
"videoWorkflow": "Flujo de trabajo de vídeo"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"button": {
|
||||
"proceed": "Continúe con los controles del flujo de trabajo",
|
||||
"process": "Proceso al flujo de trabajo",
|
||||
"select": "Seleccionar",
|
||||
"upload": "Subir vídeo"
|
||||
},
|
||||
"headings": {
|
||||
"title": "Flujo de trabajo de vídeo"
|
||||
},
|
||||
"tabTitle": {
|
||||
"library": "Elige entre los medios",
|
||||
"original": "Original",
|
||||
"preview": "Avance",
|
||||
"upload": "Subir"
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
"videoFaceAnimator": "Animateur de visage",
|
||||
"videoMotionCapture": "Capture de mouvement vidéo",
|
||||
"videoTitle": "Génération vidéo",
|
||||
"videoW2l": "Synchronisation labiale vidéo"
|
||||
"videoW2l": "Synchronisation labiale vidéo",
|
||||
"videoWorkflow": "Flux de travail vidéo"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"button": {
|
||||
"proceed": "Passer aux contrôles de flux de travail",
|
||||
"process": "Du processus au flux de travail",
|
||||
"select": "Sélectionner",
|
||||
"upload": "Télécharger une video"
|
||||
},
|
||||
"headings": {
|
||||
"title": "Flux de travail vidéo"
|
||||
},
|
||||
"tabTitle": {
|
||||
"library": "Choisissez parmi les médias",
|
||||
"original": "Original",
|
||||
"preview": "Aperçu",
|
||||
"upload": "Télécharger"
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
"videoFaceAnimator": "फेस एनिमेटर",
|
||||
"videoMotionCapture": "वीडियो मोशन कैप्चर",
|
||||
"videoTitle": "वीडियो जनरेशन",
|
||||
"videoW2l": "वीडियो लिप सिंक"
|
||||
"videoW2l": "वीडियो लिप सिंक",
|
||||
"videoWorkflow": "वीडियो वर्कफ़्लो"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"button": {
|
||||
"proceed": "वर्कफ़्लो नियंत्रण के लिए आगे बढ़ें",
|
||||
"process": "वर्कफ़्लो की प्रक्रिया",
|
||||
"select": "चुनना",
|
||||
"upload": "विडियो को अॅॅपलोड करें"
|
||||
},
|
||||
"headings": {
|
||||
"title": "वीडियो वर्कफ़्लो"
|
||||
},
|
||||
"tabTitle": {
|
||||
"library": "मीडिया से चुनें",
|
||||
"original": "मूल",
|
||||
"preview": "पूर्व दर्शन",
|
||||
"upload": "डालना"
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
"videoFaceAnimator": "Animator Wajah",
|
||||
"videoMotionCapture": "Pengambilan Gerakan Video",
|
||||
"videoTitle": "Pembuatan Video",
|
||||
"videoW2l": "Sinkronisasi Bibir Video"
|
||||
"videoW2l": "Sinkronisasi Bibir Video",
|
||||
"videoWorkflow": "Alur Kerja Video"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"button": {
|
||||
"proceed": "Lanjutkan ke Kontrol Alur Kerja",
|
||||
"process": "Proses ke Alur Kerja",
|
||||
"select": "Pilih",
|
||||
"upload": "Mengunggah video"
|
||||
},
|
||||
"headings": {
|
||||
"title": "Alur Kerja Video"
|
||||
},
|
||||
"tabTitle": {
|
||||
"library": "Pilih dari media",
|
||||
"original": "Asli",
|
||||
"preview": "Pratinjau",
|
||||
"upload": "Mengunggah"
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
"videoFaceAnimator": "Animatore di volti",
|
||||
"videoMotionCapture": "Acquisizione del movimento video",
|
||||
"videoTitle": "Generazione video",
|
||||
"videoW2l": "Sincronizzazione labiale video"
|
||||
"videoW2l": "Sincronizzazione labiale video",
|
||||
"videoWorkflow": "Flusso di lavoro video"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"button": {
|
||||
"proceed": "Procedi ai controlli del flusso di lavoro",
|
||||
"process": "Dal processo al flusso di lavoro",
|
||||
"select": "Selezionare",
|
||||
"upload": "Carica video"
|
||||
},
|
||||
"headings": {
|
||||
"title": "Flusso di lavoro video"
|
||||
},
|
||||
"tabTitle": {
|
||||
"library": "Scegli tra i media",
|
||||
"original": "Originale",
|
||||
"preview": "Anteprima",
|
||||
"upload": "Caricamento"
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
"videoFaceAnimator": "フェイスアニメーター",
|
||||
"videoMotionCapture": "ビデオモーションキャプチャ",
|
||||
"videoTitle": "ビデオ生成",
|
||||
"videoW2l": "ビデオリップシンク"
|
||||
"videoW2l": "ビデオリップシンク",
|
||||
"videoWorkflow": "ビデオワークフロー"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"button": {
|
||||
"proceed": "ワークフロー制御に進む",
|
||||
"process": "プロセスからワークフローへ",
|
||||
"select": "選択する",
|
||||
"upload": "ビデオをアップロードする"
|
||||
},
|
||||
"headings": {
|
||||
"title": "ビデオワークフロー"
|
||||
},
|
||||
"tabTitle": {
|
||||
"library": "メディアから選ぶ",
|
||||
"original": "オリジナル",
|
||||
"preview": "プレビュー",
|
||||
"upload": "アップロード"
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
"videoFaceAnimator": "얼굴 애니메이터",
|
||||
"videoMotionCapture": "비디오 모션 캡처",
|
||||
"videoTitle": "비디오 생성",
|
||||
"videoW2l": "비디오 립싱크"
|
||||
"videoW2l": "비디오 립싱크",
|
||||
"videoWorkflow": "비디오 워크플로우"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"button": {
|
||||
"proceed": "워크플로 제어로 진행",
|
||||
"process": "워크플로 프로세스",
|
||||
"select": "선택하다",
|
||||
"upload": "비디오 업로드"
|
||||
},
|
||||
"headings": {
|
||||
"title": "비디오 워크플로우"
|
||||
},
|
||||
"tabTitle": {
|
||||
"library": "미디어에서 선택",
|
||||
"original": "원래의",
|
||||
"preview": "시사",
|
||||
"upload": "업로드"
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
"videoFaceAnimator": "Animador facial",
|
||||
"videoMotionCapture": "Captura de movimento de vídeo",
|
||||
"videoTitle": "Geração de Vídeo",
|
||||
"videoW2l": "Sincronização labial de vídeo"
|
||||
"videoW2l": "Sincronização labial de vídeo",
|
||||
"videoWorkflow": "Fluxo de trabalho de vídeo"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"button": {
|
||||
"proceed": "Prossiga para os controles de fluxo de trabalho",
|
||||
"process": "Processo para fluxo de trabalho",
|
||||
"select": "Selecione",
|
||||
"upload": "Envio vídeo"
|
||||
},
|
||||
"headings": {
|
||||
"title": "Fluxo de trabalho de vídeo"
|
||||
},
|
||||
"tabTitle": {
|
||||
"library": "Escolha entre mídias",
|
||||
"original": "Original",
|
||||
"preview": "Visualização",
|
||||
"upload": "Carregar"
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
"videoFaceAnimator": "Лицевой Аниматор",
|
||||
"videoMotionCapture": "Захват видео движения",
|
||||
"videoTitle": "Создание видео",
|
||||
"videoW2l": "Видео синхронизация губ"
|
||||
"videoW2l": "Видео синхронизация губ",
|
||||
"videoWorkflow": "Видео рабочий процесс"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"button": {
|
||||
"proceed": "Перейдите к элементам управления рабочим процессом",
|
||||
"process": "От процесса к рабочему процессу",
|
||||
"select": "Выбирать",
|
||||
"upload": "Загрузить видео"
|
||||
},
|
||||
"headings": {
|
||||
"title": "Видео рабочий процесс"
|
||||
},
|
||||
"tabTitle": {
|
||||
"library": "Выбирайте из медиа",
|
||||
"original": "Оригинал",
|
||||
"preview": "Предварительный просмотр",
|
||||
"upload": "Загрузить"
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
"videoFaceAnimator": "แอนิเมชั่นใบหน้า",
|
||||
"videoMotionCapture": "จับภาพวิดีโอเคลื่อนไหว",
|
||||
"videoTitle": "การสร้างวิดีโอ",
|
||||
"videoW2l": "ลิปซิงค์วิดีโอ"
|
||||
"videoW2l": "ลิปซิงค์วิดีโอ",
|
||||
"videoWorkflow": "เวิร์กโฟลว์วิดีโอ"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"button": {
|
||||
"proceed": "ดำเนินการควบคุมเวิร์กโฟลว์",
|
||||
"process": "กระบวนการสู่เวิร์กโฟลว์",
|
||||
"select": "เลือก",
|
||||
"upload": "อัปโหลดวิดีโอ"
|
||||
},
|
||||
"headings": {
|
||||
"title": "เวิร์กโฟลว์วิดีโอ"
|
||||
},
|
||||
"tabTitle": {
|
||||
"library": "เลือกจากสื่อ",
|
||||
"original": "ต้นฉบับ",
|
||||
"preview": "ดูตัวอย่าง",
|
||||
"upload": "ที่อัพโหลด"
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
"videoFaceAnimator": "Yüz Animatörü",
|
||||
"videoMotionCapture": "Video Hareket Yakalama",
|
||||
"videoTitle": "Video Oluşturma",
|
||||
"videoW2l": "Video Dudak Senkronizasyonu"
|
||||
"videoW2l": "Video Dudak Senkronizasyonu",
|
||||
"videoWorkflow": "Video İş Akışı"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"button": {
|
||||
"proceed": "İş Akışı Kontrollerine Devam Edin",
|
||||
"process": "İş Akışına Geçiş Süreci",
|
||||
"select": "Seçme",
|
||||
"upload": "Video yükle"
|
||||
},
|
||||
"headings": {
|
||||
"title": "Video İş Akışı"
|
||||
},
|
||||
"tabTitle": {
|
||||
"library": "Medya arasından seçim yapın",
|
||||
"original": "Orijinal",
|
||||
"preview": "Ön izleme",
|
||||
"upload": "Yüklemek"
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
"videoFaceAnimator": "khuôn mặt hoạt hình",
|
||||
"videoMotionCapture": "Quay video chuyển động",
|
||||
"videoTitle": "Tạo video",
|
||||
"videoW2l": "Hát nhép video"
|
||||
"videoW2l": "Hát nhép video",
|
||||
"videoWorkflow": "Quy trình làm việc video"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"button": {
|
||||
"proceed": "Tiến tới Kiểm soát quy trình công việc",
|
||||
"process": "Quy trình đến quy trình làm việc",
|
||||
"select": "Lựa chọn",
|
||||
"upload": "Tải video lên"
|
||||
},
|
||||
"headings": {
|
||||
"title": "Quy trình làm việc video"
|
||||
},
|
||||
"tabTitle": {
|
||||
"library": "Chọn từ phương tiện truyền thông",
|
||||
"original": "Nguyên bản",
|
||||
"preview": "Xem trước",
|
||||
"upload": "Tải lên"
|
||||
}
|
||||
}
|
||||
@@ -20,5 +20,6 @@
|
||||
"videoFaceAnimator": "脸部动画师",
|
||||
"videoMotionCapture": "视频动作捕捉",
|
||||
"videoTitle": "视频生成",
|
||||
"videoW2l": "视频唇形同步"
|
||||
"videoW2l": "视频唇形同步",
|
||||
"videoWorkflow": "视频工作流程"
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"button": {
|
||||
"proceed": "继续工作流程控制",
|
||||
"process": "流程到工作流程",
|
||||
"select": "选择",
|
||||
"upload": "上传视频"
|
||||
},
|
||||
"headings": {
|
||||
"title": "视频工作流程"
|
||||
},
|
||||
"tabTitle": {
|
||||
"library": "从媒体中选择",
|
||||
"original": "原来的",
|
||||
"preview": "预览",
|
||||
"upload": "上传"
|
||||
}
|
||||
}
|
||||
@@ -210,7 +210,7 @@ function initInferenceJobsByCategoryMap(): Map<
|
||||
inferenceJobsByCategory.set(FrontendInferenceJobType.VideoMotionCapture, []);
|
||||
inferenceJobsByCategory.set(FrontendInferenceJobType.ImageGeneration, []);
|
||||
inferenceJobsByCategory.set(FrontendInferenceJobType.ConvertFbxtoGltf, []);
|
||||
inferenceJobsByCategory.set(FrontendInferenceJobType.StorytellerFilter, [])
|
||||
inferenceJobsByCategory.set(FrontendInferenceJobType.VideoWorkflow, [])
|
||||
return inferenceJobsByCategory;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
.fy-cover-img-input {
|
||||
// background-color: red;
|
||||
// position: relative;
|
||||
// height: 0;
|
||||
// padding-bottom: 100%;
|
||||
position: relative;
|
||||
height: 0;
|
||||
padding-bottom: 100%;
|
||||
|
||||
.fy-cover-control {
|
||||
position: absolute;
|
||||
@@ -17,23 +17,23 @@
|
||||
margin: auto auto 1rem auto;
|
||||
}
|
||||
|
||||
// .weight-initial-cover-img {
|
||||
// background-size: cover;
|
||||
// background-position: center;
|
||||
// border-radius: 0.5rem;
|
||||
// position: absolute;
|
||||
// // display: flex;
|
||||
// overflow: hidden;
|
||||
// height: 100%;
|
||||
// width: 100%;
|
||||
// // padding-bottom: 100%;
|
||||
.weight-initial-cover-img {
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
border-radius: 0.5rem;
|
||||
position: absolute;
|
||||
// display: flex;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
// padding-bottom: 100%;
|
||||
|
||||
// div {
|
||||
// // background-color: red;
|
||||
// display: flex;
|
||||
// height: 100%;
|
||||
// width: 100%;
|
||||
// position: absolute;
|
||||
// }
|
||||
// }
|
||||
div {
|
||||
// background-color: red;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,24 +14,14 @@ export default function Label({
|
||||
required,
|
||||
disabled,
|
||||
}: Props) {
|
||||
return (
|
||||
<div
|
||||
{...{
|
||||
className: `fy-input-label ${disabled ? "opacity-50" : ""}`.trim(),
|
||||
}}
|
||||
>
|
||||
{label ? (
|
||||
<label
|
||||
{...{ className: `fw-medium ${required ? " required" : ""}`.trim() }}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
) : null}
|
||||
{invalidReason ? (
|
||||
<span {...{ className: "label-error form-text red is-danger" }}>
|
||||
{invalidReason}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
return label ? <div {...{ className: `fy-input-label${disabled ? "opacity-50" : "" }` }}>
|
||||
<label {...{ className: `fw-medium${required ? " required" : "" }` }} >
|
||||
{label}
|
||||
</label>
|
||||
{ invalidReason ? (
|
||||
<span {...{ className: "label-error form-text red is-danger" }}>
|
||||
{invalidReason}
|
||||
</span>
|
||||
) : null }
|
||||
</div> : null;
|
||||
}
|
||||
@@ -2,28 +2,29 @@ import React, { useState, memo, useEffect } from "react";
|
||||
import Searcher from "../Searcher";
|
||||
import Modal from "../Modal";
|
||||
import NonRouteTabs from "../Tabs/NonRouteTabs";
|
||||
import Input from "../Input";
|
||||
import Input from "../TempInput";
|
||||
import Button from "../Button";
|
||||
import { faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import SelectMediaList from "./SelectMediaList";
|
||||
import SelectWeightsList from "./SelectWeightsList";
|
||||
|
||||
export type SelectModalData = {
|
||||
token: string;
|
||||
title: string;
|
||||
}
|
||||
interface TabConfig {
|
||||
label: string;
|
||||
tabKey: string;
|
||||
type: "media" | "weights";
|
||||
weightTypeFilter?: string;
|
||||
mediaTypeFilter?: string;
|
||||
typeFilter?: string;
|
||||
searcher?: boolean;
|
||||
}
|
||||
interface SelectModalProps {
|
||||
label?: string;
|
||||
tabs: TabConfig[];
|
||||
modalTitle?: string;
|
||||
onSelect?: (data:{
|
||||
token: string,
|
||||
title: string,
|
||||
}) => void;
|
||||
value?: SelectModalData;
|
||||
onSelect?: (data: SelectModalData) => void;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
@@ -33,42 +34,44 @@ const SelectModal = memo(
|
||||
tabs,
|
||||
modalTitle = "Select",
|
||||
onSelect,
|
||||
value,
|
||||
required,
|
||||
}: SelectModalProps) => {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const initialValue = {token:"", title:""};
|
||||
const [selectedValue, setSelectedValue] = useState(initialValue);
|
||||
const [activeTab, setActiveTab] = useState(tabs[0].tabKey);
|
||||
const [mediaType, setMediaType] = useState(
|
||||
tabs[0].mediaTypeFilter || "all"
|
||||
);
|
||||
const [weightType, setWeightType] = useState(
|
||||
tabs[0].weightTypeFilter || "all"
|
||||
);
|
||||
console.log("🔫", tabs, activeTab, weightType);
|
||||
const emptyValue = {token:"", title:""};
|
||||
const [{isModalOpen, selectedValue, valueType, activeTab}, setState] = useState({
|
||||
isModalOpen:false,
|
||||
selectedValue: value ? value : emptyValue,
|
||||
activeTab: tabs[0].tabKey,
|
||||
valueType: tabs[0].typeFilter || "all"
|
||||
})
|
||||
console.log("🔫", tabs, activeTab, valueType);
|
||||
|
||||
// Update mediaType when activeTab changes
|
||||
useEffect(() => {
|
||||
const currentTab = tabs.find(tab => tab.tabKey === activeTab);
|
||||
setMediaType(currentTab?.mediaTypeFilter || "all");
|
||||
setWeightType(currentTab?.weightTypeFilter || "all");
|
||||
setState((curr)=>({...curr, valueType: currentTab?.typeFilter || "all"}))
|
||||
}, [activeTab, tabs]);
|
||||
|
||||
const openModal = () => {
|
||||
setIsModalOpen(true);
|
||||
setState((curr)=>({...curr, isModalOpen: true}));
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
setIsModalOpen(false);
|
||||
setState((curr)=>({...curr, isModalOpen: false}));
|
||||
};
|
||||
|
||||
const handleRemove = () => {
|
||||
setSelectedValue(initialValue);
|
||||
setState((curr)=>({...curr, selectedValue: emptyValue}));
|
||||
if (onSelect) onSelect(emptyValue);
|
||||
};
|
||||
|
||||
const handleOnSelect = (data:{token:string, title:string}) => {
|
||||
setSelectedValue({token: data.token, title: data.title || ""});
|
||||
setState((curr)=>({
|
||||
...curr,
|
||||
selectedValue: {token: data.token, title: data.title || ""},
|
||||
isModalOpen: false
|
||||
}));
|
||||
if (onSelect) onSelect(data);
|
||||
closeModal();
|
||||
}
|
||||
|
||||
const searchTabs = tabs.map(tab => ({
|
||||
@@ -78,20 +81,20 @@ console.log("🔫", tabs, activeTab, weightType);
|
||||
type="modal"
|
||||
onResultSelect={handleOnSelect}
|
||||
searcherKey={tab.tabKey}
|
||||
weightType={tab.weightTypeFilter}
|
||||
weightType={tab.typeFilter}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{tab.type === "media" && (
|
||||
<SelectMediaList
|
||||
mediaType={mediaType}
|
||||
mediaType={valueType}
|
||||
listKey={tab.tabKey}
|
||||
onResultSelect={handleOnSelect}
|
||||
/>
|
||||
)}
|
||||
{tab.type === "weights" && (
|
||||
<SelectWeightsList
|
||||
weightType={weightType}
|
||||
weightType={valueType}
|
||||
listKey={tab.tabKey}
|
||||
onResultSelect={handleOnSelect}
|
||||
/>
|
||||
@@ -99,7 +102,7 @@ console.log("🔫", tabs, activeTab, weightType);
|
||||
</>
|
||||
),
|
||||
padding: true,
|
||||
onClick: () => setActiveTab(tab.tabKey),
|
||||
onClick: () => setState((curr)=>({...curr, activeTab: tab.tabKey})),
|
||||
}));
|
||||
|
||||
return (
|
||||
@@ -111,15 +114,22 @@ console.log("🔫", tabs, activeTab, weightType);
|
||||
</label>
|
||||
)}
|
||||
|
||||
<div className="d-flex gap-2">
|
||||
<div className="d-flex gap-2 position-relative">
|
||||
<div
|
||||
className="position-absolute w-100 h-100"
|
||||
style={{"cursor": "pointer"}}
|
||||
onClick={openModal}
|
||||
/>
|
||||
<Input
|
||||
disabled={true}
|
||||
className="w-100"
|
||||
wrapperClassName="w-100"
|
||||
placeholder="None selected"
|
||||
onClick={openModal}
|
||||
value={selectedValue.title !==""
|
||||
? selectedValue.title
|
||||
: selectedValue.token || ""}
|
||||
/>
|
||||
|
||||
<Button label={selectedValue.token !== "" ? "Change" : "Select"} onClick={openModal} />
|
||||
{selectedValue.token && (
|
||||
<Button
|
||||
|
||||
@@ -5,6 +5,7 @@ import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
|
||||
import "./Input.scss";
|
||||
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
wrapperClassName ?: string;
|
||||
className?: string;
|
||||
icon?: IconDefinition;
|
||||
invalidReason?: string;
|
||||
@@ -14,6 +15,7 @@ interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
}
|
||||
|
||||
export default function TempInput({
|
||||
wrapperClassName,
|
||||
className,
|
||||
label,
|
||||
icon,
|
||||
@@ -24,8 +26,8 @@ export default function TempInput({
|
||||
}: InputProps) {
|
||||
return (
|
||||
// Changed fragment to div here just so that it can be laid out with bootstrap easily using d-flex, flex-column and responsive gaps which requires grouping.
|
||||
<div className="fy-input">
|
||||
<Label {...{ invalidReason, label, required }}/>
|
||||
<div className={`fy-input${wrapperClassName ? " "+ wrapperClassName : ""}`}>
|
||||
{label && <Label {...{ invalidReason, label, required }}/>}
|
||||
<div className={`fy-input ${icon ? "input-icon" : ""}`}>
|
||||
{icon && (
|
||||
<FontAwesomeIcon icon={icon} className="form-control-feedback" />
|
||||
|
||||
@@ -106,4 +106,14 @@
|
||||
|
||||
label { }
|
||||
}
|
||||
|
||||
.fy-entity-input-upload-detail {
|
||||
span {
|
||||
opacity: .66;
|
||||
display: block;
|
||||
font-size: .75rem;
|
||||
line-height: 1em;
|
||||
height: .75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import { FileWrapper, Label, Spinner } from "components/common";
|
||||
import { EntityType, MediaFilterProp, WeightFilterProp } from "components/entities/EntityTypes";
|
||||
import { useFile, useMedia, useModal, useSession } from "hooks";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import { faFileArrowUp, faGrid, faPersonWalking } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faDiagramSankey, faFile, faFileArrowUp, faGrid, faImage, faPersonWalking, faWaveform } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon as Icon } from "@fortawesome/react-fontawesome";
|
||||
import { UploadMedia, UploadMediaResponse } from "@storyteller/components/src/api/media_files/UploadMedia";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
@@ -17,6 +17,7 @@ import "./EntityInput.scss";
|
||||
interface Props {
|
||||
label?: string,
|
||||
onChange?: any,
|
||||
owner?: string,
|
||||
mediaType?: MediaFilterProp,
|
||||
aspectRatio?: "square" | "landscape" | "portrait",
|
||||
weightType?: WeightFilterProp,
|
||||
@@ -32,6 +33,7 @@ interface EmptySlideProps extends SlideProps {
|
||||
inputProps?: any,
|
||||
onSelect: any,
|
||||
open: any,
|
||||
owner: string,
|
||||
user: any
|
||||
};
|
||||
|
||||
@@ -64,24 +66,37 @@ const MocapInputFull = ({ media }: SlideProps) => {
|
||||
</>;
|
||||
};
|
||||
|
||||
const MediaPickerEmpty = ({ entityType, filterType, media, onSelect, open, inputProps, user }: EmptySlideProps) => {
|
||||
const MediaPickerEmpty = ({ entityType, filterType, media, onSelect, open, owner, inputProps, user }: EmptySlideProps) => {
|
||||
const browserClick = () => open({
|
||||
component: MediaBrowser,
|
||||
props: { entityType, filterType, onSelect, username: user?.username || "" }
|
||||
props: { entityType, filterType, onSelect, owner, username: user?.username || "" }
|
||||
});
|
||||
|
||||
const mediaIcons = () => {
|
||||
switch (filterType) {
|
||||
case "audio": return faWaveform;
|
||||
case "image": return faImage;
|
||||
case "bvh": return faPersonWalking;
|
||||
default: return faFile;
|
||||
}
|
||||
}
|
||||
|
||||
return <>
|
||||
<Icon {...{ className: "fy-entity-input-icon", icon: faPersonWalking }}/>
|
||||
<Icon {...{ className: "fy-entity-input-icon", icon: [faFile,mediaIcons(),faDiagramSankey][entityType] }}/>
|
||||
<div {...{ className: "fy-entity-input-empty-controls" }}>
|
||||
<FileWrapper {...{ containerClass: "fy-entity-input-row", panelClass: "fy-entity-input-button", noStyle: true, ...inputProps }}>
|
||||
<>
|
||||
<Icon {...{ className: "fy-entity-input-label-icon", icon: faFileArrowUp }}/>
|
||||
Upload, click or drag here
|
||||
</>
|
||||
</FileWrapper>
|
||||
{ entityType === EntityType.media &&
|
||||
<FileWrapper {...{ containerClass: "fy-entity-input-row", panelClass: "fy-entity-input-button", noStyle: true, ...inputProps }}>
|
||||
<>
|
||||
<Icon {...{ className: "fy-entity-input-label-icon", icon: faFileArrowUp }}/>
|
||||
<div {...{ className: "fy-entity-input-upload-detail" }}>
|
||||
Upload, click or drag here
|
||||
{ filterType !== "all" && <span>{ filterType } files supported</span> }
|
||||
</div>
|
||||
</>
|
||||
</FileWrapper> }
|
||||
<div {...{ className: "fy-entity-input-row fy-entity-input-button", onClick: browserClick }}>
|
||||
<Icon {...{ className: "fy-entity-input-label-icon", icon: faGrid }}/>
|
||||
Choose from your media
|
||||
Choose from your { ["","media","weights"][entityType] }
|
||||
</div>
|
||||
</div>
|
||||
</>;
|
||||
@@ -94,7 +109,7 @@ const AniMod = ({ animating, className, isLeaving, render: Render, style, ...res
|
||||
<Render {...{ ...rest, animating }} />
|
||||
</a.div>;
|
||||
|
||||
export default function EntityInput({ aspectRatio = "square", label, onChange, mediaType, weightType }: Props) {
|
||||
export default function EntityInput({ aspectRatio = "square", label, onChange, owner, mediaType, weightType }: Props) {
|
||||
const entityType = mediaType ? EntityType.media : weightType ? EntityType.weights : EntityType.unknown;
|
||||
const filterType = mediaType || weightType || "all";
|
||||
const { search } = useLocation();
|
||||
@@ -149,7 +164,7 @@ export default function EntityInput({ aspectRatio = "square", label, onChange, m
|
||||
return [
|
||||
<AniMod {...{ render: MediaBusy, className: "fy-entity-input-busy", ...sharedProps }}/>,
|
||||
<AniMod {...{ render: MocapInputFull, className: "fy-entity-input-full", ...sharedProps }}/>,
|
||||
<AniMod {...{ render: MediaPickerEmpty, className: "fy-entity-input-empty", entityType, filterType, inputProps, onSelect, open, user, ...sharedProps }}/>
|
||||
<AniMod {...{ render: MediaPickerEmpty, className: "fy-entity-input-empty", entityType, filterType, inputProps, onSelect, open, owner, user, ...sharedProps }}/>
|
||||
][i];
|
||||
|
||||
})
|
||||
|
||||
@@ -11,6 +11,7 @@ export enum MediaFilters {
|
||||
export enum WeightsFilters {
|
||||
all,
|
||||
hifigan_tt2,
|
||||
rvc_v2,
|
||||
sd_1,
|
||||
"sd_1.5",
|
||||
sdxl,
|
||||
@@ -35,6 +36,6 @@ export const EntityFilterOptions = ( mode: EntityType, t = (v:string) => v) => {
|
||||
.filter(val => isNaN(Number(val)))
|
||||
.map((value) => {
|
||||
if (typeof value === "string") return { value, label: t(value) }
|
||||
return { label: "", value: "" };
|
||||
return { label: "all", value: "all" };
|
||||
}) : [];
|
||||
};
|
||||
@@ -2,7 +2,7 @@ import React, { useRef } from "react";
|
||||
import { MediaFile } from "@storyteller/components/src/api/media_files/GetMedia";
|
||||
import MasonryGrid from "components/common/MasonryGrid/MasonryGrid";
|
||||
import { ImagePreview, MocapPreview, VideoPreview } from '../CardPreviews';
|
||||
import { AudioCard, OverlayCard, CardWrapper } from "components/entities";
|
||||
import { AudioCard, OverlayCard, CardWrapper, WeightCard } from "components/entities";
|
||||
import { EntityType } from "components/entities/EntityTypes";
|
||||
|
||||
interface MediaCardsProps {
|
||||
@@ -33,6 +33,10 @@ const MediaCards = ({ props, type }: MediaCardsProps) => {
|
||||
|
||||
const WeightsCards = ({ props, type }: MediaCardsProps) => {
|
||||
switch (type) {
|
||||
case "rvc_v2":
|
||||
return <CardWrapper {...{ ...props, card: WeightCard, padding: true }}/>;
|
||||
case "tt2":
|
||||
return <CardWrapper {...{ ...props, card: WeightCard, padding: true }}/>;
|
||||
default:
|
||||
return <div>Unsupported media type</div>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from "react";
|
||||
import moment from "moment";
|
||||
import { MakeRatingsProps } from "hooks/useRatings";
|
||||
import WeightCoverImage from "components/common/WeightCoverImage";
|
||||
import CardBadge from "../CardBadge";
|
||||
import CardFooter from "../CardFooter";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
|
||||
interface Props {
|
||||
data: any,
|
||||
makeRatingsProps?: MakeRatingsProps,
|
||||
showCover?: boolean
|
||||
showCreator?: boolean
|
||||
}
|
||||
|
||||
export default function WeightCard({ data, makeRatingsProps, showCover = true, showCreator }: Props) {
|
||||
const { cover_image, created_at, details, maybe_creator, title, token, weight_type } = data || {}
|
||||
const timeCreated = moment( created_at || "").fromNow();
|
||||
const path = cover_image.maybe_cover_image_public_bucket_path || details?.maybe_weight_data?.maybe_cover_image_public_bucket_path;
|
||||
const bucketConfig = new BucketConfig();
|
||||
const coverImage = bucketConfig.getCdnUrl(path, 110, 100);
|
||||
|
||||
return <>
|
||||
<div className="d-flex">
|
||||
{showCover && (
|
||||
<WeightCoverImage
|
||||
src={coverImage}
|
||||
height={110}
|
||||
width={110}
|
||||
coverIndex={cover_image?.default_cover?.image_index}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex-grow-1">
|
||||
<CardBadge {...{ className: `fy-entity-type${ weight_type ? "-" + weight_type : "" }`, label: weight_type }}/>
|
||||
|
||||
<div className="d-flex align-items-center mt-3">
|
||||
<div className="flex-grow-1">
|
||||
<h6 className="fw-semibold text-white mb-1">
|
||||
{ title || details.maybe_weight_data.title}
|
||||
</h6>
|
||||
<p className="fs-7 opacity-75">{ timeCreated }</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<CardFooter {...{
|
||||
creator: maybe_creator,
|
||||
entityToken: token,
|
||||
entityType: "media_file",
|
||||
makeRatingsProps
|
||||
}}/>
|
||||
</>;
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from "./WeightCard";
|
||||
@@ -3,5 +3,6 @@ import CardWrapper from "./CardWrapper";
|
||||
import EntityInput from "./EntityInput";
|
||||
import MediaList from "./MediaList";
|
||||
import OverlayCard from "./OverlayCard";
|
||||
import WeightCard from "./WeightCard";
|
||||
|
||||
export { AudioCard, CardWrapper, EntityInput, MediaList, OverlayCard };
|
||||
export { AudioCard, CardWrapper, EntityInput, MediaList, OverlayCard, WeightCard };
|
||||
@@ -271,6 +271,96 @@ export default function SideNav({
|
||||
queueStats.legacy_tts.pending_job_count +
|
||||
queueStats.inference.by_queue.pending_tacotron2_jobs;
|
||||
|
||||
let maybeVideoGeneration = <></>;
|
||||
|
||||
if (sessionWrapper.canAccessStudio()) {
|
||||
maybeVideoGeneration = (
|
||||
<>
|
||||
<li>
|
||||
<NavLink
|
||||
to="/video-mocap"
|
||||
activeClassName="active-link"
|
||||
onClick={handleNavLinkClick}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faPersonRays}
|
||||
className="sidebar-heading-icon"
|
||||
/>
|
||||
{t("videoMotionCapture")}
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<NavLink
|
||||
to="/video-workflow"
|
||||
activeClassName="active-link"
|
||||
onClick={handleNavLinkClick}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowsTurnToDots}
|
||||
className="sidebar-heading-icon"
|
||||
/>
|
||||
{t("videoWorkflow")}
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<NavLink
|
||||
to="/studio"
|
||||
activeClassName="active-link"
|
||||
onClick={handleNavLinkClick}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faCameraMovie}
|
||||
className="sidebar-heading-icon"
|
||||
/>
|
||||
Storyteller Studio
|
||||
{/* {t("videoStorytellerStudio")} */}
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink
|
||||
to="/engine-compositor"
|
||||
activeClassName="active-link"
|
||||
onClick={handleNavLinkClick}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faTransporter}
|
||||
className="sidebar-heading-icon"
|
||||
/>
|
||||
Engine Compositor
|
||||
{/* {t("videoStorytellerStudio")} */}
|
||||
</NavLink>
|
||||
</li>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
let maybeImageGeneration = <></>;
|
||||
|
||||
if (sessionWrapper.canAccessStudio()) {
|
||||
maybeImageGeneration = (
|
||||
<>
|
||||
<li className="sidebar-heading">Image Generation</li>
|
||||
<li>
|
||||
<NavLink
|
||||
to="/text-to-image"
|
||||
activeClassName="active-link"
|
||||
onClick={handleNavLinkClick}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faMessageImage}
|
||||
className="sidebar-heading-icon"
|
||||
/>
|
||||
Text to Image
|
||||
{/* {t("videoStorytellerStudio")} */}
|
||||
</NavLink>
|
||||
</li>
|
||||
<hr className="mb-3 mt-3" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div id="sidebar-wrapper" className={sidebarClassName}>
|
||||
@@ -372,80 +462,13 @@ export default function SideNav({
|
||||
{t("videoFaceAnimator")}
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink
|
||||
to="/video-mocap"
|
||||
activeClassName="active-link"
|
||||
onClick={handleNavLinkClick}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faPersonRays}
|
||||
className="sidebar-heading-icon"
|
||||
/>
|
||||
{t("videoMotionCapture")}
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<NavLink
|
||||
to="/video-workflow"
|
||||
activeClassName="active-link"
|
||||
onClick={handleNavLinkClick}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowsTurnToDots}
|
||||
className="sidebar-heading-icon"
|
||||
/>
|
||||
{t("videoWorkflow")}
|
||||
</NavLink>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<NavLink
|
||||
to="/studio"
|
||||
activeClassName="active-link"
|
||||
onClick={handleNavLinkClick}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faCameraMovie}
|
||||
className="sidebar-heading-icon"
|
||||
/>
|
||||
Storyteller Studio
|
||||
{/* {t("videoStorytellerStudio")} */}
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink
|
||||
to="/engine-compositor"
|
||||
activeClassName="active-link"
|
||||
onClick={handleNavLinkClick}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faTransporter}
|
||||
className="sidebar-heading-icon"
|
||||
/>
|
||||
Engine Compositor
|
||||
{/* {t("videoStorytellerStudio")} */}
|
||||
</NavLink>
|
||||
</li>
|
||||
<hr className="mb-3 mt-3" />
|
||||
<li className="sidebar-heading">Image Generation</li>
|
||||
<li>
|
||||
<NavLink
|
||||
to="/text-to-image"
|
||||
activeClassName="active-link"
|
||||
onClick={handleNavLinkClick}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faMessageImage}
|
||||
className="sidebar-heading-icon"
|
||||
/>
|
||||
Text to Image
|
||||
{/* {t("videoStorytellerStudio")} */}
|
||||
</NavLink>
|
||||
</li>
|
||||
{maybeVideoGeneration}
|
||||
|
||||
<hr className="mb-3 mt-3" />
|
||||
|
||||
{maybeImageGeneration}
|
||||
|
||||
<li className="sidebar-heading">{t("communityTitle")}</li>
|
||||
<li>
|
||||
<NavLink
|
||||
|
||||
@@ -20,12 +20,12 @@ interface Props {
|
||||
handleClose: any,
|
||||
mediaToken: string,
|
||||
onSelect?: any,
|
||||
owner?: string,
|
||||
filterType?: MediaFilterProp | WeightFilterProp,
|
||||
username: string,
|
||||
}
|
||||
|
||||
export default function MediaBrowser({ entityType, filterType: inputFilter, mediaToken, handleClose = () => {}, onSelect, username }: Props) {
|
||||
console.log("😎",entityType);
|
||||
export default function MediaBrowser({ entityType, filterType: inputFilter, mediaToken, handleClose = () => {}, onSelect, owner, username }: Props) {
|
||||
const ratings = useRatings();
|
||||
const [showMasonryGrid, setShowMasonryGrid] = useState(true);
|
||||
const [filterType, filterTypeSet] = useState(inputFilter || "all");
|
||||
@@ -46,7 +46,7 @@ export default function MediaBrowser({ entityType, filterType: inputFilter, medi
|
||||
setShowMasonryGrid(true);
|
||||
},
|
||||
requestList: true,
|
||||
urlParam: "echelon",
|
||||
urlParam: owner || username,
|
||||
urlUpdate: false
|
||||
});
|
||||
|
||||
@@ -74,7 +74,7 @@ export default function MediaBrowser({ entityType, filterType: inputFilter, medi
|
||||
return <>
|
||||
<header {...{ className: "fy-media-browser-header" }}>
|
||||
<div {...{ className: "fy-media-browser-tools" }}>
|
||||
<h3>{ ["","Media","Weights"][entityType] }</h3>
|
||||
<h3>{ ["",`${ owner ? owner + "'s " : "" }Media`,"Weights"][entityType] }</h3>
|
||||
<Icon {...{ className: "icon-close-button", icon: faXmark, onClick: () => handleClose() }}/>
|
||||
</div>
|
||||
<div {...{ className: "fy-media-browser-tools" }}>
|
||||
|
||||
@@ -13,10 +13,7 @@ interface ModalConfig {
|
||||
|
||||
export default function ModalProvider({ children }: Props) {
|
||||
const [modalState,modalStateSet] = useState<ModalConfig | null>(null);
|
||||
const open = (cfg: ModalConfig) => {
|
||||
console.log("🪬",cfg);
|
||||
modalStateSet(cfg);
|
||||
};
|
||||
const open = (cfg: ModalConfig) => modalStateSet(cfg);
|
||||
const close = () => modalStateSet(null);
|
||||
|
||||
return <ModalContext.Provider {...{ value: { close, open, modalState } }}>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { InferenceJobsContext } from 'context';
|
||||
|
||||
export default function useInferenceJobs(jobType: FrontendInferenceJobType) {
|
||||
const { byCategory, enqueue } = useContext(InferenceJobsContext);
|
||||
|
||||
return {
|
||||
enqueue,
|
||||
inferenceJobs: (byCategory?.get(jobType) || []),
|
||||
|
||||
@@ -33,7 +33,6 @@ export default function useListContent({
|
||||
urlParam = "",
|
||||
urlUpdate = true
|
||||
}: Props) {
|
||||
console.log("💎",fetcher);
|
||||
const { pathname, search: locSearch } = useLocation();
|
||||
const history = useHistory();
|
||||
const urlQueries = new URLSearchParams(locSearch);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import { UploadModel } from "@storyteller/components/src/api/image_generation/UploadModel";
|
||||
import { FetchStatus } from "@storyteller/components/src/api/_common/SharedFetchTypes";
|
||||
import { useCoverImgUpload } from "hooks";
|
||||
import { useCoverImgUpload, useInferenceJobs } from "hooks";
|
||||
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
// this hook is mostly for organizational purposes while I work -V
|
||||
@@ -13,6 +14,7 @@ export default function useLoraUpload() {
|
||||
const [descriptionMD, descriptionMDSet] = useState("");
|
||||
const [writeStatus, writeStatusSet] = useState(FetchStatus.paused);
|
||||
const coverImg = useCoverImgUpload();
|
||||
const { enqueue } = useInferenceJobs(FrontendInferenceJobType.ImageGeneration);
|
||||
|
||||
const onChange = ({ target }: { target: { name: string; value: any } }) => {
|
||||
const todo: { [key: string]: (x: any) => void } = {
|
||||
@@ -38,9 +40,11 @@ export default function useLoraUpload() {
|
||||
visibility,
|
||||
})
|
||||
.then((res: any) => {
|
||||
writeStatusSet(FetchStatus.success);
|
||||
console.log("🌠", res);
|
||||
// history.replace(`/weight/${token}`);
|
||||
if (res.success && res.inference_job_token) {
|
||||
console.log("🌠", res);
|
||||
writeStatusSet(FetchStatus.success);
|
||||
enqueue(res.inference_job_token, FrontendInferenceJobType.ImageGeneration);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
writeStatusSet(FetchStatus.error);
|
||||
|
||||
@@ -71,17 +71,16 @@ export default function useWeightFetch({ onRemove = n, onSuccess = n, token }: P
|
||||
statusSet(FetchStatus.in_progress);
|
||||
GetWeight(token, {})
|
||||
.then((res: any) => {
|
||||
let {
|
||||
creator_set_visibility,
|
||||
description_markdown,
|
||||
title: resTitle,
|
||||
} = res;
|
||||
statusSet(FetchStatus.success);
|
||||
titleSet(resTitle);
|
||||
descriptionMDSet(description_markdown);
|
||||
visibilitySet(creator_set_visibility);
|
||||
onSuccess(res);
|
||||
setData(res);
|
||||
if (res.success) {
|
||||
let { creator_set_visibility, description_markdown, title: resTitle } = res;
|
||||
|
||||
statusSet(FetchStatus.success);
|
||||
titleSet(resTitle);
|
||||
descriptionMDSet(description_markdown);
|
||||
visibilitySet(creator_set_visibility);
|
||||
onSuccess(res);
|
||||
setData(res);
|
||||
} else { statusSet(FetchStatus.error); }
|
||||
})
|
||||
.catch(err => {
|
||||
statusSet(FetchStatus.error);
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { DiscordLink2 } from "@storyteller/components/src/elements/DiscordLink2";
|
||||
import { Container } from "components/common";
|
||||
import PageHeader from "components/layout/PageHeader";
|
||||
import React from "react";
|
||||
|
||||
export function StudioNotAvailable() {
|
||||
const subText = (
|
||||
<p> Please visit our <DiscordLink2 /> to join the wait list. </p>
|
||||
);
|
||||
|
||||
return (
|
||||
<Container type="panel" className="mb-5">
|
||||
<PageHeader
|
||||
title={"Studio Features Not Available"}
|
||||
subText={subText}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,10 @@
|
||||
.fy-engine-compositor {
|
||||
// margin: 1rem;
|
||||
padding: 1rem;
|
||||
|
||||
.engine-compositor-container {
|
||||
padding: 1rem;
|
||||
|
||||
header {
|
||||
// background: blue;
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -14,16 +12,12 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h2 { margin-bottom: 0; }
|
||||
h2, .fy-temp-select, .fy-input { margin-bottom: 0; }
|
||||
}
|
||||
.compositor-content {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
// padding: 1rem;
|
||||
|
||||
> * {
|
||||
width: 100%;
|
||||
// background: red;
|
||||
}
|
||||
> * { width: 100%; }
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,15 @@ import { EnqueueEngineCompositing } from "@storyteller/components/src/api/engine
|
||||
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import "./EngineCompositor.scss"
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { StudioNotAvailable } from "v2/view/_common/StudioNotAvailable";
|
||||
|
||||
interface Props {
|
||||
value?: any;
|
||||
sessionWrapper: SessionWrapper;
|
||||
}
|
||||
|
||||
export default function EngineCompositor({ value }: Props) {
|
||||
export default function EngineCompositor({ value, sessionWrapper }: Props) {
|
||||
const [mediaToken,mediaTokenSet] = useState();
|
||||
const onChange = ({ target }: any) => mediaTokenSet(target.value);
|
||||
const inferenceJobs = useInferenceJobs(FrontendInferenceJobType.EngineComposition);
|
||||
@@ -23,12 +26,10 @@ export default function EngineCompositor({ value }: Props) {
|
||||
})
|
||||
.then((res: any) => {
|
||||
if (res && res.success) {
|
||||
console.log("🚛 success!",{ mediaToken, res });
|
||||
inferenceJobs.enqueue( // I need to pass this
|
||||
res.inference_job_token,
|
||||
FrontendInferenceJobType.EngineComposition
|
||||
);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -40,6 +41,10 @@ export default function EngineCompositor({ value }: Props) {
|
||||
}
|
||||
};
|
||||
|
||||
if (!sessionWrapper.canAccessStudio()) {
|
||||
return <StudioNotAvailable />
|
||||
}
|
||||
|
||||
return <div {...{ className: "fy-engine-compositor"}}>
|
||||
<div {...{ className: "panel engine-compositor-container" }}>
|
||||
<header>
|
||||
|
||||
@@ -89,6 +89,9 @@ function AboutPage(props: Props) {
|
||||
},
|
||||
];
|
||||
|
||||
// NB: just to prevent yarn lints from complaining
|
||||
console.log(teamMembers.length);
|
||||
|
||||
return (
|
||||
<Container type="panel" className="mb-5">
|
||||
<PageHeaderWithImage
|
||||
@@ -97,7 +100,7 @@ function AboutPage(props: Props) {
|
||||
headerImage="/mascot/kitsune_pose3.webp"
|
||||
yOffset="85%"
|
||||
/>
|
||||
<Panel padding={true} mb={true}>
|
||||
{/*<Panel padding={true} mb={true}>
|
||||
<h2 className="mb-3 fw-bold">Lorem Ipsum</h2>
|
||||
|
||||
<p>
|
||||
@@ -109,7 +112,7 @@ function AboutPage(props: Props) {
|
||||
pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
|
||||
culpa qui officia deserunt mollit anim id est laborum.
|
||||
</p>
|
||||
</Panel>
|
||||
</Panel>*/}
|
||||
|
||||
<Panel padding={true} mb={true}>
|
||||
<h2 className="mb-3 fw-bold">Our Mission</h2>
|
||||
@@ -123,6 +126,7 @@ function AboutPage(props: Props) {
|
||||
</p>
|
||||
</Panel>
|
||||
|
||||
{/*
|
||||
<Panel padding={true}>
|
||||
<h2 className="mb-3 fw-bold">The Team</h2>
|
||||
|
||||
@@ -184,6 +188,7 @@ function AboutPage(props: Props) {
|
||||
))}
|
||||
</div>
|
||||
</Panel>
|
||||
*/}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import { EntityInput } from "components/entities";
|
||||
import { Button } from "components/common";
|
||||
// import { useInferenceJobs } from "hooks";
|
||||
// import InferenceJobsList from "components/layout/InferenceJobsList";
|
||||
// import { EnqueueEngineCompositing } from "@storyteller/components/src/api/engine_compositor/EnqueueEngineCompositing";
|
||||
// import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
|
||||
// import { v4 as uuidv4 } from "uuid";
|
||||
import { EntityFilterOptions, EntityType, MediaFilterProp, WeightFilterProp } from "components/entities/EntityTypes";
|
||||
import { Button, SegmentButtons, TempInput, TempSelect } from "components/common";
|
||||
|
||||
interface Props {
|
||||
value?: any;
|
||||
@@ -13,34 +9,34 @@ interface Props {
|
||||
|
||||
export default function DevMediaInput({ value }: Props) {
|
||||
const [mediaToken,mediaTokenSet] = useState();
|
||||
const [mediaType,mediaTypeSet] = useState<MediaFilterProp>("all");
|
||||
const [weightType,weightTypeSet] = useState<WeightFilterProp>("all");
|
||||
const [entityType,entityTypeSet] = useState(EntityType.media);
|
||||
const [owner,ownerSet] = useState("");
|
||||
const onChange = ({ target }: any) => mediaTokenSet(target.value);
|
||||
// const inferenceJobs = useInferenceJobs(FrontendInferenceJobType.EngineComposition);
|
||||
|
||||
// const failures = (fail = "") => {
|
||||
// switch (fail) {
|
||||
// default:
|
||||
// return "Uknown failure";
|
||||
// }
|
||||
// };
|
||||
const options = [{ label: "Media", value: EntityType.media },{ label: "Weights", value: EntityType.weights }];
|
||||
const changeFilter = ({ target }: { target: any }) => [mediaTypeSet,mediaTypeSet,weightTypeSet][entityType](target.value);
|
||||
|
||||
return <div {...{ className: "fy-engine-compositor"}}>
|
||||
<div {...{ className: "panel engine-compositor-container" }}>
|
||||
<header>
|
||||
<h2>{ [526,187].map((num = 0) => String.fromCodePoint(128000 + num)) }</h2>
|
||||
<SegmentButtons {...{ onChange: ({ target }: { target: any }) => entityTypeSet(target.value), options, value: entityType }}/>
|
||||
<TempInput {...{ value: owner, onChange: ({ target }: { target: any }) => ownerSet(target.value), placeholder: "User" }}/>
|
||||
<TempSelect {...{ options: EntityFilterOptions(entityType), value: ["",mediaType,weightType][entityType], onChange: changeFilter }}/>
|
||||
<Button {...{ label: "Enqueue", variant: "primary" }}/>
|
||||
</header>
|
||||
<EntityInput {...{ aspectRatio: "landscape", label: "Choose Media File", onChange, weightType: "sd_1.5" }}/>
|
||||
<EntityInput {...{
|
||||
aspectRatio: "landscape",
|
||||
label: `Choose ${ ["","media file","weight"][entityType] }`,
|
||||
onChange,
|
||||
owner,
|
||||
...([{},{ mediaType },{ weightType }][entityType])
|
||||
}}/>
|
||||
<div>
|
||||
{ mediaToken }
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
// <InferenceJobsList
|
||||
// {...{
|
||||
// failures,
|
||||
// jobType: FrontendInferenceJobType.EngineComposition,
|
||||
// }}
|
||||
// />
|
||||
}
|
||||
</div>;
|
||||
};
|
||||
+5
@@ -5,6 +5,7 @@ import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session
|
||||
import PageHeaderWithImage from "components/layout/PageHeaderWithImage";
|
||||
import { Container, Panel } from "components/common";
|
||||
import { faCameraMovie } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { StudioNotAvailable } from "v2/view/_common/StudioNotAvailable";
|
||||
|
||||
interface Props {
|
||||
sessionWrapper: SessionWrapper;
|
||||
@@ -12,6 +13,10 @@ interface Props {
|
||||
}
|
||||
|
||||
function StorytellerStudioListPage(props: Props) {
|
||||
if (!props.sessionWrapper.canAccessStudio()) {
|
||||
return <StudioNotAvailable />
|
||||
}
|
||||
|
||||
return (
|
||||
<Container type="panel">
|
||||
<PageHeaderWithImage
|
||||
|
||||
@@ -7,9 +7,12 @@ import { SessionSubscriptionsWrapper } from "@storyteller/components/src/session
|
||||
import { faMessageImage } from "@fortawesome/pro-solid-svg-icons";
|
||||
import TextToImageJobsList from "./components/TextToImageJobsList";
|
||||
import { useInferenceJobs } from "hooks";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { StudioNotAvailable } from "v2/view/_common/StudioNotAvailable";
|
||||
|
||||
interface TextToImagePageProps {
|
||||
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
|
||||
sessionWrapper: SessionWrapper;
|
||||
enqueueInferenceJob: (
|
||||
jobToken: string,
|
||||
frontendInferenceJobType: FrontendInferenceJobType
|
||||
@@ -17,6 +20,7 @@ interface TextToImagePageProps {
|
||||
}
|
||||
|
||||
export default function TextToImagePage({
|
||||
sessionWrapper,
|
||||
sessionSubscriptionsWrapper,
|
||||
enqueueInferenceJob,
|
||||
}: TextToImagePageProps) {
|
||||
@@ -25,6 +29,10 @@ export default function TextToImagePage({
|
||||
);
|
||||
const hasImageGenJobs = inferenceJobs && inferenceJobs.length > 0;
|
||||
|
||||
if (!sessionWrapper.canAccessStudio()) {
|
||||
return <StudioNotAvailable />
|
||||
}
|
||||
|
||||
return (
|
||||
<Container type="panel">
|
||||
<PageHeaderWithImage
|
||||
|
||||
@@ -15,6 +15,8 @@ import {
|
||||
TempSelect,
|
||||
TempTextArea,
|
||||
} from "components/common";
|
||||
import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/InferenceJob";
|
||||
import InferenceJobsList from "components/layout/InferenceJobsList";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import useLoraUpload from "hooks/useLoraUpload";
|
||||
|
||||
@@ -32,6 +34,14 @@ export default function UploadLoraWeightPage({
|
||||
{ label: "Private", value: "private" },
|
||||
];
|
||||
|
||||
|
||||
const failures = (fail = "") => {
|
||||
switch (fail) {
|
||||
default:
|
||||
return "Uknown failure";
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
coverImg,
|
||||
descriptionMD,
|
||||
@@ -66,6 +76,12 @@ export default function UploadLoraWeightPage({
|
||||
|
||||
return (
|
||||
<Container type="panel">
|
||||
<InferenceJobsList
|
||||
{...{
|
||||
failures,
|
||||
jobType: FrontendInferenceJobType.ImageGeneration,
|
||||
}}
|
||||
/>
|
||||
<PageHeader
|
||||
title="Upload LoRA Weight"
|
||||
titleIcon={faWaveform}
|
||||
|
||||
@@ -21,6 +21,8 @@ import {
|
||||
faPersonRays,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import PageHeaderWithImage from "components/layout/PageHeaderWithImage";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
import { StudioNotAvailable } from "v2/view/_common/StudioNotAvailable";
|
||||
|
||||
export default function VideoMotionCapture(props: {
|
||||
enqueueInferenceJob: (
|
||||
@@ -29,8 +31,9 @@ export default function VideoMotionCapture(props: {
|
||||
) => void;
|
||||
inferenceJobs: Array<InferenceJob>;
|
||||
inferenceJobsByCategory: Map<FrontendInferenceJobType, Array<InferenceJob>>;
|
||||
sessionWrapper: SessionWrapper;
|
||||
}) {
|
||||
const { enqueueInferenceJob } = props;
|
||||
const { enqueueInferenceJob, sessionWrapper } = props;
|
||||
const { t } = useLocalize("VideoMotionCapture");
|
||||
const { NO_FILE, FILE_UPLOADING, MOCAPNET_ENQUEUED } = states;
|
||||
const [pageState, dispatchPageState] = useReducer(reducer, {
|
||||
@@ -59,6 +62,10 @@ export default function VideoMotionCapture(props: {
|
||||
}, [pageState, enqueueInferenceJob]);
|
||||
const { pathname } = useLocation();
|
||||
|
||||
if (!sessionWrapper.canAccessStudio()) {
|
||||
return <StudioNotAvailable />
|
||||
}
|
||||
|
||||
if (pathname === `/video-mocap` || pathname === `/video-mocap/`) {
|
||||
return <Redirect to={`/video-mocap/upload`} />;
|
||||
}
|
||||
|
||||
+2
-2
@@ -21,7 +21,7 @@ export default function PageVideoMocapProgress({
|
||||
pageState: State;
|
||||
dispatchPageState: (action: Action) => void;
|
||||
}) {
|
||||
const { FILE_UPLOADING, FILE_UPLOADED, FILE_SELECTED, MOCAPNET_ENQUEUEING } = states;
|
||||
const { FILE_UPLOADING, FILE_UPLOADED, FILE_SELECTED_PROCEED, MOCAPNET_ENQUEUEING } = states;
|
||||
|
||||
const handleEnqueueMocapNet = () => {
|
||||
if (pageState.mediaFileToken) {
|
||||
@@ -43,7 +43,7 @@ export default function PageVideoMocapProgress({
|
||||
}
|
||||
};
|
||||
|
||||
if (pageState.status === FILE_SELECTED){
|
||||
if (pageState.status === FILE_SELECTED_PROCEED){
|
||||
handleEnqueueMocapNet();
|
||||
}
|
||||
|
||||
|
||||
+35
-24
@@ -1,5 +1,6 @@
|
||||
import SelectModal from "components/common/SelectModal/SelectModal";
|
||||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
|
||||
import SelectModal, {SelectModalData} from "components/common/SelectModal/SelectModal";
|
||||
import { Action, State } from "../videoMocapReducer";
|
||||
import { Button } from "components/common";
|
||||
|
||||
@@ -12,35 +13,45 @@ export default function TabContentLibrary({
|
||||
pageState: State;
|
||||
dispatchPageState: (action: Action) => void;
|
||||
}) {
|
||||
const [token, setToken] = useState<string | undefined>();
|
||||
|
||||
const handleProceed = () => {
|
||||
if (token)
|
||||
dispatchPageState({ type: "proceedSelectedFile" });
|
||||
};
|
||||
const handleOnSelect = (data:SelectModalData) => {
|
||||
if (data.token !==""){
|
||||
dispatchPageState({
|
||||
type: "selectedFile",
|
||||
payload: { mediaFileToken: token },
|
||||
payload: { mediaFileToken: data.token },
|
||||
});
|
||||
};
|
||||
const handleOnSelect = (data:{token:string,title:string}) => {
|
||||
setToken(data.token);
|
||||
}else{
|
||||
dispatchPageState({type: "clearedFile"});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SelectModal
|
||||
modalTitle="Select a Video"
|
||||
label="Select a Video"
|
||||
onSelect={handleOnSelect}
|
||||
tabs={[
|
||||
{
|
||||
label: "All Videos",
|
||||
tabKey: "allVideos",
|
||||
mediaTypeFilter: "video",
|
||||
searcher: false,
|
||||
type: "media",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{token && <Button label={t("button.proceed")} onClick={handleProceed} />}
|
||||
<div className="row g-3">
|
||||
<div className="col-12">
|
||||
<SelectModal
|
||||
modalTitle="Select a Video"
|
||||
label="Select a Video"
|
||||
onSelect={handleOnSelect}
|
||||
value={{token: pageState.mediaFileToken || "", title: ""}}
|
||||
tabs={[
|
||||
{
|
||||
label: "All Videos",
|
||||
tabKey: "allVideos",
|
||||
typeFilter: "video",
|
||||
searcher: false,
|
||||
type: "media",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
{pageState.mediaFileToken &&
|
||||
<div className="col-12 d-flex justify-content-center mt-5">
|
||||
<Button label={t("button.proceed")} onClick={handleProceed} />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export default function TabContentUpload({t, pageState, dispatchPageState}: {
|
||||
dispatchPageState: (action: Action) => void;
|
||||
}) {
|
||||
const videoProps = useFile({});
|
||||
const {NO_FILE, FILE_STAGED} = states;
|
||||
const {NO_FILE, FILE_STAGED, FILE_UPLOADING} = states;
|
||||
|
||||
|
||||
const makeVideoUploadRequest = () => ({
|
||||
@@ -38,7 +38,7 @@ export default function TabContentUpload({t, pageState, dispatchPageState}: {
|
||||
};
|
||||
|
||||
// contains upload inout state and controls, see docs
|
||||
if (pageState.status === NO_FILE || pageState.status === FILE_STAGED) {
|
||||
if (pageState.status < FILE_UPLOADING) {
|
||||
return (
|
||||
<>
|
||||
<div className="row">
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
|
||||
export enum states{
|
||||
NO_FILE,
|
||||
FILE_SELECTED,
|
||||
FILE_SELECTED_PROCEED,
|
||||
FILE_STAGED,
|
||||
FILE_UPLOADING,
|
||||
FILE_UPLOADED,
|
||||
FILE_SELECTED,
|
||||
MOCAPNET_ENQUEUEING,
|
||||
MOCAPNET_ENQUEUED,
|
||||
}
|
||||
@@ -20,6 +21,7 @@ export type Action =
|
||||
| {type: 'stagedFile'}
|
||||
| {type: 'clearedFile'}
|
||||
| {type: 'selectedFile', payload:{ mediaFileToken: string}}
|
||||
| {type: 'proceedSelectedFile'}
|
||||
| {type: 'uploadFile'}
|
||||
| {type: 'uploadFileSuccess', payload:{ mediaFileToken: string}}
|
||||
| {type: 'enqueueMocapNet'}
|
||||
@@ -32,13 +34,19 @@ export function reducer (state: State, action: Action): State {
|
||||
case 'stagedFile':
|
||||
return {...state, status: states.FILE_STAGED}
|
||||
case 'clearedFile':
|
||||
return {...state, status: states.NO_FILE}
|
||||
return {
|
||||
...state,
|
||||
status: states.NO_FILE,
|
||||
mediaFileToken: undefined
|
||||
}
|
||||
case 'selectedFile':
|
||||
return {
|
||||
...state,
|
||||
status: states.FILE_SELECTED,
|
||||
mediaFileToken: action.payload.mediaFileToken
|
||||
}
|
||||
case 'proceedSelectedFile':
|
||||
return {...state, status: states.FILE_SELECTED_PROCEED }
|
||||
case 'uploadFile':
|
||||
return {...state,status: states.FILE_UPLOADING};
|
||||
case 'uploadFileSuccess':
|
||||
|
||||
@@ -8,6 +8,8 @@ import PageHeader from "components/layout/PageHeader";
|
||||
|
||||
import { states, reducer } from "./videoWorkflowReducer";
|
||||
import SubRoutes from "./videoWorkflowRoutes";
|
||||
import { SessionWrapper } from '@storyteller/components/src/session/SessionWrapper';
|
||||
import { StudioNotAvailable } from 'v2/view/_common/StudioNotAvailable';
|
||||
|
||||
export default function StorytellerFilter(props:{
|
||||
enqueueInferenceJob: (
|
||||
@@ -16,9 +18,10 @@ export default function StorytellerFilter(props:{
|
||||
) => void;
|
||||
inferenceJobs: Array<InferenceJob>;
|
||||
inferenceJobsByCategory: Map<FrontendInferenceJobType, Array<InferenceJob>>;
|
||||
sessionWrapper: SessionWrapper;
|
||||
}){
|
||||
const debug=true;
|
||||
const {t} = useLocalize("StorytellerFilter");
|
||||
const debug = true;
|
||||
const {t} = useLocalize("VideoWorkflow");
|
||||
const { NO_FILE } = states;
|
||||
const [pageState, dispatchPageState] = useReducer(reducer, {
|
||||
status: NO_FILE,
|
||||
@@ -26,16 +29,16 @@ export default function StorytellerFilter(props:{
|
||||
|
||||
const { enqueueInferenceJob } = props;
|
||||
useInferenceJobs(
|
||||
FrontendInferenceJobType.StorytellerFilter
|
||||
FrontendInferenceJobType.VideoWorkflow
|
||||
);
|
||||
useEffect(() => {
|
||||
if (
|
||||
pageState.status === states.FILTER_ENQUEUED &&
|
||||
pageState.status === states.WORKFLOW_ENQUEUED &&
|
||||
pageState.inferenceJobToken
|
||||
) {
|
||||
enqueueInferenceJob(
|
||||
pageState.inferenceJobToken,
|
||||
FrontendInferenceJobType.StorytellerFilter
|
||||
FrontendInferenceJobType.VideoWorkflow
|
||||
);
|
||||
dispatchPageState({
|
||||
type: "enqueueFilterSuccess",
|
||||
@@ -44,8 +47,13 @@ export default function StorytellerFilter(props:{
|
||||
}
|
||||
}, [pageState, enqueueInferenceJob]);
|
||||
|
||||
if (!props.sessionWrapper.canAccessStudio()) {
|
||||
return <StudioNotAvailable />
|
||||
}
|
||||
|
||||
return(
|
||||
<Container type="panel" className="mb-5">
|
||||
{debug && <p>{`Status:${pageState.status} MediaToken:${pageState.mediaFileToken}`}</p>}
|
||||
<PageHeader
|
||||
title={t("headings.title")}
|
||||
subText={t("headings.subtitle")}
|
||||
|
||||
-472
@@ -1,472 +0,0 @@
|
||||
import React, {useState} from "react";
|
||||
import { useParams, useHistory } from "react-router-dom";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { faChevronRight } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useMedia } from "hooks";
|
||||
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import { EnqueueStorytellerFilter } from "@storyteller/components/src/api/storyteller_filter";
|
||||
import { states, Action, State } from "../videoWorkflowReducer";
|
||||
import {
|
||||
Accordion,
|
||||
Button,
|
||||
BasicVideo,
|
||||
Checkbox,
|
||||
ErrorMessage,
|
||||
Input,
|
||||
InputSeed,
|
||||
NumberSliderV2,
|
||||
Panel,
|
||||
SelectModal,
|
||||
Spinner,
|
||||
TextArea
|
||||
} from "components/common";
|
||||
|
||||
|
||||
export default function PageFilterControls({
|
||||
t, pageState, dispatchPageState
|
||||
}: {
|
||||
debug?: boolean;
|
||||
t: Function;
|
||||
pageState: State;
|
||||
dispatchPageState: (action: Action) => void;
|
||||
}) {
|
||||
const { mediaToken } = useParams<any>();
|
||||
useMedia({
|
||||
mediaToken: pageState.mediaFileToken || mediaToken,
|
||||
onSuccess: (res: any) => {
|
||||
dispatchPageState({
|
||||
type: 'loadFileSuccess',
|
||||
payload: {
|
||||
mediaFile: res,
|
||||
mediaFileToken: pageState.mediaFileToken || mediaToken
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
const [filterState, setFilterState] = useState({
|
||||
outputPath: "vid2vid/SparseUpscaleInterp_00001.mp4",
|
||||
workflowConfig: "weight_q8sz47gmfw2zx02snrbz88ns9",
|
||||
seed: "",
|
||||
sdModelToken: "",
|
||||
loraModelToken: "",
|
||||
posPrompt: "",
|
||||
negPrompt: "",
|
||||
firstPass: 1,
|
||||
upscalePass: 0.42,
|
||||
motionScale:1,
|
||||
upscaleMultiplier: 1.5,
|
||||
useEmptyLatent: false,
|
||||
useFaceDetailer: false,
|
||||
denoiseFaceDetailer: 0.45,
|
||||
useLCM: false,
|
||||
lcmCFG: 2,
|
||||
lcmSteps: 8,
|
||||
framesCap:16,
|
||||
skipFrames:0,
|
||||
nthFrames:2,
|
||||
inputFps: 24,
|
||||
interpolationMutiplier: 2,
|
||||
cnTile: 0,
|
||||
cnCanny: 0,
|
||||
cnLinearAnime: 0,
|
||||
cnLinearRealistic: 0,
|
||||
cnDepth: 0,
|
||||
cnOpenPose: 0,
|
||||
cnPipeFace: 0,
|
||||
cnSparse: 0.7,
|
||||
});
|
||||
|
||||
const handleOnChange = (key: string, newValue:any,) => {
|
||||
setFilterState((curr)=>({...curr, [key]: newValue}));
|
||||
}
|
||||
|
||||
const history = useHistory();
|
||||
const handleGenerate = ()=>{
|
||||
console.log("filterState:")
|
||||
console.log(filterState)
|
||||
|
||||
const request = {
|
||||
"uuid_idempotency_token": uuidv4(),
|
||||
"maybe_sd_model": filterState.sdModelToken,
|
||||
"maybe_workflow_config": filterState.workflowConfig,
|
||||
"maybe_input_file": pageState.mediaFileToken || mediaToken,
|
||||
"maybe_output_path": filterState.outputPath,
|
||||
"maybe_json_modifications": {
|
||||
"$.510.inputs.Text": filterState.posPrompt,
|
||||
"$.8.inputs.text": filterState.negPrompt,
|
||||
"$.173.inputs.seed": filterState.seed,
|
||||
"$.401.inputs.Value": filterState.firstPass,
|
||||
"$.918.inputs.Value": filterState.motionScale,
|
||||
"$.137.inputs.Value": filterState.framesCap,
|
||||
"$.186.inputs.Value": filterState.skipFrames,
|
||||
"$.140.inputs.Value": filterState.nthFrames,
|
||||
"$.154.inputs.Value": filterState.inputFps,
|
||||
"$.445.inputs.number": filterState.interpolationMutiplier,
|
||||
"$.947.inputs.Value": filterState.cnTile,
|
||||
"$.800.inputs.Value": filterState.cnCanny,
|
||||
"$.797.inputs.Value": filterState.cnLinearAnime,
|
||||
"$.796.inputs.Value": filterState.cnLinearRealistic,
|
||||
"$.772.inputs.Value": filterState.cnDepth,
|
||||
"$.771.inputs.Value": filterState.cnOpenPose,
|
||||
"$.527.inputs.Value": filterState.cnPipeFace,
|
||||
"$.403.inputs.Value": filterState.cnSparse
|
||||
},
|
||||
}
|
||||
EnqueueStorytellerFilter(request).then(res => {
|
||||
if (res.success && res.inference_job_token) {
|
||||
dispatchPageState({
|
||||
type: 'enqueueFilterSuccess',
|
||||
payload: {
|
||||
inferenceJobToken: res.inference_job_token
|
||||
}
|
||||
})
|
||||
}else{
|
||||
console.log(res);
|
||||
}
|
||||
})
|
||||
dispatchPageState({type: 'enqueueFilter'})
|
||||
history.push("/storyteller-filter/jobs");
|
||||
}
|
||||
|
||||
if (pageState.mediaFile){
|
||||
const mediaLink = new BucketConfig().getGcsUrl(pageState.mediaFile.public_bucket_path);
|
||||
|
||||
if (mediaLink)
|
||||
return(
|
||||
<>
|
||||
<Panel>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-5">
|
||||
<BasicVideo src={mediaLink} />
|
||||
</div>
|
||||
<div className="col-1 d-flex align-items-center justify-items-center">
|
||||
<FontAwesomeIcon icon={faChevronRight} className="fa-6x"/>
|
||||
</div>
|
||||
<div className="col-6">
|
||||
<BasicVideo src={mediaLink} />
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
<Accordion className="mt-4">
|
||||
<Accordion.Item title={"Basics"} defaultOpen>
|
||||
<div className="row g-3 p-3">
|
||||
<SelectModal
|
||||
modalTitle="Select a Stable Diffusion Weight"
|
||||
label="Select a Stable Diffusion Weight"
|
||||
onSelect={({token})=>{
|
||||
handleOnChange("sdModelToken", token);
|
||||
}}
|
||||
tabs={[
|
||||
{
|
||||
label: "All Weights",
|
||||
tabKey: "allWeights",
|
||||
weightTypeFilter: "sd_1.5",
|
||||
searcher: true,
|
||||
type: "weights",
|
||||
},
|
||||
{
|
||||
label: "Bookmarked",
|
||||
tabKey: "bookmarkedWeights",
|
||||
weightTypeFilter: "sd_1.5",
|
||||
searcher: false,
|
||||
type: "weights",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<SelectModal
|
||||
modalTitle="Select a LoRA Weight"
|
||||
label="Additional LoRA Weight"
|
||||
onSelect={({token})=>{
|
||||
console.log(`calling from select modal result select ${token}`);
|
||||
handleOnChange("loraModelToken", token);
|
||||
}}
|
||||
tabs={[
|
||||
{
|
||||
label: "All LoRA Weights",
|
||||
tabKey: "allLoraWeights",
|
||||
weightTypeFilter: "rvc_v2",
|
||||
searcher: true,
|
||||
type: "weights",
|
||||
},
|
||||
{
|
||||
label: "Bookmarked",
|
||||
tabKey: "bookmarkedLoraWeights",
|
||||
weightTypeFilter: "rvc_v2",
|
||||
searcher: false,
|
||||
type: "weights",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<TextArea
|
||||
{...{
|
||||
label: "Prompt",
|
||||
placeholder: "Enter a prompt",
|
||||
onChange: (e:React.ChangeEvent<HTMLTextAreaElement>)=>handleOnChange("posPrompt", e.target.value),
|
||||
value: filterState.posPrompt,
|
||||
required: false,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<TextArea
|
||||
{...{
|
||||
label: "Negative Prompt",
|
||||
placeholder: "Enter Negative Prompt",
|
||||
onChange: (e:React.ChangeEvent<HTMLTextAreaElement>)=>handleOnChange("negPrompt", e.target.value),
|
||||
value: filterState.negPrompt,
|
||||
required: false,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<InputSeed label="Seed" onChange={
|
||||
(val:string)=>handleOnChange("seed", val)
|
||||
}/>
|
||||
</div>
|
||||
|
||||
</Accordion.Item>
|
||||
<Accordion.Item title="Advance" defaultOpen>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0.1, max: 1, step: 0.01,
|
||||
initialValue: filterState.firstPass,
|
||||
label: " Denoise First Pass",
|
||||
thumbTip: "Denoise First Pass",
|
||||
onChange: (val)=>{handleOnChange("firstPass",val)}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0.1, max: 1, step: 0.01,
|
||||
initialValue: filterState.upscalePass,
|
||||
label: "Denoise Upscale Pass",
|
||||
thumbTip: "Denoise Upscale Pass",
|
||||
onChange: (val)=>{handleOnChange("upscalePass", val)}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0.25, max: 2, step: 0.05,
|
||||
initialValue: filterState.motionScale,
|
||||
label: "Motion Scale",
|
||||
thumbTip: "Motion Scale",
|
||||
onChange: (val)=>{handleOnChange("motionScale", val)}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 1.5, max: 2, step: 0.5,
|
||||
initialValue: filterState.upscaleMultiplier,
|
||||
label: "Upscale Multiplier",
|
||||
thumbTip: "Upscale Multiplier",
|
||||
onChange: (val)=>{handleOnChange("upscaleMultiplier", val)}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<Checkbox
|
||||
label="Use Empty Latent"
|
||||
checked={filterState.useEmptyLatent}
|
||||
onChange={(e:{target:{ checked: boolean, name:string, type: string }})=>{handleOnChange("useEmptyLatent", e.target.checked)}}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<Checkbox
|
||||
label="Use Face Detailer"
|
||||
checked={filterState.useFaceDetailer}
|
||||
onChange={(e:{target:{ checked: boolean, name:string, type: string }})=>{handleOnChange("useFaceDetailer", e.target.checked)}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0.1, max: 0.7, step: 0.01,
|
||||
initialValue: filterState.denoiseFaceDetailer,
|
||||
label: "Denoise Face Detailer",
|
||||
thumbTip: "Denoise Face Detailer",
|
||||
onChange: (val)=>{handleOnChange("denoiseFaceDetailer", val)}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<Checkbox
|
||||
label="Use LCM"
|
||||
checked={filterState.useLCM}
|
||||
onChange={(e:{target:{ checked: boolean, name:string, type: string }})=>{handleOnChange("useLCM", e.target.checked)}}
|
||||
/>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 1, max: 10, step: 1,
|
||||
initialValue: filterState.lcmCFG,
|
||||
label: "LCM CFG",
|
||||
thumbTip: "LCM CFG",
|
||||
onChange: (val)=>{handleOnChange("lcmCFG", val)}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 5, max: 15, step: 1,
|
||||
initialValue: filterState.lcmSteps,
|
||||
label: "LCM Steps",
|
||||
thumbTip: "LCM Steps",
|
||||
onChange: (val)=>{handleOnChange("lcmSteps", val)}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item title="Control Nets" defaultOpen>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: filterState.cnCanny,
|
||||
label: "Canny",
|
||||
thumbTip: "Canny",
|
||||
onChange: (val)=>{handleOnChange("cnCanny", val)}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: filterState.cnLinearAnime,
|
||||
label: "Line Art Anime",
|
||||
thumbTip: "Line Art Anime",
|
||||
onChange: (val)=>{handleOnChange("cnLinearAnime", val)}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: filterState.cnDepth,
|
||||
label: "Depth",
|
||||
thumbTip: "Depth",
|
||||
onChange: (val)=>{handleOnChange("cnDepth", val)}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: filterState.cnOpenPose,
|
||||
label: "OpenPose",
|
||||
thumbTip: "OpenPose",
|
||||
onChange: (val)=>{handleOnChange("cnOpenPose", val)}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: filterState.cnPipeFace,
|
||||
label: "Media Pipe Face",
|
||||
thumbTip: "Media Pipe Face",
|
||||
onChange: (val)=>{handleOnChange("cnPipeFace", val)}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: filterState.cnSparse,
|
||||
label: "Sparse Scribble",
|
||||
thumbTip: "Sparse Scribble",
|
||||
onChange: (val)=>{handleOnChange("cnSparse", val)}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: filterState.cnLinearRealistic,
|
||||
label: "Video CN (Linear Realistic)",
|
||||
thumbTip: "Video CN",
|
||||
onChange: (val)=>{handleOnChange("cnLinearRealistic", val)}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: filterState.cnTile,
|
||||
label: "Tile CN",
|
||||
thumbTip: "Tile CN",
|
||||
onChange: (val)=>{handleOnChange("cnTile", val)}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item title="Video Settings">
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<Input label="Width" />
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<Input label="Height" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<Input label="Frames Cap" />
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<Input label="Skip Frames" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<Input label="Every n-th Frame" />
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<Input label="Input FPS" readOnly/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<Input label="Interpolation Multiplier" />
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
</div>
|
||||
</div>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
<div className="row g-3 py-3">
|
||||
<div className="col-12 d-flex justify-content-end">
|
||||
<Button
|
||||
label={t("Process Filter")}
|
||||
onClick={handleGenerate}
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}else if (pageState.status <= states.FILE_LOADING){
|
||||
return (
|
||||
<Panel>
|
||||
<p>Loading Files</p>
|
||||
<Spinner />
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
return <ErrorMessage />;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
export function isInputValid(keyValues: {[key:string]:number|string|boolean|undefined}){
|
||||
return (
|
||||
keyValues.sdModelToken
|
||||
&& keyValues.sdModelToken !== ""
|
||||
&& keyValues.posPrompt
|
||||
&& keyValues.posPrompt !== ""
|
||||
);
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export { default } from "./pageWorkflowControls";
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
import React, {useState, useRef} from "react";
|
||||
import { useParams, useHistory } from "react-router-dom";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { faChevronRight } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useMedia } from "hooks";
|
||||
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import { EnqueueVideoWorkflow } from "@storyteller/components/src/api/video_workflow";
|
||||
import { states, Action, State } from "../../videoWorkflowReducer";
|
||||
import {
|
||||
Accordion,
|
||||
Button,
|
||||
BasicVideo,
|
||||
ErrorMessage,
|
||||
InputSeed,
|
||||
Panel,
|
||||
SelectModal,
|
||||
Spinner,
|
||||
TextArea
|
||||
} from "components/common";
|
||||
|
||||
import SectionControlNets, {ControlNetsInitialValues as CnIvs} from "./sectionControlNets";
|
||||
import SectionVideoSettings from "./sectionVideoSettings";
|
||||
import SectionAdvanceOptions, {AdvanceOptionsInitialValues as AdvIvs} from "./sectionAdvanceOptions";
|
||||
import { isInputValid } from "./helpers";
|
||||
|
||||
export default function PageFilterControls({
|
||||
debug, t, pageState, dispatchPageState, parentPath
|
||||
}: {
|
||||
debug?: boolean;
|
||||
t: Function;
|
||||
pageState: State;
|
||||
parentPath: string;
|
||||
dispatchPageState: (action: Action) => void;
|
||||
}) {
|
||||
const { mediaToken } = useParams<any>();
|
||||
const videoRef = useRef<HTMLVideoElement>(null);
|
||||
useMedia({
|
||||
mediaToken: pageState.mediaFileToken || mediaToken,
|
||||
onSuccess: (res: any) => {
|
||||
dispatchPageState({
|
||||
type: 'loadFileSuccess',
|
||||
payload: {
|
||||
mediaFile: res,
|
||||
mediaFileToken: pageState.mediaFileToken || mediaToken
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const [workflowValues, setworkflowValues] = useState({
|
||||
outputPath: "vid2vid/SparseUpscaleInterp_00001.mp4",
|
||||
workflowConfig: "weight_q8sz47gmfw2zx02snrbz88ns9",
|
||||
seed: "",
|
||||
sdModelToken: "",
|
||||
loraModelToken: "",
|
||||
posPrompt: "",
|
||||
negPrompt: "",
|
||||
framesCap:16,
|
||||
skipFrames:0,
|
||||
nthFrames:2,
|
||||
inputFps: 24,
|
||||
interpolationMutiplier: 2,
|
||||
...AdvIvs,
|
||||
...CnIvs
|
||||
});
|
||||
|
||||
const handleOnChange = (key: string, newValue:any,) => {
|
||||
setworkflowValues((curr)=>({...curr, [key]: newValue}));
|
||||
}
|
||||
|
||||
const history = useHistory();
|
||||
const handleGenerate = ()=>{
|
||||
if(debug) console.log(workflowValues)
|
||||
|
||||
if (isInputValid(workflowValues)){
|
||||
const request = {
|
||||
"uuid_idempotency_token": uuidv4(),
|
||||
"maybe_sd_model": workflowValues.sdModelToken,
|
||||
"maybe_workflow_config": workflowValues.workflowConfig,
|
||||
"maybe_input_file": pageState.mediaFileToken || mediaToken,
|
||||
"maybe_output_path": workflowValues.outputPath,
|
||||
"maybe_json_modifications": {
|
||||
"$.510.inputs.Text": workflowValues.posPrompt,
|
||||
"$.8.inputs.text": workflowValues.negPrompt,
|
||||
"$.173.inputs.seed": workflowValues.seed,
|
||||
"$.401.inputs.Value": workflowValues.firstPass,
|
||||
"$.918.inputs.Value": workflowValues.motionScale,
|
||||
"$.137.inputs.Value": workflowValues.framesCap,
|
||||
"$.186.inputs.Value": workflowValues.skipFrames,
|
||||
"$.140.inputs.Value": workflowValues.nthFrames,
|
||||
"$.154.inputs.Value": workflowValues.inputFps,
|
||||
"$.445.inputs.number": workflowValues.interpolationMutiplier,
|
||||
"$.947.inputs.Value": workflowValues.cnTile,
|
||||
"$.800.inputs.Value": workflowValues.cnCanny,
|
||||
"$.797.inputs.Value": workflowValues.cnLinearAnime,
|
||||
"$.796.inputs.Value": workflowValues.cnLinearRealistic,
|
||||
"$.772.inputs.Value": workflowValues.cnDepth,
|
||||
"$.771.inputs.Value": workflowValues.cnOpenPose,
|
||||
"$.527.inputs.Value": workflowValues.cnPipeFace,
|
||||
"$.403.inputs.Value": workflowValues.cnSparse
|
||||
},
|
||||
}
|
||||
EnqueueVideoWorkflow(request).then(res => {
|
||||
if (res.success && res.inference_job_token) {
|
||||
dispatchPageState({
|
||||
type: 'enqueueFilterSuccess',
|
||||
payload: {
|
||||
inferenceJobToken: res.inference_job_token
|
||||
}
|
||||
})
|
||||
}else{
|
||||
console.log(res);
|
||||
}
|
||||
})
|
||||
dispatchPageState({type: 'enqueueFilter'})
|
||||
history.push(`${parentPath}/jobs`);
|
||||
}else{
|
||||
alert("you must pick an sd weight and input a prompt");
|
||||
}
|
||||
}
|
||||
|
||||
if (pageState.mediaFile){
|
||||
const mediaLink = new BucketConfig().getGcsUrl(pageState.mediaFile.public_bucket_path);
|
||||
|
||||
if (mediaLink)
|
||||
return(
|
||||
<>
|
||||
<Panel>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-5">
|
||||
<div className="fy-basic-video">
|
||||
<video controls ref={videoRef}>
|
||||
<source src={mediaLink} type="video/mp4"/>
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-2 d-flex align-items-center justify-content-center">
|
||||
<FontAwesomeIcon icon={faChevronRight} className="fa-7x"/>
|
||||
</div>
|
||||
<div className="col-5">
|
||||
<BasicVideo src={mediaLink} />
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
<Accordion className="mt-4">
|
||||
<Accordion.Item title="Video Settings">
|
||||
<SectionVideoSettings
|
||||
onChange={(key,val)=>handleOnChange(key,val)}
|
||||
videoElement={videoRef?.current}
|
||||
/>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item title={"Basics"} defaultOpen>
|
||||
<div className="row g-3 p-3">
|
||||
<SelectModal
|
||||
modalTitle="Select a Stable Diffusion Weight"
|
||||
label="Select a Stable Diffusion Weight"
|
||||
onSelect={({token})=>{
|
||||
handleOnChange("sdModelToken", token);
|
||||
}}
|
||||
tabs={[
|
||||
{
|
||||
label: "All Weights",
|
||||
tabKey: "allWeights",
|
||||
typeFilter: "sd_1.5",
|
||||
searcher: true,
|
||||
type: "weights",
|
||||
},
|
||||
{
|
||||
label: "Bookmarked",
|
||||
tabKey: "bookmarkedWeights",
|
||||
typeFilter: "sd_1.5",
|
||||
searcher: false,
|
||||
type: "weights",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<SelectModal
|
||||
modalTitle="Select a LoRA Weight"
|
||||
label="Additional LoRA Weight"
|
||||
onSelect={({token})=>{
|
||||
console.log(`calling from select modal result select ${token}`);
|
||||
handleOnChange("loraModelToken", token);
|
||||
}}
|
||||
tabs={[
|
||||
{
|
||||
label: "All LoRA Weights",
|
||||
tabKey: "allLoraWeights",
|
||||
typeFilter: "rvc_v2",
|
||||
searcher: true,
|
||||
type: "weights",
|
||||
},
|
||||
{
|
||||
label: "Bookmarked",
|
||||
tabKey: "bookmarkedLoraWeights",
|
||||
typeFilter: "rvc_v2",
|
||||
searcher: false,
|
||||
type: "weights",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<TextArea
|
||||
{...{
|
||||
label: "Prompt",
|
||||
placeholder: "Enter a prompt",
|
||||
onChange: (e:React.ChangeEvent<HTMLTextAreaElement>)=>handleOnChange("posPrompt", e.target.value),
|
||||
value: workflowValues.posPrompt,
|
||||
required: false,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<TextArea
|
||||
{...{
|
||||
label: "Negative Prompt",
|
||||
placeholder: "Enter Negative Prompt",
|
||||
onChange: (e:React.ChangeEvent<HTMLTextAreaElement>)=>handleOnChange("negPrompt", e.target.value),
|
||||
value: workflowValues.negPrompt,
|
||||
required: false,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<InputSeed label="Seed" onChange={
|
||||
(val:string)=>handleOnChange("seed", val)
|
||||
}/>
|
||||
</div>
|
||||
|
||||
</Accordion.Item>
|
||||
<Accordion.Item title="Advance" defaultOpen>
|
||||
<SectionAdvanceOptions
|
||||
workflowValues={workflowValues}
|
||||
onChange={(key,val)=>handleOnChange(key,val)}
|
||||
/>
|
||||
</Accordion.Item>
|
||||
<Accordion.Item title="Control Nets" defaultOpen>
|
||||
<SectionControlNets
|
||||
workflowValues={workflowValues}
|
||||
onChange={(key,val)=>handleOnChange(key,val)}
|
||||
/>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
<div className="row g-3 py-3">
|
||||
<div className="col-12 d-flex justify-content-end">
|
||||
<Button
|
||||
label={t("Process Filter")}
|
||||
onClick={handleGenerate}
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}else if (pageState.status <= states.FILE_LOADING){
|
||||
return (
|
||||
<Panel>
|
||||
<p>Loading Files</p>
|
||||
<Spinner />
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
return <ErrorMessage />;
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
import React, { memo } from 'react'
|
||||
import { NumberSliderV2, Checkbox } from 'components/common'
|
||||
|
||||
export const AdvanceOptionsInitialValues = {
|
||||
firstPass: 1,
|
||||
upscalePass: 0.42,
|
||||
motionScale:1,
|
||||
upscaleMultiplier: 1.5,
|
||||
useEmptyLatent: false,
|
||||
useFaceDetailer: false,
|
||||
denoiseFaceDetailer: 0.45,
|
||||
useLCM: false,
|
||||
lcmCFG: 2,
|
||||
lcmSteps: 8,
|
||||
}
|
||||
|
||||
export default memo( function SectionAdvanceOptions({
|
||||
workflowValues: wfVal,
|
||||
onChange : handleOnChange
|
||||
} : {
|
||||
workflowValues : any
|
||||
onChange: (key:string, val:number|boolean)=>void}
|
||||
){
|
||||
|
||||
return(
|
||||
<>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0.1, max: 1, step: 0.01,
|
||||
initialValue: wfVal.firstPass,
|
||||
label: " Denoise First Pass",
|
||||
thumbTip: "Denoise First Pass",
|
||||
onChange: (val)=>{handleOnChange("firstPass",val)}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0.1, max: 1, step: 0.01,
|
||||
initialValue: wfVal.upscalePass,
|
||||
label: "Denoise Upscale Pass",
|
||||
thumbTip: "Denoise Upscale Pass",
|
||||
onChange: (val)=>{handleOnChange("upscalePass", val)}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0.25, max: 2, step: 0.05,
|
||||
initialValue: wfVal.motionScale,
|
||||
label: "Motion Scale",
|
||||
thumbTip: "Motion Scale",
|
||||
onChange: (val)=>{handleOnChange("motionScale", val)}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 1.5, max: 2, step: 0.5,
|
||||
initialValue: wfVal.upscaleMultiplier,
|
||||
label: "Upscale Multiplier",
|
||||
thumbTip: "Upscale Multiplier",
|
||||
onChange: (val)=>{handleOnChange("upscaleMultiplier", val)}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<Checkbox
|
||||
label="Use Empty Latent"
|
||||
checked={wfVal.useEmptyLatent}
|
||||
onChange={(e:{target:{ checked: boolean, name:string, type: string }})=>{handleOnChange("useEmptyLatent", e.target.checked)}}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<Checkbox
|
||||
label="Use Face Detailer"
|
||||
checked={wfVal.useFaceDetailer}
|
||||
onChange={(e:{target:{ checked: boolean, name:string, type: string }})=>{handleOnChange("useFaceDetailer", e.target.checked)}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0.1, max: 0.7, step: 0.01,
|
||||
initialValue: wfVal.denoiseFaceDetailer,
|
||||
label: "Denoise Face Detailer",
|
||||
thumbTip: "Denoise Face Detailer",
|
||||
onChange: (val)=>{handleOnChange("denoiseFaceDetailer", val)}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<Checkbox
|
||||
label="Use LCM"
|
||||
checked={wfVal.useLCM}
|
||||
onChange={(e:{target:{ checked: boolean, name:string, type: string }})=>{handleOnChange("useLCM", e.target.checked)}}
|
||||
/>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 1, max: 10, step: 1,
|
||||
initialValue: wfVal.lcmCFG,
|
||||
label: "LCM CFG",
|
||||
thumbTip: "LCM CFG",
|
||||
onChange: (val)=>{handleOnChange("lcmCFG", val)}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 5, max: 15, step: 1,
|
||||
initialValue: wfVal.lcmSteps,
|
||||
label: "LCM Steps",
|
||||
thumbTip: "LCM Steps",
|
||||
onChange: (val)=>{handleOnChange("lcmSteps", val)}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
import React, { memo } from 'react'
|
||||
import { NumberSliderV2 } from 'components/common'
|
||||
|
||||
export const ControlNetsInitialValues = {
|
||||
cnCanny: 0,
|
||||
cnDepth: 0,
|
||||
cnLinearAnime: 0,
|
||||
cnLinearRealistic: 0,
|
||||
cnOpenPose: 0,
|
||||
cnPipeFace: 0,
|
||||
cnSparse: 0.7,
|
||||
cnTile: 0,
|
||||
}
|
||||
|
||||
export default memo( function SectionControlNets({
|
||||
workflowValues: wfVal,
|
||||
onChange : handleOnChange
|
||||
} : {
|
||||
workflowValues : any
|
||||
onChange: (key:string, val:number)=>void}
|
||||
){
|
||||
|
||||
return(
|
||||
<>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: wfVal.cnCanny,
|
||||
label: "Canny",
|
||||
thumbTip: "Canny",
|
||||
onChange: (val)=>{handleOnChange("cnCanny", val)}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: wfVal.cnLinearAnime,
|
||||
label: "Line Art Anime",
|
||||
thumbTip: "Line Art Anime",
|
||||
onChange: (val)=>{handleOnChange("cnLinearAnime", val)}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: wfVal.cnDepth,
|
||||
label: "Depth",
|
||||
thumbTip: "Depth",
|
||||
onChange: (val)=>{handleOnChange("cnDepth", val)}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: wfVal.cnOpenPose,
|
||||
label: "OpenPose",
|
||||
thumbTip: "OpenPose",
|
||||
onChange: (val)=>{handleOnChange("cnOpenPose", val)}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: wfVal.cnPipeFace,
|
||||
label: "Media Pipe Face",
|
||||
thumbTip: "Media Pipe Face",
|
||||
onChange: (val)=>{handleOnChange("cnPipeFace", val)}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: wfVal.cnSparse,
|
||||
label: "Sparse Scribble",
|
||||
thumbTip: "Sparse Scribble",
|
||||
onChange: (val)=>{handleOnChange("cnSparse", val)}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: wfVal.cnLinearRealistic,
|
||||
label: "Video CN (Linear Realistic)",
|
||||
thumbTip: "Video CN",
|
||||
onChange: (val)=>{handleOnChange("cnLinearRealistic", val)}
|
||||
}}/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<NumberSliderV2 {...{
|
||||
min: 0, max: 1, step: 0.1,
|
||||
initialValue: wfVal.cnTile,
|
||||
label: "Tile CN",
|
||||
thumbTip: "Tile CN",
|
||||
onChange: (val)=>{handleOnChange("cnTile", val)}
|
||||
}}/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import React, { memo, useState } from 'react'
|
||||
|
||||
import { TempInput as Input } from 'components/common'
|
||||
|
||||
export const VideoSettingsInitialValues = {
|
||||
width: 544,
|
||||
height: 544,
|
||||
framesCap: 16,
|
||||
skipFrames: 0,
|
||||
everyNthFrame: 2,
|
||||
inputFps: 24,
|
||||
interpolationMultiplier: 2,
|
||||
}
|
||||
|
||||
export default memo( function SectionVideoSettings({
|
||||
onChange : handleOnChange,
|
||||
videoElement: ve
|
||||
} : {
|
||||
onChange: (key:string, val:number)=>void,
|
||||
videoElement: HTMLVideoElement | null,
|
||||
}){
|
||||
const iv = VideoSettingsInitialValues;
|
||||
const [{width, height}, setDimensions] = useState({
|
||||
width:iv.width, height:iv.height
|
||||
});
|
||||
if(ve && ve!==null){
|
||||
ve.onloadedmetadata = () =>{
|
||||
if (ve.videoWidth && ve.videoHeight) {
|
||||
const aspectRatio = ve.videoWidth/ve.videoHeight
|
||||
if (aspectRatio > 1){
|
||||
setDimensions({width: 960, height: iv.height});
|
||||
}
|
||||
else if (aspectRatio < 1) {
|
||||
setDimensions({width: iv.width, height: 960});
|
||||
}else{
|
||||
console.log(`aspectRaio: ${aspectRatio}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return(
|
||||
<>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<Input label="Width" value={width} readOnly/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<Input label="Height" value={height} readOnly/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<Input label="Frames Cap" value={iv.framesCap} readOnly/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<Input label="Skip Frames" value={iv.skipFrames} readOnly/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<Input label="Every n-th Frame" value={iv.everyNthFrame} readOnly />
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<Input label="Input FPS" value={iv.inputFps} readOnly/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-3 p-3">
|
||||
<div className="col-md-6">
|
||||
<Input
|
||||
label="Interpolation Multiplier"
|
||||
value={iv.interpolationMultiplier}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
});
|
||||
+5
-5
@@ -6,7 +6,7 @@ import { FrontendInferenceJobType } from "@storyteller/components/src/jobs/Infer
|
||||
import { states, Action, State } from "../videoWorkflowReducer";
|
||||
import { Spinner } from 'components/common';
|
||||
|
||||
function StorytellerJobList(){
|
||||
function VideoWorkflowJobList(){
|
||||
const failures = (fail = "") => {
|
||||
switch (fail) {
|
||||
case "sample case":
|
||||
@@ -20,12 +20,12 @@ function StorytellerJobList(){
|
||||
<InferenceJobsList {...{
|
||||
failures,
|
||||
onSelect: () => Analytics.voiceConversionClickDownload(),
|
||||
jobType: FrontendInferenceJobType.StorytellerFilter,
|
||||
jobType: FrontendInferenceJobType.VideoWorkflow,
|
||||
}}/>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo (function PageJobList({
|
||||
export default memo (function PageWorkflowJoblist({
|
||||
t, pageState, dispatchPageState
|
||||
}: {
|
||||
debug?: boolean;
|
||||
@@ -37,13 +37,13 @@ export default memo (function PageJobList({
|
||||
return(
|
||||
<>
|
||||
<h1>Jobs</h1>
|
||||
{pageState.status === states.FILTER_ENQUEUEING &&
|
||||
{pageState.status === states.WORKFLOW_ENQUEUEING &&
|
||||
<div>
|
||||
<h2> Requesting Filter Job</h2>
|
||||
<Spinner />
|
||||
</div>
|
||||
}
|
||||
<StorytellerJobList />
|
||||
<VideoWorkflowJobList />
|
||||
</>
|
||||
);
|
||||
});
|
||||
+25
-20
@@ -2,7 +2,7 @@ import React from "react";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { states, Action, State } from "../videoWorkflowReducer";
|
||||
import { Button } from "components/common";
|
||||
import SelectModal from "components/common/SelectModal/SelectModal";
|
||||
import SelectModal, { SelectModalData } from "components/common/SelectModal/SelectModal";
|
||||
|
||||
export default function TabContentLibrary({
|
||||
t, pageState, dispatchPageState
|
||||
@@ -14,29 +14,34 @@ export default function TabContentLibrary({
|
||||
const handleProceed = ()=>{
|
||||
dispatchPageState({type: "loadFile"})
|
||||
}
|
||||
const handleOnSelect = (data:{token:string, title:string})=>{
|
||||
dispatchPageState({
|
||||
type: "selectedFile",
|
||||
payload: {mediaFileToken: data.token}
|
||||
});
|
||||
const handleOnSelect = (data:SelectModalData)=>{
|
||||
if (data.token !== ""){
|
||||
dispatchPageState({
|
||||
type: "selectedFile",
|
||||
payload: {mediaFileToken: data.token}
|
||||
});
|
||||
}else{
|
||||
dispatchPageState({type: "clearedFile"});
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="row g-3">
|
||||
<div className="col-12">
|
||||
<SelectModal
|
||||
modalTitle="Select a Video"
|
||||
label="Select a Video"
|
||||
onSelect={handleOnSelect}
|
||||
tabs={[
|
||||
{
|
||||
label: "All Videos",
|
||||
tabKey: "allVideos",
|
||||
mediaTypeFilter: "video",
|
||||
searcher: false,
|
||||
type: "media",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<SelectModal
|
||||
modalTitle="Select a Video"
|
||||
label="Select a Video"
|
||||
onSelect={handleOnSelect}
|
||||
tabs={[
|
||||
{
|
||||
label: "All Videos",
|
||||
tabKey: "allVideos",
|
||||
typeFilter: "video",
|
||||
searcher: false,
|
||||
type: "media",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
{pageState.status === states.FILE_SELECTED &&
|
||||
<div className="col-12 d-flex justify-content-center mt-5">
|
||||
|
||||
+5
-2
@@ -16,7 +16,7 @@ export default function TabContentUpload({
|
||||
dispatchPageState: (action: Action) => void;
|
||||
}) {
|
||||
const videoProps = useFile({});
|
||||
const {NO_FILE, FILE_STAGED, FILE_UPLOADING, FILE_UPLOADED} = states;
|
||||
const {NO_FILE, FILE_STAGED, FILE_SELECTED, FILE_UPLOADING, FILE_UPLOADED} = states;
|
||||
|
||||
const makeVideoUploadRequest = () => ({
|
||||
uuid_idempotency_token: uuidv4(),
|
||||
@@ -40,7 +40,10 @@ export default function TabContentUpload({
|
||||
};
|
||||
|
||||
// contains upload inout state and controls, see docs
|
||||
if (pageState.status === NO_FILE || pageState.status === FILE_STAGED) {
|
||||
if (pageState.status === NO_FILE
|
||||
|| pageState.status === FILE_STAGED
|
||||
|| pageState.status === FILE_SELECTED
|
||||
) {
|
||||
return (
|
||||
<>
|
||||
<div className="row">
|
||||
|
||||
@@ -8,8 +8,8 @@ export enum states{
|
||||
FILE_UPLOADED,
|
||||
FILE_LOADING,
|
||||
FILE_LOADED,
|
||||
FILTER_ENQUEUEING,
|
||||
FILTER_ENQUEUED
|
||||
WORKFLOW_ENQUEUEING,
|
||||
WORKFLOW_ENQUEUED
|
||||
}
|
||||
|
||||
export type State = {
|
||||
@@ -20,6 +20,7 @@ export type State = {
|
||||
}
|
||||
|
||||
export type Action =
|
||||
| {type: 'reset'}
|
||||
| {type: 'stagedFile'}
|
||||
| {type: 'clearedFile'}
|
||||
| {type: 'selectedFile', payload: { mediaFileToken: string}}
|
||||
@@ -67,14 +68,15 @@ export function reducer (state: State, action: Action): State {
|
||||
case 'enqueueFilter':
|
||||
return {
|
||||
...state,
|
||||
status: states.FILTER_ENQUEUEING
|
||||
status: states.WORKFLOW_ENQUEUEING
|
||||
}
|
||||
case 'enqueueFilterSuccess':
|
||||
return {
|
||||
...state,
|
||||
status: states.FILTER_ENQUEUED,
|
||||
status: states.WORKFLOW_ENQUEUED,
|
||||
inferenceJobToken: action.payload.inferenceJobToken
|
||||
}
|
||||
case 'reset':
|
||||
default:
|
||||
return {status: states.NO_FILE};
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React from "react";
|
||||
import {Switch, Route, useRouteMatch, Redirect} from "react-router-dom";
|
||||
import {Switch, Route, useRouteMatch, Redirect, useHistory} from "react-router-dom";
|
||||
import PageWorkflowControls from "./components/pageWorkflowControls";
|
||||
import PageVideoProvision from "./components/pageVideoProvision";
|
||||
import PageJobList from "./components/pageJobList";
|
||||
import {State,Action} from "./videoWorkflowReducer";
|
||||
import PageWorkflowJoblist from "./components/pageWorkflowJoblist";
|
||||
import {states,State,Action} from "./videoWorkflowReducer";
|
||||
|
||||
export default function SubRoutes ({
|
||||
debug, t, pageState, dispatchPageState
|
||||
@@ -14,6 +14,15 @@ export default function SubRoutes ({
|
||||
dispatchPageState: (action: Action) => void
|
||||
}){
|
||||
const { path } = useRouteMatch();
|
||||
const history = useHistory();
|
||||
history.listen(({pathname}, action) => {
|
||||
if ((pathname === path || pathname.match(/upload|select-media/g)) && pageState.status >= states.FILE_LOADING){
|
||||
//reset page if landed on page "Freshly"
|
||||
dispatchPageState({type: 'reset'})
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const commonPageProps = {
|
||||
debug, parentPath: path, t, pageState, dispatchPageState
|
||||
};
|
||||
@@ -30,7 +39,7 @@ export default function SubRoutes ({
|
||||
<PageVideoProvision {...commonPageProps}/>
|
||||
</Route>
|
||||
<Route exact path={`${path}/jobs`}>
|
||||
<PageJobList {...commonPageProps}/>
|
||||
<PageWorkflowJoblist {...commonPageProps}/>
|
||||
</Route>
|
||||
<Route path={`${path}`}>
|
||||
<Redirect to={`${path}/upload`} />
|
||||
|
||||
@@ -48,10 +48,11 @@ export default function WeightEditPage({
|
||||
update,
|
||||
visibility,
|
||||
writeStatus,
|
||||
// status
|
||||
} = useWeightFetch({ token: weight_token });
|
||||
|
||||
const basePath =
|
||||
weight?.cover_image.maybe_cover_image_public_bucket_path || "";
|
||||
weight?.cover_image?.maybe_cover_image_public_bucket_path || "";
|
||||
const currentPath = basePath ? new BucketConfig().getGcsUrl(basePath) : "";
|
||||
|
||||
usePrefixedDocumentTitle("Edit Voice");
|
||||
@@ -63,7 +64,21 @@ export default function WeightEditPage({
|
||||
|
||||
let weightToken = weight?.creator?.user_token;
|
||||
|
||||
if (!weightToken || !canEditTtsModel(user?.user_token || "")) {
|
||||
if (isLoading) {
|
||||
return <Panel padding={true}>
|
||||
<div className="d-flex flex-column gap-3">
|
||||
<Skeleton type="short" />
|
||||
<Skeleton height="40px" />
|
||||
<Skeleton type="short" />
|
||||
<Skeleton height="40px" />
|
||||
<div className="d-flex justify-content-end mt-3 gap-2">
|
||||
<Skeleton height="40px" width="120px" />
|
||||
<Skeleton height="40px" width="120px" />
|
||||
</div>
|
||||
</div>
|
||||
</Panel>;
|
||||
} else {
|
||||
if (!weightToken) {
|
||||
return (
|
||||
<Container type="panel">
|
||||
<PageHeader
|
||||
@@ -85,6 +100,28 @@ export default function WeightEditPage({
|
||||
);
|
||||
}
|
||||
|
||||
if (!canEditTtsModel(user?.user_token || "")) {
|
||||
return (
|
||||
<Container type="panel">
|
||||
<PageHeader
|
||||
titleIcon={faCircleExclamation}
|
||||
title="Access Denied"
|
||||
subText="You do not have permission to edit this model."
|
||||
panel={true}
|
||||
extension={
|
||||
<div className="d-flex">
|
||||
<Button
|
||||
label="Back to homepage"
|
||||
to={`/weight/{}`}
|
||||
className="d-flex"
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Container type="panel" className="mb-5">
|
||||
<PageHeader
|
||||
@@ -96,89 +133,72 @@ export default function WeightEditPage({
|
||||
backbuttonLabel="Back"
|
||||
backbuttonTo={`/weight/${weight_token}`}
|
||||
/>
|
||||
|
||||
<>
|
||||
{isLoading ? (
|
||||
<Panel padding={true}>
|
||||
<div className="d-flex flex-column gap-3">
|
||||
<Skeleton type="short" />
|
||||
<Skeleton height="40px" />
|
||||
<Skeleton type="short" />
|
||||
<Skeleton height="40px" />
|
||||
<div className="d-flex justify-content-end mt-3 gap-2">
|
||||
<Skeleton height="40px" width="120px" />
|
||||
<Skeleton height="40px" width="120px" />
|
||||
<SplitPanel {...{ busy: writeStatus > 0, dividerFooter: true }}>
|
||||
<SplitPanel.Body padding={true}>
|
||||
<div {...{ className: "weight-editor row gy-3 gx-4" }}>
|
||||
<div {...{ className: "col-12 col-lg-5" }}>
|
||||
<label className="sub-title">Cover Image</label>
|
||||
<CoverImageInput
|
||||
{...{
|
||||
currentPath,
|
||||
onClick: coverImg.upload,
|
||||
status: coverImg.status,
|
||||
...coverImg.fileProps,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div {...{ className: "col-lg-7 order-first order-lg-last" }}>
|
||||
<TempInput
|
||||
{...{
|
||||
label: "Title",
|
||||
name: "title",
|
||||
onChange,
|
||||
placeholder: "Title",
|
||||
value: title,
|
||||
}}
|
||||
/>
|
||||
<TempSelect
|
||||
{...{
|
||||
icon: faEye,
|
||||
label: "Visibility",
|
||||
name: "visibility",
|
||||
options: visibilityOptions,
|
||||
onChange,
|
||||
placeholder: "Voice name",
|
||||
value: visibility,
|
||||
}}
|
||||
/>
|
||||
<TempTextArea
|
||||
{...{
|
||||
label: "Description",
|
||||
name: "descriptionMD",
|
||||
onChange,
|
||||
placeholder: "Description",
|
||||
value: descriptionMD,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Panel>
|
||||
) : (
|
||||
<SplitPanel {...{ busy: writeStatus > 0, dividerFooter: true }}>
|
||||
<SplitPanel.Body padding={true}>
|
||||
<div {...{ className: "weight-editor row gy-3 gx-4" }}>
|
||||
<div {...{ className: "col-12 col-lg-5" }}>
|
||||
<label className="sub-title">Cover Image</label>
|
||||
<CoverImageInput
|
||||
{...{
|
||||
currentPath,
|
||||
onClick: coverImg.upload,
|
||||
status: coverImg.status,
|
||||
...coverImg.fileProps,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div {...{ className: "col-lg-7 order-first order-lg-last" }}>
|
||||
<TempInput
|
||||
{...{
|
||||
label: "Title",
|
||||
name: "title",
|
||||
onChange,
|
||||
placeholder: "Title",
|
||||
value: title,
|
||||
}}
|
||||
/>
|
||||
<TempSelect
|
||||
{...{
|
||||
icon: faEye,
|
||||
label: "Visibility",
|
||||
name: "visibility",
|
||||
options: visibilityOptions,
|
||||
onChange,
|
||||
placeholder: "Voice name",
|
||||
value: visibility,
|
||||
}}
|
||||
/>
|
||||
<TempTextArea
|
||||
{...{
|
||||
label: "Description",
|
||||
name: "descriptionMD",
|
||||
onChange,
|
||||
placeholder: "Description",
|
||||
value: descriptionMD,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SplitPanel.Body>
|
||||
<SplitPanel.Footer padding={true}>
|
||||
<div className="d-flex gap-2 justify-content-end">
|
||||
<Button
|
||||
{...{
|
||||
label: "Cancel",
|
||||
to: `/weight/${weight_token}`,
|
||||
variant: "secondary",
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
{...{
|
||||
label: "Save Changes",
|
||||
onClick: update,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</SplitPanel.Footer>
|
||||
</SplitPanel>
|
||||
)}
|
||||
</>
|
||||
</SplitPanel.Body>
|
||||
<SplitPanel.Footer padding={true}>
|
||||
<div className="d-flex gap-2 justify-content-end">
|
||||
<Button
|
||||
{...{
|
||||
label: "Cancel",
|
||||
to: `/weight/${weight_token}`,
|
||||
variant: "secondary",
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
{...{
|
||||
label: "Save Changes",
|
||||
onClick: update,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</SplitPanel.Footer>
|
||||
</SplitPanel>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -35,8 +35,11 @@ import WeightCoverImage from "components/common/WeightCoverImage";
|
||||
import { BucketConfig } from "@storyteller/components/src/api/BucketConfig";
|
||||
import SdInferencePanel from "./inference_panels/SdInferencePanel";
|
||||
import SdCoverImagePanel from "./cover_image_panels/SdCoverImagePanel";
|
||||
import { StudioNotAvailable } from "v2/view/_common/StudioNotAvailable";
|
||||
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
|
||||
|
||||
interface WeightProps {
|
||||
sessionWrapper: SessionWrapper;
|
||||
sessionSubscriptionsWrapper: SessionSubscriptionsWrapper;
|
||||
inferenceJobs: Array<InferenceJob>;
|
||||
ttsInferenceJobs: Array<TtsInferenceJob>;
|
||||
@@ -49,6 +52,7 @@ interface WeightProps {
|
||||
}
|
||||
|
||||
export default function WeightPage({
|
||||
sessionWrapper,
|
||||
sessionSubscriptionsWrapper,
|
||||
inferenceJobs,
|
||||
ttsInferenceJobs,
|
||||
@@ -100,6 +104,15 @@ export default function WeightPage({
|
||||
|
||||
const deleteWeight = () => remove(!!user?.can_ban_users);
|
||||
|
||||
switch (weight?.weight_type) {
|
||||
case WeightType.SD_15:
|
||||
case WeightType.SDXL:
|
||||
case WeightType.LORA:
|
||||
if (!sessionWrapper.canAccessStudio()) {
|
||||
return <StudioNotAvailable />
|
||||
}
|
||||
}
|
||||
|
||||
function renderWeightComponent(weight: Weight) {
|
||||
switch (weight.weight_category) {
|
||||
case WeightCategory.TTS:
|
||||
|
||||
@@ -299,14 +299,14 @@ function SdInferencePanel({
|
||||
{
|
||||
label: "All Weights",
|
||||
tabKey: "allWeights",
|
||||
weightTypeFilter: "sd_1.5",
|
||||
typeFilter: "sd_1.5",
|
||||
searcher: true,
|
||||
type: "weights",
|
||||
},
|
||||
{
|
||||
label: "Bookmarked",
|
||||
tabKey: "bookmarkedWeights",
|
||||
weightTypeFilter: "sd_1.5",
|
||||
typeFilter: "sd_1.5",
|
||||
searcher: false,
|
||||
type: "weights",
|
||||
},
|
||||
@@ -409,14 +409,14 @@ function SdInferencePanel({
|
||||
{
|
||||
label: "All LoRA Weights",
|
||||
tabKey: "allLoraWeights",
|
||||
weightTypeFilter: "rvc_v2",
|
||||
typeFilter: "rvc_v2",
|
||||
searcher: true,
|
||||
type: "weights",
|
||||
},
|
||||
{
|
||||
label: "Bookmarked",
|
||||
tabKey: "bookmarkedLoraWeights",
|
||||
weightTypeFilter: "rvc_v2",
|
||||
typeFilter: "rvc_v2",
|
||||
searcher: false,
|
||||
type: "weights",
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user