From 488a4419f3c03816ba67120e50ca3a1cbdc5172f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?L=E2=9D=A4=EF=B8=8F=20=E2=98=AE=EF=B8=8F=20=E2=9C=8B?=
<6723574+louisgv@users.noreply.github.com>
Date: Fri, 1 Dec 2023 21:28:33 -0400
Subject: [PATCH 1/2] feat: Integrate with OpenRouter to access GPT4V and more
---
app/components/APIKeyInput.tsx | 37 +++++++++++++++++++----------
app/hooks/useMakeReal.ts | 9 ++++---
app/hooks/useOpenRouter.ts | 43 ++++++++++++++++++++++++++++++++++
app/lib/getHtmlFromOpenAI.ts | 4 ++--
package-lock.json | 1 +
5 files changed, 75 insertions(+), 19 deletions(-)
create mode 100644 app/hooks/useOpenRouter.ts
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..d477ae98 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,7 +100,7 @@ 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',
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",
From d4bb4c36ef2d10079daca7f5fd4c6563c8510211 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?L=E2=9D=A4=EF=B8=8F=20=E2=98=AE=EF=B8=8F=20=E2=9C=8B?=
<6723574+louisgv@users.noreply.github.com>
Date: Fri, 1 Dec 2023 21:50:33 -0400
Subject: [PATCH 2/2] add tracking headers
---
app/lib/getHtmlFromOpenAI.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/app/lib/getHtmlFromOpenAI.ts b/app/lib/getHtmlFromOpenAI.ts
index d477ae98..7220acf8 100644
--- a/app/lib/getHtmlFromOpenAI.ts
+++ b/app/lib/getHtmlFromOpenAI.ts
@@ -105,6 +105,8 @@ export async function getHtmlFromOpenAI({
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
+ 'HTTP-Referer': `https://makereal.tldraw.com/`,
+ 'X-Title': `tldraw: make-real`,
},
body: JSON.stringify(body),
})