diff --git a/Troubleshooting.md b/Troubleshooting.md index 0704859..20fa815 100644 --- a/Troubleshooting.md +++ b/Troubleshooting.md @@ -1,4 +1,4 @@ -### Getting an OpenAI API key +### Getting an OpenAI API key with GPT4-Vision model access You don't need a ChatGPT Pro account. Screenshot to code uses API keys from your OpenAI developer account. In order to get access to the GPT4 Vision model, log into your OpenAI account and then, follow these instructions: @@ -10,6 +10,7 @@ You don't need a ChatGPT Pro account. Screenshot to code uses API keys from your 5. Go to Settings > Limits and check at the bottom of the page, your current tier has to be "Tier 1" to have GPT4 access 285636973-da38bd4d-8a78-4904-8027-ca67d729b933 +6. Go to Screenshot to code and paste it in the Settings dialog under OpenAI key (gear icon). Your key is only stored in your browser. Never stored on our servers. Some users have also reported that it can take upto 30 minutes after your credit purchase for the GPT4 vision model to be activated. diff --git a/backend/access_token.py b/backend/access_token.py new file mode 100644 index 0000000..e61ef12 --- /dev/null +++ b/backend/access_token.py @@ -0,0 +1,27 @@ +import json +import os +import httpx + + +async def validate_access_token(access_code: str): + async with httpx.AsyncClient() as client: + url = ( + "https://backend.buildpicoapps.com/screenshot_to_code/validate_access_token" + ) + data = json.dumps( + { + "access_code": access_code, + "secret": os.environ.get("PICO_BACKEND_SECRET"), + } + ) + headers = {"Content-Type": "application/json"} + + response = await client.post(url, content=data, headers=headers) + response_data = response.json() + + if response_data["success"]: + print("Access token is valid.") + return True + else: + print(f"Access token validation failed: {response_data['failure_reason']}") + return False diff --git a/backend/main.py b/backend/main.py index 4b75f71..4c5823b 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,6 +1,5 @@ # Load environment variables first from dotenv import load_dotenv -from pydantic import BaseModel load_dotenv() @@ -16,6 +15,7 @@ from mock import mock_completion from image_generation import create_alt_url_mapping, generate_images from prompts import assemble_prompt from routes import screenshot +from access_token import validate_access_token app = FastAPI(openapi_url=None, docs_url=None, redoc_url=None) @@ -79,13 +79,27 @@ async def stream_code(websocket: WebSocket): # 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 params["openAiApiKey"]: - openai_api_key = params["openAiApiKey"] - print("Using OpenAI API key from client-side settings dialog") + openai_api_key = None + if "accessCode" in params and params["accessCode"]: + print("Access code - using platform API key") + if await validate_access_token(params["accessCode"]): + openai_api_key = os.environ.get("PLATFORM_OPENAI_API_KEY") + else: + await websocket.send_json( + { + "type": "error", + "value": "Invalid access code or you're out of credits. Please try again.", + } + ) + return else: - openai_api_key = os.environ.get("OPENAI_API_KEY") - if openai_api_key: - print("Using OpenAI API key from environment variable") + if params["openAiApiKey"]: + openai_api_key = params["openAiApiKey"] + print("Using OpenAI API key from client-side settings dialog") + else: + openai_api_key = os.environ.get("OPENAI_API_KEY") + if openai_api_key: + print("Using OpenAI API key from environment variable") if not openai_api_key: print("OpenAI API key not found") diff --git a/frontend/index.html b/frontend/index.html index f747064..2a7fa0e 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -21,6 +21,34 @@ <%- injectHead %> Screenshot to Code + + + + + + + + + + + + + + + +
diff --git a/frontend/package.json b/frontend/package.json index 0d180b6..476a72d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,7 @@ "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-popover": "^1.0.7", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-separator": "^1.0.3", "@radix-ui/react-slot": "^1.0.2", diff --git a/frontend/public/brand/twitter-summary-card.png b/frontend/public/brand/twitter-summary-card.png new file mode 100644 index 0000000..6fcedbf Binary files /dev/null and b/frontend/public/brand/twitter-summary-card.png differ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 11aa9a0..8ba39a3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -51,12 +51,13 @@ function App() { isImageGenerationEnabled: true, editorTheme: EditorTheme.COBALT, isTermOfServiceAccepted: false, + accessCode: null, }, "setting" ); const [outputSettings, setOutputSettings] = useState({ css: CSSOption.TAILWIND, - js: JSFrameworkOption.VANILLA, + js: JSFrameworkOption.NO_FRAMEWORK, }); const [shouldIncludeResultImage, setShouldIncludeResultImage] = useState(false); @@ -170,8 +171,8 @@ function App() { }; return ( -
- {IS_RUNNING_ON_CLOUD && } +
+ {IS_RUNNING_ON_CLOUD && } {IS_RUNNING_ON_CLOUD && (
-
+

Screenshot to Code

- {appState === AppState.INITIAL && ( -

- Drag & drop a screenshot to get started. -

- )} - {appState === AppState.INITIAL && ( - - )} + - {IS_RUNNING_ON_CLOUD && !settings.openAiApiKey && } + {IS_RUNNING_ON_CLOUD && + !(settings.openAiApiKey || settings.accessCode) && ( + + )} {(appState === AppState.CODING || appState === AppState.CODE_READY) && ( @@ -210,7 +210,10 @@ function App() { {executionConsole.slice(-1)[0]}
-
@@ -236,7 +239,7 @@ function App() { className="dark:bg-gray-700" />
- + + +
+
+

Code Settings

+

+ Customize your code output +

+
+
+
+ + +
+
+ + +
+
+
+
+ + )} +
); } diff --git a/frontend/src/components/PicoBadge.tsx b/frontend/src/components/PicoBadge.tsx index bdd0083..70a4f20 100644 --- a/frontend/src/components/PicoBadge.tsx +++ b/frontend/src/components/PicoBadge.tsx @@ -1,4 +1,6 @@ -export function PicoBadge() { +import { Settings } from "../types"; + +export function PicoBadge({ settings }: { settings: Settings }) { return ( <> - -
+
- an open source project by Pico -
-
+ > + an open source project by Pico +
+ + )} + {settings.accessCode && ( + +
+ email support +
+
+ )} ); } diff --git a/frontend/src/components/SettingsDialog.tsx b/frontend/src/components/SettingsDialog.tsx index 73844f4..9d9a70b 100644 --- a/frontend/src/components/SettingsDialog.tsx +++ b/frontend/src/components/SettingsDialog.tsx @@ -15,6 +15,7 @@ import { Label } from "./ui/label"; import { Input } from "./ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger } from "./ui/select"; import { capitalize } from "../lib/utils"; +import { IS_RUNNING_ON_CLOUD } from "../config"; interface Props { settings: Settings; @@ -38,6 +39,31 @@ function SettingsDialog({ settings, setSettings }: Props) { Settings + + {/* Access code */} + {IS_RUNNING_ON_CLOUD && ( +
+ + + + setSettings((s) => ({ + ...s, + accessCode: e.target.value, + })) + } + /> +
+ )} +