Merge branch 'main' into hosted
This commit is contained in:
commit
67e1d2b3d3
@ -54,6 +54,10 @@ For debugging purposes, if you don't want to waste GPT4-Vision credits, you can
|
|||||||
MOCK=true poetry run uvicorn main:app --reload --port 7001
|
MOCK=true poetry run uvicorn main:app --reload --port 7001
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
* You can configure the OpenAI base URL if you need to use a proxy: Set OPENAI_BASE_URL in the `backend/.env` or directly in the UI in the settings dialog
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
If you have Docker installed on your system, in the root directory, run:
|
If you have Docker installed on your system, in the root directory, run:
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
"@radix-ui/react-popover": "^1.0.7",
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
|
"@radix-ui/react-progress": "^1.0.3",
|
||||||
"@radix-ui/react-select": "^2.0.0",
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
"@radix-ui/react-separator": "^1.0.3",
|
"@radix-ui/react-separator": "^1.0.3",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import {
|
|||||||
AccordionItem,
|
AccordionItem,
|
||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from "./ui/accordion";
|
} from "./ui/accordion";
|
||||||
|
import AccessCodeSection from "./settings/AccessCodeSection";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
@ -48,26 +49,7 @@ function SettingsDialog({ settings, setSettings }: Props) {
|
|||||||
|
|
||||||
{/* Access code */}
|
{/* Access code */}
|
||||||
{IS_RUNNING_ON_CLOUD && (
|
{IS_RUNNING_ON_CLOUD && (
|
||||||
<div className="flex flex-col space-y-4 bg-slate-300 p-4 rounded dark:text-white dark:bg-slate-800">
|
<AccessCodeSection settings={settings} setSettings={setSettings} />
|
||||||
<Label htmlFor="access-code">
|
|
||||||
<div>Access Code</div>
|
|
||||||
<div className="font-light mt-1 leading-relaxed">
|
|
||||||
Buy an access code.
|
|
||||||
</div>
|
|
||||||
</Label>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
id="access-code dark:border-gray-700 dark:bg-gray-800 dark:text-white"
|
|
||||||
placeholder="Enter your Screenshot to Code access code"
|
|
||||||
value={settings.accessCode || ""}
|
|
||||||
onChange={(e) =>
|
|
||||||
setSettings((s) => ({
|
|
||||||
...s,
|
|
||||||
accessCode: e.target.value,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
|
|||||||
142
frontend/src/components/settings/AccessCodeSection.tsx
Normal file
142
frontend/src/components/settings/AccessCodeSection.tsx
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Settings } from "../../types";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { Input } from "../ui/input";
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import useThrottle from "../../hooks/useThrottle";
|
||||||
|
import { Progress } from "../ui/progress";
|
||||||
|
import { PICO_BACKEND_FORM_SECRET } from "../../config";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
settings: Settings;
|
||||||
|
setSettings: React.Dispatch<React.SetStateAction<Settings>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UsageResponse {
|
||||||
|
used_credits: number;
|
||||||
|
total_credits: number;
|
||||||
|
is_valid: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FetchState {
|
||||||
|
EMPTY = "EMPTY",
|
||||||
|
LOADING = "LOADING",
|
||||||
|
INVALID = "INVALID",
|
||||||
|
VALID = "VALID",
|
||||||
|
}
|
||||||
|
|
||||||
|
function AccessCodeSection({ settings, setSettings }: Props) {
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [isValid, setIsValid] = useState(false);
|
||||||
|
const [usedCredits, setUsedCredits] = useState(0);
|
||||||
|
const [totalCredits, setTotalCredits] = useState(0);
|
||||||
|
const throttledAccessCode = useThrottle(settings.accessCode || "", 500);
|
||||||
|
|
||||||
|
const fetchState = (() => {
|
||||||
|
if (!settings.accessCode) return FetchState.EMPTY;
|
||||||
|
if (isLoading) return FetchState.LOADING;
|
||||||
|
if (!isValid) return FetchState.INVALID;
|
||||||
|
return FetchState.VALID;
|
||||||
|
})();
|
||||||
|
|
||||||
|
async function fetchUsage(accessCode: string) {
|
||||||
|
const res = await fetch(
|
||||||
|
"https://backend.buildpicoapps.com/screenshot_to_code/get_access_code_usage",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
access_code: accessCode,
|
||||||
|
secret: PICO_BACKEND_FORM_SECRET,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const usage = (await res.json()) as UsageResponse;
|
||||||
|
|
||||||
|
if (!usage.is_valid) {
|
||||||
|
setIsValid(false);
|
||||||
|
} else {
|
||||||
|
setIsValid(true);
|
||||||
|
setUsedCredits(usage.used_credits);
|
||||||
|
setTotalCredits(usage.total_credits);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Don't do anything if access code is empty
|
||||||
|
if (!throttledAccessCode) return;
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
setIsValid(true);
|
||||||
|
|
||||||
|
// Wait for 500 ms before fetching usage
|
||||||
|
setTimeout(async () => {
|
||||||
|
await fetchUsage(throttledAccessCode);
|
||||||
|
}, 500);
|
||||||
|
}, [throttledAccessCode]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col space-y-4 bg-slate-200 p-4 rounded dark:text-white dark:bg-slate-800">
|
||||||
|
<Label htmlFor="access-code">
|
||||||
|
<div>Access Code</div>
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
id="access-code"
|
||||||
|
className="border-gray-700 dark:border-gray-700 dark:bg-gray-800 dark:text-white"
|
||||||
|
placeholder="Enter your Screenshot to Code access code"
|
||||||
|
value={settings.accessCode || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
setSettings((s) => ({
|
||||||
|
...s,
|
||||||
|
accessCode: e.target.value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{fetchState === "EMPTY" && (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<a href="https://buy.stripe.com/8wM6sre70gBW1nqaEE" target="_blank">
|
||||||
|
<Button size="sm" variant="secondary">
|
||||||
|
Buy credits
|
||||||
|
</Button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{fetchState === "LOADING" && (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-xs text-gray-700">Loading...</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{fetchState === "INVALID" && (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-xs text-gray-700">Invalid access code</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{fetchState === "VALID" && (
|
||||||
|
<>
|
||||||
|
<Progress value={(usedCredits / totalCredits) * 100} />
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<span className="text-xs text-gray-700">
|
||||||
|
{usedCredits} out of {totalCredits} credits used
|
||||||
|
</span>
|
||||||
|
<a href="https://buy.stripe.com/8wM6sre70gBW1nqaEE" target="_blank">
|
||||||
|
<Button size="sm">Add credits</Button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AccessCodeSection;
|
||||||
26
frontend/src/components/ui/progress.tsx
Normal file
26
frontend/src/components/ui/progress.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Progress = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
||||||
|
>(({ className, value, ...props }, ref) => (
|
||||||
|
<ProgressPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ProgressPrimitive.Indicator
|
||||||
|
className="h-full w-full flex-1 bg-primary transition-all"
|
||||||
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||||
|
/>
|
||||||
|
</ProgressPrimitive.Root>
|
||||||
|
))
|
||||||
|
Progress.displayName = ProgressPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Progress }
|
||||||
@ -869,6 +869,15 @@
|
|||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/react-slot" "1.0.2"
|
"@radix-ui/react-slot" "1.0.2"
|
||||||
|
|
||||||
|
"@radix-ui/react-progress@^1.0.3":
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-progress/-/react-progress-1.0.3.tgz#8380272fdc64f15cbf263a294dea70a7d5d9b4fa"
|
||||||
|
integrity sha512-5G6Om/tYSxjSeEdrb1VfKkfZfn/1IlPWd731h2RfPuSbIfNUgfqAwbKfJCg/PP6nuUCTrYzalwHSpSinoWoCag==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/react-context" "1.0.1"
|
||||||
|
"@radix-ui/react-primitive" "1.0.3"
|
||||||
|
|
||||||
"@radix-ui/react-roving-focus@1.0.4":
|
"@radix-ui/react-roving-focus@1.0.4":
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user