Spaces:
Sleeping
Sleeping
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 |
+
}
|