diff options
| author | codetorso <[email protected]> | 2024-06-18 03:54:20 -0600 |
|---|---|---|
| committer | codetorso <[email protected]> | 2024-06-18 03:54:20 -0600 |
| commit | c5361aa24df2cdf50a6189df0fb1493019ecbfc3 (patch) | |
| tree | 6d4afc137b91649842344d4bd7ff6f98a5d6e7fb /apps | |
| parent | EditorAI integrated! (1/4) (diff) | |
| download | supermemory-c5361aa24df2cdf50a6189df0fb1493019ecbfc3.tar.xz supermemory-c5361aa24df2cdf50a6189df0fb1493019ecbfc3.zip | |
Create Embeddings for Canvas
Diffstat (limited to 'apps')
| -rw-r--r-- | apps/web/app/(canvas)/lib/createEmbeds.ts | 16 | ||||
| -rw-r--r-- | apps/web/app/api/editorai/route.ts | 20 | ||||
| -rw-r--r-- | apps/web/app/api/unfirlsite/route.ts | 134 | ||||
| -rw-r--r-- | apps/web/cf-env.d.ts | 5 |
4 files changed, 169 insertions, 6 deletions
diff --git a/apps/web/app/(canvas)/lib/createEmbeds.ts b/apps/web/app/(canvas)/lib/createEmbeds.ts index 322e697e..53d81533 100644 --- a/apps/web/app/(canvas)/lib/createEmbeds.ts +++ b/apps/web/app/(canvas)/lib/createEmbeds.ts @@ -50,10 +50,18 @@ export default async function createEmbedsFromUrl({url, point, sources, editor}: type: "url", url, }); - const fetchWebsite = await (await fetch(`https://unfurl-bookmark.pruthvirajthinks.workers.dev/?url=${url}`)).json() - if (fetchWebsite.title) bookmarkAsset.props.title = fetchWebsite.title; - if (fetchWebsite.image) bookmarkAsset.props.image = fetchWebsite.image; - if (fetchWebsite.description) bookmarkAsset.props.description = fetchWebsite.description; + const fetchWebsite: { + title?: string; + image?: string; + description?: string; + } = await (await fetch(`/api/unfirlsite?website=${url}`, { + method: "POST" + })).json() + if (bookmarkAsset){ + if (fetchWebsite.title) bookmarkAsset.props.title = fetchWebsite.title; + if (fetchWebsite.image) bookmarkAsset.props.image = fetchWebsite.image; + if (fetchWebsite.description) bookmarkAsset.props.description = fetchWebsite.description; + } if (!bookmarkAsset) throw Error("Could not create an asset"); asset = bookmarkAsset; } catch (e) { diff --git a/apps/web/app/api/editorai/route.ts b/apps/web/app/api/editorai/route.ts new file mode 100644 index 00000000..6ee0aed2 --- /dev/null +++ b/apps/web/app/api/editorai/route.ts @@ -0,0 +1,20 @@ +import type { NextRequest } from "next/server"; +import { ensureAuth } from "../ensureAuth"; + +export const runtime = "edge"; + +export async function POST(request: NextRequest) { + const d = await ensureAuth(request); + if (!d) { + return new Response("Unauthorized", { status: 401 }); + } + const res : {context: string, request: string} = await request.json() + + try { + const response = await fetch(`${process.env.BACKEND_BASE_URL}/api/editorai?context=${res.context}&request=${res.request}`); + const result = await response.json(); + return new Response(JSON.stringify(result)); + } catch (error) { + return new Response(`Error, ${error}`) + } +}
\ No newline at end of file diff --git a/apps/web/app/api/unfirlsite/route.ts b/apps/web/app/api/unfirlsite/route.ts new file mode 100644 index 00000000..4b8b4858 --- /dev/null +++ b/apps/web/app/api/unfirlsite/route.ts @@ -0,0 +1,134 @@ +import { load } from 'cheerio' +import { AwsClient } from "aws4fetch"; + +import type { NextRequest } from "next/server"; +import { ensureAuth } from "../ensureAuth"; + +export const runtime = "edge"; + +const r2 = new AwsClient({ + accessKeyId: process.env.R2_ACCESS_KEY_ID, + secretAccessKey: process.env.R2_SECRET_ACCESS_KEY, +}); + + +export async function POST(request: NextRequest) { + + const d = await ensureAuth(request); + if (!d) { + return new Response("Unauthorized", { status: 401 }); + } + + if ( + !process.env.R2_ACCESS_KEY_ID || + !process.env.R2_ACCOUNT_ID || + !process.env.R2_SECRET_ACCESS_KEY || + !process.env.R2_BUCKET_NAME + ) { + return new Response( + "Missing one or more R2 env variables: R2_ENDPOINT, R2_ACCESS_ID, R2_SECRET_KEY, R2_BUCKET_NAME. To get them, go to the R2 console, create and paste keys in a `.dev.vars` file in the root of this project.", + { status: 500 }, + ); + } + + const website = new URL(request.url).searchParams.get("website"); + + if (!website) { + return new Response("Missing website", { status: 400 }); + } + + const salt = () => Math.floor(Math.random() * 11); + const encodeWebsite = `${encodeURIComponent(website)}${salt()}`; + + try { + // this returns the og image, description and title of website + const response = await unfurl(website); + + if (!response.image){ + return new Response(JSON.stringify(response)) + } + + const imageUrl = await process.env.DEV_IMAGES.get(encodeWebsite) + if (imageUrl){ + return new Response(JSON.stringify({ + image: imageUrl, + title: response.title, + description: response.description, + })) + } + + const res = await fetch(`${response.image}`) + const image = await res.blob(); + + const url = new URL( + `https://${process.env.R2_BUCKET_NAME}.${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com` + ); + + url.pathname = encodeWebsite; + url.searchParams.set("X-Amz-Expires", "3600"); + + const signedPuturl = await r2.sign( + new Request(url, { + method: "PUT", + }), + { + aws: { signQuery: true }, + } + ); + await fetch(signedPuturl.url, { + method: 'PUT', + body: image, + }); + + await process.env.DEV_IMAGES.put(encodeWebsite, `${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`) + + return new Response(JSON.stringify({ + image: `${process.env.R2_PUBLIC_BUCKET_ADDRESS}/${encodeWebsite}`, + title: response.title, + description: response.description, + })); + + } catch (error) { + console.log(error) + return new Response(JSON.stringify({ + status: 500, + error: error, + })) + } + } + +export async function unfurl(url: string) { + const response = await fetch(url) + if (response.status >= 400) { + throw new Error(`Error fetching url: ${response.status}`) + } + const contentType = response.headers.get('content-type') + if (!contentType?.includes('text/html')) { + throw new Error(`Content-type not right: ${contentType}`) + } + + const content = await response.text() + const $ = load(content) + + const og: { [key: string]: string | undefined } = {} + const twitter: { [key: string]: string | undefined } = {} + + // @ts-ignore, it just works so why care of type safety if someone has better way go ahead + $('meta[property^=og:]').each((_, el) => (og[$(el).attr('property')!] = $(el).attr('content'))) + // @ts-ignore + $('meta[name^=twitter:]').each((_, el) => (twitter[$(el).attr('name')!] = $(el).attr('content'))) + + const title = og['og:title'] ?? twitter['twitter:title'] ?? $('title').text() ?? undefined + const description = + og['og:description'] ?? + twitter['twitter:description'] ?? + $('meta[name="description"]').attr('content') ?? + undefined + const image = og['og:image:secure_url'] ?? og['og:image'] ?? twitter['twitter:image'] ?? undefined + + return { + title, + description, + image, + } +} diff --git a/apps/web/cf-env.d.ts b/apps/web/cf-env.d.ts index e98c36cf..be5c991a 100644 --- a/apps/web/cf-env.d.ts +++ b/apps/web/cf-env.d.ts @@ -5,8 +5,9 @@ declare global { GOOGLE_CLIENT_SECRET: string; AUTH_SECRET: string; R2_ENDPOINT: string; - R2_ACCESS_ID: string; - R2_SECRET_KEY: string; + R2_ACCESS_KEY_ID: string; + R2_SECRET_ACCESS_KEY: string; + R2_PUBLIC_BUCKET_ADDRESS: string; R2_BUCKET_NAME: string; BACKEND_SECURITY_KEY: string; BACKEND_BASE_URL: string; |