Merge branch 'main' into pr/7
This commit is contained in:
commit
28a05153e9
19
README.md
19
README.md
@ -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.
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
24
frontend/src/components/ui/select.tsx
Normal file
24
frontend/src/components/ui/select.tsx
Normal 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 };
|
||||||
@ -1,4 +1,5 @@
|
|||||||
export interface Settings {
|
export interface Settings {
|
||||||
openAiApiKey: string | null;
|
openAiApiKey: string | null;
|
||||||
isImageGenerationEnabled: boolean;
|
isImageGenerationEnabled: boolean;
|
||||||
|
editorTheme: string;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user