"src/vscode:/vscode.git/clone" did not exist on "b521a06b5f0e09b230b62affbe20f420122e171d"
Unverified Commit 7031aa14 authored by Ased Mammad's avatar Ased Mammad Committed by GitHub
Browse files

Merge branch 'dev' into feat/add-i18n

parents 08aa60b3 a9010318
...@@ -225,33 +225,80 @@ ...@@ -225,33 +225,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}
...@@ -260,7 +307,6 @@ ...@@ -260,7 +307,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
...@@ -310,7 +356,6 @@ ...@@ -310,7 +356,6 @@
{/if} {/if}
</div> </div>
</div> </div>
{/if}
{/each} {/each}
{#if bottomPadding} {#if bottomPadding}
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,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;
...@@ -346,6 +347,7 @@ ...@@ -346,6 +347,7 @@
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`;
}} }}
/> />
...@@ -464,6 +466,7 @@ ...@@ -464,6 +466,7 @@
</div> </div>
{/if} {/if}
<Tooltip content="Edit" placement="bottom">
<button <button
class="{isLastMessage class="{isLastMessage
? 'visible' ? 'visible'
...@@ -487,7 +490,9 @@ ...@@ -487,7 +490,9 @@
/> />
</svg> </svg>
</button> </button>
</Tooltip>
<Tooltip content="Copy" placement="bottom">
<button <button
class="{isLastMessage class="{isLastMessage
? 'visible' ? 'visible'
...@@ -511,7 +516,9 @@ ...@@ -511,7 +516,9 @@
/> />
</svg> </svg>
</button> </button>
</Tooltip>
<Tooltip content="Good Response" placement="bottom">
<button <button
class="{isLastMessage class="{isLastMessage
? 'visible' ? 'visible'
...@@ -536,6 +543,9 @@ ...@@ -536,6 +543,9 @@
/></svg /></svg
> >
</button> </button>
</Tooltip>
<Tooltip content="Bad Response" placement="bottom">
<button <button
class="{isLastMessage class="{isLastMessage
? 'visible' ? 'visible'
...@@ -560,7 +570,9 @@ ...@@ -560,7 +570,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
...@@ -600,7 +612,12 @@ ...@@ -600,7 +612,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
...@@ -634,8 +651,10 @@ ...@@ -634,8 +651,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'
...@@ -698,9 +717,11 @@ ...@@ -698,9 +717,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'
...@@ -725,9 +746,11 @@ ...@@ -725,9 +746,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
...@@ -757,7 +780,9 @@ ...@@ -757,7 +780,9 @@
/> />
</svg> </svg>
</button> </button>
</Tooltip>
<Tooltip content="Regenerate" placement="bottom">
<button <button
type="button" type="button"
class="{isLastMessage class="{isLastMessage
...@@ -780,6 +805,7 @@ ...@@ -780,6 +805,7 @@
/> />
</svg> </svg>
</button> </button>
</Tooltip>
{/if} {/if}
</div> </div>
{/if} {/if}
......
...@@ -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 i18n = getContext('i18n'); const i18n = getContext('i18n');
...@@ -171,7 +172,8 @@ ...@@ -171,7 +172,8 @@
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) => {
messageEditTextAreaElement.style.height = `${messageEditTextAreaElement.scrollHeight}px`; e.target.style.height = '';
e.target.style.height = `${e.target.scrollHeight}px`;
}} }}
/> />
...@@ -248,6 +250,7 @@ ...@@ -248,6 +250,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={() => {
...@@ -269,7 +272,9 @@ ...@@ -269,7 +272,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={() => {
...@@ -291,8 +296,10 @@ ...@@ -291,8 +296,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={() => {
...@@ -314,6 +321,7 @@ ...@@ -314,6 +321,7 @@
/> />
</svg> </svg>
</button> </button>
</Tooltip>
{/if} {/if}
</div> </div>
</div> </div>
......
...@@ -4,7 +4,12 @@ ...@@ -4,7 +4,12 @@
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
import { getOllamaUrls, getOllamaVersion, updateOllamaUrls } from '$lib/apis/ollama'; import { getOllamaUrls, getOllamaVersion, updateOllamaUrls } from '$lib/apis/ollama';
import { getOpenAIKey, getOpenAIUrl, updateOpenAIKey, updateOpenAIUrl } from '$lib/apis/openai'; import {
getOpenAIKeys,
getOpenAIUrls,
updateOpenAIKeys,
updateOpenAIUrls
} from '$lib/apis/openai';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
...@@ -18,12 +23,14 @@ ...@@ -18,12 +23,14 @@
let OPENAI_API_KEY = ''; let OPENAI_API_KEY = '';
let OPENAI_API_BASE_URL = ''; let OPENAI_API_BASE_URL = '';
let OPENAI_API_KEYS = [''];
let OPENAI_API_BASE_URLS = [''];
let showOpenAI = false; let showOpenAI = false;
let showLiteLLM = false;
const updateOpenAIHandler = async () => { const updateOpenAIHandler = async () => {
OPENAI_API_BASE_URL = await updateOpenAIUrl(localStorage.token, OPENAI_API_BASE_URL); OPENAI_API_BASE_URLS = await updateOpenAIUrls(localStorage.token, OPENAI_API_BASE_URLS);
OPENAI_API_KEY = await updateOpenAIKey(localStorage.token, OPENAI_API_KEY); OPENAI_API_KEYS = await updateOpenAIKeys(localStorage.token, OPENAI_API_KEYS);
await models.set(await getModels()); await models.set(await getModels());
}; };
...@@ -45,8 +52,8 @@ ...@@ -45,8 +52,8 @@
onMount(async () => { onMount(async () => {
if ($user.role === 'admin') { if ($user.role === 'admin') {
OLLAMA_BASE_URLS = await getOllamaUrls(localStorage.token); OLLAMA_BASE_URLS = await getOllamaUrls(localStorage.token);
OPENAI_API_BASE_URL = await getOpenAIUrl(localStorage.token); OPENAI_API_BASE_URLS = await getOpenAIUrls(localStorage.token);
OPENAI_API_KEY = await getOpenAIKey(localStorage.token); OPENAI_API_KEYS = await getOpenAIKeys(localStorage.token);
} }
}); });
</script> </script>
...@@ -73,38 +80,75 @@ ...@@ -73,38 +80,75 @@
</div> </div>
{#if showOpenAI} {#if showOpenAI}
<div> <div class="flex flex-col gap-1">
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('API Key')}</div> {#each OPENAI_API_BASE_URLS as url, idx}
<div class="flex w-full"> <div class="flex w-full gap-2">
<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-800 outline-none" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder={$i18n.t('Enter OpenAI API Key')} placeholder={$i18n.t('API Base URL')}
bind:value={OPENAI_API_KEY} bind:value={url}
autocomplete="off" autocomplete="off"
/> />
</div> </div>
</div>
</div>
<div>
<div class=" mb-2.5 text-sm font-medium">{$i18n.t('API Base URL')}</div>
<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-800 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 OpenAI API Base URL" placeholder={$i18n.t('API Key')}
bind:value={OPENAI_API_BASE_URL} bind:value={OPENAI_API_KEYS[idx]}
autocomplete="off" autocomplete="off"
/> />
</div> </div>
</div> <div class="self-center flex items-center">
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500"> {#if idx === 0}
WebUI will make requests to <span class=" text-gray-200" <button
>'{OPENAI_API_BASE_URL}/chat'</span class="px-1"
on:click={() => {
OPENAI_API_BASE_URLS = [...OPENAI_API_BASE_URLS, ''];
OPENAI_API_KEYS = [...OPENAI_API_KEYS, ''];
}}
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={() => {
OPENAI_API_BASE_URLS = OPENAI_API_BASE_URLS.filter(
(url, urlIdx) => idx !== urlIdx
);
OPENAI_API_KEYS = OPENAI_API_KEYS.filter((key, keyIdx) => idx !== keyIdx);
}}
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> </div>
<div class=" mb-1 text-xs text-gray-400 dark:text-gray-500">
WebUI will make requests to <span class=" text-gray-200">'{url}/models'</span>
</div>
{/each}
</div>
{/if} {/if}
</div> </div>
</div> </div>
......
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
let modelUploadMode = 'file'; let modelUploadMode = 'file';
let modelInputFile = ''; let modelInputFile = '';
let modelFileUrl = ''; let modelFileUrl = '';
let modelFileContent = `TEMPLATE """{{ .System }}\nUSER: {{ .Prompt }}\nASSSISTANT: """\nPARAMETER num_ctx 4096\nPARAMETER stop "</s>"\nPARAMETER stop "USER:"\nPARAMETER stop "ASSSISTANT:"`; 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;
...@@ -517,7 +517,7 @@ ...@@ -517,7 +517,7 @@
{#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
> >
...@@ -599,7 +599,7 @@ ...@@ -599,7 +599,7 @@
on:change={() => { on:change={() => {
console.log(modelInputFile); console.log(modelInputFile);
}} }}
accept=".gguf" accept=".gguf,.safetensors"
required required
hidden hidden
/> />
......
...@@ -140,7 +140,9 @@ ...@@ -140,7 +140,9 @@
<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={uploadDocInputElement.click} on:click={() => {
uploadDocInputElement.click();
}}
> >
{#if inputFiles} {#if inputFiles}
{inputFiles.length > 0 ? `${inputFiles.length}` : ''} document(s) selected. {inputFiles.length > 0 ? `${inputFiles.length}` : ''} document(s) selected.
......
...@@ -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
...@@ -99,14 +99,11 @@ ...@@ -99,14 +99,11 @@
if (localDBChats.length === 0) { if (localDBChats.length === 0) {
await deleteDB('Chats'); await deleteDB('Chats');
} }
console.log('localdb', localDBChats);
} }
console.log(DB); console.log(DB);
} catch (error) { } catch (error) {
// IndexedDB Not Found // IndexedDB Not Found
console.log('IDB Not Found');
} }
console.log(); console.log();
......
...@@ -344,7 +344,7 @@ ...@@ -344,7 +344,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) => ({
...@@ -558,7 +558,7 @@ ...@@ -558,7 +558,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) => ({
......
...@@ -354,7 +354,7 @@ ...@@ -354,7 +354,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) => ({
...@@ -568,7 +568,7 @@ ...@@ -568,7 +568,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) => ({
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment