Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
open-webui
Commits
92e21acb
Unverified
Commit
92e21acb
authored
Mar 06, 2024
by
Timothy Jaeryang Baek
Committed by
GitHub
Mar 06, 2024
Browse files
Merge pull request #1033 from open-webui/dev
0.1.109
parents
eb51ad14
ad237b61
Changes
39
Show whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
955 additions
and
830 deletions
+955
-830
src/lib/components/chat/MessageInput.svelte
src/lib/components/chat/MessageInput.svelte
+8
-13
src/lib/components/chat/Messages.svelte
src/lib/components/chat/Messages.svelte
+117
-72
src/lib/components/chat/Messages/ResponseMessage.svelte
src/lib/components/chat/Messages/ResponseMessage.svelte
+250
-224
src/lib/components/chat/Messages/UserMessage.svelte
src/lib/components/chat/Messages/UserMessage.svelte
+58
-50
src/lib/components/chat/Settings/Account.svelte
src/lib/components/chat/Settings/Account.svelte
+5
-5
src/lib/components/chat/Settings/Chats.svelte
src/lib/components/chat/Settings/Chats.svelte
+10
-4
src/lib/components/chat/Settings/Connections.svelte
src/lib/components/chat/Settings/Connections.svelte
+79
-36
src/lib/components/chat/Settings/Models.svelte
src/lib/components/chat/Settings/Models.svelte
+327
-290
src/lib/components/common/Tooltip.svelte
src/lib/components/common/Tooltip.svelte
+1
-1
src/lib/components/documents/AddDocModal.svelte
src/lib/components/documents/AddDocModal.svelte
+11
-6
src/lib/components/playground/ChatCompletion.svelte
src/lib/components/playground/ChatCompletion.svelte
+8
-8
src/lib/constants.ts
src/lib/constants.ts
+1
-6
src/routes/(app)/+layout.svelte
src/routes/(app)/+layout.svelte
+15
-11
src/routes/(app)/+page.svelte
src/routes/(app)/+page.svelte
+9
-10
src/routes/(app)/c/[id]/+page.svelte
src/routes/(app)/c/[id]/+page.svelte
+9
-10
src/routes/(app)/documents/+page.svelte
src/routes/(app)/documents/+page.svelte
+3
-4
src/routes/(app)/modelfiles/+page.svelte
src/routes/(app)/modelfiles/+page.svelte
+7
-5
src/routes/(app)/playground/+page.svelte
src/routes/(app)/playground/+page.svelte
+33
-70
src/routes/(app)/prompts/+page.svelte
src/routes/(app)/prompts/+page.svelte
+4
-5
No files found.
src/lib/components/chat/MessageInput.svelte
View file @
92e21acb
...
@@ -19,7 +19,7 @@
...
@@ -19,7 +19,7 @@
export let suggestionPrompts = [];
export let suggestionPrompts = [];
export let autoScroll = true;
export let autoScroll = true;
let chatTextAreaElement:HTMLTextAreaElement
let filesInputElement;
let filesInputElement;
let promptsElement;
let promptsElement;
...
@@ -43,11 +43,9 @@
...
@@ -43,11 +43,9 @@
let speechRecognition;
let speechRecognition;
$: if (prompt) {
$: if (prompt) {
const chatInput = document.getElementById('chat-textarea');
if (chatTextAreaElement) {
chatTextAreaElement.style.height = '';
if (chatInput) {
chatTextAreaElement.style.height = Math.min(chatTextAreaElement.scrollHeight, 200) + 'px';
chatInput.style.height = '';
chatInput.style.height = Math.min(chatInput.scrollHeight, 200) + 'px';
}
}
}
}
...
@@ -86,9 +84,7 @@
...
@@ -86,9 +84,7 @@
if (res) {
if (res) {
prompt = res.text;
prompt = res.text;
await tick();
await tick();
chatTextAreaElement?.focus();
const inputElement = document.getElementById('chat-textarea');
inputElement?.focus();
if (prompt !== '' && $settings?.speechAutoSend === true) {
if (prompt !== '' && $settings?.speechAutoSend === true) {
submitPrompt(prompt, user);
submitPrompt(prompt, user);
...
@@ -191,8 +187,7 @@
...
@@ -191,8 +187,7 @@
prompt = `${prompt}${transcript}`;
prompt = `${prompt}${transcript}`;
await tick();
await tick();
const inputElement = document.getElementById('chat-textarea');
chatTextAreaElement?.focus();
inputElement?.focus();
// Restart the inactivity timeout
// Restart the inactivity timeout
timeoutId = setTimeout(() => {
timeoutId = setTimeout(() => {
...
@@ -294,8 +289,7 @@
...
@@ -294,8 +289,7 @@
};
};
onMount(() => {
onMount(() => {
const chatInput = document.getElementById('chat-textarea');
window.setTimeout(() => chatTextAreaElement?.focus(), 0);
window.setTimeout(() => chatInput?.focus(), 0);
const dropZone = document.querySelector('body');
const dropZone = document.querySelector('body');
...
@@ -663,6 +657,7 @@
...
@@ -663,6 +657,7 @@
<textarea
<textarea
id="chat-textarea"
id="chat-textarea"
bind:this={chatTextAreaElement}
class=" dark:bg-gray-900 dark:text-gray-100 outline-none w-full py-3 px-3 {fileUploadEnabled
class=" dark:bg-gray-900 dark:text-gray-100 outline-none w-full py-3 px-3 {fileUploadEnabled
? ''
? ''
: ' pl-4'} rounded-xl resize-none h-[48px]"
: ' pl-4'} rounded-xl resize-none h-[48px]"
...
...
src/lib/components/chat/Messages.svelte
View file @
92e21acb
...
@@ -223,33 +223,80 @@
...
@@ -223,33 +223,80 @@
}, 100);
}, 100);
};
};
// TODO: change delete behaviour
const messageDeleteHandler = async (messageId) => {
// const deleteMessageAndDescendants = async (messageId: string) => {
const messageToDelete = history.messages[messageId];
// if (history.messages[messageId]) {
const messageParentId = messageToDelete.parentId;
// history.messages[messageId].deleted = true;
const messageChildrenIds = messageToDelete.childrenIds ?? [];
const hasSibling = messageChildrenIds.some(
(childId) => history.messages[childId]?.childrenIds?.length > 0
);
messageChildrenIds.forEach((childId) => {
const child = history.messages[childId];
if (child && child.childrenIds) {
if (child.childrenIds.length === 0 && !hasSibling) {
// if last prompt/response pair
history.messages[messageParentId].childrenIds = [];
history.currentId = messageParentId;
} else {
child.childrenIds.forEach((grandChildId) => {
if (history.messages[grandChildId]) {
history.messages[grandChildId].parentId = messageParentId;
history.messages[messageParentId].childrenIds.push(grandChildId);
}
});
}
}
// remove response
history.messages[messageParentId].childrenIds = history.messages[
messageParentId
].childrenIds.filter((id) => id !== childId);
});
// remove prompt
history.messages[messageParentId].childrenIds = history.messages[
messageParentId
].childrenIds.filter((id) => id !== messageId);
await updateChatById(localStorage.token, chatId, {
messages: messages,
history: history
});
};
// for (const childId of history.messages[messageId].childrenIds) {
// const messageDeleteHandler = async (messageId) => {
// await deleteMessageAndDescendants(childId);
// const message = history.messages[messageId];
// const parentId = message.parentId;
// const childrenIds = message.childrenIds ?? [];
// const grandchildrenIds = [];
// // Iterate through childrenIds to find grandchildrenIds
// for (const childId of childrenIds) {
// const childMessage = history.messages[childId];
// const grandChildrenIds = childMessage.childrenIds ?? [];
// for (const grandchildId of grandchildrenIds) {
// const childMessage = history.messages[grandchildId];
// childMessage.parentId = parentId;
// }
// }
// grandchildrenIds.push(...grandChildrenIds);
// }
// }
// };
// const triggerDeleteMessageRecursive = async (messageId: string) => {
// history.messages[parentId].childrenIds.push(...grandchildrenIds);
// await deleteMessageAndDescendants(messageId);
// history.messages[parentId].childrenIds = history.messages[parentId].childrenIds.filter(
// await updateChatById(localStorage.token, chatId, { history });
// (id) => id !== messageId
// await chats.set(await getChatList(localStorage.token));
// );
// };
// // Select latest message
const messageDeleteHandler = async (messageId) => {
// let currentMessageId = grandchildrenIds.at(-1);
if (history.messages[messageId]) {
// if (currentMessageId) {
history.messages[messageId].deleted = true;
// let messageChildrenIds = history.messages[currentMessageId].childrenIds;
// while (messageChildrenIds.length !== 0) {
// currentMessageId = messageChildrenIds.at(-1);
// messageChildrenIds = history.messages[currentMessageId].childrenIds;
// }
// history.currentId = currentMessageId;
// }
for (const childId of history.messages[messageId].childrenIds) {
// await updateChatById(localStorage.token, chatId, { messages, history });
history.messages[childId].deleted = true;
// };
}
}
await updateChatById(localStorage.token, chatId, { history });
};
</script>
</script>
{#if messages.length == 0}
{#if messages.length == 0}
...
@@ -258,7 +305,6 @@
...
@@ -258,7 +305,6 @@
<div class=" pb-10">
<div class=" pb-10">
{#key chatId}
{#key chatId}
{#each messages as message, messageIdx}
{#each messages as message, messageIdx}
{#if !message.deleted}
<div class=" w-full">
<div class=" w-full">
<div
<div
class="flex flex-col justify-between px-5 mb-3 {$settings?.fullScreenMode ?? null
class="flex flex-col justify-between px-5 mb-3 {$settings?.fullScreenMode ?? null
...
@@ -308,7 +354,6 @@
...
@@ -308,7 +354,6 @@
{/if}
{/if}
</div>
</div>
</div>
</div>
{/if}
{/each}
{/each}
{#if bottomPadding}
{#if bottomPadding}
...
...
src/lib/components/chat/Messages/ResponseMessage.svelte
View file @
92e21acb
...
@@ -22,6 +22,7 @@
...
@@ -22,6 +22,7 @@
import CodeBlock from './CodeBlock.svelte';
import CodeBlock from './CodeBlock.svelte';
import Image from '$lib/components/common/Image.svelte';
import Image from '$lib/components/common/Image.svelte';
import { WEBUI_BASE_URL } from '$lib/constants';
import { WEBUI_BASE_URL } from '$lib/constants';
import Tooltip from '$lib/components/common/Tooltip.svelte';
export let modelfiles = [];
export let modelfiles = [];
export let message;
export let message;
...
@@ -40,7 +41,7 @@
...
@@ -40,7 +41,7 @@
let edit = false;
let edit = false;
let editedContent = '';
let editedContent = '';
let editTextAreaElement: HTMLTextAreaElement;
let tooltipInstance = null;
let tooltipInstance = null;
let sentencesAudio = {};
let sentencesAudio = {};
...
@@ -247,10 +248,9 @@
...
@@ -247,10 +248,9 @@
editedContent = message.content;
editedContent = message.content;
await tick();
await tick();
const editElement = document.getElementById(`message-edit-${message.id}`);
editElement.style.height = '';
edit
TextArea
Element.style.height = '';
editElement.style.height = `${editElement.scrollHeight}px`;
edit
TextArea
Element.style.height = `${edit
TextArea
Element.scrollHeight}px`;
};
};
const editMessageConfirmHandler = async () => {
const editMessageConfirmHandler = async () => {
...
@@ -341,9 +341,11 @@
...
@@ -341,9 +341,11 @@
<div class=" w-full">
<div class=" w-full">
<textarea
<textarea
id="message-edit-{message.id}"
id="message-edit-{message.id}"
bind:this={editTextAreaElement}
class=" bg-transparent outline-none w-full resize-none"
class=" bg-transparent outline-none w-full resize-none"
bind:value={editedContent}
bind:value={editedContent}
on:input={(e) => {
on:input={(e) => {
e.target.style.height = '';
e.target.style.height = `${e.target.scrollHeight}px`;
e.target.style.height = `${e.target.scrollHeight}px`;
}}
}}
/>
/>
...
@@ -462,6 +464,7 @@
...
@@ -462,6 +464,7 @@
</div>
</div>
{/if}
{/if}
<Tooltip content="Edit" placement="bottom">
<button
<button
class="{isLastMessage
class="{isLastMessage
? 'visible'
? 'visible'
...
@@ -485,7 +488,9 @@
...
@@ -485,7 +488,9 @@
/>
/>
</svg>
</svg>
</button>
</button>
</Tooltip>
<Tooltip content="Copy" placement="bottom">
<button
<button
class="{isLastMessage
class="{isLastMessage
? 'visible'
? 'visible'
...
@@ -509,7 +514,9 @@
...
@@ -509,7 +514,9 @@
/>
/>
</svg>
</svg>
</button>
</button>
</Tooltip>
<Tooltip content="Good Response" placement="bottom">
<button
<button
class="{isLastMessage
class="{isLastMessage
? 'visible'
? 'visible'
...
@@ -534,6 +541,9 @@
...
@@ -534,6 +541,9 @@
/></svg
/></svg
>
>
</button>
</button>
</Tooltip>
<Tooltip content="Bad Response" placement="bottom">
<button
<button
class="{isLastMessage
class="{isLastMessage
? 'visible'
? 'visible'
...
@@ -558,7 +568,9 @@
...
@@ -558,7 +568,9 @@
/></svg
/></svg
>
>
</button>
</button>
</Tooltip>
<Tooltip content="Read Aloud" placement="bottom">
<button
<button
id="speak-button-{message.id}"
id="speak-button-{message.id}"
class="{isLastMessage
class="{isLastMessage
...
@@ -598,7 +610,12 @@
...
@@ -598,7 +610,12 @@
cx="12"
cx="12"
cy="12"
cy="12"
r="3"
r="3"
/><circle class="spinner_S1WN spinner_JApP" cx="20" cy="12" r="3" /></svg
/><circle
class="spinner_S1WN spinner_JApP"
cx="20"
cy="12"
r="3"
/></svg
>
>
{:else if speaking}
{:else if speaking}
<svg
<svg
...
@@ -632,8 +649,10 @@
...
@@ -632,8 +649,10 @@
</svg>
</svg>
{/if}
{/if}
</button>
</button>
</Tooltip>
{#if $config.images}
{#if $config.images}
<Tooltip content="Generate Image" placement="bottom">
<button
<button
class="{isLastMessage
class="{isLastMessage
? 'visible'
? 'visible'
...
@@ -696,9 +715,11 @@
...
@@ -696,9 +715,11 @@
</svg>
</svg>
{/if}
{/if}
</button>
</button>
</Tooltip>
{/if}
{/if}
{#if message.info}
{#if message.info}
<Tooltip content="Generation Info" placement="bottom">
<button
<button
class=" {isLastMessage
class=" {isLastMessage
? 'visible'
? 'visible'
...
@@ -723,9 +744,11 @@
...
@@ -723,9 +744,11 @@
/>
/>
</svg>
</svg>
</button>
</button>
</Tooltip>
{/if}
{/if}
{#if isLastMessage}
{#if isLastMessage}
<Tooltip content="Continue Response" placement="bottom">
<button
<button
type="button"
type="button"
class="{isLastMessage
class="{isLastMessage
...
@@ -755,7 +778,9 @@
...
@@ -755,7 +778,9 @@
/>
/>
</svg>
</svg>
</button>
</button>
</Tooltip>
<Tooltip content="Regenerate" placement="bottom">
<button
<button
type="button"
type="button"
class="{isLastMessage
class="{isLastMessage
...
@@ -778,6 +803,7 @@
...
@@ -778,6 +803,7 @@
/>
/>
</svg>
</svg>
</button>
</button>
</Tooltip>
{/if}
{/if}
</div>
</div>
{/if}
{/if}
...
...
src/lib/components/chat/Messages/UserMessage.svelte
View file @
92e21acb
...
@@ -5,6 +5,7 @@
...
@@ -5,6 +5,7 @@
import Name from './Name.svelte';
import Name from './Name.svelte';
import ProfileImage from './ProfileImage.svelte';
import ProfileImage from './ProfileImage.svelte';
import { modelfiles, settings } from '$lib/stores';
import { modelfiles, settings } from '$lib/stores';
import Tooltip from '$lib/components/common/Tooltip.svelte';
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher();
...
@@ -20,18 +21,17 @@
...
@@ -20,18 +21,17 @@
let edit = false;
let edit = false;
let editedContent = '';
let editedContent = '';
let messageEditTextAreaElement: HTMLTextAreaElement;
const editMessageHandler = async () => {
const editMessageHandler = async () => {
edit = true;
edit = true;
editedContent = message.content;
editedContent = message.content;
await tick();
await tick();
const editElement = document.getElementById(`message-edit-${message.id}`);
edit
Element.style.height = '';
messageEditTextArea
Element.style.height = '';
edit
Element.style.height = `${
edit
Element.scrollHeight}px`;
messageEditTextArea
Element.style.height = `${
messageEditTextArea
Element.scrollHeight}px`;
edit
Element?.focus();
messageEditTextArea
Element?.focus();
};
};
const editMessageConfirmHandler = async () => {
const editMessageConfirmHandler = async () => {
...
@@ -165,9 +165,11 @@
...
@@ -165,9 +165,11 @@
<div class=" w-full">
<div class=" w-full">
<textarea
<textarea
id="message-edit-{message.id}"
id="message-edit-{message.id}"
bind:this={messageEditTextAreaElement}
class=" bg-transparent outline-none w-full resize-none"
class=" bg-transparent outline-none w-full resize-none"
bind:value={editedContent}
bind:value={editedContent}
on:input={(e) => {
on:input={(e) => {
e.target.style.height = '';
e.target.style.height = `${e.target.scrollHeight}px`;
e.target.style.height = `${e.target.scrollHeight}px`;
}}
}}
/>
/>
...
@@ -245,6 +247,7 @@
...
@@ -245,6 +247,7 @@
</div>
</div>
{/if}
{/if}
<Tooltip content="Edit" placement="bottom">
<button
<button
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition edit-user-message-button"
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition edit-user-message-button"
on:click={() => {
on:click={() => {
...
@@ -266,7 +269,9 @@
...
@@ -266,7 +269,9 @@
/>
/>
</svg>
</svg>
</button>
</button>
</Tooltip>
<Tooltip content="Copy" placement="bottom">
<button
<button
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
on:click={() => {
on:click={() => {
...
@@ -288,8 +293,10 @@
...
@@ -288,8 +293,10 @@
/>
/>
</svg>
</svg>
</button>
</button>
</Tooltip>
{#if !isFirstMessage}
{#if !isFirstMessage}
<Tooltip content="Delete" placement="bottom">
<button
<button
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
class="invisible group-hover:visible p-1 rounded dark:hover:text-white hover:text-black transition"
on:click={() => {
on:click={() => {
...
@@ -311,6 +318,7 @@
...
@@ -311,6 +318,7 @@
/>
/>
</svg>
</svg>
</button>
</button>
</Tooltip>
{/if}
{/if}
</div>
</div>
</div>
</div>
...
...
src/lib/components/chat/Settings/Account.svelte
View file @
92e21acb
...
@@ -15,6 +15,7 @@
...
@@ -15,6 +15,7 @@
let name = '';
let name = '';
let showJWTToken = false;
let showJWTToken = false;
let JWTTokenCopied = false;
let JWTTokenCopied = false;
let profileImageInputElement: HTMLInputElement;
const submitHandler = async () => {
const submitHandler = async () => {
const updatedUser = await updateUserProfile(localStorage.token, name, profileImageUrl).catch(
const updatedUser = await updateUserProfile(localStorage.token, name, profileImageUrl).catch(
...
@@ -40,11 +41,12 @@
...
@@ -40,11 +41,12 @@
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
<div class=" space-y-3 pr-1.5 overflow-y-scroll max-h-80">
<input
<input
id="profile-image-input"
id="profile-image-input"
bind:this={profileImageInputElement}
type="file"
type="file"
hidden
hidden
accept="image/*"
accept="image/*"
on:change={(e) => {
on:change={(e) => {
const files =
e?.target?
.files ?? [];
const files =
profileImageInputElement
.files ?? [];
let reader = new FileReader();
let reader = new FileReader();
reader.onload = (event) => {
reader.onload = (event) => {
let originalImageUrl = `${event.target.result}`;
let originalImageUrl = `${event.target.result}`;
...
@@ -86,7 +88,7 @@
...
@@ -86,7 +88,7 @@
// Display the compressed image
// Display the compressed image
profileImageUrl = compressedSrc;
profileImageUrl = compressedSrc;
e.targe
t.files = null;
profileImageInputElemen
t.files = null;
};
};
};
};
...
@@ -107,9 +109,7 @@
...
@@ -107,9 +109,7 @@
<button
<button
class="relative rounded-full dark:bg-gray-700"
class="relative rounded-full dark:bg-gray-700"
type="button"
type="button"
on:click={() => {
on:click={profileImageInputElement.click}
document.getElementById('profile-image-input')?.click();
}}
>
>
<img
<img
src={profileImageUrl !== '' ? profileImageUrl : '/user.png'}
src={profileImageUrl !== '' ? profileImageUrl : '/user.png'}
...
...
src/lib/components/chat/Settings/Chats.svelte
View file @
92e21acb
...
@@ -22,6 +22,7 @@
...
@@ -22,6 +22,7 @@
let saveChatHistory = true;
let saveChatHistory = true;
let importFiles;
let importFiles;
let showDeleteConfirm = false;
let showDeleteConfirm = false;
let chatImportInputElement: HTMLInputElement;
$: if (importFiles) {
$: if (importFiles) {
console.log(importFiles);
console.log(importFiles);
...
@@ -159,12 +160,17 @@
...
@@ -159,12 +160,17 @@
<hr class=" dark:border-gray-700" />
<hr class=" dark:border-gray-700" />
<div class="flex flex-col">
<div class="flex flex-col">
<input id="chat-import-input" bind:files={importFiles} type="file" accept=".json" hidden />
<input
id="chat-import-input"
bind:this={chatImportInputElement}
bind:files={importFiles}
type="file"
accept=".json"
hidden
/>
<button
<button
class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
class=" flex rounded-md py-2 px-3.5 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={() => {
on:click={chatImportInputElement.click}
document.getElementById('chat-import-input').click();
}}
>
>
<div class=" self-center mr-3">
<div class=" self-center mr-3">
<svg
<svg
...
...
src/lib/components/chat/Settings/Connections.svelte
View file @
92e21acb
...
@@ -3,14 +3,15 @@
...
@@ -3,14 +3,15 @@
import { createEventDispatcher, onMount } from 'svelte';
import { createEventDispatcher, onMount } from 'svelte';
const dispatch = createEventDispatcher();
const dispatch = createEventDispatcher();
import { getOllama
API
Url, getOllamaVersion, updateOllama
API
Url } from '$lib/apis/ollama';
import { getOllamaUrl
s
, getOllamaVersion, updateOllamaUrl
s
} from '$lib/apis/ollama';
import { getOpenAIKey, getOpenAIUrl, updateOpenAIKey, updateOpenAIUrl } from '$lib/apis/openai';
import { getOpenAIKey, getOpenAIUrl, updateOpenAIKey, updateOpenAIUrl } from '$lib/apis/openai';
import { toast } from 'svelte-sonner';
import { toast } from 'svelte-sonner';
export let getModels: Function;
export let getModels: Function;
// External
// External
let API_BASE_URL = '';
let OLLAMA_BASE_URL = '';
let OLLAMA_BASE_URLS = [''];
let OPENAI_API_KEY = '';
let OPENAI_API_KEY = '';
let OPENAI_API_BASE_URL = '';
let OPENAI_API_BASE_URL = '';
...
@@ -25,8 +26,8 @@
...
@@ -25,8 +26,8 @@
await models.set(await getModels());
await models.set(await getModels());
};
};
const updateOllama
API
UrlHandler = async () => {
const updateOllamaUrl
s
Handler = async () => {
API
_BASE_URL = await updateOllama
API
Url(localStorage.token,
API
_BASE_URL);
OLLAMA
_BASE_URL
S
= await updateOllamaUrl
s
(localStorage.token,
OLLAMA
_BASE_URL
S
);
const ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => {
const ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => {
toast.error(error);
toast.error(error);
...
@@ -41,7 +42,7 @@
...
@@ -41,7 +42,7 @@
onMount(async () => {
onMount(async () => {
if ($user.role === 'admin') {
if ($user.role === 'admin') {
API
_BASE_URL = await getOllama
API
Url(localStorage.token);
OLLAMA
_BASE_URL
S
= await getOllamaUrl
s
(localStorage.token);
OPENAI_API_BASE_URL = await getOpenAIUrl(localStorage.token);
OPENAI_API_BASE_URL = await getOpenAIUrl(localStorage.token);
OPENAI_API_KEY = await getOpenAIKey(localStorage.token);
OPENAI_API_KEY = await getOpenAIKey(localStorage.token);
}
}
...
@@ -53,11 +54,6 @@
...
@@ -53,11 +54,6 @@
on:submit|preventDefault={() => {
on:submit|preventDefault={() => {
updateOpenAIHandler();
updateOpenAIHandler();
dispatch('save');
dispatch('save');
// saveSettings({
// OPENAI_API_KEY: OPENAI_API_KEY !== '' ? OPENAI_API_KEY : undefined,
// OPENAI_API_BASE_URL: OPENAI_API_BASE_URL !== '' ? OPENAI_API_BASE_URL : undefined
// });
}}
}}
>
>
<div class=" pr-1.5 overflow-y-scroll max-h-[20.5rem] space-y-3">
<div class=" pr-1.5 overflow-y-scroll max-h-[20.5rem] space-y-3">
...
@@ -115,18 +111,64 @@
...
@@ -115,18 +111,64 @@
<div>
<div>
<div class=" mb-2.5 text-sm font-medium">Ollama Base URL</div>
<div class=" mb-2.5 text-sm font-medium">Ollama Base URL</div>
<div class="flex w-full">
<div class="flex w-full gap-1.5">
<div class="flex-1 mr-2">
<div class="flex-1 flex flex-col gap-2">
{#each OLLAMA_BASE_URLS as url, idx}
<div class="flex gap-1.5">
<input
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-8
0
0 outline-none"
class="w-full rounded
-lg
py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-8
5
0 outline-none"
placeholder="Enter URL (e.g. http://localhost:11434)"
placeholder="Enter URL (e.g. http://localhost:11434)"
bind:value={API_BASE_URL}
bind:value={url}
/>
<div class="self-center flex items-center">
{#if idx === 0}
<button
class="px-1"
on:click={() => {
OLLAMA_BASE_URLS = [...OLLAMA_BASE_URLS, ''];
}}
type="button"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path
d="M8.75 3.75a.75.75 0 0 0-1.5 0v3.5h-3.5a.75.75 0 0 0 0 1.5h3.5v3.5a.75.75 0 0 0 1.5 0v-3.5h3.5a.75.75 0 0 0 0-1.5h-3.5v-3.5Z"
/>
/>
</svg>
</button>
{:else}
<button
class="px-1"
on:click={() => {
OLLAMA_BASE_URLS = OLLAMA_BASE_URLS.filter((url, urlIdx) => idx !== urlIdx);
}}
type="button"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path d="M3.75 7.25a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5h-8.5Z" />
</svg>
</button>
{/if}
</div>
</div>
</div>
{/each}
</div>
<div class="">
<button
<button
class="p
x-3
bg-gray-200 hover:bg-gray-300 dark:bg-gray-
60
0 dark:hover:bg-gray-
7
00 rounded transition"
class="p
-2.5
bg-gray-200 hover:bg-gray-300 dark:bg-gray-
85
0 dark:hover:bg-gray-
8
00 rounded
-lg
transition"
on:click={() => {
on:click={() => {
updateOllama
API
UrlHandler();
updateOllamaUrl
s
Handler();
}}
}}
type="button"
type="button"
>
>
...
@@ -144,6 +186,7 @@
...
@@ -144,6 +186,7 @@
</svg>
</svg>
</button>
</button>
</div>
</div>
</div>
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
Trouble accessing Ollama?
Trouble accessing Ollama?
...
...
src/lib/components/chat/Settings/Models.svelte
View file @
92e21acb
...
@@ -2,7 +2,13 @@
...
@@ -2,7 +2,13 @@
import queue from 'async/queue';
import queue from 'async/queue';
import { toast } from 'svelte-sonner';
import { toast } from 'svelte-sonner';
import { createModel, deleteModel, getOllamaVersion, pullModel } from '$lib/apis/ollama';
import {
createModel,
deleteModel,
getOllamaUrls,
getOllamaVersion,
pullModel
} from '$lib/apis/ollama';
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
import { WEBUI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
import { WEBUI_NAME, models, user } from '$lib/stores';
import { WEBUI_NAME, models, user } from '$lib/stores';
import { splitStream } from '$lib/utils';
import { splitStream } from '$lib/utils';
...
@@ -13,7 +19,7 @@
...
@@ -13,7 +19,7 @@
let showLiteLLM = false;
let showLiteLLM = false;
let showLiteLLMParams = false;
let showLiteLLMParams = false;
let modelUploadInputElement: HTMLInputElement;
let liteLLMModelInfo = [];
let liteLLMModelInfo = [];
let liteLLMModel = '';
let liteLLMModel = '';
...
@@ -27,6 +33,9 @@
...
@@ -27,6 +33,9 @@
$: liteLLMModelName = liteLLMModel;
$: liteLLMModelName = liteLLMModel;
// Models
// Models
let OLLAMA_URLS = [];
let selectedOllamaUrlIdx: string | null = null;
let showExperimentalOllama = false;
let showExperimentalOllama = false;
let ollamaVersion = '';
let ollamaVersion = '';
const MAX_PARALLEL_DOWNLOADS = 3;
const MAX_PARALLEL_DOWNLOADS = 3;
...
@@ -45,7 +54,7 @@
...
@@ -45,7 +54,7 @@
let modelUploadMode = 'file';
let modelUploadMode = 'file';
let modelInputFile = '';
let modelInputFile = '';
let modelFileUrl = '';
let modelFileUrl = '';
let modelFileContent = `TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASS
S
ISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASS
S
ISTANT:"`;
let modelFileContent = `TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSISTANT:"`;
let modelFileDigest = '';
let modelFileDigest = '';
let uploadProgress = null;
let uploadProgress = null;
...
@@ -236,9 +245,11 @@
...
@@ -236,9 +245,11 @@
};
};
const deleteModelHandler = async () => {
const deleteModelHandler = async () => {
const res = await deleteModel(localStorage.token, deleteModelTag).catch((error) => {
const res = await deleteModel(localStorage.token, deleteModelTag, selectedOllamaUrlIdx).catch(
(error) => {
toast.error(error);
toast.error(error);
});
}
);
if (res) {
if (res) {
toast.success(`Deleted ${deleteModelTag}`);
toast.success(`Deleted ${deleteModelTag}`);
...
@@ -249,10 +260,12 @@
...
@@ -249,10 +260,12 @@
};
};
const pullModelHandlerProcessor = async (opts: { modelName: string; callback: Function }) => {
const pullModelHandlerProcessor = async (opts: { modelName: string; callback: Function }) => {
const res = await pullModel(localStorage.token, opts.modelName).catch((error) => {
const res = await pullModel(localStorage.token, opts.modelName, selectedOllamaUrlIdx).catch(
(error) => {
opts.callback({ success: false, error, modelName: opts.modelName });
opts.callback({ success: false, error, modelName: opts.modelName });
return null;
return null;
});
}
);
if (res) {
if (res) {
const reader = res.body
const reader = res.body
...
@@ -358,6 +371,15 @@
...
@@ -358,6 +371,15 @@
};
};
onMount(async () => {
onMount(async () => {
OLLAMA_URLS = await getOllamaUrls(localStorage.token).catch((error) => {
toast.error(error);
return [];
});
if (OLLAMA_URLS.length > 1) {
selectedOllamaUrlIdx = 0;
}
ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false);
ollamaVersion = await getOllamaVersion(localStorage.token).catch((error) => false);
liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token);
liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token);
});
});
...
@@ -367,20 +389,35 @@
...
@@ -367,20 +389,35 @@
<div class=" space-y-3 pr-1.5 overflow-y-scroll h-[23rem]">
<div class=" space-y-3 pr-1.5 overflow-y-scroll h-[23rem]">
{#if ollamaVersion}
{#if ollamaVersion}
<div class="space-y-2 pr-1.5">
<div class="space-y-2 pr-1.5">
<div>
<div class="text-sm font-medium">Manage Ollama Models</div>
<div class=" mb-2 text-sm font-medium">Manage Ollama Models</div>
{#if OLLAMA_URLS.length > 1}
<div class="flex-1 pb-1">
<select
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={selectedOllamaUrlIdx}
placeholder="Select an Ollama instance"
>
{#each OLLAMA_URLS as url, idx}
<option value={idx} class="bg-gray-100 dark:bg-gray-700">{url}</option>
{/each}
</select>
</div>
{/if}
<div class="space-y-2">
<div>
<div class=" mb-2 text-sm font-medium">Pull a model from Ollama.com</div>
<div class=" mb-2 text-sm font-medium">Pull a model from Ollama.com</div>
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1 mr-2">
<div class="flex-1 mr-2">
<input
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded
-lg
py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter model tag (e.g. mistral:7b)"
placeholder="Enter model tag (e.g. mistral:7b)"
bind:value={modelTag}
bind:value={modelTag}
/>
/>
</div>
</div>
<button
<button
class="px-
3
bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded transition"
class="px-
2.5
bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded
-lg
transition"
on:click={() => {
on:click={() => {
pullModelHandler();
pullModelHandler();
}}
}}
...
@@ -463,14 +500,14 @@
...
@@ -463,14 +500,14 @@
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1 mr-2">
<div class="flex-1 mr-2">
<select
<select
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded
-lg
py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={deleteModelTag}
bind:value={deleteModelTag}
placeholder="Select a model"
placeholder="Select a model"
>
>
{#if !deleteModelTag}
{#if !deleteModelTag}
<option value="" disabled selected>Select a model</option>
<option value="" disabled selected>Select a model</option>
{/if}
{/if}
{#each $models.filter((m) => m.size != null) as model}
{#each $models.filter((m) => m.size != null
&& (selectedOllamaUrlIdx === null ? true : (m?.urls ?? []).includes(selectedOllamaUrlIdx))
) as model}
<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
<option value={model.name} class="bg-gray-100 dark:bg-gray-700"
>{model.name + ' (' + (model.size / 1024 ** 3).toFixed(1) + ' GB)'}</option
>{model.name + ' (' + (model.size / 1024 ** 3).toFixed(1) + ' GB)'}</option
>
>
...
@@ -478,7 +515,7 @@
...
@@ -478,7 +515,7 @@
</select>
</select>
</div>
</div>
<button
<button
class="px-
3
bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded transition"
class="px-
2.5
bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded
-lg
transition"
on:click={() => {
on:click={() => {
deleteModelHandler();
deleteModelHandler();
}}
}}
...
@@ -499,7 +536,7 @@
...
@@ -499,7 +536,7 @@
</div>
</div>
</div>
</div>
<div>
<div
class="pt-1"
>
<div class="flex justify-between items-center text-xs">
<div class="flex justify-between items-center text-xs">
<div class=" text-sm font-medium">Experimental</div>
<div class=" text-sm font-medium">Experimental</div>
<button
<button
...
@@ -507,7 +544,7 @@
...
@@ -507,7 +544,7 @@
type="button"
type="button"
on:click={() => {
on:click={() => {
showExperimentalOllama = !showExperimentalOllama;
showExperimentalOllama = !showExperimentalOllama;
}}>{showExperimentalOllama ? '
Show
' : '
Hide
'}</button
}}>{showExperimentalOllama ? '
Hide
' : '
Show
'}</button
>
>
</div>
</div>
</div>
</div>
...
@@ -546,6 +583,7 @@
...
@@ -546,6 +583,7 @@
<div class="flex-1 {modelInputFile && modelInputFile.length > 0 ? 'mr-2' : ''}">
<div class="flex-1 {modelInputFile && modelInputFile.length > 0 ? 'mr-2' : ''}">
<input
<input
id="model-upload-input"
id="model-upload-input"
bind:this={modelUploadInputElement}
type="file"
type="file"
bind:files={modelInputFile}
bind:files={modelInputFile}
on:change={() => {
on:change={() => {
...
@@ -558,10 +596,8 @@
...
@@ -558,10 +596,8 @@
<button
<button
type="button"
type="button"
class="w-full rounded text-left py-2 px-4 dark:text-gray-300 dark:bg-gray-850"
class="w-full rounded-lg text-left py-2 px-4 dark:text-gray-300 dark:bg-gray-850"
on:click={() => {
on:click={modelUploadInputElement.click}
document.getElementById('model-upload-input').click();
}}
>
>
{#if modelInputFile && modelInputFile.length > 0}
{#if modelInputFile && modelInputFile.length > 0}
{modelInputFile[0].name}
{modelInputFile[0].name}
...
@@ -573,7 +609,7 @@
...
@@ -573,7 +609,7 @@
{:else}
{:else}
<div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}">
<div class="flex-1 {modelFileUrl !== '' ? 'mr-2' : ''}">
<input
<input
class="w-full rounded text-left py-2 px-4 dark:text-gray-300 dark:bg-gray-850 outline-none {modelFileUrl !==
class="w-full rounded
-lg
text-left py-2 px-4 dark:text-gray-300 dark:bg-gray-850 outline-none {modelFileUrl !==
''
''
? 'mr-2'
? 'mr-2'
: ''}"
: ''}"
...
@@ -677,6 +713,7 @@
...
@@ -677,6 +713,7 @@
</form>
</form>
{/if}
{/if}
</div>
</div>
</div>
<hr class=" dark:border-gray-700 my-2" />
<hr class=" dark:border-gray-700 my-2" />
{/if}
{/if}
...
@@ -693,7 +730,7 @@
...
@@ -693,7 +730,7 @@
type="button"
type="button"
on:click={() => {
on:click={() => {
showLiteLLMParams = !showLiteLLMParams;
showLiteLLMParams = !showLiteLLMParams;
}}>{showLiteLLMParams ? '
Advanced' : 'Default
'}</button
}}>{showLiteLLMParams ? '
Hide Additional Params' : 'Show Additional Params
'}</button
>
>
</div>
</div>
</div>
</div>
...
@@ -702,7 +739,7 @@
...
@@ -702,7 +739,7 @@
<div class="flex w-full mb-1.5">
<div class="flex w-full mb-1.5">
<div class="flex-1 mr-2">
<div class="flex-1 mr-2">
<input
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded
-lg
py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter LiteLLM Model (litellm_params.model)"
placeholder="Enter LiteLLM Model (litellm_params.model)"
bind:value={liteLLMModel}
bind:value={liteLLMModel}
autocomplete="off"
autocomplete="off"
...
@@ -710,7 +747,7 @@
...
@@ -710,7 +747,7 @@
</div>
</div>
<button
<button
class="px-
3
bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded transition"
class="px-
2.5
bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded
-lg
transition"
on:click={() => {
on:click={() => {
addLiteLLMModelHandler();
addLiteLLMModelHandler();
}}
}}
...
@@ -734,7 +771,7 @@
...
@@ -734,7 +771,7 @@
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1">
<div class="flex-1">
<input
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded
-lg
py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter Model Name (model_name)"
placeholder="Enter Model Name (model_name)"
bind:value={liteLLMModelName}
bind:value={liteLLMModelName}
autocomplete="off"
autocomplete="off"
...
@@ -748,7 +785,7 @@
...
@@ -748,7 +785,7 @@
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1">
<div class="flex-1">
<input
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded
-lg
py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter LiteLLM API Base URL (litellm_params.api_base)"
placeholder="Enter LiteLLM API Base URL (litellm_params.api_base)"
bind:value={liteLLMAPIBase}
bind:value={liteLLMAPIBase}
autocomplete="off"
autocomplete="off"
...
@@ -762,7 +799,7 @@
...
@@ -762,7 +799,7 @@
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1">
<div class="flex-1">
<input
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded
-lg
py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter LiteLLM API Key (litellm_params.api_key)"
placeholder="Enter LiteLLM API Key (litellm_params.api_key)"
bind:value={liteLLMAPIKey}
bind:value={liteLLMAPIKey}
autocomplete="off"
autocomplete="off"
...
@@ -776,7 +813,7 @@
...
@@ -776,7 +813,7 @@
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1">
<div class="flex-1">
<input
<input
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded
-lg
py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter LiteLLM API RPM (litellm_params.rpm)"
placeholder="Enter LiteLLM API RPM (litellm_params.rpm)"
bind:value={liteLLMRPM}
bind:value={liteLLMRPM}
autocomplete="off"
autocomplete="off"
...
@@ -803,7 +840,7 @@
...
@@ -803,7 +840,7 @@
<div class="flex w-full">
<div class="flex w-full">
<div class="flex-1 mr-2">
<div class="flex-1 mr-2">
<select
<select
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
class="w-full rounded
-lg
py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={deleteLiteLLMModelId}
bind:value={deleteLiteLLMModelId}
placeholder="Select a model"
placeholder="Select a model"
>
>
...
@@ -818,7 +855,7 @@
...
@@ -818,7 +855,7 @@
</select>
</select>
</div>
</div>
<button
<button
class="px-
3
bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded transition"
class="px-
2.5
bg-gray-100 hover:bg-gray-200 text-gray-800 dark:bg-gray-850 dark:hover:bg-gray-800 dark:text-gray-100 rounded
-lg
transition"
on:click={() => {
on:click={() => {
deleteLiteLLMModelHandler();
deleteLiteLLMModelHandler();
}}
}}
...
...
src/lib/components/common/Tooltip.svelte
View file @
92e21acb
...
@@ -29,6 +29,6 @@
...
@@ -29,6 +29,6 @@
});
});
</script>
</script>
<div bind:this={tooltipElement}>
<div bind:this={tooltipElement}
aria-label={content}
>
<slot />
<slot />
</div>
</div>
src/lib/components/documents/AddDocModal.svelte
View file @
92e21acb
...
@@ -15,7 +15,7 @@
...
@@ -15,7 +15,7 @@
export let show = false;
export let show = false;
export let selectedDoc;
export let selectedDoc;
let uploadDocInputElement: HTMLInputElement;
let inputFiles;
let inputFiles;
let tags = [];
let tags = [];
...
@@ -69,7 +69,7 @@
...
@@ -69,7 +69,7 @@
}
}
inputFiles = null;
inputFiles = null;
document.getElementById('upload-doc-input')
.value = '';
uploadDocInputElement
.value = '';
} else {
} else {
toast.error(`File not found.`);
toast.error(`File not found.`);
}
}
...
@@ -126,14 +126,19 @@
...
@@ -126,14 +126,19 @@
}}
}}
>
>
<div class="mb-3 w-full">
<div class="mb-3 w-full">
<input id="upload-doc-input" hidden bind:files={inputFiles} type="file" multiple />
<input
id="upload-doc-input"
bind:this={uploadDocInputElement}
hidden
bind:files={inputFiles}
type="file"
multiple
/>
<button
<button
class="w-full text-sm font-medium py-3 bg-gray-850 hover:bg-gray-800 text-center rounded-xl"
class="w-full text-sm font-medium py-3 bg-gray-850 hover:bg-gray-800 text-center rounded-xl"
type="button"
type="button"
on:click={() => {
on:click={uploadDocInputElement.click}
document.getElementById('upload-doc-input')?.click();
}}
>
>
{#if inputFiles}
{#if inputFiles}
{inputFiles.length > 0 ? `${inputFiles.length}` : ''} document(s) selected.
{inputFiles.length > 0 ? `${inputFiles.length}` : ''} document(s) selected.
...
...
src/lib/components/playground/ChatCompletion.svelte
View file @
92e21acb
...
@@ -2,12 +2,11 @@
...
@@ -2,12 +2,11 @@
import { onMount } from 'svelte';
import { onMount } from 'svelte';
export let messages = [];
export let messages = [];
let textAreaElement: HTMLTextAreaElement;
onMount(() => {
onMount(() => {
messages.forEach((message, idx) => {
messages.forEach((message, idx) => {
let textareaElement = document.getElementById(`${message.role}-${idx}-textarea`);
textAreaElement.style.height = '';
textareaElement.style.height = '';
textAreaElement.style.height = textAreaElement.scrollHeight + 'px';
textareaElement.style.height = textareaElement.scrollHeight + 'px';
});
});
});
});
</script>
</script>
...
@@ -27,16 +26,17 @@
...
@@ -27,16 +26,17 @@
<div class="flex-1">
<div class="flex-1">
<textarea
<textarea
id="{message.role}-{idx}-textarea"
id="{message.role}-{idx}-textarea"
bind:this={textAreaElement}
class="w-full bg-transparent outline-none rounded-lg p-2 text-sm resize-none overflow-hidden"
class="w-full bg-transparent outline-none rounded-lg p-2 text-sm resize-none overflow-hidden"
placeholder="Enter {message.role === 'user' ? 'a user' : 'an assistant'} message here"
placeholder="Enter {message.role === 'user' ? 'a user' : 'an assistant'} message here"
rows="1"
rows="1"
on:input={(e) => {
on:input={(e) => {
e.targe
t.style.height = '';
textAreaElemen
t.style.height = '';
e.targe
t.style.height =
e.targe
t.scrollHeight + 'px';
textAreaElemen
t.style.height =
textAreaElemen
t.scrollHeight + 'px';
}}
}}
on:focus={(e) => {
on:focus={(e) => {
e.targe
t.style.height = '';
textAreaElemen
t.style.height = '';
e.targe
t.style.height =
e.targe
t.scrollHeight + 'px';
textAreaElemen
t.style.height =
textAreaElemen
t.scrollHeight + 'px';
// e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
// e.target.style.height = Math.min(e.target.scrollHeight, 200) + 'px';
}}
}}
...
...
src/lib/constants.ts
View file @
92e21acb
...
@@ -7,7 +7,7 @@ export const WEBUI_BASE_URL = dev ? `http://${location.hostname}:8080` : ``;
...
@@ -7,7 +7,7 @@ export const WEBUI_BASE_URL = dev ? `http://${location.hostname}:8080` : ``;
export
const
WEBUI_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/api/v1`
;
export
const
WEBUI_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/api/v1`
;
export
const
LITELLM_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/litellm/api`
;
export
const
LITELLM_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/litellm/api`
;
export
const
OLLAMA_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/ollama
/api
`
;
export
const
OLLAMA_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/ollama`
;
export
const
OPENAI_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/openai/api`
;
export
const
OPENAI_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/openai/api`
;
export
const
AUDIO_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/audio/api/v1`
;
export
const
AUDIO_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/audio/api/v1`
;
export
const
IMAGES_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/images/api/v1`
;
export
const
IMAGES_API_BASE_URL
=
`
${
WEBUI_BASE_URL
}
/images/api/v1`
;
...
@@ -90,8 +90,3 @@ export const SUPPORTED_FILE_EXTENSIONS = [
...
@@ -90,8 +90,3 @@ export const SUPPORTED_FILE_EXTENSIONS = [
// This feature, akin to $env/static/private, exclusively incorporates environment variables
// This feature, akin to $env/static/private, exclusively incorporates environment variables
// that are prefixed with config.kit.env.publicPrefix (usually set to PUBLIC_).
// that are prefixed with config.kit.env.publicPrefix (usually set to PUBLIC_).
// Consequently, these variables can be securely exposed to client-side code.
// Consequently, these variables can be securely exposed to client-side code.
// Example of the .env configuration:
// OLLAMA_API_BASE_URL="http://localhost:11434/api"
// # Public
// PUBLIC_API_BASE_URL=$OLLAMA_API_BASE_URL
src/routes/(app)/+layout.svelte
View file @
92e21acb
...
@@ -34,10 +34,11 @@
...
@@ -34,10 +34,11 @@
import Sidebar from '$lib/components/layout/Sidebar.svelte';
import Sidebar from '$lib/components/layout/Sidebar.svelte';
import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte';
import ShortcutsModal from '$lib/components/chat/ShortcutsModal.svelte';
import ChangelogModal from '$lib/components/ChangelogModal.svelte';
import ChangelogModal from '$lib/components/ChangelogModal.svelte';
import Tooltip from '$lib/components/common/Tooltip.svelte';
let ollamaVersion = '';
let ollamaVersion = '';
let loaded = false;
let loaded = false;
let showShortcutsButtonElement: HTMLButtonElement;
let DB = null;
let DB = null;
let localDBChats = [];
let localDBChats = [];
...
@@ -184,7 +185,7 @@
...
@@ -184,7 +185,7 @@
if (isCtrlPressed && event.key === '/') {
if (isCtrlPressed && event.key === '/') {
event.preventDefault();
event.preventDefault();
console.log('showShortcuts');
console.log('showShortcuts');
document.getElementById('
show
-s
hortcuts
-b
utton
')?
.click();
show
S
hortcuts
B
utton
Element
.click();
}
}
});
});
...
@@ -201,8 +202,10 @@
...
@@ -201,8 +202,10 @@
{#if loaded}
{#if loaded}
<div class=" hidden lg:flex fixed bottom-0 right-0 px-3 py-3 z-10">
<div class=" hidden lg:flex fixed bottom-0 right-0 px-3 py-3 z-10">
<Tooltip content="help" placement="left">
<button
<button
id="show-shortcuts-button"
id="show-shortcuts-button"
bind:this={showShortcutsButtonElement}
class="text-gray-600 dark:text-gray-300 bg-gray-300/20 w-6 h-6 flex items-center justify-center text-xs rounded-full"
class="text-gray-600 dark:text-gray-300 bg-gray-300/20 w-6 h-6 flex items-center justify-center text-xs rounded-full"
on:click={() => {
on:click={() => {
showShortcuts = !showShortcuts;
showShortcuts = !showShortcuts;
...
@@ -210,6 +213,7 @@
...
@@ -210,6 +213,7 @@
>
>
?
?
</button>
</button>
</Tooltip>
</div>
</div>
<ShortcutsModal bind:show={showShortcuts} />
<ShortcutsModal bind:show={showShortcuts} />
...
...
src/routes/(app)/+page.svelte
View file @
92e21acb
...
@@ -42,7 +42,7 @@
...
@@ -42,7 +42,7 @@
let
stopResponseFlag
=
false
;
let
stopResponseFlag
=
false
;
let
autoScroll
=
true
;
let
autoScroll
=
true
;
let
processing
=
''
;
let
processing
=
''
;
let
messagesContainerElement
:
HTMLDivElement
;
let
currentRequestId
=
null
;
let
currentRequestId
=
null
;
let
selectedModels
=
[
''
];
let
selectedModels
=
[
''
];
...
@@ -140,8 +140,7 @@
...
@@ -140,8 +140,7 @@
};
};
const
scrollToBottom
=
()
=>
{
const
scrollToBottom
=
()
=>
{
const
element
=
document
.
getElementById
(
'messages-container'
);
messagesContainerElement
.
scrollTop
=
messagesContainerElement
.
scrollHeight
;
element
.
scrollTop
=
element
.
scrollHeight
;
};
};
//////////////////////////
//////////////////////////
...
@@ -340,7 +339,7 @@
...
@@ -340,7 +339,7 @@
content
:
$
settings
.
system
content
:
$
settings
.
system
}
}
:
undefined
,
:
undefined
,
...
messages
.
filter
((
message
)
=>
!message.deleted)
...
messages
]
]
.
filter
((
message
)
=>
message
)
.
filter
((
message
)
=>
message
)
.
map
((
message
,
idx
,
arr
)
=>
({
.
map
((
message
,
idx
,
arr
)
=>
({
...
@@ -548,7 +547,7 @@
...
@@ -548,7 +547,7 @@
content
:
$
settings
.
system
content
:
$
settings
.
system
}
}
:
undefined
,
:
undefined
,
...
messages
.
filter
((
message
)
=>
!message.deleted)
...
messages
]
]
.
filter
((
message
)
=>
message
)
.
filter
((
message
)
=>
message
)
.
map
((
message
,
idx
,
arr
)
=>
({
.
map
((
message
,
idx
,
arr
)
=>
({
...
@@ -821,8 +820,11 @@
...
@@ -821,8 +820,11 @@
<
div
<
div
class
=
" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
class
=
" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
id
=
"messages-container"
id
=
"messages-container"
bind
:
this
={
messagesContainerElement
}
on
:
scroll
={(
e
)
=>
{
on
:
scroll
={(
e
)
=>
{
autoScroll
=
e
.
target
.
scrollHeight
-
e
.
target
.
scrollTop
<=
e
.
target
.
clientHeight
+
50
;
autoScroll
=
messagesContainerElement
.
scrollHeight
-
messagesContainerElement
.
scrollTop
<=
messagesContainerElement
.
clientHeight
+
50
;
}}
}}
>
>
<
div
<
div
...
@@ -830,10 +832,7 @@
...
@@ -830,10 +832,7 @@
? 'max-w-full'
? 'max-w-full'
: 'max-w-2xl md:px-0'} mx-auto w-full px-4"
: 'max-w-2xl md:px-0'} mx-auto w-full px-4"
>
>
<
ModelSelector
<
ModelSelector
bind
:
selectedModels
/>
bind
:
selectedModels
disabled
={
messages
.
length
>
0
&&
!selectedModels.includes('')}
/>
</
div
>
</
div
>
<
div
class
=
" h-full w-full flex flex-col py-8"
>
<
div
class
=
" h-full w-full flex flex-col py-8"
>
...
...
src/routes/(app)/c/[id]/+page.svelte
View file @
92e21acb
...
@@ -45,7 +45,7 @@
...
@@ -45,7 +45,7 @@
let
stopResponseFlag
=
false
;
let
stopResponseFlag
=
false
;
let
autoScroll
=
true
;
let
autoScroll
=
true
;
let
processing
=
''
;
let
processing
=
''
;
let
messagesContainerElement
:
HTMLDivElement
;
let
currentRequestId
=
null
;
let
currentRequestId
=
null
;
//
let
chatId
=
$
page
.
params
.
id
;
//
let
chatId
=
$
page
.
params
.
id
;
...
@@ -160,8 +160,7 @@
...
@@ -160,8 +160,7 @@
};
};
const
scrollToBottom
=
()
=>
{
const
scrollToBottom
=
()
=>
{
const
element
=
document
.
getElementById
(
'messages-container'
);
messagesContainerElement
.
scrollTop
=
messagesContainerElement
.
scrollHeight
;
element
.
scrollTop
=
element
.
scrollHeight
;
};
};
//////////////////////////
//////////////////////////
...
@@ -353,7 +352,7 @@
...
@@ -353,7 +352,7 @@
content
:
$
settings
.
system
content
:
$
settings
.
system
}
}
:
undefined
,
:
undefined
,
...
messages
.
filter
((
message
)
=>
!message.deleted)
...
messages
]
]
.
filter
((
message
)
=>
message
)
.
filter
((
message
)
=>
message
)
.
map
((
message
,
idx
,
arr
)
=>
({
.
map
((
message
,
idx
,
arr
)
=>
({
...
@@ -561,7 +560,7 @@
...
@@ -561,7 +560,7 @@
content
:
$
settings
.
system
content
:
$
settings
.
system
}
}
:
undefined
,
:
undefined
,
...
messages
.
filter
((
message
)
=>
!message.deleted)
...
messages
]
]
.
filter
((
message
)
=>
message
)
.
filter
((
message
)
=>
message
)
.
map
((
message
,
idx
,
arr
)
=>
({
.
map
((
message
,
idx
,
arr
)
=>
({
...
@@ -852,8 +851,11 @@
...
@@ -852,8 +851,11 @@
<
div
<
div
class
=
" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
class
=
" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
id
=
"messages-container"
id
=
"messages-container"
bind
:
this
={
messagesContainerElement
}
on
:
scroll
={(
e
)
=>
{
on
:
scroll
={(
e
)
=>
{
autoScroll
=
e
.
target
.
scrollHeight
-
e
.
target
.
scrollTop
<=
e
.
target
.
clientHeight
+
50
;
autoScroll
=
messagesContainerElement
.
scrollHeight
-
messagesContainerElement
.
scrollTop
<=
messagesContainerElement
.
clientHeight
+
50
;
}}
}}
>
>
<
div
<
div
...
@@ -861,10 +863,7 @@
...
@@ -861,10 +863,7 @@
? 'max-w-full'
? 'max-w-full'
: 'max-w-2xl md:px-0'} mx-auto w-full px-4"
: 'max-w-2xl md:px-0'} mx-auto w-full px-4"
>
>
<
ModelSelector
<
ModelSelector
bind
:
selectedModels
/>
bind
:
selectedModels
disabled
={
messages
.
length
>
0
&&
!selectedModels.includes('')}
/>
</
div
>
</
div
>
<
div
class
=
" h-full w-full flex flex-col py-8"
>
<
div
class
=
" h-full w-full flex flex-col py-8"
>
...
...
src/routes/(app)/documents/+page.svelte
View file @
92e21acb
...
@@ -21,7 +21,7 @@
...
@@ -21,7 +21,7 @@
let inputFiles = '';
let inputFiles = '';
let query = '';
let query = '';
let documentsImportInputElement: HTMLInputElement;
let tags = [];
let tags = [];
let showSettingsModal = false;
let showSettingsModal = false;
...
@@ -524,6 +524,7 @@
...
@@ -524,6 +524,7 @@
<div class="flex space-x-2">
<div class="flex space-x-2">
<input
<input
id="documents-import-input"
id="documents-import-input"
bind:this={documentsImportInputElement}
bind:files={importFiles}
bind:files={importFiles}
type="file"
type="file"
accept=".json"
accept=".json"
...
@@ -558,9 +559,7 @@
...
@@ -558,9 +559,7 @@
<button
<button
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={async () => {
on:click={documentsImportInputElement.click}
document.getElementById('documents-import-input')?.click();
}}
>
>
<div class=" self-center mr-2 font-medium">Import Documents Mapping</div>
<div class=" self-center mr-2 font-medium">Import Documents Mapping</div>
...
...
src/routes/(app)/modelfiles/+page.svelte
View file @
92e21acb
...
@@ -16,11 +16,14 @@
...
@@ -16,11 +16,14 @@
let localModelfiles = [];
let localModelfiles = [];
let importFiles;
let importFiles;
let modelfilesImportInputElement: HTMLInputElement;
const deleteModelHandler = async (tagName) => {
const deleteModelHandler = async (tagName) => {
let success = null;
let success = null;
success = await deleteModel(localStorage.token, tagName);
success = await deleteModel(localStorage.token, tagName).catch((err) => {
toast.error(err);
return null;
});
if (success) {
if (success) {
toast.success(`Deleted ${tagName}`);
toast.success(`Deleted ${tagName}`);
...
@@ -235,6 +238,7 @@
...
@@ -235,6 +238,7 @@
<div class="flex space-x-1">
<div class="flex space-x-1">
<input
<input
id="modelfiles-import-input"
id="modelfiles-import-input"
bind:this={modelfilesImportInputElement}
bind:files={importFiles}
bind:files={importFiles}
type="file"
type="file"
accept=".json"
accept=".json"
...
@@ -262,9 +266,7 @@
...
@@ -262,9 +266,7 @@
<button
<button
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={async () => {
on:click={modelfilesImportInputElement.click}
document.getElementById('modelfiles-import-input')?.click();
}}
>
>
<div class=" self-center mr-2 font-medium">Import Modelfiles</div>
<div class=" self-center mr-2 font-medium">Import Modelfiles</div>
...
...
src/routes/(app)/playground/+page.svelte
View file @
92e21acb
<script>
<script
lang="ts"
>
import { goto } from '$app/navigation';
import { goto } from '$app/navigation';
import { onMount, tick } from 'svelte';
import { onMount, tick } from 'svelte';
...
@@ -21,15 +21,17 @@
...
@@ -21,15 +21,17 @@
let mode = 'chat';
let mode = 'chat';
let loaded = false;
let loaded = false;
let text = '';
let text = '';
let selectedModelId = '';
let selectedModelId = '';
let loading = false;
let loading = false;
let currentRequestId;
let currentRequestId
= null
;
let stopResponseFlag = false;
let stopResponseFlag = false;
let messagesContainerElement: HTMLDivElement;
let textCompletionAreaElement: HTMLTextAreaElement;
let system = '';
let system = '';
let messages = [
let messages = [
{
{
...
@@ -39,13 +41,7 @@
...
@@ -39,13 +41,7 @@
];
];
const scrollToBottom = () => {
const scrollToBottom = () => {
let element;
const element = mode === 'chat' ? messagesContainerElement : textCompletionAreaElement;
if (mode === 'chat') {
element = document.getElementById('messages-container');
} else {
element = document.getElementById('text-completion-textarea');
}
if (element) {
if (element) {
element.scrollTop = element?.scrollHeight;
element.scrollTop = element?.scrollHeight;
...
@@ -96,6 +92,10 @@
...
@@ -96,6 +92,10 @@
while (true) {
while (true) {
const { value, done } = await reader.read();
const { value, done } = await reader.read();
if (done || stopResponseFlag) {
if (done || stopResponseFlag) {
if (stopResponseFlag) {
await cancelChatCompletion(localStorage.token, currentRequestId);
}
currentRequestId = null;
currentRequestId = null;
break;
break;
}
}
...
@@ -112,10 +112,14 @@
...
@@ -112,10 +112,14 @@
let data = JSON.parse(line.replace(/^data: /, ''));
let data = JSON.parse(line.replace(/^data: /, ''));
console.log(data);
console.log(data);
if ('request_id' in data) {
currentRequestId = data.request_id;
} else {
text += data.choices[0].delta.content ?? '';
text += data.choices[0].delta.content ?? '';
}
}
}
}
}
}
}
} catch (error) {
} catch (error) {
console.log(error);
console.log(error);
}
}
...
@@ -150,16 +154,6 @@
...
@@ -150,16 +154,6 @@
: `${OLLAMA_API_BASE_URL}/v1`
: `${OLLAMA_API_BASE_URL}/v1`
);
);
// const [res, controller] = await generateChatCompletion(localStorage.token, {
// model: selectedModelId,
// messages: [
// {
// role: 'assistant',
// content: text
// }
// ]
// });
let responseMessage;
let responseMessage;
if (messages.at(-1)?.role === 'assistant') {
if (messages.at(-1)?.role === 'assistant') {
responseMessage = messages.at(-1);
responseMessage = messages.at(-1);
...
@@ -184,6 +178,11 @@
...
@@ -184,6 +178,11 @@
while (true) {
while (true) {
const { value, done } = await reader.read();
const { value, done } = await reader.read();
if (done || stopResponseFlag) {
if (done || stopResponseFlag) {
if (stopResponseFlag) {
await cancelChatCompletion(localStorage.token, currentRequestId);
}
currentRequestId = null;
break;
break;
}
}
...
@@ -200,6 +199,9 @@
...
@@ -200,6 +199,9 @@
let data = JSON.parse(line.replace(/^data: /, ''));
let data = JSON.parse(line.replace(/^data: /, ''));
console.log(data);
console.log(data);
if ('request_id' in data) {
currentRequestId = data.request_id;
} else {
if (responseMessage.content == '' && data.choices[0].delta.content == '\n') {
if (responseMessage.content == '' && data.choices[0].delta.content == '\n') {
continue;
continue;
} else {
} else {
...
@@ -215,54 +217,13 @@
...
@@ -215,54 +217,13 @@
}
}
}
}
}
}
}
} catch (error) {
} catch (error) {
console.log(error);
console.log(error);
}
}
scrollToBottom();
scrollToBottom();
}
}
// while (true) {
// const { value, done } = await reader.read();
// if (done || stopResponseFlag) {
// if (stopResponseFlag) {
// await cancelChatCompletion(localStorage.token, currentRequestId);
// }
// currentRequestId = null;
// break;
// }
// try {
// let lines = value.split('\n');
// for (const line of lines) {
// if (line !== '') {
// console.log(line);
// let data = JSON.parse(line);
// if ('detail' in data) {
// throw data;
// }
// if ('id' in data) {
// console.log(data);
// currentRequestId = data.id;
// } else {
// if (data.done == false) {
// text += data.message.content;
// } else {
// console.log('done');
// }
// }
// }
// }
// } catch (error) {
// console.log(error);
// }
// scrollToBottom();
// }
}
}
};
};
...
@@ -417,12 +378,14 @@
...
@@ -417,12 +378,14 @@
<div
<div
class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
class=" pb-2.5 flex flex-col justify-between w-full flex-auto overflow-auto h-0"
id="messages-container"
id="messages-container"
bind:this={messagesContainerElement}
>
>
<div class=" h-full w-full flex flex-col">
<div class=" h-full w-full flex flex-col">
<div class="flex-1 p-1">
<div class="flex-1 p-1">
{#if mode === 'complete'}
{#if mode === 'complete'}
<textarea
<textarea
id="text-completion-textarea"
id="text-completion-textarea"
bind:this={textCompletionAreaElement}
class="w-full h-full p-3 bg-transparent outline outline-1 outline-gray-200 dark:outline-gray-800 resize-none rounded-lg text-sm"
class="w-full h-full p-3 bg-transparent outline outline-1 outline-gray-200 dark:outline-gray-800 resize-none rounded-lg text-sm"
bind:value={text}
bind:value={text}
placeholder="You're a helpful assistant."
placeholder="You're a helpful assistant."
...
...
src/routes/(app)/prompts/+page.svelte
View file @
92e21acb
...
@@ -11,7 +11,7 @@
...
@@ -11,7 +11,7 @@
let importFiles = '';
let importFiles = '';
let query = '';
let query = '';
let promptsImportInputElement: HTMLInputElement;
const sharePrompt = async (prompt) => {
const sharePrompt = async (prompt) => {
toast.success('Redirecting you to OpenWebUI Community');
toast.success('Redirecting you to OpenWebUI Community');
...
@@ -208,6 +208,7 @@
...
@@ -208,6 +208,7 @@
<div class="flex space-x-2">
<div class="flex space-x-2">
<input
<input
id="prompts-import-input"
id="prompts-import-input"
bind:this={promptsImportInputElement}
bind:files={importFiles}
bind:files={importFiles}
type="file"
type="file"
accept=".json"
accept=".json"
...
@@ -241,9 +242,7 @@
...
@@ -241,9 +242,7 @@
<button
<button
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={async () => {
on:click={promptsImportInputElement.click}
document.getElementById('prompts-import-input')?.click();
}}
>
>
<div class=" self-center mr-2 font-medium">Import Prompts</div>
<div class=" self-center mr-2 font-medium">Import Prompts</div>
...
@@ -266,7 +265,7 @@
...
@@ -266,7 +265,7 @@
<button
<button
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
class="flex text-xs items-center space-x-1 px-3 py-1.5 rounded-xl bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 dark:text-gray-200 transition"
on:click={async () => {
on:click={async () => {
//
document.getElementById('modelfiles-i
mport
-i
nput
')?
.click();
//
promptsI
mport
I
nput
Element
.click();
let blob = new Blob([JSON.stringify($prompts)], {
let blob = new Blob([JSON.stringify($prompts)], {
type: 'application/json'
type: 'application/json'
});
});
...
...
Prev
1
2
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment