trigger & grouping
This commit is contained in:
parent
c622ff25a7
commit
3abd8b5502
10 changed files with 135 additions and 40 deletions
|
|
@ -15,6 +15,7 @@
|
||||||
--popover: oklch(1 0 0);
|
--popover: oklch(1 0 0);
|
||||||
--popover-foreground: oklch(0.3257 0.1161 325.0372);
|
--popover-foreground: oklch(0.3257 0.1161 325.0372);
|
||||||
--primary: oklch(0.5316 0.1409 355.1999);
|
--primary: oklch(0.5316 0.1409 355.1999);
|
||||||
|
--heading: oklch(0.5797 0.1194 237.7893);
|
||||||
--primary-foreground: oklch(1 0 0);
|
--primary-foreground: oklch(1 0 0);
|
||||||
--secondary: oklch(0.8696 0.0675 334.8991);
|
--secondary: oklch(0.8696 0.0675 334.8991);
|
||||||
--secondary-foreground: oklch(0.4448 0.1341 324.7991);
|
--secondary-foreground: oklch(0.4448 0.1341 324.7991);
|
||||||
|
|
@ -62,6 +63,7 @@
|
||||||
--popover: oklch(0.1548 0.0132 338.9015);
|
--popover: oklch(0.1548 0.0132 338.9015);
|
||||||
--popover-foreground: oklch(0.9647 0.0091 341.8035);
|
--popover-foreground: oklch(0.9647 0.0091 341.8035);
|
||||||
--primary: oklch(0.5797 0.1194 237.7893);
|
--primary: oklch(0.5797 0.1194 237.7893);
|
||||||
|
--heading: oklch(0.85 0.1194 237.7893);
|
||||||
--primary-foreground: oklch(1 0 0);
|
--primary-foreground: oklch(1 0 0);
|
||||||
--secondary: oklch(0.3137 0.0306 310.061);
|
--secondary: oklch(0.3137 0.0306 310.061);
|
||||||
--secondary-foreground: oklch(0.8483 0.0382 307.9613);
|
--secondary-foreground: oklch(0.8483 0.0382 307.9613);
|
||||||
|
|
@ -108,6 +110,7 @@
|
||||||
--color-popover-foreground: var(--popover-foreground);
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
--color-primary: var(--primary);
|
--color-primary: var(--primary);
|
||||||
--color-primary-foreground: var(--primary-foreground);
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
|
--color-heading: var(--heading);
|
||||||
--color-secondary: var(--secondary);
|
--color-secondary: var(--secondary);
|
||||||
--color-secondary-foreground: var(--secondary-foreground);
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
--color-muted: var(--muted);
|
--color-muted: var(--muted);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { v } from 'convex/values';
|
import { v } from 'convex/values';
|
||||||
import { Provider } from '../../types';
|
import { Provider } from '../../types';
|
||||||
import { internal } from './_generated/api';
|
import { internal } from './_generated/api';
|
||||||
import { mutation, query } from './_generated/server';
|
import { query } from './_generated/server';
|
||||||
import { providerValidator } from './schema';
|
import { providerValidator } from './schema';
|
||||||
|
import { mutation } from './functions';
|
||||||
|
|
||||||
export const all = query({
|
export const all = query({
|
||||||
args: {
|
args: {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import { v } from 'convex/values';
|
import { v } from 'convex/values';
|
||||||
import { api } from './_generated/api';
|
import { api } from './_generated/api';
|
||||||
import { mutation, query } from './_generated/server';
|
import { query } from './_generated/server';
|
||||||
import { type Id } from './_generated/dataModel';
|
import { type Id } from './_generated/dataModel';
|
||||||
import { type SessionObj } from './betterAuth';
|
import { type SessionObj } from './betterAuth';
|
||||||
import { messageRoleValidator } from './schema';
|
import { messageRoleValidator } from './schema';
|
||||||
|
import { mutation } from './functions';
|
||||||
|
|
||||||
export const get = query({
|
export const get = query({
|
||||||
args: {
|
args: {
|
||||||
|
|
@ -24,7 +25,12 @@ export const get = query({
|
||||||
.withIndex('by_user', (q) => q.eq('user_id', s.userId))
|
.withIndex('by_user', (q) => q.eq('user_id', s.userId))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
return conversations;
|
return conversations.sort((a, b) => {
|
||||||
|
const aTime = a.updated_at ?? 0;
|
||||||
|
const bTime = b.updated_at ?? 0;
|
||||||
|
|
||||||
|
return bTime - aTime;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -45,6 +51,7 @@ export const create = mutation({
|
||||||
title: 'Untitled (for now)',
|
title: 'Untitled (for now)',
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Id type is janking out
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Id type is janking out
|
||||||
user_id: session.userId as any,
|
user_id: session.userId as any,
|
||||||
|
updated_at: Date.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|
@ -73,6 +80,7 @@ export const createAndAddMessage = mutation({
|
||||||
title: 'Untitled (for now)',
|
title: 'Untitled (for now)',
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Id type is janking out
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Id type is janking out
|
||||||
user_id: session.userId as any,
|
user_id: session.userId as any,
|
||||||
|
updated_at: Date.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const messageId = await ctx.runMutation(api.messages.create, {
|
const messageId = await ctx.runMutation(api.messages.create, {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import {
|
||||||
|
|
||||||
import { customCtx, customMutation } from 'convex-helpers/server/customFunctions';
|
import { customCtx, customMutation } from 'convex-helpers/server/customFunctions';
|
||||||
import { Triggers } from 'convex-helpers/server/triggers';
|
import { Triggers } from 'convex-helpers/server/triggers';
|
||||||
import { DataModel } from './_generated/dataModel';
|
import { type Id, type DataModel } from './_generated/dataModel';
|
||||||
|
|
||||||
const triggers = new Triggers<DataModel>();
|
const triggers = new Triggers<DataModel>();
|
||||||
|
|
||||||
|
|
@ -24,6 +24,20 @@ triggers.register('conversations', async (ctx, change) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update conversation updated_at when a message is created/updated
|
||||||
|
triggers.register('messages', async (ctx, change) => {
|
||||||
|
if (change.operation === 'insert' || change.operation === 'update') {
|
||||||
|
const conversationId = change.newDoc.conversation_id;
|
||||||
|
const conversation = await ctx.db.get(conversationId as Id<'conversations'>);
|
||||||
|
|
||||||
|
if (!conversation) return;
|
||||||
|
|
||||||
|
await ctx.db.patch(conversationId as Id<'conversations'>, {
|
||||||
|
updated_at: Date.now(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// TODO: Cascade delete rules when a user is deleted
|
// TODO: Cascade delete rules when a user is deleted
|
||||||
|
|
||||||
// create wrappers that replace the built-in `mutation` and `internalMutation`
|
// create wrappers that replace the built-in `mutation` and `internalMutation`
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { v } from 'convex/values';
|
import { v } from 'convex/values';
|
||||||
import { mutation, query } from './_generated/server';
|
|
||||||
import { api } from './_generated/api';
|
import { api } from './_generated/api';
|
||||||
import { messageRoleValidator, providerValidator } from './schema';
|
|
||||||
import { type Id } from './_generated/dataModel';
|
import { type Id } from './_generated/dataModel';
|
||||||
|
import { query } from './_generated/server';
|
||||||
|
import { messageRoleValidator, providerValidator } from './schema';
|
||||||
|
import { mutation } from './functions';
|
||||||
|
|
||||||
export const getAllFromConversation = query({
|
export const getAllFromConversation = query({
|
||||||
args: {
|
args: {
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ export default defineSchema({
|
||||||
conversations: defineTable({
|
conversations: defineTable({
|
||||||
user_id: v.string(),
|
user_id: v.string(),
|
||||||
title: v.string(),
|
title: v.string(),
|
||||||
|
updated_at: v.optional(v.number()),
|
||||||
}).index('by_user', ['user_id']),
|
}).index('by_user', ['user_id']),
|
||||||
messages: defineTable({
|
messages: defineTable({
|
||||||
conversation_id: v.string(),
|
conversation_id: v.string(),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { query, mutation } from './_generated/server';
|
import { query } from './_generated/server';
|
||||||
|
import { mutation } from './functions';
|
||||||
import { v } from 'convex/values';
|
import { v } from 'convex/values';
|
||||||
import { providerValidator } from './schema';
|
import { providerValidator } from './schema';
|
||||||
import * as array from '../../utils/array';
|
import * as array from '../../utils/array';
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
import { v } from 'convex/values';
|
import { v } from 'convex/values';
|
||||||
import { Provider } from '../../types';
|
import { Provider } from '../../types';
|
||||||
import { api, internal } from './_generated/api';
|
import { api, internal } from './_generated/api';
|
||||||
import { mutation, query } from './_generated/server';
|
import { query } from './_generated/server';
|
||||||
|
import { mutation } from './functions';
|
||||||
import { providerValidator } from './schema';
|
import { providerValidator } from './schema';
|
||||||
import { type SessionObj } from './betterAuth';
|
import { type SessionObj } from './betterAuth';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { v } from 'convex/values';
|
import { v } from 'convex/values';
|
||||||
import { mutation, query } from './_generated/server';
|
import { query } from './_generated/server';
|
||||||
|
import { mutation } from './functions';
|
||||||
import { internal } from './_generated/api';
|
import { internal } from './_generated/api';
|
||||||
import { ruleAttachValidator } from './schema';
|
import { ruleAttachValidator } from './schema';
|
||||||
import { type Doc } from './_generated/dataModel';
|
import { type Doc } from './_generated/dataModel';
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { useCachedQuery } from '$lib/cache/cached-query.svelte.js';
|
import { useCachedQuery } from '$lib/cache/cached-query.svelte.js';
|
||||||
import { api } from '$lib/backend/convex/_generated/api.js';
|
import { api } from '$lib/backend/convex/_generated/api.js';
|
||||||
|
import { type Doc } from '$lib/backend/convex/_generated/dataModel.js';
|
||||||
import { TextareaAutosize } from '$lib/spells/textarea-autosize.svelte.js';
|
import { TextareaAutosize } from '$lib/spells/textarea-autosize.svelte.js';
|
||||||
import Tooltip from '$lib/components/ui/tooltip.svelte';
|
import Tooltip from '$lib/components/ui/tooltip.svelte';
|
||||||
|
|
||||||
|
|
@ -51,6 +52,49 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
const _autosize = new TextareaAutosize();
|
const _autosize = new TextareaAutosize();
|
||||||
|
|
||||||
|
function groupConversationsByTime(conversations: Doc<'conversations'>[]) {
|
||||||
|
const now = Date.now();
|
||||||
|
const oneDay = 24 * 60 * 60 * 1000;
|
||||||
|
const sevenDays = 7 * oneDay;
|
||||||
|
const thirtyDays = 30 * oneDay;
|
||||||
|
|
||||||
|
const groups = {
|
||||||
|
today: [] as Doc<'conversations'>[],
|
||||||
|
yesterday: [] as Doc<'conversations'>[],
|
||||||
|
lastWeek: [] as Doc<'conversations'>[],
|
||||||
|
lastMonth: [] as Doc<'conversations'>[],
|
||||||
|
older: [] as Doc<'conversations'>[],
|
||||||
|
};
|
||||||
|
|
||||||
|
conversations.forEach((conversation) => {
|
||||||
|
const updatedAt = conversation.updated_at ?? 0;
|
||||||
|
const timeDiff = now - updatedAt;
|
||||||
|
|
||||||
|
if (timeDiff < oneDay) {
|
||||||
|
groups.today.push(conversation);
|
||||||
|
} else if (timeDiff < 2 * oneDay) {
|
||||||
|
groups.yesterday.push(conversation);
|
||||||
|
} else if (timeDiff < sevenDays) {
|
||||||
|
groups.lastWeek.push(conversation);
|
||||||
|
} else if (timeDiff < thirtyDays) {
|
||||||
|
groups.lastMonth.push(conversation);
|
||||||
|
} else {
|
||||||
|
groups.older.push(conversation);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupedConversations = $derived(groupConversationsByTime(conversationsQuery.data ?? []));
|
||||||
|
const templateConversations = $derived([
|
||||||
|
{ key: 'today', label: 'Today', conversations: groupedConversations.today },
|
||||||
|
{ key: 'yesterday', label: 'Yesterday', conversations: groupedConversations.yesterday },
|
||||||
|
{ key: 'lastWeek', label: 'Last 7 days', conversations: groupedConversations.lastWeek },
|
||||||
|
{ key: 'lastMonth', label: 'Last 30 days', conversations: groupedConversations.lastMonth },
|
||||||
|
{ key: 'older', label: 'Older', conversations: groupedConversations.older },
|
||||||
|
]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|
@ -71,17 +115,30 @@
|
||||||
New Chat
|
New Chat
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="relative flex flex-1 flex-col">
|
||||||
|
<div
|
||||||
|
class="from-sidebar pointer-events-none absolute top-0 right-0 left-0 z-10 h-4 bg-gradient-to-b to-transparent"
|
||||||
|
></div>
|
||||||
<div class="flex flex-1 flex-col overflow-y-auto py-2">
|
<div class="flex flex-1 flex-col overflow-y-auto py-2">
|
||||||
{#each conversationsQuery.data ?? [] as conversation (conversation._id)}
|
{#each templateConversations as group, index (group.key)}
|
||||||
<a
|
{#if group.conversations.length > 0}
|
||||||
href={`/chat/${conversation._id}`}
|
<div class="px-2 py-1" class:mt-2={index > 0}>
|
||||||
class="group relative overflow-clip py-0.5 pr-2.5 text-left text-sm"
|
<h3 class="text-heading text-xs font-medium">{group.label}</h3>
|
||||||
|
</div>
|
||||||
|
{#each group.conversations as conversation (conversation._id)}
|
||||||
|
{@const isActive = page.params.id === conversation._id}
|
||||||
|
<a href={`/chat/${conversation._id}`} class="group py-0.5 pr-2.5 text-left text-sm">
|
||||||
|
<div class="relative overflow-clip">
|
||||||
|
<p
|
||||||
|
class={[
|
||||||
|
' rounded-lg py-2 pl-3',
|
||||||
|
isActive ? 'bg-sidebar-accent' : 'group-hover:bg-sidebar-accent ',
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<p class="group-hover:bg-sidebar-accent rounded-md py-1.5 pl-3">
|
<span>{conversation.title}</span>
|
||||||
{conversation.title}
|
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
class=" to-sidebar-accent pointer-events-none absolute inset-y-0 right-0 flex translate-x-full items-center gap-2 rounded-r-lg bg-gradient-to-r from-transparent pr-2 transition group-hover:pointer-events-auto group-hover:translate-0"
|
class=" to-sidebar-accent pointer-events-none absolute inset-y-0.5 right-0 flex translate-x-full items-center gap-2 rounded-r-lg bg-gradient-to-r from-transparent pr-2 transition group-hover:pointer-events-auto group-hover:translate-0"
|
||||||
>
|
>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
{#snippet trigger(tooltip)}
|
{#snippet trigger(tooltip)}
|
||||||
|
|
@ -100,8 +157,15 @@
|
||||||
Delete thread
|
Delete thread
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="from-sidebar pointer-events-none absolute right-0 bottom-0 left-0 z-10 h-4 bg-gradient-to-t to-transparent"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="py-2">
|
<div class="py-2">
|
||||||
{#if data.session !== null}
|
{#if data.session !== null}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue