refac
This commit is contained in:
1 parent
45fcf272ef
commit
76854d1424
10 files changed
+146
-83
No files matched your search
@@ -443,6 +443,15 @@ async def get_shared_folder_chats(
|
||||
|
||||
chats = await Chats.get_all_chats_by_folder_id(id, db=db)
|
||||
|
||||
# Resolve owner names for display (avatar URLs are constructed client-side)
|
||||
owner_cache: dict[str, str] = {}
|
||||
for chat in chats:
|
||||
uid = chat['user_id']
|
||||
if uid not in owner_cache:
|
||||
u = await Users.get_user_by_id(uid, db=db)
|
||||
owner_cache[uid] = u.name if u else 'Unknown'
|
||||
chat['owner_name'] = owner_cache[uid]
|
||||
|
||||
return {
|
||||
'chats': [
|
||||
{**chat, 'readonly': chat['user_id'] != user.id}
|
||||
|
||||
@@ -3030,6 +3030,7 @@
|
||||
<FilesOverlay show={dragged} />
|
||||
<Navbar
|
||||
bind:this={navbarElement}
|
||||
{readOnly}
|
||||
chat={{
|
||||
id: $chatId,
|
||||
chat: {
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let initNewChat: Function;
|
||||
export let readOnly: boolean = false;
|
||||
export let shareEnabled: boolean = false;
|
||||
export let scrollTop = 0;
|
||||
export let scrollToTop: (() => void) | null = null;
|
||||
@@ -122,7 +123,7 @@
|
||||
"
|
||||
>
|
||||
{#if showModelSelector}
|
||||
<ModelSelector bind:selectedModels showSetDefault={!shareEnabled} />
|
||||
<ModelSelector bind:selectedModels showSetDefault={!shareEnabled && !readOnly} disabled={readOnly} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -203,6 +204,7 @@
|
||||
<Menu
|
||||
{chat}
|
||||
{shareEnabled}
|
||||
{readOnly}
|
||||
{scrollToTop}
|
||||
shareHandler={() => {
|
||||
showShareChatModal = !showShareChatModal;
|
||||
|
||||
@@ -73,6 +73,9 @@
|
||||
}
|
||||
|
||||
$: models = selectedModels.map((id) => $_models.find((m) => m.id === id));
|
||||
|
||||
// True when viewing a shared folder the current user doesn't own
|
||||
$: folderReadOnly = $selectedFolder != null && $selectedFolder.user_id !== $user?.id;
|
||||
</script>
|
||||
|
||||
<div class="m-auto w-full max-w-6xl px-2 @2xl:px-20 translate-y-6 py-24 text-center">
|
||||
@@ -94,18 +97,19 @@
|
||||
<div class="w-full flex flex-col justify-center items-center">
|
||||
{#if $selectedFolder}
|
||||
<FolderTitle
|
||||
folder={$selectedFolder}
|
||||
onUpdate={async (folder) => {
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
currentChatPage.set(1);
|
||||
}}
|
||||
onDelete={async () => {
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
currentChatPage.set(1);
|
||||
folder={$selectedFolder}
|
||||
readOnly={folderReadOnly}
|
||||
onUpdate={async (folder) => {
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
currentChatPage.set(1);
|
||||
}}
|
||||
onDelete={async () => {
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
currentChatPage.set(1);
|
||||
|
||||
selectedFolder.set(null);
|
||||
}}
|
||||
/>
|
||||
selectedFolder.set(null);
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="flex flex-row justify-center gap-2.5 @sm:gap-3 w-fit px-5 max-w-xl">
|
||||
<div class="flex shrink-0 justify-center">
|
||||
@@ -210,33 +214,35 @@
|
||||
{/if}
|
||||
|
||||
<div class="text-base font-normal @md:max-w-3xl w-full py-3 {atSelectedModel ? 'mt-2' : ''}">
|
||||
<MessageInput
|
||||
bind:this={messageInput}
|
||||
{history}
|
||||
{selectedModels}
|
||||
bind:files
|
||||
bind:prompt
|
||||
bind:autoScroll
|
||||
bind:selectedToolIds
|
||||
bind:selectedSkillIds
|
||||
bind:selectedFilterIds
|
||||
bind:imageGenerationEnabled
|
||||
bind:codeInterpreterEnabled
|
||||
bind:webSearchEnabled
|
||||
bind:atSelectedModel
|
||||
bind:showCommands
|
||||
bind:dragged
|
||||
{pendingOAuthTools}
|
||||
{toolServers}
|
||||
{stopResponse}
|
||||
{createMessagePair}
|
||||
placeholder={$i18n.t('How can I help you today?')}
|
||||
{onChange}
|
||||
{onUpload}
|
||||
on:submit={(e) => {
|
||||
dispatch('submit', e.detail);
|
||||
}}
|
||||
/>
|
||||
{#if !($selectedFolder && folderReadOnly)}
|
||||
<MessageInput
|
||||
bind:this={messageInput}
|
||||
{history}
|
||||
{selectedModels}
|
||||
bind:files
|
||||
bind:prompt
|
||||
bind:autoScroll
|
||||
bind:selectedToolIds
|
||||
bind:selectedSkillIds
|
||||
bind:selectedFilterIds
|
||||
bind:imageGenerationEnabled
|
||||
bind:codeInterpreterEnabled
|
||||
bind:webSearchEnabled
|
||||
bind:atSelectedModel
|
||||
bind:showCommands
|
||||
bind:dragged
|
||||
{pendingOAuthTools}
|
||||
{toolServers}
|
||||
{stopResponse}
|
||||
{createMessagePair}
|
||||
placeholder={$i18n.t('How can I help you today?')}
|
||||
{onChange}
|
||||
{onUpload}
|
||||
on:submit={(e) => {
|
||||
dispatch('submit', e.detail);
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
|
||||
import Loader from '$lib/components/common/Loader.svelte';
|
||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
@@ -54,6 +55,7 @@
|
||||
let orderBy = 'updated_at';
|
||||
let direction = 'desc'; // 'asc' or 'desc'
|
||||
|
||||
|
||||
$: if (chats) {
|
||||
init();
|
||||
}
|
||||
@@ -157,10 +159,20 @@
|
||||
{chat?.title}
|
||||
</div>
|
||||
|
||||
<div class="hidden sm:flex sm:basis-2/5 items-center justify-end">
|
||||
<div class="hidden sm:flex sm:basis-2/5 items-center justify-end gap-2">
|
||||
<div class=" text-gray-500 dark:text-gray-400 text-xs">
|
||||
{dayjs(chat?.updated_at * 1000).calendar()}
|
||||
</div>
|
||||
|
||||
{#if chat.user_id && chat.owner_name}
|
||||
<Tooltip content={chat.owner_name}>
|
||||
<img
|
||||
src="/api/v1/users/{chat.user_id}/profile/image"
|
||||
alt=""
|
||||
class="size-5 rounded-full shrink-0 object-cover"
|
||||
/>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
import EmojiPicker from '$lib/components/common/EmojiPicker.svelte';
|
||||
|
||||
export let folder = null;
|
||||
export let readOnly: boolean = false;
|
||||
|
||||
export let onUpdate: Function = (folderId) => {};
|
||||
export let onDelete: Function = (folderId) => {};
|
||||
@@ -205,58 +206,72 @@
|
||||
|
||||
<div class="mb-3 px-6 @md:max-w-3xl justify-between w-full flex relative group items-center">
|
||||
<div class="text-center flex gap-3.5 items-center">
|
||||
<EmojiPicker
|
||||
onClose={() => {}}
|
||||
selected={folder?.meta?.icon ?? null}
|
||||
onSubmit={(name) => {
|
||||
console.log(name);
|
||||
updateIconHandler(name);
|
||||
}}
|
||||
>
|
||||
<button
|
||||
aria-label={$i18n.t('Change folder icon')}
|
||||
class=" rounded-full bg-gray-50 dark:bg-gray-800 size-11 flex justify-center items-center"
|
||||
{#if readOnly}
|
||||
<div
|
||||
class="rounded-full bg-gray-50 dark:bg-gray-800 size-11 flex justify-center items-center"
|
||||
>
|
||||
{#if folder?.meta?.icon}
|
||||
<Emoji className="size-6" shortCode={folder.meta.icon} />
|
||||
{:else}
|
||||
<Folder className="size-4.5" strokeWidth="2" />
|
||||
{/if}
|
||||
</button>
|
||||
</EmojiPicker>
|
||||
</div>
|
||||
{:else}
|
||||
<EmojiPicker
|
||||
onClose={() => {}}
|
||||
selected={folder?.meta?.icon ?? null}
|
||||
onSubmit={(name) => {
|
||||
console.log(name);
|
||||
updateIconHandler(name);
|
||||
}}
|
||||
>
|
||||
<button
|
||||
aria-label={$i18n.t('Change folder icon')}
|
||||
class=" rounded-full bg-gray-50 dark:bg-gray-800 size-11 flex justify-center items-center"
|
||||
>
|
||||
{#if folder?.meta?.icon}
|
||||
<Emoji className="size-6" shortCode={folder.meta.icon} />
|
||||
{:else}
|
||||
<Folder className="size-4.5" strokeWidth="2" />
|
||||
{/if}
|
||||
</button>
|
||||
</EmojiPicker>
|
||||
{/if}
|
||||
|
||||
<div class="text-3xl line-clamp-1">
|
||||
{folder.name}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center translate-x-2.5">
|
||||
<FolderMenu
|
||||
align="end"
|
||||
onEdit={() => {
|
||||
showFolderModal = true;
|
||||
}}
|
||||
onShare={() => {
|
||||
showShareModal = true;
|
||||
}}
|
||||
onDelete={() => {
|
||||
showDeleteConfirm = true;
|
||||
}}
|
||||
onExport={() => {
|
||||
exportHandler();
|
||||
}}
|
||||
onCreateSubFolder={() => {
|
||||
showCreateSubFolderModal = true;
|
||||
}}
|
||||
>
|
||||
<button
|
||||
class="p-1.5 dark:hover:bg-gray-850 rounded-full touch-auto"
|
||||
aria-label={$i18n.t('Folder options')}
|
||||
on:click={(e) => {}}
|
||||
{#if !readOnly}
|
||||
<div class="flex items-center translate-x-2.5">
|
||||
<FolderMenu
|
||||
align="end"
|
||||
onEdit={() => {
|
||||
showFolderModal = true;
|
||||
}}
|
||||
onShare={() => {
|
||||
showShareModal = true;
|
||||
}}
|
||||
onDelete={() => {
|
||||
showDeleteConfirm = true;
|
||||
}}
|
||||
onExport={() => {
|
||||
exportHandler();
|
||||
}}
|
||||
onCreateSubFolder={() => {
|
||||
showCreateSubFolderModal = true;
|
||||
}}
|
||||
>
|
||||
<EllipsisHorizontal className="size-4" strokeWidth="2.5" />
|
||||
</button>
|
||||
</FolderMenu>
|
||||
</div>
|
||||
<button
|
||||
class="p-1.5 dark:hover:bg-gray-850 rounded-full touch-auto"
|
||||
aria-label={$i18n.t('Folder options')}
|
||||
on:click={(e) => {}}
|
||||
>
|
||||
<EllipsisHorizontal className="size-4" strokeWidth="2.5" />
|
||||
</button>
|
||||
</FolderMenu>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -39,6 +39,7 @@
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
export let shareEnabled: boolean = false;
|
||||
export let readOnly: boolean = false;
|
||||
|
||||
export let shareHandler: Function;
|
||||
export let moveChatHandler: Function;
|
||||
@@ -364,7 +365,7 @@
|
||||
<hr class="border-gray-50/30 dark:border-gray-800/30 my-1" />
|
||||
{/if}
|
||||
|
||||
{#if !$temporaryChatEnabled && ($user?.role === 'admin' || ($user.permissions?.chat?.share ?? true))}
|
||||
{#if !readOnly && !$temporaryChatEnabled && ($user?.role === 'admin' || ($user.permissions?.chat?.share ?? true))}
|
||||
<button
|
||||
draggable="false"
|
||||
class="flex gap-2 items-center px-3 py-1.5 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl select-none w-full"
|
||||
@@ -438,7 +439,7 @@
|
||||
<div class="flex items-center">{$i18n.t('Copy')}</div>
|
||||
</button>
|
||||
|
||||
{#if !$temporaryChatEnabled && chat?.id}
|
||||
{#if !readOnly && !$temporaryChatEnabled && chat?.id}
|
||||
<hr class="border-gray-50/30 dark:border-gray-800/30 my-1" />
|
||||
|
||||
{#if $folders.length > 0}
|
||||
|
||||
@@ -65,6 +65,9 @@
|
||||
export let shiftKey = false;
|
||||
export let readonly = false;
|
||||
|
||||
export let ownerName: string | null = null;
|
||||
export let ownerUserId: string | null = null;
|
||||
|
||||
export let onDragEnd = () => {};
|
||||
|
||||
function formatTimeAgo(timestamp: number): string {
|
||||
@@ -534,6 +537,15 @@
|
||||
{/if}
|
||||
|
||||
<div class="flex self-center flex-1 w-full min-w-0">
|
||||
{#if ownerUserId}
|
||||
<Tooltip content={ownerName || 'Unknown'}>
|
||||
<img
|
||||
src="/api/v1/users/{ownerUserId}/profile/image"
|
||||
alt=""
|
||||
class="size-4 rounded-full shrink-0 object-cover mr-1.5"
|
||||
/>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
{#if unread}
|
||||
<div class="shrink-0 self-center pr-2.5 flex transition-opacity duration-300">
|
||||
<div class="size-1.5 bg-sky-500 rounded-full" />
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import { goto } from '$app/navigation';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
import { chatId, mobile, selectedFolder, showSidebar } from '$lib/stores';
|
||||
import { chatId, mobile, selectedFolder, showSidebar, user } from '$lib/stores';
|
||||
|
||||
import {
|
||||
deleteFolderById,
|
||||
@@ -703,6 +703,9 @@
|
||||
createdAt={chat.created_at}
|
||||
updatedAt={chat.updated_at}
|
||||
lastReadAt={chat.last_read_at}
|
||||
ownerName={chat.owner_name ?? null}
|
||||
ownerUserId={chat.owner_name ? chat.user_id : null}
|
||||
readonly={chat.user_id !== $user?.id}
|
||||
{shiftKey}
|
||||
on:change={(e) => {
|
||||
dispatch('change', e.detail);
|
||||
|
||||
@@ -108,6 +108,8 @@
|
||||
title={chat.title}
|
||||
createdAt={chat.created_at}
|
||||
updatedAt={chat.updated_at}
|
||||
ownerName={chat.owner_name}
|
||||
ownerUserId={chat.user_id}
|
||||
readonly={chat.readonly ?? !isWritable}
|
||||
/>
|
||||
{/each}
|
||||
|
||||
Reference in new issue
Block a user