Spaces:
Sleeping
Sleeping
wuyiqunLu
commited on
feat: virtual scroll for sidebar list (#46)
Browse fileshttps://github.com/landing-ai/vision-agent-ui/assets/132986242/34e787a6-c708-4f87-958a-594696c2fb42
- app/all/layout.tsx +3 -1
- app/chat/layout.tsx +5 -1
- auth.ts +5 -1
- components/chat-sidebar/ChatCard.tsx +1 -1
- components/chat-sidebar/ChatListSidebar.tsx +48 -13
- package.json +3 -0
- pnpm-lock.yaml +46 -0
app/all/layout.tsx
CHANGED
@@ -10,10 +10,12 @@ interface ChatLayoutProps {
|
|
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();
|
|
|
10 |
}
|
11 |
|
12 |
export default async function Layout({ children }: ChatLayoutProps) {
|
13 |
+
const { isAdmin, user } = await authEmail();
|
14 |
|
15 |
if (!isAdmin) {
|
16 |
redirect('/');
|
17 |
+
} else if (!user) {
|
18 |
+
return null;
|
19 |
}
|
20 |
|
21 |
const chats = await adminGetAllKVChats();
|
app/chat/layout.tsx
CHANGED
@@ -9,8 +9,12 @@ interface ChatLayoutProps {
|
|
9 |
}
|
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
|
|
|
9 |
}
|
10 |
|
11 |
export default async function Layout({ children }: ChatLayoutProps) {
|
12 |
+
const { email, isAdmin, user } = await authEmail();
|
13 |
const chats = await getKVChats();
|
14 |
+
if (!user) {
|
15 |
+
return null;
|
16 |
+
}
|
17 |
+
|
18 |
return (
|
19 |
<div className="relative flex h-[calc(100vh_-_theme(spacing.16))] overflow-hidden">
|
20 |
<div
|
auth.ts
CHANGED
@@ -62,5 +62,9 @@ export const {
|
|
62 |
export async function authEmail() {
|
63 |
const session = await auth();
|
64 |
const email = session?.user?.email;
|
65 |
-
return {
|
|
|
|
|
|
|
|
|
66 |
}
|
|
|
62 |
export async function authEmail() {
|
63 |
const session = await auth();
|
64 |
const email = session?.user?.email;
|
65 |
+
return {
|
66 |
+
email,
|
67 |
+
isAdmin: !!email?.endsWith('landing.ai'),
|
68 |
+
user: session?.user,
|
69 |
+
};
|
70 |
}
|
components/chat-sidebar/ChatCard.tsx
CHANGED
@@ -23,7 +23,7 @@ export const ChatCardLayout: React.FC<
|
|
23 |
return (
|
24 |
<Link
|
25 |
className={cn(
|
26 |
-
'p-2
|
27 |
classNames,
|
28 |
)}
|
29 |
href={link}
|
|
|
23 |
return (
|
24 |
<Link
|
25 |
className={cn(
|
26 |
+
'p-2 bg-background max-h-[100px] rounded-xl shadow-md flex items-center border border-transparent hover:border-gray-500 transition-all cursor-pointer w-full',
|
27 |
classNames,
|
28 |
)}
|
29 |
href={link}
|
components/chat-sidebar/ChatListSidebar.tsx
CHANGED
@@ -1,34 +1,69 @@
|
|
|
|
|
|
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 |
-
<
|
23 |
-
<
|
24 |
-
<
|
25 |
-
|
26 |
-
|
27 |
-
|
|
|
|
|
28 |
)}
|
29 |
-
|
30 |
-
|
31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
</>
|
33 |
);
|
34 |
}
|
|
|
1 |
+
'use client';
|
2 |
+
|
3 |
import ChatCard, { ChatCardLayout } from './ChatCard';
|
4 |
import { IconPlus } from '../ui/Icons';
|
5 |
import { auth } from '@/auth';
|
6 |
import { ChatEntity } from '@/lib/types';
|
7 |
+
import { VariableSizeList as List } from 'react-window';
|
8 |
+
import { cleanInputMessage } from '@/lib/messageUtils';
|
9 |
+
import AutoSizer from 'react-virtualized-auto-sizer';
|
10 |
|
11 |
export interface ChatSidebarListProps {
|
12 |
chats: ChatEntity[];
|
13 |
isAdminView?: boolean;
|
14 |
}
|
15 |
|
16 |
+
const getItemSize = (message: string, isAdminView?: boolean) => {
|
17 |
+
if (message.length >= 45) return 116;
|
18 |
+
else if (message.length >= 20) return 104;
|
19 |
+
else return 88;
|
20 |
+
};
|
21 |
+
|
22 |
export default async function ChatSidebarList({
|
23 |
chats,
|
24 |
isAdminView,
|
25 |
}: ChatSidebarListProps) {
|
|
|
|
|
|
|
|
|
26 |
return (
|
27 |
<>
|
28 |
{!isAdminView && (
|
29 |
+
<div className="p-2">
|
30 |
+
<ChatCardLayout link="/chat">
|
31 |
+
<div className="overflow-hidden flex items-center size-full">
|
32 |
+
<IconPlus className="w-1/4 font-bold" />
|
33 |
+
<p className="text-sm w-3/4 ml-3 font-bold">New chat</p>
|
34 |
+
</div>
|
35 |
+
</ChatCardLayout>
|
36 |
+
</div>
|
37 |
)}
|
38 |
+
<AutoSizer>
|
39 |
+
{({ height, width }) => (
|
40 |
+
<List
|
41 |
+
itemData={chats}
|
42 |
+
height={height}
|
43 |
+
itemCount={chats.length}
|
44 |
+
itemSize={index =>
|
45 |
+
getItemSize(
|
46 |
+
cleanInputMessage(chats[index].messages?.[0]?.content ?? ''),
|
47 |
+
isAdminView,
|
48 |
+
)
|
49 |
+
}
|
50 |
+
width={width}
|
51 |
+
>
|
52 |
+
{({ style, index, data }) => (
|
53 |
+
<div
|
54 |
+
style={style}
|
55 |
+
className="px-2 flex items-center overflow-hidden"
|
56 |
+
>
|
57 |
+
<ChatCard
|
58 |
+
key={data[index].id}
|
59 |
+
chat={data[index]}
|
60 |
+
isAdminView={isAdminView}
|
61 |
+
/>
|
62 |
+
</div>
|
63 |
+
)}
|
64 |
+
</List>
|
65 |
+
)}
|
66 |
+
</AutoSizer>
|
67 |
</>
|
68 |
);
|
69 |
}
|
package.json
CHANGED
@@ -49,6 +49,8 @@
|
|
49 |
"react-markdown": "^8.0.7",
|
50 |
"react-syntax-highlighter": "^15.5.0",
|
51 |
"react-textarea-autosize": "^8.5.3",
|
|
|
|
|
52 |
"remark-gfm": "^3.0.1",
|
53 |
"remark-math": "^5.1.1",
|
54 |
"sharp": "^0.33.3",
|
@@ -60,6 +62,7 @@
|
|
60 |
"@types/react": "^18.2.48",
|
61 |
"@types/react-dom": "^18.2.18",
|
62 |
"@types/react-syntax-highlighter": "^15.5.11",
|
|
|
63 |
"@types/uuid": "^9.0.8",
|
64 |
"@typescript-eslint/parser": "^6.19.0",
|
65 |
"autoprefixer": "^10.4.17",
|
|
|
49 |
"react-markdown": "^8.0.7",
|
50 |
"react-syntax-highlighter": "^15.5.0",
|
51 |
"react-textarea-autosize": "^8.5.3",
|
52 |
+
"react-virtualized-auto-sizer": "^1.0.24",
|
53 |
+
"react-window": "^1.8.10",
|
54 |
"remark-gfm": "^3.0.1",
|
55 |
"remark-math": "^5.1.1",
|
56 |
"sharp": "^0.33.3",
|
|
|
62 |
"@types/react": "^18.2.48",
|
63 |
"@types/react-dom": "^18.2.18",
|
64 |
"@types/react-syntax-highlighter": "^15.5.11",
|
65 |
+
"@types/react-window": "^1.8.8",
|
66 |
"@types/uuid": "^9.0.8",
|
67 |
"@typescript-eslint/parser": "^6.19.0",
|
68 |
"autoprefixer": "^10.4.17",
|
pnpm-lock.yaml
CHANGED
@@ -116,6 +116,12 @@ importers:
|
|
116 |
react-textarea-autosize:
|
117 |
specifier: ^8.5.3
|
118 |
version: 8.5.3(@types/react@18.2.79)(react@18.2.0)
|
|
|
|
|
|
|
|
|
|
|
|
|
119 |
remark-gfm:
|
120 |
specifier: ^3.0.1
|
121 |
version: 3.0.1
|
@@ -144,6 +150,9 @@ importers:
|
|
144 |
'@types/react-syntax-highlighter':
|
145 |
specifier: ^15.5.11
|
146 |
version: 15.5.11
|
|
|
|
|
|
|
147 |
'@types/uuid':
|
148 |
specifier: ^9.0.8
|
149 |
version: 9.0.8
|
@@ -1298,6 +1307,9 @@ packages:
|
|
1298 |
'@types/react-syntax-highlighter@15.5.11':
|
1299 |
resolution: {integrity: sha512-ZqIJl+Pg8kD+47kxUjvrlElrraSUrYa4h0dauY/U/FTUuprSCqvUj+9PNQNQzVc6AJgIWUUxn87/gqsMHNbRjw==}
|
1300 |
|
|
|
|
|
|
|
1301 |
'@types/react@18.2.79':
|
1302 |
resolution: {integrity: sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==}
|
1303 |
|
@@ -2571,6 +2583,9 @@ packages:
|
|
2571 |
mdn-data@2.0.30:
|
2572 |
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
|
2573 |
|
|
|
|
|
|
|
2574 |
merge2@1.4.1:
|
2575 |
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
2576 |
engines: {node: '>= 8'}
|
@@ -3122,6 +3137,19 @@ packages:
|
|
3122 |
peerDependencies:
|
3123 |
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
3124 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
3125 |
react@18.2.0:
|
3126 |
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
3127 |
engines: {node: '>=0.10.0'}
|
@@ -5294,6 +5322,10 @@ snapshots:
|
|
5294 |
dependencies:
|
5295 |
'@types/react': 18.2.79
|
5296 |
|
|
|
|
|
|
|
|
|
5297 |
'@types/react@18.2.79':
|
5298 |
dependencies:
|
5299 |
'@types/prop-types': 15.7.12
|
@@ -6804,6 +6836,8 @@ snapshots:
|
|
6804 |
|
6805 |
mdn-data@2.0.30: {}
|
6806 |
|
|
|
|
|
6807 |
merge2@1.4.1: {}
|
6808 |
|
6809 |
micromark-core-commonmark@1.1.0:
|
@@ -7460,6 +7494,18 @@ snapshots:
|
|
7460 |
transitivePeerDependencies:
|
7461 |
- '@types/react'
|
7462 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7463 |
react@18.2.0:
|
7464 |
dependencies:
|
7465 |
loose-envify: 1.4.0
|
|
|
116 |
react-textarea-autosize:
|
117 |
specifier: ^8.5.3
|
118 |
version: 8.5.3(@types/react@18.2.79)(react@18.2.0)
|
119 |
+
react-virtualized-auto-sizer:
|
120 |
+
specifier: ^1.0.24
|
121 |
+
version: 1.0.24(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
122 |
+
react-window:
|
123 |
+
specifier: ^1.8.10
|
124 |
+
version: 1.8.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
125 |
remark-gfm:
|
126 |
specifier: ^3.0.1
|
127 |
version: 3.0.1
|
|
|
150 |
'@types/react-syntax-highlighter':
|
151 |
specifier: ^15.5.11
|
152 |
version: 15.5.11
|
153 |
+
'@types/react-window':
|
154 |
+
specifier: ^1.8.8
|
155 |
+
version: 1.8.8
|
156 |
'@types/uuid':
|
157 |
specifier: ^9.0.8
|
158 |
version: 9.0.8
|
|
|
1307 |
'@types/react-syntax-highlighter@15.5.11':
|
1308 |
resolution: {integrity: sha512-ZqIJl+Pg8kD+47kxUjvrlElrraSUrYa4h0dauY/U/FTUuprSCqvUj+9PNQNQzVc6AJgIWUUxn87/gqsMHNbRjw==}
|
1309 |
|
1310 |
+
'@types/react-window@1.8.8':
|
1311 |
+
resolution: {integrity: sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==}
|
1312 |
+
|
1313 |
'@types/react@18.2.79':
|
1314 |
resolution: {integrity: sha512-RwGAGXPl9kSXwdNTafkOEuFrTBD5SA2B3iEB96xi8+xu5ddUa/cpvyVCSNn+asgLCTHkb5ZxN8gbuibYJi4s1w==}
|
1315 |
|
|
|
2583 |
mdn-data@2.0.30:
|
2584 |
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
|
2585 |
|
2586 |
+
memoize-one@5.2.1:
|
2587 |
+
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
|
2588 |
+
|
2589 |
merge2@1.4.1:
|
2590 |
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
2591 |
engines: {node: '>= 8'}
|
|
|
3137 |
peerDependencies:
|
3138 |
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
3139 |
|
3140 |
+
react-virtualized-auto-sizer@1.0.24:
|
3141 |
+
resolution: {integrity: sha512-3kCn7N9NEb3FlvJrSHWGQ4iVl+ydQObq2fHMn12i5wbtm74zHOPhz/i64OL3c1S1vi9i2GXtZqNqUJTQ+BnNfg==}
|
3142 |
+
peerDependencies:
|
3143 |
+
react: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0
|
3144 |
+
react-dom: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0
|
3145 |
+
|
3146 |
+
react-window@1.8.10:
|
3147 |
+
resolution: {integrity: sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==}
|
3148 |
+
engines: {node: '>8.0.0'}
|
3149 |
+
peerDependencies:
|
3150 |
+
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
3151 |
+
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
|
3152 |
+
|
3153 |
react@18.2.0:
|
3154 |
resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
|
3155 |
engines: {node: '>=0.10.0'}
|
|
|
5322 |
dependencies:
|
5323 |
'@types/react': 18.2.79
|
5324 |
|
5325 |
+
'@types/react-window@1.8.8':
|
5326 |
+
dependencies:
|
5327 |
+
'@types/react': 18.2.79
|
5328 |
+
|
5329 |
'@types/react@18.2.79':
|
5330 |
dependencies:
|
5331 |
'@types/prop-types': 15.7.12
|
|
|
6836 |
|
6837 |
mdn-data@2.0.30: {}
|
6838 |
|
6839 |
+
memoize-one@5.2.1: {}
|
6840 |
+
|
6841 |
merge2@1.4.1: {}
|
6842 |
|
6843 |
micromark-core-commonmark@1.1.0:
|
|
|
7494 |
transitivePeerDependencies:
|
7495 |
- '@types/react'
|
7496 |
|
7497 |
+
react-virtualized-auto-sizer@1.0.24(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
7498 |
+
dependencies:
|
7499 |
+
react: 18.2.0
|
7500 |
+
react-dom: 18.2.0(react@18.2.0)
|
7501 |
+
|
7502 |
+
react-window@1.8.10(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
|
7503 |
+
dependencies:
|
7504 |
+
'@babel/runtime': 7.24.4
|
7505 |
+
memoize-one: 5.2.1
|
7506 |
+
react: 18.2.0
|
7507 |
+
react-dom: 18.2.0(react@18.2.0)
|
7508 |
+
|
7509 |
react@18.2.0:
|
7510 |
dependencies:
|
7511 |
loose-envify: 1.4.0
|