From 5569aa278cc7f5580151e6f990740ce631b28410 Mon Sep 17 00:00:00 2001 From: Dhravya Date: Wed, 12 Jun 2024 23:37:06 -0500 Subject: =?UTF-8?q?chat=20UI=20and=20markdown=20renderer=20=E2=9A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/app/api/chat/route.ts | 103 +++++++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 44 deletions(-) (limited to 'apps/web/app/api') diff --git a/apps/web/app/api/chat/route.ts b/apps/web/app/api/chat/route.ts index 34099848..aba8784c 100644 --- a/apps/web/app/api/chat/route.ts +++ b/apps/web/app/api/chat/route.ts @@ -1,6 +1,11 @@ import { type NextRequest } from "next/server"; -import { ChatHistory } from "@repo/shared-types"; +import { + ChatHistory, + ChatHistoryZod, + convertChatHistoryList, +} from "@repo/shared-types"; import { ensureAuth } from "../ensureAuth"; +import { z } from "zod"; export const runtime = "edge"; @@ -15,59 +20,69 @@ export async function POST(req: NextRequest) { return new Response("Missing BACKEND_SECURITY_KEY", { status: 500 }); } - const query = new URL(req.url).searchParams.get("q"); - const spaces = new URL(req.url).searchParams.get("spaces"); + const url = new URL(req.url); - const sourcesOnly = - new URL(req.url).searchParams.get("sourcesOnly") ?? "false"; + const query = url.searchParams.get("q"); + const spaces = url.searchParams.get("spaces"); - const chatHistory = (await req.json()) as { - chatHistory: ChatHistory[]; - }; + const sourcesOnly = url.searchParams.get("sourcesOnly") ?? "false"; - console.log("CHathistory", chatHistory); + const chatHistory = await req.json(); - if (!query) { + if (!query || query.trim.length < 0) { return new Response(JSON.stringify({ message: "Invalid query" }), { status: 400, }); } - try { - const resp = await fetch( - `https://cf-ai-backend.dhravya.workers.dev/chat?q=${query}&user=${session.user.email ?? session.user.name}&sourcesOnly=${sourcesOnly}&spaces=${spaces}`, - { - headers: { - "X-Custom-Auth-Key": process.env.BACKEND_SECURITY_KEY!, - }, - method: "POST", - body: JSON.stringify({ - chatHistory: chatHistory.chatHistory ?? [], - }), + const validated = z + .object({ chatHistory: z.array(ChatHistoryZod) }) + .safeParse(chatHistory ?? []); + + if (!validated.success) { + return new Response( + JSON.stringify({ + message: "Invalid chat history", + error: validated.error, + }), + { status: 400 }, + ); + } + + const modelCompatible = await convertChatHistoryList( + validated.data.chatHistory, + ); + + const resp = await fetch( + `https://new-cf-ai-backend.dhravya.workers.dev/api/chat?query=${query}&user=${session.user.email}&sourcesOnly=${sourcesOnly}&spaces=${spaces}`, + { + headers: { + Authorization: `Bearer ${process.env.BACKEND_SECURITY_KEY}`, + "Content-Type": "application/json", }, + method: "POST", + body: JSON.stringify({ + chatHistory: modelCompatible, + }), + }, + ); + + console.log("sourcesOnly", sourcesOnly); + + if (sourcesOnly == "true") { + const data = await resp.json(); + console.log("data", data); + return new Response(JSON.stringify(data), { status: 200 }); + } + + if (resp.status !== 200 || !resp.ok) { + const errorData = await resp.text(); + console.log(errorData); + return new Response( + JSON.stringify({ message: "Error in CF function", error: errorData }), + { status: resp.status }, ); + } - console.log("sourcesOnly", sourcesOnly); - - if (sourcesOnly == "true") { - const data = await resp.json(); - console.log("data", data); - return new Response(JSON.stringify(data), { status: 200 }); - } - - if (resp.status !== 200 || !resp.ok) { - const errorData = await resp.json(); - console.log(errorData); - return new Response( - JSON.stringify({ message: "Error in CF function", error: errorData }), - { status: resp.status }, - ); - } - - // Stream the response back to the client - const { readable, writable } = new TransformStream(); - resp && resp.body!.pipeTo(writable); - - return new Response(readable, { status: 200 }); - } catch {} + return new Response(resp.body, { status: 200 }); } -- cgit v1.2.3 From 4daee14a8254b67152285a4a649f2c306c695a24 Mon Sep 17 00:00:00 2001 From: Dhravya Date: Sun, 16 Jun 2024 00:12:58 -0500 Subject: added way to save content and vectorize them. also refactored code and added a bypass to browser rendering --- apps/web/app/api/[...nextauth]/route.ts | 2 +- apps/web/app/api/chat/route.ts | 2 +- apps/web/app/api/ensureAuth.ts | 4 ++-- apps/web/app/api/getCount/route.ts | 8 ++++---- apps/web/app/api/me/route.ts | 4 ++-- apps/web/app/api/spaces/route.ts | 4 ++-- apps/web/app/api/store/route.ts | 31 ++++++++++++------------------- 7 files changed, 24 insertions(+), 31 deletions(-) (limited to 'apps/web/app/api') diff --git a/apps/web/app/api/[...nextauth]/route.ts b/apps/web/app/api/[...nextauth]/route.ts index 50807ab1..e19cc16e 100644 --- a/apps/web/app/api/[...nextauth]/route.ts +++ b/apps/web/app/api/[...nextauth]/route.ts @@ -1,2 +1,2 @@ -export { GET, POST } from "../../helpers/server/auth"; +export { GET, POST } from "../../../server/auth"; export const runtime = "edge"; diff --git a/apps/web/app/api/chat/route.ts b/apps/web/app/api/chat/route.ts index aba8784c..541ced34 100644 --- a/apps/web/app/api/chat/route.ts +++ b/apps/web/app/api/chat/route.ts @@ -54,7 +54,7 @@ export async function POST(req: NextRequest) { ); const resp = await fetch( - `https://new-cf-ai-backend.dhravya.workers.dev/api/chat?query=${query}&user=${session.user.email}&sourcesOnly=${sourcesOnly}&spaces=${spaces}`, + `${process.env.BACKEND_BASE_URL}/api/chat?query=${query}&user=${session.user.email}&sourcesOnly=${sourcesOnly}&spaces=${spaces}`, { headers: { Authorization: `Bearer ${process.env.BACKEND_SECURITY_KEY}`, diff --git a/apps/web/app/api/ensureAuth.ts b/apps/web/app/api/ensureAuth.ts index a1401a07..d2fbac0b 100644 --- a/apps/web/app/api/ensureAuth.ts +++ b/apps/web/app/api/ensureAuth.ts @@ -1,6 +1,6 @@ import { NextRequest } from "next/server"; -import { db } from "../helpers/server/db"; -import { sessions, users } from "../helpers/server/db/schema"; +import { db } from "../../server/db"; +import { sessions, users } from "../../server/db/schema"; import { eq } from "drizzle-orm"; export async function ensureAuth(req: NextRequest) { diff --git a/apps/web/app/api/getCount/route.ts b/apps/web/app/api/getCount/route.ts index f760c145..7cd2a2d3 100644 --- a/apps/web/app/api/getCount/route.ts +++ b/apps/web/app/api/getCount/route.ts @@ -1,6 +1,6 @@ -import { db } from "@/app/helpers/server/db"; +import { db } from "@/server/db"; import { and, eq, ne, sql } from "drizzle-orm"; -import { sessions, storedContent, users } from "@/app/helpers/server/db/schema"; +import { sessions, storedContent, users } from "@/server/db/schema"; import { type NextRequest, NextResponse } from "next/server"; import { ensureAuth } from "../ensureAuth"; @@ -20,7 +20,7 @@ export async function GET(req: NextRequest) { .from(storedContent) .where( and( - eq(storedContent.user, session.user.id), + eq(storedContent.userId, session.user.id), eq(storedContent.type, "twitter-bookmark"), ), ); @@ -32,7 +32,7 @@ export async function GET(req: NextRequest) { .from(storedContent) .where( and( - eq(storedContent.user, session.user.id), + eq(storedContent.userId, session.user.id), ne(storedContent.type, "twitter-bookmark"), ), ); diff --git a/apps/web/app/api/me/route.ts b/apps/web/app/api/me/route.ts index 20b6aece..621dcbfe 100644 --- a/apps/web/app/api/me/route.ts +++ b/apps/web/app/api/me/route.ts @@ -1,6 +1,6 @@ -import { db } from "@/app/helpers/server/db"; +import { db } from "@/server/db"; import { eq } from "drizzle-orm"; -import { sessions, users } from "@/app/helpers/server/db/schema"; +import { sessions, users } from "@/server/db/schema"; import { type NextRequest, NextResponse } from "next/server"; export const runtime = "edge"; diff --git a/apps/web/app/api/spaces/route.ts b/apps/web/app/api/spaces/route.ts index c46b02fc..cbed547d 100644 --- a/apps/web/app/api/spaces/route.ts +++ b/apps/web/app/api/spaces/route.ts @@ -1,5 +1,5 @@ -import { db } from "@/app/helpers/server/db"; -import { sessions, space, users } from "@/app/helpers/server/db/schema"; +import { db } from "@/server/db"; +import { sessions, space, users } from "@/server/db/schema"; import { eq } from "drizzle-orm"; import { NextRequest, NextResponse } from "next/server"; import { ensureAuth } from "../ensureAuth"; diff --git a/apps/web/app/api/store/route.ts b/apps/web/app/api/store/route.ts index f96f90cf..cb10db24 100644 --- a/apps/web/app/api/store/route.ts +++ b/apps/web/app/api/store/route.ts @@ -1,4 +1,4 @@ -import { db } from "@/app/helpers/server/db"; +import { db } from "@/server/db"; import { and, eq, sql, inArray } from "drizzle-orm"; import { contentToSpace, @@ -6,10 +6,12 @@ import { storedContent, users, space, -} from "@/app/helpers/server/db/schema"; +} from "@/server/db/schema"; import { type NextRequest, NextResponse } from "next/server"; -import { getMetaData } from "@/app/helpers/lib/get-metadata"; +import { getMetaData } from "@/lib/get-metadata"; import { ensureAuth } from "../ensureAuth"; +import { limit } from "@/app/actions/doers"; +import { LIMITS } from "@/lib/constants"; export const runtime = "edge"; @@ -33,22 +35,13 @@ export async function POST(req: NextRequest) { storeToSpaces = []; } - const count = await db - .select({ - count: sql`count(*)`.mapWith(Number), - }) - .from(storedContent) - .where( - and( - eq(storedContent.user, session.user.id), - eq(storedContent.type, "page"), - ), - ); - - if (count[0]!.count > 100) { + if (!(await limit(session.user.id))) { return NextResponse.json( - { message: "Error", error: "Limit exceeded" }, - { status: 499 }, + { + message: "Error: Ratelimit exceeded", + error: `You have exceeded the limit of ${LIMITS["page"]} pages.`, + }, + { status: 429 }, ); } @@ -62,7 +55,7 @@ export async function POST(req: NextRequest) { baseUrl: metadata.baseUrl, image: metadata.image, savedAt: new Date(), - user: session.user.id, + userId: session.user.id, }) .returning({ id: storedContent.id }); -- cgit v1.2.3 From 5cb5bcdbda329b6935291113a2034943da9a635a Mon Sep 17 00:00:00 2001 From: Dhravya Date: Sun, 16 Jun 2024 11:58:27 -0500 Subject: fixed a bug --- apps/web/app/api/chat/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'apps/web/app/api') diff --git a/apps/web/app/api/chat/route.ts b/apps/web/app/api/chat/route.ts index 541ced34..c19ce92b 100644 --- a/apps/web/app/api/chat/route.ts +++ b/apps/web/app/api/chat/route.ts @@ -54,7 +54,7 @@ export async function POST(req: NextRequest) { ); const resp = await fetch( - `${process.env.BACKEND_BASE_URL}/api/chat?query=${query}&user=${session.user.email}&sourcesOnly=${sourcesOnly}&spaces=${spaces}`, + `${process.env.BACKEND_BASE_URL}/api/chat?query=${query}&user=${session.user.id}&sourcesOnly=${sourcesOnly}&spaces=${spaces}`, { headers: { Authorization: `Bearer ${process.env.BACKEND_SECURITY_KEY}`, -- cgit v1.2.3 From c5361aa24df2cdf50a6189df0fb1493019ecbfc3 Mon Sep 17 00:00:00 2001 From: codetorso Date: Tue, 18 Jun 2024 03:54:20 -0600 Subject: Create Embeddings for Canvas --- apps/web/app/api/editorai/route.ts | 20 ++++++ apps/web/app/api/unfirlsite/route.ts | 134 +++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 apps/web/app/api/editorai/route.ts create mode 100644 apps/web/app/api/unfirlsite/route.ts (limited to 'apps/web/app/api') 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, + } +} -- cgit v1.2.3 From f28f566473d1087afa4fc1b8a51c0a811060b125 Mon Sep 17 00:00:00 2001 From: codetorso Date: Wed, 19 Jun 2024 02:12:47 -0600 Subject: Improve code, failed attempt at Streaming text --- apps/web/app/api/editorai/route.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'apps/web/app/api') diff --git a/apps/web/app/api/editorai/route.ts b/apps/web/app/api/editorai/route.ts index 6ee0aed2..43d8eb64 100644 --- a/apps/web/app/api/editorai/route.ts +++ b/apps/web/app/api/editorai/route.ts @@ -4,16 +4,17 @@ 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 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)); + return new Response(response.body, { status: 200 }); + // const result = await response.json(); + // return new Response(JSON.stringify(result)); } catch (error) { return new Response(`Error, ${error}`) } -- cgit v1.2.3 From a7cca293a66e7b042178fa54f182cb887c3e072f Mon Sep 17 00:00:00 2001 From: codetorso Date: Wed, 19 Jun 2024 08:37:30 -0600 Subject: Another Failed Attempt at streaming --- apps/web/app/api/editorai/route.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) (limited to 'apps/web/app/api') diff --git a/apps/web/app/api/editorai/route.ts b/apps/web/app/api/editorai/route.ts index 43d8eb64..5e1fbf0c 100644 --- a/apps/web/app/api/editorai/route.ts +++ b/apps/web/app/api/editorai/route.ts @@ -3,6 +3,8 @@ import { ensureAuth } from "../ensureAuth"; export const runtime = "edge"; +// ERROR #2 - This the the next function that calls the backend, I sometimes think this is redundency, but whatever +// I have commented the auth code, It should not work in development, but it still does sometimes export async function POST(request: NextRequest) { // const d = await ensureAuth(request); // if (!d) { @@ -11,10 +13,17 @@ export async function POST(request: NextRequest) { 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}`); - return new Response(response.body, { status: 200 }); - // const result = await response.json(); - // return new Response(JSON.stringify(result)); + const resp = await fetch(`${process.env.BACKEND_BASE_URL}/api/editorai?context=${res.context}&request=${res.request}`); + // this just checks if there are erros I am keeping it commented for you to better understand the important pieces + // if (resp.status !== 200 || !resp.ok) { + // const errorData = await resp.text(); + // console.log(errorData); + // return new Response( + // JSON.stringify({ message: "Error in CF function", error: errorData }), + // { status: resp.status }, + // ); + // } + return new Response(resp.body, { status: 200 }); } catch (error) { return new Response(`Error, ${error}`) } -- cgit v1.2.3 From a2f8a27e771f59380719f2e9997cd926d5d8e83e Mon Sep 17 00:00:00 2001 From: Dhravya Date: Sat, 22 Jun 2024 17:43:16 -0500 Subject: addeed chathistory functionality --- apps/web/app/api/chat/route.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) (limited to 'apps/web/app/api') diff --git a/apps/web/app/api/chat/route.ts b/apps/web/app/api/chat/route.ts index c19ce92b..25b1845b 100644 --- a/apps/web/app/api/chat/route.ts +++ b/apps/web/app/api/chat/route.ts @@ -1,9 +1,5 @@ import { type NextRequest } from "next/server"; -import { - ChatHistory, - ChatHistoryZod, - convertChatHistoryList, -} from "@repo/shared-types"; +import { ChatHistoryZod, convertChatHistoryList } from "@repo/shared-types"; import { ensureAuth } from "../ensureAuth"; import { z } from "zod"; @@ -49,6 +45,8 @@ export async function POST(req: NextRequest) { ); } + console.log("validated.data.chatHistory", validated.data.chatHistory); + const modelCompatible = await convertChatHistoryList( validated.data.chatHistory, ); -- cgit v1.2.3 From 445acf7c47b061107fdf1ce7bad5558ea2a909b3 Mon Sep 17 00:00:00 2001 From: Dhravya Date: Sat, 22 Jun 2024 18:23:05 -0500 Subject: cleanup --- apps/web/app/api/chat/route.ts | 5 ----- 1 file changed, 5 deletions(-) (limited to 'apps/web/app/api') diff --git a/apps/web/app/api/chat/route.ts b/apps/web/app/api/chat/route.ts index 25b1845b..d0e53066 100644 --- a/apps/web/app/api/chat/route.ts +++ b/apps/web/app/api/chat/route.ts @@ -45,8 +45,6 @@ export async function POST(req: NextRequest) { ); } - console.log("validated.data.chatHistory", validated.data.chatHistory); - const modelCompatible = await convertChatHistoryList( validated.data.chatHistory, ); @@ -65,11 +63,8 @@ export async function POST(req: NextRequest) { }, ); - console.log("sourcesOnly", sourcesOnly); - if (sourcesOnly == "true") { const data = await resp.json(); - console.log("data", data); return new Response(JSON.stringify(data), { status: 200 }); } -- cgit v1.2.3 From 9df975e88f3ebbed5a9f064b98e67c91f21cd39a Mon Sep 17 00:00:00 2001 From: Dhravya Date: Sun, 23 Jun 2024 17:24:10 -0500 Subject: added indexes and stuff --- apps/web/app/api/telegram/route.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 apps/web/app/api/telegram/route.ts (limited to 'apps/web/app/api') diff --git a/apps/web/app/api/telegram/route.ts b/apps/web/app/api/telegram/route.ts new file mode 100644 index 00000000..b80f6173 --- /dev/null +++ b/apps/web/app/api/telegram/route.ts @@ -0,0 +1,28 @@ +import { Bot, webhookCallback } from "grammy"; +import { User } from "grammy/types"; + +export const runtime = "edge"; + +if (!process.env.TELEGRAM_BOT_TOKEN) { + throw new Error("TELEGRAM_BOT_TOKEN is not defined"); +} + +console.log("Telegram bot activated"); +const token = process.env.TELEGRAM_BOT_TOKEN; + +const bot = new Bot(token); + +bot.command("start", async (ctx) => { + const user: User = (await ctx.getAuthor()).user; + await ctx.reply( + `Welcome to Supermemory bot, ${user.first_name}. I am here to help you remember things better.`, + ); +}); + +bot.on("message", async (ctx) => { + await ctx.reply( + "Hi there! This is Supermemory bot. I am here to help you remember things better.", + ); +}); + +export const POST = webhookCallback(bot, "std/http"); -- cgit v1.2.3 From 9f751ba89cc37516daa261bbae8723d5566a6602 Mon Sep 17 00:00:00 2001 From: Dhravya Date: Sun, 23 Jun 2024 17:54:05 -0500 Subject: feat: vector lookup and chat is twice as fast now --- apps/web/app/api/chat/route.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) (limited to 'apps/web/app/api') diff --git a/apps/web/app/api/chat/route.ts b/apps/web/app/api/chat/route.ts index d0e53066..d1730baa 100644 --- a/apps/web/app/api/chat/route.ts +++ b/apps/web/app/api/chat/route.ts @@ -1,5 +1,10 @@ import { type NextRequest } from "next/server"; -import { ChatHistoryZod, convertChatHistoryList } from "@repo/shared-types"; +import { + ChatHistory, + ChatHistoryZod, + convertChatHistoryList, + SourcesFromApi, +} from "@repo/shared-types"; import { ensureAuth } from "../ensureAuth"; import { z } from "zod"; @@ -23,7 +28,11 @@ export async function POST(req: NextRequest) { const sourcesOnly = url.searchParams.get("sourcesOnly") ?? "false"; - const chatHistory = await req.json(); + const jsonRequest = (await req.json()) as { + chatHistory: ChatHistory[]; + sources: SourcesFromApi[] | undefined; + }; + const { chatHistory, sources } = jsonRequest; if (!query || query.trim.length < 0) { return new Response(JSON.stringify({ message: "Invalid query" }), { @@ -31,9 +40,7 @@ export async function POST(req: NextRequest) { }); } - const validated = z - .object({ chatHistory: z.array(ChatHistoryZod) }) - .safeParse(chatHistory ?? []); + const validated = z.array(ChatHistoryZod).safeParse(chatHistory ?? []); if (!validated.success) { return new Response( @@ -45,9 +52,7 @@ export async function POST(req: NextRequest) { ); } - const modelCompatible = await convertChatHistoryList( - validated.data.chatHistory, - ); + const modelCompatible = await convertChatHistoryList(validated.data); const resp = await fetch( `${process.env.BACKEND_BASE_URL}/api/chat?query=${query}&user=${session.user.id}&sourcesOnly=${sourcesOnly}&spaces=${spaces}`, @@ -59,12 +64,13 @@ export async function POST(req: NextRequest) { method: "POST", body: JSON.stringify({ chatHistory: modelCompatible, + sources, }), }, ); if (sourcesOnly == "true") { - const data = await resp.json(); + const data = (await resp.json()) as SourcesFromApi; return new Response(JSON.stringify(data), { status: 200 }); } -- cgit v1.2.3 From 51dd5ec9dd35d87c2e93a8e5a64fa963caaa182a Mon Sep 17 00:00:00 2001 From: Dhravya Date: Sun, 23 Jun 2024 20:04:09 -0500 Subject: made and documented the telegram bot (HYPE) --- apps/web/app/api/telegram/readme.md | 55 +++++++++++++++++++++++ apps/web/app/api/telegram/route.ts | 89 +++++++++++++++++++++++++++++++++++-- 2 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 apps/web/app/api/telegram/readme.md (limited to 'apps/web/app/api') diff --git a/apps/web/app/api/telegram/readme.md b/apps/web/app/api/telegram/readme.md new file mode 100644 index 00000000..79bd6f1c --- /dev/null +++ b/apps/web/app/api/telegram/readme.md @@ -0,0 +1,55 @@ +## how telegram bot stuff works + +### Let's start with the important bit: authentication. + +We wanted to find a good and secure way to authenticate users, or "link their supermemory account" to their telegram account. This was kinda challenging - because the requirements were tight and privacy was a big concern. + +1. No personally identifiable information should be stored, except the user's telegram ID and supermemory email. +2. The link should be as simple as a click of a button +3. it should work two-ways: If the user signs in to the website first, or uses the telegram bot first. +4. The user should be able to unlink their account at any time. +5. Should be very, very easy to host the telegram bot. + +We started out by trying to mingle with next-auth credentials provider - but that was a dead end. It would _work_, but would be too hard for us to implement and maintain, and would be a very bad user experience (get the token, copy it, paste it, etc). + +So we decided to go with a simple, yet secure, way of doing it. + +### the solution + +Well, the solution is simple af, surprisingly. To meet all these requirements, + +First off, we used the `grammy` library to create a telegram bot that works using websockets. (so, it's hosted with the website, and doesn't need a separate server) + +Now, let's examine both the flows: + +1. User signs in to the website first +2. Saves a bunch of stuff +3. wants to link their telegram account + +and... + +1. User uses the telegram bot first +2. Saves a bunch of stuff +3. wants to see their stuff in the supermemory account. + +What we ended up doing is creating a simple, yet secure way - always require signin through supermemory.ai website. +And if the user comes from the telegram bot, we just redirect them to the website with a token in the URL. + +The token. + +The token is literally just their telegram ID, but encrypted. We use a simple encryption algorithm to encrypt the telegram ID, and then decrypt it on the website. + +Why encryption? Because we don't want any random person to link any telegram account with their user id. The encryption is also interesting, done using an algorithm called [hushh](https://github.com/dhravya/hushh) that I made a while ago. It's simple and secure and all that's really needed is a secret key. + +Once the user signs in, we take the decrypted token and link it to their account. And that's it. The user can now use the telegram bot to access their stuff. Because it's on the same codebase on the server side, it's very easy to make database calls and also calls to the cf-ai-backend to generate stuff. + +### Natural language generation + +I wanted to add this: the bot actually does both - adding content and talking to the user - at the same time. + +How tho? +We use function calling in the backend repo smartly to decide what the user's intent would be. So, i can literally send the message "yo, can you remember this? (with anything else, can even be a URL!)" and the bot will understand that it's a command to add content. + +orr, i can send "hey, can you tell me about the time i went to the beach?" and the bot will understand that it's a command to get content. + +it's pretty cool. function calling using a cheap model works very well. diff --git a/apps/web/app/api/telegram/route.ts b/apps/web/app/api/telegram/route.ts index b80f6173..065a102a 100644 --- a/apps/web/app/api/telegram/route.ts +++ b/apps/web/app/api/telegram/route.ts @@ -1,3 +1,7 @@ +import { db } from "@/server/db"; +import { storedContent, users } from "@/server/db/schema"; +import { cipher, decipher } from "@/server/encrypt"; +import { eq } from "drizzle-orm"; import { Bot, webhookCallback } from "grammy"; import { User } from "grammy/types"; @@ -12,17 +16,96 @@ const token = process.env.TELEGRAM_BOT_TOKEN; const bot = new Bot(token); +const getUserByTelegramId = async (telegramId: string) => { + return await db.query.users + .findFirst({ + where: eq(users.telegramId, telegramId), + }) + .execute(); +}; + bot.command("start", async (ctx) => { const user: User = (await ctx.getAuthor()).user; + const cipherd = cipher(user.id.toString()); await ctx.reply( - `Welcome to Supermemory bot, ${user.first_name}. I am here to help you remember things better.`, + `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your accont: http://localhost:3000/signin?telegramUser=${cipherd}`, ); }); bot.on("message", async (ctx) => { - await ctx.reply( - "Hi there! This is Supermemory bot. I am here to help you remember things better.", + const user: User = (await ctx.getAuthor()).user; + const cipherd = cipher(user.id.toString()); + + const dbUser = await getUserByTelegramId(user.id.toString()); + + if (!dbUser) { + await ctx.reply( + `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your accont: http://localhost:3000/signin?telegramUser=${cipherd}`, + ); + + return; + } + + const message = await ctx.reply("I'm thinking..."); + + const response = await fetch( + `${process.env.BACKEND_BASE_URL}/api/autoChatOrAdd?query=${ctx.message.text}&user=${dbUser.id}`, + { + method: "POST", + headers: { + Authorization: "Bearer " + process.env.BACKEND_SECURITY_KEY, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + // TODO: we can use the conversations API to get the last 5 messages + // get chatHistory from this conversation. + // Basically the last 5 messages between the user and the assistant. + // In ths form of [{role: 'user' | 'assistant', content: string}] + // https://grammy.dev/plugins/conversations + chatHistory: [], + }), + }, ); + + if (response.status !== 200) { + console.log("Failed to get response from backend"); + console.log(response.status); + console.log(await response.text()); + await ctx.reply( + "Sorry, I am not able to process your request at the moment.", + ); + return; + } + + const data = (await response.json()) as { + status: string; + response: string; + contentAdded: { + type: string; + content: string; + url: string; + }; + }; + + // TODO: we might want to enrich this data with more information + if (data.contentAdded) { + await db + .insert(storedContent) + .values({ + content: data.contentAdded.content, + title: `${data.contentAdded.content.slice(0, 30)}... (Added from chatbot)`, + description: "", + url: data.contentAdded.url, + baseUrl: data.contentAdded.url, + image: "", + savedAt: new Date(), + userId: dbUser.id, + type: data.contentAdded.type, + }) + .returning({ id: storedContent.id }); + } + + await ctx.api.editMessageText(ctx.chat.id, message.message_id, data.response); }); export const POST = webhookCallback(bot, "std/http"); -- cgit v1.2.3 From 75585fd97d7abaf68e0c314f41d0cef414a57b1e Mon Sep 17 00:00:00 2001 From: Dhravya Date: Sun, 23 Jun 2024 20:45:05 -0500 Subject: some important housekeeping, crushed all build errors --- apps/web/app/api/store/route.ts | 114 ------------------------ apps/web/app/api/telegram/route.ts | 18 ++-- apps/web/app/api/unfirlsite/route.ts | 162 ++++++++++++++++++++--------------- 3 files changed, 100 insertions(+), 194 deletions(-) delete mode 100644 apps/web/app/api/store/route.ts (limited to 'apps/web/app/api') diff --git a/apps/web/app/api/store/route.ts b/apps/web/app/api/store/route.ts deleted file mode 100644 index cb10db24..00000000 --- a/apps/web/app/api/store/route.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { db } from "@/server/db"; -import { and, eq, sql, inArray } from "drizzle-orm"; -import { - contentToSpace, - sessions, - storedContent, - users, - space, -} from "@/server/db/schema"; -import { type NextRequest, NextResponse } from "next/server"; -import { getMetaData } from "@/lib/get-metadata"; -import { ensureAuth } from "../ensureAuth"; -import { limit } from "@/app/actions/doers"; -import { LIMITS } from "@/lib/constants"; - -export const runtime = "edge"; - -export async function POST(req: NextRequest) { - const session = await ensureAuth(req); - - if (!session) { - return new Response("Unauthorized", { status: 401 }); - } - - const data = (await req.json()) as { - pageContent: string; - url: string; - spaces?: string[]; - }; - - const metadata = await getMetaData(data.url); - let storeToSpaces = data.spaces; - - if (!storeToSpaces) { - storeToSpaces = []; - } - - if (!(await limit(session.user.id))) { - return NextResponse.json( - { - message: "Error: Ratelimit exceeded", - error: `You have exceeded the limit of ${LIMITS["page"]} pages.`, - }, - { status: 429 }, - ); - } - - const rep = await db - .insert(storedContent) - .values({ - content: data.pageContent, - title: metadata.title, - description: metadata.description, - url: data.url, - baseUrl: metadata.baseUrl, - image: metadata.image, - savedAt: new Date(), - userId: session.user.id, - }) - .returning({ id: storedContent.id }); - - const id = rep[0]?.id; - - if (!id) { - return NextResponse.json( - { message: "Error", error: "Error in CF function" }, - { status: 500 }, - ); - } - - if (storeToSpaces.length > 0) { - const spaceData = await db - .select() - .from(space) - .where( - and( - inArray(space.name, storeToSpaces ?? []), - eq(space.user, session.user.id), - ), - ) - .all(); - - await Promise.all([ - spaceData.forEach(async (space) => { - await db - .insert(contentToSpace) - .values({ contentId: id, spaceId: space.id }); - }), - ]); - } - - const res = (await Promise.race([ - fetch("https://cf-ai-backend.dhravya.workers.dev/add", { - method: "POST", - headers: { - "X-Custom-Auth-Key": process.env.BACKEND_SECURITY_KEY, - }, - body: JSON.stringify({ ...data, user: session.user.email }), - }), - new Promise((_, reject) => - setTimeout(() => reject(new Error("Request timed out")), 40000), - ), - ])) as Response; - - if (res.status !== 200) { - console.log(res.status, res.statusText); - return NextResponse.json( - { message: "Error", error: "Error in CF function" }, - { status: 500 }, - ); - } - - return NextResponse.json({ message: "OK", data: "Success" }, { status: 200 }); -} diff --git a/apps/web/app/api/telegram/route.ts b/apps/web/app/api/telegram/route.ts index 065a102a..b0d05655 100644 --- a/apps/web/app/api/telegram/route.ts +++ b/apps/web/app/api/telegram/route.ts @@ -1,6 +1,6 @@ import { db } from "@/server/db"; import { storedContent, users } from "@/server/db/schema"; -import { cipher, decipher } from "@/server/encrypt"; +import { cipher } from "@/server/encrypt"; import { eq } from "drizzle-orm"; import { Bot, webhookCallback } from "grammy"; import { User } from "grammy/types"; @@ -16,16 +16,9 @@ const token = process.env.TELEGRAM_BOT_TOKEN; const bot = new Bot(token); -const getUserByTelegramId = async (telegramId: string) => { - return await db.query.users - .findFirst({ - where: eq(users.telegramId, telegramId), - }) - .execute(); -}; - bot.command("start", async (ctx) => { const user: User = (await ctx.getAuthor()).user; + const cipherd = cipher(user.id.toString()); await ctx.reply( `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your accont: http://localhost:3000/signin?telegramUser=${cipherd}`, @@ -34,9 +27,14 @@ bot.command("start", async (ctx) => { bot.on("message", async (ctx) => { const user: User = (await ctx.getAuthor()).user; + const cipherd = cipher(user.id.toString()); - const dbUser = await getUserByTelegramId(user.id.toString()); + const dbUser = await db.query.users + .findFirst({ + where: eq(users.telegramId, user.id.toString()), + }) + .execute(); if (!dbUser) { await ctx.reply( diff --git a/apps/web/app/api/unfirlsite/route.ts b/apps/web/app/api/unfirlsite/route.ts index 4b8b4858..87f5348e 100644 --- a/apps/web/app/api/unfirlsite/route.ts +++ b/apps/web/app/api/unfirlsite/route.ts @@ -1,4 +1,4 @@ -import { load } from 'cheerio' +import { load } from "cheerio"; import { AwsClient } from "aws4fetch"; import type { NextRequest } from "next/server"; @@ -6,14 +6,60 @@ 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 r2 = new AwsClient({ + accessKeyId: process.env.R2_ACCESS_KEY_ID, + secretAccessKey: process.env.R2_SECRET_ACCESS_KEY, + }); + + 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, + }; + } -export async function POST(request: NextRequest) { - const d = await ensureAuth(request); if (!d) { return new Response("Unauthorized", { status: 401 }); @@ -32,7 +78,7 @@ export async function POST(request: NextRequest) { } const website = new URL(request.url).searchParams.get("website"); - + if (!website) { return new Response("Missing website", { status: 400 }); } @@ -41,27 +87,33 @@ export async function POST(request: NextRequest) { const encodeWebsite = `${encodeURIComponent(website)}${salt()}`; try { - // this returns the og image, description and title of website + // this returns the og image, description and title of website const response = await unfurl(website); - if (!response.image){ - return new Response(JSON.stringify(response)) + 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, - })) + if (!process.env.DEV_IMAGES) { + return new Response("Missing DEV_IMAGES namespace.", { status: 500 }); } - const res = await fetch(`${response.image}`) + 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` + `https://${process.env.R2_BUCKET_NAME}.${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`, ); url.pathname = encodeWebsite; @@ -73,62 +125,32 @@ export async function POST(request: NextRequest) { }), { aws: { signQuery: true }, - } + }, ); await fetch(signedPuturl.url, { - method: 'PUT', + 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, - })); + 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, - })) - } + 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, - } } -- cgit v1.2.3 From 4c86f1eaa2866ee645b33a92e742726a77f354f1 Mon Sep 17 00:00:00 2001 From: Dhravya Date: Sun, 23 Jun 2024 20:48:38 -0500 Subject: ts-ignore at the right place --- apps/web/app/api/unfirlsite/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'apps/web/app/api') diff --git a/apps/web/app/api/unfirlsite/route.ts b/apps/web/app/api/unfirlsite/route.ts index 87f5348e..36e47987 100644 --- a/apps/web/app/api/unfirlsite/route.ts +++ b/apps/web/app/api/unfirlsite/route.ts @@ -28,12 +28,12 @@ export async function POST(request: NextRequest) { 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( + // @ts-ignore, it just works so why care of type safety if someone has better way go ahead (_, el) => (og[$(el).attr("property")!] = $(el).attr("content")), ); - // @ts-ignore $("meta[name^=twitter:]").each( + // @ts-ignore (_, el) => (twitter[$(el).attr("name")!] = $(el).attr("content")), ); -- cgit v1.2.3 From e7a80ffd441ea93f499d60aec240d280b5501d5a Mon Sep 17 00:00:00 2001 From: Dhravya Date: Mon, 24 Jun 2024 20:40:17 -0500 Subject: drizzle-orm in dependencies, prod wrangler.toml --- apps/web/app/api/telegram/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'apps/web/app/api') diff --git a/apps/web/app/api/telegram/route.ts b/apps/web/app/api/telegram/route.ts index b0d05655..59177c4e 100644 --- a/apps/web/app/api/telegram/route.ts +++ b/apps/web/app/api/telegram/route.ts @@ -21,7 +21,7 @@ bot.command("start", async (ctx) => { const cipherd = cipher(user.id.toString()); await ctx.reply( - `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your accont: http://localhost:3000/signin?telegramUser=${cipherd}`, + `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your accont: https://beta.supermemory.ai/signin?telegramUser=${cipherd}`, ); }); @@ -38,7 +38,7 @@ bot.on("message", async (ctx) => { if (!dbUser) { await ctx.reply( - `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your accont: http://localhost:3000/signin?telegramUser=${cipherd}`, + `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your accont: https://beta.supermemory.ai/signin?telegramUser=${cipherd}`, ); return; -- cgit v1.2.3 From dcf5cffdf0a90318f9408115b7446c5669c3611a Mon Sep 17 00:00:00 2001 From: Dhravya Date: Mon, 24 Jun 2024 22:11:05 -0500 Subject: use gpt-4o again, responses are considerably better. --- apps/web/app/api/telegram/route.ts | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'apps/web/app/api') diff --git a/apps/web/app/api/telegram/route.ts b/apps/web/app/api/telegram/route.ts index 59177c4e..000b473a 100644 --- a/apps/web/app/api/telegram/route.ts +++ b/apps/web/app/api/telegram/route.ts @@ -107,3 +107,7 @@ bot.on("message", async (ctx) => { }); export const POST = webhookCallback(bot, "std/http"); + +export const GET = async () => { + return new Response("OK", { status: 200 }); +}; -- cgit v1.2.3 From 657ced2a380d470a11a47b6c2057ffa5c102baba Mon Sep 17 00:00:00 2001 From: Dhravya Date: Mon, 24 Jun 2024 23:31:44 -0500 Subject: added toast for loading and fixed chathistory --- apps/web/app/api/telegram/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'apps/web/app/api') diff --git a/apps/web/app/api/telegram/route.ts b/apps/web/app/api/telegram/route.ts index 000b473a..c6c673b2 100644 --- a/apps/web/app/api/telegram/route.ts +++ b/apps/web/app/api/telegram/route.ts @@ -21,7 +21,7 @@ bot.command("start", async (ctx) => { const cipherd = cipher(user.id.toString()); await ctx.reply( - `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your accont: https://beta.supermemory.ai/signin?telegramUser=${cipherd}`, + `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your account: https://beta.supermemory.ai/signin?telegramUser=${cipherd}`, ); }); @@ -38,7 +38,7 @@ bot.on("message", async (ctx) => { if (!dbUser) { await ctx.reply( - `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your accont: https://beta.supermemory.ai/signin?telegramUser=${cipherd}`, + `Welcome to Supermemory bot. I am here to help you remember things better. Click here to create and link your account: https://beta.supermemory.ai/signin?telegramUser=${cipherd}`, ); return; -- cgit v1.2.3