wuyiqunLu commited on
Commit
5411802
1 Parent(s): 34afd2e

feat: support internal access to all chat (#45)

Browse files

<img width="1326" alt="image"
src="https://github.com/landing-ai/vision-agent-ui/assets/132986242/98a7d32a-c9af-49a5-a680-a44014c7e8f3">

app/all/chat/[id]/page.tsx ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { getKVChat } from '@/lib/kv/chat';
2
+ import { Chat } from '@/components/chat';
3
+ import { auth } from '@/auth';
4
+
5
+ interface PageProps {
6
+ params: {
7
+ id: string;
8
+ };
9
+ }
10
+
11
+ export default async function Page({ params }: PageProps) {
12
+ const { id: chatId } = params;
13
+ const chat = await getKVChat(chatId);
14
+ const session = await auth();
15
+ return <Chat chat={chat} session={session} isAdminView />;
16
+ }
app/all/layout.tsx ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Suspense } from 'react';
2
+ import Loading from '@/components/ui/Loading';
3
+ import { authEmail } from '@/auth';
4
+ import { redirect } from 'next/navigation';
5
+ import { adminGetAllKVChats } from '@/lib/kv/chat';
6
+ import ChatSidebarList from '@/components/chat-sidebar/ChatListSidebar';
7
+
8
+ interface ChatLayoutProps {
9
+ children: React.ReactNode;
10
+ }
11
+
12
+ export default async function Layout({ children }: ChatLayoutProps) {
13
+ const { isAdmin } = await authEmail();
14
+
15
+ if (!isAdmin) {
16
+ redirect('/');
17
+ }
18
+
19
+ const chats = await adminGetAllKVChats();
20
+
21
+ return (
22
+ <div className="relative flex h-[calc(100vh_-_theme(spacing.16))] overflow-hidden">
23
+ <div
24
+ data-state="open"
25
+ className="peer absolute inset-y-0 z-30 hidden border-r bg-muted duration-300 ease-in-out translate-x-0 lg:flex lg:w-[250px] xl:w-[300px] h-full flex-col dark:bg-zinc-950 overflow-auto py-2"
26
+ >
27
+ <Suspense fallback={<Loading />}>
28
+ <ChatSidebarList chats={chats} isAdminView />
29
+ </Suspense>
30
+ </div>
31
+ <Suspense fallback={<Loading />}>
32
+ <div className="group w-full overflow-auto pl-0 animate-in duration-300 ease-in-out peer-[[data-state=open]]:lg:pl-[250px] peer-[[data-state=open]]:xl:pl-[300px]">
33
+ {children}
34
+ </div>
35
+ </Suspense>
36
+ </div>
37
+ );
38
+ }
app/all/page.tsx ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ interface PageProps {}
2
+
3
+ export default async function Page({}: PageProps) {
4
+ return <div></div>;
5
+ }
app/chat/layout.tsx CHANGED
@@ -1,6 +1,7 @@
1
- import { auth, authEmail } from '@/auth';
2
  import ChatSidebarList from '@/components/chat-sidebar/ChatListSidebar';
3
  import Loading from '@/components/ui/Loading';
 
4
  import { Suspense } from 'react';
5
 
6
  interface ChatLayoutProps {
@@ -9,6 +10,7 @@ interface ChatLayoutProps {
9
 
10
  export default async function Layout({ children }: ChatLayoutProps) {
11
  const { email, isAdmin } = await authEmail();
 
12
  return (
13
  <div className="relative flex h-[calc(100vh_-_theme(spacing.16))] overflow-hidden">
14
  <div
@@ -16,7 +18,7 @@ export default async function Layout({ children }: ChatLayoutProps) {
16
  className="peer absolute inset-y-0 z-30 hidden border-r bg-muted duration-300 ease-in-out -translate-x-full data-[state=open]:translate-x-0 lg:flex lg:w-[250px] h-full flex-col overflow-auto py-2"
17
  >
18
  <Suspense fallback={<Loading />}>
19
- <ChatSidebarList />
20
  </Suspense>
21
  </div>
22
  <Suspense fallback={<Loading />}>
 
1
+ import { authEmail } from '@/auth';
2
  import ChatSidebarList from '@/components/chat-sidebar/ChatListSidebar';
3
  import Loading from '@/components/ui/Loading';
4
+ import { getKVChats } from '@/lib/kv/chat';
5
  import { Suspense } from 'react';
6
 
7
  interface ChatLayoutProps {
 
10
 
11
  export default async function Layout({ children }: ChatLayoutProps) {
12
  const { email, isAdmin } = await authEmail();
13
+ const chats = await getKVChats();
14
  return (
15
  <div className="relative flex h-[calc(100vh_-_theme(spacing.16))] overflow-hidden">
16
  <div
 
18
  className="peer absolute inset-y-0 z-30 hidden border-r bg-muted duration-300 ease-in-out -translate-x-full data-[state=open]:translate-x-0 lg:flex lg:w-[250px] h-full flex-col overflow-auto py-2"
19
  >
20
  <Suspense fallback={<Loading />}>
21
+ <ChatSidebarList chats={chats} />
22
  </Suspense>
23
  </div>
24
  <Suspense fallback={<Loading />}>
app/chat/page.tsx CHANGED
@@ -2,9 +2,8 @@
2
 
3
  import ImageSelector from '@/components/chat/ImageSelector';
4
  import { generateInputImageMarkdown } from '@/lib/messageUtils';
5
- import { ChatEntity, MessageBase } from '@/lib/types';
6
  import { fetcher } from '@/lib/utils';
7
- import Image from 'next/image';
8
  import { useRouter } from 'next/navigation';
9
 
10
  import {
 
2
 
3
  import ImageSelector from '@/components/chat/ImageSelector';
4
  import { generateInputImageMarkdown } from '@/lib/messageUtils';
5
+ import { ChatEntity } from '@/lib/types';
6
  import { fetcher } from '@/lib/utils';
 
7
  import { useRouter } from 'next/navigation';
8
 
9
  import {
components/Header.tsx CHANGED
@@ -38,6 +38,11 @@ export async function Header() {
38
  </TooltipTrigger>
39
  <TooltipContent>New chat</TooltipContent>
40
  </Tooltip> */}
 
 
 
 
 
41
  {isAdmin && (
42
  <Button variant="link" asChild className="mr-2">
43
  <Link href="/project">Projects (Internal)</Link>
 
38
  </TooltipTrigger>
39
  <TooltipContent>New chat</TooltipContent>
40
  </Tooltip> */}
41
+ {isAdmin && (
42
+ <Button variant="link" asChild className="mr-2">
43
+ <Link href="/all">All Chats (Internal)</Link>
44
+ </Button>
45
+ )}
46
  {isAdmin && (
47
  <Button variant="link" asChild className="mr-2">
48
  <Link href="/project">Projects (Internal)</Link>
components/chat-sidebar/ChatCard.tsx CHANGED
@@ -14,6 +14,7 @@ import { cleanInputMessage } from '@/lib/messageUtils';
14
 
15
  type ChatCardProps = PropsWithChildren<{
16
  chat: ChatEntity;
 
17
  }>;
18
 
19
  export const ChatCardLayout: React.FC<
@@ -32,9 +33,8 @@ export const ChatCardLayout: React.FC<
32
  );
33
  };
34
 
35
- const ChatCard: React.FC<ChatCardProps> = ({ chat }) => {
36
  const { id: chatIdFromParam } = useParams();
37
- const pathname = usePathname();
38
  const { id, url, messages, user, updatedAt } = chat;
39
  const firstMessage = cleanInputMessage(messages?.[0]?.content ?? '');
40
  const title = firstMessage
@@ -44,7 +44,7 @@ const ChatCard: React.FC<ChatCardProps> = ({ chat }) => {
44
  : '(No messages yet)';
45
  return (
46
  <ChatCardLayout
47
- link={`/chat/${id}`}
48
  classNames={chatIdFromParam === id && 'border-gray-500'}
49
  >
50
  <div className="overflow-hidden flex items-center size-full">
@@ -54,6 +54,7 @@ const ChatCard: React.FC<ChatCardProps> = ({ chat }) => {
54
  <p className="text-xs text-gray-500">
55
  {updatedAt ? format(Number(updatedAt), 'yyyy-MM-dd') : '-'}
56
  </p>
 
57
  </div>
58
  </div>
59
  </ChatCardLayout>
 
14
 
15
  type ChatCardProps = PropsWithChildren<{
16
  chat: ChatEntity;
17
+ isAdminView?: boolean;
18
  }>;
19
 
20
  export const ChatCardLayout: React.FC<
 
33
  );
34
  };
35
 
36
+ const ChatCard: React.FC<ChatCardProps> = ({ chat, isAdminView }) => {
37
  const { id: chatIdFromParam } = useParams();
 
38
  const { id, url, messages, user, updatedAt } = chat;
39
  const firstMessage = cleanInputMessage(messages?.[0]?.content ?? '');
40
  const title = firstMessage
 
44
  : '(No messages yet)';
45
  return (
46
  <ChatCardLayout
47
+ link={isAdminView ? `/all/chat/${id}` : `/chat/${id}`}
48
  classNames={chatIdFromParam === id && 'border-gray-500'}
49
  >
50
  <div className="overflow-hidden flex items-center size-full">
 
54
  <p className="text-xs text-gray-500">
55
  {updatedAt ? format(Number(updatedAt), 'yyyy-MM-dd') : '-'}
56
  </p>
57
+ {isAdminView && <p className="text-xs text-gray-500">{user}</p>}
58
  </div>
59
  </div>
60
  </ChatCardLayout>
components/chat-sidebar/ChatListSidebar.tsx CHANGED
@@ -1,26 +1,33 @@
1
- import { getKVChats } from '@/lib/kv/chat';
2
  import ChatCard, { ChatCardLayout } from './ChatCard';
3
  import { IconPlus } from '../ui/Icons';
4
  import { auth } from '@/auth';
 
5
 
6
- export interface ChatSidebarListProps {}
 
 
 
7
 
8
- export default async function ChatSidebarList({}: ChatSidebarListProps) {
 
 
 
9
  const session = await auth();
10
  if (!session || !session.user) {
11
  return null;
12
  }
13
- const chats = await getKVChats();
14
  return (
15
  <>
16
- <ChatCardLayout link="/chat">
17
- <div className="overflow-hidden flex items-center size-full">
18
- <IconPlus className="w-1/4 font-bold" />
19
- <p className="text-sm w-3/4 ml-3 font-bold">New chat</p>
20
- </div>
21
- </ChatCardLayout>
 
 
22
  {chats.map(chat => (
23
- <ChatCard key={chat.id} chat={chat} />
24
  ))}
25
  </>
26
  );
 
 
1
  import ChatCard, { ChatCardLayout } from './ChatCard';
2
  import { IconPlus } from '../ui/Icons';
3
  import { auth } from '@/auth';
4
+ import { ChatEntity } from '@/lib/types';
5
 
6
+ export interface ChatSidebarListProps {
7
+ chats: ChatEntity[];
8
+ isAdminView?: boolean;
9
+ }
10
 
11
+ export default async function ChatSidebarList({
12
+ chats,
13
+ isAdminView,
14
+ }: ChatSidebarListProps) {
15
  const session = await auth();
16
  if (!session || !session.user) {
17
  return null;
18
  }
 
19
  return (
20
  <>
21
+ {!isAdminView && (
22
+ <ChatCardLayout link="/chat">
23
+ <div className="overflow-hidden flex items-center size-full">
24
+ <IconPlus className="w-1/4 font-bold" />
25
+ <p className="text-sm w-3/4 ml-3 font-bold">New chat</p>
26
+ </div>
27
+ </ChatCardLayout>
28
+ )}
29
  {chats.map(chat => (
30
+ <ChatCard key={chat.id} chat={chat} isAdminView={isAdminView} />
31
  ))}
32
  </>
33
  );
components/chat/index.tsx CHANGED
@@ -10,10 +10,11 @@ import { useState } from 'react';
10
 
11
  export interface ChatProps extends React.ComponentProps<'div'> {
12
  chat: ChatEntity;
 
13
  session: Session | null;
14
  }
15
 
16
- export function Chat({ chat, session }: ChatProps) {
17
  const { url, id } = chat;
18
  const [enableSelfReflection, setEnableSelfReflection] =
19
  useState<boolean>(true);
@@ -35,23 +36,25 @@ export function Chat({ chat, session }: ChatProps) {
35
  <div className="h-px w-full" ref={visibilityRef} />
36
  </div>
37
  </div>
38
- <div className="fixed inset-x-0 bottom-0 w-full animate-in duration-300 ease-in-out peer-[[data-state=open]]:group-[]:lg:pl-[250px] peer-[[data-state=open]]:group-[]:xl:pl-[300px] h-[178px]">
39
- <Composer
40
- id={id}
41
- url={url}
42
- isLoading={isLoading}
43
- stop={stop}
44
- append={append}
45
- reload={reload}
46
- messages={messages}
47
- input={input}
48
- setInput={setInput}
49
- isAtBottom={isAtBottom}
50
- scrollToBottom={scrollToBottom}
51
- enableSelfReflection={enableSelfReflection}
52
- setEnableSelfReflection={setEnableSelfReflection}
53
- />
54
- </div>
 
 
55
  </>
56
  );
57
  }
 
10
 
11
  export interface ChatProps extends React.ComponentProps<'div'> {
12
  chat: ChatEntity;
13
+ isAdminView?: boolean;
14
  session: Session | null;
15
  }
16
 
17
+ export function Chat({ chat, session, isAdminView }: ChatProps) {
18
  const { url, id } = chat;
19
  const [enableSelfReflection, setEnableSelfReflection] =
20
  useState<boolean>(true);
 
36
  <div className="h-px w-full" ref={visibilityRef} />
37
  </div>
38
  </div>
39
+ {!isAdminView && (
40
+ <div className="fixed inset-x-0 bottom-0 w-full animate-in duration-300 ease-in-out peer-[[data-state=open]]:group-[]:lg:pl-[250px] peer-[[data-state=open]]:group-[]:xl:pl-[300px] h-[178px]">
41
+ <Composer
42
+ id={id}
43
+ url={url}
44
+ isLoading={isLoading}
45
+ stop={stop}
46
+ append={append}
47
+ reload={reload}
48
+ messages={messages}
49
+ input={input}
50
+ setInput={setInput}
51
+ isAtBottom={isAtBottom}
52
+ scrollToBottom={scrollToBottom}
53
+ enableSelfReflection={enableSelfReflection}
54
+ setEnableSelfReflection={setEnableSelfReflection}
55
+ />
56
+ </div>
57
+ )}
58
  </>
59
  );
60
  }