Spaces:
Running
Running
Update components/editor/ask-ai/index.tsx
Browse files- components/editor/ask-ai/index.tsx +139 -13
components/editor/ask-ai/index.tsx
CHANGED
|
@@ -11,6 +11,7 @@ import ProModal from "@/components/pro-modal";
|
|
| 11 |
import { Button } from "@/components/ui/button";
|
| 12 |
import { MODELS } from "@/lib/providers";
|
| 13 |
import { HtmlHistory, Page, Project } from "@/types";
|
|
|
|
| 14 |
import { Settings } from "@/components/editor/ask-ai/settings";
|
| 15 |
import { LoginModal } from "@/components/login-modal";
|
| 16 |
import { ReImagine } from "@/components/editor/ask-ai/re-imagine";
|
|
@@ -34,7 +35,6 @@ export function AskAI({
|
|
| 34 |
onScrollToBottom,
|
| 35 |
isAiWorking,
|
| 36 |
setisAiWorking,
|
| 37 |
-
setAiThinking, // Add this prop
|
| 38 |
isEditableModeEnabled = false,
|
| 39 |
pages,
|
| 40 |
htmlHistory,
|
|
@@ -58,7 +58,6 @@ export function AskAI({
|
|
| 58 |
onNewPrompt: (prompt: string) => void;
|
| 59 |
htmlHistory?: HtmlHistory[];
|
| 60 |
setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
|
| 61 |
-
setAiThinking: React.Dispatch<React.SetStateAction<string | null>>; // Add type for the prop
|
| 62 |
isNew?: boolean;
|
| 63 |
onSuccess: (page: Page[], p: string, n?: number[][]) => void;
|
| 64 |
isEditableModeEnabled: boolean;
|
|
@@ -81,6 +80,7 @@ export function AskAI({
|
|
| 81 |
const [openProModal, setOpenProModal] = useState(false);
|
| 82 |
const [openThink, setOpenThink] = useState(false);
|
| 83 |
const [isThinking, setIsThinking] = useState(true);
|
|
|
|
| 84 |
const [isFollowUp, setIsFollowUp] = useState(true);
|
| 85 |
const [isUploading, setIsUploading] = useState(false);
|
| 86 |
const [files, setFiles] = useState<string[]>(images ?? []);
|
|
@@ -101,7 +101,6 @@ export function AskAI({
|
|
| 101 |
pages,
|
| 102 |
isAiWorking,
|
| 103 |
setisAiWorking,
|
| 104 |
-
setAiThinking, // Pass setAiThinking to the hook
|
| 105 |
});
|
| 106 |
|
| 107 |
const selectedModel = useMemo(() => {
|
|
@@ -112,9 +111,8 @@ export function AskAI({
|
|
| 112 |
if (isAiWorking) return;
|
| 113 |
if (!redesignMarkdown && !prompt.trim()) return;
|
| 114 |
|
| 115 |
-
setAiThinking(""); // Reset thinking text on new request
|
| 116 |
-
|
| 117 |
if (isFollowUp && !redesignMarkdown && !isSameHtml) {
|
|
|
|
| 118 |
const selectedElementHtml = selectedElement
|
| 119 |
? selectedElement.outerHTML
|
| 120 |
: "";
|
|
@@ -160,7 +158,11 @@ export function AskAI({
|
|
| 160 |
prompt,
|
| 161 |
model,
|
| 162 |
provider,
|
| 163 |
-
redesignMarkdown
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
);
|
| 165 |
|
| 166 |
if (result?.error) {
|
|
@@ -177,6 +179,12 @@ export function AskAI({
|
|
| 177 |
}
|
| 178 |
};
|
| 179 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
const handleError = (error: string, message?: string) => {
|
| 181 |
switch (error) {
|
| 182 |
case "login_required":
|
|
@@ -200,6 +208,12 @@ export function AskAI({
|
|
| 200 |
}
|
| 201 |
};
|
| 202 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
useUpdateEffect(() => {
|
| 204 |
if (!isThinking) {
|
| 205 |
setOpenThink(false);
|
|
@@ -213,6 +227,43 @@ export function AskAI({
|
|
| 213 |
return (
|
| 214 |
<div className="px-3">
|
| 215 |
<div className="relative bg-neutral-800 border border-neutral-700 rounded-2xl ring-[4px] focus-within:ring-neutral-500/30 focus-within:border-neutral-600 ring-transparent z-10 w-full group">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
<SelectedFiles
|
| 217 |
files={selectedFiles}
|
| 218 |
isAiWorking={isAiWorking}
|
|
@@ -235,7 +286,68 @@ export function AskAI({
|
|
| 235 |
<div className="flex items-center justify-start gap-2">
|
| 236 |
<Loading overlay={false} className="!size-4 opacity-50" />
|
| 237 |
<p className="text-neutral-400 text-sm">
|
| 238 |
-
{isUploading ?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
</p>
|
| 240 |
</div>
|
| 241 |
{isAiWorking && (
|
|
@@ -253,20 +365,21 @@ export function AskAI({
|
|
| 253 |
disabled={isAiWorking}
|
| 254 |
className={classNames(
|
| 255 |
"w-full bg-transparent text-sm outline-none text-white placeholder:text-neutral-400 p-4 resize-none",
|
| 256 |
-
{
|
|
|
|
|
|
|
| 257 |
)}
|
| 258 |
placeholder={
|
| 259 |
selectedElement
|
| 260 |
-
? `Ask about ${selectedElement.tagName.toLowerCase()}...`
|
| 261 |
: isFollowUp && (!isSameHtml || pages?.length > 1)
|
| 262 |
-
? "Ask for edits
|
| 263 |
-
: "Ask anything..."
|
| 264 |
}
|
| 265 |
value={prompt}
|
| 266 |
onChange={(e) => setPrompt(e.target.value)}
|
| 267 |
onKeyDown={(e) => {
|
| 268 |
if (e.key === "Enter" && !e.shiftKey) {
|
| 269 |
-
e.preventDefault();
|
| 270 |
callAi();
|
| 271 |
}
|
| 272 |
}}
|
|
@@ -313,10 +426,12 @@ export function AskAI({
|
|
| 313 |
align="start"
|
| 314 |
className="bg-neutral-950 text-xs text-neutral-200 py-1 px-2 rounded-md -translate-y-0.5"
|
| 315 |
>
|
| 316 |
-
Select an element to edit it
|
|
|
|
| 317 |
</TooltipContent>
|
| 318 |
</Tooltip>
|
| 319 |
)}
|
|
|
|
| 320 |
</div>
|
| 321 |
<div className="flex items-center justify-end gap-2">
|
| 322 |
<Settings
|
|
@@ -344,6 +459,16 @@ export function AskAI({
|
|
| 344 |
open={openProModal}
|
| 345 |
onClose={() => setOpenProModal(false)}
|
| 346 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
{!isSameHtml && (
|
| 348 |
<div className="absolute top-0 right-0 -translate-y-[calc(100%+8px)] select-none text-xs text-neutral-400 flex items-center justify-center gap-2 bg-neutral-800 border border-neutral-700 rounded-md p-1 pr-2.5">
|
| 349 |
<label
|
|
@@ -368,6 +493,7 @@ export function AskAI({
|
|
| 368 |
</div>
|
| 369 |
<audio ref={hookAudio} id="audio" className="hidden">
|
| 370 |
<source src="/success.mp3" type="audio/mpeg" />
|
|
|
|
| 371 |
</audio>
|
| 372 |
</div>
|
| 373 |
);
|
|
|
|
| 11 |
import { Button } from "@/components/ui/button";
|
| 12 |
import { MODELS } from "@/lib/providers";
|
| 13 |
import { HtmlHistory, Page, Project } from "@/types";
|
| 14 |
+
// import { InviteFriends } from "@/components/invite-friends";
|
| 15 |
import { Settings } from "@/components/editor/ask-ai/settings";
|
| 16 |
import { LoginModal } from "@/components/login-modal";
|
| 17 |
import { ReImagine } from "@/components/editor/ask-ai/re-imagine";
|
|
|
|
| 35 |
onScrollToBottom,
|
| 36 |
isAiWorking,
|
| 37 |
setisAiWorking,
|
|
|
|
| 38 |
isEditableModeEnabled = false,
|
| 39 |
pages,
|
| 40 |
htmlHistory,
|
|
|
|
| 58 |
onNewPrompt: (prompt: string) => void;
|
| 59 |
htmlHistory?: HtmlHistory[];
|
| 60 |
setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
|
|
|
|
| 61 |
isNew?: boolean;
|
| 62 |
onSuccess: (page: Page[], p: string, n?: number[][]) => void;
|
| 63 |
isEditableModeEnabled: boolean;
|
|
|
|
| 80 |
const [openProModal, setOpenProModal] = useState(false);
|
| 81 |
const [openThink, setOpenThink] = useState(false);
|
| 82 |
const [isThinking, setIsThinking] = useState(true);
|
| 83 |
+
const [think, setThink] = useState("");
|
| 84 |
const [isFollowUp, setIsFollowUp] = useState(true);
|
| 85 |
const [isUploading, setIsUploading] = useState(false);
|
| 86 |
const [files, setFiles] = useState<string[]>(images ?? []);
|
|
|
|
| 101 |
pages,
|
| 102 |
isAiWorking,
|
| 103 |
setisAiWorking,
|
|
|
|
| 104 |
});
|
| 105 |
|
| 106 |
const selectedModel = useMemo(() => {
|
|
|
|
| 111 |
if (isAiWorking) return;
|
| 112 |
if (!redesignMarkdown && !prompt.trim()) return;
|
| 113 |
|
|
|
|
|
|
|
| 114 |
if (isFollowUp && !redesignMarkdown && !isSameHtml) {
|
| 115 |
+
// Use follow-up function for existing projects
|
| 116 |
const selectedElementHtml = selectedElement
|
| 117 |
? selectedElement.outerHTML
|
| 118 |
: "";
|
|
|
|
| 158 |
prompt,
|
| 159 |
model,
|
| 160 |
provider,
|
| 161 |
+
redesignMarkdown,
|
| 162 |
+
handleThink,
|
| 163 |
+
() => {
|
| 164 |
+
setIsThinking(false);
|
| 165 |
+
}
|
| 166 |
);
|
| 167 |
|
| 168 |
if (result?.error) {
|
|
|
|
| 179 |
}
|
| 180 |
};
|
| 181 |
|
| 182 |
+
const handleThink = (think: string) => {
|
| 183 |
+
setThink(think);
|
| 184 |
+
setIsThinking(true);
|
| 185 |
+
setOpenThink(true);
|
| 186 |
+
};
|
| 187 |
+
|
| 188 |
const handleError = (error: string, message?: string) => {
|
| 189 |
switch (error) {
|
| 190 |
case "login_required":
|
|
|
|
| 208 |
}
|
| 209 |
};
|
| 210 |
|
| 211 |
+
useUpdateEffect(() => {
|
| 212 |
+
if (refThink.current) {
|
| 213 |
+
refThink.current.scrollTop = refThink.current.scrollHeight;
|
| 214 |
+
}
|
| 215 |
+
}, [think]);
|
| 216 |
+
|
| 217 |
useUpdateEffect(() => {
|
| 218 |
if (!isThinking) {
|
| 219 |
setOpenThink(false);
|
|
|
|
| 227 |
return (
|
| 228 |
<div className="px-3">
|
| 229 |
<div className="relative bg-neutral-800 border border-neutral-700 rounded-2xl ring-[4px] focus-within:ring-neutral-500/30 focus-within:border-neutral-600 ring-transparent z-10 w-full group">
|
| 230 |
+
{think && (
|
| 231 |
+
<div className="w-full border-b border-neutral-700 relative overflow-hidden">
|
| 232 |
+
<header
|
| 233 |
+
className="flex items-center justify-between px-5 py-2.5 group hover:bg-neutral-600/20 transition-colors duration-200 cursor-pointer"
|
| 234 |
+
onClick={() => {
|
| 235 |
+
setOpenThink(!openThink);
|
| 236 |
+
}}
|
| 237 |
+
>
|
| 238 |
+
<p className="text-sm font-medium text-neutral-300 group-hover:text-neutral-200 transition-colors duration-200">
|
| 239 |
+
{isThinking ? "DeepSite is thinking..." : "DeepSite's plan"}
|
| 240 |
+
</p>
|
| 241 |
+
<ChevronDown
|
| 242 |
+
className={classNames(
|
| 243 |
+
"size-4 text-neutral-400 group-hover:text-neutral-300 transition-all duration-200",
|
| 244 |
+
{
|
| 245 |
+
"rotate-180": openThink,
|
| 246 |
+
}
|
| 247 |
+
)}
|
| 248 |
+
/>
|
| 249 |
+
</header>
|
| 250 |
+
<main
|
| 251 |
+
ref={refThink}
|
| 252 |
+
className={classNames(
|
| 253 |
+
"overflow-y-auto transition-all duration-200 ease-in-out",
|
| 254 |
+
{
|
| 255 |
+
"max-h-[0px]": !openThink,
|
| 256 |
+
"min-h-[250px] max-h-[250px] border-t border-neutral-700":
|
| 257 |
+
openThink,
|
| 258 |
+
}
|
| 259 |
+
)}
|
| 260 |
+
>
|
| 261 |
+
<p className="text-[13px] text-neutral-400 whitespace-pre-line px-5 pb-4 pt-3">
|
| 262 |
+
{think}
|
| 263 |
+
</p>
|
| 264 |
+
</main>
|
| 265 |
+
</div>
|
| 266 |
+
)}
|
| 267 |
<SelectedFiles
|
| 268 |
files={selectedFiles}
|
| 269 |
isAiWorking={isAiWorking}
|
|
|
|
| 286 |
<div className="flex items-center justify-start gap-2">
|
| 287 |
<Loading overlay={false} className="!size-4 opacity-50" />
|
| 288 |
<p className="text-neutral-400 text-sm">
|
| 289 |
+
{isUploading ? (
|
| 290 |
+
"Uploading images..."
|
| 291 |
+
) : isAiWorking && !isSameHtml ? (
|
| 292 |
+
"AI is working..."
|
| 293 |
+
) : (
|
| 294 |
+
<span className="inline-flex">
|
| 295 |
+
{[
|
| 296 |
+
"D",
|
| 297 |
+
"e",
|
| 298 |
+
"e",
|
| 299 |
+
"p",
|
| 300 |
+
"S",
|
| 301 |
+
"i",
|
| 302 |
+
"t",
|
| 303 |
+
"e",
|
| 304 |
+
" ",
|
| 305 |
+
"i",
|
| 306 |
+
"s",
|
| 307 |
+
" ",
|
| 308 |
+
"T",
|
| 309 |
+
"h",
|
| 310 |
+
"i",
|
| 311 |
+
"n",
|
| 312 |
+
"k",
|
| 313 |
+
"i",
|
| 314 |
+
"n",
|
| 315 |
+
"g",
|
| 316 |
+
".",
|
| 317 |
+
".",
|
| 318 |
+
".",
|
| 319 |
+
" ",
|
| 320 |
+
"W",
|
| 321 |
+
"a",
|
| 322 |
+
"i",
|
| 323 |
+
"t",
|
| 324 |
+
" ",
|
| 325 |
+
"a",
|
| 326 |
+
" ",
|
| 327 |
+
"m",
|
| 328 |
+
"o",
|
| 329 |
+
"m",
|
| 330 |
+
"e",
|
| 331 |
+
"n",
|
| 332 |
+
"t",
|
| 333 |
+
".",
|
| 334 |
+
".",
|
| 335 |
+
".",
|
| 336 |
+
].map((char, index) => (
|
| 337 |
+
<span
|
| 338 |
+
key={index}
|
| 339 |
+
className="bg-gradient-to-r from-neutral-100 to-neutral-300 bg-clip-text text-transparent animate-pulse"
|
| 340 |
+
style={{
|
| 341 |
+
animationDelay: `${index * 0.1}s`,
|
| 342 |
+
animationDuration: "1.3s",
|
| 343 |
+
animationIterationCount: "infinite",
|
| 344 |
+
}}
|
| 345 |
+
>
|
| 346 |
+
{char === " " ? "\u00A0" : char}
|
| 347 |
+
</span>
|
| 348 |
+
))}
|
| 349 |
+
</span>
|
| 350 |
+
)}
|
| 351 |
</p>
|
| 352 |
</div>
|
| 353 |
{isAiWorking && (
|
|
|
|
| 365 |
disabled={isAiWorking}
|
| 366 |
className={classNames(
|
| 367 |
"w-full bg-transparent text-sm outline-none text-white placeholder:text-neutral-400 p-4 resize-none",
|
| 368 |
+
{
|
| 369 |
+
"!pt-2.5": selectedElement && !isAiWorking,
|
| 370 |
+
}
|
| 371 |
)}
|
| 372 |
placeholder={
|
| 373 |
selectedElement
|
| 374 |
+
? `Ask DeepSite about ${selectedElement.tagName.toLowerCase()}...`
|
| 375 |
: isFollowUp && (!isSameHtml || pages?.length > 1)
|
| 376 |
+
? "Ask DeepSite for edits"
|
| 377 |
+
: "Ask DeepSite anything..."
|
| 378 |
}
|
| 379 |
value={prompt}
|
| 380 |
onChange={(e) => setPrompt(e.target.value)}
|
| 381 |
onKeyDown={(e) => {
|
| 382 |
if (e.key === "Enter" && !e.shiftKey) {
|
|
|
|
| 383 |
callAi();
|
| 384 |
}
|
| 385 |
}}
|
|
|
|
| 426 |
align="start"
|
| 427 |
className="bg-neutral-950 text-xs text-neutral-200 py-1 px-2 rounded-md -translate-y-0.5"
|
| 428 |
>
|
| 429 |
+
Select an element on the page to ask DeepSite edit it
|
| 430 |
+
directly.
|
| 431 |
</TooltipContent>
|
| 432 |
</Tooltip>
|
| 433 |
)}
|
| 434 |
+
{/* <InviteFriends /> */}
|
| 435 |
</div>
|
| 436 |
<div className="flex items-center justify-end gap-2">
|
| 437 |
<Settings
|
|
|
|
| 459 |
open={openProModal}
|
| 460 |
onClose={() => setOpenProModal(false)}
|
| 461 |
/>
|
| 462 |
+
{pages.length === 1 && (
|
| 463 |
+
<div className="border border-sky-500/20 bg-sky-500/40 hover:bg-sky-600 transition-all duration-200 text-sky-500 pl-2 pr-4 py-1.5 text-xs rounded-full absolute top-0 -translate-y-[calc(100%+8px)] left-0 max-w-max flex items-center justify-start gap-2">
|
| 464 |
+
<span className="rounded-full text-[10px] font-semibold bg-white text-neutral-900 px-1.5 py-0.5">
|
| 465 |
+
NEW
|
| 466 |
+
</span>
|
| 467 |
+
<p className="text-sm text-neutral-100">
|
| 468 |
+
DeepSite can now create multiple pages at once. Try it!
|
| 469 |
+
</p>
|
| 470 |
+
</div>
|
| 471 |
+
)}
|
| 472 |
{!isSameHtml && (
|
| 473 |
<div className="absolute top-0 right-0 -translate-y-[calc(100%+8px)] select-none text-xs text-neutral-400 flex items-center justify-center gap-2 bg-neutral-800 border border-neutral-700 rounded-md p-1 pr-2.5">
|
| 474 |
<label
|
|
|
|
| 493 |
</div>
|
| 494 |
<audio ref={hookAudio} id="audio" className="hidden">
|
| 495 |
<source src="/success.mp3" type="audio/mpeg" />
|
| 496 |
+
Your browser does not support the audio element.
|
| 497 |
</audio>
|
| 498 |
</div>
|
| 499 |
);
|