forked from open-webui/open-webui
Merge branch 'dev' into buroa/hybrid-search
This commit is contained in:
commit
db801aee79
25 changed files with 701 additions and 204 deletions
70
src/lib/apis/streaming/index.ts
Normal file
70
src/lib/apis/streaming/index.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
type TextStreamUpdate = {
|
||||
done: boolean;
|
||||
value: string;
|
||||
};
|
||||
|
||||
// createOpenAITextStream takes a ReadableStreamDefaultReader from an SSE response,
|
||||
// and returns an async generator that emits delta updates with large deltas chunked into random sized chunks
|
||||
export async function createOpenAITextStream(
|
||||
messageStream: ReadableStreamDefaultReader,
|
||||
splitLargeDeltas: boolean
|
||||
): Promise<AsyncGenerator<TextStreamUpdate>> {
|
||||
let iterator = openAIStreamToIterator(messageStream);
|
||||
if (splitLargeDeltas) {
|
||||
iterator = streamLargeDeltasAsRandomChunks(iterator);
|
||||
}
|
||||
return iterator;
|
||||
}
|
||||
|
||||
async function* openAIStreamToIterator(
|
||||
reader: ReadableStreamDefaultReader
|
||||
): AsyncGenerator<TextStreamUpdate> {
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
if (done) {
|
||||
yield { done: true, value: '' };
|
||||
break;
|
||||
}
|
||||
const lines = value.split('\n');
|
||||
for (const line of lines) {
|
||||
if (line !== '') {
|
||||
console.log(line);
|
||||
if (line === 'data: [DONE]') {
|
||||
yield { done: true, value: '' };
|
||||
} else {
|
||||
const data = JSON.parse(line.replace(/^data: /, ''));
|
||||
console.log(data);
|
||||
|
||||
yield { done: false, value: data.choices[0].delta.content ?? '' };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// streamLargeDeltasAsRandomChunks will chunk large deltas (length > 5) into random sized chunks between 1-3 characters
|
||||
// This is to simulate a more fluid streaming, even though some providers may send large chunks of text at once
|
||||
async function* streamLargeDeltasAsRandomChunks(
|
||||
iterator: AsyncGenerator<TextStreamUpdate>
|
||||
): AsyncGenerator<TextStreamUpdate> {
|
||||
for await (const textStreamUpdate of iterator) {
|
||||
if (textStreamUpdate.done) {
|
||||
yield textStreamUpdate;
|
||||
return;
|
||||
}
|
||||
let content = textStreamUpdate.value;
|
||||
if (content.length < 5) {
|
||||
yield { done: false, value: content };
|
||||
continue;
|
||||
}
|
||||
while (content != '') {
|
||||
const chunkSize = Math.min(Math.floor(Math.random() * 3) + 1, content.length);
|
||||
const chunk = content.slice(0, chunkSize);
|
||||
yield { done: false, value: chunk };
|
||||
await sleep(5);
|
||||
content = content.slice(chunkSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
@ -1,6 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { downloadDatabase } from '$lib/apis/utils';
|
||||
import { onMount, getContext } from 'svelte';
|
||||
import { config } from '$lib/stores';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
|
@ -24,32 +25,34 @@
|
|||
<div class=" flex w-full justify-between">
|
||||
<!-- <div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Deletion')}</div> -->
|
||||
|
||||
<button
|
||||
class=" flex rounded-md py-1.5 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
// exportAllUserChats();
|
||||
{#if $config?.admin_export_enabled ?? true}
|
||||
<button
|
||||
class=" flex rounded-md py-1.5 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
// exportAllUserChats();
|
||||
|
||||
downloadDatabase(localStorage.token);
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM8.75 7.75a.75.75 0 0 0-1.5 0v2.69L6.03 9.22a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06l-1.22 1.22V7.75Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class=" self-center text-sm font-medium">{$i18n.t('Download Database')}</div>
|
||||
</button>
|
||||
downloadDatabase(localStorage.token);
|
||||
}}
|
||||
>
|
||||
<div class=" self-center mr-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
class="w-4 h-4"
|
||||
>
|
||||
<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM8.75 7.75a.75.75 0 0 0-1.5 0v2.69L6.03 9.22a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06l-1.22 1.22V7.75Z"
|
||||
clip-rule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class=" self-center text-sm font-medium">{$i18n.t('Download Database')}</div>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -316,24 +316,22 @@
|
|||
console.log(e);
|
||||
|
||||
if (e.dataTransfer?.files) {
|
||||
let reader = new FileReader();
|
||||
|
||||
reader.onload = (event) => {
|
||||
files = [
|
||||
...files,
|
||||
{
|
||||
type: 'image',
|
||||
url: `${event.target.result}`
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const inputFiles = Array.from(e.dataTransfer?.files);
|
||||
|
||||
if (inputFiles && inputFiles.length > 0) {
|
||||
inputFiles.forEach((file) => {
|
||||
console.log(file, file.name.split('.').at(-1));
|
||||
if (['image/gif', 'image/jpeg', 'image/png'].includes(file['type'])) {
|
||||
let reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
files = [
|
||||
...files,
|
||||
{
|
||||
type: 'image',
|
||||
url: `${event.target.result}`
|
||||
}
|
||||
];
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} else if (
|
||||
SUPPORTED_FILE_TYPE.includes(file['type']) ||
|
||||
|
@ -470,23 +468,22 @@
|
|||
hidden
|
||||
multiple
|
||||
on:change={async () => {
|
||||
let reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
files = [
|
||||
...files,
|
||||
{
|
||||
type: 'image',
|
||||
url: `${event.target.result}`
|
||||
}
|
||||
];
|
||||
inputFiles = null;
|
||||
filesInputElement.value = '';
|
||||
};
|
||||
|
||||
if (inputFiles && inputFiles.length > 0) {
|
||||
const _inputFiles = Array.from(inputFiles);
|
||||
_inputFiles.forEach((file) => {
|
||||
if (['image/gif', 'image/jpeg', 'image/png'].includes(file['type'])) {
|
||||
let reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
files = [
|
||||
...files,
|
||||
{
|
||||
type: 'image',
|
||||
url: `${event.target.result}`
|
||||
}
|
||||
];
|
||||
inputFiles = null;
|
||||
filesInputElement.value = '';
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} else if (
|
||||
SUPPORTED_FILE_TYPE.includes(file['type']) ||
|
||||
|
|
|
@ -301,7 +301,7 @@
|
|||
</button>
|
||||
{/if}
|
||||
|
||||
{#if $user?.role === 'admin'}
|
||||
{#if $user?.role === 'admin' && ($config?.admin_export_enabled ?? true)}
|
||||
<hr class=" dark:border-gray-700" />
|
||||
|
||||
<button
|
||||
|
|
|
@ -17,11 +17,17 @@
|
|||
let titleAutoGenerateModelExternal = '';
|
||||
let fullScreenMode = false;
|
||||
let titleGenerationPrompt = '';
|
||||
let splitLargeChunks = false;
|
||||
|
||||
// Interface
|
||||
let promptSuggestions = [];
|
||||
let showUsername = false;
|
||||
|
||||
const toggleSplitLargeChunks = async () => {
|
||||
splitLargeChunks = !splitLargeChunks;
|
||||
saveSettings({ splitLargeChunks: splitLargeChunks });
|
||||
};
|
||||
|
||||
const toggleFullScreenMode = async () => {
|
||||
fullScreenMode = !fullScreenMode;
|
||||
saveSettings({ fullScreenMode: fullScreenMode });
|
||||
|
@ -197,6 +203,28 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class=" py-0.5 flex w-full justify-between">
|
||||
<div class=" self-center text-xs font-medium">
|
||||
{$i18n.t('Fluidly stream large external response chunks')}
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="p-1 px-3 text-xs flex rounded transition"
|
||||
on:click={() => {
|
||||
toggleSplitLargeChunks();
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{#if splitLargeChunks === true}
|
||||
<span class="ml-2 self-center">{$i18n.t('On')}</span>
|
||||
{:else}
|
||||
<span class="ml-2 self-center">{$i18n.t('Off')}</span>
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class=" dark:border-gray-700" />
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
let liteLLMRPM = '';
|
||||
let liteLLMMaxTokens = '';
|
||||
|
||||
let deleteLiteLLMModelId = '';
|
||||
let deleteLiteLLMModelName = '';
|
||||
|
||||
$: liteLLMModelName = liteLLMModel;
|
||||
|
||||
|
@ -472,7 +472,7 @@
|
|||
};
|
||||
|
||||
const deleteLiteLLMModelHandler = async () => {
|
||||
const res = await deleteLiteLLMModel(localStorage.token, deleteLiteLLMModelId).catch(
|
||||
const res = await deleteLiteLLMModel(localStorage.token, deleteLiteLLMModelName).catch(
|
||||
(error) => {
|
||||
toast.error(error);
|
||||
return null;
|
||||
|
@ -485,7 +485,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
deleteLiteLLMModelId = '';
|
||||
deleteLiteLLMModelName = '';
|
||||
liteLLMModelInfo = await getLiteLLMModelInfo(localStorage.token);
|
||||
models.set(await getModels());
|
||||
};
|
||||
|
@ -1099,14 +1099,14 @@
|
|||
<div class="flex-1 mr-2">
|
||||
<select
|
||||
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={deleteLiteLLMModelName}
|
||||
placeholder={$i18n.t('Select a model')}
|
||||
>
|
||||
{#if !deleteLiteLLMModelId}
|
||||
{#if !deleteLiteLLMModelName}
|
||||
<option value="" disabled selected>{$i18n.t('Select a model')}</option>
|
||||
{/if}
|
||||
{#each liteLLMModelInfo as model}
|
||||
<option value={model.model_info.id} class="bg-gray-100 dark:bg-gray-700"
|
||||
<option value={model.model_name} class="bg-gray-100 dark:bg-gray-700"
|
||||
>{model.model_name}</option
|
||||
>
|
||||
{/each}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import { toast } from 'svelte-sonner';
|
||||
import { models, settings, user } from '$lib/stores';
|
||||
|
||||
import { getModels } from '$lib/utils';
|
||||
import { getModels as _getModels } from '$lib/utils';
|
||||
|
||||
import Modal from '../common/Modal.svelte';
|
||||
import Account from './Settings/Account.svelte';
|
||||
|
@ -23,10 +23,14 @@
|
|||
const saveSettings = async (updated) => {
|
||||
console.log(updated);
|
||||
await settings.set({ ...$settings, ...updated });
|
||||
await models.set(await getModels(localStorage.token));
|
||||
await models.set(await getModels());
|
||||
localStorage.setItem('settings', JSON.stringify($settings));
|
||||
};
|
||||
|
||||
const getModels = async () => {
|
||||
return await _getModels(localStorage.token);
|
||||
};
|
||||
|
||||
let selectedTab = 'general';
|
||||
</script>
|
||||
|
||||
|
|
|
@ -134,11 +134,36 @@
|
|||
<button
|
||||
class=" self-center flex items-center gap-1 px-3.5 py-2 rounded-xl text-sm font-medium bg-emerald-600 hover:bg-emerald-500 text-white"
|
||||
type="button"
|
||||
on:pointerdown={() => {
|
||||
shareLocalChat();
|
||||
}}
|
||||
on:click={async () => {
|
||||
copyToClipboard(shareUrl);
|
||||
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
|
||||
if (isSafari) {
|
||||
// Oh, Safari, you're so special, let's give you some extra love and attention
|
||||
console.log('isSafari');
|
||||
|
||||
const getUrlPromise = async () => {
|
||||
const url = await shareLocalChat();
|
||||
return new Blob([url], { type: 'text/plain' });
|
||||
};
|
||||
|
||||
navigator.clipboard
|
||||
.write([
|
||||
new ClipboardItem({
|
||||
'text/plain': getUrlPromise()
|
||||
})
|
||||
])
|
||||
.then(() => {
|
||||
console.log('Async: Copying to clipboard was successful!');
|
||||
return true;
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Async: Could not copy text: ', error);
|
||||
return false;
|
||||
});
|
||||
} else {
|
||||
copyToClipboard(await shareLocalChat());
|
||||
}
|
||||
|
||||
toast.success($i18n.t('Copied shared chat URL to clipboard!'));
|
||||
show = false;
|
||||
}}
|
||||
|
|
|
@ -67,7 +67,7 @@
|
|||
<div class="flex flex-col md:flex-row w-full px-5 py-4 md:space-x-4 dark:text-gray-200">
|
||||
<div class=" flex flex-col w-full sm:flex-row sm:justify-center sm:space-x-6">
|
||||
{#if chats.length > 0}
|
||||
<div class="text-left text-sm w-full mb-4">
|
||||
<div class="text-left text-sm w-full mb-4 max-h-[22rem] overflow-y-scroll">
|
||||
<div class="relative overflow-x-auto">
|
||||
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400 table-auto">
|
||||
<thead
|
||||
|
@ -75,7 +75,7 @@
|
|||
>
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-2"> {$i18n.t('Name')} </th>
|
||||
<th scope="col" class="px-3 py-2"> {$i18n.t('Created At')} </th>
|
||||
<th scope="col" class="px-3 py-2 hidden md:flex"> {$i18n.t('Created At')} </th>
|
||||
<th scope="col" class="px-3 py-2 text-right" />
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -93,8 +93,10 @@
|
|||
</a>
|
||||
</td>
|
||||
|
||||
<td class=" px-3 py-1">
|
||||
{dayjs(chat.created_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))}
|
||||
<td class=" px-3 py-1 hidden md:flex h-[2.5rem]">
|
||||
<div class="my-auto">
|
||||
{dayjs(chat.created_at * 1000).format($i18n.t('MMMM DD, YYYY HH:mm'))}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td class="px-3 py-1 text-right">
|
||||
|
|
|
@ -152,6 +152,7 @@
|
|||
"File Mode": "",
|
||||
"File not found.": "",
|
||||
"Fingerprint spoofing detected: Unable to use initials as avatar. Defaulting to default profile image.": "",
|
||||
"Fluidly stream large external response chunks": "",
|
||||
"Focus chat input": "",
|
||||
"Format your variables using square brackets like this:": "",
|
||||
"From (Base Model)": "",
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { APP_NAME } from '$lib/constants';
|
||||
import { writable } from 'svelte/store';
|
||||
import { type Writable, writable } from 'svelte/store';
|
||||
|
||||
// Backend
|
||||
export const WEBUI_NAME = writable(APP_NAME);
|
||||
export const config = writable(undefined);
|
||||
export const user = writable(undefined);
|
||||
export const config: Writable<Config | undefined> = writable(undefined);
|
||||
export const user: Writable<SessionUser | undefined> = writable(undefined);
|
||||
|
||||
// Frontend
|
||||
export const MODEL_DOWNLOAD_POOL = writable({});
|
||||
|
@ -14,10 +14,10 @@ export const chatId = writable('');
|
|||
|
||||
export const chats = writable([]);
|
||||
export const tags = writable([]);
|
||||
export const models = writable([]);
|
||||
export const models: Writable<Model[]> = writable([]);
|
||||
|
||||
export const modelfiles = writable([]);
|
||||
export const prompts = writable([]);
|
||||
export const prompts: Writable<Prompt[]> = writable([]);
|
||||
export const documents = writable([
|
||||
{
|
||||
collection_name: 'collection_name',
|
||||
|
@ -33,6 +33,109 @@ export const documents = writable([
|
|||
}
|
||||
]);
|
||||
|
||||
export const settings = writable({});
|
||||
export const settings: Writable<Settings> = writable({});
|
||||
export const showSettings = writable(false);
|
||||
export const showChangelog = writable(false);
|
||||
|
||||
type Model = OpenAIModel | OllamaModel;
|
||||
|
||||
type OpenAIModel = {
|
||||
id: string;
|
||||
name: string;
|
||||
external: boolean;
|
||||
source?: string;
|
||||
};
|
||||
|
||||
type OllamaModel = {
|
||||
id: string;
|
||||
name: string;
|
||||
|
||||
// Ollama specific fields
|
||||
details: OllamaModelDetails;
|
||||
size: number;
|
||||
description: string;
|
||||
model: string;
|
||||
modified_at: string;
|
||||
digest: string;
|
||||
};
|
||||
|
||||
type OllamaModelDetails = {
|
||||
parent_model: string;
|
||||
format: string;
|
||||
family: string;
|
||||
families: string[] | null;
|
||||
parameter_size: string;
|
||||
quantization_level: string;
|
||||
};
|
||||
|
||||
type Settings = {
|
||||
models?: string[];
|
||||
conversationMode?: boolean;
|
||||
speechAutoSend?: boolean;
|
||||
responseAutoPlayback?: boolean;
|
||||
audio?: AudioSettings;
|
||||
showUsername?: boolean;
|
||||
saveChatHistory?: boolean;
|
||||
notificationEnabled?: boolean;
|
||||
title?: TitleSettings;
|
||||
|
||||
system?: string;
|
||||
requestFormat?: string;
|
||||
keepAlive?: string;
|
||||
seed?: number;
|
||||
temperature?: string;
|
||||
repeat_penalty?: string;
|
||||
top_k?: string;
|
||||
top_p?: string;
|
||||
num_ctx?: string;
|
||||
options?: ModelOptions;
|
||||
};
|
||||
|
||||
type ModelOptions = {
|
||||
stop?: boolean;
|
||||
};
|
||||
|
||||
type AudioSettings = {
|
||||
STTEngine?: string;
|
||||
TTSEngine?: string;
|
||||
speaker?: string;
|
||||
};
|
||||
|
||||
type TitleSettings = {
|
||||
auto?: boolean;
|
||||
model?: string;
|
||||
modelExternal?: string;
|
||||
prompt?: string;
|
||||
};
|
||||
|
||||
type Prompt = {
|
||||
command: string;
|
||||
user_id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
timestamp: number;
|
||||
};
|
||||
|
||||
type Config = {
|
||||
status?: boolean;
|
||||
name?: string;
|
||||
version?: string;
|
||||
default_locale?: string;
|
||||
images?: boolean;
|
||||
default_models?: string[];
|
||||
default_prompt_suggestions?: PromptSuggestion[];
|
||||
trusted_header_auth?: boolean;
|
||||
};
|
||||
|
||||
type PromptSuggestion = {
|
||||
content: string;
|
||||
title: [string, string];
|
||||
};
|
||||
|
||||
type SessionUser = {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
role: string;
|
||||
profile_image_url: string;
|
||||
};
|
||||
|
|
|
@ -35,7 +35,6 @@ export const sanitizeResponseContent = (content: string) => {
|
|||
.replace(/<\|[a-z]+\|$/, '')
|
||||
.replace(/<$/, '')
|
||||
.replaceAll(/<\|[a-z]+\|>/g, ' ')
|
||||
.replaceAll(/<br\s?\/?>/gi, '\n')
|
||||
.replaceAll('<', '<')
|
||||
.trim();
|
||||
};
|
||||
|
|
|
@ -39,6 +39,7 @@
|
|||
import { RAGTemplate } from '$lib/utils/rag';
|
||||
import { LITELLM_API_BASE_URL, OLLAMA_API_BASE_URL, OPENAI_API_BASE_URL } from '$lib/constants';
|
||||
import { WEBUI_BASE_URL } from '$lib/constants';
|
||||
import { createOpenAITextStream } from '$lib/apis/streaming';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
|
@ -599,38 +600,22 @@
|
|||
.pipeThrough(splitStream('\n'))
|
||||
.getReader();
|
||||
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
const textStream = await createOpenAITextStream(reader, $settings.splitLargeChunks);
|
||||
console.log(textStream);
|
||||
|
||||
for await (const update of textStream) {
|
||||
const { value, done } = update;
|
||||
if (done || stopResponseFlag || _chatId !== $chatId) {
|
||||
responseMessage.done = true;
|
||||
messages = messages;
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
let lines = value.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line !== '') {
|
||||
console.log(line);
|
||||
if (line === 'data: [DONE]') {
|
||||
responseMessage.done = true;
|
||||
messages = messages;
|
||||
} else {
|
||||
let data = JSON.parse(line.replace(/^data: /, ''));
|
||||
console.log(data);
|
||||
|
||||
if (responseMessage.content == '' && data.choices[0].delta.content == '\n') {
|
||||
continue;
|
||||
} else {
|
||||
responseMessage.content += data.choices[0].delta.content ?? '';
|
||||
messages = messages;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
if (responseMessage.content == '' && value == '\n') {
|
||||
continue;
|
||||
} else {
|
||||
responseMessage.content += value;
|
||||
messages = messages;
|
||||
}
|
||||
|
||||
if ($settings.notificationEnabled && !document.hasFocus()) {
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
OLLAMA_API_BASE_URL,
|
||||
WEBUI_BASE_URL
|
||||
} from '$lib/constants';
|
||||
import { createOpenAITextStream } from '$lib/apis/streaming';
|
||||
|
||||
const i18n = getContext('i18n');
|
||||
|
||||
|
@ -611,38 +612,22 @@
|
|||
.pipeThrough(splitStream('\n'))
|
||||
.getReader();
|
||||
|
||||
while (true) {
|
||||
const { value, done } = await reader.read();
|
||||
const textStream = await createOpenAITextStream(reader, $settings.splitLargeChunks);
|
||||
console.log(textStream);
|
||||
|
||||
for await (const update of textStream) {
|
||||
const { value, done } = update;
|
||||
if (done || stopResponseFlag || _chatId !== $chatId) {
|
||||
responseMessage.done = true;
|
||||
messages = messages;
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
let lines = value.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line !== '') {
|
||||
console.log(line);
|
||||
if (line === 'data: [DONE]') {
|
||||
responseMessage.done = true;
|
||||
messages = messages;
|
||||
} else {
|
||||
let data = JSON.parse(line.replace(/^data: /, ''));
|
||||
console.log(data);
|
||||
|
||||
if (responseMessage.content == '' && data.choices[0].delta.content == '\n') {
|
||||
continue;
|
||||
} else {
|
||||
responseMessage.content += data.choices[0].delta.content ?? '';
|
||||
messages = messages;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
if (responseMessage.content == '' && value == '\n') {
|
||||
continue;
|
||||
} else {
|
||||
responseMessage.content += value;
|
||||
messages = messages;
|
||||
}
|
||||
|
||||
if ($settings.notificationEnabled && !document.hasFocus()) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue