refac: shared chat

This commit is contained in:
Timothy Jaeryang Baek
2026-04-17 10:16:32 +09:00
parent 4d2f189810
commit 2e52ad8ff2
7 changed files with 818 additions and 241 deletions
+71
View File
@@ -953,6 +953,77 @@ export const deleteSharedChatById = async (token: string, id: string) => {
return res;
};
export const updateChatAccessGrants = async (
token: string,
id: string,
accessGrants: object[]
) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/shared/${id}/access/update`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
...(token && { authorization: `Bearer ${token}` })
},
body: JSON.stringify({
access_grants: accessGrants
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err;
console.error(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const getChatAccessGrants = async (token: string, id: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/chats/shared/${id}/access`, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
...(token && { authorization: `Bearer ${token}` })
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.then((json) => {
return json;
})
.catch((err) => {
error = err;
console.error(err);
return null;
});
if (error) {
throw error;
}
return res;
};
export const updateChatById = async (token: string, id: string, chat: object) => {
let error = null;
+99 -67
View File
@@ -3,24 +3,32 @@
import { models, config } from '$lib/stores';
import { toast } from 'svelte-sonner';
import { deleteSharedChatById, getChatById, shareChatById } from '$lib/apis/chats';
import {
deleteSharedChatById,
getChatById,
shareChatById,
getChatAccessGrants,
updateChatAccessGrants
} from '$lib/apis/chats';
import { copyToClipboard } from '$lib/utils';
import Modal from '../common/Modal.svelte';
import Link from '../icons/Link.svelte';
import XMark from '$lib/components/icons/XMark.svelte';
import AccessControl from '$lib/components/workspace/common/AccessControl.svelte';
export let chatId;
let chat = null;
let shareUrl = null;
let accessGrants: any[] = [];
const i18n = getContext('i18n');
const shareLocalChat = async () => {
const _chat = chat;
const sharedChat = await shareChatById(localStorage.token, chatId);
shareUrl = `${window.location.origin}/s/${sharedChat.id}`;
shareUrl = `${window.location.origin}/s/${sharedChat.share_id}`;
console.log(shareUrl);
chat = await getChatById(localStorage.token, chatId);
@@ -54,6 +62,25 @@
);
};
const loadAccessGrants = async () => {
if (!chatId) return;
try {
accessGrants = (await getChatAccessGrants(localStorage.token, chatId)) ?? [];
} catch (e) {
console.error('Failed to load access grants', e);
accessGrants = [];
}
};
const saveAccessGrants = async () => {
try {
await updateChatAccessGrants(localStorage.token, chatId, accessGrants);
toast.success($i18n.t('Access updated'));
} catch (e) {
toast.error(`${e}`);
}
};
export let show = false;
const isDifferentChat = (_chat) => {
@@ -73,8 +100,10 @@
if (isDifferentChat(_chat)) {
chat = _chat;
}
await loadAccessGrants();
} else {
chat = null;
accessGrants = [];
console.log(chat);
}
})();
@@ -97,8 +126,8 @@
</div>
{#if chat}
<div class="px-5 pt-4 pb-5 w-full flex flex-col justify-center">
<div class=" text-sm dark:text-gray-300 mb-1">
<div class="px-5 pt-4 pb-5 w-full flex flex-col">
<div class="text-sm dark:text-gray-300">
{#if chat.share_id}
<a href="/s/{chat.share_id}" target="_blank"
>{$i18n.t('You have shared this chat')}
@@ -124,70 +153,73 @@
{/if}
</div>
<div class="flex justify-end">
<div class="flex flex-col items-end space-x-1 mt-3">
<div class="flex gap-1">
{#if $config?.features.enable_community_sharing}
<button
class="self-center flex items-center gap-1 px-3.5 py-2 text-sm font-medium bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:text-white dark:hover:bg-gray-800 transition rounded-full"
type="button"
on:click={() => {
shareChat();
show = false;
}}
>
{$i18n.t('Share to Open WebUI Community')}
</button>
{/if}
<button
class="self-center flex items-center gap-1 px-3.5 py-2 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
type="button"
id="copy-and-share-chat-button"
on:click={async () => {
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
if (isSafari) {
// Oh, Safari, you're so special, let's give you some extra love and attention
console.log('isSafari');
const getUrlPromise = async () => {
const url = await shareLocalChat();
return new Blob([url], { type: 'text/plain' });
};
navigator.clipboard
.write([
new ClipboardItem({
'text/plain': getUrlPromise()
})
])
.then(() => {
console.log('Async: Copying to clipboard was successful!');
return true;
})
.catch((error) => {
console.error('Async: Could not copy text: ', error);
return false;
});
} else {
copyToClipboard(await shareLocalChat());
}
toast.success($i18n.t('Copied shared chat URL to clipboard!'));
show = false;
}}
>
<Link />
{#if chat.share_id}
{$i18n.t('Update and Copy Link')}
{:else}
{$i18n.t('Copy Link')}
{/if}
</button>
</div>
{#if chat.share_id}
<div class="mt-3">
<AccessControl
bind:accessGrants
accessRoles={['read']}
onChange={saveAccessGrants}
/>
</div>
{/if}
<div class="flex justify-end gap-1 mt-3">
{#if $config?.features.enable_community_sharing}
<button
class="flex items-center gap-1 px-3.5 py-2 text-sm font-medium bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:text-white dark:hover:bg-gray-800 transition rounded-full"
type="button"
on:click={() => {
shareChat();
}}
>
{$i18n.t('Share to Open WebUI Community')}
</button>
{/if}
<button
class="flex items-center gap-1 px-3.5 py-2 text-sm font-medium bg-black hover:bg-gray-900 text-white dark:bg-white dark:text-black dark:hover:bg-gray-100 transition rounded-full"
type="button"
id="copy-and-share-chat-button"
on:click={async () => {
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
if (isSafari) {
console.log('isSafari');
const getUrlPromise = async () => {
const url = await shareLocalChat();
return new Blob([url], { type: 'text/plain' });
};
navigator.clipboard
.write([
new ClipboardItem({
'text/plain': getUrlPromise()
})
])
.then(() => {
console.log('Async: Copying to clipboard was successful!');
return true;
})
.catch((error) => {
console.error('Async: Could not copy text: ', error);
return false;
});
} else {
copyToClipboard(await shareLocalChat());
}
toast.success($i18n.t('Copied shared chat URL to clipboard!'));
}}
>
<Link />
{#if chat.share_id}
{$i18n.t('Update and Copy Link')}
{:else}
{$i18n.t('Copy Link')}
{/if}
</button>
</div>
</div>
{/if}