route video requets (not tested)
This commit is contained in:
parent
59260258e7
commit
a41d4ed204
|
|
@ -0,0 +1,58 @@
|
|||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
|
||||
use crate::commands::enqueue::generate_error::{CredentialProblemReason, GenerateError};
|
||||
use crate::credentials::credential::Credential;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
|
||||
/// Resolve the stored credential a generation request names via
|
||||
/// `credential_id`. Every failure is a [`GenerateError::CredentialProblem`],
|
||||
/// which flashes the frontend's dismissable credential-error modal.
|
||||
pub fn resolve_generation_credential(
|
||||
maybe_credential_id: Option<&str>,
|
||||
app_data_root: &AppDataRoot,
|
||||
) -> Result<Credential, GenerateError> {
|
||||
let credential_id = maybe_credential_id
|
||||
.filter(|id| !id.trim().is_empty())
|
||||
.ok_or(GenerateError::CredentialProblem(
|
||||
CredentialProblemReason::NoCredentialSupplied,
|
||||
))?;
|
||||
|
||||
let maybe_credential = app_data_root
|
||||
.credentials_dir()
|
||||
.find_credential_by_id(credential_id)
|
||||
.map_err(GenerateError::from)?;
|
||||
|
||||
maybe_credential.ok_or_else(|| {
|
||||
GenerateError::CredentialProblem(CredentialProblemReason::CredentialNotFound {
|
||||
credential_id: credential_id.to_string(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Rebuild the web-session credential set from a stored cookie credential.
|
||||
pub fn storyteller_creds_from_credential(
|
||||
credential: &Credential,
|
||||
) -> Result<StorytellerCredentialSet, GenerateError> {
|
||||
let cookie = credential.cookies().ok_or_else(|| {
|
||||
credential_not_usable(credential, "the account has no session cookies")
|
||||
})?;
|
||||
|
||||
StorytellerCredentialSet::parse_multi_cookie_header(&cookie.cookie_header)
|
||||
.map_err(|err| {
|
||||
credential_not_usable(
|
||||
credential,
|
||||
&format!("the stored session cookies could not be parsed ({})", err),
|
||||
)
|
||||
})?
|
||||
.filter(|creds| !creds.is_empty())
|
||||
.ok_or_else(|| {
|
||||
credential_not_usable(credential, "the stored session cookies are empty")
|
||||
})
|
||||
}
|
||||
|
||||
pub fn credential_not_usable(credential: &Credential, reason: &str) -> GenerateError {
|
||||
GenerateError::CredentialProblem(CredentialProblemReason::CredentialNotUsable {
|
||||
credential_id: credential.id.to_string(),
|
||||
reason: reason.to_string(),
|
||||
})
|
||||
}
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
pub mod generation_credential;
|
||||
pub mod router_image_request_to_artcraft_prompt;
|
||||
pub mod router_video_request_to_artcraft_prompt;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use artcraft_client::utils::api_host::ApiHost;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use log::{info, warn};
|
||||
|
|
@ -15,8 +13,9 @@ use tokens::tokens::media_files::MediaFileToken;
|
|||
|
||||
use crate::api_adapters::models::image::tauri_image_model_to_generation_model::tauri_image_model_to_generation_model;
|
||||
use crate::api_adapters::models::image::tauri_image_model_to_router_model::tauri_image_model_to_router_model;
|
||||
use crate::commands::enqueue::generate_error::{CredentialProblemReason, GenerateError};
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::common::generation_credential::{credential_not_usable, resolve_generation_credential, storyteller_creds_from_credential};
|
||||
use crate::commands::generate::generate_image::providers::router::handle_api_providers::handle_api_key_provider;
|
||||
use crate::commands::generate::generate_image::providers::router::utils::convert_enums_to_router::{convert_aspect_ratio, convert_quality, convert_resolution};
|
||||
use crate::commands::generate::generate_image::tauri_generate_image_request::TauriGenerateImageRequest;
|
||||
|
|
@ -37,7 +36,10 @@ pub async fn handle_credential_router(
|
|||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
let credential = resolve_credential(request, app_data_root)?;
|
||||
let credential = resolve_generation_credential(
|
||||
request.credential_id.as_deref(),
|
||||
app_data_root,
|
||||
)?;
|
||||
|
||||
info!(
|
||||
"handle_credential_router: credential={} service={}",
|
||||
|
|
@ -70,29 +72,6 @@ pub async fn handle_credential_router(
|
|||
}
|
||||
}
|
||||
|
||||
/// Require and load the credential named by the request.
|
||||
fn resolve_credential(
|
||||
request: &TauriGenerateImageRequest,
|
||||
app_data_root: &AppDataRoot,
|
||||
) -> Result<Credential, GenerateError> {
|
||||
let credential_id = request.credential_id.as_deref()
|
||||
.filter(|id| !id.trim().is_empty())
|
||||
.ok_or(GenerateError::CredentialProblem(
|
||||
CredentialProblemReason::NoCredentialSupplied,
|
||||
))?;
|
||||
|
||||
let maybe_credential = app_data_root
|
||||
.credentials_dir()
|
||||
.find_credential_by_id(credential_id)
|
||||
.map_err(GenerateError::from)?;
|
||||
|
||||
maybe_credential.ok_or_else(|| {
|
||||
GenerateError::CredentialProblem(CredentialProblemReason::CredentialNotFound {
|
||||
credential_id: credential_id.to_string(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate via the router's Artcraft provider, authenticating with the
|
||||
/// stored session cookies. Raw canvas/scene/mask bytes are uploaded to
|
||||
/// ArtCraft first (with the same credential) so the router request only
|
||||
|
|
@ -186,27 +165,6 @@ async fn handle_artcraft_credential(
|
|||
})
|
||||
}
|
||||
|
||||
/// Rebuild the web-session credential set from the stored cookie header.
|
||||
fn storyteller_creds_from_credential(
|
||||
credential: &Credential,
|
||||
) -> Result<StorytellerCredentialSet, GenerateError> {
|
||||
let cookie = credential.cookies().ok_or_else(|| {
|
||||
credential_not_usable(credential, "the account has no session cookies")
|
||||
})?;
|
||||
|
||||
StorytellerCredentialSet::parse_multi_cookie_header(&cookie.cookie_header)
|
||||
.map_err(|err| {
|
||||
credential_not_usable(
|
||||
credential,
|
||||
&format!("the stored session cookies could not be parsed ({})", err),
|
||||
)
|
||||
})?
|
||||
.filter(|creds| !creds.is_empty())
|
||||
.ok_or_else(|| {
|
||||
credential_not_usable(credential, "the stored session cookies are empty")
|
||||
})
|
||||
}
|
||||
|
||||
/// Reference images for the router request: canvas first, then scene, then
|
||||
/// the un-semantic reference images. (Artcraft accepts media tokens
|
||||
/// directly; no URL resolution needed.)
|
||||
|
|
@ -235,9 +193,3 @@ fn collect_image_inputs(
|
|||
}
|
||||
}
|
||||
|
||||
fn credential_not_usable(credential: &Credential, reason: &str) -> GenerateError {
|
||||
GenerateError::CredentialProblem(CredentialProblemReason::CredentialNotUsable {
|
||||
credential_id: credential.id.to_string(),
|
||||
reason: reason.to_string(),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,5 +26,5 @@ pub async fn handle_video_artcraft(
|
|||
let router_model = tauri_video_model_to_router_model(model);
|
||||
let generation_model = tauri_video_model_to_generation_model(model);
|
||||
|
||||
handle_artcraft_video_via_router(request, app_env_configs, &creds, router_model, generation_model).await
|
||||
handle_artcraft_video_via_router(request, &app_env_configs.storyteller_host, &creds, router_model, generation_model).await
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ use crate::commands::enqueue::generate_error::GenerateError;
|
|||
use crate::commands::generate::generate_video::request::TauriGenerateVideoRequest;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::events::generation_events::common::GenerationModel;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use artcraft_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use artcraft_client::utils::api_host::ApiHost;
|
||||
use router::api::audio_list_ref::AudioListRef;
|
||||
use router::api::character_list_ref::CharacterListRef;
|
||||
use router::api::router_video_model::RouterVideoModel;
|
||||
|
|
@ -21,15 +21,15 @@ use enums::common::generation_provider::GenerationProvider;
|
|||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use log::{error, info};
|
||||
|
||||
pub(super) async fn handle_artcraft_video_via_router(
|
||||
pub async fn handle_artcraft_video_via_router(
|
||||
request: &TauriGenerateVideoRequest,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
api_host: &ApiHost,
|
||||
creds: &StorytellerCredentialSet,
|
||||
model: RouterVideoModel,
|
||||
generation_model: GenerationModel,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
let client = RouterClient::Artcraft(RouterArtcraftClient::new(
|
||||
app_env_configs.storyteller_host.clone(),
|
||||
api_host.clone(),
|
||||
creds.clone(),
|
||||
));
|
||||
|
||||
|
|
@ -70,6 +70,12 @@ pub(super) async fn handle_artcraft_video_via_router(
|
|||
.map(|p| p.inference_job_token.to_string())
|
||||
.ok_or(GenerateError::ResponseHadNoJobTokens)?;
|
||||
|
||||
info!(
|
||||
"Router video generation enqueued: inference_job_token={}, response={:?}",
|
||||
job_id,
|
||||
response,
|
||||
);
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
task_type: TaskType::VideoGeneration,
|
||||
model: Some(generation_model),
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
mod get_storyteller_creds_or_error;
|
||||
mod handle_artcraft_video_via_router;
|
||||
pub (super) mod handle_artcraft_video;
|
||||
pub mod get_storyteller_creds_or_error;
|
||||
pub mod handle_artcraft_video_via_router;
|
||||
pub mod handle_artcraft_video;
|
||||
|
|
|
|||
|
|
@ -1,29 +1,19 @@
|
|||
use crate::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason};
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::generate_video::artcraft::handle_artcraft_video::handle_video_artcraft;
|
||||
use crate::commands::generate::generate_video::grok::handle_grok_video::handle_grok_video;
|
||||
use crate::commands::generate::generate_video::handle_credential_router::handle_credential_router;
|
||||
use crate::commands::generate::generate_video::request::{
|
||||
TauriGenerateVideoErrorType, TauriGenerateVideoRequest, TauriGenerateVideoResponse,
|
||||
TauriVideoModel,
|
||||
};
|
||||
use crate::commands::generate::generate_video::sora2::handle_sora_sora2::handle_sora_sora2;
|
||||
use crate::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::commands::response::shorthand::Response;
|
||||
use crate::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::events::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent;
|
||||
use crate::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::state::artcraft_usage_tracker::artcraft_usage_tracker::ArtcraftUsageTracker;
|
||||
use crate::state::artcraft_usage_tracker::artcraft_usage_type::{ArtcraftUsagePage, ArtcraftUsageType};
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::state::task_database::TaskDatabase;
|
||||
use crate::services::grok::state::grok_credential_manager::GrokCredentialManager;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use log::{error, info, warn};
|
||||
use tauri::{AppHandle, State};
|
||||
|
||||
|
|
@ -31,15 +21,9 @@ use tauri::{AppHandle, State};
|
|||
pub async fn generate_video_command(
|
||||
mut request: TauriGenerateVideoRequest,
|
||||
app: AppHandle,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
artcraft_usage_tracker: State<'_, ArtcraftUsageTracker>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
grok_creds_manager: State<'_, GrokCredentialManager>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
sora_creds_manager: State<'_, SoraCredentialManager>,
|
||||
) -> Response<TauriGenerateVideoResponse, TauriGenerateVideoErrorType, ()> {
|
||||
|
||||
info!("generate_video_command called, request: {:?}", request);
|
||||
|
|
@ -58,15 +42,9 @@ pub async fn generate_video_command(
|
|||
|
||||
let result = handle_request(
|
||||
request,
|
||||
&app,
|
||||
&app_env_configs,
|
||||
&app_data_root,
|
||||
&artcraft_usage_tracker,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&grok_creds_manager,
|
||||
&sora_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
|
|
@ -77,35 +55,43 @@ pub async fn generate_video_command(
|
|||
|
||||
let mut status = CommandErrorStatus::ServerError;
|
||||
let mut error_type = TauriGenerateVideoErrorType::ServerError;
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.";
|
||||
let mut error_message = "A server error occurred. Please try again. If it continues, please tell our staff about the problem.".to_string();
|
||||
|
||||
match err {
|
||||
GenerateError::BadInput(BadInputReason::NoModelSpecified) => {
|
||||
status = CommandErrorStatus::BadRequest;
|
||||
error_type = TauriGenerateVideoErrorType::ModelNotSpecified;
|
||||
error_message = "No model specified for video generation";
|
||||
error_message = "No model specified for video generation".to_string();
|
||||
}
|
||||
GenerateError::NoProviderAvailable => {
|
||||
status = CommandErrorStatus::ServerError;
|
||||
error_type = TauriGenerateVideoErrorType::NoProviderAvailable;
|
||||
error_message = "No configured provider available for video generation";
|
||||
error_message = "No configured provider available for video generation".to_string();
|
||||
}
|
||||
GenerateError::CredentialProblem(_) => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = TauriGenerateVideoErrorType::CredentialProblem;
|
||||
error_message = "There's a problem with the selected account.".to_string();
|
||||
}
|
||||
GenerateError::NotYetImplemented(message) => {
|
||||
error_message = message;
|
||||
}
|
||||
GenerateError::MissingCredentials(MissingCredentialsReason::NeedsFalApiKey) => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = TauriGenerateVideoErrorType::NeedsFalApiKey;
|
||||
error_message = "You need to set a FAL api key";
|
||||
error_message = "You need to set a FAL api key".to_string();
|
||||
},
|
||||
GenerateError::MissingCredentials(MissingCredentialsReason::NeedsStorytellerCredentials) => {
|
||||
status = CommandErrorStatus::Unauthorized;
|
||||
error_type = TauriGenerateVideoErrorType::NeedsStorytellerCredentials;
|
||||
error_message = "You need to be logged into Artcraft.";
|
||||
error_message = "You need to be logged into Artcraft.".to_string();
|
||||
}
|
||||
_ => {}, // Fall-through
|
||||
}
|
||||
|
||||
Err(CommandErrorResponseWrapper {
|
||||
status,
|
||||
error_message: Some(error_message.to_string()),
|
||||
error_message: Some(error_message),
|
||||
error_type: Some(error_type),
|
||||
error_details: None,
|
||||
})
|
||||
|
|
@ -130,63 +116,14 @@ pub async fn generate_video_command(
|
|||
|
||||
async fn handle_request(
|
||||
request: TauriGenerateVideoRequest,
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
artcraft_usage_tracker: &ArtcraftUsageTracker,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
grok_creds_manager: &GrokCredentialManager,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let model = match request.model {
|
||||
Some(model) => model,
|
||||
None => {
|
||||
return Err(GenerateError::no_model_specified())
|
||||
}
|
||||
};
|
||||
|
||||
let provider = match (model, request.provider) {
|
||||
(TauriVideoModel::GrokVideo, _) => GenerationProvider::Grok,
|
||||
// NB: Grok Imagine 1.5 is served via Artcraft (storyteller-web / artcraft_router)
|
||||
// for now — force it regardless of the requested provider. In the future we
|
||||
// may serve it natively via the Grok provider.
|
||||
(TauriVideoModel::GrokImagineVideo1p5, _) => GenerationProvider::Artcraft,
|
||||
_ => request.provider.unwrap_or(GenerationProvider::Artcraft),
|
||||
};
|
||||
|
||||
info!("generate video with {:?} via provider {:?}", &model, &provider);
|
||||
|
||||
let result = match provider {
|
||||
GenerationProvider::Grok => {
|
||||
handle_grok_video(
|
||||
&request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
grok_creds_manager,
|
||||
).await
|
||||
}
|
||||
GenerationProvider::Sora => {
|
||||
handle_sora_sora2(
|
||||
&request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
sora_creds_manager,
|
||||
).await
|
||||
}
|
||||
_ => {
|
||||
handle_video_artcraft(
|
||||
&request,
|
||||
app,
|
||||
app_env_configs,
|
||||
storyteller_creds_manager,
|
||||
).await
|
||||
}
|
||||
};
|
||||
// Generation is credential-driven: the request names a stored credential,
|
||||
// and the router dispatches to that credential's service.
|
||||
let result = handle_credential_router(&request, app_data_root).await;
|
||||
|
||||
let success_event = match result {
|
||||
Err(err) => return Err(err),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
use log::info;
|
||||
|
||||
use crate::api_adapters::models::video::tauri_video_model_to_generation_model::tauri_video_model_to_generation_model;
|
||||
use crate::api_adapters::models::video::tauri_video_model_to_router_model::tauri_video_model_to_router_model;
|
||||
use crate::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::commands::generate::common::generation_credential::{credential_not_usable, resolve_generation_credential, storyteller_creds_from_credential};
|
||||
use crate::commands::generate::generate_video::artcraft::handle_artcraft_video_via_router::handle_artcraft_video_via_router;
|
||||
use crate::commands::generate::generate_video::request::TauriGenerateVideoRequest;
|
||||
use crate::credentials::artcraft_api_host::maybe_artcraft_api_host_for_service;
|
||||
use crate::credentials::credential::Credential;
|
||||
use crate::credentials::credential_service_type::CredentialServiceType;
|
||||
use crate::state::data_dir::app_data_root::AppDataRoot;
|
||||
|
||||
/// Credential-driven video generation: resolve the stored credential named
|
||||
/// by the request's `credential_id`, then route to that credential's service
|
||||
/// via the router. The router owns upload + dispatch mechanics per provider.
|
||||
pub async fn handle_credential_router(
|
||||
request: &TauriGenerateVideoRequest,
|
||||
app_data_root: &AppDataRoot,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
let credential = resolve_generation_credential(
|
||||
request.credential_id.as_deref(),
|
||||
app_data_root,
|
||||
)?;
|
||||
|
||||
info!(
|
||||
"handle_credential_router (video): credential={} service={}",
|
||||
credential.id.as_str(),
|
||||
credential.service,
|
||||
);
|
||||
|
||||
match credential.service {
|
||||
CredentialServiceType::Artcraft
|
||||
| CredentialServiceType::ArtcraftLocal
|
||||
| CredentialServiceType::ArtcraftCookies => {
|
||||
handle_artcraft_credential(request, &credential).await
|
||||
}
|
||||
other => Err(credential_not_usable(
|
||||
&credential,
|
||||
&format!("accounts for service {} can't generate videos yet", other),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate via the router's Artcraft provider, authenticating with the
|
||||
/// stored session cookies. Video requests carry media tokens only, which
|
||||
/// Artcraft accepts directly — no pre-upload step is needed.
|
||||
async fn handle_artcraft_credential(
|
||||
request: &TauriGenerateVideoRequest,
|
||||
credential: &Credential,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
let tauri_model = request.model.ok_or(GenerateError::no_model_specified())?;
|
||||
|
||||
let router_model = tauri_video_model_to_router_model(tauri_model);
|
||||
let generation_model = tauri_video_model_to_generation_model(tauri_model);
|
||||
|
||||
let api_host = maybe_artcraft_api_host_for_service(credential.service)
|
||||
.expect("caller only routes ArtCraft services here");
|
||||
|
||||
let creds = storyteller_creds_from_credential(credential)?;
|
||||
|
||||
handle_artcraft_video_via_router(
|
||||
request,
|
||||
&api_host,
|
||||
&creds,
|
||||
router_model,
|
||||
generation_model,
|
||||
).await
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
pub mod generate_video_command;
|
||||
mod handle_credential_router;
|
||||
pub mod request;
|
||||
mod artcraft;
|
||||
mod grok;
|
||||
pub mod artcraft;
|
||||
pub mod grok;
|
||||
pub mod sora2;
|
||||
|
|
|
|||
|
|
@ -84,6 +84,11 @@ pub enum TauriVideoModel {
|
|||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
pub struct TauriGenerateVideoRequest {
|
||||
/// Stable id (`credential_{entropy}`) of the stored credential (account)
|
||||
/// to generate with. Loaded from disk; generation routes to the
|
||||
/// credential's service.
|
||||
pub credential_id: Option<String>,
|
||||
|
||||
pub provider: Option<GenerationProvider>,
|
||||
pub model: Option<TauriVideoModel>,
|
||||
|
||||
|
|
@ -149,4 +154,7 @@ pub enum TauriGenerateVideoErrorType {
|
|||
NeedsFalApiKey,
|
||||
FalError,
|
||||
NeedsStorytellerCredentials,
|
||||
/// Problem with the selected account credential (absent, unknown, or
|
||||
/// unusable). The backend also flashes a dismissable modal.
|
||||
CredentialProblem,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import { GenerationProvider } from "@storyteller/api-enums";
|
|||
import { Clapperboard } from "lucide-react";
|
||||
import { PromptShell, useComposerTasks } from "~/components/PromptShell";
|
||||
import { AccountSelector } from "~/components/account-selector/AccountSelector";
|
||||
import { useAccountSelectorStore } from "~/components/account-selector/accountSelectorStore";
|
||||
|
||||
const PAGE_ID: ModelPage = ModelPage.ImageToVideo;
|
||||
|
||||
|
|
@ -39,6 +40,7 @@ interface ImageToVideoProps {
|
|||
const ImageToVideo = ({ imageMediaId, imageUrl }: ImageToVideoProps) => {
|
||||
const imageToVideoModelList = useImageToVideoPageModelList();
|
||||
const startBatch = useImageToVideoStore((s) => s.startBatch);
|
||||
const selectedAccountId = useAccountSelectorStore((s) => s.selectedAccountId);
|
||||
const completeBatch = useImageToVideoStore((s) => s.completeBatch);
|
||||
|
||||
const selectedVideoModel: VideoModel | undefined =
|
||||
|
|
@ -93,6 +95,7 @@ const ImageToVideo = ({ imageMediaId, imageUrl }: ImageToVideoProps) => {
|
|||
uploadAudio={UploadAudioMedia}
|
||||
credits={videoCredits}
|
||||
accountSelector={<AccountSelector />}
|
||||
credentialId={selectedAccountId}
|
||||
modelSelector={
|
||||
<ClassyModelSelector
|
||||
variant="embedded"
|
||||
|
|
|
|||
|
|
@ -132,6 +132,10 @@ interface PromptBoxVideoProps {
|
|||
/** Optional account-picker slot rendered at the very start of the toolbar
|
||||
* (left of the model selector). */
|
||||
accountSelector?: React.ReactNode;
|
||||
/** Stable id (`credential_{entropy}`) of the selected account credential,
|
||||
* sent with generation requests so the backend knows which stored
|
||||
* credential to generate with. */
|
||||
credentialId?: string | null;
|
||||
/** Optional model-picker slot rendered at the start of the toolbar
|
||||
* (left of the aspect-ratio picker). */
|
||||
modelSelector?: React.ReactNode;
|
||||
|
|
@ -152,6 +156,7 @@ export const PromptBoxVideo = ({
|
|||
uploadAudio,
|
||||
credits,
|
||||
accountSelector,
|
||||
credentialId,
|
||||
modelSelector,
|
||||
fullBleed = false,
|
||||
}: PromptBoxVideoProps) => {
|
||||
|
|
@ -1080,6 +1085,10 @@ export const PromptBoxVideo = ({
|
|||
frontend_subscriber_id: subscriberId,
|
||||
};
|
||||
|
||||
if (!!credentialId) {
|
||||
request.credential_id = credentialId;
|
||||
}
|
||||
|
||||
if (!!selectedProvider) {
|
||||
request.provider = selectedProvider;
|
||||
}
|
||||
|
|
@ -1208,7 +1217,11 @@ export const PromptBoxVideo = ({
|
|||
await Promise.all(enqueuePromises);
|
||||
} catch (err) {
|
||||
console.error("PromptBoxVideo - enqueue failed", err);
|
||||
toast.error("Failed to start video generation. Please try again.");
|
||||
const backendMessage = (err as { error_message?: string } | null)
|
||||
?.error_message;
|
||||
toast.error(
|
||||
backendMessage || "Failed to start video generation. Please try again.",
|
||||
);
|
||||
}
|
||||
|
||||
onEnqueuePressed?.(prompt, subscriberIds);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@ import {
|
|||
import { GenerationProvider } from "@storyteller/api-enums";
|
||||
|
||||
export interface GenerateVideoRequest {
|
||||
// Stable id (`credential_{entropy}`) of the stored credential (account)
|
||||
// to generate with. The backend loads it from disk and routes to that
|
||||
// credential's service.
|
||||
credential_id?: string;
|
||||
|
||||
// The provider to use (defaults to Artcraft/Storyteller).
|
||||
provider?: GenerationProvider;
|
||||
|
||||
|
|
@ -50,6 +55,7 @@ export interface GenerateVideoRequest {
|
|||
}
|
||||
|
||||
interface RawGenerateVideoRequest {
|
||||
credential_id?: string;
|
||||
provider?: GenerationProvider;
|
||||
model?: string;
|
||||
prompt?: string;
|
||||
|
|
@ -117,6 +123,7 @@ export const GenerateVideo = async (
|
|||
model: modelName,
|
||||
};
|
||||
|
||||
if (!!request.credential_id) mutableRequest.credential_id = request.credential_id;
|
||||
if (!!request.provider) mutableRequest.provider = request.provider;
|
||||
if (!!request.prompt) mutableRequest.prompt = request.prompt;
|
||||
if (!!request.negative_prompt) mutableRequest.negative_prompt = request.negative_prompt;
|
||||
|
|
|
|||
Loading…
Reference in New Issue