redo how output settings is configured
This commit is contained in:
parent
45a64326f6
commit
f51e70d701
@ -69,15 +69,11 @@ async def stream_code(websocket: WebSocket):
|
||||
|
||||
print("Received params")
|
||||
|
||||
# Read the output settings from the request. Fall back to default if not provided.
|
||||
output_settings = {"css": "tailwind", "js": "vanilla" , "components" : "html"}
|
||||
if params["outputSettings"] and params["outputSettings"]["css"]:
|
||||
output_settings["css"] = params["outputSettings"]["css"]
|
||||
if params["outputSettings"] and params["outputSettings"]["js"]:
|
||||
output_settings["js"] = params["outputSettings"]["js"]
|
||||
if params["outputSettings"] and params["outputSettings"]["components"]:
|
||||
output_settings["components"] = params["outputSettings"]["components"]
|
||||
print("Using output settings:", output_settings)
|
||||
# Read the code config settings from the request. Fall back to default if not provided.
|
||||
generated_code_config = ""
|
||||
if "generatedCodeConfig" in params and params["generatedCodeConfig"]:
|
||||
generated_code_config = params["generatedCodeConfig"]
|
||||
print(f"Generating {generated_code_config} code")
|
||||
|
||||
# Get the OpenAI API key from the request. Fall back to environment variable if not provided.
|
||||
# If neither is provided, we throw an error.
|
||||
@ -143,10 +139,10 @@ async def stream_code(websocket: WebSocket):
|
||||
|
||||
if params.get("resultImage") and params["resultImage"]:
|
||||
prompt_messages = assemble_prompt(
|
||||
params["image"], output_settings, params["resultImage"]
|
||||
params["image"], generated_code_config, params["resultImage"]
|
||||
)
|
||||
else:
|
||||
prompt_messages = assemble_prompt(params["image"], output_settings)
|
||||
prompt_messages = assemble_prompt(params["image"], generated_code_config)
|
||||
|
||||
# Image cache for updates so that we don't have to regenerate images
|
||||
image_cache = {}
|
||||
|
||||
@ -116,21 +116,21 @@ Generate code for a web page that looks exactly like this.
|
||||
"""
|
||||
|
||||
|
||||
def assemble_prompt(image_data_url, output_settings: dict, result_image_data_url=None):
|
||||
def assemble_prompt(
|
||||
image_data_url, generated_code_config: str, result_image_data_url=None
|
||||
):
|
||||
# Set the system prompt based on the output settings
|
||||
chosen_prompt_name = "tailwind"
|
||||
system_content = TAILWIND_SYSTEM_PROMPT
|
||||
if output_settings["css"] == "bootstrap":
|
||||
chosen_prompt_name = "bootstrap"
|
||||
system_content = BOOTSTRAP_SYSTEM_PROMPT
|
||||
if output_settings["js"] == "react":
|
||||
chosen_prompt_name = "react-tailwind"
|
||||
if generated_code_config == "html_tailwind":
|
||||
system_content = TAILWIND_SYSTEM_PROMPT
|
||||
elif generated_code_config == "react_tailwind":
|
||||
system_content = REACT_TAILWIND_SYSTEM_PROMPT
|
||||
if output_settings["components"] == "ionic":
|
||||
chosen_prompt_name = "ionic-tailwind"
|
||||
elif generated_code_config == "bootstrap":
|
||||
system_content = BOOTSTRAP_SYSTEM_PROMPT
|
||||
elif generated_code_config == "ionic_tailwind":
|
||||
system_content = IONIC_TAILWIND_SYSTEM_PROMPT
|
||||
|
||||
print("Using system prompt:", chosen_prompt_name)
|
||||
else:
|
||||
raise Exception("Code config is not one of available options")
|
||||
|
||||
user_content = [
|
||||
{
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import ImageUpload from "./components/ImageUpload";
|
||||
import CodePreview from "./components/CodePreview";
|
||||
import Preview from "./components/Preview";
|
||||
@ -18,15 +18,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs";
|
||||
import SettingsDialog from "./components/SettingsDialog";
|
||||
import {
|
||||
Settings,
|
||||
EditorTheme,
|
||||
AppState,
|
||||
CSSOption,
|
||||
OutputSettings,
|
||||
JSFrameworkOption,
|
||||
UIComponentOption,
|
||||
} from "./types";
|
||||
import { Settings, EditorTheme, AppState, GeneratedCodeConfig } from "./types";
|
||||
import { IS_RUNNING_ON_CLOUD } from "./config";
|
||||
import { PicoBadge } from "./components/PicoBadge";
|
||||
import { OnboardingNote } from "./components/OnboardingNote";
|
||||
@ -52,21 +44,31 @@ function App() {
|
||||
screenshotOneApiKey: null,
|
||||
isImageGenerationEnabled: true,
|
||||
editorTheme: EditorTheme.COBALT,
|
||||
generatedCodeConfig: GeneratedCodeConfig.HTML_TAILWIND,
|
||||
// Only relevant for hosted version
|
||||
isTermOfServiceAccepted: false,
|
||||
accessCode: null,
|
||||
},
|
||||
"setting"
|
||||
);
|
||||
const [outputSettings, setOutputSettings] = useState<OutputSettings>({
|
||||
css: CSSOption.TAILWIND,
|
||||
components: UIComponentOption.HTML,
|
||||
js: JSFrameworkOption.NO_FRAMEWORK,
|
||||
});
|
||||
|
||||
const [shouldIncludeResultImage, setShouldIncludeResultImage] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const wsRef = useRef<WebSocket>(null);
|
||||
|
||||
// When the user already has the settings in local storage, newly added keys
|
||||
// do not get added to the settings so if it's falsy, we populate it with the default
|
||||
// value
|
||||
useEffect(() => {
|
||||
if (!settings.generatedCodeConfig) {
|
||||
setSettings((prev) => ({
|
||||
...prev,
|
||||
generatedCodeConfig: GeneratedCodeConfig.HTML_TAILWIND,
|
||||
}));
|
||||
}
|
||||
}, [settings.generatedCodeConfig, setSettings]);
|
||||
|
||||
const takeScreenshot = async (): Promise<string> => {
|
||||
const iframeElement = document.querySelector(
|
||||
"#preview-desktop"
|
||||
@ -116,7 +118,7 @@ function App() {
|
||||
setAppState(AppState.CODING);
|
||||
|
||||
// Merge settings with params
|
||||
const updatedParams = { ...params, ...settings, outputSettings };
|
||||
const updatedParams = { ...params, ...settings };
|
||||
|
||||
generateCode(
|
||||
wsRef,
|
||||
@ -190,8 +192,13 @@ function App() {
|
||||
</div>
|
||||
|
||||
<OutputSettingsSection
|
||||
outputSettings={outputSettings}
|
||||
setOutputSettings={setOutputSettings}
|
||||
generatedCodeConfig={settings.generatedCodeConfig}
|
||||
setGeneratedCodeConfig={(config: GeneratedCodeConfig) =>
|
||||
setSettings((prev) => ({
|
||||
...prev,
|
||||
generatedCodeConfig: config,
|
||||
}))
|
||||
}
|
||||
shouldDisableUpdates={
|
||||
appState === AppState.CODING || appState === AppState.CODE_READY
|
||||
}
|
||||
|
||||
@ -5,324 +5,86 @@ import {
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
} from "./ui/select";
|
||||
import {
|
||||
CSSOption,
|
||||
UIComponentOption,
|
||||
JSFrameworkOption,
|
||||
OutputSettings,
|
||||
} from "../types";
|
||||
import { capitalize } from "../lib/utils";
|
||||
import toast from "react-hot-toast";
|
||||
import { useEffect } from "react";
|
||||
import { Label } from "@radix-ui/react-label";
|
||||
import { Button } from "./ui/button";
|
||||
import { Popover, PopoverTrigger, PopoverContent } from "./ui/popover";
|
||||
import { GeneratedCodeConfig } from "../types";
|
||||
|
||||
function displayCSSOption(option: CSSOption) {
|
||||
switch (option) {
|
||||
case CSSOption.TAILWIND:
|
||||
return "Tailwind";
|
||||
case CSSOption.BOOTSTRAP:
|
||||
return "Bootstrap";
|
||||
default:
|
||||
return option;
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
switch (option) {
|
||||
case "tailwind":
|
||||
return CSSOption.TAILWIND;
|
||||
case "bootstrap":
|
||||
return CSSOption.BOOTSTRAP;
|
||||
default:
|
||||
throw new Error(`Unknown CSS option: ${option}`);
|
||||
}
|
||||
}
|
||||
|
||||
function generateDisplayString(settings: OutputSettings) {
|
||||
if (
|
||||
settings.js === JSFrameworkOption.REACT &&
|
||||
settings.css === CSSOption.TAILWIND
|
||||
) {
|
||||
function generateDisplayComponent(config: GeneratedCodeConfig) {
|
||||
switch (config) {
|
||||
case GeneratedCodeConfig.HTML_TAILWIND:
|
||||
return (
|
||||
<div className="text-gray-800 dark:text-white">
|
||||
Generating <span className="font-bold">React</span> +{" "}
|
||||
<span className="font-bold">Tailwind</span> code
|
||||
<div>
|
||||
<span className="font-semibold">HTML</span> +{" "}
|
||||
<span className="font-semibold">Tailwind</span>
|
||||
</div>
|
||||
);
|
||||
} else if (
|
||||
settings.js === JSFrameworkOption.NO_FRAMEWORK &&
|
||||
settings.css === CSSOption.TAILWIND
|
||||
) {
|
||||
case GeneratedCodeConfig.REACT_TAILWIND:
|
||||
return (
|
||||
<div className="text-gray-800 dark:text-white">
|
||||
Generating <span className="font-bold">HTML</span> +{" "}
|
||||
<span className="font-bold">Tailwind</span> code
|
||||
<div>
|
||||
<span className="font-semibold">React</span> +{" "}
|
||||
<span className="font-semibold">Tailwind</span>
|
||||
</div>
|
||||
);
|
||||
} else if (
|
||||
settings.js === JSFrameworkOption.NO_FRAMEWORK &&
|
||||
settings.css === CSSOption.BOOTSTRAP
|
||||
) {
|
||||
case GeneratedCodeConfig.BOOTSTRAP:
|
||||
return (
|
||||
<div className="text-gray-800 dark:text-white">
|
||||
Generating <span className="font-bold">HTML</span> +{" "}
|
||||
<span className="font-bold">Bootstrap</span> code
|
||||
<div>
|
||||
<span className="font-semibold">Bootstrap</span>
|
||||
</div>
|
||||
);
|
||||
case GeneratedCodeConfig.IONIC_TAILWIND:
|
||||
return (
|
||||
<div>
|
||||
<span className="font-semibold">Ionic</span> +{" "}
|
||||
<span className="font-semibold">Tailwind</span>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
// TODO: Should never reach this out. Error out
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
outputSettings: OutputSettings;
|
||||
setOutputSettings: React.Dispatch<React.SetStateAction<OutputSettings>>;
|
||||
generatedCodeConfig: GeneratedCodeConfig;
|
||||
setGeneratedCodeConfig: (config: GeneratedCodeConfig) => void;
|
||||
shouldDisableUpdates?: boolean;
|
||||
}
|
||||
|
||||
function OutputSettingsSection({
|
||||
outputSettings,
|
||||
setOutputSettings,
|
||||
generatedCodeConfig,
|
||||
setGeneratedCodeConfig,
|
||||
shouldDisableUpdates = false,
|
||||
}: Props) {
|
||||
const onCSSValueChange = (value: string) => {
|
||||
setOutputSettings((prev) => {
|
||||
if (prev.js === JSFrameworkOption.REACT) {
|
||||
if (value !== CSSOption.TAILWIND) {
|
||||
toast.error(
|
||||
'React only supports Tailwind CSS. Change JS framework to "No Framework" to use Bootstrap.'
|
||||
);
|
||||
}
|
||||
return {
|
||||
css: CSSOption.TAILWIND,
|
||||
js: JSFrameworkOption.REACT,
|
||||
components: UIComponentOption.HTML,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...prev,
|
||||
css: convertStringToCSSOption(value),
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onJsFrameworkChange = (value: string) => {
|
||||
if (value === JSFrameworkOption.REACT) {
|
||||
setOutputSettings(() => ({
|
||||
css: CSSOption.TAILWIND,
|
||||
js: value as JSFrameworkOption,
|
||||
components: UIComponentOption.HTML,
|
||||
}));
|
||||
} else {
|
||||
setOutputSettings((prev) => ({
|
||||
...prev,
|
||||
js: value as JSFrameworkOption,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const onUIComponentOptionChange = (value: string) => {
|
||||
if (value === UIComponentOption.IONIC) {
|
||||
setOutputSettings(() => ({
|
||||
css: CSSOption.TAILWIND,
|
||||
js: JSFrameworkOption.NO_FRAMEWORK,
|
||||
components: value as UIComponentOption,
|
||||
}));
|
||||
} else {
|
||||
setOutputSettings((prev) => ({
|
||||
...prev,
|
||||
components: value as UIComponentOption,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const checkUIComponentOptionOrDefault = (
|
||||
valueItem: UIComponentOption
|
||||
): UIComponentOption => {
|
||||
switch (valueItem) {
|
||||
case UIComponentOption.IONIC:
|
||||
if (
|
||||
outputSettings.js != JSFrameworkOption.NO_FRAMEWORK ||
|
||||
outputSettings.css != CSSOption.TAILWIND
|
||||
) {
|
||||
return UIComponentOption.HTML;
|
||||
}
|
||||
}
|
||||
return valueItem;
|
||||
};
|
||||
|
||||
const checkCSSOptionOrDefault = (valueItem: CSSOption): CSSOption => {
|
||||
switch (valueItem) {
|
||||
default:
|
||||
return valueItem;
|
||||
}
|
||||
};
|
||||
|
||||
const checkJSFrameworkOptionOrDefault = (
|
||||
valueItem: JSFrameworkOption
|
||||
): JSFrameworkOption => {
|
||||
switch (valueItem) {
|
||||
case JSFrameworkOption.REACT:
|
||||
if (outputSettings.css != CSSOption.TAILWIND) {
|
||||
return JSFrameworkOption.NO_FRAMEWORK;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return valueItem;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
checkOutputSettingsOptions();
|
||||
}, [outputSettings]);
|
||||
|
||||
const checkOutputSettingsOptions = () => {
|
||||
if (
|
||||
isHiddenOption(outputSettings.css) ||
|
||||
isHiddenOption(outputSettings.js) ||
|
||||
isHiddenOption(outputSettings.components)
|
||||
) {
|
||||
setOutputSettings((prev) => {
|
||||
return {
|
||||
css: checkCSSOptionOrDefault(prev.css),
|
||||
js: checkJSFrameworkOptionOrDefault(prev.js),
|
||||
components: checkUIComponentOptionOrDefault(prev.components),
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const isHiddenOption = (
|
||||
option: CSSOption | JSFrameworkOption | UIComponentOption
|
||||
): boolean => {
|
||||
if (Object.values(CSSOption).includes(option as CSSOption)) {
|
||||
return checkCSSOptionOrDefault(option as CSSOption) != option;
|
||||
}
|
||||
if (
|
||||
Object.values(JSFrameworkOption).includes(option as JSFrameworkOption)
|
||||
) {
|
||||
return (
|
||||
checkJSFrameworkOptionOrDefault(option as JSFrameworkOption) != option
|
||||
);
|
||||
}
|
||||
if (
|
||||
Object.values(UIComponentOption).includes(option as UIComponentOption)
|
||||
) {
|
||||
return (
|
||||
checkUIComponentOptionOrDefault(option as UIComponentOption) != option
|
||||
);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2 justify-between text-sm">
|
||||
{generateDisplayString(outputSettings)}{" "}
|
||||
{!shouldDisableUpdates && (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline">Customize</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 text-sm">
|
||||
<div className="grid gap-4">
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium leading-none">Code Settings</h4>
|
||||
<p className="text-muted-foreground">
|
||||
Customize your code output
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="output-settings-js">JS</Label>
|
||||
<span>Generating:</span>
|
||||
<Select
|
||||
value={outputSettings.js}
|
||||
onValueChange={onJsFrameworkChange}
|
||||
value={generatedCodeConfig}
|
||||
onValueChange={(value: string) =>
|
||||
setGeneratedCodeConfig(value as GeneratedCodeConfig)
|
||||
}
|
||||
disabled={shouldDisableUpdates}
|
||||
>
|
||||
<SelectTrigger
|
||||
className="col-span-2 h-8"
|
||||
id="output-settings-js"
|
||||
>
|
||||
{displayJSOption(outputSettings.js)}
|
||||
<SelectTrigger className="col-span-2" id="output-settings-js">
|
||||
{generateDisplayComponent(generatedCodeConfig)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value={JSFrameworkOption.NO_FRAMEWORK}>
|
||||
No Framework
|
||||
<SelectItem value={GeneratedCodeConfig.HTML_TAILWIND}>
|
||||
{generateDisplayComponent(GeneratedCodeConfig.HTML_TAILWIND)}
|
||||
</SelectItem>
|
||||
<SelectItem value={JSFrameworkOption.REACT}>
|
||||
React
|
||||
<SelectItem value={GeneratedCodeConfig.REACT_TAILWIND}>
|
||||
{generateDisplayComponent(GeneratedCodeConfig.REACT_TAILWIND)}
|
||||
</SelectItem>
|
||||
<SelectItem value={GeneratedCodeConfig.BOOTSTRAP}>
|
||||
{generateDisplayComponent(GeneratedCodeConfig.BOOTSTRAP)}
|
||||
</SelectItem>
|
||||
<SelectItem value={GeneratedCodeConfig.IONIC_TAILWIND}>
|
||||
{generateDisplayComponent(GeneratedCodeConfig.IONIC_TAILWIND)}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<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 className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="output-settings-component">Components</Label>
|
||||
<Select
|
||||
value={outputSettings.components}
|
||||
onValueChange={onUIComponentOptionChange}
|
||||
>
|
||||
<SelectTrigger
|
||||
id="output-settings-component"
|
||||
className="col-span-2 h-8"
|
||||
>
|
||||
{capitalize(outputSettings.components)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value={UIComponentOption.HTML}>
|
||||
HTML
|
||||
</SelectItem>
|
||||
<SelectItem
|
||||
value={UIComponentOption.IONIC}
|
||||
disabled={isHiddenOption(UIComponentOption.IONIC)}
|
||||
>
|
||||
Ionic
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,26 +3,12 @@ export enum EditorTheme {
|
||||
COBALT = "cobalt",
|
||||
}
|
||||
|
||||
export enum CSSOption {
|
||||
TAILWIND = "tailwind",
|
||||
// Keep in sync with backend (prompts.py)
|
||||
export enum GeneratedCodeConfig {
|
||||
HTML_TAILWIND = "html_tailwind",
|
||||
REACT_TAILWIND = "react_tailwind",
|
||||
BOOTSTRAP = "bootstrap",
|
||||
}
|
||||
|
||||
export enum JSFrameworkOption {
|
||||
NO_FRAMEWORK = "vanilla",
|
||||
REACT = "react",
|
||||
VUE = "vue",
|
||||
}
|
||||
|
||||
export enum UIComponentOption {
|
||||
HTML = 'HTML',
|
||||
IONIC = 'ionic'
|
||||
}
|
||||
|
||||
export interface OutputSettings {
|
||||
css: CSSOption;
|
||||
js: JSFrameworkOption;
|
||||
components: UIComponentOption;
|
||||
IONIC_TAILWIND = "ionic_tailwind",
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
@ -31,8 +17,10 @@ export interface Settings {
|
||||
screenshotOneApiKey: string | null;
|
||||
isImageGenerationEnabled: boolean;
|
||||
editorTheme: EditorTheme;
|
||||
isTermOfServiceAccepted: boolean; // Only relevant for hosted version
|
||||
accessCode: string | null; // Only relevant for hosted version
|
||||
generatedCodeConfig: GeneratedCodeConfig;
|
||||
// Only relevant for hosted version
|
||||
isTermOfServiceAccepted: boolean;
|
||||
accessCode: string | null;
|
||||
}
|
||||
|
||||
export enum AppState {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user