MingruiZhang commited on
Commit
1c2515f
1 Parent(s): daf39e8

feat: Internal page default filter out examples (#116)

Browse files

<img width="1219" alt="image"
src="https://github.com/landing-ai/vision-agent-ui/assets/5669963/a367061a-0cdf-448a-9fb2-9a58cfee47c9">

<img width="1373" alt="image"
src="https://github.com/landing-ai/vision-agent-ui/assets/5669963/e939fdb8-d0fa-400c-93c5-3386d7b8c3c0">

app/internal/page.tsx CHANGED
@@ -1,11 +1,13 @@
1
- import { DatePicker } from '@/components/DatePicker';
2
- import Loading from '@/components/ui/Loading';
3
  import { isValidDate } from '@/lib/utils';
4
  import { format } from 'date-fns';
5
  import { redirect } from 'next/navigation';
6
  import { Suspense } from 'react';
7
- import InternalServer from './server';
8
  import { sessionUser } from '@/auth';
 
 
 
 
9
 
10
  export interface pageProps {
11
  searchParams?: { [key: string]: string | string[] | undefined };
@@ -16,32 +18,43 @@ export default async function page({ searchParams }: pageProps) {
16
  if (!isAdmin) {
17
  redirect('/');
18
  }
19
- const date = searchParams?.date as string;
20
 
21
- if (!date || !isValidDate(date)) {
 
 
 
 
 
22
  const today = new Date();
23
  // default to today
24
  redirect(`/internal?date=${format(today, 'yyyy-MM-dd')}`);
25
  }
26
 
 
 
 
 
 
 
 
 
 
 
 
27
  return (
28
- <Suspense
29
- fallback={
30
- <div className="h-screen w-screen flex justify-center items-center">
31
- <Loading />
32
- </div>
33
- }
34
- >
35
- <div className="w-[1600px] max-w-full mx-auto flex flex-col space-y-4 items-center">
36
- <DatePicker
37
- date={date}
38
- setDate={async newDate => {
39
- 'use server';
40
- redirect(`/internal?date=${newDate}`);
41
- }}
42
- />
43
- <InternalServer date={date} />
44
- </div>
45
- </Suspense>
46
  );
47
  }
 
 
 
1
  import { isValidDate } from '@/lib/utils';
2
  import { format } from 'date-fns';
3
  import { redirect } from 'next/navigation';
4
  import { Suspense } from 'react';
5
+ import MessageGridServer from '../../components/internal/MessageGridServer';
6
  import { sessionUser } from '@/auth';
7
+ import MessageFilter from '@/components/internal/MessageFilter';
8
+ import { MessageFilterParams } from '@/lib/types';
9
+ import { IconLoading } from '@/components/ui/Icons';
10
+ import Loading from '@/components/ui/Loading';
11
 
12
  export interface pageProps {
13
  searchParams?: { [key: string]: string | string[] | undefined };
 
18
  if (!isAdmin) {
19
  redirect('/');
20
  }
 
21
 
22
+ // Default filter is today's date
23
+ if (
24
+ !searchParams ||
25
+ !searchParams?.date ||
26
+ !isValidDate(searchParams?.date as string)
27
+ ) {
28
  const today = new Date();
29
  // default to today
30
  redirect(`/internal?date=${format(today, 'yyyy-MM-dd')}`);
31
  }
32
 
33
+ const messageFilter = Object.entries(searchParams).reduce((acc, entry) => {
34
+ switch (entry[0]) {
35
+ case 'date':
36
+ return { ...acc, date: entry[1] as string };
37
+ case 'includeExamples':
38
+ return { ...acc, includeExamples: entry[1] === 'true' };
39
+ default:
40
+ return acc;
41
+ }
42
+ }, {} as MessageFilterParams);
43
+
44
  return (
45
+ <div className="w-[1600px] max-w-full mx-auto flex flex-col space-y-4 items-center">
46
+ <MessageFilter messageFilter={messageFilter} />
47
+ <Suspense
48
+ // https://stackoverflow.com/questions/76644147/suspense-fallback-is-not-showing-in-nextjs-13-when-navigate-by-userouter
49
+ key={JSON.stringify(searchParams)}
50
+ fallback={
51
+ <div className="h-screen w-screen flex justify-center items-center">
52
+ <Loading />
53
+ </div>
54
+ }
55
+ >
56
+ <MessageGridServer messageFilter={messageFilter} />
57
+ </Suspense>
58
+ </div>
 
 
 
 
59
  );
60
  }
app/internal/server.tsx DELETED
@@ -1,16 +0,0 @@
1
- import { Card } from '@/components/ui/Card';
2
- import Img from '@/components/ui/Img';
3
- import { dbInternalGetAllMessageByDate } from '@/lib/db/functions';
4
- import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/Dialog';
5
- import { Button } from '@/components/ui/Button';
6
- import { IconArrowUpRight } from '@/components/ui/Icons';
7
- import MessageGrid from '@/components/internal/MessageGrid';
8
-
9
- export interface InternalServerProps {
10
- date: string;
11
- }
12
-
13
- export default async function InternalServer({ date }: InternalServerProps) {
14
- const messages = await dbInternalGetAllMessageByDate(date);
15
- return <MessageGrid messages={messages} />;
16
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/DatePicker.tsx CHANGED
@@ -15,7 +15,7 @@ import {
15
 
16
  type DatePickerProps = {
17
  date: string;
18
- setDate: (date?: string) => Promise<void>;
19
  };
20
 
21
  export function DatePicker({ date, setDate }: DatePickerProps) {
@@ -39,9 +39,9 @@ export function DatePicker({ date, setDate }: DatePickerProps) {
39
  <Calendar
40
  mode="single"
41
  selected={new Date(date)}
42
- onSelect={async newDate => {
43
  if (newDate) {
44
- await setDate(format(newDate, 'yyyy-MM-dd'));
45
  setCalendarOpen(false);
46
  }
47
  }}
 
15
 
16
  type DatePickerProps = {
17
  date: string;
18
+ setDate: (date: string) => void;
19
  };
20
 
21
  export function DatePicker({ date, setDate }: DatePickerProps) {
 
39
  <Calendar
40
  mode="single"
41
  selected={new Date(date)}
42
+ onSelect={newDate => {
43
  if (newDate) {
44
+ setDate(format(newDate, 'yyyy-MM-dd'));
45
  setCalendarOpen(false);
46
  }
47
  }}
components/Header.tsx CHANGED
@@ -47,26 +47,6 @@ export async function Header() {
47
  </Suspense>
48
  )}
49
  <div className="grow" />
50
- {/* <Tooltip>
51
- <TooltipTrigger asChild>
52
- <Button variant="link" asChild className="mr-2">
53
- <Link href="/chat">
54
- <IconPlus />
55
- </Link>
56
- </Button>
57
- </TooltipTrigger>
58
- <TooltipContent>New chat</TooltipContent>
59
- </Tooltip> */}
60
- {/* {isAdmin && (
61
- <Button variant="link" asChild className="mr-2">
62
- <Link href="/all">All Chats (Internal)</Link>
63
- </Button>
64
- )}
65
- {isAdmin && (
66
- <Button variant="link" asChild className="mr-2">
67
- <Link href="/project">Projects (Internal)</Link>
68
- </Button>
69
- )} */}
70
  <Button variant="link" asChild className="mr-2">
71
  <Link href="/">New conversation</Link>
72
  </Button>
@@ -86,7 +66,7 @@ export async function Header() {
86
  <Tooltip>
87
  <TooltipTrigger asChild>
88
  <Button variant="link" size="icon" asChild className="mr-2">
89
- <Link href="https://discord.gg/gSC5p7ED" target="_blank">
90
  <IconDiscord className="size-5" />
91
  </Link>
92
  </Button>
 
47
  </Suspense>
48
  )}
49
  <div className="grow" />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  <Button variant="link" asChild className="mr-2">
51
  <Link href="/">New conversation</Link>
52
  </Button>
 
66
  <Tooltip>
67
  <TooltipTrigger asChild>
68
  <Button variant="link" size="icon" asChild className="mr-2">
69
+ <Link href="https://discord.gg/RVcW3j9RgR" target="_blank">
70
  <IconDiscord className="size-5" />
71
  </Link>
72
  </Button>
components/internal/MessageFilter.tsx ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { usePathname, useRouter, useSearchParams } from 'next/navigation';
4
+ import React from 'react';
5
+ import { DatePicker } from '../DatePicker';
6
+ import { MessageFilterParams } from '@/lib/types';
7
+ import { Checkbox } from '../ui/checkbox';
8
+
9
+ export interface MessageFilterProps {
10
+ messageFilter: MessageFilterParams;
11
+ }
12
+
13
+ const MessageFilter: React.FC<MessageFilterProps> = ({ messageFilter }) => {
14
+ const searchParams = useSearchParams();
15
+ const pathname = usePathname();
16
+ const { push, refresh } = useRouter();
17
+ return (
18
+ <div className="flex flex-row space-x-4">
19
+ <DatePicker
20
+ date={messageFilter.date}
21
+ setDate={newDate => {
22
+ const params = new URLSearchParams(searchParams);
23
+ params.set('date', newDate);
24
+ push(`${pathname}?${params.toString()}`);
25
+ }}
26
+ />
27
+ <div className="flex items-center space-x-2">
28
+ <Checkbox
29
+ id="include-example"
30
+ checked={messageFilter.includeExamples}
31
+ onCheckedChange={checked => {
32
+ const params = new URLSearchParams(searchParams);
33
+ params.set('includeExamples', String(checked));
34
+ push(`${pathname}?${params.toString()}`);
35
+ }}
36
+ />
37
+ <label
38
+ htmlFor="terms"
39
+ className="include-example font-medium leading-none"
40
+ >
41
+ Include examples
42
+ </label>
43
+ </div>
44
+ </div>
45
+ );
46
+ };
47
+
48
+ export default MessageFilter;
components/internal/MessageGridServer.tsx ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { dbInternalGetAllMessageByDate } from '@/lib/db/functions';
2
+ import MessageGrid from '@/components/internal/MessageGrid';
3
+ import { MessageFilterParams } from '@/lib/types';
4
+
5
+ export interface MessageGridServerProps {
6
+ messageFilter: MessageFilterParams;
7
+ }
8
+
9
+ export default async function MessageGridServer({
10
+ messageFilter,
11
+ }: MessageGridServerProps) {
12
+ const messages = await dbInternalGetAllMessageByDate(messageFilter);
13
+ if (!messages.length) {
14
+ return <p className="text-sm italic">No messages</p>;
15
+ }
16
+ return <MessageGrid messages={messages} />;
17
+ }
components/ui/checkbox.tsx ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
5
+ import { CheckIcon } from '@radix-ui/react-icons';
6
+
7
+ import { cn } from '@/lib/utils';
8
+
9
+ const Checkbox = React.forwardRef<
10
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
11
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
12
+ >(({ className, ...props }, ref) => (
13
+ <CheckboxPrimitive.Root
14
+ ref={ref}
15
+ className={cn(
16
+ 'peer size-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
17
+ className,
18
+ )}
19
+ {...props}
20
+ >
21
+ <CheckboxPrimitive.Indicator
22
+ className={cn('flex items-center justify-center text-current')}
23
+ >
24
+ <CheckIcon className="h-4 w-4" />
25
+ </CheckboxPrimitive.Indicator>
26
+ </CheckboxPrimitive.Root>
27
+ ));
28
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName;
29
+
30
+ export { Checkbox };
lib/db/functions.ts CHANGED
@@ -5,6 +5,7 @@ import prisma from './prisma';
5
  import {
6
  ChatWithMessages,
7
  MessageAssistantResponse,
 
8
  MessageUserInput,
9
  } from '../types';
10
  import { revalidatePath } from 'next/cache';
@@ -217,7 +218,10 @@ export async function dbDeleteChat(chatId: string) {
217
  * @returns A promise that resolves to an array of messages.
218
  * @throws Error if the provided date is not valid.
219
  */
220
- export async function dbInternalGetAllMessageByDate(date: string) {
 
 
 
221
  // date must be in the format 'YYYY-MM-DD'
222
  if (!isValidDate(date)) {
223
  throw Error('Not valid date');
@@ -236,6 +240,9 @@ export async function dbInternalGetAllMessageByDate(date: string) {
236
  gte: new Date(date + 'T00:00:00Z'),
237
  lt: new Date(date + 'T23:59:59Z'),
238
  },
 
 
 
239
  },
240
  });
241
  }
 
5
  import {
6
  ChatWithMessages,
7
  MessageAssistantResponse,
8
+ MessageFilterParams,
9
  MessageUserInput,
10
  } from '../types';
11
  import { revalidatePath } from 'next/cache';
 
218
  * @returns A promise that resolves to an array of messages.
219
  * @throws Error if the provided date is not valid.
220
  */
221
+ export async function dbInternalGetAllMessageByDate(
222
+ messageFilter: MessageFilterParams,
223
+ ) {
224
+ const { date, includeExamples } = messageFilter;
225
  // date must be in the format 'YYYY-MM-DD'
226
  if (!isValidDate(date)) {
227
  throw Error('Not valid date');
 
240
  gte: new Date(date + 'T00:00:00Z'),
241
  lt: new Date(date + 'T23:59:59Z'),
242
  },
243
+ mediaUrl: includeExamples
244
+ ? undefined
245
+ : { not: { contains: '/examples/' } },
246
  },
247
  });
248
  }
lib/types.ts CHANGED
@@ -21,3 +21,8 @@ export interface SignedPayload {
21
  signedUrl: string;
22
  fields: Record<string, string>;
23
  }
 
 
 
 
 
 
21
  signedUrl: string;
22
  fields: Record<string, string>;
23
  }
24
+
25
+ export interface MessageFilterParams {
26
+ date: string;
27
+ includeExamples: boolean;
28
+ }
package.json CHANGED
@@ -19,6 +19,7 @@
19
  "@aws-sdk/credential-providers": "^3.556.0",
20
  "@aws-sdk/s3-presigned-post": "^3.556.0",
21
  "@prisma/client": "5.14.0",
 
22
  "@radix-ui/react-dialog": "^1.0.5",
23
  "@radix-ui/react-dropdown-menu": "^2.0.6",
24
  "@radix-ui/react-icons": "^1.3.0",
 
19
  "@aws-sdk/credential-providers": "^3.556.0",
20
  "@aws-sdk/s3-presigned-post": "^3.556.0",
21
  "@prisma/client": "5.14.0",
22
+ "@radix-ui/react-checkbox": "^1.1.0",
23
  "@radix-ui/react-dialog": "^1.0.5",
24
  "@radix-ui/react-dropdown-menu": "^2.0.6",
25
  "@radix-ui/react-icons": "^1.3.0",
pnpm-lock.yaml CHANGED
@@ -20,6 +20,9 @@ importers:
20
  '@prisma/client':
21
  specifier: 5.14.0
22
  version: 5.14.0(prisma@5.14.0)
 
 
 
23
  '@radix-ui/react-dialog':
24
  specifier: ^1.0.5
25
  version: 1.0.5(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -809,6 +812,19 @@ packages:
809
  '@types/react-dom':
810
  optional: true
811
 
 
 
 
 
 
 
 
 
 
 
 
 
 
812
  '@radix-ui/react-collection@1.0.3':
813
  resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
814
  peerDependencies:
@@ -1280,6 +1296,15 @@ packages:
1280
  '@types/react':
1281
  optional: true
1282
 
 
 
 
 
 
 
 
 
 
1283
  '@radix-ui/react-use-rect@1.0.1':
1284
  resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==}
1285
  peerDependencies:
@@ -5112,6 +5137,22 @@ snapshots:
5112
  '@types/react': 18.2.79
5113
  '@types/react-dom': 18.2.25
5114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5115
  '@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
5116
  dependencies:
5117
  '@babel/runtime': 7.24.4
@@ -5598,6 +5639,12 @@ snapshots:
5598
  optionalDependencies:
5599
  '@types/react': 18.2.79
5600
 
 
 
 
 
 
 
5601
  '@radix-ui/react-use-rect@1.0.1(@types/react@18.2.79)(react@18.2.0)':
5602
  dependencies:
5603
  '@babel/runtime': 7.24.4
 
20
  '@prisma/client':
21
  specifier: 5.14.0
22
  version: 5.14.0(prisma@5.14.0)
23
+ '@radix-ui/react-checkbox':
24
+ specifier: ^1.1.0
25
+ version: 1.1.0(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
26
  '@radix-ui/react-dialog':
27
  specifier: ^1.0.5
28
  version: 1.0.5(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
 
812
  '@types/react-dom':
813
  optional: true
814
 
815
+ '@radix-ui/react-checkbox@1.1.0':
816
+ resolution: {integrity: sha512-3+kSzVfMONtP3B6CvaOrXLVTyGYws7tGmG5kOY0AfyH9sexkLytIwciNwjZhY0RoGOEbxI7bMS21XYB8H5itWQ==}
817
+ peerDependencies:
818
+ '@types/react': '*'
819
+ '@types/react-dom': '*'
820
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
821
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
822
+ peerDependenciesMeta:
823
+ '@types/react':
824
+ optional: true
825
+ '@types/react-dom':
826
+ optional: true
827
+
828
  '@radix-ui/react-collection@1.0.3':
829
  resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
830
  peerDependencies:
 
1296
  '@types/react':
1297
  optional: true
1298
 
1299
+ '@radix-ui/react-use-previous@1.1.0':
1300
+ resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==}
1301
+ peerDependencies:
1302
+ '@types/react': '*'
1303
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
1304
+ peerDependenciesMeta:
1305
+ '@types/react':
1306
+ optional: true
1307
+
1308
  '@radix-ui/react-use-rect@1.0.1':
1309
  resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==}
1310
  peerDependencies:
 
5137
  '@types/react': 18.2.79
5138
  '@types/react-dom': 18.2.25
5139
 
5140
+ '@radix-ui/react-checkbox@1.1.0(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
5141
+ dependencies:
5142
+ '@radix-ui/primitive': 1.1.0
5143
+ '@radix-ui/react-compose-refs': 1.1.0(@types/react@18.2.79)(react@18.2.0)
5144
+ '@radix-ui/react-context': 1.1.0(@types/react@18.2.79)(react@18.2.0)
5145
+ '@radix-ui/react-presence': 1.1.0(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
5146
+ '@radix-ui/react-primitive': 2.0.0(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
5147
+ '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.2.79)(react@18.2.0)
5148
+ '@radix-ui/react-use-previous': 1.1.0(@types/react@18.2.79)(react@18.2.0)
5149
+ '@radix-ui/react-use-size': 1.1.0(@types/react@18.2.79)(react@18.2.0)
5150
+ react: 18.2.0
5151
+ react-dom: 18.2.0(react@18.2.0)
5152
+ optionalDependencies:
5153
+ '@types/react': 18.2.79
5154
+ '@types/react-dom': 18.2.25
5155
+
5156
  '@radix-ui/react-collection@1.0.3(@types/react-dom@18.2.25)(@types/react@18.2.79)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
5157
  dependencies:
5158
  '@babel/runtime': 7.24.4
 
5639
  optionalDependencies:
5640
  '@types/react': 18.2.79
5641
 
5642
+ '@radix-ui/react-use-previous@1.1.0(@types/react@18.2.79)(react@18.2.0)':
5643
+ dependencies:
5644
+ react: 18.2.0
5645
+ optionalDependencies:
5646
+ '@types/react': 18.2.79
5647
+
5648
  '@radix-ui/react-use-rect@1.0.1(@types/react@18.2.79)(react@18.2.0)':
5649
  dependencies:
5650
  '@babel/runtime': 7.24.4