refac
This commit is contained in:
+107
-125
@@ -81,7 +81,6 @@
|
||||
import { processWeb, processWebSearch, processYoutubeVideo } from '$lib/apis/retrieval';
|
||||
import { getAndUpdateUserLocation, getUserSettings } from '$lib/apis/users';
|
||||
import {
|
||||
chatCompleted,
|
||||
generateQueries,
|
||||
chatAction,
|
||||
generateMoACompletion,
|
||||
@@ -491,6 +490,22 @@
|
||||
if (autoScroll) {
|
||||
scrollToBottom('smooth');
|
||||
}
|
||||
} else if (type === 'chat:outlet') {
|
||||
// Outlet filter ran on backend — sync in-memory state
|
||||
const outletMessages = data.messages ?? [];
|
||||
for (const msg of outletMessages) {
|
||||
if (msg?.id && history.messages[msg.id]) {
|
||||
const existing = history.messages[msg.id];
|
||||
if (existing.content !== msg.content) {
|
||||
history.messages[msg.id] = {
|
||||
...existing,
|
||||
originalContent: existing.content,
|
||||
...msg
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
history = history;
|
||||
} else if (type === 'chat:message:favorite') {
|
||||
// Update message favorite status
|
||||
message.favorite = data.favorite;
|
||||
@@ -1361,6 +1376,17 @@
|
||||
taskIds = taskRes.task_ids;
|
||||
}
|
||||
|
||||
// If no active tasks and current message is incomplete, generation was interrupted
|
||||
const currentMessage = history.currentId ? history.messages[history.currentId] : null;
|
||||
if (
|
||||
currentMessage &&
|
||||
currentMessage.role === 'assistant' &&
|
||||
!currentMessage.done &&
|
||||
(!taskIds || taskIds.length === 0)
|
||||
) {
|
||||
currentMessage.done = true;
|
||||
}
|
||||
|
||||
await tick();
|
||||
|
||||
return true;
|
||||
@@ -1416,71 +1442,12 @@
|
||||
};
|
||||
|
||||
const chatCompletedHandler = async (_chatId, modelId, responseMessageId, messages) => {
|
||||
if (!responseMessageId) {
|
||||
console.error('chatCompleted: missing message id', {
|
||||
chatId: _chatId,
|
||||
modelId,
|
||||
messageCount: messages?.length ?? 0
|
||||
});
|
||||
return;
|
||||
// Backend handles outlet filters and persistence inline.
|
||||
// Just refresh the sidebar chat list.
|
||||
if ($chatId == _chatId && !$temporaryChatEnabled) {
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
}
|
||||
|
||||
const res = await chatCompleted(localStorage.token, {
|
||||
model: modelId,
|
||||
messages: messages.map((m) => ({
|
||||
id: m.id,
|
||||
role: m.role,
|
||||
content: m.content,
|
||||
info: m.info ? m.info : undefined,
|
||||
timestamp: m.timestamp,
|
||||
...(m.usage ? { usage: m.usage } : {}),
|
||||
...(m.sources ? { sources: m.sources } : {})
|
||||
})),
|
||||
filter_ids: selectedFilterIds.length > 0 ? selectedFilterIds : undefined,
|
||||
model_item: $models.find((m) => m.id === modelId),
|
||||
chat_id: _chatId,
|
||||
session_id: $socket?.id,
|
||||
id: responseMessageId
|
||||
}).catch((error) => {
|
||||
toast.error(`${error}`);
|
||||
messages.at(-1).error = { content: error };
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
if (res !== null && res.messages) {
|
||||
// Update chat history with the new messages
|
||||
for (const message of res.messages) {
|
||||
if (message?.id) {
|
||||
// Add null check for message and message.id
|
||||
history.messages[message.id] = {
|
||||
...history.messages[message.id],
|
||||
...(history.messages[message.id].content !== message.content
|
||||
? { originalContent: history.messages[message.id].content }
|
||||
: {}),
|
||||
...message
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await tick();
|
||||
|
||||
if ($chatId == _chatId) {
|
||||
if (!$temporaryChatEnabled) {
|
||||
chat = await updateChatById(localStorage.token, _chatId, {
|
||||
models: selectedModels,
|
||||
messages: messages,
|
||||
history: history,
|
||||
params: params,
|
||||
files: chatFiles
|
||||
});
|
||||
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
}
|
||||
}
|
||||
|
||||
taskIds = null;
|
||||
};
|
||||
|
||||
@@ -1894,7 +1861,7 @@
|
||||
|
||||
saveSessionSelectedModels();
|
||||
|
||||
await sendMessage(history, userMessageId, { newChat: true });
|
||||
await sendMessage(history, userMessageId);
|
||||
};
|
||||
|
||||
const submitHandler = async (userPrompt, { _raw = false } = {}) => {
|
||||
@@ -1994,13 +1961,11 @@
|
||||
{
|
||||
messages = null,
|
||||
modelId = null,
|
||||
modelIdx = null,
|
||||
newChat = false
|
||||
modelIdx = null
|
||||
}: {
|
||||
messages?: any[] | null;
|
||||
modelId?: string | null;
|
||||
modelIdx?: number | null;
|
||||
newChat?: boolean;
|
||||
} = {}
|
||||
) => {
|
||||
if (autoScroll) {
|
||||
@@ -2019,6 +1984,8 @@
|
||||
: selectedModels;
|
||||
|
||||
// Create response messages for each selected model
|
||||
// Build message_ids map: {model_id: assistant_message_id}
|
||||
const messageIdsMap: Record<string, string> = {};
|
||||
for (const [_modelIdx, modelId] of selectedModelIds.entries()) {
|
||||
const model = $models.filter((m) => m.id === modelId).at(0);
|
||||
|
||||
@@ -2043,7 +2010,6 @@
|
||||
|
||||
// Append messageId to childrenIds of parent message
|
||||
if (parentId !== null && history.messages[parentId]) {
|
||||
// Add null check before accessing childrenIds
|
||||
history.messages[parentId].childrenIds = [
|
||||
...history.messages[parentId].childrenIds,
|
||||
responseMessageId
|
||||
@@ -2051,68 +2017,71 @@
|
||||
}
|
||||
|
||||
responseMessageIds[`${modelId}-${modelIdx ? modelIdx : _modelIdx}`] = responseMessageId;
|
||||
messageIdsMap[modelId] = responseMessageId;
|
||||
}
|
||||
}
|
||||
history = history;
|
||||
|
||||
// Create new chat if newChat is true and first user message
|
||||
if (newChat && _history.messages[_history.currentId].parentId === null) {
|
||||
_chatId = await initChatHandler(_history);
|
||||
// New chat — backend generates the chat_id on first request
|
||||
if (!_chatId) {
|
||||
if ($temporaryChatEnabled) {
|
||||
_chatId = `local:${$socket?.id}`;
|
||||
await chatId.set(_chatId);
|
||||
}
|
||||
await tick();
|
||||
}
|
||||
|
||||
await tick();
|
||||
|
||||
// Re-clone history so sendMessageSocket gets the response messages we just added
|
||||
_history = structuredClone(history);
|
||||
// Save chat after all messages have been created
|
||||
await saveChatHandler(_chatId, _history);
|
||||
|
||||
await Promise.all(
|
||||
selectedModelIds.map(async (modelId, _modelIdx) => {
|
||||
console.log('modelId', modelId);
|
||||
const model = $models.filter((m) => m.id === modelId).at(0);
|
||||
// Vision capability check
|
||||
for (const mid of selectedModelIds) {
|
||||
const model = $models.filter((m) => m.id === mid).at(0);
|
||||
if (model) {
|
||||
const hasImages = createMessagesList(_history, parentId).some((message) =>
|
||||
message.files?.some(
|
||||
(file) => file.type === 'image' || (file?.content_type ?? '').startsWith('image/')
|
||||
)
|
||||
);
|
||||
|
||||
if (model) {
|
||||
// If there are image files, check if model is vision capable
|
||||
// Skip this check if image generation is enabled, as images may be for editing or are generated outputs in the history
|
||||
const hasImages = createMessagesList(_history, parentId).some((message) =>
|
||||
message.files?.some(
|
||||
(file) => file.type === 'image' || (file?.content_type ?? '').startsWith('image/')
|
||||
)
|
||||
if (
|
||||
hasImages &&
|
||||
!(model.info?.meta?.capabilities?.vision ?? true) &&
|
||||
!imageGenerationEnabled
|
||||
) {
|
||||
toast.error(
|
||||
$i18n.t('Model {{modelName}} is not vision capable', {
|
||||
modelName: model.name ?? model.id
|
||||
})
|
||||
);
|
||||
|
||||
if (
|
||||
hasImages &&
|
||||
!(model.info?.meta?.capabilities?.vision ?? true) &&
|
||||
!imageGenerationEnabled
|
||||
) {
|
||||
toast.error(
|
||||
$i18n.t('Model {{modelName}} is not vision capable', {
|
||||
modelName: model.name ?? model.id
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
let responseMessageId =
|
||||
responseMessageIds[`${modelId}-${modelIdx ? modelIdx : _modelIdx}`];
|
||||
const chatEventEmitter = await getChatEventEmitter(model.id, _chatId);
|
||||
|
||||
scrollToBottom();
|
||||
await sendMessageSocket(
|
||||
model,
|
||||
messages && messages.length > 0
|
||||
? messages
|
||||
: createMessagesList(_history, responseMessageId),
|
||||
_history,
|
||||
responseMessageId,
|
||||
_chatId
|
||||
);
|
||||
|
||||
if (chatEventEmitter) clearInterval(chatEventEmitter);
|
||||
} else {
|
||||
toast.error($i18n.t(`Model {{modelId}} not found`, { modelId }));
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Single request — backend fans out to all models
|
||||
const primaryModelId = selectedModelIds[0];
|
||||
const primaryModel = $models.filter((m) => m.id === primaryModelId).at(0);
|
||||
const primaryResponseMessageId = messageIdsMap[primaryModelId];
|
||||
|
||||
if (primaryModel && primaryResponseMessageId) {
|
||||
const chatEventEmitter = await getChatEventEmitter(primaryModel.id, _chatId);
|
||||
|
||||
scrollToBottom();
|
||||
await sendMessageSocket(
|
||||
primaryModel,
|
||||
messages && messages.length > 0
|
||||
? messages
|
||||
: createMessagesList(_history, primaryResponseMessageId),
|
||||
_history,
|
||||
primaryResponseMessageId,
|
||||
_chatId,
|
||||
selectedModelIds.length > 1 ? messageIdsMap : undefined
|
||||
);
|
||||
|
||||
if (chatEventEmitter) clearInterval(chatEventEmitter);
|
||||
}
|
||||
};
|
||||
|
||||
const getFeatures = () => {
|
||||
@@ -2167,7 +2136,7 @@
|
||||
.map((token) => decodeURIComponent(JSON.parse(`"${token.replace(/"/g, '\\"')}"`)));
|
||||
};
|
||||
|
||||
const sendMessageSocket = async (model, _messages, _history, responseMessageId, _chatId) => {
|
||||
const sendMessageSocket = async (model, _messages, _history, responseMessageId, _chatId, messageIdsMap?: Record<string, string>) => {
|
||||
const responseMessage = _history.messages[responseMessageId];
|
||||
const userMessage = _history.messages[responseMessage.parentId];
|
||||
|
||||
@@ -2357,12 +2326,13 @@
|
||||
model_item: $models.find((m) => m.id === model.id),
|
||||
|
||||
session_id: $socket?.id,
|
||||
chat_id: $chatId,
|
||||
chat_id: _chatId || undefined,
|
||||
folder_id: $selectedFolder?.id ?? undefined,
|
||||
|
||||
id: responseMessageId,
|
||||
parent_id: userMessage?.id ?? null,
|
||||
parent_message: userMessage,
|
||||
...(messageIdsMap ? { message_ids: messageIdsMap } : {}),
|
||||
parent_id: userMessage?.parentId ?? null,
|
||||
user_message: userMessage,
|
||||
|
||||
background_tasks: {
|
||||
...(!$temporaryChatEnabled &&
|
||||
@@ -2419,10 +2389,22 @@
|
||||
if (res.error) {
|
||||
await handleOpenAIError(res.error, responseMessage);
|
||||
} else {
|
||||
// Backend returns task_ids (multi-model) or task_id (single model)
|
||||
const newTaskIds = res.task_ids ?? (res.task_id ? [res.task_id] : []);
|
||||
if (taskIds) {
|
||||
taskIds.push(res.task_id);
|
||||
taskIds.push(...newTaskIds);
|
||||
} else {
|
||||
taskIds = [res.task_id];
|
||||
taskIds = newTaskIds;
|
||||
}
|
||||
|
||||
// Backend returns chat_id for new chats — set store + URL
|
||||
if (res.chat_id && $chatId !== res.chat_id) {
|
||||
await chatId.set(res.chat_id);
|
||||
if (!$temporaryChatEnabled) {
|
||||
window.history.replaceState(history.state, '', `/c/${res.chat_id}`);
|
||||
currentChatPage.set(1);
|
||||
await chats.set(await getChatList(localStorage.token, $currentChatPage));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,10 +88,21 @@
|
||||
|
||||
let mouseOver = false;
|
||||
|
||||
// Local state: tracks the last updatedAt seen while the user was viewing
|
||||
// this chat. Survives prop refreshes from sidebar data re-fetches that
|
||||
// would overwrite the `lastReadAt` prop with a stale server value.
|
||||
let viewedAt: number | null = null;
|
||||
|
||||
$: if (id === $chatId) {
|
||||
viewedAt = updatedAt ?? Date.now() / 1000;
|
||||
}
|
||||
|
||||
$: effectiveReadAt = Math.max(lastReadAt ?? 0, viewedAt ?? 0) || null;
|
||||
|
||||
$: unread =
|
||||
id !== $chatId &&
|
||||
!$activeChatIds.has(id) &&
|
||||
(lastReadAt === null || (updatedAt !== null && updatedAt > lastReadAt));
|
||||
(effectiveReadAt === null || (updatedAt !== null && updatedAt > effectiveReadAt));
|
||||
|
||||
const loadChat = async () => {
|
||||
if (!chat) {
|
||||
|
||||
Reference in New Issue
Block a user