Spaces:
Sleeping
Sleeping
wuyiqun0718
commited on
Merge branch 'main' into feat/upload-api
Browse files- app/api/chat/route.ts +4 -14
- app/api/vision-agent/route.ts +95 -0
- app/chat/[id]/page.tsx +12 -3
- components/chat/ChatList.tsx +2 -2
- components/chat/ChatMessage.tsx +2 -2
- components/chat/ChatMessageActions.tsx +2 -2
- components/chat/ChatPanel.tsx +2 -2
- components/chat/ImageSelector.tsx +5 -1
- components/chat/index.tsx +22 -15
- lib/hooks/useImageUpload.ts +1 -0
- lib/hooks/{useChatWithDataset.ts → useVisionAgent.tsx} +12 -42
- lib/kv/chat.ts +13 -3
- lib/types.ts +2 -1
app/api/chat/route.ts
CHANGED
@@ -7,8 +7,7 @@ import {
|
|
7 |
ChatCompletionContentPart,
|
8 |
ChatCompletionContentPartImage,
|
9 |
} from 'openai/resources';
|
10 |
-
import {
|
11 |
-
// import { postAgentChat } from '@/lib/fetch';
|
12 |
|
13 |
export const runtime = 'edge';
|
14 |
|
@@ -19,9 +18,10 @@ const openai = new OpenAI({
|
|
19 |
export async function POST(req: Request) {
|
20 |
const json = await req.json();
|
21 |
const { messages } = json as {
|
22 |
-
messages:
|
|
|
|
|
23 |
};
|
24 |
-
console.log('[Ming] ~ POST ~ messages:', messages);
|
25 |
|
26 |
const session = await auth();
|
27 |
if (!session?.user?.email) {
|
@@ -32,20 +32,11 @@ export async function POST(req: Request) {
|
|
32 |
|
33 |
const formattedMessage: ChatCompletionMessageParam[] = messages.map(
|
34 |
message => {
|
35 |
-
const { dataset, ...rest } = message;
|
36 |
-
|
37 |
const contentWithImage: ChatCompletionContentPart[] = [
|
38 |
{
|
39 |
type: 'text',
|
40 |
text: message.content as string,
|
41 |
},
|
42 |
-
...(dataset ?? []).map(
|
43 |
-
entity =>
|
44 |
-
({
|
45 |
-
type: 'image_url',
|
46 |
-
image_url: { url: entity.url },
|
47 |
-
}) satisfies ChatCompletionContentPartImage,
|
48 |
-
),
|
49 |
];
|
50 |
return {
|
51 |
role: 'user',
|
@@ -53,7 +44,6 @@ export async function POST(req: Request) {
|
|
53 |
};
|
54 |
},
|
55 |
);
|
56 |
-
|
57 |
const res = await openai.chat.completions.create({
|
58 |
model: 'gpt-4-vision-preview',
|
59 |
messages: formattedMessage,
|
|
|
7 |
ChatCompletionContentPart,
|
8 |
ChatCompletionContentPartImage,
|
9 |
} from 'openai/resources';
|
10 |
+
import { MessageBase } from '../../../lib/types';
|
|
|
11 |
|
12 |
export const runtime = 'edge';
|
13 |
|
|
|
18 |
export async function POST(req: Request) {
|
19 |
const json = await req.json();
|
20 |
const { messages } = json as {
|
21 |
+
messages: MessageBase[];
|
22 |
+
id: string;
|
23 |
+
url: string;
|
24 |
};
|
|
|
25 |
|
26 |
const session = await auth();
|
27 |
if (!session?.user?.email) {
|
|
|
32 |
|
33 |
const formattedMessage: ChatCompletionMessageParam[] = messages.map(
|
34 |
message => {
|
|
|
|
|
35 |
const contentWithImage: ChatCompletionContentPart[] = [
|
36 |
{
|
37 |
type: 'text',
|
38 |
text: message.content as string,
|
39 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
];
|
41 |
return {
|
42 |
role: 'user',
|
|
|
44 |
};
|
45 |
},
|
46 |
);
|
|
|
47 |
const res = await openai.chat.completions.create({
|
48 |
model: 'gpt-4-vision-preview',
|
49 |
messages: formattedMessage,
|
app/api/vision-agent/route.ts
ADDED
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { 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 |
+
export async function POST(req: Request) {
|
15 |
+
const json = await req.json();
|
16 |
+
const { messages, url } = json as {
|
17 |
+
messages: MessageBase[];
|
18 |
+
id: string;
|
19 |
+
url: string;
|
20 |
+
};
|
21 |
+
|
22 |
+
const session = await auth();
|
23 |
+
if (!session?.user?.email) {
|
24 |
+
return new Response('Unauthorized', {
|
25 |
+
status: 401,
|
26 |
+
});
|
27 |
+
}
|
28 |
+
|
29 |
+
function parseVisionAgentStream(): AIStreamParser {
|
30 |
+
let previous = '';
|
31 |
+
|
32 |
+
return data => {
|
33 |
+
console.log('[Ming] ~ parseVisionAgentStream ~ data:', data);
|
34 |
+
// const json = JSON.parse(data) as {
|
35 |
+
// completion: string;
|
36 |
+
// stop: string | null;
|
37 |
+
// stop_reason: string | null;
|
38 |
+
// truncated: boolean;
|
39 |
+
// log_id: string;
|
40 |
+
// model: string;
|
41 |
+
// exception: string | null;
|
42 |
+
// };
|
43 |
+
|
44 |
+
// // Anthropic's `completion` field is cumulative unlike OpenAI's
|
45 |
+
// // deltas. In order to compute the delta, we must slice out the text
|
46 |
+
// // we previously received.
|
47 |
+
// const text = json.completion;
|
48 |
+
// console.log('[Ming] ~ parseVisionAgentStream ~ text:', text);
|
49 |
+
// const delta = text.slice(previous.length);
|
50 |
+
// previous = text;
|
51 |
+
|
52 |
+
return data;
|
53 |
+
};
|
54 |
+
}
|
55 |
+
|
56 |
+
function visionAgentStream(
|
57 |
+
res: Response,
|
58 |
+
cb?: AIStreamCallbacksAndOptions,
|
59 |
+
): ReadableStream {
|
60 |
+
return AIStream(res, parseVisionAgentStream(), cb);
|
61 |
+
}
|
62 |
+
|
63 |
+
const formData = new FormData();
|
64 |
+
formData.append('input', JSON.stringify(messages));
|
65 |
+
formData.append('image', url);
|
66 |
+
|
67 |
+
const fetchResponse = await fetcher(
|
68 |
+
'https://api.dev.landing.ai/v1/agent/chat?agent_class=vision_agent',
|
69 |
+
{
|
70 |
+
method: 'POST',
|
71 |
+
headers: {
|
72 |
+
apikey: 'land_sk_DKeoYtaZZrYqJ9TMMiXe4BIQgJcZ0s3XAoB0JT3jv73FFqnr6k',
|
73 |
+
},
|
74 |
+
body: formData,
|
75 |
+
},
|
76 |
+
);
|
77 |
+
console.log('[Ming] ~ POST ~ fetchResponse:', fetchResponse);
|
78 |
+
const stream = visionAgentStream(fetchResponse, {
|
79 |
+
onStart: async () => {
|
80 |
+
console.log('Stream started');
|
81 |
+
},
|
82 |
+
onCompletion: async completion => {
|
83 |
+
console.log('Completion completed', completion);
|
84 |
+
},
|
85 |
+
onFinal: async completion => {
|
86 |
+
console.log('Stream completed', completion);
|
87 |
+
},
|
88 |
+
onToken: async token => {
|
89 |
+
console.log('Token received', token);
|
90 |
+
},
|
91 |
+
});
|
92 |
+
// Now you can consume the VisionAgentStream
|
93 |
+
|
94 |
+
return new StreamingTextResponse(stream);
|
95 |
+
}
|
app/chat/[id]/page.tsx
CHANGED
@@ -1,8 +1,17 @@
|
|
1 |
import { nanoid } from '@/lib/utils';
|
2 |
import { Chat } from '@/components/chat';
|
|
|
3 |
|
4 |
-
|
5 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
|
7 |
-
return <Chat
|
8 |
}
|
|
|
1 |
import { nanoid } from '@/lib/utils';
|
2 |
import { Chat } from '@/components/chat';
|
3 |
+
import { getKVChat } from '@/lib/kv/chat';
|
4 |
|
5 |
+
interface PageProps {
|
6 |
+
params: {
|
7 |
+
id: string;
|
8 |
+
};
|
9 |
+
}
|
10 |
+
|
11 |
+
export default async function Page({ params }: PageProps) {
|
12 |
+
const { id: chatId } = params;
|
13 |
+
|
14 |
+
const chat = await getKVChat(chatId);
|
15 |
|
16 |
+
return <Chat chat={chat} />;
|
17 |
}
|
components/chat/ChatList.tsx
CHANGED
@@ -2,10 +2,10 @@
|
|
2 |
|
3 |
import { Separator } from '@/components/ui/Separator';
|
4 |
import { ChatMessage } from '@/components/chat/ChatMessage';
|
5 |
-
import {
|
6 |
|
7 |
export interface ChatList {
|
8 |
-
messages:
|
9 |
}
|
10 |
|
11 |
export function ChatList({ messages }: ChatList) {
|
|
|
2 |
|
3 |
import { Separator } from '@/components/ui/Separator';
|
4 |
import { ChatMessage } from '@/components/chat/ChatMessage';
|
5 |
+
import { MessageBase } from '../../lib/types';
|
6 |
|
7 |
export interface ChatList {
|
8 |
+
messages: MessageBase[];
|
9 |
}
|
10 |
|
11 |
export function ChatList({ messages }: ChatList) {
|
components/chat/ChatMessage.tsx
CHANGED
@@ -9,10 +9,10 @@ import { CodeBlock } from '@/components/ui/CodeBlock';
|
|
9 |
import { MemoizedReactMarkdown } from '@/components/chat/MemoizedReactMarkdown';
|
10 |
import { IconOpenAI, IconUser } from '@/components/ui/Icons';
|
11 |
import { ChatMessageActions } from '@/components/chat/ChatMessageActions';
|
12 |
-
import {
|
13 |
|
14 |
export interface ChatMessageProps {
|
15 |
-
message:
|
16 |
}
|
17 |
|
18 |
export function ChatMessage({ message, ...props }: ChatMessageProps) {
|
|
|
9 |
import { MemoizedReactMarkdown } from '@/components/chat/MemoizedReactMarkdown';
|
10 |
import { IconOpenAI, IconUser } from '@/components/ui/Icons';
|
11 |
import { ChatMessageActions } from '@/components/chat/ChatMessageActions';
|
12 |
+
import { MessageBase } from '../../lib/types';
|
13 |
|
14 |
export interface ChatMessageProps {
|
15 |
+
message: MessageBase;
|
16 |
}
|
17 |
|
18 |
export function ChatMessage({ message, ...props }: ChatMessageProps) {
|
components/chat/ChatMessageActions.tsx
CHANGED
@@ -6,10 +6,10 @@ import { Button } from '@/components/ui/Button';
|
|
6 |
import { IconCheck, IconCopy } from '@/components/ui/Icons';
|
7 |
import { useCopyToClipboard } from '@/lib/hooks/useCopyToClipboard';
|
8 |
import { cn } from '@/lib/utils';
|
9 |
-
import {
|
10 |
|
11 |
interface ChatMessageActionsProps extends React.ComponentProps<'div'> {
|
12 |
-
message:
|
13 |
}
|
14 |
|
15 |
export function ChatMessageActions({
|
|
|
6 |
import { IconCheck, IconCopy } from '@/components/ui/Icons';
|
7 |
import { useCopyToClipboard } from '@/lib/hooks/useCopyToClipboard';
|
8 |
import { cn } from '@/lib/utils';
|
9 |
+
import { MessageBase } from '../../lib/types';
|
10 |
|
11 |
interface ChatMessageActionsProps extends React.ComponentProps<'div'> {
|
12 |
+
message: MessageBase;
|
13 |
}
|
14 |
|
15 |
export function ChatMessageActions({
|
components/chat/ChatPanel.tsx
CHANGED
@@ -5,7 +5,7 @@ import { Button } from '@/components/ui/Button';
|
|
5 |
import { PromptForm } from '@/components/chat/PromptForm';
|
6 |
import { ButtonScrollToBottom } from '@/components/chat/ButtonScrollToBottom';
|
7 |
import { IconRefresh, IconShare, IconStop } from '@/components/ui/Icons';
|
8 |
-
import {
|
9 |
|
10 |
export interface ChatPanelProps
|
11 |
extends Pick<
|
@@ -14,7 +14,7 @@ export interface ChatPanelProps
|
|
14 |
> {
|
15 |
id?: string;
|
16 |
title?: string;
|
17 |
-
messages:
|
18 |
}
|
19 |
|
20 |
export function ChatPanel({
|
|
|
5 |
import { PromptForm } from '@/components/chat/PromptForm';
|
6 |
import { ButtonScrollToBottom } from '@/components/chat/ButtonScrollToBottom';
|
7 |
import { IconRefresh, IconShare, IconStop } from '@/components/ui/Icons';
|
8 |
+
import { MessageBase } from '../../lib/types';
|
9 |
|
10 |
export interface ChatPanelProps
|
11 |
extends Pick<
|
|
|
14 |
> {
|
15 |
id?: string;
|
16 |
title?: string;
|
17 |
+
messages: MessageBase[];
|
18 |
}
|
19 |
|
20 |
export function ChatPanel({
|
components/chat/ImageSelector.tsx
CHANGED
@@ -18,7 +18,11 @@ const examples: Example[] = [
|
|
18 |
{
|
19 |
url: 'https://landing-lens-support.s3.us-east-2.amazonaws.com/vision-agent-examples/cereal-example.jpg',
|
20 |
initMessages: [
|
21 |
-
{
|
|
|
|
|
|
|
|
|
22 |
],
|
23 |
},
|
24 |
// 'https://landing-lens-support.s3.us-east-2.amazonaws.com/vision-agent-examples/people-example.jpeg',
|
|
|
18 |
{
|
19 |
url: 'https://landing-lens-support.s3.us-east-2.amazonaws.com/vision-agent-examples/cereal-example.jpg',
|
20 |
initMessages: [
|
21 |
+
{
|
22 |
+
role: 'user',
|
23 |
+
content: 'how many cereals are there in the image?',
|
24 |
+
id: 'fake-id-1',
|
25 |
+
},
|
26 |
],
|
27 |
},
|
28 |
// 'https://landing-lens-support.s3.us-east-2.amazonaws.com/vision-agent-examples/people-example.jpeg',
|
components/chat/index.tsx
CHANGED
@@ -1,37 +1,44 @@
|
|
1 |
'use client';
|
|
|
2 |
import { cn } from '@/lib/utils';
|
3 |
import { ChatList } from '@/components/chat/ChatList';
|
4 |
import { ChatPanel } from '@/components/chat/ChatPanel';
|
5 |
import { ChatScrollAnchor } from '@/components/chat/ChatScrollAnchor';
|
6 |
-
import
|
7 |
-
import
|
8 |
-
import
|
9 |
-
import { Button } from '../ui/Button';
|
10 |
-
import ImageSelector from './ImageSelector';
|
11 |
|
12 |
export interface ChatProps extends React.ComponentProps<'div'> {
|
13 |
-
|
14 |
}
|
15 |
|
16 |
-
export function Chat({
|
|
|
17 |
const { messages, append, reload, stop, isLoading, input, setInput } =
|
18 |
-
|
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">
|
25 |
-
|
26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
</div>
|
28 |
<div className="w-1/2 relative overflow-auto">
|
29 |
-
|
30 |
<ChatScrollAnchor trackVisibility={isLoading} />
|
31 |
</div>
|
32 |
</div>
|
33 |
</div>
|
34 |
-
|
35 |
id={id}
|
36 |
isLoading={isLoading}
|
37 |
stop={stop}
|
@@ -40,7 +47,7 @@ export function Chat({ id, className }: ChatProps) {
|
|
40 |
messages={messages}
|
41 |
input={input}
|
42 |
setInput={setInput}
|
43 |
-
/>
|
44 |
</>
|
45 |
);
|
46 |
}
|
|
|
1 |
'use client';
|
2 |
+
|
3 |
import { cn } from '@/lib/utils';
|
4 |
import { ChatList } from '@/components/chat/ChatList';
|
5 |
import { ChatPanel } from '@/components/chat/ChatPanel';
|
6 |
import { ChatScrollAnchor } from '@/components/chat/ChatScrollAnchor';
|
7 |
+
import { ChatEntity } from '@/lib/types';
|
8 |
+
import Image from 'next/image';
|
9 |
+
import useVisionAgent from '@/lib/hooks/useVisionAgent';
|
|
|
|
|
10 |
|
11 |
export interface ChatProps extends React.ComponentProps<'div'> {
|
12 |
+
chat: ChatEntity;
|
13 |
}
|
14 |
|
15 |
+
export function Chat({ chat }: ChatProps) {
|
16 |
+
const { url, id } = chat;
|
17 |
const { messages, append, reload, stop, isLoading, input, setInput } =
|
18 |
+
useVisionAgent(chat);
|
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 |
+
objectFit="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
|
42 |
id={id}
|
43 |
isLoading={isLoading}
|
44 |
stop={stop}
|
|
|
47 |
messages={messages}
|
48 |
input={input}
|
49 |
setInput={setInput}
|
50 |
+
/>
|
51 |
</>
|
52 |
);
|
53 |
}
|
lib/hooks/useImageUpload.ts
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
import { DropzoneOptions, useDropzone } from 'react-dropzone';
|
|
|
2 |
|
3 |
const useImageUpload = (
|
4 |
options?: Partial<DropzoneOptions>,
|
|
|
1 |
import { DropzoneOptions, useDropzone } from 'react-dropzone';
|
2 |
+
// import { toast } from 'react-hot-toast';
|
3 |
|
4 |
const useImageUpload = (
|
5 |
options?: Partial<DropzoneOptions>,
|
lib/hooks/{useChatWithDataset.ts → useVisionAgent.tsx}
RENAMED
@@ -1,12 +1,10 @@
|
|
1 |
import { useChat, type Message } from 'ai/react';
|
2 |
-
import { useAtom } from 'jotai';
|
3 |
import { toast } from 'react-hot-toast';
|
4 |
-
import { datasetAtom } from '../../state';
|
5 |
import { useEffect, useState } from 'react';
|
6 |
-
import {
|
7 |
|
8 |
-
const
|
9 |
-
const
|
10 |
const {
|
11 |
messages,
|
12 |
append,
|
@@ -18,11 +16,17 @@ const useChatWithDataset = () => {
|
|
18 |
setMessages,
|
19 |
} = useChat({
|
20 |
sendExtraMessageFields: true,
|
|
|
21 |
onResponse(response) {
|
22 |
if (response.status !== 200) {
|
23 |
toast.error(response.statusText);
|
24 |
}
|
25 |
},
|
|
|
|
|
|
|
|
|
|
|
26 |
});
|
27 |
|
28 |
const [loadingDots, setLoadingDots] = useState('');
|
@@ -67,43 +71,9 @@ const useChatWithDataset = () => {
|
|
67 |
? [...messages, assistantLoadingMessage]
|
68 |
: messages;
|
69 |
|
70 |
-
const selectedDataset = dataset.find(entity => entity.selected)
|
71 |
-
? dataset.filter(entity => entity.selected)
|
72 |
-
: // If there is no selected dataset, use the entire dataset
|
73 |
-
dataset;
|
74 |
-
|
75 |
-
const appendWithDataset: typeof append = message => {
|
76 |
-
// const newSystemMessage: Message = {
|
77 |
-
// id: 'fake-id',
|
78 |
-
// content:
|
79 |
-
// 'For the next prompt, here are names of images provided by user, please use these name if you need reference: ' +
|
80 |
-
// selectedDataset.map(entity => entity.name).join(', '),
|
81 |
-
// role: 'system',
|
82 |
-
// };
|
83 |
-
// const newSystemMessage: Message = {
|
84 |
-
// id: 'fake-id',
|
85 |
-
// content: `For the next prompt, please use tags provided by the user to assign to corresponding images.
|
86 |
-
// For example:
|
87 |
-
|
88 |
-
// Input:
|
89 |
-
// red, blue, round
|
90 |
-
|
91 |
-
// Answer (each in a new line):
|
92 |
-
// Image 1: red\n
|
93 |
-
// Image 2: blue,round\n`,
|
94 |
-
// role: 'system',
|
95 |
-
// };
|
96 |
-
// setMessages([...messages, newSystemMessage]);
|
97 |
-
return append({
|
98 |
-
...message,
|
99 |
-
// @ts-ignore this is extra fields
|
100 |
-
dataset: selectedDataset,
|
101 |
-
} as MessageWithSelectedDataset);
|
102 |
-
};
|
103 |
-
|
104 |
return {
|
105 |
-
messages: messageWithLoading as
|
106 |
-
append
|
107 |
reload,
|
108 |
stop,
|
109 |
isLoading,
|
@@ -112,4 +82,4 @@ const useChatWithDataset = () => {
|
|
112 |
};
|
113 |
};
|
114 |
|
115 |
-
export default
|
|
|
1 |
import { useChat, type Message } from 'ai/react';
|
|
|
2 |
import { toast } from 'react-hot-toast';
|
|
|
3 |
import { useEffect, useState } from 'react';
|
4 |
+
import { ChatEntity, MessageBase } from '../types';
|
5 |
|
6 |
+
const useVisionAgent = (chat: ChatEntity) => {
|
7 |
+
const { messages: initialMessages, id, url } = chat;
|
8 |
const {
|
9 |
messages,
|
10 |
append,
|
|
|
16 |
setMessages,
|
17 |
} = useChat({
|
18 |
sendExtraMessageFields: true,
|
19 |
+
api: '/api/vision-agent',
|
20 |
onResponse(response) {
|
21 |
if (response.status !== 200) {
|
22 |
toast.error(response.statusText);
|
23 |
}
|
24 |
},
|
25 |
+
initialMessages: initialMessages,
|
26 |
+
body: {
|
27 |
+
url,
|
28 |
+
id,
|
29 |
+
},
|
30 |
});
|
31 |
|
32 |
const [loadingDots, setLoadingDots] = useState('');
|
|
|
71 |
? [...messages, assistantLoadingMessage]
|
72 |
: messages;
|
73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
return {
|
75 |
+
messages: messageWithLoading as MessageBase[],
|
76 |
+
append,
|
77 |
reload,
|
78 |
stop,
|
79 |
isLoading,
|
|
|
82 |
};
|
83 |
};
|
84 |
|
85 |
+
export default useVisionAgent;
|
lib/kv/chat.ts
CHANGED
@@ -5,14 +5,19 @@ import { kv } from '@vercel/kv';
|
|
5 |
|
6 |
import { auth } from '@/auth';
|
7 |
import { ChatEntity } from '@/lib/types';
|
|
|
8 |
|
9 |
-
|
10 |
const session = await auth();
|
11 |
const email = session?.user?.email;
|
12 |
-
|
13 |
if (!email) {
|
14 |
-
|
15 |
}
|
|
|
|
|
|
|
|
|
|
|
16 |
|
17 |
try {
|
18 |
const pipeline = kv.pipeline();
|
@@ -33,8 +38,13 @@ export async function getKVChats() {
|
|
33 |
}
|
34 |
|
35 |
export async function getKVChat(id: string) {
|
|
|
36 |
const chat = await kv.hgetall<ChatEntity>(`chat:${id}`);
|
37 |
|
|
|
|
|
|
|
|
|
38 |
return chat;
|
39 |
}
|
40 |
|
|
|
5 |
|
6 |
import { auth } from '@/auth';
|
7 |
import { ChatEntity } from '@/lib/types';
|
8 |
+
import { redirect } from 'next/navigation';
|
9 |
|
10 |
+
async function authCheck() {
|
11 |
const session = await auth();
|
12 |
const email = session?.user?.email;
|
|
|
13 |
if (!email) {
|
14 |
+
redirect('/');
|
15 |
}
|
16 |
+
return { email, isAdmin: email.endsWith('landing.ai') };
|
17 |
+
}
|
18 |
+
|
19 |
+
export async function getKVChats() {
|
20 |
+
const { email } = await authCheck();
|
21 |
|
22 |
try {
|
23 |
const pipeline = kv.pipeline();
|
|
|
38 |
}
|
39 |
|
40 |
export async function getKVChat(id: string) {
|
41 |
+
const { email, isAdmin } = await authCheck();
|
42 |
const chat = await kv.hgetall<ChatEntity>(`chat:${id}`);
|
43 |
|
44 |
+
if (chat?.user !== email || !isAdmin) {
|
45 |
+
redirect('/');
|
46 |
+
}
|
47 |
+
|
48 |
return chat;
|
49 |
}
|
50 |
|
lib/types.ts
CHANGED
@@ -24,8 +24,9 @@ export type MessageWithSelectedDataset = Message & {
|
|
24 |
};
|
25 |
|
26 |
export type MessageBase = {
|
27 |
-
role: '
|
28 |
content: string;
|
|
|
29 |
};
|
30 |
|
31 |
export type ChatEntity = {
|
|
|
24 |
};
|
25 |
|
26 |
export type MessageBase = {
|
27 |
+
role: Message['role'];
|
28 |
content: string;
|
29 |
+
id: string;
|
30 |
};
|
31 |
|
32 |
export type ChatEntity = {
|