mirror of
https://github.com/open-webui/open-webui
synced 2025-02-25 06:44:07 +00:00
Merge remote-tracking branch 'upstream/dev' into feat/rag-status
This commit is contained in:
commit
857b3f0da8
@ -356,15 +356,16 @@ WEBUI_SECRET_KEY = os.environ.get(
|
|||||||
), # DEPRECATED: remove at next major version
|
), # DEPRECATED: remove at next major version
|
||||||
)
|
)
|
||||||
|
|
||||||
WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get(
|
WEBUI_SESSION_COOKIE_SAME_SITE = os.environ.get("WEBUI_SESSION_COOKIE_SAME_SITE", "lax")
|
||||||
"WEBUI_SESSION_COOKIE_SAME_SITE",
|
|
||||||
os.environ.get("WEBUI_SESSION_COOKIE_SAME_SITE", "lax"),
|
|
||||||
)
|
|
||||||
|
|
||||||
WEBUI_SESSION_COOKIE_SECURE = os.environ.get(
|
WEBUI_SESSION_COOKIE_SECURE = os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false").lower() == "true"
|
||||||
"WEBUI_SESSION_COOKIE_SECURE",
|
|
||||||
os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false").lower() == "true",
|
WEBUI_AUTH_COOKIE_SAME_SITE = os.environ.get("WEBUI_AUTH_COOKIE_SAME_SITE", WEBUI_SESSION_COOKIE_SAME_SITE)
|
||||||
)
|
|
||||||
|
WEBUI_AUTH_COOKIE_SECURE = os.environ.get(
|
||||||
|
"WEBUI_AUTH_COOKIE_SECURE",
|
||||||
|
os.environ.get("WEBUI_SESSION_COOKIE_SECURE", "false")
|
||||||
|
).lower() == "true"
|
||||||
|
|
||||||
if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
|
if WEBUI_AUTH and WEBUI_SECRET_KEY == "":
|
||||||
raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND)
|
raise ValueError(ERROR_MESSAGES.ENV_VAR_NOT_FOUND)
|
||||||
|
@ -25,8 +25,8 @@ from open_webui.env import (
|
|||||||
WEBUI_AUTH,
|
WEBUI_AUTH,
|
||||||
WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
|
WEBUI_AUTH_TRUSTED_EMAIL_HEADER,
|
||||||
WEBUI_AUTH_TRUSTED_NAME_HEADER,
|
WEBUI_AUTH_TRUSTED_NAME_HEADER,
|
||||||
WEBUI_SESSION_COOKIE_SAME_SITE,
|
WEBUI_AUTH_COOKIE_SAME_SITE,
|
||||||
WEBUI_SESSION_COOKIE_SECURE,
|
WEBUI_AUTH_COOKIE_SECURE,
|
||||||
SRC_LOG_LEVELS,
|
SRC_LOG_LEVELS,
|
||||||
)
|
)
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||||
@ -95,8 +95,8 @@ async def get_session_user(
|
|||||||
value=token,
|
value=token,
|
||||||
expires=datetime_expires_at,
|
expires=datetime_expires_at,
|
||||||
httponly=True, # Ensures the cookie is not accessible via JavaScript
|
httponly=True, # Ensures the cookie is not accessible via JavaScript
|
||||||
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
|
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
|
||||||
secure=WEBUI_SESSION_COOKIE_SECURE,
|
secure=WEBUI_AUTH_COOKIE_SECURE,
|
||||||
)
|
)
|
||||||
|
|
||||||
user_permissions = get_permissions(
|
user_permissions = get_permissions(
|
||||||
@ -378,8 +378,8 @@ async def signin(request: Request, response: Response, form_data: SigninForm):
|
|||||||
value=token,
|
value=token,
|
||||||
expires=datetime_expires_at,
|
expires=datetime_expires_at,
|
||||||
httponly=True, # Ensures the cookie is not accessible via JavaScript
|
httponly=True, # Ensures the cookie is not accessible via JavaScript
|
||||||
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
|
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
|
||||||
secure=WEBUI_SESSION_COOKIE_SECURE,
|
secure=WEBUI_AUTH_COOKIE_SECURE,
|
||||||
)
|
)
|
||||||
|
|
||||||
user_permissions = get_permissions(
|
user_permissions = get_permissions(
|
||||||
@ -473,8 +473,8 @@ async def signup(request: Request, response: Response, form_data: SignupForm):
|
|||||||
value=token,
|
value=token,
|
||||||
expires=datetime_expires_at,
|
expires=datetime_expires_at,
|
||||||
httponly=True, # Ensures the cookie is not accessible via JavaScript
|
httponly=True, # Ensures the cookie is not accessible via JavaScript
|
||||||
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
|
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
|
||||||
secure=WEBUI_SESSION_COOKIE_SECURE,
|
secure=WEBUI_AUTH_COOKIE_SECURE,
|
||||||
)
|
)
|
||||||
|
|
||||||
if request.app.state.config.WEBHOOK_URL:
|
if request.app.state.config.WEBHOOK_URL:
|
||||||
|
@ -264,7 +264,10 @@ def add_file_to_knowledge_by_id(
|
|||||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
if knowledge.user_id != user.id and user.role != "admin":
|
if (knowledge.user_id != user.id
|
||||||
|
and not has_access(user.id, "write", knowledge.access_control)
|
||||||
|
and user.role != "admin"
|
||||||
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||||
@ -342,7 +345,12 @@ def update_file_from_knowledge_by_id(
|
|||||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
if knowledge.user_id != user.id and user.role != "admin":
|
if (
|
||||||
|
knowledge.user_id != user.id
|
||||||
|
and not has_access(user.id, "write", knowledge.access_control)
|
||||||
|
and user.role != "admin"
|
||||||
|
):
|
||||||
|
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||||
@ -406,7 +414,11 @@ def remove_file_from_knowledge_by_id(
|
|||||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
if knowledge.user_id != user.id and user.role != "admin":
|
if (
|
||||||
|
knowledge.user_id != user.id
|
||||||
|
and not has_access(user.id, "write", knowledge.access_control)
|
||||||
|
and user.role != "admin"
|
||||||
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||||
@ -484,7 +496,11 @@ async def delete_knowledge_by_id(id: str, user=Depends(get_verified_user)):
|
|||||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
if knowledge.user_id != user.id and user.role != "admin":
|
if (
|
||||||
|
knowledge.user_id != user.id
|
||||||
|
and not has_access(user.id, "write", knowledge.access_control)
|
||||||
|
and user.role != "admin"
|
||||||
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||||
@ -543,7 +559,11 @@ async def reset_knowledge_by_id(id: str, user=Depends(get_verified_user)):
|
|||||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
if knowledge.user_id != user.id and user.role != "admin":
|
if (
|
||||||
|
knowledge.user_id != user.id
|
||||||
|
and not has_access(user.id, "write", knowledge.access_control)
|
||||||
|
and user.role != "admin"
|
||||||
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||||
@ -582,7 +602,11 @@ def add_files_to_knowledge_batch(
|
|||||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
if knowledge.user_id != user.id and user.role != "admin":
|
if (
|
||||||
|
knowledge.user_id != user.id
|
||||||
|
and not has_access(user.id, "write", knowledge.access_control)
|
||||||
|
and user.role != "admin"
|
||||||
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_400_BAD_REQUEST,
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||||
|
@ -183,7 +183,11 @@ async def delete_model_by_id(id: str, user=Depends(get_verified_user)):
|
|||||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
if model.user_id != user.id and user.role != "admin":
|
if (
|
||||||
|
user.role == "admin"
|
||||||
|
or model.user_id == user.id
|
||||||
|
or has_access(user.id, "write", model.access_control)
|
||||||
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||||
|
@ -147,7 +147,11 @@ async def delete_prompt_by_command(command: str, user=Depends(get_verified_user)
|
|||||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
if prompt.user_id != user.id and user.role != "admin":
|
if (
|
||||||
|
prompt.user_id != user.id
|
||||||
|
and not has_access(user.id, "write", prompt.access_control)
|
||||||
|
and user.role != "admin"
|
||||||
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
detail=ERROR_MESSAGES.ACCESS_PROHIBITED,
|
||||||
|
@ -227,7 +227,11 @@ async def delete_tools_by_id(
|
|||||||
detail=ERROR_MESSAGES.NOT_FOUND,
|
detail=ERROR_MESSAGES.NOT_FOUND,
|
||||||
)
|
)
|
||||||
|
|
||||||
if tools.user_id != user.id and user.role != "admin":
|
if (
|
||||||
|
tools.user_id != user.id
|
||||||
|
and not has_access(user.id, "write", tools.access_control)
|
||||||
|
and user.role != "admin"
|
||||||
|
):
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
detail=ERROR_MESSAGES.UNAUTHORIZED,
|
||||||
|
@ -149,6 +149,7 @@ def openai_chat_chunk_message_template(
|
|||||||
template["choices"][0]["delta"] = {"content": message}
|
template["choices"][0]["delta"] = {"content": message}
|
||||||
else:
|
else:
|
||||||
template["choices"][0]["finish_reason"] = "stop"
|
template["choices"][0]["finish_reason"] = "stop"
|
||||||
|
template["choices"][0]["delta"] = {}
|
||||||
|
|
||||||
if usage:
|
if usage:
|
||||||
template["usage"] = usage
|
template["usage"] = usage
|
||||||
|
@ -35,7 +35,7 @@ from open_webui.config import (
|
|||||||
AppConfig,
|
AppConfig,
|
||||||
)
|
)
|
||||||
from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
|
from open_webui.constants import ERROR_MESSAGES, WEBHOOK_MESSAGES
|
||||||
from open_webui.env import WEBUI_SESSION_COOKIE_SAME_SITE, WEBUI_SESSION_COOKIE_SECURE
|
from open_webui.env import WEBUI_AUTH_COOKIE_SAME_SITE, WEBUI_AUTH_COOKIE_SECURE
|
||||||
from open_webui.utils.misc import parse_duration
|
from open_webui.utils.misc import parse_duration
|
||||||
from open_webui.utils.auth import get_password_hash, create_token
|
from open_webui.utils.auth import get_password_hash, create_token
|
||||||
from open_webui.utils.webhook import post_webhook
|
from open_webui.utils.webhook import post_webhook
|
||||||
@ -323,8 +323,8 @@ class OAuthManager:
|
|||||||
key="token",
|
key="token",
|
||||||
value=jwt_token,
|
value=jwt_token,
|
||||||
httponly=True, # Ensures the cookie is not accessible via JavaScript
|
httponly=True, # Ensures the cookie is not accessible via JavaScript
|
||||||
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
|
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
|
||||||
secure=WEBUI_SESSION_COOKIE_SECURE,
|
secure=WEBUI_AUTH_COOKIE_SECURE,
|
||||||
)
|
)
|
||||||
|
|
||||||
if ENABLE_OAUTH_SIGNUP.value:
|
if ENABLE_OAUTH_SIGNUP.value:
|
||||||
@ -333,8 +333,8 @@ class OAuthManager:
|
|||||||
key="oauth_id_token",
|
key="oauth_id_token",
|
||||||
value=oauth_id_token,
|
value=oauth_id_token,
|
||||||
httponly=True,
|
httponly=True,
|
||||||
samesite=WEBUI_SESSION_COOKIE_SAME_SITE,
|
samesite=WEBUI_AUTH_COOKIE_SAME_SITE,
|
||||||
secure=WEBUI_SESSION_COOKIE_SECURE,
|
secure=WEBUI_AUTH_COOKIE_SECURE,
|
||||||
)
|
)
|
||||||
# Redirect back to the frontend with the JWT token
|
# Redirect back to the frontend with the JWT token
|
||||||
redirect_url = f"{request.base_url}auth#token={jwt_token}"
|
redirect_url = f"{request.base_url}auth#token={jwt_token}"
|
||||||
|
@ -6,7 +6,9 @@
|
|||||||
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
dayjs.extend(localizedFormat);
|
||||||
|
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
|
|
||||||
@ -364,7 +366,7 @@
|
|||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class=" px-3 py-1">
|
<td class=" px-3 py-1">
|
||||||
{dayjs(user.created_at * 1000).format($i18n.t('MMMM DD, YYYY'))}
|
{dayjs(user.created_at * 1000).format('LL')}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class=" px-3 py-1"> {user.oauth_sub ?? ''} </td>
|
<td class=" px-3 py-1"> {user.oauth_sub ?? ''} </td>
|
||||||
|
@ -7,9 +7,11 @@
|
|||||||
import { updateUserById } from '$lib/apis/users';
|
import { updateUserById } from '$lib/apis/users';
|
||||||
|
|
||||||
import Modal from '$lib/components/common/Modal.svelte';
|
import Modal from '$lib/components/common/Modal.svelte';
|
||||||
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
dayjs.extend(localizedFormat);
|
||||||
|
|
||||||
export let show = false;
|
export let show = false;
|
||||||
export let selectedUser;
|
export let selectedUser;
|
||||||
@ -87,7 +89,7 @@
|
|||||||
|
|
||||||
<div class="text-xs text-gray-500">
|
<div class="text-xs text-gray-500">
|
||||||
{$i18n.t('Created at')}
|
{$i18n.t('Created at')}
|
||||||
{dayjs(selectedUser.created_at * 1000).format($i18n.t('MMMM DD, YYYY'))}
|
{dayjs(selectedUser.created_at * 1000).format('LL')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -2,8 +2,10 @@
|
|||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { getContext, createEventDispatcher } from 'svelte';
|
import { getContext, createEventDispatcher } from 'svelte';
|
||||||
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
dayjs.extend(localizedFormat);
|
||||||
|
|
||||||
import { getChatListByUserId, deleteChatById, getArchivedChatList } from '$lib/apis/chats';
|
import { getChatListByUserId, deleteChatById, getArchivedChatList } from '$lib/apis/chats';
|
||||||
|
|
||||||
@ -130,7 +132,7 @@
|
|||||||
|
|
||||||
<td class=" px-3 py-1 hidden md:flex h-[2.5rem] justify-end">
|
<td class=" px-3 py-1 hidden md:flex h-[2.5rem] justify-end">
|
||||||
<div class="my-auto shrink-0">
|
<div class="my-auto shrink-0">
|
||||||
{dayjs(chat.updated_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))}
|
{dayjs(chat.updated_at * 1000).format('LLL')}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
import isToday from 'dayjs/plugin/isToday';
|
import isToday from 'dayjs/plugin/isToday';
|
||||||
import isYesterday from 'dayjs/plugin/isYesterday';
|
import isYesterday from 'dayjs/plugin/isYesterday';
|
||||||
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
dayjs.extend(isToday);
|
dayjs.extend(isToday);
|
||||||
dayjs.extend(isYesterday);
|
dayjs.extend(isYesterday);
|
||||||
|
dayjs.extend(localizedFormat);
|
||||||
|
|
||||||
import { getContext, onMount } from 'svelte';
|
import { getContext, onMount } from 'svelte';
|
||||||
const i18n = getContext<Writable<i18nType>>('i18n');
|
const i18n = getContext<Writable<i18nType>>('i18n');
|
||||||
@ -154,9 +156,9 @@
|
|||||||
class="mt-1.5 flex flex-shrink-0 items-center text-xs self-center invisible group-hover:visible text-gray-500 font-medium first-letter:capitalize"
|
class="mt-1.5 flex flex-shrink-0 items-center text-xs self-center invisible group-hover:visible text-gray-500 font-medium first-letter:capitalize"
|
||||||
>
|
>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={dayjs(message.created_at / 1000000).format('dddd, DD MMMM YYYY HH:mm')}
|
content={dayjs(message.created_at / 1000000).format('LLLL')}
|
||||||
>
|
>
|
||||||
{dayjs(message.created_at / 1000000).format('HH:mm')}
|
{dayjs(message.created_at / 1000000).format('LT')}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@ -175,7 +177,7 @@
|
|||||||
class=" self-center text-xs invisible group-hover:visible text-gray-400 font-medium first-letter:capitalize ml-0.5 translate-y-[1px]"
|
class=" self-center text-xs invisible group-hover:visible text-gray-400 font-medium first-letter:capitalize ml-0.5 translate-y-[1px]"
|
||||||
>
|
>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={dayjs(message.created_at / 1000000).format('dddd, DD MMMM YYYY HH:mm')}
|
content={dayjs(message.created_at / 1000000).format('LLLL')}
|
||||||
>
|
>
|
||||||
<span class="line-clamp-1">{formatDate(message.created_at / 1000000)}</span>
|
<span class="line-clamp-1">{formatDate(message.created_at / 1000000)}</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -82,10 +82,12 @@
|
|||||||
import EventConfirmDialog from '../common/ConfirmDialog.svelte';
|
import EventConfirmDialog from '../common/ConfirmDialog.svelte';
|
||||||
import Placeholder from './Placeholder.svelte';
|
import Placeholder from './Placeholder.svelte';
|
||||||
import NotificationToast from '../NotificationToast.svelte';
|
import NotificationToast from '../NotificationToast.svelte';
|
||||||
|
import Spinner from '../common/Spinner.svelte';
|
||||||
|
|
||||||
export let chatIdProp = '';
|
export let chatIdProp = '';
|
||||||
|
|
||||||
let loaded = false;
|
let loading = false;
|
||||||
|
|
||||||
const eventTarget = new EventTarget();
|
const eventTarget = new EventTarget();
|
||||||
let controlPane;
|
let controlPane;
|
||||||
let controlPaneComponent;
|
let controlPaneComponent;
|
||||||
@ -133,6 +135,7 @@
|
|||||||
|
|
||||||
$: if (chatIdProp) {
|
$: if (chatIdProp) {
|
||||||
(async () => {
|
(async () => {
|
||||||
|
loading = true;
|
||||||
console.log(chatIdProp);
|
console.log(chatIdProp);
|
||||||
|
|
||||||
prompt = '';
|
prompt = '';
|
||||||
@ -141,11 +144,9 @@
|
|||||||
webSearchEnabled = false;
|
webSearchEnabled = false;
|
||||||
imageGenerationEnabled = false;
|
imageGenerationEnabled = false;
|
||||||
|
|
||||||
loaded = false;
|
|
||||||
|
|
||||||
if (chatIdProp && (await loadChat())) {
|
if (chatIdProp && (await loadChat())) {
|
||||||
await tick();
|
await tick();
|
||||||
loaded = true;
|
loading = false;
|
||||||
|
|
||||||
if (localStorage.getItem(`chat-input-${chatIdProp}`)) {
|
if (localStorage.getItem(`chat-input-${chatIdProp}`)) {
|
||||||
try {
|
try {
|
||||||
@ -1866,7 +1867,7 @@
|
|||||||
: ' '} w-full max-w-full flex flex-col"
|
: ' '} w-full max-w-full flex flex-col"
|
||||||
id="chat-container"
|
id="chat-container"
|
||||||
>
|
>
|
||||||
{#if !chatIdProp || (loaded && chatIdProp)}
|
{#if chatIdProp === '' || (!loading && chatIdProp)}
|
||||||
{#if $settings?.backgroundImageUrl ?? null}
|
{#if $settings?.backgroundImageUrl ?? null}
|
||||||
<div
|
<div
|
||||||
class="absolute {$showSidebar
|
class="absolute {$showSidebar
|
||||||
@ -2070,5 +2071,11 @@
|
|||||||
{eventTarget}
|
{eventTarget}
|
||||||
/>
|
/>
|
||||||
</PaneGroup>
|
</PaneGroup>
|
||||||
|
{:else if loading}
|
||||||
|
<div class=" flex items-center justify-center h-full w-full">
|
||||||
|
<div class="m-auto">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,6 +48,9 @@
|
|||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let fileUploadEnabled = true;
|
||||||
|
$: fileUploadEnabled = $user.role === 'admin' || $user?.permissions?.chat?.file_upload;
|
||||||
|
|
||||||
const init = async () => {
|
const init = async () => {
|
||||||
if ($_tools === null) {
|
if ($_tools === null) {
|
||||||
await _tools.set(await getTools(localStorage.token));
|
await _tools.set(await getTools(localStorage.token));
|
||||||
@ -166,26 +169,44 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if !$mobile}
|
{#if !$mobile}
|
||||||
<DropdownMenu.Item
|
<Tooltip
|
||||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
|
content={!fileUploadEnabled ? $i18n.t('You do not have permission to upload files') : ''}
|
||||||
on:click={() => {
|
className="w-full"
|
||||||
screenCaptureHandler();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<CameraSolid />
|
<DropdownMenu.Item
|
||||||
<div class=" line-clamp-1">{$i18n.t('Capture')}</div>
|
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl {!fileUploadEnabled
|
||||||
</DropdownMenu.Item>
|
? 'opacity-50'
|
||||||
|
: ''}"
|
||||||
|
on:click={() => {
|
||||||
|
if (fileUploadEnabled) {
|
||||||
|
screenCaptureHandler();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CameraSolid />
|
||||||
|
<div class=" line-clamp-1">{$i18n.t('Capture')}</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</Tooltip>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<DropdownMenu.Item
|
<Tooltip
|
||||||
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl"
|
content={!fileUploadEnabled ? $i18n.t('You do not have permission to upload files') : ''}
|
||||||
on:click={() => {
|
className="w-full"
|
||||||
uploadFilesHandler();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<DocumentArrowUpSolid />
|
<DropdownMenu.Item
|
||||||
<div class="line-clamp-1">{$i18n.t('Upload Files')}</div>
|
class="flex gap-2 items-center px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 rounded-xl {!fileUploadEnabled
|
||||||
</DropdownMenu.Item>
|
? 'opacity-50'
|
||||||
|
: ''}"
|
||||||
|
on:click={() => {
|
||||||
|
if (fileUploadEnabled) {
|
||||||
|
uploadFilesHandler();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DocumentArrowUpSolid />
|
||||||
|
<div class="line-clamp-1">{$i18n.t('Upload Files')}</div>
|
||||||
|
</DropdownMenu.Item>
|
||||||
|
</Tooltip>
|
||||||
|
|
||||||
{#if $config?.features?.enable_google_drive_integration}
|
{#if $config?.features?.enable_google_drive_integration}
|
||||||
<DropdownMenu.Item
|
<DropdownMenu.Item
|
||||||
|
@ -16,7 +16,9 @@
|
|||||||
import Markdown from './Markdown.svelte';
|
import Markdown from './Markdown.svelte';
|
||||||
import Name from './Name.svelte';
|
import Name from './Name.svelte';
|
||||||
import Skeleton from './Skeleton.svelte';
|
import Skeleton from './Skeleton.svelte';
|
||||||
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
dayjs.extend(localizedFormat);
|
||||||
|
|
||||||
export let chatId;
|
export let chatId;
|
||||||
export let history;
|
export let history;
|
||||||
@ -264,7 +266,7 @@
|
|||||||
<span
|
<span
|
||||||
class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium uppercase ml-0.5 -mt-0.5"
|
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($i18n.t('h:mm a'))}
|
{dayjs(message.timestamp * 1000).format('LT')}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</Name>
|
</Name>
|
||||||
|
@ -500,7 +500,7 @@
|
|||||||
<div
|
<div
|
||||||
class=" self-center text-xs invisible group-hover:visible text-gray-400 font-medium first-letter:capitalize ml-0.5 translate-y-[1px]"
|
class=" self-center text-xs invisible group-hover:visible text-gray-400 font-medium first-letter:capitalize ml-0.5 translate-y-[1px]"
|
||||||
>
|
>
|
||||||
<Tooltip content={dayjs(message.timestamp * 1000).format('dddd, DD MMMM YYYY HH:mm')}>
|
<Tooltip content={dayjs(message.timestamp * 1000).format('LLLL')}>
|
||||||
<span class="line-clamp-1">{formatDate(message.timestamp * 1000)}</span>
|
<span class="line-clamp-1">{formatDate(message.timestamp * 1000)}</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,8 +13,10 @@
|
|||||||
import FileItem from '$lib/components/common/FileItem.svelte';
|
import FileItem from '$lib/components/common/FileItem.svelte';
|
||||||
import Markdown from './Markdown.svelte';
|
import Markdown from './Markdown.svelte';
|
||||||
import Image from '$lib/components/common/Image.svelte';
|
import Image from '$lib/components/common/Image.svelte';
|
||||||
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
dayjs.extend(localizedFormat);
|
||||||
|
|
||||||
export let user;
|
export let user;
|
||||||
|
|
||||||
@ -112,7 +114,7 @@
|
|||||||
<div
|
<div
|
||||||
class=" self-center text-xs invisible group-hover:visible text-gray-400 font-medium first-letter:capitalize ml-0.5 translate-y-[1px]"
|
class=" self-center text-xs invisible group-hover:visible text-gray-400 font-medium first-letter:capitalize ml-0.5 translate-y-[1px]"
|
||||||
>
|
>
|
||||||
<Tooltip content={dayjs(message.timestamp * 1000).format('dddd, DD MMMM YYYY HH:mm')}>
|
<Tooltip content={dayjs(message.timestamp * 1000).format('LLLL')}>
|
||||||
<span class="line-clamp-1">{formatDate(message.timestamp * 1000)}</span>
|
<span class="line-clamp-1">{formatDate(message.timestamp * 1000)}</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,8 +11,10 @@
|
|||||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||||
import { error } from '@sveltejs/kit';
|
import { error } from '@sveltejs/kit';
|
||||||
import EditMemoryModal from './EditMemoryModal.svelte';
|
import EditMemoryModal from './EditMemoryModal.svelte';
|
||||||
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
dayjs.extend(localizedFormat);
|
||||||
|
|
||||||
export let show = false;
|
export let show = false;
|
||||||
|
|
||||||
@ -84,9 +86,7 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
|
<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
|
||||||
<div class="my-auto whitespace-nowrap">
|
<div class="my-auto whitespace-nowrap">
|
||||||
{dayjs(memory.updated_at * 1000).format(
|
{dayjs(memory.updated_at * 1000).format('LLL')}
|
||||||
$i18n.t('MMMM DD, YYYY hh:mm:ss A')
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="px-3 py-1">
|
<td class="px-3 py-1">
|
||||||
|
@ -4,6 +4,9 @@
|
|||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { getContext, createEventDispatcher } from 'svelte';
|
import { getContext, createEventDispatcher } from 'svelte';
|
||||||
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||||
|
|
||||||
|
dayjs.extend(localizedFormat);
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
@ -159,7 +162,7 @@
|
|||||||
|
|
||||||
<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
|
<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
|
||||||
<div class="my-auto">
|
<div class="my-auto">
|
||||||
{dayjs(chat.created_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))}
|
{dayjs(chat.created_at * 1000).format('LLL')}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
@ -112,7 +112,10 @@
|
|||||||
|
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<div class="px-3 py-2 bg-gray-50 dark:bg-gray-950 rounded-lg">
|
<div class="px-3 py-2 bg-gray-50 dark:bg-gray-950 rounded-lg">
|
||||||
<AccessControl bind:accessControl />
|
<AccessControl
|
||||||
|
bind:accessControl
|
||||||
|
accessRoles={['read', 'write']}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -531,7 +531,10 @@
|
|||||||
|
|
||||||
<div class="my-2">
|
<div class="my-2">
|
||||||
<div class="px-3 py-2 bg-gray-50 dark:bg-gray-950 rounded-lg">
|
<div class="px-3 py-2 bg-gray-50 dark:bg-gray-950 rounded-lg">
|
||||||
<AccessControl bind:accessControl />
|
<AccessControl
|
||||||
|
bind:accessControl
|
||||||
|
accessRoles={['read', 'write']}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -767,7 +767,7 @@
|
|||||||
"Reset": "",
|
"Reset": "",
|
||||||
"Reset All Models": "",
|
"Reset All Models": "",
|
||||||
"Reset Upload Directory": "アップロードディレクトリをリセット",
|
"Reset Upload Directory": "アップロードディレクトリをリセット",
|
||||||
"Reset Vector Storage/Knowledge": "ベクターストレージとナレッジべーうをリセット",
|
"Reset Vector Storage/Knowledge": "ベクターストレージとナレッジベースをリセット",
|
||||||
"Reset view": "",
|
"Reset view": "",
|
||||||
"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
|
"Response notifications cannot be activated as the website permissions have been denied. Please visit your browser settings to grant the necessary access.": "",
|
||||||
"Response splitting": "応答の分割",
|
"Response splitting": "応答の分割",
|
||||||
|
@ -935,7 +935,7 @@
|
|||||||
"This will delete all models including custom models and cannot be undone.": "這將刪除所有模型,包括自訂模型,且無法復原。",
|
"This will delete all models including custom models and cannot be undone.": "這將刪除所有模型,包括自訂模型,且無法復原。",
|
||||||
"This will reset the knowledge base and sync all files. Do you wish to continue?": "這將重設知識庫並同步所有檔案。您確定要繼續嗎?",
|
"This will reset the knowledge base and sync all files. Do you wish to continue?": "這將重設知識庫並同步所有檔案。您確定要繼續嗎?",
|
||||||
"Thorough explanation": "詳細解釋",
|
"Thorough explanation": "詳細解釋",
|
||||||
"Thought for {{DURATION}}": "",
|
"Thought for {{DURATION}}": "{{DURATION}} 思考中",
|
||||||
"Tika": "Tika",
|
"Tika": "Tika",
|
||||||
"Tika Server URL required.": "需要 Tika 伺服器 URL。",
|
"Tika Server URL required.": "需要 Tika 伺服器 URL。",
|
||||||
"Tiktoken": "Tiktoken",
|
"Tiktoken": "Tiktoken",
|
||||||
|
@ -5,10 +5,12 @@ import dayjs from 'dayjs';
|
|||||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
import isToday from 'dayjs/plugin/isToday';
|
import isToday from 'dayjs/plugin/isToday';
|
||||||
import isYesterday from 'dayjs/plugin/isYesterday';
|
import isYesterday from 'dayjs/plugin/isYesterday';
|
||||||
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
dayjs.extend(isToday);
|
dayjs.extend(isToday);
|
||||||
dayjs.extend(isYesterday);
|
dayjs.extend(isYesterday);
|
||||||
|
dayjs.extend(localizedFormat);
|
||||||
|
|
||||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||||
import { TTS_RESPONSE_SPLIT } from '$lib/types';
|
import { TTS_RESPONSE_SPLIT } from '$lib/types';
|
||||||
@ -295,11 +297,11 @@ export const formatDate = (inputDate) => {
|
|||||||
const now = dayjs();
|
const now = dayjs();
|
||||||
|
|
||||||
if (date.isToday()) {
|
if (date.isToday()) {
|
||||||
return `Today at ${date.format('HH:mm')}`;
|
return `Today at ${date.format('LT')}`;
|
||||||
} else if (date.isYesterday()) {
|
} else if (date.isYesterday()) {
|
||||||
return `Yesterday at ${date.format('HH:mm')}`;
|
return `Yesterday at ${date.format('LT')}`;
|
||||||
} else {
|
} else {
|
||||||
return `${date.format('DD/MM/YYYY')} at ${date.format('HH:mm')}`;
|
return `${date.format('L')} at ${date.format('LT')}`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -16,8 +16,10 @@
|
|||||||
import { getUserById } from '$lib/apis/users';
|
import { getUserById } from '$lib/apis/users';
|
||||||
import { getModels } from '$lib/apis';
|
import { getModels } from '$lib/apis';
|
||||||
import { toast } from 'svelte-sonner';
|
import { toast } from 'svelte-sonner';
|
||||||
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
dayjs.extend(localizedFormat);
|
||||||
|
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
|
|
||||||
@ -138,7 +140,7 @@
|
|||||||
|
|
||||||
<div class="flex text-sm justify-between items-center mt-1">
|
<div class="flex text-sm justify-between items-center mt-1">
|
||||||
<div class="text-gray-400">
|
<div class="text-gray-400">
|
||||||
{dayjs(chat.chat.timestamp).format($i18n.t('MMMM DD, YYYY'))}
|
{dayjs(chat.chat.timestamp).format('LLL')}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user