enzostvs HF Staff commited on
Commit
1302517
·
1 Parent(s): 478187a

add manual controls

Browse files
package.json CHANGED
@@ -50,6 +50,7 @@
50
  "elkjs": "^0.11.0",
51
  "lint": "^0.8.19",
52
  "mode-watcher": "^1.1.0",
 
53
  "svelte-markdown": "^0.4.1",
54
  "tailwind-merge": "^3.4.0"
55
  }
 
50
  "elkjs": "^0.11.0",
51
  "lint": "^0.8.19",
52
  "mode-watcher": "^1.1.0",
53
+ "svelte-highlight": "^7.9.0",
54
  "svelte-markdown": "^0.4.1",
55
  "tailwind-merge": "^3.4.0"
56
  }
pnpm-lock.yaml CHANGED
@@ -32,6 +32,9 @@ importers:
32
  mode-watcher:
33
  specifier: ^1.1.0
34
  version: 1.1.0(svelte@5.50.1)
 
 
 
35
  svelte-markdown:
36
  specifier: ^0.4.1
37
  version: 0.4.1(svelte@5.50.1)
@@ -1322,6 +1325,10 @@ packages:
1322
  resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
1323
  engines: {node: '>= 0.4'}
1324
 
 
 
 
 
1325
  http-signature@1.2.0:
1326
  resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==}
1327
  engines: {node: '>=0.8', npm: '>=1.3.7'}
@@ -1989,6 +1996,9 @@ packages:
1989
  svelte:
1990
  optional: true
1991
 
 
 
 
1992
  svelte-markdown@0.4.1:
1993
  resolution: {integrity: sha512-pOlLY6EruKJaWI9my/2bKX8PdTeP5CM0s4VMmwmC2prlOkjAf+AOmTM4wW/l19Y6WZ87YmP8+ZCJCCwBChWjYw==}
1994
  peerDependencies:
@@ -3281,6 +3291,8 @@ snapshots:
3281
  dependencies:
3282
  function-bind: 1.1.2
3283
 
 
 
3284
  http-signature@1.2.0:
3285
  dependencies:
3286
  assert-plus: 1.0.0
@@ -3880,6 +3892,10 @@ snapshots:
3880
  optionalDependencies:
3881
  svelte: 5.50.1
3882
 
 
 
 
 
3883
  svelte-markdown@0.4.1(svelte@5.50.1):
3884
  dependencies:
3885
  '@types/marked': 5.0.2
 
32
  mode-watcher:
33
  specifier: ^1.1.0
34
  version: 1.1.0(svelte@5.50.1)
35
+ svelte-highlight:
36
+ specifier: ^7.9.0
37
+ version: 7.9.0
38
  svelte-markdown:
39
  specifier: ^0.4.1
40
  version: 0.4.1(svelte@5.50.1)
 
1325
  resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
1326
  engines: {node: '>= 0.4'}
1327
 
1328
+ highlight.js@11.11.1:
1329
+ resolution: {integrity: sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==}
1330
+ engines: {node: '>=12.0.0'}
1331
+
1332
  http-signature@1.2.0:
1333
  resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==}
1334
  engines: {node: '>=0.8', npm: '>=1.3.7'}
 
1996
  svelte:
1997
  optional: true
1998
 
1999
+ svelte-highlight@7.9.0:
2000
+ resolution: {integrity: sha512-226LBTtvTnM2L2JkQq8mZeKEeMfPLYyta7VxZatFT4UPX5zdHEerKeMTvrfbxm7MVTWc7TPThsNoVdhWC177KQ==}
2001
+
2002
  svelte-markdown@0.4.1:
2003
  resolution: {integrity: sha512-pOlLY6EruKJaWI9my/2bKX8PdTeP5CM0s4VMmwmC2prlOkjAf+AOmTM4wW/l19Y6WZ87YmP8+ZCJCCwBChWjYw==}
2004
  peerDependencies:
 
3291
  dependencies:
3292
  function-bind: 1.1.2
3293
 
3294
+ highlight.js@11.11.1: {}
3295
+
3296
  http-signature@1.2.0:
3297
  dependencies:
3298
  assert-plus: 1.0.0
 
3892
  optionalDependencies:
3893
  svelte: 5.50.1
3894
 
3895
+ svelte-highlight@7.9.0:
3896
+ dependencies:
3897
+ highlight.js: 11.11.1
3898
+
3899
  svelte-markdown@0.4.1(svelte@5.50.1):
3900
  dependencies:
3901
  '@types/marked': 5.0.2
src/lib/components/chat/Assistant.svelte CHANGED
@@ -26,7 +26,7 @@
26
  </script>
27
 
28
  <article class="w-full rounded-3xl border border-border bg-background p-5 shadow-lg/5 lg:w-[600px]">
29
- <div class="nodrag cursor-auto">
30
  <header class="mb-3 flex items-center justify-between">
31
  <div class="flex flex-wrap items-center gap-1">
32
  <div
 
26
  </script>
27
 
28
  <article class="w-full rounded-3xl border border-border bg-background p-5 shadow-lg/5 lg:w-[600px]">
29
+ <div class="nodrag pointer-events-auto cursor-auto">
30
  <header class="mb-3 flex items-center justify-between">
31
  <div class="flex flex-wrap items-center gap-1">
32
  <div
src/lib/components/chat/Message.svelte CHANGED
@@ -28,7 +28,9 @@
28
  };
29
  </script>
30
 
31
- <main class="cursor-auto p-1 text-lg leading-relaxed text-accent-foreground select-auto">
 
 
32
  {#if message?.role === 'user'}
33
  <p class="">
34
  {message.content}
 
28
  };
29
  </script>
30
 
31
+ <main
32
+ class="pointer-events-auto cursor-auto p-1 text-lg leading-relaxed text-accent-foreground select-text"
33
+ >
34
  {#if message?.role === 'user'}
35
  <p class="">
36
  {message.content}
src/lib/components/chat/User.svelte CHANGED
@@ -23,13 +23,13 @@
23
  import { SUGGESTIONS_PROMPT } from '$lib/consts';
24
  import { breakpointsState } from '$lib/state/breakpoints.svelte';
25
 
26
- let { id }: NodeProps = $props();
27
 
28
  // svelte-ignore state_referenced_locally
29
  const nodeData = useNodesData(id);
30
  const { update: updateNodes } = useNodes();
31
  const { update: updateEdges } = useEdges();
32
- const { fitView, updateNodeData } = useSvelteFlow();
33
 
34
  let selectedModels = $derived<ChatModel[]>(
35
  (nodeData.current?.data.selectedModels as ChatModel[]) ?? []
@@ -185,7 +185,7 @@
185
  </script>
186
 
187
  <article class="w-full rounded-3xl border border-border bg-background p-5 shadow-lg/5 lg:w-[600px]">
188
- <div class="nodrag cursor-auto">
189
  <header class="mb-3 flex items-center justify-between">
190
  <div class="flex flex-wrap items-center gap-1">
191
  {#each selectedModels as model}
 
23
  import { SUGGESTIONS_PROMPT } from '$lib/consts';
24
  import { breakpointsState } from '$lib/state/breakpoints.svelte';
25
 
26
+ let { id, selected }: NodeProps = $props();
27
 
28
  // svelte-ignore state_referenced_locally
29
  const nodeData = useNodesData(id);
30
  const { update: updateNodes } = useNodes();
31
  const { update: updateEdges } = useEdges();
32
+ const { updateNodeData } = useSvelteFlow();
33
 
34
  let selectedModels = $derived<ChatModel[]>(
35
  (nodeData.current?.data.selectedModels as ChatModel[]) ?? []
 
185
  </script>
186
 
187
  <article class="w-full rounded-3xl border border-border bg-background p-5 shadow-lg/5 lg:w-[600px]">
188
+ <div class="nodrag pointer-events-auto cursor-auto">
189
  <header class="mb-3 flex items-center justify-between">
190
  <div class="flex flex-wrap items-center gap-1">
191
  {#each selectedModels as model}
src/lib/components/chat/markdown/Code.svelte CHANGED
@@ -1,4 +1,9 @@
1
  <script lang="ts">
 
 
 
 
 
2
  let { lang, text }: { lang?: string; text: string } = $props();
3
  </script>
4
 
@@ -8,7 +13,14 @@
8
  <span class="font-mono text-[11px] text-muted-foreground">{lang}</span>
9
  </div>
10
  {/if}
11
- <pre class="overflow-x-auto p-3"><code
12
- class="font-mono text-[13px] leading-relaxed text-foreground/90">{text}</code
13
- ></pre>
 
 
 
 
 
 
 
14
  </div>
 
1
  <script lang="ts">
2
+ import Button from '$lib/components/ui/button/button.svelte';
3
+ import { Copy } from '@lucide/svelte';
4
+ import { HighlightAuto } from 'svelte-highlight';
5
+ import 'svelte-highlight/styles/github.css';
6
+
7
  let { lang, text }: { lang?: string; text: string } = $props();
8
  </script>
9
 
 
13
  <span class="font-mono text-[11px] text-muted-foreground">{lang}</span>
14
  </div>
15
  {/if}
16
+ <div class="group relative">
17
+ <HighlightAuto code={text} class="font-mono text-[13px] leading-relaxed" />
18
+ <Button
19
+ variant="outline"
20
+ size="icon-xs"
21
+ class="absolute top-2 right-2 opacity-0 group-hover:opacity-100"
22
+ >
23
+ <Copy />
24
+ </Button>
25
+ </div>
26
  </div>
src/lib/components/flow/FitViewOnResize.svelte CHANGED
@@ -4,6 +4,7 @@
4
  import { useNodes, useEdges } from '@xyflow/svelte';
5
  import { useSvelteFlow } from '@xyflow/svelte';
6
  import { breakpointsState } from '$lib/state/breakpoints.svelte';
 
7
 
8
  let { initialNodes }: { initialNodes: Node[] } = $props();
9
 
@@ -19,6 +20,45 @@
19
 
20
  let lastLayoutKey = $state<string | null>(null);
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  /** Get the actual measured height of a node, or fallback */
23
  function getMeasuredHeight(node: Node): number {
24
  return node.measured?.height ?? DEFAULT_HEIGHT;
@@ -67,6 +107,7 @@
67
  });
68
 
69
  function handleWindowResize() {
 
70
  fitView({
71
  maxZoom: 1,
72
  minZoom: breakpointsState.isMobile ? 1 : 0.8,
@@ -81,6 +122,7 @@
81
 
82
  onDestroy(() => {
83
  window.removeEventListener('resize', handleWindowResize);
 
84
  });
85
 
86
  /**
@@ -197,11 +239,6 @@
197
  );
198
  }
199
 
200
- fitView({
201
- maxZoom: 1,
202
- minZoom: breakpointsState.isMobile ? 1 : 0.8,
203
- interpolate: 'smooth',
204
- duration: 250
205
- });
206
  }
207
  </script>
 
4
  import { useNodes, useEdges } from '@xyflow/svelte';
5
  import { useSvelteFlow } from '@xyflow/svelte';
6
  import { breakpointsState } from '$lib/state/breakpoints.svelte';
7
+ import { viewState } from '$lib/state/view.svelte';
8
 
9
  let { initialNodes }: { initialNodes: Node[] } = $props();
10
 
 
20
 
21
  let lastLayoutKey = $state<string | null>(null);
22
 
23
+ const FIT_VIEW_THROTTLE_MS = 500;
24
+ let fitViewTimer: ReturnType<typeof setTimeout> | null = null;
25
+ let lastFitViewTime = 0;
26
+
27
+ function throttledFitView() {
28
+ if (viewState.freeView) return;
29
+
30
+ const now = Date.now();
31
+ const elapsed = now - lastFitViewTime;
32
+
33
+ if (fitViewTimer) {
34
+ clearTimeout(fitViewTimer);
35
+ fitViewTimer = null;
36
+ }
37
+
38
+ if (elapsed >= FIT_VIEW_THROTTLE_MS) {
39
+ lastFitViewTime = now;
40
+ fitView({
41
+ maxZoom: 1,
42
+ minZoom: breakpointsState.isMobile ? 1 : 0.7,
43
+ interpolate: 'smooth',
44
+ duration: 250
45
+ });
46
+ } else {
47
+ fitViewTimer = setTimeout(() => {
48
+ lastFitViewTime = Date.now();
49
+ fitViewTimer = null;
50
+ if (!viewState.freeView) {
51
+ fitView({
52
+ maxZoom: 1,
53
+ minZoom: breakpointsState.isMobile ? 1 : 0.7,
54
+ interpolate: 'smooth',
55
+ duration: 250
56
+ });
57
+ }
58
+ }, FIT_VIEW_THROTTLE_MS - elapsed);
59
+ }
60
+ }
61
+
62
  /** Get the actual measured height of a node, or fallback */
63
  function getMeasuredHeight(node: Node): number {
64
  return node.measured?.height ?? DEFAULT_HEIGHT;
 
107
  });
108
 
109
  function handleWindowResize() {
110
+ if (viewState.freeView) return;
111
  fitView({
112
  maxZoom: 1,
113
  minZoom: breakpointsState.isMobile ? 1 : 0.8,
 
122
 
123
  onDestroy(() => {
124
  window.removeEventListener('resize', handleWindowResize);
125
+ if (fitViewTimer) clearTimeout(fitViewTimer);
126
  });
127
 
128
  /**
 
239
  );
240
  }
241
 
242
+ throttledFitView();
 
 
 
 
 
243
  }
244
  </script>
src/lib/components/flow/actions/PanelCanvasActions.svelte ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { Maximize, Lock, LockOpen, Minus, Plus } from '@lucide/svelte';
3
+ import { Panel } from '@xyflow/svelte';
4
+
5
+ import { Button } from '$lib/components/ui/button';
6
+ import { breakpointsState } from '$lib/state/breakpoints.svelte';
7
+ import { viewState } from '$lib/state/view.svelte';
8
+ import { useSvelteFlow } from '@xyflow/svelte';
9
+ import HFLogo from '$lib/assets/hf-logo.svg';
10
+ import { Separator } from '$lib/components/ui/separator';
11
+
12
+ const { fitView, zoomIn, zoomOut, getZoom, getViewport } = useSvelteFlow();
13
+ </script>
14
+
15
+ <Panel position="bottom-left" class="space-y-2 p-1 lg:p-2">
16
+ <div
17
+ class="inline-flex w-fit flex-col gap-0.5 rounded-md border border-border bg-background p-0.5"
18
+ >
19
+ <Button
20
+ variant="ghost"
21
+ size="icon-xs"
22
+ onclick={() => zoomIn({ duration: 200 })}
23
+ disabled={getZoom() >= 1}
24
+ >
25
+ <Plus />
26
+ </Button>
27
+ <Button
28
+ variant="ghost"
29
+ size="icon-xs"
30
+ onclick={() => zoomOut({ duration: 200 })}
31
+ disabled={getZoom() <= 0.7}
32
+ >
33
+ <Minus />
34
+ </Button>
35
+ <Button
36
+ variant="ghost"
37
+ size="icon-xs"
38
+ onclick={() =>
39
+ fitView({
40
+ maxZoom: 1,
41
+ minZoom: breakpointsState.isMobile ? 1 : 0.7,
42
+ interpolate: 'smooth',
43
+ duration: 250
44
+ })}
45
+ >
46
+ <Maximize />
47
+ </Button>
48
+ <Separator />
49
+ <Button
50
+ variant="ghost"
51
+ size="icon-xs"
52
+ onclick={() => (viewState.freeView = !viewState.freeView)}
53
+ >
54
+ {#if viewState.freeView}
55
+ <Lock class="fill-transparent!" />
56
+ {:else}
57
+ <LockOpen class="fill-transparent!" />
58
+ {/if}
59
+ </Button>
60
+ </div>
61
+ <div
62
+ class="flex items-center justify-center gap-1.5 rounded-lg border border-border bg-background py-1.5 pr-3.5 pl-2.5 shadow-xs"
63
+ >
64
+ <img src={HFLogo} alt="HF Logo" class="size-5 lg:size-7" />
65
+ <p class="text-xs text-accent-foreground">Hugging Face Playground</p>
66
+ </div>
67
+ </Panel>
src/lib/components/flow/actions/PanelRightActions.svelte CHANGED
@@ -1,5 +1,5 @@
1
  <script lang="ts">
2
- import { Cog, Contrast, Monitor, Moon, RefreshCcw, Sun } from '@lucide/svelte';
3
  import { Panel } from '@xyflow/svelte';
4
  import { setMode } from 'mode-watcher';
5
 
@@ -18,7 +18,7 @@
18
  }
19
  </script>
20
 
21
- <Panel position="top-right" class="flex items-center justify-end gap-2 p-1 lg:p-3">
22
  <Button variant="outline" class="" onclick={handleReset}>
23
  <RefreshCcw />
24
  Start new chat
 
1
  <script lang="ts">
2
+ import { Cog, Contrast, Monitor, Moon, Move, RefreshCcw, Sun } from '@lucide/svelte';
3
  import { Panel } from '@xyflow/svelte';
4
  import { setMode } from 'mode-watcher';
5
 
 
18
  }
19
  </script>
20
 
21
+ <Panel position="top-right" class="flex items-center justify-end gap-2 p-1 lg:p-2">
22
  <Button variant="outline" class="" onclick={handleReset}>
23
  <RefreshCcw />
24
  Start new chat
src/lib/components/ui/checkbox/checkbox.svelte ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { Checkbox as CheckboxPrimitive } from 'bits-ui';
3
+ import CheckIcon from '@lucide/svelte/icons/check';
4
+ import MinusIcon from '@lucide/svelte/icons/minus';
5
+ import { cn, type WithoutChildrenOrChild } from '$lib/utils.js';
6
+
7
+ let {
8
+ ref = $bindable(null),
9
+ checked = $bindable(false),
10
+ indeterminate = $bindable(false),
11
+ class: className,
12
+ ...restProps
13
+ }: WithoutChildrenOrChild<CheckboxPrimitive.RootProps> = $props();
14
+ </script>
15
+
16
+ <CheckboxPrimitive.Root
17
+ bind:ref
18
+ data-slot="checkbox"
19
+ class={cn(
20
+ 'peer flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-[4px] border border-input shadow-xs transition-shadow outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:aria-invalid:ring-destructive/40 dark:data-[state=checked]:bg-primary',
21
+ className
22
+ )}
23
+ bind:checked
24
+ bind:indeterminate
25
+ {...restProps}
26
+ >
27
+ {#snippet children({ checked, indeterminate })}
28
+ <div data-slot="checkbox-indicator" class="text-current transition-none">
29
+ {#if checked}
30
+ <CheckIcon class="size-3.5" />
31
+ {:else if indeterminate}
32
+ <MinusIcon class="size-3.5" />
33
+ {/if}
34
+ </div>
35
+ {/snippet}
36
+ </CheckboxPrimitive.Root>
src/lib/components/ui/checkbox/index.ts ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import Root from "./checkbox.svelte";
2
+ export {
3
+ Root,
4
+ //
5
+ Root as Checkbox,
6
+ };
src/lib/state/view.svelte.ts ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ export const viewState = $state({
2
+ freeView: false
3
+ });
src/routes/+page.svelte CHANGED
@@ -3,10 +3,11 @@
3
  SvelteFlow,
4
  Background,
5
  MiniMap,
6
- Panel,
7
  BackgroundVariant,
 
8
  type Node,
9
- type Edge
 
10
  } from '@xyflow/svelte';
11
  import '@xyflow/svelte/dist/style.css';
12
  import { mode } from 'mode-watcher';
@@ -15,12 +16,12 @@
15
  import { tokenModalState } from '$lib/state/token-modal.svelte';
16
  import User from '$lib/components/chat/User.svelte';
17
  import Assistant from '$lib/components/chat/Assistant.svelte';
18
- import HFLogo from '$lib/assets/hf-logo.svg';
19
  import type { ChatModel } from '$lib/helpers/types';
20
  import FitViewOnResize from '$lib/components/flow/FitViewOnResize.svelte';
21
  import PanelRightActions from '$lib/components/flow/actions/PanelRightActions.svelte';
22
  import { MAX_DEFAULT_MODELS } from '$lib';
23
  import { breakpointsState } from '$lib/state/breakpoints.svelte';
 
24
 
25
  const nodeTypes = {
26
  user: User,
@@ -60,17 +61,21 @@
60
  bind:nodes
61
  bind:edges
62
  {nodeTypes}
63
- minZoom={0.8}
64
- maxZoom={1.5}
65
  fitView
 
 
 
66
  colorMode={mode.current}
67
  proOptions={{ hideAttribution: true }}
68
  fitViewOptions={{
69
  maxZoom: 1,
70
- minZoom: breakpointsState.isMobile ? 1 : 0.8,
71
  interpolate: 'smooth',
72
  duration: 500
73
  }}
 
74
  defaultEdgeOptions={{ type: 'smoothstep' }}
75
  class="bg-background!"
76
  >
@@ -86,13 +91,23 @@
86
  patternColor={mode.current === 'dark' ? 'rgba(255, 255, 255, 0.04)' : 'rgba(0, 0, 0, 0.04)'}
87
  class="bg-background!"
88
  />
 
 
 
 
 
 
 
 
 
89
  <PanelRightActions canReset={nodes.length > 1} />
90
- <Panel
91
- position="bottom-left"
92
- class="flex items-center justify-center gap-1.5 rounded-lg border border-border bg-background py-1.5 pr-3.5 pl-2.5 shadow-xs"
93
- >
94
- <img src={HFLogo} alt="HF Logo" class="size-5 lg:size-7" />
95
- <p class="text-xs text-accent-foreground">Hugging Face Playground</p>
96
- </Panel>
 
97
  </SvelteFlow>
98
  </div>
 
3
  SvelteFlow,
4
  Background,
5
  MiniMap,
 
6
  BackgroundVariant,
7
+ PanOnScrollMode,
8
  type Node,
9
+ type Edge,
10
+ useSvelteFlow
11
  } from '@xyflow/svelte';
12
  import '@xyflow/svelte/dist/style.css';
13
  import { mode } from 'mode-watcher';
 
16
  import { tokenModalState } from '$lib/state/token-modal.svelte';
17
  import User from '$lib/components/chat/User.svelte';
18
  import Assistant from '$lib/components/chat/Assistant.svelte';
 
19
  import type { ChatModel } from '$lib/helpers/types';
20
  import FitViewOnResize from '$lib/components/flow/FitViewOnResize.svelte';
21
  import PanelRightActions from '$lib/components/flow/actions/PanelRightActions.svelte';
22
  import { MAX_DEFAULT_MODELS } from '$lib';
23
  import { breakpointsState } from '$lib/state/breakpoints.svelte';
24
+ import PanelCanvasActions from '$lib/components/flow/actions/PanelCanvasActions.svelte';
25
 
26
  const nodeTypes = {
27
  user: User,
 
61
  bind:nodes
62
  bind:edges
63
  {nodeTypes}
64
+ minZoom={breakpointsState.isMobile ? 1 : 0.7}
65
+ maxZoom={breakpointsState.isMobile ? 1 : 1}
66
  fitView
67
+ zoomOnScroll={false}
68
+ panOnScroll={true}
69
+ panOnScrollMode={PanOnScrollMode.Free}
70
  colorMode={mode.current}
71
  proOptions={{ hideAttribution: true }}
72
  fitViewOptions={{
73
  maxZoom: 1,
74
+ minZoom: breakpointsState.isMobile ? 1 : 0.7,
75
  interpolate: 'smooth',
76
  duration: 500
77
  }}
78
+ onbeforedelete={() => Promise.resolve(false)}
79
  defaultEdgeOptions={{ type: 'smoothstep' }}
80
  class="bg-background!"
81
  >
 
91
  patternColor={mode.current === 'dark' ? 'rgba(255, 255, 255, 0.04)' : 'rgba(0, 0, 0, 0.04)'}
92
  class="bg-background!"
93
  />
94
+ <!-- <Controls class="bottom-16! left-2.5! max-lg:hidden" showLock={false}>
95
+ <ControlButton onclick={() => (viewState.freeView = !viewState.freeView)}>
96
+ {#if viewState.freeView}
97
+ <Lock class="fill-transparent!" />
98
+ {:else}
99
+ <LockOpen class="fill-transparent!" />
100
+ {/if}
101
+ </ControlButton>
102
+ </Controls> -->
103
  <PanelRightActions canReset={nodes.length > 1} />
104
+ <PanelCanvasActions />
105
+ <!-- <Panel position="bottom-right" class="p-1 max-lg:hidden lg:p-2 lg:pb-40">
106
+ <Button variant="outline" onclick={() => (viewState.freeView = !viewState.freeView)}>
107
+ <Checkbox checked={viewState.freeView} />
108
+ <Move />
109
+ Free view
110
+ </Button>
111
+ </Panel> -->
112
  </SvelteFlow>
113
  </div>