Spaces:
Sleeping
Sleeping
MingruiZhang
commited on
Commit
•
1f931d5
1
Parent(s):
4ec47c5
feat: Composer refactor and layout scroll (#62)
Browse files![image](https://github.com/landing-ai/vision-agent-ui/assets/5669963/6f92a2d2-0f4d-49a5-ab3d-770706e3b0bf)
- app/layout.tsx +3 -1
- components/chat/ChatClient.tsx +36 -34
- components/chat/ChatServer.tsx +2 -3
- components/chat/Composer.tsx +92 -114
- components/ui/Chip.tsx +7 -6
- components/ui/DropdownMenu.tsx +1 -1
- components/ui/Icons.tsx +22 -103
- components/ui/Tooltip.tsx +1 -1
app/layout.tsx
CHANGED
@@ -53,7 +53,9 @@ export default function RootLayout(props: RootLayoutProps) {
|
|
53 |
>
|
54 |
<div className="flex flex-col min-h-screen">
|
55 |
<Header />
|
56 |
-
<main className="flex flex-col flex-1 bg-muted/50">
|
|
|
|
|
57 |
</div>
|
58 |
<TailwindIndicator />
|
59 |
</Providers>
|
|
|
53 |
>
|
54 |
<div className="flex flex-col min-h-screen">
|
55 |
<Header />
|
56 |
+
<main className="flex max-h-[calc(100vh-64px)] flex-col flex-1 bg-muted/50 overflow-hidden">
|
57 |
+
{children}
|
58 |
+
</main>
|
59 |
</div>
|
60 |
<TailwindIndicator />
|
61 |
</Providers>
|
components/chat/ChatClient.tsx
CHANGED
@@ -1,20 +1,19 @@
|
|
1 |
'use client';
|
2 |
|
3 |
-
import { ChatList } from '@/components/chat/ChatList';
|
4 |
import { Composer } from '@/components/chat/Composer';
|
5 |
import useVisionAgent from '@/lib/hooks/useVisionAgent';
|
6 |
import { useScrollAnchor } from '@/lib/hooks/useScrollAnchor';
|
7 |
import { Session } from 'next-auth';
|
8 |
import { useState } from 'react';
|
9 |
import { ChatWithMessages } from '@/lib/db/types';
|
|
|
10 |
|
11 |
-
export interface
|
12 |
chat: ChatWithMessages;
|
13 |
-
isAdminView?: boolean;
|
14 |
-
session: Session | null;
|
15 |
}
|
16 |
|
17 |
-
|
18 |
const { mediaUrl, id } = chat;
|
19 |
const { messages, append, reload, stop, isLoading, input, setInput } =
|
20 |
useVisionAgent(chat);
|
@@ -23,34 +22,37 @@ export function Chat({ chat, session, isAdminView }: ChatProps) {
|
|
23 |
useScrollAnchor();
|
24 |
|
25 |
return (
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
|
|
|
|
|
|
|
|
|
|
36 |
</div>
|
37 |
-
|
38 |
-
<
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
/>
|
52 |
-
</div>
|
53 |
-
)}
|
54 |
-
</>
|
55 |
);
|
56 |
-
}
|
|
|
|
|
|
1 |
'use client';
|
2 |
|
3 |
+
// import { ChatList } from '@/components/chat/ChatList';
|
4 |
import { Composer } from '@/components/chat/Composer';
|
5 |
import useVisionAgent from '@/lib/hooks/useVisionAgent';
|
6 |
import { useScrollAnchor } from '@/lib/hooks/useScrollAnchor';
|
7 |
import { Session } from 'next-auth';
|
8 |
import { useState } from 'react';
|
9 |
import { ChatWithMessages } from '@/lib/db/types';
|
10 |
+
import { ChatMessage } from './ChatMessage';
|
11 |
|
12 |
+
export interface ChatClientProps {
|
13 |
chat: ChatWithMessages;
|
|
|
|
|
14 |
}
|
15 |
|
16 |
+
const ChatClient: React.FC<ChatClientProps> = ({ chat }) => {
|
17 |
const { mediaUrl, id } = chat;
|
18 |
const { messages, append, reload, stop, isLoading, input, setInput } =
|
19 |
useVisionAgent(chat);
|
|
|
22 |
useScrollAnchor();
|
23 |
|
24 |
return (
|
25 |
+
<div
|
26 |
+
className="h-full my-8 overflow-auto mx-auto max-w-5xl border rounded-lg relative"
|
27 |
+
ref={scrollRef}
|
28 |
+
>
|
29 |
+
<div className="overflow-auto h-full pt-6 px-6" ref={messagesRef}>
|
30 |
+
{messages
|
31 |
+
// .filter(message => message.role !== 'system')
|
32 |
+
.map((message, index) => (
|
33 |
+
<ChatMessage
|
34 |
+
key={index}
|
35 |
+
message={message}
|
36 |
+
isLoading={isLoading && index === messages.length - 1}
|
37 |
+
/>
|
38 |
+
))}
|
39 |
+
<div className="h-px w-full" ref={visibilityRef} />
|
40 |
</div>
|
41 |
+
<div className="sticky bottom-3 w-full">
|
42 |
+
<Composer
|
43 |
+
id={id}
|
44 |
+
mediaUrl={mediaUrl}
|
45 |
+
isLoading={isLoading}
|
46 |
+
stop={stop}
|
47 |
+
append={append}
|
48 |
+
reload={reload}
|
49 |
+
messages={messages}
|
50 |
+
input={input}
|
51 |
+
setInput={setInput}
|
52 |
+
/>
|
53 |
+
</div>
|
54 |
+
</div>
|
|
|
|
|
|
|
|
|
55 |
);
|
56 |
+
};
|
57 |
+
|
58 |
+
export default ChatClient;
|
components/chat/ChatServer.tsx
CHANGED
@@ -1,4 +1,4 @@
|
|
1 |
-
import
|
2 |
import { auth } from '@/auth';
|
3 |
import { dbGetChat } from '@/lib/db/functions';
|
4 |
import { redirect } from 'next/navigation';
|
@@ -15,6 +15,5 @@ export default async function ChatServer({ id }: ChatServerProps) {
|
|
15 |
revalidatePath('/');
|
16 |
redirect('/');
|
17 |
}
|
18 |
-
|
19 |
-
return <Chat chat={chat} session={session} />;
|
20 |
}
|
|
|
1 |
+
import ChatClient from './ChatClient';
|
2 |
import { auth } from '@/auth';
|
3 |
import { dbGetChat } from '@/lib/db/functions';
|
4 |
import { redirect } from 'next/navigation';
|
|
|
15 |
revalidatePath('/');
|
16 |
redirect('/');
|
17 |
}
|
18 |
+
return <ChatClient chat={chat} />;
|
|
|
19 |
}
|
components/chat/Composer.tsx
CHANGED
@@ -16,12 +16,14 @@ import {
|
|
16 |
import {
|
17 |
IconArrowDown,
|
18 |
IconArrowElbow,
|
|
|
19 |
IconRefresh,
|
20 |
IconStop,
|
21 |
} from '@/components/ui/Icons';
|
22 |
import { cn } from '@/lib/utils';
|
23 |
import { generateInputImageMarkdown } from '@/lib/messageUtils';
|
24 |
import { Switch } from '../ui/Switch';
|
|
|
25 |
|
26 |
export interface ComposerProps
|
27 |
extends Pick<
|
@@ -31,24 +33,17 @@ export interface ComposerProps
|
|
31 |
id?: string;
|
32 |
title?: string;
|
33 |
messages: MessageBase[];
|
34 |
-
|
35 |
-
isAtBottom: boolean;
|
36 |
-
scrollToBottom: () => void;
|
37 |
}
|
38 |
|
39 |
export function Composer({
|
40 |
id,
|
41 |
-
title,
|
42 |
isLoading,
|
43 |
-
stop,
|
44 |
append,
|
45 |
-
reload,
|
46 |
input,
|
47 |
setInput,
|
48 |
-
|
49 |
-
isAtBottom,
|
50 |
-
scrollToBottom,
|
51 |
-
url,
|
52 |
}: ComposerProps) {
|
53 |
const { formRef, onKeyDown } = useEnterSubmit();
|
54 |
const inputRef = React.useRef<HTMLTextAreaElement>(null);
|
@@ -58,92 +53,62 @@ export function Composer({
|
|
58 |
}
|
59 |
}, []);
|
60 |
|
|
|
61 |
return (
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
}
|
71 |
-
setInput('');
|
72 |
-
await append({
|
73 |
-
id,
|
74 |
-
content:
|
75 |
-
input + (url ? '\n\n' + generateInputImageMarkdown(url) : ''),
|
76 |
-
role: 'user',
|
77 |
-
});
|
78 |
-
scrollToBottom();
|
79 |
-
}}
|
80 |
-
ref={formRef}
|
81 |
-
className="h-full"
|
82 |
-
>
|
83 |
-
<div className="relative flex px-8 pl-2 overflow-hidden size-full bg-background sm:rounded-md sm:border sm:px-12 sm:pl-2 items-start">
|
84 |
-
{url && (
|
85 |
-
<div className="w-1/5 p-2 h-full flex items-center justify-center relative">
|
86 |
-
<Tooltip>
|
87 |
-
<TooltipTrigger asChild>
|
88 |
-
<Img
|
89 |
-
src={url}
|
90 |
-
className="cursor-zoom-in"
|
91 |
-
alt="preview-image"
|
92 |
-
/>
|
93 |
-
</TooltipTrigger>
|
94 |
-
<TooltipContent>
|
95 |
-
<Img
|
96 |
-
src={url}
|
97 |
-
className="m-2"
|
98 |
-
quality={100}
|
99 |
-
width={500}
|
100 |
-
alt="zoomed-in-image"
|
101 |
-
/>
|
102 |
-
</TooltipContent>
|
103 |
-
</Tooltip>
|
104 |
</div>
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
|
|
|
|
|
|
147 |
{isLoading ? (
|
148 |
<Tooltip>
|
149 |
<TooltipTrigger asChild>
|
@@ -175,26 +140,39 @@ export function Composer({
|
|
175 |
</Tooltip>
|
176 |
)
|
177 |
)}
|
178 |
-
</div>
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
197 |
</div>
|
198 |
-
// </div>
|
199 |
);
|
200 |
}
|
|
|
16 |
import {
|
17 |
IconArrowDown,
|
18 |
IconArrowElbow,
|
19 |
+
IconImage,
|
20 |
IconRefresh,
|
21 |
IconStop,
|
22 |
} from '@/components/ui/Icons';
|
23 |
import { cn } from '@/lib/utils';
|
24 |
import { generateInputImageMarkdown } from '@/lib/messageUtils';
|
25 |
import { Switch } from '../ui/Switch';
|
26 |
+
import Chip from '../ui/Chip';
|
27 |
|
28 |
export interface ComposerProps
|
29 |
extends Pick<
|
|
|
33 |
id?: string;
|
34 |
title?: string;
|
35 |
messages: MessageBase[];
|
36 |
+
mediaUrl?: string;
|
|
|
|
|
37 |
}
|
38 |
|
39 |
export function Composer({
|
40 |
id,
|
|
|
41 |
isLoading,
|
|
|
42 |
append,
|
|
|
43 |
input,
|
44 |
setInput,
|
45 |
+
mediaUrl,
|
46 |
+
// isAtBottom,
|
|
|
|
|
47 |
}: ComposerProps) {
|
48 |
const { formRef, onKeyDown } = useEnterSubmit();
|
49 |
const inputRef = React.useRef<HTMLTextAreaElement>(null);
|
|
|
53 |
}
|
54 |
}, []);
|
55 |
|
56 |
+
const mediaName = mediaUrl?.split('/').pop();
|
57 |
return (
|
58 |
+
<div className="size-full mx-auto max-w-2xl px-6 py-3 space-y-4 bg-zinc-700 rounded-xl relative shadow-lg shadow-zinc-800/40">
|
59 |
+
{mediaUrl && (
|
60 |
+
<Tooltip>
|
61 |
+
<TooltipTrigger>
|
62 |
+
<Chip>
|
63 |
+
<div className="flex flex-row items-center">
|
64 |
+
<IconImage className="size-3 mr-1" />
|
65 |
+
<p>{mediaName ?? 'media(0)'}</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
</div>
|
67 |
+
</Chip>
|
68 |
+
</TooltipTrigger>
|
69 |
+
<TooltipContent sideOffset={20}>
|
70 |
+
<Img
|
71 |
+
src={mediaUrl}
|
72 |
+
className="m-1"
|
73 |
+
quality={100}
|
74 |
+
alt="zoomed-in-image"
|
75 |
+
/>
|
76 |
+
</TooltipContent>
|
77 |
+
</Tooltip>
|
78 |
+
)}
|
79 |
+
<form
|
80 |
+
onSubmit={async e => {
|
81 |
+
e.preventDefault();
|
82 |
+
if (!input?.trim()) {
|
83 |
+
return;
|
84 |
+
}
|
85 |
+
setInput('');
|
86 |
+
await append({
|
87 |
+
id,
|
88 |
+
content:
|
89 |
+
input +
|
90 |
+
(mediaUrl ? '\n\n' + generateInputImageMarkdown(mediaUrl) : ''),
|
91 |
+
role: 'user',
|
92 |
+
});
|
93 |
+
}}
|
94 |
+
ref={formRef}
|
95 |
+
className="h-full"
|
96 |
+
>
|
97 |
+
{/* <div className="border-gray-500 flex overflow-hidden size-full flex flex-row items-center"> */}
|
98 |
+
<Textarea
|
99 |
+
ref={inputRef}
|
100 |
+
tabIndex={0}
|
101 |
+
onKeyDown={onKeyDown}
|
102 |
+
rows={1}
|
103 |
+
value={input}
|
104 |
+
disabled={isLoading}
|
105 |
+
onChange={e => setInput(e.target.value)}
|
106 |
+
placeholder={isLoading ? '🤖 ✨ ...' : 'Message Vision Agent'}
|
107 |
+
spellCheck={false}
|
108 |
+
className="grow resize-none bg-transparent focus-within:outline-none text-sm"
|
109 |
+
/>
|
110 |
+
{/* Stop / Regenerate Icon */}
|
111 |
+
{/* <div className="absolute bottom-14 right-4">
|
112 |
{isLoading ? (
|
113 |
<Tooltip>
|
114 |
<TooltipTrigger asChild>
|
|
|
140 |
</Tooltip>
|
141 |
)
|
142 |
)}
|
143 |
+
</div> */}
|
144 |
+
{/* </div> */}
|
145 |
+
{/* Submit Icon */}
|
146 |
+
<Tooltip>
|
147 |
+
<TooltipTrigger asChild>
|
148 |
+
<Button
|
149 |
+
type="submit"
|
150 |
+
size="icon"
|
151 |
+
className="size-6 absolute bottom-3 right-3"
|
152 |
+
disabled={isLoading || input === ''}
|
153 |
+
>
|
154 |
+
<IconArrowElbow className="size-3" />
|
155 |
+
</Button>
|
156 |
+
</TooltipTrigger>
|
157 |
+
<TooltipContent>Send message</TooltipContent>
|
158 |
+
</Tooltip>
|
159 |
+
</form>
|
160 |
+
{/* Scroll to bottom Icon */}
|
161 |
+
{/* <Tooltip>
|
162 |
+
<TooltipTrigger asChild>
|
163 |
+
<Button
|
164 |
+
size="icon"
|
165 |
+
className={cn(
|
166 |
+
'absolute top-1 right-3 transition-opacity duration-300 size-6',
|
167 |
+
isAtBottom ? 'opacity-0' : 'opacity-100',
|
168 |
+
)}
|
169 |
+
onClick={() => scrollToBottom()}
|
170 |
+
>
|
171 |
+
<IconArrowDown className="size-3" />
|
172 |
+
</Button>
|
173 |
+
</TooltipTrigger>
|
174 |
+
<TooltipContent>Scroll to bottom</TooltipContent>
|
175 |
+
</Tooltip> */}
|
176 |
</div>
|
|
|
177 |
);
|
178 |
}
|
components/ui/Chip.tsx
CHANGED
@@ -1,28 +1,29 @@
|
|
1 |
import { cn } from '@/lib/utils';
|
|
|
2 |
|
3 |
-
|
4 |
label?: string;
|
5 |
-
value
|
6 |
color?: 'gray' | 'blue' | 'yellow' | 'purple';
|
7 |
className?: string;
|
8 |
-
}
|
9 |
|
10 |
const Chip: React.FC<ChipProps> = ({
|
11 |
-
label,
|
12 |
value,
|
13 |
className,
|
14 |
color = 'gray',
|
|
|
15 |
}) => {
|
16 |
return (
|
17 |
<div
|
18 |
className={cn(
|
19 |
-
'inline-flex items-center
|
20 |
`bg-${color}-100 text-${color}-500`,
|
21 |
className,
|
22 |
)}
|
23 |
>
|
24 |
-
{label && <span className="font-medium">{label} :</span>}
|
25 |
<span>{value}</span>
|
|
|
26 |
</div>
|
27 |
);
|
28 |
};
|
|
|
1 |
import { cn } from '@/lib/utils';
|
2 |
+
import React from 'react';
|
3 |
|
4 |
+
type ChipProps = {
|
5 |
label?: string;
|
6 |
+
value?: string;
|
7 |
color?: 'gray' | 'blue' | 'yellow' | 'purple';
|
8 |
className?: string;
|
9 |
+
} & React.ComponentProps<'div'>;
|
10 |
|
11 |
const Chip: React.FC<ChipProps> = ({
|
|
|
12 |
value,
|
13 |
className,
|
14 |
color = 'gray',
|
15 |
+
children,
|
16 |
}) => {
|
17 |
return (
|
18 |
<div
|
19 |
className={cn(
|
20 |
+
'inline-flex items-center rounded-full text-xs mr-2 bg-gray-100 text-gray-500 px-2 py-0.5',
|
21 |
`bg-${color}-100 text-${color}-500`,
|
22 |
className,
|
23 |
)}
|
24 |
>
|
|
|
25 |
<span>{value}</span>
|
26 |
+
{children}
|
27 |
</div>
|
28 |
);
|
29 |
};
|
components/ui/DropdownMenu.tsx
CHANGED
@@ -51,7 +51,7 @@ const DropdownMenuSubContent = React.forwardRef<
|
|
51 |
<DropdownMenuPrimitive.SubContent
|
52 |
ref={ref}
|
53 |
className={cn(
|
54 |
-
'z-50 min-w-
|
55 |
className,
|
56 |
)}
|
57 |
{...props}
|
|
|
51 |
<DropdownMenuPrimitive.SubContent
|
52 |
ref={ref}
|
53 |
className={cn(
|
54 |
+
'z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
55 |
className,
|
56 |
)}
|
57 |
{...props}
|
components/ui/Icons.tsx
CHANGED
@@ -4,90 +4,6 @@ import * as React from 'react';
|
|
4 |
|
5 |
import { cn } from '@/lib/utils';
|
6 |
|
7 |
-
function IconNextChat({
|
8 |
-
className,
|
9 |
-
inverted,
|
10 |
-
...props
|
11 |
-
}: React.ComponentProps<'svg'> & { inverted?: boolean }) {
|
12 |
-
const id = React.useId();
|
13 |
-
|
14 |
-
return (
|
15 |
-
<svg
|
16 |
-
viewBox="0 0 17 17"
|
17 |
-
fill="none"
|
18 |
-
xmlns="http://www.w3.org/2000/svg"
|
19 |
-
className={cn('size-4', className)}
|
20 |
-
{...props}
|
21 |
-
>
|
22 |
-
<defs>
|
23 |
-
<linearGradient
|
24 |
-
id={`gradient-${id}-1`}
|
25 |
-
x1="10.6889"
|
26 |
-
y1="10.3556"
|
27 |
-
x2="13.8445"
|
28 |
-
y2="14.2667"
|
29 |
-
gradientUnits="userSpaceOnUse"
|
30 |
-
>
|
31 |
-
<stop stopColor={inverted ? 'white' : 'black'} />
|
32 |
-
<stop
|
33 |
-
offset={1}
|
34 |
-
stopColor={inverted ? 'white' : 'black'}
|
35 |
-
stopOpacity={0}
|
36 |
-
/>
|
37 |
-
</linearGradient>
|
38 |
-
<linearGradient
|
39 |
-
id={`gradient-${id}-2`}
|
40 |
-
x1="11.7555"
|
41 |
-
y1="4.8"
|
42 |
-
x2="11.7376"
|
43 |
-
y2="9.50002"
|
44 |
-
gradientUnits="userSpaceOnUse"
|
45 |
-
>
|
46 |
-
<stop stopColor={inverted ? 'white' : 'black'} />
|
47 |
-
<stop
|
48 |
-
offset={1}
|
49 |
-
stopColor={inverted ? 'white' : 'black'}
|
50 |
-
stopOpacity={0}
|
51 |
-
/>
|
52 |
-
</linearGradient>
|
53 |
-
</defs>
|
54 |
-
<path
|
55 |
-
d="M1 16L2.58314 11.2506C1.83084 9.74642 1.63835 8.02363 2.04013 6.39052C2.4419 4.75741 3.41171 3.32057 4.776 2.33712C6.1403 1.35367 7.81003 0.887808 9.4864 1.02289C11.1628 1.15798 12.7364 1.8852 13.9256 3.07442C15.1148 4.26363 15.842 5.83723 15.9771 7.5136C16.1122 9.18997 15.6463 10.8597 14.6629 12.224C13.6794 13.5883 12.2426 14.5581 10.6095 14.9599C8.97637 15.3616 7.25358 15.1692 5.74942 14.4169L1 16Z"
|
56 |
-
fill={inverted ? 'black' : 'white'}
|
57 |
-
stroke={inverted ? 'black' : 'white'}
|
58 |
-
strokeWidth={2}
|
59 |
-
strokeLinecap="round"
|
60 |
-
strokeLinejoin="round"
|
61 |
-
/>
|
62 |
-
<mask
|
63 |
-
id="mask0_91_2047"
|
64 |
-
style={{ maskType: 'alpha' }}
|
65 |
-
maskUnits="userSpaceOnUse"
|
66 |
-
x={1}
|
67 |
-
y={0}
|
68 |
-
width={16}
|
69 |
-
height={16}
|
70 |
-
>
|
71 |
-
<circle cx={9} cy={8} r={8} fill={inverted ? 'black' : 'white'} />
|
72 |
-
</mask>
|
73 |
-
<g mask="url(#mask0_91_2047)">
|
74 |
-
<circle cx={9} cy={8} r={8} fill={inverted ? 'black' : 'white'} />
|
75 |
-
<path
|
76 |
-
d="M14.2896 14.0018L7.146 4.8H5.80005V11.1973H6.87681V6.16743L13.4444 14.6529C13.7407 14.4545 14.0231 14.2369 14.2896 14.0018Z"
|
77 |
-
fill={`url(#gradient-${id}-1)`}
|
78 |
-
/>
|
79 |
-
<rect
|
80 |
-
x="11.2222"
|
81 |
-
y="4.8"
|
82 |
-
width="1.06667"
|
83 |
-
height="6.4"
|
84 |
-
fill={`url(#gradient-${id}-2)`}
|
85 |
-
/>
|
86 |
-
</g>
|
87 |
-
</svg>
|
88 |
-
);
|
89 |
-
}
|
90 |
-
|
91 |
function IconLandingAI({ className, ...props }: React.ComponentProps<'svg'>) {
|
92 |
return (
|
93 |
<svg
|
@@ -146,23 +62,6 @@ function IconLandingAI({ className, ...props }: React.ComponentProps<'svg'>) {
|
|
146 |
);
|
147 |
}
|
148 |
|
149 |
-
function IconVercel({ className, ...props }: React.ComponentProps<'svg'>) {
|
150 |
-
return (
|
151 |
-
<svg
|
152 |
-
aria-label="Vercel logomark"
|
153 |
-
role="img"
|
154 |
-
viewBox="0 0 74 64"
|
155 |
-
className={cn('size-4', className)}
|
156 |
-
{...props}
|
157 |
-
>
|
158 |
-
<path
|
159 |
-
d="M37.5896 0.25L74.5396 64.25H0.639648L37.5896 0.25Z"
|
160 |
-
fill="currentColor"
|
161 |
-
></path>
|
162 |
-
</svg>
|
163 |
-
);
|
164 |
-
}
|
165 |
-
|
166 |
function IconGitHub({ className, ...props }: React.ComponentProps<'svg'>) {
|
167 |
return (
|
168 |
<svg
|
@@ -615,11 +514,30 @@ function IconExclamationTriangle({
|
|
615 |
);
|
616 |
}
|
617 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
618 |
export {
|
619 |
IconEdit,
|
620 |
-
IconNextChat,
|
621 |
IconLandingAI,
|
622 |
-
IconVercel,
|
623 |
IconGitHub,
|
624 |
IconSeparator,
|
625 |
IconArrowDown,
|
@@ -647,4 +565,5 @@ export {
|
|
647 |
IconLoading,
|
648 |
IconDiscord,
|
649 |
IconExclamationTriangle,
|
|
|
650 |
};
|
|
|
4 |
|
5 |
import { cn } from '@/lib/utils';
|
6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
function IconLandingAI({ className, ...props }: React.ComponentProps<'svg'>) {
|
8 |
return (
|
9 |
<svg
|
|
|
62 |
);
|
63 |
}
|
64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
65 |
function IconGitHub({ className, ...props }: React.ComponentProps<'svg'>) {
|
66 |
return (
|
67 |
<svg
|
|
|
514 |
);
|
515 |
}
|
516 |
|
517 |
+
function IconImage({ className, ...props }: React.ComponentProps<'svg'>) {
|
518 |
+
return (
|
519 |
+
<svg
|
520 |
+
data-testid="geist-icon"
|
521 |
+
height="16"
|
522 |
+
stroke-linejoin="round"
|
523 |
+
viewBox="0 0 16 16"
|
524 |
+
width="16"
|
525 |
+
className={cn('size-4', className)}
|
526 |
+
{...props}
|
527 |
+
>
|
528 |
+
<path
|
529 |
+
fill-rule="evenodd"
|
530 |
+
clip-rule="evenodd"
|
531 |
+
d="M14.5 2.5H1.5V9.18933L2.96966 7.71967L3.18933 7.5H3.49999H6.63001H6.93933L6.96966 7.46967L10.4697 3.96967L11.5303 3.96967L14.5 6.93934V2.5ZM8.00066 8.55999L9.53034 10.0897L10.0607 10.62L9.00001 11.6807L8.46968 11.1503L6.31935 9H3.81065L1.53032 11.2803L1.5 11.3106V12.5C1.5 13.0523 1.94772 13.5 2.5 13.5H13.5C14.0523 13.5 14.5 13.0523 14.5 12.5V9.06066L11 5.56066L8.03032 8.53033L8.00066 8.55999ZM4.05312e-06 10.8107V12.5C4.05312e-06 13.8807 1.11929 15 2.5 15H13.5C14.8807 15 16 13.8807 16 12.5V9.56066L16.5607 9L16.0303 8.46967L16 8.43934V2.5V1H14.5H1.5H4.05312e-06V2.5V10.6893L-0.0606689 10.75L4.05312e-06 10.8107Z"
|
532 |
+
fill="currentColor"
|
533 |
+
></path>
|
534 |
+
</svg>
|
535 |
+
);
|
536 |
+
}
|
537 |
+
|
538 |
export {
|
539 |
IconEdit,
|
|
|
540 |
IconLandingAI,
|
|
|
541 |
IconGitHub,
|
542 |
IconSeparator,
|
543 |
IconArrowDown,
|
|
|
565 |
IconLoading,
|
566 |
IconDiscord,
|
567 |
IconExclamationTriangle,
|
568 |
+
IconImage,
|
569 |
};
|
components/ui/Tooltip.tsx
CHANGED
@@ -19,7 +19,7 @@ const TooltipContent = React.forwardRef<
|
|
19 |
ref={ref}
|
20 |
sideOffset={sideOffset}
|
21 |
className={cn(
|
22 |
-
'z-50 overflow-hidden rounded-md bg-
|
23 |
className,
|
24 |
)}
|
25 |
{...props}
|
|
|
19 |
ref={ref}
|
20 |
sideOffset={sideOffset}
|
21 |
className={cn(
|
22 |
+
'z-50 overflow-hidden rounded-md bg-muted px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
23 |
className,
|
24 |
)}
|
25 |
{...props}
|