mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-10 22:41:17 +00:00
added browser rendering for getting clean page content
This commit is contained in:
commit
74206a9cf8
9 changed files with 92 additions and 13 deletions
1
apps/cf-ai-backend/src/env.d.ts
vendored
1
apps/cf-ai-backend/src/env.d.ts
vendored
|
|
@ -6,6 +6,7 @@ interface Env {
|
|||
GOOGLE_AI_API_KEY: string;
|
||||
MY_QUEUE: Queue<TweetData[]>;
|
||||
KV: KVNamespace;
|
||||
MYBROWSER: BrowserWorker;
|
||||
}
|
||||
|
||||
interface TweetData {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import * as apiQuery from './routes/query';
|
|||
import * as apiAsk from './routes/ask';
|
||||
import * as apiChat from './routes/chat';
|
||||
import * as apiBatchUploadTweets from './routes/batchUploadTweets';
|
||||
import * as apiGetPageContent from './routes/getPageContent';
|
||||
import { OpenAIEmbeddings } from './OpenAIEmbedder';
|
||||
import { GenerativeModel } from '@google/generative-ai';
|
||||
import { Request } from '@cloudflare/workers-types';
|
||||
|
|
@ -28,6 +29,7 @@ routeMap.set('/ask', apiAsk);
|
|||
routeMap.set('/chat', apiChat);
|
||||
|
||||
routeMap.set('/batchUploadTweets', apiBatchUploadTweets);
|
||||
routeMap.set('/getPageContent', apiGetPageContent);
|
||||
|
||||
// Add more route mappings as needed
|
||||
// routeMap.set('/api/otherRoute', { ... });
|
||||
|
|
|
|||
31
apps/cf-ai-backend/src/routes/getPageContent.ts
Normal file
31
apps/cf-ai-backend/src/routes/getPageContent.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { GenerativeModel } from '@google/generative-ai';
|
||||
import { OpenAIEmbeddings } from '../OpenAIEmbedder';
|
||||
import { CloudflareVectorizeStore } from '@langchain/cloudflare';
|
||||
import { Request } from '@cloudflare/workers-types';
|
||||
import puppeteer from '@cloudflare/puppeteer';
|
||||
|
||||
export async function GET(request: Request, _: CloudflareVectorizeStore, embeddings: OpenAIEmbeddings, model: GenerativeModel, env?: Env) {
|
||||
const { searchParams } = new URL(request.url);
|
||||
let url = searchParams.get('url');
|
||||
let img: Buffer;
|
||||
if (url) {
|
||||
url = new URL(url).toString(); // normalize
|
||||
const browser = await puppeteer.launch(env?.MYBROWSER);
|
||||
const page = await browser.newPage();
|
||||
await page.goto(url);
|
||||
|
||||
// Innertext of content
|
||||
const contentElement = await page.$('body');
|
||||
const content = await page.evaluate((element) => element.innerText, contentElement);
|
||||
|
||||
await browser.close();
|
||||
|
||||
return new Response(content, {
|
||||
headers: {
|
||||
'content-type': 'text/html',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return new Response('Please add an ?url=https://example.com/ parameter');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
name = "cf-ai-backend"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2024-02-23"
|
||||
compatibility_flags = ['nodejs_compat']
|
||||
|
||||
[[vectorize]]
|
||||
binding = "VECTORIZE_INDEX"
|
||||
|
|
@ -19,6 +20,13 @@ binding = "AI"
|
|||
[[kv_namespaces]]
|
||||
binding = "KV"
|
||||
id = "37a90353da63401e84e20e71165531d0"
|
||||
preview_id = "c58b6202814f4224acea97627d0c18aa"
|
||||
|
||||
[browser]
|
||||
binding = "MYBROWSER"
|
||||
|
||||
[placement]
|
||||
mode = "smart"
|
||||
|
||||
# Variable bindings. These are arbitrary, plaintext strings (similar to environment variables)
|
||||
# Note: Use secrets to store sensitive data.
|
||||
|
|
|
|||
|
|
@ -16,10 +16,12 @@ import { Loader, Plus, X } from "lucide-react";
|
|||
import { StoredContent } from "@/server/db/schema";
|
||||
import { cleanUrl } from "@/lib/utils";
|
||||
import { motion } from "framer-motion";
|
||||
import { getMetaData } from "@/server/helpers";
|
||||
|
||||
export function AddMemoryPage() {
|
||||
export function AddMemoryPage({ closeDialog }: { closeDialog: () => void }) {
|
||||
const { addMemory } = useMemory();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [url, setUrl] = useState("");
|
||||
const [selectedSpacesId, setSelectedSpacesId] = useState<number[]>([]);
|
||||
|
||||
|
|
@ -37,38 +39,59 @@ export function AddMemoryPage() {
|
|||
placeholder="Enter the URL of the page"
|
||||
type="url"
|
||||
data-modal-autofocus
|
||||
className="bg-rgray-4 mt-2 w-full"
|
||||
className="bg-rgray-4 mt-2 w-full disabled:cursor-not-allowed disabled:opacity-70"
|
||||
value={url}
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
disabled={loading}
|
||||
/>
|
||||
<DialogFooter>
|
||||
<FilterSpaces
|
||||
selectedSpaces={selectedSpacesId}
|
||||
setSelectedSpaces={setSelectedSpacesId}
|
||||
className="hover:bg-rgray-5 mr-auto bg-white/5"
|
||||
className="hover:bg-rgray-5 mr-auto bg-white/5 disabled:cursor-not-allowed disabled:opacity-70"
|
||||
name={"Spaces"}
|
||||
disabled={loading}
|
||||
/>
|
||||
<button
|
||||
type={"submit"}
|
||||
disabled={loading}
|
||||
onClick={async () => {
|
||||
// @Dhravya this is adding a memory with insufficient information fix pls
|
||||
setLoading(true);
|
||||
const metadata = await getMetaData(url);
|
||||
await addMemory(
|
||||
{
|
||||
title: url,
|
||||
title: metadata.title,
|
||||
description: metadata.description,
|
||||
content: "",
|
||||
type: "page",
|
||||
url: url,
|
||||
image: "/icons/logo_without_bg.png",
|
||||
image: metadata.image,
|
||||
savedAt: new Date(),
|
||||
},
|
||||
selectedSpacesId,
|
||||
);
|
||||
closeDialog();
|
||||
}}
|
||||
className="bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2"
|
||||
className="bg-rgray-4 hover:bg-rgray-5 focus-visible:bg-rgray-5 focus-visible:ring-rgray-7 relative rounded-md px-4 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
|
||||
>
|
||||
Add
|
||||
<motion.div
|
||||
initial={{ x: "-50%", y: "-100%" }}
|
||||
animate={loading && { y: "-50%", x: "-50%", opacity: 1 }}
|
||||
className="absolute left-1/2 top-1/2 -translate-x-1/2 translate-y-[-100%] opacity-0"
|
||||
>
|
||||
<Loader className="text-rgray-11 h-5 w-5 animate-spin" />
|
||||
</motion.div>
|
||||
<motion.div
|
||||
initial={{ y: "0%" }}
|
||||
animate={loading && { opacity: 0, y: "30%" }}
|
||||
>
|
||||
Add
|
||||
</motion.div>
|
||||
</button>
|
||||
<DialogClose className="hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2">
|
||||
<DialogClose
|
||||
disabled={loading}
|
||||
className="hover:bg-rgray-4 focus-visible:bg-rgray-4 focus-visible:ring-rgray-7 rounded-md px-3 py-2 ring-transparent transition focus-visible:outline-none focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-70"
|
||||
>
|
||||
Cancel
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
|
|
|
|||
|
|
@ -210,7 +210,15 @@ export function MemoryItem({ id, title, image, type }: StoredContent) {
|
|||
|
||||
<div className="flex h-24 w-24 items-center justify-center">
|
||||
{type === "page" ? (
|
||||
<img className="h-16 w-16" id={id.toString()} src={image!} />
|
||||
<img
|
||||
className="h-16 w-16"
|
||||
id={id.toString()}
|
||||
src={image!}
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).src =
|
||||
"/icons/white_without_bg.png";
|
||||
}}
|
||||
/>
|
||||
) : type === "note" ? (
|
||||
<div className="bg-rgray-4 flex items-center justify-center rounded-md p-2 shadow-md">
|
||||
<Text className="h-10 w-10" />
|
||||
|
|
@ -406,7 +414,7 @@ export function SpaceMoreButton({
|
|||
className="focus:bg-red-100 focus:text-red-400 dark:focus:bg-red-100/10"
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" strokeWidth={1.5} />
|
||||
Move to Trash
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DialogTrigger>
|
||||
</DropdownMenuContent>
|
||||
|
|
@ -471,7 +479,7 @@ export function AddMemoryModal({
|
|||
className="w-max max-w-[auto]"
|
||||
>
|
||||
{type === "page" ? (
|
||||
<AddMemoryPage />
|
||||
<AddMemoryPage closeDialog={() => setIsDialogOpen(false)} />
|
||||
) : type === "note" ? (
|
||||
<NoteAddPage closeDialog={() => setIsDialogOpen(false)} />
|
||||
) : type === "space" ? (
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
"use server";
|
||||
import * as cheerio from "cheerio";
|
||||
|
||||
export async function getMetaData(url: string) {
|
||||
|
|
|
|||
|
|
@ -17,4 +17,7 @@ type = "ratelimit"
|
|||
namespace_id = "1001"
|
||||
|
||||
# 25 requests per 10 seconds
|
||||
simple = { limit = 25, period = 10 }
|
||||
simple = { limit = 25, period = 10 }
|
||||
|
||||
[placement]
|
||||
mode = "smart"
|
||||
|
|
@ -41,6 +41,7 @@
|
|||
"@auth/drizzle-adapter": "^0.7.0",
|
||||
"@cloudflare/ai": "^1.0.52",
|
||||
"@cloudflare/next-on-pages-next-dev": "^0.0.1",
|
||||
"@cloudflare/puppeteer": "^0.0.6",
|
||||
"@crxjs/vite-plugin": "^1.0.14",
|
||||
"@google/generative-ai": "^0.3.1",
|
||||
"@heroicons/react": "^2.1.1",
|
||||
|
|
@ -53,6 +54,7 @@
|
|||
"ai": "^3.0.0",
|
||||
"better-sqlite3": "^9.4.3",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"cloudflare": "^3.0.0",
|
||||
"dotenv-cli": "^7.3.0",
|
||||
"drizzle-orm": "^0.29.4",
|
||||
"eslint-plugin-next-on-pages": "^1.9.0",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue