Spaces:
Running
Running
wuyiqunLu commited on
feat: adjust image to use next/image (#20)
Browse filesIgnore the image size from the video, it was using the unoptimized
property which renders the origin image
https://github.com/landing-ai/vision-agent-ui/assets/132986242/e90977b6-4ced-4ee7-bf02-df599e506631
<img width="970" alt="image"
src="https://github.com/landing-ai/vision-agent-ui/assets/132986242/531e5d7e-dca1-4f74-ad2c-8e60cdf1b178">
<img width="903" alt="image"
src="https://github.com/landing-ai/vision-agent-ui/assets/132986242/49456cf1-efc2-4383-a12b-7cd156caa660">
- components/chat-sidebar/ChatCard.tsx +1 -1
- components/chat/ChatList.tsx +1 -1
- components/chat/ChatMessage.tsx +23 -1
- components/chat/PromptForm.tsx +6 -2
- components/ui/Img.tsx +3 -3
- lib/hooks/useCleanedUpMessages.ts +10 -3
- lib/hooks/useVisionAgent.tsx +9 -8
- public/loading.gif +0 -0
components/chat-sidebar/ChatCard.tsx
CHANGED
|
@@ -44,7 +44,7 @@ const ChatCard: React.FC<ChatCardProps> = ({ chat }) => {
|
|
| 44 |
classNames={chatIdFromParam === id && 'border-gray-500'}
|
| 45 |
>
|
| 46 |
<div className="overflow-hidden flex items-center size-full">
|
| 47 |
-
<Img src={url} className="w-1/4 " />
|
| 48 |
<div className="flex items-start h-full">
|
| 49 |
<p className="text-sm w-3/4 ml-3">{title}</p>
|
| 50 |
</div>
|
|
|
|
| 44 |
classNames={chatIdFromParam === id && 'border-gray-500'}
|
| 45 |
>
|
| 46 |
<div className="overflow-hidden flex items-center size-full">
|
| 47 |
+
<Img src={url} alt={`chat-${id}-card-image`} className="w-1/4 " />
|
| 48 |
<div className="flex items-start h-full">
|
| 49 |
<p className="text-sm w-3/4 ml-3">{title}</p>
|
| 50 |
</div>
|
components/chat/ChatList.tsx
CHANGED
|
@@ -10,7 +10,7 @@ export interface ChatList {
|
|
| 10 |
|
| 11 |
export function ChatList({ messages }: ChatList) {
|
| 12 |
return (
|
| 13 |
-
<div className="relative mx-auto max-w-5xl px-8 pr-12">
|
| 14 |
{messages
|
| 15 |
// .filter(message => message.role !== 'system')
|
| 16 |
.map((message, index) => (
|
|
|
|
| 10 |
|
| 11 |
export function ChatList({ messages }: ChatList) {
|
| 12 |
return (
|
| 13 |
+
<div className="relative mx-auto max-w-5xl px-8 pr-12 pb-[100px]">
|
| 14 |
{messages
|
| 15 |
// .filter(message => message.role !== 'system')
|
| 16 |
.map((message, index) => (
|
components/chat/ChatMessage.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import { IconOpenAI, IconUser } from '@/components/ui/Icons';
|
|
| 11 |
import { ChatMessageActions } from '@/components/chat/ChatMessageActions';
|
| 12 |
import { MessageBase } from '../../lib/types';
|
| 13 |
import { useCleanedUpMessages } from '@/lib/hooks/useCleanedUpMessages';
|
|
|
|
| 14 |
|
| 15 |
export interface ChatMessageProps {
|
| 16 |
message: MessageBase;
|
|
@@ -60,11 +61,32 @@ export function ChatMessage({ message, ...props }: ChatMessageProps) {
|
|
| 60 |
className="break-words"
|
| 61 |
remarkPlugins={[remarkGfm, remarkMath]}
|
| 62 |
components={{
|
| 63 |
-
p({ children }) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
return (
|
| 65 |
<p className="my-2 last:mb-0 whitespace-pre-line">{children}</p>
|
| 66 |
);
|
| 67 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
code({ node, inline, className, children, ...props }) {
|
| 69 |
if (children.length) {
|
| 70 |
if (children[0] == '▍') {
|
|
|
|
| 11 |
import { ChatMessageActions } from '@/components/chat/ChatMessageActions';
|
| 12 |
import { MessageBase } from '../../lib/types';
|
| 13 |
import { useCleanedUpMessages } from '@/lib/hooks/useCleanedUpMessages';
|
| 14 |
+
import Img from '../ui/Img';
|
| 15 |
|
| 16 |
export interface ChatMessageProps {
|
| 17 |
message: MessageBase;
|
|
|
|
| 61 |
className="break-words"
|
| 62 |
remarkPlugins={[remarkGfm, remarkMath]}
|
| 63 |
components={{
|
| 64 |
+
p({ children, ...props }) {
|
| 65 |
+
if (
|
| 66 |
+
props.node.children.some(
|
| 67 |
+
child => child.type === 'element' && child.tagName === 'img',
|
| 68 |
+
)
|
| 69 |
+
) {
|
| 70 |
+
return (
|
| 71 |
+
<p className="flex flex-wrap gap-2 items-start">{children}</p>
|
| 72 |
+
);
|
| 73 |
+
}
|
| 74 |
return (
|
| 75 |
<p className="my-2 last:mb-0 whitespace-pre-line">{children}</p>
|
| 76 |
);
|
| 77 |
},
|
| 78 |
+
img(props) {
|
| 79 |
+
return (
|
| 80 |
+
<Img
|
| 81 |
+
src={props.src ?? '/landing.png'}
|
| 82 |
+
alt={props.alt ?? 'answer-image'}
|
| 83 |
+
quality={100}
|
| 84 |
+
sizes="(min-width: 66em) 40vw,
|
| 85 |
+
(min-width: 44em) 66vw,
|
| 86 |
+
100vw"
|
| 87 |
+
/>
|
| 88 |
+
);
|
| 89 |
+
},
|
| 90 |
code({ node, inline, className, children, ...props }) {
|
| 91 |
if (children.length) {
|
| 92 |
if (children[0] == '▍') {
|
components/chat/PromptForm.tsx
CHANGED
|
@@ -60,10 +60,14 @@ export function PromptForm({
|
|
| 60 |
{url && (
|
| 61 |
<Tooltip>
|
| 62 |
<TooltipTrigger asChild>
|
| 63 |
-
<Img
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
</TooltipTrigger>
|
| 65 |
<TooltipContent>
|
| 66 |
-
<Img src={url} className="m-2" />
|
| 67 |
</TooltipContent>
|
| 68 |
</Tooltip>
|
| 69 |
)}
|
|
|
|
| 60 |
{url && (
|
| 61 |
<Tooltip>
|
| 62 |
<TooltipTrigger asChild>
|
| 63 |
+
<Img
|
| 64 |
+
alt="prompt-image"
|
| 65 |
+
src={url}
|
| 66 |
+
className="w-1/5 my-4 mx-2 cursor-zoom-in"
|
| 67 |
+
/>
|
| 68 |
</TooltipTrigger>
|
| 69 |
<TooltipContent>
|
| 70 |
+
<Img alt="prompt-hovered-image" src={url} className="m-2" />
|
| 71 |
</TooltipContent>
|
| 72 |
</Tooltip>
|
| 73 |
)}
|
components/ui/Img.tsx
CHANGED
|
@@ -10,8 +10,8 @@ const placeholder =
|
|
| 10 |
// const Props = Omit<React.ComponentPropsWithoutRef<typeof Image>, 'alt'>;
|
| 11 |
const Img = React.forwardRef<
|
| 12 |
React.ElementRef<typeof Image>,
|
| 13 |
-
|
| 14 |
-
>(({ src, onLoad, width, height, className, ...props }, ref) => {
|
| 15 |
const [dimensions, setDimensions] = React.useState({
|
| 16 |
width: width ?? 200,
|
| 17 |
height: height ?? 200,
|
|
@@ -24,7 +24,7 @@ const Img = React.forwardRef<
|
|
| 24 |
placeholder={placeholder}
|
| 25 |
width={dimensions.width}
|
| 26 |
height={dimensions.height}
|
| 27 |
-
alt=
|
| 28 |
ref={ref}
|
| 29 |
className={cn('rounded-md', className)}
|
| 30 |
onLoad={e => {
|
|
|
|
| 10 |
// const Props = Omit<React.ComponentPropsWithoutRef<typeof Image>, 'alt'>;
|
| 11 |
const Img = React.forwardRef<
|
| 12 |
React.ElementRef<typeof Image>,
|
| 13 |
+
React.ComponentPropsWithoutRef<typeof Image>
|
| 14 |
+
>(({ src, alt, onLoad, width, height, className, ...props }, ref) => {
|
| 15 |
const [dimensions, setDimensions] = React.useState({
|
| 16 |
width: width ?? 200,
|
| 17 |
height: height ?? 200,
|
|
|
|
| 24 |
placeholder={placeholder}
|
| 25 |
width={dimensions.width}
|
| 26 |
height={dimensions.height}
|
| 27 |
+
alt={alt ?? 'image'}
|
| 28 |
ref={ref}
|
| 29 |
className={cn('rounded-md', className)}
|
| 30 |
onLoad={e => {
|
lib/hooks/useCleanedUpMessages.ts
CHANGED
|
@@ -62,11 +62,18 @@ export const getCleanedUpMessages = ({
|
|
| 62 |
}
|
| 63 |
cleanedLogs.push(content.substring(left, right));
|
| 64 |
const [answerText, imagesStr = ''] = answer.split('<VIZ>');
|
| 65 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
return {
|
| 67 |
logs: cleanedLogs.join('').replace(/│/g, '|').split('|\n\n|').join('|\n|'),
|
| 68 |
-
content:
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
| 70 |
};
|
| 71 |
};
|
| 72 |
|
|
|
|
| 62 |
}
|
| 63 |
cleanedLogs.push(content.substring(left, right));
|
| 64 |
const [answerText, imagesStr = ''] = answer.split('<VIZ>');
|
| 65 |
+
const [imagesArrayStr, ...rest] = imagesStr.split('</VIZ>');
|
| 66 |
+
const images = imagesArrayStr
|
| 67 |
+
.split('</IMG>')
|
| 68 |
+
.map(str => str.replace('<IMG>', ''))
|
| 69 |
+
.slice(0, -1);
|
| 70 |
return {
|
| 71 |
logs: cleanedLogs.join('').replace(/│/g, '|').split('|\n\n|').join('|\n|'),
|
| 72 |
+
content:
|
| 73 |
+
answerText.replace('</</ANSWER>', '').replace('</ANSWER>', '') +
|
| 74 |
+
images.map((_, index) => ``).join('') +
|
| 75 |
+
rest.join(''),
|
| 76 |
+
images: images,
|
| 77 |
};
|
| 78 |
};
|
| 79 |
|
lib/hooks/useVisionAgent.tsx
CHANGED
|
@@ -72,17 +72,18 @@ const useVisionAgent = (chat: ChatEntity) => {
|
|
| 72 |
uploadBase64(image, message.id, id, index),
|
| 73 |
),
|
| 74 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
const newMessage = {
|
| 76 |
...message,
|
| 77 |
-
content:
|
| 78 |
-
logs +
|
| 79 |
-
CLEANED_SEPARATOR +
|
| 80 |
-
content +
|
| 81 |
-
'\n' +
|
| 82 |
-
publicUrls
|
| 83 |
-
.map((url, index) => ``)
|
| 84 |
-
.join('\n'),
|
| 85 |
};
|
|
|
|
|
|
|
| 86 |
saveKVChatMessage(id, newMessage);
|
| 87 |
} else {
|
| 88 |
saveKVChatMessage(id, {
|
|
|
|
| 72 |
uploadBase64(image, message.id, id, index),
|
| 73 |
),
|
| 74 |
);
|
| 75 |
+
const newContent = publicUrls.reduce((accum, url, index) => {
|
| 76 |
+
return accum.replace(
|
| 77 |
+
``,
|
| 78 |
+
``,
|
| 79 |
+
);
|
| 80 |
+
}, content);
|
| 81 |
const newMessage = {
|
| 82 |
...message,
|
| 83 |
+
content: logs + CLEANED_SEPARATOR + newContent,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
};
|
| 85 |
+
console.log(messages);
|
| 86 |
+
setMessages([...messages, newMessage]);
|
| 87 |
saveKVChatMessage(id, newMessage);
|
| 88 |
} else {
|
| 89 |
saveKVChatMessage(id, {
|
public/loading.gif
ADDED
|