import type { LayoutServerLoad } from "./$types"; import { collections } from "$lib/server/database"; import type { Conversation } from "$lib/types/Conversation"; import { UrlDependency } from "$lib/types/UrlDependency"; import { defaultModel, models, oldModels, validateModel } from "$lib/server/models"; import { authCondition, requiresUser } from "$lib/server/auth"; import { DEFAULT_SETTINGS } from "$lib/types/Settings"; import { env } from "$env/dynamic/private"; import { ObjectId } from "mongodb"; import type { ConvSidebar } from "$lib/types/ConvSidebar"; import { toolFromConfigs } from "$lib/server/tools"; import { MetricsServer } from "$lib/server/metrics"; import type { ToolFront, ToolInputFile } from "$lib/types/Tool"; import { ReviewStatus } from "$lib/types/Review"; import { base } from "$app/paths"; export const load: LayoutServerLoad = async ({ locals, depends, fetch }) => { depends(UrlDependency.ConversationList); const settings = await collections.settings.findOne(authCondition(locals)); // If the active model in settings is not valid, set it to the default model. This can happen if model was disabled. if ( settings && !validateModel(models).safeParse(settings?.activeModel).success && !settings.assistants?.map((el) => el.toString())?.includes(settings?.activeModel) ) { settings.activeModel = defaultModel.id; await collections.settings.updateOne(authCondition(locals), { $set: { activeModel: defaultModel.id }, }); } // if the model is unlisted, set the active model to the default model if ( settings?.activeModel && models.find((m) => m.id === settings?.activeModel)?.unlisted === true ) { settings.activeModel = defaultModel.id; await collections.settings.updateOne(authCondition(locals), { $set: { activeModel: defaultModel.id }, }); } const enableAssistants = env.ENABLE_ASSISTANTS === "true"; const assistantActive = !models.map(({ id }) => id).includes(settings?.activeModel ?? ""); const assistant = assistantActive ? await collections.assistants.findOne({ _id: new ObjectId(settings?.activeModel), }) : null; const nConversations = await collections.conversations.countDocuments(authCondition(locals)); const conversations = nConversations === 0 ? Promise.resolve([]) : fetch(`${base}/api/conversations`) .then((res) => res.json()) .then( ( convs: Pick[] ) => convs.map((conv) => ({ ...conv, updatedAt: new Date(conv.updatedAt), })) ); const userAssistants = settings?.assistants?.map((assistantId) => assistantId.toString()) ?? []; const userAssistantsSet = new Set(userAssistants); const assistants = conversations.then((conversations) => collections.assistants .find({ _id: { $in: [ ...userAssistants.map((el) => new ObjectId(el)), ...(conversations.map((conv) => conv.assistantId).filter((el) => !!el) as ObjectId[]), ], }, }) .toArray() ); const messagesBeforeLogin = env.MESSAGES_BEFORE_LOGIN ? parseInt(env.MESSAGES_BEFORE_LOGIN) : 0; let loginRequired = false; if (requiresUser && !locals.user) { if (messagesBeforeLogin === 0) { loginRequired = true; } else if (nConversations >= messagesBeforeLogin) { loginRequired = true; } else { // get the number of messages where `from === "assistant"` across all conversations. const totalMessages = ( await collections.conversations .aggregate([ { $match: { ...authCondition(locals), "messages.from": "assistant" } }, { $project: { messages: 1 } }, { $limit: messagesBeforeLogin + 1 }, { $unwind: "$messages" }, { $match: { "messages.from": "assistant" } }, { $count: "messages" }, ]) .toArray() )[0]?.messages ?? 0; loginRequired = totalMessages >= messagesBeforeLogin; } } const toolUseDuration = (await MetricsServer.getMetrics().tool.toolUseDuration.get()).values; const configToolIds = toolFromConfigs.map((el) => el._id.toString()); let activeCommunityToolIds = (settings?.tools ?? []).filter( (key) => !configToolIds.includes(key) ); if (assistant) { activeCommunityToolIds = [...activeCommunityToolIds, ...(assistant.tools ?? [])]; } const communityTools = await collections.tools .find({ _id: { $in: activeCommunityToolIds.map((el) => new ObjectId(el)) } }) .toArray() .then((tools) => tools.map((tool) => ({ ...tool, isHidden: false, isOnByDefault: true, isLocked: true, })) ); return { nConversations, conversations: await conversations.then( async (convs) => await Promise.all( convs.map(async (conv) => { if (settings?.hideEmojiOnSidebar) { conv.title = conv.title.replace(/\p{Emoji}/gu, ""); } // remove invalid unicode and trim whitespaces conv.title = conv.title.replace(/\uFFFD/gu, "").trimStart(); let avatarUrl: string | undefined = undefined; if (conv.assistantId) { const hash = ( await collections.assistants.findOne({ _id: new ObjectId(conv.assistantId), }) )?.avatar; if (hash) { avatarUrl = `/settings/assistants/${conv.assistantId}/avatar.jpg?hash=${hash}`; } } return { id: conv._id.toString(), title: conv.title, model: conv.model ?? defaultModel, updatedAt: conv.updatedAt, assistantId: conv.assistantId?.toString(), avatarUrl, } satisfies ConvSidebar; }) ) ), settings: { searchEnabled: !!( env.SERPAPI_KEY || env.SERPER_API_KEY || env.SERPSTACK_API_KEY || env.SEARCHAPI_KEY || env.YDC_API_KEY || env.USE_LOCAL_WEBSEARCH || env.SEARXNG_QUERY_URL || env.BING_SUBSCRIPTION_KEY ), ethicsModalAccepted: !!settings?.ethicsModalAcceptedAt, ethicsModalAcceptedAt: settings?.ethicsModalAcceptedAt ?? null, activeModel: settings?.activeModel ?? DEFAULT_SETTINGS.activeModel, hideEmojiOnSidebar: settings?.hideEmojiOnSidebar ?? false, shareConversationsWithModelAuthors: settings?.shareConversationsWithModelAuthors ?? DEFAULT_SETTINGS.shareConversationsWithModelAuthors, customPrompts: settings?.customPrompts ?? {}, assistants: userAssistants, tools: settings?.tools ?? toolFromConfigs .filter((el) => !el.isHidden && el.isOnByDefault) .map((el) => el._id.toString()), disableStream: settings?.disableStream ?? DEFAULT_SETTINGS.disableStream, directPaste: settings?.directPaste ?? DEFAULT_SETTINGS.directPaste, }, models: models.map((model) => ({ id: model.id, name: model.name, websiteUrl: model.websiteUrl, modelUrl: model.modelUrl, tokenizer: model.tokenizer, datasetName: model.datasetName, datasetUrl: model.datasetUrl, displayName: model.displayName, description: model.description, reasoning: !!model.reasoning, logoUrl: model.logoUrl, promptExamples: model.promptExamples, parameters: model.parameters, preprompt: model.preprompt, multimodal: model.multimodal, multimodalAcceptedMimetypes: model.multimodalAcceptedMimetypes, tools: model.tools, unlisted: model.unlisted, hasInferenceAPI: model.hasInferenceAPI, })), oldModels, tools: [...toolFromConfigs, ...communityTools] .filter((tool) => !tool?.isHidden) .map( (tool) => ({ _id: tool._id.toString(), type: tool.type, displayName: tool.displayName, name: tool.name, description: tool.description, mimeTypes: (tool.inputs ?? []) .filter((input): input is ToolInputFile => input.type === "file") .map((input) => (input as ToolInputFile).mimeTypes) .flat(), isOnByDefault: tool.isOnByDefault ?? true, isLocked: tool.isLocked ?? true, timeToUseMS: toolUseDuration.find( (el) => el.labels.tool === tool._id.toString() && el.labels.quantile === 0.9 )?.value ?? 15_000, color: tool.color, icon: tool.icon, }) satisfies ToolFront ), communityToolCount: await collections.tools.countDocuments({ type: "community", review: ReviewStatus.APPROVED, }), assistants: assistants.then((assistants) => assistants .filter((el) => userAssistantsSet.has(el._id.toString())) .map((el) => ({ ...el, _id: el._id.toString(), createdById: undefined, createdByMe: el.createdById.toString() === (locals.user?._id ?? locals.sessionId).toString(), })) ), user: locals.user && { id: locals.user._id.toString(), username: locals.user.username, avatarUrl: locals.user.avatarUrl, email: locals.user.email, logoutDisabled: locals.user.logoutDisabled, isAdmin: locals.user.isAdmin ?? false, isEarlyAccess: locals.user.isEarlyAccess ?? false, }, assistant: assistant ? JSON.parse(JSON.stringify(assistant)) : null, enableAssistants, enableAssistantsRAG: env.ENABLE_ASSISTANTS_RAG === "true", enableCommunityTools: env.COMMUNITY_TOOLS === "true", loginRequired, loginEnabled: requiresUser, guestMode: requiresUser && messagesBeforeLogin > 0, }; };