gpt-image-2-web 0.6.7

Self-hosted Docker/Web server for GPT Image 2.
use std::{
    collections::{BTreeMap, VecDeque},
    env, fs,
    path::{Path as FsPath, PathBuf},
    sync::{Arc, Mutex, mpsc},
    thread,
    time::{SystemTime, UNIX_EPOCH},
};

use axum::{
    Json, Router,
    body::Body,
    extract::{Path, Query, State},
    http::{StatusCode, header},
    response::{IntoResponse, Response},
    routing::{get, post, put},
};
use gpt_image_2_core::{
    AppConfig, CONFIG_DIR_NAME, CredentialRef, EditRequest, GenerateRequest, HistoryListOptions,
    KEYCHAIN_SERVICE, NotificationConfig, PathConfig, ProductRuntime, ProviderConfig,
    StorageConfig, StorageReadbackOptions, StorageTargetConfig, StorageUploadOverrides, UploadFile,
    annotate_recovery_job_dir, append_history_job_event, batch_output_path, batch_recovery_job_dir,
    batch_recovery_job_id, build_recovery_descriptor, default_config_path,
    default_keychain_account, delete_history_job, dispatch_task_notifications,
    edit_args_with_recovery, generate_args_with_recovery, generation_slots_from_batch_payload,
    generation_slots_from_outputs, history_db_path, initialize_product_runtime_paths,
    legacy_jobs_dir, legacy_shared_codex_dir, list_active_history_jobs, list_history_job_events,
    list_history_jobs_page, load_app_config, mark_interrupted_jobs_on_startup,
    materialize_openai_raw_response, merge_recovery_metadata, missing_generation_slot_indices,
    notification_status_allowed, output_extension, preserve_notification_secrets,
    preserve_storage_secrets, product_app_data_dir, product_default_export_dir,
    product_default_export_dirs, product_result_library_dir, product_storage_fallback_dir,
    raw_response_path, read_job_output_from_storage_with_options, read_keychain_secret,
    recovery_job_dir, redact_app_config, requested_n, run_json, save_app_config, shared_config_dir,
    show_history_job, test_fault, test_storage_target, upload_job_outputs_to_storage,
    upsert_history_job, write_batch_recovery_summary, write_keychain_secret,
};
use serde::Deserialize;
use serde_json::{Value, json};
use tower_http::services::{ServeDir, ServeFile};

mod config_api;
mod error;
mod file_api;
mod history_api;
mod job_execution;
mod provider_config;
mod queue_api;
mod queue_workers;
mod recovery_api;
mod retry_api;
mod server;
mod support;
mod types;

pub(crate) use config_api::*;
pub(crate) use error::*;
pub(crate) use file_api::*;
pub(crate) use history_api::*;
pub(crate) use job_execution::*;
pub(crate) use provider_config::*;
pub(crate) use queue_api::*;
pub(crate) use queue_workers::*;
pub(crate) use recovery_api::*;
pub(crate) use retry_api::*;
pub(crate) use server::*;
pub(crate) use support::*;
pub(crate) use types::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let _ = initialize_product_runtime_paths(ProductRuntime::DockerWeb);
    let settings = parse_settings().map_err(std::io::Error::other)?;
    if !settings.static_dir.is_dir() {
        return Err(format!(
            "Static directory does not exist: {}",
            settings.static_dir.display()
        )
        .into());
    }
    let _ = mark_interrupted_jobs_on_startup();
    let static_files = ServeDir::new(&settings.static_dir)
        .not_found_service(ServeFile::new(settings.static_dir.join("index.html")));
    let app = Router::new()
        .nest("/api", api_router(JobQueueState::default()))
        .fallback_service(static_files);
    let listener =
        tokio::net::TcpListener::bind(format!("{}:{}", settings.host, settings.port)).await?;
    println!(
        "gpt-image-2-web listening on http://{}:{}",
        settings.host, settings.port
    );
    axum::serve(listener, app).await?;
    Ok(())
}