mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-22 01:29:31 -04:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54b1125854 | ||
|
|
5138b24b54 | ||
|
|
19ac49a69c |
@@ -1,38 +1,65 @@
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { AUTH_STATUS } from "~/enums";
|
||||
import { authentication } from "~/signals";
|
||||
//import { ButtonLink } from "@storyteller/ui-button-link";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import ProfileDropdown from "./ProfileDropdown";
|
||||
import { LoginModal } from "@storyteller/ui-login-modal";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
disableHotkeyInput,
|
||||
DomLevels,
|
||||
enableHotkeyInput,
|
||||
} from "~/pages/PageEnigma/signals";
|
||||
|
||||
export const AuthButtons = () => {
|
||||
useSignals();
|
||||
const [showLoginModal, setShowLoginModal] = useState(false);
|
||||
const [isSignUp, setIsSignUp] = useState(false);
|
||||
|
||||
const { status } = authentication;
|
||||
const { status, userInfo } = authentication;
|
||||
|
||||
console.log("Current State:");
|
||||
console.log(status.value);
|
||||
if (status.value === AUTH_STATUS.LOGGED_IN) {
|
||||
console.log("SHOWING DROPDOWN");
|
||||
return <ProfileDropdown />;
|
||||
} else {
|
||||
return (
|
||||
<div className="flex items-center gap-3.5">
|
||||
<span className="text-white/20">|</span>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* <ButtonLink
|
||||
to="/login"
|
||||
variant="secondary"
|
||||
reloadDocument={true}
|
||||
className="h-[38px]" // TODO(bt,2025-04-19): Once we have in-page routing, get rid of this.
|
||||
<>
|
||||
<div className="flex items-center gap-3.5">
|
||||
<span className="text-white/20">|</span>
|
||||
<Button
|
||||
className="h-[38px]"
|
||||
onClick={() => {
|
||||
setIsSignUp(true);
|
||||
setShowLoginModal(true);
|
||||
}}
|
||||
>
|
||||
Login
|
||||
</ButtonLink>
|
||||
<ButtonLink
|
||||
to="/signup"
|
||||
reloadDocument={true}
|
||||
className="h-[38px]" // TODO(bt,2025-04-19): Once we have in-page routing, get rid of this.
|
||||
>
|
||||
Sign Up
|
||||
</ButtonLink> */}
|
||||
Login / Sign Up
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{showLoginModal && (
|
||||
<LoginModal
|
||||
onClose={() => setShowLoginModal(false)}
|
||||
isSignUp={isSignUp}
|
||||
videoSrc2D="/resources/videos/artcraft-canvas-demo.mp4"
|
||||
videoSrc3D="/resources/videos/artcraft-3d-demo.mp4"
|
||||
onArtCraftAuthSuccess={(userInfo_value) => {
|
||||
userInfo.value = userInfo_value;
|
||||
status.value = AUTH_STATUS.LOGGED_IN;
|
||||
setShowLoginModal(false);
|
||||
}}
|
||||
onOpenChange={(isOpen: boolean) => {
|
||||
if (isOpen) {
|
||||
disableHotkeyInput(DomLevels.DIALOGUE);
|
||||
} else {
|
||||
enableHotkeyInput(DomLevels.DIALOGUE);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -689,7 +689,7 @@ export const PaintSurface = ({
|
||||
scaleY: finalScaleY,
|
||||
offsetX: finalOffsetX,
|
||||
offsetY: finalOffsetY,
|
||||
});
|
||||
}, true);
|
||||
} else {
|
||||
store.updateNode(
|
||||
nodeId,
|
||||
|
||||
@@ -7,6 +7,15 @@ interface DeleteHotkeysProps {
|
||||
|
||||
export const useDeleteHotkeys = ({ onDelete, target = document }: DeleteHotkeysProps): void => {
|
||||
const handleKeyDown = useCallback((event: KeyboardEvent) => {
|
||||
// Check if the event target is an input field or contentEditable element
|
||||
const target = event.target as HTMLElement;
|
||||
if (target.tagName === 'INPUT' ||
|
||||
target.tagName === 'TEXTAREA' ||
|
||||
target.isContentEditable ||
|
||||
target.getAttribute('role') === 'textbox') {
|
||||
return; // Don't prevent default for input fields
|
||||
}
|
||||
|
||||
const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
|
||||
const isDelete = event.key === 'Delete' ||
|
||||
event.key === 'Backspace' ||
|
||||
|
||||
@@ -69,6 +69,14 @@ export const ArtCraftSignUp = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputFocus = () => {
|
||||
|
||||
};
|
||||
|
||||
const handleInputBlur = () => {
|
||||
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full">
|
||||
<h2 className="text-3xl font-bold mb-3 text-center">
|
||||
@@ -88,6 +96,8 @@ export const ArtCraftSignUp = ({
|
||||
placeholder="Username"
|
||||
required
|
||||
autoComplete="off"
|
||||
onFocus={handleInputFocus}
|
||||
onBlur={handleInputBlur}
|
||||
/>
|
||||
<Input
|
||||
label="Email"
|
||||
@@ -98,6 +108,8 @@ export const ArtCraftSignUp = ({
|
||||
required={isSignUp}
|
||||
style={isSignUp ? {} : { display: "none" }}
|
||||
autoComplete="off"
|
||||
onFocus={handleInputFocus}
|
||||
onBlur={handleInputBlur}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
@@ -108,6 +120,8 @@ export const ArtCraftSignUp = ({
|
||||
placeholder="Email or Username"
|
||||
required
|
||||
autoComplete="off"
|
||||
onFocus={handleInputFocus}
|
||||
onBlur={handleInputBlur}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -119,6 +133,8 @@ export const ArtCraftSignUp = ({
|
||||
placeholder="Password"
|
||||
required
|
||||
autoComplete="off"
|
||||
onFocus={handleInputFocus}
|
||||
onBlur={handleInputBlur}
|
||||
/>
|
||||
{isSignUp && (
|
||||
<Input
|
||||
@@ -129,6 +145,8 @@ export const ArtCraftSignUp = ({
|
||||
placeholder="Confirm Password"
|
||||
required
|
||||
autoComplete="off"
|
||||
onFocus={handleInputFocus}
|
||||
onBlur={handleInputBlur}
|
||||
/>
|
||||
)}
|
||||
<button type="submit" className="hidden" />
|
||||
|
||||
@@ -23,6 +23,7 @@ interface LoginModalProps {
|
||||
openAiLogo?: string;
|
||||
onOpenChange?: (isOpen: boolean) => void;
|
||||
onArtCraftAuthSuccess?: (userInfo: any) => void;
|
||||
isSignUp?: boolean;
|
||||
}
|
||||
|
||||
export function LoginModal({
|
||||
@@ -32,35 +33,21 @@ export function LoginModal({
|
||||
openAiLogo,
|
||||
onOpenChange,
|
||||
onArtCraftAuthSuccess,
|
||||
isSignUp: initialIsSignUp = true,
|
||||
}: LoginModalProps) {
|
||||
const [step, setStep] = useState(1);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isLoggedInArtCraft, setIsLoggedInArtCraft] = useState(false);
|
||||
const [isSignUp, setIsSignUp] = useState(true);
|
||||
const [isSignUp, setIsSignUp] = useState(initialIsSignUp);
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
const artCraftFormRef = useRef<HTMLFormElement>(null);
|
||||
const [initialArtCraftLoginStatusForUI, setInitialArtCraftLoginStatusForUI] =
|
||||
useState<boolean | null>(null);
|
||||
const [initialSoraSessionStatusForUI, setInitialSoraSessionStatusForUI] =
|
||||
useState<boolean | null>(null);
|
||||
const [showDiscord, setShowDiscord] = useState(false);
|
||||
const [showSuccess, setShowSuccess] = useState(false);
|
||||
|
||||
// Determine total steps and current step for UI
|
||||
let uiTotalSteps = 4;
|
||||
let uiCurrentStep = step;
|
||||
if (initialSoraSessionStatusForUI) {
|
||||
// If Sora session exists, remove the Sora step (step 3)
|
||||
uiTotalSteps = 3;
|
||||
if (step === 1) uiCurrentStep = 1;
|
||||
else if (step === 2) uiCurrentStep = 2;
|
||||
else if (step === 4) uiCurrentStep = 3;
|
||||
} else if (initialArtCraftLoginStatusForUI) {
|
||||
// If ArtCraft is logged in but not Sora, keep 4 steps
|
||||
uiTotalSteps = 4;
|
||||
if (step === 1) uiCurrentStep = 1;
|
||||
else if (step === 3) uiCurrentStep = 2;
|
||||
else if (step === 4) uiCurrentStep = 3;
|
||||
}
|
||||
// Now we have 3 steps: Welcome, Sign up/Login, and Success
|
||||
const uiTotalSteps = 3;
|
||||
const uiCurrentStep = showSuccess ? 3 : showDiscord ? 2 : step;
|
||||
|
||||
const initSession = async () => {
|
||||
const result = await CheckSoraSession();
|
||||
@@ -82,34 +69,15 @@ export function LoginModal({
|
||||
|
||||
// Check session on component mount
|
||||
useEffect(() => {
|
||||
// Run both checks in parallel
|
||||
Promise.all([initSession(), checkArtCraftLogin()]).then(
|
||||
([soraSessionExists, loggedIn]) => {
|
||||
setInitialArtCraftLoginStatusForUI(loggedIn || false); // Set initial status for UI step counting
|
||||
setInitialSoraSessionStatusForUI(soraSessionExists || false); // Set initial Sora status for UI step counting
|
||||
|
||||
if (soraSessionExists && loggedIn) {
|
||||
setIsLoggedInArtCraft(true);
|
||||
setIsOpen(false); // Close the modal if fully logged in
|
||||
return;
|
||||
} else if (soraSessionExists && !loggedIn) {
|
||||
// Sora session exists, but not logged in to ArtCraft
|
||||
setIsOpen(true);
|
||||
setStep(2);
|
||||
setIsLoggedInArtCraft(false);
|
||||
} else if (!soraSessionExists && loggedIn) {
|
||||
// No Sora session, but logged in to ArtCraft
|
||||
setIsOpen(true);
|
||||
setStep(3);
|
||||
setIsLoggedInArtCraft(true);
|
||||
} else if (!soraSessionExists) {
|
||||
// No Sora session, start at step 1
|
||||
setIsOpen(true);
|
||||
setStep(1);
|
||||
setIsLoggedInArtCraft(false); // Ensure this is also set
|
||||
}
|
||||
checkArtCraftLogin().then((loggedIn) => {
|
||||
if (loggedIn) {
|
||||
setIsLoggedInArtCraft(true);
|
||||
setIsOpen(false);
|
||||
} else {
|
||||
setIsOpen(true);
|
||||
setStep(1); // Always start at step 1
|
||||
}
|
||||
);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -122,31 +90,7 @@ export function LoginModal({
|
||||
};
|
||||
|
||||
const handleNext = async () => {
|
||||
if (step === 3) {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
if (IsDesktopApp()) {
|
||||
await invoke("open_sora_login_command");
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
const result = await CheckSoraSession();
|
||||
const sessionExists = result.state === SoraSessionState.Valid;
|
||||
if (sessionExists) {
|
||||
setStep(4); // Always go to the final step after Sora login
|
||||
}
|
||||
} else {
|
||||
alert("Please open the desktop app to login");
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000));
|
||||
const sessionExists = true;
|
||||
if (sessionExists) {
|
||||
setStep(4); // Always go to the final step after Sora login
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Login failed:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
} else if (step === 2) {
|
||||
if (step === 2) {
|
||||
// Trigger the form submit in ArtCraftSignUp
|
||||
if (artCraftFormRef.current) {
|
||||
artCraftFormRef.current.requestSubmit();
|
||||
@@ -174,12 +118,68 @@ export function LoginModal({
|
||||
</div>
|
||||
);
|
||||
|
||||
const handleDiscordJoin = () => {
|
||||
window.open("https://discord.gg/75svZP2Vje", "_blank");
|
||||
setShowDiscord(false);
|
||||
setShowSuccess(true);
|
||||
};
|
||||
|
||||
// Step content rendering
|
||||
const renderStepContent = () => {
|
||||
if (step === 2 && isLoggedInArtCraft) {
|
||||
setStep(3);
|
||||
return null;
|
||||
if (showSuccess) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full">
|
||||
<h2 className="text-3xl font-bold mb-2 text-center">
|
||||
Thank you for signing in!
|
||||
</h2>
|
||||
<p className="text-white/70 mb-6 text-center">
|
||||
You're all set to start creating amazing content.
|
||||
</p>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleClose}
|
||||
icon={faArrowRight}
|
||||
iconFlip={true}
|
||||
className="text-md"
|
||||
>
|
||||
Get Started
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (showDiscord) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full">
|
||||
<h2 className="text-3xl font-bold mb-2 text-center">
|
||||
Join Our Community
|
||||
</h2>
|
||||
<p className="text-white/70 mb-6 text-center px-24">
|
||||
Connect with other creators, share your work, and get the latest updates in our Discord community.
|
||||
</p>
|
||||
<div className="flex gap-4">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
setShowDiscord(false);
|
||||
setShowSuccess(true);
|
||||
}}
|
||||
>
|
||||
Skip for now
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleDiscordJoin}
|
||||
icon={faDiscord}
|
||||
className="text-md bg-[#5865F2] hover:bg-[#6a76ff]"
|
||||
>
|
||||
Join Discord
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
switch (step) {
|
||||
case 1:
|
||||
return (
|
||||
@@ -240,12 +240,15 @@ export function LoginModal({
|
||||
try {
|
||||
let signupResponse, loginResponse;
|
||||
if (isSignUp) {
|
||||
console.log("Sign up!")
|
||||
signupResponse = await usersApi.Signup({
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
passwordConfirmation,
|
||||
});
|
||||
console.log(signupResponse)
|
||||
|
||||
if (!signupResponse.success) {
|
||||
setErrorMessage(
|
||||
signupResponse.errorMessage ||
|
||||
@@ -258,41 +261,32 @@ export function LoginModal({
|
||||
usernameOrEmail: username || email,
|
||||
password,
|
||||
});
|
||||
if (!loginResponse.success) {
|
||||
setErrorMessage(
|
||||
loginResponse.errorMessage ||
|
||||
"Login after signup failed, please try again. "
|
||||
);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
loginResponse = await usersApi.Login({
|
||||
usernameOrEmail: username || email,
|
||||
password,
|
||||
});
|
||||
if (!loginResponse.success) {
|
||||
setErrorMessage(
|
||||
loginResponse.errorMessage ||
|
||||
"Login failed, please try again."
|
||||
);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!loginResponse.success) {
|
||||
setErrorMessage(
|
||||
loginResponse.errorMessage ||
|
||||
"Login failed, please try again."
|
||||
);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoggedInArtCraft(true);
|
||||
console.log("ARTCRAFT LOGIN SUCCeSS")
|
||||
if (onArtCraftAuthSuccess) {
|
||||
const session = await usersApi.GetSession();
|
||||
const userInfo = session.data?.user;
|
||||
console.log("USERINFO")
|
||||
console.log(userInfo)
|
||||
if (userInfo) onArtCraftAuthSuccess(userInfo);
|
||||
}
|
||||
// Check for Sora session and skip to step 4 if present
|
||||
const soraSessionExists = await initSession();
|
||||
if (soraSessionExists) {
|
||||
setStep(4);
|
||||
} else {
|
||||
setStep(3);
|
||||
}
|
||||
setShowDiscord(true); // Show Discord step after successful login
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
setErrorMessage(
|
||||
@@ -308,53 +302,40 @@ export function LoginModal({
|
||||
errorMessage={errorMessage}
|
||||
/>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full">
|
||||
<h2 className="text-3xl font-bold mb-2 text-center">
|
||||
Login with your OpenAI account
|
||||
</h2>
|
||||
<p className="text-white/70 mb-6 text-center px-16">
|
||||
We're the application layer for images and video. To provide you
|
||||
with <b>100% free service</b>, we use your existing AI accounts.
|
||||
To start off, you'll need to add your OpenAI Sora account. If you
|
||||
have a ChatGPT subscription, you can log in with Sora.
|
||||
</p>
|
||||
<img
|
||||
src={openAiLogo}
|
||||
alt="OpenAI Logo"
|
||||
className="w-72 h-72 mx-auto grow"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
case 4:
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-full">
|
||||
<h2 className="text-3xl font-bold mb-2 text-center">
|
||||
Thank you for signing in!
|
||||
</h2>
|
||||
<p className="text-white/70 mb-6 text-center px-24">
|
||||
You're all set to start creating amazing content. Join our Discord
|
||||
community to connect with other creators, share your work, and get
|
||||
the latest updates.
|
||||
</p>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
window.open("https://discord.gg/75svZP2Vje", "_blank")
|
||||
}
|
||||
icon={faDiscord}
|
||||
className="text-md bg-[#5865F2] hover:bg-[#6a76ff] rounded-xl"
|
||||
>
|
||||
Join our Discord
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const renderFooterButtons = () => {
|
||||
if (showSuccess || showDiscord) {
|
||||
return null; // No footer buttons needed in success or discord states
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-end justify-center gap-2.5 mt-10 grow">
|
||||
{step === 2 && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleBack}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
icon={faArrowRight}
|
||||
iconFlip={true}
|
||||
onClick={handleNext}
|
||||
loading={isLoading}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{step === 1 ? "Continue" : isSignUp ? "Sign up" : "Login"}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return createPortal(
|
||||
<Transition appear show={isOpen}>
|
||||
<div className="fixed inset-0 z-[100]">
|
||||
@@ -382,58 +363,16 @@ export function LoginModal({
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="flex flex-col gap-4 p-8 h-full">
|
||||
<span className="text-sm text-center opacity-60 font-medium">
|
||||
Step {uiCurrentStep} of {uiTotalSteps}
|
||||
</span>
|
||||
{renderProgress()}
|
||||
{!showSuccess && (
|
||||
<>
|
||||
<span className="text-sm text-center opacity-60 font-medium">
|
||||
Step {uiCurrentStep} of {uiTotalSteps}
|
||||
</span>
|
||||
{renderProgress()}
|
||||
</>
|
||||
)}
|
||||
{renderStepContent()}
|
||||
<div className="flex items-end justify-center gap-2.5 mt-10 grow">
|
||||
{step === 2 ? (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleBack}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
) : null}
|
||||
{step === 3 && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleClose}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Skip for now
|
||||
</Button>
|
||||
)}
|
||||
{step === 4 ? (
|
||||
<Button
|
||||
icon={faArrowRight}
|
||||
iconFlip={true}
|
||||
onClick={handleClose}
|
||||
>
|
||||
Start Creating Now
|
||||
</Button>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
icon={step === 3 ? faRightToBracket : faArrowRight}
|
||||
iconFlip={step !== 3}
|
||||
onClick={handleNext}
|
||||
loading={isLoading}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{step === 1
|
||||
? "Continue"
|
||||
: step === 2
|
||||
? isSignUp
|
||||
? "Sign up"
|
||||
: "Login"
|
||||
: "Login with OpenAI"}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{renderFooterButtons()}
|
||||
</div>
|
||||
</div>
|
||||
</TransitionChild>
|
||||
|
||||
+2
-3
@@ -5,6 +5,7 @@ import { SoraAccountButton } from "./SoraAccountButton";
|
||||
import { SoraAccountBlock } from "./SoraAccountBlock";
|
||||
import { ArtcraftAccountBlock } from "./ArtcraftAccountBlock";
|
||||
import { FalApiKeyBlock } from "./FalApiKeyBlock";
|
||||
import { OpenAIApiKeyBlock } from "./OpenAIKeyBlock";
|
||||
|
||||
interface AccountInfo {
|
||||
username: string;
|
||||
@@ -57,11 +58,9 @@ export const AccountSettingsPane = ({
|
||||
*/}
|
||||
|
||||
<ArtcraftAccountBlock globalAccountLogoutCallback={globalAccountLogoutCallback}/>
|
||||
|
||||
<SoraAccountBlock />
|
||||
|
||||
<FalApiKeyBlock />
|
||||
|
||||
<OpenAIApiKeyBlock/>
|
||||
{/*
|
||||
<hr />
|
||||
|
||||
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { GetOpenAIApiKey, SetOpenAIApiKey } from "@storyteller/tauri-api";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
|
||||
|
||||
export const OpenAIApiKeyBlock = () => {
|
||||
const [openAIApiKey, setOpenAIApiKey] = useState<string | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const [isCheckingOpenAIApiKey, setIsCheckingOpenAIApiKey] =
|
||||
useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchKey = async () => {
|
||||
setIsCheckingOpenAIApiKey(true);
|
||||
try {
|
||||
const result = await GetOpenAIApiKey();
|
||||
if ("payload" in result) {
|
||||
setOpenAIApiKey(result.payload.key);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error fetching Fal API key", e);
|
||||
setOpenAIApiKey(undefined);
|
||||
} finally {
|
||||
setIsCheckingOpenAIApiKey(false);
|
||||
}
|
||||
};
|
||||
fetchKey();
|
||||
}, []);
|
||||
|
||||
const syncOpenAIApiKey = async (key: string) => {
|
||||
await SetOpenAIApiKey(key);
|
||||
setOpenAIApiKey(key);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex justify-between items-center">
|
||||
<span>Open AI API Key:</span>
|
||||
<Input
|
||||
value={openAIApiKey}
|
||||
onChange={(e) => syncOpenAIApiKey(e.target.value)}
|
||||
placeholder="Enter Open AI API key"
|
||||
/>
|
||||
|
||||
{/*<Button
|
||||
variant={
|
||||
isCheckingArtcraftSession
|
||||
? "secondary"
|
||||
: isLoggedIn
|
||||
? "destructive"
|
||||
: "primary"
|
||||
}
|
||||
className="h-[30px]"
|
||||
onClick={handleArtcraftButton}
|
||||
disabled={isCheckingArtcraftSession}
|
||||
>
|
||||
{isCheckingArtcraftSession ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faSpinnerThird}
|
||||
className="animate-spin text-sm"
|
||||
/>
|
||||
) : isLoggedIn ? (
|
||||
"Log Out"
|
||||
) : (
|
||||
"Log In"
|
||||
)}
|
||||
</Button>*/}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -6,6 +6,8 @@ export * from "./lib/fal/FalHunyuanImageTo3d.js";
|
||||
export * from "./lib/fal/FalKlingImageToVideo.js";
|
||||
export * from "./lib/fal/GetFalApiKey.js";
|
||||
export * from "./lib/fal/SetFalApiKey.js";
|
||||
export * from "./lib/openai/SetOpenAIApiKey.js";
|
||||
export * from "./lib/openai/GetOpenAIApiKey.js";
|
||||
export * from "./lib/settings/GetAppPreferences.js";
|
||||
export * from "./lib/settings/UpdateAppPreference.js";
|
||||
export * from "./lib/sora/CheckSoraSession.js";
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { CommandResult } from "../common/CommandStatus";
|
||||
|
||||
export interface GetOpenAIApiKeySuccessResult extends CommandResult {
|
||||
payload: {
|
||||
key: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type GetOpenAIApiKeyResult = GetOpenAIApiKeySuccessResult | CommandResult;
|
||||
|
||||
export const GetOpenAIApiKey = async (): Promise<GetOpenAIApiKeyResult> => {
|
||||
const result = await invoke("get_openai_api_key_command");
|
||||
return result as GetOpenAIApiKeyResult;
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { CommandResult } from "../common/CommandStatus";
|
||||
|
||||
export const SetOpenAIApiKey = async (key: string): Promise<CommandResult> => {
|
||||
const result = await invoke("set_openai_api_key_command", {
|
||||
request: {
|
||||
key: key,
|
||||
}
|
||||
});
|
||||
return result as CommandResult;
|
||||
};
|
||||
Reference in New Issue
Block a user