Merge branch 'main' into canvas

This commit is contained in:
CodeTorso 2024-07-26 04:08:09 +05:30 committed by GitHub
commit a484aed11a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 1036 additions and 1862 deletions

View file

@ -1,81 +1,134 @@
# Setup guide
# Self Hosting Guide
This guide will help you set up your own instance of Supermemory. This is neccessary if you want to contribute to the project or if you want to self host the project. You can read more about the stack [here](https://github.com/supermemoryai/supermemory/?tab=readme-ov-file#-the-stack).
## Prerequisites
- [bun](https://bun.sh/)
- [turbo](https://turbo.build/repo/docs/installing)
- [wrangler](https://developers.cloudflare.com/workers/cli-wrangler/install-update)
- [yarn](https://yarnpkg.com/getting-started/install): yarn is required to run scripts using turborepo. bun is not supported by turborepo yet vercel/turbo#4762
- [Cloudflare Workers](https://developers.cloudflare.com/workers/platform/pricing/): You also need to have a paid Workers plan to use the vectorize feature which is needed run the AI backend. It is currently $5/mo + usage costs.
- [Cloudflare R2](https://developers.cloudflare.com/r2/): You need to enable R2 in the Cloudflare Dashboard for use in the web app.
## Steps
1. Clone the repo
2. Run `bun install` in the root directory
3. Create a `.dev.vars` file in `apps/web` with the following content:
### web
1. You need to create OAuth credentials for Google which is need for auth.js (nextauth). Visit https://developers.google.com/identity/protocols/oauth2 to learn more and https://console.cloud.google.com/apis/dashboard to create a new project and OAuth credentials. You need to set the redirect URL to `http://localhost:3000/api/auth/callback/google` for development. You can also set the redirect URL to your own domain if you are deploying the app.
2. Create a `.dev.vars` file in `apps/web` with the following content:
```bash
GOOGLE_CLIENT_ID="-" // required, visit https://developers.google.com/identity/protocols/oauth2
GOOGLE_CLIENT_SECRET="-" // required
NEXTAUTH_SECRET='nextauthsecret'
GOOGLE_CLIENT_ID="" // required
GOOGLE_CLIENT_SECRET="" // required
NEXTAUTH_SECRET="" // generate by running `openssl rand -base64 32`
DATABASE_URL='database.sqlite'
NEXTAUTH_URL='http://localhost:3000'
BACKEND_SECURITY_KEY='veryrandomsecuritykey'
BACKEND_BASE_URL="where your backend is hosted"
BACKEND_SECURITY_KEY="" // used to authenticate with the backend. generate a random string using `openssl rand -base64 32`
BACKEND_BASE_URL="http://localhost:8686"
```
4. Setup the database:
> [!NOTE]
> The `BACKEND_SECURITY_KEY` should be the same as the `SECURITY_KEY` in the `.dev.vars` file in `apps/cf-ai-backend`.
First, edit the `wrangler.toml` file in `apps/web` to point the d1 database to your account.
3. KV Namespaces
You can create a d1 database by running this command
```
bunx wrangler d1 create <YOUR_DATABASE_NAME>
```bash
bunx wrangler kv namespace create canvas-snaps
```
And then replace database_name and database_id with the values
```bash
bunx wrangler kv namespace create recommendations
```
Do not change the binding value in the `wrangler.toml` but update the id for the namespaces with the values you get from the above commands.
4. R2 Storage
```bash
bunx wrangler r2 bucket create supermemory-r2
```
Update bucket_name in the `wrangler.toml` file in `apps/web` to `supermemory-r2`
5. D1 Database
```bash
bunx wrangler d1 create supermemory-db-prod
```
Update the database_name and database_id in `[[env.production.d1_databases]]` with the values you get from the above command.
```bash
bunx wrangler d1 create supermemory-db-preview
```
Update the database_name and database_id in `[[d1_databases]]` and `[[env.preview.d1_databases]]` with the values you get from the above command.
> [!NOTE]
> please don't change the binding value even if wrangler cli suggests you to do so.
```
```bash
[[d1_databases]]
binding = "DATABASE"
database_name = "YOUR_DATABASE_NAME"
database_name = "supermemory-db-preview"
database_id = "YOUR_DB_ID"
```
Simply run this command in `apps/web`
```
bunx wrangler d1 migrations apply <YOUR_DATABASE_NAME>
```bash
bunx wrangler d1 migrations apply supermemory-db-preview
```
If it runs, you can set up the cloud database as well by removing the `--local` flag,
If it runs, you can set up the cloud database as well by add the `--remote` flag,
if you just want to contribute to frontend then just run `bun run dev` in the root of the project and done! (you won't be able to try ai stuff), otherwise continue...
5. You need to host your own worker for the `apps/cf-ai-backend` module.
### cf-ai-backend
1. You need to host your own worker for the `apps/cf-ai-backend` module.
To do this, first edit the `.dev.vars` file in `apps/cf-ai-backend` with the following content:
```bash
SECURITY_KEY="veryrandomsecuritykey"
SECURITY_KEY="veryrandomsecuritykey" // same as BACKEND_SECURITY_KEY in web
// Why? to generate embeddings with 4000+ tokens
OPENAI_API_KEY="sk-"
```
6. Run this command to initialise vector database
2. Run this command to initialise vector database
> Note: You need to use the workers paid plan to use vectorize for now.
```
wrangler vectorize create --dimensions=1536 supermem-vector-1 --metric=cosine
```bash
bunx wrangler vectorize create --dimensions=1536 supermemory --metric=cosine
```
7. Change the `wrangler.toml` file in `apps/cf-ai-backend` to point to your KV namespace
Update the index_name for `[[vectorize]]` in `wrangler.toml` file in `apps/cf-ai-backend` with the `supermemory` or the name you used in the above command.
8. Run `bun dev` in the root directory and Voila! You have your own supermemory instance running!
3. Create KV namespaces for the `cf-ai-backend` module
> Note: You need to replace the url `https://cf-ai-backend.dhr.wtf` everywhere with your own url for the cf-ai-backend module.
```bash
bunx wrangler kv namespace create prod
```
Update the id in `[[kv_namespaces]]` in the `wrangler.toml` file in `apps/cf-ai-backend` with the value you get from the above command.
```bash
bunx wrangler kv namespace create preview
```
Update the preview_id in `[[kv_namespaces]]` in the `wrangler.toml` file in `apps/cf-ai-backend` with the value you get from the above command.
## Local Development
- Run `bun dev` in the root directory and Voila! You have your own supermemory instance running!
> [!NOTE]
> It sometimes takes multiple tries to successfully run the `bun dev` command. If you encounter any issues, try running the command again.
## Deploying

View file

@ -1,7 +1,7 @@
{
"manifest_version": 3,
"$schema": "https://json.schemastore.org/chrome-manifest",
"version": "2.63",
"version": "2.63.1",
"name": "supermemory",
"description": "An extension for https://supermemory.ai - an AI hub for all your bookmarks.",
"background": {

View file

@ -1,10 +1,10 @@
"use client";
import {
ArrowUturnDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
QuestionMarkCircleIcon,
ArrowTurnDownLeftIcon,
} from "@heroicons/react/24/solid";
import { CheckIcon, PlusCircleIcon } from "@heroicons/react/24/outline";
import { motion } from "framer-motion";
@ -311,7 +311,7 @@ function StepThree({ currStep }: { currStep: number }) {
type="submit"
className="rounded-lg bg-[#369DFD1A] p-3 absolute bottom-4 right-2"
>
<ArrowTurnDownLeftIcon className="w-4 h-4 text-[#369DFD]" />
<ArrowUturnDownIcon className="w-4 h-4 text-[#369DFD]" />
</button>
</form>
</li>

View file

@ -6,7 +6,13 @@ import Logo from "../../../public/logo.svg";
import { getChatHistory } from "../../actions/fetchers";
import NewChatButton from "./newChatButton";
import AutoBreadCrumbs from "./autoBreadCrumbs";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@repo/ui/shadcn/dropdown-menu";
import SignOutButton from "./signOutButton";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@repo/ui/shadcn/dropdown-menu";
import { CaretDownIcon } from "@radix-ui/react-icons";
async function Header() {
@ -36,9 +42,9 @@ async function Header() {
<DropdownMenu>
<DropdownMenuTrigger className="inline-flex flex-row flex-nowrap items-center text-muted-foreground hover:text-foreground">
History
<CaretDownIcon />
</DropdownMenuTrigger>
History
<CaretDownIcon />
</DropdownMenuTrigger>
<DropdownMenuContent className="p-4 w-full md:w-[400px] max-h-[70vh] overflow-auto border-none">
{chatThreads.data.map((thread) => (
<DropdownMenuItem asChild>
@ -54,6 +60,8 @@ async function Header() {
))}
</DropdownMenuContent>
</DropdownMenu>
<SignOutButton />
</div>
</div>
</div>

View file

@ -0,0 +1,17 @@
import { signOut } from "@/server/auth";
import { Button } from "@repo/ui/shadcn/button";
export default function SignOutButton() {
return (
<form
action={async () => {
"use server"
await signOut()
}}
>
<Button variant="ghost" size="sm" type="submit" className="text-[#7D8994]">
Sign Out
</Button>
</form>
);
}

View file

@ -1,12 +1,8 @@
import { getChatHistory } from "@repo/web/app/actions/fetchers";
import { ArrowLongRightIcon } from "@heroicons/react/24/outline";
import { Skeleton } from "@repo/ui/shadcn/skeleton";
import Link from "next/link";
import { memo, useEffect, useState } from "react";
import { motion } from "framer-motion";
import { chatThreads } from "@/server/db/schema";
import { getQuerySuggestions } from "@/app/actions/doers";
import { Button } from "@repo/ui/shadcn/button";
const History = memo(({ setQuery }: { setQuery: (q: string) => void }) => {
const [suggestions, setSuggestions] = useState<string[] | null>(null);
@ -21,7 +17,12 @@ const History = memo(({ setQuery }: { setQuery: (q: string) => void }) => {
}
console.log(suggestions);
if (typeof suggestions.data === "string") {
setSuggestions(JSON.parse(suggestions.data));
const queries = suggestions.data.slice(1, -1).split(", ");
const parsedQueries = queries.map((query) =>
query.replace(/^'|'$/g, ""),
);
console.log(parsedQueries);
setSuggestions(parsedQueries);
return;
}
setSuggestions(suggestions.data.reverse().slice(0, 3));

View file

@ -458,6 +458,7 @@ export const createChatObject = async (
answer: lastChat.answer.parts.map((part) => part.text).join(""),
answerSources: JSON.stringify(lastChat.answer.sources),
threadId,
createdAt: new Date(),
});
if (!saved) {
@ -782,7 +783,7 @@ export async function getQuerySuggestions() {
messages: [
{
role: "system",
content: `You are a model that suggests questions based on the user's content. you MUST suggest atleast 1 question to ask. Create 3 suggestions at most.`,
content: `You are a model that suggests questions based on the user's content. you MUST suggest atleast 1 question to ask. AT MAX, create 3 suggestions. not more than that.`,
},
{
role: "user",

View file

@ -7,6 +7,10 @@ import {
} from "@repo/shared-types";
import { ensureAuth } from "../ensureAuth";
import { z } from "zod";
import { db } from "@/server/db";
import { chatHistory as chatHistoryDb, chatThreads } from "@/server/db/schema";
import { and, eq, gt, sql } from "drizzle-orm";
import { join } from "path";
export const runtime = "edge";
@ -21,6 +25,49 @@ export async function POST(req: NextRequest) {
return new Response("Missing BACKEND_SECURITY_KEY", { status: 500 });
}
const ip = req.headers.get("cf-connecting-ip");
if (ip) {
if (process.env.RATELIMITER) {
const { success } = await process.env.RATELIMITER.limit({
key: `chat-${ip}`,
});
if (!success) {
console.error("rate limit exceeded");
return new Response("Rate limit exceeded", { status: 429 });
}
} else {
console.info("RATELIMITER not found in env");
}
} else {
console.info("cf-connecting-ip not found in headers");
}
const lastHour = new Date(new Date().getTime() - 3600000);
// Only allow 5 requests per hour for each user, something lke this but this one is bad because chathistory.userid doesnt exist, we have to do a join and get it from the threads table
const result = await db
.select({
count: sql<number>`count(*)`.mapWith(Number),
})
.from(chatHistoryDb)
.innerJoin(chatThreads, eq(chatHistoryDb.threadId, chatThreads.id))
.where(
and(
eq(chatThreads.userId, session.user.id),
gt(chatHistoryDb.createdAt, lastHour)
)
)
.execute();
if (result[0]?.count && result[0]?.count >= 5) {
// return new Response(`Too many requests ${result[0]?.count}`, { status: 429 });
console.log(result[0]?.count)
} else {
console.log("count", result);
}
const url = new URL(req.url);
const query = url.searchParams.get("q");

View file

@ -4,7 +4,7 @@ import { ensureAuth } from "../ensureAuth";
import { z } from "zod";
import { db } from "@/server/db";
import { contentToSpace, space, storedContent } from "@/server/db/schema";
import { and, eq, inArray } from "drizzle-orm";
import { and, eq, gt, inArray, sql } from "drizzle-orm";
import { LIMITS } from "@/lib/constants";
import { limit } from "@/app/actions/doers";
@ -22,6 +22,24 @@ const createMemoryFromAPI = async (input: {
};
}
// Get number of items saved in the last 2 hours
const last2Hours = new Date(Date.now() - 2 * 60 * 60 * 1000);
const numberOfItemsSavedInLast2Hours = await db
.select({
count: sql<number>`count(*)`.mapWith(Number),
})
.from(storedContent)
.where(and(gt(storedContent.savedAt, last2Hours), eq(storedContent.userId, input.userId)))
if (numberOfItemsSavedInLast2Hours[0]!.count >= 20) {
return {
success: false,
data: 0,
error: `You have exceeded the limit`,
};
}
const vectorSaveResponse = await fetch(
`${process.env.BACKEND_BASE_URL}/api/add`,
{

View file

@ -66,9 +66,14 @@ export default function RootLayout({
}): JSX.Element {
return (
<html lang="en" className="overflow-x-hidden" suppressHydrationWarning>
{/* <head>
<ThemeScript />
</head> */}
<head>
{/* Cloudflare web analytics */}
<script
defer
src="https://static.cloudflareinsights.com/beacon.min.js"
data-cf-beacon='{"token": "16d76ebb82c74d9983b71d09ab6617bc"}'
></script>
</head>
{/* TODO: when lightmode support is added, remove the 'dark' class from the body tag */}
<body
className={cn(

View file

@ -19,6 +19,10 @@ declare global {
CLOUDFLARE_D1_TOKEN: string;
MOBILE_TRUST_TOKEN: string;
RATELIMITER: {
limit: ({key: string}) => {success: boolean}
};
}
}
}

View file

@ -43,6 +43,7 @@ CREATE TABLE `chatHistory` (
`answerParts` text,
`answerSources` text,
`answerJustification` text,
`createdAt` integer DEFAULT '"2024-07-25T22:31:50.848Z"' NOT NULL,
FOREIGN KEY (`threadId`) REFERENCES `chatThread`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
@ -106,7 +107,8 @@ CREATE TABLE `user` (
`email` text NOT NULL,
`emailVerified` integer,
`image` text,
`telegramId` text
`telegramId` text,
`hasOnboarded` integer DEFAULT false
);
--> statement-breakpoint
CREATE TABLE `verificationToken` (

View file

@ -1 +0,0 @@
ALTER TABLE `user` ADD `hasOnboarded` integer DEFAULT false;

File diff suppressed because it is too large Load diff

View file

@ -1,903 +0,0 @@
{
"version": "6",
"dialect": "sqlite",
"id": "1f43694b-f42b-4074-876e-8501fc18bf38",
"prevId": "e8646bed-105d-4f69-b385-b8b6fee8a6a9",
"tables": {
"account": {
"name": "account",
"columns": {
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"provider": {
"name": "provider",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"providerAccountId": {
"name": "providerAccountId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"expires_at": {
"name": "expires_at",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"token_type": {
"name": "token_type",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"id_token": {
"name": "id_token",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"session_state": {
"name": "session_state",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"account_userId_user_id_fk": {
"name": "account_userId_user_id_fk",
"tableFrom": "account",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"account_provider_providerAccountId_pk": {
"columns": [
"provider",
"providerAccountId"
],
"name": "account_provider_providerAccountId_pk"
}
},
"uniqueConstraints": {}
},
"authenticator": {
"name": "authenticator",
"columns": {
"credentialID": {
"name": "credentialID",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"providerAccountId": {
"name": "providerAccountId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"credentialPublicKey": {
"name": "credentialPublicKey",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"counter": {
"name": "counter",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"credentialDeviceType": {
"name": "credentialDeviceType",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"credentialBackedUp": {
"name": "credentialBackedUp",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"transports": {
"name": "transports",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"authenticator_credentialID_unique": {
"name": "authenticator_credentialID_unique",
"columns": [
"credentialID"
],
"isUnique": true
}
},
"foreignKeys": {
"authenticator_userId_user_id_fk": {
"name": "authenticator_userId_user_id_fk",
"tableFrom": "authenticator",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"authenticator_userId_credentialID_pk": {
"columns": [
"credentialID",
"userId"
],
"name": "authenticator_userId_credentialID_pk"
}
},
"uniqueConstraints": {}
},
"canvas": {
"name": "canvas",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"title": {
"name": "title",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'Untitled'"
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'Untitled'"
},
"url": {
"name": "url",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "''"
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"canvas_user_userId": {
"name": "canvas_user_userId",
"columns": [
"userId"
],
"isUnique": false
}
},
"foreignKeys": {
"canvas_userId_user_id_fk": {
"name": "canvas_userId_user_id_fk",
"tableFrom": "canvas",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"chatHistory": {
"name": "chatHistory",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"threadId": {
"name": "threadId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"question": {
"name": "question",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"answerParts": {
"name": "answerParts",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"answerSources": {
"name": "answerSources",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"answerJustification": {
"name": "answerJustification",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"chatHistory_thread_idx": {
"name": "chatHistory_thread_idx",
"columns": [
"threadId"
],
"isUnique": false
}
},
"foreignKeys": {
"chatHistory_threadId_chatThread_id_fk": {
"name": "chatHistory_threadId_chatThread_id_fk",
"tableFrom": "chatHistory",
"tableTo": "chatThread",
"columnsFrom": [
"threadId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"chatThread": {
"name": "chatThread",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"firstMessage": {
"name": "firstMessage",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {
"chatThread_user_idx": {
"name": "chatThread_user_idx",
"columns": [
"userId"
],
"isUnique": false
}
},
"foreignKeys": {
"chatThread_userId_user_id_fk": {
"name": "chatThread_userId_user_id_fk",
"tableFrom": "chatThread",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"contentToSpace": {
"name": "contentToSpace",
"columns": {
"contentId": {
"name": "contentId",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"spaceId": {
"name": "spaceId",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"contentToSpace_contentId_storedContent_id_fk": {
"name": "contentToSpace_contentId_storedContent_id_fk",
"tableFrom": "contentToSpace",
"tableTo": "storedContent",
"columnsFrom": [
"contentId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"contentToSpace_spaceId_space_id_fk": {
"name": "contentToSpace_spaceId_space_id_fk",
"tableFrom": "contentToSpace",
"tableTo": "space",
"columnsFrom": [
"spaceId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"contentToSpace_contentId_spaceId_pk": {
"columns": [
"contentId",
"spaceId"
],
"name": "contentToSpace_contentId_spaceId_pk"
}
},
"uniqueConstraints": {}
},
"session": {
"name": "session",
"columns": {
"sessionToken": {
"name": "sessionToken",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"userId": {
"name": "userId",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"expires": {
"name": "expires",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"session_userId_user_id_fk": {
"name": "session_userId_user_id_fk",
"tableFrom": "session",
"tableTo": "user",
"columnsFrom": [
"userId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"space": {
"name": "space",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": "'none'"
},
"user": {
"name": "user",
"type": "text(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"createdAt": {
"name": "createdAt",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"numItems": {
"name": "numItems",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false,
"default": 0
}
},
"indexes": {
"space_name_unique": {
"name": "space_name_unique",
"columns": [
"name"
],
"isUnique": true
},
"spaces_name_idx": {
"name": "spaces_name_idx",
"columns": [
"name"
],
"isUnique": false
},
"spaces_user_idx": {
"name": "spaces_user_idx",
"columns": [
"user"
],
"isUnique": false
}
},
"foreignKeys": {
"space_user_user_id_fk": {
"name": "space_user_user_id_fk",
"tableFrom": "space",
"tableTo": "user",
"columnsFrom": [
"user"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"spacesAccess": {
"name": "spacesAccess",
"columns": {
"spaceId": {
"name": "spaceId",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"userEmail": {
"name": "userEmail",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {
"spacesAccess_spaceId_space_id_fk": {
"name": "spacesAccess_spaceId_space_id_fk",
"tableFrom": "spacesAccess",
"tableTo": "space",
"columnsFrom": [
"spaceId"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"spacesAccess_spaceId_userEmail_pk": {
"columns": [
"spaceId",
"userEmail"
],
"name": "spacesAccess_spaceId_userEmail_pk"
}
},
"uniqueConstraints": {}
},
"storedContent": {
"name": "storedContent",
"columns": {
"id": {
"name": "id",
"type": "integer",
"primaryKey": true,
"notNull": true,
"autoincrement": true
},
"content": {
"name": "content",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"title": {
"name": "title",
"type": "text(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"description": {
"name": "description",
"type": "text(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"url": {
"name": "url",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"savedAt": {
"name": "savedAt",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"baseUrl": {
"name": "baseUrl",
"type": "text(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"ogImage": {
"name": "ogImage",
"type": "text(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"type": {
"name": "type",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": "'page'"
},
"image": {
"name": "image",
"type": "text(255)",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"user": {
"name": "user",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"noteId": {
"name": "noteId",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
}
},
"indexes": {
"storedContent_baseUrl_unique": {
"name": "storedContent_baseUrl_unique",
"columns": [
"baseUrl"
],
"isUnique": true
},
"storedContent_url_idx": {
"name": "storedContent_url_idx",
"columns": [
"url"
],
"isUnique": false
},
"storedContent_savedAt_idx": {
"name": "storedContent_savedAt_idx",
"columns": [
"savedAt"
],
"isUnique": false
},
"storedContent_title_idx": {
"name": "storedContent_title_idx",
"columns": [
"title"
],
"isUnique": false
},
"storedContent_user_idx": {
"name": "storedContent_user_idx",
"columns": [
"user"
],
"isUnique": false
}
},
"foreignKeys": {
"storedContent_user_user_id_fk": {
"name": "storedContent_user_user_id_fk",
"tableFrom": "storedContent",
"tableTo": "user",
"columnsFrom": [
"user"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"user": {
"name": "user",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true,
"autoincrement": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"emailVerified": {
"name": "emailVerified",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"telegramId": {
"name": "telegramId",
"type": "text",
"primaryKey": false,
"notNull": false,
"autoincrement": false
},
"hasOnboarded": {
"name": "hasOnboarded",
"type": "integer",
"primaryKey": false,
"notNull": false,
"autoincrement": false,
"default": false
}
},
"indexes": {
"users_email_idx": {
"name": "users_email_idx",
"columns": [
"email"
],
"isUnique": false
},
"users_telegram_idx": {
"name": "users_telegram_idx",
"columns": [
"telegramId"
],
"isUnique": false
},
"users_id_idx": {
"name": "users_id_idx",
"columns": [
"id"
],
"isUnique": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {}
},
"verificationToken": {
"name": "verificationToken",
"columns": {
"identifier": {
"name": "identifier",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"token": {
"name": "token",
"type": "text",
"primaryKey": false,
"notNull": true,
"autoincrement": false
},
"expires": {
"name": "expires",
"type": "integer",
"primaryKey": false,
"notNull": true,
"autoincrement": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"verificationToken_identifier_token_pk": {
"columns": [
"identifier",
"token"
],
"name": "verificationToken_identifier_token_pk"
}
},
"uniqueConstraints": {}
}
},
"enums": {},
"_meta": {
"schemas": {},
"tables": {},
"columns": {}
}
}

View file

@ -1,20 +1,13 @@
{
"version": "6",
"dialect": "sqlite",
"entries": [
{
"idx": 0,
"version": "6",
"when": 1721746132570,
"tag": "0000_silky_havok",
"breakpoints": true
},
{
"idx": 1,
"version": "6",
"when": 1721775651258,
"tag": "0001_dear_sally_floyd",
"breakpoints": true
}
]
}
"version": "6",
"dialect": "sqlite",
"entries": [
{
"idx": 0,
"version": "6",
"when": 1721946710900,
"tag": "0000_steep_moira_mactaggert",
"breakpoints": true
}
]
}

View file

@ -1,3 +1,4 @@
import { create } from "domain";
import { relations, sql } from "drizzle-orm";
import {
index,
@ -211,6 +212,7 @@ export const chatHistory = createTable(
answer: text("answerParts"), // Single answer part as string
answerSources: text("answerSources"), // JSON stringified array of objects
answerJustification: text("answerJustification"),
createdAt: int("createdAt", { mode: "timestamp" }).notNull().default(new Date()),
},
(history) => ({
threadIdx: index("chatHistory_thread_idx").on(history.threadId),