mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
client integration
This commit is contained in:
1 parent
62622fc125
commit
27775bdc94
15 files changed
+321
-5
No files matched your search
@@ -1,6 +1,12 @@
|
||||
|
||||
/// Type for Post IDs.
|
||||
/// Post IDs are UUIds.
|
||||
/// Type for Post IDs.
|
||||
/// Post IDs are UUIds.
|
||||
/// These are often shared with media/image uploads' `FileId`s, but seldom video generation `FileId`s
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PostId(pub String);
|
||||
|
||||
impl PostId {
|
||||
pub fn to_string(&self) -> String {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod request_client_secrets;
|
||||
mod upload_image_and_generate_video;
|
||||
pub mod upload_image_and_generate_video;
|
||||
pub mod upload_image_and_generate_video_with_retry;
|
||||
@@ -32,7 +32,6 @@ pub struct UploadImageAndGenerateVideo<'a, P: AsRef<Path>> {
|
||||
pub wait_for_generation: bool,
|
||||
|
||||
pub individual_request_timeout: Option<Duration>,
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use crate::credentials::grok_full_credentials::GrokFullCredentials;
|
||||
use crate::datatypes::file_upload_spec::FileUploadSpec;
|
||||
|
||||
pub struct UploadImageAndGenerateVideoWithRetry<'a, P: AsRef<Path>> {
|
||||
pub credentials: &'a GrokFullCredentials,
|
||||
|
||||
// NB: Must be owned.
|
||||
pub file: FileUploadSpec<P>,
|
||||
|
||||
/// Video generation prompt
|
||||
pub prompt: Option<&'a str>,
|
||||
|
||||
/// Wait for the full video to be generated before returning
|
||||
/// By default, the endpoint stays open for 30-ish seconds while
|
||||
/// the video generates and streams back JSON progress updates.
|
||||
/// If we set this to false, we'll wait for the video ID and exit
|
||||
/// the request early, before the video is finished asynchronously
|
||||
/// generating.
|
||||
pub wait_for_generation: bool,
|
||||
|
||||
pub individual_request_timeout: Option<Duration>,
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use base64::DecodeError;
|
||||
use errors::AnyhowError;
|
||||
use grok_client::error::grok_error::GrokError;
|
||||
use midjourney_client::error::midjourney_error::MidjourneyError;
|
||||
use openai_sora_client::error::sora_error::SoraError;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
@@ -53,6 +54,7 @@ pub enum BadInputReason {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MissingCredentialsReason {
|
||||
NeedsGrokCredentials,
|
||||
NeedsFalApiKey,
|
||||
NeedsMidjourneyCredentials,
|
||||
NeedsMidjourneyUserId,
|
||||
@@ -76,6 +78,7 @@ pub enum BillingProvider {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ProviderFailureReason {
|
||||
GrokError(GrokError),
|
||||
//Fal(FalErrorPlus),
|
||||
MidjourneyError(MidjourneyError),
|
||||
/// NB: The midjourney client doesn't categorize all errors, so we have to do so on our end.
|
||||
@@ -113,6 +116,10 @@ impl GenerateError {
|
||||
pub fn needs_fal_api_key() -> Self {
|
||||
Self::MissingCredentials(MissingCredentialsReason::NeedsFalApiKey)
|
||||
}
|
||||
|
||||
pub fn needs_grok_credentials() -> Self {
|
||||
Self::MissingCredentials(MissingCredentialsReason::NeedsGrokCredentials)
|
||||
}
|
||||
|
||||
pub fn needs_midjourney_credentials() -> Self {
|
||||
Self::MissingCredentials(MissingCredentialsReason::NeedsMidjourneyCredentials)
|
||||
@@ -150,6 +157,12 @@ impl From<ArtcraftError> for GenerateError {
|
||||
// }
|
||||
//}
|
||||
|
||||
impl From<GrokError> for GenerateError {
|
||||
fn from(value: GrokError) -> Self {
|
||||
Self::ProviderFailure(ProviderFailureReason::GrokError(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MidjourneyError> for GenerateError {
|
||||
fn from(value: MidjourneyError) -> Self {
|
||||
Self::ProviderFailure(ProviderFailureReason::MidjourneyError(value))
|
||||
|
||||
+17
@@ -1,6 +1,7 @@
|
||||
use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason, ProviderFailureReason};
|
||||
use crate::core::commands::enqueue::image_to_video::generic::handle_video::handle_video;
|
||||
use crate::core::commands::enqueue::image_to_video::grok::handle_grok_video::handle_grok_video;
|
||||
use crate::core::commands::enqueue::image_to_video::sora2::handle_sora2_video::handle_sora2_video;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
@@ -20,12 +21,16 @@ use log::{error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::services::grok::state::grok_credential_manager::GrokCredentialManager;
|
||||
|
||||
/// This is used in the Tauri command bridge.
|
||||
/// Don't change the serializations without coordinating with the frontend.
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum VideoModel {
|
||||
#[serde(rename = "grok")]
|
||||
Grok,
|
||||
|
||||
#[serde(rename = "kling_1.6_pro")]
|
||||
Kling16Pro,
|
||||
|
||||
@@ -115,6 +120,7 @@ pub async fn enqueue_image_to_video_command(
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
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>,
|
||||
@@ -129,6 +135,7 @@ pub async fn enqueue_image_to_video_command(
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&grok_creds_manager,
|
||||
&sora_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
).await;
|
||||
@@ -200,11 +207,21 @@ pub async fn handle_request(
|
||||
app_data_root: &AppDataRoot,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
grok_creds_manager: &GrokCredentialManager,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let result = match request.model {
|
||||
Some(VideoModel::Grok) => {
|
||||
handle_grok_video(
|
||||
&request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
grok_creds_manager,
|
||||
).await
|
||||
}
|
||||
Some(VideoModel::Sora2) => {
|
||||
handle_sora2_video(
|
||||
&request,
|
||||
|
||||
+5
@@ -71,6 +71,11 @@ pub async fn handle_video_artcraft(
|
||||
None => {
|
||||
return Err(GenerateError::no_model_specified());
|
||||
}
|
||||
Some(VideoModel::Grok) => {
|
||||
return Err(GenerateError::AnyhowError(
|
||||
anyhow!("wrong logic: another branch should handle this: {:?}",
|
||||
request.model)));
|
||||
}
|
||||
Some(VideoModel::Sora2) => {
|
||||
return Err(GenerateError::AnyhowError(
|
||||
anyhow!("wrong logic: another branch should handle this: {:?}",
|
||||
|
||||
+125
@@ -0,0 +1,125 @@
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::EnqueueContextualEditImageCommand;
|
||||
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, SoraOrientation};
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent;
|
||||
use crate::core::events::generation_events::common::GenerationModel;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::services::grok::state::grok_credential_manager::GrokCredentialManager;
|
||||
use crate::services::grok::util::upload_image_media_file_to_grok::{upload_image_media_file_to_grok, UploadImageMediaFileToGrok};
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::sora::utils::upload_images_to_sora::{upload_images_to_sora, UploadImagesToSoraArgs};
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use grok_client::credentials::grok_cookies::GrokCookies;
|
||||
use grok_client::credentials::grok_full_credentials::GrokFullCredentials;
|
||||
use grok_client::datatypes::file_upload_spec::FileUploadSpec;
|
||||
use grok_client::recipes::request_client_secrets::{request_client_secrets, RequestClientSecretsArgs};
|
||||
use grok_client::recipes::upload_image_and_generate_video::{upload_image_and_generate_video, UploadImageAndGenerateVideo};
|
||||
use log::{error, info, warn};
|
||||
use openai_sora_client::recipes::generate_sora2_video::generate_sora2_video_with_session_auto_renew::generate_sora2_video_with_session_auto_renew;
|
||||
use openai_sora_client::requests::generate_sora2_video::generate_sora2_video::{GenerateSora2VideoArgs, Orientation};
|
||||
use std::time::Duration;
|
||||
use tauri::AppHandle;
|
||||
|
||||
const GROK_IMAGE_UPLOAD_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds
|
||||
|
||||
pub async fn handle_grok_video(
|
||||
request: &EnqueueImageToVideoRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
grok_credential_manager: &GrokCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let creds = match grok_credential_manager.maybe_copy_full_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Grok, &app);
|
||||
return Err(GenerateError::needs_grok_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
let image_token = match request.image_media_token.as_ref() {
|
||||
Some(token) => token,
|
||||
None => {
|
||||
return Err(GenerateError::BadInput(BadInputReason::InvalidNumberOfInputImages { min: 1, max: 1, provided: 0 }))
|
||||
}
|
||||
};
|
||||
|
||||
info!("Downloading image media file...");
|
||||
|
||||
let local_image_file = download_media_file_to_temp_dir(
|
||||
&app_env_configs,
|
||||
&app_data_root,
|
||||
image_token,
|
||||
).await?;
|
||||
|
||||
info!("Calling Grok Video generate...");
|
||||
|
||||
let upload = upload_image_and_generate_video(UploadImageAndGenerateVideo {
|
||||
full_credentials: &creds,
|
||||
file: FileUploadSpec::Path(local_image_file.path()),
|
||||
prompt: request.prompt.as_deref(),
|
||||
wait_for_generation: false,
|
||||
individual_request_timeout: Some(GROK_IMAGE_UPLOAD_TIMEOUT)
|
||||
}).await;
|
||||
|
||||
let post_id = match upload {
|
||||
Err(err) => {
|
||||
error!("Failed to use Grok video: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
Ok(result) => {
|
||||
info!("Successfully enqueued Grok Video: post_id = {:?}", result.post_id);
|
||||
result.post_id
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Sora,
|
||||
model: Some(GenerationModel::Sora2),
|
||||
provider_job_id: Some(post_id.to_string()),
|
||||
task_type: TaskType::VideoGeneration,
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_grok_creds(app: &AppHandle, grok_credential_manager: &GrokCredentialManager) -> Result<GrokFullCredentials, GenerateError> {
|
||||
if let Some(creds) = grok_credential_manager.maybe_copy_full_credentials()? {
|
||||
return Ok(creds);
|
||||
}
|
||||
|
||||
let cookies = match grok_credential_manager.maybe_copy_cookie_store()? {
|
||||
Some(cookies) => cookies.to_cookie_string(),
|
||||
None => {
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Grok, &app);
|
||||
return Err(GenerateError::needs_grok_credentials())
|
||||
}
|
||||
};
|
||||
|
||||
let cookies = GrokCookies::new(cookies);
|
||||
|
||||
let upgraded = request_client_secrets(RequestClientSecretsArgs {
|
||||
cookies: &cookies,
|
||||
}).await;
|
||||
|
||||
match upgraded {
|
||||
Err(err) => {
|
||||
error!("Failed to fetch Grok client secrets: {}", err); // NB: Fall-through
|
||||
}
|
||||
Ok(secrets) => {
|
||||
let full_creds = GrokFullCredentials::from_cookies_and_client_secrets(cookies, secrets);
|
||||
grok_credential_manager.replace_full_credentials(full_creds.clone())?;
|
||||
return Ok(full_creds)
|
||||
}
|
||||
}
|
||||
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Grok, &app);
|
||||
Err(GenerateError::needs_grok_credentials())
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub (super) mod handle_grok_video;
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod enqueue_image_to_video_command;
|
||||
mod generic;
|
||||
pub mod sora2;
|
||||
pub mod sora2;
|
||||
mod grok;
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod commands;
|
||||
pub mod state;
|
||||
pub mod util;
|
||||
pub mod windows;
|
||||
@@ -1,5 +1,6 @@
|
||||
use cookie_store::cookie_store::CookieStore;
|
||||
use grok_client::credentials::grok_client_secrets::GrokClientSecrets;
|
||||
use grok_client::credentials::grok_full_credentials::GrokFullCredentials;
|
||||
use grok_client::credentials::grok_user_data::GrokUserData;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -9,6 +10,10 @@ pub struct GrokCredentialHolder {
|
||||
/// The Grok client consumes a string-only form (rather than this cookie jar)
|
||||
pub browser_cookies: Option<CookieStore>,
|
||||
|
||||
/// Full credentials.
|
||||
/// NOT PERSISTED TO DISK.
|
||||
pub grok_full_credentials: Option<GrokFullCredentials>,
|
||||
|
||||
/// Email, user id, etc.
|
||||
/// These can be persisted.
|
||||
pub grok_user_data: Option<GrokUserData>,
|
||||
@@ -23,6 +28,7 @@ impl GrokCredentialHolder {
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
browser_cookies: None,
|
||||
grok_full_credentials: None,
|
||||
grok_user_data: None,
|
||||
grok_client_secrets: None,
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ use crate::services::grok::state::grok_serializable_state::{GrokSerializableStat
|
||||
use cookie_store::cookie_store::CookieStore;
|
||||
use errors::AnyhowResult;
|
||||
use grok_client::credentials::grok_client_secrets::GrokClientSecrets;
|
||||
use grok_client::credentials::grok_cookies::GrokCookies;
|
||||
use grok_client::credentials::grok_full_credentials::GrokFullCredentials;
|
||||
use grok_client::credentials::grok_user_data::GrokUserData;
|
||||
use log::{info, warn};
|
||||
use std::fs::read_to_string;
|
||||
@@ -50,6 +52,7 @@ impl GrokCredentialManager {
|
||||
credential_data = Arc::new(RwLock::new(GrokCredentialHolder {
|
||||
browser_cookies: maybe_cookies,
|
||||
grok_user_data: user_data,
|
||||
grok_full_credentials: None, // NB: We don't want to keep this on disk. It goes stale.
|
||||
grok_client_secrets: None, // NB: We don't want to keep this on disk. It goes stale.
|
||||
}));
|
||||
}
|
||||
@@ -68,6 +71,29 @@ impl GrokCredentialManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maybe_copy_cookie_header_string(&self) -> anyhow::Result<Option<String>> {
|
||||
let maybe_cookies = self.maybe_copy_cookie_store()?;
|
||||
let maybe_cookies = maybe_cookies.map(|cookies| {
|
||||
cookies.to_cookie_string()
|
||||
});
|
||||
Ok(maybe_cookies)
|
||||
}
|
||||
|
||||
pub fn maybe_copy_typed_cookies(&self) -> anyhow::Result<Option<GrokCookies>> {
|
||||
let maybe_cookies = self.maybe_copy_cookie_header_string()?;
|
||||
let maybe_cookies = maybe_cookies.map(|cookies| {
|
||||
GrokCookies::new(cookies)
|
||||
});
|
||||
Ok(maybe_cookies)
|
||||
}
|
||||
|
||||
pub fn maybe_copy_full_credentials(&self) -> anyhow::Result<Option<GrokFullCredentials>> {
|
||||
match self.credential_data.read() {
|
||||
Err(err) => Err(anyhow::anyhow!("Failed to acquire read lock: {:?}", err)),
|
||||
Ok(holder) => Ok(holder.grok_full_credentials.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maybe_copy_client_secrets(&self) -> anyhow::Result<Option<GrokClientSecrets>> {
|
||||
match self.credential_data.read() {
|
||||
Err(err) => Err(anyhow::anyhow!("Failed to acquire read lock: {:?}", err)),
|
||||
@@ -85,6 +111,16 @@ impl GrokCredentialManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_full_credentials(&self, creds: GrokFullCredentials) -> anyhow::Result<()> {
|
||||
match self.credential_data.write() {
|
||||
Err(err) => Err(anyhow::anyhow!("Failed to acquire write lock: {:?}", err)),
|
||||
Ok(mut holder) => {
|
||||
holder.grok_full_credentials = Some(creds);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_client_secrets(&self, secrets: GrokClientSecrets) -> anyhow::Result<()> {
|
||||
match self.credential_data.write() {
|
||||
Err(err) => Err(anyhow::anyhow!("Failed to acquire write lock: {:?}", err)),
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub mod upload_image_media_file_to_grok;
|
||||
@@ -0,0 +1,80 @@
|
||||
use crate::core::state::app_env_configs::app_env_configs_serializeable::StorytellerApiHost;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::core::utils::simple_http_download::simple_http_download;
|
||||
use crate::services::grok::state::grok_credential_manager::GrokCredentialManager;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
use grok_client::datatypes::api::file_id::FileId;
|
||||
use grok_client::datatypes::file_upload_spec::FileUploadSpec;
|
||||
use grok_client::requests::upload_file::grok_upload_file::GrokUploadFile;
|
||||
use log::{error, info};
|
||||
use openai_sora_client::creds::sora_credential_set::SoraCredentialSet;
|
||||
use openai_sora_client::recipes::image_upload_from_file_with_session_auto_renew::{image_upload_from_file_with_session_auto_renew, ImageUploadFromFileAutoRenewRequest};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use std::time::Duration;
|
||||
use storyteller_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use storyteller_client::endpoints::media_files::get_media_file::get_media_file;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
const GROK_IMAGE_UPLOAD_TIMEOUT: Duration = Duration::from_millis(1000 * 30); // 30 seconds
|
||||
|
||||
pub struct UploadImageMediaFileToGrok<'a> {
|
||||
pub storyteller_host: &'a ApiHost,
|
||||
pub image_media_token: &'a MediaFileToken,
|
||||
|
||||
pub app_data_root: &'a AppDataRoot,
|
||||
pub grok_creds_manager: &'a GrokCredentialManager,
|
||||
}
|
||||
|
||||
pub struct UploadImageMediaFileToGrokResult {
|
||||
pub grok_file_id: FileId,
|
||||
}
|
||||
|
||||
pub async fn upload_image_media_file_to_grok(
|
||||
args: UploadImageMediaFileToGrok<'_>,
|
||||
) -> AnyhowResult<UploadImageMediaFileToGrokResult> {
|
||||
|
||||
info!("Calling get media file API: {:?}", args.storyteller_host);
|
||||
|
||||
info!("Using media token: {:?}", args.image_media_token);
|
||||
|
||||
let response = get_media_file(
|
||||
args.storyteller_host,
|
||||
args.image_media_token
|
||||
).await?;
|
||||
|
||||
let media_file_url = &response.media_file.media_links.cdn_url;
|
||||
let extension_with_dot = get_url_file_extension(media_file_url)
|
||||
.map(|ext| format!(".{}", ext))
|
||||
.unwrap_or_else(|| ".png".to_string());
|
||||
|
||||
let filename = format!("{}{}", response.media_file.token.as_str(), extension_with_dot);
|
||||
let filename = args.app_data_root.downloads_dir().path().join(&filename);
|
||||
|
||||
simple_http_download(&media_file_url, &filename).await?;
|
||||
|
||||
let cookies = args.grok_creds_manager.maybe_copy_cookie_header_string()?
|
||||
.ok_or_else(|| anyhow!("Missing Grok cookies"))?;
|
||||
|
||||
info!("Uploading image to Grok...");
|
||||
|
||||
let upload = GrokUploadFile {
|
||||
file: FileUploadSpec::Path(filename),
|
||||
cookie: cookies,
|
||||
request_timeout: Some(GROK_IMAGE_UPLOAD_TIMEOUT),
|
||||
};
|
||||
|
||||
let response = upload.upload().await?;
|
||||
|
||||
let file_id = response.file_id
|
||||
.ok_or_else(|| anyhow!("Media upload did not produce a file_id!"))?;
|
||||
|
||||
Ok(UploadImageMediaFileToGrokResult {
|
||||
grok_file_id: file_id,
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user