|
@@ -3,13 +3,13 @@
|
|
import { toast } from 'svelte-sonner';
|
|
import { toast } from 'svelte-sonner';
|
|
import mermaid from 'mermaid';
|
|
import mermaid from 'mermaid';
|
|
|
|
|
|
- import { getContext, onMount, tick } from 'svelte';
|
|
|
|
|
|
+ import { getContext, onDestroy, onMount, tick } from 'svelte';
|
|
import { goto } from '$app/navigation';
|
|
import { goto } from '$app/navigation';
|
|
import { page } from '$app/stores';
|
|
import { page } from '$app/stores';
|
|
|
|
|
|
- import type { Writable } from 'svelte/store';
|
|
|
|
|
|
+ import type { Unsubscriber, Writable } from 'svelte/store';
|
|
import type { i18n as i18nType } from 'i18next';
|
|
import type { i18n as i18nType } from 'i18next';
|
|
- import { OLLAMA_API_BASE_URL, OPENAI_API_BASE_URL, WEBUI_BASE_URL } from '$lib/constants';
|
|
|
|
|
|
+ import { WEBUI_BASE_URL } from '$lib/constants';
|
|
|
|
|
|
import {
|
|
import {
|
|
chatId,
|
|
chatId,
|
|
@@ -19,31 +19,26 @@
|
|
models,
|
|
models,
|
|
settings,
|
|
settings,
|
|
showSidebar,
|
|
showSidebar,
|
|
- tags as _tags,
|
|
|
|
WEBUI_NAME,
|
|
WEBUI_NAME,
|
|
banners,
|
|
banners,
|
|
user,
|
|
user,
|
|
socket,
|
|
socket,
|
|
showCallOverlay,
|
|
showCallOverlay,
|
|
- tools,
|
|
|
|
currentChatPage,
|
|
currentChatPage,
|
|
temporaryChatEnabled
|
|
temporaryChatEnabled
|
|
} from '$lib/stores';
|
|
} from '$lib/stores';
|
|
import {
|
|
import {
|
|
convertMessagesToHistory,
|
|
convertMessagesToHistory,
|
|
copyToClipboard,
|
|
copyToClipboard,
|
|
|
|
+ getMessageContentParts,
|
|
extractSentencesForAudio,
|
|
extractSentencesForAudio,
|
|
- getUserPosition,
|
|
|
|
promptTemplate,
|
|
promptTemplate,
|
|
splitStream
|
|
splitStream
|
|
} from '$lib/utils';
|
|
} from '$lib/utils';
|
|
|
|
|
|
import { generateChatCompletion } from '$lib/apis/ollama';
|
|
import { generateChatCompletion } from '$lib/apis/ollama';
|
|
import {
|
|
import {
|
|
- addTagById,
|
|
|
|
createNewChat,
|
|
createNewChat,
|
|
- deleteTagById,
|
|
|
|
- getAllChatTags,
|
|
|
|
getChatById,
|
|
getChatById,
|
|
getChatList,
|
|
getChatList,
|
|
getTagsById,
|
|
getTagsById,
|
|
@@ -66,8 +61,6 @@
|
|
import MessageInput from '$lib/components/chat/MessageInput.svelte';
|
|
import MessageInput from '$lib/components/chat/MessageInput.svelte';
|
|
import Messages from '$lib/components/chat/Messages.svelte';
|
|
import Messages from '$lib/components/chat/Messages.svelte';
|
|
import Navbar from '$lib/components/layout/Navbar.svelte';
|
|
import Navbar from '$lib/components/layout/Navbar.svelte';
|
|
- import CallOverlay from './MessageInput/CallOverlay.svelte';
|
|
|
|
- import { error } from '@sveltejs/kit';
|
|
|
|
import ChatControls from './ChatControls.svelte';
|
|
import ChatControls from './ChatControls.svelte';
|
|
import EventConfirmDialog from '../common/ConfirmDialog.svelte';
|
|
import EventConfirmDialog from '../common/ConfirmDialog.svelte';
|
|
|
|
|
|
@@ -118,6 +111,8 @@
|
|
|
|
|
|
let params = {};
|
|
let params = {};
|
|
|
|
|
|
|
|
+ let chatIdUnsubscriber: Unsubscriber | undefined;
|
|
|
|
+
|
|
$: if (history.currentId !== null) {
|
|
$: if (history.currentId !== null) {
|
|
let _messages = [];
|
|
let _messages = [];
|
|
|
|
|
|
@@ -207,47 +202,51 @@
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
|
|
- onMount(async () => {
|
|
|
|
- const onMessageHandler = async (event) => {
|
|
|
|
- if (event.origin === window.origin) {
|
|
|
|
- // Replace with your iframe's origin
|
|
|
|
- console.log('Message received from iframe:', event.data);
|
|
|
|
- if (event.data.type === 'input:prompt') {
|
|
|
|
- console.log(event.data.text);
|
|
|
|
-
|
|
|
|
- const inputElement = document.getElementById('chat-textarea');
|
|
|
|
-
|
|
|
|
- if (inputElement) {
|
|
|
|
- prompt = event.data.text;
|
|
|
|
- inputElement.focus();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ const onMessageHandler = async (event: {
|
|
|
|
+ origin: string;
|
|
|
|
+ data: { type: string; text: string };
|
|
|
|
+ }) => {
|
|
|
|
+ if (event.origin !== window.origin) {
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
|
|
- if (event.data.type === 'action:submit') {
|
|
|
|
- console.log(event.data.text);
|
|
|
|
|
|
+ // Replace with your iframe's origin
|
|
|
|
+ if (event.data.type === 'input:prompt') {
|
|
|
|
+ console.debug(event.data.text);
|
|
|
|
|
|
- if (prompt !== '') {
|
|
|
|
- await tick();
|
|
|
|
- submitPrompt(prompt);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ const inputElement = document.getElementById('chat-textarea');
|
|
|
|
|
|
- if (event.data.type === 'input:prompt:submit') {
|
|
|
|
- console.log(event.data.text);
|
|
|
|
|
|
+ if (inputElement) {
|
|
|
|
+ prompt = event.data.text;
|
|
|
|
+ inputElement.focus();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- if (prompt !== '') {
|
|
|
|
- await tick();
|
|
|
|
- submitPrompt(event.data.text);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ if (event.data.type === 'action:submit') {
|
|
|
|
+ console.debug(event.data.text);
|
|
|
|
+
|
|
|
|
+ if (prompt !== '') {
|
|
|
|
+ await tick();
|
|
|
|
+ submitPrompt(prompt);
|
|
}
|
|
}
|
|
- };
|
|
|
|
- window.addEventListener('message', onMessageHandler);
|
|
|
|
|
|
+ }
|
|
|
|
|
|
- $socket.on('chat-events', chatEventHandler);
|
|
|
|
|
|
+ if (event.data.type === 'input:prompt:submit') {
|
|
|
|
+ console.debug(event.data.text);
|
|
|
|
+
|
|
|
|
+ if (prompt !== '') {
|
|
|
|
+ await tick();
|
|
|
|
+ submitPrompt(event.data.text);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ onMount(async () => {
|
|
|
|
+ window.addEventListener('message', onMessageHandler);
|
|
|
|
+ $socket?.on('chat-events', chatEventHandler);
|
|
|
|
|
|
if (!$chatId) {
|
|
if (!$chatId) {
|
|
- chatId.subscribe(async (value) => {
|
|
|
|
|
|
+ chatIdUnsubscriber = chatId.subscribe(async (value) => {
|
|
if (!value) {
|
|
if (!value) {
|
|
await initNewChat();
|
|
await initNewChat();
|
|
}
|
|
}
|
|
@@ -257,12 +256,12 @@
|
|
await goto('/');
|
|
await goto('/');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ });
|
|
|
|
|
|
- return () => {
|
|
|
|
- window.removeEventListener('message', onMessageHandler);
|
|
|
|
-
|
|
|
|
- $socket.off('chat-events');
|
|
|
|
- };
|
|
|
|
|
|
+ onDestroy(() => {
|
|
|
|
+ chatIdUnsubscriber?.();
|
|
|
|
+ window.removeEventListener('message', onMessageHandler);
|
|
|
|
+ $socket?.off('chat-events');
|
|
});
|
|
});
|
|
|
|
|
|
//////////////////////////
|
|
//////////////////////////
|
|
@@ -595,11 +594,11 @@
|
|
};
|
|
};
|
|
|
|
|
|
const sendPrompt = async (
|
|
const sendPrompt = async (
|
|
- prompt,
|
|
|
|
- parentId,
|
|
|
|
|
|
+ prompt: string,
|
|
|
|
+ parentId: string,
|
|
{ modelId = null, modelIdx = null, newChat = false } = {}
|
|
{ modelId = null, modelIdx = null, newChat = false } = {}
|
|
) => {
|
|
) => {
|
|
- let _responses = [];
|
|
|
|
|
|
+ let _responses: string[] = [];
|
|
|
|
|
|
// If modelId is provided, use it, else use selected model
|
|
// If modelId is provided, use it, else use selected model
|
|
let selectedModelIds = modelId
|
|
let selectedModelIds = modelId
|
|
@@ -609,7 +608,7 @@
|
|
: selectedModels;
|
|
: selectedModels;
|
|
|
|
|
|
// Create response messages for each selected model
|
|
// Create response messages for each selected model
|
|
- const responseMessageIds = {};
|
|
|
|
|
|
+ const responseMessageIds: Record<PropertyKey, string> = {};
|
|
for (const [_modelIdx, modelId] of selectedModelIds.entries()) {
|
|
for (const [_modelIdx, modelId] of selectedModelIds.entries()) {
|
|
const model = $models.filter((m) => m.id === modelId).at(0);
|
|
const model = $models.filter((m) => m.id === modelId).at(0);
|
|
|
|
|
|
@@ -739,13 +738,13 @@
|
|
);
|
|
);
|
|
|
|
|
|
currentChatPage.set(1);
|
|
currentChatPage.set(1);
|
|
- await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
|
|
|
|
|
+ chats.set(await getChatList(localStorage.token, $currentChatPage));
|
|
|
|
|
|
return _responses;
|
|
return _responses;
|
|
};
|
|
};
|
|
|
|
|
|
const sendPromptOllama = async (model, userPrompt, responseMessageId, _chatId) => {
|
|
const sendPromptOllama = async (model, userPrompt, responseMessageId, _chatId) => {
|
|
- let _response = null;
|
|
|
|
|
|
+ let _response: string | null = null;
|
|
|
|
|
|
const responseMessage = history.messages[responseMessageId];
|
|
const responseMessage = history.messages[responseMessageId];
|
|
const userMessage = history.messages[responseMessage.parentId];
|
|
const userMessage = history.messages[responseMessage.parentId];
|
|
@@ -776,7 +775,7 @@
|
|
...messages
|
|
...messages
|
|
]
|
|
]
|
|
.filter((message) => message?.content?.trim())
|
|
.filter((message) => message?.content?.trim())
|
|
- .map((message, idx, arr) => {
|
|
|
|
|
|
+ .map((message) => {
|
|
// Prepare the base message object
|
|
// Prepare the base message object
|
|
const baseMessage = {
|
|
const baseMessage = {
|
|
role: message.role,
|
|
role: message.role,
|
|
@@ -928,18 +927,26 @@
|
|
navigator.vibrate(5);
|
|
navigator.vibrate(5);
|
|
}
|
|
}
|
|
|
|
|
|
- const sentences = extractSentencesForAudio(responseMessage.content);
|
|
|
|
- sentences.pop();
|
|
|
|
|
|
+ const messageContentParts = getMessageContentParts(
|
|
|
|
+ responseMessage.content,
|
|
|
|
+ $config?.audio?.tts?.split_on ?? 'punctuation'
|
|
|
|
+ );
|
|
|
|
+ messageContentParts.pop();
|
|
|
|
|
|
// dispatch only last sentence and make sure it hasn't been dispatched before
|
|
// dispatch only last sentence and make sure it hasn't been dispatched before
|
|
if (
|
|
if (
|
|
- sentences.length > 0 &&
|
|
|
|
- sentences[sentences.length - 1] !== responseMessage.lastSentence
|
|
|
|
|
|
+ messageContentParts.length > 0 &&
|
|
|
|
+ messageContentParts[messageContentParts.length - 1] !==
|
|
|
|
+ responseMessage.lastSentence
|
|
) {
|
|
) {
|
|
- responseMessage.lastSentence = sentences[sentences.length - 1];
|
|
|
|
|
|
+ responseMessage.lastSentence =
|
|
|
|
+ messageContentParts[messageContentParts.length - 1];
|
|
eventTarget.dispatchEvent(
|
|
eventTarget.dispatchEvent(
|
|
new CustomEvent('chat', {
|
|
new CustomEvent('chat', {
|
|
- detail: { id: responseMessageId, content: sentences[sentences.length - 1] }
|
|
|
|
|
|
+ detail: {
|
|
|
|
+ id: responseMessageId,
|
|
|
|
+ content: messageContentParts[messageContentParts.length - 1]
|
|
|
|
+ }
|
|
})
|
|
})
|
|
);
|
|
);
|
|
}
|
|
}
|
|
@@ -1042,14 +1049,19 @@
|
|
stopResponseFlag = false;
|
|
stopResponseFlag = false;
|
|
await tick();
|
|
await tick();
|
|
|
|
|
|
- let lastSentence = extractSentencesForAudio(responseMessage.content)?.at(-1) ?? '';
|
|
|
|
- if (lastSentence) {
|
|
|
|
|
|
+ let lastMessageContentPart =
|
|
|
|
+ getMessageContentParts(
|
|
|
|
+ responseMessage.content,
|
|
|
|
+ $config?.audio?.tts?.split_on ?? 'punctuation'
|
|
|
|
+ )?.at(-1) ?? '';
|
|
|
|
+ if (lastMessageContentPart) {
|
|
eventTarget.dispatchEvent(
|
|
eventTarget.dispatchEvent(
|
|
new CustomEvent('chat', {
|
|
new CustomEvent('chat', {
|
|
- detail: { id: responseMessageId, content: lastSentence }
|
|
|
|
|
|
+ detail: { id: responseMessageId, content: lastMessageContentPart }
|
|
})
|
|
})
|
|
);
|
|
);
|
|
}
|
|
}
|
|
|
|
+
|
|
eventTarget.dispatchEvent(
|
|
eventTarget.dispatchEvent(
|
|
new CustomEvent('chat:finish', {
|
|
new CustomEvent('chat:finish', {
|
|
detail: {
|
|
detail: {
|
|
@@ -1249,18 +1261,24 @@
|
|
navigator.vibrate(5);
|
|
navigator.vibrate(5);
|
|
}
|
|
}
|
|
|
|
|
|
- const sentences = extractSentencesForAudio(responseMessage.content);
|
|
|
|
- sentences.pop();
|
|
|
|
|
|
+ const messageContentParts = getMessageContentParts(
|
|
|
|
+ responseMessage.content,
|
|
|
|
+ $config?.audio?.tts?.split_on ?? 'punctuation'
|
|
|
|
+ );
|
|
|
|
+ messageContentParts.pop();
|
|
|
|
|
|
// dispatch only last sentence and make sure it hasn't been dispatched before
|
|
// dispatch only last sentence and make sure it hasn't been dispatched before
|
|
if (
|
|
if (
|
|
- sentences.length > 0 &&
|
|
|
|
- sentences[sentences.length - 1] !== responseMessage.lastSentence
|
|
|
|
|
|
+ messageContentParts.length > 0 &&
|
|
|
|
+ messageContentParts[messageContentParts.length - 1] !== responseMessage.lastSentence
|
|
) {
|
|
) {
|
|
- responseMessage.lastSentence = sentences[sentences.length - 1];
|
|
|
|
|
|
+ responseMessage.lastSentence = messageContentParts[messageContentParts.length - 1];
|
|
eventTarget.dispatchEvent(
|
|
eventTarget.dispatchEvent(
|
|
new CustomEvent('chat', {
|
|
new CustomEvent('chat', {
|
|
- detail: { id: responseMessageId, content: sentences[sentences.length - 1] }
|
|
|
|
|
|
+ detail: {
|
|
|
|
+ id: responseMessageId,
|
|
|
|
+ content: messageContentParts[messageContentParts.length - 1]
|
|
|
|
+ }
|
|
})
|
|
})
|
|
);
|
|
);
|
|
}
|
|
}
|
|
@@ -1315,11 +1333,15 @@
|
|
stopResponseFlag = false;
|
|
stopResponseFlag = false;
|
|
await tick();
|
|
await tick();
|
|
|
|
|
|
- let lastSentence = extractSentencesForAudio(responseMessage.content)?.at(-1) ?? '';
|
|
|
|
- if (lastSentence) {
|
|
|
|
|
|
+ let lastMessageContentPart =
|
|
|
|
+ getMessageContentParts(
|
|
|
|
+ responseMessage.content,
|
|
|
|
+ $config?.audio?.tts?.split_on ?? 'punctuation'
|
|
|
|
+ )?.at(-1) ?? '';
|
|
|
|
+ if (lastMessageContentPart) {
|
|
eventTarget.dispatchEvent(
|
|
eventTarget.dispatchEvent(
|
|
new CustomEvent('chat', {
|
|
new CustomEvent('chat', {
|
|
- detail: { id: responseMessageId, content: lastSentence }
|
|
|
|
|
|
+ detail: { id: responseMessageId, content: lastMessageContentPart }
|
|
})
|
|
})
|
|
);
|
|
);
|
|
}
|
|
}
|