refac
This commit is contained in:
@@ -27,6 +27,7 @@ from open_webui.utils.terminals import (
|
|||||||
terminal_context_available,
|
terminal_context_available,
|
||||||
terminal_context_config,
|
terminal_context_config,
|
||||||
terminal_context_id,
|
terminal_context_id,
|
||||||
|
terminal_chat_uploads,
|
||||||
terminal_contexts,
|
terminal_contexts,
|
||||||
)
|
)
|
||||||
from open_webui.utils.tools import bearer_auth_header, normalize_bearer_token
|
from open_webui.utils.tools import bearer_auth_header, normalize_bearer_token
|
||||||
@@ -87,6 +88,7 @@ async def list_terminal_servers(request: Request, user=Depends(get_verified_user
|
|||||||
'url': connection.get('url', ''),
|
'url': connection.get('url', ''),
|
||||||
'name': connection.get('name', ''),
|
'name': connection.get('name', ''),
|
||||||
'contexts': terminal_contexts(connection),
|
'contexts': terminal_contexts(connection),
|
||||||
|
'config': {'chat_uploads': terminal_chat_uploads(connection)},
|
||||||
}
|
}
|
||||||
for connection in connections
|
for connection in connections
|
||||||
if connection.get('enabled', True) and await has_connection_access(user, connection, user_group_ids)
|
if connection.get('enabled', True) and await has_connection_access(user, connection, user_group_ids)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ TERMINAL_CONTEXT_HEADER = 'X-Terminal-Context-Id'
|
|||||||
TERMINAL_CONTEXT_DEFAULT = 'default'
|
TERMINAL_CONTEXT_DEFAULT = 'default'
|
||||||
TERMINAL_CONTEXT_TYPES = {'chat', 'automation'}
|
TERMINAL_CONTEXT_TYPES = {'chat', 'automation'}
|
||||||
TERMINAL_CONTEXT_ID_SOURCES = {'chat': 'chat_id', 'automation': 'automation_id'}
|
TERMINAL_CONTEXT_ID_SOURCES = {'chat': 'chat_id', 'automation': 'automation_id'}
|
||||||
|
TERMINAL_CHAT_UPLOAD_MODES = {'default', 'filesystem'}
|
||||||
|
|
||||||
|
|
||||||
def is_terminal_orchestrator(connection: dict) -> bool:
|
def is_terminal_orchestrator(connection: dict) -> bool:
|
||||||
@@ -104,3 +105,9 @@ def terminal_contexts(connection: dict) -> dict:
|
|||||||
else:
|
else:
|
||||||
result[context] = {}
|
result[context] = {}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def terminal_chat_uploads(connection: dict) -> str:
|
||||||
|
"""Return normalized main-chat upload behavior for this connection."""
|
||||||
|
value = (connection.get('config') or {}).get('chat_uploads')
|
||||||
|
return value if value in TERMINAL_CHAT_UPLOAD_MODES else 'default'
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ export type TerminalServer = {
|
|||||||
url: string;
|
url: string;
|
||||||
name: string;
|
name: string;
|
||||||
contexts?: Record<string, false | { context_id?: string }>;
|
contexts?: Record<string, false | { context_id?: string }>;
|
||||||
|
config?: {
|
||||||
|
chat_uploads?: 'default' | 'filesystem';
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getTerminalServers = async (token: string): Promise<TerminalServer[]> => {
|
export const getTerminalServers = async (token: string): Promise<TerminalServer[]> => {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
let auth_type = 'bearer';
|
let auth_type = 'bearer';
|
||||||
let path = '/openapi.json';
|
let path = '/openapi.json';
|
||||||
let enabled = false;
|
let enabled = false;
|
||||||
|
let chatUploads: 'default' | 'filesystem' = 'default';
|
||||||
let chatContextMode: 'default' | 'chat_id' | 'off' = 'default';
|
let chatContextMode: 'default' | 'chat_id' | 'off' = 'default';
|
||||||
let automationContextMode: 'default' | 'automation_id' | 'off' = 'default';
|
let automationContextMode: 'default' | 'automation_id' | 'off' = 'default';
|
||||||
let showAdvanced = false;
|
let showAdvanced = false;
|
||||||
@@ -80,6 +81,7 @@
|
|||||||
auth_type = connection?.auth_type ?? 'bearer';
|
auth_type = connection?.auth_type ?? 'bearer';
|
||||||
path = connection?.path ?? '/openapi.json';
|
path = connection?.path ?? '/openapi.json';
|
||||||
enabled = connection?.enabled ?? true;
|
enabled = connection?.enabled ?? true;
|
||||||
|
chatUploads = connection?.config?.chat_uploads === 'filesystem' ? 'filesystem' : 'default';
|
||||||
accessGrants = connection?.config?.access_grants ?? [];
|
accessGrants = connection?.config?.access_grants ?? [];
|
||||||
|
|
||||||
// Restore policy state
|
// Restore policy state
|
||||||
@@ -125,6 +127,7 @@
|
|||||||
auth_type = 'bearer';
|
auth_type = 'bearer';
|
||||||
path = '/openapi.json';
|
path = '/openapi.json';
|
||||||
enabled = false;
|
enabled = false;
|
||||||
|
chatUploads = 'default';
|
||||||
accessGrants = [];
|
accessGrants = [];
|
||||||
chatContextMode = 'default';
|
chatContextMode = 'default';
|
||||||
automationContextMode = 'default';
|
automationContextMode = 'default';
|
||||||
@@ -373,6 +376,14 @@
|
|||||||
contexts.automation = { context_id: 'automation_id' };
|
contexts.automation = { context_id: 'automation_id' };
|
||||||
}
|
}
|
||||||
const useContexts = !direct && serverType === 'orchestrator' && Object.keys(contexts).length > 0;
|
const useContexts = !direct && serverType === 'orchestrator' && Object.keys(contexts).length > 0;
|
||||||
|
const connectionConfig: Record<string, any> =
|
||||||
|
connection?.config && typeof connection.config === 'object' ? { ...connection.config } : {};
|
||||||
|
if (!direct) connectionConfig.access_grants = accessGrants;
|
||||||
|
else delete connectionConfig.access_grants;
|
||||||
|
if (useContexts) connectionConfig.contexts = contexts;
|
||||||
|
else delete connectionConfig.contexts;
|
||||||
|
if (chatUploads === 'filesystem') connectionConfig.chat_uploads = 'filesystem';
|
||||||
|
else delete connectionConfig.chat_uploads;
|
||||||
|
|
||||||
const result = {
|
const result = {
|
||||||
...(!direct && id.trim() ? { id: id.trim() } : {}),
|
...(!direct && id.trim() ? { id: id.trim() } : {}),
|
||||||
@@ -382,10 +393,7 @@
|
|||||||
path,
|
path,
|
||||||
auth_type,
|
auth_type,
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
config: {
|
config: connectionConfig,
|
||||||
...(!direct ? { access_grants: accessGrants } : {}),
|
|
||||||
...(useContexts ? { contexts } : {})
|
|
||||||
},
|
|
||||||
// Policy fields
|
// Policy fields
|
||||||
...(serverType ? { server_type: serverType } : {}),
|
...(serverType ? { server_type: serverType } : {}),
|
||||||
...(serverType === 'orchestrator' && policyId ? { policy_id: policyId } : {})
|
...(serverType === 'orchestrator' && policyId ? { policy_id: policyId } : {})
|
||||||
@@ -532,6 +540,26 @@
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex gap-2 mt-2">
|
||||||
|
<div class="flex flex-col w-full">
|
||||||
|
<div class="flex justify-between mb-0.5">
|
||||||
|
<label for="terminal-chat-uploads" class={`text-xs text-gray-500`}
|
||||||
|
>{$i18n.t('Chat Uploads')}</label
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-1 items-center">
|
||||||
|
<select
|
||||||
|
id="terminal-chat-uploads"
|
||||||
|
class={`w-full text-sm ${selectClass}`}
|
||||||
|
bind:value={chatUploads}
|
||||||
|
>
|
||||||
|
<option value="default">{$i18n.t('Default')}</option>
|
||||||
|
<option value="filesystem">{$i18n.t('Filesystem')}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Policy section (orchestrator only, admin only) -->
|
<!-- Policy section (orchestrator only, admin only) -->
|
||||||
{#if serverType === 'orchestrator' && !direct}
|
{#if serverType === 'orchestrator' && !direct}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -94,7 +94,8 @@
|
|||||||
url: `${WEBUI_API_BASE_URL}/terminals/${t.id}`,
|
url: `${WEBUI_API_BASE_URL}/terminals/${t.id}`,
|
||||||
name: t.name,
|
name: t.name,
|
||||||
key: localStorage.token,
|
key: localStorage.token,
|
||||||
contexts: t.contexts ?? {}
|
contexts: t.contexts ?? {},
|
||||||
|
config: t.config ?? {}
|
||||||
}));
|
}));
|
||||||
terminalServers.set([...existingDirectTerminals, ...systemEntries] as any);
|
terminalServers.set([...existingDirectTerminals, ...systemEntries] as any);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@
|
|||||||
getWeekday
|
getWeekday
|
||||||
} from '$lib/utils';
|
} from '$lib/utils';
|
||||||
import { uploadFile } from '$lib/apis/files';
|
import { uploadFile } from '$lib/apis/files';
|
||||||
|
import { getCwd, uploadToTerminal } from '$lib/apis/terminal';
|
||||||
import { generateAutoCompletion } from '$lib/apis';
|
import { generateAutoCompletion } from '$lib/apis';
|
||||||
import { deleteFileById } from '$lib/apis/files';
|
import { deleteFileById } from '$lib/apis/files';
|
||||||
import { getChatById } from '$lib/apis/chats';
|
import { getChatById } from '$lib/apis/chats';
|
||||||
@@ -860,19 +861,43 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getFilesystemUploadTerminal = (
|
||||||
|
selectedId = $selectedTerminalId,
|
||||||
|
servers: any[] | null = $terminalServers,
|
||||||
|
settingsValue: any = $settings
|
||||||
|
) => {
|
||||||
|
if (!selectedId) return null;
|
||||||
|
|
||||||
|
const systemTerminal = (servers ?? []).find(
|
||||||
|
(t: any) => t.id && t.id === selectedId && t.config?.chat_uploads === 'filesystem'
|
||||||
|
);
|
||||||
|
if (systemTerminal) return systemTerminal;
|
||||||
|
|
||||||
|
return (
|
||||||
|
(settingsValue?.terminalServers ?? []).find(
|
||||||
|
(t: any) =>
|
||||||
|
t.url === selectedId &&
|
||||||
|
t.enabled &&
|
||||||
|
t.config?.chat_uploads === 'filesystem'
|
||||||
|
) ?? null
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const uploadFileHandler = async (file, process = true, itemData = {}) => {
|
const uploadFileHandler = async (file, process = true, itemData = {}) => {
|
||||||
if ($_user?.role !== 'admin' && !($_user?.permissions?.chat?.file_upload ?? true)) {
|
if ($_user?.role !== 'admin' && !($_user?.permissions?.chat?.file_upload ?? true)) {
|
||||||
toast.error($i18n.t('You do not have permission to upload files.'));
|
toast.error($i18n.t('You do not have permission to upload files.'));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileUploadCapableModels.length !== selectedModelIds.length) {
|
const filesystemUploadTerminal = getFilesystemUploadTerminal();
|
||||||
|
|
||||||
|
if (!filesystemUploadTerminal && fileUploadCapableModels.length !== selectedModelIds.length) {
|
||||||
toast.error($i18n.t('Model(s) do not support file upload'));
|
toast.error($i18n.t('Model(s) do not support file upload'));
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tempItemId = uuidv4();
|
const tempItemId = uuidv4();
|
||||||
const fileItem = {
|
const fileItem: any = {
|
||||||
type: 'file',
|
type: 'file',
|
||||||
file: '',
|
file: '',
|
||||||
id: null,
|
id: null,
|
||||||
@@ -896,6 +921,48 @@
|
|||||||
|
|
||||||
files = [...files, fileItem];
|
files = [...files, fileItem];
|
||||||
|
|
||||||
|
if (filesystemUploadTerminal) {
|
||||||
|
try {
|
||||||
|
const cwd =
|
||||||
|
(await getCwd(
|
||||||
|
filesystemUploadTerminal.url,
|
||||||
|
filesystemUploadTerminal.key,
|
||||||
|
chatId || undefined
|
||||||
|
))?.cwd || '/';
|
||||||
|
const uploadedFile = await uploadToTerminal(
|
||||||
|
filesystemUploadTerminal.url,
|
||||||
|
filesystemUploadTerminal.key,
|
||||||
|
cwd,
|
||||||
|
file,
|
||||||
|
chatId || undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
if (uploadedFile) {
|
||||||
|
fileItem.type = 'terminal_file';
|
||||||
|
fileItem.status = 'uploaded';
|
||||||
|
fileItem.id = uploadedFile.path;
|
||||||
|
fileItem.path = uploadedFile.path;
|
||||||
|
fileItem.url = uploadedFile.path;
|
||||||
|
fileItem.size = uploadedFile.size ?? file.size;
|
||||||
|
fileItem.file = uploadedFile;
|
||||||
|
files = files;
|
||||||
|
} else {
|
||||||
|
fileItem.status = 'error';
|
||||||
|
fileItem.error = $i18n.t('Failed to upload file.');
|
||||||
|
toast.error(fileItem.error);
|
||||||
|
files = files.filter((item) => item?.itemId !== tempItemId);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
fileItem.status = 'error';
|
||||||
|
fileItem.error = `${e}`;
|
||||||
|
toast.error(`${e}`);
|
||||||
|
files = files.filter((item) => item?.itemId !== tempItemId);
|
||||||
|
} finally {
|
||||||
|
onUpdate({ file: fileItem });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!$temporaryChatEnabled) {
|
if (!$temporaryChatEnabled) {
|
||||||
try {
|
try {
|
||||||
// If the file is an audio file, provide the language for STT.
|
// If the file is an audio file, provide the language for STT.
|
||||||
@@ -2078,7 +2145,13 @@
|
|||||||
<InputMenu
|
<InputMenu
|
||||||
bind:files
|
bind:files
|
||||||
selectedModels={selectedModelIds}
|
selectedModels={selectedModelIds}
|
||||||
{fileUploadCapableModels}
|
fileUploadCapableModels={getFilesystemUploadTerminal(
|
||||||
|
$selectedTerminalId,
|
||||||
|
$terminalServers,
|
||||||
|
$settings
|
||||||
|
)
|
||||||
|
? selectedModelIds
|
||||||
|
: fileUploadCapableModels}
|
||||||
{toolApprovalMode}
|
{toolApprovalMode}
|
||||||
{onToolApprovalModeChange}
|
{onToolApprovalModeChange}
|
||||||
{screenCaptureHandler}
|
{screenCaptureHandler}
|
||||||
|
|||||||
@@ -47,7 +47,14 @@
|
|||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
data = data.filter((d) => d && !d.error);
|
data = data.filter((d) => d && !d.error);
|
||||||
terminalServers.set([...data, ...existingSystemTerminals]);
|
terminalServers.set([
|
||||||
|
...data.map((d, i) => ({
|
||||||
|
...d,
|
||||||
|
key: activeTerminals[i]?.key ?? '',
|
||||||
|
config: activeTerminals[i]?.config ?? d?.config ?? {}
|
||||||
|
})),
|
||||||
|
...existingSystemTerminals
|
||||||
|
]);
|
||||||
} else {
|
} else {
|
||||||
terminalServers.set(existingSystemTerminals);
|
terminalServers.set(existingSystemTerminals);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,14 @@
|
|||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
terminalServersData = terminalServersData.filter((data: any) => data && !data.error);
|
terminalServersData = terminalServersData.filter((data: any) => data && !data.error);
|
||||||
terminalServers.set([...terminalServersData, ...existingSystemTerminals] as any);
|
terminalServers.set([
|
||||||
|
...terminalServersData.map((data: any, i: number) => ({
|
||||||
|
...data,
|
||||||
|
key: activeTerminals[i]?.key ?? '',
|
||||||
|
config: activeTerminals[i]?.config ?? data?.config ?? {}
|
||||||
|
})),
|
||||||
|
...existingSystemTerminals
|
||||||
|
] as any);
|
||||||
} else {
|
} else {
|
||||||
terminalServers.set(existingSystemTerminals as any);
|
terminalServers.set(existingSystemTerminals as any);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -434,6 +434,7 @@
|
|||||||
"Chat Permissions": "",
|
"Chat Permissions": "",
|
||||||
"Chat Tags Auto-Generation": "",
|
"Chat Tags Auto-Generation": "",
|
||||||
"Chat unshared successfully.": "",
|
"Chat unshared successfully.": "",
|
||||||
|
"Chat Uploads": "",
|
||||||
"Chat Variables": "",
|
"Chat Variables": "",
|
||||||
"Chat Variables have conflicting model definitions": "",
|
"Chat Variables have conflicting model definitions": "",
|
||||||
"chats": "",
|
"chats": "",
|
||||||
@@ -1236,6 +1237,7 @@
|
|||||||
"File uploaded successfully": "",
|
"File uploaded successfully": "",
|
||||||
"Filename": "",
|
"Filename": "",
|
||||||
"Files": "",
|
"Files": "",
|
||||||
|
"Filesystem": "",
|
||||||
"Fill the required fields first.": "",
|
"Fill the required fields first.": "",
|
||||||
"Fill the source fields and test query first.": "",
|
"Fill the source fields and test query first.": "",
|
||||||
"Filter": "",
|
"Filter": "",
|
||||||
|
|||||||
@@ -435,6 +435,7 @@
|
|||||||
"Chat Permissions": "",
|
"Chat Permissions": "",
|
||||||
"Chat Tags Auto-Generation": "",
|
"Chat Tags Auto-Generation": "",
|
||||||
"Chat unshared successfully.": "",
|
"Chat unshared successfully.": "",
|
||||||
|
"Chat Uploads": "",
|
||||||
"Chat Variables": "",
|
"Chat Variables": "",
|
||||||
"Chat Variables have conflicting model definitions": "",
|
"Chat Variables have conflicting model definitions": "",
|
||||||
"chats": "",
|
"chats": "",
|
||||||
@@ -1238,6 +1239,7 @@
|
|||||||
"File uploaded successfully": "",
|
"File uploaded successfully": "",
|
||||||
"Filename": "",
|
"Filename": "",
|
||||||
"Files": "",
|
"Files": "",
|
||||||
|
"Filesystem": "",
|
||||||
"Fill the required fields first.": "",
|
"Fill the required fields first.": "",
|
||||||
"Fill the source fields and test query first.": "",
|
"Fill the source fields and test query first.": "",
|
||||||
"Filter": "",
|
"Filter": "",
|
||||||
|
|||||||
@@ -161,7 +161,8 @@
|
|||||||
})
|
})
|
||||||
.map((data, i) => ({
|
.map((data, i) => ({
|
||||||
...data,
|
...data,
|
||||||
key: enabledTerminals[i]?.key ?? ''
|
key: enabledTerminals[i]?.key ?? '',
|
||||||
|
config: enabledTerminals[i]?.config ?? data?.config ?? {}
|
||||||
}))
|
}))
|
||||||
: []),
|
: []),
|
||||||
// Store with proxy URL and session key for FileNav file browsing
|
// Store with proxy URL and session key for FileNav file browsing
|
||||||
@@ -170,7 +171,8 @@
|
|||||||
url: `${WEBUI_API_BASE_URL}/terminals/${t.id}`,
|
url: `${WEBUI_API_BASE_URL}/terminals/${t.id}`,
|
||||||
name: t.name,
|
name: t.name,
|
||||||
key: localStorage.token,
|
key: localStorage.token,
|
||||||
contexts: t.contexts ?? {}
|
contexts: t.contexts ?? {},
|
||||||
|
config: t.config ?? {}
|
||||||
}))
|
}))
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user