toggle public private and protect messages (#25)

This commit is contained in:
Aidan Bleser 2025-06-18 13:51:56 -05:00 committed by GitHub
parent f778dbd9ec
commit f5efc2490a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 57 additions and 23 deletions

View file

@ -1,5 +1,5 @@
import { v } from 'convex/values';
import enhancedSearch, { type SearchResult } from '../../utils/fuzzy-search';
import enhancedSearch from '../../utils/fuzzy-search';
import { getFirstSentence } from '../../utils/strings';
import { api } from './_generated/api';
import { type Doc, type Id } from './_generated/dataModel';
@ -54,7 +54,7 @@ export const getById = query({
const conversation = await ctx.db.get(args.conversation_id);
if (!conversation || (conversation.user_id !== session.userId && !conversation.public)) {
if (!conversation || (!conversation.public && conversation.user_id !== session.userId)) {
throw new Error('Conversation not found or unauthorized');
}
@ -227,9 +227,10 @@ export const updateCostUsd = mutation({
},
});
export const makePublic = mutation({
export const setPublic = mutation({
args: {
conversation_id: v.id('conversations'),
public: v.boolean(),
session_token: v.string(),
},
handler: async (ctx, args) => {
@ -245,7 +246,7 @@ export const makePublic = mutation({
}
await ctx.db.patch(args.conversation_id, {
public: true,
public: args.public,
});
},
});

View file

@ -15,16 +15,21 @@ export const getAllFromConversation = query({
session_token: args.session_token,
});
if (!session) {
if (!session) throw new Error('Unauthorized');
const [messages, conversation] = await Promise.all([
ctx.db
.query('messages')
.withIndex('by_conversation', (q) => q.eq('conversation_id', args.conversation_id))
.order('asc')
.collect(),
ctx.db.get(args.conversation_id as Id<'conversations'>),
]);
if (!conversation?.public && conversation?.user_id !== session.userId) {
throw new Error('Unauthorized');
}
const messages = await ctx.db
.query('messages')
.withIndex('by_conversation', (q) => q.eq('conversation_id', args.conversation_id))
.order('asc')
.collect();
return messages;
},
});

View file

@ -362,8 +362,9 @@
if (!page.params.id || !session.current?.session.token) return;
const result = await ResultAsync.fromPromise(
client.mutation(api.conversations.makePublic, {
client.mutation(api.conversations.setPublic, {
conversation_id: page.params.id as Id<'conversations'>,
public: true,
session_token: session.current?.session.token ?? '',
}),
(e) => e
@ -379,6 +380,19 @@
clipboard.copy(page.url.toString());
}
async function togglePublic() {
if (!page.params.id || !session.current?.session.token) return;
await ResultAsync.fromPromise(
client.mutation(api.conversations.setPublic, {
conversation_id: page.params.id as Id<'conversations'>,
public: !currentConversationQuery.data?.public,
session_token: session.current?.session.token ?? '',
}),
(e) => e
);
}
const textareaSize = new ElementSize(() => textarea);
let textareaWrapper = $state<HTMLDivElement>();
@ -451,11 +465,14 @@
Toggle Sidebar ({cmdOrCtrl} + B)
</Tooltip>
{#if page.params.id}
{#if page.params.id && currentConversationQuery.data}
<Tooltip>
{#snippet trigger(tooltip)}
<div
class="z-50 flex size-8 items-center justify-center md:top-1 md:left-auto"
<Button
class="bg-sidebar size-8"
size="icon"
variant="ghost"
onClickPromise={togglePublic}
{...tooltip.trigger}
>
{#if currentConversationQuery.data?.public}
@ -463,7 +480,7 @@
{:else}
<LockIcon class="size-4" />
{/if}
</div>
</Button>
{/snippet}
{currentConversationQuery.data?.public ? 'Public' : 'Private'}
</Tooltip>
@ -477,7 +494,7 @@
{ 'hidden md:flex': sidebarOpen }
)}
>
{#if page.params.id}
{#if page.params.id && currentConversationQuery.data}
<Tooltip>
{#snippet trigger(tooltip)}
<Button
@ -761,7 +778,7 @@
</div>
<!-- Credits in bottom-right, only on large screens -->
<div class="fixed right-4 bottom-4 hidden flex-col items-end gap-1 xl:flex">
<div class="fixed right-4 bottom-4 hidden flex-col items-end gap-1 2xl:flex">
<a
href="https://github.com/TGlide/thom-chat"
class="text-muted-foreground flex place-items-center gap-1 text-xs"

View file

@ -9,6 +9,7 @@
import Message from './message.svelte';
import { last } from '$lib/utils/array';
import { settings } from '$lib/state/settings.svelte';
import Button from '$lib/components/ui/button/button.svelte';
const messages = useCachedQuery(api.messages.getAllFromConversation, () => ({
conversation_id: page.params.id ?? '',
@ -61,10 +62,20 @@
</svelte:head>
<div class="flex h-full flex-1 flex-col py-4">
{#each messages.data ?? [] as message (message._id)}
<Message {message} />
{/each}
{#if conversation.data?.generating && !lastMessageHasContent}
<LoadingDots />
{#if !conversation.data && !conversation.isLoading}
<div class="flex flex-1 flex-col items-center justify-center gap-4 pt-[25svh]">
<div>
<h1 class="text-center font-mono text-8xl font-semibold">404</h1>
<p class="text-muted-foreground text-center text-2xl">Conversation not found</p>
</div>
<Button size="sm" variant="outline" href="/chat">Create a new conversation</Button>
</div>
{:else}
{#each messages.data ?? [] as message (message._id)}
<Message {message} />
{/each}
{#if conversation.data?.generating && !lastMessageHasContent}
<LoadingDots />
{/if}
{/if}
</div>