diff --git a/website/src/components/Messages/MessageTableEntry.tsx b/website/src/components/Messages/MessageTableEntry.tsx
index 2bc17201..d272b960 100644
--- a/website/src/components/Messages/MessageTableEntry.tsx
+++ b/website/src/components/Messages/MessageTableEntry.tsx
@@ -3,7 +3,17 @@ import { boolean } from "boolean";
import NextLink from "next/link";
import { FlaggableElement } from "../FlaggableElement";
-export function MessageTableEntry({ item, idx }) {
+interface Message {
+ text: string;
+ id: string;
+ is_assistant: boolean;
+}
+interface MessageTableEntryProps {
+ item: Message;
+ idx: number;
+}
+export function MessageTableEntry(props: MessageTableEntryProps) {
+ const { item, idx } = props;
const bgColor = useColorModeValue(idx % 2 === 0 ? "bg-slate-800" : "bg-black", "bg-sky-900");
return (
diff --git a/website/src/components/Messages/MessageWithChildren.tsx b/website/src/components/Messages/MessageWithChildren.tsx
new file mode 100644
index 00000000..8fcd8658
--- /dev/null
+++ b/website/src/components/Messages/MessageWithChildren.tsx
@@ -0,0 +1,105 @@
+import { Box, CircularProgress, Flex, HStack, StackDivider, Text, TextProps, StackProps } from "@chakra-ui/react";
+import { useState } from "react";
+import useSWR from "swr";
+
+import fetcher from "src/lib/fetcher";
+import { MessageTableEntry } from "./MessageTableEntry";
+import { boolean } from "boolean";
+
+const MessageHeaderProps: TextProps = {
+ align: "center",
+ fontSize: "xl",
+ py: "2",
+};
+
+const MessageStackProps: StackProps = {
+ spacing: "2",
+ alignItems: "start",
+ justifyContent: "center",
+ divider: ,
+};
+
+interface MessageWithChildrenProps {
+ id: string;
+ depth?: number;
+ maxDepth?: number;
+ isOnlyChild?: boolean;
+}
+
+export function MessageWithChildren(props: MessageWithChildrenProps) {
+ const { id, depth, maxDepth, isOnlyChild = true } = props;
+
+ const [message, setMessage] = useState(null);
+ const [children, setChildren] = useState(null);
+
+ const { isLoading } = useSWR(id ? `/api/messages/${id}` : null, fetcher, {
+ onSuccess: (data) => {
+ setMessage(data);
+ },
+ onError: (err, key, config) => {
+ setMessage(null);
+ },
+ });
+ const { isLoading: isLoadingChildren } = useSWR(id ? `/api/messages/${id}/children` : null, fetcher, {
+ onSuccess: (data) => {
+ setChildren(data);
+ },
+ onError: (err, key, config) => {
+ setChildren(null);
+ },
+ });
+
+ const renderRecursive = maxDepth && ((depth && depth < maxDepth) || !depth);
+ const isFirst = depth === 0 || !depth;
+ const isFirstOrOnly = isFirst || boolean(isOnlyChild);
+
+ if (isLoading || isLoadingChildren) {
+ return ;
+ }
+
+ return (
+ <>
+ {message && (
+ <>
+ {isFirst ? "Message" : depth === 1 ? "Children" : "Ancestor"}
+
+
+
+
+
+
+
+ >
+ )}
+ {children && Array.isArray(children) && children.length > 0 ? (
+ renderRecursive ? (
+
+ {children.map((item, idx) => (
+
+
+
+ ))}
+
+ ) : (
+ <>
+ {isFirstOrOnly ? "Children" : "Ancestor"}
+
+ {children.map((item, idx) => (
+
+
+
+ ))}
+
+ >
+ )
+ ) : (
+ <>>
+ )}
+ >
+ );
+}
diff --git a/website/src/pages/api/messages/[id]/children.ts b/website/src/pages/api/messages/[id]/children.ts
new file mode 100644
index 00000000..9c8fb84a
--- /dev/null
+++ b/website/src/pages/api/messages/[id]/children.ts
@@ -0,0 +1,27 @@
+import { getToken } from "next-auth/jwt";
+
+const handler = async (req, res) => {
+ const token = await getToken({ req });
+
+ // Return nothing if the user isn't registered.
+ if (!token) {
+ res.status(401).end();
+ return;
+ }
+
+ const { id } = req.query;
+
+ const messagesRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/messages/${id}/children`, {
+ method: "GET",
+ headers: {
+ "X-API-Key": process.env.FASTAPI_KEY,
+ "Content-Type": "application/json",
+ },
+ });
+ const messages = await messagesRes.json();
+
+ // Send recieved messages to the client.
+ res.status(200).json(messages);
+};
+
+export default handler;
diff --git a/website/src/pages/api/messages/[id]/conversation.ts b/website/src/pages/api/messages/[id]/conversation.ts
new file mode 100644
index 00000000..6fa8feb9
--- /dev/null
+++ b/website/src/pages/api/messages/[id]/conversation.ts
@@ -0,0 +1,27 @@
+import { getToken } from "next-auth/jwt";
+
+const handler = async (req, res) => {
+ const token = await getToken({ req });
+
+ // Return nothing if the user isn't registered.
+ if (!token) {
+ res.status(401).end();
+ return;
+ }
+
+ const { id } = req.query;
+
+ const messagesRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/messages/${id}/conversation`, {
+ method: "GET",
+ headers: {
+ "X-API-Key": process.env.FASTAPI_KEY,
+ "Content-Type": "application/json",
+ },
+ });
+ const messages = await messagesRes.json();
+
+ // Send recieved messages to the client.
+ res.status(200).json(messages);
+};
+
+export default handler;
diff --git a/website/src/pages/api/messages/[id]/index.ts b/website/src/pages/api/messages/[id]/index.ts
new file mode 100644
index 00000000..8e056532
--- /dev/null
+++ b/website/src/pages/api/messages/[id]/index.ts
@@ -0,0 +1,27 @@
+import { getToken } from "next-auth/jwt";
+
+const handler = async (req, res) => {
+ const token = await getToken({ req });
+
+ // Return nothing if the user isn't registered.
+ if (!token) {
+ res.status(401).end();
+ return;
+ }
+
+ const { id } = req.query;
+
+ const messageRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/messages/${id}`, {
+ method: "GET",
+ headers: {
+ "X-API-Key": process.env.FASTAPI_KEY,
+ "Content-Type": "application/json",
+ },
+ });
+ const message = await messageRes.json();
+
+ // Send recieved messages to the client.
+ res.status(200).json(message);
+};
+
+export default handler;
diff --git a/website/src/pages/api/messages/[id]/parent.ts b/website/src/pages/api/messages/[id]/parent.ts
new file mode 100644
index 00000000..de6fbac6
--- /dev/null
+++ b/website/src/pages/api/messages/[id]/parent.ts
@@ -0,0 +1,48 @@
+import { getToken } from "next-auth/jwt";
+
+const handler = async (req, res) => {
+ const token = await getToken({ req });
+
+ // Return nothing if the user isn't registered.
+ if (!token) {
+ res.status(401).end();
+ return;
+ }
+
+ const { id } = req.query;
+
+ if (!id) {
+ res.status(400).end();
+ return;
+ }
+
+ const messageRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/messages/${id}`, {
+ method: "GET",
+ headers: {
+ "X-API-Key": process.env.FASTAPI_KEY,
+ "Content-Type": "application/json",
+ },
+ });
+
+ const message = await messageRes.json();
+
+ if (!message.parent_id) {
+ res.status(404).end();
+ return;
+ }
+
+ const parentRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/messages/${message.parent_id}`, {
+ method: "GET",
+ headers: {
+ "X-API-Key": process.env.FASTAPI_KEY,
+ "Content-Type": "application/json",
+ },
+ });
+
+ const parent = await parentRes.json();
+
+ // Send recieved messages to the client.
+ res.status(200).json(parent);
+};
+
+export default handler;
diff --git a/website/src/pages/api/messages/index.tsx b/website/src/pages/api/messages/index.ts
similarity index 100%
rename from website/src/pages/api/messages/index.tsx
rename to website/src/pages/api/messages/index.ts
diff --git a/website/src/pages/api/messages/user.tsx b/website/src/pages/api/messages/user.ts
similarity index 100%
rename from website/src/pages/api/messages/user.tsx
rename to website/src/pages/api/messages/user.ts
diff --git a/website/src/pages/messages/[id]/index.tsx b/website/src/pages/messages/[id]/index.tsx
new file mode 100644
index 00000000..e778e74a
--- /dev/null
+++ b/website/src/pages/messages/[id]/index.tsx
@@ -0,0 +1,64 @@
+import { Box, Container, Text, useColorModeValue } from "@chakra-ui/react";
+import Head from "next/head";
+import { useRouter } from "next/router";
+import { useState } from "react";
+import useSWR from "swr";
+
+import fetcher from "src/lib/fetcher";
+import { MessageTableEntry } from "src/components/Messages/MessageTableEntry";
+import { LoadingScreen } from "src/components/Loading/LoadingScreen";
+import { MessageWithChildren } from "src/components/Messages/MessageWithChildren";
+
+const MessageDetail = ({ id }) => {
+ const mainBg = useColorModeValue("bg-slate-300", "bg-slate-900");
+
+ const [parent, setParent] = useState(null);
+
+ const { isLoading: isLoadingParent } = useSWR(id ? `/api/messages/${id}/parent` : null, fetcher, {
+ onSuccess: (data) => {
+ setParent(data);
+ },
+ onError: (err, key, config) => {
+ setParent(null);
+ },
+ });
+
+ if (isLoadingParent) {
+ return ;
+ }
+ return (
+ <>
+
+ Open Assistant
+
+
+
+
+ {parent && (
+ <>
+
+ Parent
+
+
+
+
+ >
+ )}
+
+
+
+
+
+ >
+ );
+};
+
+MessageDetail.getInitialProps = async ({ query }) => {
+ const { id } = query;
+ return { id };
+};
+
+export default MessageDetail;