minimax feature (#1865)

This commit is contained in:
hanashi
2026-08-08 16:34:49 -04:00
committed by GitHub
parent 4d839d3b8b
commit 9646a791aa
5 changed files with 174 additions and 3 deletions
@@ -49,6 +49,14 @@ pub enum UserFeatureFlag {
/// Access to API key creation
#[serde(rename = "api_key")]
ApiKey,
/// Access to Minimax
#[serde(rename = "minimax")]
Minimax,
/// Access to Minimax with debugging priority (admins)
#[serde(rename = "minimax_p")]
MinimaxPriority,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
@@ -69,6 +77,8 @@ impl UserFeatureFlag {
Self::HappyHorseRateLimit => "hh_rl",
Self::ReferralsProgram => "referrals",
Self::ApiKey => "api_key",
Self::Minimax => "minimax",
Self::MinimaxPriority => "minimax_p",
}
}
@@ -83,6 +93,8 @@ impl UserFeatureFlag {
"hh_rl" => Ok(Self::HappyHorseRateLimit),
"referrals" => Ok(Self::ReferralsProgram),
"api_key" => Ok(Self::ApiKey),
"minimax" => Ok(Self::Minimax),
"minimax_p" => Ok(Self::MinimaxPriority),
_ => Err(format!("invalid value: {:?}", value)),
}
}
@@ -98,6 +110,8 @@ impl UserFeatureFlag {
Self::HappyHorseRateLimit => "Happy Horse (with Rate Limit)",
Self::ReferralsProgram => "Referrals Program",
Self::ApiKey => "API Keys",
Self::Minimax => "Minimax",
Self::MinimaxPriority => "Minimax (Debugging Priority)",
}
}
@@ -112,6 +126,8 @@ impl UserFeatureFlag {
Self::HappyHorseRateLimit => "Access to Happy Horse (with a rate limit)",
Self::ReferralsProgram => "Access to the referrals program",
Self::ApiKey => "Access to API key creation",
Self::Minimax => "Access to Minimax",
Self::MinimaxPriority => "Access to Minimax with debugging priority (admins)",
}
}
@@ -128,6 +144,8 @@ impl UserFeatureFlag {
Self::HappyHorseRateLimit,
Self::ReferralsProgram,
Self::ApiKey,
Self::Minimax,
Self::MinimaxPriority,
])
}
}
@@ -151,6 +169,8 @@ mod tests {
assert_serialization(UserFeatureFlag::HappyHorseRateLimit, "hh_rl");
assert_serialization(UserFeatureFlag::ReferralsProgram, "referrals");
assert_serialization(UserFeatureFlag::ApiKey, "api_key");
assert_serialization(UserFeatureFlag::Minimax, "minimax");
assert_serialization(UserFeatureFlag::MinimaxPriority, "minimax_p");
}
#[test]
@@ -164,6 +184,8 @@ mod tests {
assert_eq!(UserFeatureFlag::HappyHorseRateLimit.to_str(), "hh_rl");
assert_eq!(UserFeatureFlag::ReferralsProgram.to_str(), "referrals");
assert_eq!(UserFeatureFlag::ApiKey.to_str(), "api_key");
assert_eq!(UserFeatureFlag::Minimax.to_str(), "minimax");
assert_eq!(UserFeatureFlag::MinimaxPriority.to_str(), "minimax_p");
}
#[test]
@@ -177,13 +199,15 @@ mod tests {
assert_eq!(UserFeatureFlag::from_str("hh_rl").unwrap(), UserFeatureFlag::HappyHorseRateLimit);
assert_eq!(UserFeatureFlag::from_str("referrals").unwrap(), UserFeatureFlag::ReferralsProgram);
assert_eq!(UserFeatureFlag::from_str("api_key").unwrap(), UserFeatureFlag::ApiKey);
assert_eq!(UserFeatureFlag::from_str("minimax").unwrap(), UserFeatureFlag::Minimax);
assert_eq!(UserFeatureFlag::from_str("minimax_p").unwrap(), UserFeatureFlag::MinimaxPriority);
assert!(UserFeatureFlag::from_str("foo").is_err());
}
#[test]
fn all_variants() {
let mut variants = UserFeatureFlag::all_variants();
assert_eq!(variants.len(), 9);
assert_eq!(variants.len(), 11);
assert_eq!(variants.pop_first(), Some(UserFeatureFlag::ExploreMedia));
assert_eq!(variants.pop_first(), Some(UserFeatureFlag::Studio));
assert_eq!(variants.pop_first(), Some(UserFeatureFlag::Upload3d));
@@ -193,6 +217,8 @@ mod tests {
assert_eq!(variants.pop_first(), Some(UserFeatureFlag::HappyHorseRateLimit));
assert_eq!(variants.pop_first(), Some(UserFeatureFlag::ReferralsProgram));
assert_eq!(variants.pop_first(), Some(UserFeatureFlag::ApiKey));
assert_eq!(variants.pop_first(), Some(UserFeatureFlag::Minimax));
assert_eq!(variants.pop_first(), Some(UserFeatureFlag::MinimaxPriority));
assert_eq!(variants.pop_first(), None);
}
}
@@ -46,5 +46,81 @@ pub fn minimax_video_models() -> Vec<OmniGenVideoModelDetails> {
..Default::default()
});
models.push(OmniGenVideoModelDetails {
model: CommonVideoModel::MinimaxH3Turbo,
model_creator: Some(ModelCreator::Hailuo),
full_name: Some("MiniMax H3 Turbo".to_string()),
text_prompt_supported: Some(true),
text_prompt_max_length: Some(7000),
starting_keyframe_supported: Some(true),
ending_keyframe_supported: Some(true),
image_references_supported: Some(true),
image_references_max: Some(9),
video_references_supported: Some(true),
video_references_max: Some(3),
video_references_max_total_duration_seconds: Some(15),
audio_references_supported: Some(true),
audio_references_max: Some(3),
audio_references_max_total_duration_seconds: Some(15),
aspect_ratio_options: Some(vec![
CommonAspectRatio::WideTwentyOneByNine,
CommonAspectRatio::WideSixteenByNine,
CommonAspectRatio::WideFourByThree,
CommonAspectRatio::Square,
CommonAspectRatio::TallThreeByFour,
CommonAspectRatio::TallNineBySixteen,
]),
aspect_ratio_default: Some(CommonAspectRatio::WideSixteenByNine),
// The model renders 768P or 2K; 720p and below land on 768P, 1080p and
// above land on 2K.
resolution_options: Some(vec![
CommonResolution::SevenTwentyP,
CommonResolution::TwoK,
]),
resolution_default: Some(CommonResolution::TwoK),
duration_seconds_min: Some(5),
duration_seconds_max: Some(15),
duration_seconds_default: Some(5),
..Default::default()
});
models.push(OmniGenVideoModelDetails {
model: CommonVideoModel::MinimaxH3Ultra,
model_creator: Some(ModelCreator::Hailuo),
full_name: Some("MiniMax H3 Ultra".to_string()),
text_prompt_supported: Some(true),
text_prompt_max_length: Some(7000),
starting_keyframe_supported: Some(true),
ending_keyframe_supported: Some(true),
image_references_supported: Some(true),
image_references_max: Some(9),
video_references_supported: Some(true),
video_references_max: Some(3),
video_references_max_total_duration_seconds: Some(15),
audio_references_supported: Some(true),
audio_references_max: Some(3),
audio_references_max_total_duration_seconds: Some(15),
aspect_ratio_options: Some(vec![
CommonAspectRatio::WideTwentyOneByNine,
CommonAspectRatio::WideSixteenByNine,
CommonAspectRatio::WideFourByThree,
CommonAspectRatio::Square,
CommonAspectRatio::TallThreeByFour,
CommonAspectRatio::TallNineBySixteen,
]),
aspect_ratio_default: Some(CommonAspectRatio::WideSixteenByNine),
// The model renders 768P or 2K; 720p and below land on 768P, 1080p and
// above land on 2K.
resolution_options: Some(vec![
CommonResolution::SevenTwentyP,
CommonResolution::TwoK,
]),
resolution_default: Some(CommonResolution::TwoK),
duration_seconds_min: Some(5),
duration_seconds_max: Some(15),
duration_seconds_default: Some(5),
..Default::default()
});
models
}
@@ -107,6 +107,14 @@ fn build_omni_gen_video_model_providers() -> Vec<OmniGenVideoModelProviderDetail
model: CommonVideoModel::MinimaxH3,
overrides: None,
},
OmniGenVideoProviderModelDetails {
model: CommonVideoModel::MinimaxH3Turbo,
overrides: None,
},
OmniGenVideoProviderModelDetails {
model: CommonVideoModel::MinimaxH3Ultra,
overrides: None,
},
OmniGenVideoProviderModelDetails {
model: CommonVideoModel::Flux3,
overrides: None,
@@ -13,6 +13,13 @@ use artcraft_api_defs::omni_gen::models::omni_gen_video_models::{
use enums::common::generation::common_video_model::CommonVideoModel;
use log::warn;
/// Models hidden behind the Minimax feature flags. MinimaxH3 itself stays
/// available to everyone.
const MINIMAX_GATED_MODELS: [CommonVideoModel; 2] = [
CommonVideoModel::MinimaxH3Turbo,
CommonVideoModel::MinimaxH3Ultra,
];
/// List available video models.
#[utoipa::path(
get,
@@ -25,14 +32,60 @@ use log::warn;
),
)]
pub async fn omni_gen_video_models_handler(
_http_request: HttpRequest,
_server_state: web::Data<Arc<ServerState>>,
http_request: HttpRequest,
server_state: web::Data<Arc<ServerState>>,
_query: Query<OmniGenVideoModelsQuery>,
) -> Result<Json<OmniGenVideoModelsResponse>, CommonWebError> {
let mut response = (*OMNI_GEN_VIDEO_MODELS_AND_PROVIDERS).clone();
if !can_see_minimax(&http_request, &server_state).await? {
remove_models(&mut response, &MINIMAX_GATED_MODELS);
}
Ok(Json(response))
}
/// Whether the (optional) logged-in user holds a Minimax feature flag.
/// Anonymous requests see the ungated model list.
pub async fn can_see_minimax(
http_request: &HttpRequest,
server_state: &ServerState,
) -> Result<bool, CommonWebError> {
let mut mysql_connection = server_state.mysql_pool.acquire().await?;
let maybe_user_session = server_state
.session_checker
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
.await
.map_err(|e| {
warn!("Session checker error: {:?}", e);
CommonWebError::from(e)
})?;
let user_session = match maybe_user_session {
Some(session) => session,
None => return Ok(false),
};
let user_feature_flags =
UserSessionFeatureFlags::new(user_session.maybe_feature_flags.as_deref());
let can_use_minimax = user_feature_flags.can_use_minimax();
let can_use_minimax_priority = user_feature_flags.can_use_minimax_priority();
Ok(can_use_minimax || can_use_minimax_priority)
}
/// Strip the given models from both the top-level model list and every
/// provider's model list.
fn remove_models(response: &mut OmniGenVideoModelsResponse, hidden_models: &[CommonVideoModel]) {
response.models.retain(|details| !hidden_models.contains(&details.model));
for provider in &mut response.providers {
provider.models.retain(|details| !hidden_models.contains(&details.model));
}
}
// NB: Keeping this for future reference if we need to feature gate other models.
pub async fn can_see_happy_horse(
http_request: &HttpRequest,
@@ -101,6 +101,14 @@ impl UserSessionFeatureFlags {
pub fn can_use_referrals_program(&self) -> bool {
self.feature_flags.contains(&UserFeatureFlag::ReferralsProgram)
}
pub fn can_use_minimax(&self) -> bool {
self.feature_flags.contains(&UserFeatureFlag::Minimax)
}
pub fn can_use_minimax_priority(&self) -> bool {
self.feature_flags.contains(&UserFeatureFlag::MinimaxPriority)
}
}
#[cfg(test)]