enzostvs HF Staff commited on
Commit
bb42e25
·
1 Parent(s): 3109fce

context menu to add new chat

Browse files
src/lib/components/chat/User.svelte CHANGED
@@ -30,7 +30,7 @@
30
 
31
  // svelte-ignore state_referenced_locally
32
  const nodeData = useNodesData(id);
33
- const { current: currentNodes, update: updateNodes } = useNodes();
34
  const { update: updateEdges } = useEdges();
35
  const { updateNodeData } = useSvelteFlow();
36
 
@@ -39,11 +39,11 @@
39
  );
40
  let messages = $derived((nodeData.current?.data.messages as ChatMessage[]) ?? []);
41
  let isFirstNode = $derived((nodeData.current?.data.isFirstNode as boolean) ?? false);
 
42
 
43
  let prompt = $state.raw<string>((nodeData.current?.data.prompt as string) ?? '');
44
  let loading = $state.raw<boolean>(false);
45
  let errorMessage = $state.raw<string>('');
46
- let showWelcome = $state.raw<boolean>(true);
47
 
48
  const randomSuggestions = SUGGESTIONS_PROMPT.sort(() => Math.random() - 0.5).slice(
49
  0,
@@ -272,9 +272,9 @@
272
  const value = (e.target as HTMLTextAreaElement).value;
273
  if (isFirstNode) {
274
  if (value.trim() === '') {
275
- showWelcome = true;
276
  } else {
277
- showWelcome = false;
278
  }
279
  }
280
  }
@@ -370,7 +370,11 @@
370
  class="rounded-full! shadow-none!"
371
  disabled={!selectedModels.length}
372
  onclick={() => {
373
- showWelcome = false;
 
 
 
 
374
  prompt = suggestion;
375
  handleTriggerAction();
376
  }}
 
30
 
31
  // svelte-ignore state_referenced_locally
32
  const nodeData = useNodesData(id);
33
+ const { update: updateNodes } = useNodes();
34
  const { update: updateEdges } = useEdges();
35
  const { updateNodeData } = useSvelteFlow();
36
 
 
39
  );
40
  let messages = $derived((nodeData.current?.data.messages as ChatMessage[]) ?? []);
41
  let isFirstNode = $derived((nodeData.current?.data.isFirstNode as boolean) ?? false);
42
+ let showWelcome = $derived((nodeData.current?.data.showWelcome as boolean) ?? true);
43
 
44
  let prompt = $state.raw<string>((nodeData.current?.data.prompt as string) ?? '');
45
  let loading = $state.raw<boolean>(false);
46
  let errorMessage = $state.raw<string>('');
 
47
 
48
  const randomSuggestions = SUGGESTIONS_PROMPT.sort(() => Math.random() - 0.5).slice(
49
  0,
 
272
  const value = (e.target as HTMLTextAreaElement).value;
273
  if (isFirstNode) {
274
  if (value.trim() === '') {
275
+ updateNodeData(id, { ...nodeData.current?.data, showWelcome: true }, { replace: true });
276
  } else {
277
+ updateNodeData(id, { ...nodeData.current?.data, showWelcome: false }, { replace: true });
278
  }
279
  }
280
  }
 
370
  class="rounded-full! shadow-none!"
371
  disabled={!selectedModels.length}
372
  onclick={() => {
373
+ updateNodeData(
374
+ id,
375
+ { ...nodeData.current?.data, showWelcome: false },
376
+ { replace: true }
377
+ );
378
  prompt = suggestion;
379
  handleTriggerAction();
380
  }}
src/lib/components/flow/actions/ContextMenu.svelte ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { MessageCirclePlus } from '@lucide/svelte';
3
+ import { useNodes, useSvelteFlow, type Node } from '@xyflow/svelte';
4
+
5
+ import * as ContextMenu from '$lib/components/ui/context-menu/index.js';
6
+ import { MAX_DEFAULT_MODELS } from '$lib';
7
+ import type { ChatModel } from '$lib/helpers/types';
8
+ import { modelsState } from '$lib/state/models.svelte';
9
+
10
+ let triggerRef: HTMLDivElement | null = null;
11
+ const { update: updateNodes } = useNodes();
12
+
13
+ export function openAt(e: MouseEvent) {
14
+ const syntheticEvent = new MouseEvent('contextmenu', {
15
+ clientX: e.clientX,
16
+ clientY: e.clientY,
17
+ bubbles: true
18
+ });
19
+ (syntheticEvent as MouseEvent & { __contextMenuSynthetic?: true }).__contextMenuSynthetic =
20
+ true;
21
+ triggerRef?.dispatchEvent(syntheticEvent);
22
+ }
23
+
24
+ function handleNewChat() {
25
+ const newNodeId = `user-${crypto.randomUUID()}`;
26
+ const newNode: Node = {
27
+ id: newNodeId,
28
+ type: 'user',
29
+ position: {
30
+ x: 0,
31
+ y: 0
32
+ },
33
+ data: {
34
+ role: 'user',
35
+ selectedModels: modelsState.models.slice(0, MAX_DEFAULT_MODELS) as ChatModel[]
36
+ }
37
+ };
38
+ updateNodes((currentNodes) => {
39
+ const firstNode = currentNodes.find((node) => node.data.isFirstNode);
40
+ const updatedNodes = currentNodes.map((node) => {
41
+ if (node.id === firstNode?.id) {
42
+ return { ...node, data: { ...node.data, showWelcome: false } };
43
+ }
44
+ return node;
45
+ });
46
+ return [...updatedNodes, newNode];
47
+ });
48
+ }
49
+ </script>
50
+
51
+ <ContextMenu.Root>
52
+ <ContextMenu.Trigger bind:ref={triggerRef}>
53
+ <div class="absolute inset-0" aria-hidden="true"></div>
54
+ </ContextMenu.Trigger>
55
+ <ContextMenu.Content>
56
+ <ContextMenu.Item onclick={handleNewChat}>
57
+ <MessageCirclePlus />
58
+ New chat
59
+ </ContextMenu.Item>
60
+ </ContextMenu.Content>
61
+ </ContextMenu.Root>
src/lib/components/flow/actions/PanelCanvasActions.svelte CHANGED
@@ -65,7 +65,9 @@
65
  </Button>
66
  </Tooltip.Trigger>
67
  <Tooltip.Content>
68
- <p class="flex items-center gap-1"><MousePointer class="size-3.5" /> Free mode</p>
 
 
69
  </Tooltip.Content>
70
  </Tooltip.Root>
71
  <Tooltip.Root>
@@ -79,7 +81,9 @@
79
  </Button>
80
  </Tooltip.Trigger>
81
  <Tooltip.Content>
82
- <p class="flex items-center gap-1"><Spotlight class="size-3.5" /> Spotlight mode</p>
 
 
83
  </Tooltip.Content>
84
  </Tooltip.Root>
85
  </div>
 
65
  </Button>
66
  </Tooltip.Trigger>
67
  <Tooltip.Content>
68
+ <p class="flex items-center gap-1">
69
+ <MousePointer class="size-3.5" /> Free: Move the canvas freely
70
+ </p>
71
  </Tooltip.Content>
72
  </Tooltip.Root>
73
  <Tooltip.Root>
 
81
  </Button>
82
  </Tooltip.Trigger>
83
  <Tooltip.Content>
84
+ <p class="flex items-center gap-1">
85
+ <Spotlight class="size-3.5" /> Spotlight: Focus on the selected node
86
+ </p>
87
  </Tooltip.Content>
88
  </Tooltip.Root>
89
  </div>
src/lib/components/ui/context-menu/context-menu-checkbox-item.svelte ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
3
+ import CheckIcon from "@lucide/svelte/icons/check";
4
+ import { cn, type WithoutChildrenOrChild } from "$lib/utils.js";
5
+ import type { Snippet } from "svelte";
6
+
7
+ let {
8
+ ref = $bindable(null),
9
+ checked = $bindable(false),
10
+ indeterminate = $bindable(false),
11
+ class: className,
12
+ children: childrenProp,
13
+ ...restProps
14
+ }: WithoutChildrenOrChild<ContextMenuPrimitive.CheckboxItemProps> & {
15
+ children?: Snippet;
16
+ } = $props();
17
+ </script>
18
+
19
+ <ContextMenuPrimitive.CheckboxItem
20
+ bind:ref
21
+ bind:checked
22
+ bind:indeterminate
23
+ data-slot="context-menu-checkbox-item"
24
+ class={cn(
25
+ "data-highlighted:bg-accent data-highlighted:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 ps-8 pe-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
26
+ className
27
+ )}
28
+ {...restProps}
29
+ >
30
+ {#snippet children({ checked })}
31
+ <span
32
+ class="pointer-events-none absolute start-2 flex size-3.5 items-center justify-center"
33
+ >
34
+ {#if checked}
35
+ <CheckIcon class="size-4" />
36
+ {/if}
37
+ </span>
38
+ {@render childrenProp?.()}
39
+ {/snippet}
40
+ </ContextMenuPrimitive.CheckboxItem>
src/lib/components/ui/context-menu/context-menu-content.svelte ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
3
+ import { cn } from "$lib/utils.js";
4
+ import ContextMenuPortal from "./context-menu-portal.svelte";
5
+ import type { ComponentProps } from "svelte";
6
+ import type { WithoutChildrenOrChild } from "$lib/utils.js";
7
+
8
+ let {
9
+ ref = $bindable(null),
10
+ portalProps,
11
+ class: className,
12
+ ...restProps
13
+ }: ContextMenuPrimitive.ContentProps & {
14
+ portalProps?: WithoutChildrenOrChild<ComponentProps<typeof ContextMenuPortal>>;
15
+ } = $props();
16
+ </script>
17
+
18
+ <ContextMenuPortal {...portalProps}>
19
+ <ContextMenuPrimitive.Content
20
+ bind:ref
21
+ data-slot="context-menu-content"
22
+ class={cn(
23
+ "bg-popover text-popover-foreground 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-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--bits-context-menu-content-available-height) min-w-[8rem] origin-(--bits-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
24
+ className
25
+ )}
26
+ {...restProps}
27
+ />
28
+ </ContextMenuPortal>
src/lib/components/ui/context-menu/context-menu-group-heading.svelte ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
3
+ import { cn } from "$lib/utils.js";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ inset,
9
+ ...restProps
10
+ }: ContextMenuPrimitive.GroupHeadingProps & {
11
+ inset?: boolean;
12
+ } = $props();
13
+ </script>
14
+
15
+ <ContextMenuPrimitive.GroupHeading
16
+ bind:ref
17
+ data-slot="context-menu-group-heading"
18
+ data-inset={inset}
19
+ class={cn("text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:ps-8", className)}
20
+ {...restProps}
21
+ />
src/lib/components/ui/context-menu/context-menu-group.svelte ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
3
+
4
+ let { ref = $bindable(null), ...restProps }: ContextMenuPrimitive.GroupProps = $props();
5
+ </script>
6
+
7
+ <ContextMenuPrimitive.Group bind:ref data-slot="context-menu-group" {...restProps} />
src/lib/components/ui/context-menu/context-menu-item.svelte ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { ContextMenu as ContextMenuPrimitive } from 'bits-ui';
3
+ import { cn } from '$lib/utils.js';
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ inset,
9
+ variant = 'default',
10
+ ...restProps
11
+ }: ContextMenuPrimitive.ItemProps & {
12
+ inset?: boolean;
13
+ variant?: 'default' | 'destructive';
14
+ } = $props();
15
+ </script>
16
+
17
+ <ContextMenuPrimitive.Item
18
+ bind:ref
19
+ data-slot="context-menu-item"
20
+ data-inset={inset}
21
+ data-variant={variant}
22
+ class={cn(
23
+ "relative flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:ps-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:data-highlighted:bg-destructive/10 data-[variant=destructive]:data-highlighted:text-destructive dark:data-[variant=destructive]:data-highlighted:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:!text-destructive",
24
+ className
25
+ )}
26
+ {...restProps}
27
+ />
src/lib/components/ui/context-menu/context-menu-label.svelte ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ inset,
9
+ children,
10
+ ...restProps
11
+ }: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
12
+ inset?: boolean;
13
+ } = $props();
14
+ </script>
15
+
16
+ <div
17
+ bind:this={ref}
18
+ data-slot="context-menu-label"
19
+ data-inset={inset}
20
+ class={cn("text-foreground px-2 py-1.5 text-sm font-medium data-[inset]:ps-8", className)}
21
+ {...restProps}
22
+ >
23
+ {@render children?.()}
24
+ </div>
src/lib/components/ui/context-menu/context-menu-portal.svelte ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
3
+
4
+ let { ...restProps }: ContextMenuPrimitive.PortalProps = $props();
5
+ </script>
6
+
7
+ <ContextMenuPrimitive.Portal {...restProps} />
src/lib/components/ui/context-menu/context-menu-radio-group.svelte ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
3
+
4
+ let {
5
+ ref = $bindable(null),
6
+ value = $bindable(""),
7
+ ...restProps
8
+ }: ContextMenuPrimitive.RadioGroupProps = $props();
9
+ </script>
10
+
11
+ <ContextMenuPrimitive.RadioGroup
12
+ bind:ref
13
+ bind:value
14
+ data-slot="context-menu-radio-group"
15
+ {...restProps}
16
+ />
src/lib/components/ui/context-menu/context-menu-radio-item.svelte ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
3
+ import CircleIcon from "@lucide/svelte/icons/circle";
4
+ import { cn, type WithoutChild } from "$lib/utils.js";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ children: childrenProp,
10
+ ...restProps
11
+ }: WithoutChild<ContextMenuPrimitive.RadioItemProps> = $props();
12
+ </script>
13
+
14
+ <ContextMenuPrimitive.RadioItem
15
+ bind:ref
16
+ data-slot="context-menu-radio-item"
17
+ class={cn(
18
+ "data-highlighted:bg-accent data-highlighted:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 ps-8 pe-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
19
+ className
20
+ )}
21
+ {...restProps}
22
+ >
23
+ {#snippet children({ checked })}
24
+ <span
25
+ class="pointer-events-none absolute start-2 flex size-3.5 items-center justify-center"
26
+ >
27
+ {#if checked}
28
+ <CircleIcon class="size-2 fill-current" />
29
+ {/if}
30
+ </span>
31
+ {@render childrenProp?.({ checked })}
32
+ {/snippet}
33
+ </ContextMenuPrimitive.RadioItem>
src/lib/components/ui/context-menu/context-menu-separator.svelte ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
3
+ import { cn } from "$lib/utils.js";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ ...restProps
9
+ }: ContextMenuPrimitive.SeparatorProps = $props();
10
+ </script>
11
+
12
+ <ContextMenuPrimitive.Separator
13
+ bind:ref
14
+ data-slot="context-menu-separator"
15
+ class={cn("bg-border -mx-1 my-1 h-px", className)}
16
+ {...restProps}
17
+ />
src/lib/components/ui/context-menu/context-menu-shortcut.svelte ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { cn, type WithElementRef } from "$lib/utils.js";
3
+ import type { HTMLAttributes } from "svelte/elements";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ children,
9
+ ...restProps
10
+ }: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
11
+ </script>
12
+
13
+ <span
14
+ bind:this={ref}
15
+ data-slot="context-menu-shortcut"
16
+ class={cn("text-muted-foreground ms-auto text-xs tracking-widest", className)}
17
+ {...restProps}
18
+ >
19
+ {@render children?.()}
20
+ </span>
src/lib/components/ui/context-menu/context-menu-sub-content.svelte ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
3
+ import { cn } from "$lib/utils.js";
4
+
5
+ let {
6
+ ref = $bindable(null),
7
+ class: className,
8
+ ...restProps
9
+ }: ContextMenuPrimitive.SubContentProps = $props();
10
+ </script>
11
+
12
+ <ContextMenuPrimitive.SubContent
13
+ bind:ref
14
+ data-slot="context-menu-sub-content"
15
+ class={cn(
16
+ "bg-popover text-popover-foreground 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-end-2 data-[side=right]:slide-in-from-start-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--bits-context-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
17
+ className
18
+ )}
19
+ {...restProps}
20
+ />
src/lib/components/ui/context-menu/context-menu-sub-trigger.svelte ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
3
+ import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
4
+ import { cn, type WithoutChild } from "$lib/utils.js";
5
+
6
+ let {
7
+ ref = $bindable(null),
8
+ class: className,
9
+ inset,
10
+ children,
11
+ ...restProps
12
+ }: WithoutChild<ContextMenuPrimitive.SubTriggerProps> & {
13
+ inset?: boolean;
14
+ } = $props();
15
+ </script>
16
+
17
+ <ContextMenuPrimitive.SubTrigger
18
+ bind:ref
19
+ data-slot="context-menu-sub-trigger"
20
+ data-inset={inset}
21
+ class={cn(
22
+ "data-highlighted:bg-accent data-highlighted:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:ps-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
23
+ className
24
+ )}
25
+ {...restProps}
26
+ >
27
+ {@render children?.()}
28
+ <ChevronRightIcon class="ms-auto" />
29
+ </ContextMenuPrimitive.SubTrigger>
src/lib/components/ui/context-menu/context-menu-sub.svelte ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
3
+
4
+ let { open = $bindable(false), ...restProps }: ContextMenuPrimitive.SubProps = $props();
5
+ </script>
6
+
7
+ <ContextMenuPrimitive.Sub bind:open {...restProps} />
src/lib/components/ui/context-menu/context-menu-trigger.svelte ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
3
+
4
+ let { ref = $bindable(null), ...restProps }: ContextMenuPrimitive.TriggerProps = $props();
5
+ </script>
6
+
7
+ <ContextMenuPrimitive.Trigger bind:ref data-slot="context-menu-trigger" {...restProps} />
src/lib/components/ui/context-menu/context-menu.svelte ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
3
+
4
+ let { open = $bindable(false), ...restProps }: ContextMenuPrimitive.RootProps = $props();
5
+ </script>
6
+
7
+ <ContextMenuPrimitive.Root bind:open {...restProps} />
src/lib/components/ui/context-menu/index.ts ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Root from "./context-menu.svelte";
2
+ import Sub from "./context-menu-sub.svelte";
3
+ import Portal from "./context-menu-portal.svelte";
4
+ import Trigger from "./context-menu-trigger.svelte";
5
+ import Group from "./context-menu-group.svelte";
6
+ import RadioGroup from "./context-menu-radio-group.svelte";
7
+ import Item from "./context-menu-item.svelte";
8
+ import GroupHeading from "./context-menu-group-heading.svelte";
9
+ import Content from "./context-menu-content.svelte";
10
+ import Shortcut from "./context-menu-shortcut.svelte";
11
+ import RadioItem from "./context-menu-radio-item.svelte";
12
+ import Separator from "./context-menu-separator.svelte";
13
+ import SubContent from "./context-menu-sub-content.svelte";
14
+ import SubTrigger from "./context-menu-sub-trigger.svelte";
15
+ import CheckboxItem from "./context-menu-checkbox-item.svelte";
16
+ import Label from "./context-menu-label.svelte";
17
+
18
+ export {
19
+ Root,
20
+ Sub,
21
+ Portal,
22
+ Item,
23
+ GroupHeading,
24
+ Label,
25
+ Group,
26
+ Trigger,
27
+ Content,
28
+ Shortcut,
29
+ Separator,
30
+ RadioItem,
31
+ SubContent,
32
+ SubTrigger,
33
+ RadioGroup,
34
+ CheckboxItem,
35
+ //
36
+ Root as ContextMenu,
37
+ Sub as ContextMenuSub,
38
+ Portal as ContextMenuPortal,
39
+ Item as ContextMenuItem,
40
+ GroupHeading as ContextMenuGroupHeading,
41
+ Group as ContextMenuGroup,
42
+ Content as ContextMenuContent,
43
+ Trigger as ContextMenuTrigger,
44
+ Shortcut as ContextMenuShortcut,
45
+ RadioItem as ContextMenuRadioItem,
46
+ Separator as ContextMenuSeparator,
47
+ RadioGroup as ContextMenuRadioGroup,
48
+ SubContent as ContextMenuSubContent,
49
+ SubTrigger as ContextMenuSubTrigger,
50
+ CheckboxItem as ContextMenuCheckboxItem,
51
+ Label as ContextMenuLabel,
52
+ };
src/routes/+page.svelte CHANGED
@@ -20,7 +20,7 @@
20
  import { MAX_DEFAULT_MODELS } from '$lib';
21
  import PanelCanvasActions from '$lib/components/flow/actions/PanelCanvasActions.svelte';
22
  import { viewState } from '$lib/state/view.svelte';
23
- import PanelLeftMenu from '$lib/components/flow/actions/PanelLeftMenu.svelte';
24
 
25
  const nodeTypes = {
26
  user: User,
@@ -36,6 +36,7 @@
36
  position: { x: 0, y: 0 },
37
  data: {
38
  isFirstNode: true,
 
39
  selectedModels: modelsState.models.slice(0, MAX_DEFAULT_MODELS) as ChatModel[]
40
  }
41
  }
@@ -50,6 +51,7 @@
50
 
51
  let nodes = $state.raw<Node[]>(initialNodes);
52
  let edges = $state.raw<Edge[]>([]);
 
53
  </script>
54
 
55
  <svelte:window
@@ -60,6 +62,7 @@
60
  // }
61
  }}
62
  />
 
63
  <div class="h-screen w-screen overflow-hidden">
64
  <SvelteFlow
65
  bind:nodes
@@ -77,8 +80,14 @@
77
  panOnDrag={viewState.draggable}
78
  onbeforedelete={() => Promise.resolve(false)}
79
  defaultEdgeOptions={{ type: 'smoothstep' }}
 
 
 
 
 
80
  class="bg-background!"
81
  >
 
82
  <FitViewOnResize {initialNodes} />
83
  {#key mode.current}
84
  <MiniMap
 
20
  import { MAX_DEFAULT_MODELS } from '$lib';
21
  import PanelCanvasActions from '$lib/components/flow/actions/PanelCanvasActions.svelte';
22
  import { viewState } from '$lib/state/view.svelte';
23
+ import ContextMenuComponent from '$lib/components/flow/actions/ContextMenu.svelte';
24
 
25
  const nodeTypes = {
26
  user: User,
 
36
  position: { x: 0, y: 0 },
37
  data: {
38
  isFirstNode: true,
39
+ showWelcome: true,
40
  selectedModels: modelsState.models.slice(0, MAX_DEFAULT_MODELS) as ChatModel[]
41
  }
42
  }
 
51
 
52
  let nodes = $state.raw<Node[]>(initialNodes);
53
  let edges = $state.raw<Edge[]>([]);
54
+ let contextMenuRef: ContextMenuComponent;
55
  </script>
56
 
57
  <svelte:window
 
62
  // }
63
  }}
64
  />
65
+
66
  <div class="h-screen w-screen overflow-hidden">
67
  <SvelteFlow
68
  bind:nodes
 
80
  panOnDrag={viewState.draggable}
81
  onbeforedelete={() => Promise.resolve(false)}
82
  defaultEdgeOptions={{ type: 'smoothstep' }}
83
+ oncontextmenu={(e) => {
84
+ if ((e as MouseEvent & { __contextMenuSynthetic?: true }).__contextMenuSynthetic) return;
85
+ e.preventDefault();
86
+ contextMenuRef?.openAt(e);
87
+ }}
88
  class="bg-background!"
89
  >
90
+ <ContextMenuComponent bind:this={contextMenuRef} />
91
  <FitViewOnResize {initialNodes} />
92
  {#key mode.current}
93
  <MiniMap