Compare commits

...
72 changed files with 277 additions and 359 deletions
+1 -1
View File
@@ -174,7 +174,7 @@ serde_derive = "1.0.219"
serde_json = "1.0.140"
serde_yaml = "0.9.1"
sha3 = "0.10.8"
tauri = { version = "2.4.1", features = ["unstable"] }
tauri = { version = "2.4.1", features = ["unstable", "devtools"] } # NB: "devtools" prevents App Store distribution
thiserror = "2.0.12"
toml = "0.8.20"
url = { version = "2.5.4", features = ["serde"] }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1023 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

@@ -54,7 +54,9 @@ pub async fn sora_image_remix_command(
sora_creds_manager: State<'_, SoraCredentialManager>,
sora_task_queue: State<'_, SoraTaskQueue>,
) -> Result<String, String> {
info!("image_generation_command called; processing image...");
info!("image_generation_command called; scene media token: {:?}, additional images: {:?}",
request.snapshot_media_token, request.maybe_additional_images);
// TODO(bt,2025-04-24): Better error messages to caller
@@ -0,0 +1,8 @@
{
"build": {
"frontendDist": "../../../frontend/apps/editor3d/dist",
"beforeDevCommand": "echo 'no dev'",
"devUrl": "http://localhost:5173",
"beforeBuildCommand": "VITE_ENVIRONMENT_TYPE=production nx run editor3d:build"
}
}
+19
View File
@@ -305,6 +305,25 @@ pub async fn fetch<R: Runtime>(
headers.remove(header::ORIGIN);
};
// NB(bt): In production builds, the origin seems to be "null". This is what our server CORS filter sees:
// [2025-05-01T23:42:35Z WARN actix_cors_configs::configs::fakeyou] Invalid origin: "null"
// [2025-05-01T23:42:35Z WARN actix_cors_configs::util::netlify_branch_domain_matches] Invalid origin: "null"
// [2025-05-01T23:42:35Z WARN actix_cors_configs::util::netlify_branch_domain_matches] Invalid origin: "null"
// ...
// [2025-05-01T23:42:35Z WARN actix_cors_configs::util::netlify_branch_domain_matches] Invalid origin: "null"
// [2025-05-01T23:42:35Z INFO actix_web::middleware::logger] [storyteller-web-85f9b4974-ddvq7] IP=[redact]
// 939322e1095ee5e4-IAD "POST /v1/login HTTP/1.1" 400 42 "-" "tauri-plugin-http/2.4.3" 0.000076
let mut maybe_origin_header = headers.get(header::ORIGIN);
let replace_origin_header = maybe_origin_header.is_none()
|| maybe_origin_header == Some(&HeaderValue::from_static("null"));
if replace_origin_header {
// TODO(bt): Make this a sentinel origin, eg. sentinel.storyteller.ai.
// NB: HeadersMap is a multimap, but this should ensure we just have one value.
headers.insert(header::ORIGIN, HeaderValue::from_static("https://studio.storyteller.ai"));
}
if let Some(data) = data {
request = request.body(data);
}
@@ -77,6 +77,12 @@ export const Login = () => {
return (
<div className="pegboard fixed flex h-full w-full flex-col items-center justify-center bg-center bg-repeat">
<Button
className="absolute left-4 top-4 px-3 py-2 text-sm"
onClick={() => navigate("/")}
>
Back to Home
</Button>
<div className="mb-7 flex w-10/12 max-w-2xl items-center justify-center gap-4">
<img
src="/brand/artcraft-logo.png"
@@ -126,12 +132,12 @@ export const Login = () => {
)}
</div>
<div className="align-items flex w-full max-w-xl rounded-b-xl bg-ui-controls px-6 py-4">
<a
{/* <a
href="https://storyteller.ai/password-reset"
className="grow text-sm !text-primary-400 transition-all duration-150 hover:!text-primary-300"
>
Forgot your password?
</a>
</a> */}
<div className="flex justify-end gap-1 text-sm">
<p className="opacity-75">Don&apos;t have an account?</p>
<Link to="/signup">Sign Up</Link>
@@ -75,6 +75,12 @@ export const Signup = () => {
return (
<div className="pegboard fixed flex h-full w-full flex-col items-center justify-center bg-center bg-repeat">
<Button
className="absolute left-4 top-4 px-3 py-2 text-sm"
onClick={() => navigate("/")}
>
Back to Home
</Button>
<div className="mb-7 flex w-10/12 max-w-2xl items-center justify-center gap-4">
<img
src="/brand/artcraft-logo.png"
@@ -28,11 +28,11 @@ import { getCanvasRenderBitmap } from "~/signal/canvasRenderBitmap";
import { EncodeImageBitmapToBase64 } from "~/utilities/EncodeImageBitmapToBase64";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { PopoverItem } from "~/components/reusable/Popover";
import { Api } from "~/KonvaApp/Api";
import { useJobContext } from "~/components/JobContext";
import { IsDesktopApp } from "@storyteller/tauri-utils";
import { GalleryItem, GalleryModal } from "@storyteller/ui-gallery-modal";
import { PromptsApi } from "@storyteller/api";
interface ReferenceImage {
id: string;
url: string;
@@ -215,7 +215,7 @@ export const PromptBox = () => {
};
const handleTauriEnqueue = async () => {
const api = new Api();
const api = new PromptsApi();
let image = getCanvasRenderBitmap();
if (image === undefined) {
return;
@@ -261,7 +261,7 @@ export const PromptBox = () => {
};
const handleWebEnqueue = async () => {
const api = new Api();
const api = new PromptsApi();
let image = getCanvasRenderBitmap();
if (image === undefined) {
toast.error(
@@ -52,7 +52,7 @@ export const ToolbarTopLeft = () => {
className="h-[28px] select-none"
/>
<Button
{/* <Button
variant="secondary"
icon={faChevronRight}
iconClassName="text-xs"
@@ -61,7 +61,7 @@ export const ToolbarTopLeft = () => {
className="bg-transparent p-0 text-sm text-white/80 hover:bg-transparent hover:text-white hover:underline hover:underline-offset-2"
>
Go to 3D Stage Editor
</Button>
</Button> */}
{/* <TabSelector
tabs={tabs}
@@ -1,4 +1,5 @@
import { twMerge } from "tailwind-merge";
import { Fragment } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faChevronDown,
@@ -65,18 +66,18 @@ export function ProfileDropdown() {
)}
>
<MenuItem
as="a"
as="button"
href={profileUrl}
target="_blank"
rel="noreferrer"
className={twMerge(
"flex w-full items-center gap-3 rounded-lg px-3 py-2",
"text-md font-medium text-gray-700 hover:bg-gray-100",
"text-md font-medium text-white hover:bg-gray-100 hover:text-gray-700",
"transition-colors duration-150",
)}
>
<FontAwesomeIcon icon={faUser} className="w-4 text-gray-700" />
<span className="text-gray-700">My Profile</span>
<FontAwesomeIcon icon={faUser} className="w-4" />
My Profile
</MenuItem>
{menuOptions.map((option, index) => (
@@ -85,7 +86,7 @@ export function ProfileDropdown() {
as="button"
className={twMerge(
"flex w-full items-center gap-3 rounded-lg px-3 py-2",
"text-md font-medium text-gray-700 hover:bg-gray-100",
"text-md font-medium text-white hover:bg-gray-100 hover:text-gray-700",
"transition-colors duration-150",
)}
onClick={(e) => {
@@ -94,7 +95,7 @@ export function ProfileDropdown() {
option.onClick();
}}
>
<FontAwesomeIcon icon={option.icon} className="w-4 text-gray-700" />
<FontAwesomeIcon icon={option.icon} className="w-4" />
<span>{option.label}</span>
</MenuItem>
))}
+10 -4
View File
@@ -31,20 +31,26 @@
{
"path": "../../libs/components/settings-modal/tsconfig.lib.json"
},
{
"path": "../../libs/components/button/tsconfig.lib.json"
},
{
"path": "../../libs/components/gallery-modal/tsconfig.lib.json"
},
{
"path": "../../libs/tauri-utils/tsconfig.lib.json"
"path": "../../libs/components/modal/tsconfig.lib.json"
},
{
"path": "../../libs/components/button/tsconfig.lib.json"
},
{
"path": "../../libs/components/tooltip/tsconfig.lib.json"
},
{
"path": "../../libs/components/demo-modal/tsconfig.lib.json"
},
{
"path": "../../libs/api/tsconfig.lib.json"
},
{
"path": "../../libs/tauri-utils/tsconfig.lib.json"
}
]
}
+10 -4
View File
@@ -4,14 +4,17 @@
{
"path": "../../libs/components/settings-modal"
},
{
"path": "../../libs/components/button"
},
{
"path": "../../libs/components/gallery-modal"
},
{
"path": "../../libs/tauri-utils"
"path": "../../libs/components/modal"
},
{
"path": "../../libs/components/button"
},
{
"path": "../../libs/components/tooltip"
},
{
"path": "../../libs/components/demo-modal"
@@ -19,6 +22,9 @@
{
"path": "../../libs/api"
},
{
"path": "../../libs/tauri-utils"
},
{
"path": "./tsconfig.app.json"
},
@@ -58,7 +58,7 @@ export const TopBar = ({ pageName }: Props) => {
Back to Editor
</ButtonLink>
)}
<Button
{/* <Button
variant="secondary"
icon={faChevronRight}
iconClassName="text-xs"
@@ -67,7 +67,7 @@ export const TopBar = ({ pageName }: Props) => {
className="bg-transparent p-0 text-sm text-white/80 hover:bg-transparent hover:text-white hover:underline hover:underline-offset-2"
>
Go to 2D Canvas
</Button>
</Button> */}
</div>
<div className="flex items-center justify-center gap-2 font-medium">
+7 -1
View File
@@ -124,11 +124,17 @@ export default function LoginScreen() {
)}
</div>
<div className="align-items flex w-full max-w-xl rounded-b-xl bg-ui-controls px-6 py-4">
<a
{/* <a
href="https://storyteller.ai/password-reset"
className="grow text-sm font-medium !text-brand-primary-400 transition-all duration-150 hover:!text-brand-primary-300"
>
Forgot your password?
</a> */}
<a
href="/"
className="grow text-sm font-medium !text-brand-primary-400 transition-all duration-150 hover:!text-brand-primary-300"
>
Back
</a>
<div className="flex justify-end gap-1 text-sm">
<p className="opacity-75">Don&apos;t have an account?</p>
@@ -18,6 +18,7 @@ import { GetFrontendEnvironment } from "~/Classes/GetFrontendEnvironment";
import { gridVisibility } from "../signals/engine";
import { InfiniteGridHelper } from "./InfiniteGridHelper";
import { cameras, selectedCameraId } from "../signals/camera";
import { MediaFilesApi } from "@storyteller/api";
class Scene {
name: string;
@@ -42,6 +43,8 @@ class Scene {
// loading indicator manager
placeholder_manager: LoadingPlaceHolderManager | undefined;
mediaFilesApi: MediaFilesApi;
updateSurfaceIdAttributeToMesh: Function;
helper: MMDAnimationHelper;
ambientLight: THREE.AmbientLight | undefined;
@@ -74,6 +77,7 @@ class Scene {
// global names
this.camera_name = camera_name;
this.placeholder_manager = undefined;
this.mediaFilesApi = new MediaFilesApi();
this.updateSurfaceIdAttributeToMesh = updateSurfaceIdAttributeToMesh;
this.current_scene_checksum = "";
@@ -427,16 +431,10 @@ class Scene {
// TODO: REPLACE
async getMediaURL(media_id: string) {
//This is for prod when we have the proper info on the url.
const api_base_url = environmentVariables.values.BASE_API;
const url = `${api_base_url}/v1/media_files/file/${media_id}`;
const response = await fetch(url);
const json = await JSON.parse(await response.text());
const bucketPath = json["media_file"]["public_bucket_path"];
//const media_base_url = `${media_api_base_url}/vocodes-public`;
const media_base_url = GetCdnOrigin();
const media_url = `${media_base_url}${bucketPath}`;
return media_url;
const response = await this.mediaFilesApi.GetMediaFileByToken({
mediaFileToken: media_id
});
return response.data!.media_links.cdn_url;
}
private floatToPercent(value: number) {
@@ -43,6 +43,7 @@ import {
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
import Queue from "~/pages/PageEnigma/Queue/Queue";
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
import { PromptsApi } from "@storyteller/api";
import {
cameras,
@@ -452,18 +453,20 @@ export const PromptBox = () => {
if (editorEngine) {
editorEngine.positive_prompt = prompt;
const engineApi = new EngineApi();
//const engineApi = new EngineApi();
const promptsApi = new PromptsApi();
const snapshot = editorEngine.snapShotOfCurrentFrame(false);
if (snapshot) {
const snapshotResult = await engineApi.uploadSceneSnapshot({
const snapshotResult = await promptsApi.uploadSceneSnapshot({
screenshot: snapshot.file,
sceneMediaToken: "",
});
console.log("useSystemPrompt", useSystemPrompt);
const response = await engineApi.enqueueImageGeneration({
const response = await promptsApi.enqueueImageGeneration({
disableSystemPrompt: !useSystemPrompt,
prompt: prompt,
snapshotMediaToken: snapshotResult.data || "",
@@ -504,22 +507,16 @@ export const PromptBox = () => {
if (editorEngine) {
editorEngine.positive_prompt = prompt;
const engineApi = new EngineApi();
const promptsApi = new PromptsApi();
const snapshot = editorEngine.snapShotOfCurrentFrame(false);
if (snapshot) {
const snapshotResult = await engineApi.uploadSceneSnapshot({
const snapshotResult = await promptsApi.uploadSceneSnapshot({
screenshot: snapshot.file,
//sceneMediaToken: "",
});
//const response = await engineApi.enqueueImageGeneration({
// disableSystemPrompt: !useSystemPrompt,
// prompt: prompt,
// snapshotMediaToken: "",
// additionalImages: referenceImages.map((image) => image.mediaToken),
//});
const generateResponse = await invoke("sora_image_remix_command", {
request: {
snapshot_media_token: snapshotResult.data!,
@@ -1,148 +0,0 @@
import { useRef } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { useSignalEffect, useSignals } from "@preact/signals-react/runtime";
import { faKey, faUser } from "@fortawesome/pro-solid-svg-icons";
import { AUTH_STATUS } from "~/enums";
import { authentication, login, logout } from "~/signals";
import {
Button,
H1,
Input,
Link,
P,
LoadingDots,
ConfirmationModal,
} from "~/components";
export default function LoginScreen() {
useSignals();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const formRef = useRef<HTMLFormElement | null>(null);
const { status: authStatus } = authentication;
const authLoaderMessage = getAuthLoaderMessage();
const shouldShowLoader = checkShouldShowLoader();
const showNoAccessModal = authStatus.value === AUTH_STATUS.NO_ACCESS;
const handleOnSumbit = (ev: React.FormEvent<HTMLFormElement>) => {
ev.preventDefault();
if (formRef.current) {
const form = new FormData(formRef.current);
const usernameOrEmail = form.get("usernameOrEmail")?.toString();
const password = form.get("password")?.toString();
if (usernameOrEmail && password && login) {
login({
usernameOrEmail,
password,
});
}
}
}; // end handleOnSubmit
useSignalEffect(() => {
const redirectPath = searchParams.get("redirect");
if (authStatus.value === AUTH_STATUS.LOGGED_IN) {
navigate(redirectPath ? redirectPath : "/");
}
});
return (
<div className="fixed w-full" style={{ height: "calc(100% - 72px)" }}>
<div className="mx-auto my-6 w-10/12 max-w-2xl">
<H1 className="text-center text-[32px] font-bold">
Login to Storyteller
</H1>
</div>
<div className="relative mx-auto my-6 w-10/12 max-w-2xl overflow-hidden rounded-lg border border-ui-panel-border bg-ui-panel p-6">
<form
ref={formRef}
onSubmit={handleOnSumbit}
className="flex flex-col gap-4"
>
<Input
label="Username or Email"
icon={faUser}
name="usernameOrEmail"
placeholder="Username or Email"
autoComplete="username"
required
/>
<Input
label="Password"
icon={faKey}
type="password"
name="password"
placeholder="Password"
autoComplete="current-password"
required
/>
<div className="align-items mb-3 flex">
<a
href="https://storyteller.ai/password-reset"
className="grow text-sm text-brand-primary transition-all duration-150 hover:text-brand-primary-400"
>
Forgot your password?
</a>
<div className="flex justify-end gap-1 text-sm">
<P>Don&apos;t have an account?</P>
<Link
to="/signup"
reloadDocument={true} // TODO(bt,2025-04-19): Once we have in-page routing, get rid of this.
>Sign Up</Link>
</div>
</div>
<Button className="h-11 w-full text-[15px]">Login</Button>
</form>
<LoadingDots
className="absolute left-0 top-0 h-full w-full"
isShowing={shouldShowLoader}
message={authLoaderMessage}
type="bricks"
/>
<ConfirmationModal
text="We're in a closed beta and you'll need a beta key to use this app."
title="Unauthorized"
open={showNoAccessModal}
onClose={handleClose}
cancelText="Close"
onCancel={handleClose}
okText="Get Beta Key"
onOk={() => {
handleClose();
if (window) {
window.open("https://storyteller.ai/beta-key/redeem", "_blank");
}
}}
/>
</div>
</div>
);
}
const handleClose = () => {
if (authentication.status.value !== AUTH_STATUS.LOGGED_OUT) {
logout();
}
};
const checkShouldShowLoader = () => {
return (
authentication.status.value === AUTH_STATUS.LOGGING ||
authentication.status.value === AUTH_STATUS.LOGGED_IN
);
};
const getAuthLoaderMessage = () => {
if (authentication.status.value === AUTH_STATUS.LOGGED_IN) {
return "Authenticated, Redirecting...";
}
if (authentication.status.value === AUTH_STATUS.GET_USER_INFO) {
return "Getting User Info...";
}
return "Getting Session...";
};
+144 -140
View File
@@ -1,146 +1,150 @@
import { faEnvelope, faKey, faUser } from "@fortawesome/pro-solid-svg-icons";
import { Button, H1, Input, Link, P } from "~/components";
import { addToast, authentication, signUp } from "~/signals";
import { FormEvent, useRef, useState } from "react";
import { AUTH_STATUS, ToastTypes } from "~/enums";
import { useSignalEffect, useSignals } from "@preact/signals-react/runtime";
import { useNavigate, useSearchParams } from "react-router-dom";
// import { faEnvelope, faKey, faUser } from "@fortawesome/pro-solid-svg-icons";
// import { Button, H1, Input, Link, P } from "~/components";
// import { addToast, authentication, signUp } from "~/signals";
// import { FormEvent, useRef, useState } from "react";
// import { AUTH_STATUS, ToastTypes } from "~/enums";
// import { useSignalEffect, useSignals } from "@preact/signals-react/runtime";
// import { useNavigate, useSearchParams } from "react-router-dom";
const regEx =
/^(([^<>()[\].,;:\s@"]+(.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i;
// const regEx =
// /^(([^<>()[\].,;:\s@"]+(.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i;
export default function SignUpScreen() {
useSignals();
const navigate = useNavigate();
const formRef = useRef<HTMLFormElement | null>(null);
const [usernameError, setUsernameError] = useState("");
const [emailError, setEmailError] = useState("");
const [passwordError, setPasswordError] = useState("");
const [passwordConfirmationError, setPasswordConfirmationError] =
useState("");
const [searchParams] = useSearchParams();
const { status: authStatus } = authentication;
// export default function SignUpScreen() {
// useSignals();
// const navigate = useNavigate();
// const formRef = useRef<HTMLFormElement | null>(null);
// const [usernameError, setUsernameError] = useState("");
// const [emailError, setEmailError] = useState("");
// const [passwordError, setPasswordError] = useState("");
// const [passwordConfirmationError, setPasswordConfirmationError] =
// useState("");
// const [searchParams] = useSearchParams();
// const { status: authStatus } = authentication;
const handleOnSubmit = (ev: FormEvent<HTMLFormElement>) => {
ev.preventDefault();
if (formRef.current) {
const form = new FormData(formRef.current);
const email = form.get("email")?.toString();
const password = form.get("password")?.toString();
const passwordConfirmation = form
.get("password-confirmation")
?.toString();
const username = form.get("username")?.toString();
setUsernameError("");
if (!username || username.length < 6) {
setUsernameError("Username must be at least 6 characters long");
}
setEmailError("");
if (!email || !email.match(regEx)) {
setEmailError("Email must be a valid email address");
}
setPasswordError("");
if (!password || password.length < 8) {
setPasswordError("Password must be at least 8 characters long");
}
setPasswordConfirmationError("");
if (!passwordError && passwordConfirmation !== password) {
setPasswordConfirmationError("Passwords do not match");
}
// const handleOnSubmit = (ev: FormEvent<HTMLFormElement>) => {
// ev.preventDefault();
// if (formRef.current) {
// const form = new FormData(formRef.current);
// const email = form.get("email")?.toString();
// const password = form.get("password")?.toString();
// const passwordConfirmation = form
// .get("password-confirmation")
// ?.toString();
// const username = form.get("username")?.toString();
// setUsernameError("");
// if (!username || username.length < 6) {
// setUsernameError("Username must be at least 6 characters long");
// }
// setEmailError("");
// if (!email || !email.match(regEx)) {
// setEmailError("Email must be a valid email address");
// }
// setPasswordError("");
// if (!password || password.length < 8) {
// setPasswordError("Password must be at least 8 characters long");
// }
// setPasswordConfirmationError("");
// if (!passwordError && passwordConfirmation !== password) {
// setPasswordConfirmationError("Passwords do not match");
// }
if (
!usernameError &&
!passwordError &&
!emailError &&
!passwordConfirmationError &&
signUp
) {
signUp({
username: username!,
email: email!,
password: password!,
passwordConfirmation: passwordConfirmation!,
}).then((error) => {
console.log("Error occured", error);
if (error) {
addToast(ToastTypes.ERROR, error);
}
});
}
}
}; // end handleOnSubmit
// if (
// !usernameError &&
// !passwordError &&
// !emailError &&
// !passwordConfirmationError &&
// signUp
// ) {
// signUp({
// username: username!,
// email: email!,
// password: password!,
// passwordConfirmation: passwordConfirmation!,
// }).then((error) => {
// console.log("Error occured", error);
// if (error) {
// addToast(ToastTypes.ERROR, error);
// }
// });
// }
// }
// }; // end handleOnSubmit
useSignalEffect(() => {
const redirectPath = searchParams.get("redirect");
if (authStatus.value === AUTH_STATUS.LOGGED_IN) {
navigate(redirectPath ? redirectPath : "/");
return;
}
if (authStatus.value === AUTH_STATUS.NO_ACCESS) {
window.open("https://storyteller.ai/", "_self");
return;
}
});
// useSignalEffect(() => {
// const redirectPath = searchParams.get("redirect");
// if (authStatus.value === AUTH_STATUS.LOGGED_IN) {
// navigate(redirectPath ? redirectPath : "/");
// return;
// }
// if (authStatus.value === AUTH_STATUS.NO_ACCESS) {
// window.open("https://storyteller.ai/", "_self");
// return;
// }
// });
return (
<div
className="fixed w-full overflow-scroll"
style={{ height: "calc(100% - 72px)" }}
>
<div className="mx-auto my-6 w-10/12 max-w-2xl">
<H1 className="text-center">Sign Up to Storyteller</H1>
</div>
<div className="mx-auto my-6 w-10/12 max-w-2xl rounded-lg border border-ui-panel-border bg-ui-panel p-6">
<form ref={formRef} onSubmit={handleOnSubmit}>
<Input
label="Username"
icon={faUser}
placeholder="Username"
name="username"
errorMessage={usernameError}
required
/>
<br />
<Input
type="email"
label="Email"
icon={faEnvelope}
placeholder="Email"
name="email"
errorMessage={emailError}
required
/>
<br />
<Input
type="password"
label="Password"
icon={faKey}
name="password"
placeholder="Password"
autoComplete="current-password"
errorMessage={passwordError}
required
/>
<br />
<Input
type="password"
label="Password Confrimation"
icon={faKey}
placeholder="Password Confirmation"
name="password-confirmation"
errorMessage={passwordConfirmationError}
required
/>
<br />
<br />
<Button>Sign up</Button>
<br />
<div className="flex gap-2">
<P>Already have an account?</P>
<Link to="/login">Log in instead</Link>
</div>
</form>
</div>
</div>
);
}
// return (
// <div
// className="fixed w-full overflow-scroll"
// style={{ height: "calc(100% - 72px)" }}
// >
// <div className="mx-auto my-6 w-10/12 max-w-2xl">
// <H1 className="text-center">Sign Up to Storyteller</H1>
// </div>
// <div className="mx-auto my-6 w-10/12 max-w-2xl rounded-lg border border-ui-panel-border bg-ui-panel p-6">
// <form ref={formRef} onSubmit={handleOnSubmit}>
// <Input
// label="Username"
// icon={faUser}
// placeholder="Username"
// name="username"
// errorMessage={usernameError}
// required
// />
// <br />
// <Input
// type="email"
// label="Email"
// icon={faEnvelope}
// placeholder="Email"
// name="email"
// errorMessage={emailError}
// required
// />
// <br />
// <Input
// type="password"
// label="Password"
// icon={faKey}
// name="password"
// placeholder="Password"
// autoComplete="current-password"
// errorMessage={passwordError}
// required
// />
// <br />
// <Input
// type="password"
// label="Password Confrimation"
// icon={faKey}
// placeholder="Password Confirmation"
// name="password-confirmation"
// errorMessage={passwordConfirmationError}
// required
// />
// <br />
// <br />
// <Button>Sign up</Button>
// <div className="flex gap-2">
// <P>Back to home</P>
// <Link to="/">Back</Link>
// </div>
// <br />
// <div className="flex gap-2">
// <P>Already have an account?</P>
// <Link to="/login">Log in instead</Link>
// </div>
// </form>
// </div>
// </div>
// );
// }
@@ -160,6 +160,15 @@ export default function SignUpScreen() {
</form>
</div>
<div className="align-items flex w-full max-w-xl rounded-b-xl bg-ui-controls px-6 py-4">
<div className="flex w-full justify-start gap-1 text-sm">
<p className="opacity-75"></p>
<a
href="/"
className="font-medium text-brand-primary-400 hover:text-brand-primary-300"
>
Back
</a>
</div>
<div className="flex w-full justify-end gap-1 text-sm">
<p className="opacity-75">Already have an account?</p>
<a
+3
View File
@@ -65,6 +65,9 @@
},
{
"path": "../../libs/tauri-utils"
},
{
"path": "../../libs/api"
}
]
}
+17 -17
View File
@@ -194,48 +194,48 @@ export class PromptsApi extends ApiManager {
public async uploadSceneSnapshot({
screenshot,
sceneMediaToken,
}: {
}: {
screenshot: File; // base64 encoded PNG
sceneMediaToken?: string;
}): Promise<ApiResponse<string>> {
}): Promise<ApiResponse<string>> {
const endpoint = `${this.ApiTargets.BaseApi}/v1/image_studio/scene_snapshot`;
const formData = new FormData();
formData.append("snapshot", screenshot); // Changed from "screenshot" to "snapshot" to match API spec
if (sceneMediaToken) {
formData.append("scene_media_token", sceneMediaToken);
formData.append("scene_media_token", sceneMediaToken);
}
const uuidIdempotencyToken = crypto.randomUUID(); // Generate a new UUID
formData.append("uuid_idempotency_token", uuidIdempotencyToken); // Added uuid_idempotency_token
const uuidIdempotencyToken = crypto.randomUUID();
formData.append("uuid_idempotency_token", uuidIdempotencyToken);
const response = await fetch(endpoint, {
method: "POST",
headers: {
method: "POST",
headers: {
Accept: "application/json",
},
credentials: "include",
body: formData,
},
credentials: "include",
body: formData,
});
const postResponse = await response.json();
console.log(postResponse);
let result: { success: boolean; data?: string; errorMessage?: string };
if (postResponse.success) {
result = {
result = {
success: true,
data: postResponse.snapshot_media_token,
errorMessage: undefined,
};
};
} else {
result = {
result = {
success: false,
errorMessage: "Failed to generate snapshot.",
};
};
}
return result;
}
}
}
-3
View File
@@ -12,9 +12,6 @@
{
"path": "./apps/editor3d"
},
{
"path": "./apps/my-app"
},
{
"path": "./libs/components/toaster"
},
-2
View File
@@ -2,5 +2,3 @@
set -euxo pipefail
# calls these
./script/desktop/launch_rust_only_3d_for_michael.sh
echo "All scripts completed!"