mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-27 20:20:07 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88e8f99243 |
+5
-18
@@ -40,6 +40,7 @@ pub struct MediaFileInsertBuilder {
|
||||
origin_product_category: MediaFileOriginProductCategory, // NB: Non-nullable field
|
||||
// maybe_origin_model_type: Option<MediaFileOriginModelType>,
|
||||
// maybe_origin_model_token: Option<&'a ModelWeightToken>,
|
||||
// maybe_origin_filename: Option<String>,
|
||||
|
||||
// Media info
|
||||
maybe_mime_type: Option<String>,
|
||||
@@ -50,10 +51,6 @@ pub struct MediaFileInsertBuilder {
|
||||
maybe_frame_width: Option<u32>,
|
||||
maybe_frame_height: Option<u32>,
|
||||
checksum_sha2: Option<String>, // NB: Non-nullable field
|
||||
|
||||
// File name
|
||||
maybe_title: Option<String>,
|
||||
maybe_origin_filename: Option<String>,
|
||||
|
||||
// Media info for certain product areas
|
||||
maybe_engine_category: Option<MediaFileEngineCategory>, // TODO: Deprecate
|
||||
@@ -103,8 +100,6 @@ impl MediaFileInsertBuilder {
|
||||
maybe_frame_width: None,
|
||||
maybe_frame_height: None,
|
||||
checksum_sha2: None,
|
||||
maybe_title: None,
|
||||
maybe_origin_filename: None,
|
||||
maybe_engine_category: None,
|
||||
maybe_batch_generation_token: None,
|
||||
public_bucket_directory_hash: None,
|
||||
@@ -181,6 +176,7 @@ impl MediaFileInsertBuilder {
|
||||
|
||||
// TODO: Method for maybe_origin_model_type
|
||||
// TODO: Method for maybe_origin_model_token
|
||||
// TODO: Method for maybe_origin_filename
|
||||
|
||||
pub fn mime_type(mut self, mime_type: &str) -> Self {
|
||||
self.maybe_mime_type = Some(mime_type.to_string());
|
||||
@@ -230,16 +226,7 @@ impl MediaFileInsertBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn maybe_title(mut self, maybe_title: Option<&str>) -> Self {
|
||||
self.maybe_title = maybe_title.map(|title| title.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn maybe_origin_filename(mut self, maybe_origin_filename: Option<&str>) -> Self {
|
||||
self.maybe_origin_filename = maybe_origin_filename.map(|s| s.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
// TODO: maybe_title
|
||||
// TODO: maybe_text_transcript
|
||||
// TODO: maybe_scene_source_media_file_token
|
||||
// TODO: maybe_prompt_token
|
||||
@@ -287,7 +274,7 @@ impl MediaFileInsertBuilder {
|
||||
origin_product_category: self.origin_product_category,
|
||||
maybe_origin_model_type: None, // TODO
|
||||
maybe_origin_model_token: None, // TODO
|
||||
maybe_origin_filename: self.maybe_origin_filename,
|
||||
maybe_origin_filename: None, // TODO
|
||||
maybe_mime_type: self.maybe_mime_type.as_deref(),
|
||||
file_size_bytes: self.file_size_bytes,
|
||||
maybe_duration_millis: self.maybe_duration_millis,
|
||||
@@ -297,7 +284,7 @@ impl MediaFileInsertBuilder {
|
||||
maybe_frame_height: self.maybe_frame_height,
|
||||
maybe_engine_category: self.maybe_engine_category,
|
||||
checksum_sha2: &checksum_sha2,
|
||||
maybe_title: self.maybe_title.as_deref(),
|
||||
maybe_title: None, // TODO
|
||||
maybe_text_transcript: None, // TODO
|
||||
maybe_scene_source_media_file_token: None, // TODO
|
||||
maybe_prompt_token: self.maybe_prompt_token.as_ref(),
|
||||
|
||||
+26
-63
@@ -7,7 +7,6 @@ use log::{error, info, warn};
|
||||
|
||||
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
|
||||
use enums::by_table::media_files::media_file_class::MediaFileClass;
|
||||
use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory;
|
||||
use enums::by_table::media_files::media_file_type::MediaFileType;
|
||||
use enums::common::visibility::Visibility;
|
||||
use hashing::sha256::sha256_hash_bytes::sha256_hash_bytes;
|
||||
@@ -16,7 +15,6 @@ use media::decode_basic_audio_info::decode_basic_audio_bytes_info;
|
||||
use mimetypes::mimetype_for_bytes::get_mimetype_for_bytes;
|
||||
use mimetypes::mimetype_to_extension::mimetype_to_extension;
|
||||
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
|
||||
use mysql_queries::queries::media_files::create::insert_builder::media_file_insert_builder::MediaFileInsertBuilder;
|
||||
use mysql_queries::queries::media_files::create::specialized_insert::insert_media_file_from_file_upload::{insert_media_file_from_file_upload, InsertMediaFileFromUploadArgs, UploadType};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
@@ -358,23 +356,10 @@ pub async fn process_upload_media_file(
|
||||
MediaFileType::Bvh => Some(".bvh".to_string()),
|
||||
MediaFileType::Fbx => Some(".fbx".to_string()),
|
||||
MediaFileType::Gltf => Some(".gltf".to_string()),
|
||||
MediaFileType::Spz => Some(".spz".to_string()),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
|
||||
// For WorldLabs spz files.
|
||||
let is_world_labs_spz= upload_media_request.file_name
|
||||
.as_deref()
|
||||
.map(|filename| filename.trim().to_ascii_lowercase())
|
||||
.filter(|filename| filename.contains("ceramic"))
|
||||
.filter(|filename| filename.ends_with(".spz"))
|
||||
.is_some();
|
||||
|
||||
if is_world_labs_spz {
|
||||
extension = Some(".ceramic.spz".to_string());
|
||||
}
|
||||
|
||||
const PREFIX : Option<&str> = Some("upload_");
|
||||
|
||||
let public_upload_path = MediaFileBucketPath::generate_new(PREFIX, extension.as_deref());
|
||||
@@ -391,61 +376,39 @@ pub async fn process_upload_media_file(
|
||||
MediaFileUploadError::ServerError
|
||||
})?;
|
||||
|
||||
let media_token = MediaFileInsertBuilder::new()
|
||||
.maybe_creator_user(maybe_user_token.as_ref())
|
||||
.maybe_creator_anonymous_visitor(maybe_avt_token.as_ref())
|
||||
.creator_ip_address(&ip_address)
|
||||
.public_bucket_directory_hash(&public_upload_path)
|
||||
.media_file_class(media_file_class)
|
||||
.media_file_type(media_file_type)
|
||||
.media_file_origin_category(MediaFileOriginCategory::Upload)
|
||||
.creator_set_visibility(creator_set_visibility)
|
||||
.mime_type(mime_type)
|
||||
.file_size_bytes(file_size_bytes as u64)
|
||||
.checksum_sha2(&hash)
|
||||
.maybe_title(upload_media_request.title.as_deref())
|
||||
.maybe_origin_filename(upload_media_request.file_name.as_deref())
|
||||
.is_intermediate_system_file(false)
|
||||
.insert_pool(&server_state.mysql_pool)
|
||||
let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
|
||||
maybe_media_class: Some(media_file_class),
|
||||
media_file_type,
|
||||
maybe_creator_user_token: maybe_user_token.as_ref(),
|
||||
maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility,
|
||||
upload_type,
|
||||
maybe_engine_category: None,
|
||||
maybe_prompt_token: None,
|
||||
maybe_batch_token: None,
|
||||
maybe_animation_type: None,
|
||||
maybe_mime_type: Some(mime_type),
|
||||
file_size_bytes: file_size_bytes as u64,
|
||||
maybe_duration_millis: None, // TODO: Fix this?
|
||||
sha256_checksum: &hash,
|
||||
maybe_title: upload_media_request.title.as_deref(),
|
||||
maybe_scene_source_media_file_token: None,
|
||||
is_intermediate_system_file: false, // NB: is_user_upload = TRUE
|
||||
public_bucket_directory_hash: public_upload_path.get_object_hash(),
|
||||
maybe_public_bucket_prefix: PREFIX,
|
||||
maybe_public_bucket_extension: extension.as_deref(),
|
||||
pool: &server_state.mysql_pool,
|
||||
})
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("New generic download creation DB error: {:?}", err);
|
||||
MediaFileUploadError::ServerError
|
||||
})?;
|
||||
|
||||
//let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
|
||||
// maybe_media_class: Some(media_file_class), // DONE
|
||||
// media_file_type, // DONE
|
||||
// maybe_creator_user_token: maybe_user_token.as_ref(), // DONE
|
||||
// maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(), // DONE
|
||||
// creator_ip_address: &ip_address, // DONE
|
||||
// creator_set_visibility, // DONE
|
||||
// upload_type, // DONE - weird type
|
||||
// maybe_engine_category: None, // DONE
|
||||
// maybe_prompt_token: None, // DONE
|
||||
// maybe_batch_token: None, // DONE
|
||||
// maybe_animation_type: None, // DONE
|
||||
// maybe_mime_type: Some(mime_type), // DONE
|
||||
// file_size_bytes: file_size_bytes as u64, // DONE
|
||||
// maybe_duration_millis: None, // TODO: Fix this? // DONE
|
||||
// sha256_checksum: &hash, // DONE
|
||||
// maybe_title: upload_media_request.title.as_deref(), // DONE
|
||||
// maybe_scene_source_media_file_token: None, // DONE
|
||||
// is_intermediate_system_file: false, // NB: is_user_upload = TRUE // DONE
|
||||
// public_bucket_directory_hash: public_upload_path.get_object_hash(), // DONE
|
||||
// maybe_public_bucket_prefix: PREFIX, // DONE
|
||||
// maybe_public_bucket_extension: extension.as_deref(), // DONE
|
||||
// pool: &server_state.mysql_pool,
|
||||
//})
|
||||
// .await
|
||||
// .map_err(|err| {
|
||||
// warn!("New generic download creation DB error: {:?}", err);
|
||||
// MediaFileUploadError::ServerError
|
||||
// })?;
|
||||
|
||||
info!("new media file - token: {:?}", &media_token);
|
||||
info!("new media file id: {} token: {:?}", record_id, &token);
|
||||
|
||||
Ok(SuccessCase::MediaSuccessfullyUploaded {
|
||||
media_file_token: media_token,
|
||||
media_file_token: token,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import {
|
||||
faFilm,
|
||||
faPalette,
|
||||
faImage,
|
||||
faPenNib,
|
||||
faDroplet,
|
||||
faPhotoFilm,
|
||||
faGlobe,
|
||||
faPencil,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { useTabStore, TabId } from "~/pages/Stores/TabState";
|
||||
import { set3DPageMounted } from "~/pages/PageEnigma/Editor/editor";
|
||||
@@ -47,7 +47,7 @@ export const APP_DESCRIPTORS: AppDescriptor[] = [
|
||||
{
|
||||
id: "EDIT",
|
||||
label: "Edit Image",
|
||||
icon: faPencil,
|
||||
icon: faPenNib,
|
||||
imageSrc: "/resources/gifs/INPAINT_CANVAS_DEMO.gif",
|
||||
description: "Modify your image with AI",
|
||||
large: true,
|
||||
@@ -123,7 +123,7 @@ export const ALL_APPS: FullAppItem[] = [
|
||||
id: "edit-image",
|
||||
label: "Edit Image",
|
||||
description: "Change with inpainting",
|
||||
icon: faPencil,
|
||||
icon: faPenNib,
|
||||
category: "edit",
|
||||
action: "EDIT",
|
||||
color: "bg-purple-600/40",
|
||||
|
||||
@@ -283,11 +283,11 @@ const PageDraw = () => {
|
||||
onAspectRatioChange={async (ratio: string) => {
|
||||
const ratioToType = (ratio: string): AspectRatioType => {
|
||||
switch (ratio) {
|
||||
case "tall":
|
||||
case "2:3":
|
||||
return AspectRatioType.PORTRAIT;
|
||||
case "wide":
|
||||
case "3:2":
|
||||
return AspectRatioType.LANDSCAPE;
|
||||
case "square":
|
||||
case "1:1":
|
||||
return AspectRatioType.SQUARE;
|
||||
default:
|
||||
return AspectRatioType.NONE;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { faImages, faPencil } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faImage, faImages } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { downloadFileFromUrl } from "libs/api/src/lib/LocalApi";
|
||||
import { GalleryModal, GalleryItem } from "@storyteller/ui-gallery-modal";
|
||||
import { useState } from "react";
|
||||
@@ -153,7 +153,7 @@ export const BaseImageSelector = ({
|
||||
<div className="flex h-full w-full items-center justify-center overflow-hidden bg-ui-panel text-base-fg">
|
||||
<div className="aspect-video w-full max-w-5xl bg-ui-background">
|
||||
<UploadEntryCard
|
||||
icon={faPencil}
|
||||
icon={faImage}
|
||||
title="Edit Image"
|
||||
description="Click to upload or drag and drop an image here to edit"
|
||||
accentBackgroundClass="bg-blue-500/40"
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import { useState, useRef, useEffect, useCallback } from "react";
|
||||
import Konva from "konva"; // just for types
|
||||
import {
|
||||
EnqueueEditImage,
|
||||
EnqueueImageInpaint,
|
||||
EnqueueEditImageSize,
|
||||
EnqueueEditImageResolution,
|
||||
} from "@storyteller/tauri-api";
|
||||
import { PromptsApi } from "@storyteller/api";
|
||||
import { RefImage, usePromptEditStore } from "@storyteller/ui-promptbox";
|
||||
import { EnqueueEditImage, EnqueueImageInpaint } from "@storyteller/tauri-api";
|
||||
import { ContextMenuContainer } from "../PageDraw/components/ui/ContextMenu";
|
||||
import { useCopyPasteHotkeys } from "../PageDraw/hooks/useCopyPasteHotkeys";
|
||||
import { useDeleteHotkeys } from "../PageDraw/hooks/useDeleteHotkeys";
|
||||
@@ -65,7 +58,6 @@ const PageEdit = () => {
|
||||
const historyImageBundles = useEditStore(
|
||||
(state) => state.historyImageBundles,
|
||||
);
|
||||
const referenceImages = usePromptEditStore((s) => s.referenceImages);
|
||||
|
||||
// Pass store actions directly as callbacks
|
||||
useDeleteHotkeys({ onDelete: store.deleteSelectedItems });
|
||||
@@ -154,16 +146,17 @@ const PageEdit = () => {
|
||||
// Top toolbar (MarkerToolControlBar): ~140px from top (toolbar + padding)
|
||||
// Bottom prompt box: ~200px from bottom (can vary with reference images)
|
||||
// Mode selector above prompt: ~60px
|
||||
const hasTopToolbar =
|
||||
const topToolbarHeight =
|
||||
store.activeTool === "marker" ||
|
||||
store.activeTool === "eraser" ||
|
||||
store.activeTool === "edit";
|
||||
const topToolbarHeight = hasTopToolbar ? 140 : 80;
|
||||
store.activeTool === "edit"
|
||||
? 140
|
||||
: 0;
|
||||
|
||||
// Check if model supports masking or is nano banana (has mode selector)
|
||||
const hasModeSelectorUI =
|
||||
selectedImageModel?.usesInpaintingMask ||
|
||||
selectedImageModel?.isNanoBananaModel();
|
||||
selectedImageModel?.id === "gemini_25_flash";
|
||||
const bottomUIHeight = hasModeSelectorUI ? 260 : 200;
|
||||
const totalVerticalReserved = topToolbarHeight + bottomUIHeight;
|
||||
|
||||
@@ -276,54 +269,8 @@ const PageEdit = () => {
|
||||
return new Uint8Array(arrayBuffer);
|
||||
};
|
||||
|
||||
const getCompositeCanvasFile = async (): Promise<File | null> => {
|
||||
if (
|
||||
!stageRef.current ||
|
||||
!leftPanelRef.current ||
|
||||
!baseImageKonvaRef.current ||
|
||||
!store.baseImageBitmap
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rect = baseImageKonvaRef.current;
|
||||
const width = rect.width();
|
||||
const height = rect.height();
|
||||
|
||||
const canvas = new OffscreenCanvas(width, height);
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return null;
|
||||
|
||||
ctx.drawImage(store.baseImageBitmap, 0, 0, width, height);
|
||||
|
||||
const markerLayerCanvas = leftPanelRef.current.toCanvas({
|
||||
x: stageRef.current.x(),
|
||||
y: stageRef.current.y(),
|
||||
width: rect.width() * stageRef.current.scaleX(),
|
||||
height: rect.height() * stageRef.current.scaleY(),
|
||||
pixelRatio: 1 / stageRef.current.scaleX(),
|
||||
});
|
||||
const fittedMarkerCanvas = normalizeCanvas(
|
||||
markerLayerCanvas,
|
||||
width,
|
||||
height,
|
||||
);
|
||||
ctx.drawImage(fittedMarkerCanvas, 0, 0, width, height);
|
||||
|
||||
const blob = await canvas.convertToBlob({ type: "image/png" });
|
||||
const uuid = crypto.randomUUID();
|
||||
return new File([blob], `${uuid}.png`, { type: "image/png" });
|
||||
};
|
||||
|
||||
const handleGenerate = useCallback(
|
||||
async (
|
||||
prompt: string,
|
||||
options?: {
|
||||
aspectRatio?: string;
|
||||
resolution?: string;
|
||||
images?: RefImage[];
|
||||
},
|
||||
) => {
|
||||
async (prompt: string) => {
|
||||
const editedImageToken = store.baseImageInfo?.mediaToken;
|
||||
|
||||
if (!editedImageToken) {
|
||||
@@ -331,39 +278,7 @@ const PageEdit = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Helper to map aspect ratio string to enum
|
||||
const mapAspectRatio = (
|
||||
ratio?: string,
|
||||
): EnqueueEditImageSize | undefined => {
|
||||
switch (ratio) {
|
||||
case "auto":
|
||||
return EnqueueEditImageSize.Auto;
|
||||
case "wide":
|
||||
return EnqueueEditImageSize.Wide;
|
||||
case "tall":
|
||||
return EnqueueEditImageSize.Tall;
|
||||
case "square":
|
||||
return EnqueueEditImageSize.Square;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
// Helper to map resolution string to enum
|
||||
const mapResolution = (
|
||||
res?: string,
|
||||
): EnqueueEditImageResolution | undefined => {
|
||||
switch (res) {
|
||||
case "1k":
|
||||
return EnqueueEditImageResolution.OneK;
|
||||
case "2k":
|
||||
return EnqueueEditImageResolution.TwoK;
|
||||
case "4k":
|
||||
return EnqueueEditImageResolution.FourK;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
const arrayBuffer = await getMaskArrayBuffer();
|
||||
|
||||
const subscriberId: string =
|
||||
crypto?.randomUUID?.() ??
|
||||
@@ -373,7 +288,6 @@ const PageEdit = () => {
|
||||
let result;
|
||||
|
||||
if (selectedImageModel?.editingIsInpainting) {
|
||||
const arrayBuffer = await getMaskArrayBuffer();
|
||||
result = await EnqueueImageInpaint({
|
||||
model: selectedImageModel,
|
||||
image_media_token: editedImageToken,
|
||||
@@ -383,51 +297,17 @@ const PageEdit = () => {
|
||||
frontend_caller: "image_editor",
|
||||
frontend_subscriber_id: subscriberId,
|
||||
});
|
||||
} else if (selectedImageModel?.isNanoBananaModel()) {
|
||||
const compositeFile = await getCompositeCanvasFile();
|
||||
if (!compositeFile) {
|
||||
console.error("Failed to create composite canvas");
|
||||
return;
|
||||
}
|
||||
const api = new PromptsApi();
|
||||
const snapshotResult = await api.uploadSceneSnapshot({
|
||||
screenshot: compositeFile,
|
||||
});
|
||||
if (!snapshotResult.data) {
|
||||
console.error("Failed to upload scene snapshot");
|
||||
return;
|
||||
}
|
||||
const imgs = options?.images || [];
|
||||
result = await EnqueueEditImage({
|
||||
model: selectedImageModel,
|
||||
scene_image_media_token: snapshotResult.data,
|
||||
image_media_tokens: imgs
|
||||
.map((img) => img.mediaToken)
|
||||
.filter((t) => t.length > 0),
|
||||
prompt: prompt,
|
||||
image_count: generationCount,
|
||||
frontend_caller: "image_editor",
|
||||
frontend_subscriber_id: subscriberId,
|
||||
aspect_ratio: mapAspectRatio(options?.aspectRatio),
|
||||
image_resolution: mapResolution(options?.resolution),
|
||||
});
|
||||
} else {
|
||||
const imgs = options?.images || [];
|
||||
result = await EnqueueEditImage({
|
||||
model: selectedImageModel,
|
||||
image_media_tokens: [
|
||||
editedImageToken,
|
||||
...imgs
|
||||
.filter((img) => img.mediaToken !== editedImageToken)
|
||||
.map((img) => img.mediaToken),
|
||||
].filter((t) => t.length > 0),
|
||||
disable_system_prompt: true,
|
||||
image_media_tokens: [editedImageToken],
|
||||
disable_system_prompt: true, // No meaning yet
|
||||
prompt: prompt,
|
||||
image_count: generationCount,
|
||||
frontend_caller: "image_editor",
|
||||
frontend_subscriber_id: subscriberId,
|
||||
aspect_ratio: mapAspectRatio(options?.aspectRatio),
|
||||
image_resolution: mapResolution(options?.resolution),
|
||||
//scene_image_media_token,
|
||||
//aspect_ratio: aspectRatio,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -444,13 +324,7 @@ const PageEdit = () => {
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
generationCount,
|
||||
selectedImageModel,
|
||||
store.baseImageInfo?.mediaToken,
|
||||
referenceImages,
|
||||
],
|
||||
[generationCount, selectedImageModel, store.baseImageInfo?.mediaToken],
|
||||
);
|
||||
|
||||
const isNanoBananaModel = selectedImageModel?.isNanoBananaModel();
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import React from "react";
|
||||
import { PromptBoxEdit, PromptBoxEditProps } from "@storyteller/ui-promptbox";
|
||||
import { uploadImage } from "../../../components/reusable/UploadModalMedia/uploadImage";
|
||||
|
||||
const PromptEditor: React.FC<PromptBoxEditProps> = (props) => {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-3xl flex-col space-y-2">
|
||||
<PromptBoxEdit {...props} uploadImage={uploadImage} />
|
||||
<PromptBoxEdit {...props} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as THREE from "three";
|
||||
import { TransformControls } from "three/examples/jsm/Addons";
|
||||
import { FKBoneBlacklistStrings } from "./FKBoneFilter";
|
||||
import { TransformControls } from "../TransformControls";
|
||||
|
||||
export const FKHelperSphereName = "__FKHelperSphere__";
|
||||
|
||||
|
||||
@@ -59,14 +59,14 @@ interface LightboxModalProps {
|
||||
onDownloadClicked?: (url: string, mediaClass?: string) => Promise<void>;
|
||||
onAddToSceneClicked?: (
|
||||
url: string,
|
||||
media_id: string | undefined,
|
||||
media_id: string | undefined
|
||||
) => Promise<void>;
|
||||
mediaClass?: string;
|
||||
onPromptCopy?: (prompt: string) => void;
|
||||
onEditClicked?: (url: string, media_id?: string) => Promise<void> | void;
|
||||
onTurnIntoVideoClicked?: (
|
||||
url: string,
|
||||
media_id?: string,
|
||||
media_id?: string
|
||||
) => Promise<void> | void;
|
||||
batchImageToken?: string;
|
||||
}
|
||||
@@ -104,7 +104,7 @@ export function LightboxModal({
|
||||
const [hasPromptToken, setHasPromptToken] = useState<boolean>(false);
|
||||
const [isPromptHovered, setIsPromptHovered] = useState<boolean>(false);
|
||||
const [generationProvider, setGenerationProvider] = useState<string | null>(
|
||||
null,
|
||||
null
|
||||
);
|
||||
const [modelType, setModelType] = useState<string | null>(null);
|
||||
const [contextImages, setContextImages] = useState<Array<{
|
||||
@@ -121,7 +121,7 @@ export function LightboxModal({
|
||||
const shareCopiedTimeoutRef = useRef<number | null>(null);
|
||||
|
||||
const [currentMediaId, setCurrentMediaId] = useState<string | undefined>(
|
||||
mediaId,
|
||||
mediaId
|
||||
);
|
||||
useEffect(() => {
|
||||
setCurrentMediaId(mediaId);
|
||||
@@ -236,7 +236,7 @@ export function LightboxModal({
|
||||
}))
|
||||
.filter(
|
||||
(item): item is { url: string; token: string } =>
|
||||
Boolean(item.url) && Boolean(item.token),
|
||||
Boolean(item.url) && Boolean(item.token)
|
||||
);
|
||||
|
||||
if (items.length > 0) {
|
||||
@@ -293,7 +293,7 @@ export function LightboxModal({
|
||||
if (!emblaMainApi || !emblaThumbsApi) return;
|
||||
emblaMainApi.scrollTo(index);
|
||||
},
|
||||
[emblaMainApi, emblaThumbsApi],
|
||||
[emblaMainApi, emblaThumbsApi]
|
||||
);
|
||||
|
||||
const onSelect = useCallback(() => {
|
||||
@@ -427,9 +427,7 @@ export function LightboxModal({
|
||||
PLACEHOLDER_IMAGES.DEFAULT;
|
||||
e.currentTarget.style.opacity = "0.3";
|
||||
// Set the `data-brokenurl` property for debugging the broken images:
|
||||
(
|
||||
e.currentTarget as HTMLImageElement
|
||||
).dataset.brokenurl = url || "";
|
||||
(e.currentTarget as HTMLImageElement).dataset.brokenurl = url || "";
|
||||
}
|
||||
}}
|
||||
onLoad={() => {
|
||||
@@ -462,7 +460,7 @@ export function LightboxModal({
|
||||
"embla-thumbs__slide relative h-20 w-20 flex-[0_0_5rem] overflow-hidden rounded-md border-2 transition-all",
|
||||
isSelected
|
||||
? "border-brand-primary-400 opacity-100"
|
||||
: "border-transparent opacity-60 hover:border-white/40 hover:opacity-100",
|
||||
: "border-transparent opacity-60 hover:border-white/40 hover:opacity-100"
|
||||
)}
|
||||
>
|
||||
<img
|
||||
@@ -480,13 +478,11 @@ export function LightboxModal({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!mediaLoaded &&
|
||||
selectedImageUrl &&
|
||||
mediaClass !== "dimensional" && (
|
||||
<div className="absolute inset-0 bg-ui-panel flex items-center justify-center">
|
||||
<LoadingSpinner className="h-12 w-12 text-base-fg" />
|
||||
</div>
|
||||
)}
|
||||
{!mediaLoaded && selectedImageUrl && mediaClass !== "dimensional" && (
|
||||
<div className="absolute inset-0 bg-ui-panel flex items-center justify-center">
|
||||
<LoadingSpinner className="h-12 w-12 text-base-fg" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* info + actions */}
|
||||
@@ -516,7 +512,7 @@ export function LightboxModal({
|
||||
</div>
|
||||
<div
|
||||
className={twMerge(
|
||||
"relative text-sm text-base-fg break-words p-3 rounded-lg cursor-pointer transition-colors duration-100 leading-relaxed",
|
||||
"relative text-sm text-base-fg break-words p-3 rounded-lg cursor-pointer transition-colors duration-100 leading-relaxed"
|
||||
)}
|
||||
style={{
|
||||
background: isPromptHovered
|
||||
@@ -551,7 +547,7 @@ export function LightboxModal({
|
||||
<div
|
||||
className={twMerge(
|
||||
"pointer-events-none absolute inset-0 flex items-end justify-end opacity-0 transition-opacity duration-50",
|
||||
isPromptHovered && "opacity-100",
|
||||
isPromptHovered && "opacity-100"
|
||||
)}
|
||||
>
|
||||
<div
|
||||
@@ -572,7 +568,7 @@ export function LightboxModal({
|
||||
<div className="text-sm font-medium text-base-fg/90">
|
||||
Reference Images
|
||||
</div>
|
||||
<div className="grid grid-cols-6 gap-2 w-fit">
|
||||
<div className="grid grid-cols-6 gap-2">
|
||||
{contextImages.map((contextImage, index) => {
|
||||
const { thumbnail, fullSize } =
|
||||
getContextImageThumbnail(contextImage, {
|
||||
@@ -582,7 +578,7 @@ export function LightboxModal({
|
||||
return (
|
||||
<div
|
||||
key={contextImage.media_token}
|
||||
className="glass relative aspect-square overflow-hidden rounded-lg w-12 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer hover:cursor-zoom-in"
|
||||
className="glass relative aspect-square overflow-hidden rounded-lg w-14 border-2 border-white/30 hover:border-white/80 transition-all group cursor-pointer hover:cursor-zoom-in"
|
||||
onClick={() => setRefPreviewUrl(fullSize)}
|
||||
>
|
||||
<img
|
||||
@@ -635,7 +631,7 @@ export function LightboxModal({
|
||||
<div className="flex items-center gap-2">
|
||||
{getProviderIconByName(
|
||||
generationProvider,
|
||||
"h-4 w-4 invert",
|
||||
"h-4 w-4 invert"
|
||||
)}
|
||||
<span className="text-sm text-base-fg rounded">
|
||||
{getProviderDisplayName(generationProvider)}
|
||||
@@ -685,7 +681,7 @@ export function LightboxModal({
|
||||
setShareCopied(true);
|
||||
if (shareCopiedTimeoutRef.current) {
|
||||
window.clearTimeout(
|
||||
shareCopiedTimeoutRef.current,
|
||||
shareCopiedTimeoutRef.current
|
||||
);
|
||||
}
|
||||
shareCopiedTimeoutRef.current = window.setTimeout(
|
||||
@@ -693,7 +689,7 @@ export function LightboxModal({
|
||||
setShareCopied(false);
|
||||
shareCopiedTimeoutRef.current = null;
|
||||
},
|
||||
1500,
|
||||
1500
|
||||
);
|
||||
} catch (err) {
|
||||
toast.error("Unable to copy link");
|
||||
@@ -716,7 +712,7 @@ export function LightboxModal({
|
||||
gtagEvent("edit_image_clicked");
|
||||
await onEditClicked(
|
||||
actionUrl,
|
||||
selectedMediaToken,
|
||||
selectedMediaToken
|
||||
);
|
||||
}}
|
||||
>
|
||||
@@ -735,7 +731,7 @@ export function LightboxModal({
|
||||
gtagEvent("turn_into_video_clicked");
|
||||
await onTurnIntoVideoClicked(
|
||||
actionUrl,
|
||||
selectedMediaToken,
|
||||
selectedMediaToken
|
||||
);
|
||||
}}
|
||||
>
|
||||
@@ -752,7 +748,7 @@ export function LightboxModal({
|
||||
gtagEvent("add_to_scene_clicked");
|
||||
await onAddToSceneClicked(
|
||||
actionUrl,
|
||||
selectedMediaToken,
|
||||
selectedMediaToken
|
||||
);
|
||||
onClose();
|
||||
onCloseGallery();
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
faSpinnerThird,
|
||||
faFrame,
|
||||
faCopy,
|
||||
faExpand,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import {
|
||||
faRectangleVertical,
|
||||
@@ -31,7 +30,6 @@ import { PromptsApi } from "@storyteller/api";
|
||||
import {
|
||||
EnqueueEditImage,
|
||||
EnqueueEditImageSize,
|
||||
EnqueueEditImageResolution,
|
||||
} from "@storyteller/tauri-api";
|
||||
import { usePrompt2DStore } from "./promptStore";
|
||||
import { gtagEvent } from "@storyteller/google-analytics";
|
||||
@@ -40,7 +38,7 @@ import { ImageModel } from "@storyteller/model-list";
|
||||
import { ImagePromptRow } from "./ImagePromptRow";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export type AspectRatio = "wide" | "tall" | "square";
|
||||
export type AspectRatio = "1:1" | "3:2" | "2:3";
|
||||
|
||||
interface PromptBox2DProps {
|
||||
uploadImage: ({
|
||||
@@ -84,8 +82,6 @@ export const PromptBox2D = ({
|
||||
const setUseSystemPrompt = usePrompt2DStore((s) => s.setUseSystemPrompt);
|
||||
const aspectRatio = usePrompt2DStore((s) => s.aspectRatio);
|
||||
const setAspectRatio = usePrompt2DStore((s) => s.setAspectRatio);
|
||||
const resolution = usePrompt2DStore((s) => s.resolution);
|
||||
const setResolution = usePrompt2DStore((s) => s.setResolution);
|
||||
const generationCount = usePrompt2DStore((s) => s.generationCount);
|
||||
const setGenerationCount = usePrompt2DStore((s) => s.setGenerationCount);
|
||||
|
||||
@@ -95,42 +91,24 @@ export const PromptBox2D = ({
|
||||
const [showImagePrompts, setShowImagePrompts] = useState(false);
|
||||
const [aspectRatioList, setAspectRatioList] = useState<PopoverItem[]>([
|
||||
{
|
||||
label: "Wide",
|
||||
selected: aspectRatio === "wide",
|
||||
label: "3:2",
|
||||
selected: aspectRatio === "3:2",
|
||||
icon: <FontAwesomeIcon icon={faRectangle} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "Tall",
|
||||
selected: aspectRatio === "tall",
|
||||
label: "2:3",
|
||||
selected: aspectRatio === "2:3",
|
||||
icon: <FontAwesomeIcon icon={faRectangleVertical} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "Square",
|
||||
selected: aspectRatio === "square",
|
||||
label: "1:1",
|
||||
selected: aspectRatio === "1:1",
|
||||
icon: <FontAwesomeIcon icon={faSquare} className="h-4 w-4" />,
|
||||
},
|
||||
]);
|
||||
|
||||
const [resolutionList, setResolutionList] = useState<PopoverItem[]>([
|
||||
{
|
||||
label: "1K",
|
||||
selected: resolution === "1k",
|
||||
icon: <FontAwesomeIcon icon={faExpand} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "2K",
|
||||
selected: resolution === "2k",
|
||||
icon: <FontAwesomeIcon icon={faExpand} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "4K",
|
||||
selected: resolution === "4k",
|
||||
icon: <FontAwesomeIcon icon={faExpand} className="h-4 w-4" />,
|
||||
},
|
||||
]);
|
||||
|
||||
const [generationCountList, setGenerationCountList] = useState<PopoverItem[]>(
|
||||
[],
|
||||
[]
|
||||
);
|
||||
|
||||
// Update generation count options from selected model capabilities
|
||||
@@ -138,7 +116,7 @@ export const PromptBox2D = ({
|
||||
const caps = getCapabilitiesForModel(selectedImageModel);
|
||||
const items: PopoverItem[] = Array.from(
|
||||
{ length: caps.maxGenerationCount },
|
||||
(_, i) => i + 1,
|
||||
(_, i) => i + 1
|
||||
).map((count) => ({
|
||||
label: String(count),
|
||||
selected: count === generationCount,
|
||||
@@ -148,7 +126,7 @@ export const PromptBox2D = ({
|
||||
// Clamp selection to allowed range
|
||||
if (generationCount < 1 || generationCount > caps.maxGenerationCount) {
|
||||
setGenerationCount(
|
||||
Math.min(Math.max(1, generationCount), caps.maxGenerationCount),
|
||||
Math.min(Math.max(1, generationCount), caps.maxGenerationCount)
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -160,19 +138,10 @@ export const PromptBox2D = ({
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label === String(generationCount),
|
||||
})),
|
||||
}))
|
||||
);
|
||||
}, [generationCount]);
|
||||
|
||||
useEffect(() => {
|
||||
setResolutionList((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label.toLowerCase() === resolution,
|
||||
})),
|
||||
);
|
||||
}, [resolution]);
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const isImageRowVisible =
|
||||
showImagePrompts ||
|
||||
@@ -187,21 +156,16 @@ export const PromptBox2D = ({
|
||||
});
|
||||
|
||||
const handleAspectRatioSelect = (selectedItem: PopoverItem) => {
|
||||
const value = selectedItem.label.toLowerCase() as AspectRatio;
|
||||
onAspectRatioChange?.(value);
|
||||
setAspectRatio(value);
|
||||
onAspectRatioChange?.(selectedItem.label as AspectRatio);
|
||||
setAspectRatio(selectedItem.label as AspectRatio);
|
||||
setAspectRatioList((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label === selectedItem.label,
|
||||
})),
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
const handleResolutionSelect = (selectedItem: PopoverItem) => {
|
||||
setResolution(selectedItem.label.toLowerCase() as any);
|
||||
};
|
||||
|
||||
// Image prompt row replaces legacy upload/gallery UI
|
||||
|
||||
const handleGenerationCountSelect = (selectedItem: PopoverItem) => {
|
||||
@@ -211,7 +175,7 @@ export const PromptBox2D = ({
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label === selectedItem.label,
|
||||
})),
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
@@ -270,19 +234,15 @@ export const PromptBox2D = ({
|
||||
console.log("Snapshot media token:", snapshotMediaToken.data);
|
||||
|
||||
const aspectRatio = getCurrentAspectRatio();
|
||||
const resolution = getCurrentResolution();
|
||||
|
||||
const generateResponse = await EnqueueEditImage({
|
||||
model: selectedImageModel,
|
||||
scene_image_media_token: snapshotMediaToken.data!,
|
||||
image_media_tokens: referenceImages
|
||||
.map((image) => image.mediaToken)
|
||||
.filter((t) => t.length > 0),
|
||||
image_media_tokens: referenceImages.map((image) => image.mediaToken),
|
||||
disable_system_prompt: !useSystemPrompt,
|
||||
prompt: prompt,
|
||||
image_count: generationCount,
|
||||
aspect_ratio: aspectRatio,
|
||||
image_resolution: resolution,
|
||||
});
|
||||
|
||||
console.log("generateResponse", generateResponse);
|
||||
@@ -296,7 +256,7 @@ export const PromptBox2D = ({
|
||||
let image = getCanvasRenderBitmap();
|
||||
if (image === undefined) {
|
||||
toast.error(
|
||||
"Error: Unable to generate image. Please check the input and try again.",
|
||||
"Error: Unable to generate image. Please check the input and try again."
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -330,9 +290,7 @@ export const PromptBox2D = ({
|
||||
disableSystemPrompt: !useSystemPrompt,
|
||||
prompt: prompt,
|
||||
snapshotMediaToken: snapshotMediaToken.data,
|
||||
additionalImages: referenceImages
|
||||
.map((image) => image.mediaToken)
|
||||
.filter((t) => t.length > 0),
|
||||
additionalImages: referenceImages.map((image) => image.mediaToken),
|
||||
});
|
||||
|
||||
if (response.success === true) {
|
||||
@@ -367,7 +325,7 @@ export const PromptBox2D = ({
|
||||
"(4) Enqueuing with prompt:",
|
||||
prompt,
|
||||
"and reference images:",
|
||||
referenceImages,
|
||||
referenceImages
|
||||
);
|
||||
|
||||
const isDesktop = IsDesktopApp();
|
||||
@@ -381,7 +339,7 @@ export const PromptBox2D = ({
|
||||
} catch (error) {
|
||||
console.error("Error during image generation:", error);
|
||||
toast.error(
|
||||
"An error occurred while generating the image. Please try again.",
|
||||
"An error occurred while generating the image. Please try again."
|
||||
);
|
||||
} finally {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
@@ -398,31 +356,17 @@ export const PromptBox2D = ({
|
||||
|
||||
const getCurrentAspectRatio = (): EnqueueEditImageSize => {
|
||||
const selected = aspectRatioList.find((item) => item.selected);
|
||||
switch (selected?.label.toLowerCase()) {
|
||||
case "wide":
|
||||
switch (selected?.label) {
|
||||
case "3:2":
|
||||
return EnqueueEditImageSize.Wide;
|
||||
case "tall":
|
||||
case "2:3":
|
||||
return EnqueueEditImageSize.Tall;
|
||||
case "square":
|
||||
case "1:1":
|
||||
default:
|
||||
return EnqueueEditImageSize.Square;
|
||||
}
|
||||
};
|
||||
|
||||
const getCurrentResolution = (): EnqueueEditImageResolution | undefined => {
|
||||
const selected = resolutionList.find((item) => item.selected);
|
||||
switch (selected?.label) {
|
||||
case "1k":
|
||||
return EnqueueEditImageResolution.OneK;
|
||||
case "2k":
|
||||
return EnqueueEditImageResolution.TwoK;
|
||||
case "4k":
|
||||
return EnqueueEditImageResolution.FourK;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
// Stop propagation of keyboard events to prevent them from reaching the canvas
|
||||
e.stopPropagation();
|
||||
@@ -450,7 +394,7 @@ export const PromptBox2D = ({
|
||||
visible={true}
|
||||
maxImagePromptCount={Math.max(
|
||||
1,
|
||||
selectedImageModel?.maxImagePromptCount ?? 1,
|
||||
selectedImageModel?.maxImagePromptCount ?? 1
|
||||
)}
|
||||
allowUpload={true}
|
||||
referenceImages={referenceImages}
|
||||
@@ -462,7 +406,7 @@ export const PromptBox2D = ({
|
||||
src={image.url}
|
||||
alt="Reference preview"
|
||||
className="w-full h-full object-contain"
|
||||
/>,
|
||||
/>
|
||||
);
|
||||
setIsModalOpen(true);
|
||||
}}
|
||||
@@ -473,7 +417,7 @@ export const PromptBox2D = ({
|
||||
"glass w-[730px] rounded-xl p-4",
|
||||
selectedImageModel?.canUseImagePrompt &&
|
||||
isImageRowVisible &&
|
||||
"rounded-t-none",
|
||||
"rounded-t-none"
|
||||
)}
|
||||
>
|
||||
<div className="flex justify-center gap-2">
|
||||
@@ -488,7 +432,7 @@ export const PromptBox2D = ({
|
||||
variant="action"
|
||||
className={twMerge(
|
||||
"h-8 w-8 p-0 bg-transparent hover:bg-transparent group transition-all border-0 shadow-none",
|
||||
isImageRowVisible && "text-primary",
|
||||
isImageRowVisible && "text-primary"
|
||||
)}
|
||||
onClick={() => setShowImagePrompts((prev) => !prev)}
|
||||
>
|
||||
@@ -525,47 +469,26 @@ export const PromptBox2D = ({
|
||||
</div>
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedImageModel?.canChangeAspectRatio && (
|
||||
<Tooltip
|
||||
content="Aspect ratio"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={aspectRatioList}
|
||||
onSelect={handleAspectRatioSelect}
|
||||
mode="toggle"
|
||||
panelTitle="Aspect Ratio"
|
||||
showIconsInList
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon
|
||||
icon={getCurrentAspectRatioIcon()}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
{selectedImageModel?.canChangeResolution && (
|
||||
<Tooltip
|
||||
content="Resolution"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={resolutionList}
|
||||
onSelect={handleResolutionSelect}
|
||||
mode="toggle"
|
||||
panelTitle="Resolution"
|
||||
showIconsInList
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon icon={faExpand} className="h-4 w-4" />
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip
|
||||
content="Aspect ratio"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={aspectRatioList}
|
||||
onSelect={handleAspectRatioSelect}
|
||||
mode="toggle"
|
||||
panelTitle="Aspect Ratio"
|
||||
showIconsInList
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon
|
||||
icon={getCurrentAspectRatioIcon()}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={
|
||||
useSystemPrompt
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
faSpinnerThird,
|
||||
faMessageCheck,
|
||||
faMessageXmark,
|
||||
faExpand,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import {
|
||||
faRectangleWide,
|
||||
@@ -41,7 +40,6 @@ import {
|
||||
CommandSuccessStatus,
|
||||
EnqueueEditImage,
|
||||
EnqueueEditImageSize,
|
||||
EnqueueEditImageResolution,
|
||||
} from "@storyteller/tauri-api";
|
||||
import { usePrompt3DStore } from "./promptStore";
|
||||
import { gtagEvent } from "@storyteller/google-analytics";
|
||||
@@ -106,46 +104,27 @@ export const PromptBox3D = ({
|
||||
const setPrompt = usePrompt3DStore((s) => s.setPrompt);
|
||||
const useSystemPrompt = usePrompt3DStore((s) => s.useSystemPrompt);
|
||||
const setUseSystemPrompt = usePrompt3DStore((s) => s.setUseSystemPrompt);
|
||||
const resolution = usePrompt3DStore((s) => s.resolution);
|
||||
const setResolution = usePrompt3DStore((s) => s.setResolution);
|
||||
const [isEnqueueing, setIsEnqueueing] = useState(false);
|
||||
const referenceImages = usePrompt3DStore((s) => s.referenceImages);
|
||||
const setReferenceImages = usePrompt3DStore((s) => s.setReferenceImages);
|
||||
const [showImagePrompts, setShowImagePrompts] = useState(false);
|
||||
const [aspectRatioList, setAspectRatioList] = useState<PopoverItem[]>([
|
||||
{
|
||||
label: "Wide",
|
||||
label: "3:2",
|
||||
selected: true,
|
||||
icon: <FontAwesomeIcon icon={faRectangle} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "Tall",
|
||||
label: "2:3",
|
||||
selected: false,
|
||||
icon: <FontAwesomeIcon icon={faRectangleVertical} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "Square",
|
||||
label: "1:1",
|
||||
selected: false,
|
||||
icon: <FontAwesomeIcon icon={faSquare} className="h-4 w-4" />,
|
||||
},
|
||||
]);
|
||||
const [resolutionList, setResolutionList] = useState<PopoverItem[]>([
|
||||
{
|
||||
label: "1K",
|
||||
selected: resolution === "1k",
|
||||
icon: <FontAwesomeIcon icon={faExpand} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "2K",
|
||||
selected: resolution === "2k",
|
||||
icon: <FontAwesomeIcon icon={faExpand} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "4K",
|
||||
selected: resolution === "4k",
|
||||
icon: <FontAwesomeIcon icon={faExpand} className="h-4 w-4" />,
|
||||
},
|
||||
]);
|
||||
const [isCameraSettingsOpen, setIsCameraSettingsOpen] = useState(false);
|
||||
const isImageRowVisible =
|
||||
showImagePrompts ||
|
||||
@@ -159,26 +138,19 @@ export const PromptBox3D = ({
|
||||
}
|
||||
}, [prompt]);
|
||||
|
||||
useEffect(() => {
|
||||
setResolutionList((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label.toLowerCase() === resolution,
|
||||
}))
|
||||
);
|
||||
}, [resolution]);
|
||||
|
||||
// Update aspect ratio list based on the current cameraAspectRatio signal
|
||||
useEffect(() => {
|
||||
setAspectRatioList((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected:
|
||||
(item.label === "Wide" &&
|
||||
(item.label === "16:9" &&
|
||||
cameraAspectRatio.value === CameraAspectRatio.HORIZONTAL_16_9) ||
|
||||
(item.label === "3:2" &&
|
||||
cameraAspectRatio.value === CameraAspectRatio.HORIZONTAL_3_2) ||
|
||||
(item.label === "Tall" &&
|
||||
(item.label === "2:3" &&
|
||||
cameraAspectRatio.value === CameraAspectRatio.VERTICAL_2_3) ||
|
||||
(item.label === "Square" &&
|
||||
(item.label === "1:1" &&
|
||||
cameraAspectRatio.value === CameraAspectRatio.SQUARE_1_1),
|
||||
}))
|
||||
);
|
||||
@@ -195,27 +167,26 @@ export const PromptBox3D = ({
|
||||
|
||||
// Map the selected label to the corresponding CameraAspectRatio enum value
|
||||
let newRatio: CameraAspectRatio;
|
||||
switch (selectedItem.label.toLowerCase()) {
|
||||
case "wide":
|
||||
switch (selectedItem.label) {
|
||||
case "16:9":
|
||||
newRatio = CameraAspectRatio.HORIZONTAL_16_9;
|
||||
break;
|
||||
case "3:2":
|
||||
newRatio = CameraAspectRatio.HORIZONTAL_3_2;
|
||||
break;
|
||||
case "tall":
|
||||
case "2:3":
|
||||
newRatio = CameraAspectRatio.VERTICAL_2_3;
|
||||
break;
|
||||
case "square":
|
||||
case "1:1":
|
||||
newRatio = CameraAspectRatio.SQUARE_1_1;
|
||||
break;
|
||||
default:
|
||||
newRatio = CameraAspectRatio.HORIZONTAL_3_2;
|
||||
newRatio = CameraAspectRatio.HORIZONTAL_16_9;
|
||||
}
|
||||
|
||||
onAspectRatioSelect(newRatio);
|
||||
};
|
||||
|
||||
const handleResolutionSelect = (selectedItem: PopoverItem) => {
|
||||
setResolution(selectedItem.label.toLowerCase() as any);
|
||||
};
|
||||
|
||||
// ImagePromptRow will handle uploads and gallery internally
|
||||
|
||||
const handlePaste = (e: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
||||
@@ -364,7 +335,6 @@ export const PromptBox3D = ({
|
||||
console.log("snapshotResult", snapshotResult);
|
||||
|
||||
const aspectRatio = getCurrentAspectRatio();
|
||||
const resolution = getCurrentResolution();
|
||||
|
||||
const generateResponse = await EnqueueEditImage({
|
||||
model: selectedImageModel,
|
||||
@@ -374,7 +344,6 @@ export const PromptBox3D = ({
|
||||
prompt: prompt,
|
||||
image_count: 1,
|
||||
aspect_ratio: aspectRatio,
|
||||
image_resolution: resolution,
|
||||
});
|
||||
|
||||
console.log("generateResponse", generateResponse);
|
||||
@@ -444,20 +413,6 @@ export const PromptBox3D = ({
|
||||
}
|
||||
};
|
||||
|
||||
const getCurrentResolution = (): EnqueueEditImageResolution | undefined => {
|
||||
const selected = resolutionList.find((item) => item.selected);
|
||||
switch (selected?.label) {
|
||||
case "1k":
|
||||
return EnqueueEditImageResolution.OneK;
|
||||
case "2k":
|
||||
return EnqueueEditImageResolution.TwoK;
|
||||
case "4k":
|
||||
return EnqueueEditImageResolution.FourK;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveFrame = async () => {
|
||||
if (!snapshotCurrentFrame) {
|
||||
return;
|
||||
@@ -584,47 +539,26 @@ export const PromptBox3D = ({
|
||||
</div>
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedImageModel?.canChangeAspectRatio && (
|
||||
<Tooltip
|
||||
content="Aspect ratio"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={aspectRatioList}
|
||||
onSelect={handleAspectRatioSelect}
|
||||
mode="toggle"
|
||||
panelTitle="Aspect Ratio"
|
||||
showIconsInList
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon
|
||||
icon={getCurrentAspectRatioIcon()}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
{selectedImageModel?.canChangeResolution && (
|
||||
<Tooltip
|
||||
content="Resolution"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={resolutionList}
|
||||
onSelect={handleResolutionSelect}
|
||||
mode="toggle"
|
||||
panelTitle="Resolution"
|
||||
showIconsInList
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon icon={faExpand} className="h-4 w-4" />
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip
|
||||
content="Aspect ratio"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={aspectRatioList}
|
||||
onSelect={handleAspectRatioSelect}
|
||||
mode="toggle"
|
||||
panelTitle="Aspect Ratio"
|
||||
showIconsInList
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon
|
||||
icon={getCurrentAspectRatioIcon()}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content="Camera"
|
||||
position="top"
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import {
|
||||
faRectangle,
|
||||
faRectangleVertical,
|
||||
faSquare,
|
||||
} from "@fortawesome/pro-regular-svg-icons";
|
||||
import {
|
||||
faEdit,
|
||||
// faExpand,
|
||||
faMessageCheck,
|
||||
faMessageXmark,
|
||||
faMousePointer,
|
||||
@@ -16,8 +12,6 @@ import {
|
||||
faEraser,
|
||||
faUndo,
|
||||
faRedo,
|
||||
faExpand,
|
||||
faArrowsUpDownLeftRight,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Button, ToggleButton } from "@storyteller/ui-button";
|
||||
@@ -28,19 +22,12 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { ImageModel, getCapabilitiesForModel } from "@storyteller/model-list";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { ImagePromptRow, type UploadImageFn } from "./ImagePromptRow";
|
||||
import { RefImage, usePromptEditStore } from "./promptStore";
|
||||
import { usePromptEditStore } from "./promptStore";
|
||||
|
||||
export interface PromptBoxEditProps {
|
||||
onModeChange?: (mode: string) => void;
|
||||
selectedMode?: string;
|
||||
onGenerateClick: (
|
||||
prompt: string,
|
||||
options?: {
|
||||
aspectRatio?: string;
|
||||
resolution?: string;
|
||||
images?: RefImage[];
|
||||
},
|
||||
) => void | Promise<void>;
|
||||
onGenerateClick: (prompt: string) => void | Promise<void>;
|
||||
isDisabled?: boolean;
|
||||
onFitPressed?: () => void | Promise<void>;
|
||||
selectedImageModel?: ImageModel;
|
||||
@@ -74,102 +61,17 @@ export const PromptBoxEdit = ({
|
||||
const [useSystemPrompt, setUseSystemPrompt] = useState(true);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [generationCount, setGenerationCount] = useState<number>(
|
||||
typeof generationCountProp === "number" ? generationCountProp : 1,
|
||||
typeof generationCountProp === "number" ? generationCountProp : 1
|
||||
);
|
||||
const [internalEnqueueing, setInternalEnqueueing] = useState(false);
|
||||
const referenceImages = usePromptEditStore((s) => s.referenceImages);
|
||||
const setReferenceImages = usePromptEditStore((s) => s.setReferenceImages);
|
||||
const aspectRatio = usePromptEditStore((s) => s.aspectRatio);
|
||||
const setAspectRatio = usePromptEditStore((s) => s.setAspectRatio);
|
||||
const resolution = usePromptEditStore((s) => s.resolution);
|
||||
const setResolution = usePromptEditStore((s) => s.setResolution);
|
||||
|
||||
const [showImagePrompts, setShowImagePrompts] = useState(false);
|
||||
|
||||
const [generationCountList, setGenerationCountList] = useState<PopoverItem[]>(
|
||||
[],
|
||||
[]
|
||||
);
|
||||
|
||||
const [aspectRatioList, setAspectRatioList] = useState<PopoverItem[]>([
|
||||
{
|
||||
label: "Auto",
|
||||
selected: aspectRatio === "auto",
|
||||
icon: (
|
||||
<FontAwesomeIcon icon={faArrowsUpDownLeftRight} className="h-4 w-4" />
|
||||
),
|
||||
},
|
||||
{
|
||||
label: "Wide",
|
||||
selected: aspectRatio === "wide",
|
||||
icon: <FontAwesomeIcon icon={faRectangle} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "Tall",
|
||||
selected: aspectRatio === "tall",
|
||||
icon: <FontAwesomeIcon icon={faRectangleVertical} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "Square",
|
||||
selected: aspectRatio === "square",
|
||||
icon: <FontAwesomeIcon icon={faSquare} className="h-4 w-4" />,
|
||||
},
|
||||
]);
|
||||
|
||||
const [resolutionList, setResolutionList] = useState<PopoverItem[]>([
|
||||
{
|
||||
label: "1K",
|
||||
selected: resolution === "1k",
|
||||
icon: <FontAwesomeIcon icon={faExpand} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "2K",
|
||||
selected: resolution === "2k",
|
||||
icon: <FontAwesomeIcon icon={faExpand} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "4K",
|
||||
selected: resolution === "4k",
|
||||
icon: <FontAwesomeIcon icon={faExpand} className="h-4 w-4" />,
|
||||
},
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
setAspectRatioList((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected:
|
||||
item.label.toLowerCase() === aspectRatio ||
|
||||
(item.label === "Auto" && aspectRatio === "auto"),
|
||||
})),
|
||||
);
|
||||
}, [aspectRatio]);
|
||||
|
||||
useEffect(() => {
|
||||
setResolutionList((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label.toLowerCase() === resolution,
|
||||
})),
|
||||
);
|
||||
}, [resolution]);
|
||||
|
||||
const handleAspectRatioSelect = (selectedItem: PopoverItem) => {
|
||||
const value =
|
||||
selectedItem.label === "Auto" ? "auto" : selectedItem.label.toLowerCase();
|
||||
setAspectRatio(value as any);
|
||||
};
|
||||
|
||||
const handleResolutionSelect = (selectedItem: PopoverItem) => {
|
||||
setResolution(selectedItem.label.toLowerCase() as any);
|
||||
};
|
||||
|
||||
const getCurrentAspectRatioIcon = () => {
|
||||
const selected = aspectRatioList.find((item) => item.selected);
|
||||
if (!selected || !selected.icon) return faRectangle;
|
||||
const iconElement = selected.icon as React.ReactElement<{ icon: any }>;
|
||||
return iconElement.props.icon;
|
||||
};
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
const isImageRowVisible =
|
||||
(selectedImageModel?.canUseImagePrompt &&
|
||||
@@ -183,17 +85,20 @@ export const PromptBoxEdit = ({
|
||||
}
|
||||
});
|
||||
|
||||
// Sync internal state if a controlled prop is provided
|
||||
useEffect(() => {
|
||||
if (typeof generationCountProp === "number") {
|
||||
setGenerationCount(generationCountProp);
|
||||
}
|
||||
}, [generationCountProp]);
|
||||
|
||||
// Build generation count options based on selected model
|
||||
useEffect(() => {
|
||||
const caps = getCapabilitiesForModel(selectedImageModel);
|
||||
console.log(">>> caps", caps);
|
||||
const items: PopoverItem[] = Array.from(
|
||||
{ length: caps.maxGenerationCount },
|
||||
(_, i) => i + 1,
|
||||
(_, i) => i + 1
|
||||
).map((count) => ({
|
||||
label: String(count),
|
||||
selected: count === generationCount,
|
||||
@@ -201,10 +106,11 @@ export const PromptBoxEdit = ({
|
||||
}));
|
||||
setGenerationCountList(items);
|
||||
|
||||
// Clamp selection
|
||||
if (generationCount < 1 || generationCount > caps.maxGenerationCount) {
|
||||
const clamped = Math.min(
|
||||
Math.max(1, generationCount),
|
||||
caps.maxGenerationCount,
|
||||
caps.maxGenerationCount
|
||||
);
|
||||
setGenerationCount(clamped);
|
||||
onGenerationCountChange?.(clamped);
|
||||
@@ -212,12 +118,13 @@ export const PromptBoxEdit = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedImageModel]);
|
||||
|
||||
// Keep UI selection in sync when store/value changes
|
||||
useEffect(() => {
|
||||
setGenerationCountList((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label === String(generationCount),
|
||||
})),
|
||||
}))
|
||||
);
|
||||
}, [generationCount]);
|
||||
|
||||
@@ -230,6 +137,7 @@ export const PromptBoxEdit = ({
|
||||
const next =
|
||||
value.slice(0, selectionStart) + pastedText + value.slice(selectionEnd);
|
||||
setPrompt(next);
|
||||
// Restore caret after React updates the value
|
||||
requestAnimationFrame(() => {
|
||||
const pos = selectionStart + pastedText.length;
|
||||
textareaRef.current?.setSelectionRange(pos, pos);
|
||||
@@ -242,10 +150,12 @@ export const PromptBoxEdit = ({
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
// Stop propagation of keyboard events to prevent them from reaching the canvas
|
||||
e.stopPropagation();
|
||||
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
// Block Enter if requirements are not met
|
||||
const busy = Boolean(isEnqueueing ?? internalEnqueueing);
|
||||
if (!prompt.trim() || isDisabled || busy) return;
|
||||
void handleGenerate();
|
||||
@@ -260,7 +170,7 @@ export const PromptBoxEdit = ({
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label === selectedItem.label,
|
||||
})),
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
@@ -272,13 +182,7 @@ export const PromptBoxEdit = ({
|
||||
setInternalEnqueueing(false);
|
||||
}, 10000);
|
||||
try {
|
||||
await Promise.resolve(
|
||||
onGenerateClick(prompt, {
|
||||
aspectRatio,
|
||||
resolution,
|
||||
images: referenceImages,
|
||||
}),
|
||||
);
|
||||
await Promise.resolve(onGenerateClick(prompt));
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
setInternalEnqueueing(false);
|
||||
@@ -299,43 +203,50 @@ export const PromptBoxEdit = ({
|
||||
},
|
||||
]
|
||||
: supportsMaskedInpainting
|
||||
? [
|
||||
{
|
||||
value: "edit",
|
||||
icon: faEdit,
|
||||
text: "Edit Region",
|
||||
},
|
||||
{
|
||||
value: "eraser",
|
||||
icon: faEraser,
|
||||
text: "Eraser",
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
value: "edit",
|
||||
icon: faEdit,
|
||||
text: "Edit Region",
|
||||
},
|
||||
{
|
||||
value: "select",
|
||||
icon: faMousePointer,
|
||||
text: "Select",
|
||||
},
|
||||
];
|
||||
? [
|
||||
{
|
||||
value: "edit",
|
||||
icon: faEdit,
|
||||
text: "Edit Region",
|
||||
},
|
||||
{
|
||||
value: "eraser",
|
||||
icon: faEraser,
|
||||
text: "Eraser",
|
||||
},
|
||||
]
|
||||
: [
|
||||
{
|
||||
value: "edit",
|
||||
icon: faEdit,
|
||||
text: "Edit Region",
|
||||
},
|
||||
{
|
||||
value: "select",
|
||||
icon: faMousePointer,
|
||||
text: "Select",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="absolute bottom-4 left-1/2 flex -translate-x-1/2 flex-col gap-3">
|
||||
{(supportsMaskedInpainting || isNanoBananaModel) && (
|
||||
<div
|
||||
className={twMerge(
|
||||
"glass w-fit mx-auto rounded-xl px-2 py-2 flex items-center gap-3",
|
||||
selectedImageModel?.canUseImagePrompt &&
|
||||
isImageRowVisible &&
|
||||
"mb-[72px]",
|
||||
{selectedImageModel?.canUseImagePrompt && isImageRowVisible && (
|
||||
<ImagePromptRow
|
||||
visible={true}
|
||||
maxImagePromptCount={Math.max(
|
||||
1,
|
||||
selectedImageModel?.maxImagePromptCount ?? 1
|
||||
)}
|
||||
>
|
||||
allowUpload={true}
|
||||
referenceImages={referenceImages}
|
||||
setReferenceImages={setReferenceImages}
|
||||
uploadImage={uploadImage}
|
||||
onImageClick={() => {}}
|
||||
/>
|
||||
)}
|
||||
{(supportsMaskedInpainting || isNanoBananaModel) && (
|
||||
<div className="glass w-fit mx-auto rounded-xl px-2 py-2 flex items-center gap-3">
|
||||
<ButtonIconSelect
|
||||
options={modes}
|
||||
onOptionChange={onModeSelectionChange}
|
||||
@@ -364,199 +275,140 @@ export const PromptBoxEdit = ({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="relative z-20 flex flex-col">
|
||||
{selectedImageModel?.canUseImagePrompt && isImageRowVisible && (
|
||||
<ImagePromptRow
|
||||
visible={true}
|
||||
maxImagePromptCount={Math.max(
|
||||
1,
|
||||
selectedImageModel?.maxImagePromptCount ?? 1,
|
||||
)}
|
||||
allowUpload={true}
|
||||
referenceImages={referenceImages}
|
||||
setReferenceImages={setReferenceImages}
|
||||
uploadImage={uploadImage}
|
||||
onImageClick={() => {}}
|
||||
className=""
|
||||
/>
|
||||
<div
|
||||
className={twMerge(
|
||||
"glass w-[730px] rounded-xl p-4",
|
||||
isFocused && "ring-1 ring-primary border-primary",
|
||||
selectedImageModel?.canUseImagePrompt &&
|
||||
isImageRowVisible &&
|
||||
"rounded-t-none"
|
||||
)}
|
||||
<div
|
||||
className={twMerge(
|
||||
"glass w-[730px] rounded-xl p-4",
|
||||
isFocused && "ring-1 ring-primary border-primary",
|
||||
selectedImageModel?.canUseImagePrompt &&
|
||||
isImageRowVisible &&
|
||||
"rounded-t-none",
|
||||
)}
|
||||
>
|
||||
<div className="flex justify-center gap-2">
|
||||
{selectedImageModel?.canUseImagePrompt && (
|
||||
<Tooltip
|
||||
content="Add Image"
|
||||
position="top"
|
||||
closeOnClick={true}
|
||||
className={twMerge(isImageRowVisible && "hidden opacity-0")}
|
||||
>
|
||||
<div className="flex justify-center gap-2">
|
||||
{selectedImageModel?.canUseImagePrompt && (
|
||||
<Tooltip
|
||||
content="Add Image"
|
||||
position="top"
|
||||
closeOnClick={true}
|
||||
className={twMerge(isImageRowVisible && "hidden opacity-0")}
|
||||
>
|
||||
<Button
|
||||
variant="action"
|
||||
className={twMerge(
|
||||
"h-8 w-8 p-0 bg-transparent hover:bg-transparent group transition-all border-0 shadow-none",
|
||||
isImageRowVisible && "text-primary"
|
||||
)}
|
||||
onClick={() => setShowImagePrompts((prev) => !prev)}
|
||||
>
|
||||
<Button
|
||||
variant="action"
|
||||
className={twMerge(
|
||||
"h-8 w-8 p-0 bg-transparent hover:bg-transparent group transition-all border-0 shadow-none",
|
||||
isImageRowVisible && "text-primary",
|
||||
)}
|
||||
onClick={() => setShowImagePrompts((prev) => !prev)}
|
||||
<svg
|
||||
width="24"
|
||||
height="20"
|
||||
viewBox="0 0 24 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="group-hover:opacity-100 opacity-80 transition-all"
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="20"
|
||||
viewBox="0 0 24 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="group-hover:opacity-100 opacity-80 transition-all"
|
||||
>
|
||||
<path
|
||||
opacity="1"
|
||||
d="M2.66667 2H16C16.3667 2 16.6667 2.3 16.6667 2.66667V6.1125C17.1 6.04167 17.5458 6 18 6C18.225 6 18.4458 6.00833 18.6667 6.02917V2.66667C18.6667 1.19583 17.4708 0 16 0H2.66667C1.19583 0 0 1.19583 0 2.66667V16C0 17.4708 1.19583 18.6667 2.66667 18.6667H11.5C11.0625 18.0583 10.7083 17.3875 10.4542 16.6667H2.66667C2.3 16.6667 2 16.3667 2 16V2.66667C2 2.3 2.3 2 2.66667 2ZM11.8625 7.49167C11.6833 7.1875 11.3542 7 11 7C10.6458 7 10.3167 7.1875 10.1375 7.49167L8.2 10.7833L7.48333 9.75833C7.29583 9.49167 6.99167 9.33333 6.6625 9.33333C6.33333 9.33333 6.02917 9.49167 5.84167 9.75833L3.50833 13.0917C3.29583 13.3958 3.26667 13.7958 3.44167 14.125C3.61667 14.4542 3.9625 14.6667 4.33333 14.6667H10.0292C10.0125 14.4458 10 14.225 10 14C10 11.7833 10.9 9.77917 12.3542 8.33333L11.8625 7.49583V7.49167ZM5.33333 6.66667C6.07083 6.66667 6.66667 6.07083 6.66667 5.33333C6.66667 4.59583 6.07083 4 5.33333 4C4.59583 4 4 4.59583 4 5.33333C4 6.07083 4.59583 6.66667 5.33333 6.66667ZM18 20C21.3125 20 24 17.3125 24 14C24 10.6875 21.3125 8 18 8C14.6875 8 12 10.6875 12 14C12 17.3125 14.6875 20 18 20ZM18.6667 11.3333V13.3333H20.6667C21.0333 13.3333 21.3333 13.6333 21.3333 14C21.3333 14.3667 21.0333 14.6667 20.6667 14.6667H18.6667V16.6667C18.6667 17.0333 18.3667 17.3333 18 17.3333C17.6333 17.3333 17.3333 17.0333 17.3333 16.6667V14.6667H15.3333C14.9667 14.6667 14.6667 14.3667 14.6667 14C14.6667 13.6333 14.9667 13.3333 15.3333 13.3333H17.3333V11.3333C17.3333 10.9667 17.6333 10.6667 18 10.6667C18.3667 10.6667 18.6667 10.9667 18.6667 11.3333Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
rows={1}
|
||||
placeholder="Write what you want to change in your image and click generate..."
|
||||
className="text-md mb-2 max-h-[5.5em] flex-1 resize-none overflow-y-auto rounded bg-transparent pb-2 pr-2 pt-1 text-white placeholder-white placeholder:text-white/60 focus:outline-none"
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
onPaste={handlePaste}
|
||||
onKeyDown={handleKeyDown}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
/>
|
||||
<path
|
||||
opacity="1"
|
||||
d="M2.66667 2H16C16.3667 2 16.6667 2.3 16.6667 2.66667V6.1125C17.1 6.04167 17.5458 6 18 6C18.225 6 18.4458 6.00833 18.6667 6.02917V2.66667C18.6667 1.19583 17.4708 0 16 0H2.66667C1.19583 0 0 1.19583 0 2.66667V16C0 17.4708 1.19583 18.6667 2.66667 18.6667H11.5C11.0625 18.0583 10.7083 17.3875 10.4542 16.6667H2.66667C2.3 16.6667 2 16.3667 2 16V2.66667C2 2.3 2.3 2 2.66667 2ZM11.8625 7.49167C11.6833 7.1875 11.3542 7 11 7C10.6458 7 10.3167 7.1875 10.1375 7.49167L8.2 10.7833L7.48333 9.75833C7.29583 9.49167 6.99167 9.33333 6.6625 9.33333C6.33333 9.33333 6.02917 9.49167 5.84167 9.75833L3.50833 13.0917C3.29583 13.3958 3.26667 13.7958 3.44167 14.125C3.61667 14.4542 3.9625 14.6667 4.33333 14.6667H10.0292C10.0125 14.4458 10 14.225 10 14C10 11.7833 10.9 9.77917 12.3542 8.33333L11.8625 7.49583V7.49167ZM5.33333 6.66667C6.07083 6.66667 6.66667 6.07083 6.66667 5.33333C6.66667 4.59583 6.07083 4 5.33333 4C4.59583 4 4 4.59583 4 5.33333C4 6.07083 4.59583 6.66667 5.33333 6.66667ZM18 20C21.3125 20 24 17.3125 24 14C24 10.6875 21.3125 8 18 8C14.6875 8 12 10.6875 12 14C12 17.3125 14.6875 20 18 20ZM18.6667 11.3333V13.3333H20.6667C21.0333 13.3333 21.3333 13.6333 21.3333 14C21.3333 14.3667 21.0333 14.6667 20.6667 14.6667H18.6667V16.6667C18.6667 17.0333 18.3667 17.3333 18 17.3333C17.6333 17.3333 17.3333 17.0333 17.3333 16.6667V14.6667H15.3333C14.9667 14.6667 14.6667 14.3667 14.6667 14C14.6667 13.6333 14.9667 13.3333 15.3333 13.3333H17.3333V11.3333C17.3333 10.9667 17.6333 10.6667 18 10.6667C18.3667 10.6667 18.6667 10.9667 18.6667 11.3333Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
rows={1}
|
||||
placeholder="Write what you want to change in your image and click generate..."
|
||||
className="text-md mb-2 max-h-[5.5em] flex-1 resize-none overflow-y-auto rounded bg-transparent pb-2 pr-2 pt-1 text-white placeholder-white placeholder:text-white/60 focus:outline-none"
|
||||
value={prompt}
|
||||
onChange={handleChange}
|
||||
onPaste={handlePaste}
|
||||
onKeyDown={handleKeyDown}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Tooltip
|
||||
content={
|
||||
useSystemPrompt
|
||||
? "Use system prompt: ON"
|
||||
: "Use system prompt: OFF"
|
||||
}
|
||||
position="top"
|
||||
className="z-50"
|
||||
delay={200}
|
||||
>
|
||||
<ToggleButton
|
||||
isActive={useSystemPrompt}
|
||||
icon={faMessageXmark}
|
||||
activeIcon={faMessageCheck}
|
||||
onClick={() => setUseSystemPrompt(!useSystemPrompt)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedImageModel?.canChangeAspectRatio && (
|
||||
<Tooltip
|
||||
content="Aspect Ratio"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={aspectRatioList}
|
||||
onSelect={handleAspectRatioSelect}
|
||||
mode="toggle"
|
||||
panelTitle="Aspect Ratio"
|
||||
showIconsInList
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon
|
||||
icon={getCurrentAspectRatioIcon()}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
{selectedImageModel?.canChangeResolution && (
|
||||
<Tooltip
|
||||
content="Resolution"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={resolutionList}
|
||||
onSelect={handleResolutionSelect}
|
||||
mode="toggle"
|
||||
panelTitle="Resolution"
|
||||
showIconsInList
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon icon={faExpand} className="h-4 w-4" />
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
{onFitPressed && (
|
||||
<Tooltip
|
||||
content={
|
||||
useSystemPrompt
|
||||
? "Use system prompt: ON"
|
||||
: "Use system prompt: OFF"
|
||||
}
|
||||
content={"Fit canvas to screen"}
|
||||
position="top"
|
||||
className="z-50"
|
||||
delay={200}
|
||||
>
|
||||
<ToggleButton
|
||||
isActive={useSystemPrompt}
|
||||
icon={faMessageXmark}
|
||||
activeIcon={faMessageCheck}
|
||||
onClick={() => setUseSystemPrompt(!useSystemPrompt)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{onFitPressed && (
|
||||
<Tooltip
|
||||
content={"Fit canvas to screen"}
|
||||
position="top"
|
||||
className="z-50"
|
||||
delay={200}
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="h-9 bg-ui-controls/60 px-3 text-base-fg hover:bg-ui-controls/90"
|
||||
onClick={onFitPressed}
|
||||
>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="h-9 bg-ui-controls/60 px-3 text-base-fg hover:bg-ui-controls/90"
|
||||
onClick={onFitPressed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faFrame} className="h-4 w-4" />
|
||||
Fit
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip
|
||||
content="Number of generations"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={generationCountList}
|
||||
onSelect={handleGenerationCountSelect}
|
||||
mode="toggle"
|
||||
panelTitle="No. of images"
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon icon={faCopy} className="h-4 w-4" />
|
||||
}
|
||||
panelClassName="min-w-28"
|
||||
buttonClassName="h-9"
|
||||
/>
|
||||
<FontAwesomeIcon icon={faFrame} className="h-4 w-4" />
|
||||
Fit
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Button
|
||||
className="flex items-center border-none bg-primary px-3 text-sm text-white disabled:cursor-not-allowed disabled:opacity-50"
|
||||
icon={
|
||||
!(isEnqueueing ?? internalEnqueueing) && !isDisabled
|
||||
? faSparkles
|
||||
: undefined
|
||||
)}
|
||||
<Tooltip
|
||||
content="Number of generations"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={generationCountList}
|
||||
onSelect={handleGenerationCountSelect}
|
||||
mode="toggle"
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon icon={faCopy} className="h-4 w-4" />
|
||||
}
|
||||
onClick={handleGenerate}
|
||||
disabled={
|
||||
(isEnqueueing ?? internalEnqueueing) ||
|
||||
isDisabled ||
|
||||
!prompt.trim()
|
||||
}
|
||||
>
|
||||
{(isEnqueueing ?? internalEnqueueing) ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-lg"
|
||||
/>
|
||||
) : (
|
||||
"Generate"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
panelClassName="min-w-28"
|
||||
panelTitle="No. of images"
|
||||
buttonClassName="h-9"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Button
|
||||
className="flex items-center border-none bg-primary px-3 text-sm text-white disabled:cursor-not-allowed disabled:opacity-50"
|
||||
icon={
|
||||
!(isEnqueueing ?? internalEnqueueing) && !isDisabled
|
||||
? faSparkles
|
||||
: undefined
|
||||
}
|
||||
onClick={handleGenerate}
|
||||
disabled={
|
||||
(isEnqueueing ?? internalEnqueueing) ||
|
||||
isDisabled ||
|
||||
!prompt.trim()
|
||||
}
|
||||
>
|
||||
{isEnqueueing ?? internalEnqueueing ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-lg"
|
||||
/>
|
||||
) : (
|
||||
"Generate"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
EnqueueTextToImage,
|
||||
EnqueueTextToImageRequest,
|
||||
EnqueueTextToImageSize,
|
||||
EnqueueTextToImageResolution,
|
||||
} from "@storyteller/tauri-api";
|
||||
import {
|
||||
faMessageXmark,
|
||||
@@ -18,7 +17,6 @@ import {
|
||||
faSparkles,
|
||||
faSpinnerThird,
|
||||
faCopy,
|
||||
faExpand,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import {
|
||||
faRectangle,
|
||||
@@ -47,7 +45,7 @@ interface PromptBoxImageProps {
|
||||
onEnqueuePressed?: (
|
||||
prompt: string,
|
||||
count: number,
|
||||
subscriberId: string,
|
||||
subscriberId: string
|
||||
) => void | Promise<void>;
|
||||
selectedModel?: ImageModel;
|
||||
imageMediaId?: string;
|
||||
@@ -89,8 +87,6 @@ export const PromptBoxImage = ({
|
||||
const setUseSystemPrompt = usePromptImageStore((s) => s.setUseSystemPrompt);
|
||||
const aspectRatio = usePromptImageStore((s) => s.aspectRatio);
|
||||
const setAspectRatio = usePromptImageStore((s) => s.setAspectRatio);
|
||||
const resolution = usePromptImageStore((s) => s.resolution);
|
||||
const setResolution = usePromptImageStore((s) => s.setResolution);
|
||||
const generationCount = usePromptImageStore((s) => s.generationCount);
|
||||
const setGenerationCount = usePromptImageStore((s) => s.setGenerationCount);
|
||||
const [isEnqueueing, setIsEnqueueing] = useState(false);
|
||||
@@ -111,40 +107,23 @@ export const PromptBoxImage = ({
|
||||
}, [isImageRowVisible, onImageRowVisibilityChange]);
|
||||
const [aspectRatioList, setAspectRatioList] = useState<PopoverItem[]>([
|
||||
{
|
||||
label: "Wide",
|
||||
selected: aspectRatio === "wide",
|
||||
label: "3:2",
|
||||
selected: aspectRatio === "3:2",
|
||||
icon: <FontAwesomeIcon icon={faRectangle} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "Tall",
|
||||
selected: aspectRatio === "tall",
|
||||
label: "2:3",
|
||||
selected: aspectRatio === "2:3",
|
||||
icon: <FontAwesomeIcon icon={faRectangleVertical} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "Square",
|
||||
selected: aspectRatio === "square",
|
||||
label: "1:1",
|
||||
selected: aspectRatio === "1:1",
|
||||
icon: <FontAwesomeIcon icon={faSquare} className="h-4 w-4" />,
|
||||
},
|
||||
]);
|
||||
const [resolutionList, setResolutionList] = useState<PopoverItem[]>([
|
||||
{
|
||||
label: "1K",
|
||||
selected: resolution === "1k",
|
||||
icon: <FontAwesomeIcon icon={faExpand} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "2K",
|
||||
selected: resolution === "2k",
|
||||
icon: <FontAwesomeIcon icon={faExpand} className="h-4 w-4" />,
|
||||
},
|
||||
{
|
||||
label: "4K",
|
||||
selected: resolution === "4k",
|
||||
icon: <FontAwesomeIcon icon={faExpand} className="h-4 w-4" />,
|
||||
},
|
||||
]);
|
||||
const [generationCountList, setGenerationCountList] = useState<PopoverItem[]>(
|
||||
[],
|
||||
[]
|
||||
);
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
@@ -172,14 +151,14 @@ export const PromptBoxImage = ({
|
||||
const caps = getCapabilitiesForModel(selectedModel);
|
||||
const defaultCount = Math.min(
|
||||
Math.max(1, caps.defaultGenerationCount ?? 1),
|
||||
caps.maxGenerationCount,
|
||||
caps.maxGenerationCount
|
||||
);
|
||||
|
||||
setGenerationCount(defaultCount);
|
||||
|
||||
const items: PopoverItem[] = Array.from(
|
||||
{ length: caps.maxGenerationCount },
|
||||
(_, i) => i + 1,
|
||||
(_, i) => i + 1
|
||||
).map((count) => ({
|
||||
label: String(count),
|
||||
selected: count === defaultCount,
|
||||
@@ -193,7 +172,7 @@ export const PromptBoxImage = ({
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label === String(generationCount),
|
||||
})),
|
||||
}))
|
||||
);
|
||||
}, [generationCount]);
|
||||
|
||||
@@ -201,34 +180,21 @@ export const PromptBoxImage = ({
|
||||
setAspectRatioList((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label.toLowerCase() === aspectRatio,
|
||||
})),
|
||||
selected: item.label === aspectRatio,
|
||||
}))
|
||||
);
|
||||
}, [aspectRatio]);
|
||||
|
||||
useEffect(() => {
|
||||
setResolutionList((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label.toLowerCase() === resolution,
|
||||
})),
|
||||
);
|
||||
}, [resolution]);
|
||||
|
||||
const handleAspectRatioSelect = (selectedItem: PopoverItem) => {
|
||||
setAspectRatio(selectedItem.label.toLowerCase() as any);
|
||||
setAspectRatio(selectedItem.label as any);
|
||||
setAspectRatioList((prev) =>
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label === selectedItem.label,
|
||||
})),
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
const handleResolutionSelect = (selectedItem: PopoverItem) => {
|
||||
setResolution(selectedItem.label.toLowerCase() as any);
|
||||
};
|
||||
|
||||
const handleGenerationCountSelect = (selectedItem: PopoverItem) => {
|
||||
const count = parseInt(selectedItem.label, 10);
|
||||
setGenerationCount(count);
|
||||
@@ -236,7 +202,7 @@ export const PromptBoxImage = ({
|
||||
prev.map((item) => ({
|
||||
...item,
|
||||
selected: item.label === selectedItem.label,
|
||||
})),
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
@@ -269,8 +235,8 @@ export const PromptBoxImage = ({
|
||||
return;
|
||||
}
|
||||
|
||||
console.debug("Selected model:", selectedModel);
|
||||
console.debug("Prompt:", prompt);
|
||||
console.debug("Selected model:", selectedModel)
|
||||
console.debug("Prompt:", prompt)
|
||||
|
||||
setIsEnqueueing(true);
|
||||
|
||||
@@ -287,13 +253,11 @@ export const PromptBoxImage = ({
|
||||
|
||||
try {
|
||||
const aspectRatio = getCurrentAspectRatio();
|
||||
const resolution = getCurrentResolution();
|
||||
|
||||
const request: EnqueueTextToImageRequest = {
|
||||
prompt: prompt,
|
||||
model: selectedModel,
|
||||
aspect_ratio: aspectRatio,
|
||||
image_resolution: resolution,
|
||||
number_images: generationCount,
|
||||
frontend_caller: "text_to_image",
|
||||
frontend_subscriber_id: subscriberId,
|
||||
@@ -304,9 +268,9 @@ export const PromptBoxImage = ({
|
||||
!!referenceImages &&
|
||||
referenceImages.length > 0
|
||||
) {
|
||||
request.image_media_tokens = referenceImages
|
||||
.map((image) => image.mediaToken)
|
||||
.filter((t) => t.length > 0);
|
||||
request.image_media_tokens = referenceImages.map(
|
||||
(image) => image.mediaToken
|
||||
);
|
||||
}
|
||||
|
||||
const generateResponse = await EnqueueTextToImage(request);
|
||||
@@ -330,31 +294,17 @@ export const PromptBoxImage = ({
|
||||
|
||||
const getCurrentAspectRatio = (): EnqueueTextToImageSize => {
|
||||
const selected = aspectRatioList.find((item) => item.selected);
|
||||
switch (selected?.label.toLowerCase()) {
|
||||
case "wide":
|
||||
switch (selected?.label) {
|
||||
case "3:2":
|
||||
return EnqueueTextToImageSize.Wide;
|
||||
case "tall":
|
||||
case "2:3":
|
||||
return EnqueueTextToImageSize.Tall;
|
||||
case "square":
|
||||
case "1:1":
|
||||
default:
|
||||
return EnqueueTextToImageSize.Square;
|
||||
}
|
||||
};
|
||||
|
||||
const getCurrentResolution = (): EnqueueTextToImageResolution | undefined => {
|
||||
const selected = resolutionList.find((item) => item.selected);
|
||||
switch (selected?.label) {
|
||||
case "1k":
|
||||
return EnqueueTextToImageResolution.OneK;
|
||||
case "2k":
|
||||
return EnqueueTextToImageResolution.TwoK;
|
||||
case "4k":
|
||||
return EnqueueTextToImageResolution.FourK;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
@@ -381,7 +331,7 @@ export const PromptBoxImage = ({
|
||||
visible={true}
|
||||
maxImagePromptCount={Math.max(
|
||||
1,
|
||||
selectedModel?.maxImagePromptCount ?? 1,
|
||||
selectedModel?.maxImagePromptCount ?? 1
|
||||
)}
|
||||
allowUpload={true}
|
||||
referenceImages={referenceImages}
|
||||
@@ -395,7 +345,7 @@ export const PromptBoxImage = ({
|
||||
src={image.url}
|
||||
alt="Reference preview"
|
||||
className="w-full h-full object-contain"
|
||||
/>,
|
||||
/>
|
||||
);
|
||||
setIsModalOpen(true);
|
||||
}}
|
||||
@@ -410,7 +360,7 @@ export const PromptBoxImage = ({
|
||||
"rounded-t-none",
|
||||
isFocused
|
||||
? "ring-1 ring-primary border-primary"
|
||||
: "ring-1 ring-transparent",
|
||||
: "ring-1 ring-transparent"
|
||||
)}
|
||||
>
|
||||
<div className="flex justify-center gap-2">
|
||||
@@ -425,7 +375,7 @@ export const PromptBoxImage = ({
|
||||
variant="action"
|
||||
className={twMerge(
|
||||
"h-8 w-8 p-0 bg-transparent hover:bg-transparent group transition-all border-0 shadow-none",
|
||||
isImageRowVisible && "text-primary",
|
||||
isImageRowVisible && "text-primary"
|
||||
)}
|
||||
onClick={() => setShowImagePrompts((prev) => !prev)}
|
||||
>
|
||||
@@ -462,47 +412,26 @@ export const PromptBoxImage = ({
|
||||
</div>
|
||||
<div className="mt-2 flex items-center justify-between gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedModel?.canChangeAspectRatio && (
|
||||
<Tooltip
|
||||
content="Aspect Ratio"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={aspectRatioList}
|
||||
onSelect={handleAspectRatioSelect}
|
||||
mode="toggle"
|
||||
panelTitle="Aspect Ratio"
|
||||
showIconsInList
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon
|
||||
icon={getCurrentResolutionIcon()}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
{selectedModel?.canChangeResolution && (
|
||||
<Tooltip
|
||||
content="Resolution"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={resolutionList}
|
||||
onSelect={handleResolutionSelect}
|
||||
mode="toggle"
|
||||
panelTitle="Resolution"
|
||||
showIconsInList
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon icon={faExpand} className="h-4 w-4" />
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip
|
||||
content="Aspect Ratio"
|
||||
position="top"
|
||||
className="z-50"
|
||||
closeOnClick={true}
|
||||
>
|
||||
<PopoverMenu
|
||||
items={aspectRatioList}
|
||||
onSelect={handleAspectRatioSelect}
|
||||
mode="toggle"
|
||||
panelTitle="Aspect Ratio"
|
||||
showIconsInList
|
||||
triggerIcon={
|
||||
<FontAwesomeIcon
|
||||
icon={getCurrentResolutionIcon()}
|
||||
className="h-4 w-4"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={
|
||||
useSystemPrompt
|
||||
|
||||
@@ -15,13 +15,11 @@ import {
|
||||
faMessageCheck,
|
||||
faSparkles,
|
||||
faSpinnerThird,
|
||||
faSquare,
|
||||
faPortrait,
|
||||
faWaveformLines,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import {
|
||||
faRectangle,
|
||||
faSquare,
|
||||
faRectangleVertical,
|
||||
} from "@fortawesome/pro-regular-svg-icons";
|
||||
import { faRectangle } from "@fortawesome/pro-regular-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { IconProp } from "@fortawesome/fontawesome-svg-core";
|
||||
import { GalleryItem, GalleryModal } from "@storyteller/ui-gallery-modal";
|
||||
@@ -56,7 +54,7 @@ interface PromptBoxVideoProps {
|
||||
useJobContext: () => JobContextType;
|
||||
onEnqueuePressed?: (
|
||||
prompt: string,
|
||||
subscriberId: string,
|
||||
subscriberId: string
|
||||
) => void | Promise<void>;
|
||||
selectedModel?: VideoModel;
|
||||
imageMediaId?: string;
|
||||
@@ -97,14 +95,14 @@ export const PromptBoxVideo = ({
|
||||
const setUseSystemPrompt = usePromptVideoStore((s) => s.setUseSystemPrompt);
|
||||
const generateWithSound = usePromptVideoStore((s) => s.generateWithSound);
|
||||
const setGenerateWithSound = usePromptVideoStore(
|
||||
(s) => s.setGenerateWithSound,
|
||||
(s) => s.setGenerateWithSound
|
||||
);
|
||||
const resolution = usePromptVideoStore((s) => s.resolution);
|
||||
const setResolution = usePromptVideoStore((s) => s.setResolution);
|
||||
const [isEnqueueing, setIsEnqueueing] = useState(false);
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [selectedGalleryImages, setSelectedGalleryImages] = useState<string[]>(
|
||||
[],
|
||||
[]
|
||||
);
|
||||
const referenceImages = usePromptVideoStore((s) => s.referenceImages);
|
||||
const setReferenceImages = usePromptVideoStore((s) => s.setReferenceImages);
|
||||
@@ -126,7 +124,7 @@ export const PromptBoxVideo = ({
|
||||
// When switching to a new model, the existing resolution might not be correct.
|
||||
// This is a gross and nasty hack to handle the case where the resolution is not found.
|
||||
const resolutionExists = selectedModel.sizeOptions.some(
|
||||
(option) => option.textLabel === resolution,
|
||||
(option) => option.textLabel === resolution
|
||||
);
|
||||
const useFirstOption = !resolutionExists;
|
||||
|
||||
@@ -137,7 +135,7 @@ export const PromptBoxVideo = ({
|
||||
faIcon = faRectangle;
|
||||
break;
|
||||
case SizeIconOption.Portrait:
|
||||
faIcon = faRectangleVertical;
|
||||
faIcon = faPortrait;
|
||||
break;
|
||||
case SizeIconOption.Square:
|
||||
faIcon = faSquare;
|
||||
@@ -153,7 +151,7 @@ export const PromptBoxVideo = ({
|
||||
});
|
||||
} else {
|
||||
const resolutionExists = DEFAULT_RESOLUTIONS.some(
|
||||
(option) => option.textLabel === resolution,
|
||||
(option) => option.textLabel === resolution
|
||||
);
|
||||
const useFirstOption = !resolutionExists;
|
||||
|
||||
@@ -164,7 +162,7 @@ export const PromptBoxVideo = ({
|
||||
faIcon = faRectangle;
|
||||
break;
|
||||
case SizeIconOption.Portrait:
|
||||
faIcon = faRectangleVertical;
|
||||
faIcon = faPortrait;
|
||||
break;
|
||||
case SizeIconOption.Square:
|
||||
faIcon = faSquare;
|
||||
@@ -215,7 +213,7 @@ export const PromptBoxVideo = ({
|
||||
resolutionOptions.map((item) => ({
|
||||
...item,
|
||||
selected: item.label === selectedItem.label,
|
||||
})),
|
||||
}))
|
||||
);
|
||||
};
|
||||
|
||||
@@ -331,11 +329,12 @@ export const PromptBoxVideo = ({
|
||||
};
|
||||
|
||||
const getGrokAspectRatio = (): GROK_ASPECT_RATIO => {
|
||||
// NB: This function was just written to give us better type safety.
|
||||
// NB: This function was just written to give us better type safety.
|
||||
// There has to be a cleaner appraoach.
|
||||
const maybeAspectRatio = selectedModel?.sizeOptions?.find(
|
||||
(option) => option.textLabel === resolution,
|
||||
)?.tauriValue;
|
||||
const maybeAspectRatio = selectedModel?.
|
||||
sizeOptions?.
|
||||
find((option) => option.textLabel === resolution)?.
|
||||
tauriValue;
|
||||
|
||||
switch (maybeAspectRatio) {
|
||||
case "landscape":
|
||||
@@ -347,7 +346,7 @@ export const PromptBoxVideo = ({
|
||||
default:
|
||||
return "landscape";
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const [isGalleryModalOpen, setIsGalleryModalOpen] = useState(false);
|
||||
|
||||
@@ -387,7 +386,7 @@ export const PromptBoxVideo = ({
|
||||
src={image.url}
|
||||
alt="Reference preview"
|
||||
className="w-full h-full object-contain"
|
||||
/>,
|
||||
/>
|
||||
);
|
||||
setIsModalOpen(true);
|
||||
}}
|
||||
@@ -404,7 +403,7 @@ export const PromptBoxVideo = ({
|
||||
isImageRowVisible && "rounded-t-none",
|
||||
isFocused
|
||||
? "ring-1 ring-primary border-primary"
|
||||
: "ring-1 ring-transparent",
|
||||
: "ring-1 ring-transparent"
|
||||
)}
|
||||
>
|
||||
<div className="flex justify-center gap-2">
|
||||
|
||||
@@ -8,19 +8,16 @@ export interface RefImage {
|
||||
}
|
||||
|
||||
// ----- 2D Prompt Box Store -----
|
||||
type AspectRatio = "wide" | "tall" | "square";
|
||||
type Resolution = "1k" | "2k" | "4k";
|
||||
type AspectRatio = "3:2" | "2:3" | "1:1";
|
||||
|
||||
interface Prompt2DStore {
|
||||
prompt: string;
|
||||
aspectRatio: AspectRatio;
|
||||
resolution: Resolution;
|
||||
useSystemPrompt: boolean;
|
||||
referenceImages: RefImage[];
|
||||
generationCount: number;
|
||||
setPrompt: (prompt: string) => void;
|
||||
setAspectRatio: (ratio: AspectRatio) => void;
|
||||
setResolution: (resolution: Resolution) => void;
|
||||
setUseSystemPrompt: (value: boolean) => void;
|
||||
setReferenceImages: (images: RefImage[]) => void;
|
||||
setGenerationCount: (count: number) => void;
|
||||
@@ -28,14 +25,12 @@ interface Prompt2DStore {
|
||||
|
||||
export const usePrompt2DStore = create<Prompt2DStore>()((set) => ({
|
||||
prompt: "",
|
||||
aspectRatio: "wide",
|
||||
resolution: "1k",
|
||||
aspectRatio: "3:2",
|
||||
useSystemPrompt: true,
|
||||
referenceImages: [],
|
||||
generationCount: 1,
|
||||
setPrompt: (prompt) => set({ prompt }),
|
||||
setAspectRatio: (aspectRatio) => set({ aspectRatio }),
|
||||
setResolution: (resolution) => set({ resolution }),
|
||||
setUseSystemPrompt: (useSystemPrompt) => set({ useSystemPrompt }),
|
||||
setReferenceImages: (referenceImages) => set({ referenceImages }),
|
||||
setGenerationCount: (generationCount) => set({ generationCount }),
|
||||
@@ -46,22 +41,18 @@ export { usePrompt2DStore as usePromptStore };
|
||||
// ----- 3D Prompt Box Store -----
|
||||
interface Prompt3DStore {
|
||||
prompt: string;
|
||||
resolution: Resolution;
|
||||
useSystemPrompt: boolean;
|
||||
referenceImages: RefImage[];
|
||||
setPrompt: (prompt: string) => void;
|
||||
setResolution: (resolution: Resolution) => void;
|
||||
setUseSystemPrompt: (value: boolean) => void;
|
||||
setReferenceImages: (images: RefImage[]) => void;
|
||||
}
|
||||
|
||||
export const usePrompt3DStore = create<Prompt3DStore>()((set) => ({
|
||||
prompt: "",
|
||||
resolution: "1k",
|
||||
useSystemPrompt: true,
|
||||
referenceImages: [],
|
||||
setPrompt: (prompt) => set({ prompt }),
|
||||
setResolution: (resolution) => set({ resolution }),
|
||||
setUseSystemPrompt: (useSystemPrompt) => set({ useSystemPrompt }),
|
||||
setReferenceImages: (referenceImages) => set({ referenceImages }),
|
||||
}));
|
||||
@@ -70,13 +61,11 @@ export const usePrompt3DStore = create<Prompt3DStore>()((set) => ({
|
||||
interface PromptImageStore {
|
||||
prompt: string;
|
||||
aspectRatio: AspectRatio;
|
||||
resolution: Resolution;
|
||||
useSystemPrompt: boolean;
|
||||
referenceImages: RefImage[];
|
||||
generationCount: number;
|
||||
setPrompt: (prompt: string) => void;
|
||||
setAspectRatio: (ratio: AspectRatio) => void;
|
||||
setResolution: (resolution: Resolution) => void;
|
||||
setUseSystemPrompt: (value: boolean) => void;
|
||||
setReferenceImages: (images: RefImage[]) => void;
|
||||
setGenerationCount: (count: number) => void;
|
||||
@@ -84,14 +73,12 @@ interface PromptImageStore {
|
||||
|
||||
export const usePromptImageStore = create<PromptImageStore>()((set) => ({
|
||||
prompt: "",
|
||||
aspectRatio: "wide",
|
||||
resolution: "1k",
|
||||
aspectRatio: "3:2",
|
||||
useSystemPrompt: true,
|
||||
referenceImages: [],
|
||||
generationCount: 1,
|
||||
setPrompt: (prompt) => set({ prompt }),
|
||||
setAspectRatio: (aspectRatio) => set({ aspectRatio }),
|
||||
setResolution: (resolution) => set({ resolution }),
|
||||
setUseSystemPrompt: (useSystemPrompt) => set({ useSystemPrompt }),
|
||||
setReferenceImages: (referenceImages) => set({ referenceImages }),
|
||||
setGenerationCount: (generationCount) => set({ generationCount }),
|
||||
@@ -131,22 +118,12 @@ export const usePromptVideoStore = create<PromptVideoStore>()((set) => ({
|
||||
}));
|
||||
|
||||
// ----- Edit Prompt Box Store -----
|
||||
type EditAspectRatio = "auto" | "wide" | "tall" | "square";
|
||||
|
||||
interface PromptEditStore {
|
||||
referenceImages: RefImage[];
|
||||
aspectRatio: EditAspectRatio;
|
||||
resolution: Resolution;
|
||||
setReferenceImages: (images: RefImage[]) => void;
|
||||
setAspectRatio: (ratio: EditAspectRatio) => void;
|
||||
setResolution: (resolution: Resolution) => void;
|
||||
}
|
||||
|
||||
export const usePromptEditStore = create<PromptEditStore>()((set) => ({
|
||||
referenceImages: [],
|
||||
aspectRatio: "auto",
|
||||
resolution: "1k",
|
||||
setReferenceImages: (referenceImages) => set({ referenceImages }),
|
||||
setAspectRatio: (aspectRatio) => set({ aspectRatio }),
|
||||
setResolution: (resolution) => set({ resolution }),
|
||||
}));
|
||||
|
||||
@@ -36,12 +36,6 @@ export class ImageModel extends Model {
|
||||
// If true, it'll be displayed on the text-to-image page.
|
||||
readonly canTextToImage: boolean;
|
||||
|
||||
// Whether the model supports changing resolution (e.g. 1k, 2k, 4k)
|
||||
readonly canChangeResolution: boolean;
|
||||
|
||||
// Whether the model supports changing aspect ratio
|
||||
readonly canChangeAspectRatio: boolean;
|
||||
|
||||
constructor(args: {
|
||||
id: string;
|
||||
tauriId: string;
|
||||
@@ -59,8 +53,6 @@ export class ImageModel extends Model {
|
||||
canUseImagePrompt?: boolean;
|
||||
maxImagePromptCount?: number;
|
||||
canTextToImage?: boolean;
|
||||
canChangeResolution?: boolean;
|
||||
canChangeAspectRatio?: boolean;
|
||||
tags?: ModelTag[];
|
||||
progressBarTime?: number;
|
||||
}) {
|
||||
@@ -84,8 +76,6 @@ export class ImageModel extends Model {
|
||||
this.canUseImagePrompt = args.canUseImagePrompt ?? false;
|
||||
this.maxImagePromptCount = Math.max(0, args.maxImagePromptCount ?? 1);
|
||||
this.canTextToImage = args.canTextToImage === false ? false : true; // Default to true !
|
||||
this.canChangeResolution = args.canChangeResolution ?? false;
|
||||
this.canChangeAspectRatio = args.canChangeAspectRatio ?? false;
|
||||
}
|
||||
|
||||
// If the model is a "Nano Banana"-type model, we may want to enable certain features.
|
||||
|
||||
@@ -20,7 +20,7 @@ const cfg = (
|
||||
id: string;
|
||||
category: ModelCategory;
|
||||
info: ModelInfo;
|
||||
},
|
||||
}
|
||||
): ModelConfig => ({
|
||||
label: m.label ?? m.info.name,
|
||||
description: m.description,
|
||||
@@ -134,21 +134,6 @@ const ALL_MODELS: ModelConfig[] = [
|
||||
},
|
||||
tags: [ModelTag.InstructiveEdit, ModelTag.NonMaskedInpainting],
|
||||
}),
|
||||
cfg({
|
||||
id: "gemini_25_flash",
|
||||
category: "image",
|
||||
info: {
|
||||
name: "Nano Banana",
|
||||
tauri_id: "gemini_25_flash",
|
||||
creator: mc.Google,
|
||||
},
|
||||
description: "Incredibly instructive editing",
|
||||
badges: [{ label: "25 sec." }],
|
||||
capabilities: {
|
||||
maxGenerationCount: 4,
|
||||
},
|
||||
tags: [ModelTag.InstructiveEdit, ModelTag.NonMaskedInpainting],
|
||||
}),
|
||||
cfg({
|
||||
id: "nano_banana_pro",
|
||||
category: "image",
|
||||
@@ -267,22 +252,23 @@ export const getModelsByCategory = (category: ModelCategory): ModelConfig[] =>
|
||||
|
||||
export const getInstructiveImageEditModels = (): ModelConfig[] =>
|
||||
ALL_MODELS.filter(
|
||||
(m) => m.category === "image" && m.tags?.includes("instructiveEdit"),
|
||||
(m) => m.category === "image" && m.tags?.includes("instructiveEdit")
|
||||
);
|
||||
|
||||
export const getMaskedInpaintModels = (): ModelConfig[] =>
|
||||
ALL_MODELS.filter(
|
||||
(m) =>
|
||||
m.category === "image" && m.tags?.includes(ModelTag.MaskedInpainting),
|
||||
(m) => m.category === "image" && m.tags?.includes(ModelTag.MaskedInpainting)
|
||||
);
|
||||
|
||||
export const lookupModelByTauriId = (
|
||||
tauriId: string,
|
||||
tauriId: string
|
||||
): ModelConfig | undefined =>
|
||||
ALL_MODELS.find((m) => m.info.tauri_id === tauriId);
|
||||
|
||||
// Single exported capability resolver so callers never need to touch anything else
|
||||
export const getCapabilitiesForModel = (model?: Model): ModelCapabilities => {
|
||||
export const getCapabilitiesForModel = (
|
||||
model?: Model
|
||||
): ModelCapabilities => {
|
||||
if (!model) return DEFAULT_CAPABILITIES;
|
||||
const cfg = lookupModelByTauriId(model.tauriId);
|
||||
return cfg?.capabilities ?? DEFAULT_CAPABILITIES;
|
||||
|
||||
@@ -19,7 +19,6 @@ export const IMAGE_MODELS: ImageModel[] = [
|
||||
canUseImagePrompt: false,
|
||||
maxImagePromptCount: 6,
|
||||
progressBarTime: 10000,
|
||||
canChangeAspectRatio: true,
|
||||
}),
|
||||
new ImageModel({
|
||||
id: "midjourney",
|
||||
@@ -35,7 +34,6 @@ export const IMAGE_MODELS: ImageModel[] = [
|
||||
canUseImagePrompt: false,
|
||||
maxImagePromptCount: 6,
|
||||
progressBarTime: 45000,
|
||||
canChangeAspectRatio: true,
|
||||
}),
|
||||
new ImageModel({
|
||||
id: "flux_pro_1_1_ultra",
|
||||
@@ -51,7 +49,6 @@ export const IMAGE_MODELS: ImageModel[] = [
|
||||
canUseImagePrompt: true,
|
||||
maxImagePromptCount: 6,
|
||||
progressBarTime: 35000,
|
||||
canChangeAspectRatio: true,
|
||||
}),
|
||||
new ImageModel({
|
||||
id: "flux_pro_1_1",
|
||||
@@ -67,7 +64,6 @@ export const IMAGE_MODELS: ImageModel[] = [
|
||||
canUseImagePrompt: true,
|
||||
maxImagePromptCount: 6,
|
||||
progressBarTime: 10000,
|
||||
canChangeAspectRatio: true,
|
||||
}),
|
||||
new ImageModel({
|
||||
id: "flux_1_dev",
|
||||
@@ -83,7 +79,6 @@ export const IMAGE_MODELS: ImageModel[] = [
|
||||
canUseImagePrompt: true,
|
||||
maxImagePromptCount: 6,
|
||||
progressBarTime: 10000,
|
||||
canChangeAspectRatio: true,
|
||||
}),
|
||||
new ImageModel({
|
||||
id: "flux_1_schnell",
|
||||
@@ -99,7 +94,6 @@ export const IMAGE_MODELS: ImageModel[] = [
|
||||
canUseImagePrompt: true,
|
||||
maxImagePromptCount: 6,
|
||||
progressBarTime: 10000,
|
||||
canChangeAspectRatio: true,
|
||||
}),
|
||||
new ImageModel({
|
||||
id: "gemini_25_flash",
|
||||
@@ -118,8 +112,6 @@ export const IMAGE_MODELS: ImageModel[] = [
|
||||
canTextToImage: false,
|
||||
tags: [ModelTag.InstructiveEdit],
|
||||
progressBarTime: 25000,
|
||||
// canChangeResolution: true,
|
||||
canChangeAspectRatio: true,
|
||||
}),
|
||||
new ImageModel({
|
||||
id: "nano_banana_pro",
|
||||
@@ -138,8 +130,6 @@ export const IMAGE_MODELS: ImageModel[] = [
|
||||
canTextToImage: true,
|
||||
tags: [ModelTag.InstructiveEdit],
|
||||
progressBarTime: 25000,
|
||||
canChangeResolution: true,
|
||||
canChangeAspectRatio: true,
|
||||
}),
|
||||
new ImageModel({
|
||||
id: "gpt_image_1",
|
||||
@@ -157,7 +147,6 @@ export const IMAGE_MODELS: ImageModel[] = [
|
||||
canUseImagePrompt: true,
|
||||
maxImagePromptCount: 6,
|
||||
progressBarTime: 60000,
|
||||
canChangeAspectRatio: true,
|
||||
}),
|
||||
new ImageModel({
|
||||
id: "flux_pro_kontext_max",
|
||||
@@ -174,7 +163,6 @@ export const IMAGE_MODELS: ImageModel[] = [
|
||||
canTextToImage: false,
|
||||
tags: [ModelTag.InstructiveEdit],
|
||||
progressBarTime: 20000,
|
||||
canChangeAspectRatio: true,
|
||||
}),
|
||||
new ImageModel({
|
||||
id: "flux_pro_inpaint",
|
||||
@@ -192,7 +180,6 @@ export const IMAGE_MODELS: ImageModel[] = [
|
||||
editingIsInpainting: true,
|
||||
canTextToImage: false,
|
||||
progressBarTime: 30000,
|
||||
canChangeAspectRatio: true,
|
||||
}),
|
||||
new ImageModel({
|
||||
id: "flux_dev_juggernaut_inpaint",
|
||||
@@ -210,12 +197,11 @@ export const IMAGE_MODELS: ImageModel[] = [
|
||||
editingIsInpainting: true,
|
||||
canTextToImage: false,
|
||||
progressBarTime: 10000,
|
||||
canChangeAspectRatio: true,
|
||||
}),
|
||||
];
|
||||
|
||||
export const IMAGE_MODELS_BY_ID: Map<string, ImageModel> = new Map(
|
||||
IMAGE_MODELS.map((model) => [model.id, model]),
|
||||
IMAGE_MODELS.map((model) => [model.id, model])
|
||||
);
|
||||
|
||||
if (IMAGE_MODELS_BY_ID.size !== IMAGE_MODELS.length) {
|
||||
|
||||
@@ -23,9 +23,6 @@ export interface EnqueueTextToImageRequest {
|
||||
// The desired output aspect ratio.
|
||||
aspect_ratio?: EnqueueTextToImageSize;
|
||||
|
||||
// Image resolution. Support: Nano Banana Pro
|
||||
image_resolution?: EnqueueTextToImageResolution;
|
||||
|
||||
// The number of images to generate.
|
||||
number_images?: number;
|
||||
|
||||
@@ -49,7 +46,6 @@ interface EnqueueTextToImageRawRequest {
|
||||
prompt?: string;
|
||||
model?: EnqueueTextToImageModel | string; // TODO: Shouldn't allow string
|
||||
aspect_ratio?: EnqueueTextToImageSize;
|
||||
image_resolution?: EnqueueTextToImageResolution;
|
||||
number_images?: number;
|
||||
image_media_tokens?: string[];
|
||||
frontend_caller?: string;
|
||||
@@ -69,12 +65,6 @@ export enum EnqueueTextToImageSize {
|
||||
Tall = "tall",
|
||||
}
|
||||
|
||||
export enum EnqueueTextToImageResolution {
|
||||
OneK = "one_k",
|
||||
TwoK = "two_k",
|
||||
FourK = "four_k",
|
||||
}
|
||||
|
||||
export interface EnqueueTextToImageError extends CommandResult {
|
||||
error_type: EnqueueTextToImageErrorType;
|
||||
error_message?: string;
|
||||
@@ -117,10 +107,6 @@ export const EnqueueTextToImage = async (request: EnqueueTextToImageRequest) : P
|
||||
mutableRequest.aspect_ratio = request.aspect_ratio;
|
||||
}
|
||||
|
||||
if (!!request.image_resolution) {
|
||||
mutableRequest.image_resolution = request.image_resolution;
|
||||
}
|
||||
|
||||
if (!!request.number_images) {
|
||||
mutableRequest.number_images = request.number_images;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user