redo how output settings is configured

This commit is contained in:
Abi Raja 2023-11-30 15:58:01 -05:00
parent 45a64326f6
commit f51e70d701
5 changed files with 118 additions and 365 deletions

View File

@ -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 = {}

View File

@ -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 = [
{

View File

@ -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
}

View File

@ -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";
function generateDisplayComponent(config: GeneratedCodeConfig) {
switch (config) {
case GeneratedCodeConfig.HTML_TAILWIND:
return (
<div>
<span className="font-semibold">HTML</span> +{" "}
<span className="font-semibold">Tailwind</span>
</div>
);
case GeneratedCodeConfig.REACT_TAILWIND:
return (
<div>
<span className="font-semibold">React</span> +{" "}
<span className="font-semibold">Tailwind</span>
</div>
);
case GeneratedCodeConfig.BOOTSTRAP:
return (
<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:
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
) {
return (
<div className="text-gray-800 dark:text-white">
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 dark:text-white">
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 className="text-gray-800 dark:text-white">
Generating <span className="font-bold">HTML</span> +{" "}
<span className="font-bold">Bootstrap</span> code
</div>
);
// 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>
<Select
value={outputSettings.js}
onValueChange={onJsFrameworkChange}
>
<SelectTrigger
className="col-span-2 h-8"
id="output-settings-js"
>
{displayJSOption(outputSettings.js)}
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value={JSFrameworkOption.NO_FRAMEWORK}>
No Framework
</SelectItem>
<SelectItem value={JSFrameworkOption.REACT}>
React
</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 className="grid grid-cols-3 items-center gap-4">
<span>Generating:</span>
<Select
value={generatedCodeConfig}
onValueChange={(value: string) =>
setGeneratedCodeConfig(value as GeneratedCodeConfig)
}
disabled={shouldDisableUpdates}
>
<SelectTrigger className="col-span-2" id="output-settings-js">
{generateDisplayComponent(generatedCodeConfig)}
</SelectTrigger>
<SelectContent>
<SelectGroup>
<SelectItem value={GeneratedCodeConfig.HTML_TAILWIND}>
{generateDisplayComponent(GeneratedCodeConfig.HTML_TAILWIND)}
</SelectItem>
<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>
);
}

View File

@ -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 {