Spaces:
Sleeping
Sleeping
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 +16 -0
- app/all/layout.tsx +38 -0
- app/all/page.tsx +5 -0
- app/chat/layout.tsx +4 -2
- app/chat/page.tsx +1 -2
- components/Header.tsx +5 -0
- components/chat-sidebar/ChatCard.tsx +4 -3
- components/chat-sidebar/ChatListSidebar.tsx +18 -11
- components/chat/index.tsx +21 -18
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 {
|
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
|
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({
|
|
|
|
|
|
|
9 |
const session = await auth();
|
10 |
if (!session || !session.user) {
|
11 |
return null;
|
12 |
}
|
13 |
-
const chats = await getKVChats();
|
14 |
return (
|
15 |
<>
|
16 |
-
|
17 |
-
<
|
18 |
-
<
|
19 |
-
|
20 |
-
|
21 |
-
|
|
|
|
|
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 |
-
|
39 |
-
<
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
|
|
|
|
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 |
}
|