wuyiqunLu commited on
Commit
9333689
1 Parent(s): 5411802

feat: virtual scroll for sidebar list (#46)

Browse files

https://github.com/landing-ai/vision-agent-ui/assets/132986242/34e787a6-c708-4f87-958a-594696c2fb42

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 { email, isAdmin: !!email?.endsWith('landing.ai') };
 
 
 
 
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 m-2 bg-background max-h-[100px] rounded-xl shadow-md flex items-center border border-transparent hover:border-gray-500 transition-all cursor-pointer',
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
- <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
  );
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