enzostvs HF Staff commited on
Commit
c9b2910
·
1 Parent(s): 995e38d

wip target element + no follow up option

Browse files
app/api/ask-ai/route.ts CHANGED
@@ -223,7 +223,7 @@ export async function PUT(request: NextRequest) {
223
  const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
224
 
225
  const body = await request.json();
226
- const { prompt, html, previousPrompt, provider } = body;
227
 
228
  if (!prompt || !html) {
229
  return NextResponse.json(
@@ -294,7 +294,9 @@ export async function PUT(request: NextRequest) {
294
  },
295
  {
296
  role: "assistant",
297
- content: `The current code is: \n\`\`\`html\n${html}\n\`\`\``,
 
 
298
  },
299
  {
300
  role: "user",
 
223
  const userToken = request.cookies.get(MY_TOKEN_KEY())?.value;
224
 
225
  const body = await request.json();
226
+ const { prompt, html, previousPrompt, provider, selectedElementHtml } = body;
227
 
228
  if (!prompt || !html) {
229
  return NextResponse.json(
 
294
  },
295
  {
296
  role: "assistant",
297
+ content: selectedElementHtml
298
+ ? `Here is the selected HTML element:\n\n${selectedElementHtml}`
299
+ : `The current code is: \n\`\`\`html\n${html}\n\`\`\``,
300
  },
301
  {
302
  role: "user",
components/editor/ask-ai/index.tsx CHANGED
@@ -4,7 +4,7 @@ import { useState, useRef } from "react";
4
  import classNames from "classnames";
5
  import { toast } from "sonner";
6
  import { useLocalStorage, useUpdateEffect } from "react-use";
7
- import { ArrowUp, ChevronDown } from "lucide-react";
8
  import { FaStopCircle } from "react-icons/fa";
9
 
10
  import { defaultHTML } from "@/lib/consts";
@@ -12,11 +12,20 @@ import ProModal from "@/components/pro-modal";
12
  import { Button } from "@/components/ui/button";
13
  import { MODELS } from "@/lib/providers";
14
  import { HtmlHistory } from "@/types";
15
- import { InviteFriends } from "@/components/invite-friends";
16
  import { Settings } from "@/components/editor/ask-ai/settings";
17
  import { LoginModal } from "@/components/login-modal";
18
  import { ReImagine } from "@/components/editor/ask-ai/re-imagine";
19
  import Loading from "@/components/loading";
 
 
 
 
 
 
 
 
 
20
 
21
  export function AskAI({
22
  html,
@@ -24,6 +33,10 @@ export function AskAI({
24
  onScrollToBottom,
25
  isAiWorking,
26
  setisAiWorking,
 
 
 
 
27
  onNewPrompt,
28
  onSuccess,
29
  }: {
@@ -35,6 +48,10 @@ export function AskAI({
35
  htmlHistory?: HtmlHistory[];
36
  setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
37
  onSuccess: (h: string, p: string, n?: number[][]) => void;
 
 
 
 
38
  }) {
39
  const refThink = useRef<HTMLDivElement | null>(null);
40
  const audio = useRef<HTMLAudioElement | null>(null);
@@ -52,6 +69,7 @@ export function AskAI({
52
  const [openThink, setOpenThink] = useState(false);
53
  const [isThinking, setIsThinking] = useState(true);
54
  const [controller, setController] = useState<AbortController | null>(null);
 
55
 
56
  const callAi = async (redesignMarkdown?: string) => {
57
  if (isAiWorking) return;
@@ -66,13 +84,14 @@ export function AskAI({
66
  let thinkResponse = "";
67
  let lastRenderTime = 0;
68
 
69
- const isFollowUp = html !== defaultHTML;
70
  const abortController = new AbortController();
71
  setController(abortController);
72
  try {
73
  onNewPrompt(prompt);
74
- if (isFollowUp && !redesignMarkdown) {
75
- // TODO use @/lib/api instead of fetch directly (if possible)
 
 
76
  const request = await fetch("/api/ask-ai", {
77
  method: "PUT",
78
  body: JSON.stringify({
@@ -81,6 +100,7 @@ export function AskAI({
81
  previousPrompt,
82
  model,
83
  html,
 
84
  }),
85
  headers: {
86
  "Content-Type": "application/json",
@@ -263,6 +283,43 @@ export function AskAI({
263
 
264
  return (
265
  <>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  <div className="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">
267
  {think && (
268
  <div className="w-full border-b border-neutral-700 relative overflow-hidden">
@@ -301,6 +358,14 @@ export function AskAI({
301
  </main>
302
  </div>
303
  )}
 
 
 
 
 
 
 
 
304
  <div className="w-full relative flex items-center justify-between">
305
  {isAiWorking && (
306
  <div className="absolute bg-neutral-800 rounded-lg bottom-0 left-4 w-[calc(100%-30px)] h-full z-1 flex items-center justify-between max-lg:text-sm">
@@ -322,9 +387,18 @@ export function AskAI({
322
  <input
323
  type="text"
324
  disabled={isAiWorking}
325
- className="w-full bg-transparent text-sm outline-none text-white placeholder:text-neutral-400 p-4"
 
 
 
 
 
326
  placeholder={
327
- hasAsked ? "Ask DeepSite for edits" : "Ask DeepSite anything..."
 
 
 
 
328
  }
329
  value={prompt}
330
  onChange={(e) => setPrompt(e.target.value)}
@@ -338,7 +412,33 @@ export function AskAI({
338
  <div className="flex items-center justify-between gap-2 px-4 pb-3">
339
  <div className="flex-1 flex items-center justify-start gap-1.5">
340
  <ReImagine onRedesign={(md) => callAi(md)} />
341
- <InviteFriends />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  </div>
343
  <div className="flex items-center justify-end gap-2">
344
  <Settings
 
4
  import classNames from "classnames";
5
  import { toast } from "sonner";
6
  import { useLocalStorage, useUpdateEffect } from "react-use";
7
+ import { ArrowUp, ChevronDown, Info, Crosshair } from "lucide-react";
8
  import { FaStopCircle } from "react-icons/fa";
9
 
10
  import { defaultHTML } from "@/lib/consts";
 
12
  import { Button } from "@/components/ui/button";
13
  import { MODELS } from "@/lib/providers";
14
  import { HtmlHistory } from "@/types";
15
+ // import { InviteFriends } from "@/components/invite-friends";
16
  import { Settings } from "@/components/editor/ask-ai/settings";
17
  import { LoginModal } from "@/components/login-modal";
18
  import { ReImagine } from "@/components/editor/ask-ai/re-imagine";
19
  import Loading from "@/components/loading";
20
+ import {
21
+ Popover,
22
+ PopoverContent,
23
+ PopoverTrigger,
24
+ } from "@/components/ui/popover";
25
+ import { Checkbox } from "@/components/ui/checkbox";
26
+ import { Tooltip, TooltipTrigger } from "@/components/ui/tooltip";
27
+ import { TooltipContent } from "@radix-ui/react-tooltip";
28
+ import { SelectedHtmlElement } from "./selected-html-element";
29
 
30
  export function AskAI({
31
  html,
 
33
  onScrollToBottom,
34
  isAiWorking,
35
  setisAiWorking,
36
+ isEditableModeEnabled = false,
37
+ selectedElement,
38
+ setSelectedElement,
39
+ setIsEditableModeEnabled,
40
  onNewPrompt,
41
  onSuccess,
42
  }: {
 
48
  htmlHistory?: HtmlHistory[];
49
  setisAiWorking: React.Dispatch<React.SetStateAction<boolean>>;
50
  onSuccess: (h: string, p: string, n?: number[][]) => void;
51
+ isEditableModeEnabled: boolean;
52
+ setIsEditableModeEnabled: React.Dispatch<React.SetStateAction<boolean>>;
53
+ selectedElement?: HTMLElement | null;
54
+ setSelectedElement: React.Dispatch<React.SetStateAction<HTMLElement | null>>;
55
  }) {
56
  const refThink = useRef<HTMLDivElement | null>(null);
57
  const audio = useRef<HTMLAudioElement | null>(null);
 
69
  const [openThink, setOpenThink] = useState(false);
70
  const [isThinking, setIsThinking] = useState(true);
71
  const [controller, setController] = useState<AbortController | null>(null);
72
+ const [isFollowUp, setIsFollowUp] = useState(true);
73
 
74
  const callAi = async (redesignMarkdown?: string) => {
75
  if (isAiWorking) return;
 
84
  let thinkResponse = "";
85
  let lastRenderTime = 0;
86
 
 
87
  const abortController = new AbortController();
88
  setController(abortController);
89
  try {
90
  onNewPrompt(prompt);
91
+ if (isFollowUp && !redesignMarkdown && html !== defaultHTML) {
92
+ const selectedElementHtml = selectedElement
93
+ ? selectedElement.outerHTML
94
+ : "";
95
  const request = await fetch("/api/ask-ai", {
96
  method: "PUT",
97
  body: JSON.stringify({
 
100
  previousPrompt,
101
  model,
102
  html,
103
+ selectedElementHtml,
104
  }),
105
  headers: {
106
  "Content-Type": "application/json",
 
283
 
284
  return (
285
  <>
286
+ <div className="ml-auto 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 max-w-max">
287
+ <label
288
+ htmlFor="follow-up-checkbox"
289
+ className="flex items-center gap-1.5 cursor-pointer"
290
+ >
291
+ <Checkbox
292
+ id="follow-up-checkbox"
293
+ checked={isFollowUp}
294
+ onCheckedChange={(e) => {
295
+ setIsFollowUp(e === true);
296
+ }}
297
+ />
298
+ Follow-Up
299
+ </label>
300
+ <Popover>
301
+ <PopoverTrigger asChild>
302
+ <Info className="size-3 text-neutral-300 cursor-pointer" />
303
+ </PopoverTrigger>
304
+ <PopoverContent
305
+ align="start"
306
+ className="!rounded-2xl !p-0 min-w-xs text-center overflow-hidden"
307
+ >
308
+ <header className="bg-neutral-950 px-4 py-3 border-b border-neutral-700/70">
309
+ <p className="text-base text-neutral-200 font-semibold">
310
+ What is a Follow-Up?
311
+ </p>
312
+ </header>
313
+ <main className="p-4">
314
+ <p className="text-sm text-neutral-400">
315
+ A Follow-Up is a request to DeepSite to edit the current HTML
316
+ instead of starting from scratch. This is useful when you want
317
+ to make small changes or improvements to the existing design.
318
+ </p>
319
+ </main>
320
+ </PopoverContent>
321
+ </Popover>
322
+ </div>
323
  <div className="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">
324
  {think && (
325
  <div className="w-full border-b border-neutral-700 relative overflow-hidden">
 
358
  </main>
359
  </div>
360
  )}
361
+ {!isAiWorking && selectedElement && (
362
+ <div className="px-4 pt-3">
363
+ <SelectedHtmlElement
364
+ element={selectedElement}
365
+ onDelete={() => setSelectedElement(null)}
366
+ />
367
+ </div>
368
+ )}
369
  <div className="w-full relative flex items-center justify-between">
370
  {isAiWorking && (
371
  <div className="absolute bg-neutral-800 rounded-lg bottom-0 left-4 w-[calc(100%-30px)] h-full z-1 flex items-center justify-between max-lg:text-sm">
 
387
  <input
388
  type="text"
389
  disabled={isAiWorking}
390
+ className={classNames(
391
+ "w-full bg-transparent text-sm outline-none text-white placeholder:text-neutral-400 p-4",
392
+ {
393
+ "!pt-2.5": selectedElement && !isAiWorking,
394
+ }
395
+ )}
396
  placeholder={
397
+ selectedElement
398
+ ? `Ask DeepSite about ${selectedElement.tagName.toLowerCase()}...`
399
+ : hasAsked
400
+ ? "Ask DeepSite for edits"
401
+ : "Ask DeepSite anything..."
402
  }
403
  value={prompt}
404
  onChange={(e) => setPrompt(e.target.value)}
 
412
  <div className="flex items-center justify-between gap-2 px-4 pb-3">
413
  <div className="flex-1 flex items-center justify-start gap-1.5">
414
  <ReImagine onRedesign={(md) => callAi(md)} />
415
+ {html !== defaultHTML && (
416
+ <Tooltip>
417
+ <TooltipTrigger asChild>
418
+ <Button
419
+ size="xs"
420
+ variant={isEditableModeEnabled ? "default" : "outline"}
421
+ onClick={() => {
422
+ setIsEditableModeEnabled?.(!isEditableModeEnabled);
423
+ }}
424
+ className={classNames("h-[28px]", {
425
+ "!text-neutral-400 hover:!text-neutral-200 !border-neutral-600 !hover:!border-neutral-500":
426
+ !isEditableModeEnabled,
427
+ })}
428
+ >
429
+ <Crosshair className="size-4" />
430
+ Edit
431
+ </Button>
432
+ </TooltipTrigger>
433
+ <TooltipContent
434
+ align="start"
435
+ className="bg-neutral-950 text-xs text-neutral-200 py-1 px-2 rounded-md -translate-y-0.5"
436
+ >
437
+ Select an element on the page to ask DeepSite edit it
438
+ directly.
439
+ </TooltipContent>
440
+ </Tooltip>
441
+ )}
442
  </div>
443
  <div className="flex items-center justify-end gap-2">
444
  <Settings
components/editor/ask-ai/re-imagine.tsx CHANGED
@@ -10,6 +10,7 @@ import {
10
  } from "@/components/ui/popover";
11
  import { Input } from "@/components/ui/input";
12
  import Loading from "@/components/loading";
 
13
 
14
  export function ReImagine({
15
  onRedesign,
@@ -29,6 +30,7 @@ export function ReImagine({
29
  };
30
 
31
  const handleClick = async () => {
 
32
  if (!url) {
33
  toast.error("Please enter a URL.");
34
  return;
@@ -37,24 +39,25 @@ export function ReImagine({
37
  toast.error("Please enter a valid URL.");
38
  return;
39
  }
 
40
  // Here you would typically handle the re-design logic
41
  setIsLoading(true);
42
- const request = await fetch("/api/re-design", {
43
- method: "POST",
44
- body: JSON.stringify({ url }),
45
- headers: {
46
- "Content-Type": "application/json",
47
- },
48
- });
49
- const response = await request.json();
50
- if (response.ok) {
51
- setOpen(false);
52
- setUrl("");
53
- onRedesign(response.markdown);
54
- toast.success("DeepSite is redesigning your site! Let him cook... 🔥");
55
- } else {
56
- toast.error(response.message || "Failed to redesign the site.");
57
- }
58
  setIsLoading(false);
59
  };
60
 
@@ -126,10 +129,20 @@ export function ReImagine({
126
  variant="black"
127
  onClick={handleClick}
128
  className="relative w-full"
129
- disabled={isLoading}
130
  >
131
- Redesign <Paintbrush className="size-4" />
132
- {isLoading && <Loading className="ml-2 size-4 animate-spin" />}
 
 
 
 
 
 
 
 
 
 
 
133
  </Button>
134
  </div>
135
  </main>
 
10
  } from "@/components/ui/popover";
11
  import { Input } from "@/components/ui/input";
12
  import Loading from "@/components/loading";
13
+ import { api } from "@/lib/api";
14
 
15
  export function ReImagine({
16
  onRedesign,
 
30
  };
31
 
32
  const handleClick = async () => {
33
+ if (isLoading) return; // Prevent multiple clicks while loading
34
  if (!url) {
35
  toast.error("Please enter a URL.");
36
  return;
 
39
  toast.error("Please enter a valid URL.");
40
  return;
41
  }
42
+ // TODO implement the API call to redesign the site
43
  // Here you would typically handle the re-design logic
44
  setIsLoading(true);
45
+ // const request = await api.post("/api/re-design", {
46
+ // method: "POST",
47
+ // body: JSON.stringify({ url }),
48
+ // headers: {
49
+ // "Content-Type": "application/json",
50
+ // },
51
+ // });
52
+ // const response = await request.json();
53
+ // if (response.ok) {
54
+ // setOpen(false);
55
+ // setUrl("");
56
+ // onRedesign(response.markdown);
57
+ // toast.success("DeepSite is redesigning your site! Let him cook... 🔥");
58
+ // } else {
59
+ // toast.error(response.message || "Failed to redesign the site.");
60
+ // }
61
  setIsLoading(false);
62
  };
63
 
 
129
  variant="black"
130
  onClick={handleClick}
131
  className="relative w-full"
 
132
  >
133
+ {isLoading ? (
134
+ <>
135
+ <Loading
136
+ overlay={false}
137
+ className="ml-2 size-4 animate-spin"
138
+ />
139
+ Fetching your site...
140
+ </>
141
+ ) : (
142
+ <>
143
+ Redesign <Paintbrush className="size-4" />
144
+ </>
145
+ )}
146
  </Button>
147
  </div>
148
  </main>
components/editor/ask-ai/selected-html-element.tsx ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Collapsible, CollapsibleTrigger } from "@/components/ui/collapsible";
2
+ import { htmlTagToText } from "@/lib/html-tag-to-text";
3
+ import { Code, XCircle } from "lucide-react";
4
+
5
+ export const SelectedHtmlElement = ({
6
+ element,
7
+ onDelete,
8
+ }: {
9
+ element: HTMLElement | null;
10
+ onDelete?: () => void;
11
+ }) => {
12
+ if (!element) return null;
13
+
14
+ const tagName = element.tagName.toLowerCase();
15
+ return (
16
+ <Collapsible
17
+ className="border border-neutral-700 rounded-xl p-1.5 pr-3 max-w-max hover:brightness-110 transition-all duration-200 ease-in-out !cursor-pointer"
18
+ onClick={onDelete}
19
+ >
20
+ <CollapsibleTrigger className="flex items-center justify-start gap-2 cursor-pointer">
21
+ <div className="rounded-lg bg-neutral-700 size-6 flex items-center justify-center">
22
+ <Code className="text-neutral-300 size-3.5" />
23
+ </div>
24
+ <p className="text-sm font-semibold text-neutral-300">
25
+ {htmlTagToText(tagName)}
26
+ </p>
27
+ <XCircle className="text-neutral-300 size-4" />
28
+ </CollapsibleTrigger>
29
+ {/* <CollapsibleContent className="border-t border-neutral-700 pt-2 mt-2">
30
+ <div className="text-xs text-neutral-400">
31
+ <p>
32
+ <span className="font-semibold">ID:</span> {element.id || "No ID"}
33
+ </p>
34
+ <p>
35
+ <span className="font-semibold">Classes:</span>{" "}
36
+ {element.className || "No classes"}
37
+ </p>
38
+ </div>
39
+ </CollapsibleContent> */}
40
+ </Collapsible>
41
+ );
42
+ };
components/editor/index.tsx CHANGED
@@ -47,6 +47,10 @@ export const AppEditor = ({ project }: { project?: Project | null }) => {
47
  const [device, setDevice] = useState<"desktop" | "mobile">("desktop");
48
  const [isResizing, setIsResizing] = useState(false);
49
  const [isAiWorking, setIsAiWorking] = useState(false);
 
 
 
 
50
 
51
  /**
52
  * Resets the layout based on screen size
@@ -158,6 +162,7 @@ export const AppEditor = ({ project }: { project?: Project | null }) => {
158
  resizer.current.addEventListener("mousedown", handleMouseDown);
159
  }
160
  } else {
 
161
  if (preview.current) {
162
  // Reset preview width when switching to preview tab
163
  preview.current.style.width = "100%";
@@ -234,6 +239,7 @@ export const AppEditor = ({ project }: { project?: Project | null }) => {
234
  prompt: p,
235
  });
236
  setHtmlHistory(currentHistory);
 
237
  // if xs or sm
238
  if (window.innerWidth <= 1024) {
239
  setCurrentTab("preview");
@@ -269,6 +275,10 @@ export const AppEditor = ({ project }: { project?: Project | null }) => {
269
  editorRef.current?.getModel()?.getLineCount() ?? 0
270
  );
271
  }}
 
 
 
 
272
  />
273
  </div>
274
  <div
@@ -284,7 +294,12 @@ export const AppEditor = ({ project }: { project?: Project | null }) => {
284
  ref={preview}
285
  device={device}
286
  currentTab={currentTab}
 
287
  iframeRef={iframeRef}
 
 
 
 
288
  />
289
  </main>
290
  <Footer
 
47
  const [device, setDevice] = useState<"desktop" | "mobile">("desktop");
48
  const [isResizing, setIsResizing] = useState(false);
49
  const [isAiWorking, setIsAiWorking] = useState(false);
50
+ const [isEditableModeEnabled, setIsEditableModeEnabled] = useState(false);
51
+ const [selectedElement, setSelectedElement] = useState<HTMLElement | null>(
52
+ null
53
+ );
54
 
55
  /**
56
  * Resets the layout based on screen size
 
162
  resizer.current.addEventListener("mousedown", handleMouseDown);
163
  }
164
  } else {
165
+ setIsEditableModeEnabled(false);
166
  if (preview.current) {
167
  // Reset preview width when switching to preview tab
168
  preview.current.style.width = "100%";
 
239
  prompt: p,
240
  });
241
  setHtmlHistory(currentHistory);
242
+ setSelectedElement(null);
243
  // if xs or sm
244
  if (window.innerWidth <= 1024) {
245
  setCurrentTab("preview");
 
275
  editorRef.current?.getModel()?.getLineCount() ?? 0
276
  );
277
  }}
278
+ isEditableModeEnabled={isEditableModeEnabled}
279
+ setIsEditableModeEnabled={setIsEditableModeEnabled}
280
+ selectedElement={selectedElement}
281
+ setSelectedElement={setSelectedElement}
282
  />
283
  </div>
284
  <div
 
294
  ref={preview}
295
  device={device}
296
  currentTab={currentTab}
297
+ isEditableModeEnabled={isEditableModeEnabled}
298
  iframeRef={iframeRef}
299
+ onClickElement={(element) => {
300
+ setIsEditableModeEnabled(false);
301
+ setSelectedElement(element);
302
+ }}
303
  />
304
  </main>
305
  <Footer
components/editor/preview/index.tsx CHANGED
@@ -1,9 +1,12 @@
1
  "use client";
 
 
2
  import classNames from "classnames";
3
  import { toast } from "sonner";
4
 
5
  import { cn } from "@/lib/utils";
6
  import { GridPattern } from "@/components/magic-ui/grid-pattern";
 
7
 
8
  export const Preview = ({
9
  html,
@@ -13,6 +16,8 @@ export const Preview = ({
13
  device,
14
  currentTab,
15
  iframeRef,
 
 
16
  }: {
17
  html: string;
18
  isResizing: boolean;
@@ -21,7 +26,80 @@ export const Preview = ({
21
  iframeRef?: React.RefObject<HTMLIFrameElement | null>;
22
  device: "desktop" | "mobile";
23
  currentTab: string;
 
 
24
  }) => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  return (
26
  <div
27
  ref={ref}
@@ -49,6 +127,27 @@ export const Preview = ({
49
  "[mask-image:radial-gradient(900px_circle_at_center,white,transparent)]"
50
  )}
51
  />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  <iframe
53
  id="preview-iframe"
54
  ref={iframeRef}
 
1
  "use client";
2
+ import { useUpdateEffect } from "react-use";
3
+ import { useMemo, useState } from "react";
4
  import classNames from "classnames";
5
  import { toast } from "sonner";
6
 
7
  import { cn } from "@/lib/utils";
8
  import { GridPattern } from "@/components/magic-ui/grid-pattern";
9
+ import { htmlTagToText } from "@/lib/html-tag-to-text";
10
 
11
  export const Preview = ({
12
  html,
 
16
  device,
17
  currentTab,
18
  iframeRef,
19
+ isEditableModeEnabled,
20
+ onClickElement,
21
  }: {
22
  html: string;
23
  isResizing: boolean;
 
26
  iframeRef?: React.RefObject<HTMLIFrameElement | null>;
27
  device: "desktop" | "mobile";
28
  currentTab: string;
29
+ isEditableModeEnabled?: boolean;
30
+ onClickElement?: (element: HTMLElement) => void;
31
  }) => {
32
+ const [hoveredElement, setHoveredElement] = useState<HTMLElement | null>(
33
+ null
34
+ );
35
+
36
+ // add event listener to the iframe to track hovered elements
37
+ const handleMouseOver = (event: MouseEvent) => {
38
+ if (iframeRef?.current) {
39
+ const iframeDocument = iframeRef.current.contentDocument;
40
+ if (iframeDocument) {
41
+ const targetElement = event.target as HTMLElement;
42
+ if (
43
+ hoveredElement !== targetElement &&
44
+ targetElement !== iframeDocument.body
45
+ ) {
46
+ setHoveredElement(targetElement);
47
+ targetElement.classList.add("hovered-element");
48
+ } else {
49
+ return setHoveredElement(null);
50
+ }
51
+ }
52
+ }
53
+ };
54
+ const handleMouseOut = () => {
55
+ setHoveredElement(null);
56
+ };
57
+ const handleClick = (event: MouseEvent) => {
58
+ if (iframeRef?.current) {
59
+ const iframeDocument = iframeRef.current.contentDocument;
60
+ if (iframeDocument) {
61
+ const targetElement = event.target as HTMLElement;
62
+ if (targetElement !== iframeDocument.body) {
63
+ onClickElement?.(targetElement);
64
+ }
65
+ }
66
+ }
67
+ };
68
+
69
+ useUpdateEffect(() => {
70
+ const cleanupListeners = () => {
71
+ if (iframeRef?.current?.contentDocument) {
72
+ const iframeDocument = iframeRef.current.contentDocument;
73
+ iframeDocument.removeEventListener("mouseover", handleMouseOver);
74
+ iframeDocument.removeEventListener("mouseout", handleMouseOut);
75
+ iframeDocument.removeEventListener("click", handleClick);
76
+ }
77
+ };
78
+
79
+ if (iframeRef?.current) {
80
+ const iframeDocument = iframeRef.current.contentDocument;
81
+ if (iframeDocument) {
82
+ // Clean up existing listeners first
83
+ cleanupListeners();
84
+
85
+ if (isEditableModeEnabled) {
86
+ iframeDocument.addEventListener("mouseover", handleMouseOver);
87
+ iframeDocument.addEventListener("mouseout", handleMouseOut);
88
+ iframeDocument.addEventListener("click", handleClick);
89
+ }
90
+ }
91
+ }
92
+
93
+ // Clean up when component unmounts or dependencies change
94
+ return cleanupListeners;
95
+ }, [iframeRef, isEditableModeEnabled]);
96
+
97
+ const selectedElement = useMemo(() => {
98
+ if (!isEditableModeEnabled) return null;
99
+ if (!hoveredElement) return null;
100
+ return hoveredElement;
101
+ }, [hoveredElement, isEditableModeEnabled]);
102
+
103
  return (
104
  <div
105
  ref={ref}
 
127
  "[mask-image:radial-gradient(900px_circle_at_center,white,transparent)]"
128
  )}
129
  />
130
+ {!isAiWorking && hoveredElement && selectedElement && (
131
+ <div
132
+ className="cursor-pointer absolute bg-sky-500/10 border-[2px] border-dashed border-sky-500 rounded-r-lg rounded-b-lg p-3 z-10 pointer-events-none"
133
+ style={{
134
+ top:
135
+ selectedElement.getBoundingClientRect().top +
136
+ (iframeRef?.current?.contentWindow?.scrollY || 0) +
137
+ 24,
138
+ left:
139
+ selectedElement.getBoundingClientRect().left +
140
+ (iframeRef?.current?.contentWindow?.scrollX || 0) +
141
+ 24,
142
+ width: selectedElement.getBoundingClientRect().width,
143
+ height: selectedElement.getBoundingClientRect().height,
144
+ }}
145
+ >
146
+ <span className="bg-sky-500 rounded-t-md text-sm text-neutral-100 px-2 py-0.5 -translate-y-7 absolute top-0 left-0">
147
+ {htmlTagToText(selectedElement.tagName.toLowerCase())}
148
+ </span>
149
+ </div>
150
+ )}
151
  <iframe
152
  id="preview-iframe"
153
  ref={iframeRef}
components/public/navigation/index.tsx CHANGED
@@ -85,7 +85,7 @@ export default function Navigation() {
85
  }
86
  )}
87
  >
88
- <nav className="grid grid-cols-3 p-4 container mx-auto">
89
  <Link href="/" className="flex items-center gap-1">
90
  <Image
91
  src={Logo}
@@ -96,7 +96,7 @@ export default function Navigation() {
96
  />
97
  <p className="font-sans text-white text-xl font-bold">DeepSite</p>
98
  </Link>
99
- <ul className="flex items-center justify-center gap-6">
100
  {navigationLinks.map((link) => (
101
  <li
102
  key={link.name}
 
85
  }
86
  )}
87
  >
88
+ <nav className="grid grid-cols-2 p-4 container mx-auto">
89
  <Link href="/" className="flex items-center gap-1">
90
  <Image
91
  src={Logo}
 
96
  />
97
  <p className="font-sans text-white text-xl font-bold">DeepSite</p>
98
  </Link>
99
+ <ul className="items-center justify-center gap-6 hidden">
100
  {navigationLinks.map((link) => (
101
  <li
102
  key={link.name}
components/space/ask-ai/index.tsx CHANGED
@@ -8,34 +8,39 @@ import { Button } from "@/components/ui/button";
8
 
9
  export const AskAi = () => {
10
  return (
11
- <div className="bg-neutral-800 border border-neutral-700 rounded-2xl ring-[4px] focus-within:ring-neutral-500/30 focus-within:border-neutral-600 ring-transparent group">
12
- <textarea
13
- rows={3}
14
- className="w-full bg-transparent text-sm outline-none text-white placeholder:text-neutral-400 p-4 resize-none mb-1"
15
- placeholder="Ask DeepSite anything..."
16
- onChange={() => {}}
17
- onKeyDown={() => {}}
18
- />
19
- <div className="flex items-center justify-between gap-2 px-4 pb-3">
20
- <div className="flex-1 flex justify-start">
21
- <Button
22
- size="iconXs"
23
- variant="outline"
24
- className="!border-neutral-600 !text-neutral-400 !hover:!border-neutral-500 hover:!text-neutral-300"
25
- >
26
- <TiUserAdd className="size-4" />
27
- </Button>
28
- </div>
29
- <div className="flex items-center justify-end gap-2">
30
- <Button variant="black" size="sm">
31
- <PiGearSixFill className="size-4" />
32
- Settings
33
- </Button>
34
- <Button size="iconXs">
35
- <ArrowUp className="size-4" />
36
- </Button>
 
 
 
 
 
37
  </div>
38
  </div>
39
- </div>
40
  );
41
  };
 
8
 
9
  export const AskAi = () => {
10
  return (
11
+ <>
12
+ <div className="bg-red-500 flex items-center justify-end">
13
+ No Follow-up mode
14
+ </div>
15
+ <div className="bg-neutral-800 border border-neutral-700 rounded-2xl ring-[4px] focus-within:ring-neutral-500/30 focus-within:border-neutral-600 ring-transparent group">
16
+ <textarea
17
+ rows={3}
18
+ className="w-full bg-transparent text-sm outline-none text-white placeholder:text-neutral-400 p-4 resize-none mb-1"
19
+ placeholder="Ask DeepSite anything..."
20
+ onChange={() => {}}
21
+ onKeyDown={() => {}}
22
+ />
23
+ <div className="flex items-center justify-between gap-2 px-4 pb-3">
24
+ <div className="flex-1 flex justify-start">
25
+ <Button
26
+ size="iconXs"
27
+ variant="outline"
28
+ className="!border-neutral-600 !text-neutral-400 !hover:!border-neutral-500 hover:!text-neutral-300"
29
+ >
30
+ <TiUserAdd className="size-4" />
31
+ </Button>
32
+ </div>
33
+ <div className="flex items-center justify-end gap-2">
34
+ <Button variant="black" size="sm">
35
+ <PiGearSixFill className="size-4" />
36
+ Settings
37
+ </Button>
38
+ <Button size="iconXs">
39
+ <ArrowUp className="size-4" />
40
+ </Button>
41
+ </div>
42
  </div>
43
  </div>
44
+ </>
45
  );
46
  };
components/ui/checkbox.tsx ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
5
+ import { CheckIcon } from "lucide-react";
6
+
7
+ import { cn } from "@/lib/utils";
8
+
9
+ function Checkbox({
10
+ className,
11
+ ...props
12
+ }: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
13
+ return (
14
+ <CheckboxPrimitive.Root
15
+ data-slot="checkbox"
16
+ className={cn(
17
+ "peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-3.5 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
18
+ className
19
+ )}
20
+ {...props}
21
+ >
22
+ <CheckboxPrimitive.Indicator
23
+ data-slot="checkbox-indicator"
24
+ className="flex items-center justify-center text-current transition-none"
25
+ >
26
+ <CheckIcon className="size-3" />
27
+ </CheckboxPrimitive.Indicator>
28
+ </CheckboxPrimitive.Root>
29
+ );
30
+ }
31
+
32
+ export { Checkbox };
components/ui/collapsible.tsx ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
4
+
5
+ function Collapsible({
6
+ ...props
7
+ }: React.ComponentProps<typeof CollapsiblePrimitive.Root>) {
8
+ return <CollapsiblePrimitive.Root data-slot="collapsible" {...props} />
9
+ }
10
+
11
+ function CollapsibleTrigger({
12
+ ...props
13
+ }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleTrigger>) {
14
+ return (
15
+ <CollapsiblePrimitive.CollapsibleTrigger
16
+ data-slot="collapsible-trigger"
17
+ {...props}
18
+ />
19
+ )
20
+ }
21
+
22
+ function CollapsibleContent({
23
+ ...props
24
+ }: React.ComponentProps<typeof CollapsiblePrimitive.CollapsibleContent>) {
25
+ return (
26
+ <CollapsiblePrimitive.CollapsibleContent
27
+ data-slot="collapsible-content"
28
+ {...props}
29
+ />
30
+ )
31
+ }
32
+
33
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent }
components/ui/switch.tsx ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SwitchPrimitive from "@radix-ui/react-switch"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ function Switch({
9
+ className,
10
+ ...props
11
+ }: React.ComponentProps<typeof SwitchPrimitive.Root>) {
12
+ return (
13
+ <SwitchPrimitive.Root
14
+ data-slot="switch"
15
+ className={cn(
16
+ "peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
17
+ className
18
+ )}
19
+ {...props}
20
+ >
21
+ <SwitchPrimitive.Thumb
22
+ data-slot="switch-thumb"
23
+ className={cn(
24
+ "bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0"
25
+ )}
26
+ />
27
+ </SwitchPrimitive.Root>
28
+ )
29
+ }
30
+
31
+ export { Switch }
lib/html-tag-to-text.ts ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const htmlTagToText = (tagName: string): string => {
2
+ switch (tagName.toLowerCase()) {
3
+ case "h1":
4
+ return "Heading 1";
5
+ case "h2":
6
+ return "Heading 2";
7
+ case "h3":
8
+ return "Heading 3";
9
+ case "h4":
10
+ return "Heading 4";
11
+ case "h5":
12
+ return "Heading 5";
13
+ case "h6":
14
+ return "Heading 6";
15
+ case "p":
16
+ return "Text Paragraph";
17
+ case "span":
18
+ return "Inline Text";
19
+ case "button":
20
+ return "Button";
21
+ case "input":
22
+ return "Input Field";
23
+ case "select":
24
+ return "Select Dropdown";
25
+ case "textarea":
26
+ return "Text Area";
27
+ case "form":
28
+ return "Form";
29
+ case "table":
30
+ return "Table";
31
+ case "thead":
32
+ return "Table Header";
33
+ case "tbody":
34
+ return "Table Body";
35
+ case "tr":
36
+ return "Table Row";
37
+ case "th":
38
+ return "Table Header Cell";
39
+ case "td":
40
+ return "Table Data Cell";
41
+ case "nav":
42
+ return "Navigation";
43
+ case "header":
44
+ return "Header";
45
+ case "footer":
46
+ return "Footer";
47
+ case "section":
48
+ return "Section";
49
+ case "article":
50
+ return "Article";
51
+ case "aside":
52
+ return "Aside";
53
+ case "div":
54
+ return "Division";
55
+ case "main":
56
+ return "Main Content";
57
+ case "details":
58
+ return "Details";
59
+ case "summary":
60
+ return "Summary";
61
+ case "code":
62
+ return "Code Snippet";
63
+ case "pre":
64
+ return "Preformatted Text";
65
+ case "kbd":
66
+ return "Keyboard Input";
67
+ case "label":
68
+ return "Label";
69
+ case "canvas":
70
+ return "Canvas";
71
+ case "svg":
72
+ return "SVG Graphic";
73
+ case "video":
74
+ return "Video Player";
75
+ case "audio":
76
+ return "Audio Player";
77
+ case "iframe":
78
+ return "Embedded Frame";
79
+ case "link":
80
+ return "Link";
81
+ case "a":
82
+ return "Link";
83
+ case "img":
84
+ return "Image";
85
+ case "ul":
86
+ return "Unordered List";
87
+ case "ol":
88
+ return "Ordered List";
89
+ case "li":
90
+ return "List Item";
91
+ case "blockquote":
92
+ return "Blockquote";
93
+ default:
94
+ return tagName.charAt(0).toUpperCase() + tagName.slice(1);
95
+ }
96
+ };
package-lock.json CHANGED
@@ -12,11 +12,14 @@
12
  "@huggingface/inference": "^4.0.3",
13
  "@monaco-editor/react": "^4.7.0",
14
  "@radix-ui/react-avatar": "^1.1.10",
 
 
15
  "@radix-ui/react-dialog": "^1.1.14",
16
  "@radix-ui/react-dropdown-menu": "^2.1.15",
17
  "@radix-ui/react-popover": "^1.1.14",
18
  "@radix-ui/react-select": "^2.2.5",
19
  "@radix-ui/react-slot": "^1.2.3",
 
20
  "@radix-ui/react-tabs": "^1.1.12",
21
  "@radix-ui/react-toggle": "^1.1.9",
22
  "@radix-ui/react-toggle-group": "^1.1.10",
@@ -1169,6 +1172,66 @@
1169
  }
1170
  }
1171
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1172
  "node_modules/@radix-ui/react-collection": {
1173
  "version": "1.1.7",
1174
  "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
@@ -1662,6 +1725,35 @@
1662
  }
1663
  }
1664
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1665
  "node_modules/@radix-ui/react-tabs": {
1666
  "version": "1.1.12",
1667
  "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.12.tgz",
 
12
  "@huggingface/inference": "^4.0.3",
13
  "@monaco-editor/react": "^4.7.0",
14
  "@radix-ui/react-avatar": "^1.1.10",
15
+ "@radix-ui/react-checkbox": "^1.3.2",
16
+ "@radix-ui/react-collapsible": "^1.1.11",
17
  "@radix-ui/react-dialog": "^1.1.14",
18
  "@radix-ui/react-dropdown-menu": "^2.1.15",
19
  "@radix-ui/react-popover": "^1.1.14",
20
  "@radix-ui/react-select": "^2.2.5",
21
  "@radix-ui/react-slot": "^1.2.3",
22
+ "@radix-ui/react-switch": "^1.2.5",
23
  "@radix-ui/react-tabs": "^1.1.12",
24
  "@radix-ui/react-toggle": "^1.1.9",
25
  "@radix-ui/react-toggle-group": "^1.1.10",
 
1172
  }
1173
  }
1174
  },
1175
+ "node_modules/@radix-ui/react-checkbox": {
1176
+ "version": "1.3.2",
1177
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.2.tgz",
1178
+ "integrity": "sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA==",
1179
+ "license": "MIT",
1180
+ "dependencies": {
1181
+ "@radix-ui/primitive": "1.1.2",
1182
+ "@radix-ui/react-compose-refs": "1.1.2",
1183
+ "@radix-ui/react-context": "1.1.2",
1184
+ "@radix-ui/react-presence": "1.1.4",
1185
+ "@radix-ui/react-primitive": "2.1.3",
1186
+ "@radix-ui/react-use-controllable-state": "1.2.2",
1187
+ "@radix-ui/react-use-previous": "1.1.1",
1188
+ "@radix-ui/react-use-size": "1.1.1"
1189
+ },
1190
+ "peerDependencies": {
1191
+ "@types/react": "*",
1192
+ "@types/react-dom": "*",
1193
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
1194
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1195
+ },
1196
+ "peerDependenciesMeta": {
1197
+ "@types/react": {
1198
+ "optional": true
1199
+ },
1200
+ "@types/react-dom": {
1201
+ "optional": true
1202
+ }
1203
+ }
1204
+ },
1205
+ "node_modules/@radix-ui/react-collapsible": {
1206
+ "version": "1.1.11",
1207
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.11.tgz",
1208
+ "integrity": "sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg==",
1209
+ "license": "MIT",
1210
+ "dependencies": {
1211
+ "@radix-ui/primitive": "1.1.2",
1212
+ "@radix-ui/react-compose-refs": "1.1.2",
1213
+ "@radix-ui/react-context": "1.1.2",
1214
+ "@radix-ui/react-id": "1.1.1",
1215
+ "@radix-ui/react-presence": "1.1.4",
1216
+ "@radix-ui/react-primitive": "2.1.3",
1217
+ "@radix-ui/react-use-controllable-state": "1.2.2",
1218
+ "@radix-ui/react-use-layout-effect": "1.1.1"
1219
+ },
1220
+ "peerDependencies": {
1221
+ "@types/react": "*",
1222
+ "@types/react-dom": "*",
1223
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
1224
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1225
+ },
1226
+ "peerDependenciesMeta": {
1227
+ "@types/react": {
1228
+ "optional": true
1229
+ },
1230
+ "@types/react-dom": {
1231
+ "optional": true
1232
+ }
1233
+ }
1234
+ },
1235
  "node_modules/@radix-ui/react-collection": {
1236
  "version": "1.1.7",
1237
  "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
 
1725
  }
1726
  }
1727
  },
1728
+ "node_modules/@radix-ui/react-switch": {
1729
+ "version": "1.2.5",
1730
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.5.tgz",
1731
+ "integrity": "sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ==",
1732
+ "license": "MIT",
1733
+ "dependencies": {
1734
+ "@radix-ui/primitive": "1.1.2",
1735
+ "@radix-ui/react-compose-refs": "1.1.2",
1736
+ "@radix-ui/react-context": "1.1.2",
1737
+ "@radix-ui/react-primitive": "2.1.3",
1738
+ "@radix-ui/react-use-controllable-state": "1.2.2",
1739
+ "@radix-ui/react-use-previous": "1.1.1",
1740
+ "@radix-ui/react-use-size": "1.1.1"
1741
+ },
1742
+ "peerDependencies": {
1743
+ "@types/react": "*",
1744
+ "@types/react-dom": "*",
1745
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
1746
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1747
+ },
1748
+ "peerDependenciesMeta": {
1749
+ "@types/react": {
1750
+ "optional": true
1751
+ },
1752
+ "@types/react-dom": {
1753
+ "optional": true
1754
+ }
1755
+ }
1756
+ },
1757
  "node_modules/@radix-ui/react-tabs": {
1758
  "version": "1.1.12",
1759
  "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.12.tgz",
package.json CHANGED
@@ -13,11 +13,14 @@
13
  "@huggingface/inference": "^4.0.3",
14
  "@monaco-editor/react": "^4.7.0",
15
  "@radix-ui/react-avatar": "^1.1.10",
 
 
16
  "@radix-ui/react-dialog": "^1.1.14",
17
  "@radix-ui/react-dropdown-menu": "^2.1.15",
18
  "@radix-ui/react-popover": "^1.1.14",
19
  "@radix-ui/react-select": "^2.2.5",
20
  "@radix-ui/react-slot": "^1.2.3",
 
21
  "@radix-ui/react-tabs": "^1.1.12",
22
  "@radix-ui/react-toggle": "^1.1.9",
23
  "@radix-ui/react-toggle-group": "^1.1.10",
 
13
  "@huggingface/inference": "^4.0.3",
14
  "@monaco-editor/react": "^4.7.0",
15
  "@radix-ui/react-avatar": "^1.1.10",
16
+ "@radix-ui/react-checkbox": "^1.3.2",
17
+ "@radix-ui/react-collapsible": "^1.1.11",
18
  "@radix-ui/react-dialog": "^1.1.14",
19
  "@radix-ui/react-dropdown-menu": "^2.1.15",
20
  "@radix-ui/react-popover": "^1.1.14",
21
  "@radix-ui/react-select": "^2.2.5",
22
  "@radix-ui/react-slot": "^1.2.3",
23
+ "@radix-ui/react-switch": "^1.2.5",
24
  "@radix-ui/react-tabs": "^1.1.12",
25
  "@radix-ui/react-toggle": "^1.1.9",
26
  "@radix-ui/react-toggle-group": "^1.1.10",