tauri window wip

This commit is contained in:
Brandon Thomas committed 2025-10-17 02:26:01 -04:00
1 parent 512059c0f5
commit 89286ba459
19 files changed
+612 -1

No files matched your search

@@ -32,6 +32,7 @@ impl TaskEnqueueSuccess{
match self.provider {
GenerationProvider::Artcraft => GenerationServiceProvider::Artcraft,
GenerationProvider::Fal => GenerationServiceProvider::Fal,
GenerationProvider::Grok => GenerationServiceProvider::Grok,
GenerationProvider::Midjourney => GenerationServiceProvider::Midjourney,
GenerationProvider::Sora => GenerationServiceProvider::Sora,
}
@@ -5,6 +5,7 @@ use serde::Serialize;
pub enum GenerationServiceProvider {
Artcraft,
Fal,
Grok,
Midjourney,
Sora,
}
@@ -9,6 +9,7 @@ use crate::core::lifecycle::startup::tasks::spawn_storyteller_threads::spawn_sto
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
use crate::core::state::artcraft_platform_info::ArtcraftPlatformInfo;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
use crate::services::grok::state::grok_credential_manager::GrokCredentialManager;
use crate::services::midjourney::state::midjourney_credential_manager::MidjourneyCredentialManager;
use crate::services::midjourney::threads::midjourney_long_polling_thread::midjourney_long_polling_thread;
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
@@ -26,6 +27,7 @@ pub async fn handle_tauri_startup(
sora_credential_manager: SoraCredentialManager,
sora_task_queue: SoraTaskQueue,
mj_creds_manager: MidjourneyCredentialManager,
grok_creds_manager: GrokCredentialManager,
) -> AnyhowResult<()> {
set_app_log_level(
@@ -21,6 +21,14 @@ impl DataSubdir for AppCredentialsDir {
}
impl AppCredentialsDir {
pub fn get_grok_state_path(&self) -> PathBuf {
self.path.join("grok_state.json")
}
pub fn get_grok_cookies_path(&self) -> PathBuf {
self.path.join("grok_cookies.txt")
}
pub fn get_sora_cookie_file_path(&self) -> PathBuf {
self.path.join("sora_cookies.txt")
}
@@ -6,6 +6,7 @@ pub fn to_generation_service_provider(provider: GenerationProvider) -> Generatio
match provider {
GenerationProvider::Artcraft => GenerationServiceProvider::Artcraft,
GenerationProvider::Fal => GenerationServiceProvider::Fal,
GenerationProvider::Grok => GenerationServiceProvider::Grok,
GenerationProvider::Midjourney => GenerationServiceProvider::Midjourney,
GenerationProvider::Sora => GenerationServiceProvider::Sora,
}
+8
View File
@@ -29,6 +29,8 @@ use crate::core::state::data_dir::app_data_root::AppDataRoot;
use crate::core::state::provider_priority::ProviderPriorityStore;
use crate::core::threads::discord_presence_thread::discord_presence_thread;
use crate::core::threads::main_window_thread::main_window_thread::main_window_thread;
use crate::services::grok::commands::grok_open_login_command::grok_open_login_command;
use crate::services::grok::state::grok_credential_manager::GrokCredentialManager;
use crate::services::midjourney::commands::midjourney_clear_credentials_command::midjourney_clear_credentials_command;
use crate::services::midjourney::commands::midjourney_get_credential_info_command::midjourney_get_credential_info_command;
use crate::services::midjourney::commands::midjourney_open_login_command::midjourney_open_login_command;
@@ -97,6 +99,9 @@ pub fn run() {
let midjourney_creds_manager = MidjourneyCredentialManager::initialize_from_disk_infallible(&app_data_root);
let midjourney_creds_manager_2 = midjourney_creds_manager.clone();
let grok_creds_manager = GrokCredentialManager::initialize_from_disk_infallible(&app_data_root);
let grok_creds_manager_2 = grok_creds_manager.clone();
println!("Initializing backend runtime...");
let builder = tauri::Builder::default()
@@ -134,6 +139,7 @@ pub fn run() {
sora_creds,
sora_tasks,
midjourney_creds_manager_2,
grok_creds_manager_2,
).await;
if let Err(err) = result {
@@ -148,6 +154,7 @@ pub fn run() {
.manage(app_env_configs)
.manage(app_preferences)
.manage(artcraft_platform_info)
.manage(grok_creds_manager)
.manage(midjourney_creds_manager)
.manage(sora_creds_manager)
.manage(sora_task_queue)
@@ -168,6 +175,7 @@ pub fn run() {
get_app_preferences_command,
get_provider_order_command,
get_task_queue_command,
grok_open_login_command,
load_without_cors_command,
mark_task_as_dismissed_command,
media_file_delete_command,
@@ -0,0 +1,38 @@
use crate::core::state::data_dir::app_data_root::AppDataRoot;
use crate::services::grok::windows::grok_login_window::grok_login_window_open::grok_login_window_open;
use crate::services::midjourney::state::midjourney_credential_manager::MidjourneyCredentialManager;
use errors::AnyhowResult;
use log::{error, info};
use tauri::{AppHandle, State};
use crate::services::grok::state::grok_credential_manager::GrokCredentialManager;
#[tauri::command]
pub async fn grok_open_login_command(
app: AppHandle,
app_data_root: State<'_, AppDataRoot>,
grok_creds_manager: State<'_, GrokCredentialManager>,
) -> Result<String, String> {
info!("grok_open_login_command called");
do_open_login(&app, &app_data_root, &grok_creds_manager)
.await
.map_err(|err| {
error!("Error opening login: {:?}", err);
format!("Error opening login: {:?}", err)
})?;
Ok("result".to_string())
}
async fn do_open_login(
app: &AppHandle,
app_data_root: &AppDataRoot,
grok_creds_manager: &GrokCredentialManager,
) -> AnyhowResult<()> {
info!("Building login window...");
grok_login_window_open(app, app_data_root, grok_creds_manager).await?;
info!("Done.");
Ok(())
}
@@ -0,0 +1 @@
pub mod grok_open_login_command;
@@ -0,0 +1,3 @@
pub mod commands;
pub mod state;
pub mod windows;
@@ -0,0 +1,173 @@
use crate::core::state::data_dir::app_data_root::AppDataRoot;
use crate::services::grok::state::grok_serializable_state::{GrokSerializableState, SERIALIZABLE_GROK_STATE_VERSION};
use cookie_store::cookie_store::CookieStore;
use errors::AnyhowResult;
use log::{info, warn};
use std::fs::read_to_string;
use std::sync::{Arc, RwLock};
#[derive(Clone)]
pub struct GrokCredentialManager {
// TODO: Put last write/last read timestamps on these.
cookies: Arc<RwLock<Option<CookieStore>>>,
//user_info: Arc<RwLock<Option<MidjourneyUserInfo>>>,
app_data_root: AppDataRoot,
}
impl GrokCredentialManager {
pub fn initialize_empty(app_data_root: &AppDataRoot) -> Self {
Self {
cookies: Arc::new(RwLock::new(None)),
app_data_root: app_data_root.clone(),
}
}
pub fn initialize_from_disk_infallible(app_data_root: &AppDataRoot) -> Self {
let mut cookies;
//let mut user_info;
let result = GrokSerializableState::read_from_disk(app_data_root);
match result {
Err(err) => {
warn!("Failed to read midjourney state from disk: {:?}", err);
cookies = Arc::new(RwLock::new(None));
//user_info = Arc::new(RwLock::new(None));
},
Ok(None) => {
cookies = Arc::new(RwLock::new(None));
//user_info = Arc::new(RwLock::new(None));
}
Ok(Some(state)) => {
let maybe_cookies = state.user_cookies
.map(|cookies| cookies.to_cookie_store());
//let maybe_user_info = state.user_info
// .map(|info| info.to_user_info());
cookies = Arc::new(RwLock::new(maybe_cookies));
//user_info = Arc::new(RwLock::new(maybe_user_info));
}
};
Self {
cookies,
app_data_root: app_data_root.clone(),
}
}
pub fn maybe_copy_cookie_store(&self) -> anyhow::Result<Option<CookieStore>> {
match self.cookies.read() {
Err(err) => Err(anyhow::anyhow!("Failed to acquire write lock: {:?}", err)),
Ok(store) => Ok(store.clone()),
}
}
//pub fn maybe_copy_user_info(&self) -> anyhow::Result<Option<MidjourneyUserInfo>> {
// match self.user_info.read() {
// Err(err) => Err(anyhow::anyhow!("Failed to acquire read lock: {:?}", err)),
// Ok(info) => Ok(info.clone()),
// }
//}
pub fn replace_cookie_store(&self, store: CookieStore) -> anyhow::Result<()> {
match self.cookies.write() {
Err(err) => Err(anyhow::anyhow!("Failed to acquire write lock: {:?}", err)),
Ok(mut current_store) => {
*current_store = Some(store);
Ok(())
}
}
}
//pub fn replace_user_info(&self, user_info: MidjourneyUserInfo) -> anyhow::Result<()> {
// match self.user_info.write() {
// Err(err) => Err(anyhow::anyhow!("Failed to acquire write lock: {:?}", err)),
// Ok(mut current_info) => {
// *current_info = Some(user_info);
// Ok(())
// }
// }
//}
// NB: This is just a heuristic. We'll add better checks later.
pub fn session_appears_active(&self) -> anyhow::Result<bool> {
let maybe_cookies = match self.cookies.read() {
Err(err) => return Err(anyhow::anyhow!("Failed to acquire read lock: {:?}", err)),
Ok(store) => store.clone(),
};
//let maybe_user_info = match self.user_info.read() {
// Err(err) => return Err(anyhow::anyhow!("Failed to acquire read lock: {:?}", err)),
// Ok(info) => info.clone(),
//};
if maybe_cookies.is_none() {
return Ok(false);
}
let cookies = maybe_cookies.unwrap();
//let user_info = maybe_user_info.unwrap();
// NB: We consider the session active if we have auth cookies and a user id.
//let has_user_id = user_info.user_id.is_some();
//let maybe_has_auth_cookies = cookie_store_has_auth_cookies(&cookies);
let maybe_has_auth_cookies = true; // TODO TODO TODO - fix this
// Misc cookies without login cookies are ~1055 length
// AUTH_I is ~1500 length
// AUTH_R is ~500 length
let maybe_has_big_enough_cookie = cookies.calculate_approx_cookie_character_length() > 2100;
// TODO: Heuristic should count.
// TODO: Consolidate with "login window thread" logic.
// TODO: Check timestamp of last valid request.
Ok(maybe_has_auth_cookies && maybe_has_big_enough_cookie && maybe_has_big_enough_cookie)
}
pub fn clear_credentials(&self) -> anyhow::Result<()> {
match self.cookies.write() {
Err(err) => return Err(anyhow::anyhow!("Failed to acquire write lock: {:?}", err)),
Ok(mut store) => *store = None,
}
//match self.user_info.write() {
// Err(err) => return Err(anyhow::anyhow!("Failed to acquire write lock: {:?}", err)),
// Ok(mut info) => *info = None,
//}
Ok(())
}
pub fn persist_to_disk(&self) -> anyhow::Result<()> {
let maybe_cookies = match self.cookies.read() {
Err(err) => return Err(anyhow::anyhow!("Failed to acquire read lock: {:?}", err)),
Ok(store) => store.clone(),
};
//let maybe_user_info = match self.user_info.read() {
// Err(err) => return Err(anyhow::anyhow!("Failed to acquire read lock: {:?}", err)),
// Ok(info) => info.clone(),
//};
let maybe_cookies= maybe_cookies
.map(|cookies| cookies.to_serializable());
//let maybe_user_info= maybe_user_info
// .map(|info| info.to_serializable());
if maybe_cookies.is_none() {
info!("Nothing to write to disk, skipping.");
return Ok(());
}
let state = GrokSerializableState {
version: SERIALIZABLE_GROK_STATE_VERSION,
user_cookies: maybe_cookies,
};
let path = self.app_data_root.credentials_dir().get_midjourney_state_path();
let serialized = serde_json::to_string(&state)?;
std::fs::write(path, serialized)?;
Ok(())
}
}
@@ -0,0 +1,60 @@
use std::fs::read_to_string;
use cookie_store::serialized_cookie_store::SerializableCookieStore;
use serde_derive::{Deserialize, Serialize};
use crate::core::state::data_dir::app_data_root::AppDataRoot;
pub (super) const SERIALIZABLE_GROK_STATE_VERSION: u32 = 1;
#[derive(Serialize, Deserialize)]
pub (super) struct GrokSerializableState {
pub (super) version: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub (super) user_cookies: Option<SerializableCookieStore>,
//#[serde(skip_serializing_if = "Option::is_none")]
//pub (super) user_info: Option<SerializableMidjourneyUserInfo>,
}
//#[derive(Serialize, Deserialize)]
//pub (super) struct SerializableMidjourneyUserInfo {
// #[serde(skip_serializing_if = "Option::is_none")]
// pub (super) user_id: Option<String>,
//
// #[serde(skip_serializing_if = "Option::is_none")]
// pub (super) email: Option<String>,
//
// #[serde(skip_serializing_if = "Option::is_none")]
// pub (super) websocket_token: Option<String>,
//}
//impl SerializableMidjourneyUserInfo {
// pub fn to_user_info(&self) -> MidjourneyUserInfo {
// MidjourneyUserInfo {
// user_id: self.user_id
// .as_deref()
// .map(|id| MidjourneyUserId::from_str(id)),
// email: self.email.clone(),
// websocket_token: self.websocket_token.clone(),
// }
// }
//}
impl GrokSerializableState {
pub fn read_from_disk(root: &AppDataRoot) -> anyhow::Result<Option<GrokSerializableState>> {
let midjourney_state_path= root.credentials_dir().get_grok_state_path();
if !midjourney_state_path.exists() {
return Ok(None);
}
let contents = read_to_string(&midjourney_state_path)?
.trim()
.to_string();
let state: GrokSerializableState = serde_json::from_str(&contents)?;
Ok(Some(state))
}
}
@@ -0,0 +1,2 @@
pub mod grok_credential_manager;
pub mod grok_serializable_state;
@@ -0,0 +1,44 @@
use cookie_store::cookie_store::CookieStore;
use errors::AnyhowResult;
use once_cell::sync::Lazy;
use reqwest::Url;
use std::collections::HashSet;
use tauri::webview::Cookie;
use tauri::WebviewWindow;
static WWW_COOKIE_URL: Lazy<Url> = Lazy::new(|| {
Url::parse("https://www.grok.com").expect("URL should parse")
});
static ROOT_COOKIE_URL: Lazy<Url> = Lazy::new(|| {
Url::parse("https://grok.com").expect("URL should parse")
});
pub fn grok_login_webview_extract_cookies(webview: &WebviewWindow) -> AnyhowResult<CookieStore> {
let mut cookie_store = CookieStore::empty();
let cookies = get_all_grok_cookies(webview)?;
for cookie in cookies.iter() {
cookie_store.add_cookie_name_and_value(
cookie.name().to_string(),
cookie.value().to_string(),
);
}
Ok(cookie_store)
}
fn get_all_grok_cookies(webview: &WebviewWindow) -> AnyhowResult<Vec<Cookie>> {
let www_cookies = webview.cookies_for_url(WWW_COOKIE_URL.clone())?;
let root_cookies = webview.cookies_for_url(ROOT_COOKIE_URL.clone())?;
let mut all_cookies = www_cookies;
let mut cookie_names = HashSet::new();
for cookie in root_cookies.iter() {
if !cookie_names.contains(cookie.name()) {
cookie_names.insert(cookie.name().to_string());
all_cookies.push(cookie.clone());
}
}
Ok(all_cookies)
}
@@ -0,0 +1,74 @@
use crate::core::state::data_dir::app_data_root::AppDataRoot;
use crate::core::utils::clear_all_webview_cookies::clear_all_webview_cookies;
use crate::services::midjourney::state::midjourney_credential_manager::MidjourneyCredentialManager;
use crate::services::midjourney::windows::midjourney_login_window_thread::midjourney_login_window_thread;
use anyhow::anyhow;
use errors::AnyhowResult;
use once_cell::sync::Lazy;
use reqwest::Url;
use std::time::Duration;
use tauri::{AppHandle, Manager, WebviewUrl, WebviewWindowBuilder};
use crate::services::grok::state::grok_credential_manager::GrokCredentialManager;
use crate::services::grok::windows::grok_login_window::grok_login_window_thread::grok_login_window_thread;
/// Name of the window
pub (super) const GROK_LOGIN_WINDOW_NAME: &str = "grok_login_window";
pub (super) static START_URL: Lazy<Url> = Lazy::new(|| {
Url::parse("https://google.com").expect("URL should parse")
});
pub (super) static GROK_HOMEPAGE_URL: Lazy<Url> = Lazy::new(|| {
Url::parse("https://grok.com/").expect("URL should parse")
});
pub (super) static GROK_LOGIN_URL: Lazy<Url> = Lazy::new(|| {
Url::parse("https://accounts.x.ai/sign-in?redirect=grok-com").expect("URL should parse")
});
pub async fn grok_login_window_open(
app: &AppHandle,
app_data_root: &AppDataRoot,
grok_creds_manager: &GrokCredentialManager,
) -> AnyhowResult<()> {
if app.get_window(GROK_LOGIN_WINDOW_NAME).is_some() {
return Err(anyhow!("Login window already open"));
}
let url = WebviewUrl::External(START_URL.clone());
let window = WebviewWindowBuilder::new(app, GROK_LOGIN_WINDOW_NAME, url)
//.user_agent(openai_sora_client::credentials::USER_AGENT)
.always_on_top(false)
.title("Login to Grok")
.center()
.resizable(true)
.visible(true)
.closable(true)
.min_inner_size(200.0, 800.0)
.focused(true)
.devtools(true)
.build()?;
let webview = window.get_webview(GROK_LOGIN_WINDOW_NAME)
.ok_or_else(|| anyhow!("no webview found"))?;
clear_all_webview_cookies(&webview)?;
webview.navigate(GROK_HOMEPAGE_URL.clone())?;
// NB: We're starting to get Cloudflare protection screens. Let's try to avoid.
tokio::time::sleep(Duration::from_millis(100)).await;
webview.navigate(GROK_LOGIN_URL.clone())?;
let app_handle = app.clone();
let app_data_root = app_data_root.clone();
let grok_creds_manager= grok_creds_manager.clone();
let _ = tauri::async_runtime::spawn(async move {
grok_login_window_thread(app_handle, app_data_root, grok_creds_manager).await;
});
Ok(())
}
@@ -0,0 +1,182 @@
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
use crate::core::events::functional_events::refresh_account_state_event::RefreshAccountStateEvent;
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
use crate::core::events::generation_events::generation_complete_event::GenerationCompleteEvent;
use crate::core::events::sendable_event_trait::SendableEvent;
use crate::core::state::data_dir::app_data_root::AppDataRoot;
use crate::core::utils::window::get_webview_window_hostname::get_webview_window_hostname;
use crate::services::grok::state::grok_credential_manager::GrokCredentialManager;
use crate::services::grok::windows::grok_login_window::grok_login_webview_extract_cookies::grok_login_webview_extract_cookies;
use crate::services::grok::windows::grok_login_window::grok_login_window_open::GROK_LOGIN_WINDOW_NAME;
use crate::services::sora::events::sora_login_success_event::SoraLoginSuccessEvent;
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
use crate::services::sora::windows::sora_login_window::extract_sora_webview_cookies::extract_sora_webview_cookies;
use anyhow::anyhow;
use cookie_store::cookie_store::CookieStore;
use enums::common::generation_provider::GenerationProvider;
use errors::AnyhowResult;
use log::{error, info};
use openai_sora_client::creds::sora_credential_set::SoraCredentialSet;
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
use openai_sora_client::utils::has_session_cookie::{has_session_cookie, SessionCookiePresence};
use tauri::{AppHandle, Manager, WebviewWindow};
pub async fn grok_login_window_thread(
app: AppHandle,
app_data_root: AppDataRoot,
grok_creds_manager: GrokCredentialManager,
) {
let mut visited_login = false;
loop {
let login_webview_window = match app.get_webview_window(GROK_LOGIN_WINDOW_NAME) {
Some(webview) => webview,
None => {
info!("Exit Grok login thread.");
return; // NB: Only exit if we don't have the webview.
}
};
let result = check_login_window(
&app,
&login_webview_window,
&app_data_root,
&grok_creds_manager,
&mut visited_login,
).await;
match result {
Err(err) => {
error!("Error checking Grok login window: {:?}", err);
}
Ok(false) => {} // Continue iteration and try again...
Ok(true) => {
info!("Successfully saved cookies from login window. Closing.");
if let Err(err) = login_webview_window.close() {
error!("Error closing login window: {:?}", err);
}
return;
}
}
tokio::time::sleep(std::time::Duration::from_millis(2_000)).await;
}
}
/// Returns true if we can exit.
async fn check_login_window(
app_handle: &AppHandle,
webview_window: &WebviewWindow,
app_data_root: &AppDataRoot,
grok_creds_manager: &GrokCredentialManager,
visited_login: &mut bool,
) -> AnyhowResult<bool> {
/* Login flow looks like this:
1. Start: https://accounts.x.ai/sign-in?redirect=grok-com
2. SSO / Google Login (etc): https://accounts.google.com/v3/signin/challenge/pwd?[query]
3. Done / Landing: https://grok.com/...
*/
let hostname = get_webview_window_hostname(webview_window)?;
let mut maybe_at_destination = false;
match hostname.as_str() {
"www.grok.com" |
"grok.com"
=> {
maybe_at_destination = true;
}
// chatgpt.com/auth is also an auth domain
"accounts.x.ai" |
"auth.openai.com" |
"accounts.google.com" |
"accounts.youtube.com" |
"login.live.com" |
"appleid.apple.com"
=> {
// NB: We're in auth flow.
info!("Grok webview is in auth flow; hostname `{}`.", hostname);
*visited_login = true;
return Ok(false)
}
_ => {}, // We just don't know...
}
let cookie_store = grok_login_webview_extract_cookies(webview_window)?;
let maybe_has_auth_cookies = true; //cookie_store_has_auth_cookies(&cookie_store); // TODO TODO FIXME
let maybe_has_enough_cookies = cookie_store.len() > 6;
let maybe_completed_login_cycle = *visited_login && maybe_at_destination;
// Misc cookies without login cookies are ~1055 length
// AUTH_I is ~1500 length
// AUTH_R is ~500 length
let maybe_has_big_enough_cookie = cookie_store.calculate_approx_cookie_character_length() > 2100;
let mut heuristic_count = 0;
if maybe_has_auth_cookies {
heuristic_count += 1;
}
if maybe_has_enough_cookies {
heuristic_count += 1;
}
if maybe_completed_login_cycle {
heuristic_count += 1;
}
if maybe_has_big_enough_cookie {
heuristic_count += 1;
}
if heuristic_count < 3 {
return Ok(false);
}
info!("Heuristic count is ({}); we're likely done.", heuristic_count);
info!("Current cookies (len {}): {:?}", cookie_store.len(), cookie_store.to_cookie_string());
//let response = get_user_info(GetUserInfoRequest {
// hostname: MidjourneyHostname::Standard,
// cookie_header: cookie_store.to_cookie_string(),
//}).await;
//match response {
// Err(err) => {
// error!("Error getting midjourney user info: {:?}", err);
// // NB: Fall through. Allow us to update the cookies.
// }
// Ok(user_info) => {
// info!("Got midjourney user info: {:?}", user_info);
// let user_info = MidjourneyUserInfo::from_api_response(user_info);
//
// //if let Err(err) = grok_creds_manager.replace_user_info(user_info) {
// // error!("Error saving midjourney user info: {:?}", err);
// // // NB: Fall through. Allow us to update the cookies.
// //}
// }
//}
grok_creds_manager.replace_cookie_store(cookie_store)?;
let result = grok_creds_manager.persist_to_disk();
if let Err(err) = result {
error!("Error persisting grok cookies to disk: {:?}", err);
return Ok(false);
}
let event = RefreshAccountStateEvent {
provider: Some(GenerationProvider::Grok),
};
if let Err(err) = event.send(&app_handle) {
error!("Failed to send RefreshAccountStateEvent: {:?}", err); // Fail open
}
Ok(true)
}
@@ -0,0 +1,3 @@
pub mod grok_login_window_open;
pub (super) mod grok_login_window_thread;
pub (super) mod grok_login_webview_extract_cookies;
@@ -0,0 +1 @@
pub mod grok_login_window;
@@ -1,3 +1,4 @@
pub mod grok;
pub mod midjourney;
pub mod sora;
pub mod storyteller;
@@ -15,6 +15,7 @@ use utoipa::ToSchema;
pub enum GenerationProvider {
Artcraft,
Fal,
Grok,
Midjourney,
Sora,
}
@@ -33,6 +34,7 @@ impl GenerationProvider {
match self {
Self::Artcraft => "artcraft",
Self::Fal => "fal",
Self::Grok => "grok",
Self::Midjourney => "midjourney",
Self::Sora => "sora",
}
@@ -42,6 +44,7 @@ impl GenerationProvider {
match value {
"artcraft" => Ok(Self::Artcraft),
"fal" => Ok(Self::Fal),
"grok" => Ok(Self::Grok),
"midjourney" => Ok(Self::Midjourney),
"sora" => Ok(Self::Sora),
_ => Err(EnumError::CouldNotConvertFromString(value.to_string())),
@@ -54,6 +57,7 @@ impl GenerationProvider {
BTreeSet::from([
Self::Artcraft,
Self::Fal,
Self::Grok,
Self::Midjourney,
Self::Sora,
])
@@ -73,6 +77,7 @@ mod tests {
fn test_serialization() {
assert_serialization(GenerationProvider::Artcraft, "artcraft");
assert_serialization(GenerationProvider::Fal, "fal");
assert_serialization(GenerationProvider::Grok, "grok");
assert_serialization(GenerationProvider::Midjourney, "midjourney");
assert_serialization(GenerationProvider::Sora, "sora");
}
@@ -81,6 +86,7 @@ mod tests {
fn to_str() {
assert_eq!(GenerationProvider::Artcraft.to_str(), "artcraft");
assert_eq!(GenerationProvider::Fal.to_str(), "fal");
assert_eq!(GenerationProvider::Grok.to_str(), "grok");
assert_eq!(GenerationProvider::Midjourney.to_str(), "midjourney");
assert_eq!(GenerationProvider::Sora.to_str(), "sora");
}
@@ -89,6 +95,7 @@ mod tests {
fn from_str() {
assert_eq!(GenerationProvider::from_str("artcraft").unwrap(), GenerationProvider::Artcraft);
assert_eq!(GenerationProvider::from_str("fal").unwrap(), GenerationProvider::Fal);
assert_eq!(GenerationProvider::from_str("grok").unwrap(), GenerationProvider::Grok);
assert_eq!(GenerationProvider::from_str("midjourney").unwrap(), GenerationProvider::Midjourney);
assert_eq!(GenerationProvider::from_str("sora").unwrap(), GenerationProvider::Sora);
}
@@ -107,9 +114,10 @@ mod tests {
#[test]
fn all_variants() {
let mut variants = GenerationProvider::all_variants();
assert_eq!(variants.len(), 4);
assert_eq!(variants.len(), 5);
assert_eq!(variants.pop_first(), Some(GenerationProvider::Artcraft));
assert_eq!(variants.pop_first(), Some(GenerationProvider::Fal));
assert_eq!(variants.pop_first(), Some(GenerationProvider::Grok));
assert_eq!(variants.pop_first(), Some(GenerationProvider::Midjourney));
assert_eq!(variants.pop_first(), Some(GenerationProvider::Sora));
assert_eq!(variants.pop_first(), None);