Unverified Commit 89634046 authored by Timothy Jaeryang Baek's avatar Timothy Jaeryang Baek Committed by GitHub
Browse files

Merge pull request #1107 from open-webui/dev

0.1.111
parents 04ddbf43 88d324b5
...@@ -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 chatTextAreaElement: HTMLTextAreaElement;
let filesInputElement; let filesInputElement;
let promptsElement; let promptsElement;
...@@ -359,12 +359,12 @@ ...@@ -359,12 +359,12 @@
{#if dragged} {#if dragged}
<div <div
class="fixed w-full h-full flex z-50 touch-none pointer-events-none" class="fixed lg:w-[calc(100%-260px)] w-full h-full flex z-50 touch-none pointer-events-none"
id="dropzone" id="dropzone"
role="region" role="region"
aria-label="Drag and Drop Container" aria-label="Drag and Drop Container"
> >
<div class="absolute rounded-xl w-full h-full backdrop-blur bg-gray-800/40 flex justify-center"> <div class="absolute w-full h-full backdrop-blur bg-gray-800/40 flex justify-center">
<div class="m-auto pt-64 flex flex-col justify-center"> <div class="m-auto pt-64 flex flex-col justify-center">
<div class="max-w-md"> <div class="max-w-md">
<AddFilesPlaceholder /> <AddFilesPlaceholder />
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
import auto_render from 'katex/dist/contrib/auto-render.mjs'; import auto_render from 'katex/dist/contrib/auto-render.mjs';
import 'katex/dist/katex.min.css'; import 'katex/dist/katex.min.css';
import { fade } from 'svelte/transition';
import { createEventDispatcher } from 'svelte'; import { createEventDispatcher } from 'svelte';
import { onMount, tick } from 'svelte'; import { onMount, tick } from 'svelte';
...@@ -276,13 +277,15 @@ ...@@ -276,13 +277,15 @@
const generateImage = async (message) => { const generateImage = async (message) => {
generatingImage = true; generatingImage = true;
const res = await imageGenerations(localStorage.token, message.content); const res = await imageGenerations(localStorage.token, message.content).catch((error) => {
toast.error(error);
});
console.log(res); console.log(res);
if (res) { if (res) {
message.files = res.images.map((image) => ({ message.files = res.map((image) => ({
type: 'image', type: 'image',
url: `data:image/png;base64,${image}` url: `${image.url}`
})); }));
dispatch('save', message); dispatch('save', message);
...@@ -477,7 +480,7 @@ ...@@ -477,7 +480,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -503,7 +506,7 @@ ...@@ -503,7 +506,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -622,7 +625,7 @@ ...@@ -622,7 +625,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -637,7 +640,7 @@ ...@@ -637,7 +640,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -703,7 +706,7 @@ ...@@ -703,7 +706,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -733,7 +736,7 @@ ...@@ -733,7 +736,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -762,7 +765,7 @@ ...@@ -762,7 +765,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -792,7 +795,7 @@ ...@@ -792,7 +795,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
......
...@@ -258,7 +258,7 @@ ...@@ -258,7 +258,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -282,7 +282,7 @@ ...@@ -282,7 +282,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
...@@ -307,7 +307,7 @@ ...@@ -307,7 +307,7 @@
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke-width="1.5" stroke-width="2"
stroke="currentColor" stroke="currentColor"
class="w-4 h-4" class="w-4 h-4"
> >
......
...@@ -271,7 +271,7 @@ ...@@ -271,7 +271,7 @@
<div class="flex justify-end pt-3 text-sm font-medium"> <div class="flex justify-end pt-3 text-sm font-medium">
<button <button
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded" class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
on:click={async () => { on:click={async () => {
const res = await submitHandler(); const res = await submitHandler();
......
...@@ -251,7 +251,7 @@ ...@@ -251,7 +251,7 @@
<div class="flex justify-end pt-3 text-sm font-medium"> <div class="flex justify-end pt-3 text-sm font-medium">
<button <button
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded" class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
type="submit" type="submit"
> >
Save Save
......
...@@ -247,7 +247,7 @@ ...@@ -247,7 +247,7 @@
<div class="flex justify-end pt-3 text-sm font-medium"> <div class="flex justify-end pt-3 text-sm font-medium">
<button <button
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded" class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
type="submit" type="submit"
> >
Save Save
......
...@@ -176,7 +176,7 @@ ...@@ -176,7 +176,7 @@
<div class=" my-2.5 text-sm font-medium">System Prompt</div> <div class=" my-2.5 text-sm font-medium">System Prompt</div>
<textarea <textarea
bind:value={system} bind:value={system}
class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none" class="w-full rounded-lg p-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
rows="4" rows="4"
/> />
</div> </div>
...@@ -262,7 +262,7 @@ ...@@ -262,7 +262,7 @@
<div class="flex justify-end pt-3 text-sm font-medium"> <div class="flex justify-end pt-3 text-sm font-medium">
<button <button
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded" class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
on:click={() => { on:click={() => {
saveSettings({ saveSettings({
system: system !== '' ? system : undefined, system: system !== '' ? system : undefined,
......
...@@ -5,16 +5,18 @@ ...@@ -5,16 +5,18 @@
import { config, user } from '$lib/stores'; import { config, user } from '$lib/stores';
import { import {
getAUTOMATIC1111Url, getAUTOMATIC1111Url,
getDefaultDiffusionModel, getImageGenerationModels,
getDiffusionModels, getDefaultImageGenerationModel,
getImageGenerationEnabledStatus, updateDefaultImageGenerationModel,
getImageSize, getImageSize,
toggleImageGenerationEnabledStatus, getImageGenerationConfig,
updateImageGenerationConfig,
updateAUTOMATIC1111Url, updateAUTOMATIC1111Url,
updateDefaultDiffusionModel,
updateImageSize, updateImageSize,
getImageSteps, getImageSteps,
updateImageSteps updateImageSteps,
getOpenAIKey,
updateOpenAIKey
} from '$lib/apis/images'; } from '$lib/apis/images';
import { getBackendConfig } from '$lib/apis'; import { getBackendConfig } from '$lib/apis';
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
...@@ -23,8 +25,11 @@ ...@@ -23,8 +25,11 @@
let loading = false; let loading = false;
let imageGenerationEngine = '';
let enableImageGeneration = false; let enableImageGeneration = false;
let AUTOMATIC1111_BASE_URL = ''; let AUTOMATIC1111_BASE_URL = '';
let OPENAI_API_KEY = '';
let selectedModel = ''; let selectedModel = '';
let models = null; let models = null;
...@@ -33,11 +38,11 @@ ...@@ -33,11 +38,11 @@
let steps = 50; let steps = 50;
const getModels = async () => { const getModels = async () => {
models = await getDiffusionModels(localStorage.token).catch((error) => { models = await getImageGenerationModels(localStorage.token).catch((error) => {
toast.error(error); toast.error(error);
return null; return null;
}); });
selectedModel = await getDefaultDiffusionModel(localStorage.token).catch((error) => { selectedModel = await getDefaultImageGenerationModel(localStorage.token).catch((error) => {
return ''; return '';
}); });
}; };
...@@ -62,33 +67,45 @@ ...@@ -62,33 +67,45 @@
AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token); AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token);
} }
}; };
const toggleImageGeneration = async () => { const updateImageGeneration = async () => {
if (AUTOMATIC1111_BASE_URL) { const res = await updateImageGenerationConfig(
enableImageGeneration = await toggleImageGenerationEnabledStatus(localStorage.token).catch( localStorage.token,
(error) => { imageGenerationEngine,
toast.error(error); enableImageGeneration
return false; ).catch((error) => {
} toast.error(error);
); return null;
});
if (enableImageGeneration) { if (res) {
config.set(await getBackendConfig(localStorage.token)); imageGenerationEngine = res.engine;
getModels(); enableImageGeneration = res.enabled;
} }
} else {
enableImageGeneration = false; if (enableImageGeneration) {
toast.error('AUTOMATIC1111_BASE_URL not provided'); config.set(await getBackendConfig(localStorage.token));
getModels();
} }
}; };
onMount(async () => { onMount(async () => {
if ($user.role === 'admin') { if ($user.role === 'admin') {
enableImageGeneration = await getImageGenerationEnabledStatus(localStorage.token); const res = await getImageGenerationConfig(localStorage.token).catch((error) => {
toast.error(error);
return null;
});
if (res) {
imageGenerationEngine = res.engine;
enableImageGeneration = res.enabled;
}
AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token); AUTOMATIC1111_BASE_URL = await getAUTOMATIC1111Url(localStorage.token);
OPENAI_API_KEY = await getOpenAIKey(localStorage.token);
imageSize = await getImageSize(localStorage.token);
steps = await getImageSteps(localStorage.token);
if (enableImageGeneration && AUTOMATIC1111_BASE_URL) { if (enableImageGeneration) {
imageSize = await getImageSize(localStorage.token);
steps = await getImageSteps(localStorage.token);
getModels(); getModels();
} }
} }
...@@ -99,7 +116,11 @@ ...@@ -99,7 +116,11 @@
class="flex flex-col h-full justify-between space-y-3 text-sm" class="flex flex-col h-full justify-between space-y-3 text-sm"
on:submit|preventDefault={async () => { on:submit|preventDefault={async () => {
loading = true; loading = true;
await updateDefaultDiffusionModel(localStorage.token, selectedModel); await updateOpenAIKey(localStorage.token, OPENAI_API_KEY);
await updateDefaultImageGenerationModel(localStorage.token, selectedModel);
await updateDefaultImageGenerationModel(localStorage.token, selectedModel);
await updateImageSize(localStorage.token, imageSize).catch((error) => { await updateImageSize(localStorage.token, imageSize).catch((error) => {
toast.error(error); toast.error(error);
return null; return null;
...@@ -117,6 +138,23 @@ ...@@ -117,6 +138,23 @@
<div> <div>
<div class=" mb-1 text-sm font-medium">Image Settings</div> <div class=" mb-1 text-sm font-medium">Image Settings</div>
<div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">Image Generation Engine</div>
<div class="flex items-center relative">
<select
class="w-fit pr-8 rounded px-2 p-1 text-xs bg-transparent outline-none text-right"
bind:value={imageGenerationEngine}
placeholder="Select a mode"
on:change={async () => {
await updateImageGeneration();
}}
>
<option value="">Default (Automatic1111)</option>
<option value="openai">Open AI (Dall-E)</option>
</select>
</div>
</div>
<div> <div>
<div class=" py-0.5 flex w-full justify-between"> <div class=" py-0.5 flex w-full justify-between">
<div class=" self-center text-xs font-medium">Image Generation (Experimental)</div> <div class=" self-center text-xs font-medium">Image Generation (Experimental)</div>
...@@ -124,7 +162,17 @@ ...@@ -124,7 +162,17 @@
<button <button
class="p-1 px-3 text-xs flex rounded transition" class="p-1 px-3 text-xs flex rounded transition"
on:click={() => { on:click={() => {
toggleImageGeneration(); if (imageGenerationEngine === '' && AUTOMATIC1111_BASE_URL === '') {
toast.error('AUTOMATIC1111 Base URL is required.');
enableImageGeneration = false;
} else if (imageGenerationEngine === 'openai' && OPENAI_API_KEY === '') {
toast.error('OpenAI API Key is required.');
enableImageGeneration = false;
} else {
enableImageGeneration = !enableImageGeneration;
}
updateImageGeneration();
}} }}
type="button" type="button"
> >
...@@ -139,49 +187,62 @@ ...@@ -139,49 +187,62 @@
</div> </div>
<hr class=" dark:border-gray-700" /> <hr class=" dark:border-gray-700" />
<div class=" mb-2.5 text-sm font-medium">AUTOMATIC1111 Base URL</div> {#if imageGenerationEngine === ''}
<div class="flex w-full"> <div class=" mb-2.5 text-sm font-medium">AUTOMATIC1111 Base URL</div>
<div class="flex-1 mr-2"> <div class="flex w-full">
<input <div class="flex-1 mr-2">
class="w-full rounded py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none" <input
placeholder="Enter URL (e.g. http://127.0.0.1:7860/)" class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
bind:value={AUTOMATIC1111_BASE_URL} placeholder="Enter URL (e.g. http://127.0.0.1:7860/)"
/> bind:value={AUTOMATIC1111_BASE_URL}
/>
</div>
<button
class="px-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-700 rounded-lg transition"
type="button"
on:click={() => {
// updateOllamaAPIUrlHandler();
updateAUTOMATIC1111UrlHandler();
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
>
<path
fill-rule="evenodd"
d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z"
clip-rule="evenodd"
/>
</svg>
</button>
</div> </div>
<button
class="px-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-700 rounded transition" <div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
type="button" Include `--api` flag when running stable-diffusion-webui
on:click={() => { <a
// updateOllamaAPIUrlHandler(); class=" text-gray-300 font-medium"
href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/3734"
updateAUTOMATIC1111UrlHandler(); target="_blank"
}}
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-4 h-4"
> >
<path (e.g. `sh webui.sh --api`)
fill-rule="evenodd" </a>
d="M15.312 11.424a5.5 5.5 0 01-9.201 2.466l-.312-.311h2.433a.75.75 0 000-1.5H3.989a.75.75 0 00-.75.75v4.242a.75.75 0 001.5 0v-2.43l.31.31a7 7 0 0011.712-3.138.75.75 0 00-1.449-.39zm1.23-3.723a.75.75 0 00.219-.53V2.929a.75.75 0 00-1.5 0V5.36l-.31-.31A7 7 0 003.239 8.188a.75.75 0 101.448.389A5.5 5.5 0 0113.89 6.11l.311.31h-2.432a.75.75 0 000 1.5h4.243a.75.75 0 00.53-.219z" </div>
clip-rule="evenodd" {:else if imageGenerationEngine === 'openai'}
<div class=" mb-2.5 text-sm font-medium">OpenAI API Key</div>
<div class="flex w-full">
<div class="flex-1 mr-2">
<input
class="w-full rounded-lg py-2 px-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none"
placeholder="Enter API Key"
bind:value={OPENAI_API_KEY}
/> />
</svg> </div>
</button> </div>
</div> {/if}
<div class="mt-2 text-xs text-gray-400 dark:text-gray-500">
Include `--api` flag when running stable-diffusion-webui
<a
class=" text-gray-300 font-medium"
href="https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/3734"
target="_blank"
>
(e.g. `sh webui.sh --api`)
</a>
</div>
{#if enableImageGeneration} {#if enableImageGeneration}
<hr class=" dark:border-gray-700" /> <hr class=" dark:border-gray-700" />
...@@ -191,7 +252,7 @@ ...@@ -191,7 +252,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-800 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={selectedModel} bind:value={selectedModel}
placeholder="Select a model" placeholder="Select a model"
> >
...@@ -199,9 +260,7 @@ ...@@ -199,9 +260,7 @@
<option value="" disabled selected>Select a model</option> <option value="" disabled selected>Select a model</option>
{/if} {/if}
{#each models ?? [] as model} {#each models ?? [] as model}
<option value={model.title} class="bg-gray-100 dark:bg-gray-700" <option value={model.id} class="bg-gray-100 dark:bg-gray-700">{model.name}</option>
>{model.model_name}</option
>
{/each} {/each}
</select> </select>
</div> </div>
...@@ -213,7 +272,7 @@ ...@@ -213,7 +272,7 @@
<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-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 Image Size (e.g. 512x512)" placeholder="Enter Image Size (e.g. 512x512)"
bind:value={imageSize} bind:value={imageSize}
/> />
...@@ -226,7 +285,7 @@ ...@@ -226,7 +285,7 @@
<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-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 Number of Steps (e.g. 50)" placeholder="Enter Number of Steps (e.g. 50)"
bind:value={steps} bind:value={steps}
/> />
...@@ -238,7 +297,7 @@ ...@@ -238,7 +297,7 @@
<div class="flex justify-end pt-3 text-sm font-medium"> <div class="flex justify-end pt-3 text-sm font-medium">
<button <button
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded flex flex-row space-x-1 items-center {loading class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg flex flex-row space-x-1 items-center {loading
? ' cursor-not-allowed' ? ' cursor-not-allowed'
: ''}" : ''}"
type="submit" type="submit"
......
...@@ -63,6 +63,7 @@ ...@@ -63,6 +63,7 @@
} }
saveSettings({ saveSettings({
titleAutoGenerateModel: titleAutoGenerateModel !== '' ? titleAutoGenerateModel : undefined,
titleGenerationPrompt: titleGenerationPrompt ? titleGenerationPrompt : undefined titleGenerationPrompt: titleGenerationPrompt ? titleGenerationPrompt : undefined
}); });
}; };
...@@ -186,7 +187,7 @@ ...@@ -186,7 +187,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-800 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={titleAutoGenerateModel} bind:value={titleAutoGenerateModel}
placeholder="Select a model" placeholder="Select a model"
> >
...@@ -200,35 +201,12 @@ ...@@ -200,35 +201,12 @@
{/each} {/each}
</select> </select>
</div> </div>
<button
class="px-3 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-800 dark:text-gray-100 rounded transition"
on:click={() => {
saveSettings({
titleAutoGenerateModel:
titleAutoGenerateModel !== '' ? titleAutoGenerateModel : undefined
});
}}
type="button"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-3.5 h-3.5"
>
<path
fill-rule="evenodd"
d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.932.75.75 0 0 1-1.3-.75 6 6 0 0 1 9.44-1.242l.842.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44 1.241l-.84-.84v1.371a.75.75 0 0 1-1.5 0V9.591a.75.75 0 0 1 .75-.75H5.35a.75.75 0 0 1 0 1.5H3.98l.841.841a4.5 4.5 0 0 0 7.08-.932.75.75 0 0 1 1.025-.273Z"
clip-rule="evenodd"
/>
</svg>
</button>
</div> </div>
<div class="mt-3"> <div class="mt-3 mr-2">
<div class=" mb-2.5 text-sm font-medium">Title Generation Prompt</div> <div class=" mb-2.5 text-sm font-medium">Title Generation Prompt</div>
<textarea <textarea
bind:value={titleGenerationPrompt} bind:value={titleGenerationPrompt}
class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none" class="w-full rounded-lg p-4 text-sm dark:text-gray-300 dark:bg-gray-850 outline-none resize-none"
rows="3" rows="3"
/> />
</div> </div>
...@@ -321,7 +299,7 @@ ...@@ -321,7 +299,7 @@
<div class="flex justify-end pt-3 text-sm font-medium"> <div class="flex justify-end pt-3 text-sm font-medium">
<button <button
class=" px-4 py-2 bg-emerald-600 hover:bg-emerald-700 text-gray-100 transition rounded" class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
type="submit" type="submit"
> >
Save Save
......
...@@ -326,7 +326,7 @@ ...@@ -326,7 +326,7 @@
{getModels} {getModels}
{saveSettings} {saveSettings}
on:save={() => { on:save={() => {
show = false; toast.success('Settings saved successfully!');
}} }}
/> />
{:else if selectedTab === 'models'} {:else if selectedTab === 'models'}
...@@ -335,28 +335,28 @@ ...@@ -335,28 +335,28 @@
<Connections <Connections
{getModels} {getModels}
on:save={() => { on:save={() => {
show = false; toast.success('Settings saved successfully!');
}} }}
/> />
{:else if selectedTab === 'interface'} {:else if selectedTab === 'interface'}
<Interface <Interface
{saveSettings} {saveSettings}
on:save={() => { on:save={() => {
show = false; toast.success('Settings saved successfully!');
}} }}
/> />
{:else if selectedTab === 'audio'} {:else if selectedTab === 'audio'}
<Audio <Audio
{saveSettings} {saveSettings}
on:save={() => { on:save={() => {
show = false; toast.success('Settings saved successfully!');
}} }}
/> />
{:else if selectedTab === 'images'} {:else if selectedTab === 'images'}
<Images <Images
{saveSettings} {saveSettings}
on:save={() => { on:save={() => {
show = false; toast.success('Settings saved successfully!');
}} }}
/> />
{:else if selectedTab === 'chats'} {:else if selectedTab === 'chats'}
...@@ -364,7 +364,7 @@ ...@@ -364,7 +364,7 @@
{:else if selectedTab === 'account'} {:else if selectedTab === 'account'}
<Account <Account
saveHandler={() => { saveHandler={() => {
show = false; toast.success('Settings saved successfully!');
}} }}
/> />
{:else if selectedTab === 'about'} {:else if selectedTab === 'about'}
......
<script lang="ts"> <script lang="ts">
import { WEBUI_BASE_URL } from '$lib/constants';
import ImagePreview from './ImagePreview.svelte'; import ImagePreview from './ImagePreview.svelte';
export let src = ''; export let src = '';
export let alt = ''; export let alt = '';
let _src = '';
$: _src = src.startsWith('/') ? `${WEBUI_BASE_URL}${src}` : src;
let showImagePreview = false; let showImagePreview = false;
</script> </script>
<ImagePreview bind:show={showImagePreview} {src} {alt} /> <ImagePreview bind:show={showImagePreview} src={_src} {alt} />
<button <button
on:click={() => { on:click={() => {
console.log('image preview'); console.log('image preview');
showImagePreview = true; showImagePreview = true;
}} }}
> >
<img {src} {alt} class=" max-h-96 rounded-lg" draggable="false" /> <img src={_src} {alt} class=" max-h-96 rounded-lg" draggable="false" />
</button> </button>
<script lang="ts"> <script lang="ts">
import { getDocs } from '$lib/apis/documents'; import { getDocs } from '$lib/apis/documents';
import { import {
getChunkParams, getRAGConfig,
updateRAGConfig,
getQuerySettings, getQuerySettings,
scanDocs, scanDocs,
updateChunkParams,
updateQuerySettings updateQuerySettings
} from '$lib/apis/rag'; } from '$lib/apis/rag';
import { documents } from '$lib/stores'; import { documents } from '$lib/stores';
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
let chunkSize = 0; let chunkSize = 0;
let chunkOverlap = 0; let chunkOverlap = 0;
let pdfExtractImages = true;
let querySettings = { let querySettings = {
template: '', template: '',
...@@ -35,16 +36,24 @@ ...@@ -35,16 +36,24 @@
}; };
const submitHandler = async () => { const submitHandler = async () => {
const res = await updateChunkParams(localStorage.token, chunkSize, chunkOverlap); const res = await updateRAGConfig(localStorage.token, {
pdf_extract_images: pdfExtractImages,
chunk: {
chunk_overlap: chunkOverlap,
chunk_size: chunkSize
}
});
querySettings = await updateQuerySettings(localStorage.token, querySettings); querySettings = await updateQuerySettings(localStorage.token, querySettings);
}; };
onMount(async () => { onMount(async () => {
const res = await getChunkParams(localStorage.token); const res = await getRAGConfig(localStorage.token);
if (res) { if (res) {
chunkSize = res.chunk_size; pdfExtractImages = res.pdf_extract_images;
chunkOverlap = res.chunk_overlap;
chunkSize = res.chunk.chunk_size;
chunkOverlap = res.chunk.chunk_overlap;
} }
querySettings = await getQuerySettings(localStorage.token); querySettings = await getQuerySettings(localStorage.token);
...@@ -124,82 +133,100 @@ ...@@ -124,82 +133,100 @@
<hr class=" dark:border-gray-700" /> <hr class=" dark:border-gray-700" />
<div class=" "> <div class=" space-y-3">
<div class=" text-sm font-medium">Chunk Params</div> <div class=" space-y-3">
<div class=" text-sm font-medium">Chunk Params</div>
<div class=" flex">
<div class=" flex w-full justify-between"> <div class=" flex gap-2">
<div class="self-center text-xs font-medium min-w-fit">Chunk Size</div> <div class=" flex w-full justify-between gap-2">
<div class="self-center text-xs font-medium min-w-fit">Chunk Size</div>
<div class="self-center p-3">
<input <div class="self-center">
class=" w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600" <input
type="number" class=" w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
placeholder="Enter Chunk Size" type="number"
bind:value={chunkSize} placeholder="Enter Chunk Size"
autocomplete="off" bind:value={chunkSize}
min="0" autocomplete="off"
/> min="0"
/>
</div>
</div> </div>
</div>
<div class="flex w-full"> <div class="flex w-full gap-2">
<div class=" self-center text-xs font-medium min-w-fit">Chunk Overlap</div> <div class=" self-center text-xs font-medium min-w-fit">Chunk Overlap</div>
<div class="self-center p-3"> <div class="self-center">
<input <input
class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600" class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
type="number" type="number"
placeholder="Enter Chunk Overlap" placeholder="Enter Chunk Overlap"
bind:value={chunkOverlap} bind:value={chunkOverlap}
autocomplete="off" autocomplete="off"
min="0" min="0"
/> />
</div>
</div> </div>
</div> </div>
</div>
<div class=" text-sm font-medium">Query Params</div>
<div class=" flex"> <div>
<div class=" flex w-full justify-between"> <div class="flex justify-between items-center text-xs">
<div class="self-center text-xs font-medium flex-1">Top K</div> <div class=" text-xs font-medium">PDF Extract Images (OCR)</div>
<div class="self-center p-3"> <button
<input class=" text-xs font-medium text-gray-500"
class=" w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600" type="button"
type="number" on:click={() => {
placeholder="Enter Top K" pdfExtractImages = !pdfExtractImages;
bind:value={querySettings.k} }}>{pdfExtractImages ? 'On' : 'Off'}</button
autocomplete="off" >
min="0"
/>
</div> </div>
</div> </div>
<!-- <div class="flex w-full">
<div class=" self-center text-xs font-medium min-w-fit">Chunk Overlap</div>
<div class="self-center p-3">
<input
class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
type="number"
placeholder="Enter Chunk Overlap"
bind:value={chunkOverlap}
autocomplete="off"
min="0"
/>
</div>
</div> -->
</div> </div>
<div> <div>
<div class=" mb-2.5 text-sm font-medium">RAG Template</div> <div class=" text-sm font-medium">Query Params</div>
<textarea
bind:value={querySettings.template} <div class=" flex py-2">
class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none" <div class=" flex w-full justify-between gap-2">
rows="4" <div class="self-center text-xs font-medium flex-1">Top K</div>
/>
<div class="self-center">
<input
class=" w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
type="number"
placeholder="Enter Top K"
bind:value={querySettings.k}
autocomplete="off"
min="0"
/>
</div>
</div>
<!-- <div class="flex w-full">
<div class=" self-center text-xs font-medium min-w-fit">Chunk Overlap</div>
<div class="self-center p-3">
<input
class="w-full rounded py-1.5 px-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none border border-gray-100 dark:border-gray-600"
type="number"
placeholder="Enter Chunk Overlap"
bind:value={chunkOverlap}
autocomplete="off"
min="0"
/>
</div>
</div> -->
</div>
<div>
<div class=" mb-2.5 text-sm font-medium">RAG Template</div>
<textarea
bind:value={querySettings.template}
class="w-full rounded p-4 text-sm dark:text-gray-300 dark:bg-gray-800 outline-none resize-none"
rows="4"
/>
</div>
</div> </div>
</div> </div>
</div> </div>
......
...@@ -61,12 +61,16 @@ ...@@ -61,12 +61,16 @@
}; };
const editChatTitle = async (id, _title) => { const editChatTitle = async (id, _title) => {
title = _title; if (_title === '') {
toast.error('Title cannot be an empty string.');
await updateChatById(localStorage.token, id, { } else {
title: _title title = _title;
});
await chats.set(await getChatList(localStorage.token)); await updateChatById(localStorage.token, id, {
title: _title
});
await chats.set(await getChatList(localStorage.token));
}
}; };
const deleteChat = async (id) => { const deleteChat = async (id) => {
...@@ -388,12 +392,13 @@ ...@@ -388,12 +392,13 @@
show = false; show = false;
} }
}} }}
draggable="false"
> >
<div class=" flex self-center flex-1 w-full"> <div class=" flex self-center flex-1 w-full">
<div <div
class=" text-left self-center overflow-hidden {chat.id === $chatId class=" text-left self-center overflow-hidden {chat.id === $chatId
? 'w-[160px]' ? 'w-[160px]'
: 'w-full'} " : 'w-full'} h-[20px]"
> >
{chat.title} {chat.title}
</div> </div>
......
...@@ -232,53 +232,6 @@ ...@@ -232,53 +232,6 @@
const sendPrompt = async (prompt, parentId) => { const sendPrompt = async (prompt, parentId) => {
const _chatId = JSON.parse(JSON.stringify($chatId)); const _chatId = JSON.parse(JSON.stringify($chatId));
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
console.log(docs);
if (docs.length > 0) {
processing = 'Reading';
const query = history.messages[parentId].content;
let relevantContexts = await Promise.all(
docs.map(async (doc) => {
if (doc.type === 'collection') {
return await queryCollection(localStorage.token, doc.collection_names, query).catch(
(error) => {
console.log(error);
return null;
}
);
} else {
return await queryDoc(localStorage.token, doc.collection_name, query).catch((error) => {
console.log(error);
return null;
});
}
})
);
relevantContexts = relevantContexts.filter((context) => context);
const contextString = relevantContexts.reduce((a, context, i, arr) => {
return `${a}${context.documents.join(' ')}\n`;
}, '');
console.log(contextString);
history.messages[parentId].raContent = await RAGTemplate(
localStorage.token,
contextString,
query
);
history.messages[parentId].contexts = relevantContexts;
await tick();
processing = '';
}
await Promise.all( await Promise.all(
selectedModels.map(async (modelId) => { selectedModels.map(async (modelId) => {
const model = $models.filter((m) => m.id === modelId).at(0); const model = $models.filter((m) => m.id === modelId).at(0);
...@@ -342,15 +295,25 @@ ...@@ -342,15 +295,25 @@
...messages ...messages
] ]
.filter((message) => message) .filter((message) => message)
.map((message, idx, arr) => ({ .map((message, idx, arr) => {
role: message.role, // Prepare the base message object
content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content, const baseMessage = {
...(message.files && { role: message.role,
images: message.files content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content
.filter((file) => file.type === 'image') };
.map((file) => file.url.slice(file.url.indexOf(',') + 1))
}) // Extract and format image URLs if any exist
})); const imageUrls = message.files
?.filter((file) => file.type === 'image')
.map((file) => file.url.slice(file.url.indexOf(',') + 1));
// Add images array only if it contains elements
if (imageUrls && imageUrls.length > 0) {
baseMessage.images = imageUrls;
}
return baseMessage;
});
let lastImageIndex = -1; let lastImageIndex = -1;
...@@ -368,6 +331,13 @@ ...@@ -368,6 +331,13 @@
} }
}); });
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
const [res, controller] = await generateChatCompletion(localStorage.token, { const [res, controller] = await generateChatCompletion(localStorage.token, {
model: model, model: model,
messages: messagesBody, messages: messagesBody,
...@@ -375,7 +345,8 @@ ...@@ -375,7 +345,8 @@
...($settings.options ?? {}) ...($settings.options ?? {})
}, },
format: $settings.requestFormat ?? undefined, format: $settings.requestFormat ?? undefined,
keep_alive: $settings.keepAlive ?? undefined keep_alive: $settings.keepAlive ?? undefined,
docs: docs.length > 0 ? docs : undefined
}); });
if (res && res.ok) { if (res && res.ok) {
...@@ -535,6 +506,15 @@ ...@@ -535,6 +506,15 @@
const responseMessage = history.messages[responseMessageId]; const responseMessage = history.messages[responseMessageId];
scrollToBottom(); scrollToBottom();
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
console.log(docs);
const res = await generateOpenAIChatCompletion( const res = await generateOpenAIChatCompletion(
localStorage.token, localStorage.token,
{ {
...@@ -583,7 +563,8 @@ ...@@ -583,7 +563,8 @@
top_p: $settings?.options?.top_p ?? undefined, top_p: $settings?.options?.top_p ?? undefined,
num_ctx: $settings?.options?.num_ctx ?? undefined, num_ctx: $settings?.options?.num_ctx ?? undefined,
frequency_penalty: $settings?.options?.repeat_penalty ?? undefined, frequency_penalty: $settings?.options?.repeat_penalty ?? undefined,
max_tokens: $settings?.options?.num_predict ?? undefined max_tokens: $settings?.options?.num_predict ?? undefined,
docs: docs.length > 0 ? docs : undefined
}, },
model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}` model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}`
); );
...@@ -694,7 +675,12 @@ ...@@ -694,7 +675,12 @@
if (messages.length == 2) { if (messages.length == 2) {
window.history.replaceState(history.state, '', `/c/${_chatId}`); window.history.replaceState(history.state, '', `/c/${_chatId}`);
await setChatTitle(_chatId, userPrompt);
if ($settings?.titleAutoGenerateModel) {
await generateChatTitle(_chatId, userPrompt);
} else {
await setChatTitle(_chatId, userPrompt);
}
} }
}; };
......
...@@ -245,53 +245,6 @@ ...@@ -245,53 +245,6 @@
const sendPrompt = async (prompt, parentId) => { const sendPrompt = async (prompt, parentId) => {
const _chatId = JSON.parse(JSON.stringify($chatId)); const _chatId = JSON.parse(JSON.stringify($chatId));
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
console.log(docs);
if (docs.length > 0) {
processing = 'Reading';
const query = history.messages[parentId].content;
let relevantContexts = await Promise.all(
docs.map(async (doc) => {
if (doc.type === 'collection') {
return await queryCollection(localStorage.token, doc.collection_names, query).catch(
(error) => {
console.log(error);
return null;
}
);
} else {
return await queryDoc(localStorage.token, doc.collection_name, query).catch((error) => {
console.log(error);
return null;
});
}
})
);
relevantContexts = relevantContexts.filter((context) => context);
const contextString = relevantContexts.reduce((a, context, i, arr) => {
return `${a}${context.documents.join(' ')}\n`;
}, '');
console.log(contextString);
history.messages[parentId].raContent = await RAGTemplate(
localStorage.token,
contextString,
query
);
history.messages[parentId].contexts = relevantContexts;
await tick();
processing = '';
}
await Promise.all( await Promise.all(
selectedModels.map(async (modelId) => { selectedModels.map(async (modelId) => {
const model = $models.filter((m) => m.id === modelId).at(0); const model = $models.filter((m) => m.id === modelId).at(0);
...@@ -355,15 +308,25 @@ ...@@ -355,15 +308,25 @@
...messages ...messages
] ]
.filter((message) => message) .filter((message) => message)
.map((message, idx, arr) => ({ .map((message, idx, arr) => {
role: message.role, // Prepare the base message object
content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content, const baseMessage = {
...(message.files && { role: message.role,
images: message.files content: arr.length - 2 !== idx ? message.content : message?.raContent ?? message.content
.filter((file) => file.type === 'image') };
.map((file) => file.url.slice(file.url.indexOf(',') + 1))
}) // Extract and format image URLs if any exist
})); const imageUrls = message.files
?.filter((file) => file.type === 'image')
.map((file) => file.url.slice(file.url.indexOf(',') + 1));
// Add images array only if it contains elements
if (imageUrls && imageUrls.length > 0) {
baseMessage.images = imageUrls;
}
return baseMessage;
});
let lastImageIndex = -1; let lastImageIndex = -1;
...@@ -381,6 +344,13 @@ ...@@ -381,6 +344,13 @@
} }
}); });
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
const [res, controller] = await generateChatCompletion(localStorage.token, { const [res, controller] = await generateChatCompletion(localStorage.token, {
model: model, model: model,
messages: messagesBody, messages: messagesBody,
...@@ -388,7 +358,8 @@ ...@@ -388,7 +358,8 @@
...($settings.options ?? {}) ...($settings.options ?? {})
}, },
format: $settings.requestFormat ?? undefined, format: $settings.requestFormat ?? undefined,
keep_alive: $settings.keepAlive ?? undefined keep_alive: $settings.keepAlive ?? undefined,
docs: docs.length > 0 ? docs : undefined
}); });
if (res && res.ok) { if (res && res.ok) {
...@@ -548,6 +519,15 @@ ...@@ -548,6 +519,15 @@
const responseMessage = history.messages[responseMessageId]; const responseMessage = history.messages[responseMessageId];
scrollToBottom(); scrollToBottom();
const docs = messages
.filter((message) => message?.files ?? null)
.map((message) =>
message.files.filter((item) => item.type === 'doc' || item.type === 'collection')
)
.flat(1);
console.log(docs);
const res = await generateOpenAIChatCompletion( const res = await generateOpenAIChatCompletion(
localStorage.token, localStorage.token,
{ {
...@@ -596,7 +576,8 @@ ...@@ -596,7 +576,8 @@
top_p: $settings?.options?.top_p ?? undefined, top_p: $settings?.options?.top_p ?? undefined,
num_ctx: $settings?.options?.num_ctx ?? undefined, num_ctx: $settings?.options?.num_ctx ?? undefined,
frequency_penalty: $settings?.options?.repeat_penalty ?? undefined, frequency_penalty: $settings?.options?.repeat_penalty ?? undefined,
max_tokens: $settings?.options?.num_predict ?? undefined max_tokens: $settings?.options?.num_predict ?? undefined,
docs: docs.length > 0 ? docs : undefined
}, },
model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}` model.source === 'litellm' ? `${LITELLM_API_BASE_URL}/v1` : `${OPENAI_API_BASE_URL}`
); );
...@@ -710,6 +691,7 @@ ...@@ -710,6 +691,7 @@
await setChatTitle(_chatId, userPrompt); await setChatTitle(_chatId, userPrompt);
} }
}; };
const stopResponse = () => { const stopResponse = () => {
stopResponseFlag = true; stopResponseFlag = true;
console.log('stopResponse'); console.log('stopResponse');
......
...@@ -267,7 +267,7 @@ ...@@ -267,7 +267,7 @@
<div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white"> <div class="min-h-screen max-h-[100dvh] w-full flex justify-center dark:text-white">
<div class=" flex flex-col justify-between w-full overflow-y-auto h-[100dvh]"> <div class=" flex flex-col justify-between w-full overflow-y-auto h-[100dvh]">
<div class="max-w-2xl mx-auto w-full px-3 p-3 md:px-0 h-full"> <div class="max-w-2xl mx-auto w-full px-3 md:px-0 my-10 h-full">
<div class=" flex flex-col h-full"> <div class=" flex flex-col h-full">
<div class="flex flex-col justify-between mb-2.5 gap-1"> <div class="flex flex-col justify-between mb-2.5 gap-1">
<div class="flex justify-between items-center gap-2"> <div class="flex justify-between items-center gap-2">
......
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