Spaces:
Running
Running
Delete chat and clean up toggle into search params (#48)
Browse files<img width="246" alt="image"
src="https://github.com/landing-ai/vision-agent-ui/assets/5669963/2b1b7cdc-7671-4211-a9be-32e8954c73e1">
- app/api/vision-agent/route.ts +1 -1
- components/chat-sidebar/ChatCard.tsx +13 -1
- components/chat-sidebar/ChatListSidebar.tsx +1 -1
- components/chat/Composer.tsx +0 -13
- components/chat/index.tsx +1 -5
- components/project/ProjectChat.tsx +6 -13
- lib/hooks/useVisionAgent.ts +6 -2
- lib/kv/chat.ts +13 -18
- package.json +1 -1
app/api/vision-agent/route.ts
CHANGED
@@ -57,7 +57,7 @@ export const POST = withLogging(
|
|
57 |
|
58 |
const fetchResponse = await fetch(
|
59 |
`https://api.dev.landing.ai/v1/agent/chat?agent_class=vision_agent&visualize_output=true&self_reflection=${enableSelfReflection}`,
|
60 |
-
//
|
61 |
{
|
62 |
method: 'POST',
|
63 |
headers: {
|
|
|
57 |
|
58 |
const fetchResponse = await fetch(
|
59 |
`https://api.dev.landing.ai/v1/agent/chat?agent_class=vision_agent&visualize_output=true&self_reflection=${enableSelfReflection}`,
|
60 |
+
// `http://localhost:5001/v1/agent/chat?agent_class=vision_agent&visualize_output=true&self_reflection=${enableSelfReflection}`,
|
61 |
{
|
62 |
method: 'POST',
|
63 |
headers: {
|
components/chat-sidebar/ChatCard.tsx
CHANGED
@@ -10,6 +10,8 @@ import clsx from 'clsx';
|
|
10 |
import Img from '../ui/Img';
|
11 |
import { format } from 'date-fns';
|
12 |
import { cleanInputMessage } from '@/lib/messageUtils';
|
|
|
|
|
13 |
// import { format } from 'date-fns';
|
14 |
|
15 |
type ChatCardProps = PropsWithChildren<{
|
@@ -36,6 +38,9 @@ export const ChatCardLayout: React.FC<
|
|
36 |
const ChatCard: React.FC<ChatCardProps> = ({ chat, isAdminView }) => {
|
37 |
const { id: chatIdFromParam } = useParams();
|
38 |
const { id, url, messages, user, updatedAt } = chat;
|
|
|
|
|
|
|
39 |
const firstMessage = cleanInputMessage(messages?.[0]?.content ?? '');
|
40 |
const title = firstMessage
|
41 |
? firstMessage.length > 50
|
@@ -47,7 +52,7 @@ const ChatCard: React.FC<ChatCardProps> = ({ chat, isAdminView }) => {
|
|
47 |
link={isAdminView ? `/all/chat/${id}` : `/chat/${id}`}
|
48 |
classNames={chatIdFromParam === id && 'border-gray-500'}
|
49 |
>
|
50 |
-
<div className="overflow-hidden flex items-center size-full">
|
51 |
<Img src={url} alt={`chat-${id}-card-image`} className="w-1/4" />
|
52 |
<div className="flex items-start flex-col h-full ml-3 w-3/4">
|
53 |
<p className="text-sm mb-1">{title}</p>
|
@@ -55,6 +60,13 @@ const ChatCard: React.FC<ChatCardProps> = ({ chat, isAdminView }) => {
|
|
55 |
{updatedAt ? format(Number(updatedAt), 'yyyy-MM-dd') : '-'}
|
56 |
</p>
|
57 |
{isAdminView && <p className="text-xs text-gray-500">{user}</p>}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
</div>
|
59 |
</div>
|
60 |
</ChatCardLayout>
|
|
|
10 |
import Img from '../ui/Img';
|
11 |
import { format } from 'date-fns';
|
12 |
import { cleanInputMessage } from '@/lib/messageUtils';
|
13 |
+
import { IconClose } from '../ui/Icons';
|
14 |
+
import { removeKVChat } from '@/lib/kv/chat';
|
15 |
// import { format } from 'date-fns';
|
16 |
|
17 |
type ChatCardProps = PropsWithChildren<{
|
|
|
38 |
const ChatCard: React.FC<ChatCardProps> = ({ chat, isAdminView }) => {
|
39 |
const { id: chatIdFromParam } = useParams();
|
40 |
const { id, url, messages, user, updatedAt } = chat;
|
41 |
+
if (!id) {
|
42 |
+
return null;
|
43 |
+
}
|
44 |
const firstMessage = cleanInputMessage(messages?.[0]?.content ?? '');
|
45 |
const title = firstMessage
|
46 |
? firstMessage.length > 50
|
|
|
52 |
link={isAdminView ? `/all/chat/${id}` : `/chat/${id}`}
|
53 |
classNames={chatIdFromParam === id && 'border-gray-500'}
|
54 |
>
|
55 |
+
<div className="overflow-hidden flex items-center size-full group">
|
56 |
<Img src={url} alt={`chat-${id}-card-image`} className="w-1/4" />
|
57 |
<div className="flex items-start flex-col h-full ml-3 w-3/4">
|
58 |
<p className="text-sm mb-1">{title}</p>
|
|
|
60 |
{updatedAt ? format(Number(updatedAt), 'yyyy-MM-dd') : '-'}
|
61 |
</p>
|
62 |
{isAdminView && <p className="text-xs text-gray-500">{user}</p>}
|
63 |
+
<IconClose
|
64 |
+
onClick={async e => {
|
65 |
+
e.stopPropagation();
|
66 |
+
await removeKVChat(id);
|
67 |
+
}}
|
68 |
+
className="absolute right-4 opacity-0 group-hover:opacity-100 top-1/2 -translate-y-1/2"
|
69 |
+
/>
|
70 |
</div>
|
71 |
</div>
|
72 |
</ChatCardLayout>
|
components/chat-sidebar/ChatListSidebar.tsx
CHANGED
@@ -19,7 +19,7 @@ const getItemSize = (message: string, isAdminView?: boolean) => {
|
|
19 |
else return 88;
|
20 |
};
|
21 |
|
22 |
-
export default
|
23 |
chats,
|
24 |
isAdminView,
|
25 |
}: ChatSidebarListProps) {
|
|
|
19 |
else return 88;
|
20 |
};
|
21 |
|
22 |
+
export default function ChatSidebarList({
|
23 |
chats,
|
24 |
isAdminView,
|
25 |
}: ChatSidebarListProps) {
|
components/chat/Composer.tsx
CHANGED
@@ -34,8 +34,6 @@ export interface ComposerProps
|
|
34 |
url?: string;
|
35 |
isAtBottom: boolean;
|
36 |
scrollToBottom: () => void;
|
37 |
-
enableSelfReflection: boolean;
|
38 |
-
setEnableSelfReflection: (value: boolean) => void;
|
39 |
}
|
40 |
|
41 |
export function Composer({
|
@@ -50,8 +48,6 @@ export function Composer({
|
|
50 |
messages,
|
51 |
isAtBottom,
|
52 |
scrollToBottom,
|
53 |
-
enableSelfReflection,
|
54 |
-
setEnableSelfReflection,
|
55 |
url,
|
56 |
}: ComposerProps) {
|
57 |
const { formRef, onKeyDown } = useEnterSubmit();
|
@@ -124,15 +120,6 @@ export function Composer({
|
|
124 |
spellCheck={false}
|
125 |
className="min-h-[60px] resize-none bg-transparent py-[1.3em] focus-within:outline-none sm:text-sm"
|
126 |
/>
|
127 |
-
<div className="flex items-center gap-2 mt-4">
|
128 |
-
<Switch
|
129 |
-
checked={enableSelfReflection}
|
130 |
-
onCheckedChange={checked => {
|
131 |
-
setEnableSelfReflection(checked);
|
132 |
-
}}
|
133 |
-
/>
|
134 |
-
<p className="text-sm">Self Reflection</p>
|
135 |
-
</div>
|
136 |
</div>
|
137 |
{/* Scroll to bottom Icon */}
|
138 |
<div
|
|
|
34 |
url?: string;
|
35 |
isAtBottom: boolean;
|
36 |
scrollToBottom: () => void;
|
|
|
|
|
37 |
}
|
38 |
|
39 |
export function Composer({
|
|
|
48 |
messages,
|
49 |
isAtBottom,
|
50 |
scrollToBottom,
|
|
|
|
|
51 |
url,
|
52 |
}: ComposerProps) {
|
53 |
const { formRef, onKeyDown } = useEnterSubmit();
|
|
|
120 |
spellCheck={false}
|
121 |
className="min-h-[60px] resize-none bg-transparent py-[1.3em] focus-within:outline-none sm:text-sm"
|
122 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
</div>
|
124 |
{/* Scroll to bottom Icon */}
|
125 |
<div
|
components/chat/index.tsx
CHANGED
@@ -16,10 +16,8 @@ export interface ChatProps extends React.ComponentProps<'div'> {
|
|
16 |
|
17 |
export function Chat({ chat, session, isAdminView }: ChatProps) {
|
18 |
const { url, id } = chat;
|
19 |
-
const [enableSelfReflection, setEnableSelfReflection] =
|
20 |
-
useState<boolean>(true);
|
21 |
const { messages, append, reload, stop, isLoading, input, setInput } =
|
22 |
-
useVisionAgent(chat
|
23 |
|
24 |
const { messagesRef, scrollRef, visibilityRef, isAtBottom, scrollToBottom } =
|
25 |
useScrollAnchor();
|
@@ -50,8 +48,6 @@ export function Chat({ chat, session, isAdminView }: ChatProps) {
|
|
50 |
setInput={setInput}
|
51 |
isAtBottom={isAtBottom}
|
52 |
scrollToBottom={scrollToBottom}
|
53 |
-
enableSelfReflection={enableSelfReflection}
|
54 |
-
setEnableSelfReflection={setEnableSelfReflection}
|
55 |
/>
|
56 |
</div>
|
57 |
)}
|
|
|
16 |
|
17 |
export function Chat({ chat, session, isAdminView }: ChatProps) {
|
18 |
const { url, id } = chat;
|
|
|
|
|
19 |
const { messages, append, reload, stop, isLoading, input, setInput } =
|
20 |
+
useVisionAgent(chat);
|
21 |
|
22 |
const { messagesRef, scrollRef, visibilityRef, isAtBottom, scrollToBottom } =
|
23 |
useScrollAnchor();
|
|
|
48 |
setInput={setInput}
|
49 |
isAtBottom={isAtBottom}
|
50 |
scrollToBottom={scrollToBottom}
|
|
|
|
|
51 |
/>
|
52 |
</div>
|
53 |
)}
|
components/project/ProjectChat.tsx
CHANGED
@@ -19,18 +19,13 @@ const ProjectChat: React.FC<ChatProps> = ({ mediaList }) => {
|
|
19 |
// fallback to the first media
|
20 |
const selectedMedia =
|
21 |
mediaList.find(media => media.id === selectedMediaId) ?? mediaList[0];
|
22 |
-
const [enableSelfReflection, setEnableSelfReflection] =
|
23 |
-
useState<boolean>(true);
|
24 |
const { messages, append, reload, stop, isLoading, input, setInput } =
|
25 |
-
useVisionAgent(
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
},
|
32 |
-
enableSelfReflection,
|
33 |
-
);
|
34 |
|
35 |
const { messagesRef, scrollRef, visibilityRef, isAtBottom, scrollToBottom } =
|
36 |
useScrollAnchor();
|
@@ -55,8 +50,6 @@ const ProjectChat: React.FC<ChatProps> = ({ mediaList }) => {
|
|
55 |
setInput={setInput}
|
56 |
isAtBottom={isAtBottom}
|
57 |
scrollToBottom={scrollToBottom}
|
58 |
-
enableSelfReflection={enableSelfReflection}
|
59 |
-
setEnableSelfReflection={setEnableSelfReflection}
|
60 |
/>
|
61 |
</div>
|
62 |
</>
|
|
|
19 |
// fallback to the first media
|
20 |
const selectedMedia =
|
21 |
mediaList.find(media => media.id === selectedMediaId) ?? mediaList[0];
|
|
|
|
|
22 |
const { messages, append, reload, stop, isLoading, input, setInput } =
|
23 |
+
useVisionAgent({
|
24 |
+
url: selectedMedia.url,
|
25 |
+
messages: [],
|
26 |
+
user: '[email protected]',
|
27 |
+
updatedAt: Date.now(),
|
28 |
+
});
|
|
|
|
|
|
|
29 |
|
30 |
const { messagesRef, scrollRef, visibilityRef, isAtBottom, scrollToBottom } =
|
31 |
useScrollAnchor();
|
|
|
50 |
setInput={setInput}
|
51 |
isAtBottom={isAtBottom}
|
52 |
scrollToBottom={scrollToBottom}
|
|
|
|
|
53 |
/>
|
54 |
</div>
|
55 |
</>
|
lib/hooks/useVisionAgent.ts
CHANGED
@@ -10,6 +10,7 @@ import {
|
|
10 |
generateInputImageMarkdown,
|
11 |
} from '../messageUtils';
|
12 |
import { CLEANED_SEPARATOR } from '../constants';
|
|
|
13 |
|
14 |
const uploadBase64 = async (
|
15 |
base64: string,
|
@@ -49,8 +50,11 @@ const uploadBase64 = async (
|
|
49 |
}
|
50 |
};
|
51 |
|
52 |
-
const useVisionAgent = (chat: ChatEntity
|
53 |
const { messages: initialMessages, id, url } = chat;
|
|
|
|
|
|
|
54 |
const {
|
55 |
messages,
|
56 |
append: appendRaw,
|
@@ -120,7 +124,7 @@ const useVisionAgent = (chat: ChatEntity, enableSelfReflection = true) => {
|
|
120 |
body: {
|
121 |
url,
|
122 |
id,
|
123 |
-
enableSelfReflection,
|
124 |
},
|
125 |
});
|
126 |
|
|
|
10 |
generateInputImageMarkdown,
|
11 |
} from '../messageUtils';
|
12 |
import { CLEANED_SEPARATOR } from '../constants';
|
13 |
+
import { useSearchParams } from 'next/navigation';
|
14 |
|
15 |
const uploadBase64 = async (
|
16 |
base64: string,
|
|
|
50 |
}
|
51 |
};
|
52 |
|
53 |
+
const useVisionAgent = (chat: ChatEntity) => {
|
54 |
const { messages: initialMessages, id, url } = chat;
|
55 |
+
const searchParams = useSearchParams();
|
56 |
+
const reflectionValue = searchParams.get('reflection');
|
57 |
+
|
58 |
const {
|
59 |
messages,
|
60 |
append: appendRaw,
|
|
|
124 |
body: {
|
125 |
url,
|
126 |
id,
|
127 |
+
enableSelfReflection: reflectionValue === 'true',
|
128 |
},
|
129 |
});
|
130 |
|
lib/kv/chat.ts
CHANGED
@@ -23,8 +23,11 @@ export async function getKVChats() {
|
|
23 |
|
24 |
const results = (await pipeline.exec()) as ChatEntity[];
|
25 |
|
26 |
-
return results
|
|
|
|
|
27 |
} catch (error) {
|
|
|
28 |
return [];
|
29 |
}
|
30 |
}
|
@@ -94,30 +97,22 @@ export async function saveKVChatMessage(id: string, message: MessageBase) {
|
|
94 |
messages: [...messages, message],
|
95 |
updatedAt: Date.now(),
|
96 |
});
|
97 |
-
revalidatePath('/chat', 'layout');
|
98 |
}
|
99 |
|
100 |
-
export async function removeKVChat(
|
101 |
-
const
|
102 |
-
|
103 |
-
if (!session) {
|
104 |
-
return {
|
105 |
-
error: 'Unauthorized',
|
106 |
-
};
|
107 |
-
}
|
108 |
-
|
109 |
-
//Convert uid to string for consistent comparison with session.user.id
|
110 |
-
const uid = String(await kv.hget(`chat:${id}`, 'userId'));
|
111 |
|
112 |
-
if (
|
113 |
return {
|
114 |
error: 'Unauthorized',
|
115 |
};
|
116 |
}
|
117 |
|
118 |
-
await
|
119 |
-
|
|
|
|
|
120 |
|
121 |
-
revalidatePath('/chat
|
122 |
-
return revalidatePath(path);
|
123 |
}
|
|
|
23 |
|
24 |
const results = (await pipeline.exec()) as ChatEntity[];
|
25 |
|
26 |
+
return results
|
27 |
+
.filter(r => !!r)
|
28 |
+
.sort((r1, r2) => r2.updatedAt - r1.updatedAt);
|
29 |
} catch (error) {
|
30 |
+
console.error('getKVChats error:', error);
|
31 |
return [];
|
32 |
}
|
33 |
}
|
|
|
97 |
messages: [...messages, message],
|
98 |
updatedAt: Date.now(),
|
99 |
});
|
100 |
+
return revalidatePath('/chat', 'layout');
|
101 |
}
|
102 |
|
103 |
+
export async function removeKVChat(id: string) {
|
104 |
+
const { email } = await authEmail();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
|
106 |
+
if (!email) {
|
107 |
return {
|
108 |
error: 'Unauthorized',
|
109 |
};
|
110 |
}
|
111 |
|
112 |
+
await Promise.all([
|
113 |
+
kv.zrem(`user:chat:${email}`, `chat:${id}`),
|
114 |
+
kv.del(`chat:${id}`),
|
115 |
+
]);
|
116 |
|
117 |
+
return revalidatePath('/chat', 'layout');
|
|
|
118 |
}
|
package.json
CHANGED
@@ -78,4 +78,4 @@
|
|
78 |
"typescript": "^5.3.3"
|
79 |
},
|
80 |
"packageManager": "[email protected]"
|
81 |
-
}
|
|
|
78 |
"typescript": "^5.3.3"
|
79 |
},
|
80 |
"packageManager": "[email protected]"
|
81 |
+
}
|