Skip to content

Commit 45fcf27

Browse files
committed
refac
1 parent c783fd3 commit 45fcf27

11 files changed

Lines changed: 474 additions & 66 deletions

File tree

src/lib/apis/folders/index.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,3 +234,89 @@ export const deleteFolderById = async (token: string, id: string, deleteContents
234234

235235
return res;
236236
};
237+
238+
export const updateFolderAccessById = async (
239+
token: string,
240+
id: string,
241+
accessGrants: any[]
242+
) => {
243+
let error = null;
244+
245+
const res = await fetch(`${WEBUI_API_BASE_URL}/folders/${id}/access/update`, {
246+
method: 'POST',
247+
headers: {
248+
Accept: 'application/json',
249+
'Content-Type': 'application/json',
250+
authorization: `Bearer ${token}`
251+
},
252+
body: JSON.stringify({ access_grants: accessGrants })
253+
})
254+
.then(async (res) => {
255+
if (!res.ok) throw await res.json();
256+
return res.json();
257+
})
258+
.catch((err) => {
259+
error = err.detail;
260+
return null;
261+
});
262+
263+
if (error) {
264+
throw error;
265+
}
266+
267+
return res;
268+
};
269+
270+
export const getSharedFolders = async (token: string) => {
271+
let error = null;
272+
273+
const res = await fetch(`${WEBUI_API_BASE_URL}/folders/shared`, {
274+
method: 'GET',
275+
headers: {
276+
Accept: 'application/json',
277+
'Content-Type': 'application/json',
278+
authorization: `Bearer ${token}`
279+
}
280+
})
281+
.then(async (res) => {
282+
if (!res.ok) throw await res.json();
283+
return res.json();
284+
})
285+
.catch((err) => {
286+
error = err.detail;
287+
return [];
288+
});
289+
290+
if (error) {
291+
throw error;
292+
}
293+
294+
return res;
295+
};
296+
297+
export const getSharedFolderChats = async (token: string, folderId: string) => {
298+
let error = null;
299+
300+
const res = await fetch(`${WEBUI_API_BASE_URL}/folders/${folderId}/shared/chats`, {
301+
method: 'GET',
302+
headers: {
303+
Accept: 'application/json',
304+
'Content-Type': 'application/json',
305+
authorization: `Bearer ${token}`
306+
}
307+
})
308+
.then(async (res) => {
309+
if (!res.ok) throw await res.json();
310+
return res.json();
311+
})
312+
.catch((err) => {
313+
error = err.detail;
314+
return null;
315+
});
316+
317+
if (error) {
318+
throw error;
319+
}
320+
321+
return res;
322+
};

src/lib/components/chat/Chat.svelte

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@
169169
let chat = null;
170170
let tags = [];
171171
172+
// Read-only when viewing someone else's chat (e.g. via shared folder access)
173+
$: readOnly = chat != null && chat.user_id !== $user?.id;
174+
172175
let chatTasks = [];
173176
174177
let history = {
@@ -3103,6 +3106,7 @@
31033106
<Messages
31043107
bind:this={messagesRef}
31053108
chatId={$chatId}
3109+
{readOnly}
31063110
bind:history
31073111
bind:autoScroll
31083112
bind:prompt
@@ -3126,7 +3130,16 @@
31263130
</div>
31273131
</div>
31283132

3129-
<div class=" pb-2 {dragged ? 'z-0' : 'z-10'}">
3133+
{#if readOnly}
3134+
<div class="pb-6 z-10">
3135+
<div
3136+
class="text-xs text-gray-400 dark:text-gray-500 text-center"
3137+
>
3138+
{$i18n.t('Read only')}
3139+
</div>
3140+
</div>
3141+
{:else}
3142+
<div class=" pb-2 {dragged ? 'z-0' : 'z-10'}">
31303143
<MessageInput
31313144
bind:this={messageInput}
31323145
{history}
@@ -3208,6 +3221,7 @@
32083221
<!-- {$i18n.t('LLMs can make mistakes. Verify important information.')} -->
32093222
</div>
32103223
</div>
3224+
{/if}
32113225
{:else}
32123226
<div class="flex items-center h-full">
32133227
<Placeholder

src/lib/components/chat/Placeholder/FolderPlaceholder.svelte

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@
44
55
const i18n: Writable<any> = getContext('i18n');
66
7+
import { user } from '$lib/stores';
8+
79
import { fade } from 'svelte/transition';
810
911
import ChatList from './ChatList.svelte';
1012
import FolderKnowledge from './FolderKnowledge.svelte';
1113
import Spinner from '$lib/components/common/Spinner.svelte';
1214
import { getChatListByFolderId } from '$lib/apis/chats';
15+
import { getSharedFolderChats } from '$lib/apis/folders';
1316
1417
export let folder: any = null;
1518
@@ -22,24 +25,8 @@
2225
let allChatsLoaded = false;
2326
2427
const loadChats = async () => {
25-
chatListLoading = true;
26-
27-
page += 1;
28-
29-
let newChatList: any[] = [];
30-
31-
newChatList = await getChatListByFolderId(localStorage.token, folder.id, page).catch(
32-
(error) => {
33-
console.error(error);
34-
return [];
35-
}
36-
);
37-
38-
// once the bottom of the list has been reached (no results) there is no need to continue querying
39-
allChatsLoaded = newChatList.length === 0;
40-
chats = [...(chats || []), ...(newChatList || [])];
41-
42-
chatListLoading = false;
28+
// getSharedFolderChats returns all users' chats in one shot; no pagination
29+
allChatsLoaded = true;
4330
};
4431
4532
const setChatList = async () => {
@@ -49,12 +36,21 @@
4936
chatListLoading = false;
5037
5138
if (folder && folder.id) {
52-
const res = await getChatListByFolderId(localStorage.token, folder.id, page);
53-
54-
if (res) {
55-
chats = res;
39+
// Always use the shared folder endpoint so owners also see
40+
// chats created by users who have write access to this folder.
41+
const res = await getSharedFolderChats(localStorage.token, folder.id).catch(
42+
(error) => {
43+
console.error(error);
44+
return null;
45+
}
46+
);
47+
if (res && res.chats) {
48+
chats = res.chats;
49+
allChatsLoaded = true;
5650
} else {
57-
chats = [];
51+
// Fallback to regular API (e.g. if user has no shared access)
52+
const fallback = await getChatListByFolderId(localStorage.token, folder.id, page).catch(() => []);
53+
chats = fallback || [];
5854
}
5955
} else {
6056
chats = [];

src/lib/components/chat/Placeholder/FolderTitle.svelte

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import { getChatsByFolderId } from '$lib/apis/chats';
2121
2222
import FolderModal from '$lib/components/layout/Sidebar/Folders/FolderModal.svelte';
23+
import FolderShareModal from '$lib/components/layout/Sidebar/Folders/FolderShareModal.svelte';
2324
2425
import Folder from '$lib/components/icons/Folder.svelte';
2526
import XMark from '$lib/components/icons/XMark.svelte';
@@ -36,6 +37,7 @@
3637
3738
let showFolderModal = false;
3839
let showCreateSubFolderModal = false;
40+
let showShareModal = false;
3941
let showDeleteConfirm = false;
4042
let deleteFolderContents = true;
4143
@@ -173,6 +175,8 @@
173175
onSubmit={createSubFolderHandler}
174176
/>
175177

178+
<FolderShareModal bind:show={showShareModal} {folder} />
179+
176180
<DeleteConfirmDialog
177181
bind:show={showDeleteConfirm}
178182
title={$i18n.t('Delete folder?')}
@@ -232,6 +236,9 @@
232236
onEdit={() => {
233237
showFolderModal = true;
234238
}}
239+
onShare={() => {
240+
showShareModal = true;
241+
}}
235242
onDelete={() => {
236243
showDeleteConfirm = true;
237244
}}

src/lib/components/layout/Sidebar.svelte

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
deleteAllChats,
4747
getChatListBySearchText
4848
} from '$lib/apis/chats';
49-
import { createNewFolder, getFolders, updateFolderParentIdById } from '$lib/apis/folders';
49+
import { createNewFolder, getFolders, getSharedFolders, updateFolderParentIdById } from '$lib/apis/folders';
5050
import { createNewNote, getPinnedNoteList, toggleNotePinnedStatusById } from '$lib/apis/notes';
5151
import { updateUserSettings } from '$lib/apis/users';
5252
import { checkActiveChats } from '$lib/apis/tasks';
@@ -61,6 +61,7 @@
6161
import Folder from '../common/Folder.svelte';
6262
import Tooltip from '../common/Tooltip.svelte';
6363
import Folders from './Sidebar/Folders.svelte';
64+
import SharedFolderItem from './Sidebar/SharedFolderItem.svelte';
6465
import { getChannels, createNewChannel } from '$lib/apis/channels';
6566
import ChannelModal from './Sidebar/ChannelModal.svelte';
6667
import ChannelItem from './Sidebar/ChannelItem.svelte';
@@ -98,12 +99,15 @@
9899
let showPinnedNotes = false;
99100
let showChannels = false;
100101
let showFolders = false;
102+
let showSharedFolders = false;
101103
102104
let folders = {};
103105
let folderRegistry = {};
104106
105107
let newFolderId = null;
106108
109+
let sharedFolders: any[] = [];
110+
107111
$: pinnedItems = $settings?.pinnedMenuItems ?? DEFAULT_PINNED_ITEMS;
108112
109113
const isMenuItemVisible = (id) => {
@@ -215,6 +219,31 @@
215219
});
216220
}
217221
}
222+
223+
// Merge shared folders into the same structure
224+
try {
225+
sharedFolders = await getSharedFolders(localStorage.token);
226+
} catch (e) {
227+
sharedFolders = [];
228+
}
229+
230+
for (const sf of sharedFolders) {
231+
if (folders[sf.id]) continue; // Already owned by user
232+
folders[sf.id] = { ...sf, shared: true };
233+
}
234+
235+
// Build parent-child relationships for shared folders
236+
for (const sf of sharedFolders) {
237+
if (folders[sf.id]?.shared && sf.parent_id && folders[sf.parent_id]) {
238+
folders[sf.parent_id].childrenIds = folders[sf.parent_id].childrenIds
239+
? [...new Set([...folders[sf.parent_id].childrenIds, sf.id])]
240+
: [sf.id];
241+
}
242+
}
243+
};
244+
245+
const initSharedFolders = async () => {
246+
await initFolders();
218247
};
219248
220249
const createFolder = async ({ name, data, parent_id }) => {
@@ -291,6 +320,7 @@
291320
scrollPaginationEnabled.set(false);
292321
293322
initFolders();
323+
initSharedFolders();
294324
await Promise.all([
295325
await (async () => {
296326
console.log('Init tags');

src/lib/components/layout/Sidebar/ChatItem.svelte

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
6464
export let selected = false;
6565
export let shiftKey = false;
66+
export let readonly = false;
6667
6768
export let onDragEnd = () => {};
6869
@@ -445,7 +446,7 @@
445446
id="sidebar-chat-group"
446447
bind:this={itemElement}
447448
class=" w-full {className} relative group"
448-
draggable={!confirmEdit}
449+
draggable={!confirmEdit && !readonly}
449450
>
450451
{#if confirmEdit}
451452
<div
@@ -509,6 +510,7 @@
509510
lastReadAt = Date.now() / 1000;
510511
}}
511512
on:dblclick={async (e) => {
513+
if (readonly) return;
512514
e.preventDefault();
513515
e.stopPropagation();
514516

@@ -557,6 +559,7 @@
557559
{/if}
558560

559561
<!-- svelte-ignore a11y-no-static-element-interactions -->
562+
{#if !readonly}
560563
<div
561564
id="sidebar-chat-item-menu"
562565
class="
@@ -691,4 +694,5 @@
691694
</div>
692695
{/if}
693696
</div>
697+
{/if}
694698
</div>

0 commit comments

Comments
 (0)