Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

July launch PR #55

Closed
wants to merge 90 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
f3d7f89
Add Extension Template
CodeTorso Jun 2, 2024
8ecbde0
Implemented Highlight in Extension
CodeTorso Jun 3, 2024
7ceaddf
Implement extension
CodeTorso Jun 4, 2024
004c952
Add Dynamic Island
CodeTorso Jun 8, 2024
99659df
before color edits
CodeTorso Jun 9, 2024
743976d
Add Memories, Canvas, Editor
CodeTorso Jun 9, 2024
04b6add
AI code refactored
CodeTorso Jun 11, 2024
a341908
Adding keyboard shortcuts, responsive design that 99% don't need but …
CodeTorso Jun 11, 2024
5ab5d8f
minor changes and improvements
Dhravya Jun 12, 2024
aabdef4
some documentation for backend
Dhravya Jun 12, 2024
03c9fb0
full screen mode implemented
CodeTorso Jun 12, 2024
1f6d419
Icons now in our website integrated
CodeTorso Jun 12, 2024
1619488
Merge pull request #56 from CodeTorso/codetorso
CodeTorso Jun 12, 2024
ef682da
smoothening animatio
CodeTorso Jun 12, 2024
53f0856
Merge pull request #57 from CodeTorso/codetorso
CodeTorso Jun 12, 2024
1c1d14b
Merge pull request #58 from CodeTorso/v2
CodeTorso Jun 12, 2024
d376dd1
Revert "Add code for extension"
CodeTorso Jun 13, 2024
778ddec
fix backend
Dhravya Jun 13, 2024
a711d1b
added zod validation to embedQuery
Dhravya Jun 13, 2024
f59cede
Merge branch 'codetorso' of https://github.com/Dhravya/supermemory in…
Dhravya Jun 13, 2024
5569aa2
chat UI and markdown renderer ⚡
Dhravya Jun 13, 2024
a20163d
make header and menu fixed instead of absolute to prevent movement on…
Dhravya Jun 13, 2024
d5a8117
Merge pull request #59 from Dhravya/revert-58-v2
CodeTorso Jun 13, 2024
0fb924d
small stuff
Dhravya Jun 13, 2024
80a2c6b
omg
Dhravya Jun 14, 2024
ac3b161
Merge branch 'codetorso' of https://github.com/Dhravya/supermemory in…
Dhravya Jun 14, 2024
9f0fb14
[MIGRATION REQUIRED]Data fetchers and other server actions, spaces cr…
Dhravya Jun 15, 2024
84455ad
getspaces and other features integrated with the backend
Dhravya Jun 15, 2024
e755aa7
chore: Remove unused variables and dependencies
kartikk-k Jun 15, 2024
8ecfc65
Merge branch 'codetorso' of https://github.com/Dhravya/supermemory in…
kartikk-k Jun 15, 2024
4daee14
added way to save content and vectorize them. also refactored code an…
Dhravya Jun 16, 2024
e0b83ad
Update Setup Guide
CodeTorso Jun 16, 2024
39ba654
improve: /home page
kartikk-k Jun 16, 2024
4cd16ec
ui update: /chat
kartikk-k Jun 16, 2024
375609d
form to add content [PENDING LOADING STATE]
Dhravya Jun 16, 2024
a68d7f0
Merge branch 'codetorso' of https://github.com/Dhravya/supermemory in…
Dhravya Jun 16, 2024
5cb5bcd
fixed a bug
Dhravya Jun 16, 2024
2ffb054
proper URLs
Dhravya Jun 16, 2024
a9f5ebc
include metadata in response, add type to metadata
Dhravya Jun 16, 2024
c5b31e5
use a dev vectorize database to prevent pollution
Dhravya Jun 16, 2024
62c4d88
add: animated query input
kartikk-k Jun 16, 2024
9588768
added image support in the backend
Dhravya Jun 16, 2024
a87030b
OCR try
Dhravya Jun 16, 2024
0f3d5c4
store full info
Dhravya Jun 16, 2024
a4c835e
added sources to the response
Dhravya Jun 17, 2024
76c48cc
add number of chunks to the respnose and only show unique values
Dhravya Jun 17, 2024
be71655
gpt-4o is already a default model, so no error-checking required
CodeTorso Jun 17, 2024
88289d1
add js docs
CodeTorso Jun 17, 2024
5af20f7
Merge pull request #70 from CodeTorso/codetorso
Dhravya Jun 17, 2024
957f131
vector duplication no longer an issue. support for querying with mult…
Dhravya Jun 18, 2024
066833a
include all selected spaces in the fetch call to backend
Dhravya Jun 18, 2024
6aa8dc4
Merge pull request #71 from Dhravya/vector-deduplication
Dhravya Jun 18, 2024
242cbf7
Add Editor Endpoint
CodeTorso Jun 18, 2024
251b6b4
codetorso pull remote
CodeTorso Jun 18, 2024
6cfb533
EditorAI integrated! (1/4)
CodeTorso Jun 18, 2024
c5361aa
Create Embeddings for Canvas
CodeTorso Jun 18, 2024
09af2ec
darkmode by default
Dhravya Jun 18, 2024
62054df
added logic to handle justification properly
Dhravya Jun 18, 2024
c704541
fix: Cta component background color
MaheshtheDev Jun 19, 2024
898cb32
Merge pull request #75 from MaheshtheDev/fix/cta-bg
Dhravya Jun 19, 2024
770eb99
Drag and Drop in Canvas!
CodeTorso Jun 19, 2024
28a257f
Add Drag & Drop code
CodeTorso Jun 19, 2024
f28f566
Improve code, failed attempt at Streaming text
CodeTorso Jun 19, 2024
a7cca29
Another Failed Attempt at streaming
CodeTorso Jun 19, 2024
e4c6c1a
works
Dhravya Jun 20, 2024
074ea24
added multi-turn conversations
Dhravya Jun 20, 2024
af90b96
Merge branch 'codetorso' into kartik
CodeTorso Jun 20, 2024
2b2ec6f
Merge pull request #76 from Dhravya/kartik
CodeTorso Jun 20, 2024
a5f384e
Fine tuning canvas (1/2)
CodeTorso Jun 22, 2024
2ac315c
improve canvas drag and drop make sidepanel work
CodeTorso Jun 22, 2024
becb306
Unreadable Canvas code, jk 😂
CodeTorso Jun 22, 2024
3a97c2a
delete packagelock
CodeTorso Jun 22, 2024
a2f8a27
addeed chathistory functionality
Dhravya Jun 22, 2024
02d7f08
merged code
Dhravya Jun 22, 2024
5140a4d
deleted chatpage
Dhravya Jun 22, 2024
445acf7
cleanup
Dhravya Jun 22, 2024
47e7528
Merge pull request #79 from Dhravya/chathistory
Dhravya Jun 23, 2024
4b6a932
fix interactive canvas (2/2)
CodeTorso Jun 23, 2024
4bdc819
centered layout
CodeTorso Jun 23, 2024
86e2f2a
clean code+ user canvas (1/2)
CodeTorso Jun 23, 2024
9df975e
added indexes and stuff
Dhravya Jun 23, 2024
557c7b8
Add Extension (1/2)
CodeTorso Jun 23, 2024
9f751ba
feat: vector lookup and chat is twice as fast now
Dhravya Jun 23, 2024
0d069e8
Merge branch 'codetorso' of https://github.com/Dhravya/supermemory in…
Dhravya Jun 23, 2024
fff8b7a
added backend route for telegram bot and others to be possible
Dhravya Jun 23, 2024
51dd5ec
made and documented the telegram bot (HYPE)
Dhravya Jun 24, 2024
75585fd
some important housekeeping, crushed all build errors
Dhravya Jun 24, 2024
4c86f1e
ts-ignore at the right place
Dhravya Jun 24, 2024
cd304b3
reduced db calls here, we don't need that complex logic, can just do …
Dhravya Jun 24, 2024
4581b2c
Merge pull request #80 from Dhravya/telegram-bot
Dhravya Jun 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
made and documented the telegram bot (HYPE)
  • Loading branch information
Dhravya committed Jun 24, 2024
commit 51dd5ec9dd35d87c2e93a8e5a64fa963caaa182a
8 changes: 7 additions & 1 deletion apps/cf-ai-backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { zValidator } from "@hono/zod-validator";
import chunkText from "./utils/chonker";
import { systemPrompt, template } from "./prompts/prompt1";
import { swaggerUI } from "@hono/swagger-ui";
import { createOpenAI } from "@ai-sdk/openai";

const app = new Hono<{ Bindings: Env }>();

Expand Down Expand Up @@ -187,13 +188,18 @@ app.post(

const { store, model } = await initQuery(c);

// we're creating another instance of the model here because we want to use a cheaper model for this.
const openai = createOpenAI({
apiKey: c.env.OPENAI_API_KEY,
});

let task: "add" | "chat" = "chat";
let thingToAdd: "page" | "image" | "text" | undefined = undefined;
let addContent: string | undefined = undefined;

// This is a "router". this finds out if the user wants to add a document, or chat with the AI to get a response.
const routerQuery = await generateText({
model,
model: openai.chat("gpt-3.5-turbo"),
system: `You are Supermemory chatbot. You can either add a document to the supermemory database, or return a chat response. Based on this query,
You must determine what to do. Basically if it feels like a "question", then you should intiate a chat. If it feels like a "command" or feels like something that could be forwarded to the AI, then you should add a document.
You must also extract the "thing" to add and what type of thing it is.`,
Expand Down
14 changes: 12 additions & 2 deletions apps/web/app/(auth)/signin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@ import { Google } from "@repo/ui/components/icons";

export const runtime = "edge";

async function Signin() {
async function Signin({
searchParams,
}: {
searchParams: Record<string, string | string[] | undefined>;
}) {
const searchParamsAsString = Object.keys(searchParams)
.map((key) => {
return `${key}=${searchParams[key]}`;
})
.join("&");

return (
<div className="flex items-center justify-between min-h-screen">
<div className="relative w-full lg:w-1/2 flex items-center justify-center lg:justify-start min-h-screen bg-secondary p-8">
Expand All @@ -30,7 +40,7 @@ async function Signin() {
action={async () => {
"use server";
await signIn("google", {
redirectTo: "/home?firstTime=true",
redirectTo: `/home?firstTime=true&${searchParamsAsString}`,
});
}}
>
Expand Down
22 changes: 21 additions & 1 deletion apps/web/app/(dash)/home/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import QueryInput from "./queryinput";
import { homeSearchParamsCache } from "@/lib/searchParams";
import { getSpaces } from "@/app/actions/fetchers";
import { useRouter } from "next/navigation";
import { createChatThread } from "@/app/actions/doers";
import { createChatThread, linkTelegramToUser } from "@/app/actions/doers";
import { toast } from "sonner";

function Page({
searchParams,
Expand All @@ -14,11 +15,30 @@ function Page({
}) {
// TODO: use this to show a welcome page/modal
// const { firstTime } = homeSearchParamsCache.parse(searchParams);

const [telegramUser, setTelegramUser] = useState<string | undefined>(
searchParams.telegramUser as string,
);

const { push } = useRouter();

const [spaces, setSpaces] = useState<{ id: number; name: string }[]>([]);

useEffect(() => {
if (telegramUser) {
const linkTelegram = async () => {
const response = await linkTelegramToUser(telegramUser);

if (response.success) {
toast.success("Your telegram has been linked successfully.");
} else {
toast.error("Failed to link telegram. Please try again.");
}
};

linkTelegram();
}

getSpaces().then((res) => {
if (res.success && res.data) {
setSpaces(res.data);
Expand Down
31 changes: 31 additions & 0 deletions apps/web/app/actions/doers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
contentToSpace,
space,
storedContent,
users,
} from "../../server/db/schema";
import { ServerActionReturnType } from "./types";
import { auth } from "../../server/auth";
Expand All @@ -17,6 +18,7 @@ import { and, eq, inArray, sql } from "drizzle-orm";
import { LIMITS } from "@/lib/constants";
import { z } from "zod";
import { ChatHistory } from "@repo/shared-types";
import { decipher } from "@/server/encrypt";

export const createSpace = async (
input: string | FormData,
Expand Down Expand Up @@ -344,3 +346,32 @@ export const createChatObject = async (
data: true,
};
};

export const linkTelegramToUser = async (
telegramUser: string,
): ServerActionReturnType<boolean> => {
const data = await auth();

if (!data || !data.user || !data.user.id) {
return { error: "Not authenticated", success: false };
}

const user = await db
.update(users)
.set({ telegramId: decipher(telegramUser) })
.where(eq(users.id, data.user.id))
.execute();

if (!user) {
return {
success: false,
data: false,
error: "Failed to link telegram to user",
};
}

return {
success: true,
data: true,
};
};
55 changes: 55 additions & 0 deletions apps/web/app/api/telegram/readme.md
Original file line number Diff line number Diff line change
@@ -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.
89 changes: 86 additions & 3 deletions apps/web/app/api/telegram/route.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand All @@ -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:https://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:https://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");
Loading