mirror of
https://github.com/open-webui/open-webui
synced 2025-06-04 03:37:35 +00:00
Merge pull request #5640 from open-webui/messages-render-optimisation
refac: messages render optimisation
This commit is contained in:
commit
35f64cc53f
@ -5,6 +5,8 @@
|
|||||||
import { PaneGroup, Pane, PaneResizer } from 'paneforge';
|
import { PaneGroup, Pane, PaneResizer } from 'paneforge';
|
||||||
|
|
||||||
import { getContext, onDestroy, onMount, tick } from 'svelte';
|
import { getContext, onDestroy, onMount, tick } from 'svelte';
|
||||||
|
const i18n: Writable<i18nType> = getContext('i18n');
|
||||||
|
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
@ -67,11 +69,9 @@
|
|||||||
import Navbar from '$lib/components/layout/Navbar.svelte';
|
import Navbar from '$lib/components/layout/Navbar.svelte';
|
||||||
import ChatControls from './ChatControls.svelte';
|
import ChatControls from './ChatControls.svelte';
|
||||||
import EventConfirmDialog from '../common/ConfirmDialog.svelte';
|
import EventConfirmDialog from '../common/ConfirmDialog.svelte';
|
||||||
import EllipsisVertical from '../icons/EllipsisVertical.svelte';
|
|
||||||
|
|
||||||
const i18n: Writable<i18nType> = getContext('i18n');
|
|
||||||
|
|
||||||
export let chatIdProp = '';
|
export let chatIdProp = '';
|
||||||
|
|
||||||
let loaded = false;
|
let loaded = false;
|
||||||
const eventTarget = new EventTarget();
|
const eventTarget = new EventTarget();
|
||||||
let controlPane;
|
let controlPane;
|
||||||
@ -89,9 +89,10 @@
|
|||||||
let eventConfirmationInputValue = '';
|
let eventConfirmationInputValue = '';
|
||||||
let eventCallback = null;
|
let eventCallback = null;
|
||||||
|
|
||||||
|
let chatIdUnsubscriber: Unsubscriber | undefined;
|
||||||
|
|
||||||
let selectedModels = [''];
|
let selectedModels = [''];
|
||||||
let atSelectedModel: Model | undefined;
|
let atSelectedModel: Model | undefined;
|
||||||
|
|
||||||
let selectedModelIds = [];
|
let selectedModelIds = [];
|
||||||
$: selectedModelIds = atSelectedModel !== undefined ? [atSelectedModel.id] : selectedModels;
|
$: selectedModelIds = atSelectedModel !== undefined ? [atSelectedModel.id] : selectedModels;
|
||||||
|
|
||||||
@ -102,35 +103,17 @@
|
|||||||
let tags = [];
|
let tags = [];
|
||||||
|
|
||||||
let title = '';
|
let title = '';
|
||||||
let prompt = '';
|
|
||||||
|
|
||||||
let chatFiles = [];
|
|
||||||
let files = [];
|
|
||||||
let messages = [];
|
|
||||||
let history = {
|
let history = {
|
||||||
messages: {},
|
messages: {},
|
||||||
currentId: null
|
currentId: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Chat Input
|
||||||
|
let prompt = '';
|
||||||
|
let chatFiles = [];
|
||||||
|
let files = [];
|
||||||
let params = {};
|
let params = {};
|
||||||
|
|
||||||
let chatIdUnsubscriber: Unsubscriber | undefined;
|
|
||||||
|
|
||||||
$: if (history.currentId !== null) {
|
|
||||||
let _messages = [];
|
|
||||||
let currentMessage = history.messages[history.currentId];
|
|
||||||
while (currentMessage) {
|
|
||||||
_messages.unshift({ ...currentMessage });
|
|
||||||
currentMessage =
|
|
||||||
currentMessage.parentId !== null ? history.messages[currentMessage.parentId] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is most likely causing the performance issue
|
|
||||||
messages = _messages;
|
|
||||||
} else {
|
|
||||||
messages = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
$: if (chatIdProp) {
|
$: if (chatIdProp) {
|
||||||
(async () => {
|
(async () => {
|
||||||
console.log(chatIdProp);
|
console.log(chatIdProp);
|
||||||
@ -227,8 +210,6 @@
|
|||||||
} else {
|
} else {
|
||||||
console.log('Unknown message type', data);
|
console.log('Unknown message type', data);
|
||||||
}
|
}
|
||||||
|
|
||||||
messages = messages;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -310,6 +291,9 @@
|
|||||||
showOverview.set(false);
|
showOverview.set(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const chatInput = document.getElementById('chat-textarea');
|
||||||
|
chatInput?.focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
@ -331,7 +315,6 @@
|
|||||||
autoScroll = true;
|
autoScroll = true;
|
||||||
|
|
||||||
title = '';
|
title = '';
|
||||||
messages = [];
|
|
||||||
history = {
|
history = {
|
||||||
messages: {},
|
messages: {},
|
||||||
currentId: null
|
currentId: null
|
||||||
@ -428,8 +411,8 @@
|
|||||||
autoScroll = true;
|
autoScroll = true;
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
if (messages.length > 0) {
|
if (history.currentId) {
|
||||||
history.messages[messages.at(-1).id].done = true;
|
history.messages[history.currentId].done = true;
|
||||||
}
|
}
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
@ -448,8 +431,12 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createMessagesList = (responseMessageId) => {
|
const createMessagesList = (responseMessageId) => {
|
||||||
|
if (responseMessageId === null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const message = history.messages[responseMessageId];
|
const message = history.messages[responseMessageId];
|
||||||
if (message.parentId) {
|
if (message?.parentId) {
|
||||||
return [...createMessagesList(message.parentId), message];
|
return [...createMessagesList(message.parentId), message];
|
||||||
} else {
|
} else {
|
||||||
return [message];
|
return [message];
|
||||||
@ -510,6 +497,8 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const chatActionHandler = async (chatId, actionId, modelId, responseMessageId, event = null) => {
|
const chatActionHandler = async (chatId, actionId, modelId, responseMessageId, event = null) => {
|
||||||
|
const messages = createMessagesList(responseMessageId);
|
||||||
|
|
||||||
const res = await chatAction(localStorage.token, actionId, {
|
const res = await chatAction(localStorage.token, actionId, {
|
||||||
model: modelId,
|
model: modelId,
|
||||||
messages: messages.map((m) => ({
|
messages: messages.map((m) => ({
|
||||||
@ -575,6 +564,7 @@
|
|||||||
const submitPrompt = async (userPrompt, { _raw = false } = {}) => {
|
const submitPrompt = async (userPrompt, { _raw = false } = {}) => {
|
||||||
let _responses = [];
|
let _responses = [];
|
||||||
console.log('submitPrompt', $chatId);
|
console.log('submitPrompt', $chatId);
|
||||||
|
const messages = createMessagesList(history.currentId);
|
||||||
|
|
||||||
selectedModels = selectedModels.map((modelId) =>
|
selectedModels = selectedModels.map((modelId) =>
|
||||||
$models.map((m) => m.id).includes(modelId) ? modelId : ''
|
$models.map((m) => m.id).includes(modelId) ? modelId : ''
|
||||||
@ -668,8 +658,34 @@
|
|||||||
parentId: string,
|
parentId: string,
|
||||||
{ modelId = null, modelIdx = null, newChat = false } = {}
|
{ modelId = null, modelIdx = null, newChat = false } = {}
|
||||||
) => {
|
) => {
|
||||||
let _responses: string[] = [];
|
// Create new chat if newChat is true and first user message
|
||||||
|
if (
|
||||||
|
newChat &&
|
||||||
|
history.messages[history.currentId].parentId === null &&
|
||||||
|
history.messages[history.currentId].role === 'user'
|
||||||
|
) {
|
||||||
|
if (!$temporaryChatEnabled) {
|
||||||
|
chat = await createNewChat(localStorage.token, {
|
||||||
|
id: $chatId,
|
||||||
|
title: $i18n.t('New Chat'),
|
||||||
|
models: selectedModels,
|
||||||
|
system: $settings.system ?? undefined,
|
||||||
|
params: params,
|
||||||
|
history: history,
|
||||||
|
tags: [],
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
currentChatPage.set(1);
|
||||||
|
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||||
|
await chatId.set(chat.id);
|
||||||
|
} else {
|
||||||
|
await chatId.set('local');
|
||||||
|
}
|
||||||
|
await tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
? [modelId]
|
? [modelId]
|
||||||
@ -714,38 +730,14 @@
|
|||||||
}
|
}
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
// Create new chat if only one message in messages
|
|
||||||
if (newChat && messages.length == 2) {
|
|
||||||
if (!$temporaryChatEnabled) {
|
|
||||||
chat = await createNewChat(localStorage.token, {
|
|
||||||
id: $chatId,
|
|
||||||
title: $i18n.t('New Chat'),
|
|
||||||
models: selectedModels,
|
|
||||||
system: $settings.system ?? undefined,
|
|
||||||
params: params,
|
|
||||||
messages: messages,
|
|
||||||
history: history,
|
|
||||||
tags: [],
|
|
||||||
timestamp: Date.now()
|
|
||||||
});
|
|
||||||
|
|
||||||
currentChatPage.set(1);
|
|
||||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
|
||||||
await chatId.set(chat.id);
|
|
||||||
} else {
|
|
||||||
await chatId.set('local');
|
|
||||||
}
|
|
||||||
await tick();
|
|
||||||
}
|
|
||||||
|
|
||||||
const _chatId = JSON.parse(JSON.stringify($chatId));
|
const _chatId = JSON.parse(JSON.stringify($chatId));
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
selectedModelIds.map(async (modelId, _modelIdx) => {
|
selectedModelIds.map(async (modelId, _modelIdx) => {
|
||||||
console.log('modelId', modelId);
|
console.log('modelId', modelId);
|
||||||
const model = $models.filter((m) => m.id === modelId).at(0);
|
const model = $models.filter((m) => m.id === modelId).at(0);
|
||||||
|
|
||||||
if (model) {
|
if (model) {
|
||||||
|
const messages = createMessagesList(parentId);
|
||||||
// If there are image files, check if model is vision capable
|
// If there are image files, check if model is vision capable
|
||||||
const hasImages = messages.some((message) =>
|
const hasImages = messages.some((message) =>
|
||||||
message.files?.some((file) => file.type === 'image')
|
message.files?.some((file) => file.type === 'image')
|
||||||
@ -844,7 +836,7 @@
|
|||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
...messages
|
...createMessagesList(responseMessageId)
|
||||||
]
|
]
|
||||||
.filter((message) => message?.content?.trim())
|
.filter((message) => message?.content?.trim())
|
||||||
.map((message) => {
|
.map((message) => {
|
||||||
@ -895,7 +887,7 @@
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
files.push(...model.info.meta.knowledge);
|
files.push(...model.info.meta.knowledge);
|
||||||
messages = messages; // Trigger Svelte update
|
history.messages[responseMessageId] = responseMessage;
|
||||||
}
|
}
|
||||||
files.push(
|
files.push(
|
||||||
...(userMessage?.files ?? []).filter((item) =>
|
...(userMessage?.files ?? []).filter((item) =>
|
||||||
@ -969,7 +961,7 @@
|
|||||||
const { value, done } = await reader.read();
|
const { value, done } = await reader.read();
|
||||||
if (done || stopResponseFlag || _chatId !== $chatId) {
|
if (done || stopResponseFlag || _chatId !== $chatId) {
|
||||||
responseMessage.done = true;
|
responseMessage.done = true;
|
||||||
messages = messages;
|
history.messages[responseMessageId] = responseMessage;
|
||||||
|
|
||||||
if (stopResponseFlag) {
|
if (stopResponseFlag) {
|
||||||
controller.abort('User: Stop Response');
|
controller.abort('User: Stop Response');
|
||||||
@ -1036,7 +1028,7 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
messages = messages;
|
history.messages[responseMessageId] = responseMessage;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
responseMessage.done = true;
|
responseMessage.done = true;
|
||||||
@ -1059,7 +1051,8 @@
|
|||||||
eval_count: data.eval_count,
|
eval_count: data.eval_count,
|
||||||
eval_duration: data.eval_duration
|
eval_duration: data.eval_duration
|
||||||
};
|
};
|
||||||
messages = messages;
|
|
||||||
|
history.messages[responseMessageId] = responseMessage;
|
||||||
|
|
||||||
if ($settings.notificationEnabled && !document.hasFocus()) {
|
if ($settings.notificationEnabled && !document.hasFocus()) {
|
||||||
const notification = new Notification(`${model.id}`, {
|
const notification = new Notification(`${model.id}`, {
|
||||||
@ -1128,7 +1121,7 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
messages = messages;
|
history.messages[responseMessageId] = responseMessage;
|
||||||
}
|
}
|
||||||
await saveChatHandler(_chatId);
|
await saveChatHandler(_chatId);
|
||||||
|
|
||||||
@ -1161,7 +1154,8 @@
|
|||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (messages.length == 2 && messages.at(1).content !== '' && selectedModels[0] === model.id) {
|
const messages = createMessagesList(responseMessageId);
|
||||||
|
if (messages.length == 2 && messages.at(-1).content !== '' && selectedModels[0] === model.id) {
|
||||||
window.history.replaceState(history.state, '', `/c/${_chatId}`);
|
window.history.replaceState(history.state, '', `/c/${_chatId}`);
|
||||||
const _title = await generateChatTitle(userPrompt);
|
const _title = await generateChatTitle(userPrompt);
|
||||||
await setChatTitle(_chatId, _title);
|
await setChatTitle(_chatId, _title);
|
||||||
@ -1189,7 +1183,7 @@
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
files.push(...model.info.meta.knowledge);
|
files.push(...model.info.meta.knowledge);
|
||||||
messages = messages; // Trigger Svelte update
|
history.messages[responseMessageId] = responseMessage;
|
||||||
}
|
}
|
||||||
files.push(
|
files.push(
|
||||||
...(userMessage?.files ?? []).filter((item) =>
|
...(userMessage?.files ?? []).filter((item) =>
|
||||||
@ -1240,7 +1234,7 @@
|
|||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
...messages
|
...createMessagesList(responseMessageId)
|
||||||
]
|
]
|
||||||
.filter((message) => message?.content?.trim())
|
.filter((message) => message?.content?.trim())
|
||||||
.map((message, idx, arr) => ({
|
.map((message, idx, arr) => ({
|
||||||
@ -1318,7 +1312,7 @@
|
|||||||
}
|
}
|
||||||
if (done || stopResponseFlag || _chatId !== $chatId) {
|
if (done || stopResponseFlag || _chatId !== $chatId) {
|
||||||
responseMessage.done = true;
|
responseMessage.done = true;
|
||||||
messages = messages;
|
history.messages[responseMessageId] = responseMessage;
|
||||||
|
|
||||||
if (stopResponseFlag) {
|
if (stopResponseFlag) {
|
||||||
controller.abort('User: Stop Response');
|
controller.abort('User: Stop Response');
|
||||||
@ -1373,7 +1367,7 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
messages = messages;
|
history.messages[responseMessageId] = responseMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (autoScroll) {
|
if (autoScroll) {
|
||||||
@ -1414,7 +1408,7 @@
|
|||||||
|
|
||||||
await saveChatHandler(_chatId);
|
await saveChatHandler(_chatId);
|
||||||
|
|
||||||
messages = messages;
|
history.messages[responseMessageId] = responseMessage;
|
||||||
|
|
||||||
stopResponseFlag = false;
|
stopResponseFlag = false;
|
||||||
await tick();
|
await tick();
|
||||||
@ -1445,9 +1439,9 @@
|
|||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const messages = createMessagesList(responseMessageId);
|
||||||
if (messages.length == 2 && selectedModels[0] === model.id) {
|
if (messages.length == 2 && selectedModels[0] === model.id) {
|
||||||
window.history.replaceState(history.state, '', `/c/${_chatId}`);
|
window.history.replaceState(history.state, '', `/c/${_chatId}`);
|
||||||
|
|
||||||
const _title = await generateChatTitle(userPrompt);
|
const _title = await generateChatTitle(userPrompt);
|
||||||
await setChatTitle(_chatId, _title);
|
await setChatTitle(_chatId, _title);
|
||||||
}
|
}
|
||||||
@ -1497,7 +1491,7 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
messages = messages;
|
history.messages[responseMessageId] = responseMessage;
|
||||||
};
|
};
|
||||||
|
|
||||||
const stopResponse = () => {
|
const stopResponse = () => {
|
||||||
@ -1508,7 +1502,7 @@
|
|||||||
const regenerateResponse = async (message) => {
|
const regenerateResponse = async (message) => {
|
||||||
console.log('regenerateResponse');
|
console.log('regenerateResponse');
|
||||||
|
|
||||||
if (messages.length != 0) {
|
if (history.currentId) {
|
||||||
let userMessage = history.messages[message.parentId];
|
let userMessage = history.messages[message.parentId];
|
||||||
let userPrompt = userMessage.content;
|
let userPrompt = userMessage.content;
|
||||||
|
|
||||||
@ -1526,11 +1520,11 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const continueGeneration = async () => {
|
const continueResponse = async () => {
|
||||||
console.log('continueGeneration');
|
console.log('continueResponse');
|
||||||
const _chatId = JSON.parse(JSON.stringify($chatId));
|
const _chatId = JSON.parse(JSON.stringify($chatId));
|
||||||
|
|
||||||
if (messages.length != 0 && messages.at(-1).done == true) {
|
if (history.currentId && history.messages[history.currentId].done == true) {
|
||||||
const responseMessage = history.messages[history.currentId];
|
const responseMessage = history.messages[history.currentId];
|
||||||
responseMessage.done = false;
|
responseMessage.done = false;
|
||||||
await tick();
|
await tick();
|
||||||
@ -1558,6 +1552,53 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const mergeResponses = async (messageId, responses, _chatId) => {
|
||||||
|
console.log('mergeResponses', messageId, responses);
|
||||||
|
const message = history.messages[messageId];
|
||||||
|
const mergedResponse = {
|
||||||
|
status: true,
|
||||||
|
content: ''
|
||||||
|
};
|
||||||
|
message.merged = mergedResponse;
|
||||||
|
history.messages[messageId] = message;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [res, controller] = await generateMoACompletion(
|
||||||
|
localStorage.token,
|
||||||
|
message.model,
|
||||||
|
history.messages[message.parentId].content,
|
||||||
|
responses
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res && res.ok && res.body) {
|
||||||
|
const textStream = await createOpenAITextStream(res.body, $settings.splitLargeChunks);
|
||||||
|
for await (const update of textStream) {
|
||||||
|
const { value, done, citations, error, usage } = update;
|
||||||
|
if (error || done) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mergedResponse.content == '' && value == '\n') {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
mergedResponse.content += value;
|
||||||
|
history.messages[messageId] = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (autoScroll) {
|
||||||
|
scrollToBottom();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await saveChatHandler(_chatId);
|
||||||
|
} else {
|
||||||
|
console.error(res);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const generateChatTitle = async (userPrompt) => {
|
const generateChatTitle = async (userPrompt) => {
|
||||||
if ($settings?.title?.auto ?? true) {
|
if ($settings?.title?.auto ?? true) {
|
||||||
const title = await generateTitle(
|
const title = await generateTitle(
|
||||||
@ -1600,7 +1641,7 @@
|
|||||||
description: $i18n.t('Generating search query')
|
description: $i18n.t('Generating search query')
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
messages = messages;
|
history.messages[responseMessageId] = responseMessage;
|
||||||
|
|
||||||
const prompt = userMessage.content;
|
const prompt = userMessage.content;
|
||||||
let searchQuery = await generateSearchQuery(
|
let searchQuery = await generateSearchQuery(
|
||||||
@ -1620,7 +1661,7 @@
|
|||||||
action: 'web_search',
|
action: 'web_search',
|
||||||
description: $i18n.t('No search query generated')
|
description: $i18n.t('No search query generated')
|
||||||
});
|
});
|
||||||
messages = messages;
|
history.messages[responseMessageId] = responseMessage;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1629,7 +1670,7 @@
|
|||||||
action: 'web_search',
|
action: 'web_search',
|
||||||
description: $i18n.t(`Searching "{{searchQuery}}"`, { searchQuery })
|
description: $i18n.t(`Searching "{{searchQuery}}"`, { searchQuery })
|
||||||
});
|
});
|
||||||
messages = messages;
|
history.messages[responseMessageId] = responseMessage;
|
||||||
|
|
||||||
const results = await runWebSearch(localStorage.token, searchQuery).catch((error) => {
|
const results = await runWebSearch(localStorage.token, searchQuery).catch((error) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@ -1657,8 +1698,7 @@
|
|||||||
type: 'web_search_results',
|
type: 'web_search_results',
|
||||||
urls: results.filenames
|
urls: results.filenames
|
||||||
});
|
});
|
||||||
|
history.messages[responseMessageId] = responseMessage;
|
||||||
messages = messages;
|
|
||||||
} else {
|
} else {
|
||||||
responseMessage.statusHistory.push({
|
responseMessage.statusHistory.push({
|
||||||
done: true,
|
done: true,
|
||||||
@ -1666,7 +1706,7 @@
|
|||||||
action: 'web_search',
|
action: 'web_search',
|
||||||
description: 'No search results found'
|
description: 'No search results found'
|
||||||
});
|
});
|
||||||
messages = messages;
|
history.messages[responseMessageId] = responseMessage;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1680,9 +1720,8 @@
|
|||||||
if ($chatId == _chatId) {
|
if ($chatId == _chatId) {
|
||||||
if (!$temporaryChatEnabled) {
|
if (!$temporaryChatEnabled) {
|
||||||
chat = await updateChatById(localStorage.token, _chatId, {
|
chat = await updateChatById(localStorage.token, _chatId, {
|
||||||
messages: messages,
|
|
||||||
history: history,
|
|
||||||
models: selectedModels,
|
models: selectedModels,
|
||||||
|
history: history,
|
||||||
params: params,
|
params: params,
|
||||||
files: chatFiles
|
files: chatFiles
|
||||||
});
|
});
|
||||||
@ -1692,52 +1731,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const mergeResponses = async (messageId, responses, _chatId) => {
|
|
||||||
console.log('mergeResponses', messageId, responses);
|
|
||||||
const message = history.messages[messageId];
|
|
||||||
const mergedResponse = {
|
|
||||||
status: true,
|
|
||||||
content: ''
|
|
||||||
};
|
|
||||||
message.merged = mergedResponse;
|
|
||||||
messages = messages;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const [res, controller] = await generateMoACompletion(
|
|
||||||
localStorage.token,
|
|
||||||
message.model,
|
|
||||||
history.messages[message.parentId].content,
|
|
||||||
responses
|
|
||||||
);
|
|
||||||
|
|
||||||
if (res && res.ok && res.body) {
|
|
||||||
const textStream = await createOpenAITextStream(res.body, $settings.splitLargeChunks);
|
|
||||||
for await (const update of textStream) {
|
|
||||||
const { value, done, citations, error, usage } = update;
|
|
||||||
if (error || done) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mergedResponse.content == '' && value == '\n') {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
mergedResponse.content += value;
|
|
||||||
messages = messages;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (autoScroll) {
|
|
||||||
scrollToBottom();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await saveChatHandler(_chatId);
|
|
||||||
} else {
|
|
||||||
console.error(res);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@ -1788,11 +1781,11 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Navbar {chat} {title} bind:selectedModels shareEnabled={messages.length > 0} {initNewChat} />
|
<Navbar {chat} {title} bind:selectedModels shareEnabled={!!history.currentId} {initNewChat} />
|
||||||
|
|
||||||
<PaneGroup direction="horizontal" class="w-full h-full">
|
<PaneGroup direction="horizontal" class="w-full h-full">
|
||||||
<Pane defaultSize={50} class="h-full flex w-full relative">
|
<Pane defaultSize={50} class="h-full flex w-full relative">
|
||||||
{#if $banners.length > 0 && messages.length === 0 && !$chatId && selectedModels.length <= 1}
|
{#if $banners.length > 0 && !history.currentId && !$chatId && selectedModels.length <= 1}
|
||||||
<div class="absolute top-3 left-0 right-0 w-full z-20">
|
<div class="absolute top-3 left-0 right-0 w-full z-20">
|
||||||
<div class=" flex flex-col gap-1 w-full">
|
<div class=" flex flex-col gap-1 w-full">
|
||||||
{#each $banners.filter( (b) => (b.dismissible ? !JSON.parse(localStorage.getItem('dismissedBannerIds') ?? '[]').includes(b.id) : true) ) as banner}
|
{#each $banners.filter( (b) => (b.dismissible ? !JSON.parse(localStorage.getItem('dismissedBannerIds') ?? '[]').includes(b.id) : true) ) as banner}
|
||||||
@ -1834,15 +1827,13 @@
|
|||||||
bind:history
|
bind:history
|
||||||
bind:autoScroll
|
bind:autoScroll
|
||||||
bind:prompt
|
bind:prompt
|
||||||
{messages}
|
|
||||||
{selectedModels}
|
{selectedModels}
|
||||||
{processing}
|
|
||||||
{sendPrompt}
|
{sendPrompt}
|
||||||
{continueGeneration}
|
{showMessage}
|
||||||
|
{continueResponse}
|
||||||
{regenerateResponse}
|
{regenerateResponse}
|
||||||
{mergeResponses}
|
{mergeResponses}
|
||||||
{chatActionHandler}
|
{chatActionHandler}
|
||||||
{showMessage}
|
|
||||||
bottomPadding={files.length > 0}
|
bottomPadding={files.length > 0}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -1850,13 +1841,13 @@
|
|||||||
|
|
||||||
<div class="">
|
<div class="">
|
||||||
<MessageInput
|
<MessageInput
|
||||||
|
{history}
|
||||||
bind:files
|
bind:files
|
||||||
bind:prompt
|
bind:prompt
|
||||||
bind:autoScroll
|
bind:autoScroll
|
||||||
bind:selectedToolIds
|
bind:selectedToolIds
|
||||||
bind:webSearchEnabled
|
bind:webSearchEnabled
|
||||||
bind:atSelectedModel
|
bind:atSelectedModel
|
||||||
{messages}
|
|
||||||
{selectedModels}
|
{selectedModels}
|
||||||
availableToolIds={selectedModelIds.reduce((a, e, i, arr) => {
|
availableToolIds={selectedModelIds.reduce((a, e, i, arr) => {
|
||||||
const model = $models.find((m) => m.id === e);
|
const model = $models.find((m) => m.id === e);
|
||||||
|
@ -61,15 +61,14 @@
|
|||||||
let user = null;
|
let user = null;
|
||||||
let chatInputPlaceholder = '';
|
let chatInputPlaceholder = '';
|
||||||
|
|
||||||
export let files = [];
|
export let history;
|
||||||
|
|
||||||
|
export let prompt = '';
|
||||||
|
export let files = [];
|
||||||
export let availableToolIds = [];
|
export let availableToolIds = [];
|
||||||
export let selectedToolIds = [];
|
export let selectedToolIds = [];
|
||||||
export let webSearchEnabled = false;
|
export let webSearchEnabled = false;
|
||||||
|
|
||||||
export let prompt = '';
|
|
||||||
export let messages = [];
|
|
||||||
|
|
||||||
let visionCapableModels = [];
|
let visionCapableModels = [];
|
||||||
$: visionCapableModels = [...(atSelectedModel ? [atSelectedModel] : selectedModels)].filter(
|
$: visionCapableModels = [...(atSelectedModel ? [atSelectedModel] : selectedModels)].filter(
|
||||||
(model) => $models.find((m) => m.id === model)?.info?.meta?.capabilities?.vision ?? true
|
(model) => $models.find((m) => m.id === model)?.info?.meta?.capabilities?.vision ?? true
|
||||||
@ -272,7 +271,7 @@
|
|||||||
<div class=" -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
|
<div class=" -mb-0.5 mx-auto inset-x-0 bg-transparent flex justify-center">
|
||||||
<div class="flex flex-col max-w-6xl px-2.5 md:px-6 w-full">
|
<div class="flex flex-col max-w-6xl px-2.5 md:px-6 w-full">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
{#if autoScroll === false && messages.length > 0}
|
{#if autoScroll === false && history?.currentId}
|
||||||
<div
|
<div
|
||||||
class=" absolute -top-12 left-0 right-0 flex justify-center z-30 pointer-events-none"
|
class=" absolute -top-12 left-0 right-0 flex justify-center z-30 pointer-events-none"
|
||||||
>
|
>
|
||||||
@ -692,7 +691,7 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="self-end mb-2 flex space-x-1 mr-1">
|
<div class="self-end mb-2 flex space-x-1 mr-1">
|
||||||
{#if messages.length == 0 || messages.at(-1).done == true}
|
{#if !history?.currentId || history.messages[history.currentId]?.done == true}
|
||||||
<Tooltip content={$i18n.t('Record voice')}>
|
<Tooltip content={$i18n.t('Record voice')}>
|
||||||
<button
|
<button
|
||||||
id="voice-input-button"
|
id="voice-input-button"
|
||||||
@ -744,7 +743,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-end w-10">
|
<div class="flex items-end w-10">
|
||||||
{#if messages.length == 0 || messages.at(-1).done == true}
|
{#if !history.currentId || history.messages[history.currentId]?.done == true}
|
||||||
{#if prompt === ''}
|
{#if prompt === ''}
|
||||||
<div class=" flex items-center mb-1">
|
<div class=" flex items-center mb-1">
|
||||||
<Tooltip content={$i18n.t('Call')}>
|
<Tooltip content={$i18n.t('Call')}>
|
||||||
|
@ -7,31 +7,45 @@
|
|||||||
import { getChatList, updateChatById } from '$lib/apis/chats';
|
import { getChatList, updateChatById } from '$lib/apis/chats';
|
||||||
import { copyToClipboard, findWordIndices } from '$lib/utils';
|
import { copyToClipboard, findWordIndices } from '$lib/utils';
|
||||||
|
|
||||||
import UserMessage from './Messages/UserMessage.svelte';
|
|
||||||
import ResponseMessage from './Messages/ResponseMessage.svelte';
|
|
||||||
import Placeholder from './Messages/Placeholder.svelte';
|
import Placeholder from './Messages/Placeholder.svelte';
|
||||||
import MultiResponseMessages from './Messages/MultiResponseMessages.svelte';
|
import Message from './Messages/Message.svelte';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
export let chatId = '';
|
export let chatId = '';
|
||||||
export let readOnly = false;
|
export let user = $_user;
|
||||||
|
|
||||||
|
export let prompt;
|
||||||
|
export let history = {};
|
||||||
|
export let selectedModels;
|
||||||
|
|
||||||
|
let messages = [];
|
||||||
|
|
||||||
export let sendPrompt: Function;
|
export let sendPrompt: Function;
|
||||||
export let continueGeneration: Function;
|
export let continueResponse: Function;
|
||||||
export let regenerateResponse: Function;
|
export let regenerateResponse: Function;
|
||||||
export let mergeResponses: Function;
|
export let mergeResponses: Function;
|
||||||
export let chatActionHandler: Function;
|
export let chatActionHandler: Function;
|
||||||
export let showMessage: Function = () => {};
|
export let showMessage: Function = () => {};
|
||||||
|
|
||||||
export let user = $_user;
|
export let readOnly = false;
|
||||||
export let prompt;
|
|
||||||
export let processing = '';
|
|
||||||
export let bottomPadding = false;
|
export let bottomPadding = false;
|
||||||
export let autoScroll;
|
export let autoScroll;
|
||||||
export let history = {};
|
|
||||||
export let messages = [];
|
|
||||||
|
|
||||||
export let selectedModels;
|
$: if (history.currentId) {
|
||||||
|
let _messages = [];
|
||||||
|
|
||||||
|
let message = history.messages[history.currentId];
|
||||||
|
while (message) {
|
||||||
|
_messages.unshift({ ...message });
|
||||||
|
message = message.parentId !== null ? history.messages[message.parentId] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
messages = _messages;
|
||||||
|
} else {
|
||||||
|
messages = [];
|
||||||
|
}
|
||||||
|
|
||||||
$: if (autoScroll && bottomPadding) {
|
$: if (autoScroll && bottomPadding) {
|
||||||
(async () => {
|
(async () => {
|
||||||
@ -45,56 +59,9 @@
|
|||||||
element.scrollTop = element.scrollHeight;
|
element.scrollTop = element.scrollHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
const copyToClipboardWithToast = async (text) => {
|
const updateChatHistory = async () => {
|
||||||
const res = await copyToClipboard(text);
|
|
||||||
if (res) {
|
|
||||||
toast.success($i18n.t('Copying to clipboard was successful!'));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const confirmEditMessage = async (messageId, content, submit = true) => {
|
|
||||||
if (submit) {
|
|
||||||
let userPrompt = content;
|
|
||||||
let userMessageId = uuidv4();
|
|
||||||
|
|
||||||
let userMessage = {
|
|
||||||
id: userMessageId,
|
|
||||||
parentId: history.messages[messageId].parentId,
|
|
||||||
childrenIds: [],
|
|
||||||
role: 'user',
|
|
||||||
content: userPrompt,
|
|
||||||
...(history.messages[messageId].files && { files: history.messages[messageId].files }),
|
|
||||||
models: selectedModels
|
|
||||||
};
|
|
||||||
|
|
||||||
let messageParentId = history.messages[messageId].parentId;
|
|
||||||
|
|
||||||
if (messageParentId !== null) {
|
|
||||||
history.messages[messageParentId].childrenIds = [
|
|
||||||
...history.messages[messageParentId].childrenIds,
|
|
||||||
userMessageId
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
history.messages[userMessageId] = userMessage;
|
|
||||||
history.currentId = userMessageId;
|
|
||||||
|
|
||||||
await tick();
|
|
||||||
await sendPrompt(userPrompt, userMessageId);
|
|
||||||
} else {
|
|
||||||
history.messages[messageId].content = content;
|
|
||||||
await tick();
|
|
||||||
await updateChatById(localStorage.token, chatId, {
|
|
||||||
messages: messages,
|
|
||||||
history: history
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const updateChatMessages = async () => {
|
|
||||||
await tick();
|
await tick();
|
||||||
await updateChatById(localStorage.token, chatId, {
|
await updateChatById(localStorage.token, chatId, {
|
||||||
messages: messages,
|
|
||||||
history: history
|
history: history
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -102,49 +69,6 @@
|
|||||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmEditResponseMessage = async (messageId, content) => {
|
|
||||||
history.messages[messageId].originalContent = history.messages[messageId].content;
|
|
||||||
history.messages[messageId].content = content;
|
|
||||||
|
|
||||||
await updateChatMessages();
|
|
||||||
};
|
|
||||||
|
|
||||||
const saveNewResponseMessage = async (message, content) => {
|
|
||||||
const responseMessageId = uuidv4();
|
|
||||||
const parentId = message.parentId;
|
|
||||||
|
|
||||||
const responseMessage = {
|
|
||||||
...message,
|
|
||||||
id: responseMessageId,
|
|
||||||
parentId: parentId,
|
|
||||||
childrenIds: [],
|
|
||||||
content: content,
|
|
||||||
timestamp: Math.floor(Date.now() / 1000) // Unix epoch
|
|
||||||
};
|
|
||||||
|
|
||||||
history.messages[responseMessageId] = responseMessage;
|
|
||||||
history.currentId = responseMessageId;
|
|
||||||
|
|
||||||
// Append messageId to childrenIds of parent message
|
|
||||||
if (parentId !== null) {
|
|
||||||
history.messages[parentId].childrenIds = [
|
|
||||||
...history.messages[parentId].childrenIds,
|
|
||||||
responseMessageId
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
await updateChatMessages();
|
|
||||||
};
|
|
||||||
|
|
||||||
const rateMessage = async (messageId, rating) => {
|
|
||||||
history.messages[messageId].annotation = {
|
|
||||||
...history.messages[messageId].annotation,
|
|
||||||
rating: rating
|
|
||||||
};
|
|
||||||
|
|
||||||
await updateChatMessages();
|
|
||||||
};
|
|
||||||
|
|
||||||
const showPreviousMessage = async (message) => {
|
const showPreviousMessage = async (message) => {
|
||||||
if (message.parentId !== null) {
|
if (message.parentId !== null) {
|
||||||
let messageId =
|
let messageId =
|
||||||
@ -243,7 +167,89 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteMessageHandler = async (messageId) => {
|
const rateMessage = async (messageId, rating) => {
|
||||||
|
history.messages[messageId].annotation = {
|
||||||
|
...history.messages[messageId].annotation,
|
||||||
|
rating: rating
|
||||||
|
};
|
||||||
|
|
||||||
|
await updateChatHistory();
|
||||||
|
};
|
||||||
|
|
||||||
|
const editMessage = async (messageId, content, submit = true) => {
|
||||||
|
if (history.messages[messageId].role === 'user') {
|
||||||
|
if (submit) {
|
||||||
|
// New user message
|
||||||
|
let userPrompt = content;
|
||||||
|
let userMessageId = uuidv4();
|
||||||
|
|
||||||
|
let userMessage = {
|
||||||
|
id: userMessageId,
|
||||||
|
parentId: history.messages[messageId].parentId,
|
||||||
|
childrenIds: [],
|
||||||
|
role: 'user',
|
||||||
|
content: userPrompt,
|
||||||
|
...(history.messages[messageId].files && { files: history.messages[messageId].files }),
|
||||||
|
models: selectedModels
|
||||||
|
};
|
||||||
|
|
||||||
|
let messageParentId = history.messages[messageId].parentId;
|
||||||
|
|
||||||
|
if (messageParentId !== null) {
|
||||||
|
history.messages[messageParentId].childrenIds = [
|
||||||
|
...history.messages[messageParentId].childrenIds,
|
||||||
|
userMessageId
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
history.messages[userMessageId] = userMessage;
|
||||||
|
history.currentId = userMessageId;
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
await sendPrompt(userPrompt, userMessageId);
|
||||||
|
} else {
|
||||||
|
// Edit user message
|
||||||
|
history.messages[messageId].content = content;
|
||||||
|
await updateChatHistory();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (submit) {
|
||||||
|
// New response message
|
||||||
|
const responseMessageId = uuidv4();
|
||||||
|
const message = history.messages[messageId];
|
||||||
|
const parentId = message.parentId;
|
||||||
|
|
||||||
|
const responseMessage = {
|
||||||
|
...message,
|
||||||
|
id: responseMessageId,
|
||||||
|
parentId: parentId,
|
||||||
|
childrenIds: [],
|
||||||
|
content: content,
|
||||||
|
timestamp: Math.floor(Date.now() / 1000) // Unix epoch
|
||||||
|
};
|
||||||
|
|
||||||
|
history.messages[responseMessageId] = responseMessage;
|
||||||
|
history.currentId = responseMessageId;
|
||||||
|
|
||||||
|
// Append messageId to childrenIds of parent message
|
||||||
|
if (parentId !== null) {
|
||||||
|
history.messages[parentId].childrenIds = [
|
||||||
|
...history.messages[parentId].childrenIds,
|
||||||
|
responseMessageId
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
await updateChatHistory();
|
||||||
|
} else {
|
||||||
|
// Edit response message
|
||||||
|
history.messages[messageId].originalContent = history.messages[messageId].content;
|
||||||
|
history.messages[messageId].content = content;
|
||||||
|
await updateChatHistory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteMessage = async (messageId) => {
|
||||||
const messageToDelete = history.messages[messageId];
|
const messageToDelete = history.messages[messageId];
|
||||||
const parentMessageId = messageToDelete.parentId;
|
const parentMessageId = messageToDelete.parentId;
|
||||||
const childMessageIds = messageToDelete.childrenIds ?? [];
|
const childMessageIds = messageToDelete.childrenIds ?? [];
|
||||||
@ -278,15 +284,12 @@
|
|||||||
showMessage({ id: parentMessageId });
|
showMessage({ id: parentMessageId });
|
||||||
|
|
||||||
// Update the chat
|
// Update the chat
|
||||||
await updateChatById(localStorage.token, chatId, {
|
await updateChatHistory();
|
||||||
messages: messages,
|
|
||||||
history: history
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="h-full flex">
|
<div class="h-full flex">
|
||||||
{#if messages.length == 0}
|
{#if Object.keys(history?.messages ?? {}).length == 0}
|
||||||
<Placeholder
|
<Placeholder
|
||||||
modelIds={selectedModels}
|
modelIds={selectedModels}
|
||||||
submitPrompt={async (p) => {
|
submitPrompt={async (p) => {
|
||||||
@ -327,116 +330,39 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="w-full pt-2">
|
<div class="w-full pt-2">
|
||||||
{#key chatId}
|
{#key chatId}
|
||||||
{#each messages as message, messageIdx (message.id)}
|
<div class="w-full">
|
||||||
<div class=" w-full {messageIdx === messages.length - 1 ? ' pb-12' : ''}">
|
{#each messages as message, messageIdx (message.id)}
|
||||||
<div
|
<Message
|
||||||
class="flex flex-col justify-between px-5 mb-3 {($settings?.widescreenMode ?? null)
|
{chatId}
|
||||||
? 'max-w-full'
|
bind:history
|
||||||
: 'max-w-5xl'} mx-auto rounded-lg group"
|
messageId={message.id}
|
||||||
>
|
idx={messageIdx}
|
||||||
{#if message.role === 'user'}
|
{user}
|
||||||
<UserMessage
|
{showPreviousMessage}
|
||||||
on:delete={() => deleteMessageHandler(message.id)}
|
{showNextMessage}
|
||||||
{user}
|
{editMessage}
|
||||||
{readOnly}
|
{deleteMessage}
|
||||||
{message}
|
{rateMessage}
|
||||||
isFirstMessage={messageIdx === 0}
|
{regenerateResponse}
|
||||||
siblings={message.parentId !== null
|
{continueResponse}
|
||||||
? (history.messages[message.parentId]?.childrenIds ?? [])
|
{mergeResponses}
|
||||||
: (Object.values(history.messages)
|
{updateChatHistory}
|
||||||
.filter((message) => message.parentId === null)
|
{chatActionHandler}
|
||||||
.map((message) => message.id) ?? [])}
|
{readOnly}
|
||||||
{confirmEditMessage}
|
on:scroll={() => {
|
||||||
{showPreviousMessage}
|
if (autoScroll) {
|
||||||
{showNextMessage}
|
const element = document.getElementById('messages-container');
|
||||||
copyToClipboard={copyToClipboardWithToast}
|
autoScroll =
|
||||||
/>
|
element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
|
||||||
{:else if (history.messages[message.parentId]?.models?.length ?? 1) === 1}
|
setTimeout(() => {
|
||||||
{#key message.id}
|
scrollToBottom();
|
||||||
<ResponseMessage
|
}, 100);
|
||||||
{message}
|
}
|
||||||
siblings={history.messages[message.parentId]?.childrenIds ?? []}
|
}}
|
||||||
isLastMessage={messageIdx + 1 === messages.length}
|
/>
|
||||||
{readOnly}
|
{/each}
|
||||||
{updateChatMessages}
|
</div>
|
||||||
{confirmEditResponseMessage}
|
<div class="pb-12" />
|
||||||
{saveNewResponseMessage}
|
|
||||||
{showPreviousMessage}
|
|
||||||
{showNextMessage}
|
|
||||||
{rateMessage}
|
|
||||||
copyToClipboard={copyToClipboardWithToast}
|
|
||||||
{continueGeneration}
|
|
||||||
{regenerateResponse}
|
|
||||||
on:action={async (e) => {
|
|
||||||
console.log('action', e);
|
|
||||||
if (typeof e.detail === 'string') {
|
|
||||||
await chatActionHandler(chatId, e.detail, message.model, message.id);
|
|
||||||
} else {
|
|
||||||
const { id, event } = e.detail;
|
|
||||||
await chatActionHandler(chatId, id, message.model, message.id, event);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
on:save={async (e) => {
|
|
||||||
console.log('save', e);
|
|
||||||
|
|
||||||
const message = e.detail;
|
|
||||||
history.messages[message.id] = message;
|
|
||||||
await updateChatById(localStorage.token, chatId, {
|
|
||||||
messages: messages,
|
|
||||||
history: history
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
{:else}
|
|
||||||
{#key message.parentId}
|
|
||||||
<MultiResponseMessages
|
|
||||||
bind:history
|
|
||||||
isLastMessage={messageIdx + 1 === messages.length}
|
|
||||||
{messages}
|
|
||||||
{readOnly}
|
|
||||||
{chatId}
|
|
||||||
parentMessage={history.messages[message.parentId]}
|
|
||||||
{messageIdx}
|
|
||||||
{updateChatMessages}
|
|
||||||
{saveNewResponseMessage}
|
|
||||||
{confirmEditResponseMessage}
|
|
||||||
{rateMessage}
|
|
||||||
copyToClipboard={copyToClipboardWithToast}
|
|
||||||
{continueGeneration}
|
|
||||||
{mergeResponses}
|
|
||||||
{regenerateResponse}
|
|
||||||
on:action={async (e) => {
|
|
||||||
console.log('action', e);
|
|
||||||
if (typeof e.detail === 'string') {
|
|
||||||
await chatActionHandler(chatId, e.detail, message.model, message.id);
|
|
||||||
} else {
|
|
||||||
const { id, event } = e.detail;
|
|
||||||
await chatActionHandler(chatId, id, message.model, message.id, event);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
on:change={async () => {
|
|
||||||
await updateChatById(localStorage.token, chatId, {
|
|
||||||
messages: messages,
|
|
||||||
history: history
|
|
||||||
});
|
|
||||||
|
|
||||||
if (autoScroll) {
|
|
||||||
const element = document.getElementById('messages-container');
|
|
||||||
autoScroll =
|
|
||||||
element.scrollHeight - element.scrollTop <= element.clientHeight + 50;
|
|
||||||
setTimeout(() => {
|
|
||||||
scrollToBottom();
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
{/key}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
|
|
||||||
{#if bottomPadding}
|
{#if bottomPadding}
|
||||||
<div class=" pb-6" />
|
<div class=" pb-6" />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -226,7 +226,7 @@ __builtins__.input = input`);
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$: if (token.raw) {
|
$: if (token) {
|
||||||
if (lang === 'mermaid' && (token?.raw ?? '').slice(-4).includes('```')) {
|
if (lang === 'mermaid' && (token?.raw ?? '').slice(-4).includes('```')) {
|
||||||
(async () => {
|
(async () => {
|
||||||
await drawMermaidDiagram();
|
await drawMermaidDiagram();
|
||||||
@ -245,6 +245,7 @@ __builtins__.input = input`);
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
console.log('codeblock', lang, code);
|
||||||
if (document.documentElement.classList.contains('dark')) {
|
if (document.documentElement.classList.contains('dark')) {
|
||||||
mermaid.initialize({
|
mermaid.initialize({
|
||||||
startOnLoad: true,
|
startOnLoad: true,
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- {JSON.stringify(tokens)} -->
|
<!-- {JSON.stringify(tokens)} -->
|
||||||
{#each tokens as token, tokenIdx}
|
{#each tokens as token, tokenIdx (tokenIdx)}
|
||||||
{#if token.type === 'hr'}
|
{#if token.type === 'hr'}
|
||||||
<hr />
|
<hr />
|
||||||
{:else if token.type === 'heading'}
|
{:else if token.type === 'heading'}
|
||||||
|
166
src/lib/components/chat/Messages/Message.svelte
Normal file
166
src/lib/components/chat/Messages/Message.svelte
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { toast } from 'svelte-sonner';
|
||||||
|
|
||||||
|
import { tick, getContext, onMount, createEventDispatcher } from 'svelte';
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
|
import { settings } from '$lib/stores';
|
||||||
|
import { copyToClipboard } from '$lib/utils';
|
||||||
|
|
||||||
|
import MultiResponseMessages from './MultiResponseMessages.svelte';
|
||||||
|
import ResponseMessage from './ResponseMessage.svelte';
|
||||||
|
import UserMessage from './UserMessage.svelte';
|
||||||
|
import { updateChatById } from '$lib/apis/chats';
|
||||||
|
|
||||||
|
export let chatId;
|
||||||
|
export let idx = 0;
|
||||||
|
|
||||||
|
export let history;
|
||||||
|
export let messageId;
|
||||||
|
|
||||||
|
export let user;
|
||||||
|
|
||||||
|
export let updateChatHistory;
|
||||||
|
export let chatActionHandler;
|
||||||
|
|
||||||
|
export let showPreviousMessage;
|
||||||
|
export let showNextMessage;
|
||||||
|
|
||||||
|
export let editMessage;
|
||||||
|
export let deleteMessage;
|
||||||
|
export let rateMessage;
|
||||||
|
|
||||||
|
export let regenerateResponse;
|
||||||
|
export let continueResponse;
|
||||||
|
|
||||||
|
// MultiResponseMessages
|
||||||
|
export let mergeResponses;
|
||||||
|
|
||||||
|
export let autoScroll = false;
|
||||||
|
export let readOnly = false;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
console.log('message', idx);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex flex-col justify-between px-5 mb-3 w-full {($settings?.widescreenMode ?? null)
|
||||||
|
? 'max-w-full'
|
||||||
|
: 'max-w-5xl'} mx-auto rounded-lg group"
|
||||||
|
>
|
||||||
|
{#if history.messages[messageId]}
|
||||||
|
{#if history.messages[messageId].role === 'user'}
|
||||||
|
<UserMessage
|
||||||
|
{user}
|
||||||
|
{history}
|
||||||
|
{messageId}
|
||||||
|
isFirstMessage={idx === 0}
|
||||||
|
siblings={history.messages[messageId].parentId !== null
|
||||||
|
? (history.messages[history.messages[messageId].parentId]?.childrenIds ?? [])
|
||||||
|
: (Object.values(history.messages)
|
||||||
|
.filter((message) => message.parentId === null)
|
||||||
|
.map((message) => message.id) ?? [])}
|
||||||
|
{showPreviousMessage}
|
||||||
|
{showNextMessage}
|
||||||
|
{editMessage}
|
||||||
|
on:delete={() => deleteMessage(messageId)}
|
||||||
|
{readOnly}
|
||||||
|
/>
|
||||||
|
{:else if (history.messages[history.messages[messageId].parentId]?.models?.length ?? 1) === 1}
|
||||||
|
<ResponseMessage
|
||||||
|
{history}
|
||||||
|
{messageId}
|
||||||
|
isLastMessage={messageId === history.currentId}
|
||||||
|
siblings={history.messages[history.messages[messageId].parentId]?.childrenIds ?? []}
|
||||||
|
{showPreviousMessage}
|
||||||
|
{showNextMessage}
|
||||||
|
{editMessage}
|
||||||
|
{rateMessage}
|
||||||
|
{continueResponse}
|
||||||
|
{regenerateResponse}
|
||||||
|
on:action={async (e) => {
|
||||||
|
console.log('action', e);
|
||||||
|
const message = history.messages[messageId];
|
||||||
|
if (typeof e.detail === 'string') {
|
||||||
|
await chatActionHandler(chatId, e.detail, message.model, message.id);
|
||||||
|
} else {
|
||||||
|
const { id, event } = e.detail;
|
||||||
|
await chatActionHandler(chatId, id, message.model, message.id, event);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
on:update={async (e) => {
|
||||||
|
console.log('update', e);
|
||||||
|
updateChatHistory();
|
||||||
|
}}
|
||||||
|
on:save={async (e) => {
|
||||||
|
console.log('save', e);
|
||||||
|
|
||||||
|
const message = e.detail;
|
||||||
|
if (message) {
|
||||||
|
history.messages[message.id] = message;
|
||||||
|
await updateChatById(localStorage.token, chatId, {
|
||||||
|
history: history
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await updateChatById(localStorage.token, chatId, {
|
||||||
|
history: history
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{readOnly}
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<MultiResponseMessages
|
||||||
|
bind:history
|
||||||
|
{chatId}
|
||||||
|
{messageId}
|
||||||
|
isLastMessage={messageId === history?.currentId}
|
||||||
|
{rateMessage}
|
||||||
|
{editMessage}
|
||||||
|
{continueResponse}
|
||||||
|
{regenerateResponse}
|
||||||
|
{mergeResponses}
|
||||||
|
on:action={async (e) => {
|
||||||
|
console.log('action', e);
|
||||||
|
const message = history.messages[messageId];
|
||||||
|
if (typeof e.detail === 'string') {
|
||||||
|
await chatActionHandler(chatId, e.detail, message.model, message.id);
|
||||||
|
} else {
|
||||||
|
const { id, event } = e.detail;
|
||||||
|
await chatActionHandler(chatId, id, message.model, message.id, event);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
on:update={async (e) => {
|
||||||
|
console.log('update', e);
|
||||||
|
updateChatHistory();
|
||||||
|
}}
|
||||||
|
on:save={async (e) => {
|
||||||
|
console.log('save', e);
|
||||||
|
|
||||||
|
const message = e.detail;
|
||||||
|
if (message) {
|
||||||
|
history.messages[message.id] = message;
|
||||||
|
await updateChatById(localStorage.token, chatId, {
|
||||||
|
history: history
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await updateChatById(localStorage.token, chatId, {
|
||||||
|
history: history
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
on:change={async () => {
|
||||||
|
await tick();
|
||||||
|
await updateChatById(localStorage.token, chatId, {
|
||||||
|
history: history
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch('scroll');
|
||||||
|
}}
|
||||||
|
{readOnly}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
</div>
|
@ -20,39 +20,36 @@
|
|||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
export let chatId;
|
export let chatId;
|
||||||
|
|
||||||
export let history;
|
export let history;
|
||||||
export let messages = [];
|
export let messageId;
|
||||||
export let messageIdx;
|
|
||||||
|
|
||||||
export let parentMessage;
|
|
||||||
export let isLastMessage;
|
export let isLastMessage;
|
||||||
|
|
||||||
export let readOnly = false;
|
export let readOnly = false;
|
||||||
|
|
||||||
export let updateChatMessages: Function;
|
export let editMessage: Function;
|
||||||
export let confirmEditResponseMessage: Function;
|
|
||||||
export let rateMessage: Function;
|
export let rateMessage: Function;
|
||||||
|
|
||||||
export let copyToClipboard: Function;
|
export let continueResponse: Function;
|
||||||
export let continueGeneration: Function;
|
|
||||||
export let mergeResponses: Function;
|
|
||||||
export let regenerateResponse: Function;
|
export let regenerateResponse: Function;
|
||||||
export let saveNewResponseMessage: Function;
|
export let mergeResponses: Function;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
let currentMessageId;
|
let currentMessageId;
|
||||||
let groupedMessages = {};
|
let parentMessage;
|
||||||
let groupedMessagesIdx = {};
|
let groupedMessageIds = {};
|
||||||
|
let groupedMessageIdsIdx = {};
|
||||||
|
|
||||||
$: if (parentMessage) {
|
let message = JSON.parse(JSON.stringify(history.messages[messageId]));
|
||||||
initHandler();
|
$: if (history.messages) {
|
||||||
|
if (JSON.stringify(message) !== JSON.stringify(history.messages[messageId])) {
|
||||||
|
message = JSON.parse(JSON.stringify(history.messages[messageId]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const showPreviousMessage = (modelIdx) => {
|
const showPreviousMessage = (modelIdx) => {
|
||||||
groupedMessagesIdx[modelIdx] = Math.max(0, groupedMessagesIdx[modelIdx] - 1);
|
groupedMessageIdsIdx[modelIdx] = Math.max(0, groupedMessageIdsIdx[modelIdx] - 1);
|
||||||
let messageId = groupedMessages[modelIdx].messages[groupedMessagesIdx[modelIdx]].id;
|
let messageId = groupedMessageIds[modelIdx].messages[groupedMessageIdsIdx[modelIdx]].id;
|
||||||
|
|
||||||
console.log(messageId);
|
console.log(messageId);
|
||||||
let messageChildrenIds = history.messages[messageId].childrenIds;
|
let messageChildrenIds = history.messages[messageId].childrenIds;
|
||||||
@ -67,12 +64,12 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const showNextMessage = (modelIdx) => {
|
const showNextMessage = (modelIdx) => {
|
||||||
groupedMessagesIdx[modelIdx] = Math.min(
|
groupedMessageIdsIdx[modelIdx] = Math.min(
|
||||||
groupedMessages[modelIdx].messages.length - 1,
|
groupedMessageIds[modelIdx].messages.length - 1,
|
||||||
groupedMessagesIdx[modelIdx] + 1
|
groupedMessageIdsIdx[modelIdx] + 1
|
||||||
);
|
);
|
||||||
|
|
||||||
let messageId = groupedMessages[modelIdx].messages[groupedMessagesIdx[modelIdx]].id;
|
let messageId = groupedMessageIds[modelIdx].messages[groupedMessageIdsIdx[modelIdx]].id;
|
||||||
console.log(messageId);
|
console.log(messageId);
|
||||||
|
|
||||||
let messageChildrenIds = history.messages[messageId].childrenIds;
|
let messageChildrenIds = history.messages[messageId].childrenIds;
|
||||||
@ -87,33 +84,43 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const initHandler = async () => {
|
const initHandler = async () => {
|
||||||
|
console.log('multiresponse:initHandler');
|
||||||
await tick();
|
await tick();
|
||||||
currentMessageId = messages[messageIdx].id;
|
|
||||||
|
|
||||||
groupedMessages = parentMessage?.models.reduce((a, model, modelIdx) => {
|
currentMessageId = messageId;
|
||||||
|
parentMessage = history.messages[messageId].parentId
|
||||||
|
? history.messages[history.messages[messageId].parentId]
|
||||||
|
: null;
|
||||||
|
|
||||||
|
groupedMessageIds = parentMessage?.models.reduce((a, model, modelIdx) => {
|
||||||
// Find all messages that are children of the parent message and have the same model
|
// Find all messages that are children of the parent message and have the same model
|
||||||
let modelMessages = parentMessage?.childrenIds
|
let modelMessageIds = parentMessage?.childrenIds
|
||||||
.map((id) => history.messages[id])
|
.map((id) => history.messages[id])
|
||||||
.filter((m) => m?.modelIdx === modelIdx);
|
.filter((m) => m?.modelIdx === modelIdx)
|
||||||
|
.map((m) => m.id);
|
||||||
|
|
||||||
if (modelMessages.length === 0) {
|
// Legacy support for messages that don't have a modelIdx
|
||||||
modelMessages = parentMessage?.childrenIds
|
// Find all messages that are children of the parent message and have the same model
|
||||||
|
if (modelMessageIds.length === 0) {
|
||||||
|
let modelMessages = parentMessage?.childrenIds
|
||||||
.map((id) => history.messages[id])
|
.map((id) => history.messages[id])
|
||||||
.filter((m) => m?.model === model);
|
.filter((m) => m?.model === model);
|
||||||
|
|
||||||
modelMessages.forEach((m) => {
|
modelMessages.forEach((m) => {
|
||||||
m.modelIdx = modelIdx;
|
m.modelIdx = modelIdx;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelMessageIds = modelMessages.map((m) => m.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...a,
|
...a,
|
||||||
[modelIdx]: { messages: modelMessages }
|
[modelIdx]: { messageIds: modelMessageIds }
|
||||||
};
|
};
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
groupedMessagesIdx = parentMessage?.models.reduce((a, model, modelIdx) => {
|
groupedMessageIdsIdx = parentMessage?.models.reduce((a, model, modelIdx) => {
|
||||||
const idx = groupedMessages[modelIdx].messages.findIndex((m) => m.id === currentMessageId);
|
const idx = groupedMessageIds[modelIdx].messageIds.findIndex((id) => id === messageId);
|
||||||
if (idx !== -1) {
|
if (idx !== -1) {
|
||||||
return {
|
return {
|
||||||
...a,
|
...a,
|
||||||
@ -126,14 +133,19 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
console.log(groupedMessageIds, groupedMessageIdsIdx);
|
||||||
|
|
||||||
|
await tick();
|
||||||
};
|
};
|
||||||
|
|
||||||
const mergeResponsesHandler = async () => {
|
const mergeResponsesHandler = async () => {
|
||||||
const responses = Object.keys(groupedMessages).map((modelIdx) => {
|
const responses = Object.keys(groupedMessageIds).map((modelIdx) => {
|
||||||
const { messages } = groupedMessages[modelIdx];
|
const { messageIds } = groupedMessageIds[modelIdx];
|
||||||
return messages[groupedMessagesIdx[modelIdx]].content;
|
|
||||||
|
return messages[groupedMessageIdsIdx[modelIdx]].content;
|
||||||
});
|
});
|
||||||
mergeResponses(currentMessageId, responses, chatId);
|
mergeResponses(messageId, responses, chatId);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
@ -141,20 +153,21 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
{#if parentMessage}
|
||||||
<div
|
<div>
|
||||||
class="flex snap-x snap-mandatory overflow-x-auto scrollbar-hidden"
|
<div
|
||||||
id="responses-container-{chatId}-{parentMessage.id}"
|
class="flex snap-x snap-mandatory overflow-x-auto scrollbar-hidden"
|
||||||
>
|
id="responses-container-{chatId}-{parentMessage.id}"
|
||||||
{#key currentMessageId}
|
>
|
||||||
{#each Object.keys(groupedMessages) as modelIdx}
|
{#each Object.keys(groupedMessageIds) as modelIdx}
|
||||||
{#if groupedMessagesIdx[modelIdx] !== undefined && groupedMessages[modelIdx].messages.length > 0}
|
{#if groupedMessageIdsIdx[modelIdx] !== undefined && groupedMessageIds[modelIdx].messageIds.length > 0}
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||||
{@const message = groupedMessages[modelIdx].messages[groupedMessagesIdx[modelIdx]]}
|
{@const _messageId =
|
||||||
|
groupedMessageIds[modelIdx].messageIds[groupedMessageIdsIdx[modelIdx]]}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class=" snap-center w-full max-w-full m-1 border {history.messages[currentMessageId]
|
class=" snap-center w-full max-w-full m-1 border {history.messages[messageId]
|
||||||
?.modelIdx == modelIdx
|
?.modelIdx == modelIdx
|
||||||
? `border-gray-100 dark:border-gray-800 border-[1.5px] ${
|
? `border-gray-100 dark:border-gray-800 border-[1.5px] ${
|
||||||
$mobile ? 'min-w-full' : 'min-w-[32rem]'
|
$mobile ? 'min-w-full' : 'min-w-[32rem]'
|
||||||
@ -163,17 +176,13 @@
|
|||||||
$mobile ? 'min-w-full' : 'min-w-80'
|
$mobile ? 'min-w-full' : 'min-w-80'
|
||||||
}`} transition-all p-5 rounded-2xl"
|
}`} transition-all p-5 rounded-2xl"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
if (currentMessageId != message.id) {
|
if (messageId != _messageId) {
|
||||||
currentMessageId = message.id;
|
let messageChildrenIds = history.messages[_messageId].childrenIds;
|
||||||
let messageId = message.id;
|
|
||||||
console.log(messageId);
|
|
||||||
//
|
|
||||||
let messageChildrenIds = history.messages[messageId].childrenIds;
|
|
||||||
while (messageChildrenIds.length !== 0) {
|
while (messageChildrenIds.length !== 0) {
|
||||||
messageId = messageChildrenIds.at(-1);
|
messageId = messageChildrenIds.at(-1);
|
||||||
messageChildrenIds = history.messages[messageId].childrenIds;
|
messageChildrenIds = history.messages[_messageId].childrenIds;
|
||||||
}
|
}
|
||||||
history.currentId = messageId;
|
history.currentId = _messageId;
|
||||||
dispatch('change');
|
dispatch('change');
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -181,96 +190,92 @@
|
|||||||
{#key history.currentId}
|
{#key history.currentId}
|
||||||
{#if message}
|
{#if message}
|
||||||
<ResponseMessage
|
<ResponseMessage
|
||||||
{message}
|
{history}
|
||||||
siblings={groupedMessages[modelIdx].messages.map((m) => m.id)}
|
messageId={_messageId}
|
||||||
isLastMessage={true}
|
isLastMessage={true}
|
||||||
{updateChatMessages}
|
siblings={groupedMessageIds[modelIdx].messageIds}
|
||||||
{saveNewResponseMessage}
|
|
||||||
{confirmEditResponseMessage}
|
|
||||||
showPreviousMessage={() => showPreviousMessage(modelIdx)}
|
showPreviousMessage={() => showPreviousMessage(modelIdx)}
|
||||||
showNextMessage={() => showNextMessage(modelIdx)}
|
showNextMessage={() => showNextMessage(modelIdx)}
|
||||||
{readOnly}
|
|
||||||
{rateMessage}
|
{rateMessage}
|
||||||
{copyToClipboard}
|
{editMessage}
|
||||||
{continueGeneration}
|
{continueResponse}
|
||||||
regenerateResponse={async (message) => {
|
regenerateResponse={async (message) => {
|
||||||
regenerateResponse(message);
|
regenerateResponse(message);
|
||||||
await tick();
|
await tick();
|
||||||
groupedMessagesIdx[modelIdx] = groupedMessages[modelIdx].messages.length - 1;
|
groupedMessageIdsIdx[modelIdx] =
|
||||||
|
groupedMessageIds[modelIdx].messageIds.length - 1;
|
||||||
}}
|
}}
|
||||||
on:action={async (e) => {
|
on:action={async (e) => {
|
||||||
dispatch('action', e.detail);
|
dispatch('action', e.detail);
|
||||||
}}
|
}}
|
||||||
on:save={async (e) => {
|
on:update={async (e) => {
|
||||||
console.log('save', e);
|
dispatch('update', e.detail);
|
||||||
|
|
||||||
const message = e.detail;
|
|
||||||
history.messages[message.id] = message;
|
|
||||||
await updateChatById(localStorage.token, chatId, {
|
|
||||||
messages: messages,
|
|
||||||
history: history
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
|
on:save={async (e) => {
|
||||||
|
dispatch('save', e.detail);
|
||||||
|
}}
|
||||||
|
{readOnly}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
{/key}
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if !readOnly && isLastMessage}
|
{#if !readOnly && isLastMessage}
|
||||||
{#if !Object.keys(groupedMessages).find((modelIdx) => {
|
{#if !Object.keys(groupedMessageIds).find((modelIdx) => {
|
||||||
const { messages } = groupedMessages[modelIdx];
|
const { messageIds } = groupedMessageIds[modelIdx];
|
||||||
return !messages[groupedMessagesIdx[modelIdx]]?.done ?? false;
|
const _messageId = messageIds[groupedMessageIdsIdx[modelIdx]];
|
||||||
})}
|
return !history.messages[_messageId]?.done ?? false;
|
||||||
<div class="flex justify-end">
|
})}
|
||||||
<div class="w-full">
|
<div class="flex justify-end">
|
||||||
{#if history.messages[currentMessageId]?.merged?.status}
|
<div class="w-full">
|
||||||
{@const message = history.messages[currentMessageId]?.merged}
|
{#if history.messages[messageId]?.merged?.status}
|
||||||
|
{@const message = history.messages[messageId]?.merged}
|
||||||
|
|
||||||
<div class="w-full rounded-xl pl-5 pr-2 py-2">
|
<div class="w-full rounded-xl pl-5 pr-2 py-2">
|
||||||
<Name>
|
<Name>
|
||||||
Merged Response
|
Merged Response
|
||||||
|
|
||||||
{#if message.timestamp}
|
{#if message.timestamp}
|
||||||
<span
|
<span
|
||||||
class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium uppercase ml-0.5 -mt-0.5"
|
class=" self-center invisible group-hover:visible text-gray-400 text-xs font-medium uppercase ml-0.5 -mt-0.5"
|
||||||
>
|
>
|
||||||
{dayjs(message.timestamp * 1000).format($i18n.t('h:mm a'))}
|
{dayjs(message.timestamp * 1000).format($i18n.t('h:mm a'))}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
</Name>
|
</Name>
|
||||||
|
|
||||||
<div class="mt-1 markdown-prose w-full min-w-full">
|
<div class="mt-1 markdown-prose w-full min-w-full">
|
||||||
{#if (message?.content ?? '') === ''}
|
{#if (message?.content ?? '') === ''}
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
{:else}
|
{:else}
|
||||||
<Markdown id={`merged`} content={message.content ?? ''} />
|
<Markdown id={`merged`} content={message.content ?? ''} />
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class=" flex-shrink-0 text-gray-600 dark:text-gray-500 mt-1">
|
<div class=" flex-shrink-0 text-gray-600 dark:text-gray-500 mt-1">
|
||||||
<Tooltip content={$i18n.t('Merge Responses')} placement="bottom">
|
<Tooltip content={$i18n.t('Merge Responses')} placement="bottom">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
id="merge-response-button"
|
id="merge-response-button"
|
||||||
class="{true
|
class="{true
|
||||||
? 'visible'
|
? 'visible'
|
||||||
: 'invisible group-hover:visible'} p-1 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
|
: 'invisible group-hover:visible'} p-1 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
mergeResponsesHandler();
|
mergeResponsesHandler();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Merge className=" size-5 " />
|
<Merge className=" size-5 " />
|
||||||
</button>
|
</button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
import { synthesizeOpenAISpeech } from '$lib/apis/audio';
|
import { synthesizeOpenAISpeech } from '$lib/apis/audio';
|
||||||
import { imageGenerations } from '$lib/apis/images';
|
import { imageGenerations } from '$lib/apis/images';
|
||||||
import {
|
import {
|
||||||
|
copyToClipboard as _copyToClipboard,
|
||||||
approximateToHumanReadable,
|
approximateToHumanReadable,
|
||||||
extractParagraphsForAudio,
|
extractParagraphsForAudio,
|
||||||
extractSentencesForAudio,
|
extractSentencesForAudio,
|
||||||
@ -76,25 +77,30 @@
|
|||||||
annotation?: { type: string; rating: number };
|
annotation?: { type: string; rating: number };
|
||||||
}
|
}
|
||||||
|
|
||||||
export let message: MessageType;
|
export let history;
|
||||||
|
export let messageId;
|
||||||
|
|
||||||
|
let message: MessageType = JSON.parse(JSON.stringify(history.messages[messageId]));
|
||||||
|
$: if (history.messages) {
|
||||||
|
if (JSON.stringify(message) !== JSON.stringify(history.messages[messageId])) {
|
||||||
|
message = JSON.parse(JSON.stringify(history.messages[messageId]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export let siblings;
|
export let siblings;
|
||||||
|
|
||||||
export let isLastMessage = true;
|
|
||||||
|
|
||||||
export let readOnly = false;
|
|
||||||
|
|
||||||
export let updateChatMessages: Function;
|
|
||||||
export let confirmEditResponseMessage: Function;
|
|
||||||
export let saveNewResponseMessage: Function = () => {};
|
|
||||||
|
|
||||||
export let showPreviousMessage: Function;
|
export let showPreviousMessage: Function;
|
||||||
export let showNextMessage: Function;
|
export let showNextMessage: Function;
|
||||||
|
|
||||||
|
export let editMessage: Function;
|
||||||
export let rateMessage: Function;
|
export let rateMessage: Function;
|
||||||
|
|
||||||
export let copyToClipboard: Function;
|
export let continueResponse: Function;
|
||||||
export let continueGeneration: Function;
|
|
||||||
export let regenerateResponse: Function;
|
export let regenerateResponse: Function;
|
||||||
|
|
||||||
|
export let isLastMessage = true;
|
||||||
|
export let readOnly = false;
|
||||||
|
|
||||||
let model = null;
|
let model = null;
|
||||||
$: model = $models.find((m) => m.id === message.model);
|
$: model = $models.find((m) => m.id === message.model);
|
||||||
|
|
||||||
@ -111,6 +117,13 @@
|
|||||||
|
|
||||||
let showRateComment = false;
|
let showRateComment = false;
|
||||||
|
|
||||||
|
const copyToClipboard = async (text) => {
|
||||||
|
const res = await _copyToClipboard(text);
|
||||||
|
if (res) {
|
||||||
|
toast.success($i18n.t('Copying to clipboard was successful!'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const playAudio = (idx: number) => {
|
const playAudio = (idx: number) => {
|
||||||
return new Promise<void>((res) => {
|
return new Promise<void>((res) => {
|
||||||
speakingIdx = idx;
|
speakingIdx = idx;
|
||||||
@ -260,11 +273,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const editMessageConfirmHandler = async () => {
|
const editMessageConfirmHandler = async () => {
|
||||||
if (editedContent === '') {
|
editMessage(message.id, editedContent ? editedContent : '', false);
|
||||||
editedContent = ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
confirmEditResponseMessage(message.id, editedContent);
|
|
||||||
|
|
||||||
edit = false;
|
edit = false;
|
||||||
editedContent = '';
|
editedContent = '';
|
||||||
@ -272,8 +281,8 @@
|
|||||||
await tick();
|
await tick();
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveNewMessageHandler = async () => {
|
const saveAsCopyHandler = async () => {
|
||||||
saveNewResponseMessage(message, editedContent);
|
editMessage(message.id, editedContent ? editedContent : '');
|
||||||
|
|
||||||
edit = false;
|
edit = false;
|
||||||
editedContent = '';
|
editedContent = '';
|
||||||
@ -313,6 +322,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
console.log('ResponseMessage mounted');
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
@ -424,7 +435,7 @@
|
|||||||
id="save-new-message-button"
|
id="save-new-message-button"
|
||||||
class=" px-4 py-2 bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 border dark:border-gray-700 text-gray-700 dark:text-gray-200 transition rounded-3xl"
|
class=" px-4 py-2 bg-gray-50 hover:bg-gray-100 dark:bg-gray-800 dark:hover:bg-gray-700 border dark:border-gray-700 text-gray-700 dark:text-gray-200 transition rounded-3xl"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
saveNewMessageHandler();
|
saveAsCopyHandler();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{$i18n.t('Save As Copy')}
|
{$i18n.t('Save As Copy')}
|
||||||
@ -909,7 +920,7 @@
|
|||||||
? 'visible'
|
? 'visible'
|
||||||
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
|
: 'invisible group-hover:visible'} p-1.5 hover:bg-black/5 dark:hover:bg-white/5 rounded-lg dark:hover:text-white hover:text-black transition regenerate-response-button"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
continueGeneration();
|
continueResponse();
|
||||||
|
|
||||||
(model?.actions ?? [])
|
(model?.actions ?? [])
|
||||||
.filter((action) => action?.__webui__ ?? false)
|
.filter((action) => action?.__webui__ ?? false)
|
||||||
@ -1028,8 +1039,7 @@
|
|||||||
bind:show={showRateComment}
|
bind:show={showRateComment}
|
||||||
bind:message
|
bind:message
|
||||||
on:submit={(e) => {
|
on:submit={(e) => {
|
||||||
updateChatMessages();
|
dispatch('update');
|
||||||
|
|
||||||
(model?.actions ?? [])
|
(model?.actions ?? [])
|
||||||
.filter((action) => action?.__webui__ ?? false)
|
.filter((action) => action?.__webui__ ?? false)
|
||||||
.forEach((action) => {
|
.forEach((action) => {
|
||||||
|
@ -1,38 +1,58 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
import { toast } from 'svelte-sonner';
|
||||||
|
import { tick, createEventDispatcher, getContext, onMount } from 'svelte';
|
||||||
|
|
||||||
|
import { models, settings } from '$lib/stores';
|
||||||
|
import { user as _user } from '$lib/stores';
|
||||||
|
import {
|
||||||
|
copyToClipboard as _copyToClipboard,
|
||||||
|
processResponseContent,
|
||||||
|
replaceTokens
|
||||||
|
} from '$lib/utils';
|
||||||
|
|
||||||
import { tick, createEventDispatcher, getContext } from 'svelte';
|
|
||||||
import Name from './Name.svelte';
|
import Name from './Name.svelte';
|
||||||
import ProfileImage from './ProfileImage.svelte';
|
import ProfileImage from './ProfileImage.svelte';
|
||||||
import { models, settings } from '$lib/stores';
|
|
||||||
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
import Tooltip from '$lib/components/common/Tooltip.svelte';
|
||||||
|
|
||||||
import { user as _user } from '$lib/stores';
|
|
||||||
import { getFileContentById } from '$lib/apis/files';
|
|
||||||
import FileItem from '$lib/components/common/FileItem.svelte';
|
import FileItem from '$lib/components/common/FileItem.svelte';
|
||||||
import { marked } from 'marked';
|
|
||||||
import { processResponseContent, replaceTokens } from '$lib/utils';
|
|
||||||
import MarkdownTokens from './Markdown/MarkdownTokens.svelte';
|
|
||||||
import Markdown from './Markdown.svelte';
|
import Markdown from './Markdown.svelte';
|
||||||
|
|
||||||
const i18n = getContext('i18n');
|
const i18n = getContext('i18n');
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
export let user;
|
export let user;
|
||||||
export let message;
|
|
||||||
export let siblings;
|
|
||||||
export let isFirstMessage: boolean;
|
|
||||||
export let readOnly: boolean;
|
|
||||||
|
|
||||||
export let confirmEditMessage: Function;
|
export let history;
|
||||||
|
export let messageId;
|
||||||
|
|
||||||
|
export let siblings;
|
||||||
|
|
||||||
export let showPreviousMessage: Function;
|
export let showPreviousMessage: Function;
|
||||||
export let showNextMessage: Function;
|
export let showNextMessage: Function;
|
||||||
export let copyToClipboard: Function;
|
|
||||||
|
export let editMessage: Function;
|
||||||
|
|
||||||
|
export let isFirstMessage: boolean;
|
||||||
|
export let readOnly: boolean;
|
||||||
|
|
||||||
let edit = false;
|
let edit = false;
|
||||||
let editedContent = '';
|
let editedContent = '';
|
||||||
let messageEditTextAreaElement: HTMLTextAreaElement;
|
let messageEditTextAreaElement: HTMLTextAreaElement;
|
||||||
|
|
||||||
|
let message = JSON.parse(JSON.stringify(history.messages[messageId]));
|
||||||
|
$: if (history.messages) {
|
||||||
|
if (JSON.stringify(message) !== JSON.stringify(history.messages[messageId])) {
|
||||||
|
message = JSON.parse(JSON.stringify(history.messages[messageId]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyToClipboard = async (text) => {
|
||||||
|
const res = await _copyToClipboard(text);
|
||||||
|
if (res) {
|
||||||
|
toast.success($i18n.t('Copying to clipboard was successful!'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const editMessageHandler = async () => {
|
const editMessageHandler = async () => {
|
||||||
edit = true;
|
edit = true;
|
||||||
editedContent = message.content;
|
editedContent = message.content;
|
||||||
@ -46,7 +66,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const editMessageConfirmHandler = async (submit = true) => {
|
const editMessageConfirmHandler = async (submit = true) => {
|
||||||
confirmEditMessage(message.id, editedContent, submit);
|
editMessage(message.id, editedContent, submit);
|
||||||
|
|
||||||
edit = false;
|
edit = false;
|
||||||
editedContent = '';
|
editedContent = '';
|
||||||
@ -60,6 +80,10 @@
|
|||||||
const deleteMessageHandler = async () => {
|
const deleteMessageHandler = async () => {
|
||||||
dispatch('delete', message.id);
|
dispatch('delete', message.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
console.log('UserMessage mounted');
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class=" flex w-full user-message" dir={$settings.chatDirection} id="message-{message.id}">
|
<div class=" flex w-full user-message" dir={$settings.chatDirection} id="message-{message.id}">
|
||||||
|
@ -155,7 +155,7 @@
|
|||||||
bind:autoScroll
|
bind:autoScroll
|
||||||
bottomPadding={files.length > 0}
|
bottomPadding={files.length > 0}
|
||||||
sendPrompt={() => {}}
|
sendPrompt={() => {}}
|
||||||
continueGeneration={() => {}}
|
continueResponse={() => {}}
|
||||||
regenerateResponse={() => {}}
|
regenerateResponse={() => {}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user