Spaces:
Sleeping
Sleeping
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 +1 -55
- app/chat/layout.tsx +2 -1
- app/layout.tsx +2 -2
- components/Providers.tsx +5 -1
- components/ThemeToggle.tsx +1 -1
- components/chat-sidebar/ChatCard.tsx +28 -12
- components/chat-sidebar/ChatListSidebar.tsx +8 -1
- components/chat/index.tsx +13 -17
- components/ui/Icons.tsx +1 -1
app/api/vision-agent/route.ts
CHANGED
@@ -1,62 +1,10 @@
|
|
1 |
-
import {
|
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
|
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: '
|
16 |
-
template: `%s -
|
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>
|
|
|
|
|
|
|
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 |
-
{
|
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 |
-
|
10 |
chat: ChatEntity;
|
11 |
-
}
|
12 |
|
13 |
-
const
|
14 |
-
|
15 |
-
|
16 |
return (
|
17 |
<Link
|
18 |
className={cn(
|
19 |
-
'p-2 m-2 bg-
|
20 |
-
|
21 |
)}
|
22 |
-
href={
|
23 |
>
|
24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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-
|
33 |
-
{
|
34 |
</p>
|
35 |
</div>
|
36 |
-
</
|
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]
|
23 |
-
<div className="flex h-
|
24 |
-
<
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
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
|
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"
|