diff --git a/app/components/APIKeyInput.tsx b/app/components/APIKeyInput.tsx index 580727fb..27516ec8 100644 --- a/app/components/APIKeyInput.tsx +++ b/app/components/APIKeyInput.tsx @@ -1,9 +1,11 @@ import { Icon, useBreakpoint, useEditor, useValue } from '@tldraw/tldraw' -import { ChangeEvent, useCallback, useState } from 'react' +import { ChangeEvent, useCallback, useEffect, useState } from 'react' +import { useOpenRouter } from '../hooks/useOpenRouter' export function APIKeyInput() { const breakpoint = useBreakpoint() const [cool, setCool] = useState(false) + const { apiKey, getCode, removeApiKey } = useOpenRouter() const editor = useEditor() const isFocusMode = useValue('is focus mode', () => editor.getInstanceState().isFocusMode, [ @@ -28,7 +30,7 @@ export function APIKeyInput() { }, []) const handleQuestionClick = useCallback(() => { - const message = `Sorry, this is weird. The OpenAI APIs that we use are very new. If you have an OpenAI developer key, you can put it in this input and we'll use it. We don't save / store / upload these.\n\nSee https://platform.openai.com/api-keys to get a key.\n\nThis app's source code: https://github.com/tldraw/draw-a-ui` + const message = `OpenRouter lets app leverage AI without breaking the developer's bank - users pay for what they use!\n\nThis app's source code: https://github.com/tldraw/draw-a-ui` window.alert(message) }, []) @@ -37,16 +39,27 @@ export function APIKeyInput() { return (
-
- -
+ {!apiKey ? ( + + ) : ( +
+ + OpenRouter Connected + + +
+ )} + diff --git a/app/hooks/useMakeReal.ts b/app/hooks/useMakeReal.ts index c118972b..05e79c30 100644 --- a/app/hooks/useMakeReal.ts +++ b/app/hooks/useMakeReal.ts @@ -1,16 +1,15 @@ import { useEditor, useToasts } from '@tldraw/tldraw' -import { useCallback } from 'react' +import { useCallback, useEffect } from 'react' import { makeReal } from '../lib/makeReal' import { track } from '@vercel/analytics/react' +import { useOpenRouter } from './useOpenRouter' export function useMakeReal() { const editor = useEditor() const toast = useToasts() + const { apiKey } = useOpenRouter() return useCallback(async () => { - const input = document.getElementById('openai_key_risky_but_cool') as HTMLInputElement - const apiKey = input?.value ?? null - track('make_real', { timestamp: Date.now() }) try { @@ -34,5 +33,5 @@ export function useMakeReal() { ], }) } - }, [editor, toast]) + }, [apiKey, editor, toast]) } diff --git a/app/hooks/useOpenRouter.ts b/app/hooks/useOpenRouter.ts new file mode 100644 index 00000000..1e6f4bdc --- /dev/null +++ b/app/hooks/useOpenRouter.ts @@ -0,0 +1,43 @@ +import { useEffect, useState } from 'react' + +const LOCAL_STORAGE_KEY = 'make-real:openrouter-api-key' + +export function useOpenRouter() { + const [apiKey, setApiKey] = useState(localStorage.getItem(LOCAL_STORAGE_KEY)) + + useEffect(() => { + if (window.location.search.includes('code=')) { + const params = new URLSearchParams(window.location.search) + const code = params.get('code') + if (code) { + fetch('https://openrouter.ai/api/v1/auth/keys', { + method: 'POST', + body: JSON.stringify({ + code, + }), + }) + .then((res) => res.json()) + .then((res) => { + localStorage.setItem(LOCAL_STORAGE_KEY, res.key) + setApiKey(res.key) + window.location.search = '' + }) + } + } + }, []) + + const getCode = () => { + window.open(`https://openrouter.ai/auth?callback_url=${window.location.href}`, '_self') + } + + const removeApiKey = () => { + localStorage.removeItem(LOCAL_STORAGE_KEY) + setApiKey('') + } + + return { + apiKey, + getCode, + removeApiKey, + } +} diff --git a/app/lib/getHtmlFromOpenAI.ts b/app/lib/getHtmlFromOpenAI.ts index 85310943..7220acf8 100644 --- a/app/lib/getHtmlFromOpenAI.ts +++ b/app/lib/getHtmlFromOpenAI.ts @@ -18,7 +18,7 @@ export async function getHtmlFromOpenAI({ theme?: string previousPreviews?: PreviewShape[] }) { - if (!apiKey) throw Error('You need to provide an API key (sorry)') + if (!apiKey) throw Error('You need to connect with an AI provider') const messages: GPT4VCompletionRequest['messages'] = [ { @@ -100,11 +100,13 @@ export async function getHtmlFromOpenAI({ let json = null try { - const resp = await fetch('https://api.openai.com/v1/chat/completions', { + const resp = await fetch('https://openrouter.ai/api/v1/chat/completions', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}`, + 'HTTP-Referer': `https://makereal.tldraw.com/`, + 'X-Title': `tldraw: make-real`, }, body: JSON.stringify(body), }) diff --git a/package-lock.json b/package-lock.json index b98ea410..bc2b8805 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "draw-a-ui", "version": "0.1.0", + "license": "AGPL-3.0-or-later", "dependencies": { "@tldraw/tldraw": "^2.0.0-canary.ba4091c59418", "@vercel/analytics": "^1.1.1",