refac
This commit is contained in:
@@ -75,6 +75,7 @@ from open_webui.socket.main import (
|
||||
)
|
||||
from open_webui.utils.access_control import has_connection_access, has_permission
|
||||
from open_webui.utils.access_control.files import get_accessible_folder_files
|
||||
from open_webui.utils.access_control.folders import has_folder_access
|
||||
from open_webui.utils.chat import generate_chat_completion
|
||||
from open_webui.utils.code_interpreter import execute_code_jupyter
|
||||
from open_webui.utils.context_compaction import compact_messages_for_request
|
||||
@@ -2353,13 +2354,14 @@ async def process_chat_payload(request, form_data, user, metadata, model):
|
||||
folder_id = metadata.get('folder_id', None)
|
||||
|
||||
if folder_id and user:
|
||||
folder = await Folders.get_folder_by_id_and_user_id(folder_id, user.id)
|
||||
folder = await Folders.get_folder_by_id(folder_id)
|
||||
if folder and user.role != 'admin' and not await has_folder_access(user.id, folder, 'read', db=None):
|
||||
folder = None
|
||||
|
||||
if folder and folder.data:
|
||||
if 'system_prompt' in folder.data:
|
||||
form_data = await apply_system_prompt_to_body(folder.data['system_prompt'], form_data, metadata, user)
|
||||
if 'files' in folder.data:
|
||||
# Defensive: filter to entries the caller can still read.
|
||||
allowed_files = await get_accessible_folder_files(folder.data['files'], user)
|
||||
if metadata.get('params', {}).get('function_calling') == 'legacy':
|
||||
form_data['files'] = [
|
||||
@@ -2583,7 +2585,11 @@ async def process_chat_payload(request, form_data, user, metadata, model):
|
||||
# Get folder files
|
||||
folder_id = file_item.get('id', None)
|
||||
if folder_id:
|
||||
folder = await Folders.get_folder_by_id_and_user_id(folder_id, user.id)
|
||||
folder = await Folders.get_folder_by_id(folder_id)
|
||||
if folder and user.role != 'admin' and not await has_folder_access(
|
||||
user.id, folder, 'read', db=None
|
||||
):
|
||||
folder = None
|
||||
if folder and folder.data and 'files' in folder.data:
|
||||
files = [f for f in files if f.get('id', None) != folder_id]
|
||||
files = [*files, *await get_accessible_folder_files(folder.data['files'], user)]
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import dayjs from 'dayjs';
|
||||
import { onMount, tick, getContext } from 'svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
@@ -16,12 +15,11 @@
|
||||
import Markdown from './Markdown.svelte';
|
||||
import Name from './Name.svelte';
|
||||
import Skeleton from './Skeleton.svelte';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import ProfileImage from './ProfileImage.svelte';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
import equal from 'fast-deep-equal';
|
||||
import { formatMessageTimestamp, formatMessageTimestampFull } from '$lib/utils';
|
||||
const i18n = getContext('i18n');
|
||||
dayjs.extend(localizedFormat);
|
||||
|
||||
export let chatId;
|
||||
export let history;
|
||||
@@ -402,14 +400,6 @@
|
||||
<div class="w-full rounded-xl pl-5 pr-2 py-2 mt-2">
|
||||
<Name>
|
||||
{$i18n.t('Merged Response')}
|
||||
|
||||
{#if message.timestamp}
|
||||
<span
|
||||
class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium uppercase ml-0.5 -mt-0.5"
|
||||
>
|
||||
{dayjs(message.timestamp * 1000).format('LT')}
|
||||
</span>
|
||||
{/if}
|
||||
</Name>
|
||||
|
||||
<div class="mt-1 markdown-prose w-full min-w-full">
|
||||
@@ -419,6 +409,23 @@
|
||||
<Markdown id={`merged`} content={message.content ?? ''} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if message.timestamp}
|
||||
<div class="mt-0.5 flex justify-start text-gray-600 dark:text-gray-500">
|
||||
<Tooltip
|
||||
className="flex self-center"
|
||||
content={formatMessageTimestampFull(message.timestamp * 1000)}
|
||||
placement="bottom"
|
||||
>
|
||||
<time
|
||||
datetime={new Date(message.timestamp * 1000).toISOString()}
|
||||
class="invisible group-hover:visible ml-1 text-[0.6875rem] tabular-nums text-gray-400 dark:text-gray-600 select-none"
|
||||
>
|
||||
{formatMessageTimestamp(message.timestamp * 1000)}
|
||||
</time>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { toast } from 'svelte-sonner';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { createEventDispatcher, onDestroy } from 'svelte';
|
||||
import { onMount, tick, getContext } from 'svelte';
|
||||
@@ -32,7 +31,8 @@
|
||||
getMessageContentParts,
|
||||
sanitizeResponseContent,
|
||||
createMessagesList,
|
||||
formatDate,
|
||||
formatMessageTimestamp,
|
||||
formatMessageTimestampFull,
|
||||
removeDetails,
|
||||
removeAllDetails
|
||||
} from '$lib/utils';
|
||||
@@ -668,24 +668,6 @@
|
||||
{model?.name ?? message.model}
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
{#if message.timestamp}
|
||||
<div
|
||||
class="self-center text-xs font-medium first-letter:capitalize ml-0.5 translate-y-[1px] {($settings?.highContrastMode ??
|
||||
false)
|
||||
? 'dark:text-gray-100 text-gray-900'
|
||||
: 'invisible group-hover:visible transition text-gray-400'}"
|
||||
>
|
||||
<Tooltip content={dayjs(message.timestamp * 1000).format('LLLL')}>
|
||||
<span class="line-clamp-1"
|
||||
>{$i18n.t(formatDate(message.timestamp * 1000), {
|
||||
LOCALIZED_TIME: dayjs(message.timestamp * 1000).format('LT'),
|
||||
LOCALIZED_DATE: dayjs(message.timestamp * 1000).format('L')
|
||||
})}</span
|
||||
>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
</Name>
|
||||
|
||||
<div>
|
||||
@@ -1496,6 +1478,21 @@
|
||||
</button>
|
||||
</Tooltip>
|
||||
{/each}
|
||||
|
||||
{#if message.timestamp}
|
||||
<Tooltip
|
||||
className="flex self-center"
|
||||
content={formatMessageTimestampFull(message.timestamp * 1000)}
|
||||
placement="bottom"
|
||||
>
|
||||
<time
|
||||
datetime={new Date(message.timestamp * 1000).toISOString()}
|
||||
class="invisible group-hover:visible ml-1 text-[0.6875rem] tabular-nums text-gray-400 dark:text-gray-600 select-none"
|
||||
>
|
||||
{formatMessageTimestamp(message.timestamp * 1000)}
|
||||
</time>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
<script lang="ts">
|
||||
import dayjs from 'dayjs';
|
||||
import { toast } from 'svelte-sonner';
|
||||
import { tick, getContext, onMount } from 'svelte';
|
||||
|
||||
import { models, settings } from '$lib/stores';
|
||||
import { user as _user } from '$lib/stores';
|
||||
import { copyToClipboard as _copyToClipboard, formatDate } from '$lib/utils';
|
||||
import {
|
||||
copyToClipboard as _copyToClipboard,
|
||||
formatMessageTimestamp,
|
||||
formatMessageTimestampFull
|
||||
} from '$lib/utils';
|
||||
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
|
||||
import equal from 'fast-deep-equal';
|
||||
|
||||
@@ -18,10 +21,7 @@
|
||||
import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||
import SubagentResultRow from './SubagentResultRow.svelte';
|
||||
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
dayjs.extend(localizedFormat);
|
||||
type SubagentResult = {
|
||||
async_subagent_result: true;
|
||||
delegation_id?: string;
|
||||
@@ -165,48 +165,8 @@
|
||||
{:else}
|
||||
{$i18n.t('You')}
|
||||
{/if}
|
||||
|
||||
{#if message.timestamp}
|
||||
<div
|
||||
class="self-center text-xs font-medium first-letter:capitalize ml-0.5 translate-y-[1px] {($settings?.highContrastMode ??
|
||||
false)
|
||||
? 'dark:text-gray-100 text-gray-900'
|
||||
: 'invisible group-hover:visible transition'}"
|
||||
>
|
||||
<Tooltip content={dayjs(message.timestamp * 1000).format('LLLL')}>
|
||||
<!-- $i18n.t('Today at {{LOCALIZED_TIME}}') -->
|
||||
<!-- $i18n.t('Yesterday at {{LOCALIZED_TIME}}') -->
|
||||
<!-- $i18n.t('{{LOCALIZED_DATE}} at {{LOCALIZED_TIME}}') -->
|
||||
|
||||
<span class="line-clamp-1"
|
||||
>{$i18n.t(formatDate(message.timestamp * 1000), {
|
||||
LOCALIZED_TIME: dayjs(message.timestamp * 1000).format('LT'),
|
||||
LOCALIZED_DATE: dayjs(message.timestamp * 1000).format('L')
|
||||
})}</span
|
||||
>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{/if}
|
||||
</Name>
|
||||
</div>
|
||||
{:else if message.timestamp && !subagentResult}
|
||||
<div class="flex justify-end pr-2 text-xs">
|
||||
<div
|
||||
class="text-[0.65rem] font-medium first-letter:capitalize mb-0.5 {($settings?.highContrastMode ??
|
||||
false)
|
||||
? 'dark:text-gray-100 text-gray-900'
|
||||
: 'invisible group-hover:visible transition text-gray-400'}"
|
||||
>
|
||||
<Tooltip content={dayjs(message.timestamp * 1000).format('LLLL')}>
|
||||
<span class="line-clamp-1"
|
||||
>{$i18n.t(formatDate(message.timestamp * 1000), {
|
||||
LOCALIZED_TIME: dayjs(message.timestamp * 1000).format('LT'),
|
||||
LOCALIZED_DATE: dayjs(message.timestamp * 1000).format('L')
|
||||
})}</span
|
||||
>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="chat-{message.role} w-full min-w-full markdown-prose">
|
||||
@@ -412,6 +372,21 @@
|
||||
? 'justify-end'
|
||||
: ''} text-gray-600 dark:text-gray-500"
|
||||
>
|
||||
{#if message.timestamp}
|
||||
<Tooltip
|
||||
className="flex self-center"
|
||||
content={formatMessageTimestampFull(message.timestamp * 1000)}
|
||||
placement="bottom"
|
||||
>
|
||||
<time
|
||||
datetime={new Date(message.timestamp * 1000).toISOString()}
|
||||
class="invisible group-hover:visible mr-1 text-[0.6875rem] tabular-nums text-gray-400 dark:text-gray-600 select-none"
|
||||
>
|
||||
{formatMessageTimestamp(message.timestamp * 1000)}
|
||||
</time>
|
||||
</Tooltip>
|
||||
{/if}
|
||||
|
||||
{#if !($settings?.chatBubble ?? true)}
|
||||
{#if siblings.length > 1}
|
||||
<div class="flex self-center" dir="ltr">
|
||||
|
||||
@@ -526,7 +526,7 @@
|
||||
isResizing = true;
|
||||
|
||||
startClientX = e.clientX;
|
||||
startWidth = $sidebarWidth ?? 250;
|
||||
startWidth = $sidebarWidth ?? 245;
|
||||
|
||||
document.body.style.userSelect = 'none';
|
||||
};
|
||||
@@ -847,11 +847,11 @@
|
||||
<div class=" self-center flex items-center justify-center size-9">
|
||||
<img
|
||||
src="{WEBUI_BASE_URL}/static/favicon.png"
|
||||
class="sidebar-new-chat-icon size-6 rounded-full group-hover:hidden"
|
||||
class="sidebar-new-chat-icon size-5 rounded-full group-hover:hidden"
|
||||
alt=""
|
||||
/>
|
||||
|
||||
<Sidebar className="size-5 hidden group-hover:flex" />
|
||||
<Sidebar className="size-4.5 hidden group-hover:flex" />
|
||||
</div>
|
||||
</button>
|
||||
</Tooltip>
|
||||
@@ -874,7 +874,7 @@
|
||||
aria-label={$i18n.t('New Chat')}
|
||||
>
|
||||
<div class=" self-center flex items-center justify-center size-9">
|
||||
<PencilSquare className="size-4.5" />
|
||||
<PencilSquare className="size-4" />
|
||||
</div>
|
||||
</a>
|
||||
</Tooltip>
|
||||
@@ -894,7 +894,7 @@
|
||||
aria-label={$i18n.t('Search')}
|
||||
>
|
||||
<div class=" self-center flex items-center justify-center size-9">
|
||||
<Search className="size-4.5" />
|
||||
<Search className="size-4" />
|
||||
</div>
|
||||
</button>
|
||||
</Tooltip>
|
||||
@@ -919,7 +919,7 @@
|
||||
>
|
||||
<div class=" self-center flex items-center justify-center size-9">
|
||||
{#if itemId === 'notes'}
|
||||
<Note className="size-4.5" />
|
||||
<Note className="size-4" />
|
||||
{:else if itemId === 'workspace'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -927,7 +927,7 @@
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-4.5"
|
||||
class="size-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
@@ -942,7 +942,7 @@
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-4.5"
|
||||
class="size-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
@@ -957,7 +957,7 @@
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-4.5"
|
||||
class="size-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
@@ -966,7 +966,7 @@
|
||||
/>
|
||||
</svg>
|
||||
{:else if itemId === 'playground'}
|
||||
<Code className="size-4.5" />
|
||||
<Code className="size-4" />
|
||||
{/if}
|
||||
</div>
|
||||
</a>
|
||||
@@ -1047,7 +1047,7 @@
|
||||
: 'invisible'}"
|
||||
>
|
||||
<div
|
||||
class="sidebar px-1.5 pt-2 pb-1.5 flex justify-between space-x-1 text-gray-600 dark:text-gray-400 sticky top-0 z-10 -mb-3"
|
||||
class="sidebar px-1.5 pt-1.5 pb-1 flex justify-between space-x-1 text-gray-600 dark:text-gray-400 sticky top-0 z-10 -mb-2"
|
||||
>
|
||||
<a
|
||||
class="flex items-center rounded-xl size-8.5 h-full justify-center hover:bg-gray-100/50 dark:hover:bg-gray-850/50 transition no-drag-region"
|
||||
@@ -1058,7 +1058,7 @@
|
||||
<img
|
||||
crossorigin="anonymous"
|
||||
src="{WEBUI_BASE_URL}/static/favicon.png"
|
||||
class="sidebar-new-chat-icon size-6 rounded-full"
|
||||
class="sidebar-new-chat-icon size-5 rounded-full"
|
||||
alt=""
|
||||
/>
|
||||
</a>
|
||||
@@ -1085,7 +1085,7 @@
|
||||
aria-label={$showSidebar ? $i18n.t('Close Sidebar') : $i18n.t('Open Sidebar')}
|
||||
>
|
||||
<div class=" self-center p-1.5">
|
||||
<Sidebar />
|
||||
<Sidebar className="size-4.5" />
|
||||
</div>
|
||||
</button>
|
||||
</Tooltip>
|
||||
@@ -1098,7 +1098,7 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative flex flex-col flex-1 overflow-y-auto scrollbar-hidden pt-3 pb-3"
|
||||
class="relative flex flex-col flex-1 overflow-y-auto scrollbar-hidden pt-2.5 pb-2.5"
|
||||
on:scroll={(e) => {
|
||||
if (e.target.scrollTop === 0) {
|
||||
scrollTop = 0;
|
||||
@@ -1107,18 +1107,18 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="pb-1.5">
|
||||
<div class="pb-1">
|
||||
<div class="px-1.5 flex justify-center text-gray-800 dark:text-gray-200">
|
||||
<a
|
||||
id="sidebar-new-chat-button"
|
||||
class="group grow flex items-center space-x-2.5 rounded-xl px-2.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition outline-none"
|
||||
class="group grow flex items-center space-x-2.5 rounded-xl px-2.5 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-900 transition outline-none"
|
||||
href="/"
|
||||
draggable="false"
|
||||
on:click={newChatHandler}
|
||||
aria-label={$i18n.t('New Chat')}
|
||||
>
|
||||
<div class="self-center">
|
||||
<PencilSquare className=" size-4.5" strokeWidth="2" />
|
||||
<PencilSquare className=" size-4" strokeWidth="2" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-1 self-center translate-y-[0.5px]">
|
||||
@@ -1132,7 +1132,7 @@
|
||||
<div class="px-1.5 flex justify-center text-gray-800 dark:text-gray-200">
|
||||
<button
|
||||
id="sidebar-search-button"
|
||||
class="group grow flex items-center space-x-2.5 rounded-xl px-2.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition outline-none"
|
||||
class="group grow flex items-center space-x-2.5 rounded-xl px-2.5 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-900 transition outline-none"
|
||||
on:click={() => {
|
||||
showSearch.set(true);
|
||||
}}
|
||||
@@ -1140,7 +1140,7 @@
|
||||
aria-label={$i18n.t('Search')}
|
||||
>
|
||||
<div class="self-center">
|
||||
<Search strokeWidth="2" className="size-4.5" />
|
||||
<Search strokeWidth="2" className="size-4" />
|
||||
</div>
|
||||
|
||||
<div class="flex flex-1 self-center translate-y-[0.5px]">
|
||||
@@ -1160,7 +1160,7 @@
|
||||
>
|
||||
<a
|
||||
id="sidebar-{itemId}-button"
|
||||
class="grow flex items-center space-x-2.5 rounded-xl px-2.5 py-2 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
|
||||
class="grow flex items-center space-x-2.5 rounded-xl px-2.5 py-1.5 hover:bg-gray-100 dark:hover:bg-gray-900 transition"
|
||||
href={meta.href}
|
||||
on:click={itemClickHandler}
|
||||
draggable="false"
|
||||
@@ -1168,7 +1168,7 @@
|
||||
>
|
||||
<div class="self-center">
|
||||
{#if itemId === 'notes'}
|
||||
<Note className="size-4.5" strokeWidth="2" />
|
||||
<Note className="size-4" strokeWidth="2" />
|
||||
{:else if itemId === 'workspace'}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -1176,7 +1176,7 @@
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="size-4.5"
|
||||
class="size-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
@@ -1191,7 +1191,7 @@
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="size-4.5"
|
||||
class="size-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
@@ -1206,7 +1206,7 @@
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="2"
|
||||
stroke="currentColor"
|
||||
class="size-4.5"
|
||||
class="size-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
@@ -1215,7 +1215,7 @@
|
||||
/>
|
||||
</svg>
|
||||
{:else if itemId === 'playground'}
|
||||
<Code className="size-4.5" strokeWidth="2" />
|
||||
<Code className="size-4" strokeWidth="2" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -1524,7 +1524,7 @@
|
||||
class="w-full pl-2.5 text-xs text-gray-500 dark:text-gray-500 font-medium {idx ===
|
||||
0
|
||||
? ''
|
||||
: 'pt-5'} pb-1.5"
|
||||
: 'pt-4'} pb-1"
|
||||
>
|
||||
{$i18n.t(chat.time_range)}
|
||||
<!-- localisation keys for time_range to be recognized from the i18next parser (so they don't get automatically removed):
|
||||
@@ -1602,7 +1602,7 @@
|
||||
</Folder>
|
||||
</div>
|
||||
|
||||
<div class="px-1.5 pt-1.5 pb-2 sticky bottom-0 z-10 -mt-3 sidebar">
|
||||
<div class="px-1.5 pt-1 pb-1.5 sticky bottom-0 z-10 -mt-2 sidebar">
|
||||
<div
|
||||
class=" sidebar-bg-gradient-to-t bg-linear-to-t from-gray-50 dark:from-gray-950 to-transparent from-50% pointer-events-none absolute inset-0 -z-10 -mt-6"
|
||||
></div>
|
||||
|
||||
@@ -122,13 +122,13 @@
|
||||
|
||||
<div slot="content">
|
||||
<div
|
||||
class="{className} user-menu rounded-xl p-0.5 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg text-xs"
|
||||
class="{className} user-menu rounded-xl p-0.5 border border-gray-100 dark:border-gray-800 z-50 bg-white dark:bg-gray-850 dark:text-white shadow-lg text-[13px]"
|
||||
>
|
||||
{#if profile}
|
||||
{#if $user?.status_emoji || $user?.status_message}
|
||||
<div class="user-menu-status">
|
||||
<button
|
||||
class="w-full gap-2 hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none text-xs flex items-center"
|
||||
class="w-full gap-2 hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none text-[13px] flex items-center"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
show = false;
|
||||
@@ -180,7 +180,7 @@
|
||||
{:else}
|
||||
<div class="user-menu-status">
|
||||
<button
|
||||
class="w-full gap-1 hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none text-xs flex items-center justify-center"
|
||||
class="w-full gap-1 hover:bg-gray-50/60 dark:hover:bg-gray-800/60 transition cursor-pointer select-none text-[13px] flex items-center justify-center"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
show = false;
|
||||
|
||||
@@ -88,7 +88,7 @@ export const chatRequestQueues: Writable<
|
||||
Record<string, { id: string; prompt: string; files: any[] }[]>
|
||||
> = writable({});
|
||||
|
||||
export const sidebarWidth = writable(250);
|
||||
export const sidebarWidth = writable(245);
|
||||
|
||||
export const showSidebar = writable(false);
|
||||
export const showSearch = writable(false);
|
||||
|
||||
@@ -493,6 +493,10 @@ export const formatDate = (inputDate) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const formatMessageTimestamp = (inputDate) => dayjs(inputDate).format('MMM D, LT');
|
||||
|
||||
export const formatMessageTimestampFull = (inputDate) => dayjs(inputDate).format('LLLL');
|
||||
|
||||
export const copyToClipboard = async (text, html = null, formatted = false) => {
|
||||
if (formatted) {
|
||||
let styledHtml = '';
|
||||
|
||||
Reference in New Issue
Block a user