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") print("Received params")
# Read the output settings from the request. Fall back to default if not provided. # Read the code config settings from the request. Fall back to default if not provided.
output_settings = {"css": "tailwind", "js": "vanilla" , "components" : "html"} generated_code_config = ""
if params["outputSettings"] and params["outputSettings"]["css"]: if "generatedCodeConfig" in params and params["generatedCodeConfig"]:
output_settings["css"] = params["outputSettings"]["css"] generated_code_config = params["generatedCodeConfig"]
if params["outputSettings"] and params["outputSettings"]["js"]: print(f"Generating {generated_code_config} code")
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)
# 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.
@ -143,10 +139,10 @@ async def stream_code(websocket: WebSocket):
if params.get("resultImage") and params["resultImage"]: if params.get("resultImage") and params["resultImage"]:
prompt_messages = assemble_prompt( prompt_messages = assemble_prompt(
params["image"], output_settings, params["resultImage"] params["image"], generated_code_config, params["resultImage"]
) )
else: 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 for updates so that we don't have to regenerate images
image_cache = {} 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 # Set the system prompt based on the output settings
chosen_prompt_name = "tailwind"
system_content = TAILWIND_SYSTEM_PROMPT system_content = TAILWIND_SYSTEM_PROMPT
if output_settings["css"] == "bootstrap": if generated_code_config == "html_tailwind":
chosen_prompt_name = "bootstrap" system_content = TAILWIND_SYSTEM_PROMPT
system_content = BOOTSTRAP_SYSTEM_PROMPT elif generated_code_config == "react_tailwind":
if output_settings["js"] == "react":
chosen_prompt_name = "react-tailwind"
system_content = REACT_TAILWIND_SYSTEM_PROMPT system_content = REACT_TAILWIND_SYSTEM_PROMPT
if output_settings["components"] == "ionic": elif generated_code_config == "bootstrap":
chosen_prompt_name = "ionic-tailwind" system_content = BOOTSTRAP_SYSTEM_PROMPT
elif generated_code_config == "ionic_tailwind":
system_content = IONIC_TAILWIND_SYSTEM_PROMPT system_content = IONIC_TAILWIND_SYSTEM_PROMPT
else:
print("Using system prompt:", chosen_prompt_name) raise Exception("Code config is not one of available options")
user_content = [ 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 ImageUpload from "./components/ImageUpload";
import CodePreview from "./components/CodePreview"; import CodePreview from "./components/CodePreview";
import Preview from "./components/Preview"; import Preview from "./components/Preview";
@ -18,15 +18,7 @@ import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs";
import SettingsDialog from "./components/SettingsDialog"; import SettingsDialog from "./components/SettingsDialog";
import { import { Settings, EditorTheme, AppState, GeneratedCodeConfig } from "./types";
Settings,
EditorTheme,
AppState,
CSSOption,
OutputSettings,
JSFrameworkOption,
UIComponentOption,
} from "./types";
import { IS_RUNNING_ON_CLOUD } from "./config"; import { IS_RUNNING_ON_CLOUD } from "./config";
import { PicoBadge } from "./components/PicoBadge"; import { PicoBadge } from "./components/PicoBadge";
import { OnboardingNote } from "./components/OnboardingNote"; import { OnboardingNote } from "./components/OnboardingNote";
@ -52,21 +44,31 @@ function App() {
screenshotOneApiKey: null, screenshotOneApiKey: null,
isImageGenerationEnabled: true, isImageGenerationEnabled: true,
editorTheme: EditorTheme.COBALT, editorTheme: EditorTheme.COBALT,
generatedCodeConfig: GeneratedCodeConfig.HTML_TAILWIND,
// Only relevant for hosted version
isTermOfServiceAccepted: false, isTermOfServiceAccepted: false,
accessCode: null, accessCode: null,
}, },
"setting" "setting"
); );
const [outputSettings, setOutputSettings] = useState<OutputSettings>({
css: CSSOption.TAILWIND,
components: UIComponentOption.HTML,
js: JSFrameworkOption.NO_FRAMEWORK,
});
const [shouldIncludeResultImage, setShouldIncludeResultImage] = const [shouldIncludeResultImage, setShouldIncludeResultImage] =
useState<boolean>(false); useState<boolean>(false);
const wsRef = useRef<WebSocket>(null); 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 takeScreenshot = async (): Promise<string> => {
const iframeElement = document.querySelector( const iframeElement = document.querySelector(
"#preview-desktop" "#preview-desktop"
@ -116,7 +118,7 @@ function App() {
setAppState(AppState.CODING); setAppState(AppState.CODING);
// Merge settings with params // Merge settings with params
const updatedParams = { ...params, ...settings, outputSettings }; const updatedParams = { ...params, ...settings };
generateCode( generateCode(
wsRef, wsRef,
@ -190,8 +192,13 @@ function App() {
</div> </div>
<OutputSettingsSection <OutputSettingsSection
outputSettings={outputSettings} generatedCodeConfig={settings.generatedCodeConfig}
setOutputSettings={setOutputSettings} setGeneratedCodeConfig={(config: GeneratedCodeConfig) =>
setSettings((prev) => ({
...prev,
generatedCodeConfig: config,
}))
}
shouldDisableUpdates={ shouldDisableUpdates={
appState === AppState.CODING || appState === AppState.CODE_READY appState === AppState.CODING || appState === AppState.CODE_READY
} }

View File

@ -5,324 +5,86 @@ import {
SelectItem, SelectItem,
SelectTrigger, SelectTrigger,
} from "./ui/select"; } from "./ui/select";
import { import { GeneratedCodeConfig } from "../types";
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";
function displayCSSOption(option: CSSOption) { function generateDisplayComponent(config: GeneratedCodeConfig) {
switch (option) { switch (config) {
case CSSOption.TAILWIND: case GeneratedCodeConfig.HTML_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
) {
return ( return (
<div className="text-gray-800 dark:text-white"> <div>
Generating <span className="font-bold">React</span> +{" "} <span className="font-semibold">HTML</span> +{" "}
<span className="font-bold">Tailwind</span> code <span className="font-semibold">Tailwind</span>
</div> </div>
); );
} else if ( case GeneratedCodeConfig.REACT_TAILWIND:
settings.js === JSFrameworkOption.NO_FRAMEWORK &&
settings.css === CSSOption.TAILWIND
) {
return ( return (
<div className="text-gray-800 dark:text-white"> <div>
Generating <span className="font-bold">HTML</span> +{" "} <span className="font-semibold">React</span> +{" "}
<span className="font-bold">Tailwind</span> code <span className="font-semibold">Tailwind</span>
</div> </div>
); );
} else if ( case GeneratedCodeConfig.BOOTSTRAP:
settings.js === JSFrameworkOption.NO_FRAMEWORK &&
settings.css === CSSOption.BOOTSTRAP
) {
return ( return (
<div className="text-gray-800 dark:text-white"> <div>
Generating <span className="font-bold">HTML</span> +{" "} <span className="font-semibold">Bootstrap</span>
<span className="font-bold">Bootstrap</span> code
</div> </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 { interface Props {
outputSettings: OutputSettings; generatedCodeConfig: GeneratedCodeConfig;
setOutputSettings: React.Dispatch<React.SetStateAction<OutputSettings>>; setGeneratedCodeConfig: (config: GeneratedCodeConfig) => void;
shouldDisableUpdates?: boolean; shouldDisableUpdates?: boolean;
} }
function OutputSettingsSection({ function OutputSettingsSection({
outputSettings, generatedCodeConfig,
setOutputSettings, setGeneratedCodeConfig,
shouldDisableUpdates = false, shouldDisableUpdates = false,
}: Props) { }: 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 ( return (
<div className="flex flex-col gap-y-2 justify-between text-sm"> <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"> <div className="grid grid-cols-3 items-center gap-4">
<Label htmlFor="output-settings-js">JS</Label> <span>Generating:</span>
<Select <Select
value={outputSettings.js} value={generatedCodeConfig}
onValueChange={onJsFrameworkChange} onValueChange={(value: string) =>
setGeneratedCodeConfig(value as GeneratedCodeConfig)
}
disabled={shouldDisableUpdates}
> >
<SelectTrigger <SelectTrigger className="col-span-2" id="output-settings-js">
className="col-span-2 h-8" {generateDisplayComponent(generatedCodeConfig)}
id="output-settings-js"
>
{displayJSOption(outputSettings.js)}
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectItem value={JSFrameworkOption.NO_FRAMEWORK}> <SelectItem value={GeneratedCodeConfig.HTML_TAILWIND}>
No Framework {generateDisplayComponent(GeneratedCodeConfig.HTML_TAILWIND)}
</SelectItem> </SelectItem>
<SelectItem value={JSFrameworkOption.REACT}> <SelectItem value={GeneratedCodeConfig.REACT_TAILWIND}>
React {generateDisplayComponent(GeneratedCodeConfig.REACT_TAILWIND)}
</SelectItem>
<SelectItem value={GeneratedCodeConfig.BOOTSTRAP}>
{generateDisplayComponent(GeneratedCodeConfig.BOOTSTRAP)}
</SelectItem>
<SelectItem value={GeneratedCodeConfig.IONIC_TAILWIND}>
{generateDisplayComponent(GeneratedCodeConfig.IONIC_TAILWIND)}
</SelectItem> </SelectItem>
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </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> </div>
); );
} }

View File

@ -3,26 +3,12 @@ export enum EditorTheme {
COBALT = "cobalt", COBALT = "cobalt",
} }
export enum CSSOption { // Keep in sync with backend (prompts.py)
TAILWIND = "tailwind", export enum GeneratedCodeConfig {
HTML_TAILWIND = "html_tailwind",
REACT_TAILWIND = "react_tailwind",
BOOTSTRAP = "bootstrap", BOOTSTRAP = "bootstrap",
} IONIC_TAILWIND = "ionic_tailwind",
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;
} }
export interface Settings { export interface Settings {
@ -31,8 +17,10 @@ export interface Settings {
screenshotOneApiKey: string | null; screenshotOneApiKey: string | null;
isImageGenerationEnabled: boolean; isImageGenerationEnabled: boolean;
editorTheme: EditorTheme; editorTheme: EditorTheme;
isTermOfServiceAccepted: boolean; // Only relevant for hosted version generatedCodeConfig: GeneratedCodeConfig;
accessCode: string | null; // Only relevant for hosted version // Only relevant for hosted version
isTermOfServiceAccepted: boolean;
accessCode: string | null;
} }
export enum AppState { export enum AppState {