Merge branch 'main' into pr/7

This commit is contained in:
Abi Raja 2023-11-19 21:46:27 -05:00
commit 28a05153e9
6 changed files with 76 additions and 17 deletions

View File

@ -1,20 +1,23 @@
# screenshot-to-code # screenshot-to-code
This is a simple app that converts a screenshot to HTML/Tailwind CSS. It uses GPT-4 Vision to generate the code, and DALL-E 3 to generate similar looking images. This simple app converts a screenshot to HTML/Tailwind CSS. It uses GPT-4 Vision to generate the code and DALL-E 3 to generate similar-looking images.
https://github.com/abi/screenshot-to-code/assets/23818/6cebadae-2fe3-4986-ac6a-8fb9db030045 https://github.com/abi/screenshot-to-code/assets/23818/6cebadae-2fe3-4986-ac6a-8fb9db030045
See [Examples](#examples) section below for more demos. See the [Examples](#examples) section below for more demos.
## 🚀 Try It Out!
🆕 [Try it here](https://picoapps.xyz/free-tools/screenshot-to-code) (bring your own OpenAI key - **your key must have access to GPT-4 Vision. See [FAQ](#faqs) section below for details**). Or see [Getting Started](#getting-started) below for local install instructions. 🆕 [Try it here](https://picoapps.xyz/free-tools/screenshot-to-code) (bring your own OpenAI key - **your key must have access to GPT-4 Vision. See [FAQ](#faqs) section below for details**). Or see [Getting Started](#getting-started) below for local install instructions.
## Updates ## 🌟 Recent Updates
- Nov 19 - Support for dark/light code editor theme - thanks https://github.com/kachbit
- Nov 16 - Added a setting to disable DALL-E image generation if you don't need that - Nov 16 - Added a setting to disable DALL-E image generation if you don't need that
- Nov 16 - View code directly within the app - Nov 16 - View code directly within the app
- Nov 15 - 🔥 You can now instruct the AI to update the code as you wish. Useful if the AI messed up some styles or missed a section. - Nov 15 - 🔥 You can now instruct the AI to update the code as you wish. It is helpful if the AI messed up some styles or missed a section.
## Getting Started ## 🛠 Getting Started
The app has a React/Vite frontend and a FastAPI backend. You will need an OpenAI API key with access to the GPT-4 Vision API. The app has a React/Vite frontend and a FastAPI backend. You will need an OpenAI API key with access to the GPT-4 Vision API.
@ -53,13 +56,13 @@ Application will be up and running at http://localhost:5173
Note that you can't develop the application with this setup as the file changes won't trigger a rebuild. Note that you can't develop the application with this setup as the file changes won't trigger a rebuild.
## FAQs ## 🙋‍♂️ FAQs
- **I'm running into an error when setting up the backend. How can I fix it?** [Try this](https://github.com/abi/screenshot-to-code/issues/3#issuecomment-1814777959). If that still doesn't work, open an issue. - **I'm running into an error when setting up the backend. How can I fix it?** [Try this](https://github.com/abi/screenshot-to-code/issues/3#issuecomment-1814777959). If that still doesn't work, open an issue.
- **How do I get an OpenAI API key that has the GPT4 Vision model available?** Create an OpenAI account. And then, you need to buy at least $1 worth of credit on the [Billing dashboard](https://platform.openai.com/account/billing/overview). - **How do I get an OpenAI API key that has the GPT4 Vision model available?** Create an OpenAI account. And then, you need to buy at least $1 worth of credit on the [Billing dashboard](https://platform.openai.com/account/billing/overview).
- **How can I provide feedback?** For feedback, feature requests and bug reports, open an issue or ping me on [Twitter](https://twitter.com/_abi_). - **How can I provide feedback?** For feedback, feature requests and bug reports, open an issue or ping me on [Twitter](https://twitter.com/_abi_).
## Examples ## 📚 Examples
**NYTimes** **NYTimes**
@ -75,6 +78,6 @@ https://github.com/abi/screenshot-to-code/assets/23818/503eb86a-356e-4dfc-926a-d
https://github.com/abi/screenshot-to-code/assets/23818/3fec0f77-44e8-4fb3-a769-ac7410315e5d https://github.com/abi/screenshot-to-code/assets/23818/3fec0f77-44e8-4fb3-a769-ac7410315e5d
## Hosted Version ## 🌍 Hosted Version
🆕 [Try it here](https://picoapps.xyz/free-tools/screenshot-to-code) (bring your own OpenAI key - **your key must have access to GPT-4 Vision. See [FAQ](#faqs) section for details**). Or see [Getting Started](#getting-started) for local install instructions. 🆕 [Try it here](https://picoapps.xyz/free-tools/screenshot-to-code) (bring your own OpenAI key - **your key must have access to GPT-4 Vision. See [FAQ](#faqs) section for details**). Or see [Getting Started](#getting-started) for local install instructions.

View File

@ -34,6 +34,7 @@ function App() {
const [settings, setSettings] = useState<Settings>({ const [settings, setSettings] = useState<Settings>({
openAiApiKey: null, openAiApiKey: null,
isImageGenerationEnabled: true, isImageGenerationEnabled: true,
editorTheme: "cobalt"
}); });
const downloadCode = () => { const downloadCode = () => {
@ -180,7 +181,6 @@ function App() {
Original Screenshot Original Screenshot
</div> </div>
</div> </div>
<div className="bg-gray-400 px-4 py-2 rounded text-sm hidden"> <div className="bg-gray-400 px-4 py-2 rounded text-sm hidden">
<h2 className="text-lg mb-4 border-b border-gray-800"> <h2 className="text-lg mb-4 border-b border-gray-800">
Console Console
@ -231,7 +231,7 @@ function App() {
<Preview code={generatedCode} device="mobile" /> <Preview code={generatedCode} device="mobile" />
</TabsContent> </TabsContent>
<TabsContent value="code"> <TabsContent value="code">
<CodeMirror code={generatedCode} /> <CodeMirror code={generatedCode} editorTheme={settings.editorTheme} />
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</div> </div>

View File

@ -1,7 +1,7 @@
import { useRef, useEffect } from "react"; import { useRef, useEffect } from "react";
import { EditorState } from "@codemirror/state"; import { EditorState } from "@codemirror/state";
import { EditorView, keymap, lineNumbers } from "@codemirror/view"; import { EditorView, keymap, lineNumbers } from "@codemirror/view";
import { cobalt } from "thememirror"; import { espresso, cobalt } from "thememirror";
import { import {
defaultKeymap, defaultKeymap,
history, history,
@ -14,14 +14,18 @@ import { html } from "@codemirror/lang-html";
interface Props { interface Props {
code: string; code: string;
editorTheme: string;
} }
function CodeMirror({ code }: Props) { function CodeMirror({ code, editorTheme }: Props) {
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
const view = useRef<EditorView | null>(null); const view = useRef<EditorView | null>(null);
// Initialize the editor when the component mounts
useEffect(() => { useEffect(() => {
let selectedTheme = cobalt;
if (editorTheme === "espresso") {
selectedTheme = espresso;
}
view.current = new EditorView({ view.current = new EditorView({
state: EditorState.create({ state: EditorState.create({
doc: code, doc: code,
@ -36,7 +40,7 @@ function CodeMirror({ code }: Props) {
lineNumbers(), lineNumbers(),
bracketMatching(), bracketMatching(),
html(), html(),
cobalt, selectedTheme,
EditorView.lineWrapping, EditorView.lineWrapping,
], ],
}), }),
@ -49,9 +53,8 @@ function CodeMirror({ code }: Props) {
view.current = null; view.current = null;
} }
}; };
}, []); }, [code, editorTheme]);
// Update the contents of the editor when the code changes
useEffect(() => { useEffect(() => {
if (view.current && view.current.state.doc.toString() !== code) { if (view.current && view.current.state.doc.toString() !== code) {
view.current.dispatch({ view.current.dispatch({
@ -60,6 +63,9 @@ function CodeMirror({ code }: Props) {
} }
}, [code]); }, [code]);
return <div className="overflow-x-scroll overflow-y-scroll mx-2" ref={ref} />; return (
<div className="overflow-x-scroll overflow-y-scroll mx-2 border-[4px] border-black rounded-[20px]" ref={ref} />
);
} }
export default CodeMirror; export default CodeMirror;

View File

@ -1,3 +1,4 @@
import React from "react";
import { import {
Dialog, Dialog,
DialogClose, DialogClose,
@ -12,6 +13,7 @@ import { Settings } from "../types";
import { Switch } from "./ui/switch"; import { Switch } from "./ui/switch";
import { Label } from "./ui/label"; import { Label } from "./ui/label";
import { Input } from "./ui/input"; import { Input } from "./ui/input";
import { Select } from "./ui/select";
interface Props { interface Props {
settings: Settings; settings: Settings;
@ -19,6 +21,13 @@ interface Props {
} }
function SettingsDialog({ settings, setSettings }: Props) { function SettingsDialog({ settings, setSettings }: Props) {
const handleThemeChange = (theme: string) => {
setSettings((s) => ({
...s,
editorTheme: theme,
}));
};
return ( return (
<Dialog> <Dialog>
<DialogTrigger> <DialogTrigger>
@ -65,7 +74,23 @@ function SettingsDialog({ settings, setSettings }: Props) {
})) }))
} }
/> />
<Label htmlFor="editor-theme">
<div>Editor Theme</div>
</Label>
<div>
<Select // Use the custom Select component here
id="editor-theme"
value={settings.editorTheme}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
handleThemeChange(e.target.value)
}
>
<option value="cobalt">Cobalt</option>
<option value="espresso">Espresso</option>
</Select>
</div> </div>
</div>
<DialogFooter> <DialogFooter>
<DialogClose>Save</DialogClose> <DialogClose>Save</DialogClose>
</DialogFooter> </DialogFooter>

View File

@ -0,0 +1,24 @@
import * as React from "react";
import { cn } from "@/lib/utils";
export interface SelectProps
extends React.SelectHTMLAttributes<HTMLSelectElement> {}
const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
({ className, ...props }, ref) => {
return (
<select
className={cn(
"block appearance-none w-full text-sm shadow-sm rounded-md border border-input bg-white py-2 px-3 focus:outline-none focus:border-black",
className
)}
ref={ref}
{...props}
/>
);
}
);
Select.displayName = "Select";
export { Select };

View File

@ -1,4 +1,5 @@
export interface Settings { export interface Settings {
openAiApiKey: string | null; openAiApiKey: string | null;
isImageGenerationEnabled: boolean; isImageGenerationEnabled: boolean;
editorTheme: string;
} }