Merge branch 'main' into pr/115
This commit is contained in:
commit
ea10e191bb
@ -1,4 +1,4 @@
|
|||||||
### Getting an OpenAI API key
|
### Getting an OpenAI API key with GPT4-Vision model access
|
||||||
|
|
||||||
You don't need a ChatGPT Pro account. Screenshot to code uses API keys from your OpenAI developer account. In order to get access to the GPT4 Vision model, log into your OpenAI account and then, follow these instructions:
|
You don't need a ChatGPT Pro account. Screenshot to code uses API keys from your OpenAI developer account. In order to get access to the GPT4 Vision model, log into your OpenAI account and then, follow these instructions:
|
||||||
|
|
||||||
@ -10,6 +10,7 @@ You don't need a ChatGPT Pro account. Screenshot to code uses API keys from your
|
|||||||
|
|
||||||
5. Go to Settings > Limits and check at the bottom of the page, your current tier has to be "Tier 1" to have GPT4 access
|
5. Go to Settings > Limits and check at the bottom of the page, your current tier has to be "Tier 1" to have GPT4 access
|
||||||
<img width="785" alt="285636973-da38bd4d-8a78-4904-8027-ca67d729b933" src="https://github.com/abi/screenshot-to-code/assets/23818/8d07cd84-0cf9-4f88-bc00-80eba492eadf">
|
<img width="785" alt="285636973-da38bd4d-8a78-4904-8027-ca67d729b933" src="https://github.com/abi/screenshot-to-code/assets/23818/8d07cd84-0cf9-4f88-bc00-80eba492eadf">
|
||||||
|
6. Go to Screenshot to code and paste it in the Settings dialog under OpenAI key (gear icon). Your key is only stored in your browser. Never stored on our servers.
|
||||||
|
|
||||||
Some users have also reported that it can take upto 30 minutes after your credit purchase for the GPT4 vision model to be activated.
|
Some users have also reported that it can take upto 30 minutes after your credit purchase for the GPT4 vision model to be activated.
|
||||||
|
|
||||||
|
|||||||
27
backend/access_token.py
Normal file
27
backend/access_token.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
|
||||||
|
async def validate_access_token(access_code: str):
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
url = (
|
||||||
|
"https://backend.buildpicoapps.com/screenshot_to_code/validate_access_token"
|
||||||
|
)
|
||||||
|
data = json.dumps(
|
||||||
|
{
|
||||||
|
"access_code": access_code,
|
||||||
|
"secret": os.environ.get("PICO_BACKEND_SECRET"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
|
||||||
|
response = await client.post(url, content=data, headers=headers)
|
||||||
|
response_data = response.json()
|
||||||
|
|
||||||
|
if response_data["success"]:
|
||||||
|
print("Access token is valid.")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"Access token validation failed: {response_data['failure_reason']}")
|
||||||
|
return False
|
||||||
@ -1,6 +1,5 @@
|
|||||||
# Load environment variables first
|
# Load environment variables first
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
@ -16,6 +15,7 @@ from mock import mock_completion
|
|||||||
from image_generation import create_alt_url_mapping, generate_images
|
from image_generation import create_alt_url_mapping, generate_images
|
||||||
from prompts import assemble_prompt
|
from prompts import assemble_prompt
|
||||||
from routes import screenshot
|
from routes import screenshot
|
||||||
|
from access_token import validate_access_token
|
||||||
|
|
||||||
app = FastAPI(openapi_url=None, docs_url=None, redoc_url=None)
|
app = FastAPI(openapi_url=None, docs_url=None, redoc_url=None)
|
||||||
|
|
||||||
@ -79,13 +79,27 @@ async def stream_code(websocket: WebSocket):
|
|||||||
|
|
||||||
# Get the OpenAI API key from the request. Fall back to environment variable if not provided.
|
# Get the OpenAI API key from the request. Fall back to environment variable if not provided.
|
||||||
# If neither is provided, we throw an error.
|
# If neither is provided, we throw an error.
|
||||||
if params["openAiApiKey"]:
|
openai_api_key = None
|
||||||
openai_api_key = params["openAiApiKey"]
|
if "accessCode" in params and params["accessCode"]:
|
||||||
print("Using OpenAI API key from client-side settings dialog")
|
print("Access code - using platform API key")
|
||||||
|
if await validate_access_token(params["accessCode"]):
|
||||||
|
openai_api_key = os.environ.get("PLATFORM_OPENAI_API_KEY")
|
||||||
|
else:
|
||||||
|
await websocket.send_json(
|
||||||
|
{
|
||||||
|
"type": "error",
|
||||||
|
"value": "Invalid access code or you're out of credits. Please try again.",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
openai_api_key = os.environ.get("OPENAI_API_KEY")
|
if params["openAiApiKey"]:
|
||||||
if openai_api_key:
|
openai_api_key = params["openAiApiKey"]
|
||||||
print("Using OpenAI API key from environment variable")
|
print("Using OpenAI API key from client-side settings dialog")
|
||||||
|
else:
|
||||||
|
openai_api_key = os.environ.get("OPENAI_API_KEY")
|
||||||
|
if openai_api_key:
|
||||||
|
print("Using OpenAI API key from environment variable")
|
||||||
|
|
||||||
if not openai_api_key:
|
if not openai_api_key:
|
||||||
print("OpenAI API key not found")
|
print("OpenAI API key not found")
|
||||||
|
|||||||
@ -21,6 +21,34 @@
|
|||||||
<%- injectHead %>
|
<%- injectHead %>
|
||||||
|
|
||||||
<title>Screenshot to Code</title>
|
<title>Screenshot to Code</title>
|
||||||
|
|
||||||
|
<!-- Open Graph Meta Tags -->
|
||||||
|
<meta property="og:title" content="Screenshot to Code" />
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="Convert any screenshot or design to clean code"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:image"
|
||||||
|
content="https://screenshottocode.com/brand/twitter-summary-card.png"
|
||||||
|
/>
|
||||||
|
<meta property="og:image:width" content="1200" />
|
||||||
|
<meta property="og:image:height" content="628" />
|
||||||
|
<meta property="og:url" content="https://screenshottocode.com" />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<!-- Twitter Card tags -->
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:site" content="@picoapps" />
|
||||||
|
<!-- Keep in sync with og:title, og:description and og:image -->
|
||||||
|
<meta name="twitter:title" content="Screenshot to Code" />
|
||||||
|
<meta
|
||||||
|
name="twitter:description"
|
||||||
|
content="Convert any screenshot or design to clean code"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="twitter:image"
|
||||||
|
content="https://screenshottocode.com/brand/twitter-summary-card.png"
|
||||||
|
/>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
"@radix-ui/react-dialog": "^1.0.5",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
"@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-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",
|
||||||
|
|||||||
BIN
frontend/public/brand/twitter-summary-card.png
Normal file
BIN
frontend/public/brand/twitter-summary-card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 330 KiB |
@ -51,12 +51,13 @@ function App() {
|
|||||||
isImageGenerationEnabled: true,
|
isImageGenerationEnabled: true,
|
||||||
editorTheme: EditorTheme.COBALT,
|
editorTheme: EditorTheme.COBALT,
|
||||||
isTermOfServiceAccepted: false,
|
isTermOfServiceAccepted: false,
|
||||||
|
accessCode: null,
|
||||||
},
|
},
|
||||||
"setting"
|
"setting"
|
||||||
);
|
);
|
||||||
const [outputSettings, setOutputSettings] = useState<OutputSettings>({
|
const [outputSettings, setOutputSettings] = useState<OutputSettings>({
|
||||||
css: CSSOption.TAILWIND,
|
css: CSSOption.TAILWIND,
|
||||||
js: JSFrameworkOption.VANILLA,
|
js: JSFrameworkOption.NO_FRAMEWORK,
|
||||||
});
|
});
|
||||||
const [shouldIncludeResultImage, setShouldIncludeResultImage] =
|
const [shouldIncludeResultImage, setShouldIncludeResultImage] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
@ -170,8 +171,8 @@ function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-2 dark:bg-black dark:text-white">
|
<div className="mt-2">
|
||||||
{IS_RUNNING_ON_CLOUD && <PicoBadge />}
|
{IS_RUNNING_ON_CLOUD && <PicoBadge settings={settings} />}
|
||||||
{IS_RUNNING_ON_CLOUD && (
|
{IS_RUNNING_ON_CLOUD && (
|
||||||
<TermsOfServiceDialog
|
<TermsOfServiceDialog
|
||||||
open={!settings.isTermOfServiceAccepted}
|
open={!settings.isTermOfServiceAccepted}
|
||||||
@ -180,24 +181,23 @@ function App() {
|
|||||||
)}
|
)}
|
||||||
<div className="lg:fixed lg:inset-y-0 lg:z-40 lg:flex lg:w-96 lg:flex-col">
|
<div className="lg:fixed lg:inset-y-0 lg:z-40 lg:flex lg:w-96 lg:flex-col">
|
||||||
<div className="flex grow flex-col gap-y-2 overflow-y-auto border-r border-gray-200 bg-white px-6 dark:bg-zinc-950 dark:text-white">
|
<div className="flex grow flex-col gap-y-2 overflow-y-auto border-r border-gray-200 bg-white px-6 dark:bg-zinc-950 dark:text-white">
|
||||||
<div className="flex items-center justify-between mt-10">
|
<div className="flex items-center justify-between mt-10 mb-2">
|
||||||
<h1 className="text-2xl ">Screenshot to Code</h1>
|
<h1 className="text-2xl ">Screenshot to Code</h1>
|
||||||
<SettingsDialog settings={settings} setSettings={setSettings} />
|
<SettingsDialog settings={settings} setSettings={setSettings} />
|
||||||
</div>
|
</div>
|
||||||
{appState === AppState.INITIAL && (
|
|
||||||
<h2 className="text-sm text-gray-500 mb-2">
|
|
||||||
Drag & drop a screenshot to get started.
|
|
||||||
</h2>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{appState === AppState.INITIAL && (
|
<OutputSettingsSection
|
||||||
<OutputSettingsSection
|
outputSettings={outputSettings}
|
||||||
outputSettings={outputSettings}
|
setOutputSettings={setOutputSettings}
|
||||||
setOutputSettings={setOutputSettings}
|
shouldDisableUpdates={
|
||||||
/>
|
appState === AppState.CODING || appState === AppState.CODE_READY
|
||||||
)}
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
{IS_RUNNING_ON_CLOUD && !settings.openAiApiKey && <OnboardingNote />}
|
{IS_RUNNING_ON_CLOUD &&
|
||||||
|
!(settings.openAiApiKey || settings.accessCode) && (
|
||||||
|
<OnboardingNote />
|
||||||
|
)}
|
||||||
|
|
||||||
{(appState === AppState.CODING ||
|
{(appState === AppState.CODING ||
|
||||||
appState === AppState.CODE_READY) && (
|
appState === AppState.CODE_READY) && (
|
||||||
@ -210,7 +210,10 @@ function App() {
|
|||||||
{executionConsole.slice(-1)[0]}
|
{executionConsole.slice(-1)[0]}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex mt-4 w-full">
|
<div className="flex mt-4 w-full">
|
||||||
<Button onClick={stop} className="w-full dark:text-white dark:bg-gray-700">
|
<Button
|
||||||
|
onClick={stop}
|
||||||
|
className="w-full dark:text-white dark:bg-gray-700"
|
||||||
|
>
|
||||||
Stop
|
Stop
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,8 +2,15 @@ export function OnboardingNote() {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-4 bg-green-700 p-2 rounded text-stone-200 text-sm">
|
<div className="flex flex-col space-y-4 bg-green-700 p-2 rounded text-stone-200 text-sm">
|
||||||
<span>
|
<span>
|
||||||
To use Screenshot to Code, you need an OpenAI API key with GPT4 vision
|
To use Screenshot to Code,{" "}
|
||||||
access.{" "}
|
<a
|
||||||
|
className="inline underline hover:opacity-70"
|
||||||
|
href="https://buy.stripe.com/8wM6sre70gBW1nqaEE"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
buy some credits (100 generations for $36)
|
||||||
|
</a>{" "}
|
||||||
|
or use your own OpenAI API key with GPT4 vision access.{" "}
|
||||||
<a
|
<a
|
||||||
href="https://github.com/abi/screenshot-to-code/blob/main/Troubleshooting.md"
|
href="https://github.com/abi/screenshot-to-code/blob/main/Troubleshooting.md"
|
||||||
className="inline underline hover:opacity-70"
|
className="inline underline hover:opacity-70"
|
||||||
@ -11,18 +18,8 @@ export function OnboardingNote() {
|
|||||||
>
|
>
|
||||||
Follow these instructions to get yourself a key.
|
Follow these instructions to get yourself a key.
|
||||||
</a>{" "}
|
</a>{" "}
|
||||||
Then, paste it in the Settings dialog (gear icon above).
|
and paste it in the Settings dialog (gear icon above). Your key is only
|
||||||
</span>
|
stored in your browser. Never stored on our servers.
|
||||||
<span>
|
|
||||||
Your key is only stored in your browser. Never stored on our servers. If
|
|
||||||
you prefer, you can also run this app completely locally.{" "}
|
|
||||||
<a
|
|
||||||
href="https://github.com/abi/screenshot-to-code"
|
|
||||||
className="inline underline hover:opacity-70"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
See the Github project for instructions.
|
|
||||||
</a>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -6,14 +6,10 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
} from "./ui/select";
|
} from "./ui/select";
|
||||||
import { CSSOption, JSFrameworkOption, OutputSettings } from "../types";
|
import { CSSOption, JSFrameworkOption, OutputSettings } from "../types";
|
||||||
import {
|
|
||||||
Accordion,
|
|
||||||
AccordionContent,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionTrigger,
|
|
||||||
} from "./ui/accordion";
|
|
||||||
import { capitalize } from "../lib/utils";
|
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
|
import { Label } from "@radix-ui/react-label";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
import { Popover, PopoverTrigger, PopoverContent } from "./ui/popover";
|
||||||
|
|
||||||
function displayCSSOption(option: CSSOption) {
|
function displayCSSOption(option: CSSOption) {
|
||||||
switch (option) {
|
switch (option) {
|
||||||
@ -26,6 +22,17 @@ function displayCSSOption(option: CSSOption) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function displayJSOption(option: JSFrameworkOption) {
|
||||||
|
switch (option) {
|
||||||
|
case JSFrameworkOption.REACT:
|
||||||
|
return "React";
|
||||||
|
case JSFrameworkOption.NO_FRAMEWORK:
|
||||||
|
return "No Framework";
|
||||||
|
default:
|
||||||
|
return option;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function convertStringToCSSOption(option: string) {
|
function convertStringToCSSOption(option: string) {
|
||||||
switch (option) {
|
switch (option) {
|
||||||
case "tailwind":
|
case "tailwind":
|
||||||
@ -37,18 +44,57 @@ function convertStringToCSSOption(option: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateDisplayString(settings: OutputSettings) {
|
||||||
|
if (
|
||||||
|
settings.js === JSFrameworkOption.REACT &&
|
||||||
|
settings.css === CSSOption.TAILWIND
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Generating <span className="font-bold">React</span> +{" "}
|
||||||
|
<span className="font-bold">Tailwind</span> code
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
settings.js === JSFrameworkOption.NO_FRAMEWORK &&
|
||||||
|
settings.css === CSSOption.TAILWIND
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div className="text-gray-800">
|
||||||
|
Generating <span className="font-bold">HTML</span> +{" "}
|
||||||
|
<span className="font-bold">Tailwind</span> code
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
settings.js === JSFrameworkOption.NO_FRAMEWORK &&
|
||||||
|
settings.css === CSSOption.BOOTSTRAP
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Generating <span className="font-bold">HTML</span> +{" "}
|
||||||
|
<span className="font-bold">Bootstrap</span> code
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
outputSettings: OutputSettings;
|
outputSettings: OutputSettings;
|
||||||
setOutputSettings: React.Dispatch<React.SetStateAction<OutputSettings>>;
|
setOutputSettings: React.Dispatch<React.SetStateAction<OutputSettings>>;
|
||||||
|
shouldDisableUpdates?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) {
|
function OutputSettingsSection({
|
||||||
|
outputSettings,
|
||||||
|
setOutputSettings,
|
||||||
|
shouldDisableUpdates = false,
|
||||||
|
}: Props) {
|
||||||
const onCSSValueChange = (value: string) => {
|
const onCSSValueChange = (value: string) => {
|
||||||
setOutputSettings((prev) => {
|
setOutputSettings((prev) => {
|
||||||
if (prev.js === JSFrameworkOption.REACT) {
|
if (prev.js === JSFrameworkOption.REACT) {
|
||||||
if (value !== CSSOption.TAILWIND) {
|
if (value !== CSSOption.TAILWIND) {
|
||||||
toast.error(
|
toast.error(
|
||||||
"React only supports Tailwind CSS. Change JS framework to Vanilla to use Bootstrap."
|
'React only supports Tailwind CSS. Change JS framework to "No Framework" to use Bootstrap.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@ -79,48 +125,76 @@ function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion type="single" collapsible className="w-full">
|
<div className="flex flex-col gap-y-2 justify-between text-sm">
|
||||||
<AccordionItem value="item-1">
|
{generateDisplayString(outputSettings)}{" "}
|
||||||
<AccordionTrigger>
|
{!shouldDisableUpdates && (
|
||||||
<div className="flex gap-x-2">Output Settings </div>
|
<Popover>
|
||||||
</AccordionTrigger>
|
<PopoverTrigger asChild>
|
||||||
<AccordionContent className="gap-y-2 flex flex-col pt-2">
|
<Button variant="outline">Customize</Button>
|
||||||
<div className="flex justify-between items-center pr-2">
|
</PopoverTrigger>
|
||||||
<span className="text-sm">CSS</span>
|
<PopoverContent className="w-80 text-sm">
|
||||||
<Select value={outputSettings.css} onValueChange={onCSSValueChange}>
|
<div className="grid gap-4">
|
||||||
<SelectTrigger className="w-[180px]">
|
<div className="space-y-2">
|
||||||
{displayCSSOption(outputSettings.css)}
|
<h4 className="font-medium leading-none">Code Settings</h4>
|
||||||
</SelectTrigger>
|
<p className="text-muted-foreground">
|
||||||
<SelectContent>
|
Customize your code output
|
||||||
<SelectGroup>
|
</p>
|
||||||
<SelectItem value={CSSOption.TAILWIND}>Tailwind</SelectItem>
|
</div>
|
||||||
<SelectItem value={CSSOption.BOOTSTRAP}>Bootstrap</SelectItem>
|
<div className="grid gap-2">
|
||||||
</SelectGroup>
|
<div className="grid grid-cols-3 items-center gap-4">
|
||||||
</SelectContent>
|
<Label htmlFor="output-settings-js">JS</Label>
|
||||||
</Select>
|
<Select
|
||||||
</div>
|
value={outputSettings.js}
|
||||||
<div className="flex justify-between items-center pr-2">
|
onValueChange={onJsFrameworkChange}
|
||||||
<span className="text-sm">JS Framework</span>
|
>
|
||||||
<Select
|
<SelectTrigger
|
||||||
value={outputSettings.js}
|
className="col-span-2 h-8"
|
||||||
onValueChange={onJsFrameworkChange}
|
id="output-settings-js"
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-[180px]">
|
{displayJSOption(outputSettings.js)}
|
||||||
{capitalize(outputSettings.js)}
|
</SelectTrigger>
|
||||||
</SelectTrigger>
|
<SelectContent>
|
||||||
<SelectContent>
|
<SelectGroup>
|
||||||
<SelectGroup>
|
<SelectItem value={JSFrameworkOption.NO_FRAMEWORK}>
|
||||||
<SelectItem value={JSFrameworkOption.VANILLA}>
|
No Framework
|
||||||
Vanilla
|
</SelectItem>
|
||||||
</SelectItem>
|
<SelectItem value={JSFrameworkOption.REACT}>
|
||||||
<SelectItem value={JSFrameworkOption.REACT}>React</SelectItem>
|
React
|
||||||
</SelectGroup>
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectGroup>
|
||||||
</Select>
|
</SelectContent>
|
||||||
</div>
|
</Select>
|
||||||
</AccordionContent>
|
</div>
|
||||||
</AccordionItem>
|
<div className="grid grid-cols-3 items-center gap-4">
|
||||||
</Accordion>
|
<Label htmlFor="output-settings-css">CSS</Label>
|
||||||
|
<Select
|
||||||
|
value={outputSettings.css}
|
||||||
|
onValueChange={onCSSValueChange}
|
||||||
|
>
|
||||||
|
<SelectTrigger
|
||||||
|
className="col-span-2 h-8"
|
||||||
|
id="output-settings-css"
|
||||||
|
>
|
||||||
|
{displayCSSOption(outputSettings.css)}
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectGroup>
|
||||||
|
<SelectItem value={CSSOption.TAILWIND}>
|
||||||
|
Tailwind
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value={CSSOption.BOOTSTRAP}>
|
||||||
|
Bootstrap
|
||||||
|
</SelectItem>
|
||||||
|
</SelectGroup>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
export function PicoBadge() {
|
import { Settings } from "../types";
|
||||||
|
|
||||||
|
export function PicoBadge({ settings }: { settings: Settings }) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<a
|
<a
|
||||||
@ -12,14 +14,26 @@ export function PicoBadge() {
|
|||||||
feature requests?
|
feature requests?
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://picoapps.xyz?ref=screenshot-to-code" target="_blank">
|
{!settings.accessCode && (
|
||||||
<div
|
<a href="https://picoapps.xyz?ref=screenshot-to-code" target="_blank">
|
||||||
className="fixed z-50 bottom-5 right-5 rounded-md shadow text-black
|
<div
|
||||||
|
className="fixed z-50 bottom-5 right-5 rounded-md shadow text-black
|
||||||
bg-white px-4 text-xs py-3 cursor-pointer"
|
bg-white px-4 text-xs py-3 cursor-pointer"
|
||||||
>
|
>
|
||||||
an open source project by Pico
|
an open source project by Pico
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
)}
|
||||||
|
{settings.accessCode && (
|
||||||
|
<a href="mailto:support@picoapps.xyz" target="_blank">
|
||||||
|
<div
|
||||||
|
className="fixed z-50 bottom-5 right-5 rounded-md shadow text-black
|
||||||
|
bg-white px-4 text-xs py-3 cursor-pointer"
|
||||||
|
>
|
||||||
|
email support
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { Label } from "./ui/label";
|
|||||||
import { Input } from "./ui/input";
|
import { Input } from "./ui/input";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger } from "./ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger } from "./ui/select";
|
||||||
import { capitalize } from "../lib/utils";
|
import { capitalize } from "../lib/utils";
|
||||||
|
import { IS_RUNNING_ON_CLOUD } from "../config";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
@ -38,6 +39,31 @@ function SettingsDialog({ settings, setSettings }: Props) {
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="mb-4">Settings</DialogTitle>
|
<DialogTitle className="mb-4">Settings</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
|
{/* Access code */}
|
||||||
|
{IS_RUNNING_ON_CLOUD && (
|
||||||
|
<div className="flex flex-col space-y-4 bg-slate-300 p-4 rounded">
|
||||||
|
<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"
|
||||||
|
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">
|
||||||
<Label htmlFor="image-generation">
|
<Label htmlFor="image-generation">
|
||||||
<div>DALL-E Placeholder Image Generation</div>
|
<div>DALL-E Placeholder Image Generation</div>
|
||||||
|
|||||||
29
frontend/src/components/ui/popover.tsx
Normal file
29
frontend/src/components/ui/popover.tsx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Popover = PopoverPrimitive.Root
|
||||||
|
|
||||||
|
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||||
|
|
||||||
|
const PopoverContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||||
|
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||||
|
<PopoverPrimitive.Portal>
|
||||||
|
<PopoverPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
align={align}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</PopoverPrimitive.Portal>
|
||||||
|
))
|
||||||
|
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Popover, PopoverTrigger, PopoverContent }
|
||||||
@ -9,7 +9,7 @@ export enum CSSOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export enum JSFrameworkOption {
|
export enum JSFrameworkOption {
|
||||||
VANILLA = "vanilla",
|
NO_FRAMEWORK = "vanilla",
|
||||||
REACT = "react",
|
REACT = "react",
|
||||||
VUE = "vue",
|
VUE = "vue",
|
||||||
}
|
}
|
||||||
@ -25,6 +25,7 @@ export interface Settings {
|
|||||||
isImageGenerationEnabled: boolean;
|
isImageGenerationEnabled: boolean;
|
||||||
editorTheme: EditorTheme;
|
editorTheme: EditorTheme;
|
||||||
isTermOfServiceAccepted: boolean; // Only relevant for hosted version
|
isTermOfServiceAccepted: boolean; // Only relevant for hosted version
|
||||||
|
accessCode: string | null; // Only relevant for hosted version
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AppState {
|
export enum AppState {
|
||||||
|
|||||||
@ -805,6 +805,28 @@
|
|||||||
"@babel/runtime" "^7.13.10"
|
"@babel/runtime" "^7.13.10"
|
||||||
"@radix-ui/react-primitive" "1.0.3"
|
"@radix-ui/react-primitive" "1.0.3"
|
||||||
|
|
||||||
|
"@radix-ui/react-popover@^1.0.7":
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.0.7.tgz#23eb7e3327330cb75ec7b4092d685398c1654e3c"
|
||||||
|
integrity sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.13.10"
|
||||||
|
"@radix-ui/primitive" "1.0.1"
|
||||||
|
"@radix-ui/react-compose-refs" "1.0.1"
|
||||||
|
"@radix-ui/react-context" "1.0.1"
|
||||||
|
"@radix-ui/react-dismissable-layer" "1.0.5"
|
||||||
|
"@radix-ui/react-focus-guards" "1.0.1"
|
||||||
|
"@radix-ui/react-focus-scope" "1.0.4"
|
||||||
|
"@radix-ui/react-id" "1.0.1"
|
||||||
|
"@radix-ui/react-popper" "1.1.3"
|
||||||
|
"@radix-ui/react-portal" "1.0.4"
|
||||||
|
"@radix-ui/react-presence" "1.0.1"
|
||||||
|
"@radix-ui/react-primitive" "1.0.3"
|
||||||
|
"@radix-ui/react-slot" "1.0.2"
|
||||||
|
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||||
|
aria-hidden "^1.1.1"
|
||||||
|
react-remove-scroll "2.5.5"
|
||||||
|
|
||||||
"@radix-ui/react-popper@1.1.3":
|
"@radix-ui/react-popper@1.1.3":
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42"
|
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user