enzostvs HF Staff commited on
Commit
822d5b9
·
1 Parent(s): 1a07728

can update previous message

Browse files
src/lib/components/chat/Message.svelte CHANGED
@@ -1,4 +1,5 @@
1
  <script lang="ts">
 
2
  import SvelteMarkdown from 'svelte-markdown';
3
 
4
  import type { ChatMessage } from '$lib/helpers/types';
@@ -12,8 +13,13 @@
12
  import Link from './markdown/Link.svelte';
13
  import Hr from './markdown/Hr.svelte';
14
  import Think from './markdown/Think.svelte';
 
15
 
16
- let { message }: { message: ChatMessage } = $props();
 
 
 
 
17
 
18
  const renderers = {
19
  paragraph: Paragraph,
@@ -26,15 +32,71 @@
26
  link: Link,
27
  hr: Hr
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}
37
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  {:else}
39
  {#if message.reasoning}
40
  <Think
 
1
  <script lang="ts">
2
+ import { Check, PenLine, X } from '@lucide/svelte';
3
  import SvelteMarkdown from 'svelte-markdown';
4
 
5
  import type { ChatMessage } from '$lib/helpers/types';
 
13
  import Link from './markdown/Link.svelte';
14
  import Hr from './markdown/Hr.svelte';
15
  import Think from './markdown/Think.svelte';
16
+ import Button from '../ui/button/button.svelte';
17
 
18
+ let {
19
+ message,
20
+ nodeId,
21
+ onEdit
22
+ }: { message: ChatMessage; nodeId: string; onEdit?: (newContent: string) => void } = $props();
23
 
24
  const renderers = {
25
  paragraph: Paragraph,
 
32
  link: Link,
33
  hr: Hr
34
  };
35
+
36
+ let isEditing = $state(false);
37
+ let editContent = $state('');
38
+
39
+ function handleEdit() {
40
+ editContent = message.content as string;
41
+ isEditing = true;
42
+ }
43
+
44
+ function handleSubmitEdit() {
45
+ const trimmed = editContent.trim();
46
+ if (trimmed) {
47
+ onEdit?.(trimmed);
48
+ }
49
+ isEditing = false;
50
+ }
51
+
52
+ function handleCancelEdit() {
53
+ isEditing = false;
54
+ }
55
+
56
+ function handleKeydown(e: KeyboardEvent) {
57
+ if (e.key === 'Enter' && !e.shiftKey) {
58
+ e.preventDefault();
59
+ handleSubmitEdit();
60
+ } else if (e.key === 'Escape') {
61
+ handleCancelEdit();
62
+ }
63
+ }
64
  </script>
65
 
66
  <main
67
+ class="group/message pointer-events-auto cursor-auto p-1 text-lg leading-relaxed text-accent-foreground select-text"
68
  >
69
  {#if message?.role === 'user'}
70
+ {#if isEditing}
71
+ <div class="w-full">
72
+ <p
73
+ contenteditable="true"
74
+ bind:textContent={editContent}
75
+ class="w-full rounded-lg bg-accent px-2 py-1 outline-none"
76
+ onkeydown={handleKeydown}
77
+ ></p>
78
+ <div class="mt-1.5 flex items-center justify-end gap-1">
79
+ <Button variant="transparent" size="icon-2xs" onclick={handleCancelEdit}>
80
+ <X class="size-3" />
81
+ </Button>
82
+ <Button variant="transparent" size="icon-2xs" onclick={handleSubmitEdit}>
83
+ <Check class="size-3 text-primary" />
84
+ </Button>
85
+ </div>
86
+ </div>
87
+ {:else}
88
+ <p class="relative max-w-max pr-8">
89
+ {message.content}
90
+ <Button
91
+ variant="transparent"
92
+ size="icon-2xs"
93
+ class="absolute top-0.5 right-0 opacity-0 group-hover/message:opacity-100"
94
+ onclick={handleEdit}
95
+ >
96
+ <PenLine class="size-3" />
97
+ </Button>
98
+ </p>
99
+ {/if}
100
  {:else}
101
  {#if message.reasoning}
102
  <Think
src/lib/components/chat/User.svelte CHANGED
@@ -1,5 +1,5 @@
1
  <script lang="ts">
2
- import { Send, X } from '@lucide/svelte';
3
  import {
4
  Handle,
5
  useEdges,
@@ -43,6 +43,7 @@
43
  let isFirstNode = $derived((nodeData.current?.data.isFirstNode as boolean) ?? false);
44
  let showWelcome = $derived((nodeData.current?.data.showWelcome as boolean) ?? true);
45
  let isParentNode = $derived((nodeData.current?.data.isParentNode as boolean) ?? false);
 
46
 
47
  let prompt = $state.raw<string>((nodeData.current?.data.prompt as string) ?? '');
48
  let loading = $state.raw<boolean>(false);
@@ -153,6 +154,33 @@
153
  deleteElements({ nodes: [{ id: id }] });
154
  }
155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  onMount(() => {
157
  if (prompt.trim() !== '' && prompt) {
158
  handleTriggerAction();
@@ -165,6 +193,14 @@
165
  class="group/user relative z-10 w-[calc(100dvw-2rem)] rounded-3xl border border-border bg-background p-5 shadow-lg/5 lg:w-[600px]"
166
  >
167
  <div class="nodrag pointer-events-auto cursor-auto">
 
 
 
 
 
 
 
 
168
  <header class="mb-3 flex items-center justify-between">
169
  <div class="flex flex-wrap items-center gap-1">
170
  <ListModels {selectedModels} showSelector={!lastMessage} onToggleModel={toggleModel} />
@@ -175,7 +211,7 @@
175
  </header>
176
  <ErrorMessage bind:error={errorMessage} />
177
  {#if lastMessage}
178
- <Message message={lastMessage} />
179
  {:else}
180
  <footer class="flex flex-col items-end transition-all duration-300">
181
  <textarea
 
1
  <script lang="ts">
2
+ import { PenLine, Send, X } from '@lucide/svelte';
3
  import {
4
  Handle,
5
  useEdges,
 
43
  let isFirstNode = $derived((nodeData.current?.data.isFirstNode as boolean) ?? false);
44
  let showWelcome = $derived((nodeData.current?.data.showWelcome as boolean) ?? true);
45
  let isParentNode = $derived((nodeData.current?.data.isParentNode as boolean) ?? false);
46
+ let isFromEdit = $derived((nodeData.current?.data.isFromEdit as boolean) ?? false);
47
 
48
  let prompt = $state.raw<string>((nodeData.current?.data.prompt as string) ?? '');
49
  let loading = $state.raw<boolean>(false);
 
154
  deleteElements({ nodes: [{ id: id }] });
155
  }
156
 
157
+ function handleEditMessage(newContent: string) {
158
+ const newNodeId = `user-${crypto.randomUUID()}`;
159
+ const prevMessages = messages.slice(0, -1);
160
+ const newNode: Node = {
161
+ id: newNodeId,
162
+ type: 'user',
163
+ position: { x: 0, y: 0 },
164
+ data: {
165
+ role: 'user',
166
+ selectedModels,
167
+ messages: prevMessages,
168
+ isFirstNode: false,
169
+ isFromEdit: true,
170
+ showWelcome: false,
171
+ isParentNode: true,
172
+ prompt: newContent
173
+ }
174
+ };
175
+ const newEdge: Edge = {
176
+ id: `edge-${crypto.randomUUID()}`,
177
+ source: id,
178
+ target: newNodeId
179
+ };
180
+ updateNodes((currentNodes) => [...currentNodes, newNode]);
181
+ updateEdges((currentEdges) => [...currentEdges, newEdge]);
182
+ }
183
+
184
  onMount(() => {
185
  if (prompt.trim() !== '' && prompt) {
186
  handleTriggerAction();
 
193
  class="group/user relative z-10 w-[calc(100dvw-2rem)] rounded-3xl border border-border bg-background p-5 shadow-lg/5 lg:w-[600px]"
194
  >
195
  <div class="nodrag pointer-events-auto cursor-auto">
196
+ {#if isFromEdit}
197
+ <span
198
+ class="mb-2 inline-flex items-center justify-center gap-1 rounded-md bg-accent px-2 py-1 text-[11px] text-muted-foreground"
199
+ >
200
+ <PenLine class="size-2.5" />
201
+ Edited message
202
+ </span>
203
+ {/if}
204
  <header class="mb-3 flex items-center justify-between">
205
  <div class="flex flex-wrap items-center gap-1">
206
  <ListModels {selectedModels} showSelector={!lastMessage} onToggleModel={toggleModel} />
 
211
  </header>
212
  <ErrorMessage bind:error={errorMessage} />
213
  {#if lastMessage}
214
+ <Message nodeId={id} message={lastMessage} onEdit={handleEditMessage} />
215
  {:else}
216
  <footer class="flex flex-col items-end transition-all duration-300">
217
  <textarea
src/lib/components/chat/markdown/Code.svelte CHANGED
@@ -38,11 +38,11 @@
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-[11px] text-muted-foreground">{lang}</span>
42
  </div>
43
  {/if}
44
  <div class="group relative">
45
- <HighlightAuto code={text} class="font-mono text-[11px] leading-relaxed" />
46
  <Button
47
  variant="outline"
48
  class="absolute top-2 right-2 opacity-0 group-hover:opacity-100"
 
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"
src/lib/components/chat/markdown/Codespan.svelte CHANGED
@@ -3,6 +3,6 @@
3
  </script>
4
 
5
  <code
6
- class="rounded-md border border-border/40 bg-accent-foreground/5 px-1.5 py-0.5 font-mono text-[11px] text-foreground/85"
7
  >{raw.replace(/`/g, '')}</code
8
  >
 
3
  </script>
4
 
5
  <code
6
+ class="rounded-md border border-border/40 bg-accent-foreground/5 px-1.5 py-0.5 font-mono text-xs text-foreground/85"
7
  >{raw.replace(/`/g, '')}</code
8
  >
src/lib/components/chat/markdown/Heading.svelte CHANGED
@@ -5,19 +5,19 @@
5
  </script>
6
 
7
  {#if depth === 1}
8
- <h1 class="mt-4 mb-2 text-base font-semibold text-foreground first:mt-0">
9
  {@render children?.()}
10
  </h1>
11
  {:else if depth === 2}
12
- <h2 class="mt-3.5 mb-2 text-base font-semibold text-foreground first:mt-0">
13
  {@render children?.()}
14
  </h2>
15
  {:else if depth === 3}
16
- <h3 class="mt-3 mb-1.5 text-sm font-semibold text-foreground first:mt-0">
17
  {@render children?.()}
18
  </h3>
19
  {:else}
20
- <h4 class="mt-2.5 mb-1 text-sm font-semibold text-foreground first:mt-0">
21
  {@render children?.()}
22
  </h4>
23
  {/if}
 
5
  </script>
6
 
7
  {#if depth === 1}
8
+ <h1 class="mt-4 mb-2 text-2xl font-semibold text-foreground first:mt-0">
9
  {@render children?.()}
10
  </h1>
11
  {:else if depth === 2}
12
+ <h2 class="mt-3.5 mb-2 text-xl font-semibold text-foreground first:mt-0">
13
  {@render children?.()}
14
  </h2>
15
  {:else if depth === 3}
16
+ <h3 class="mt-3 mb-1.5 text-lg font-semibold text-foreground first:mt-0">
17
  {@render children?.()}
18
  </h3>
19
  {:else}
20
+ <h4 class="mt-2.5 mb-1 text-base font-semibold text-foreground first:mt-0">
21
  {@render children?.()}
22
  </h4>
23
  {/if}
src/lib/components/chat/markdown/List.svelte CHANGED
@@ -6,14 +6,14 @@
6
 
7
  {#if ordered}
8
  <ol
9
- class="my-2 list-outside list-decimal space-y-1 pl-5 text-xs text-foreground/90 marker:text-muted-foreground"
10
  {start}
11
  >
12
  {@render children?.()}
13
  </ol>
14
  {:else}
15
  <ul
16
- class="my-2 list-outside list-disc space-y-1 pl-5 text-xs text-foreground/90 marker:text-muted-foreground"
17
  >
18
  {@render children?.()}
19
  </ul>
 
6
 
7
  {#if ordered}
8
  <ol
9
+ class="my-2 list-outside list-decimal space-y-1 pl-5 text-sm text-foreground/90 marker:text-muted-foreground"
10
  {start}
11
  >
12
  {@render children?.()}
13
  </ol>
14
  {:else}
15
  <ul
16
+ class="my-2 list-outside list-disc space-y-1 pl-5 text-sm text-foreground/90 marker:text-muted-foreground"
17
  >
18
  {@render children?.()}
19
  </ul>
src/lib/components/chat/markdown/Paragraph.svelte CHANGED
@@ -3,4 +3,4 @@
3
  let { children }: { children?: Snippet } = $props();
4
  </script>
5
 
6
- <p class="mb-3 text-xs leading-relaxed text-foreground/90 last:mb-0">{@render children?.()}</p>
 
3
  let { children }: { children?: Snippet } = $props();
4
  </script>
5
 
6
+ <p class="mb-3 text-sm leading-relaxed text-foreground/90 last:mb-0">{@render children?.()}</p>
src/lib/components/chat/markdown/think/Heading.svelte CHANGED
@@ -5,19 +5,19 @@
5
  </script>
6
 
7
  {#if depth === 1}
8
- <h1 class="mt-4 mb-2 text-2xl font-semibold text-foreground first:mt-0">
9
  {@render children?.()}
10
  </h1>
11
  {:else if depth === 2}
12
- <h2 class="mt-3.5 mb-2 text-xl font-semibold text-foreground first:mt-0">
13
  {@render children?.()}
14
  </h2>
15
  {:else if depth === 3}
16
- <h3 class="mt-3 mb-1.5 text-lg font-semibold text-foreground first:mt-0">
17
  {@render children?.()}
18
  </h3>
19
  {:else}
20
- <h4 class="mt-2.5 mb-1 text-base font-semibold text-foreground first:mt-0">
21
  {@render children?.()}
22
  </h4>
23
  {/if}
 
5
  </script>
6
 
7
  {#if depth === 1}
8
+ <h1 class="mt-4 mb-2 text-base font-semibold text-foreground first:mt-0">
9
  {@render children?.()}
10
  </h1>
11
  {:else if depth === 2}
12
+ <h2 class="mt-3.5 mb-2 text-sm font-semibold text-foreground first:mt-0">
13
  {@render children?.()}
14
  </h2>
15
  {:else if depth === 3}
16
+ <h3 class="mt-3 mb-1.5 text-sm font-semibold text-foreground first:mt-0">
17
  {@render children?.()}
18
  </h3>
19
  {:else}
20
+ <h4 class="mt-2.5 mb-1 text-xs font-semibold text-foreground first:mt-0">
21
  {@render children?.()}
22
  </h4>
23
  {/if}
src/lib/components/ui/button/button.svelte CHANGED
@@ -23,7 +23,9 @@
23
  'bg-blue-500/10 text-blue-500 border border-blue-500/20 hover:bg-blue-500/20 shadow-xs',
24
  'outline-destructive':
25
  'bg-rose-500/10 hover:bg-rose-500/20 text-rose-600 border border-rose-500/20 shadow-xs',
26
- amber: 'bg-amber-500 text-white hover:brightness-110 shadow-xs'
 
 
27
  },
28
  size: {
29
  default: 'h-9 px-4 py-2 has-[>svg]:px-3',
 
23
  'bg-blue-500/10 text-blue-500 border border-blue-500/20 hover:bg-blue-500/20 shadow-xs',
24
  'outline-destructive':
25
  'bg-rose-500/10 hover:bg-rose-500/20 text-rose-600 border border-rose-500/20 shadow-xs',
26
+ amber: 'bg-amber-500 text-white hover:brightness-110 shadow-xs',
27
+ transparent:
28
+ 'bg-gray-200/50 text-gray-500 hover:bg-gray-200/80 border-transparent! dark:bg-gray-800/50 dark:text-gray-400 dark:hover:bg-gray-800/80'
29
  },
30
  size: {
31
  default: 'h-9 px-4 py-2 has-[>svg]:px-3',