enzostvs HF Staff commited on
Commit
c4ae44d
·
1 Parent(s): 5e1ca8c

Show preview + autosize textarea

Browse files
src/lib/actions/autosize.ts ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const DEFAULT_MIN_ROWS = 1;
2
+
3
+ export function autosize(
4
+ node: HTMLTextAreaElement,
5
+ options?: { minRows?: number; maxRows?: number; value?: string }
6
+ ) {
7
+ const minRows = options?.minRows ?? DEFAULT_MIN_ROWS;
8
+ const maxRows = options?.maxRows;
9
+
10
+ function resize() {
11
+ node.style.height = 'auto';
12
+ const lineHeight = parseInt(getComputedStyle(node).lineHeight, 10) || 24;
13
+ const minHeight = lineHeight * minRows;
14
+ let height = Math.max(minHeight, node.scrollHeight);
15
+ if (maxRows != null) {
16
+ const maxHeight = lineHeight * maxRows;
17
+ height = Math.min(height, maxHeight);
18
+ node.style.overflowY = height >= maxHeight ? 'auto' : 'hidden';
19
+ } else {
20
+ node.style.overflowY = '';
21
+ }
22
+ node.style.height = `${height}px`;
23
+ }
24
+
25
+ node.addEventListener('input', resize);
26
+ resize();
27
+
28
+ return {
29
+ update() {
30
+ resize();
31
+ },
32
+ destroy() {
33
+ node.removeEventListener('input', resize);
34
+ node.style.height = 'auto';
35
+ }
36
+ };
37
+ }
src/lib/components/chat/User.svelte CHANGED
@@ -27,6 +27,7 @@
27
  import Welcome from './Welcome.svelte';
28
  import ListModels from '$lib/components/model/ListModels.svelte';
29
  import { breakpointsState } from '$lib/state/breakpoints.svelte';
 
30
 
31
  let { id }: NodeProps = $props();
32
 
@@ -217,6 +218,7 @@
217
  {:else}
218
  <footer class="flex flex-col items-end transition-all duration-300">
219
  <textarea
 
220
  name="message"
221
  id="message"
222
  placeholder="Ask me anything..."
 
27
  import Welcome from './Welcome.svelte';
28
  import ListModels from '$lib/components/model/ListModels.svelte';
29
  import { breakpointsState } from '$lib/state/breakpoints.svelte';
30
+ import { autosize } from '$lib/actions/autosize';
31
 
32
  let { id }: NodeProps = $props();
33
 
 
218
  {:else}
219
  <footer class="flex flex-col items-end transition-all duration-300">
220
  <textarea
221
+ use:autosize={{ minRows: 1, maxRows: 8, value: prompt }}
222
  name="message"
223
  id="message"
224
  placeholder="Ask me anything..."
src/lib/components/chat/markdown/Code.svelte CHANGED
@@ -5,6 +5,7 @@
5
  import { mode } from 'mode-watcher';
6
  import githubDarkUrl from 'svelte-highlight/styles/github-dark.css?url';
7
  import githubUrl from 'svelte-highlight/styles/github.css?url';
 
8
 
9
  let {
10
  lang,
@@ -20,6 +21,19 @@
20
  setTimeout(() => (copiedCode = false), 2000);
21
  }
22
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  $effect(() => {
24
  const themeUrl = mode.current === 'dark' ? githubDarkUrl : githubUrl;
25
  let link = document.getElementById('highlight-theme') as HTMLLinkElement | null;
@@ -31,6 +45,10 @@
31
  }
32
  link.href = themeUrl;
33
  });
 
 
 
 
34
  </script>
35
 
36
  <div class="overflow-hidden {className}">
@@ -38,22 +56,42 @@
38
  <div
39
  class="flex items-center justify-between border-b border-border/60 bg-muted px-3 py-1.5 dark:bg-accent/30"
40
  >
41
- <span class="font-mono text-sm text-muted-foreground">{lang}</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  </div>
43
  {/if}
44
- <div class="group relative">
45
- <HighlightAuto code={text} class="font-mono text-sm leading-relaxed" />
46
- <Button
47
- variant="outline"
48
- class="absolute top-2 right-2 opacity-0 group-hover:opacity-100"
49
- size="icon-xs"
50
- onclick={() => copy(text)}
51
- >
52
- {#if copiedCode}
53
- <Check class="size-3.5 text-green-500" />
54
- {:else}
55
- <Copy class="size-3.5" />
56
- {/if}
57
- </Button>
58
- </div>
59
  </div>
 
5
  import { mode } from 'mode-watcher';
6
  import githubDarkUrl from 'svelte-highlight/styles/github-dark.css?url';
7
  import githubUrl from 'svelte-highlight/styles/github.css?url';
8
+ import Switch from '$lib/components/ui/switch/switch.svelte';
9
 
10
  let {
11
  lang,
 
21
  setTimeout(() => (copiedCode = false), 2000);
22
  }
23
 
24
+ function hasStrictHtml5Doctype(input: string): boolean {
25
+ if (!input) return false;
26
+ const withoutBOM = input.replace(/^\uFEFF/, '');
27
+ const trimmed = withoutBOM.trimStart();
28
+ // Strict HTML5 doctype: <!doctype html> with optional whitespace before >
29
+ return /^<!doctype\s+html\s*>/i.test(trimmed);
30
+ }
31
+
32
+ function isSvgDocument(input: string): boolean {
33
+ const trimmed = input.trimStart();
34
+ return /^(?:<\?xml[^>]*>\s*)?(?:<!doctype\s+svg[^>]*>\s*)?<svg[\s>]/i.test(trimmed);
35
+ }
36
+
37
  $effect(() => {
38
  const themeUrl = mode.current === 'dark' ? githubDarkUrl : githubUrl;
39
  let link = document.getElementById('highlight-theme') as HTMLLinkElement | null;
 
45
  }
46
  link.href = themeUrl;
47
  });
48
+
49
+ let canShowPreview = $derived(hasStrictHtml5Doctype(text) || isSvgDocument(text));
50
+ // svelte-ignore state_referenced_locally
51
+ let showPreview = $state.raw(canShowPreview);
52
  </script>
53
 
54
  <div class="overflow-hidden {className}">
 
56
  <div
57
  class="flex items-center justify-between border-b border-border/60 bg-muted px-3 py-1.5 dark:bg-accent/30"
58
  >
59
+ <span class="font-mono text-xs text-muted-foreground">
60
+ {#if showPreview}
61
+ Live Preview
62
+ {:else}
63
+ {lang}
64
+ {/if}
65
+ </span>
66
+ {#if canShowPreview}
67
+ <div class="flex items-center gap-1.5">
68
+ <p class="font-mono text-[10px] text-muted-foreground">
69
+ Show {showPreview ? 'Preview' : 'Code'}
70
+ </p>
71
+ <Switch bind:checked={showPreview} />
72
+ </div>
73
+ {/if}
74
+ </div>
75
+ {/if}
76
+ {#if showPreview}
77
+ <div class="group relative">
78
+ <iframe srcDoc={text} class="h-[500px] w-full" title="Preview"></iframe>
79
+ </div>
80
+ {:else}
81
+ <div class="group relative">
82
+ <HighlightAuto code={text} class="font-mono text-sm leading-relaxed" />
83
+ <Button
84
+ variant="outline"
85
+ class="absolute top-2 right-2 opacity-0 group-hover:opacity-100"
86
+ size="icon-xs"
87
+ onclick={() => copy(text)}
88
+ >
89
+ {#if copiedCode}
90
+ <Check class="size-3.5 text-green-500" />
91
+ {:else}
92
+ <Copy class="size-3.5" />
93
+ {/if}
94
+ </Button>
95
  </div>
96
  {/if}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  </div>