mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-07 08:26:15 +00:00
Merge branch 'codetorso' of https://github.com/Dhravya/supermemory into codetorso
This commit is contained in:
commit
8ecfc654c7
36 changed files with 1266 additions and 317 deletions
|
|
@ -88,8 +88,8 @@ app.post(
|
|||
"query",
|
||||
z.object({
|
||||
query: z.string(),
|
||||
topK: z.number().optional().default(10),
|
||||
user: z.string(),
|
||||
topK: z.number().optional().default(10),
|
||||
spaces: z.string().optional(),
|
||||
sourcesOnly: z.string().optional().default("false"),
|
||||
model: z.string().optional().default("gpt-4o"),
|
||||
|
|
@ -100,28 +100,25 @@ app.post(
|
|||
const query = c.req.valid("query");
|
||||
const body = c.req.valid("json");
|
||||
|
||||
if (body.chatHistory) {
|
||||
body.chatHistory = body.chatHistory.map((i) => ({
|
||||
...i,
|
||||
content: i.parts.length > 0 ? i.parts.join(" ") : i.content,
|
||||
}));
|
||||
}
|
||||
|
||||
const sourcesOnly = query.sourcesOnly === "true";
|
||||
const spaces = query.spaces?.split(",") || [undefined];
|
||||
const spaces = query.spaces?.split(",") ?? [""];
|
||||
|
||||
// Get the AI model maker and vector store
|
||||
const { model, store } = await initQuery(c, query.model);
|
||||
|
||||
const filter: VectorizeVectorMetadataFilter = { user: query.user };
|
||||
console.log("Spaces", spaces);
|
||||
|
||||
// Converting the query to a vector so that we can search for similar vectors
|
||||
const queryAsVector = await store.embeddings.embedQuery(query.query);
|
||||
const responses: VectorizeMatches = { matches: [], count: 0 };
|
||||
|
||||
console.log("hello world", spaces);
|
||||
|
||||
// SLICED to 5 to avoid too many queries
|
||||
for (const space of spaces.slice(0, 5)) {
|
||||
if (space !== undefined) {
|
||||
console.log("space", space);
|
||||
if (space !== "") {
|
||||
// it's possible for space list to be [undefined] so we only add space filter conditionally
|
||||
filter.space = space;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,15 +6,12 @@ To generate your answer:
|
|||
- Carefully analyze the question and identify the key information needed to address it
|
||||
- Locate the specific parts of each context that contain this key information
|
||||
- Compare the relevance scores of the provided contexts
|
||||
- In the <justification> tags, provide a brief justification for which context(s) are more relevant to answering the question based on the scores
|
||||
- Concisely summarize the relevant information from the higher-scoring context(s) in your own words
|
||||
- Provide a direct answer to the question
|
||||
- Use markdown formatting in your answer, including bold, italics, and bullet points as appropriate to improve readability and highlight key points
|
||||
- Give detailed and accurate responses for things like 'write a blog' or long-form questions.
|
||||
- The normalisedScore is a value in which the scores are 'balanced' to give a better representation of the relevance of the context, between 1 and 100, out of the top 10 results
|
||||
|
||||
Provide your justification between <justification> tags and your final answer between <answer> tags, formatting both in markdown.
|
||||
|
||||
- provide your justification in the end, in a <justification> </justification> tag
|
||||
If no context is provided, introduce yourself and explain that the user can save content which will allow you to answer questions about that content in the future. Do not provide an answer if no context is provided.`;
|
||||
|
||||
export const template = ({ contexts, question }) => {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { z } from "zod";
|
||||
|
||||
interface OpenAIEmbeddingsParams {
|
||||
apiKey: string;
|
||||
modelName: string;
|
||||
|
|
@ -32,12 +34,22 @@ export class OpenAIEmbeddings {
|
|||
}),
|
||||
});
|
||||
|
||||
const data = (await response.json()) as {
|
||||
data: {
|
||||
embedding: number[];
|
||||
}[];
|
||||
};
|
||||
const data = await response.json();
|
||||
|
||||
return data.data[0].embedding;
|
||||
const zodTypeExpected = z.object({
|
||||
data: z.array(
|
||||
z.object({
|
||||
embedding: z.array(z.number()),
|
||||
}),
|
||||
),
|
||||
});
|
||||
|
||||
const json = zodTypeExpected.safeParse(data);
|
||||
|
||||
if (!json.success) {
|
||||
throw new Error("Invalid response from OpenAI: " + json.error.message);
|
||||
}
|
||||
|
||||
return json.data.data[0].embedding;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,48 +0,0 @@
|
|||
"use server";
|
||||
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { db } from "../helpers/server/db";
|
||||
import { sessions, users, space } from "../helpers/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export async function ensureAuth() {
|
||||
const token =
|
||||
cookies().get("next-auth.session-token")?.value ??
|
||||
cookies().get("__Secure-authjs.session-token")?.value ??
|
||||
cookies().get("authjs.session-token")?.value ??
|
||||
headers().get("Authorization")?.replace("Bearer ", "");
|
||||
|
||||
if (!token) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const sessionData = await db
|
||||
.select()
|
||||
.from(sessions)
|
||||
.innerJoin(users, eq(users.id, sessions.userId))
|
||||
.where(eq(sessions.sessionToken, token));
|
||||
|
||||
if (!sessionData || sessionData.length < 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
user: sessionData[0]!.user,
|
||||
session: sessionData[0]!,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getSpaces() {
|
||||
const data = await ensureAuth();
|
||||
if (!data) {
|
||||
redirect("/signin");
|
||||
}
|
||||
|
||||
const sp = await db
|
||||
.select()
|
||||
.from(space)
|
||||
.where(eq(space.user, data.user.email));
|
||||
|
||||
return sp;
|
||||
}
|
||||
90
apps/web/app/(dash)/chat/CodeBlock.tsx
Normal file
90
apps/web/app/(dash)/chat/CodeBlock.tsx
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import React, { useRef, useState } from "react";
|
||||
|
||||
const CodeBlock = ({
|
||||
lang,
|
||||
codeChildren,
|
||||
}: {
|
||||
lang: string;
|
||||
codeChildren: React.ReactNode & React.ReactNode[];
|
||||
}) => {
|
||||
const codeRef = useRef<HTMLElement>(null);
|
||||
|
||||
return (
|
||||
<div className="bg-black rounded-md">
|
||||
<CodeBar lang={lang} codeRef={codeRef} />
|
||||
<div className="p-4 overflow-y-auto">
|
||||
<code ref={codeRef} className={`!whitespace-pre hljs language-${lang}`}>
|
||||
{codeChildren}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const CodeBar = React.memo(
|
||||
({
|
||||
lang,
|
||||
codeRef,
|
||||
}: {
|
||||
lang: string;
|
||||
codeRef: React.RefObject<HTMLElement>;
|
||||
}) => {
|
||||
const [isCopied, setIsCopied] = useState<boolean>(false);
|
||||
return (
|
||||
<div className="flex items-center relative text-gray-200 bg-gray-800 px-4 py-2 text-xs font-sans">
|
||||
<span className="">{lang}</span>
|
||||
<button
|
||||
className="flex ml-auto gap-2"
|
||||
aria-label="copy codeblock"
|
||||
onClick={async () => {
|
||||
const codeString = codeRef.current?.textContent;
|
||||
if (codeString)
|
||||
navigator.clipboard.writeText(codeString).then(() => {
|
||||
setIsCopied(true);
|
||||
setTimeout(() => setIsCopied(false), 3000);
|
||||
});
|
||||
}}
|
||||
>
|
||||
{isCopied ? (
|
||||
<>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="size-4"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M11.35 3.836c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75 2.25 2.25 0 0 0-.1-.664m-5.8 0A2.251 2.251 0 0 1 13.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m8.9-4.414c.376.023.75.05 1.124.08 1.131.094 1.976 1.057 1.976 2.192V16.5A2.25 2.25 0 0 1 18 18.75h-2.25m-7.5-10.5H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V18.75m-7.5-10.5h6.375c.621 0 1.125.504 1.125 1.125v9.375m-8.25-3 1.5 1.5 3-3.75"
|
||||
/>
|
||||
</svg>
|
||||
Copied!
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="size-4"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 0 1-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 0 1 1.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 0 0-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 0 1-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 0 0-3.375-3.375h-1.5a1.125 1.125 0 0 1-1.125-1.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H9.75"
|
||||
/>
|
||||
</svg>
|
||||
Copy code
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
export default CodeBlock;
|
||||
|
|
@ -1 +0,0 @@
|
|||
"use server";
|
||||
|
|
@ -6,29 +6,92 @@ import QueryInput from "../home/queryinput";
|
|||
import { cn } from "@repo/ui/lib/utils";
|
||||
import { motion } from "framer-motion";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { ChatHistory } from "@repo/shared-types";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "@repo/ui/shadcn/accordion";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkMath from "remark-math";
|
||||
import rehypeKatex from "rehype-katex";
|
||||
import rehypeHighlight from "rehype-highlight";
|
||||
import { code, p } from "./markdownRenderHelpers";
|
||||
import { codeLanguageSubset } from "@/app/helpers/constants";
|
||||
|
||||
function ChatWindow({ q }: { q: string }) {
|
||||
function ChatWindow({
|
||||
q,
|
||||
spaces,
|
||||
}: {
|
||||
q: string;
|
||||
spaces: { id: string; name: string }[];
|
||||
}) {
|
||||
const [layout, setLayout] = useState<"chat" | "initial">("initial");
|
||||
const [chatHistory, setChatHistory] = useState<ChatHistory[]>([
|
||||
{
|
||||
question: q,
|
||||
answer: {
|
||||
parts: [
|
||||
// {
|
||||
// text: `It seems like there might be a typo in your question. Could you please clarify or provide more context? If you meant "interesting," please let me know what specific information or topic you find interesting, and I can help you with that.`,
|
||||
// },
|
||||
],
|
||||
sources: [],
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const getAnswer = async (query: string, spaces: string[]) => {
|
||||
const resp = await fetch(`/api/chat?q=${query}&spaces=${spaces}`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ chatHistory }),
|
||||
});
|
||||
|
||||
const reader = resp.body?.getReader();
|
||||
let done = false;
|
||||
let result = "";
|
||||
while (!done && reader) {
|
||||
const { value, done: d } = await reader.read();
|
||||
done = d;
|
||||
|
||||
setChatHistory((prevChatHistory) => {
|
||||
const newChatHistory = [...prevChatHistory];
|
||||
const lastAnswer = newChatHistory[newChatHistory.length - 1];
|
||||
if (!lastAnswer) return prevChatHistory;
|
||||
lastAnswer.answer.parts.push({ text: new TextDecoder().decode(value) });
|
||||
return newChatHistory;
|
||||
});
|
||||
}
|
||||
|
||||
console.log(result);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (q !== "") {
|
||||
if (q.trim().length > 0) {
|
||||
getAnswer(
|
||||
q,
|
||||
spaces.map((s) => s.id),
|
||||
);
|
||||
setTimeout(() => {
|
||||
setLayout("chat");
|
||||
}, 300);
|
||||
} else {
|
||||
router.push("/home");
|
||||
}
|
||||
}, [q]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="h-full">
|
||||
<AnimatePresence mode="popLayout">
|
||||
{layout === "initial" ? (
|
||||
<motion.div
|
||||
exit={{ opacity: 0 }}
|
||||
key="initial"
|
||||
className="max-w-3xl flex mx-auto w-full flex-col"
|
||||
className="max-w-3xl h-full justify-center items-center flex mx-auto w-full flex-col"
|
||||
>
|
||||
<div className="w-full h-96">
|
||||
<QueryInput initialQuery={q} initialSpaces={[]} disabled />
|
||||
|
|
@ -36,16 +99,111 @@ function ChatWindow({ q }: { q: string }) {
|
|||
</motion.div>
|
||||
) : (
|
||||
<div
|
||||
className="max-w-3xl flex mx-auto w-full flex-col mt-8"
|
||||
className="max-w-3xl flex mx-auto w-full flex-col mt-24"
|
||||
key="chat"
|
||||
>
|
||||
<h2
|
||||
className={cn(
|
||||
"transition-all transform translate-y-0 opacity-100 duration-500 ease-in-out font-semibold text-2xl",
|
||||
)}
|
||||
>
|
||||
{q}
|
||||
</h2>
|
||||
{chatHistory.map((chat, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`mt-8 ${idx != chatHistory.length - 1 ? "pb-2 border-b" : ""}`}
|
||||
>
|
||||
<h2
|
||||
className={cn(
|
||||
"text-white transition-all transform translate-y-0 opacity-100 duration-500 ease-in-out font-semibold text-2xl",
|
||||
)}
|
||||
>
|
||||
{chat.question}
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-col gap-2 mt-2">
|
||||
<div
|
||||
className={`${chat.answer.sources.length > 0 || chat.answer.parts.length === 0 ? "flex" : "hidden"}`}
|
||||
>
|
||||
<Accordion
|
||||
defaultValue={
|
||||
idx === chatHistory.length - 1 ? "memories" : ""
|
||||
}
|
||||
type="single"
|
||||
collapsible
|
||||
>
|
||||
<AccordionItem value="memories">
|
||||
<AccordionTrigger className="text-foreground-menu">
|
||||
Related Memories
|
||||
</AccordionTrigger>
|
||||
{/* TODO: fade out content on the right side, the fade goes away when the user scrolls */}
|
||||
<AccordionContent
|
||||
className="relative flex gap-2 max-w-3xl overflow-auto no-scrollbar"
|
||||
defaultChecked
|
||||
>
|
||||
{/* Loading state */}
|
||||
{chat.answer.sources.length > 0 ||
|
||||
(chat.answer.parts.length === 0 && (
|
||||
<>
|
||||
{[1, 2, 3, 4].map((_, idx) => (
|
||||
<div
|
||||
key={`loadingState-${idx}`}
|
||||
className="rounded-xl bg-secondary p-4 flex flex-col gap-2 min-w-72 animate-pulse"
|
||||
>
|
||||
<div className="bg-slate-700 h-2 rounded-full w-1/2"></div>
|
||||
<div className="bg-slate-700 h-2 rounded-full w-full"></div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
))}
|
||||
{chat.answer.sources.map((source, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="rounded-xl bg-secondary p-4 flex flex-col gap-2 min-w-72"
|
||||
>
|
||||
<div className="text-foreground-menu">
|
||||
{source.type}
|
||||
</div>
|
||||
<div>{source.title}</div>
|
||||
</div>
|
||||
))}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
|
||||
{/* Summary */}
|
||||
<div>
|
||||
<div className="text-foreground-menu py-2">Summary</div>
|
||||
<div className="text-base">
|
||||
{chat.answer.parts.length === 0 && (
|
||||
<div className="animate-pulse flex space-x-4">
|
||||
<div className="flex-1 space-y-3 py-1">
|
||||
<div className="h-2 bg-slate-700 rounded"></div>
|
||||
<div className="h-2 bg-slate-700 rounded"></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Markdown
|
||||
remarkPlugins={[remarkGfm, [remarkMath]]}
|
||||
rehypePlugins={[
|
||||
rehypeKatex,
|
||||
[
|
||||
rehypeHighlight,
|
||||
{
|
||||
detect: true,
|
||||
ignoreMissing: true,
|
||||
subset: codeLanguageSubset,
|
||||
},
|
||||
],
|
||||
]}
|
||||
components={{
|
||||
code: code as any,
|
||||
p: p as any,
|
||||
}}
|
||||
className="flex flex-col gap-2"
|
||||
>
|
||||
{chat.answer.parts.map((part) => part.text).join("")}
|
||||
</Markdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
|
|
|||
25
apps/web/app/(dash)/chat/markdownRenderHelpers.tsx
Normal file
25
apps/web/app/(dash)/chat/markdownRenderHelpers.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { DetailedHTMLProps, HTMLAttributes, memo } from "react";
|
||||
import { ExtraProps } from "react-markdown";
|
||||
import CodeBlock from "./CodeBlock";
|
||||
|
||||
export const code = memo((props: JSX.IntrinsicElements["code"]) => {
|
||||
const { className, children } = props;
|
||||
const match = /language-(\w+)/.exec(className || "");
|
||||
const lang = match && match[1];
|
||||
|
||||
return <CodeBlock lang={lang || "text"} codeChildren={children as any} />;
|
||||
});
|
||||
|
||||
export const p = memo(
|
||||
(
|
||||
props?: Omit<
|
||||
DetailedHTMLProps<
|
||||
HTMLAttributes<HTMLParagraphElement>,
|
||||
HTMLParagraphElement
|
||||
>,
|
||||
"ref"
|
||||
>,
|
||||
) => {
|
||||
return <p className="whitespace-pre-wrap">{props?.children}</p>;
|
||||
},
|
||||
);
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import ChatWindow from "./chatWindow";
|
||||
import { chatSearchParamsCache } from "../../helpers/lib/searchParams";
|
||||
// @ts-expect-error
|
||||
await import("katex/dist/katex.min.css");
|
||||
|
||||
function Page({
|
||||
searchParams,
|
||||
|
|
@ -10,7 +12,7 @@ function Page({
|
|||
|
||||
console.log(spaces);
|
||||
|
||||
return <ChatWindow q={q} />;
|
||||
return <ChatWindow q={q} spaces={[]} />;
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,17 @@ import { motion } from "framer-motion";
|
|||
import { Label } from "@repo/ui/shadcn/label";
|
||||
import { Input } from "@repo/ui/shadcn/input";
|
||||
import { Textarea } from "@repo/ui/shadcn/textarea";
|
||||
import { createSpace } from "../actions/doers";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@repo/ui/shadcn/select";
|
||||
import { Space } from "../actions/types";
|
||||
import { getSpaces } from "../actions/fetchers";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export function DynamicIsland() {
|
||||
const { scrollYProgress } = useScroll();
|
||||
|
|
@ -80,7 +91,7 @@ function DynamicIslandContent() {
|
|||
{show ? (
|
||||
<div
|
||||
onClick={() => setshow(!show)}
|
||||
className="bg-[#1F2428] px-3 w-[2.23rem] overflow-hidden hover:w-[9.2rem] whitespace-nowrap py-2 rounded-3xl transition-[width] cursor-pointer"
|
||||
className="bg-secondary px-3 w-[2.23rem] overflow-hidden hover:w-[9.2rem] whitespace-nowrap py-2 rounded-3xl transition-[width] cursor-pointer"
|
||||
>
|
||||
<div className="flex gap-4 items-center">
|
||||
<Image src={AddIcon} alt="Add icon" />
|
||||
|
|
@ -99,7 +110,25 @@ function DynamicIslandContent() {
|
|||
const fakeitems = ["spaces", "page", "note"];
|
||||
|
||||
function ToolBar({ cancelfn }: { cancelfn: () => void }) {
|
||||
const [spaces, setSpaces] = useState<Space[]>([]);
|
||||
|
||||
const [index, setIndex] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
let spaces = await getSpaces();
|
||||
|
||||
if (!spaces.success || !spaces.data) {
|
||||
toast.warning("Unable to get spaces", {
|
||||
richColors: true,
|
||||
});
|
||||
setSpaces([]);
|
||||
return;
|
||||
}
|
||||
setSpaces(spaces.data);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
|
|
@ -120,7 +149,7 @@ function ToolBar({ cancelfn }: { cancelfn: () => void }) {
|
|||
}}
|
||||
className="flex flex-col items-center"
|
||||
>
|
||||
<div className="bg-[#1F2428] py-[.35rem] px-[.6rem] rounded-2xl">
|
||||
<div className="bg-secondary py-[.35rem] px-[.6rem] rounded-2xl">
|
||||
<HoverEffect
|
||||
items={fakeitems}
|
||||
index={index}
|
||||
|
|
@ -130,9 +159,9 @@ function ToolBar({ cancelfn }: { cancelfn: () => void }) {
|
|||
{index === 0 ? (
|
||||
<SpaceForm cancelfn={cancelfn} />
|
||||
) : index === 1 ? (
|
||||
<PageForm cancelfn={cancelfn} />
|
||||
<PageForm cancelfn={cancelfn} spaces={spaces} />
|
||||
) : (
|
||||
<NoteForm cancelfn={cancelfn} />
|
||||
<NoteForm cancelfn={cancelfn} spaces={spaces} />
|
||||
)}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
|
|
@ -182,7 +211,10 @@ export const HoverEffect = ({
|
|||
|
||||
function SpaceForm({ cancelfn }: { cancelfn: () => void }) {
|
||||
return (
|
||||
<div className="bg-[#1F2428] px-4 py-3 rounded-2xl mt-2 flex flex-col gap-3">
|
||||
<form
|
||||
action={createSpace}
|
||||
className="bg-secondary border border-muted-foreground px-4 py-3 rounded-2xl mt-2 flex flex-col gap-3"
|
||||
>
|
||||
<div>
|
||||
<Label className="text-[#858B92]" htmlFor="name">
|
||||
Name
|
||||
|
|
@ -190,34 +222,55 @@ function SpaceForm({ cancelfn }: { cancelfn: () => void }) {
|
|||
<Input
|
||||
className="bg-[#2B3237] focus-visible:ring-0 border-none focus-visible:ring-offset-0"
|
||||
id="name"
|
||||
name="name"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<a className="text-blue-500" href="">
|
||||
pull from store
|
||||
</a>
|
||||
<div
|
||||
{/* <div
|
||||
onClick={cancelfn}
|
||||
className="bg-[#2B3237] px-2 py-1 rounded-xl cursor-pointer"
|
||||
>
|
||||
cancel
|
||||
</div>
|
||||
</div> */}
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-[#2B3237] px-2 py-1 rounded-xl cursor-pointer"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
function PageForm({ cancelfn }: { cancelfn: () => void }) {
|
||||
function PageForm({
|
||||
cancelfn,
|
||||
spaces,
|
||||
}: {
|
||||
cancelfn: () => void;
|
||||
spaces: Space[];
|
||||
}) {
|
||||
return (
|
||||
<div className="bg-[#1F2428] px-4 py-3 rounded-2xl mt-2 flex flex-col gap-3">
|
||||
<div className="bg-secondary border border-muted-foreground px-4 py-3 rounded-2xl mt-2 flex flex-col gap-3">
|
||||
<div>
|
||||
<Label className="text-[#858B92]" htmlFor="name">
|
||||
<Label className="text-[#858B92]" htmlFor="space">
|
||||
Space
|
||||
</Label>
|
||||
<Input
|
||||
className="bg-[#2B3237] focus-visible:ring-0 border-none focus-visible:ring-offset-0"
|
||||
id="name"
|
||||
/>
|
||||
<Select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Space" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-secondary text-white">
|
||||
{spaces.map((space) => (
|
||||
<SelectItem key={space.id} value={space.id.toString()}>
|
||||
{space.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-[#858B92]" htmlFor="name">
|
||||
|
|
@ -240,17 +293,31 @@ function PageForm({ cancelfn }: { cancelfn: () => void }) {
|
|||
);
|
||||
}
|
||||
|
||||
function NoteForm({ cancelfn }: { cancelfn: () => void }) {
|
||||
function NoteForm({
|
||||
cancelfn,
|
||||
spaces,
|
||||
}: {
|
||||
cancelfn: () => void;
|
||||
spaces: Space[];
|
||||
}) {
|
||||
return (
|
||||
<div className="bg-[#1F2428] px-4 py-3 rounded-2xl mt-2 flex flex-col gap-3">
|
||||
<div className="bg-secondary border border-muted-foreground px-4 py-3 rounded-2xl mt-2 flex flex-col gap-3">
|
||||
<div>
|
||||
<Label className="text-[#858B92]" htmlFor="name">
|
||||
Space
|
||||
</Label>
|
||||
<Input
|
||||
className="bg-[#2B3237] focus-visible:ring-0 border-none focus-visible:ring-offset-0"
|
||||
id="name"
|
||||
/>
|
||||
<Select>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Space" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="bg-secondary text-white">
|
||||
{spaces.map((space) => (
|
||||
<SelectItem key={space.id} value={space.id.toString()}>
|
||||
{space.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-[#858B92]" htmlFor="name">
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import DynamicIsland from "./dynamicisland";
|
|||
function Header() {
|
||||
return (
|
||||
<div>
|
||||
<div className="absolute left-0 w-full flex items-center justify-between z-10">
|
||||
<div className="fixed left-0 w-full flex items-center justify-between z-10">
|
||||
<Link className="px-5" href="/home">
|
||||
<Image
|
||||
src={Logo}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import Menu from "../menu";
|
|||
import Header from "../header";
|
||||
import QueryInput from "./queryinput";
|
||||
import { homeSearchParamsCache } from "@/app/helpers/lib/searchParams";
|
||||
import { getSpaces } from "../actions";
|
||||
import { getSpaces } from "@/app/actions/fetchers";
|
||||
|
||||
async function Page({
|
||||
searchParams,
|
||||
|
|
@ -13,7 +13,12 @@ async function Page({
|
|||
// TODO: use this to show a welcome page/modal
|
||||
const { firstTime } = homeSearchParamsCache.parse(searchParams);
|
||||
|
||||
const spaces = await getSpaces();
|
||||
let spaces = await getSpaces();
|
||||
|
||||
if (!spaces.success) {
|
||||
// TODO: handle this error properly.
|
||||
spaces.data = [];
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl h-full justify-center flex mx-auto w-full flex-col">
|
||||
|
|
@ -21,7 +26,7 @@ async function Page({
|
|||
{/* <div className="">hi {firstTime ? 'first time' : ''}</div> */}
|
||||
|
||||
<div className="w-full h-96">
|
||||
<QueryInput initialSpaces={spaces} />
|
||||
<QueryInput initialSpaces={spaces.data} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
import { ArrowRightIcon } from "@repo/ui/icons";
|
||||
import Image from "next/image";
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import Divider from "@repo/ui/shadcn/divider";
|
||||
import { MultipleSelector, Option } from "@repo/ui/shadcn/combobox";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { getSpaces } from "@/app/actions/fetchers";
|
||||
|
||||
function QueryInput({
|
||||
initialQuery = "",
|
||||
|
|
@ -13,7 +14,10 @@ function QueryInput({
|
|||
disabled = false,
|
||||
}: {
|
||||
initialQuery?: string;
|
||||
initialSpaces?: { user: string | null; id: number; name: string }[];
|
||||
initialSpaces?: {
|
||||
id: number;
|
||||
name: string;
|
||||
}[];
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
const [q, setQ] = useState(initialQuery);
|
||||
|
|
@ -41,10 +45,14 @@ function QueryInput({
|
|||
return newQ;
|
||||
};
|
||||
|
||||
const options = initialSpaces.map((x) => ({
|
||||
label: x.name,
|
||||
value: x.id.toString(),
|
||||
}));
|
||||
const options = useMemo(
|
||||
() =>
|
||||
initialSpaces.map((x) => ({
|
||||
label: x.name,
|
||||
value: x.id.toString(),
|
||||
})),
|
||||
[initialSpaces],
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
|
@ -82,6 +90,7 @@ function QueryInput({
|
|||
{/* selected sources */}
|
||||
<div className="flex items-center gap-6 p-2 h-auto bg-secondary rounded-b-[24px]">
|
||||
<MultipleSelector
|
||||
key={options.length}
|
||||
disabled={disabled}
|
||||
defaultOptions={options}
|
||||
onChange={(e) => setSelectedSpaces(e.map((x) => parseInt(x.value)))}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import Header from "./header";
|
||||
import Menu from "./menu";
|
||||
import { ensureAuth } from "./actions";
|
||||
import { redirect } from "next/navigation";
|
||||
import { auth } from "../helpers/server/auth";
|
||||
import { Toaster } from "@repo/ui/shadcn/sonner";
|
||||
|
||||
async function Layout({ children }: { children: React.ReactNode }) {
|
||||
const info = await ensureAuth();
|
||||
const info = await auth();
|
||||
|
||||
if (!info) {
|
||||
return redirect("/signin");
|
||||
|
|
@ -17,6 +18,8 @@ async function Layout({ children }: { children: React.ReactNode }) {
|
|||
<Menu />
|
||||
|
||||
{children}
|
||||
|
||||
<Toaster />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ function Menu() {
|
|||
];
|
||||
|
||||
return (
|
||||
<div className="absolute h-screen pb-[25vh] w-full p-4 flex items-end justify-end lg:justify-start lg:items-center top-0 left-0 pointer-events-none">
|
||||
<div className="fixed h-screen pb-[25vh] w-full p-4 flex items-end justify-end lg:justify-start lg:items-center top-0 left-0 pointer-events-none">
|
||||
<div className="">
|
||||
<div className="pointer-events-auto group flex w-14 text-foreground-menu text-[15px] font-medium flex-col items-start gap-6 overflow-hidden rounded-[28px] bg-secondary px-3 py-4 duration-200 hover:w-40">
|
||||
{menuItems.map((item) => (
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"scripts": {
|
||||
"vercel-build": "next build"
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ export default async function Home() {
|
|||
console.log(user);
|
||||
|
||||
if (user) {
|
||||
// await redirect("/home")
|
||||
await redirect("/home");
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
43
apps/web/app/actions/doers.ts
Normal file
43
apps/web/app/actions/doers.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { db } from "../helpers/server/db";
|
||||
import { space } from "../helpers/server/db/schema";
|
||||
import { ServerActionReturnType } from "./types";
|
||||
import { auth } from "../helpers/server/auth";
|
||||
|
||||
export const createSpace = async (
|
||||
input: string | FormData,
|
||||
): ServerActionReturnType<number> => {
|
||||
const data = await auth();
|
||||
|
||||
if (!data || !data.user) {
|
||||
return { error: "Not authenticated", success: false };
|
||||
}
|
||||
|
||||
if (typeof input === "object") {
|
||||
input = (input as FormData).get("name") as string;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await db
|
||||
.insert(space)
|
||||
.values({ name: input, user: data.user.id });
|
||||
|
||||
revalidatePath("/home");
|
||||
return { success: true, data: 1 };
|
||||
} catch (e: unknown) {
|
||||
const error = e as Error;
|
||||
if (
|
||||
error.message.includes("D1_ERROR: UNIQUE constraint failed: space.name")
|
||||
) {
|
||||
return { success: false, data: 0, error: "Space already exists" };
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
data: 0,
|
||||
error: "Failed to create space with error: " + error.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
25
apps/web/app/actions/fetchers.ts
Normal file
25
apps/web/app/actions/fetchers.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
"use server";
|
||||
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../helpers/server/db";
|
||||
import { users } from "../helpers/server/db/schema";
|
||||
import { ServerActionReturnType, Space } from "./types";
|
||||
import { auth } from "../helpers/server/auth";
|
||||
|
||||
export const getSpaces = async (): ServerActionReturnType<Space[]> => {
|
||||
const data = await auth();
|
||||
|
||||
if (!data || !data.user) {
|
||||
return { error: "Not authenticated", success: false };
|
||||
}
|
||||
|
||||
const spaces = await db.query.space.findMany({
|
||||
where: eq(users, data.user.id),
|
||||
});
|
||||
|
||||
const spacesWithoutUser = spaces.map((space) => {
|
||||
return { ...space, user: undefined };
|
||||
});
|
||||
|
||||
return { success: true, data: spacesWithoutUser };
|
||||
};
|
||||
10
apps/web/app/actions/types.ts
Normal file
10
apps/web/app/actions/types.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
export type Space = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type ServerActionReturnType<T> = Promise<{
|
||||
error?: string;
|
||||
success: boolean;
|
||||
data?: T;
|
||||
}>;
|
||||
|
|
@ -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 },
|
||||
);
|
||||
}
|
||||
|
||||
console.log("sourcesOnly", sourcesOnly);
|
||||
const modelCompatible = await convertChatHistoryList(
|
||||
validated.data.chatHistory,
|
||||
);
|
||||
|
||||
if (sourcesOnly == "true") {
|
||||
const data = await resp.json();
|
||||
console.log("data", data);
|
||||
return new Response(JSON.stringify(data), { status: 200 });
|
||||
}
|
||||
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,
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
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 },
|
||||
);
|
||||
}
|
||||
console.log("sourcesOnly", sourcesOnly);
|
||||
|
||||
// Stream the response back to the client
|
||||
const { readable, writable } = new TransformStream();
|
||||
resp && resp.body!.pipeTo(writable);
|
||||
if (sourcesOnly == "true") {
|
||||
const data = await resp.json();
|
||||
console.log("data", data);
|
||||
return new Response(JSON.stringify(data), { status: 200 });
|
||||
}
|
||||
|
||||
return new Response(readable, { status: 200 });
|
||||
} catch {}
|
||||
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 });
|
||||
}
|
||||
|
|
|
|||
37
apps/web/app/helpers/constants.ts
Normal file
37
apps/web/app/helpers/constants.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
export const codeLanguageSubset = [
|
||||
"python",
|
||||
"javascript",
|
||||
"java",
|
||||
"go",
|
||||
"bash",
|
||||
"c",
|
||||
"cpp",
|
||||
"csharp",
|
||||
"css",
|
||||
"diff",
|
||||
"graphql",
|
||||
"json",
|
||||
"kotlin",
|
||||
"less",
|
||||
"lua",
|
||||
"makefile",
|
||||
"markdown",
|
||||
"objectivec",
|
||||
"perl",
|
||||
"php",
|
||||
"php-template",
|
||||
"plaintext",
|
||||
"python-repl",
|
||||
"r",
|
||||
"ruby",
|
||||
"rust",
|
||||
"scss",
|
||||
"shell",
|
||||
"sql",
|
||||
"swift",
|
||||
"typescript",
|
||||
"vbnet",
|
||||
"wasm",
|
||||
"xml",
|
||||
"yaml",
|
||||
];
|
||||
|
|
@ -2,6 +2,7 @@ import NextAuth, { NextAuthResult } from "next-auth";
|
|||
import Google from "next-auth/providers/google";
|
||||
import { DrizzleAdapter } from "@auth/drizzle-adapter";
|
||||
import { db } from "./db";
|
||||
import { accounts, sessions, users, verificationTokens } from "./db/schema";
|
||||
|
||||
export const {
|
||||
handlers: { GET, POST },
|
||||
|
|
@ -10,16 +11,20 @@ export const {
|
|||
auth,
|
||||
} = NextAuth({
|
||||
secret: process.env.BACKEND_SECURITY_KEY,
|
||||
callbacks: {
|
||||
session: ({ session, token, user }) => ({
|
||||
...session,
|
||||
user: {
|
||||
...session.user,
|
||||
id: user.id,
|
||||
},
|
||||
}),
|
||||
},
|
||||
adapter: DrizzleAdapter(db),
|
||||
// callbacks: {
|
||||
// session: ({ session, token, user }) => ({
|
||||
// ...session,
|
||||
// user: {
|
||||
// ...session.user,
|
||||
// },
|
||||
// }),
|
||||
// },
|
||||
adapter: DrizzleAdapter(db, {
|
||||
usersTable: users,
|
||||
accountsTable: accounts,
|
||||
sessionsTable: sessions,
|
||||
verificationTokensTable: verificationTokens,
|
||||
}),
|
||||
providers: [
|
||||
Google({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID,
|
||||
|
|
|
|||
|
|
@ -7,75 +7,88 @@ import {
|
|||
text,
|
||||
integer,
|
||||
} from "drizzle-orm/sqlite-core";
|
||||
import type { AdapterAccountType } from "next-auth/adapters";
|
||||
|
||||
export const createTable = sqliteTableCreator((name) => `${name}`);
|
||||
|
||||
export const users = createTable("user", {
|
||||
id: text("id", { length: 255 }).notNull().primaryKey(),
|
||||
name: text("name", { length: 255 }),
|
||||
email: text("email", { length: 255 }).notNull(),
|
||||
emailVerified: int("emailVerified", { mode: "timestamp" }).default(
|
||||
sql`CURRENT_TIMESTAMP`,
|
||||
),
|
||||
image: text("image", { length: 255 }),
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
name: text("name"),
|
||||
email: text("email").notNull(),
|
||||
emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
|
||||
image: text("image"),
|
||||
});
|
||||
|
||||
export type User = typeof users.$inferSelect;
|
||||
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
accounts: many(accounts),
|
||||
sessions: many(sessions),
|
||||
}));
|
||||
|
||||
export const accounts = createTable(
|
||||
"account",
|
||||
{
|
||||
id: integer("id").notNull().primaryKey({ autoIncrement: true }),
|
||||
userId: text("userId", { length: 255 })
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
type: text("type", { length: 255 }).notNull(),
|
||||
provider: text("provider", { length: 255 }).notNull(),
|
||||
providerAccountId: text("providerAccountId", { length: 255 }).notNull(),
|
||||
type: text("type").$type<AdapterAccountType>().notNull(),
|
||||
provider: text("provider").notNull(),
|
||||
providerAccountId: text("providerAccountId").notNull(),
|
||||
refresh_token: text("refresh_token"),
|
||||
access_token: text("access_token"),
|
||||
expires_at: int("expires_at"),
|
||||
token_type: text("token_type", { length: 255 }),
|
||||
scope: text("scope", { length: 255 }),
|
||||
expires_at: integer("expires_at"),
|
||||
token_type: text("token_type"),
|
||||
scope: text("scope"),
|
||||
id_token: text("id_token"),
|
||||
session_state: text("session_state", { length: 255 }),
|
||||
oauth_token_secret: text("oauth_token_secret"),
|
||||
oauth_token: text("oauth_token"),
|
||||
session_state: text("session_state"),
|
||||
},
|
||||
(account) => ({
|
||||
userIdIdx: index("account_userId_idx").on(account.userId),
|
||||
compoundKey: primaryKey({
|
||||
columns: [account.provider, account.providerAccountId],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const sessions = createTable(
|
||||
"session",
|
||||
{
|
||||
id: integer("id").notNull().primaryKey({ autoIncrement: true }),
|
||||
sessionToken: text("sessionToken", { length: 255 }).notNull(),
|
||||
userId: text("userId", { length: 255 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
expires: int("expires", { mode: "timestamp" }).notNull(),
|
||||
},
|
||||
(session) => ({
|
||||
userIdIdx: index("session_userId_idx").on(session.userId),
|
||||
}),
|
||||
);
|
||||
export const sessions = createTable("session", {
|
||||
sessionToken: text("sessionToken").primaryKey(),
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
|
||||
});
|
||||
|
||||
export const verificationTokens = createTable(
|
||||
"verificationToken",
|
||||
{
|
||||
identifier: text("identifier", { length: 255 }).notNull(),
|
||||
token: text("token", { length: 255 }).notNull(),
|
||||
expires: int("expires", { mode: "timestamp" }).notNull(),
|
||||
identifier: text("identifier").notNull(),
|
||||
token: text("token").notNull(),
|
||||
expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
|
||||
},
|
||||
(vt) => ({
|
||||
compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }),
|
||||
(verificationToken) => ({
|
||||
compositePk: primaryKey({
|
||||
columns: [verificationToken.identifier, verificationToken.token],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const authenticators = createTable(
|
||||
"authenticator",
|
||||
{
|
||||
credentialID: text("credentialID").notNull().unique(),
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
providerAccountId: text("providerAccountId").notNull(),
|
||||
credentialPublicKey: text("credentialPublicKey").notNull(),
|
||||
counter: integer("counter").notNull(),
|
||||
credentialDeviceType: text("credentialDeviceType").notNull(),
|
||||
credentialBackedUp: integer("credentialBackedUp", {
|
||||
mode: "boolean",
|
||||
}).notNull(),
|
||||
transports: text("transports"),
|
||||
},
|
||||
(authenticator) => ({
|
||||
compositePK: primaryKey({
|
||||
columns: [authenticator.userId, authenticator.credentialID],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
@ -94,7 +107,7 @@ export const storedContent = createTable(
|
|||
"page",
|
||||
),
|
||||
image: text("image", { length: 255 }),
|
||||
user: text("user", { length: 255 }).references(() => users.id, {
|
||||
userId: int("user").references(() => users.id, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
},
|
||||
|
|
@ -102,7 +115,7 @@ export const storedContent = createTable(
|
|||
urlIdx: index("storedContent_url_idx").on(sc.url),
|
||||
savedAtIdx: index("storedContent_savedAt_idx").on(sc.savedAt),
|
||||
titleInx: index("storedContent_title_idx").on(sc.title),
|
||||
userIdx: index("storedContent_user_idx").on(sc.user),
|
||||
userIdx: index("storedContent_user_idx").on(sc.userId),
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,29 @@
|
|||
CREATE TABLE `account` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`userId` text(255) NOT NULL,
|
||||
`type` text(255) NOT NULL,
|
||||
`provider` text(255) NOT NULL,
|
||||
`providerAccountId` text(255) NOT NULL,
|
||||
`userId` text NOT NULL,
|
||||
`type` text NOT NULL,
|
||||
`provider` text NOT NULL,
|
||||
`providerAccountId` text NOT NULL,
|
||||
`refresh_token` text,
|
||||
`access_token` text,
|
||||
`expires_at` integer,
|
||||
`token_type` text(255),
|
||||
`scope` text(255),
|
||||
`token_type` text,
|
||||
`scope` text,
|
||||
`id_token` text,
|
||||
`session_state` text(255),
|
||||
`oauth_token_secret` text,
|
||||
`oauth_token` text,
|
||||
`session_state` text,
|
||||
PRIMARY KEY(`provider`, `providerAccountId`),
|
||||
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `authenticator` (
|
||||
`credentialID` text NOT NULL,
|
||||
`userId` text NOT NULL,
|
||||
`providerAccountId` text NOT NULL,
|
||||
`credentialPublicKey` text NOT NULL,
|
||||
`counter` integer NOT NULL,
|
||||
`credentialDeviceType` text NOT NULL,
|
||||
`credentialBackedUp` integer NOT NULL,
|
||||
`transports` text,
|
||||
PRIMARY KEY(`credentialID`, `userId`),
|
||||
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
|
|
@ -25,9 +36,8 @@ CREATE TABLE `contentToSpace` (
|
|||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `session` (
|
||||
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
`sessionToken` text(255) NOT NULL,
|
||||
`userId` text(255) NOT NULL,
|
||||
`sessionToken` text PRIMARY KEY NOT NULL,
|
||||
`userId` text NOT NULL,
|
||||
`expires` integer NOT NULL,
|
||||
FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
|
|
@ -50,27 +60,26 @@ CREATE TABLE `storedContent` (
|
|||
`ogImage` text(255),
|
||||
`type` text DEFAULT 'page',
|
||||
`image` text(255),
|
||||
`user` text(255),
|
||||
`user` integer,
|
||||
FOREIGN KEY (`user`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `user` (
|
||||
`id` text(255) PRIMARY KEY NOT NULL,
|
||||
`name` text(255),
|
||||
`email` text(255) NOT NULL,
|
||||
`emailVerified` integer DEFAULT CURRENT_TIMESTAMP,
|
||||
`image` text(255)
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`name` text,
|
||||
`email` text NOT NULL,
|
||||
`emailVerified` integer,
|
||||
`image` text
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `verificationToken` (
|
||||
`identifier` text(255) NOT NULL,
|
||||
`token` text(255) NOT NULL,
|
||||
`identifier` text NOT NULL,
|
||||
`token` text NOT NULL,
|
||||
`expires` integer NOT NULL,
|
||||
PRIMARY KEY(`identifier`, `token`)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX `account_userId_idx` ON `account` (`userId`);--> statement-breakpoint
|
||||
CREATE INDEX `session_userId_idx` ON `session` (`userId`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `authenticator_credentialID_unique` ON `authenticator` (`credentialID`);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `space_name_unique` ON `space` (`name`);--> statement-breakpoint
|
||||
CREATE INDEX `spaces_name_idx` ON `space` (`name`);--> statement-breakpoint
|
||||
CREATE INDEX `spaces_user_idx` ON `space` (`user`);--> statement-breakpoint
|
||||
|
|
|
|||
|
|
@ -1,43 +1,36 @@
|
|||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "409cec60-0c4b-4cda-8751-3e70768bbb6c",
|
||||
"id": "4a568d9b-a0e6-44ed-946b-694e34b063f3",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"account": {
|
||||
"name": "account",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "text(255)",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text(255)",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"provider": {
|
||||
"name": "provider",
|
||||
"type": "text(255)",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"providerAccountId": {
|
||||
"name": "providerAccountId",
|
||||
"type": "text(255)",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
|
|
@ -65,14 +58,14 @@
|
|||
},
|
||||
"token_type": {
|
||||
"name": "token_type",
|
||||
"type": "text(255)",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"scope": {
|
||||
"name": "scope",
|
||||
"type": "text(255)",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
|
|
@ -86,33 +79,13 @@
|
|||
},
|
||||
"session_state": {
|
||||
"name": "session_state",
|
||||
"type": "text(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"oauth_token_secret": {
|
||||
"name": "oauth_token_secret",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"oauth_token": {
|
||||
"name": "oauth_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"account_userId_idx": {
|
||||
"name": "account_userId_idx",
|
||||
"columns": ["userId"],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"account_userId_user_id_fk": {
|
||||
"name": "account_userId_user_id_fk",
|
||||
|
|
@ -124,7 +97,98 @@
|
|||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"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": {}
|
||||
},
|
||||
"contentToSpace": {
|
||||
|
|
@ -177,23 +241,16 @@
|
|||
"session": {
|
||||
"name": "session",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"sessionToken": {
|
||||
"name": "sessionToken",
|
||||
"type": "text(255)",
|
||||
"primaryKey": false,
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"userId": {
|
||||
"name": "userId",
|
||||
"type": "text(255)",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
|
|
@ -206,13 +263,7 @@
|
|||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"session_userId_idx": {
|
||||
"name": "session_userId_idx",
|
||||
"columns": ["userId"],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"session_userId_user_id_fk": {
|
||||
"name": "session_userId_user_id_fk",
|
||||
|
|
@ -360,7 +411,7 @@
|
|||
},
|
||||
"user": {
|
||||
"name": "user",
|
||||
"type": "text(255)",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
|
|
@ -407,21 +458,21 @@
|
|||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text(255)",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text(255)",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text(255)",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
|
|
@ -431,12 +482,11 @@
|
|||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "CURRENT_TIMESTAMP"
|
||||
"autoincrement": false
|
||||
},
|
||||
"image": {
|
||||
"name": "image",
|
||||
"type": "text(255)",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
|
|
@ -452,14 +502,14 @@
|
|||
"columns": {
|
||||
"identifier": {
|
||||
"name": "identifier",
|
||||
"type": "text(255)",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"token": {
|
||||
"name": "token",
|
||||
"type": "text(255)",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
{
|
||||
"idx": 0,
|
||||
"version": "6",
|
||||
"when": 1716677954608,
|
||||
"tag": "0000_calm_monster_badoon",
|
||||
"when": 1718412145023,
|
||||
"tag": "0000_absurd_pandemic",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { setupDevPlatform } from "@cloudflare/next-on-pages/next-dev";
|
|||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
transpilePackages: ["@repo/ui"],
|
||||
reactStrictMode: false,
|
||||
};
|
||||
export default MillionLint.next({
|
||||
rsc: true,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
"pages:build": "bunx @cloudflare/next-on-pages",
|
||||
"preview": "bun pages:build && wrangler pages dev",
|
||||
"deploy": "bun pages:build && wrangler pages deploy",
|
||||
"schema-update": "bunx drizzle-orm"
|
||||
"schema-update": "bunx drizzle-kit generate sqlite",
|
||||
"update-local-db": "bunx wrangler d1 execute dev-d1-anycontext --local"
|
||||
},
|
||||
"dependencies": {
|
||||
"@million/lint": "^1.0.0-rc.11",
|
||||
|
|
|
|||
14
package.json
14
package.json
|
|
@ -27,7 +27,7 @@
|
|||
"readline-sync": "^1.4.10",
|
||||
"tailwindcss": "^3.4.3",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"turbo": "latest",
|
||||
"turbo": "2.0.3",
|
||||
"vercel": "^34.2.0"
|
||||
},
|
||||
"engines": {
|
||||
|
|
@ -52,10 +52,12 @@
|
|||
"@iarna/toml": "^2.2.5",
|
||||
"@langchain/cloudflare": "^0.0.6",
|
||||
"@million/lint": "^1.0.0-rc.18",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-progress": "^1.0.3",
|
||||
"@radix-ui/react-scroll-area": "^1.0.5",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
|
|
@ -67,13 +69,21 @@
|
|||
"compromise": "^14.13.0",
|
||||
"drizzle-orm": "^0.30.10",
|
||||
"framer-motion": "^11.2.6",
|
||||
"katex": "^0.16.10",
|
||||
"lucide-react": "^0.379.0",
|
||||
"next-app-theme": "^0.1.10",
|
||||
"next-auth": "^5.0.0-beta.18",
|
||||
"next-themes": "^0.3.0",
|
||||
"random-js": "^2.1.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-hook-form": "^7.51.5",
|
||||
"sonner": "^1.4.41",
|
||||
"react-markdown": "^9.0.1",
|
||||
"rehype-highlight": "^7.0.0",
|
||||
"rehype-katex": "^7.0.0",
|
||||
"remark-gfm": "^4.0.0",
|
||||
"remark-math": "^6.0.0",
|
||||
"sonner": "^1.5.0",
|
||||
"tailwind-scrollbar": "^3.1.0",
|
||||
"tldraw": "^2.1.4",
|
||||
"uploadthing": "^6.10.4",
|
||||
"zod": "^3.23.8"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,54 @@
|
|||
export type ChatHistory = {
|
||||
question: string;
|
||||
answer: {
|
||||
parts: { text: string }[];
|
||||
sources: { isNote: boolean; source: string }[];
|
||||
};
|
||||
};
|
||||
import { z } from "zod";
|
||||
|
||||
export const ChatHistoryZod = z.object({
|
||||
question: z.string(),
|
||||
answer: z.object({
|
||||
parts: z.array(z.object({ text: z.string() })),
|
||||
sources: z.array(
|
||||
z.object({
|
||||
type: z.enum(["note", "page", "tweet"]),
|
||||
source: z.string(),
|
||||
title: z.string(),
|
||||
content: z.string(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
export type ChatHistory = z.infer<typeof ChatHistoryZod>;
|
||||
|
||||
export const ModelCompatibleChatHistoryZod = z.array(
|
||||
z.object({
|
||||
role: z.union([
|
||||
z.literal("user"),
|
||||
z.literal("assistant"),
|
||||
z.literal("system"),
|
||||
]),
|
||||
content: z.string(),
|
||||
}),
|
||||
);
|
||||
|
||||
export type ModelCompatibleChatHistory = z.infer<
|
||||
typeof ModelCompatibleChatHistoryZod
|
||||
>;
|
||||
|
||||
export function convertChatHistoryList(
|
||||
chatHistoryList: ChatHistory[],
|
||||
): ModelCompatibleChatHistory {
|
||||
let convertedChats: ModelCompatibleChatHistory = [];
|
||||
|
||||
chatHistoryList.forEach((chat) => {
|
||||
convertedChats.push(
|
||||
{
|
||||
role: "user",
|
||||
content: chat.question,
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: chat.answer.parts.map((part) => part.text).join(" "),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return convertedChats;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,6 +49,42 @@ body {
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.markdown table {
|
||||
--tw-border-spacing-x: 0px;
|
||||
--tw-border-spacing-y: 0px;
|
||||
border-collapse: separate;
|
||||
border-spacing: var(--tw-border-spacing-x) var(--tw-border-spacing-y);
|
||||
width: 100%;
|
||||
}
|
||||
.markdown th {
|
||||
background-color: rgba(236, 236, 241, 0.2);
|
||||
border-bottom-width: 1px;
|
||||
border-left-width: 1px;
|
||||
border-top-width: 1px;
|
||||
padding: 0.25rem 0.75rem;
|
||||
}
|
||||
.markdown th:first-child {
|
||||
border-top-left-radius: 0.375rem;
|
||||
}
|
||||
.markdown th:last-child {
|
||||
border-right-width: 1px;
|
||||
border-top-right-radius: 0.375rem;
|
||||
}
|
||||
.markdown td {
|
||||
border-bottom-width: 1px;
|
||||
border-left-width: 1px;
|
||||
padding: 0.25rem 0.75rem;
|
||||
}
|
||||
.markdown td:last-child {
|
||||
border-right-width: 1px;
|
||||
}
|
||||
.markdown tbody tr:last-child td:first-child {
|
||||
border-bottom-left-radius: 0.375rem;
|
||||
}
|
||||
.markdown tbody tr:last-child td:last-child {
|
||||
border-bottom-right-radius: 0.375rem;
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
|
|
@ -57,6 +93,34 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
.markdown ol,
|
||||
.markdown ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
.markdown ol li,
|
||||
.markdown ol li > p,
|
||||
.markdown ol ol,
|
||||
.markdown ol ul,
|
||||
.markdown ul li,
|
||||
.markdown ul li > p,
|
||||
.markdown ul ol,
|
||||
.markdown ul ul {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.markdown ul li:before {
|
||||
content: "•";
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
margin-left: -1rem;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
|
||||
.gradient-background {
|
||||
background: linear-gradient(
|
||||
150deg,
|
||||
|
|
@ -84,3 +148,62 @@ body {
|
|||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #22303d;
|
||||
}
|
||||
|
||||
.no-scrollbar {
|
||||
/* For WebKit (Safari, Chrome, etc.) */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* For Firefox */
|
||||
scrollbar-width: none;
|
||||
|
||||
/* For IE and Edge */
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
:not(pre) > code.hljs,
|
||||
:not(pre) > code[class*="language-"] {
|
||||
border-radius: 0.3em;
|
||||
white-space: normal;
|
||||
}
|
||||
.hljs-comment {
|
||||
color: hsla(0, 0%, 100%, 0.5);
|
||||
}
|
||||
.hljs-meta {
|
||||
color: hsla(0, 0%, 100%, 0.6);
|
||||
}
|
||||
.hljs-built_in,
|
||||
.hljs-class .hljs-title {
|
||||
color: #e9950c;
|
||||
}
|
||||
.hljs-doctag,
|
||||
.hljs-formula,
|
||||
.hljs-keyword,
|
||||
.hljs-literal {
|
||||
color: #2e95d3;
|
||||
}
|
||||
.hljs-addition,
|
||||
.hljs-attribute,
|
||||
.hljs-meta-string,
|
||||
.hljs-regexp,
|
||||
.hljs-string {
|
||||
color: #00a67d;
|
||||
}
|
||||
.hljs-attr,
|
||||
.hljs-number,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-pseudo,
|
||||
.hljs-template-variable,
|
||||
.hljs-type,
|
||||
.hljs-variable {
|
||||
color: #df3079;
|
||||
}
|
||||
.hljs-bullet,
|
||||
.hljs-link,
|
||||
.hljs-selector-id,
|
||||
.hljs-symbol,
|
||||
.hljs-title {
|
||||
color: #f22c3d;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,11 @@ const config = {
|
|||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
|
||||
plugins: [
|
||||
require("tailwindcss-animate"),
|
||||
require("@tailwindcss/typography"),
|
||||
require("tailwind-scrollbar"),
|
||||
],
|
||||
} satisfies Config;
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
54
packages/ui/shadcn/accordion.tsx
Normal file
54
packages/ui/shadcn/accordion.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
|
||||
import { cn } from "@repo/ui/lib/utils";
|
||||
|
||||
const Accordion = AccordionPrimitive.Root;
|
||||
|
||||
const AccordionItem = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AccordionPrimitive.Item ref={ref} className={cn(className)} {...props} />
|
||||
));
|
||||
AccordionItem.displayName = "AccordionItem";
|
||||
|
||||
const AccordionTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-1 items-center gap-2 py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
));
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
|
||||
|
||||
const AccordionContent = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||
{...props}
|
||||
>
|
||||
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
));
|
||||
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
|
||||
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
|
||||
160
packages/ui/shadcn/select.tsx
Normal file
160
packages/ui/shadcn/select.tsx
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||
import { Check, ChevronDown, ChevronUp } from "lucide-react";
|
||||
|
||||
import { cn } from "@repo/ui/lib/utils";
|
||||
|
||||
const Select = SelectPrimitive.Root;
|
||||
|
||||
const SelectGroup = SelectPrimitive.Group;
|
||||
|
||||
const SelectValue = SelectPrimitive.Value;
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
));
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
||||
|
||||
const SelectScrollUpButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
));
|
||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
|
||||
|
||||
const SelectScrollDownButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
));
|
||||
SelectScrollDownButton.displayName =
|
||||
SelectPrimitive.ScrollDownButton.displayName;
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className,
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
));
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-foreground-menu data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
));
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
};
|
||||
31
packages/ui/shadcn/sonner.tsx
Normal file
31
packages/ui/shadcn/sonner.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
"use client";
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
import { Toaster as Sonner } from "sonner";
|
||||
|
||||
type ToasterProps = React.ComponentProps<typeof Sonner>;
|
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const { theme = "system" } = useTheme();
|
||||
|
||||
return (
|
||||
<Sonner
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
className="toaster group"
|
||||
toastOptions={{
|
||||
classNames: {
|
||||
toast:
|
||||
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
||||
description: "group-[.toast]:text-muted-foreground",
|
||||
actionButton:
|
||||
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
||||
cancelButton:
|
||||
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export { Toaster };
|
||||
Loading…
Add table
Reference in a new issue