Skip to content

Commit 83b5728

Browse files
Jasonwarp-agent
andcommitted
feat(chat): add starring to assistant messages and fix state handling
- Add star/unstar toggle for assistant messages with optimistic UI and server sync - Track starred state via reactive effect instead of capturing initial message value to satisfy Svelte state rules Co-Authored-By: Warp <agent@warp.dev>
1 parent 231186c commit 83b5728

1 file changed

Lines changed: 58 additions & 0 deletions

File tree

src/routes/chat/[id]/message.svelte

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import ChevronDownIcon from '~icons/lucide/chevron-down';
2626
import RefreshCwIcon from '~icons/lucide/refresh-cw';
2727
import PencilIcon from '~icons/lucide/pencil';
28+
import StarIcon from '~icons/lucide/star';
2829
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
2930
import { AnnotationSchema, type Annotation } from '$lib/types';
3031
import ExternalLinkIcon from '~icons/lucide/external-link';
@@ -186,6 +187,11 @@
186187
187188
let isEditing = $state(false);
188189
let editedContent = $state('');
190+
let isStarred = $state<boolean>(false);
191+
192+
$effect(() => {
193+
isStarred = message.starred ?? false;
194+
});
189195
190196
function startEditing() {
191197
editedContent = message.content;
@@ -274,6 +280,37 @@
274280
}
275281
}
276282
}
283+
284+
async function toggleStarred() {
285+
if (!session.current?.session.token) return;
286+
if (message.role !== 'assistant') return;
287+
288+
const previous = isStarred;
289+
isStarred = !isStarred;
290+
291+
try {
292+
const res = await fetch(api.messages.setStarred.url, {
293+
method: 'POST',
294+
headers: { 'Content-Type': 'application/json' },
295+
body: JSON.stringify({
296+
action: 'setStarred',
297+
messageId: message.id,
298+
starred: isStarred,
299+
}),
300+
});
301+
302+
if (!res.ok) {
303+
console.error('Failed to update starred state');
304+
isStarred = previous;
305+
return;
306+
}
307+
308+
invalidateQueryPattern(api.messages.getAllFromConversation.url);
309+
} catch (e) {
310+
console.error('Error updating starred state:', e);
311+
isStarred = previous;
312+
}
313+
}
277314
</script>
278315

279316
{#if message.role !== 'system' && !(message.role === 'assistant' && message.content.length === 0 && message.reasoning?.length === 0 && !message.error)}
@@ -480,6 +517,27 @@
480517
{/snippet}
481518
{message.role === 'user' ? 'Branch and regenerate message' : 'Branch off this message'}
482519
</Tooltip>
520+
{#if message.role === 'assistant' && message.content.length > 0 && !message.error}
521+
<Tooltip>
522+
{#snippet trigger(tooltip)}
523+
<Button
524+
size="icon"
525+
variant="ghost"
526+
class="order-1 size-7"
527+
onclick={toggleStarred}
528+
{...tooltip.trigger}
529+
>
530+
<StarIcon
531+
class={cn('size-4', {
532+
'text-yellow-400 fill-yellow-400': isStarred,
533+
'text-muted-foreground/60': !isStarred,
534+
})}
535+
/>
536+
</Button>
537+
{/snippet}
538+
{isStarred ? 'Unstar message' : 'Star message'}
539+
</Tooltip>
540+
{/if}
483541
{#if message.content.length > 0}
484542
<Tooltip>
485543
{#snippet trigger(tooltip)}

0 commit comments

Comments
 (0)