mirror of
https://github.com/open-webui/open-webui.git
synced 2026-10-29 06:32:52 +00:00
refac
This commit is contained in:
parent
5e62c8997f
commit
049d75e176
4 changed files with 157 additions and 107 deletions
|
|
@ -2389,7 +2389,7 @@
|
|||
if (!$chatId) {
|
||||
const modelId = selectedModelIds[0];
|
||||
const _chatId = await initChatHandler(history);
|
||||
if (!$temporaryChatEnabled) {
|
||||
if (_chatId && !$temporaryChatEnabled) {
|
||||
// Realtime replies need no chat completion; start metadata work at chat creation.
|
||||
void generateVoiceChatMetadata(_chatId, modelId, content, chat.title).catch((error) => {
|
||||
console.error('Could not save voice chat metadata', error);
|
||||
|
|
@ -3793,6 +3793,37 @@
|
|||
prompt = '';
|
||||
};
|
||||
|
||||
const getChatId = async (_history: typeof history) => {
|
||||
let _chatId = $chatId;
|
||||
// Empty embedded drafts create their backing chat only when the first message is sent.
|
||||
if (!_chatId) {
|
||||
if (embedded && onCreateEmbeddedChat) {
|
||||
const createdChat = await onCreateEmbeddedChat();
|
||||
if (!createdChat?.id) {
|
||||
toast.error($i18n.t('Failed to create chat'));
|
||||
return null;
|
||||
}
|
||||
if (history !== _history) return null;
|
||||
|
||||
chat = createdChat;
|
||||
_chatId = createdChat.id;
|
||||
loadedChatIdProp = _chatId;
|
||||
await chatId.set(_chatId);
|
||||
await chatTitle.set(createdChat?.chat?.title ?? createdChat?.title ?? $i18n.t('Chat'));
|
||||
|
||||
params = structuredClone(createdChat?.chat?.params ?? {});
|
||||
delete params.note_id;
|
||||
chatFiles = mergeFiles(chatFiles, createdChat?.chat?.files ?? []);
|
||||
await onSelectEmbeddedChat?.(_chatId);
|
||||
} else if ($temporaryChatEnabled) {
|
||||
_chatId = createTemporaryChatId($socket?.id);
|
||||
await chatId.set(_chatId);
|
||||
}
|
||||
await tick();
|
||||
}
|
||||
return _chatId;
|
||||
};
|
||||
|
||||
const submitHandler = async (
|
||||
userPrompt: string,
|
||||
{
|
||||
|
|
@ -3800,6 +3831,7 @@
|
|||
bridge: bridgeRequest = null
|
||||
}: { _raw?: boolean; bridge?: { userMessageId: string; modelId: string } | null } = {}
|
||||
): Promise<BridgeSubmission> => {
|
||||
if (processing === 'uploading') return { status: 'deferred' };
|
||||
emitTyping(false);
|
||||
if (bridgeRequest && (selectedModelIds.length !== 1 || !bridgeRequest.modelId))
|
||||
return { status: 'rejected' };
|
||||
|
|
@ -3880,6 +3912,26 @@
|
|||
return { status: 'deferred' };
|
||||
}
|
||||
|
||||
const currentHistory = history;
|
||||
processing = 'uploading';
|
||||
try {
|
||||
const _chatId = await getChatId(currentHistory);
|
||||
if (_chatId === null) return { status: 'rejected' };
|
||||
|
||||
if (history !== currentHistory) return { status: 'rejected' };
|
||||
if (
|
||||
files.some((file) => file.status === 'pending' && file.file instanceof File) &&
|
||||
!(await messageInput?.uploadPendingFiles(_chatId || (await initChatHandler(history))))
|
||||
)
|
||||
return { status: 'rejected' };
|
||||
if (history !== currentHistory) return { status: 'rejected' };
|
||||
} catch (error) {
|
||||
toast.error(`${error}`);
|
||||
return { status: 'rejected' };
|
||||
} finally {
|
||||
processing = '';
|
||||
}
|
||||
|
||||
if (
|
||||
($chatRequestQueues[$chatId] ?? []).some((m) =>
|
||||
(m.files ?? []).some((file) => ['uploading', 'error'].includes(file.status))
|
||||
|
|
@ -4036,32 +4088,6 @@
|
|||
}
|
||||
history = history;
|
||||
|
||||
// Empty embedded drafts create their backing chat only when the first message is sent.
|
||||
if (!_chatId) {
|
||||
if (embedded && onCreateEmbeddedChat) {
|
||||
const createdChat = await onCreateEmbeddedChat();
|
||||
if (!createdChat?.id) {
|
||||
toast.error($i18n.t('Failed to create chat'));
|
||||
return;
|
||||
}
|
||||
|
||||
chat = createdChat;
|
||||
_chatId = createdChat.id;
|
||||
loadedChatIdProp = _chatId;
|
||||
await chatId.set(_chatId);
|
||||
await chatTitle.set(createdChat?.chat?.title ?? createdChat?.title ?? $i18n.t('Chat'));
|
||||
|
||||
params = structuredClone(createdChat?.chat?.params ?? {});
|
||||
delete params.note_id;
|
||||
chatFiles = mergeFiles(chatFiles, createdChat?.chat?.files ?? []);
|
||||
await onSelectEmbeddedChat?.(_chatId);
|
||||
} else if ($temporaryChatEnabled) {
|
||||
_chatId = createTemporaryChatId($socket?.id);
|
||||
await chatId.set(_chatId);
|
||||
}
|
||||
await tick();
|
||||
}
|
||||
|
||||
await tick();
|
||||
|
||||
// Re-clone history so sendMessageSocket gets the response messages we just added
|
||||
|
|
@ -4405,8 +4431,7 @@
|
|||
background_tasks: {
|
||||
...(!$temporaryChatEnabled &&
|
||||
(!_chatId ||
|
||||
(embedded &&
|
||||
(userMessage?.parentId ?? null) === null &&
|
||||
((userMessage?.parentId ?? null) === null &&
|
||||
createMessagesList(_history, responseMessageId).length === 2))
|
||||
? {
|
||||
title_generation: $settings?.title?.auto ?? true,
|
||||
|
|
@ -4733,12 +4758,12 @@
|
|||
}
|
||||
};
|
||||
|
||||
const initChatHandler = async (history) => {
|
||||
const initChatHandler = async (_history: typeof history) => {
|
||||
let _chatId = $chatId;
|
||||
const selectedFolderId = $selectedFolder?.id;
|
||||
|
||||
if (!$temporaryChatEnabled) {
|
||||
chat = await createNewChat(
|
||||
const createdChat = await createNewChat(
|
||||
localStorage.token,
|
||||
{
|
||||
id: _chatId,
|
||||
|
|
@ -4746,8 +4771,8 @@
|
|||
models: selectedModels,
|
||||
system: $settings.system ?? undefined,
|
||||
params: params,
|
||||
history: history,
|
||||
messages: createMessagesList(history, history.currentId),
|
||||
history: _history,
|
||||
messages: createMessagesList(_history, _history.currentId),
|
||||
tags: [],
|
||||
timestamp: Date.now()
|
||||
},
|
||||
|
|
@ -4755,6 +4780,9 @@
|
|||
chatVariables
|
||||
);
|
||||
|
||||
if (!createdChat?.id) throw new Error($i18n.t('Failed to create chat'));
|
||||
if (history !== _history || $chatId !== _chatId) return '';
|
||||
chat = createdChat;
|
||||
_chatId = chat.id;
|
||||
await chatId.set(_chatId);
|
||||
|
||||
|
|
@ -4828,7 +4856,7 @@
|
|||
const getChatInputDraft = () => ({
|
||||
prompt,
|
||||
files: files
|
||||
.filter((file) => file.type !== 'image')
|
||||
.filter((file) => file.type !== 'image' && !(file.file instanceof File))
|
||||
.map((file) => ({
|
||||
...file,
|
||||
user: undefined,
|
||||
|
|
@ -5475,6 +5503,7 @@
|
|||
{:else}
|
||||
<div class="flex items-center h-full">
|
||||
<Placeholder
|
||||
chatId={$chatId}
|
||||
{terminalContext}
|
||||
callActive={!!(bridge?.connected || bridge?.connecting)}
|
||||
callEnabled={!isShared}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@
|
|||
} from '$lib/utils';
|
||||
import { uploadFile } from '$lib/apis/files';
|
||||
import { getCwd, uploadNewFileToTerminal } from '$lib/apis/terminal';
|
||||
import { isSavedChatId } from '$lib/utils/chatId';
|
||||
import { generateAutoCompletion } from '$lib/apis';
|
||||
import { deleteFileById } from '$lib/apis/files';
|
||||
import { getChatById } from '$lib/apis/chats';
|
||||
|
|
@ -161,7 +162,6 @@
|
|||
|
||||
export let autoScroll = false;
|
||||
export let generating = false;
|
||||
export let uploadPending = false;
|
||||
|
||||
export let atSelectedModel: Model | undefined = undefined;
|
||||
export let selectedModels: [''];
|
||||
|
|
@ -258,7 +258,7 @@
|
|||
$: chatInputDraft = {
|
||||
prompt,
|
||||
files: files
|
||||
.filter((file) => file.type !== 'image')
|
||||
.filter((file) => file.type !== 'image' && !(file.file instanceof File))
|
||||
.map((file) => {
|
||||
return {
|
||||
...file,
|
||||
|
|
@ -980,6 +980,67 @@
|
|||
);
|
||||
};
|
||||
|
||||
const uploadTerminalFile = async (fileItem, sessionId: string | null) => {
|
||||
fileItem.status = 'uploading';
|
||||
fileItem.error = '';
|
||||
files = files;
|
||||
try {
|
||||
const terminal = getFilesystemUploadTerminal(fileItem.terminal_id);
|
||||
if (!terminal) throw new Error('The attached file’s terminal is unavailable.');
|
||||
if (fileItem.session_id && fileItem.session_id !== sessionId)
|
||||
throw new Error('Attached files must belong to this chat.');
|
||||
if (terminal.contexts?.chat?.context_id === 'chat_id' && !isSavedChatId(sessionId))
|
||||
throw new Error('A saved chat is required for this terminal');
|
||||
|
||||
const cwd =
|
||||
(terminalContext?.terminalId === fileItem.terminal_id
|
||||
? terminalContext?.currentDirectory
|
||||
: null) || (await getCwd(terminal.url, terminal.key, sessionId || undefined))?.cwd;
|
||||
if (!cwd) throw new Error($i18n.t('Could not determine the upload folder.'));
|
||||
const uploadedFile = await uploadNewFileToTerminal(
|
||||
terminal.url,
|
||||
terminal.key,
|
||||
cwd,
|
||||
fileItem.file,
|
||||
sessionId || undefined
|
||||
);
|
||||
if (!uploadedFile) throw new Error($i18n.t('Failed to upload file.'));
|
||||
|
||||
fileItem.status = 'uploaded';
|
||||
fileItem.session_id = sessionId;
|
||||
fileItem.name = uploadedFile.path.split('/').pop() || fileItem.name;
|
||||
fileItem.id = uploadedFile.path;
|
||||
fileItem.path = uploadedFile.path;
|
||||
fileItem.url = uploadedFile.path;
|
||||
fileItem.size = uploadedFile.size ?? fileItem.size;
|
||||
fileItem.file = uploadedFile;
|
||||
showFileNavDir.set(uploadedFile.path);
|
||||
} catch (error) {
|
||||
fileItem.status = 'error';
|
||||
fileItem.error = `${error}`;
|
||||
toast.error(fileItem.error);
|
||||
return false;
|
||||
} finally {
|
||||
files = files;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
export const uploadPendingFiles = async (sessionId: string) => {
|
||||
if (!sessionId) return false;
|
||||
for (const fileItem of files.filter(
|
||||
(item) => item.status === 'pending' && item.file instanceof File
|
||||
)) {
|
||||
if (chatId !== sessionId) return false;
|
||||
if (!(await uploadTerminalFile(fileItem, sessionId))) {
|
||||
fileItem.status = 'pending';
|
||||
files = files;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !files.some((item) => item.status === 'pending' && item.file instanceof File);
|
||||
};
|
||||
|
||||
const uploadFileHandler = async (file, process = true, itemData = {}) => {
|
||||
if ($_user?.role !== 'admin' && !($_user?.permissions?.chat?.file_upload ?? true)) {
|
||||
toast.error($i18n.t('You do not have permission to upload files.'));
|
||||
|
|
@ -1019,54 +1080,20 @@
|
|||
files = [...files, fileItem];
|
||||
|
||||
if (filesystemUploadTerminal) {
|
||||
fileItem.type = 'filesystem';
|
||||
fileItem.file = file;
|
||||
fileItem.terminal_id = $selectedTerminalId;
|
||||
fileItem.session_id = chatId || null;
|
||||
try {
|
||||
const cwd =
|
||||
(terminalContext?.terminalId === $selectedTerminalId
|
||||
? terminalContext?.currentDirectory
|
||||
: null) ||
|
||||
(
|
||||
await getCwd(
|
||||
filesystemUploadTerminal.url,
|
||||
filesystemUploadTerminal.key,
|
||||
fileItem.session_id || undefined
|
||||
)
|
||||
)?.cwd;
|
||||
if (!cwd) throw new Error($i18n.t('Could not determine the upload folder.'));
|
||||
const uploadedFile = await uploadNewFileToTerminal(
|
||||
filesystemUploadTerminal.url,
|
||||
filesystemUploadTerminal.key,
|
||||
cwd,
|
||||
file,
|
||||
fileItem.session_id || undefined
|
||||
);
|
||||
|
||||
if (uploadedFile) {
|
||||
fileItem.type = 'filesystem';
|
||||
fileItem.status = 'uploaded';
|
||||
fileItem.name = uploadedFile.path.split('/').pop() || file.name;
|
||||
fileItem.id = uploadedFile.path;
|
||||
fileItem.path = uploadedFile.path;
|
||||
fileItem.url = uploadedFile.path;
|
||||
fileItem.size = uploadedFile.size ?? file.size;
|
||||
fileItem.file = uploadedFile;
|
||||
files = files;
|
||||
showFileNavDir.set(uploadedFile.path);
|
||||
} 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 });
|
||||
if (!chatId && filesystemUploadTerminal.contexts?.chat?.context_id === 'chat_id') {
|
||||
fileItem.status = 'pending';
|
||||
files = files;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await uploadTerminalFile(fileItem, fileItem.session_id))) {
|
||||
files = files.filter((item) => item?.itemId !== tempItemId);
|
||||
}
|
||||
onUpdate({ file: fileItem });
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -2883,38 +2910,28 @@
|
|||
</div>
|
||||
{:else}
|
||||
<div class=" flex items-center">
|
||||
<Tooltip
|
||||
content={uploadPending
|
||||
? $i18n.t('Waiting for upload...')
|
||||
: $i18n.t('Send message')}
|
||||
>
|
||||
<Tooltip content={$i18n.t('Send message')}>
|
||||
<button
|
||||
id="send-message-button"
|
||||
aria-label={uploadPending
|
||||
? $i18n.t('Waiting for upload...')
|
||||
: $i18n.t('Send message')}
|
||||
class="{!(prompt === '' && files.length === 0) || uploadPending
|
||||
aria-label={$i18n.t('Send message')}
|
||||
class="{!(prompt === '' && files.length === 0)
|
||||
? 'bg-black text-white hover:bg-gray-900 dark:bg-white dark:text-black dark:hover:bg-gray-100 '
|
||||
: 'text-white bg-gray-200 dark:text-gray-900 dark:bg-gray-700 disabled'} transition rounded-full p-[0.3125rem] self-center"
|
||||
type="submit"
|
||||
disabled={(prompt === '' && files.length === 0) || uploadPending}
|
||||
disabled={prompt === '' && files.length === 0}
|
||||
>
|
||||
{#if uploadPending}
|
||||
<Spinner className="size-5" />
|
||||
{:else}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="size-5"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8 14a.75.75 0 0 1-.75-.75V4.56L4.03 7.78a.75.75 0 0 1-1.06-1.06l4.5-4.5a.75.75 0 0 1 1.06 0l4.5 4.5a.75.75 0 0 1-1.06 1.06L8.75 4.56v8.69A.75.75 0 0 1 8 14Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="size-5"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M8 14a.75.75 0 0 1-.75-.75V4.56L4.03 7.78a.75.75 0 0 1-1.06-1.06l4.5-4.5a.75.75 0 0 1 1.06 0l4.5 4.5a.75.75 0 0 1-1.06 1.06L8.75 4.56v8.69A.75.75 0 0 1 8 14Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@
|
|||
export let selectedModels: [''];
|
||||
|
||||
export let history;
|
||||
export let chatId = '';
|
||||
|
||||
export let prompt = '';
|
||||
export let files = [];
|
||||
|
|
@ -252,6 +253,7 @@
|
|||
<div class="text-base font-normal @md:max-w-3xl w-full py-3 {atSelectedModel ? 'mt-2' : ''}">
|
||||
{#if !($selectedFolder && folderReadOnly)}
|
||||
<MessageInput
|
||||
{chatId}
|
||||
{terminalContext}
|
||||
{callActive}
|
||||
{callEnabled}
|
||||
|
|
|
|||
|
|
@ -2524,6 +2524,8 @@
|
|||
"settings.admin.authentication.clientId.label": "Client ID",
|
||||
"settings.admin.authentication.clientSecret.description": "OAuth client secret from the provider.",
|
||||
"settings.admin.authentication.clientSecret.label": "Client Secret",
|
||||
"settings.admin.authentication.defaultEmailDomain.description": "Uses username@domain when the LDAP email attribute is empty. Leave empty to disable. Signs in to an existing account with that email; use a domain you control.",
|
||||
"settings.admin.authentication.defaultEmailDomain.label": "Default Email Domain",
|
||||
"settings.admin.authentication.defaultGroup.description": "Group assigned to new users by default.",
|
||||
"settings.admin.authentication.defaultGroup.label": "Default Group",
|
||||
"settings.admin.authentication.defaultUserRole.description": "Role assigned to new users when they create an account.",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue