mirror of
https://github.com/open-webui/open-webui.git
synced 2026-11-15 09:31:21 +00:00
refac
This commit is contained in:
parent
b659b534d4
commit
151f7a84d1
10 changed files with 124 additions and 51 deletions
|
|
@ -1919,6 +1919,8 @@ USER_PERMISSIONS_CHAT_MULTIPLE_MODELS = os.getenv('USER_PERMISSIONS_CHAT_MULTIPL
|
|||
|
||||
USER_PERMISSIONS_CHAT_TEMPORARY = os.getenv('USER_PERMISSIONS_CHAT_TEMPORARY', 'True').lower() == 'true'
|
||||
|
||||
USER_PERMISSIONS_CHAT_SIDE_CHAT = os.getenv('USER_PERMISSIONS_CHAT_SIDE_CHAT', 'False').lower() == 'true'
|
||||
|
||||
USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED = (
|
||||
os.getenv('USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED', 'False').lower() == 'true'
|
||||
)
|
||||
|
|
@ -2024,6 +2026,7 @@ DEFAULT_USER_PERMISSIONS = {
|
|||
'call': USER_PERMISSIONS_CHAT_CALL,
|
||||
'multiple_models': USER_PERMISSIONS_CHAT_MULTIPLE_MODELS,
|
||||
'temporary': USER_PERMISSIONS_CHAT_TEMPORARY,
|
||||
'side_chat': USER_PERMISSIONS_CHAT_SIDE_CHAT,
|
||||
'temporary_enforced': USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED,
|
||||
},
|
||||
'features': {
|
||||
|
|
|
|||
|
|
@ -249,6 +249,7 @@ class ChatPermissions(BaseModel):
|
|||
call: bool = True
|
||||
multiple_models: bool = True
|
||||
temporary: bool = True
|
||||
side_chat: bool = False
|
||||
temporary_enforced: bool = False
|
||||
|
||||
|
||||
|
|
@ -424,6 +425,12 @@ async def get_default_user_permissions(request: Request, user=Depends(get_admin_
|
|||
async def update_default_user_permissions(request: Request, form_data: UserPermissions, user=Depends(get_admin_user)):
|
||||
user_permissions = form_data.model_dump(by_alias=True)
|
||||
await Config.upsert({'user.permissions': user_permissions})
|
||||
from open_webui.socket.main import sio
|
||||
|
||||
try:
|
||||
await sio.emit('access:updated', {})
|
||||
except Exception:
|
||||
log.exception('Unable to refresh default user permissions')
|
||||
await publish_event(
|
||||
request,
|
||||
EVENTS.USER_PERMISSIONS_UPDATED,
|
||||
|
|
|
|||
|
|
@ -949,7 +949,21 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
{#if permissions.chat.temporary}
|
||||
{#if permissions.chat.temporary || defaultPermissions?.chat?.temporary}
|
||||
<div class="ml-2 flex flex-col">
|
||||
<div class="flex w-full justify-between my-1">
|
||||
<div class="self-center text-xs font-normal">
|
||||
{$i18n.t('Allow Side Chat')}
|
||||
</div>
|
||||
<Switch bind:state={permissions.chat.side_chat} ariaLabel={$i18n.t('Allow Side Chat')} />
|
||||
</div>
|
||||
{#if defaultPermissions?.chat?.side_chat && !permissions.chat.side_chat}
|
||||
<div class="text-xs text-gray-500">
|
||||
{$i18n.t('This is a default user permission and will remain enabled.')}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="flex w-full justify-between my-1">
|
||||
<div class=" self-center text-xs font-normal">
|
||||
|
|
|
|||
|
|
@ -434,6 +434,11 @@
|
|||
$: isShared = chat != null && chat.user_id !== $user?.id;
|
||||
$: if (isShared && $showCallOverlay) showCallOverlay.set(false);
|
||||
$: canClone = isShared && ($user?.role === 'admin' || ($user?.permissions?.chat?.import ?? true));
|
||||
$: sideChatAllowed =
|
||||
$user?.role === 'admin' ||
|
||||
($user?.role === 'user' &&
|
||||
($user.permissions?.chat?.temporary ?? true) &&
|
||||
($user.permissions?.chat?.side_chat ?? false));
|
||||
let cloning = false;
|
||||
|
||||
const cloneSharedChat = async () => {
|
||||
|
|
@ -3931,6 +3936,10 @@
|
|||
return { status: 'rejected' };
|
||||
}
|
||||
const sideCommand = String(userPrompt).trim().match(/^\/(?:side|btw)(?:\s+([\s\S]*))?$/i);
|
||||
if (sideCommand && !sideChatAllowed) {
|
||||
toast.error($i18n.t('You do not have permission to use side chat.'));
|
||||
return { status: 'rejected' };
|
||||
}
|
||||
if (sideCommand && !embedded && $chatId && history.currentId) {
|
||||
if (selectedModels.length !== 1 || !selectedModelIds[0]) {
|
||||
toast.error($i18n.t('Select only one model to open a side chat.'));
|
||||
|
|
@ -5676,7 +5685,7 @@
|
|||
chatId={$chatId}
|
||||
chatUser={chatOwner}
|
||||
modelId={selectedModelIds?.at(0) ?? null}
|
||||
canSideChat={selectedModels.length === 1 && !!selectedModelIds[0]}
|
||||
canSideChat={sideChatAllowed && selectedModels.length === 1 && !!selectedModelIds[0]}
|
||||
systemPrompt={params?.system || $settings.system || ''}
|
||||
submitPrompt={submitHandler}
|
||||
onAskFile={askFile}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
temporaryChatEnabled,
|
||||
terminalServers,
|
||||
showTabs,
|
||||
sideChatRequest,
|
||||
showOverview,
|
||||
showSidebar,
|
||||
mobile,
|
||||
|
|
@ -574,10 +575,20 @@
|
|||
else if (options.length === 1) options[0]();
|
||||
hasOpenedTabs = true;
|
||||
}
|
||||
$: if (
|
||||
!(
|
||||
$user?.role === 'admin' ||
|
||||
($user?.role === 'user' &&
|
||||
($user.permissions?.chat?.temporary ?? true) &&
|
||||
($user.permissions?.chat?.side_chat ?? false))
|
||||
) &&
|
||||
$sideChatRequest?.parentId === chatId
|
||||
) sideChatRequest.set(null);
|
||||
$: if ($showTabs === 'files' && showFilesTab) openFiles();
|
||||
$: if ($showTabs === 'side' && chatId && hasMessages) {
|
||||
openFeature('side');
|
||||
showTabs.set(true);
|
||||
if (canSideChat) openFeature('side');
|
||||
else sideChatRequest.set(null);
|
||||
showTabs.set(tabs.tabs.length > 0);
|
||||
}
|
||||
$: if (
|
||||
($showTabs === 'comments' || (typeof $showTabs === 'object' && $showTabs)) &&
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { v4 as uuidv4 } from 'uuid';
|
||||
import {
|
||||
socket,
|
||||
user,
|
||||
openTemporaryChatIds,
|
||||
sideChatRequest,
|
||||
models,
|
||||
|
|
@ -28,6 +29,11 @@
|
|||
export let modelId: string;
|
||||
export let systemPrompt = '';
|
||||
const i18n = getContext<any>('i18n');
|
||||
$: sideChatAllowed =
|
||||
$user?.role === 'admin' ||
|
||||
($user?.role === 'user' &&
|
||||
($user.permissions?.chat?.temporary ?? true) &&
|
||||
($user.permissions?.chat?.side_chat ?? false));
|
||||
// Snapshot the active branch once; later parent edits and streaming stay in the parent.
|
||||
const context = (() => {
|
||||
const branch: any[] = [];
|
||||
|
|
@ -115,7 +121,8 @@
|
|||
callback &&
|
||||
['request:user_input', 'request:elicitation', 'confirmation', 'input'].includes(type)
|
||||
) {
|
||||
if (data?.tool_call && toolApprovalMode === 'full') callback(true);
|
||||
if (!sideChatAllowed) callback({ status: 'cancelled', answers: {} });
|
||||
else if (data?.tool_call && toolApprovalMode === 'full') callback(true);
|
||||
else
|
||||
interactions = [
|
||||
...interactions,
|
||||
|
|
@ -159,10 +166,10 @@
|
|||
};
|
||||
|
||||
const reconnect = async () => {
|
||||
if (!id || !$socket?.id) return;
|
||||
if (!sideChatAllowed || !id || !$socket?.id) return;
|
||||
try {
|
||||
const result = await resumeTemporaryChat(localStorage.token, id, $socket.id);
|
||||
if (disposed) return;
|
||||
if (disposed || !sideChatAllowed) return;
|
||||
for (const stream of result.streams) {
|
||||
const message = messages.find((m) => m.id === stream.message_id);
|
||||
if (message && !message.done) message.output = stream.output;
|
||||
|
|
@ -184,6 +191,7 @@
|
|||
};
|
||||
|
||||
const send = async () => {
|
||||
if (!sideChatAllowed) return;
|
||||
const question = draft.trim();
|
||||
if ((!question && !files.length) || busy) return;
|
||||
if (!atSelectedModel && selectedModels.length !== 1) {
|
||||
|
|
@ -204,7 +212,8 @@
|
|||
openTemporaryChatIds.update((ids) => new Set([...ids, id]));
|
||||
}
|
||||
await tick();
|
||||
if (!(await input.uploadPendingFiles(id)) || busy) return;
|
||||
if (!sideChatAllowed) return;
|
||||
if (!(await input?.uploadPendingFiles(id)) || busy || !sideChatAllowed) return;
|
||||
const userMessage = {
|
||||
id: uuidv4(),
|
||||
parentId: messages.at(-1)?.id ?? null,
|
||||
|
|
@ -325,10 +334,15 @@
|
|||
}
|
||||
};
|
||||
|
||||
$: if (!sideChatAllowed) {
|
||||
cancelInteractions();
|
||||
if (busy) void stop();
|
||||
}
|
||||
|
||||
$: if (ready && $sideChatRequest?.parentId === parentId) {
|
||||
const pending = $sideChatRequest;
|
||||
sideChatRequest.set(null);
|
||||
if (pending.prompt) {
|
||||
if (sideChatAllowed && pending.prompt) {
|
||||
draft = pending.prompt;
|
||||
input?.setText(draft);
|
||||
if (!busy) void send();
|
||||
|
|
@ -408,40 +422,46 @@
|
|||
</div>
|
||||
<div class="shrink-0 pb-2 z-10">
|
||||
{#if error}<p role="alert" class="px-4 py-2 text-xs text-red-500">{error}</p>{/if}
|
||||
<MessageInput
|
||||
bind:this={input}
|
||||
bind:prompt={draft}
|
||||
bind:autoScroll
|
||||
chatId={id}
|
||||
history={sideHistory}
|
||||
bind:selectedModels
|
||||
multipleModels={false}
|
||||
bind:atSelectedModel
|
||||
bind:files
|
||||
bind:params
|
||||
bind:selectedToolIds
|
||||
bind:selectedSkillIds
|
||||
bind:selectedFilterIds
|
||||
bind:webSearchEnabled
|
||||
bind:imageGenerationEnabled
|
||||
bind:codeInterpreterEnabled
|
||||
{toolApprovalMode}
|
||||
onToolApprovalModeChange={(mode: string) => {
|
||||
params = { ...params, tool_approval_mode: mode };
|
||||
}}
|
||||
{askUser}
|
||||
dropzoneId={`${inputId}-pane`}
|
||||
{inputId}
|
||||
{messagesContainerId}
|
||||
temporary={true}
|
||||
embedded
|
||||
callEnabled={false}
|
||||
terminalSelectionEnabled={false}
|
||||
generating={busy}
|
||||
stopResponse={stop}
|
||||
createMessagePair={() => {}}
|
||||
on:submit={() => void send()}
|
||||
/>
|
||||
{#if sideChatAllowed}
|
||||
<MessageInput
|
||||
bind:this={input}
|
||||
bind:prompt={draft}
|
||||
bind:autoScroll
|
||||
chatId={id}
|
||||
history={sideHistory}
|
||||
bind:selectedModels
|
||||
multipleModels={false}
|
||||
bind:atSelectedModel
|
||||
bind:files
|
||||
bind:params
|
||||
bind:selectedToolIds
|
||||
bind:selectedSkillIds
|
||||
bind:selectedFilterIds
|
||||
bind:webSearchEnabled
|
||||
bind:imageGenerationEnabled
|
||||
bind:codeInterpreterEnabled
|
||||
{toolApprovalMode}
|
||||
onToolApprovalModeChange={(mode: string) => {
|
||||
params = { ...params, tool_approval_mode: mode };
|
||||
}}
|
||||
{askUser}
|
||||
dropzoneId={`${inputId}-pane`}
|
||||
{inputId}
|
||||
{messagesContainerId}
|
||||
temporary={true}
|
||||
embedded
|
||||
callEnabled={false}
|
||||
terminalSelectionEnabled={false}
|
||||
generating={busy}
|
||||
stopResponse={stop}
|
||||
createMessagePair={() => {}}
|
||||
on:submit={() => void send()}
|
||||
/>
|
||||
{:else}
|
||||
<p role="status" class="px-4 py-2 text-xs text-gray-500">
|
||||
{$i18n.t('You do not have permission to use side chat.')}
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -203,6 +203,16 @@
|
|||
(history.currentId && history.messages[history.currentId]?.done != true) ||
|
||||
generating);
|
||||
$: canCompact = !!history?.currentId;
|
||||
$: canSide =
|
||||
($_user?.role === 'admin' ||
|
||||
($_user?.role === 'user' &&
|
||||
($_user.permissions?.chat?.temporary ?? true) &&
|
||||
($_user.permissions?.chat?.side_chat ?? false))) &&
|
||||
!embedded &&
|
||||
selectedModels.length === 1 &&
|
||||
!!selectedModelIds[0] &&
|
||||
!!chatId &&
|
||||
!!history?.currentId;
|
||||
$: canComments = !!(
|
||||
!embedded &&
|
||||
$config?.features?.enable_comments &&
|
||||
|
|
@ -1626,7 +1636,7 @@
|
|||
compactDisabled: () => isActive,
|
||||
canStatus: () => !!history?.currentId,
|
||||
canComments: () => canComments,
|
||||
canSide: () => !embedded && selectedModels.length === 1 && !!selectedModelIds[0] && !!chatId && !!history?.currentId,
|
||||
canSide: () => canSide,
|
||||
canFork: () =>
|
||||
!!history?.currentId &&
|
||||
!isTemporary &&
|
||||
|
|
@ -1641,6 +1651,7 @@
|
|||
onStatus: statusHandler,
|
||||
onComments: openComments,
|
||||
onSide: () => {
|
||||
if (!canSide) return;
|
||||
sideChatRequest.set({ parentId: chatId, prompt: '' });
|
||||
showTabs.set('side');
|
||||
},
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ export const DEFAULT_PERMISSIONS = {
|
|||
call: true,
|
||||
multiple_models: true,
|
||||
temporary: true,
|
||||
side_chat: false,
|
||||
temporary_enforced: false
|
||||
},
|
||||
features: {
|
||||
|
|
|
|||
|
|
@ -241,6 +241,7 @@
|
|||
"Allow Sharing With Groups": "",
|
||||
"Allow Sharing With Users": "",
|
||||
"Allow Speech to Text": "",
|
||||
"Allow Side Chat": "",
|
||||
"Allow Temporary Chat": "",
|
||||
"Allow Text to Speech": "",
|
||||
"Allow the AI to delegate tasks to sub-agents. Each sub-agent creates a real chat with full tool access. Uses additional LLM calls.": "",
|
||||
|
|
@ -4343,6 +4344,7 @@
|
|||
"You do not have permission to upload files to this knowledge base.": "",
|
||||
"You do not have permission to upload files.": "",
|
||||
"You do not have permission to upload web content.": "",
|
||||
"You do not have permission to use side chat.": "",
|
||||
"You have more than 50 users, which often means this workspace is supporting organizational use. Open WebUI is free to use as-is, with no restrictions or hidden limits, and we want to keep it that way.": "",
|
||||
"You have no archived conversations.": "",
|
||||
"You have no shared conversations.": "",
|
||||
|
|
|
|||
|
|
@ -198,14 +198,9 @@
|
|||
|
||||
onMount(() => {
|
||||
const socketInstance = $socket;
|
||||
const scheduleAccessRefresh = () => {
|
||||
socketInstance?.off('connect', refreshAccess);
|
||||
socketInstance?.once('connect', refreshAccess);
|
||||
};
|
||||
socketInstance?.on('access:updated', scheduleAccessRefresh);
|
||||
socketInstance?.on('access:updated', refreshAccess);
|
||||
return () => {
|
||||
socketInstance?.off('access:updated', scheduleAccessRefresh);
|
||||
socketInstance?.off('connect', refreshAccess);
|
||||
socketInstance?.off('access:updated', refreshAccess);
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue