MingruiZhang commited on
Commit
97e41aa
1 Parent(s): 81e89a4

Layout updates and chat sidebar layout fixes #12

Browse files
app/api/vision-agent/route.ts CHANGED
@@ -1,62 +1,10 @@
1
- import { OpenAIStream, StreamingTextResponse } from 'ai';
2
 
3
  import { auth } from '@/auth';
4
- import {
5
- AIStream,
6
- type AIStreamParser,
7
- type AIStreamCallbacksAndOptions,
8
- } from 'ai';
9
  import { MessageBase } from '../../../lib/types';
10
- import { fetcher } from '@/lib/utils';
11
 
12
  export const runtime = 'edge';
13
 
14
- function parseVisionAgentStream(): AIStreamParser {
15
- let previous = '';
16
-
17
- return data => {
18
- console.log('[Ming] ~ parseVisionAgentStream ~ data:', data);
19
- // const json = JSON.parse(data) as {
20
- // completion: string;
21
- // stop: string | null;
22
- // stop_reason: string | null;
23
- // truncated: boolean;
24
- // log_id: string;
25
- // model: string;
26
- // exception: string | null;
27
- // };
28
-
29
- // // Anthropic's `completion` field is cumulative unlike OpenAI's
30
- // // deltas. In order to compute the delta, we must slice out the text
31
- // // we previously received.
32
- // const text = json.completion;
33
- // console.log('[Ming] ~ parseVisionAgentStream ~ text:', text);
34
- // const delta = text.slice(previous.length);
35
- // previous = text;
36
-
37
- return data;
38
- };
39
- }
40
-
41
- function visionAgentStream(
42
- res: Response,
43
- cb?: AIStreamCallbacksAndOptions,
44
- ): ReadableStream {
45
- return AIStream(res, parseVisionAgentStream(), cb);
46
- }
47
-
48
- function readChunks(reader: any) {
49
- return {
50
- async *[Symbol.asyncIterator]() {
51
- let readResult = await reader.read();
52
- while (!readResult.done) {
53
- yield readResult.value;
54
- readResult = await reader.read();
55
- }
56
- },
57
- };
58
- }
59
-
60
  export async function POST(req: Request) {
61
  const json = await req.json();
62
  const { messages, url } = json as {
@@ -88,8 +36,6 @@ export async function POST(req: Request) {
88
  },
89
  );
90
 
91
- // console.log('[Ming] ~ POST ~ fetchResponse:', fetchResponse);
92
-
93
  if (fetchResponse.body) {
94
  return new StreamingTextResponse(fetchResponse.body);
95
  } else {
 
1
+ import { StreamingTextResponse } from 'ai';
2
 
3
  import { auth } from '@/auth';
 
 
 
 
 
4
  import { MessageBase } from '../../../lib/types';
 
5
 
6
  export const runtime = 'edge';
7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  export async function POST(req: Request) {
9
  const json = await req.json();
10
  const { messages, url } = json as {
 
36
  },
37
  );
38
 
 
 
39
  if (fetchResponse.body) {
40
  return new StreamingTextResponse(fetchResponse.body);
41
  } else {
app/chat/layout.tsx CHANGED
@@ -1,3 +1,4 @@
 
1
  import ChatSidebarList from '@/components/chat-sidebar/ChatListSidebar';
2
  import Loading from '@/components/ui/Loading';
3
  import { Suspense } from 'react';
@@ -11,7 +12,7 @@ export default async function Layout({ children }: ChatLayoutProps) {
11
  <div className="relative flex h-[calc(100vh_-_theme(spacing.16))] overflow-hidden">
12
  <div
13
  data-state="open"
14
- 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] h-full flex-col dark:bg-zinc-950 overflow-auto py-2"
15
  >
16
  <Suspense fallback={<Loading />}>
17
  <ChatSidebarList />
 
1
+ import { ThemeToggle } from '@/components/ThemeToggle';
2
  import ChatSidebarList from '@/components/chat-sidebar/ChatListSidebar';
3
  import Loading from '@/components/ui/Loading';
4
  import { Suspense } from 'react';
 
12
  <div className="relative flex h-[calc(100vh_-_theme(spacing.16))] overflow-hidden">
13
  <div
14
  data-state="open"
15
+ 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] h-full flex-col overflow-auto py-2"
16
  >
17
  <Suspense fallback={<Loading />}>
18
  <ChatSidebarList />
app/layout.tsx CHANGED
@@ -12,8 +12,8 @@ import { ThemeToggle } from '@/components/ThemeToggle';
12
  export const metadata = {
13
  metadataBase: new URL(`https://${process.env.VERCEL_URL}`),
14
  title: {
15
- default: 'Insight Playground',
16
- template: `%s - Insight Playground`,
17
  },
18
  description: 'By Landing AI',
19
  icons: {
 
12
  export const metadata = {
13
  metadataBase: new URL(`https://${process.env.VERCEL_URL}`),
14
  title: {
15
+ default: 'Vision Agent',
16
+ template: `%s - Vision Agent`,
17
  },
18
  description: 'By Landing AI',
19
  icons: {
components/Providers.tsx CHANGED
@@ -4,11 +4,15 @@ import * as React from 'react';
4
  import { ThemeProvider as NextThemesProvider } from 'next-themes';
5
  import { ThemeProviderProps } from 'next-themes/dist/types';
6
  import { TooltipProvider } from '@/components/ui/Tooltip';
 
7
 
8
  export function Providers({ children, ...props }: ThemeProviderProps) {
9
  return (
10
  <NextThemesProvider {...props}>
11
- <TooltipProvider>{children}</TooltipProvider>
 
 
 
12
  </NextThemesProvider>
13
  );
14
  }
 
4
  import { ThemeProvider as NextThemesProvider } from 'next-themes';
5
  import { ThemeProviderProps } from 'next-themes/dist/types';
6
  import { TooltipProvider } from '@/components/ui/Tooltip';
7
+ import { ThemeToggle } from './ThemeToggle';
8
 
9
  export function Providers({ children, ...props }: ThemeProviderProps) {
10
  return (
11
  <NextThemesProvider {...props}>
12
+ <TooltipProvider>
13
+ {children}
14
+ <ThemeToggle />
15
+ </TooltipProvider>
16
  </NextThemesProvider>
17
  );
18
  }
components/ThemeToggle.tsx CHANGED
@@ -21,7 +21,7 @@ export function ThemeToggle() {
21
  });
22
  }}
23
  >
24
- {!theme ? null : theme === 'dark' ? (
25
  <IconMoon className="transition-all" />
26
  ) : (
27
  <IconSun className="transition-all" />
 
21
  });
22
  }}
23
  >
24
+ {theme === 'dark' ? (
25
  <IconMoon className="transition-all" />
26
  ) : (
27
  <IconSun className="transition-all" />
components/chat-sidebar/ChatCard.tsx CHANGED
@@ -1,27 +1,43 @@
1
  'use client';
2
 
 
3
  import Link from 'next/link';
4
  import { useParams } from 'next/navigation';
5
  import { cn } from '@/lib/utils';
6
  import { ChatEntity } from '@/lib/types';
7
  import Image from 'next/image';
 
8
 
9
- export interface ChatCardProps {
10
  chat: ChatEntity;
11
- }
12
 
13
- const ChatCard: React.FC<ChatCardProps> = ({ chat }) => {
14
- const { chatId: chatIdFromParam } = useParams();
15
- const { id, url, messages, user } = chat;
16
  return (
17
  <Link
18
  className={cn(
19
- 'p-2 m-2 bg-white l:h-[250px] rounded-xl shadow-md flex items-center border border-transparent hover:border-gray-500 transition-all cursor-pointer',
20
- chatIdFromParam === id && 'border-gray-500',
21
  )}
22
- href={`/chat/${id}`}
23
  >
24
- <div className="overflow-hidden flex items-center">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  <Image
26
  src={url}
27
  alt={url}
@@ -29,11 +45,11 @@ const ChatCard: React.FC<ChatCardProps> = ({ chat }) => {
29
  height={50}
30
  className="rounded w-1/4 "
31
  />
32
- <p className="text-xs text-gray-500 w-3/4 ml-2">
33
- {messages?.[0]?.content.slice(0, 50) + ' ...' ?? 'new chat'}
34
  </p>
35
  </div>
36
- </Link>
37
  );
38
  };
39
 
 
1
  'use client';
2
 
3
+ import { PropsWithChildren } from 'react';
4
  import Link from 'next/link';
5
  import { useParams } from 'next/navigation';
6
  import { cn } from '@/lib/utils';
7
  import { ChatEntity } from '@/lib/types';
8
  import Image from 'next/image';
9
+ import clsx from 'clsx';
10
 
11
+ type ChatCardProps = PropsWithChildren<{
12
  chat: ChatEntity;
13
+ }>;
14
 
15
+ export const ChatCardLayout: React.FC<
16
+ PropsWithChildren<{ link: string; classNames?: clsx.ClassValue }>
17
+ > = ({ link, children, classNames }) => {
18
  return (
19
  <Link
20
  className={cn(
21
+ 'p-2 m-2 bg-background l:h-[250px] rounded-xl shadow-md flex items-center border border-transparent hover:border-gray-500 transition-all cursor-pointer',
22
+ classNames,
23
  )}
24
+ href={link}
25
  >
26
+ {children}
27
+ </Link>
28
+ );
29
+ };
30
+
31
+ const ChatCard: React.FC<ChatCardProps> = ({ chat }) => {
32
+ const { id: chatIdFromParam } = useParams();
33
+ const { id, url, messages, user } = chat;
34
+ const firstMessage = messages?.[0]?.content.slice(0, 50);
35
+ return (
36
+ <ChatCardLayout
37
+ link={`/chat/${id}`}
38
+ classNames={chatIdFromParam === id && 'border-gray-500'}
39
+ >
40
+ <div className="overflow-hidden flex items-center size-full">
41
  <Image
42
  src={url}
43
  alt={url}
 
45
  height={50}
46
  className="rounded w-1/4 "
47
  />
48
+ <p className="text-sm w-3/4 ml-3">
49
+ {firstMessage ? firstMessage + ' ...' : '(No messages yet)'}
50
  </p>
51
  </div>
52
+ </ChatCardLayout>
53
  );
54
  };
55
 
components/chat-sidebar/ChatListSidebar.tsx CHANGED
@@ -1,5 +1,6 @@
1
  import { getKVChats } from '@/lib/kv/chat';
2
- import ChatCard from './ChatCard';
 
3
 
4
  export interface ChatSidebarListProps {}
5
 
@@ -7,6 +8,12 @@ export default async function ChatSidebarList({}: ChatSidebarListProps) {
7
  const chats = await getKVChats();
8
  return (
9
  <>
 
 
 
 
 
 
10
  {chats.map(chat => (
11
  <ChatCard key={chat.id} chat={chat} />
12
  ))}
 
1
  import { getKVChats } from '@/lib/kv/chat';
2
+ import ChatCard, { ChatCardLayout } from './ChatCard';
3
+ import { IconPlus } from '../ui/Icons';
4
 
5
  export interface ChatSidebarListProps {}
6
 
 
8
  const chats = await getKVChats();
9
  return (
10
  <>
11
+ <ChatCardLayout link="/chat">
12
+ <div className="overflow-hidden flex items-center size-full">
13
+ <IconPlus className="w-1/4 font-bold" />
14
+ <p className="text-sm w-3/4 ml-3 font-bold">New chat</p>
15
+ </div>
16
+ </ChatCardLayout>
17
  {chats.map(chat => (
18
  <ChatCard key={chat.id} chat={chat} />
19
  ))}
components/chat/index.tsx CHANGED
@@ -19,23 +19,19 @@ export function Chat({ chat }: ChatProps) {
19
 
20
  return (
21
  <>
22
- <div className={cn('pb-[150px] pt-4 md:pt-10 h-full')}>
23
- <div className="flex h-full">
24
- <div className="w-1/2 relative border-r border-gray-400 overflow-auto p-4 flex items-center justify-center">
25
- <div className="max-h-[600px] size-full relative">
26
- <Image
27
- draggable={false}
28
- src={url}
29
- alt={url}
30
- fill
31
- className="object-contain"
32
- />
33
- </div>
34
- </div>
35
- <div className="w-1/2 relative overflow-auto">
36
- <ChatList messages={messages} />
37
- <ChatScrollAnchor trackVisibility={isLoading} />
38
- </div>
39
  </div>
40
  </div>
41
  <ChatPanel
 
19
 
20
  return (
21
  <>
22
+ <div className={cn('pb-[150px] h-full')}>
23
+ <div className="relative border-b border-gray-400 overflow-auto p-4 flex flex-row items-start h-[300px]">
24
+ <Image
25
+ draggable={false}
26
+ src={url}
27
+ alt={url}
28
+ fill
29
+ className="object-contain"
30
+ />
31
+ </div>
32
+ <div className="overflow-auto">
33
+ <ChatList messages={messages} />
34
+ <ChatScrollAnchor trackVisibility={isLoading} />
 
 
 
 
35
  </div>
36
  </div>
37
  <ChatPanel
components/ui/Icons.tsx CHANGED
@@ -511,7 +511,7 @@ function IconLoading({ className, ...props }: React.ComponentProps<'svg'>) {
511
  return (
512
  <svg
513
  aria-hidden="true"
514
- className="size-8 text-gray-200 animate-spin dark:text-gray-600 fill-gray-600"
515
  viewBox="0 0 100 101"
516
  fill="none"
517
  xmlns="http://www.w3.org/2000/svg"
 
511
  return (
512
  <svg
513
  aria-hidden="true"
514
+ className="size-8 text-gray-200 animate-spin fill-gray-600"
515
  viewBox="0 0 100 101"
516
  fill="none"
517
  xmlns="http://www.w3.org/2000/svg"