add pin functionality
This commit is contained in:
parent
fae7798119
commit
ef6aead37a
3 changed files with 84 additions and 5 deletions
|
|
@ -124,3 +124,32 @@ export const updateTitle = mutation({
|
|||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const togglePin = mutation({
|
||||
args: {
|
||||
conversation_id: v.id('conversations'),
|
||||
session_token: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const session = await ctx.runQuery(api.betterAuth.publicGetSession, {
|
||||
session_token: args.session_token,
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
|
||||
// Verify the conversation belongs to the user
|
||||
const conversation = await ctx.db.get(args.conversation_id);
|
||||
if (!conversation || conversation.user_id !== session.userId) {
|
||||
throw new Error('Conversation not found or unauthorized');
|
||||
}
|
||||
|
||||
await ctx.db.patch(args.conversation_id, {
|
||||
pinned: !conversation.pinned,
|
||||
updated_at: Date.now(),
|
||||
});
|
||||
|
||||
return { pinned: !conversation.pinned };
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ export default defineSchema({
|
|||
user_id: v.string(),
|
||||
title: v.string(),
|
||||
updated_at: v.optional(v.number()),
|
||||
pinned: v.optional(v.boolean()),
|
||||
}).index('by_user', ['user_id']),
|
||||
messages: defineTable({
|
||||
conversation_id: v.string(),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import { Avatar } from 'melt/components';
|
||||
import PanelLeftIcon from '~icons/lucide/panel-left';
|
||||
import PinIcon from '~icons/lucide/pin';
|
||||
import PinOffIcon from '~icons/lucide/pin-off';
|
||||
import XIcon from '~icons/lucide/x';
|
||||
import SendIcon from '~icons/lucide/send';
|
||||
import { callGenerateMessage } from '../api/generate-message/call.js';
|
||||
|
|
@ -16,9 +17,12 @@
|
|||
import { goto } from '$app/navigation';
|
||||
import { useCachedQuery } from '$lib/cache/cached-query.svelte.js';
|
||||
import { api } from '$lib/backend/convex/_generated/api.js';
|
||||
import { type Doc } from '$lib/backend/convex/_generated/dataModel.js';
|
||||
import { type Doc, type Id } from '$lib/backend/convex/_generated/dataModel.js';
|
||||
import { TextareaAutosize } from '$lib/spells/textarea-autosize.svelte.js';
|
||||
import Tooltip from '$lib/components/ui/tooltip.svelte';
|
||||
import { useConvexClient } from 'convex-svelte';
|
||||
|
||||
const client = useConvexClient();
|
||||
|
||||
let { data, children } = $props();
|
||||
|
||||
|
|
@ -60,6 +64,7 @@
|
|||
const thirtyDays = 30 * oneDay;
|
||||
|
||||
const groups = {
|
||||
pinned: [] as Doc<'conversations'>[],
|
||||
today: [] as Doc<'conversations'>[],
|
||||
yesterday: [] as Doc<'conversations'>[],
|
||||
lastWeek: [] as Doc<'conversations'>[],
|
||||
|
|
@ -68,6 +73,12 @@
|
|||
};
|
||||
|
||||
conversations.forEach((conversation) => {
|
||||
// Pinned conversations go to pinned group regardless of time
|
||||
if (conversation.pinned) {
|
||||
groups.pinned.push(conversation);
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedAt = conversation.updated_at ?? 0;
|
||||
const timeDiff = now - updatedAt;
|
||||
|
||||
|
|
@ -84,11 +95,32 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Sort pinned conversations by updated_at (most recent first)
|
||||
groups.pinned.sort((a, b) => {
|
||||
const aTime = a.updated_at ?? 0;
|
||||
const bTime = b.updated_at ?? 0;
|
||||
return bTime - aTime;
|
||||
});
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
const groupedConversations = $derived(groupConversationsByTime(conversationsQuery.data ?? []));
|
||||
|
||||
async function togglePin(conversationId: string) {
|
||||
if (!session.current?.session.token) return;
|
||||
|
||||
try {
|
||||
await client.mutation(api.conversations.togglePin, {
|
||||
conversation_id: conversationId as Id<'conversations'>,
|
||||
session_token: session.current.session.token,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle pin:', error);
|
||||
}
|
||||
}
|
||||
const templateConversations = $derived([
|
||||
{ key: 'pinned', label: 'Pinned', conversations: groupedConversations.pinned, icon: PinIcon },
|
||||
{ key: 'today', label: 'Today', conversations: groupedConversations.today },
|
||||
{ key: 'yesterday', label: 'Yesterday', conversations: groupedConversations.yesterday },
|
||||
{ key: 'lastWeek', label: 'Last 7 days', conversations: groupedConversations.lastWeek },
|
||||
|
|
@ -123,7 +155,12 @@
|
|||
{#each templateConversations as group, index (group.key)}
|
||||
{#if group.conversations.length > 0}
|
||||
<div class="px-2 py-1" class:mt-2={index > 0}>
|
||||
<h3 class="text-heading text-xs font-medium">{group.label}</h3>
|
||||
<h3 class="text-heading text-xs font-medium">
|
||||
{#if group.icon}
|
||||
<svelte:component this={group.icon} class="inline size-3" />
|
||||
{/if}
|
||||
{group.label}
|
||||
</h3>
|
||||
</div>
|
||||
{#each group.conversations as conversation (conversation._id)}
|
||||
{@const isActive = page.params.id === conversation._id}
|
||||
|
|
@ -145,11 +182,23 @@
|
|||
>
|
||||
<Tooltip>
|
||||
{#snippet trigger(tooltip)}
|
||||
<button {...tooltip.trigger} class="hover:bg-muted rounded-md p-1">
|
||||
<PinIcon class="size-4" />
|
||||
<button
|
||||
{...tooltip.trigger}
|
||||
class="hover:bg-muted rounded-md p-1"
|
||||
onclick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
togglePin(conversation._id);
|
||||
}}
|
||||
>
|
||||
{#if conversation.pinned}
|
||||
<PinOffIcon class="size-4" />
|
||||
{:else}
|
||||
<PinIcon class="size-4" />
|
||||
{/if}
|
||||
</button>
|
||||
{/snippet}
|
||||
Pin thread
|
||||
{conversation.pinned ? 'Unpin thread' : 'Pin thread'}
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
{#snippet trigger(tooltip)}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue