mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 08:12:00 +02:00
🪨 feat: AWS Bedrock support (#3935)
* feat: Add BedrockIcon component to SVG library * feat: EModelEndpoint.bedrock * feat: first pass, bedrock chat. note: AgentClient is returning `agents` as conversation.endpoint * fix: declare endpoint in initialization step * chore: Update @librechat/agents dependency to version 1.4.5 * feat: backend content aggregation for agents/bedrock * feat: abort agent requests * feat: AWS Bedrock icons * WIP: agent provider schema parsing * chore: Update EditIcon props type * refactor(useGenerationsByLatest): make agents and bedrock editable * refactor: non-assistant message content, parts * fix: Bedrock response `sender` * fix: use endpointOption.model_parameters not endpointOption.modelOptions * fix: types for step handler * refactor: Update Agents.ToolCallDelta type * refactor: Remove unnecessary assignment of parentMessageId in AskController * refactor: remove unnecessary assignment of parentMessageId (agent request handler) * fix(bedrock/agents): message regeneration * refactor: dynamic form elements using react-hook-form Controllers * fix: agent icons/labels for messages * fix: agent actions * fix: use of new dynamic tags causing application crash * refactor: dynamic settings touch-ups * refactor: update Slider component to allow custom track class name * refactor: update DynamicSlider component styles * refactor: use Constants value for GLOBAL_PROJECT_NAME (enum) * feat: agent share global methods/controllers * fix: agents query * fix: `getResponseModel` * fix: share prompt a11y issue * refactor: update SharePrompt dialog theme styles * refactor: explicit typing for SharePrompt * feat: add agent roles/permissions * chore: update @librechat/agents dependency to version 1.4.7 for tool_call_ids edge case * fix(Anthropic): messages.X.content.Y.tool_use.input: Input should be a valid dictionary * fix: handle text parts with tool_call_ids and empty text * fix: role initialization * refactor: don't make instructions required * refactor: improve typing of Text part * fix: setShowStopButton for agents route * chore: remove params for now * fix: add streamBuffer and streamRate to help prevent 'Overloaded' errors from Anthropic API * refactor: remove console.log statement in ContentRender component * chore: typing, rename Context to Delete Button * chore(DeleteButton): logging * refactor(Action): make accessible * style(Action): improve a11y again * refactor: remove use/mention of mongoose sessions * feat: first pass, sharing agents * feat: visual indicator for global agent, remove author when serving to non-author * wip: params * chore: fix typing issues * fix(schemas): typing * refactor: improve accessibility of ListCard component and fix console React warning * wip: reset templates for non-legacy new convos * Revert "wip: params" This reverts commitf8067e91d4
. * Revert "refactor: dynamic form elements using react-hook-form Controllers" This reverts commit2150c4815d
. * fix(Parameters): types and parameter effect update to only update local state to parameters * refactor: optimize useDebouncedInput hook for better performance * feat: first pass, anthropic bedrock params * chore: paramEndpoints check for endpointType too * fix: maxTokens to use coerceNumber.optional(), * feat: extra chat model params * chore: reduce code repetition * refactor: improve preset title handling in SaveAsPresetDialog component * refactor: improve preset handling in HeaderOptions component * chore: improve typing, replace legacy dialog for SaveAsPresetDialog * feat: save as preset from parameters panel * fix: multi-search in select dropdown when using Option type * refactor: update default showDefault value to false in Dynamic components * feat: Bedrock presets settings * chore: config, fix agents schema, update config version * refactor: update AWS region variable name in bedrock options endpoint to BEDROCK_AWS_DEFAULT_REGION * refactor: update baseEndpointSchema in config.ts to include baseURL property * refactor: update createRun function to include req parameter and set streamRate based on provider * feat: availableRegions via config * refactor: remove unused demo agent controller file * WIP: title * Update @librechat/agents to version 1.5.0 * chore: addTitle.js to handle empty responseText * feat: support images and titles * feat: context token updates * Refactor BaseClient test to use expect.objectContaining * refactor: add model select, remove header options params, move side panel params below prompts * chore: update models list, catch title error * feat: model service for bedrock models (env) * chore: Remove verbose debug log in AgentClient class following stream * feat(bedrock): track token spend; fix: token rates, value key mapping for AWS models * refactor: handle streamRate in `handleLLMNewToken` callback * chore: AWS Bedrock example config in `.env.example` * refactor: Rename bedrockMeta to bedrockGeneral in settings.ts and use for AI21 and Amazon Bedrock providers * refactor: Update `.env.example` with AWS Bedrock model IDs URL and additional notes * feat: titleModel support for bedrock * refactor: Update `.env.example` with additional notes for AWS Bedrock model IDs
This commit is contained in:
parent
8c14360263
commit
d59b62174f
134 changed files with 3684 additions and 1213 deletions
17
.env.example
17
.env.example
|
@ -111,6 +111,23 @@ ANTHROPIC_API_KEY=user_provided
|
||||||
BINGAI_TOKEN=user_provided
|
BINGAI_TOKEN=user_provided
|
||||||
# BINGAI_HOST=https://cn.bing.com
|
# BINGAI_HOST=https://cn.bing.com
|
||||||
|
|
||||||
|
#=================#
|
||||||
|
# AWS Bedrock #
|
||||||
|
#=================#
|
||||||
|
|
||||||
|
# BEDROCK_AWS_DEFAULT_REGION=us-east-1 # A default region must be provided
|
||||||
|
# BEDROCK_AWS_ACCESS_KEY_ID=someAccessKey
|
||||||
|
# BEDROCK_AWS_SECRET_ACCESS_KEY=someSecretAccessKey
|
||||||
|
|
||||||
|
# Note: This example list is not meant to be exhaustive. If omitted, all known, supported model IDs will be included for you.
|
||||||
|
# BEDROCK_AWS_MODELS=anthropic.claude-3-5-sonnet-20240620-v1:0,meta.llama3-1-8b-instruct-v1:0
|
||||||
|
|
||||||
|
# See all Bedrock model IDs here: https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html#model-ids-arns
|
||||||
|
|
||||||
|
# Notes on specific models:
|
||||||
|
# 'ai21.j2-mid-v1', # Not supported, as it doesn't support streaming
|
||||||
|
# 'ai21.j2-ultra-v1', # Not supported, as it doesn't support conversation history
|
||||||
|
|
||||||
#============#
|
#============#
|
||||||
# Google #
|
# Google #
|
||||||
#============#
|
#============#
|
||||||
|
|
|
@ -2,6 +2,7 @@ const crypto = require('crypto');
|
||||||
const fetch = require('node-fetch');
|
const fetch = require('node-fetch');
|
||||||
const {
|
const {
|
||||||
supportsBalanceCheck,
|
supportsBalanceCheck,
|
||||||
|
isAgentsEndpoint,
|
||||||
ErrorTypes,
|
ErrorTypes,
|
||||||
Constants,
|
Constants,
|
||||||
CacheKeys,
|
CacheKeys,
|
||||||
|
@ -66,6 +67,17 @@ class BaseClient {
|
||||||
throw new Error('Subclasses attempted to call summarizeMessages without implementing it');
|
throw new Error('Subclasses attempted to call summarizeMessages without implementing it');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
getResponseModel() {
|
||||||
|
if (isAgentsEndpoint(this.options.endpoint) && this.options.agent && this.options.agent.id) {
|
||||||
|
return this.options.agent.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.modelOptions.model;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract method to get the token count for a message. Subclasses must implement this method.
|
* Abstract method to get the token count for a message. Subclasses must implement this method.
|
||||||
* @param {TMessage} responseMessage
|
* @param {TMessage} responseMessage
|
||||||
|
@ -217,6 +229,7 @@ class BaseClient {
|
||||||
userMessage,
|
userMessage,
|
||||||
conversationId,
|
conversationId,
|
||||||
responseMessageId,
|
responseMessageId,
|
||||||
|
sender: this.sender,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -557,7 +570,7 @@ class BaseClient {
|
||||||
parentMessageId: userMessage.messageId,
|
parentMessageId: userMessage.messageId,
|
||||||
isCreatedByUser: false,
|
isCreatedByUser: false,
|
||||||
isEdited,
|
isEdited,
|
||||||
model: this.modelOptions.model,
|
model: this.getResponseModel(),
|
||||||
sender: this.sender,
|
sender: this.sender,
|
||||||
promptTokens,
|
promptTokens,
|
||||||
iconURL: this.options.iconURL,
|
iconURL: this.options.iconURL,
|
||||||
|
|
|
@ -170,7 +170,15 @@ const formatAgentMessages = (payload) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: `tool_calls` list is defined when constructed by `AIMessage` class, and outputs should be excluded from it
|
// Note: `tool_calls` list is defined when constructed by `AIMessage` class, and outputs should be excluded from it
|
||||||
const { output, ...tool_call } = part.tool_call;
|
const { output, args: _args, ...tool_call } = part.tool_call;
|
||||||
|
// TODO: investigate; args as dictionary may need to be provider-or-tool-specific
|
||||||
|
let args = _args;
|
||||||
|
try {
|
||||||
|
args = JSON.parse(args);
|
||||||
|
} catch (e) {
|
||||||
|
// failed to parse, leave as is
|
||||||
|
}
|
||||||
|
tool_call.args = args;
|
||||||
lastAIMessage.tool_calls.push(tool_call);
|
lastAIMessage.tool_calls.push(tool_call);
|
||||||
|
|
||||||
// Add the corresponding ToolMessage
|
// Add the corresponding ToolMessage
|
||||||
|
|
|
@ -565,11 +565,13 @@ describe('BaseClient', () => {
|
||||||
const getReqData = jest.fn();
|
const getReqData = jest.fn();
|
||||||
const opts = { getReqData };
|
const opts = { getReqData };
|
||||||
const response = await TestClient.sendMessage('Hello, world!', opts);
|
const response = await TestClient.sendMessage('Hello, world!', opts);
|
||||||
expect(getReqData).toHaveBeenCalledWith({
|
expect(getReqData).toHaveBeenCalledWith(
|
||||||
userMessage: expect.objectContaining({ text: 'Hello, world!' }),
|
expect.objectContaining({
|
||||||
conversationId: response.conversationId,
|
userMessage: expect.objectContaining({ text: 'Hello, world!' }),
|
||||||
responseMessageId: response.messageId,
|
conversationId: response.conversationId,
|
||||||
});
|
responseMessageId: response.messageId,
|
||||||
|
}),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('onStart is called with the correct arguments', async () => {
|
test('onStart is called with the correct arguments', async () => {
|
||||||
|
|
|
@ -5,17 +5,16 @@ const Action = mongoose.model('action', actionSchema);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update an action with new data without overwriting existing properties,
|
* Update an action with new data without overwriting existing properties,
|
||||||
* or create a new action if it doesn't exist, within a transaction session if provided.
|
* or create a new action if it doesn't exist.
|
||||||
*
|
*
|
||||||
* @param {Object} searchParams - The search parameters to find the action to update.
|
* @param {Object} searchParams - The search parameters to find the action to update.
|
||||||
* @param {string} searchParams.action_id - The ID of the action to update.
|
* @param {string} searchParams.action_id - The ID of the action to update.
|
||||||
* @param {string} searchParams.user - The user ID of the action's author.
|
* @param {string} searchParams.user - The user ID of the action's author.
|
||||||
* @param {Object} updateData - An object containing the properties to update.
|
* @param {Object} updateData - An object containing the properties to update.
|
||||||
* @param {mongoose.ClientSession} [session] - The transaction session to use.
|
|
||||||
* @returns {Promise<Action>} The updated or newly created action document as a plain object.
|
* @returns {Promise<Action>} The updated or newly created action document as a plain object.
|
||||||
*/
|
*/
|
||||||
const updateAction = async (searchParams, updateData, session = null) => {
|
const updateAction = async (searchParams, updateData) => {
|
||||||
const options = { new: true, upsert: true, session };
|
const options = { new: true, upsert: true };
|
||||||
return await Action.findOneAndUpdate(searchParams, updateData, options).lean();
|
return await Action.findOneAndUpdate(searchParams, updateData, options).lean();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -49,31 +48,27 @@ const getActions = async (searchParams, includeSensitive = false) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes an action by params, within a transaction session if provided.
|
* Deletes an action by params.
|
||||||
*
|
*
|
||||||
* @param {Object} searchParams - The search parameters to find the action to delete.
|
* @param {Object} searchParams - The search parameters to find the action to delete.
|
||||||
* @param {string} searchParams.action_id - The ID of the action to delete.
|
* @param {string} searchParams.action_id - The ID of the action to delete.
|
||||||
* @param {string} searchParams.user - The user ID of the action's author.
|
* @param {string} searchParams.user - The user ID of the action's author.
|
||||||
* @param {mongoose.ClientSession} [session] - The transaction session to use (optional).
|
|
||||||
* @returns {Promise<Action>} A promise that resolves to the deleted action document as a plain object, or null if no document was found.
|
* @returns {Promise<Action>} A promise that resolves to the deleted action document as a plain object, or null if no document was found.
|
||||||
*/
|
*/
|
||||||
const deleteAction = async (searchParams, session = null) => {
|
const deleteAction = async (searchParams) => {
|
||||||
const options = session ? { session } : {};
|
return await Action.findOneAndDelete(searchParams).lean();
|
||||||
return await Action.findOneAndDelete(searchParams, options).lean();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes actions by params, within a transaction session if provided.
|
* Deletes actions by params.
|
||||||
*
|
*
|
||||||
* @param {Object} searchParams - The search parameters to find the actions to delete.
|
* @param {Object} searchParams - The search parameters to find the actions to delete.
|
||||||
* @param {string} searchParams.action_id - The ID of the action(s) to delete.
|
* @param {string} searchParams.action_id - The ID of the action(s) to delete.
|
||||||
* @param {string} searchParams.user - The user ID of the action's author.
|
* @param {string} searchParams.user - The user ID of the action's author.
|
||||||
* @param {mongoose.ClientSession} [session] - The transaction session to use (optional).
|
|
||||||
* @returns {Promise<Number>} A promise that resolves to the number of deleted action documents.
|
* @returns {Promise<Number>} A promise that resolves to the number of deleted action documents.
|
||||||
*/
|
*/
|
||||||
const deleteActions = async (searchParams, session = null) => {
|
const deleteActions = async (searchParams) => {
|
||||||
const options = session ? { session } : {};
|
const result = await Action.deleteMany(searchParams);
|
||||||
const result = await Action.deleteMany(searchParams, options);
|
|
||||||
return result.deletedCount;
|
return result.deletedCount;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
|
const { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants;
|
||||||
|
const {
|
||||||
|
getProjectByName,
|
||||||
|
addAgentIdsToProject,
|
||||||
|
removeAgentIdsFromProject,
|
||||||
|
removeAgentFromAllProjects,
|
||||||
|
} = require('./Project');
|
||||||
const agentSchema = require('./schema/agent');
|
const agentSchema = require('./schema/agent');
|
||||||
|
|
||||||
const Agent = mongoose.model('agent', agentSchema);
|
const Agent = mongoose.model('agent', agentSchema);
|
||||||
|
@ -24,18 +31,17 @@ const createAgent = async (agentData) => {
|
||||||
const getAgent = async (searchParameter) => await Agent.findOne(searchParameter).lean();
|
const getAgent = async (searchParameter) => await Agent.findOne(searchParameter).lean();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update an agent with new data without overwriting existing properties,
|
* Update an agent with new data without overwriting existing
|
||||||
* or create a new agent if it doesn't exist, within a transaction session if provided.
|
* properties, or create a new agent if it doesn't exist.
|
||||||
*
|
*
|
||||||
* @param {Object} searchParameter - The search parameters to find the agent to update.
|
* @param {Object} searchParameter - The search parameters to find the agent to update.
|
||||||
* @param {string} searchParameter.id - The ID of the agent to update.
|
* @param {string} searchParameter.id - The ID of the agent to update.
|
||||||
* @param {string} searchParameter.author - The user ID of the agent's author.
|
* @param {string} [searchParameter.author] - The user ID of the agent's author.
|
||||||
* @param {Object} updateData - An object containing the properties to update.
|
* @param {Object} updateData - An object containing the properties to update.
|
||||||
* @param {mongoose.ClientSession} [session] - The transaction session to use (optional).
|
|
||||||
* @returns {Promise<Agent>} The updated or newly created agent document as a plain object.
|
* @returns {Promise<Agent>} The updated or newly created agent document as a plain object.
|
||||||
*/
|
*/
|
||||||
const updateAgent = async (searchParameter, updateData, session = null) => {
|
const updateAgent = async (searchParameter, updateData) => {
|
||||||
const options = { new: true, upsert: true, session };
|
const options = { new: true, upsert: true };
|
||||||
return await Agent.findOneAndUpdate(searchParameter, updateData, options).lean();
|
return await Agent.findOneAndUpdate(searchParameter, updateData, options).lean();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -44,11 +50,15 @@ const updateAgent = async (searchParameter, updateData, session = null) => {
|
||||||
*
|
*
|
||||||
* @param {Object} searchParameter - The search parameters to find the agent to delete.
|
* @param {Object} searchParameter - The search parameters to find the agent to delete.
|
||||||
* @param {string} searchParameter.id - The ID of the agent to delete.
|
* @param {string} searchParameter.id - The ID of the agent to delete.
|
||||||
* @param {string} searchParameter.author - The user ID of the agent's author.
|
* @param {string} [searchParameter.author] - The user ID of the agent's author.
|
||||||
* @returns {Promise<void>} Resolves when the agent has been successfully deleted.
|
* @returns {Promise<void>} Resolves when the agent has been successfully deleted.
|
||||||
*/
|
*/
|
||||||
const deleteAgent = async (searchParameter) => {
|
const deleteAgent = async (searchParameter) => {
|
||||||
return await Agent.findOneAndDelete(searchParameter);
|
const agent = await Agent.findOneAndDelete(searchParameter);
|
||||||
|
if (agent) {
|
||||||
|
await removeAgentFromAllProjects(agent.id);
|
||||||
|
}
|
||||||
|
return agent;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,11 +68,24 @@ const deleteAgent = async (searchParameter) => {
|
||||||
* @returns {Promise<Object>} A promise that resolves to an object containing the agents data and pagination info.
|
* @returns {Promise<Object>} A promise that resolves to an object containing the agents data and pagination info.
|
||||||
*/
|
*/
|
||||||
const getListAgents = async (searchParameter) => {
|
const getListAgents = async (searchParameter) => {
|
||||||
const agents = await Agent.find(searchParameter, {
|
const { author, ...otherParams } = searchParameter;
|
||||||
|
|
||||||
|
let query = Object.assign({ author }, otherParams);
|
||||||
|
|
||||||
|
const globalProject = await getProjectByName(GLOBAL_PROJECT_NAME, ['agentIds']);
|
||||||
|
if (globalProject && (globalProject.agentIds?.length ?? 0) > 0) {
|
||||||
|
const globalQuery = { id: { $in: globalProject.agentIds }, ...otherParams };
|
||||||
|
delete globalQuery.author;
|
||||||
|
query = { $or: [globalQuery, query] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const agents = await Agent.find(query, {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 1,
|
name: 1,
|
||||||
avatar: 1,
|
avatar: 1,
|
||||||
|
projectIds: 1,
|
||||||
}).lean();
|
}).lean();
|
||||||
|
|
||||||
const hasMore = agents.length > 0;
|
const hasMore = agents.length > 0;
|
||||||
const firstId = agents.length > 0 ? agents[0].id : null;
|
const firstId = agents.length > 0 ? agents[0].id : null;
|
||||||
const lastId = agents.length > 0 ? agents[agents.length - 1].id : null;
|
const lastId = agents.length > 0 ? agents[agents.length - 1].id : null;
|
||||||
|
@ -75,10 +98,45 @@ const getListAgents = async (searchParameter) => {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the projects associated with an agent, adding and removing project IDs as specified.
|
||||||
|
* This function also updates the corresponding projects to include or exclude the agent ID.
|
||||||
|
*
|
||||||
|
* @param {string} agentId - The ID of the agent to update.
|
||||||
|
* @param {string[]} [projectIds] - Array of project IDs to add to the agent.
|
||||||
|
* @param {string[]} [removeProjectIds] - Array of project IDs to remove from the agent.
|
||||||
|
* @returns {Promise<MongoAgent>} The updated agent document.
|
||||||
|
* @throws {Error} If there's an error updating the agent or projects.
|
||||||
|
*/
|
||||||
|
const updateAgentProjects = async (agentId, projectIds, removeProjectIds) => {
|
||||||
|
const updateOps = {};
|
||||||
|
|
||||||
|
if (removeProjectIds && removeProjectIds.length > 0) {
|
||||||
|
for (const projectId of removeProjectIds) {
|
||||||
|
await removeAgentIdsFromProject(projectId, [agentId]);
|
||||||
|
}
|
||||||
|
updateOps.$pull = { projectIds: { $in: removeProjectIds } };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectIds && projectIds.length > 0) {
|
||||||
|
for (const projectId of projectIds) {
|
||||||
|
await addAgentIdsToProject(projectId, [agentId]);
|
||||||
|
}
|
||||||
|
updateOps.$addToSet = { projectIds: { $each: projectIds } };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(updateOps).length === 0) {
|
||||||
|
return await getAgent({ id: agentId });
|
||||||
|
}
|
||||||
|
|
||||||
|
return await updateAgent({ id: agentId }, updateOps);
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createAgent,
|
createAgent,
|
||||||
getAgent,
|
getAgent,
|
||||||
updateAgent,
|
updateAgent,
|
||||||
deleteAgent,
|
deleteAgent,
|
||||||
getListAgents,
|
getListAgents,
|
||||||
|
updateAgentProjects,
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,17 +5,16 @@ const Assistant = mongoose.model('assistant', assistantSchema);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update an assistant with new data without overwriting existing properties,
|
* Update an assistant with new data without overwriting existing properties,
|
||||||
* or create a new assistant if it doesn't exist, within a transaction session if provided.
|
* or create a new assistant if it doesn't exist.
|
||||||
*
|
*
|
||||||
* @param {Object} searchParams - The search parameters to find the assistant to update.
|
* @param {Object} searchParams - The search parameters to find the assistant to update.
|
||||||
* @param {string} searchParams.assistant_id - The ID of the assistant to update.
|
* @param {string} searchParams.assistant_id - The ID of the assistant to update.
|
||||||
* @param {string} searchParams.user - The user ID of the assistant's author.
|
* @param {string} searchParams.user - The user ID of the assistant's author.
|
||||||
* @param {Object} updateData - An object containing the properties to update.
|
* @param {Object} updateData - An object containing the properties to update.
|
||||||
* @param {mongoose.ClientSession} [session] - The transaction session to use (optional).
|
|
||||||
* @returns {Promise<AssistantDocument>} The updated or newly created assistant document as a plain object.
|
* @returns {Promise<AssistantDocument>} The updated or newly created assistant document as a plain object.
|
||||||
*/
|
*/
|
||||||
const updateAssistantDoc = async (searchParams, updateData, session = null) => {
|
const updateAssistantDoc = async (searchParams, updateData) => {
|
||||||
const options = { new: true, upsert: true, session };
|
const options = { new: true, upsert: true };
|
||||||
return await Assistant.findOneAndUpdate(searchParams, updateData, options).lean();
|
return await Assistant.findOneAndUpdate(searchParams, updateData, options).lean();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const { model } = require('mongoose');
|
const { model } = require('mongoose');
|
||||||
|
const { GLOBAL_PROJECT_NAME } = require('librechat-data-provider').Constants;
|
||||||
const projectSchema = require('~/models/schema/projectSchema');
|
const projectSchema = require('~/models/schema/projectSchema');
|
||||||
|
|
||||||
const Project = model('Project', projectSchema);
|
const Project = model('Project', projectSchema);
|
||||||
|
@ -33,7 +34,7 @@ const getProjectByName = async function (projectName, fieldsToSelect = null) {
|
||||||
const update = { $setOnInsert: { name: projectName } };
|
const update = { $setOnInsert: { name: projectName } };
|
||||||
const options = {
|
const options = {
|
||||||
new: true,
|
new: true,
|
||||||
upsert: projectName === 'instance',
|
upsert: projectName === GLOBAL_PROJECT_NAME,
|
||||||
lean: true,
|
lean: true,
|
||||||
select: fieldsToSelect,
|
select: fieldsToSelect,
|
||||||
};
|
};
|
||||||
|
@ -81,10 +82,55 @@ const removeGroupFromAllProjects = async (promptGroupId) => {
|
||||||
await Project.updateMany({}, { $pull: { promptGroupIds: promptGroupId } });
|
await Project.updateMany({}, { $pull: { promptGroupIds: promptGroupId } });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an array of agent IDs to a project's agentIds array, ensuring uniqueness.
|
||||||
|
*
|
||||||
|
* @param {string} projectId - The ID of the project to update.
|
||||||
|
* @param {string[]} agentIds - The array of agent IDs to add to the project.
|
||||||
|
* @returns {Promise<MongoProject>} The updated project document.
|
||||||
|
*/
|
||||||
|
const addAgentIdsToProject = async function (projectId, agentIds) {
|
||||||
|
return await Project.findByIdAndUpdate(
|
||||||
|
projectId,
|
||||||
|
{ $addToSet: { agentIds: { $each: agentIds } } },
|
||||||
|
{ new: true },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an array of agent IDs from a project's agentIds array.
|
||||||
|
*
|
||||||
|
* @param {string} projectId - The ID of the project to update.
|
||||||
|
* @param {string[]} agentIds - The array of agent IDs to remove from the project.
|
||||||
|
* @returns {Promise<MongoProject>} The updated project document.
|
||||||
|
*/
|
||||||
|
const removeAgentIdsFromProject = async function (projectId, agentIds) {
|
||||||
|
return await Project.findByIdAndUpdate(
|
||||||
|
projectId,
|
||||||
|
{ $pull: { agentIds: { $in: agentIds } } },
|
||||||
|
{ new: true },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an agent ID from all projects.
|
||||||
|
*
|
||||||
|
* @param {string} agentId - The ID of the agent to remove from projects.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
const removeAgentFromAllProjects = async (agentId) => {
|
||||||
|
await Project.updateMany({}, { $pull: { agentIds: agentId } });
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getProjectById,
|
getProjectById,
|
||||||
getProjectByName,
|
getProjectByName,
|
||||||
|
/* prompts */
|
||||||
addGroupIdsToProject,
|
addGroupIdsToProject,
|
||||||
removeGroupIdsFromProject,
|
removeGroupIdsFromProject,
|
||||||
removeGroupFromAllProjects,
|
removeGroupFromAllProjects,
|
||||||
|
/* agents */
|
||||||
|
addAgentIdsToProject,
|
||||||
|
removeAgentIdsFromProject,
|
||||||
|
removeAgentFromAllProjects,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const { ObjectId } = require('mongodb');
|
const { ObjectId } = require('mongodb');
|
||||||
const { SystemRoles, SystemCategories } = require('librechat-data-provider');
|
const { SystemRoles, SystemCategories, Constants } = require('librechat-data-provider');
|
||||||
const {
|
const {
|
||||||
getProjectByName,
|
getProjectByName,
|
||||||
addGroupIdsToProject,
|
addGroupIdsToProject,
|
||||||
|
@ -123,7 +123,7 @@ const getAllPromptGroups = async (req, filter) => {
|
||||||
let combinedQuery = query;
|
let combinedQuery = query;
|
||||||
|
|
||||||
if (searchShared) {
|
if (searchShared) {
|
||||||
const project = await getProjectByName('instance', 'promptGroupIds');
|
const project = await getProjectByName(Constants.GLOBAL_PROJECT_NAME, 'promptGroupIds');
|
||||||
if (project && project.promptGroupIds.length > 0) {
|
if (project && project.promptGroupIds.length > 0) {
|
||||||
const projectQuery = { _id: { $in: project.promptGroupIds }, ...query };
|
const projectQuery = { _id: { $in: project.promptGroupIds }, ...query };
|
||||||
delete projectQuery.author;
|
delete projectQuery.author;
|
||||||
|
@ -177,7 +177,7 @@ const getPromptGroups = async (req, filter) => {
|
||||||
|
|
||||||
if (searchShared) {
|
if (searchShared) {
|
||||||
// const projects = req.user.projects || []; // TODO: handle multiple projects
|
// const projects = req.user.projects || []; // TODO: handle multiple projects
|
||||||
const project = await getProjectByName('instance', 'promptGroupIds');
|
const project = await getProjectByName(Constants.GLOBAL_PROJECT_NAME, 'promptGroupIds');
|
||||||
if (project && project.promptGroupIds.length > 0) {
|
if (project && project.promptGroupIds.length > 0) {
|
||||||
const projectQuery = { _id: { $in: project.promptGroupIds }, ...query };
|
const projectQuery = { _id: { $in: project.promptGroupIds }, ...query };
|
||||||
delete projectQuery.author;
|
delete projectQuery.author;
|
||||||
|
|
|
@ -4,6 +4,7 @@ const {
|
||||||
roleDefaults,
|
roleDefaults,
|
||||||
PermissionTypes,
|
PermissionTypes,
|
||||||
removeNullishValues,
|
removeNullishValues,
|
||||||
|
agentPermissionsSchema,
|
||||||
promptPermissionsSchema,
|
promptPermissionsSchema,
|
||||||
bookmarkPermissionsSchema,
|
bookmarkPermissionsSchema,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
|
@ -71,6 +72,7 @@ const updateRoleByName = async function (roleName, updates) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const permissionSchemas = {
|
const permissionSchemas = {
|
||||||
|
[PermissionTypes.AGENTS]: agentPermissionsSchema,
|
||||||
[PermissionTypes.PROMPTS]: promptPermissionsSchema,
|
[PermissionTypes.PROMPTS]: promptPermissionsSchema,
|
||||||
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
|
[PermissionTypes.BOOKMARKS]: bookmarkPermissionsSchema,
|
||||||
};
|
};
|
||||||
|
@ -130,6 +132,7 @@ async function updateAccessPermissions(roleName, permissionsUpdate) {
|
||||||
/**
|
/**
|
||||||
* Initialize default roles in the system.
|
* Initialize default roles in the system.
|
||||||
* Creates the default roles (ADMIN, USER) if they don't exist in the database.
|
* Creates the default roles (ADMIN, USER) if they don't exist in the database.
|
||||||
|
* Updates existing roles with new permission types if they're missing.
|
||||||
*
|
*
|
||||||
* @returns {Promise<void>}
|
* @returns {Promise<void>}
|
||||||
*/
|
*/
|
||||||
|
@ -137,14 +140,27 @@ const initializeRoles = async function () {
|
||||||
const defaultRoles = [SystemRoles.ADMIN, SystemRoles.USER];
|
const defaultRoles = [SystemRoles.ADMIN, SystemRoles.USER];
|
||||||
|
|
||||||
for (const roleName of defaultRoles) {
|
for (const roleName of defaultRoles) {
|
||||||
let role = await Role.findOne({ name: roleName }).select('name').lean();
|
let role = await Role.findOne({ name: roleName });
|
||||||
|
|
||||||
if (!role) {
|
if (!role) {
|
||||||
|
// Create new role if it doesn't exist
|
||||||
role = new Role(roleDefaults[roleName]);
|
role = new Role(roleDefaults[roleName]);
|
||||||
await role.save();
|
} else {
|
||||||
|
// Add missing permission types
|
||||||
|
let isUpdated = false;
|
||||||
|
for (const permType of Object.values(PermissionTypes)) {
|
||||||
|
if (!role[permType]) {
|
||||||
|
role[permType] = roleDefaults[roleName][permType];
|
||||||
|
isUpdated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isUpdated) {
|
||||||
|
await role.save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
await role.save();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getRoleByName,
|
getRoleByName,
|
||||||
initializeRoles,
|
initializeRoles,
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const { MongoMemoryServer } = require('mongodb-memory-server');
|
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||||||
const { SystemRoles, PermissionTypes } = require('librechat-data-provider');
|
const {
|
||||||
const Role = require('~/models/schema/roleSchema');
|
SystemRoles,
|
||||||
const { updateAccessPermissions } = require('~/models/Role');
|
PermissionTypes,
|
||||||
|
roleDefaults,
|
||||||
|
Permissions,
|
||||||
|
} = require('librechat-data-provider');
|
||||||
|
const { updateAccessPermissions, initializeRoles } = require('~/models/Role');
|
||||||
const getLogStores = require('~/cache/getLogStores');
|
const getLogStores = require('~/cache/getLogStores');
|
||||||
|
const Role = require('~/models/schema/roleSchema');
|
||||||
|
|
||||||
// Mock the cache
|
// Mock the cache
|
||||||
jest.mock('~/cache/getLogStores', () => {
|
jest.mock('~/cache/getLogStores', () => {
|
||||||
|
@ -195,3 +200,117 @@ describe('updateAccessPermissions', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('initializeRoles', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await Role.deleteMany({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create default roles if they do not exist', async () => {
|
||||||
|
await initializeRoles();
|
||||||
|
|
||||||
|
const adminRole = await Role.findOne({ name: SystemRoles.ADMIN }).lean();
|
||||||
|
const userRole = await Role.findOne({ name: SystemRoles.USER }).lean();
|
||||||
|
|
||||||
|
expect(adminRole).toBeTruthy();
|
||||||
|
expect(userRole).toBeTruthy();
|
||||||
|
|
||||||
|
// Check if all permission types exist
|
||||||
|
Object.values(PermissionTypes).forEach((permType) => {
|
||||||
|
expect(adminRole[permType]).toBeDefined();
|
||||||
|
expect(userRole[permType]).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if permissions match defaults (example for ADMIN role)
|
||||||
|
expect(adminRole[PermissionTypes.PROMPTS].SHARED_GLOBAL).toBe(true);
|
||||||
|
expect(adminRole[PermissionTypes.BOOKMARKS].USE).toBe(true);
|
||||||
|
expect(adminRole[PermissionTypes.AGENTS].CREATE).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not modify existing permissions for existing roles', async () => {
|
||||||
|
const customUserRole = {
|
||||||
|
name: SystemRoles.USER,
|
||||||
|
[PermissionTypes.PROMPTS]: {
|
||||||
|
[Permissions.USE]: false,
|
||||||
|
[Permissions.CREATE]: true,
|
||||||
|
[Permissions.SHARED_GLOBAL]: true,
|
||||||
|
},
|
||||||
|
[PermissionTypes.BOOKMARKS]: {
|
||||||
|
[Permissions.USE]: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await new Role(customUserRole).save();
|
||||||
|
|
||||||
|
await initializeRoles();
|
||||||
|
|
||||||
|
const userRole = await Role.findOne({ name: SystemRoles.USER }).lean();
|
||||||
|
|
||||||
|
expect(userRole[PermissionTypes.PROMPTS]).toEqual(customUserRole[PermissionTypes.PROMPTS]);
|
||||||
|
expect(userRole[PermissionTypes.BOOKMARKS]).toEqual(customUserRole[PermissionTypes.BOOKMARKS]);
|
||||||
|
expect(userRole[PermissionTypes.AGENTS]).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add new permission types to existing roles', async () => {
|
||||||
|
const partialUserRole = {
|
||||||
|
name: SystemRoles.USER,
|
||||||
|
[PermissionTypes.PROMPTS]: roleDefaults[SystemRoles.USER][PermissionTypes.PROMPTS],
|
||||||
|
[PermissionTypes.BOOKMARKS]: roleDefaults[SystemRoles.USER][PermissionTypes.BOOKMARKS],
|
||||||
|
};
|
||||||
|
|
||||||
|
await new Role(partialUserRole).save();
|
||||||
|
|
||||||
|
await initializeRoles();
|
||||||
|
|
||||||
|
const userRole = await Role.findOne({ name: SystemRoles.USER }).lean();
|
||||||
|
|
||||||
|
expect(userRole[PermissionTypes.AGENTS]).toBeDefined();
|
||||||
|
expect(userRole[PermissionTypes.AGENTS].CREATE).toBeDefined();
|
||||||
|
expect(userRole[PermissionTypes.AGENTS].USE).toBeDefined();
|
||||||
|
expect(userRole[PermissionTypes.AGENTS].SHARED_GLOBAL).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle multiple runs without duplicating or modifying data', async () => {
|
||||||
|
await initializeRoles();
|
||||||
|
await initializeRoles();
|
||||||
|
|
||||||
|
const adminRoles = await Role.find({ name: SystemRoles.ADMIN });
|
||||||
|
const userRoles = await Role.find({ name: SystemRoles.USER });
|
||||||
|
|
||||||
|
expect(adminRoles).toHaveLength(1);
|
||||||
|
expect(userRoles).toHaveLength(1);
|
||||||
|
|
||||||
|
const adminRole = adminRoles[0].toObject();
|
||||||
|
const userRole = userRoles[0].toObject();
|
||||||
|
|
||||||
|
// Check if all permission types exist
|
||||||
|
Object.values(PermissionTypes).forEach((permType) => {
|
||||||
|
expect(adminRole[permType]).toBeDefined();
|
||||||
|
expect(userRole[permType]).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update roles with missing permission types from roleDefaults', async () => {
|
||||||
|
const partialAdminRole = {
|
||||||
|
name: SystemRoles.ADMIN,
|
||||||
|
[PermissionTypes.PROMPTS]: {
|
||||||
|
[Permissions.USE]: false,
|
||||||
|
[Permissions.CREATE]: false,
|
||||||
|
[Permissions.SHARED_GLOBAL]: false,
|
||||||
|
},
|
||||||
|
[PermissionTypes.BOOKMARKS]: roleDefaults[SystemRoles.ADMIN][PermissionTypes.BOOKMARKS],
|
||||||
|
};
|
||||||
|
|
||||||
|
await new Role(partialAdminRole).save();
|
||||||
|
|
||||||
|
await initializeRoles();
|
||||||
|
|
||||||
|
const adminRole = await Role.findOne({ name: SystemRoles.ADMIN }).lean();
|
||||||
|
|
||||||
|
expect(adminRole[PermissionTypes.PROMPTS]).toEqual(partialAdminRole[PermissionTypes.PROMPTS]);
|
||||||
|
expect(adminRole[PermissionTypes.AGENTS]).toBeDefined();
|
||||||
|
expect(adminRole[PermissionTypes.AGENTS].CREATE).toBeDefined();
|
||||||
|
expect(adminRole[PermissionTypes.AGENTS].USE).toBeDefined();
|
||||||
|
expect(adminRole[PermissionTypes.AGENTS].SHARED_GLOBAL).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -57,6 +57,11 @@ const agentSchema = mongoose.Schema(
|
||||||
ref: 'User',
|
ref: 'User',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
projectIds: {
|
||||||
|
type: [mongoose.Schema.Types.ObjectId],
|
||||||
|
ref: 'Project',
|
||||||
|
index: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
|
|
|
@ -13,6 +13,11 @@ const conversationPreset = {
|
||||||
type: String,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
|
// for bedrock only
|
||||||
|
region: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
// for azureOpenAI, openAI only
|
// for azureOpenAI, openAI only
|
||||||
chatGptLabel: {
|
chatGptLabel: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -78,6 +83,9 @@ const conversationPreset = {
|
||||||
promptCache: {
|
promptCache: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
|
system: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
// files
|
// files
|
||||||
resendFiles: {
|
resendFiles: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
|
|
@ -21,6 +21,11 @@ const projectSchema = new Schema(
|
||||||
ref: 'PromptGroup',
|
ref: 'PromptGroup',
|
||||||
default: [],
|
default: [],
|
||||||
},
|
},
|
||||||
|
agentIds: {
|
||||||
|
type: [String],
|
||||||
|
ref: 'Agent',
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
timestamps: true,
|
timestamps: true,
|
||||||
|
|
|
@ -28,6 +28,20 @@ const roleSchema = new mongoose.Schema({
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[PermissionTypes.AGENTS]: {
|
||||||
|
[Permissions.SHARED_GLOBAL]: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
[Permissions.USE]: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
[Permissions.CREATE]: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const Role = mongoose.model('Role', roleSchema);
|
const Role = mongoose.model('Role', roleSchema);
|
||||||
|
|
|
@ -3,38 +3,28 @@ const defaultRate = 6;
|
||||||
|
|
||||||
/** AWS Bedrock pricing */
|
/** AWS Bedrock pricing */
|
||||||
const bedrockValues = {
|
const bedrockValues = {
|
||||||
'anthropic.claude-3-haiku-20240307-v1:0': { prompt: 0.25, completion: 1.25 },
|
'llama2-13b': { prompt: 0.75, completion: 1.0 },
|
||||||
'anthropic.claude-3-sonnet-20240229-v1:0': { prompt: 3.0, completion: 15.0 },
|
'llama2-70b': { prompt: 1.95, completion: 2.56 },
|
||||||
'anthropic.claude-3-opus-20240229-v1:0': { prompt: 15.0, completion: 75.0 },
|
'llama3-8b': { prompt: 0.3, completion: 0.6 },
|
||||||
'anthropic.claude-3-5-sonnet-20240620-v1:0': { prompt: 3.0, completion: 15.0 },
|
'llama3-70b': { prompt: 2.65, completion: 3.5 },
|
||||||
'anthropic.claude-v2:1': { prompt: 8.0, completion: 24.0 },
|
'llama3-1-8b': { prompt: 0.3, completion: 0.6 },
|
||||||
'anthropic.claude-instant-v1': { prompt: 0.8, completion: 2.4 },
|
'llama3-1-70b': { prompt: 2.65, completion: 3.5 },
|
||||||
'meta.llama2-13b-chat-v1': { prompt: 0.75, completion: 1.0 },
|
'llama3-1-405b': { prompt: 5.32, completion: 16.0 },
|
||||||
'meta.llama2-70b-chat-v1': { prompt: 1.95, completion: 2.56 },
|
'mistral-7b': { prompt: 0.15, completion: 0.2 },
|
||||||
'meta.llama3-8b-instruct-v1:0': { prompt: 0.3, completion: 0.6 },
|
'mistral-small': { prompt: 0.15, completion: 0.2 },
|
||||||
'meta.llama3-70b-instruct-v1:0': { prompt: 2.65, completion: 3.5 },
|
'mixtral-8x7b': { prompt: 0.45, completion: 0.7 },
|
||||||
'meta.llama3-1-8b-instruct-v1:0': { prompt: 0.3, completion: 0.6 },
|
'mistral-large-2402': { prompt: 4.0, completion: 12.0 },
|
||||||
'meta.llama3-1-70b-instruct-v1:0': { prompt: 2.65, completion: 3.5 },
|
'mistral-large-2407': { prompt: 3.0, completion: 9.0 },
|
||||||
'meta.llama3-1-405b-instruct-v1:0': { prompt: 5.32, completion: 16.0 },
|
'command-text': { prompt: 1.5, completion: 2.0 },
|
||||||
'mistral.mistral-7b-instruct-v0:2': { prompt: 0.15, completion: 0.2 },
|
'command-light': { prompt: 0.3, completion: 0.6 },
|
||||||
'mistral.mistral-small-2402-v1:0': { prompt: 0.15, completion: 0.2 },
|
|
||||||
'mistral.mixtral-8x7b-instruct-v0:1': { prompt: 0.45, completion: 0.7 },
|
|
||||||
'mistral.mistral-large-2402-v1:0': { prompt: 4.0, completion: 12.0 },
|
|
||||||
'mistral.mistral-large-2407-v1:0': { prompt: 3.0, completion: 9.0 },
|
|
||||||
'cohere.command-text-v14': { prompt: 1.5, completion: 2.0 },
|
|
||||||
'cohere.command-light-text-v14': { prompt: 0.3, completion: 0.6 },
|
|
||||||
'cohere.command-r-v1:0': { prompt: 0.5, completion: 1.5 },
|
|
||||||
'cohere.command-r-plus-v1:0': { prompt: 3.0, completion: 15.0 },
|
|
||||||
'ai21.j2-mid-v1': { prompt: 12.5, completion: 12.5 },
|
'ai21.j2-mid-v1': { prompt: 12.5, completion: 12.5 },
|
||||||
'ai21.j2-ultra-v1': { prompt: 18.8, completion: 18.8 },
|
'ai21.j2-ultra-v1': { prompt: 18.8, completion: 18.8 },
|
||||||
|
'ai21.jamba-instruct-v1:0': { prompt: 0.5, completion: 0.7 },
|
||||||
'amazon.titan-text-lite-v1': { prompt: 0.15, completion: 0.2 },
|
'amazon.titan-text-lite-v1': { prompt: 0.15, completion: 0.2 },
|
||||||
'amazon.titan-text-express-v1': { prompt: 0.2, completion: 0.6 },
|
'amazon.titan-text-express-v1': { prompt: 0.2, completion: 0.6 },
|
||||||
|
'amazon.titan-text-premier-v1:0': { prompt: 0.5, completion: 1.5 },
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(bedrockValues)) {
|
|
||||||
bedrockValues[`bedrock/${key}`] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mapping of model token sizes to their respective multipliers for prompt and completion.
|
* Mapping of model token sizes to their respective multipliers for prompt and completion.
|
||||||
* The rates are 1 USD per 1M tokens.
|
* The rates are 1 USD per 1M tokens.
|
||||||
|
@ -59,6 +49,7 @@ const tokenValues = Object.assign(
|
||||||
'claude-3-haiku': { prompt: 0.25, completion: 1.25 },
|
'claude-3-haiku': { prompt: 0.25, completion: 1.25 },
|
||||||
'claude-2.1': { prompt: 8, completion: 24 },
|
'claude-2.1': { prompt: 8, completion: 24 },
|
||||||
'claude-2': { prompt: 8, completion: 24 },
|
'claude-2': { prompt: 8, completion: 24 },
|
||||||
|
'claude-instant': { prompt: 0.8, completion: 2.4 },
|
||||||
'claude-': { prompt: 0.8, completion: 2.4 },
|
'claude-': { prompt: 0.8, completion: 2.4 },
|
||||||
'command-r-plus': { prompt: 3, completion: 15 },
|
'command-r-plus': { prompt: 3, completion: 15 },
|
||||||
'command-r': { prompt: 0.5, completion: 1.5 },
|
'command-r': { prompt: 0.5, completion: 1.5 },
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
const { EModelEndpoint } = require('librechat-data-provider');
|
||||||
const {
|
const {
|
||||||
defaultRate,
|
defaultRate,
|
||||||
tokenValues,
|
tokenValues,
|
||||||
|
@ -224,34 +225,18 @@ describe('AWS Bedrock Model Tests', () => {
|
||||||
|
|
||||||
it('should return the correct prompt multipliers for all models', () => {
|
it('should return the correct prompt multipliers for all models', () => {
|
||||||
const results = awsModels.map((model) => {
|
const results = awsModels.map((model) => {
|
||||||
const multiplier = getMultiplier({ valueKey: model, tokenType: 'prompt' });
|
const valueKey = getValueKey(model, EModelEndpoint.bedrock);
|
||||||
return multiplier === tokenValues[model].prompt;
|
const multiplier = getMultiplier({ valueKey, tokenType: 'prompt' });
|
||||||
|
return tokenValues[valueKey].prompt && multiplier === tokenValues[valueKey].prompt;
|
||||||
});
|
});
|
||||||
expect(results.every(Boolean)).toBe(true);
|
expect(results.every(Boolean)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the correct completion multipliers for all models', () => {
|
it('should return the correct completion multipliers for all models', () => {
|
||||||
const results = awsModels.map((model) => {
|
const results = awsModels.map((model) => {
|
||||||
const multiplier = getMultiplier({ valueKey: model, tokenType: 'completion' });
|
const valueKey = getValueKey(model, EModelEndpoint.bedrock);
|
||||||
return multiplier === tokenValues[model].completion;
|
const multiplier = getMultiplier({ valueKey, tokenType: 'completion' });
|
||||||
});
|
return tokenValues[valueKey].completion && multiplier === tokenValues[valueKey].completion;
|
||||||
expect(results.every(Boolean)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the correct prompt multipliers for all models with Bedrock prefix', () => {
|
|
||||||
const results = awsModels.map((model) => {
|
|
||||||
const modelName = `bedrock/${model}`;
|
|
||||||
const multiplier = getMultiplier({ valueKey: modelName, tokenType: 'prompt' });
|
|
||||||
return multiplier === tokenValues[model].prompt;
|
|
||||||
});
|
|
||||||
expect(results.every(Boolean)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return the correct completion multipliers for all models with Bedrock prefix', () => {
|
|
||||||
const results = awsModels.map((model) => {
|
|
||||||
const modelName = `bedrock/${model}`;
|
|
||||||
const multiplier = getMultiplier({ valueKey: modelName, tokenType: 'completion' });
|
|
||||||
return multiplier === tokenValues[model].completion;
|
|
||||||
});
|
});
|
||||||
expect(results.every(Boolean)).toBe(true);
|
expect(results.every(Boolean)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
"@langchain/core": "^0.2.18",
|
"@langchain/core": "^0.2.18",
|
||||||
"@langchain/google-genai": "^0.0.11",
|
"@langchain/google-genai": "^0.0.11",
|
||||||
"@langchain/google-vertexai": "^0.0.17",
|
"@langchain/google-vertexai": "^0.0.17",
|
||||||
"@librechat/agents": "^1.4.1",
|
"@librechat/agents": "^1.5.2",
|
||||||
"axios": "^1.3.4",
|
"axios": "^1.3.4",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"cheerio": "^1.0.0-rc.12",
|
"cheerio": "^1.0.0-rc.12",
|
||||||
|
|
|
@ -123,11 +123,6 @@ const AskController = async (req, res, next, initializeClient, addTitle) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = await client.sendMessage(text, messageOptions);
|
let response = await client.sendMessage(text, messageOptions);
|
||||||
|
|
||||||
if (overrideParentMessageId) {
|
|
||||||
response.parentMessageId = overrideParentMessageId;
|
|
||||||
}
|
|
||||||
|
|
||||||
response.endpoint = endpointOption.endpoint;
|
response.endpoint = endpointOption.endpoint;
|
||||||
|
|
||||||
const { conversation = {} } = await client.responsePromise;
|
const { conversation = {} } = await client.responsePromise;
|
||||||
|
|
|
@ -44,6 +44,14 @@ async function endpointController(req, res) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mergedConfig[EModelEndpoint.bedrock] && req.app.locals?.[EModelEndpoint.bedrock]) {
|
||||||
|
const { availableRegions } = req.app.locals[EModelEndpoint.bedrock];
|
||||||
|
mergedConfig[EModelEndpoint.bedrock] = {
|
||||||
|
...mergedConfig[EModelEndpoint.bedrock],
|
||||||
|
availableRegions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const endpointsConfig = orderEndpointsConfig(mergedConfig);
|
const endpointsConfig = orderEndpointsConfig(mergedConfig);
|
||||||
|
|
||||||
await cache.set(CacheKeys.ENDPOINT_CONFIG, endpointsConfig);
|
await cache.set(CacheKeys.ENDPOINT_CONFIG, endpointsConfig);
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
const { GraphEvents, ToolEndHandler, ChatModelStreamHandler } = require('@librechat/agents');
|
const { GraphEvents, ToolEndHandler, ChatModelStreamHandler } = require('@librechat/agents');
|
||||||
|
|
||||||
|
/** @typedef {import('@librechat/agents').Graph} Graph */
|
||||||
/** @typedef {import('@librechat/agents').EventHandler} EventHandler */
|
/** @typedef {import('@librechat/agents').EventHandler} EventHandler */
|
||||||
|
/** @typedef {import('@librechat/agents').ModelEndData} ModelEndData */
|
||||||
/** @typedef {import('@librechat/agents').ChatModelStreamHandler} ChatModelStreamHandler */
|
/** @typedef {import('@librechat/agents').ChatModelStreamHandler} ChatModelStreamHandler */
|
||||||
|
/** @typedef {import('@librechat/agents').ContentAggregatorResult['aggregateContent']} ContentAggregator */
|
||||||
/** @typedef {import('@librechat/agents').GraphEvents} GraphEvents */
|
/** @typedef {import('@librechat/agents').GraphEvents} GraphEvents */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,18 +21,55 @@ const sendEvent = (res, event) => {
|
||||||
res.write(`event: message\ndata: ${JSON.stringify(event)}\n\n`);
|
res.write(`event: message\ndata: ${JSON.stringify(event)}\n\n`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ModelEndHandler {
|
||||||
|
/**
|
||||||
|
* @param {Array<UsageMetadata>} collectedUsage
|
||||||
|
*/
|
||||||
|
constructor(collectedUsage) {
|
||||||
|
if (!Array.isArray(collectedUsage)) {
|
||||||
|
throw new Error('collectedUsage must be an array');
|
||||||
|
}
|
||||||
|
this.collectedUsage = collectedUsage;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} event
|
||||||
|
* @param {ModelEndData | undefined} data
|
||||||
|
* @param {Record<string, unknown> | undefined} metadata
|
||||||
|
* @param {Graph} graph
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
handle(event, data, metadata, graph) {
|
||||||
|
if (!graph || !metadata) {
|
||||||
|
console.warn(`Graph or metadata not found in ${event} event`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const usage = data?.output?.usage_metadata;
|
||||||
|
|
||||||
|
if (usage) {
|
||||||
|
this.collectedUsage.push(usage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get default handlers for stream events.
|
* Get default handlers for stream events.
|
||||||
* @param {{ res?: ServerResponse }} options - The options object.
|
* @param {Object} options - The options object.
|
||||||
|
* @param {ServerResponse} options.res - The options object.
|
||||||
|
* @param {ContentAggregator} options.aggregateContent - The options object.
|
||||||
|
* @param {Array<UsageMetadata>} options.collectedUsage - The list of collected usage metadata.
|
||||||
* @returns {Record<string, t.EventHandler>} The default handlers.
|
* @returns {Record<string, t.EventHandler>} The default handlers.
|
||||||
* @throws {Error} If the request is not found.
|
* @throws {Error} If the request is not found.
|
||||||
*/
|
*/
|
||||||
function getDefaultHandlers({ res }) {
|
function getDefaultHandlers({ res, aggregateContent, collectedUsage }) {
|
||||||
if (!res) {
|
if (!res || !aggregateContent) {
|
||||||
throw new Error('Request not found');
|
throw new Error(
|
||||||
|
`[getDefaultHandlers] Missing required options: res: ${!res}, aggregateContent: ${!aggregateContent}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const handlers = {
|
const handlers = {
|
||||||
// [GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(),
|
[GraphEvents.CHAT_MODEL_END]: new ModelEndHandler(collectedUsage),
|
||||||
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
[GraphEvents.TOOL_END]: new ToolEndHandler(),
|
||||||
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
[GraphEvents.CHAT_MODEL_STREAM]: new ChatModelStreamHandler(),
|
||||||
[GraphEvents.ON_RUN_STEP]: {
|
[GraphEvents.ON_RUN_STEP]: {
|
||||||
|
@ -40,6 +80,7 @@ function getDefaultHandlers({ res }) {
|
||||||
*/
|
*/
|
||||||
handle: (event, data) => {
|
handle: (event, data) => {
|
||||||
sendEvent(res, { event, data });
|
sendEvent(res, { event, data });
|
||||||
|
aggregateContent({ event, data });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
[GraphEvents.ON_RUN_STEP_DELTA]: {
|
||||||
|
@ -50,6 +91,7 @@ function getDefaultHandlers({ res }) {
|
||||||
*/
|
*/
|
||||||
handle: (event, data) => {
|
handle: (event, data) => {
|
||||||
sendEvent(res, { event, data });
|
sendEvent(res, { event, data });
|
||||||
|
aggregateContent({ event, data });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
[GraphEvents.ON_RUN_STEP_COMPLETED]: {
|
||||||
|
@ -60,6 +102,7 @@ function getDefaultHandlers({ res }) {
|
||||||
*/
|
*/
|
||||||
handle: (event, data) => {
|
handle: (event, data) => {
|
||||||
sendEvent(res, { event, data });
|
sendEvent(res, { event, data });
|
||||||
|
aggregateContent({ event, data });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[GraphEvents.ON_MESSAGE_DELTA]: {
|
[GraphEvents.ON_MESSAGE_DELTA]: {
|
||||||
|
@ -70,6 +113,7 @@ function getDefaultHandlers({ res }) {
|
||||||
*/
|
*/
|
||||||
handle: (event, data) => {
|
handle: (event, data) => {
|
||||||
sendEvent(res, { event, data });
|
sendEvent(res, { event, data });
|
||||||
|
aggregateContent({ event, data });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,9 +7,11 @@
|
||||||
// validateVisionModel,
|
// validateVisionModel,
|
||||||
// mapModelToAzureConfig,
|
// mapModelToAzureConfig,
|
||||||
// } = require('librechat-data-provider');
|
// } = require('librechat-data-provider');
|
||||||
const { Callback } = require('@librechat/agents');
|
const { Callback, createMetadataAggregator } = require('@librechat/agents');
|
||||||
const {
|
const {
|
||||||
|
Constants,
|
||||||
EModelEndpoint,
|
EModelEndpoint,
|
||||||
|
bedrockOutputParser,
|
||||||
providerEndpointMap,
|
providerEndpointMap,
|
||||||
removeNullishValues,
|
removeNullishValues,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
|
@ -23,15 +25,27 @@ const {
|
||||||
formatAgentMessages,
|
formatAgentMessages,
|
||||||
createContextHandlers,
|
createContextHandlers,
|
||||||
} = require('~/app/clients/prompts');
|
} = require('~/app/clients/prompts');
|
||||||
|
const { encodeAndFormat } = require('~/server/services/Files/images/encode');
|
||||||
const Tokenizer = require('~/server/services/Tokenizer');
|
const Tokenizer = require('~/server/services/Tokenizer');
|
||||||
|
const { spendTokens } = require('~/models/spendTokens');
|
||||||
const BaseClient = require('~/app/clients/BaseClient');
|
const BaseClient = require('~/app/clients/BaseClient');
|
||||||
// const { sleep } = require('~/server/utils');
|
// const { sleep } = require('~/server/utils');
|
||||||
const { createRun } = require('./run');
|
const { createRun } = require('./run');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
|
/** @typedef {import('@librechat/agents').MessageContentComplex} MessageContentComplex */
|
||||||
|
|
||||||
|
// const providerSchemas = {
|
||||||
|
// [EModelEndpoint.bedrock]: true,
|
||||||
|
// };
|
||||||
|
|
||||||
|
const providerParsers = {
|
||||||
|
[EModelEndpoint.bedrock]: bedrockOutputParser,
|
||||||
|
};
|
||||||
|
|
||||||
class AgentClient extends BaseClient {
|
class AgentClient extends BaseClient {
|
||||||
constructor(options = {}) {
|
constructor(options = {}) {
|
||||||
super(options);
|
super(null, options);
|
||||||
|
|
||||||
/** @type {'discard' | 'summarize'} */
|
/** @type {'discard' | 'summarize'} */
|
||||||
this.contextStrategy = 'discard';
|
this.contextStrategy = 'discard';
|
||||||
|
@ -39,11 +53,31 @@ class AgentClient extends BaseClient {
|
||||||
/** @deprecated @type {true} - Is a Chat Completion Request */
|
/** @deprecated @type {true} - Is a Chat Completion Request */
|
||||||
this.isChatCompletion = true;
|
this.isChatCompletion = true;
|
||||||
|
|
||||||
const { maxContextTokens, modelOptions = {}, ...clientOptions } = options;
|
/** @type {AgentRun} */
|
||||||
|
this.run;
|
||||||
|
|
||||||
|
const {
|
||||||
|
maxContextTokens,
|
||||||
|
modelOptions = {},
|
||||||
|
contentParts,
|
||||||
|
collectedUsage,
|
||||||
|
...clientOptions
|
||||||
|
} = options;
|
||||||
|
|
||||||
this.modelOptions = modelOptions;
|
this.modelOptions = modelOptions;
|
||||||
this.maxContextTokens = maxContextTokens;
|
this.maxContextTokens = maxContextTokens;
|
||||||
this.options = Object.assign({ endpoint: EModelEndpoint.agents }, clientOptions);
|
/** @type {MessageContentComplex[]} */
|
||||||
|
this.contentParts = contentParts;
|
||||||
|
/** @type {Array<UsageMetadata>} */
|
||||||
|
this.collectedUsage = collectedUsage;
|
||||||
|
this.options = Object.assign({ endpoint: options.endpoint }, clientOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the aggregated content parts for the current run.
|
||||||
|
* @returns {MessageContentComplex[]} */
|
||||||
|
getContentParts() {
|
||||||
|
return this.contentParts;
|
||||||
}
|
}
|
||||||
|
|
||||||
setOptions(options) {
|
setOptions(options) {
|
||||||
|
@ -112,9 +146,27 @@ class AgentClient extends BaseClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
getSaveOptions() {
|
getSaveOptions() {
|
||||||
|
const parseOptions = providerParsers[this.options.endpoint];
|
||||||
|
let runOptions =
|
||||||
|
this.options.endpoint === EModelEndpoint.agents
|
||||||
|
? {
|
||||||
|
model: undefined,
|
||||||
|
// TODO:
|
||||||
|
// would need to be override settings; otherwise, model needs to be undefined
|
||||||
|
// model: this.override.model,
|
||||||
|
// instructions: this.override.instructions,
|
||||||
|
// additional_instructions: this.override.additional_instructions,
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
if (parseOptions) {
|
||||||
|
runOptions = parseOptions(this.modelOptions);
|
||||||
|
}
|
||||||
|
|
||||||
return removeNullishValues(
|
return removeNullishValues(
|
||||||
Object.assign(
|
Object.assign(
|
||||||
{
|
{
|
||||||
|
endpoint: this.options.endpoint,
|
||||||
agent_id: this.options.agent.id,
|
agent_id: this.options.agent.id,
|
||||||
modelLabel: this.options.modelLabel,
|
modelLabel: this.options.modelLabel,
|
||||||
maxContextTokens: this.options.maxContextTokens,
|
maxContextTokens: this.options.maxContextTokens,
|
||||||
|
@ -122,15 +174,8 @@ class AgentClient extends BaseClient {
|
||||||
imageDetail: this.options.imageDetail,
|
imageDetail: this.options.imageDetail,
|
||||||
spec: this.options.spec,
|
spec: this.options.spec,
|
||||||
},
|
},
|
||||||
this.modelOptions,
|
// TODO: PARSE OPTIONS BY PROVIDER, MAY CONTAIN SENSITIVE DATA
|
||||||
{
|
runOptions,
|
||||||
model: undefined,
|
|
||||||
// TODO:
|
|
||||||
// would need to be override settings; otherwise, model needs to be undefined
|
|
||||||
// model: this.override.model,
|
|
||||||
// instructions: this.override.instructions,
|
|
||||||
// additional_instructions: this.override.additional_instructions,
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -142,6 +187,16 @@ class AgentClient extends BaseClient {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addImageURLs(message, attachments) {
|
||||||
|
const { files, image_urls } = await encodeAndFormat(
|
||||||
|
this.options.req,
|
||||||
|
attachments,
|
||||||
|
this.options.agent.provider,
|
||||||
|
);
|
||||||
|
message.image_urls = image_urls.length ? image_urls : undefined;
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
async buildMessages(
|
async buildMessages(
|
||||||
messages,
|
messages,
|
||||||
parentMessageId,
|
parentMessageId,
|
||||||
|
@ -270,25 +325,34 @@ class AgentClient extends BaseClient {
|
||||||
/** @type {sendCompletion} */
|
/** @type {sendCompletion} */
|
||||||
async sendCompletion(payload, opts = {}) {
|
async sendCompletion(payload, opts = {}) {
|
||||||
this.modelOptions.user = this.user;
|
this.modelOptions.user = this.user;
|
||||||
return await this.chatCompletion({
|
await this.chatCompletion({
|
||||||
payload,
|
payload,
|
||||||
onProgress: opts.onProgress,
|
onProgress: opts.onProgress,
|
||||||
abortController: opts.abortController,
|
abortController: opts.abortController,
|
||||||
});
|
});
|
||||||
|
return this.contentParts;
|
||||||
}
|
}
|
||||||
|
|
||||||
// async recordTokenUsage({ promptTokens, completionTokens, context = 'message' }) {
|
/**
|
||||||
// await spendTokens(
|
* @param {Object} params
|
||||||
// {
|
* @param {string} [params.model]
|
||||||
// context,
|
* @param {string} [params.context='message']
|
||||||
// model: this.modelOptions.model,
|
* @param {UsageMetadata[]} [params.collectedUsage=this.collectedUsage]
|
||||||
// conversationId: this.conversationId,
|
*/
|
||||||
// user: this.user ?? this.options.req.user?.id,
|
async recordCollectedUsage({ model, context = 'message', collectedUsage = this.collectedUsage }) {
|
||||||
// endpointTokenConfig: this.options.endpointTokenConfig,
|
for (const usage of collectedUsage) {
|
||||||
// },
|
await spendTokens(
|
||||||
// { promptTokens, completionTokens },
|
{
|
||||||
// );
|
context,
|
||||||
// }
|
model: model ?? this.modelOptions.model,
|
||||||
|
conversationId: this.conversationId,
|
||||||
|
user: this.user ?? this.options.req.user?.id,
|
||||||
|
endpointTokenConfig: this.options.endpointTokenConfig,
|
||||||
|
},
|
||||||
|
{ promptTokens: usage.input_tokens, completionTokens: usage.output_tokens },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async chatCompletion({ payload, abortController = null }) {
|
async chatCompletion({ payload, abortController = null }) {
|
||||||
try {
|
try {
|
||||||
|
@ -398,9 +462,8 @@ class AgentClient extends BaseClient {
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// const streamRate = this.options.streamRate ?? Constants.DEFAULT_STREAM_RATE;
|
|
||||||
|
|
||||||
const run = await createRun({
|
const run = await createRun({
|
||||||
|
req: this.options.req,
|
||||||
agent: this.options.agent,
|
agent: this.options.agent,
|
||||||
tools: this.options.tools,
|
tools: this.options.tools,
|
||||||
toolMap: this.options.toolMap,
|
toolMap: this.options.toolMap,
|
||||||
|
@ -415,6 +478,7 @@ class AgentClient extends BaseClient {
|
||||||
thread_id: this.conversationId,
|
thread_id: this.conversationId,
|
||||||
},
|
},
|
||||||
run_id: this.responseMessageId,
|
run_id: this.responseMessageId,
|
||||||
|
signal: abortController.signal,
|
||||||
streamMode: 'values',
|
streamMode: 'values',
|
||||||
version: 'v2',
|
version: 'v2',
|
||||||
};
|
};
|
||||||
|
@ -423,8 +487,10 @@ class AgentClient extends BaseClient {
|
||||||
throw new Error('Failed to create run');
|
throw new Error('Failed to create run');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.run = run;
|
||||||
|
|
||||||
const messages = formatAgentMessages(payload);
|
const messages = formatAgentMessages(payload);
|
||||||
const runMessages = await run.processStream({ messages }, config, {
|
await run.processStream({ messages }, config, {
|
||||||
[Callback.TOOL_ERROR]: (graph, error, toolId) => {
|
[Callback.TOOL_ERROR]: (graph, error, toolId) => {
|
||||||
logger.error(
|
logger.error(
|
||||||
'[api/server/controllers/agents/client.js #chatCompletion] Tool Error',
|
'[api/server/controllers/agents/client.js #chatCompletion] Tool Error',
|
||||||
|
@ -433,14 +499,94 @@ class AgentClient extends BaseClient {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// console.dir(runMessages, { depth: null });
|
this.recordCollectedUsage({ context: 'message' }).catch((err) => {
|
||||||
return runMessages;
|
logger.error(
|
||||||
|
'[api/server/controllers/agents/client.js #chatCompletion] Error recording collected usage',
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(
|
if (!abortController.signal.aborted) {
|
||||||
'[api/server/controllers/agents/client.js #chatCompletion] Unhandled error type',
|
logger.error(
|
||||||
|
'[api/server/controllers/agents/client.js #sendCompletion] Unhandled error type',
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.warn(
|
||||||
|
'[api/server/controllers/agents/client.js #sendCompletion] Operation aborted',
|
||||||
err,
|
err,
|
||||||
);
|
);
|
||||||
throw err;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Object} params
|
||||||
|
* @param {string} params.text
|
||||||
|
* @param {string} params.conversationId
|
||||||
|
*/
|
||||||
|
async titleConvo({ text }) {
|
||||||
|
if (!this.run) {
|
||||||
|
throw new Error('Run not initialized');
|
||||||
|
}
|
||||||
|
const { handleLLMEnd, collected: collectedMetadata } = createMetadataAggregator();
|
||||||
|
const clientOptions = {};
|
||||||
|
const providerConfig = this.options.req.app.locals[this.options.agent.provider];
|
||||||
|
if (
|
||||||
|
providerConfig &&
|
||||||
|
providerConfig.titleModel &&
|
||||||
|
providerConfig.titleModel !== Constants.CURRENT_MODEL
|
||||||
|
) {
|
||||||
|
clientOptions.model = providerConfig.titleModel;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const titleResult = await this.run.generateTitle({
|
||||||
|
inputText: text,
|
||||||
|
contentParts: this.contentParts,
|
||||||
|
clientOptions,
|
||||||
|
chainOptions: {
|
||||||
|
callbacks: [
|
||||||
|
{
|
||||||
|
handleLLMEnd,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const collectedUsage = collectedMetadata.map((item) => {
|
||||||
|
let input_tokens, output_tokens;
|
||||||
|
|
||||||
|
if (item.usage) {
|
||||||
|
input_tokens = item.usage.input_tokens || item.usage.inputTokens;
|
||||||
|
output_tokens = item.usage.output_tokens || item.usage.outputTokens;
|
||||||
|
} else if (item.tokenUsage) {
|
||||||
|
input_tokens = item.tokenUsage.promptTokens;
|
||||||
|
output_tokens = item.tokenUsage.completionTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
input_tokens: input_tokens,
|
||||||
|
output_tokens: output_tokens,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
this.recordCollectedUsage({
|
||||||
|
model: clientOptions.model,
|
||||||
|
context: 'title',
|
||||||
|
collectedUsage,
|
||||||
|
}).catch((err) => {
|
||||||
|
logger.error(
|
||||||
|
'[api/server/controllers/agents/client.js #titleConvo] Error recording collected usage',
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return titleResult.title;
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('[api/server/controllers/agents/client.js #titleConvo] Error', err);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
// Import the necessary modules
|
|
||||||
const path = require('path');
|
|
||||||
const base = path.resolve(__dirname, '..', '..', '..', '..', 'api');
|
|
||||||
console.log(base);
|
|
||||||
//api/server/controllers/agents/demo.js
|
|
||||||
require('module-alias')({ base });
|
|
||||||
const connectDb = require('~/lib/db/connectDb');
|
|
||||||
const AgentClient = require('./client');
|
|
||||||
|
|
||||||
// Define the user and message options
|
|
||||||
const user = 'user123';
|
|
||||||
const parentMessageId = 'pmid123';
|
|
||||||
const conversationId = 'cid456';
|
|
||||||
const maxContextTokens = 200000;
|
|
||||||
const req = {
|
|
||||||
user: { id: user },
|
|
||||||
};
|
|
||||||
const progressOptions = {
|
|
||||||
res: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Define the message options
|
|
||||||
const messageOptions = {
|
|
||||||
user,
|
|
||||||
parentMessageId,
|
|
||||||
conversationId,
|
|
||||||
progressOptions,
|
|
||||||
};
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
await connectDb();
|
|
||||||
const client = new AgentClient({ req, maxContextTokens });
|
|
||||||
|
|
||||||
const text = 'Hello, this is a test message.';
|
|
||||||
|
|
||||||
try {
|
|
||||||
let response = await client.sendMessage(text, messageOptions);
|
|
||||||
console.log('Response:', response);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error sending message:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
|
@ -1,4 +1,4 @@
|
||||||
const { Constants, getResponseSender } = require('librechat-data-provider');
|
const { Constants } = require('librechat-data-provider');
|
||||||
const { createAbortController, handleAbortError } = require('~/server/middleware');
|
const { createAbortController, handleAbortError } = require('~/server/middleware');
|
||||||
const { sendMessage } = require('~/server/utils');
|
const { sendMessage } = require('~/server/utils');
|
||||||
const { saveMessage } = require('~/models');
|
const { saveMessage } = require('~/models');
|
||||||
|
@ -9,22 +9,17 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => {
|
||||||
text,
|
text,
|
||||||
endpointOption,
|
endpointOption,
|
||||||
conversationId,
|
conversationId,
|
||||||
modelDisplayLabel,
|
|
||||||
parentMessageId = null,
|
parentMessageId = null,
|
||||||
overrideParentMessageId = null,
|
overrideParentMessageId = null,
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
|
let sender;
|
||||||
let userMessage;
|
let userMessage;
|
||||||
let userMessagePromise;
|
|
||||||
let promptTokens;
|
let promptTokens;
|
||||||
let userMessageId;
|
let userMessageId;
|
||||||
let responseMessageId;
|
let responseMessageId;
|
||||||
|
let userMessagePromise;
|
||||||
|
|
||||||
const sender = getResponseSender({
|
|
||||||
...endpointOption,
|
|
||||||
model: endpointOption.modelOptions.model,
|
|
||||||
modelDisplayLabel,
|
|
||||||
});
|
|
||||||
const newConvo = !conversationId;
|
const newConvo = !conversationId;
|
||||||
const user = req.user.id;
|
const user = req.user.id;
|
||||||
|
|
||||||
|
@ -39,6 +34,8 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => {
|
||||||
responseMessageId = data[key];
|
responseMessageId = data[key];
|
||||||
} else if (key === 'promptTokens') {
|
} else if (key === 'promptTokens') {
|
||||||
promptTokens = data[key];
|
promptTokens = data[key];
|
||||||
|
} else if (key === 'sender') {
|
||||||
|
sender = data[key];
|
||||||
} else if (!conversationId && key === 'conversationId') {
|
} else if (!conversationId && key === 'conversationId') {
|
||||||
conversationId = data[key];
|
conversationId = data[key];
|
||||||
}
|
}
|
||||||
|
@ -46,6 +43,7 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
/** @type {{ client: TAgentClient }} */
|
||||||
const { client } = await initializeClient({ req, res, endpointOption });
|
const { client } = await initializeClient({ req, res, endpointOption });
|
||||||
|
|
||||||
const getAbortData = () => ({
|
const getAbortData = () => ({
|
||||||
|
@ -54,8 +52,8 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => {
|
||||||
promptTokens,
|
promptTokens,
|
||||||
conversationId,
|
conversationId,
|
||||||
userMessagePromise,
|
userMessagePromise,
|
||||||
// text: getPartialText(),
|
|
||||||
messageId: responseMessageId,
|
messageId: responseMessageId,
|
||||||
|
content: client.getContentParts(),
|
||||||
parentMessageId: overrideParentMessageId ?? userMessageId,
|
parentMessageId: overrideParentMessageId ?? userMessageId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -90,11 +88,6 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = await client.sendMessage(text, messageOptions);
|
let response = await client.sendMessage(text, messageOptions);
|
||||||
|
|
||||||
if (overrideParentMessageId) {
|
|
||||||
response.parentMessageId = overrideParentMessageId;
|
|
||||||
}
|
|
||||||
|
|
||||||
response.endpoint = endpointOption.endpoint;
|
response.endpoint = endpointOption.endpoint;
|
||||||
|
|
||||||
const { conversation = {} } = await client.responsePromise;
|
const { conversation = {} } = await client.responsePromise;
|
||||||
|
@ -103,7 +96,6 @@ const AgentController = async (req, res, next, initializeClient, addTitle) => {
|
||||||
|
|
||||||
if (client.options.attachments) {
|
if (client.options.attachments) {
|
||||||
userMessage.files = client.options.attachments;
|
userMessage.files = client.options.attachments;
|
||||||
conversation.model = endpointOption.modelOptions.model;
|
|
||||||
delete userMessage.image_urls;
|
delete userMessage.image_urls;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const { Run } = require('@librechat/agents');
|
const { Run, Providers } = require('@librechat/agents');
|
||||||
const { providerEndpointMap } = require('librechat-data-provider');
|
const { providerEndpointMap } = require('librechat-data-provider');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -14,11 +14,12 @@ const { providerEndpointMap } = require('librechat-data-provider');
|
||||||
* Creates a new Run instance with custom handlers and configuration.
|
* Creates a new Run instance with custom handlers and configuration.
|
||||||
*
|
*
|
||||||
* @param {Object} options - The options for creating the Run instance.
|
* @param {Object} options - The options for creating the Run instance.
|
||||||
|
* @param {ServerRequest} [options.req] - The server request.
|
||||||
|
* @param {string | undefined} [options.runId] - Optional run ID; otherwise, a new run ID will be generated.
|
||||||
* @param {Agent} options.agent - The agent for this run.
|
* @param {Agent} options.agent - The agent for this run.
|
||||||
* @param {StructuredTool[] | undefined} [options.tools] - The tools to use in the run.
|
* @param {StructuredTool[] | undefined} [options.tools] - The tools to use in the run.
|
||||||
* @param {Record<string, StructuredTool[]> | undefined} [options.toolMap] - The tool map for the run.
|
* @param {Record<string, StructuredTool[]> | undefined} [options.toolMap] - The tool map for the run.
|
||||||
* @param {Record<GraphEvents, EventHandler> | undefined} [options.customHandlers] - Custom event handlers.
|
* @param {Record<GraphEvents, EventHandler> | undefined} [options.customHandlers] - Custom event handlers.
|
||||||
* @param {string | undefined} [options.runId] - Optional run ID; otherwise, a new run ID will be generated.
|
|
||||||
* @param {ClientOptions} [options.modelOptions] - Optional model to use; if not provided, it will use the default from modelMap.
|
* @param {ClientOptions} [options.modelOptions] - Optional model to use; if not provided, it will use the default from modelMap.
|
||||||
* @param {boolean} [options.streaming=true] - Whether to use streaming.
|
* @param {boolean} [options.streaming=true] - Whether to use streaming.
|
||||||
* @param {boolean} [options.streamUsage=true] - Whether to stream usage information.
|
* @param {boolean} [options.streamUsage=true] - Whether to stream usage information.
|
||||||
|
@ -43,15 +44,22 @@ async function createRun({
|
||||||
modelOptions,
|
modelOptions,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const graphConfig = {
|
||||||
|
runId,
|
||||||
|
llmConfig,
|
||||||
|
tools,
|
||||||
|
toolMap,
|
||||||
|
instructions: agent.instructions,
|
||||||
|
additional_instructions: agent.additional_instructions,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TEMPORARY FOR TESTING
|
||||||
|
if (agent.provider === Providers.ANTHROPIC) {
|
||||||
|
graphConfig.streamBuffer = 2000;
|
||||||
|
}
|
||||||
|
|
||||||
return Run.create({
|
return Run.create({
|
||||||
graphConfig: {
|
graphConfig,
|
||||||
runId,
|
|
||||||
llmConfig,
|
|
||||||
tools,
|
|
||||||
toolMap,
|
|
||||||
instructions: agent.instructions,
|
|
||||||
additional_instructions: agent.additional_instructions,
|
|
||||||
},
|
|
||||||
customHandlers,
|
customHandlers,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const { nanoid } = require('nanoid');
|
const { nanoid } = require('nanoid');
|
||||||
const { FileContext } = require('librechat-data-provider');
|
const { FileContext, Constants } = require('librechat-data-provider');
|
||||||
const {
|
const {
|
||||||
getAgent,
|
getAgent,
|
||||||
createAgent,
|
createAgent,
|
||||||
|
@ -9,6 +9,8 @@ const {
|
||||||
} = require('~/models/Agent');
|
} = require('~/models/Agent');
|
||||||
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
const { getStrategyFunctions } = require('~/server/services/Files/strategies');
|
||||||
const { uploadImageBuffer } = require('~/server/services/Files/process');
|
const { uploadImageBuffer } = require('~/server/services/Files/process');
|
||||||
|
const { getProjectByName } = require('~/models/Project');
|
||||||
|
const { updateAgentProjects } = require('~/models/Agent');
|
||||||
const { deleteFileByFilter } = require('~/models/File');
|
const { deleteFileByFilter } = require('~/models/File');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
|
@ -53,16 +55,35 @@ const createAgentHandler = async (req, res) => {
|
||||||
* @param {object} req - Express Request
|
* @param {object} req - Express Request
|
||||||
* @param {object} req.params - Request params
|
* @param {object} req.params - Request params
|
||||||
* @param {string} req.params.id - Agent identifier.
|
* @param {string} req.params.id - Agent identifier.
|
||||||
* @returns {Agent} 200 - success response - application/json
|
* @param {object} req.user - Authenticated user information
|
||||||
|
* @param {string} req.user.id - User ID
|
||||||
|
* @returns {Promise<Agent>} 200 - success response - application/json
|
||||||
* @returns {Error} 404 - Agent not found
|
* @returns {Error} 404 - Agent not found
|
||||||
*/
|
*/
|
||||||
const getAgentHandler = async (req, res) => {
|
const getAgentHandler = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
const agent = await getAgent({ id });
|
const author = req.user.id;
|
||||||
|
|
||||||
|
let query = { id, author };
|
||||||
|
|
||||||
|
const globalProject = await getProjectByName(Constants.GLOBAL_PROJECT_NAME, ['agentIds']);
|
||||||
|
if (globalProject && (globalProject.agentIds?.length ?? 0) > 0) {
|
||||||
|
query = {
|
||||||
|
$or: [{ id, $in: globalProject.agentIds }, query],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const agent = await getAgent(query);
|
||||||
|
|
||||||
if (!agent) {
|
if (!agent) {
|
||||||
return res.status(404).json({ error: 'Agent not found' });
|
return res.status(404).json({ error: 'Agent not found' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (agent.author !== author) {
|
||||||
|
delete agent.author;
|
||||||
|
}
|
||||||
|
|
||||||
return res.status(200).json(agent);
|
return res.status(200).json(agent);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[/Agents/:id] Error retrieving agent', error);
|
logger.error('[/Agents/:id] Error retrieving agent', error);
|
||||||
|
@ -82,7 +103,17 @@ const getAgentHandler = async (req, res) => {
|
||||||
const updateAgentHandler = async (req, res) => {
|
const updateAgentHandler = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
const updatedAgent = await updateAgent({ id, author: req.user.id }, req.body);
|
const { projectIds, removeProjectIds, ...updateData } = req.body;
|
||||||
|
|
||||||
|
let updatedAgent;
|
||||||
|
if (Object.keys(updateData).length > 0) {
|
||||||
|
updatedAgent = await updateAgent({ id, author: req.user.id }, updateData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectIds || removeProjectIds) {
|
||||||
|
updatedAgent = await updateAgentProjects(id, projectIds, removeProjectIds);
|
||||||
|
}
|
||||||
|
|
||||||
return res.json(updatedAgent);
|
return res.json(updatedAgent);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[/Agents/:id] Error updating Agent', error);
|
logger.error('[/Agents/:id] Error updating Agent', error);
|
||||||
|
@ -119,13 +150,13 @@ const deleteAgentHandler = async (req, res) => {
|
||||||
* @param {object} req - Express Request
|
* @param {object} req - Express Request
|
||||||
* @param {object} req.query - Request query
|
* @param {object} req.query - Request query
|
||||||
* @param {string} [req.query.user] - The user ID of the agent's author.
|
* @param {string} [req.query.user] - The user ID of the agent's author.
|
||||||
* @returns {AgentListResponse} 200 - success response - application/json
|
* @returns {Promise<AgentListResponse>} 200 - success response - application/json
|
||||||
*/
|
*/
|
||||||
const getListAgentsHandler = async (req, res) => {
|
const getListAgentsHandler = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { user } = req.query;
|
const data = await getListAgents({
|
||||||
const filter = user ? { author: user } : {};
|
author: req.user.id,
|
||||||
const data = await getListAgents(filter);
|
});
|
||||||
return res.json(data);
|
return res.json(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('[/Agents] Error listing Agents', error);
|
logger.error('[/Agents] Error listing Agents', error);
|
||||||
|
|
|
@ -106,6 +106,7 @@ const startServer = async () => {
|
||||||
app.use('/api/share', routes.share);
|
app.use('/api/share', routes.share);
|
||||||
app.use('/api/roles', routes.roles);
|
app.use('/api/roles', routes.roles);
|
||||||
app.use('/api/agents', routes.agents);
|
app.use('/api/agents', routes.agents);
|
||||||
|
app.use('/api/bedrock', routes.bedrock);
|
||||||
|
|
||||||
app.use('/api/tags', routes.tags);
|
app.use('/api/tags', routes.tags);
|
||||||
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ const createAbortController = (req, res, getAbortData, getReqData) => {
|
||||||
finish_reason: 'incomplete',
|
finish_reason: 'incomplete',
|
||||||
endpoint: endpointOption.endpoint,
|
endpoint: endpointOption.endpoint,
|
||||||
iconURL: endpointOption.iconURL,
|
iconURL: endpointOption.iconURL,
|
||||||
model: endpointOption.modelOptions.model,
|
model: endpointOption.modelOptions?.model ?? endpointOption.model_parameters?.model,
|
||||||
unfinished: false,
|
unfinished: false,
|
||||||
error: false,
|
error: false,
|
||||||
isCreatedByUser: false,
|
isCreatedByUser: false,
|
||||||
|
|
|
@ -5,6 +5,7 @@ const assistants = require('~/server/services/Endpoints/assistants');
|
||||||
const gptPlugins = require('~/server/services/Endpoints/gptPlugins');
|
const gptPlugins = require('~/server/services/Endpoints/gptPlugins');
|
||||||
const { processFiles } = require('~/server/services/Files/process');
|
const { processFiles } = require('~/server/services/Files/process');
|
||||||
const anthropic = require('~/server/services/Endpoints/anthropic');
|
const anthropic = require('~/server/services/Endpoints/anthropic');
|
||||||
|
const bedrock = require('~/server/services/Endpoints/bedrock');
|
||||||
const openAI = require('~/server/services/Endpoints/openAI');
|
const openAI = require('~/server/services/Endpoints/openAI');
|
||||||
const agents = require('~/server/services/Endpoints/agents');
|
const agents = require('~/server/services/Endpoints/agents');
|
||||||
const custom = require('~/server/services/Endpoints/custom');
|
const custom = require('~/server/services/Endpoints/custom');
|
||||||
|
@ -17,6 +18,7 @@ const buildFunction = {
|
||||||
[EModelEndpoint.google]: google.buildOptions,
|
[EModelEndpoint.google]: google.buildOptions,
|
||||||
[EModelEndpoint.custom]: custom.buildOptions,
|
[EModelEndpoint.custom]: custom.buildOptions,
|
||||||
[EModelEndpoint.agents]: agents.buildOptions,
|
[EModelEndpoint.agents]: agents.buildOptions,
|
||||||
|
[EModelEndpoint.bedrock]: bedrock.buildOptions,
|
||||||
[EModelEndpoint.azureOpenAI]: openAI.buildOptions,
|
[EModelEndpoint.azureOpenAI]: openAI.buildOptions,
|
||||||
[EModelEndpoint.anthropic]: anthropic.buildOptions,
|
[EModelEndpoint.anthropic]: anthropic.buildOptions,
|
||||||
[EModelEndpoint.gptPlugins]: gptPlugins.buildOptions,
|
[EModelEndpoint.gptPlugins]: gptPlugins.buildOptions,
|
||||||
|
|
|
@ -41,7 +41,7 @@ router.post('/:agent_id', async (req, res) => {
|
||||||
return res.status(400).json({ message: 'No functions provided' });
|
return res.status(400).json({ message: 'No functions provided' });
|
||||||
}
|
}
|
||||||
|
|
||||||
let metadata = encryptMetadata(_metadata);
|
let metadata = await encryptMetadata(_metadata);
|
||||||
|
|
||||||
let { domain } = metadata;
|
let { domain } = metadata;
|
||||||
domain = await domainParser(req, domain, true);
|
domain = await domainParser(req, domain, true);
|
||||||
|
|
|
@ -1,11 +1,30 @@
|
||||||
const multer = require('multer');
|
const multer = require('multer');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
const { PermissionTypes, Permissions } = require('librechat-data-provider');
|
||||||
|
const { requireJwtAuth, generateCheckAccess } = require('~/server/middleware');
|
||||||
const v1 = require('~/server/controllers/agents/v1');
|
const v1 = require('~/server/controllers/agents/v1');
|
||||||
const actions = require('./actions');
|
const actions = require('./actions');
|
||||||
|
|
||||||
const upload = multer();
|
const upload = multer();
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
const checkAgentAccess = generateCheckAccess(PermissionTypes.AGENTS, [Permissions.USE]);
|
||||||
|
const checkAgentCreate = generateCheckAccess(PermissionTypes.AGENTS, [
|
||||||
|
Permissions.USE,
|
||||||
|
Permissions.CREATE,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const checkGlobalAgentShare = generateCheckAccess(
|
||||||
|
PermissionTypes.AGENTS,
|
||||||
|
[Permissions.USE, Permissions.CREATE],
|
||||||
|
{
|
||||||
|
[Permissions.SHARED_GLOBAL]: ['projectIds', 'removeProjectIds'],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
router.use(requireJwtAuth);
|
||||||
|
router.use(checkAgentAccess);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Agent actions route.
|
* Agent actions route.
|
||||||
* @route GET|POST /agents/actions
|
* @route GET|POST /agents/actions
|
||||||
|
@ -27,7 +46,7 @@ router.use('/tools', (req, res) => {
|
||||||
* @param {AgentCreateParams} req.body - The agent creation parameters.
|
* @param {AgentCreateParams} req.body - The agent creation parameters.
|
||||||
* @returns {Agent} 201 - Success response - application/json
|
* @returns {Agent} 201 - Success response - application/json
|
||||||
*/
|
*/
|
||||||
router.post('/', v1.createAgent);
|
router.post('/', checkAgentCreate, v1.createAgent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves an agent.
|
* Retrieves an agent.
|
||||||
|
@ -35,7 +54,7 @@ router.post('/', v1.createAgent);
|
||||||
* @param {string} req.params.id - Agent identifier.
|
* @param {string} req.params.id - Agent identifier.
|
||||||
* @returns {Agent} 200 - Success response - application/json
|
* @returns {Agent} 200 - Success response - application/json
|
||||||
*/
|
*/
|
||||||
router.get('/:id', v1.getAgent);
|
router.get('/:id', checkAgentAccess, v1.getAgent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates an agent.
|
* Updates an agent.
|
||||||
|
@ -44,7 +63,7 @@ router.get('/:id', v1.getAgent);
|
||||||
* @param {AgentUpdateParams} req.body - The agent update parameters.
|
* @param {AgentUpdateParams} req.body - The agent update parameters.
|
||||||
* @returns {Agent} 200 - Success response - application/json
|
* @returns {Agent} 200 - Success response - application/json
|
||||||
*/
|
*/
|
||||||
router.patch('/:id', v1.updateAgent);
|
router.patch('/:id', checkGlobalAgentShare, v1.updateAgent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes an agent.
|
* Deletes an agent.
|
||||||
|
@ -52,7 +71,7 @@ router.patch('/:id', v1.updateAgent);
|
||||||
* @param {string} req.params.id - Agent identifier.
|
* @param {string} req.params.id - Agent identifier.
|
||||||
* @returns {Agent} 200 - success response - application/json
|
* @returns {Agent} 200 - success response - application/json
|
||||||
*/
|
*/
|
||||||
router.delete('/:id', v1.deleteAgent);
|
router.delete('/:id', checkAgentCreate, v1.deleteAgent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of agents.
|
* Returns a list of agents.
|
||||||
|
@ -60,9 +79,7 @@ router.delete('/:id', v1.deleteAgent);
|
||||||
* @param {AgentListParams} req.query - The agent list parameters for pagination and sorting.
|
* @param {AgentListParams} req.query - The agent list parameters for pagination and sorting.
|
||||||
* @returns {AgentListResponse} 200 - success response - application/json
|
* @returns {AgentListResponse} 200 - success response - application/json
|
||||||
*/
|
*/
|
||||||
router.get('/', v1.getListAgents);
|
router.get('/', checkAgentAccess, v1.getListAgents);
|
||||||
|
|
||||||
// TODO: handle private agents
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uploads and updates an avatar for a specific agent.
|
* Uploads and updates an avatar for a specific agent.
|
||||||
|
@ -72,6 +89,6 @@ router.get('/', v1.getListAgents);
|
||||||
* @param {string} [req.body.metadata] - Optional metadata for the agent's avatar.
|
* @param {string} [req.body.metadata] - Optional metadata for the agent's avatar.
|
||||||
* @returns {Object} 200 - success response - application/json
|
* @returns {Object} 200 - success response - application/json
|
||||||
*/
|
*/
|
||||||
router.post('/avatar/:agent_id', upload.single('file'), v1.uploadAgentAvatar);
|
router.post('/avatar/:agent_id', checkAgentAccess, upload.single('file'), v1.uploadAgentAvatar);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
36
api/server/routes/bedrock/chat.js
Normal file
36
api/server/routes/bedrock/chat.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
const express = require('express');
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
const {
|
||||||
|
setHeaders,
|
||||||
|
handleAbort,
|
||||||
|
// validateModel,
|
||||||
|
// validateEndpoint,
|
||||||
|
buildEndpointOption,
|
||||||
|
} = require('~/server/middleware');
|
||||||
|
const { initializeClient } = require('~/server/services/Endpoints/bedrock');
|
||||||
|
const AgentController = require('~/server/controllers/agents/request');
|
||||||
|
const addTitle = require('~/server/services/Endpoints/bedrock/title');
|
||||||
|
|
||||||
|
router.post('/abort', handleAbort());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @route POST /
|
||||||
|
* @desc Chat with an assistant
|
||||||
|
* @access Public
|
||||||
|
* @param {express.Request} req - The request object, containing the request data.
|
||||||
|
* @param {express.Response} res - The response object, used to send back a response.
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
router.post(
|
||||||
|
'/',
|
||||||
|
// validateModel,
|
||||||
|
// validateEndpoint,
|
||||||
|
buildEndpointOption,
|
||||||
|
setHeaders,
|
||||||
|
async (req, res, next) => {
|
||||||
|
await AgentController(req, res, next, initializeClient, addTitle);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
module.exports = router;
|
19
api/server/routes/bedrock/index.js
Normal file
19
api/server/routes/bedrock/index.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
const {
|
||||||
|
uaParser,
|
||||||
|
checkBan,
|
||||||
|
requireJwtAuth,
|
||||||
|
// concurrentLimiter,
|
||||||
|
// messageIpLimiter,
|
||||||
|
// messageUserLimiter,
|
||||||
|
} = require('~/server/middleware');
|
||||||
|
|
||||||
|
const chat = require('./chat');
|
||||||
|
|
||||||
|
router.use(requireJwtAuth);
|
||||||
|
router.use(checkBan);
|
||||||
|
router.use(uaParser);
|
||||||
|
router.use('/chat', chat);
|
||||||
|
|
||||||
|
module.exports = router;
|
|
@ -1,5 +1,5 @@
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { CacheKeys, defaultSocialLogins } = require('librechat-data-provider');
|
const { CacheKeys, defaultSocialLogins, Constants } = require('librechat-data-provider');
|
||||||
const { getLdapConfig } = require('~/server/services/Config/ldap');
|
const { getLdapConfig } = require('~/server/services/Config/ldap');
|
||||||
const { getProjectByName } = require('~/models/Project');
|
const { getProjectByName } = require('~/models/Project');
|
||||||
const { isEnabled } = require('~/server/utils');
|
const { isEnabled } = require('~/server/utils');
|
||||||
|
@ -32,7 +32,7 @@ router.get('/', async function (req, res) {
|
||||||
return today.getMonth() === 1 && today.getDate() === 11;
|
return today.getMonth() === 1 && today.getDate() === 11;
|
||||||
};
|
};
|
||||||
|
|
||||||
const instanceProject = await getProjectByName('instance', '_id');
|
const instanceProject = await getProjectByName(Constants.GLOBAL_PROJECT_NAME, '_id');
|
||||||
|
|
||||||
const ldap = getLdapConfig();
|
const ldap = getLdapConfig();
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ const presets = require('./presets');
|
||||||
const prompts = require('./prompts');
|
const prompts = require('./prompts');
|
||||||
const balance = require('./balance');
|
const balance = require('./balance');
|
||||||
const plugins = require('./plugins');
|
const plugins = require('./plugins');
|
||||||
|
const bedrock = require('./bedrock');
|
||||||
const search = require('./search');
|
const search = require('./search');
|
||||||
const models = require('./models');
|
const models = require('./models');
|
||||||
const convos = require('./convos');
|
const convos = require('./convos');
|
||||||
|
@ -36,6 +37,7 @@ module.exports = {
|
||||||
files,
|
files,
|
||||||
share,
|
share,
|
||||||
agents,
|
agents,
|
||||||
|
bedrock,
|
||||||
convos,
|
convos,
|
||||||
search,
|
search,
|
||||||
prompts,
|
prompts,
|
||||||
|
|
|
@ -24,6 +24,7 @@ const checkPromptCreate = generateCheckAccess(PermissionTypes.PROMPTS, [
|
||||||
Permissions.USE,
|
Permissions.USE,
|
||||||
Permissions.CREATE,
|
Permissions.CREATE,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const checkGlobalPromptShare = generateCheckAccess(
|
const checkGlobalPromptShare = generateCheckAccess(
|
||||||
PermissionTypes.PROMPTS,
|
PermissionTypes.PROMPTS,
|
||||||
[Permissions.USE, Permissions.CREATE],
|
[Permissions.USE, Permissions.CREATE],
|
||||||
|
|
|
@ -165,7 +165,7 @@ async function createActionTool({ action, requestBuilder, zodSchema, name, descr
|
||||||
* Encrypts sensitive metadata values for an action.
|
* Encrypts sensitive metadata values for an action.
|
||||||
*
|
*
|
||||||
* @param {ActionMetadata} metadata - The action metadata to encrypt.
|
* @param {ActionMetadata} metadata - The action metadata to encrypt.
|
||||||
* @returns {ActionMetadata} The updated action metadata with encrypted values.
|
* @returns {Promise<ActionMetadata>} The updated action metadata with encrypted values.
|
||||||
*/
|
*/
|
||||||
async function encryptMetadata(metadata) {
|
async function encryptMetadata(metadata) {
|
||||||
const encryptedMetadata = { ...metadata };
|
const encryptedMetadata = { ...metadata };
|
||||||
|
|
|
@ -94,18 +94,19 @@ const AppService = async (app) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endpoints?.[EModelEndpoint.openAI]) {
|
const endpointKeys = [
|
||||||
endpointLocals[EModelEndpoint.openAI] = endpoints[EModelEndpoint.openAI];
|
EModelEndpoint.openAI,
|
||||||
}
|
EModelEndpoint.google,
|
||||||
if (endpoints?.[EModelEndpoint.google]) {
|
EModelEndpoint.bedrock,
|
||||||
endpointLocals[EModelEndpoint.google] = endpoints[EModelEndpoint.google];
|
EModelEndpoint.anthropic,
|
||||||
}
|
EModelEndpoint.gptPlugins,
|
||||||
if (endpoints?.[EModelEndpoint.anthropic]) {
|
];
|
||||||
endpointLocals[EModelEndpoint.anthropic] = endpoints[EModelEndpoint.anthropic];
|
|
||||||
}
|
endpointKeys.forEach((key) => {
|
||||||
if (endpoints?.[EModelEndpoint.gptPlugins]) {
|
if (endpoints?.[key]) {
|
||||||
endpointLocals[EModelEndpoint.gptPlugins] = endpoints[EModelEndpoint.gptPlugins];
|
endpointLocals[key] = endpoints[key];
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.locals = {
|
app.locals = {
|
||||||
...defaultLocals,
|
...defaultLocals,
|
||||||
|
|
|
@ -45,6 +45,7 @@ module.exports = {
|
||||||
AZURE_ASSISTANTS_BASE_URL,
|
AZURE_ASSISTANTS_BASE_URL,
|
||||||
EModelEndpoint.azureAssistants,
|
EModelEndpoint.azureAssistants,
|
||||||
),
|
),
|
||||||
|
[EModelEndpoint.bedrock]: generateConfig(process.env.BEDROCK_AWS_SECRET_ACCESS_KEY),
|
||||||
/* key will be part of separate config */
|
/* key will be part of separate config */
|
||||||
[EModelEndpoint.agents]: generateConfig(process.env.I_AM_A_TEAPOT),
|
[EModelEndpoint.agents]: generateConfig(process.env.I_AM_A_TEAPOT),
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,22 +9,13 @@ const { config } = require('./EndpointService');
|
||||||
*/
|
*/
|
||||||
async function loadDefaultEndpointsConfig(req) {
|
async function loadDefaultEndpointsConfig(req) {
|
||||||
const { google, gptPlugins } = await loadAsyncEndpoints(req);
|
const { google, gptPlugins } = await loadAsyncEndpoints(req);
|
||||||
const {
|
const { assistants, azureAssistants, bingAI, azureOpenAI, chatGPTBrowser } = config;
|
||||||
openAI,
|
|
||||||
agents,
|
|
||||||
assistants,
|
|
||||||
azureAssistants,
|
|
||||||
bingAI,
|
|
||||||
anthropic,
|
|
||||||
azureOpenAI,
|
|
||||||
chatGPTBrowser,
|
|
||||||
} = config;
|
|
||||||
|
|
||||||
const enabledEndpoints = getEnabledEndpoints();
|
const enabledEndpoints = getEnabledEndpoints();
|
||||||
|
|
||||||
const endpointConfig = {
|
const endpointConfig = {
|
||||||
[EModelEndpoint.openAI]: openAI,
|
[EModelEndpoint.openAI]: config[EModelEndpoint.openAI],
|
||||||
[EModelEndpoint.agents]: agents,
|
[EModelEndpoint.agents]: config[EModelEndpoint.agents],
|
||||||
[EModelEndpoint.assistants]: assistants,
|
[EModelEndpoint.assistants]: assistants,
|
||||||
[EModelEndpoint.azureAssistants]: azureAssistants,
|
[EModelEndpoint.azureAssistants]: azureAssistants,
|
||||||
[EModelEndpoint.azureOpenAI]: azureOpenAI,
|
[EModelEndpoint.azureOpenAI]: azureOpenAI,
|
||||||
|
@ -32,7 +23,8 @@ async function loadDefaultEndpointsConfig(req) {
|
||||||
[EModelEndpoint.bingAI]: bingAI,
|
[EModelEndpoint.bingAI]: bingAI,
|
||||||
[EModelEndpoint.chatGPTBrowser]: chatGPTBrowser,
|
[EModelEndpoint.chatGPTBrowser]: chatGPTBrowser,
|
||||||
[EModelEndpoint.gptPlugins]: gptPlugins,
|
[EModelEndpoint.gptPlugins]: gptPlugins,
|
||||||
[EModelEndpoint.anthropic]: anthropic,
|
[EModelEndpoint.anthropic]: config[EModelEndpoint.anthropic],
|
||||||
|
[EModelEndpoint.bedrock]: config[EModelEndpoint.bedrock],
|
||||||
};
|
};
|
||||||
|
|
||||||
const orderedAndFilteredEndpoints = enabledEndpoints.reduce((config, key, index) => {
|
const orderedAndFilteredEndpoints = enabledEndpoints.reduce((config, key, index) => {
|
||||||
|
|
|
@ -3,6 +3,7 @@ const { useAzurePlugins } = require('~/server/services/Config/EndpointService').
|
||||||
const {
|
const {
|
||||||
getOpenAIModels,
|
getOpenAIModels,
|
||||||
getGoogleModels,
|
getGoogleModels,
|
||||||
|
getBedrockModels,
|
||||||
getAnthropicModels,
|
getAnthropicModels,
|
||||||
getChatGPTBrowserModels,
|
getChatGPTBrowserModels,
|
||||||
} = require('~/server/services/ModelService');
|
} = require('~/server/services/ModelService');
|
||||||
|
@ -38,6 +39,7 @@ async function loadDefaultModels(req) {
|
||||||
[EModelEndpoint.chatGPTBrowser]: chatGPTBrowser,
|
[EModelEndpoint.chatGPTBrowser]: chatGPTBrowser,
|
||||||
[EModelEndpoint.assistants]: assistants,
|
[EModelEndpoint.assistants]: assistants,
|
||||||
[EModelEndpoint.azureAssistants]: azureAssistants,
|
[EModelEndpoint.azureAssistants]: azureAssistants,
|
||||||
|
[EModelEndpoint.bedrock]: getBedrockModels(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ const { getAgent } = require('~/models/Agent');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
const buildOptions = (req, endpoint, parsedBody) => {
|
const buildOptions = (req, endpoint, parsedBody) => {
|
||||||
const { agent_id, instructions, spec, ...rest } = parsedBody;
|
const { agent_id, instructions, spec, ...model_parameters } = parsedBody;
|
||||||
|
|
||||||
const agentPromise = getAgent({
|
const agentPromise = getAgent({
|
||||||
id: agent_id,
|
id: agent_id,
|
||||||
|
@ -19,9 +19,7 @@ const buildOptions = (req, endpoint, parsedBody) => {
|
||||||
agent_id,
|
agent_id,
|
||||||
instructions,
|
instructions,
|
||||||
spec,
|
spec,
|
||||||
modelOptions: {
|
model_parameters,
|
||||||
...rest,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return endpointOption;
|
return endpointOption;
|
||||||
|
|
|
@ -11,7 +11,12 @@
|
||||||
|
|
||||||
const { z } = require('zod');
|
const { z } = require('zod');
|
||||||
const { tool } = require('@langchain/core/tools');
|
const { tool } = require('@langchain/core/tools');
|
||||||
const { EModelEndpoint, providerEndpointMap } = require('librechat-data-provider');
|
const { createContentAggregator } = require('@librechat/agents');
|
||||||
|
const {
|
||||||
|
EModelEndpoint,
|
||||||
|
providerEndpointMap,
|
||||||
|
getResponseSender,
|
||||||
|
} = require('librechat-data-provider');
|
||||||
const { getDefaultHandlers } = require('~/server/controllers/agents/callbacks');
|
const { getDefaultHandlers } = require('~/server/controllers/agents/callbacks');
|
||||||
// for testing purposes
|
// for testing purposes
|
||||||
// const createTavilySearchTool = require('~/app/clients/tools/structured/TavilySearch');
|
// const createTavilySearchTool = require('~/app/clients/tools/structured/TavilySearch');
|
||||||
|
@ -53,7 +58,8 @@ const initializeClient = async ({ req, res, endpointOption }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: use endpointOption to determine options/modelOptions
|
// TODO: use endpointOption to determine options/modelOptions
|
||||||
const eventHandlers = getDefaultHandlers({ res });
|
const { contentParts, aggregateContent } = createContentAggregator();
|
||||||
|
const eventHandlers = getDefaultHandlers({ res, aggregateContent });
|
||||||
|
|
||||||
// const tools = [createTavilySearchTool()];
|
// const tools = [createTavilySearchTool()];
|
||||||
// const tools = [_getWeather];
|
// const tools = [_getWeather];
|
||||||
|
@ -90,7 +96,7 @@ const initializeClient = async ({ req, res, endpointOption }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: pass-in override settings that are specific to current run
|
// TODO: pass-in override settings that are specific to current run
|
||||||
endpointOption.modelOptions.model = agent.model;
|
endpointOption.model_parameters.model = agent.model;
|
||||||
const options = await getOptions({
|
const options = await getOptions({
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
|
@ -101,13 +107,21 @@ const initializeClient = async ({ req, res, endpointOption }) => {
|
||||||
});
|
});
|
||||||
modelOptions = Object.assign(modelOptions, options.llmConfig);
|
modelOptions = Object.assign(modelOptions, options.llmConfig);
|
||||||
|
|
||||||
|
const sender = getResponseSender({
|
||||||
|
...endpointOption,
|
||||||
|
model: endpointOption.model_parameters.model,
|
||||||
|
});
|
||||||
|
|
||||||
const client = new AgentClient({
|
const client = new AgentClient({
|
||||||
req,
|
req,
|
||||||
agent,
|
agent,
|
||||||
tools,
|
tools,
|
||||||
|
sender,
|
||||||
toolMap,
|
toolMap,
|
||||||
|
contentParts,
|
||||||
modelOptions,
|
modelOptions,
|
||||||
eventHandlers,
|
eventHandlers,
|
||||||
|
endpoint: EModelEndpoint.agents,
|
||||||
configOptions: options.configOptions,
|
configOptions: options.configOptions,
|
||||||
maxContextTokens:
|
maxContextTokens:
|
||||||
agent.max_context_tokens ??
|
agent.max_context_tokens ??
|
||||||
|
|
|
@ -23,7 +23,7 @@ const addTitle = async (req, { text, response, client }) => {
|
||||||
|
|
||||||
const title = await client.titleConvo({
|
const title = await client.titleConvo({
|
||||||
text,
|
text,
|
||||||
responseText: response?.text,
|
responseText: response?.text ?? '',
|
||||||
conversationId: response.conversationId,
|
conversationId: response.conversationId,
|
||||||
});
|
});
|
||||||
await titleCache.set(key, title, 120000);
|
await titleCache.set(key, title, 120000);
|
||||||
|
|
44
api/server/services/Endpoints/bedrock/build.js
Normal file
44
api/server/services/Endpoints/bedrock/build.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
const { removeNullishValues, bedrockInputParser } = require('librechat-data-provider');
|
||||||
|
const generateArtifactsPrompt = require('~/app/clients/prompts/artifacts');
|
||||||
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
|
const buildOptions = (endpoint, parsedBody) => {
|
||||||
|
const {
|
||||||
|
modelLabel: name,
|
||||||
|
promptPrefix,
|
||||||
|
maxContextTokens,
|
||||||
|
resendFiles = true,
|
||||||
|
imageDetail,
|
||||||
|
iconURL,
|
||||||
|
greeting,
|
||||||
|
spec,
|
||||||
|
artifacts,
|
||||||
|
...model_parameters
|
||||||
|
} = parsedBody;
|
||||||
|
let parsedParams = model_parameters;
|
||||||
|
try {
|
||||||
|
parsedParams = bedrockInputParser.parse(model_parameters);
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('Failed to parse bedrock input', error);
|
||||||
|
}
|
||||||
|
const endpointOption = removeNullishValues({
|
||||||
|
endpoint,
|
||||||
|
name,
|
||||||
|
resendFiles,
|
||||||
|
imageDetail,
|
||||||
|
iconURL,
|
||||||
|
greeting,
|
||||||
|
spec,
|
||||||
|
promptPrefix,
|
||||||
|
maxContextTokens,
|
||||||
|
model_parameters: parsedParams,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof artifacts === 'string') {
|
||||||
|
endpointOption.artifactsPrompt = generateArtifactsPrompt({ endpoint, artifacts });
|
||||||
|
}
|
||||||
|
|
||||||
|
return endpointOption;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { buildOptions };
|
7
api/server/services/Endpoints/bedrock/index.js
Normal file
7
api/server/services/Endpoints/bedrock/index.js
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
const build = require('./build');
|
||||||
|
const initialize = require('./initialize');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...build,
|
||||||
|
...initialize,
|
||||||
|
};
|
72
api/server/services/Endpoints/bedrock/initialize.js
Normal file
72
api/server/services/Endpoints/bedrock/initialize.js
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
const { createContentAggregator } = require('@librechat/agents');
|
||||||
|
const {
|
||||||
|
EModelEndpoint,
|
||||||
|
providerEndpointMap,
|
||||||
|
getResponseSender,
|
||||||
|
} = require('librechat-data-provider');
|
||||||
|
const { getDefaultHandlers } = require('~/server/controllers/agents/callbacks');
|
||||||
|
// const { loadAgentTools } = require('~/server/services/ToolService');
|
||||||
|
const getOptions = require('~/server/services/Endpoints/bedrock/options');
|
||||||
|
const AgentClient = require('~/server/controllers/agents/client');
|
||||||
|
const { getModelMaxTokens } = require('~/utils');
|
||||||
|
|
||||||
|
const initializeClient = async ({ req, res, endpointOption }) => {
|
||||||
|
if (!endpointOption) {
|
||||||
|
throw new Error('Endpoint option not provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {Array<UsageMetadata>} */
|
||||||
|
const collectedUsage = [];
|
||||||
|
const { contentParts, aggregateContent } = createContentAggregator();
|
||||||
|
const eventHandlers = getDefaultHandlers({ res, aggregateContent, collectedUsage });
|
||||||
|
|
||||||
|
// const tools = [createTavilySearchTool()];
|
||||||
|
|
||||||
|
/** @type {Agent} */
|
||||||
|
const agent = {
|
||||||
|
id: EModelEndpoint.bedrock,
|
||||||
|
name: endpointOption.name,
|
||||||
|
instructions: endpointOption.promptPrefix,
|
||||||
|
provider: EModelEndpoint.bedrock,
|
||||||
|
model: endpointOption.model_parameters.model,
|
||||||
|
model_parameters: endpointOption.model_parameters,
|
||||||
|
};
|
||||||
|
|
||||||
|
let modelOptions = { model: agent.model };
|
||||||
|
|
||||||
|
// TODO: pass-in override settings that are specific to current run
|
||||||
|
const options = await getOptions({
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
endpointOption,
|
||||||
|
});
|
||||||
|
|
||||||
|
modelOptions = Object.assign(modelOptions, options.llmConfig);
|
||||||
|
const maxContextTokens =
|
||||||
|
agent.max_context_tokens ??
|
||||||
|
getModelMaxTokens(modelOptions.model, providerEndpointMap[agent.provider]);
|
||||||
|
|
||||||
|
const sender = getResponseSender({
|
||||||
|
...endpointOption,
|
||||||
|
model: endpointOption.model_parameters.model,
|
||||||
|
});
|
||||||
|
|
||||||
|
const client = new AgentClient({
|
||||||
|
req,
|
||||||
|
agent,
|
||||||
|
sender,
|
||||||
|
// tools,
|
||||||
|
// toolMap,
|
||||||
|
modelOptions,
|
||||||
|
contentParts,
|
||||||
|
eventHandlers,
|
||||||
|
collectedUsage,
|
||||||
|
maxContextTokens,
|
||||||
|
endpoint: EModelEndpoint.bedrock,
|
||||||
|
configOptions: options.configOptions,
|
||||||
|
attachments: endpointOption.attachments,
|
||||||
|
});
|
||||||
|
return { client };
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { initializeClient };
|
90
api/server/services/Endpoints/bedrock/options.js
Normal file
90
api/server/services/Endpoints/bedrock/options.js
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
const { HttpsProxyAgent } = require('https-proxy-agent');
|
||||||
|
const {
|
||||||
|
EModelEndpoint,
|
||||||
|
Constants,
|
||||||
|
AuthType,
|
||||||
|
removeNullishValues,
|
||||||
|
} = require('librechat-data-provider');
|
||||||
|
const { getUserKey, checkUserKeyExpiry } = require('~/server/services/UserService');
|
||||||
|
const { sleep } = require('~/server/utils');
|
||||||
|
|
||||||
|
const getOptions = async ({ req, endpointOption }) => {
|
||||||
|
const {
|
||||||
|
BEDROCK_AWS_SECRET_ACCESS_KEY,
|
||||||
|
BEDROCK_AWS_ACCESS_KEY_ID,
|
||||||
|
BEDROCK_REVERSE_PROXY,
|
||||||
|
BEDROCK_AWS_DEFAULT_REGION,
|
||||||
|
PROXY,
|
||||||
|
} = process.env;
|
||||||
|
const expiresAt = req.body.key;
|
||||||
|
const isUserProvided = BEDROCK_AWS_SECRET_ACCESS_KEY === AuthType.USER_PROVIDED;
|
||||||
|
|
||||||
|
const credentials = isUserProvided
|
||||||
|
? await getUserKey({ userId: req.user.id, name: EModelEndpoint.bedrock })
|
||||||
|
: {
|
||||||
|
accessKeyId: BEDROCK_AWS_ACCESS_KEY_ID,
|
||||||
|
secretAccessKey: BEDROCK_AWS_SECRET_ACCESS_KEY,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!credentials) {
|
||||||
|
throw new Error('Bedrock credentials not provided. Please provide them again.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expiresAt && isUserProvided) {
|
||||||
|
checkUserKeyExpiry(expiresAt, EModelEndpoint.bedrock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {number} */
|
||||||
|
let streamRate = Constants.DEFAULT_STREAM_RATE;
|
||||||
|
|
||||||
|
/** @type {undefined | TBaseEndpoint} */
|
||||||
|
const bedrockConfig = req.app.locals[EModelEndpoint.bedrock];
|
||||||
|
|
||||||
|
if (bedrockConfig && bedrockConfig.streamRate) {
|
||||||
|
streamRate = bedrockConfig.streamRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {undefined | TBaseEndpoint} */
|
||||||
|
const allConfig = req.app.locals.all;
|
||||||
|
if (allConfig && allConfig.streamRate) {
|
||||||
|
streamRate = allConfig.streamRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import('@librechat/agents').BedrockConverseClientOptions} */
|
||||||
|
const requestOptions = Object.assign(
|
||||||
|
{
|
||||||
|
credentials,
|
||||||
|
model: endpointOption.model,
|
||||||
|
region: BEDROCK_AWS_DEFAULT_REGION,
|
||||||
|
streaming: true,
|
||||||
|
streamUsage: true,
|
||||||
|
callbacks: [
|
||||||
|
{
|
||||||
|
handleLLMNewToken: async () => {
|
||||||
|
if (!streamRate) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await sleep(streamRate);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
endpointOption.model_parameters,
|
||||||
|
);
|
||||||
|
|
||||||
|
const configOptions = {};
|
||||||
|
if (PROXY) {
|
||||||
|
configOptions.httpAgent = new HttpsProxyAgent(PROXY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BEDROCK_REVERSE_PROXY) {
|
||||||
|
configOptions.endpointHost = BEDROCK_REVERSE_PROXY;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
llmConfig: removeNullishValues(requestOptions),
|
||||||
|
configOptions,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = getOptions;
|
40
api/server/services/Endpoints/bedrock/title.js
Normal file
40
api/server/services/Endpoints/bedrock/title.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
const { CacheKeys } = require('librechat-data-provider');
|
||||||
|
const getLogStores = require('~/cache/getLogStores');
|
||||||
|
const { isEnabled } = require('~/server/utils');
|
||||||
|
const { saveConvo } = require('~/models');
|
||||||
|
|
||||||
|
const addTitle = async (req, { text, response, client }) => {
|
||||||
|
const { TITLE_CONVO = true } = process.env ?? {};
|
||||||
|
if (!isEnabled(TITLE_CONVO)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client.options.titleConvo === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the request was aborted, don't generate the title.
|
||||||
|
if (client.abortController.signal.aborted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const titleCache = getLogStores(CacheKeys.GEN_TITLE);
|
||||||
|
const key = `${req.user.id}-${response.conversationId}`;
|
||||||
|
|
||||||
|
const title = await client.titleConvo({
|
||||||
|
text,
|
||||||
|
responseText: response?.text ?? '',
|
||||||
|
conversationId: response.conversationId,
|
||||||
|
});
|
||||||
|
await titleCache.set(key, title, 120000);
|
||||||
|
await saveConvo(
|
||||||
|
req,
|
||||||
|
{
|
||||||
|
conversationId: response.conversationId,
|
||||||
|
title,
|
||||||
|
},
|
||||||
|
{ context: 'api/server/services/Endpoints/bedrock/title.js' },
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = addTitle;
|
|
@ -49,7 +49,7 @@ const addTitle = async (req, { text, response, client }) => {
|
||||||
|
|
||||||
const title = await titleClient.titleConvo({
|
const title = await titleClient.titleConvo({
|
||||||
text,
|
text,
|
||||||
responseText: response?.text,
|
responseText: response?.text ?? '',
|
||||||
conversationId: response.conversationId,
|
conversationId: response.conversationId,
|
||||||
});
|
});
|
||||||
await titleCache.set(key, title, 120000);
|
await titleCache.set(key, title, 120000);
|
||||||
|
|
|
@ -23,7 +23,7 @@ const addTitle = async (req, { text, response, client }) => {
|
||||||
|
|
||||||
const title = await client.titleConvo({
|
const title = await client.titleConvo({
|
||||||
text,
|
text,
|
||||||
responseText: response?.text,
|
responseText: response?.text ?? '',
|
||||||
conversationId: response.conversationId,
|
conversationId: response.conversationId,
|
||||||
});
|
});
|
||||||
await titleCache.set(key, title, 120000);
|
await titleCache.set(key, title, 120000);
|
||||||
|
|
|
@ -23,7 +23,13 @@ async function fetchImageToBase64(url) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const base64Only = new Set([EModelEndpoint.google, EModelEndpoint.anthropic, 'Ollama', 'ollama']);
|
const base64Only = new Set([
|
||||||
|
EModelEndpoint.google,
|
||||||
|
EModelEndpoint.anthropic,
|
||||||
|
'Ollama',
|
||||||
|
'ollama',
|
||||||
|
EModelEndpoint.bedrock,
|
||||||
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes and formats the given files.
|
* Encodes and formats the given files.
|
||||||
|
|
|
@ -5,6 +5,21 @@ const { extractBaseURL, inputSchema, processModelData, logAxiosError } = require
|
||||||
const { OllamaClient } = require('~/app/clients/OllamaClient');
|
const { OllamaClient } = require('~/app/clients/OllamaClient');
|
||||||
const getLogStores = require('~/cache/getLogStores');
|
const getLogStores = require('~/cache/getLogStores');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits a string by commas and trims each resulting value.
|
||||||
|
* @param {string} input - The input string to split.
|
||||||
|
* @returns {string[]} An array of trimmed values.
|
||||||
|
*/
|
||||||
|
const splitAndTrim = (input) => {
|
||||||
|
if (!input || typeof input !== 'string') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return input
|
||||||
|
.split(',')
|
||||||
|
.map((item) => item.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
};
|
||||||
|
|
||||||
const { openAIApiKey, userProvidedOpenAI } = require('./Config/EndpointService').config;
|
const { openAIApiKey, userProvidedOpenAI } = require('./Config/EndpointService').config;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -194,7 +209,7 @@ const getOpenAIModels = async (opts) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env[key]) {
|
if (process.env[key]) {
|
||||||
models = String(process.env[key]).split(',');
|
models = splitAndTrim(process.env[key]);
|
||||||
return models;
|
return models;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,7 +223,7 @@ const getOpenAIModels = async (opts) => {
|
||||||
const getChatGPTBrowserModels = () => {
|
const getChatGPTBrowserModels = () => {
|
||||||
let models = ['text-davinci-002-render-sha', 'gpt-4'];
|
let models = ['text-davinci-002-render-sha', 'gpt-4'];
|
||||||
if (process.env.CHATGPT_MODELS) {
|
if (process.env.CHATGPT_MODELS) {
|
||||||
models = String(process.env.CHATGPT_MODELS).split(',');
|
models = splitAndTrim(process.env.CHATGPT_MODELS);
|
||||||
}
|
}
|
||||||
|
|
||||||
return models;
|
return models;
|
||||||
|
@ -217,7 +232,7 @@ const getChatGPTBrowserModels = () => {
|
||||||
const getAnthropicModels = () => {
|
const getAnthropicModels = () => {
|
||||||
let models = defaultModels[EModelEndpoint.anthropic];
|
let models = defaultModels[EModelEndpoint.anthropic];
|
||||||
if (process.env.ANTHROPIC_MODELS) {
|
if (process.env.ANTHROPIC_MODELS) {
|
||||||
models = String(process.env.ANTHROPIC_MODELS).split(',');
|
models = splitAndTrim(process.env.ANTHROPIC_MODELS);
|
||||||
}
|
}
|
||||||
|
|
||||||
return models;
|
return models;
|
||||||
|
@ -226,7 +241,16 @@ const getAnthropicModels = () => {
|
||||||
const getGoogleModels = () => {
|
const getGoogleModels = () => {
|
||||||
let models = defaultModels[EModelEndpoint.google];
|
let models = defaultModels[EModelEndpoint.google];
|
||||||
if (process.env.GOOGLE_MODELS) {
|
if (process.env.GOOGLE_MODELS) {
|
||||||
models = String(process.env.GOOGLE_MODELS).split(',');
|
models = splitAndTrim(process.env.GOOGLE_MODELS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return models;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getBedrockModels = () => {
|
||||||
|
let models = defaultModels[EModelEndpoint.bedrock];
|
||||||
|
if (process.env.BEDROCK_AWS_MODELS) {
|
||||||
|
models = splitAndTrim(process.env.BEDROCK_AWS_MODELS);
|
||||||
}
|
}
|
||||||
|
|
||||||
return models;
|
return models;
|
||||||
|
@ -234,7 +258,9 @@ const getGoogleModels = () => {
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
fetchModels,
|
fetchModels,
|
||||||
|
splitAndTrim,
|
||||||
getOpenAIModels,
|
getOpenAIModels,
|
||||||
|
getBedrockModels,
|
||||||
getChatGPTBrowserModels,
|
getChatGPTBrowserModels,
|
||||||
getAnthropicModels,
|
getAnthropicModels,
|
||||||
getGoogleModels,
|
getGoogleModels,
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
|
const { EModelEndpoint, defaultModels } = require('librechat-data-provider');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
const { fetchModels, getOpenAIModels } = require('./ModelService');
|
const {
|
||||||
|
fetchModels,
|
||||||
|
splitAndTrim,
|
||||||
|
getOpenAIModels,
|
||||||
|
getGoogleModels,
|
||||||
|
getBedrockModels,
|
||||||
|
getAnthropicModels,
|
||||||
|
} = require('./ModelService');
|
||||||
|
|
||||||
jest.mock('~/utils', () => {
|
jest.mock('~/utils', () => {
|
||||||
const originalUtils = jest.requireActual('~/utils');
|
const originalUtils = jest.requireActual('~/utils');
|
||||||
return {
|
return {
|
||||||
|
@ -329,3 +338,71 @@ describe('fetchModels with Ollama specific logic', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('splitAndTrim', () => {
|
||||||
|
it('should split a string by commas and trim each value', () => {
|
||||||
|
const input = ' model1, model2 , model3,model4 ';
|
||||||
|
const expected = ['model1', 'model2', 'model3', 'model4'];
|
||||||
|
expect(splitAndTrim(input)).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an empty array for empty input', () => {
|
||||||
|
expect(splitAndTrim('')).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an empty array for null input', () => {
|
||||||
|
expect(splitAndTrim(null)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an empty array for undefined input', () => {
|
||||||
|
expect(splitAndTrim(undefined)).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter out empty values after trimming', () => {
|
||||||
|
const input = 'model1,, ,model2,';
|
||||||
|
const expected = ['model1', 'model2'];
|
||||||
|
expect(splitAndTrim(input)).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getAnthropicModels', () => {
|
||||||
|
it('returns default models when ANTHROPIC_MODELS is not set', () => {
|
||||||
|
delete process.env.ANTHROPIC_MODELS;
|
||||||
|
const models = getAnthropicModels();
|
||||||
|
expect(models).toEqual(defaultModels[EModelEndpoint.anthropic]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns models from ANTHROPIC_MODELS when set', () => {
|
||||||
|
process.env.ANTHROPIC_MODELS = 'claude-1, claude-2 ';
|
||||||
|
const models = getAnthropicModels();
|
||||||
|
expect(models).toEqual(['claude-1', 'claude-2']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getGoogleModels', () => {
|
||||||
|
it('returns default models when GOOGLE_MODELS is not set', () => {
|
||||||
|
delete process.env.GOOGLE_MODELS;
|
||||||
|
const models = getGoogleModels();
|
||||||
|
expect(models).toEqual(defaultModels[EModelEndpoint.google]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns models from GOOGLE_MODELS when set', () => {
|
||||||
|
process.env.GOOGLE_MODELS = 'gemini-pro, bard ';
|
||||||
|
const models = getGoogleModels();
|
||||||
|
expect(models).toEqual(['gemini-pro', 'bard']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getBedrockModels', () => {
|
||||||
|
it('returns default models when BEDROCK_AWS_MODELS is not set', () => {
|
||||||
|
delete process.env.BEDROCK_AWS_MODELS;
|
||||||
|
const models = getBedrockModels();
|
||||||
|
expect(models).toEqual(defaultModels[EModelEndpoint.bedrock]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns models from BEDROCK_AWS_MODELS when set', () => {
|
||||||
|
process.env.BEDROCK_AWS_MODELS = 'anthropic.claude-v2, ai21.j2-ultra ';
|
||||||
|
const models = getBedrockModels();
|
||||||
|
expect(models).toEqual(['anthropic.claude-v2', 'ai21.j2-ultra']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -20,12 +20,30 @@
|
||||||
* @memberof typedefs
|
* @memberof typedefs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports AgentRun
|
||||||
|
* @typedef {import('@librechat/agents').Run} AgentRun
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports IState
|
||||||
|
* @typedef {import('@librechat/agents').IState} IState
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @exports ClientCallbacks
|
* @exports ClientCallbacks
|
||||||
* @typedef {import('@librechat/agents').ClientCallbacks} ClientCallbacks
|
* @typedef {import('@librechat/agents').ClientCallbacks} ClientCallbacks
|
||||||
* @memberof typedefs
|
* @memberof typedefs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports BedrockClientOptions
|
||||||
|
* @typedef {import('@librechat/agents').BedrockConverseClientOptions} BedrockClientOptions
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @exports StreamEventData
|
* @exports StreamEventData
|
||||||
* @typedef {import('@librechat/agents').StreamEventData} StreamEventData
|
* @typedef {import('@librechat/agents').StreamEventData} StreamEventData
|
||||||
|
@ -38,6 +56,12 @@
|
||||||
* @memberof typedefs
|
* @memberof typedefs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports UsageMetadata
|
||||||
|
* @typedef {import('@langchain/core/messages').UsageMetadata} UsageMetadata
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @exports Ollama
|
* @exports Ollama
|
||||||
* @typedef {import('ollama').Ollama} Ollama
|
* @typedef {import('ollama').Ollama} Ollama
|
||||||
|
@ -893,6 +917,12 @@
|
||||||
* @memberof typedefs
|
* @memberof typedefs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports TAgentClient
|
||||||
|
* @typedef {import('./server/controllers/agents/client')} TAgentClient
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @exports ImportBatchBuilder
|
* @exports ImportBatchBuilder
|
||||||
* @typedef {import('./server/utils/import/importBatchBuilder.js').ImportBatchBuilder} ImportBatchBuilder
|
* @typedef {import('./server/utils/import/importBatchBuilder.js').ImportBatchBuilder} ImportBatchBuilder
|
||||||
|
|
|
@ -7,13 +7,13 @@ const openAIModels = {
|
||||||
'gpt-4-32k': 32758, // -10 from max
|
'gpt-4-32k': 32758, // -10 from max
|
||||||
'gpt-4-32k-0314': 32758, // -10 from max
|
'gpt-4-32k-0314': 32758, // -10 from max
|
||||||
'gpt-4-32k-0613': 32758, // -10 from max
|
'gpt-4-32k-0613': 32758, // -10 from max
|
||||||
'gpt-4-1106': 127990, // -10 from max
|
'gpt-4-1106': 127500, // -500 from max
|
||||||
'gpt-4-0125': 127990, // -10 from max
|
'gpt-4-0125': 127500, // -500 from max
|
||||||
'gpt-4o': 127990, // -10 from max
|
'gpt-4o': 127500, // -500 from max
|
||||||
'gpt-4o-mini': 127990, // -10 from max
|
'gpt-4o-mini': 127500, // -500 from max
|
||||||
'gpt-4o-2024-08-06': 127990, // -10 from max
|
'gpt-4o-2024-08-06': 127500, // -500 from max
|
||||||
'gpt-4-turbo': 127990, // -10 from max
|
'gpt-4-turbo': 127500, // -500 from max
|
||||||
'gpt-4-vision': 127990, // -10 from max
|
'gpt-4-vision': 127500, // -500 from max
|
||||||
'gpt-3.5-turbo': 16375, // -10 from max
|
'gpt-3.5-turbo': 16375, // -10 from max
|
||||||
'gpt-3.5-turbo-0613': 4092, // -5 from max
|
'gpt-3.5-turbo-0613': 4092, // -5 from max
|
||||||
'gpt-3.5-turbo-0301': 4092, // -5 from max
|
'gpt-3.5-turbo-0301': 4092, // -5 from max
|
||||||
|
@ -21,9 +21,15 @@ const openAIModels = {
|
||||||
'gpt-3.5-turbo-16k-0613': 16375, // -10 from max
|
'gpt-3.5-turbo-16k-0613': 16375, // -10 from max
|
||||||
'gpt-3.5-turbo-1106': 16375, // -10 from max
|
'gpt-3.5-turbo-1106': 16375, // -10 from max
|
||||||
'gpt-3.5-turbo-0125': 16375, // -10 from max
|
'gpt-3.5-turbo-0125': 16375, // -10 from max
|
||||||
|
};
|
||||||
|
|
||||||
|
const mistralModels = {
|
||||||
'mistral-': 31990, // -10 from max
|
'mistral-': 31990, // -10 from max
|
||||||
llama3: 8187, // -5 from max
|
'mistral-7b': 31990, // -10 from max
|
||||||
'llama-3': 8187, // -5 from max
|
'mistral-small': 31990, // -10 from max
|
||||||
|
'mixtral-8x7b': 31990, // -10 from max
|
||||||
|
'mistral-large-2402': 127500,
|
||||||
|
'mistral-large-2407': 127500,
|
||||||
};
|
};
|
||||||
|
|
||||||
const cohereModels = {
|
const cohereModels = {
|
||||||
|
@ -54,6 +60,7 @@ const googleModels = {
|
||||||
|
|
||||||
const anthropicModels = {
|
const anthropicModels = {
|
||||||
'claude-': 100000,
|
'claude-': 100000,
|
||||||
|
'claude-instant': 100000,
|
||||||
'claude-2': 100000,
|
'claude-2': 100000,
|
||||||
'claude-2.1': 200000,
|
'claude-2.1': 200000,
|
||||||
'claude-3-haiku': 200000,
|
'claude-3-haiku': 200000,
|
||||||
|
@ -63,7 +70,38 @@ const anthropicModels = {
|
||||||
'claude-3.5-sonnet': 200000,
|
'claude-3.5-sonnet': 200000,
|
||||||
};
|
};
|
||||||
|
|
||||||
const aggregateModels = { ...openAIModels, ...googleModels, ...anthropicModels, ...cohereModels };
|
const metaModels = {
|
||||||
|
'llama2-13b': 4000,
|
||||||
|
'llama2-70b': 4000,
|
||||||
|
'llama3-8b': 8000,
|
||||||
|
'llama3-70b': 8000,
|
||||||
|
'llama3-1-8b': 127500,
|
||||||
|
'llama3-1-70b': 127500,
|
||||||
|
'llama3-1-405b': 127500,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ai21Models = {
|
||||||
|
'ai21.j2-mid-v1': 8182, // -10 from max
|
||||||
|
'ai21.j2-ultra-v1': 8182, // -10 from max
|
||||||
|
'ai21.jamba-instruct-v1:0': 255500, // -500 from max
|
||||||
|
};
|
||||||
|
|
||||||
|
const amazonModels = {
|
||||||
|
'amazon.titan-text-lite-v1': 4000,
|
||||||
|
'amazon.titan-text-express-v1': 8000,
|
||||||
|
'amazon.titan-text-premier-v1:0': 31500, // -500 from max
|
||||||
|
};
|
||||||
|
|
||||||
|
const bedrockModels = {
|
||||||
|
...anthropicModels,
|
||||||
|
...mistralModels,
|
||||||
|
...cohereModels,
|
||||||
|
...metaModels,
|
||||||
|
...ai21Models,
|
||||||
|
...amazonModels,
|
||||||
|
};
|
||||||
|
|
||||||
|
const aggregateModels = { ...openAIModels, ...googleModels, ...bedrockModels };
|
||||||
|
|
||||||
const maxTokensMap = {
|
const maxTokensMap = {
|
||||||
[EModelEndpoint.azureOpenAI]: openAIModels,
|
[EModelEndpoint.azureOpenAI]: openAIModels,
|
||||||
|
@ -72,6 +110,7 @@ const maxTokensMap = {
|
||||||
[EModelEndpoint.custom]: aggregateModels,
|
[EModelEndpoint.custom]: aggregateModels,
|
||||||
[EModelEndpoint.google]: googleModels,
|
[EModelEndpoint.google]: googleModels,
|
||||||
[EModelEndpoint.anthropic]: anthropicModels,
|
[EModelEndpoint.anthropic]: anthropicModels,
|
||||||
|
[EModelEndpoint.bedrock]: bedrockModels,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -20,18 +20,6 @@ describe('getModelMaxTokens', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return correct tokens for LLama 3 models', () => {
|
|
||||||
expect(getModelMaxTokens('meta-llama/llama-3-8b')).toBe(
|
|
||||||
maxTokensMap[EModelEndpoint.openAI]['llama-3'],
|
|
||||||
);
|
|
||||||
expect(getModelMaxTokens('meta-llama/llama-3-8b')).toBe(
|
|
||||||
maxTokensMap[EModelEndpoint.openAI]['llama3'],
|
|
||||||
);
|
|
||||||
expect(getModelMaxTokens('llama-3-500b')).toBe(maxTokensMap[EModelEndpoint.openAI]['llama-3']);
|
|
||||||
expect(getModelMaxTokens('llama3-70b')).toBe(maxTokensMap[EModelEndpoint.openAI]['llama3']);
|
|
||||||
expect(getModelMaxTokens('llama3:latest')).toBe(maxTokensMap[EModelEndpoint.openAI]['llama3']);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should return undefined for no match', () => {
|
test('should return undefined for no match', () => {
|
||||||
expect(getModelMaxTokens('unknown-model')).toBeUndefined();
|
expect(getModelMaxTokens('unknown-model')).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Capabilities } from 'librechat-data-provider';
|
import { Capabilities } from 'librechat-data-provider';
|
||||||
import type { Agent, AgentProvider, AgentModelParameters } from 'librechat-data-provider';
|
import type { Agent, AgentProvider, AgentModelParameters } from 'librechat-data-provider';
|
||||||
import type { Option, ExtendedFile } from './types';
|
import type { OptionWithIcon, ExtendedFile } from './types';
|
||||||
|
|
||||||
export type TAgentOption = Option &
|
export type TAgentOption = OptionWithIcon &
|
||||||
Agent & {
|
Agent & {
|
||||||
files?: Array<[string, ExtendedFile]>;
|
files?: Array<[string, ExtendedFile]>;
|
||||||
code_files?: Array<[string, ExtendedFile]>;
|
code_files?: Array<[string, ExtendedFile]>;
|
||||||
|
@ -23,5 +23,5 @@ export type AgentForm = {
|
||||||
model: string | null;
|
model: string | null;
|
||||||
model_parameters: AgentModelParameters;
|
model_parameters: AgentModelParameters;
|
||||||
tools?: string[];
|
tools?: string[];
|
||||||
provider?: AgentProvider | Option;
|
provider?: AgentProvider | OptionWithIcon;
|
||||||
} & AgentCapabilities;
|
} & AgentCapabilities;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { useRecoilState } from 'recoil';
|
||||||
import { Settings2 } from 'lucide-react';
|
import { Settings2 } from 'lucide-react';
|
||||||
import { Root, Anchor } from '@radix-ui/react-popover';
|
import { Root, Anchor } from '@radix-ui/react-popover';
|
||||||
import { useState, useEffect, useMemo } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
import { tPresetUpdateSchema, EModelEndpoint } from 'librechat-data-provider';
|
import { tPresetUpdateSchema, EModelEndpoint, paramEndpoints } from 'librechat-data-provider';
|
||||||
import type { TPreset, TInterfaceConfig } from 'librechat-data-provider';
|
import type { TPreset, TInterfaceConfig } from 'librechat-data-provider';
|
||||||
import { EndpointSettings, SaveAsPresetDialog, AlternativeSettings } from '~/components/Endpoints';
|
import { EndpointSettings, SaveAsPresetDialog, AlternativeSettings } from '~/components/Endpoints';
|
||||||
import { ModelSelect } from '~/components/Input/ModelSelect';
|
import { ModelSelect } from '~/components/Input/ModelSelect';
|
||||||
|
@ -12,7 +12,6 @@ import PopoverButtons from './PopoverButtons';
|
||||||
import { useSetIndexOptions } from '~/hooks';
|
import { useSetIndexOptions } from '~/hooks';
|
||||||
import { useChatContext } from '~/Providers';
|
import { useChatContext } from '~/Providers';
|
||||||
import { Button } from '~/components/ui';
|
import { Button } from '~/components/ui';
|
||||||
import { cn, cardStyle } from '~/utils/';
|
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
export default function HeaderOptions({
|
export default function HeaderOptions({
|
||||||
|
@ -29,10 +28,10 @@ export default function HeaderOptions({
|
||||||
useChatContext();
|
useChatContext();
|
||||||
const { setOption } = useSetIndexOptions();
|
const { setOption } = useSetIndexOptions();
|
||||||
|
|
||||||
const { endpoint, conversationId, jailbreak } = conversation ?? {};
|
const { endpoint, conversationId, jailbreak = false } = conversation ?? {};
|
||||||
|
|
||||||
const altConditions: { [key: string]: boolean } = {
|
const altConditions: { [key: string]: boolean } = {
|
||||||
bingAI: !!(latestMessage && conversation?.jailbreak && endpoint === 'bingAI'),
|
bingAI: !!(latestMessage && jailbreak && endpoint === 'bingAI'),
|
||||||
};
|
};
|
||||||
|
|
||||||
const altSettings: { [key: string]: () => void } = {
|
const altSettings: { [key: string]: () => void } = {
|
||||||
|
@ -74,7 +73,7 @@ export default function HeaderOptions({
|
||||||
<div className="my-auto lg:max-w-2xl xl:max-w-3xl">
|
<div className="my-auto lg:max-w-2xl xl:max-w-3xl">
|
||||||
<span className="flex w-full flex-col items-center justify-center gap-0 md:order-none md:m-auto md:gap-2">
|
<span className="flex w-full flex-col items-center justify-center gap-0 md:order-none md:m-auto md:gap-2">
|
||||||
<div className="z-[61] flex w-full items-center justify-center gap-2">
|
<div className="z-[61] flex w-full items-center justify-center gap-2">
|
||||||
{interfaceConfig?.modelSelect && (
|
{interfaceConfig?.modelSelect === true && (
|
||||||
<ModelSelect
|
<ModelSelect
|
||||||
conversation={conversation}
|
conversation={conversation}
|
||||||
setOption={setOption}
|
setOption={setOption}
|
||||||
|
@ -82,7 +81,9 @@ export default function HeaderOptions({
|
||||||
popover={true}
|
popover={true}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!noSettings[endpoint] && interfaceConfig?.parameters && (
|
{!noSettings[endpoint] &&
|
||||||
|
interfaceConfig?.parameters === true &&
|
||||||
|
!paramEndpoints.has(endpoint) && (
|
||||||
<Button
|
<Button
|
||||||
aria-label="Settings/parameters"
|
aria-label="Settings/parameters"
|
||||||
id="parameters-button"
|
id="parameters-button"
|
||||||
|
@ -96,11 +97,11 @@ export default function HeaderOptions({
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{interfaceConfig?.parameters && (
|
{interfaceConfig?.parameters === true && !paramEndpoints.has(endpoint) && (
|
||||||
<OptionsPopover
|
<OptionsPopover
|
||||||
visible={showPopover}
|
visible={showPopover}
|
||||||
saveAsPreset={saveAsPreset}
|
saveAsPreset={saveAsPreset}
|
||||||
presetsDisabled={!interfaceConfig.presets}
|
presetsDisabled={!(interfaceConfig.presets ?? false)}
|
||||||
PopoverButtons={<PopoverButtons />}
|
PopoverButtons={<PopoverButtons />}
|
||||||
closePopover={() => setShowPopover(false)}
|
closePopover={() => setShowPopover(false)}
|
||||||
>
|
>
|
||||||
|
@ -114,7 +115,7 @@ export default function HeaderOptions({
|
||||||
</div>
|
</div>
|
||||||
</OptionsPopover>
|
</OptionsPopover>
|
||||||
)}
|
)}
|
||||||
{interfaceConfig?.presets && (
|
{interfaceConfig?.presets === true && (
|
||||||
<SaveAsPresetDialog
|
<SaveAsPresetDialog
|
||||||
open={saveAsDialogShow}
|
open={saveAsDialogShow}
|
||||||
onOpenChange={setSaveAsDialogShow}
|
onOpenChange={setSaveAsDialogShow}
|
||||||
|
@ -125,7 +126,7 @@ export default function HeaderOptions({
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{interfaceConfig?.parameters && (
|
{interfaceConfig?.parameters === true && (
|
||||||
<PluginStoreDialog
|
<PluginStoreDialog
|
||||||
isOpen={showPluginStoreDialog}
|
isOpen={showPluginStoreDialog}
|
||||||
setIsOpen={setShowPluginStoreDialog}
|
setIsOpen={setShowPluginStoreDialog}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
CustomMinimalIcon,
|
CustomMinimalIcon,
|
||||||
AssistantIcon,
|
AssistantIcon,
|
||||||
LightningIcon,
|
LightningIcon,
|
||||||
|
BedrockIcon,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
} from '~/components/svg';
|
} from '~/components/svg';
|
||||||
import UnknownIcon from './UnknownIcon';
|
import UnknownIcon from './UnknownIcon';
|
||||||
|
@ -52,6 +53,10 @@ const AgentAvatar = ({ className = '', agentName, avatar, size }: AgentIconMapPr
|
||||||
return <BrainCircuit className={cn(agentName === '' ? 'icon-2xl' : '', className)} />;
|
return <BrainCircuit className={cn(agentName === '' ? 'icon-2xl' : '', className)} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Bedrock = ({ className = '' }: IconMapProps) => {
|
||||||
|
return <BedrockIcon className={cn(className, 'h-full w-full')} />;
|
||||||
|
};
|
||||||
|
|
||||||
export const icons = {
|
export const icons = {
|
||||||
[EModelEndpoint.azureOpenAI]: AzureMinimalIcon,
|
[EModelEndpoint.azureOpenAI]: AzureMinimalIcon,
|
||||||
[EModelEndpoint.openAI]: GPTIcon,
|
[EModelEndpoint.openAI]: GPTIcon,
|
||||||
|
@ -64,5 +69,6 @@ export const icons = {
|
||||||
[EModelEndpoint.assistants]: AssistantAvatar,
|
[EModelEndpoint.assistants]: AssistantAvatar,
|
||||||
[EModelEndpoint.azureAssistants]: AssistantAvatar,
|
[EModelEndpoint.azureAssistants]: AssistantAvatar,
|
||||||
[EModelEndpoint.agents]: AgentAvatar,
|
[EModelEndpoint.agents]: AgentAvatar,
|
||||||
|
[EModelEndpoint.bedrock]: Bedrock,
|
||||||
unknown: UnknownIcon,
|
unknown: UnknownIcon,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { TMessage } from 'librechat-data-provider';
|
import { TMessage } from 'librechat-data-provider';
|
||||||
import Files from './Files';
|
import Files from './Files';
|
||||||
|
|
||||||
const Container = ({ children, message }: { children: React.ReactNode; message: TMessage }) => (
|
const Container = ({ children, message }: { children: React.ReactNode; message?: TMessage }) => (
|
||||||
<div
|
<div
|
||||||
className="text-message flex min-h-[20px] flex-col items-start gap-3 overflow-x-auto [.text-message+&]:mt-5"
|
className="text-message flex min-h-[20px] flex-col items-start gap-3 overflow-x-auto [.text-message+&]:mt-5"
|
||||||
dir="auto"
|
dir="auto"
|
||||||
>
|
>
|
||||||
{message.isCreatedByUser && <Files message={message} />}
|
{message?.isCreatedByUser === true && <Files message={message} />}
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,51 +1,34 @@
|
||||||
import { Suspense } from 'react';
|
import { memo } from 'react';
|
||||||
import type { TMessageContentParts } from 'librechat-data-provider';
|
import type { TMessageContentParts } from 'librechat-data-provider';
|
||||||
import { UnfinishedMessage } from './MessageContent';
|
|
||||||
import { DelayedRender } from '~/components/ui';
|
|
||||||
import Part from './Part';
|
import Part from './Part';
|
||||||
|
|
||||||
const ContentParts = ({
|
type ContentPartsProps = {
|
||||||
error,
|
content: Array<TMessageContentParts | undefined>;
|
||||||
unfinished,
|
messageId: string;
|
||||||
isSubmitting,
|
isCreatedByUser: boolean;
|
||||||
isLast,
|
isLast: boolean;
|
||||||
content,
|
isSubmitting: boolean;
|
||||||
...props
|
};
|
||||||
}: // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
any) => {
|
|
||||||
if (error) {
|
|
||||||
// return <ErrorMessage text={text} />;
|
|
||||||
} else {
|
|
||||||
const { message } = props;
|
|
||||||
const { messageId } = message;
|
|
||||||
|
|
||||||
|
const ContentParts = memo(
|
||||||
|
({ content, messageId, isCreatedByUser, isLast, isSubmitting }: ContentPartsProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{content
|
{content
|
||||||
.filter((part: TMessageContentParts | undefined) => part)
|
.filter((part) => part)
|
||||||
.map((part: TMessageContentParts | undefined, idx: number) => {
|
.map((part, idx) => (
|
||||||
const showCursor = idx === content.length - 1 && isLast;
|
<Part
|
||||||
return (
|
key={`display-${messageId}-${idx}`}
|
||||||
<Part
|
part={part}
|
||||||
key={`display-${messageId}-${idx}`}
|
isSubmitting={isSubmitting}
|
||||||
showCursor={showCursor === true && isSubmitting}
|
showCursor={idx === content.length - 1 && isLast}
|
||||||
isSubmitting={isSubmitting}
|
messageId={messageId}
|
||||||
part={part}
|
isCreatedByUser={isCreatedByUser}
|
||||||
{...props}
|
/>
|
||||||
/>
|
))}
|
||||||
);
|
|
||||||
})}
|
|
||||||
{/* Temporarily remove this */}
|
|
||||||
{/* {!isSubmitting && unfinished && (
|
|
||||||
<Suspense>
|
|
||||||
<DelayedRender delay={250}>
|
|
||||||
<UnfinishedMessage message={message} key={`unfinished-${messageId}`} />
|
|
||||||
</DelayedRender>
|
|
||||||
</Suspense>
|
|
||||||
)} */}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
};
|
);
|
||||||
|
|
||||||
export default ContentParts;
|
export default ContentParts;
|
||||||
|
|
|
@ -3,7 +3,7 @@ import type { TFile, TMessage } from 'librechat-data-provider';
|
||||||
import FileContainer from '~/components/Chat/Input/Files/FileContainer';
|
import FileContainer from '~/components/Chat/Input/Files/FileContainer';
|
||||||
import Image from './Image';
|
import Image from './Image';
|
||||||
|
|
||||||
const Files = ({ message }: { message: TMessage }) => {
|
const Files = ({ message }: { message?: TMessage }) => {
|
||||||
const imageFiles = useMemo(() => {
|
const imageFiles = useMemo(() => {
|
||||||
return message?.files?.filter((file) => file.type?.startsWith('image/')) || [];
|
return message?.files?.filter((file) => file.type?.startsWith('image/')) || [];
|
||||||
}, [message?.files]);
|
}, [message?.files]);
|
||||||
|
@ -20,7 +20,7 @@ const Files = ({ message }: { message: TMessage }) => {
|
||||||
imageFiles.map((file) => (
|
imageFiles.map((file) => (
|
||||||
<Image
|
<Image
|
||||||
key={file.file_id}
|
key={file.file_id}
|
||||||
imagePath={file?.preview ?? file.filepath ?? ''}
|
imagePath={file.preview ?? file.filepath ?? ''}
|
||||||
height={file.height ?? 1920}
|
height={file.height ?? 1920}
|
||||||
width={file.width ?? 1080}
|
width={file.width ?? 1080}
|
||||||
altText={file.filename ?? 'Uploaded Image'}
|
altText={file.filename ?? 'Uploaded Image'}
|
||||||
|
|
|
@ -15,7 +15,9 @@ export const ErrorMessage = ({
|
||||||
text,
|
text,
|
||||||
message,
|
message,
|
||||||
className = '',
|
className = '',
|
||||||
}: Pick<TDisplayProps, 'text' | 'className' | 'message'>) => {
|
}: Pick<TDisplayProps, 'text' | 'className'> & {
|
||||||
|
message?: TMessage;
|
||||||
|
}) => {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
if (text === 'Error connecting to server, try refreshing the page.') {
|
if (text === 'Error connecting to server, try refreshing the page.') {
|
||||||
console.log('error message', message);
|
console.log('error message', message);
|
||||||
|
|
|
@ -4,81 +4,47 @@ import {
|
||||||
imageGenTools,
|
imageGenTools,
|
||||||
isImageVisionTool,
|
isImageVisionTool,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import { useMemo } from 'react';
|
import { memo } from 'react';
|
||||||
import type { TMessageContentParts, TMessage } from 'librechat-data-provider';
|
import type { TMessageContentParts } from 'librechat-data-provider';
|
||||||
import type { TDisplayProps } from '~/common';
|
|
||||||
import { ErrorMessage } from './MessageContent';
|
import { ErrorMessage } from './MessageContent';
|
||||||
import { useChatContext } from '~/Providers';
|
|
||||||
import RetrievalCall from './RetrievalCall';
|
import RetrievalCall from './RetrievalCall';
|
||||||
import CodeAnalyze from './CodeAnalyze';
|
import CodeAnalyze from './CodeAnalyze';
|
||||||
import Container from './Container';
|
import Container from './Container';
|
||||||
import ToolCall from './ToolCall';
|
import ToolCall from './ToolCall';
|
||||||
import Markdown from './Markdown';
|
|
||||||
import ImageGen from './ImageGen';
|
import ImageGen from './ImageGen';
|
||||||
import { cn } from '~/utils';
|
import Text from './Parts/Text';
|
||||||
import Image from './Image';
|
import Image from './Image';
|
||||||
|
|
||||||
// Display Message Component
|
type PartProps = {
|
||||||
const DisplayMessage = ({ text, isCreatedByUser = false, message, showCursor }: TDisplayProps) => {
|
part?: TMessageContentParts;
|
||||||
const { isSubmitting, latestMessage } = useChatContext();
|
|
||||||
const showCursorState = useMemo(
|
|
||||||
() => showCursor === true && isSubmitting,
|
|
||||||
[showCursor, isSubmitting],
|
|
||||||
);
|
|
||||||
const isLatestMessage = useMemo(
|
|
||||||
() => message.messageId === latestMessage?.messageId,
|
|
||||||
[message.messageId, latestMessage?.messageId],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Note: for testing purposes
|
|
||||||
// isSubmitting && isLatestMessage && logger.log('message_stream', { text, isCreatedByUser, isSubmitting, showCursorState });
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
isSubmitting ? 'submitting' : '',
|
|
||||||
showCursorState && !!text.length ? 'result-streaming' : '',
|
|
||||||
'markdown prose message-content dark:prose-invert light w-full break-words',
|
|
||||||
isCreatedByUser ? 'whitespace-pre-wrap dark:text-gray-20' : 'dark:text-gray-70',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{!isCreatedByUser ? (
|
|
||||||
<Markdown content={text} showCursor={showCursorState} isLatestMessage={isLatestMessage} />
|
|
||||||
) : (
|
|
||||||
<>{text}</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Part({
|
|
||||||
part,
|
|
||||||
showCursor,
|
|
||||||
isSubmitting,
|
|
||||||
message,
|
|
||||||
}: {
|
|
||||||
part: TMessageContentParts | undefined;
|
|
||||||
isSubmitting: boolean;
|
isSubmitting: boolean;
|
||||||
showCursor: boolean;
|
showCursor: boolean;
|
||||||
message: TMessage;
|
messageId: string;
|
||||||
}) {
|
isCreatedByUser: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Part = memo(({ part, isSubmitting, showCursor, messageId, isCreatedByUser }: PartProps) => {
|
||||||
if (!part) {
|
if (!part) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (part.type === ContentTypes.ERROR) {
|
if (part.type === ContentTypes.ERROR) {
|
||||||
return <ErrorMessage message={message} text={part[ContentTypes.TEXT].value} className="my-2" />;
|
return <ErrorMessage text={part[ContentTypes.TEXT].value} className="my-2" />;
|
||||||
} else if (part.type === ContentTypes.TEXT) {
|
} else if (part.type === ContentTypes.TEXT) {
|
||||||
const text = typeof part.text === 'string' ? part.text : part.text.value;
|
const text = typeof part.text === 'string' ? part.text : part.text.value;
|
||||||
|
|
||||||
if (typeof text !== 'string') {
|
if (typeof text !== 'string') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (part.tool_call_ids != null && !text) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Container message={message}>
|
<Container>
|
||||||
<DisplayMessage
|
<Text
|
||||||
text={text}
|
text={text}
|
||||||
isCreatedByUser={message.isCreatedByUser}
|
isCreatedByUser={isCreatedByUser}
|
||||||
message={message}
|
messageId={messageId}
|
||||||
showCursor={showCursor}
|
showCursor={showCursor}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -93,7 +59,7 @@ export default function Part({
|
||||||
if ('args' in toolCall && (!toolCall.type || toolCall.type === ToolCallTypes.TOOL_CALL)) {
|
if ('args' in toolCall && (!toolCall.type || toolCall.type === ToolCallTypes.TOOL_CALL)) {
|
||||||
return (
|
return (
|
||||||
<ToolCall
|
<ToolCall
|
||||||
args={toolCall.args}
|
args={toolCall.args ?? ''}
|
||||||
name={toolCall.name ?? ''}
|
name={toolCall.name ?? ''}
|
||||||
output={toolCall.output ?? ''}
|
output={toolCall.output ?? ''}
|
||||||
initialProgress={toolCall.progress ?? 0.1}
|
initialProgress={toolCall.progress ?? 0.1}
|
||||||
|
@ -132,11 +98,11 @@ export default function Part({
|
||||||
if (isImageVisionTool(toolCall)) {
|
if (isImageVisionTool(toolCall)) {
|
||||||
if (isSubmitting && showCursor) {
|
if (isSubmitting && showCursor) {
|
||||||
return (
|
return (
|
||||||
<Container message={message}>
|
<Container>
|
||||||
<DisplayMessage
|
<Text
|
||||||
text={''}
|
text={''}
|
||||||
isCreatedByUser={message.isCreatedByUser}
|
isCreatedByUser={isCreatedByUser}
|
||||||
message={message}
|
messageId={messageId}
|
||||||
showCursor={showCursor}
|
showCursor={showCursor}
|
||||||
/>
|
/>
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -174,4 +140,6 @@ export default function Part({
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
});
|
||||||
|
|
||||||
|
export default Part;
|
||||||
|
|
39
client/src/components/Chat/Messages/Content/Parts/Text.tsx
Normal file
39
client/src/components/Chat/Messages/Content/Parts/Text.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { memo, useMemo } from 'react';
|
||||||
|
import { useChatContext } from '~/Providers';
|
||||||
|
import Markdown from '~/components/Chat/Messages/Content/Markdown';
|
||||||
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
|
type TextPartProps = {
|
||||||
|
text: string;
|
||||||
|
isCreatedByUser: boolean;
|
||||||
|
messageId: string;
|
||||||
|
showCursor: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TextPart = memo(({ text, isCreatedByUser, messageId, showCursor }: TextPartProps) => {
|
||||||
|
const { isSubmitting, latestMessage } = useChatContext();
|
||||||
|
const showCursorState = useMemo(() => showCursor && isSubmitting, [showCursor, isSubmitting]);
|
||||||
|
const isLatestMessage = useMemo(
|
||||||
|
() => messageId === latestMessage?.messageId,
|
||||||
|
[messageId, latestMessage?.messageId],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
isSubmitting ? 'submitting' : '',
|
||||||
|
showCursorState && !!text.length ? 'result-streaming' : '',
|
||||||
|
'markdown prose message-content dark:prose-invert light w-full break-words',
|
||||||
|
isCreatedByUser ? 'whitespace-pre-wrap dark:text-gray-20' : 'dark:text-gray-70',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{!isCreatedByUser ? (
|
||||||
|
<Markdown content={text} showCursor={showCursorState} isLatestMessage={isLatestMessage} />
|
||||||
|
) : (
|
||||||
|
<>{text}</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default TextPart;
|
|
@ -1,4 +1,5 @@
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import type { TMessageContentParts } from 'librechat-data-provider';
|
||||||
import type { TMessageProps } from '~/common';
|
import type { TMessageProps } from '~/common';
|
||||||
import Icon from '~/components/Chat/Messages/MessageIcon';
|
import Icon from '~/components/Chat/Messages/MessageIcon';
|
||||||
import { useMessageHelpers, useLocalize } from '~/hooks';
|
import { useMessageHelpers, useLocalize } from '~/hooks';
|
||||||
|
@ -17,7 +18,6 @@ export default function Message(props: TMessageProps) {
|
||||||
props;
|
props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ask,
|
|
||||||
edit,
|
edit,
|
||||||
index,
|
index,
|
||||||
agent,
|
agent,
|
||||||
|
@ -33,7 +33,7 @@ export default function Message(props: TMessageProps) {
|
||||||
regenerateMessage,
|
regenerateMessage,
|
||||||
} = useMessageHelpers(props);
|
} = useMessageHelpers(props);
|
||||||
const fontSize = useRecoilValue(store.fontSize);
|
const fontSize = useRecoilValue(store.fontSize);
|
||||||
const { content, children, messageId = null, isCreatedByUser, error, unfinished } = message ?? {};
|
const { children, messageId = null, isCreatedByUser } = message ?? {};
|
||||||
|
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -82,24 +82,11 @@ export default function Message(props: TMessageProps) {
|
||||||
<div className="flex-col gap-1 md:gap-3">
|
<div className="flex-col gap-1 md:gap-3">
|
||||||
<div className="flex max-w-full flex-grow flex-col gap-0">
|
<div className="flex max-w-full flex-grow flex-col gap-0">
|
||||||
<ContentParts
|
<ContentParts
|
||||||
ask={ask}
|
content={message.content as Array<TMessageContentParts | undefined>}
|
||||||
edit={edit}
|
messageId={message.messageId}
|
||||||
|
isCreatedByUser={message.isCreatedByUser}
|
||||||
isLast={isLast}
|
isLast={isLast}
|
||||||
content={content ?? []}
|
|
||||||
message={message}
|
|
||||||
messageId={messageId}
|
|
||||||
enterEdit={enterEdit}
|
|
||||||
error={!!(error ?? false)}
|
|
||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
unfinished={unfinished ?? false}
|
|
||||||
isCreatedByUser={isCreatedByUser ?? true}
|
|
||||||
siblingIdx={siblingIdx ?? 0}
|
|
||||||
setSiblingIdx={
|
|
||||||
setSiblingIdx ??
|
|
||||||
(() => {
|
|
||||||
return;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
import { useEffect, useCallback } from 'react';
|
import { useEffect, useCallback } from 'react';
|
||||||
|
import { isAssistantsEndpoint } from 'librechat-data-provider';
|
||||||
|
import type { TMessage } from 'librechat-data-provider';
|
||||||
import type { TMessageProps } from '~/common';
|
import type { TMessageProps } from '~/common';
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
import Message from './Message';
|
import MessageContent from '~/components/Messages/MessageContent';
|
||||||
// eslint-disable-next-line import/no-cycle
|
// eslint-disable-next-line import/no-cycle
|
||||||
import MessageParts from './MessageParts';
|
import MessageParts from './MessageParts';
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
|
import Message from './Message';
|
||||||
import store from '~/store';
|
import store from '~/store';
|
||||||
|
|
||||||
export default function MultiMessage({
|
export default function MultiMessage({
|
||||||
|
@ -30,22 +34,22 @@ export default function MultiMessage({
|
||||||
}, [messagesTree?.length]);
|
}, [messagesTree?.length]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (messagesTree?.length && siblingIdx >= messagesTree?.length) {
|
if (messagesTree?.length && siblingIdx >= messagesTree.length) {
|
||||||
setSiblingIdx(0);
|
setSiblingIdx(0);
|
||||||
}
|
}
|
||||||
}, [siblingIdx, messagesTree?.length, setSiblingIdx]);
|
}, [siblingIdx, messagesTree?.length, setSiblingIdx]);
|
||||||
|
|
||||||
if (!(messagesTree && messagesTree?.length)) {
|
if (!(messagesTree && messagesTree.length)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = messagesTree[messagesTree.length - siblingIdx - 1];
|
const message = messagesTree[messagesTree.length - siblingIdx - 1] as TMessage | undefined;
|
||||||
|
|
||||||
if (!message) {
|
if (!message) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.content) {
|
if (isAssistantsEndpoint(message.endpoint) && message.content) {
|
||||||
return (
|
return (
|
||||||
<MessageParts
|
<MessageParts
|
||||||
key={message.messageId}
|
key={message.messageId}
|
||||||
|
@ -57,6 +61,18 @@ export default function MultiMessage({
|
||||||
setSiblingIdx={setSiblingIdxRev}
|
setSiblingIdx={setSiblingIdxRev}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
} else if (message.content) {
|
||||||
|
return (
|
||||||
|
<MessageContent
|
||||||
|
key={message.messageId}
|
||||||
|
message={message}
|
||||||
|
currentEditId={currentEditId}
|
||||||
|
setCurrentEditId={setCurrentEditId}
|
||||||
|
siblingIdx={messagesTree.length - siblingIdx - 1}
|
||||||
|
siblingCount={messagesTree.length}
|
||||||
|
setSiblingIdx={setSiblingIdxRev}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { EModelEndpoint, isAssistantsEndpoint } from 'librechat-data-provider';
|
import { EModelEndpoint, isAssistantsEndpoint, alternateName } from 'librechat-data-provider';
|
||||||
import UnknownIcon from '~/components/Chat/Menus/Endpoints/UnknownIcon';
|
import UnknownIcon from '~/components/Chat/Menus/Endpoints/UnknownIcon';
|
||||||
import { BrainCircuit } from 'lucide-react';
|
import { BrainCircuit } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
|
@ -7,6 +7,7 @@ import {
|
||||||
PaLMIcon,
|
PaLMIcon,
|
||||||
CodeyIcon,
|
CodeyIcon,
|
||||||
GeminiIcon,
|
GeminiIcon,
|
||||||
|
BedrockIcon,
|
||||||
AssistantIcon,
|
AssistantIcon,
|
||||||
AnthropicIcon,
|
AnthropicIcon,
|
||||||
AzureMinimalIcon,
|
AzureMinimalIcon,
|
||||||
|
@ -16,11 +17,31 @@ import {
|
||||||
import { IconProps } from '~/common';
|
import { IconProps } from '~/common';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
|
function getGoogleIcon(model: string | null | undefined, size: number) {
|
||||||
|
if (model?.toLowerCase().includes('code') === true) {
|
||||||
|
return <CodeyIcon size={size * 0.75} />;
|
||||||
|
} else if (model?.toLowerCase().includes('gemini') === true) {
|
||||||
|
return <GeminiIcon size={size * 0.7} />;
|
||||||
|
} else {
|
||||||
|
return <PaLMIcon size={size * 0.7} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getGoogleModelName(model: string | null | undefined) {
|
||||||
|
if (model?.toLowerCase().includes('code') === true) {
|
||||||
|
return 'Codey';
|
||||||
|
} else if (model?.toLowerCase().includes('gemini') === true) {
|
||||||
|
return 'Gemini';
|
||||||
|
} else {
|
||||||
|
return 'PaLM2';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
||||||
const {
|
const {
|
||||||
error,
|
error,
|
||||||
button,
|
button,
|
||||||
iconURL,
|
iconURL = '',
|
||||||
endpoint,
|
endpoint,
|
||||||
jailbreak,
|
jailbreak,
|
||||||
size = 30,
|
size = 30,
|
||||||
|
@ -30,7 +51,7 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const assistantsIcon = {
|
const assistantsIcon = {
|
||||||
icon: props.iconURL ? (
|
icon: iconURL ? (
|
||||||
<div className="relative flex h-6 w-6 items-center justify-center">
|
<div className="relative flex h-6 w-6 items-center justify-center">
|
||||||
<div
|
<div
|
||||||
title={assistantName}
|
title={assistantName}
|
||||||
|
@ -42,7 +63,7 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
className="shadow-stroke h-full w-full object-cover"
|
className="shadow-stroke h-full w-full object-cover"
|
||||||
src={props.iconURL}
|
src={iconURL}
|
||||||
alt={assistantName}
|
alt={assistantName}
|
||||||
style={{ height: '80', width: '80' }}
|
style={{ height: '80', width: '80' }}
|
||||||
/>
|
/>
|
||||||
|
@ -59,7 +80,7 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const agentsIcon = {
|
const agentsIcon = {
|
||||||
icon: props.iconURL ? (
|
icon: iconURL ? (
|
||||||
<div className="relative flex h-6 w-6 items-center justify-center">
|
<div className="relative flex h-6 w-6 items-center justify-center">
|
||||||
<div
|
<div
|
||||||
title={agentName}
|
title={agentName}
|
||||||
|
@ -71,7 +92,7 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
className="shadow-stroke h-full w-full object-cover"
|
className="shadow-stroke h-full w-full object-cover"
|
||||||
src={props.iconURL}
|
src={iconURL}
|
||||||
alt={agentName}
|
alt={agentName}
|
||||||
style={{ height: '80', width: '80' }}
|
style={{ height: '80', width: '80' }}
|
||||||
/>
|
/>
|
||||||
|
@ -104,42 +125,38 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
||||||
},
|
},
|
||||||
[EModelEndpoint.gptPlugins]: {
|
[EModelEndpoint.gptPlugins]: {
|
||||||
icon: <Plugin size={size * 0.7} />,
|
icon: <Plugin size={size * 0.7} />,
|
||||||
bg: `rgba(69, 89, 164, ${button ? 0.75 : 1})`,
|
bg: `rgba(69, 89, 164, ${button === true ? 0.75 : 1})`,
|
||||||
name: 'Plugins',
|
name: 'Plugins',
|
||||||
},
|
},
|
||||||
[EModelEndpoint.google]: {
|
[EModelEndpoint.google]: {
|
||||||
icon: model?.toLowerCase()?.includes('code') ? (
|
icon: getGoogleIcon(model, size),
|
||||||
<CodeyIcon size={size * 0.75} />
|
name: getGoogleModelName(model),
|
||||||
) : model?.toLowerCase()?.includes('gemini') ? (
|
|
||||||
<GeminiIcon size={size * 0.7} />
|
|
||||||
) : (
|
|
||||||
<PaLMIcon size={size * 0.7} />
|
|
||||||
),
|
|
||||||
name: model?.toLowerCase()?.includes('code')
|
|
||||||
? 'Codey'
|
|
||||||
: model?.toLowerCase()?.includes('gemini')
|
|
||||||
? 'Gemini'
|
|
||||||
: 'PaLM2',
|
|
||||||
},
|
},
|
||||||
[EModelEndpoint.anthropic]: {
|
[EModelEndpoint.anthropic]: {
|
||||||
icon: <AnthropicIcon size={size * 0.5555555555555556} />,
|
icon: <AnthropicIcon size={size * 0.5555555555555556} />,
|
||||||
bg: '#d09a74',
|
bg: '#d09a74',
|
||||||
name: 'Claude',
|
name: 'Claude',
|
||||||
},
|
},
|
||||||
|
[EModelEndpoint.bedrock]: {
|
||||||
|
icon: <BedrockIcon className="icon-xl text-white" />,
|
||||||
|
bg: '#268672',
|
||||||
|
name: alternateName[EModelEndpoint.bedrock],
|
||||||
|
},
|
||||||
[EModelEndpoint.bingAI]: {
|
[EModelEndpoint.bingAI]: {
|
||||||
icon: jailbreak ? (
|
icon:
|
||||||
<img src="/assets/bingai-jb.png" alt="Bing Icon" />
|
jailbreak === true ? (
|
||||||
) : (
|
<img src="/assets/bingai-jb.png" alt="Bing Icon" />
|
||||||
<img src="/assets/bingai.png" alt="Sydney Icon" />
|
) : (
|
||||||
),
|
<img src="/assets/bingai.png" alt="Sydney Icon" />
|
||||||
name: jailbreak ? 'Sydney' : 'BingAI',
|
),
|
||||||
|
name: jailbreak === true ? 'Sydney' : 'BingAI',
|
||||||
},
|
},
|
||||||
[EModelEndpoint.chatGPTBrowser]: {
|
[EModelEndpoint.chatGPTBrowser]: {
|
||||||
icon: <GPTIcon size={size * 0.5555555555555556} />,
|
icon: <GPTIcon size={size * 0.5555555555555556} />,
|
||||||
bg:
|
bg:
|
||||||
typeof model === 'string' && model.toLowerCase().includes('gpt-4')
|
typeof model === 'string' && model.toLowerCase().includes('gpt-4')
|
||||||
? '#AB68FF'
|
? '#AB68FF'
|
||||||
: `rgba(0, 163, 255, ${button ? 0.75 : 1})`,
|
: `rgba(0, 163, 255, ${button === true ? 0.75 : 1})`,
|
||||||
name: 'ChatGPT',
|
name: 'ChatGPT',
|
||||||
},
|
},
|
||||||
[EModelEndpoint.custom]: {
|
[EModelEndpoint.custom]: {
|
||||||
|
@ -152,7 +169,7 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
||||||
<div className="h-6 w-6">
|
<div className="h-6 w-6">
|
||||||
<div className="overflow-hidden rounded-full">
|
<div className="overflow-hidden rounded-full">
|
||||||
<UnknownIcon
|
<UnknownIcon
|
||||||
iconURL={props.iconURL}
|
iconURL={iconURL}
|
||||||
endpoint={endpoint ?? ''}
|
endpoint={endpoint ?? ''}
|
||||||
className="h-full w-full object-contain"
|
className="h-full w-full object-contain"
|
||||||
context="message"
|
context="message"
|
||||||
|
@ -185,11 +202,11 @@ const MessageEndpointIcon: React.FC<IconProps> = (props) => {
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative flex h-9 w-9 items-center justify-center rounded-sm p-1 text-white',
|
'relative flex h-9 w-9 items-center justify-center rounded-sm p-1 text-white',
|
||||||
props.className || '',
|
props.className ?? '',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
{error && (
|
{error === true && (
|
||||||
<span className="absolute right-0 top-[20px] -mr-2 flex h-3 w-3 items-center justify-center rounded-full border border-white bg-red-500 text-[10px] text-white">
|
<span className="absolute right-0 top-[20px] -mr-2 flex h-3 w-3 items-center justify-center rounded-full border border-white bg-red-500 text-[10px] text-white">
|
||||||
!
|
!
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { EModelEndpoint } from 'librechat-data-provider';
|
import { EModelEndpoint, alternateName } from 'librechat-data-provider';
|
||||||
import { BrainCircuit } from 'lucide-react';
|
import { BrainCircuit } from 'lucide-react';
|
||||||
import UnknownIcon from '~/components/Chat/Menus/Endpoints/UnknownIcon';
|
import UnknownIcon from '~/components/Chat/Menus/Endpoints/UnknownIcon';
|
||||||
import {
|
import {
|
||||||
|
@ -10,6 +10,7 @@ import {
|
||||||
GoogleMinimalIcon,
|
GoogleMinimalIcon,
|
||||||
CustomMinimalIcon,
|
CustomMinimalIcon,
|
||||||
AnthropicIcon,
|
AnthropicIcon,
|
||||||
|
BedrockIcon,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
} from '~/components/svg';
|
} from '~/components/svg';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
|
@ -27,17 +28,17 @@ const MinimalIcon: React.FC<IconProps> = (props) => {
|
||||||
const endpointIcons = {
|
const endpointIcons = {
|
||||||
[EModelEndpoint.azureOpenAI]: {
|
[EModelEndpoint.azureOpenAI]: {
|
||||||
icon: <AzureMinimalIcon className={iconClassName} />,
|
icon: <AzureMinimalIcon className={iconClassName} />,
|
||||||
name: props.chatGptLabel || 'ChatGPT',
|
name: props.chatGptLabel ?? 'ChatGPT',
|
||||||
},
|
},
|
||||||
[EModelEndpoint.openAI]: {
|
[EModelEndpoint.openAI]: {
|
||||||
icon: <OpenAIMinimalIcon className={iconClassName} />,
|
icon: <OpenAIMinimalIcon className={iconClassName} />,
|
||||||
name: props.chatGptLabel || 'ChatGPT',
|
name: props.chatGptLabel ?? 'ChatGPT',
|
||||||
},
|
},
|
||||||
[EModelEndpoint.gptPlugins]: { icon: <MinimalPlugin />, name: 'Plugins' },
|
[EModelEndpoint.gptPlugins]: { icon: <MinimalPlugin />, name: 'Plugins' },
|
||||||
[EModelEndpoint.google]: { icon: <GoogleMinimalIcon />, name: props.modelLabel || 'Google' },
|
[EModelEndpoint.google]: { icon: <GoogleMinimalIcon />, name: props.modelLabel ?? 'Google' },
|
||||||
[EModelEndpoint.anthropic]: {
|
[EModelEndpoint.anthropic]: {
|
||||||
icon: <AnthropicIcon className="icon-md shrink-0 dark:text-white" />,
|
icon: <AnthropicIcon className="icon-md shrink-0 dark:text-white" />,
|
||||||
name: props.modelLabel || 'Claude',
|
name: props.modelLabel ?? 'Claude',
|
||||||
},
|
},
|
||||||
[EModelEndpoint.custom]: {
|
[EModelEndpoint.custom]: {
|
||||||
icon: <CustomMinimalIcon />,
|
icon: <CustomMinimalIcon />,
|
||||||
|
@ -47,7 +48,14 @@ const MinimalIcon: React.FC<IconProps> = (props) => {
|
||||||
[EModelEndpoint.chatGPTBrowser]: { icon: <LightningIcon />, name: 'ChatGPT' },
|
[EModelEndpoint.chatGPTBrowser]: { icon: <LightningIcon />, name: 'ChatGPT' },
|
||||||
[EModelEndpoint.assistants]: { icon: <Sparkles className="icon-sm" />, name: 'Assistant' },
|
[EModelEndpoint.assistants]: { icon: <Sparkles className="icon-sm" />, name: 'Assistant' },
|
||||||
[EModelEndpoint.azureAssistants]: { icon: <Sparkles className="icon-sm" />, name: 'Assistant' },
|
[EModelEndpoint.azureAssistants]: { icon: <Sparkles className="icon-sm" />, name: 'Assistant' },
|
||||||
[EModelEndpoint.agents]: { icon: <BrainCircuit className="icon-sm" />, name: 'Agent' },
|
[EModelEndpoint.agents]: {
|
||||||
|
icon: <BrainCircuit className="icon-sm" />,
|
||||||
|
name: props.modelLabel ?? alternateName[EModelEndpoint.agents],
|
||||||
|
},
|
||||||
|
[EModelEndpoint.bedrock]: {
|
||||||
|
icon: <BedrockIcon className="icon-xl text-text-primary" />,
|
||||||
|
name: props.modelLabel ?? alternateName[EModelEndpoint.bedrock],
|
||||||
|
},
|
||||||
default: {
|
default: {
|
||||||
icon: (
|
icon: (
|
||||||
<UnknownIcon
|
<UnknownIcon
|
||||||
|
@ -76,11 +84,11 @@ const MinimalIcon: React.FC<IconProps> = (props) => {
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative flex items-center justify-center rounded-sm text-black dark:text-white',
|
'relative flex items-center justify-center rounded-sm text-black dark:text-white',
|
||||||
props.className || '',
|
props.className ?? '',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
{error && (
|
{error === true && (
|
||||||
<span className="absolute right-0 top-[20px] -mr-2 flex h-4 w-4 items-center justify-center rounded-full border border-white bg-red-500 text-[10px] text-black dark:text-white">
|
<span className="absolute right-0 top-[20px] -mr-2 flex h-4 w-4 items-center justify-center rounded-full border border-white bg-red-500 text-[10px] text-black dark:text-white">
|
||||||
!
|
!
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -2,14 +2,14 @@ import React, { useEffect, useState } from 'react';
|
||||||
import { useCreatePresetMutation } from 'librechat-data-provider/react-query';
|
import { useCreatePresetMutation } from 'librechat-data-provider/react-query';
|
||||||
import type { TEditPresetProps } from '~/common';
|
import type { TEditPresetProps } from '~/common';
|
||||||
import { cn, removeFocusOutlines, cleanupPreset, defaultTextProps } from '~/utils/';
|
import { cn, removeFocusOutlines, cleanupPreset, defaultTextProps } from '~/utils/';
|
||||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||||
import { Dialog, Input, Label } from '~/components/ui/';
|
import { OGDialog, Input, Label } from '~/components/ui/';
|
||||||
import { NotificationSeverity } from '~/common';
|
import { NotificationSeverity } from '~/common';
|
||||||
import { useToastContext } from '~/Providers';
|
import { useToastContext } from '~/Providers';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
const SaveAsPresetDialog = ({ open, onOpenChange, preset }: TEditPresetProps) => {
|
const SaveAsPresetDialog = ({ open, onOpenChange, preset }: TEditPresetProps) => {
|
||||||
const [title, setTitle] = useState<string>(preset.title || 'My Preset');
|
const [title, setTitle] = useState<string>(preset.title ?? 'My Preset');
|
||||||
const createPresetMutation = useCreatePresetMutation();
|
const createPresetMutation = useCreatePresetMutation();
|
||||||
const { showToast } = useToastContext();
|
const { showToast } = useToastContext();
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
@ -22,15 +22,15 @@ const SaveAsPresetDialog = ({ open, onOpenChange, preset }: TEditPresetProps) =>
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const toastTitle = _preset.title
|
const toastTitle =
|
||||||
? `\`${_preset.title}\``
|
_preset.title ?? '' ? `\`${_preset.title}\`` : localize('com_endpoint_preset_title');
|
||||||
: localize('com_endpoint_preset_title');
|
|
||||||
|
|
||||||
createPresetMutation.mutate(_preset, {
|
createPresetMutation.mutate(_preset, {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
showToast({
|
showToast({
|
||||||
message: `${toastTitle} ${localize('com_endpoint_preset_saved')}`,
|
message: `${toastTitle} ${localize('com_endpoint_preset_saved')}`,
|
||||||
});
|
});
|
||||||
|
onOpenChange(false); // Close the dialog on success
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
showToast({
|
showToast({
|
||||||
|
@ -42,27 +42,38 @@ const SaveAsPresetDialog = ({ open, onOpenChange, preset }: TEditPresetProps) =>
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTitle(preset.title || localize('com_endpoint_my_preset'));
|
setTitle(preset.title ?? localize('com_endpoint_my_preset'));
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
|
// Handle Enter key press
|
||||||
|
const handleKeyDown = (event: React.KeyboardEvent) => {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
event.preventDefault();
|
||||||
|
submitPreset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<OGDialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogTemplate
|
<OGDialogTemplate
|
||||||
title={localize('com_endpoint_save_as_preset')}
|
title={localize('com_endpoint_save_as_preset')}
|
||||||
className="w-11/12 sm:w-1/4"
|
className="z-[90] w-11/12 sm:w-1/4"
|
||||||
|
overlayClassName="z-[80]"
|
||||||
showCloseButton={false}
|
showCloseButton={false}
|
||||||
main={
|
main={
|
||||||
<div className="flex w-full flex-col items-center gap-2">
|
<div className="flex w-full flex-col items-center gap-2">
|
||||||
<div className="grid w-full items-center gap-2">
|
<div className="grid w-full items-center gap-2">
|
||||||
<Label htmlFor="dialog-preset-name" className="text-left text-sm font-medium">
|
<Label htmlFor="preset-custom-name" className="text-left text-sm font-medium">
|
||||||
{localize('com_endpoint_preset_name')}
|
{localize('com_endpoint_preset_name')}
|
||||||
</Label>
|
</Label>
|
||||||
<Input
|
<Input
|
||||||
id="preset-custom-name"
|
id="preset-custom-name"
|
||||||
value={title || ''}
|
value={title || ''}
|
||||||
onChange={(e) => setTitle(e.target.value || '')}
|
onChange={(e) => setTitle(e.target.value || '')}
|
||||||
placeholder="Set a custom name for this preset"
|
onKeyDown={handleKeyDown}
|
||||||
|
placeholder={localize('com_endpoint_preset_custom_name_placeholder')}
|
||||||
|
aria-label={localize('com_endpoint_preset_name')}
|
||||||
className={cn(
|
className={cn(
|
||||||
defaultTextProps,
|
defaultTextProps,
|
||||||
'flex h-10 max-h-10 w-full resize-none border-gray-100 px-3 py-2 dark:border-gray-600',
|
'flex h-10 max-h-10 w-full resize-none border-gray-100 px-3 py-2 dark:border-gray-600',
|
||||||
|
@ -78,7 +89,7 @@ const SaveAsPresetDialog = ({ open, onOpenChange, preset }: TEditPresetProps) =>
|
||||||
selectText: localize('com_ui_save'),
|
selectText: localize('com_ui_save'),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Dialog>
|
</OGDialog>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
59
client/src/components/Endpoints/Settings/Bedrock.tsx
Normal file
59
client/src/components/Endpoints/Settings/Bedrock.tsx
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { getSettingsKeys } from 'librechat-data-provider';
|
||||||
|
import type { SettingDefinition } from 'librechat-data-provider';
|
||||||
|
import type { TModelSelectProps } from '~/common';
|
||||||
|
import { componentMapping } from '~/components/SidePanel/Parameters/components';
|
||||||
|
import { presetSettings } from '~/components/SidePanel/Parameters/settings';
|
||||||
|
|
||||||
|
export default function BedrockSettings({
|
||||||
|
conversation,
|
||||||
|
setOption,
|
||||||
|
models,
|
||||||
|
readonly,
|
||||||
|
}: TModelSelectProps) {
|
||||||
|
const parameters = useMemo(() => {
|
||||||
|
const [combinedKey, endpointKey] = getSettingsKeys(
|
||||||
|
conversation?.endpoint ?? '',
|
||||||
|
conversation?.model ?? '',
|
||||||
|
);
|
||||||
|
return presetSettings[combinedKey] ?? presetSettings[endpointKey];
|
||||||
|
}, [conversation]);
|
||||||
|
|
||||||
|
if (!parameters) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderComponent = (setting: SettingDefinition) => {
|
||||||
|
const Component = componentMapping[setting.component];
|
||||||
|
const { key, default: defaultValue, ...rest } = setting;
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
key,
|
||||||
|
settingKey: key,
|
||||||
|
defaultValue,
|
||||||
|
...rest,
|
||||||
|
readonly,
|
||||||
|
setOption,
|
||||||
|
conversation,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (key === 'model') {
|
||||||
|
return <Component {...props} options={models} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Component {...props} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-auto max-w-full overflow-x-hidden p-3">
|
||||||
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-5">
|
||||||
|
<div className="flex flex-col gap-6 md:col-span-3">
|
||||||
|
{parameters.col1.map(renderComponent)}
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-6 md:col-span-2">
|
||||||
|
{parameters.col2.map(renderComponent)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -18,8 +18,7 @@ import {
|
||||||
HoverCardTrigger,
|
HoverCardTrigger,
|
||||||
} from '~/components/ui';
|
} from '~/components/ui';
|
||||||
import { cn, defaultTextProps, optionText, removeFocusOutlines, removeFocusRings } from '~/utils';
|
import { cn, defaultTextProps, optionText, removeFocusOutlines, removeFocusRings } from '~/utils';
|
||||||
import OptionHoverAlt from '~/components/SidePanel/Parameters/OptionHover';
|
import { OptionHoverAlt, DynamicTags } from '~/components/SidePanel/Parameters';
|
||||||
import { DynamicTags } from '~/components/SidePanel/Parameters';
|
|
||||||
import { useLocalize, useDebouncedInput } from '~/hooks';
|
import { useLocalize, useDebouncedInput } from '~/hooks';
|
||||||
import OptionHover from './OptionHover';
|
import OptionHover from './OptionHover';
|
||||||
import { ESide } from '~/common';
|
import { ESide } from '~/common';
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export { default as Advanced } from './Advanced';
|
export { default as Advanced } from './Advanced';
|
||||||
export { default as AssistantsSettings } from './Assistants';
|
export { default as AssistantsSettings } from './Assistants';
|
||||||
|
export { default as BedrockSettings } from './Bedrock';
|
||||||
export { default as OpenAISettings } from './OpenAI';
|
export { default as OpenAISettings } from './OpenAI';
|
||||||
export { default as BingAISettings } from './BingAI';
|
export { default as BingAISettings } from './BingAI';
|
||||||
export { default as GoogleSettings } from './Google';
|
export { default as GoogleSettings } from './Google';
|
||||||
|
|
|
@ -4,6 +4,7 @@ import type { TModelSelectProps } from '~/common';
|
||||||
import { GoogleSettings, PluginSettings } from './MultiView';
|
import { GoogleSettings, PluginSettings } from './MultiView';
|
||||||
import AssistantsSettings from './Assistants';
|
import AssistantsSettings from './Assistants';
|
||||||
import AnthropicSettings from './Anthropic';
|
import AnthropicSettings from './Anthropic';
|
||||||
|
import BedrockSettings from './Bedrock';
|
||||||
import BingAISettings from './BingAI';
|
import BingAISettings from './BingAI';
|
||||||
import OpenAISettings from './OpenAI';
|
import OpenAISettings from './OpenAI';
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ const settings: { [key: string]: FC<TModelSelectProps> } = {
|
||||||
[EModelEndpoint.azureOpenAI]: OpenAISettings,
|
[EModelEndpoint.azureOpenAI]: OpenAISettings,
|
||||||
[EModelEndpoint.bingAI]: BingAISettings,
|
[EModelEndpoint.bingAI]: BingAISettings,
|
||||||
[EModelEndpoint.anthropic]: AnthropicSettings,
|
[EModelEndpoint.anthropic]: AnthropicSettings,
|
||||||
|
[EModelEndpoint.bedrock]: BedrockSettings,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSettings = () => {
|
export const getSettings = () => {
|
||||||
|
|
|
@ -12,6 +12,7 @@ import PluginsByIndex from './PluginsByIndex';
|
||||||
export const options: { [key: string]: FC<TModelSelectProps> } = {
|
export const options: { [key: string]: FC<TModelSelectProps> } = {
|
||||||
[EModelEndpoint.openAI]: OpenAI,
|
[EModelEndpoint.openAI]: OpenAI,
|
||||||
[EModelEndpoint.custom]: OpenAI,
|
[EModelEndpoint.custom]: OpenAI,
|
||||||
|
[EModelEndpoint.bedrock]: OpenAI,
|
||||||
[EModelEndpoint.azureOpenAI]: OpenAI,
|
[EModelEndpoint.azureOpenAI]: OpenAI,
|
||||||
[EModelEndpoint.bingAI]: BingAI,
|
[EModelEndpoint.bingAI]: BingAI,
|
||||||
[EModelEndpoint.google]: Google,
|
[EModelEndpoint.google]: Google,
|
||||||
|
|
170
client/src/components/Messages/ContentRender.tsx
Normal file
170
client/src/components/Messages/ContentRender.tsx
Normal file
|
@ -0,0 +1,170 @@
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
import { useCallback, useMemo, memo } from 'react';
|
||||||
|
import type { TMessage, TMessageContentParts } from 'librechat-data-provider';
|
||||||
|
import type { TMessageProps } from '~/common';
|
||||||
|
import ContentParts from '~/components/Chat/Messages/Content/ContentParts';
|
||||||
|
import PlaceholderRow from '~/components/Chat/Messages/ui/PlaceholderRow';
|
||||||
|
import SiblingSwitch from '~/components/Chat/Messages/SiblingSwitch';
|
||||||
|
import HoverButtons from '~/components/Chat/Messages/HoverButtons';
|
||||||
|
import Icon from '~/components/Chat/Messages/MessageIcon';
|
||||||
|
import SubRow from '~/components/Chat/Messages/SubRow';
|
||||||
|
import { useMessageActions } from '~/hooks';
|
||||||
|
import { cn, logger } from '~/utils';
|
||||||
|
import store from '~/store';
|
||||||
|
|
||||||
|
type ContentRenderProps = {
|
||||||
|
message?: TMessage;
|
||||||
|
isCard?: boolean;
|
||||||
|
isMultiMessage?: boolean;
|
||||||
|
isSubmittingFamily?: boolean;
|
||||||
|
} & Pick<
|
||||||
|
TMessageProps,
|
||||||
|
'currentEditId' | 'setCurrentEditId' | 'siblingIdx' | 'setSiblingIdx' | 'siblingCount'
|
||||||
|
>;
|
||||||
|
|
||||||
|
const ContentRender = memo(
|
||||||
|
({
|
||||||
|
isCard,
|
||||||
|
siblingIdx,
|
||||||
|
siblingCount,
|
||||||
|
message: msg,
|
||||||
|
setSiblingIdx,
|
||||||
|
currentEditId,
|
||||||
|
isMultiMessage,
|
||||||
|
setCurrentEditId,
|
||||||
|
isSubmittingFamily,
|
||||||
|
}: ContentRenderProps) => {
|
||||||
|
const {
|
||||||
|
// ask,
|
||||||
|
edit,
|
||||||
|
index,
|
||||||
|
agent,
|
||||||
|
assistant,
|
||||||
|
enterEdit,
|
||||||
|
conversation,
|
||||||
|
messageLabel,
|
||||||
|
isSubmitting,
|
||||||
|
latestMessage,
|
||||||
|
handleContinue,
|
||||||
|
copyToClipboard,
|
||||||
|
setLatestMessage,
|
||||||
|
regenerateMessage,
|
||||||
|
} = useMessageActions({
|
||||||
|
message: msg,
|
||||||
|
currentEditId,
|
||||||
|
isMultiMessage,
|
||||||
|
setCurrentEditId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fontSize = useRecoilValue(store.fontSize);
|
||||||
|
const handleRegenerateMessage = useCallback(() => regenerateMessage(), [regenerateMessage]);
|
||||||
|
// const { isCreatedByUser, error, unfinished } = msg ?? {};
|
||||||
|
const isLast = useMemo(
|
||||||
|
() =>
|
||||||
|
!(msg?.children?.length ?? 0) && (msg?.depth === latestMessage?.depth || msg?.depth === -1),
|
||||||
|
[msg?.children, msg?.depth, latestMessage?.depth],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!msg) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isLatestMessage = msg.messageId === latestMessage?.messageId;
|
||||||
|
const showCardRender = isLast && !(isSubmittingFamily === true) && isCard === true;
|
||||||
|
const isLatestCard = isCard === true && !(isSubmittingFamily === true) && isLatestMessage;
|
||||||
|
const clickHandler =
|
||||||
|
showCardRender && !isLatestMessage
|
||||||
|
? () => {
|
||||||
|
logger.log(`Message Card click: Setting ${msg.messageId} as latest message`);
|
||||||
|
logger.dir(msg);
|
||||||
|
setLatestMessage(msg);
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
aria-label={`message-${msg.depth}-${msg.messageId}`}
|
||||||
|
className={cn(
|
||||||
|
'final-completion group mx-auto flex flex-1 gap-3',
|
||||||
|
isCard === true
|
||||||
|
? 'relative w-full gap-1 rounded-lg border border-border-medium bg-surface-primary-alt p-2 md:w-1/2 md:gap-3 md:p-4'
|
||||||
|
: 'md:max-w-3xl md:px-5 lg:max-w-[40rem] lg:px-1 xl:max-w-[48rem] xl:px-5',
|
||||||
|
isLatestCard === true ? 'bg-surface-secondary' : '',
|
||||||
|
showCardRender ? 'cursor-pointer transition-colors duration-300' : '',
|
||||||
|
'focus:outline-none focus:ring-2 focus:ring-border-xheavy',
|
||||||
|
)}
|
||||||
|
onClick={clickHandler}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if ((e.key === 'Enter' || e.key === ' ') && clickHandler) {
|
||||||
|
clickHandler();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
role={showCardRender ? 'button' : undefined}
|
||||||
|
tabIndex={showCardRender ? 0 : undefined}
|
||||||
|
>
|
||||||
|
{isLatestCard === true && (
|
||||||
|
<div className="absolute right-0 top-0 m-2 h-3 w-3 rounded-full bg-text-primary" />
|
||||||
|
)}
|
||||||
|
<div className="relative flex flex-shrink-0 flex-col items-end">
|
||||||
|
<div>
|
||||||
|
<div className="pt-0.5">
|
||||||
|
<div className="flex h-6 w-6 items-center justify-center overflow-hidden rounded-full">
|
||||||
|
<Icon
|
||||||
|
message={msg}
|
||||||
|
conversation={conversation}
|
||||||
|
assistant={assistant}
|
||||||
|
agent={agent}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'relative flex w-11/12 flex-col',
|
||||||
|
msg.isCreatedByUser === true ? '' : 'agent-turn',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<h2 className={cn('select-none font-semibold', fontSize)}>{messageLabel}</h2>
|
||||||
|
<div className="flex-col gap-1 md:gap-3">
|
||||||
|
<div className="flex max-w-full flex-grow flex-col gap-0">
|
||||||
|
<ContentParts
|
||||||
|
content={msg.content as Array<TMessageContentParts | undefined>}
|
||||||
|
messageId={msg.messageId}
|
||||||
|
isCreatedByUser={msg.isCreatedByUser}
|
||||||
|
isLast={isLast}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{!(msg.children?.length ?? 0) && (isSubmittingFamily === true || isSubmitting) ? (
|
||||||
|
<PlaceholderRow isCard={isCard} />
|
||||||
|
) : (
|
||||||
|
<SubRow classes="text-xs">
|
||||||
|
<SiblingSwitch
|
||||||
|
siblingIdx={siblingIdx}
|
||||||
|
siblingCount={siblingCount}
|
||||||
|
setSiblingIdx={setSiblingIdx}
|
||||||
|
/>
|
||||||
|
<HoverButtons
|
||||||
|
index={index}
|
||||||
|
isEditing={edit}
|
||||||
|
message={msg}
|
||||||
|
enterEdit={enterEdit}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
|
conversation={conversation ?? null}
|
||||||
|
regenerate={handleRegenerateMessage}
|
||||||
|
copyToClipboard={copyToClipboard}
|
||||||
|
handleContinue={handleContinue}
|
||||||
|
latestMessage={latestMessage}
|
||||||
|
isLast={isLast}
|
||||||
|
/>
|
||||||
|
</SubRow>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ContentRender;
|
82
client/src/components/Messages/MessageContent.tsx
Normal file
82
client/src/components/Messages/MessageContent.tsx
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { useMessageProcess } from '~/hooks';
|
||||||
|
import type { TMessageProps } from '~/common';
|
||||||
|
// eslint-disable-next-line import/no-cycle
|
||||||
|
import MultiMessage from '~/components/Chat/Messages/MultiMessage';
|
||||||
|
import ContentRender from './ContentRender';
|
||||||
|
|
||||||
|
const MessageContainer = React.memo(
|
||||||
|
({
|
||||||
|
handleScroll,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
handleScroll: (event?: unknown) => void;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="text-token-text-primary w-full border-0 bg-transparent dark:border-0 dark:bg-transparent"
|
||||||
|
onWheel={handleScroll}
|
||||||
|
onTouchMove={handleScroll}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function MessageContent(props: TMessageProps) {
|
||||||
|
const {
|
||||||
|
showSibling,
|
||||||
|
conversation,
|
||||||
|
handleScroll,
|
||||||
|
siblingMessage,
|
||||||
|
latestMultiMessage,
|
||||||
|
isSubmittingFamily,
|
||||||
|
} = useMessageProcess({ message: props.message });
|
||||||
|
const { message, currentEditId, setCurrentEditId } = props;
|
||||||
|
|
||||||
|
if (!message || typeof message !== 'object') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { children, messageId = null } = message;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<MessageContainer handleScroll={handleScroll}>
|
||||||
|
{showSibling ? (
|
||||||
|
<div className="m-auto my-2 flex justify-center p-4 py-2 md:gap-6">
|
||||||
|
<div className="flex w-full flex-row flex-wrap justify-between gap-1 md:max-w-5xl md:flex-nowrap md:gap-2 lg:max-w-5xl xl:max-w-6xl">
|
||||||
|
<ContentRender
|
||||||
|
{...props}
|
||||||
|
message={message}
|
||||||
|
isSubmittingFamily={isSubmittingFamily}
|
||||||
|
isCard
|
||||||
|
/>
|
||||||
|
<ContentRender
|
||||||
|
{...props}
|
||||||
|
isMultiMessage
|
||||||
|
isCard
|
||||||
|
message={siblingMessage ?? latestMultiMessage ?? undefined}
|
||||||
|
isSubmittingFamily={isSubmittingFamily}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="m-auto justify-center p-4 py-2 md:gap-6 ">
|
||||||
|
<ContentRender {...props} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</MessageContainer>
|
||||||
|
<MultiMessage
|
||||||
|
key={messageId}
|
||||||
|
messageId={messageId}
|
||||||
|
conversation={conversation}
|
||||||
|
messagesTree={children ?? []}
|
||||||
|
currentEditId={currentEditId}
|
||||||
|
setCurrentEditId={setCurrentEditId}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
import React from 'react';
|
||||||
import CategoryIcon from '~/components/Prompts/Groups/CategoryIcon';
|
import CategoryIcon from '~/components/Prompts/Groups/CategoryIcon';
|
||||||
|
|
||||||
export default function ListCard({
|
export default function ListCard({
|
||||||
|
@ -10,19 +11,33 @@ export default function ListCard({
|
||||||
category: string;
|
category: string;
|
||||||
name: string;
|
name: string;
|
||||||
snippet: string;
|
snippet: string;
|
||||||
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
onClick?: React.MouseEventHandler<HTMLDivElement | HTMLButtonElement>;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement | HTMLButtonElement>) => {
|
||||||
|
if (event.key === 'Enter' || event.key === ' ') {
|
||||||
|
event.preventDefault();
|
||||||
|
onClick?.(event as unknown as React.MouseEvent<HTMLDivElement | HTMLButtonElement>);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<div
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
className="relative my-2 flex w-full cursor-pointer flex-col gap-2 rounded-2xl border border-border-light px-3 pb-4 pt-3 text-start
|
className="relative my-2 flex w-full cursor-pointer flex-col gap-2 rounded-2xl border border-border-light px-3 pb-4 pt-3 text-start
|
||||||
align-top text-[15px] shadow-[0_0_2px_0_rgba(0,0,0,0.05),0_4px_6px_0_rgba(0,0,0,0.02)] transition-all duration-300 ease-in-out hover:bg-surface-tertiary"
|
align-top text-[15px] shadow-[0_0_2px_0_rgba(0,0,0,0.05),0_4px_6px_0_rgba(0,0,0,0.02)] transition-all duration-300 ease-in-out hover:bg-surface-tertiary"
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
aria-labelledby={`card-title-${name}`}
|
||||||
>
|
>
|
||||||
<div className="flex w-full justify-between">
|
<div className="flex w-full justify-between">
|
||||||
<div className="flex flex-row gap-2">
|
<div className="flex flex-row gap-2">
|
||||||
<CategoryIcon category={category} className="icon-md" />
|
<CategoryIcon category={category} className="icon-md" aria-hidden="true" />
|
||||||
<h3 className="break-word select-none text-balance text-sm font-semibold text-text-primary">
|
<h3
|
||||||
|
id={`card-title-${name}`}
|
||||||
|
className="break-word select-none text-balance text-sm font-semibold text-text-primary"
|
||||||
|
>
|
||||||
{name}
|
{name}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,6 +46,6 @@ export default function ListCard({
|
||||||
<div className="ellipsis max-w-full select-none text-balance text-sm text-text-secondary">
|
<div className="ellipsis max-w-full select-none text-balance text-sm text-text-secondary">
|
||||||
{snippet}
|
{snippet}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ const SharePrompt = ({ group, disabled }: { group?: TPromptGroup; disabled: bool
|
||||||
const { data: startupConfig = {} as TStartupConfig, isFetching } = useGetStartupConfig();
|
const { data: startupConfig = {} as TStartupConfig, isFetching } = useGetStartupConfig();
|
||||||
const { instanceProjectId } = startupConfig;
|
const { instanceProjectId } = startupConfig;
|
||||||
const groupIsGlobal = useMemo(
|
const groupIsGlobal = useMemo(
|
||||||
() => !!group?.projectIds?.includes(instanceProjectId),
|
() => !!(group?.projectIds ?? []).includes(instanceProjectId),
|
||||||
[group, instanceProjectId],
|
[group, instanceProjectId],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -57,7 +57,8 @@ const SharePrompt = ({ group, disabled }: { group?: TPromptGroup; disabled: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const onSubmit = (data: FormValues) => {
|
const onSubmit = (data: FormValues) => {
|
||||||
if (!group._id || !instanceProjectId) {
|
const groupId = group._id ?? '';
|
||||||
|
if (!groupId || !instanceProjectId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +71,7 @@ const SharePrompt = ({ group, disabled }: { group?: TPromptGroup; disabled: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
updateGroup.mutate({
|
updateGroup.mutate({
|
||||||
id: group._id,
|
id: groupId,
|
||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -87,24 +88,38 @@ const SharePrompt = ({ group, disabled }: { group?: TPromptGroup; disabled: bool
|
||||||
<Share2Icon className="cursor-pointer text-white " />
|
<Share2Icon className="cursor-pointer text-white " />
|
||||||
</Button>
|
</Button>
|
||||||
</OGDialogTrigger>
|
</OGDialogTrigger>
|
||||||
<OGDialogContent className="bg-white dark:border-gray-700 dark:bg-gray-850 dark:text-gray-300">
|
<OGDialogContent className="border-border-light bg-surface-primary-alt text-text-secondary">
|
||||||
<OGDialogTitle>{localize('com_ui_share_var', `"${group.name}"`)}</OGDialogTitle>
|
<OGDialogTitle>{localize('com_ui_share_var', `"${group.name}"`)}</OGDialogTitle>
|
||||||
<form className="p-2" onSubmit={handleSubmit(onSubmit)}>
|
<form className="p-2" onSubmit={handleSubmit(onSubmit)}>
|
||||||
<div className="mb-4 flex items-center justify-between gap-2 py-4">
|
<div className="mb-4 flex items-center justify-between gap-2 py-4">
|
||||||
<label
|
<div className="flex items-center">
|
||||||
className="cursor-pointer select-none"
|
<button
|
||||||
htmlFor={Permissions.SHARED_GLOBAL}
|
type="button"
|
||||||
onClick={() =>
|
className="mr-2 cursor-pointer"
|
||||||
setValue(Permissions.SHARED_GLOBAL, !getValues(Permissions.SHARED_GLOBAL), {
|
onClick={() =>
|
||||||
shouldDirty: true,
|
setValue(Permissions.SHARED_GLOBAL, !getValues(Permissions.SHARED_GLOBAL), {
|
||||||
})
|
shouldDirty: true,
|
||||||
}
|
})
|
||||||
>
|
}
|
||||||
{localize('com_ui_share_to_all_users')}
|
onKeyDown={(e) => {
|
||||||
{groupIsGlobal && (
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
<span className="ml-2 text-xs">{localize('com_ui_prompt_shared_to_all')}</span>
|
e.preventDefault();
|
||||||
)}
|
setValue(Permissions.SHARED_GLOBAL, !getValues(Permissions.SHARED_GLOBAL), {
|
||||||
</label>
|
shouldDirty: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
aria-checked={getValues(Permissions.SHARED_GLOBAL)}
|
||||||
|
role="checkbox"
|
||||||
|
>
|
||||||
|
{localize('com_ui_share_to_all_users')}
|
||||||
|
</button>
|
||||||
|
<label htmlFor={Permissions.SHARED_GLOBAL} className="select-none">
|
||||||
|
{groupIsGlobal && (
|
||||||
|
<span className="ml-2 text-xs">{localize('com_ui_prompt_shared_to_all')}</span>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<Controller
|
<Controller
|
||||||
name={Permissions.SHARED_GLOBAL}
|
name={Permissions.SHARED_GLOBAL}
|
||||||
control={control}
|
control={control}
|
||||||
|
@ -126,7 +141,7 @@ const SharePrompt = ({ group, disabled }: { group?: TPromptGroup; disabled: bool
|
||||||
{...field}
|
{...field}
|
||||||
checked={field.value}
|
checked={field.value}
|
||||||
onCheckedChange={field.onChange}
|
onCheckedChange={field.onChange}
|
||||||
value={field?.value?.toString()}
|
value={field.value.toString()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -11,9 +11,10 @@ import Action from '~/components/SidePanel/Builder/Action';
|
||||||
import { useLocalize } from '~/hooks';
|
import { useLocalize } from '~/hooks';
|
||||||
import { ToolSelectDialog } from '~/components/Tools';
|
import { ToolSelectDialog } from '~/components/Tools';
|
||||||
import { useToastContext } from '~/Providers';
|
import { useToastContext } from '~/Providers';
|
||||||
import ContextButton from './ContextButton';
|
|
||||||
import { Spinner } from '~/components/svg';
|
import { Spinner } from '~/components/svg';
|
||||||
|
import DeleteButton from './DeleteButton';
|
||||||
import AgentAvatar from './AgentAvatar';
|
import AgentAvatar from './AgentAvatar';
|
||||||
|
import ShareAgent from './ShareAgent';
|
||||||
import AgentTool from './AgentTool';
|
import AgentTool from './AgentTool';
|
||||||
import { Panel } from '~/common';
|
import { Panel } from '~/common';
|
||||||
|
|
||||||
|
@ -57,14 +58,14 @@ export default function AgentConfig({
|
||||||
() => agentsConfig?.capabilities?.includes(Capabilities.actions),
|
() => agentsConfig?.capabilities?.includes(Capabilities.actions),
|
||||||
[agentsConfig],
|
[agentsConfig],
|
||||||
);
|
);
|
||||||
const retrievalEnabled = useMemo(
|
// const retrievalEnabled = useMemo(
|
||||||
() => agentsConfig?.capabilities?.includes(Capabilities.retrieval),
|
// () => agentsConfig?.capabilities?.includes(Capabilities.retrieval),
|
||||||
[agentsConfig],
|
// [agentsConfig],
|
||||||
);
|
// );
|
||||||
const codeEnabled = useMemo(
|
// const codeEnabled = useMemo(
|
||||||
() => agentsConfig?.capabilities?.includes(Capabilities.code_interpreter),
|
// () => agentsConfig?.capabilities?.includes(Capabilities.code_interpreter),
|
||||||
[agentsConfig],
|
// [agentsConfig],
|
||||||
);
|
// );
|
||||||
|
|
||||||
/* Mutations */
|
/* Mutations */
|
||||||
const update = useUpdateAgentMutation({
|
const update = useUpdateAgentMutation({
|
||||||
|
@ -190,7 +191,7 @@ export default function AgentConfig({
|
||||||
name="id"
|
name="id"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<p className="h-3 text-xs italic text-gray-600" aria-live="polite">
|
<p className="h-3 text-xs italic text-text-secondary" aria-live="polite">
|
||||||
{field.value}
|
{field.value}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
@ -221,12 +222,11 @@ export default function AgentConfig({
|
||||||
{/* Instructions */}
|
{/* Instructions */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<label className={labelClass} htmlFor="instructions">
|
<label className={labelClass} htmlFor="instructions">
|
||||||
{localize('com_ui_instructions')} <span className="text-red-500">*</span>
|
{localize('com_ui_instructions')}
|
||||||
</label>
|
</label>
|
||||||
<Controller
|
<Controller
|
||||||
name="instructions"
|
name="instructions"
|
||||||
control={control}
|
control={control}
|
||||||
rules={{ required: true, minLength: 1 }}
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<>
|
<>
|
||||||
<textarea
|
<textarea
|
||||||
|
@ -276,16 +276,16 @@ export default function AgentConfig({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<span>{model ? model : localize('com_ui_select_model')}</span>
|
<span>{model != null ? model : localize('com_ui_select_model')}</span>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/* Agent Tools & Actions */}
|
{/* Agent Tools & Actions */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<label className={labelClass}>
|
<label className={labelClass}>
|
||||||
{`${toolsEnabled ? localize('com_assistants_tools') : ''}
|
{`${toolsEnabled === true ? localize('com_assistants_tools') : ''}
|
||||||
${toolsEnabled && actionsEnabled ? ' + ' : ''}
|
${toolsEnabled === true && actionsEnabled === true ? ' + ' : ''}
|
||||||
${actionsEnabled ? localize('com_assistants_actions') : ''}`}
|
${actionsEnabled === true ? localize('com_assistants_actions') : ''}`}
|
||||||
</label>
|
</label>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{tools?.map((func, i) => (
|
{tools?.map((func, i) => (
|
||||||
|
@ -339,11 +339,16 @@ export default function AgentConfig({
|
||||||
</div>
|
</div>
|
||||||
{/* Context Button */}
|
{/* Context Button */}
|
||||||
<div className="flex items-center justify-end gap-2">
|
<div className="flex items-center justify-end gap-2">
|
||||||
<ContextButton
|
<DeleteButton
|
||||||
agent_id={agent_id}
|
agent_id={agent_id}
|
||||||
setCurrentAgentId={setCurrentAgentId}
|
setCurrentAgentId={setCurrentAgentId}
|
||||||
createMutation={create}
|
createMutation={create}
|
||||||
/>
|
/>
|
||||||
|
<ShareAgent
|
||||||
|
agent_id={agent_id}
|
||||||
|
agentName={agent?.name ?? ''}
|
||||||
|
projectIds={agent?.projectIds ?? []}
|
||||||
|
/>
|
||||||
{/* Submit Button */}
|
{/* Submit Button */}
|
||||||
<button
|
<button
|
||||||
className="btn btn-primary focus:shadow-outline flex w-full items-center justify-center px-4 py-2 font-semibold text-white hover:bg-green-600 focus:border-green-500"
|
className="btn btn-primary focus:shadow-outline flex w-full items-center justify-center px-4 py-2 font-semibold text-white hover:bg-green-600 focus:border-green-500"
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus, EarthIcon } from 'lucide-react';
|
||||||
import { useCallback, useEffect, useRef } from 'react';
|
import { useCallback, useEffect, useRef } from 'react';
|
||||||
|
import { useGetStartupConfig } from 'librechat-data-provider/react-query';
|
||||||
import { Capabilities, defaultAgentFormValues } from 'librechat-data-provider';
|
import { Capabilities, defaultAgentFormValues } from 'librechat-data-provider';
|
||||||
import type { AgentCapabilities, AgentForm, TAgentOption } from '~/common';
|
|
||||||
import type { Agent, AgentCreateParams } from 'librechat-data-provider';
|
import type { Agent, AgentCreateParams } from 'librechat-data-provider';
|
||||||
import type { UseMutationResult } from '@tanstack/react-query';
|
import type { UseMutationResult } from '@tanstack/react-query';
|
||||||
import type { UseFormReset } from 'react-hook-form';
|
import type { UseFormReset } from 'react-hook-form';
|
||||||
|
import type { AgentCapabilities, AgentForm, TAgentOption } from '~/common';
|
||||||
import { cn, createDropdownSetter, createProviderOption, processAgentOption } from '~/utils';
|
import { cn, createDropdownSetter, createProviderOption, processAgentOption } from '~/utils';
|
||||||
import { useListAgentsQuery, useGetAgentByIdQuery } from '~/data-provider';
|
import { useListAgentsQuery, useGetAgentByIdQuery } from '~/data-provider';
|
||||||
import SelectDropDown from '~/components/ui/SelectDropDown';
|
import SelectDropDown from '~/components/ui/SelectDropDown';
|
||||||
|
@ -16,7 +17,7 @@ const keys = new Set(Object.keys(defaultAgentFormValues));
|
||||||
export default function AgentSelect({
|
export default function AgentSelect({
|
||||||
reset,
|
reset,
|
||||||
value: currentAgentValue,
|
value: currentAgentValue,
|
||||||
selectedAgentId,
|
selectedAgentId = null,
|
||||||
setCurrentAgentId,
|
setCurrentAgentId,
|
||||||
createMutation,
|
createMutation,
|
||||||
}: {
|
}: {
|
||||||
|
@ -31,21 +32,33 @@ export default function AgentSelect({
|
||||||
// const fileMap = useFileMapContext();
|
// const fileMap = useFileMapContext();
|
||||||
const lastSelectedAgent = useRef<string | null>(null);
|
const lastSelectedAgent = useRef<string | null>(null);
|
||||||
|
|
||||||
const { data: agents = [] } = useListAgentsQuery(undefined, {
|
const { data: startupConfig } = useGetStartupConfig();
|
||||||
select: (res) => res.data.map((agent) => processAgentOption(agent /*, fileMap */)),
|
const { data: agents = null } = useListAgentsQuery(undefined, {
|
||||||
|
select: (res) =>
|
||||||
|
res.data.map((agent) =>
|
||||||
|
processAgentOption({
|
||||||
|
agent,
|
||||||
|
instanceProjectId: startupConfig?.instanceProjectId,
|
||||||
|
/* fileMap */
|
||||||
|
}),
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
const agentQuery = useGetAgentByIdQuery(selectedAgentId ?? '', {
|
const agentQuery = useGetAgentByIdQuery(selectedAgentId ?? '', {
|
||||||
enabled: !!selectedAgentId,
|
enabled: !!(selectedAgentId ?? ''),
|
||||||
});
|
});
|
||||||
|
|
||||||
const resetAgentForm = useCallback(
|
const resetAgentForm = useCallback(
|
||||||
(fullAgent: Agent) => {
|
(fullAgent: Agent) => {
|
||||||
|
const { instanceProjectId } = startupConfig ?? {};
|
||||||
|
const isGlobal =
|
||||||
|
(instanceProjectId != null && fullAgent.projectIds?.includes(instanceProjectId)) ?? false;
|
||||||
const update = {
|
const update = {
|
||||||
...fullAgent,
|
...fullAgent,
|
||||||
provider: createProviderOption(fullAgent.provider),
|
provider: createProviderOption(fullAgent.provider),
|
||||||
label: fullAgent.name ?? '',
|
label: fullAgent.name ?? '',
|
||||||
value: fullAgent.id ?? '',
|
value: fullAgent.id || '',
|
||||||
|
icon: isGlobal ? <EarthIcon className={'icon-lg text-green-400'} /> : null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const actions: AgentCapabilities = {
|
const actions: AgentCapabilities = {
|
||||||
|
@ -84,7 +97,7 @@ export default function AgentSelect({
|
||||||
const onSelect = useCallback(
|
const onSelect = useCallback(
|
||||||
(selectedId: string) => {
|
(selectedId: string) => {
|
||||||
const agentExists = !!(selectedId
|
const agentExists = !!(selectedId
|
||||||
? agents.find((agent) => agent.id === selectedId)
|
? (agents ?? []).find((agent) => agent.id === selectedId)
|
||||||
: undefined);
|
: undefined);
|
||||||
|
|
||||||
createMutation.reset();
|
createMutation.reset();
|
||||||
|
@ -120,7 +133,7 @@ export default function AgentSelect({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedAgentId && agents) {
|
if (selectedAgentId != null && selectedAgentId !== '' && agents) {
|
||||||
timerId = setTimeout(() => {
|
timerId = setTimeout(() => {
|
||||||
lastSelectedAgent.current = selectedAgentId;
|
lastSelectedAgent.current = selectedAgentId;
|
||||||
onSelect(selectedAgentId);
|
onSelect(selectedAgentId);
|
||||||
|
@ -136,8 +149,9 @@ export default function AgentSelect({
|
||||||
|
|
||||||
const createAgent = localize('com_ui_create') + ' ' + localize('com_ui_agent');
|
const createAgent = localize('com_ui_create') + ' ' + localize('com_ui_agent');
|
||||||
const hasAgentValue = !!(typeof currentAgentValue === 'object'
|
const hasAgentValue = !!(typeof currentAgentValue === 'object'
|
||||||
? currentAgentValue.value
|
? currentAgentValue.value != null && currentAgentValue.value !== ''
|
||||||
: currentAgentValue);
|
: typeof currentAgentValue !== 'undefined');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectDropDown
|
<SelectDropDown
|
||||||
value={!hasAgentValue ? createAgent : (currentAgentValue as TAgentOption)}
|
value={!hasAgentValue ? createAgent : (currentAgentValue as TAgentOption)}
|
||||||
|
@ -151,9 +165,11 @@ export default function AgentSelect({
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
iconSide="left"
|
iconSide="left"
|
||||||
|
optionIconSide="right"
|
||||||
showAbove={false}
|
showAbove={false}
|
||||||
showLabel={false}
|
showLabel={false}
|
||||||
emptyTitle={true}
|
emptyTitle={true}
|
||||||
|
showOptionIcon={true}
|
||||||
containerClassName="flex-grow"
|
containerClassName="flex-grow"
|
||||||
searchClassName="dark:from-gray-850"
|
searchClassName="dark:from-gray-850"
|
||||||
searchPlaceholder={localize('com_agents_search_name')}
|
searchPlaceholder={localize('com_agents_search_name')}
|
||||||
|
|
|
@ -4,11 +4,11 @@ import { OGDialog, OGDialogTrigger, Label } from '~/components/ui';
|
||||||
import { useChatContext, useToastContext } from '~/Providers';
|
import { useChatContext, useToastContext } from '~/Providers';
|
||||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||||
import { useLocalize, useSetIndexOptions } from '~/hooks';
|
import { useLocalize, useSetIndexOptions } from '~/hooks';
|
||||||
|
import { cn, removeFocusOutlines, logger } from '~/utils';
|
||||||
import { useDeleteAgentMutation } from '~/data-provider';
|
import { useDeleteAgentMutation } from '~/data-provider';
|
||||||
import { cn, removeFocusOutlines } from '~/utils/';
|
|
||||||
import { TrashIcon } from '~/components/svg';
|
import { TrashIcon } from '~/components/svg';
|
||||||
|
|
||||||
export default function ContextButton({
|
export default function DeleteButton({
|
||||||
agent_id,
|
agent_id,
|
||||||
setCurrentAgentId,
|
setCurrentAgentId,
|
||||||
createMutation,
|
createMutation,
|
||||||
|
@ -34,8 +34,8 @@ export default function ContextButton({
|
||||||
status: 'success',
|
status: 'success',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (createMutation.data?.id) {
|
if (createMutation.data?.id ?? '') {
|
||||||
console.log('[deleteAgent] resetting createMutation');
|
logger.log('agents', 'resetting createMutation');
|
||||||
createMutation.reset();
|
createMutation.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ export default function ContextButton({
|
||||||
return setOption('agent_id')(firstAgent.id);
|
return setOption('agent_id')(firstAgent.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentAgent = updatedList?.find((agent) => agent.id === conversation?.agent_id);
|
const currentAgent = updatedList.find((agent) => agent.id === conversation?.agent_id);
|
||||||
|
|
||||||
if (currentAgent) {
|
if (currentAgent) {
|
||||||
setCurrentAgentId(currentAgent.id);
|
setCurrentAgentId(currentAgent.id);
|
||||||
|
@ -78,6 +78,7 @@ export default function ContextButton({
|
||||||
'btn btn-neutral border-token-border-light relative h-9 rounded-lg font-medium',
|
'btn btn-neutral border-token-border-light relative h-9 rounded-lg font-medium',
|
||||||
removeFocusOutlines,
|
removeFocusOutlines,
|
||||||
)}
|
)}
|
||||||
|
aria-label={localize('com_ui_delete') + ' ' + localize('com_ui_agent')}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<div className="flex w-full items-center justify-center gap-2 text-red-500">
|
<div className="flex w-full items-center justify-center gap-2 text-red-500">
|
206
client/src/components/SidePanel/Agents/ShareAgent.tsx
Normal file
206
client/src/components/SidePanel/Agents/ShareAgent.tsx
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
import React, { useEffect, useMemo } from 'react';
|
||||||
|
import { Share2Icon } from 'lucide-react';
|
||||||
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
|
import { Permissions } from 'librechat-data-provider';
|
||||||
|
import { useGetStartupConfig } from 'librechat-data-provider/react-query';
|
||||||
|
import type { TStartupConfig, AgentUpdateParams } from 'librechat-data-provider';
|
||||||
|
import {
|
||||||
|
Switch,
|
||||||
|
OGDialog,
|
||||||
|
OGDialogTitle,
|
||||||
|
OGDialogClose,
|
||||||
|
OGDialogContent,
|
||||||
|
OGDialogTrigger,
|
||||||
|
} from '~/components/ui';
|
||||||
|
import { useUpdateAgentMutation } from '~/data-provider';
|
||||||
|
import { cn, removeFocusOutlines } from '~/utils';
|
||||||
|
import { useToastContext } from '~/Providers';
|
||||||
|
import { useLocalize } from '~/hooks';
|
||||||
|
|
||||||
|
type FormValues = {
|
||||||
|
[Permissions.SHARED_GLOBAL]: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ShareAgent({
|
||||||
|
agent_id = '',
|
||||||
|
agentName,
|
||||||
|
projectIds = [],
|
||||||
|
}: {
|
||||||
|
agent_id?: string;
|
||||||
|
agentName?: string;
|
||||||
|
projectIds?: string[];
|
||||||
|
}) {
|
||||||
|
const localize = useLocalize();
|
||||||
|
const { showToast } = useToastContext();
|
||||||
|
const { data: startupConfig = {} as TStartupConfig, isFetching } = useGetStartupConfig();
|
||||||
|
const { instanceProjectId } = startupConfig;
|
||||||
|
const agentIsGlobal = useMemo(
|
||||||
|
() => !!projectIds.includes(instanceProjectId),
|
||||||
|
[projectIds, instanceProjectId],
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
setValue,
|
||||||
|
getValues,
|
||||||
|
handleSubmit,
|
||||||
|
formState: { isSubmitting },
|
||||||
|
} = useForm<FormValues>({
|
||||||
|
mode: 'onChange',
|
||||||
|
defaultValues: {
|
||||||
|
[Permissions.SHARED_GLOBAL]: agentIsGlobal,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(Permissions.SHARED_GLOBAL, agentIsGlobal);
|
||||||
|
}, [agentIsGlobal, setValue]);
|
||||||
|
|
||||||
|
const updateAgent = useUpdateAgentMutation({
|
||||||
|
onSuccess: (data) => {
|
||||||
|
showToast({
|
||||||
|
message: `${localize('com_assistants_update_success')} ${
|
||||||
|
data.name ?? localize('com_ui_agent')
|
||||||
|
}`,
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
const error = err as Error;
|
||||||
|
showToast({
|
||||||
|
message: `${localize('com_agents_update_error')}${
|
||||||
|
error.message ? ` ${localize('com_ui_error')}: ${error.message}` : ''
|
||||||
|
}`,
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!agent_id || !instanceProjectId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = (data: FormValues) => {
|
||||||
|
if (!agent_id || !instanceProjectId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {} as AgentUpdateParams;
|
||||||
|
|
||||||
|
if (data[Permissions.SHARED_GLOBAL]) {
|
||||||
|
payload.projectIds = [startupConfig.instanceProjectId];
|
||||||
|
} else {
|
||||||
|
payload.removeProjectIds = [startupConfig.instanceProjectId];
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAgent.mutate({
|
||||||
|
agent_id,
|
||||||
|
data: payload,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OGDialog>
|
||||||
|
<OGDialogTrigger asChild>
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
'btn btn-neutral border-token-border-light relative h-9 rounded-lg font-medium',
|
||||||
|
removeFocusOutlines,
|
||||||
|
)}
|
||||||
|
aria-label={localize(
|
||||||
|
'com_ui_share_var',
|
||||||
|
agentName != null && agentName !== '' ? `"${agentName}"` : localize('com_ui_agent'),
|
||||||
|
)}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<div className="flex w-full items-center justify-center gap-2 text-blue-500">
|
||||||
|
<Share2Icon className="icon-md h-4 w-4" />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
</OGDialogTrigger>
|
||||||
|
<OGDialogContent className="border-border-light bg-surface-primary-alt text-text-secondary">
|
||||||
|
<OGDialogTitle>
|
||||||
|
{localize(
|
||||||
|
'com_ui_share_var',
|
||||||
|
agentName != null && agentName !== '' ? `"${agentName}"` : localize('com_ui_agent'),
|
||||||
|
)}
|
||||||
|
</OGDialogTitle>
|
||||||
|
<form
|
||||||
|
className="p-2"
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
handleSubmit(onSubmit)(e);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="mb-4 flex items-center justify-between gap-2 py-4">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="mr-2 cursor-pointer"
|
||||||
|
onClick={() =>
|
||||||
|
setValue(Permissions.SHARED_GLOBAL, !getValues(Permissions.SHARED_GLOBAL), {
|
||||||
|
shouldDirty: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
e.preventDefault();
|
||||||
|
setValue(Permissions.SHARED_GLOBAL, !getValues(Permissions.SHARED_GLOBAL), {
|
||||||
|
shouldDirty: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
aria-checked={getValues(Permissions.SHARED_GLOBAL)}
|
||||||
|
role="checkbox"
|
||||||
|
>
|
||||||
|
{localize('com_ui_share_to_all_users')}
|
||||||
|
</button>
|
||||||
|
<label htmlFor={Permissions.SHARED_GLOBAL} className="select-none">
|
||||||
|
{agentIsGlobal && (
|
||||||
|
<span className="ml-2 text-xs">{localize('com_ui_agent_shared_to_all')}</span>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
name={Permissions.SHARED_GLOBAL}
|
||||||
|
control={control}
|
||||||
|
disabled={isFetching || updateAgent.isLoading || !instanceProjectId}
|
||||||
|
rules={{
|
||||||
|
validate: (value) => {
|
||||||
|
const isValid = !(value && agentIsGlobal);
|
||||||
|
if (!isValid) {
|
||||||
|
showToast({
|
||||||
|
message: localize('com_ui_agent_already_shared_to_all'),
|
||||||
|
status: 'warning',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return isValid;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
render={({ field }) => (
|
||||||
|
<Switch
|
||||||
|
{...field}
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
value={field.value.toString()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<OGDialogClose asChild>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={isSubmitting || isFetching}
|
||||||
|
className="btn rounded bg-green-500 font-bold text-white transition-all hover:bg-green-600"
|
||||||
|
>
|
||||||
|
{localize('com_ui_save')}
|
||||||
|
</button>
|
||||||
|
</OGDialogClose>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</OGDialogContent>
|
||||||
|
</OGDialog>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,32 +1,40 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import type { Action } from 'librechat-data-provider';
|
import type { Action } from 'librechat-data-provider';
|
||||||
import GearIcon from '~/components/svg/GearIcon';
|
import GearIcon from '~/components/svg/GearIcon';
|
||||||
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
export default function Action({ action, onClick }: { action: Action; onClick: () => void }) {
|
export default function Action({ action, onClick }: { action: Action; onClick: () => void }) {
|
||||||
const [isHovering, setIsHovering] = useState(false);
|
const [isHovering, setIsHovering] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div
|
||||||
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
|
onClick={onClick}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter' || e.key === ' ') {
|
||||||
|
onClick();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="group flex w-full rounded-lg border border-border-medium text-sm hover:cursor-pointer focus:outline-none focus:ring-2 focus:ring-text-primary"
|
||||||
|
onMouseEnter={() => setIsHovering(true)}
|
||||||
|
onMouseLeave={() => setIsHovering(false)}
|
||||||
|
aria-label={`Action for ${action.metadata.domain}`}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
onClick={onClick}
|
className="h-9 grow overflow-hidden text-ellipsis whitespace-nowrap px-3 py-2"
|
||||||
className="flex w-full rounded-lg text-sm hover:cursor-pointer"
|
style={{ wordBreak: 'break-all' }}
|
||||||
onMouseEnter={() => setIsHovering(true)}
|
|
||||||
onMouseLeave={() => setIsHovering(false)}
|
|
||||||
>
|
>
|
||||||
<div
|
{action.metadata.domain}
|
||||||
className="h-9 grow whitespace-nowrap px-3 py-2"
|
</div>
|
||||||
style={{ textOverflow: 'ellipsis', wordBreak: 'break-all', overflow: 'hidden' }}
|
<div
|
||||||
>
|
className={cn(
|
||||||
{action.metadata.domain}
|
'h-9 w-9 min-w-9 items-center justify-center rounded-lg transition-colors duration-200 hover:bg-surface-tertiary focus:outline-none focus:ring-2 focus:ring-text-primary group-focus:flex',
|
||||||
</div>
|
isHovering ? 'flex' : 'hidden',
|
||||||
{isHovering && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="transition-colors flex h-9 w-9 min-w-9 items-center justify-center rounded-lg duration-200 hover:bg-gray-200 dark:hover:bg-gray-700"
|
|
||||||
>
|
|
||||||
<GearIcon className="icon-sm" />
|
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
|
aria-label="Settings"
|
||||||
|
>
|
||||||
|
<GearIcon className="icon-sm" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -9,17 +9,17 @@ import OptionHover from './OptionHover';
|
||||||
import { ESide } from '~/common';
|
import { ESide } from '~/common';
|
||||||
|
|
||||||
function DynamicCheckbox({
|
function DynamicCheckbox({
|
||||||
label,
|
label = '',
|
||||||
settingKey,
|
settingKey,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
description,
|
description = '',
|
||||||
columnSpan,
|
columnSpan,
|
||||||
setOption,
|
setOption,
|
||||||
optionType,
|
optionType,
|
||||||
readonly = false,
|
readonly = false,
|
||||||
showDefault = true,
|
showDefault = false,
|
||||||
labelCode,
|
labelCode = false,
|
||||||
descriptionCode,
|
descriptionCode = false,
|
||||||
conversation,
|
conversation,
|
||||||
}: DynamicSettingProps) {
|
}: DynamicSettingProps) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
@ -57,7 +57,7 @@ function DynamicCheckbox({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col items-center justify-start gap-6 ${
|
className={`flex flex-col items-center justify-start gap-6 ${
|
||||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full'
|
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<HoverCard openDelay={300}>
|
<HoverCard openDelay={300}>
|
||||||
|
@ -67,11 +67,11 @@ function DynamicCheckbox({
|
||||||
htmlFor={`${settingKey}-dynamic-checkbox`}
|
htmlFor={`${settingKey}-dynamic-checkbox`}
|
||||||
className="text-left text-sm font-medium"
|
className="text-left text-sm font-medium"
|
||||||
>
|
>
|
||||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
{labelCode ? localize(label) ?? label : label || settingKey}{' '}
|
||||||
{showDefault && (
|
{showDefault && (
|
||||||
<small className="opacity-40">
|
<small className="opacity-40">
|
||||||
({localize('com_endpoint_default')}:{' '}
|
({localize('com_endpoint_default')}:{' '}
|
||||||
{defaultValue ? localize('com_ui_yes') : localize('com_ui_no')})
|
{defaultValue != null ? localize('com_ui_yes') : localize('com_ui_no')})
|
||||||
</small>
|
</small>
|
||||||
)}
|
)}
|
||||||
</Label>
|
</Label>
|
||||||
|
@ -86,7 +86,7 @@ function DynamicCheckbox({
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
{description && (
|
{description && (
|
||||||
<OptionHover
|
<OptionHover
|
||||||
description={descriptionCode ? localize(description) || description : description}
|
description={descriptionCode ? localize(description) ?? description : description}
|
||||||
side={ESide.Left}
|
side={ESide.Left}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
132
client/src/components/SidePanel/Parameters/DynamicCombobox.tsx
Normal file
132
client/src/components/SidePanel/Parameters/DynamicCombobox.tsx
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
import { useMemo, useState, useCallback } from 'react';
|
||||||
|
import { OptionTypes } from 'librechat-data-provider';
|
||||||
|
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||||
|
import { Label, HoverCard, HoverCardTrigger } from '~/components/ui';
|
||||||
|
import ControlCombobox from '~/components/ui/ControlCombobox';
|
||||||
|
import { useLocalize, useParameterEffects } from '~/hooks';
|
||||||
|
import { useChatContext } from '~/Providers';
|
||||||
|
import OptionHover from './OptionHover';
|
||||||
|
import { ESide } from '~/common';
|
||||||
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
|
function DynamicCombobox({
|
||||||
|
label = '',
|
||||||
|
settingKey,
|
||||||
|
defaultValue,
|
||||||
|
description = '',
|
||||||
|
columnSpan,
|
||||||
|
setOption,
|
||||||
|
optionType,
|
||||||
|
options: _options,
|
||||||
|
items: _items,
|
||||||
|
showLabel = true,
|
||||||
|
showDefault = false,
|
||||||
|
labelCode = false,
|
||||||
|
descriptionCode = false,
|
||||||
|
searchPlaceholderCode = false,
|
||||||
|
selectPlaceholderCode = false,
|
||||||
|
conversation,
|
||||||
|
isCollapsed = false,
|
||||||
|
SelectIcon = null,
|
||||||
|
selectPlaceholder = '',
|
||||||
|
searchPlaceholder = '',
|
||||||
|
}: DynamicSettingProps & { isCollapsed?: boolean; SelectIcon?: React.ReactNode }) {
|
||||||
|
const localize = useLocalize();
|
||||||
|
const { preset } = useChatContext();
|
||||||
|
const [inputValue, setInputValue] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const selectedValue = useMemo(() => {
|
||||||
|
if (optionType === OptionTypes.Custom) {
|
||||||
|
return inputValue;
|
||||||
|
}
|
||||||
|
return conversation?.[settingKey] ?? defaultValue;
|
||||||
|
}, [conversation, defaultValue, optionType, settingKey, inputValue]);
|
||||||
|
|
||||||
|
const items = useMemo(() => {
|
||||||
|
if (_items != null) {
|
||||||
|
return _items;
|
||||||
|
}
|
||||||
|
return (_options ?? []).map((option) => ({
|
||||||
|
label: option,
|
||||||
|
value: option,
|
||||||
|
}));
|
||||||
|
}, [_options, _items]);
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
if (optionType === OptionTypes.Custom) {
|
||||||
|
setInputValue(value);
|
||||||
|
} else {
|
||||||
|
setOption(settingKey)(value);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[optionType, setOption, settingKey],
|
||||||
|
);
|
||||||
|
|
||||||
|
useParameterEffects({
|
||||||
|
preset,
|
||||||
|
settingKey,
|
||||||
|
defaultValue,
|
||||||
|
conversation,
|
||||||
|
inputValue,
|
||||||
|
setInputValue,
|
||||||
|
preventDelayedUpdate: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const options = items ?? _options ?? [];
|
||||||
|
if (options.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'flex flex-col items-center justify-start gap-6',
|
||||||
|
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<HoverCard openDelay={300}>
|
||||||
|
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||||
|
{showLabel === true && (
|
||||||
|
<div className="flex w-full justify-between">
|
||||||
|
<Label
|
||||||
|
htmlFor={`${settingKey}-dynamic-combobox`}
|
||||||
|
className="text-left text-sm font-medium"
|
||||||
|
>
|
||||||
|
{labelCode ? localize(label) ?? label : label || settingKey}
|
||||||
|
{showDefault && (
|
||||||
|
<small className="opacity-40">
|
||||||
|
({localize('com_endpoint_default')}: {defaultValue})
|
||||||
|
</small>
|
||||||
|
)}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<ControlCombobox
|
||||||
|
displayValue={selectedValue}
|
||||||
|
selectPlaceholder={
|
||||||
|
selectPlaceholderCode === true ? localize(selectPlaceholder) : selectPlaceholder
|
||||||
|
}
|
||||||
|
searchPlaceholder={
|
||||||
|
searchPlaceholderCode === true ? localize(searchPlaceholder) : searchPlaceholder
|
||||||
|
}
|
||||||
|
isCollapsed={isCollapsed}
|
||||||
|
ariaLabel={settingKey}
|
||||||
|
selectedValue={selectedValue ?? ''}
|
||||||
|
setValue={handleChange}
|
||||||
|
items={items}
|
||||||
|
SelectIcon={SelectIcon}
|
||||||
|
/>
|
||||||
|
</HoverCardTrigger>
|
||||||
|
{description && (
|
||||||
|
<OptionHover
|
||||||
|
description={descriptionCode ? localize(description) ?? description : description}
|
||||||
|
side={ESide.Left}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</HoverCard>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DynamicCombobox;
|
|
@ -9,19 +9,22 @@ import { ESide } from '~/common';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
function DynamicDropdown({
|
function DynamicDropdown({
|
||||||
label,
|
label = '',
|
||||||
settingKey,
|
settingKey,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
description,
|
description = '',
|
||||||
columnSpan,
|
columnSpan,
|
||||||
setOption,
|
setOption,
|
||||||
optionType,
|
optionType,
|
||||||
options,
|
options,
|
||||||
// type: _type,
|
// type: _type,
|
||||||
readonly = false,
|
readonly = false,
|
||||||
showDefault = true,
|
showLabel = true,
|
||||||
labelCode,
|
showDefault = false,
|
||||||
descriptionCode,
|
labelCode = false,
|
||||||
|
descriptionCode = false,
|
||||||
|
placeholder = '',
|
||||||
|
placeholderCode = false,
|
||||||
conversation,
|
conversation,
|
||||||
}: DynamicSettingProps) {
|
}: DynamicSettingProps) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
@ -64,24 +67,26 @@ function DynamicDropdown({
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-col items-center justify-start gap-6',
|
'flex flex-col items-center justify-start gap-6',
|
||||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full',
|
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<HoverCard openDelay={300}>
|
<HoverCard openDelay={300}>
|
||||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||||
<div className="flex w-full justify-between">
|
{showLabel === true && (
|
||||||
<Label
|
<div className="flex w-full justify-between">
|
||||||
htmlFor={`${settingKey}-dynamic-dropdown`}
|
<Label
|
||||||
className="text-left text-sm font-medium"
|
htmlFor={`${settingKey}-dynamic-dropdown`}
|
||||||
>
|
className="text-left text-sm font-medium"
|
||||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}
|
>
|
||||||
{showDefault && (
|
{labelCode ? localize(label) ?? label : label || settingKey}
|
||||||
<small className="opacity-40">
|
{showDefault && (
|
||||||
({localize('com_endpoint_default')}: {defaultValue})
|
<small className="opacity-40">
|
||||||
</small>
|
({localize('com_endpoint_default')}: {defaultValue})
|
||||||
)}
|
</small>
|
||||||
</Label>
|
)}
|
||||||
</div>
|
</Label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<SelectDropDown
|
<SelectDropDown
|
||||||
showLabel={false}
|
showLabel={false}
|
||||||
emptyTitle={true}
|
emptyTitle={true}
|
||||||
|
@ -91,11 +96,12 @@ function DynamicDropdown({
|
||||||
availableValues={options}
|
availableValues={options}
|
||||||
containerClassName="w-full"
|
containerClassName="w-full"
|
||||||
id={`${settingKey}-dynamic-dropdown`}
|
id={`${settingKey}-dynamic-dropdown`}
|
||||||
|
placeholder={placeholderCode ? localize(placeholder) ?? placeholder : placeholder}
|
||||||
/>
|
/>
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
{description && (
|
{description && (
|
||||||
<OptionHover
|
<OptionHover
|
||||||
description={descriptionCode ? localize(description) || description : description}
|
description={descriptionCode ? localize(description) ?? description : description}
|
||||||
side={ESide.Left}
|
side={ESide.Left}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -9,25 +9,25 @@ import OptionHover from './OptionHover';
|
||||||
import { ESide } from '~/common';
|
import { ESide } from '~/common';
|
||||||
|
|
||||||
function DynamicInput({
|
function DynamicInput({
|
||||||
label,
|
label = '',
|
||||||
settingKey,
|
settingKey,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
description,
|
description = '',
|
||||||
columnSpan,
|
columnSpan,
|
||||||
setOption,
|
setOption,
|
||||||
optionType,
|
optionType,
|
||||||
placeholder,
|
placeholder = '',
|
||||||
readonly = false,
|
readonly = false,
|
||||||
showDefault = true,
|
showDefault = false,
|
||||||
labelCode,
|
labelCode = false,
|
||||||
descriptionCode,
|
descriptionCode = false,
|
||||||
placeholderCode,
|
placeholderCode = false,
|
||||||
conversation,
|
conversation,
|
||||||
}: DynamicSettingProps) {
|
}: DynamicSettingProps) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { preset } = useChatContext();
|
const { preset } = useChatContext();
|
||||||
|
|
||||||
const [setInputValue, inputValue] = useDebouncedInput<string | null>({
|
const [setInputValue, inputValue, setLocalValue] = useDebouncedInput<string | null>({
|
||||||
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
|
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
|
||||||
initialValue:
|
initialValue:
|
||||||
optionType !== OptionTypes.Custom
|
optionType !== OptionTypes.Custom
|
||||||
|
@ -43,13 +43,13 @@ function DynamicInput({
|
||||||
defaultValue: typeof defaultValue === 'undefined' ? '' : defaultValue,
|
defaultValue: typeof defaultValue === 'undefined' ? '' : defaultValue,
|
||||||
conversation,
|
conversation,
|
||||||
inputValue,
|
inputValue,
|
||||||
setInputValue,
|
setInputValue: setLocalValue,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col items-center justify-start gap-6 ${
|
className={`flex flex-col items-center justify-start gap-6 ${
|
||||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full'
|
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<HoverCard openDelay={300}>
|
<HoverCard openDelay={300}>
|
||||||
|
@ -59,11 +59,11 @@ function DynamicInput({
|
||||||
htmlFor={`${settingKey}-dynamic-input`}
|
htmlFor={`${settingKey}-dynamic-input`}
|
||||||
className="text-left text-sm font-medium"
|
className="text-left text-sm font-medium"
|
||||||
>
|
>
|
||||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
{labelCode ? localize(label) ?? label : label || settingKey}{' '}
|
||||||
{showDefault && (
|
{showDefault && (
|
||||||
<small className="opacity-40">
|
<small className="opacity-40">
|
||||||
(
|
(
|
||||||
{typeof defaultValue === 'undefined' || !(defaultValue as string)?.length
|
{typeof defaultValue === 'undefined' || !(defaultValue as string).length
|
||||||
? localize('com_endpoint_default_blank')
|
? localize('com_endpoint_default_blank')
|
||||||
: `${localize('com_endpoint_default')}: ${defaultValue}`}
|
: `${localize('com_endpoint_default')}: ${defaultValue}`}
|
||||||
)
|
)
|
||||||
|
@ -76,13 +76,13 @@ function DynamicInput({
|
||||||
disabled={readonly}
|
disabled={readonly}
|
||||||
value={inputValue ?? ''}
|
value={inputValue ?? ''}
|
||||||
onChange={setInputValue}
|
onChange={setInputValue}
|
||||||
placeholder={placeholderCode ? localize(placeholder ?? '') || placeholder : placeholder}
|
placeholder={placeholderCode ? localize(placeholder) ?? placeholder : placeholder}
|
||||||
className={cn(defaultTextProps, 'flex h-10 max-h-10 w-full resize-none px-3 py-2')}
|
className={cn(defaultTextProps, 'flex h-10 max-h-10 w-full resize-none px-3 py-2')}
|
||||||
/>
|
/>
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
{description && (
|
{description && (
|
||||||
<OptionHover
|
<OptionHover
|
||||||
description={descriptionCode ? localize(description) || description : description}
|
description={descriptionCode ? localize(description) ?? description : description}
|
||||||
side={ESide.Left}
|
side={ESide.Left}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -9,19 +9,19 @@ import { useChatContext } from '~/Providers';
|
||||||
import OptionHover from './OptionHover';
|
import OptionHover from './OptionHover';
|
||||||
|
|
||||||
function DynamicInputNumber({
|
function DynamicInputNumber({
|
||||||
label,
|
label = '',
|
||||||
settingKey,
|
settingKey,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
description,
|
description = '',
|
||||||
columnSpan,
|
columnSpan,
|
||||||
setOption,
|
setOption,
|
||||||
optionType,
|
optionType,
|
||||||
readonly = false,
|
readonly = false,
|
||||||
showDefault = true,
|
showDefault = false,
|
||||||
labelCode,
|
labelCode = false,
|
||||||
descriptionCode,
|
descriptionCode = false,
|
||||||
placeholderCode,
|
placeholderCode = false,
|
||||||
placeholder,
|
placeholder = '',
|
||||||
conversation,
|
conversation,
|
||||||
range,
|
range,
|
||||||
className = '',
|
className = '',
|
||||||
|
@ -30,7 +30,7 @@ function DynamicInputNumber({
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { preset } = useChatContext();
|
const { preset } = useChatContext();
|
||||||
|
|
||||||
const [setInputValue, inputValue] = useDebouncedInput<ValueType | null>({
|
const [setInputValue, inputValue, setLocalValue] = useDebouncedInput<ValueType | null>({
|
||||||
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
|
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
|
||||||
initialValue:
|
initialValue:
|
||||||
optionType !== OptionTypes.Custom
|
optionType !== OptionTypes.Custom
|
||||||
|
@ -46,14 +46,14 @@ function DynamicInputNumber({
|
||||||
defaultValue: typeof defaultValue === 'undefined' ? '' : defaultValue,
|
defaultValue: typeof defaultValue === 'undefined' ? '' : defaultValue,
|
||||||
conversation,
|
conversation,
|
||||||
inputValue,
|
inputValue,
|
||||||
setInputValue,
|
setInputValue: setLocalValue,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-col items-center justify-start gap-6',
|
'flex flex-col items-center justify-start gap-6',
|
||||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full',
|
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@ -64,7 +64,7 @@ function DynamicInputNumber({
|
||||||
htmlFor={`${settingKey}-dynamic-setting`}
|
htmlFor={`${settingKey}-dynamic-setting`}
|
||||||
className="text-left text-sm font-medium"
|
className="text-left text-sm font-medium"
|
||||||
>
|
>
|
||||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
{labelCode ? localize(label) ?? label : label || settingKey}{' '}
|
||||||
{showDefault && (
|
{showDefault && (
|
||||||
<small className="opacity-40">
|
<small className="opacity-40">
|
||||||
({localize('com_endpoint_default')}: {defaultValue})
|
({localize('com_endpoint_default')}: {defaultValue})
|
||||||
|
@ -79,9 +79,7 @@ function DynamicInputNumber({
|
||||||
min={range?.min}
|
min={range?.min}
|
||||||
max={range?.max}
|
max={range?.max}
|
||||||
step={range?.step}
|
step={range?.step}
|
||||||
placeholder={
|
placeholder={placeholderCode ? localize(placeholder) ?? placeholder : placeholder}
|
||||||
placeholderCode ? localize(placeholder ?? '') || placeholder : placeholder
|
|
||||||
}
|
|
||||||
controls={false}
|
controls={false}
|
||||||
className={cn(
|
className={cn(
|
||||||
defaultTextProps,
|
defaultTextProps,
|
||||||
|
@ -96,7 +94,7 @@ function DynamicInputNumber({
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
{description && (
|
{description && (
|
||||||
<OptionHover
|
<OptionHover
|
||||||
description={descriptionCode ? localize(description) || description : description}
|
description={descriptionCode ? localize(description) ?? description : description}
|
||||||
side={ESide.Left}
|
side={ESide.Left}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -9,27 +9,30 @@ import { useChatContext } from '~/Providers';
|
||||||
import OptionHover from './OptionHover';
|
import OptionHover from './OptionHover';
|
||||||
|
|
||||||
function DynamicSlider({
|
function DynamicSlider({
|
||||||
label,
|
label = '',
|
||||||
settingKey,
|
settingKey,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
range,
|
range,
|
||||||
description,
|
description = '',
|
||||||
columnSpan,
|
columnSpan,
|
||||||
setOption,
|
setOption,
|
||||||
optionType,
|
optionType,
|
||||||
options,
|
options,
|
||||||
readonly = false,
|
readonly = false,
|
||||||
showDefault = true,
|
showDefault = false,
|
||||||
includeInput = true,
|
includeInput = true,
|
||||||
labelCode,
|
labelCode = false,
|
||||||
descriptionCode,
|
descriptionCode = false,
|
||||||
conversation,
|
conversation,
|
||||||
}: DynamicSettingProps) {
|
}: DynamicSettingProps) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { preset } = useChatContext();
|
const { preset } = useChatContext();
|
||||||
const isEnum = useMemo(() => !range && options && options.length > 0, [options, range]);
|
const isEnum = useMemo(
|
||||||
|
() => (!range && options && options.length > 0) ?? false,
|
||||||
|
[options, range],
|
||||||
|
);
|
||||||
|
|
||||||
const [setInputValue, inputValue] = useDebouncedInput<string | number>({
|
const [setInputValue, inputValue, setLocalValue] = useDebouncedInput<string | number>({
|
||||||
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
|
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
|
||||||
initialValue: optionType !== OptionTypes.Custom ? conversation?.[settingKey] : defaultValue,
|
initialValue: optionType !== OptionTypes.Custom ? conversation?.[settingKey] : defaultValue,
|
||||||
setter: () => ({}),
|
setter: () => ({}),
|
||||||
|
@ -43,7 +46,7 @@ function DynamicSlider({
|
||||||
defaultValue,
|
defaultValue,
|
||||||
conversation,
|
conversation,
|
||||||
inputValue,
|
inputValue,
|
||||||
setInputValue,
|
setInputValue: setLocalValue,
|
||||||
preventDelayedUpdate: isEnum,
|
preventDelayedUpdate: isEnum,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -87,6 +90,16 @@ function DynamicSlider({
|
||||||
[isEnum, setInputValue, valueToEnumOption],
|
[isEnum, setInputValue, valueToEnumOption],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const max = useMemo(() => {
|
||||||
|
if (isEnum && options) {
|
||||||
|
return options.length - 1;
|
||||||
|
} else if (range) {
|
||||||
|
return range.max;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}, [isEnum, options, range]);
|
||||||
|
|
||||||
if (!range && !isEnum) {
|
if (!range && !isEnum) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -94,18 +107,18 @@ function DynamicSlider({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex flex-col items-center justify-start gap-6',
|
'flex flex-col items-center justify-start gap-2',
|
||||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full',
|
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<HoverCard openDelay={300}>
|
<HoverCard openDelay={300}>
|
||||||
<HoverCardTrigger className="grid w-full items-center gap-2">
|
<HoverCardTrigger className="grid w-full items-center gap-2">
|
||||||
<div className="flex justify-between">
|
<div className="flex w-full items-center justify-between">
|
||||||
<Label
|
<Label
|
||||||
htmlFor={`${settingKey}-dynamic-setting`}
|
htmlFor={`${settingKey}-dynamic-setting`}
|
||||||
className="text-left text-sm font-medium"
|
className="text-left text-sm font-medium"
|
||||||
>
|
>
|
||||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
{labelCode ? localize(label) ?? label : label || settingKey}{' '}
|
||||||
{showDefault && (
|
{showDefault && (
|
||||||
<small className="opacity-40">
|
<small className="opacity-40">
|
||||||
({localize('com_endpoint_default')}: {defaultValue})
|
({localize('com_endpoint_default')}: {defaultValue})
|
||||||
|
@ -156,15 +169,16 @@ function DynamicSlider({
|
||||||
]}
|
]}
|
||||||
onValueChange={(value) => handleValueChange(value[0])}
|
onValueChange={(value) => handleValueChange(value[0])}
|
||||||
doubleClickHandler={() => setInputValue(defaultValue as string | number)}
|
doubleClickHandler={() => setInputValue(defaultValue as string | number)}
|
||||||
max={isEnum && options ? options.length - 1 : range ? range.max : 0}
|
max={max}
|
||||||
min={range ? range.min : 0}
|
min={range ? range.min : 0}
|
||||||
step={range ? range.step ?? 1 : 1}
|
step={range ? range.step ?? 1 : 1}
|
||||||
className="flex h-4 w-full"
|
className="flex h-4 w-full"
|
||||||
|
trackClassName="bg-surface-hover"
|
||||||
/>
|
/>
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
{description && (
|
{description && (
|
||||||
<OptionHover
|
<OptionHover
|
||||||
description={descriptionCode ? localize(description) || description : description}
|
description={descriptionCode ? localize(description) ?? description : description}
|
||||||
side={ESide.Left}
|
side={ESide.Left}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -8,17 +8,17 @@ import OptionHover from './OptionHover';
|
||||||
import { ESide } from '~/common';
|
import { ESide } from '~/common';
|
||||||
|
|
||||||
function DynamicSwitch({
|
function DynamicSwitch({
|
||||||
label,
|
label = '',
|
||||||
settingKey,
|
settingKey,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
description,
|
description = '',
|
||||||
columnSpan,
|
columnSpan,
|
||||||
setOption,
|
setOption,
|
||||||
optionType,
|
optionType,
|
||||||
readonly = false,
|
readonly = false,
|
||||||
showDefault = true,
|
showDefault = false,
|
||||||
labelCode,
|
labelCode = false,
|
||||||
descriptionCode,
|
descriptionCode = false,
|
||||||
conversation,
|
conversation,
|
||||||
}: DynamicSettingProps) {
|
}: DynamicSettingProps) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
|
@ -55,7 +55,7 @@ function DynamicSwitch({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col items-center justify-start gap-6 ${
|
className={`flex flex-col items-center justify-start gap-6 ${
|
||||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full'
|
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<HoverCard openDelay={300}>
|
<HoverCard openDelay={300}>
|
||||||
|
@ -65,10 +65,11 @@ function DynamicSwitch({
|
||||||
htmlFor={`${settingKey}-dynamic-switch`}
|
htmlFor={`${settingKey}-dynamic-switch`}
|
||||||
className="text-left text-sm font-medium"
|
className="text-left text-sm font-medium"
|
||||||
>
|
>
|
||||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
{labelCode ? localize(label) ?? label : label || settingKey}{' '}
|
||||||
{showDefault && (
|
{showDefault && (
|
||||||
<small className="opacity-40">
|
<small className="opacity-40">
|
||||||
({localize('com_endpoint_default')}: {defaultValue ? 'com_ui_on' : 'com_ui_off'})
|
({localize('com_endpoint_default')}:{' '}
|
||||||
|
{defaultValue != null ? 'com_ui_on' : 'com_ui_off'})
|
||||||
</small>
|
</small>
|
||||||
)}
|
)}
|
||||||
</Label>
|
</Label>
|
||||||
|
@ -83,7 +84,7 @@ function DynamicSwitch({
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
{description && (
|
{description && (
|
||||||
<OptionHover
|
<OptionHover
|
||||||
description={descriptionCode ? localize(description) || description : description}
|
description={descriptionCode ? localize(description) ?? description : description}
|
||||||
side={ESide.Left}
|
side={ESide.Left}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -10,19 +10,19 @@ import OptionHover from './OptionHover';
|
||||||
import { ESide } from '~/common';
|
import { ESide } from '~/common';
|
||||||
|
|
||||||
function DynamicTags({
|
function DynamicTags({
|
||||||
label,
|
label = '',
|
||||||
settingKey,
|
settingKey,
|
||||||
defaultValue = [],
|
defaultValue = [],
|
||||||
description,
|
description = '',
|
||||||
columnSpan,
|
columnSpan,
|
||||||
setOption,
|
setOption,
|
||||||
optionType,
|
optionType,
|
||||||
placeholder,
|
placeholder = '',
|
||||||
readonly = false,
|
readonly = false,
|
||||||
showDefault = true,
|
showDefault = false,
|
||||||
labelCode,
|
labelCode = false,
|
||||||
descriptionCode,
|
descriptionCode = false,
|
||||||
placeholderCode,
|
placeholderCode = false,
|
||||||
descriptionSide = ESide.Left,
|
descriptionSide = ESide.Left,
|
||||||
conversation,
|
conversation,
|
||||||
minTags,
|
minTags,
|
||||||
|
@ -65,7 +65,7 @@ function DynamicTags({
|
||||||
return defaultValue ?? [];
|
return defaultValue ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return conversation?.[settingKey];
|
return conversation[settingKey];
|
||||||
}, [conversation, defaultValue, optionType, settingKey, tags]);
|
}, [conversation, defaultValue, optionType, settingKey, tags]);
|
||||||
|
|
||||||
const onTagRemove = useCallback(
|
const onTagRemove = useCallback(
|
||||||
|
@ -74,7 +74,7 @@ function DynamicTags({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (minTags && currentTags.length <= minTags) {
|
if (minTags != null && currentTags.length <= minTags) {
|
||||||
showToast({
|
showToast({
|
||||||
message: localize('com_ui_min_tags', minTags + ''),
|
message: localize('com_ui_min_tags', minTags + ''),
|
||||||
status: 'warning',
|
status: 'warning',
|
||||||
|
@ -93,7 +93,7 @@ function DynamicTags({
|
||||||
}
|
}
|
||||||
|
|
||||||
let update = [...(currentTags ?? []), tagText];
|
let update = [...(currentTags ?? []), tagText];
|
||||||
if (maxTags && update.length > maxTags) {
|
if (maxTags != null && update.length > maxTags) {
|
||||||
showToast({
|
showToast({
|
||||||
message: localize('com_ui_max_tags', maxTags + ''),
|
message: localize('com_ui_max_tags', maxTags + ''),
|
||||||
status: 'warning',
|
status: 'warning',
|
||||||
|
@ -117,7 +117,7 @@ function DynamicTags({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col items-center justify-start gap-6 ${
|
className={`flex flex-col items-center justify-start gap-6 ${
|
||||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full'
|
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<HoverCard openDelay={300}>
|
<HoverCard openDelay={300}>
|
||||||
|
@ -127,11 +127,11 @@ function DynamicTags({
|
||||||
htmlFor={`${settingKey}-dynamic-input`}
|
htmlFor={`${settingKey}-dynamic-input`}
|
||||||
className="text-left text-sm font-medium"
|
className="text-left text-sm font-medium"
|
||||||
>
|
>
|
||||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
{labelCode ? localize(label) ?? label : label || settingKey}{' '}
|
||||||
{showDefault && (
|
{showDefault && (
|
||||||
<small className="opacity-40">
|
<small className="opacity-40">
|
||||||
(
|
(
|
||||||
{typeof defaultValue === 'undefined' || !(defaultValue as string)?.length
|
{typeof defaultValue === 'undefined' || !(defaultValue as string).length
|
||||||
? localize('com_endpoint_default_blank')
|
? localize('com_endpoint_default_blank')
|
||||||
: `${localize('com_endpoint_default')}: ${defaultValue}`}
|
: `${localize('com_endpoint_default')}: ${defaultValue}`}
|
||||||
)
|
)
|
||||||
|
@ -171,9 +171,7 @@ function DynamicTags({
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onChange={(e) => setTagText(e.target.value)}
|
onChange={(e) => setTagText(e.target.value)}
|
||||||
placeholder={
|
placeholder={placeholderCode ? localize(placeholder) ?? placeholder : placeholder}
|
||||||
placeholderCode ? localize(placeholder ?? '') || placeholder : placeholder
|
|
||||||
}
|
|
||||||
className={cn(defaultTextProps, 'flex h-10 max-h-10 px-3 py-2')}
|
className={cn(defaultTextProps, 'flex h-10 max-h-10 px-3 py-2')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -181,7 +179,7 @@ function DynamicTags({
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
{description && (
|
{description && (
|
||||||
<OptionHover
|
<OptionHover
|
||||||
description={descriptionCode ? localize(description) || description : description}
|
description={descriptionCode ? localize(description) ?? description : description}
|
||||||
side={descriptionSide as ESide}
|
side={descriptionSide as ESide}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -9,25 +9,25 @@ import OptionHover from './OptionHover';
|
||||||
import { ESide } from '~/common';
|
import { ESide } from '~/common';
|
||||||
|
|
||||||
function DynamicTextarea({
|
function DynamicTextarea({
|
||||||
label,
|
label = '',
|
||||||
settingKey,
|
settingKey,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
description,
|
description = '',
|
||||||
columnSpan,
|
columnSpan,
|
||||||
setOption,
|
setOption,
|
||||||
optionType,
|
optionType,
|
||||||
placeholder,
|
placeholder = '',
|
||||||
readonly = false,
|
readonly = false,
|
||||||
showDefault = true,
|
showDefault = false,
|
||||||
labelCode,
|
labelCode = false,
|
||||||
descriptionCode,
|
descriptionCode = false,
|
||||||
placeholderCode,
|
placeholderCode = false,
|
||||||
conversation,
|
conversation,
|
||||||
}: DynamicSettingProps) {
|
}: DynamicSettingProps) {
|
||||||
const localize = useLocalize();
|
const localize = useLocalize();
|
||||||
const { preset } = useChatContext();
|
const { preset } = useChatContext();
|
||||||
|
|
||||||
const [setInputValue, inputValue] = useDebouncedInput<string | null>({
|
const [setInputValue, inputValue, setLocalValue] = useDebouncedInput<string | null>({
|
||||||
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
|
optionKey: optionType !== OptionTypes.Custom ? settingKey : undefined,
|
||||||
initialValue:
|
initialValue:
|
||||||
optionType !== OptionTypes.Custom
|
optionType !== OptionTypes.Custom
|
||||||
|
@ -43,13 +43,13 @@ function DynamicTextarea({
|
||||||
defaultValue: typeof defaultValue === 'undefined' ? '' : defaultValue,
|
defaultValue: typeof defaultValue === 'undefined' ? '' : defaultValue,
|
||||||
conversation,
|
conversation,
|
||||||
inputValue,
|
inputValue,
|
||||||
setInputValue,
|
setInputValue: setLocalValue,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex flex-col items-center justify-start gap-6 ${
|
className={`flex flex-col items-center justify-start gap-6 ${
|
||||||
columnSpan ? `col-span-${columnSpan}` : 'col-span-full'
|
columnSpan != null ? `col-span-${columnSpan}` : 'col-span-full'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<HoverCard openDelay={300}>
|
<HoverCard openDelay={300}>
|
||||||
|
@ -59,11 +59,11 @@ function DynamicTextarea({
|
||||||
htmlFor={`${settingKey}-dynamic-textarea`}
|
htmlFor={`${settingKey}-dynamic-textarea`}
|
||||||
className="text-left text-sm font-medium"
|
className="text-left text-sm font-medium"
|
||||||
>
|
>
|
||||||
{labelCode ? localize(label ?? '') || label : label ?? settingKey}{' '}
|
{labelCode ? localize(label) ?? label : label || settingKey}{' '}
|
||||||
{showDefault && (
|
{showDefault && (
|
||||||
<small className="opacity-40">
|
<small className="opacity-40">
|
||||||
(
|
(
|
||||||
{typeof defaultValue === 'undefined' || !(defaultValue as string)?.length
|
{typeof defaultValue === 'undefined' || !(defaultValue as string).length
|
||||||
? localize('com_endpoint_default_blank')
|
? localize('com_endpoint_default_blank')
|
||||||
: `${localize('com_endpoint_default')}: ${defaultValue}`}
|
: `${localize('com_endpoint_default')}: ${defaultValue}`}
|
||||||
)
|
)
|
||||||
|
@ -76,7 +76,7 @@ function DynamicTextarea({
|
||||||
disabled={readonly}
|
disabled={readonly}
|
||||||
value={inputValue ?? ''}
|
value={inputValue ?? ''}
|
||||||
onChange={setInputValue}
|
onChange={setInputValue}
|
||||||
placeholder={placeholderCode ? localize(placeholder ?? '') || placeholder : placeholder}
|
placeholder={placeholderCode ? localize(placeholder) ?? placeholder : placeholder}
|
||||||
className={cn(
|
className={cn(
|
||||||
defaultTextProps,
|
defaultTextProps,
|
||||||
// TODO: configurable max height
|
// TODO: configurable max height
|
||||||
|
@ -86,7 +86,7 @@ function DynamicTextarea({
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
{description && (
|
{description && (
|
||||||
<OptionHover
|
<OptionHover
|
||||||
description={descriptionCode ? localize(description) || description : description}
|
description={descriptionCode ? localize(description) ?? description : description}
|
||||||
side={ESide.Left}
|
side={ESide.Left}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,203 +1,46 @@
|
||||||
import { ComponentTypes } from 'librechat-data-provider';
|
import React, { useMemo, useState, useCallback } from 'react';
|
||||||
import type {
|
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
|
||||||
DynamicSettingProps,
|
import { getSettingsKeys, tPresetUpdateSchema } from 'librechat-data-provider';
|
||||||
SettingDefinition,
|
import type { TPreset } from 'librechat-data-provider';
|
||||||
SettingsConfiguration,
|
import { SaveAsPresetDialog } from '~/components/Endpoints';
|
||||||
} from 'librechat-data-provider';
|
import { useSetIndexOptions, useLocalize } from '~/hooks';
|
||||||
import { useSetIndexOptions } from '~/hooks';
|
import { componentMapping } from './components';
|
||||||
import { useChatContext } from '~/Providers';
|
import { useChatContext } from '~/Providers';
|
||||||
import {
|
import { settings } from './settings';
|
||||||
DynamicDropdown,
|
|
||||||
DynamicCheckbox,
|
|
||||||
DynamicTextarea,
|
|
||||||
DynamicSlider,
|
|
||||||
DynamicSwitch,
|
|
||||||
DynamicInput,
|
|
||||||
DynamicTags,
|
|
||||||
} from './';
|
|
||||||
|
|
||||||
const settingsConfiguration: SettingsConfiguration = [
|
|
||||||
{
|
|
||||||
key: 'temperature',
|
|
||||||
label: 'com_endpoint_temperature',
|
|
||||||
labelCode: true,
|
|
||||||
description: 'com_endpoint_openai_temp',
|
|
||||||
descriptionCode: true,
|
|
||||||
type: 'number',
|
|
||||||
default: 1,
|
|
||||||
range: {
|
|
||||||
min: 0,
|
|
||||||
max: 2,
|
|
||||||
step: 0.01,
|
|
||||||
},
|
|
||||||
component: 'slider',
|
|
||||||
optionType: 'model',
|
|
||||||
// columnSpan: 2,
|
|
||||||
// includeInput: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'top_p',
|
|
||||||
label: 'com_endpoint_top_p',
|
|
||||||
labelCode: true,
|
|
||||||
description: 'com_endpoint_openai_topp',
|
|
||||||
descriptionCode: true,
|
|
||||||
type: 'number',
|
|
||||||
default: 1,
|
|
||||||
range: {
|
|
||||||
min: 0,
|
|
||||||
max: 1,
|
|
||||||
step: 0.01,
|
|
||||||
},
|
|
||||||
component: 'slider',
|
|
||||||
optionType: 'model',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'presence_penalty',
|
|
||||||
label: 'com_endpoint_presence_penalty',
|
|
||||||
labelCode: true,
|
|
||||||
description: 'com_endpoint_openai_pres',
|
|
||||||
descriptionCode: true,
|
|
||||||
type: 'number',
|
|
||||||
default: 0,
|
|
||||||
range: {
|
|
||||||
min: -2,
|
|
||||||
max: 2,
|
|
||||||
step: 0.01,
|
|
||||||
},
|
|
||||||
component: 'slider',
|
|
||||||
optionType: 'model',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'frequency_penalty',
|
|
||||||
label: 'com_endpoint_frequency_penalty',
|
|
||||||
labelCode: true,
|
|
||||||
description: 'com_endpoint_openai_freq',
|
|
||||||
descriptionCode: true,
|
|
||||||
type: 'number',
|
|
||||||
default: 0,
|
|
||||||
range: {
|
|
||||||
min: -2,
|
|
||||||
max: 2,
|
|
||||||
step: 0.01,
|
|
||||||
},
|
|
||||||
component: 'slider',
|
|
||||||
optionType: 'model',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'chatGptLabel',
|
|
||||||
label: 'com_endpoint_custom_name',
|
|
||||||
labelCode: true,
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
component: 'input',
|
|
||||||
placeholder: 'com_endpoint_openai_custom_name_placeholder',
|
|
||||||
placeholderCode: true,
|
|
||||||
optionType: 'conversation',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'promptPrefix',
|
|
||||||
label: 'com_endpoint_prompt_prefix',
|
|
||||||
labelCode: true,
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
component: 'textarea',
|
|
||||||
placeholder: 'com_endpoint_openai_prompt_prefix_placeholder',
|
|
||||||
placeholderCode: true,
|
|
||||||
optionType: 'conversation',
|
|
||||||
// columnSpan: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'resendFiles',
|
|
||||||
label: 'com_endpoint_plug_resend_files',
|
|
||||||
labelCode: true,
|
|
||||||
description: 'com_endpoint_openai_resend_files',
|
|
||||||
descriptionCode: true,
|
|
||||||
type: 'boolean',
|
|
||||||
default: true,
|
|
||||||
component: 'switch',
|
|
||||||
optionType: 'conversation',
|
|
||||||
showDefault: false,
|
|
||||||
columnSpan: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'imageDetail',
|
|
||||||
label: 'com_endpoint_plug_image_detail',
|
|
||||||
labelCode: true,
|
|
||||||
description: 'com_endpoint_openai_detail',
|
|
||||||
descriptionCode: true,
|
|
||||||
type: 'enum',
|
|
||||||
default: 'auto',
|
|
||||||
options: ['low', 'auto', 'high'],
|
|
||||||
optionType: 'conversation',
|
|
||||||
component: 'slider',
|
|
||||||
showDefault: false,
|
|
||||||
columnSpan: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'stop',
|
|
||||||
label: 'com_endpoint_stop',
|
|
||||||
labelCode: true,
|
|
||||||
description: 'com_endpoint_openai_stop',
|
|
||||||
descriptionCode: true,
|
|
||||||
placeholder: 'com_endpoint_stop_placeholder',
|
|
||||||
placeholderCode: true,
|
|
||||||
type: 'array',
|
|
||||||
default: [],
|
|
||||||
component: 'tags',
|
|
||||||
optionType: 'conversation',
|
|
||||||
columnSpan: 4,
|
|
||||||
minTags: 1,
|
|
||||||
maxTags: 4,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const componentMapping: Record<ComponentTypes, React.ComponentType<DynamicSettingProps>> = {
|
|
||||||
[ComponentTypes.Slider]: DynamicSlider,
|
|
||||||
[ComponentTypes.Dropdown]: DynamicDropdown,
|
|
||||||
[ComponentTypes.Switch]: DynamicSwitch,
|
|
||||||
[ComponentTypes.Textarea]: DynamicTextarea,
|
|
||||||
[ComponentTypes.Input]: DynamicInput,
|
|
||||||
[ComponentTypes.Checkbox]: DynamicCheckbox,
|
|
||||||
[ComponentTypes.Tags]: DynamicTags,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function Parameters() {
|
export default function Parameters() {
|
||||||
|
const localize = useLocalize();
|
||||||
const { conversation } = useChatContext();
|
const { conversation } = useChatContext();
|
||||||
const { setOption } = useSetIndexOptions();
|
const { setOption } = useSetIndexOptions();
|
||||||
|
|
||||||
const temperature = settingsConfiguration.find(
|
const [isDialogOpen, setIsDialogOpen] = useState(false);
|
||||||
(setting) => setting.key === 'temperature',
|
const [preset, setPreset] = useState<TPreset | null>(null);
|
||||||
) as SettingDefinition;
|
|
||||||
const TempComponent = componentMapping[temperature.component];
|
|
||||||
const { key: temp, default: tempDefault, ...tempSettings } = temperature;
|
|
||||||
|
|
||||||
const imageDetail = settingsConfiguration.find(
|
const { data: endpointsConfig } = useGetEndpointsQuery();
|
||||||
(setting) => setting.key === 'imageDetail',
|
|
||||||
) as SettingDefinition;
|
|
||||||
const DetailComponent = componentMapping[imageDetail.component];
|
|
||||||
const { key: detail, default: detailDefault, ...detailSettings } = imageDetail;
|
|
||||||
|
|
||||||
const resendFiles = settingsConfiguration.find(
|
const bedrockRegions = useMemo(() => {
|
||||||
(setting) => setting.key === 'resendFiles',
|
return endpointsConfig?.[conversation?.endpoint ?? '']?.availableRegions ?? [];
|
||||||
) as SettingDefinition;
|
}, [endpointsConfig, conversation?.endpoint]);
|
||||||
const Switch = componentMapping[resendFiles.component];
|
|
||||||
const { key: switchKey, default: switchDefault, ...switchSettings } = resendFiles;
|
|
||||||
|
|
||||||
const promptPrefix = settingsConfiguration.find(
|
const parameters = useMemo(() => {
|
||||||
(setting) => setting.key === 'promptPrefix',
|
const [combinedKey, endpointKey] = getSettingsKeys(
|
||||||
) as SettingDefinition;
|
conversation?.endpoint ?? '',
|
||||||
const Textarea = componentMapping[promptPrefix.component];
|
conversation?.model ?? '',
|
||||||
const { key: textareaKey, default: textareaDefault, ...textareaSettings } = promptPrefix;
|
);
|
||||||
|
return settings[combinedKey] ?? settings[endpointKey];
|
||||||
|
}, [conversation]);
|
||||||
|
|
||||||
const chatGptLabel = settingsConfiguration.find(
|
const openDialog = useCallback(() => {
|
||||||
(setting) => setting.key === 'chatGptLabel',
|
const newPreset = tPresetUpdateSchema.parse({
|
||||||
) as SettingDefinition;
|
...conversation,
|
||||||
const Input = componentMapping[chatGptLabel.component];
|
}) as TPreset;
|
||||||
const { key: inputKey, default: inputDefault, ...inputSettings } = chatGptLabel;
|
setPreset(newPreset);
|
||||||
|
setIsDialogOpen(true);
|
||||||
|
}, [conversation]);
|
||||||
|
|
||||||
const stop = settingsConfiguration.find((setting) => setting.key === 'stop') as SettingDefinition;
|
if (!parameters) {
|
||||||
const Tags = componentMapping[stop.component];
|
return null;
|
||||||
const { key: stopKey, default: stopDefault, ...stopSettings } = stop;
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-auto max-w-full overflow-x-hidden p-3">
|
<div className="h-auto max-w-full overflow-x-hidden p-3">
|
||||||
|
@ -205,49 +48,38 @@ export default function Parameters() {
|
||||||
{' '}
|
{' '}
|
||||||
{/* This is the parent element containing all settings */}
|
{/* This is the parent element containing all settings */}
|
||||||
{/* Below is an example of an applied dynamic setting, each be contained by a div with the column span specified */}
|
{/* Below is an example of an applied dynamic setting, each be contained by a div with the column span specified */}
|
||||||
<Input
|
{parameters.map((setting) => {
|
||||||
settingKey={inputKey}
|
const Component = componentMapping[setting.component];
|
||||||
defaultValue={inputDefault}
|
const { key, default: defaultValue, ...rest } = setting;
|
||||||
{...inputSettings}
|
|
||||||
setOption={setOption}
|
if (key === 'region' && bedrockRegions.length) {
|
||||||
conversation={conversation}
|
rest.options = bedrockRegions;
|
||||||
/>
|
}
|
||||||
<Textarea
|
|
||||||
settingKey={textareaKey}
|
return (
|
||||||
defaultValue={textareaDefault}
|
<Component
|
||||||
{...textareaSettings}
|
key={key}
|
||||||
setOption={setOption}
|
settingKey={key}
|
||||||
conversation={conversation}
|
defaultValue={defaultValue}
|
||||||
/>
|
{...rest}
|
||||||
<TempComponent
|
setOption={setOption}
|
||||||
settingKey={temp}
|
conversation={conversation}
|
||||||
defaultValue={tempDefault}
|
/>
|
||||||
{...tempSettings}
|
);
|
||||||
setOption={setOption}
|
})}
|
||||||
conversation={conversation}
|
|
||||||
/>
|
|
||||||
<Switch
|
|
||||||
settingKey={switchKey}
|
|
||||||
defaultValue={switchDefault}
|
|
||||||
{...switchSettings}
|
|
||||||
setOption={setOption}
|
|
||||||
conversation={conversation}
|
|
||||||
/>
|
|
||||||
<DetailComponent
|
|
||||||
settingKey={detail}
|
|
||||||
defaultValue={detailDefault}
|
|
||||||
{...detailSettings}
|
|
||||||
setOption={setOption}
|
|
||||||
conversation={conversation}
|
|
||||||
/>
|
|
||||||
<Tags
|
|
||||||
settingKey={stopKey}
|
|
||||||
defaultValue={stopDefault}
|
|
||||||
{...stopSettings}
|
|
||||||
setOption={setOption}
|
|
||||||
conversation={conversation}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mt-6 flex justify-center">
|
||||||
|
<button
|
||||||
|
onClick={openDialog}
|
||||||
|
className="btn btn-primary focus:shadow-outline flex w-full items-center justify-center px-4 py-2 font-semibold text-white hover:bg-green-600 focus:border-green-500"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{localize('com_endpoint_save_as_preset')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{preset && (
|
||||||
|
<SaveAsPresetDialog open={isDialogOpen} onOpenChange={setIsDialogOpen} preset={preset} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
23
client/src/components/SidePanel/Parameters/components.tsx
Normal file
23
client/src/components/SidePanel/Parameters/components.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentTypes } from 'librechat-data-provider';
|
||||||
|
import type { DynamicSettingProps } from 'librechat-data-provider';
|
||||||
|
import {
|
||||||
|
DynamicCombobox,
|
||||||
|
DynamicDropdown,
|
||||||
|
DynamicCheckbox,
|
||||||
|
DynamicTextarea,
|
||||||
|
DynamicSlider,
|
||||||
|
DynamicSwitch,
|
||||||
|
DynamicInput,
|
||||||
|
DynamicTags,
|
||||||
|
} from './';
|
||||||
|
|
||||||
|
export const componentMapping: Record<ComponentTypes, React.ComponentType<DynamicSettingProps>> = {
|
||||||
|
[ComponentTypes.Slider]: DynamicSlider,
|
||||||
|
[ComponentTypes.Dropdown]: DynamicDropdown,
|
||||||
|
[ComponentTypes.Switch]: DynamicSwitch,
|
||||||
|
[ComponentTypes.Textarea]: DynamicTextarea,
|
||||||
|
[ComponentTypes.Input]: DynamicInput,
|
||||||
|
[ComponentTypes.Checkbox]: DynamicCheckbox,
|
||||||
|
[ComponentTypes.Tags]: DynamicTags,
|
||||||
|
[ComponentTypes.Combobox]: DynamicCombobox,
|
||||||
|
};
|
|
@ -1,4 +1,5 @@
|
||||||
export { default as DynamicInputNumber } from './DynamicInputNumber';
|
export { default as DynamicInputNumber } from './DynamicInputNumber';
|
||||||
|
export { default as DynamicCombobox } from './DynamicCombobox';
|
||||||
export { default as DynamicDropdown } from './DynamicDropdown';
|
export { default as DynamicDropdown } from './DynamicDropdown';
|
||||||
export { default as DynamicCheckbox } from './DynamicCheckbox';
|
export { default as DynamicCheckbox } from './DynamicCheckbox';
|
||||||
export { default as DynamicTextarea } from './DynamicTextarea';
|
export { default as DynamicTextarea } from './DynamicTextarea';
|
||||||
|
@ -6,3 +7,4 @@ export { default as DynamicSlider } from './DynamicSlider';
|
||||||
export { default as DynamicSwitch } from './DynamicSwitch';
|
export { default as DynamicSwitch } from './DynamicSwitch';
|
||||||
export { default as DynamicInput } from './DynamicInput';
|
export { default as DynamicInput } from './DynamicInput';
|
||||||
export { default as DynamicTags } from './DynamicTags';
|
export { default as DynamicTags } from './DynamicTags';
|
||||||
|
export { default as OptionHoverAlt } from './OptionHover';
|
||||||
|
|
354
client/src/components/SidePanel/Parameters/settings.ts
Normal file
354
client/src/components/SidePanel/Parameters/settings.ts
Normal file
|
@ -0,0 +1,354 @@
|
||||||
|
import { EModelEndpoint, BedrockProviders } from 'librechat-data-provider';
|
||||||
|
import type { SettingsConfiguration, SettingDefinition } from 'librechat-data-provider';
|
||||||
|
|
||||||
|
// Base definitions
|
||||||
|
const baseDefinitions: Record<string, Partial<SettingDefinition>> = {
|
||||||
|
model: {
|
||||||
|
key: 'model',
|
||||||
|
label: 'com_ui_model',
|
||||||
|
labelCode: true,
|
||||||
|
type: 'string',
|
||||||
|
component: 'dropdown',
|
||||||
|
optionType: 'model',
|
||||||
|
selectPlaceholder: 'com_ui_select_model',
|
||||||
|
searchPlaceholder: 'com_ui_select_search_model',
|
||||||
|
searchPlaceholderCode: true,
|
||||||
|
selectPlaceholderCode: true,
|
||||||
|
columnSpan: 4,
|
||||||
|
},
|
||||||
|
temperature: {
|
||||||
|
key: 'temperature',
|
||||||
|
label: 'com_endpoint_temperature',
|
||||||
|
labelCode: true,
|
||||||
|
description: 'com_endpoint_openai_temp',
|
||||||
|
descriptionCode: true,
|
||||||
|
type: 'number',
|
||||||
|
component: 'slider',
|
||||||
|
optionType: 'model',
|
||||||
|
columnSpan: 4,
|
||||||
|
},
|
||||||
|
topP: {
|
||||||
|
key: 'topP',
|
||||||
|
label: 'com_endpoint_top_p',
|
||||||
|
labelCode: true,
|
||||||
|
description: 'com_endpoint_anthropic_topp',
|
||||||
|
descriptionCode: true,
|
||||||
|
type: 'number',
|
||||||
|
component: 'slider',
|
||||||
|
optionType: 'model',
|
||||||
|
columnSpan: 4,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const bedrock: Record<string, SettingDefinition> = {
|
||||||
|
region: {
|
||||||
|
key: 'region',
|
||||||
|
type: 'string',
|
||||||
|
label: 'com_ui_region',
|
||||||
|
labelCode: true,
|
||||||
|
component: 'combobox',
|
||||||
|
optionType: 'conversation',
|
||||||
|
selectPlaceholder: 'com_ui_select_region',
|
||||||
|
searchPlaceholder: 'com_ui_select_search_region',
|
||||||
|
searchPlaceholderCode: true,
|
||||||
|
selectPlaceholderCode: true,
|
||||||
|
columnSpan: 2,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const createDefinition = (
|
||||||
|
base: Partial<SettingDefinition>,
|
||||||
|
overrides: Partial<SettingDefinition>,
|
||||||
|
): SettingDefinition => {
|
||||||
|
return { ...base, ...overrides } as SettingDefinition;
|
||||||
|
};
|
||||||
|
|
||||||
|
const librechat: Record<string, SettingDefinition> = {
|
||||||
|
modelLabel: {
|
||||||
|
key: 'modelLabel',
|
||||||
|
label: 'com_endpoint_custom_name',
|
||||||
|
labelCode: true,
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
component: 'input',
|
||||||
|
placeholder: 'com_endpoint_openai_custom_name_placeholder',
|
||||||
|
placeholderCode: true,
|
||||||
|
optionType: 'conversation',
|
||||||
|
},
|
||||||
|
maxContextTokens: {
|
||||||
|
key: 'maxContextTokens',
|
||||||
|
label: 'com_endpoint_context_tokens',
|
||||||
|
labelCode: true,
|
||||||
|
type: 'number',
|
||||||
|
component: 'input',
|
||||||
|
placeholder: 'com_endpoint_context_info',
|
||||||
|
placeholderCode: true,
|
||||||
|
optionType: 'model',
|
||||||
|
columnSpan: 2,
|
||||||
|
},
|
||||||
|
resendFiles: {
|
||||||
|
key: 'resendFiles',
|
||||||
|
label: 'com_endpoint_plug_resend_files',
|
||||||
|
labelCode: true,
|
||||||
|
description: 'com_endpoint_openai_resend_files',
|
||||||
|
descriptionCode: true,
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
component: 'switch',
|
||||||
|
optionType: 'conversation',
|
||||||
|
showDefault: false,
|
||||||
|
columnSpan: 2,
|
||||||
|
},
|
||||||
|
promptPrefix: {
|
||||||
|
key: 'promptPrefix',
|
||||||
|
label: 'com_endpoint_prompt_prefix',
|
||||||
|
labelCode: true,
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
component: 'textarea',
|
||||||
|
placeholder: 'com_endpoint_openai_prompt_prefix_placeholder',
|
||||||
|
placeholderCode: true,
|
||||||
|
optionType: 'model',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const anthropic: Record<string, SettingDefinition> = {
|
||||||
|
system: {
|
||||||
|
key: 'system',
|
||||||
|
label: 'com_endpoint_prompt_prefix',
|
||||||
|
labelCode: true,
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
component: 'textarea',
|
||||||
|
placeholder: 'com_endpoint_openai_prompt_prefix_placeholder',
|
||||||
|
placeholderCode: true,
|
||||||
|
optionType: 'model',
|
||||||
|
},
|
||||||
|
maxTokens: {
|
||||||
|
key: 'maxTokens',
|
||||||
|
label: 'com_endpoint_max_output_tokens',
|
||||||
|
labelCode: true,
|
||||||
|
type: 'number',
|
||||||
|
component: 'input',
|
||||||
|
placeholder: 'com_endpoint_anthropic_maxoutputtokens',
|
||||||
|
placeholderCode: true,
|
||||||
|
optionType: 'model',
|
||||||
|
columnSpan: 2,
|
||||||
|
},
|
||||||
|
temperature: createDefinition(baseDefinitions.temperature, {
|
||||||
|
default: 1,
|
||||||
|
range: { min: 0, max: 1, step: 0.01 },
|
||||||
|
}),
|
||||||
|
topP: createDefinition(baseDefinitions.topP, {
|
||||||
|
default: 0.999,
|
||||||
|
range: { min: 0, max: 1, step: 0.01 },
|
||||||
|
}),
|
||||||
|
topK: {
|
||||||
|
key: 'topK',
|
||||||
|
label: 'com_endpoint_top_k',
|
||||||
|
labelCode: true,
|
||||||
|
description: 'com_endpoint_anthropic_topk',
|
||||||
|
descriptionCode: true,
|
||||||
|
type: 'number',
|
||||||
|
range: { min: 0, max: 500, step: 1 },
|
||||||
|
component: 'slider',
|
||||||
|
optionType: 'model',
|
||||||
|
columnSpan: 4,
|
||||||
|
},
|
||||||
|
stop: {
|
||||||
|
key: 'stop',
|
||||||
|
label: 'com_endpoint_stop',
|
||||||
|
labelCode: true,
|
||||||
|
description: 'com_endpoint_openai_stop',
|
||||||
|
descriptionCode: true,
|
||||||
|
placeholder: 'com_endpoint_stop_placeholder',
|
||||||
|
placeholderCode: true,
|
||||||
|
type: 'array',
|
||||||
|
default: [],
|
||||||
|
component: 'tags',
|
||||||
|
optionType: 'conversation',
|
||||||
|
minTags: 0,
|
||||||
|
maxTags: 4,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mistral: Record<string, SettingDefinition> = {
|
||||||
|
temperature: createDefinition(baseDefinitions.temperature, {
|
||||||
|
default: 0.7,
|
||||||
|
range: { min: 0, max: 1, step: 0.01 },
|
||||||
|
}),
|
||||||
|
topP: createDefinition(baseDefinitions.topP, {
|
||||||
|
range: { min: 0, max: 1, step: 0.01 },
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const cohere: Record<string, SettingDefinition> = {
|
||||||
|
temperature: createDefinition(baseDefinitions.temperature, {
|
||||||
|
default: 0.3,
|
||||||
|
range: { min: 0, max: 1, step: 0.01 },
|
||||||
|
}),
|
||||||
|
topP: createDefinition(baseDefinitions.topP, {
|
||||||
|
default: 0.75,
|
||||||
|
range: { min: 0.01, max: 0.99, step: 0.01 },
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const meta: Record<string, SettingDefinition> = {
|
||||||
|
temperature: createDefinition(baseDefinitions.temperature, {
|
||||||
|
default: 0.5,
|
||||||
|
range: { min: 0, max: 1, step: 0.01 },
|
||||||
|
}),
|
||||||
|
topP: createDefinition(baseDefinitions.topP, {
|
||||||
|
default: 0.9,
|
||||||
|
range: { min: 0, max: 1, step: 0.01 },
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const bedrockAnthropic: SettingsConfiguration = [
|
||||||
|
librechat.modelLabel,
|
||||||
|
anthropic.system,
|
||||||
|
librechat.maxContextTokens,
|
||||||
|
anthropic.maxTokens,
|
||||||
|
anthropic.temperature,
|
||||||
|
anthropic.topP,
|
||||||
|
anthropic.topK,
|
||||||
|
anthropic.stop,
|
||||||
|
bedrock.region,
|
||||||
|
librechat.resendFiles,
|
||||||
|
];
|
||||||
|
|
||||||
|
const bedrockMistral: SettingsConfiguration = [
|
||||||
|
librechat.modelLabel,
|
||||||
|
librechat.promptPrefix,
|
||||||
|
librechat.maxContextTokens,
|
||||||
|
anthropic.maxTokens,
|
||||||
|
mistral.temperature,
|
||||||
|
mistral.topP,
|
||||||
|
bedrock.region,
|
||||||
|
librechat.resendFiles,
|
||||||
|
];
|
||||||
|
|
||||||
|
const bedrockCohere: SettingsConfiguration = [
|
||||||
|
librechat.modelLabel,
|
||||||
|
librechat.promptPrefix,
|
||||||
|
librechat.maxContextTokens,
|
||||||
|
anthropic.maxTokens,
|
||||||
|
cohere.temperature,
|
||||||
|
cohere.topP,
|
||||||
|
bedrock.region,
|
||||||
|
librechat.resendFiles,
|
||||||
|
];
|
||||||
|
|
||||||
|
const bedrockGeneral: SettingsConfiguration = [
|
||||||
|
librechat.modelLabel,
|
||||||
|
librechat.promptPrefix,
|
||||||
|
librechat.maxContextTokens,
|
||||||
|
meta.temperature,
|
||||||
|
meta.topP,
|
||||||
|
bedrock.region,
|
||||||
|
librechat.resendFiles,
|
||||||
|
];
|
||||||
|
|
||||||
|
const bedrockAnthropicCol1: SettingsConfiguration = [
|
||||||
|
baseDefinitions.model as SettingDefinition,
|
||||||
|
librechat.modelLabel,
|
||||||
|
anthropic.system,
|
||||||
|
anthropic.stop,
|
||||||
|
];
|
||||||
|
|
||||||
|
const bedrockAnthropicCol2: SettingsConfiguration = [
|
||||||
|
librechat.maxContextTokens,
|
||||||
|
anthropic.maxTokens,
|
||||||
|
anthropic.temperature,
|
||||||
|
anthropic.topP,
|
||||||
|
anthropic.topK,
|
||||||
|
bedrock.region,
|
||||||
|
librechat.resendFiles,
|
||||||
|
];
|
||||||
|
|
||||||
|
const bedrockMistralCol1: SettingsConfiguration = [
|
||||||
|
baseDefinitions.model as SettingDefinition,
|
||||||
|
librechat.modelLabel,
|
||||||
|
librechat.promptPrefix,
|
||||||
|
];
|
||||||
|
|
||||||
|
const bedrockMistralCol2: SettingsConfiguration = [
|
||||||
|
librechat.maxContextTokens,
|
||||||
|
anthropic.maxTokens,
|
||||||
|
mistral.temperature,
|
||||||
|
mistral.topP,
|
||||||
|
bedrock.region,
|
||||||
|
librechat.resendFiles,
|
||||||
|
];
|
||||||
|
|
||||||
|
const bedrockCohereCol1: SettingsConfiguration = [
|
||||||
|
baseDefinitions.model as SettingDefinition,
|
||||||
|
librechat.modelLabel,
|
||||||
|
librechat.promptPrefix,
|
||||||
|
];
|
||||||
|
|
||||||
|
const bedrockCohereCol2: SettingsConfiguration = [
|
||||||
|
librechat.maxContextTokens,
|
||||||
|
anthropic.maxTokens,
|
||||||
|
cohere.temperature,
|
||||||
|
cohere.topP,
|
||||||
|
bedrock.region,
|
||||||
|
librechat.resendFiles,
|
||||||
|
];
|
||||||
|
|
||||||
|
const bedrockGeneralCol1: SettingsConfiguration = [
|
||||||
|
baseDefinitions.model as SettingDefinition,
|
||||||
|
librechat.modelLabel,
|
||||||
|
librechat.promptPrefix,
|
||||||
|
];
|
||||||
|
|
||||||
|
const bedrockGeneralCol2: SettingsConfiguration = [
|
||||||
|
librechat.maxContextTokens,
|
||||||
|
meta.temperature,
|
||||||
|
meta.topP,
|
||||||
|
bedrock.region,
|
||||||
|
librechat.resendFiles,
|
||||||
|
];
|
||||||
|
|
||||||
|
export const settings: Record<string, SettingsConfiguration | undefined> = {
|
||||||
|
[`${EModelEndpoint.bedrock}-${BedrockProviders.Anthropic}`]: bedrockAnthropic,
|
||||||
|
[`${EModelEndpoint.bedrock}-${BedrockProviders.MistralAI}`]: bedrockMistral,
|
||||||
|
[`${EModelEndpoint.bedrock}-${BedrockProviders.Cohere}`]: bedrockCohere,
|
||||||
|
[`${EModelEndpoint.bedrock}-${BedrockProviders.Meta}`]: bedrockGeneral,
|
||||||
|
[`${EModelEndpoint.bedrock}-${BedrockProviders.AI21}`]: bedrockGeneral,
|
||||||
|
[`${EModelEndpoint.bedrock}-${BedrockProviders.Amazon}`]: bedrockGeneral,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const presetSettings: Record<
|
||||||
|
string,
|
||||||
|
| {
|
||||||
|
col1: SettingsConfiguration;
|
||||||
|
col2: SettingsConfiguration;
|
||||||
|
}
|
||||||
|
| undefined
|
||||||
|
> = {
|
||||||
|
[`${EModelEndpoint.bedrock}-${BedrockProviders.Anthropic}`]: {
|
||||||
|
col1: bedrockAnthropicCol1,
|
||||||
|
col2: bedrockAnthropicCol2,
|
||||||
|
},
|
||||||
|
[`${EModelEndpoint.bedrock}-${BedrockProviders.MistralAI}`]: {
|
||||||
|
col1: bedrockMistralCol1,
|
||||||
|
col2: bedrockMistralCol2,
|
||||||
|
},
|
||||||
|
[`${EModelEndpoint.bedrock}-${BedrockProviders.Cohere}`]: {
|
||||||
|
col1: bedrockCohereCol1,
|
||||||
|
col2: bedrockCohereCol2,
|
||||||
|
},
|
||||||
|
[`${EModelEndpoint.bedrock}-${BedrockProviders.Meta}`]: {
|
||||||
|
col1: bedrockGeneralCol1,
|
||||||
|
col2: bedrockGeneralCol2,
|
||||||
|
},
|
||||||
|
[`${EModelEndpoint.bedrock}-${BedrockProviders.AI21}`]: {
|
||||||
|
col1: bedrockGeneralCol1,
|
||||||
|
col2: bedrockGeneralCol2,
|
||||||
|
},
|
||||||
|
[`${EModelEndpoint.bedrock}-${BedrockProviders.Amazon}`]: {
|
||||||
|
col1: bedrockGeneralCol1,
|
||||||
|
col2: bedrockGeneralCol2,
|
||||||
|
},
|
||||||
|
};
|
23
client/src/components/svg/BedrockIcon.tsx
Normal file
23
client/src/components/svg/BedrockIcon.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
|
export default function BedrockIcon({
|
||||||
|
size = 25,
|
||||||
|
className = '',
|
||||||
|
}: {
|
||||||
|
size?: number;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className={cn('fill-current text-black', className)}
|
||||||
|
>
|
||||||
|
<g fill="currentColor">
|
||||||
|
<path d="M12,18.1397014 L9.574,18.9487014 L8.628,18.3177014 L9.658,17.9737014 L9.342,17.0257014 L7.574,17.6147014 L7,17.2327014 L7,14.4997014 C7,14.3107014 6.893,14.1377014 6.724,14.0527014 L5,13.1907014 L5,10.8087014 L6.5,10.0587014 L8,10.8087014 L8,12.4997014 C8,12.6897014 8.107,12.8627014 8.276,12.9477014 L10.276,13.9477014 L10.724,13.0527014 L9,12.1907014 L9,10.8087014 L10.724,9.94770136 C10.893,9.86270136 11,9.68970136 11,9.49970136 L11,7.99970136 L10,7.99970136 L10,9.19070136 L8.5,9.94070136 L7,9.19070136 L7,6.76770136 L8,6.10070136 L8,7.99970136 L9,7.99970136 L9,5.43470136 L9.574,5.05170136 L12,5.86070136 L12,18.1397014 Z M17.5,16.9997014 C17.775,16.9997014 18,17.2237014 18,17.4997014 C18,17.7757014 17.775,17.9997014 17.5,17.9997014 C17.225,17.9997014 17,17.7757014 17,17.4997014 C17,17.2237014 17.225,16.9997014 17.5,16.9997014 L17.5,16.9997014 Z M16.5,5.99970136 C16.775,5.99970136 17,6.22370136 17,6.49970136 C17,6.77570136 16.775,6.99970136 16.5,6.99970136 C16.225,6.99970136 16,6.77570136 16,6.49970136 C16,6.22370136 16.225,5.99970136 16.5,5.99970136 L16.5,5.99970136 Z M18.5,11.9997014 C18.775,11.9997014 19,12.2237014 19,12.4997014 C19,12.7757014 18.775,12.9997014 18.5,12.9997014 C18.225,12.9997014 18,12.7757014 18,12.4997014 C18,12.2237014 18.225,11.9997014 18.5,11.9997014 L18.5,11.9997014 Z M17.092,12.9997014 C17.299,13.5807014 17.849,13.9997014 18.5,13.9997014 C19.327,13.9997014 20,13.3277014 20,12.4997014 C20,11.6727014 19.327,10.9997014 18.5,10.9997014 C17.849,10.9997014 17.299,11.4197014 17.092,11.9997014 L13,11.9997014 L13,9.99970136 L16.5,9.99970136 C16.776,9.99970136 17,9.77670136 17,9.49970136 L17,7.90770136 C17.581,7.70070136 18,7.15070136 18,6.49970136 C18,5.67270136 17.327,4.99970136 16.5,4.99970136 C15.673,4.99970136 15,5.67270136 15,6.49970136 C15,7.15070136 15.419,7.70070136 16,7.90770136 L16,8.99970136 L13,8.99970136 L13,5.49970136 C13,5.28470136 12.862,5.09370136 12.658,5.02570136 L9.658,4.02570136 C9.511,3.97670136 9.351,3.99870136 9.223,4.08370136 L6.223,6.08370136 C6.084,6.17670136 6,6.33270136 6,6.49970136 L6,9.19070136 L4.276,10.0527014 C4.107,10.1377014 4,10.3107014 4,10.4997014 L4,13.4997014 C4,13.6897014 4.107,13.8627014 4.276,13.9477014 L6,14.8087014 L6,17.4997014 C6,17.6667014 6.084,17.8237014 6.223,17.9157014 L9.223,19.9157014 C9.306,19.9717014 9.402,19.9997014 9.5,19.9997014 C9.553,19.9997014 9.606,19.9917014 9.658,19.9737014 L12.658,18.9737014 C12.862,18.9067014 13,18.7157014 13,18.4997014 L13,15.9997014 L15.293,15.9997014 L16.146,16.8537014 L16.159,16.8407014 C16.061,17.0407014 16,17.2627014 16,17.4997014 C16,18.3267014 16.673,18.9997014 17.5,18.9997014 C18.327,18.9997014 19,18.3267014 19,17.4997014 C19,16.6727014 18.327,15.9997014 17.5,15.9997014 C17.262,15.9997014 17.04,16.0607014 16.841,16.1597014 L16.854,16.1467014 L15.854,15.1467014 C15.76,15.0527014 15.633,14.9997014 15.5,14.9997014 L13,14.9997014 L13,12.9997014 L17.092,12.9997014 Z" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,37 +1,34 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
|
|
||||||
const EditIcon = React.forwardRef<SVGSVGElement>(
|
type IconProps = {
|
||||||
(
|
className?: string;
|
||||||
props: {
|
size?: string;
|
||||||
className?: string;
|
};
|
||||||
size?: string;
|
|
||||||
},
|
const EditIcon = React.forwardRef<SVGSVGElement, IconProps>((props: IconProps, ref) => {
|
||||||
ref,
|
const { className = 'icon-md', size = '1.2em' } = props;
|
||||||
) => {
|
return (
|
||||||
const { className = 'icon-md', size = '1.2em' } = props;
|
<svg
|
||||||
return (
|
ref={ref}
|
||||||
<svg
|
fill="none"
|
||||||
ref={ref}
|
strokeWidth="2"
|
||||||
fill="none"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
strokeWidth="2"
|
viewBox="0 0 24 24"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
strokeLinecap="round"
|
||||||
viewBox="0 0 24 24"
|
strokeLinejoin="round"
|
||||||
strokeLinecap="round"
|
height={size}
|
||||||
strokeLinejoin="round"
|
width={size}
|
||||||
height={size}
|
className={cn(className)}
|
||||||
width={size}
|
>
|
||||||
className={cn(className)}
|
<path
|
||||||
>
|
fillRule="evenodd"
|
||||||
<path
|
clipRule="evenodd"
|
||||||
fillRule="evenodd"
|
d="M13.2929 4.29291C15.0641 2.52167 17.9359 2.52167 19.7071 4.2929C21.4783 6.06414 21.4783 8.93588 19.7071 10.7071L18.7073 11.7069L11.1603 19.2539C10.7182 19.696 10.1489 19.989 9.53219 20.0918L4.1644 20.9864C3.84584 21.0395 3.52125 20.9355 3.29289 20.7071C3.06453 20.4788 2.96051 20.1542 3.0136 19.8356L3.90824 14.4678C4.01103 13.8511 4.30396 13.2818 4.7461 12.8397L13.2929 4.29291ZM13 7.41422L6.16031 14.2539C6.01293 14.4013 5.91529 14.591 5.88102 14.7966L5.21655 18.7835L9.20339 18.119C9.40898 18.0847 9.59872 17.9871 9.7461 17.8397L16.5858 11L13 7.41422ZM18 9.5858L14.4142 6.00001L14.7071 5.70712C15.6973 4.71693 17.3027 4.71693 18.2929 5.70712C19.2831 6.69731 19.2831 8.30272 18.2929 9.29291L18 9.5858Z"
|
||||||
clipRule="evenodd"
|
fill="currentColor"
|
||||||
d="M13.2929 4.29291C15.0641 2.52167 17.9359 2.52167 19.7071 4.2929C21.4783 6.06414 21.4783 8.93588 19.7071 10.7071L18.7073 11.7069L11.1603 19.2539C10.7182 19.696 10.1489 19.989 9.53219 20.0918L4.1644 20.9864C3.84584 21.0395 3.52125 20.9355 3.29289 20.7071C3.06453 20.4788 2.96051 20.1542 3.0136 19.8356L3.90824 14.4678C4.01103 13.8511 4.30396 13.2818 4.7461 12.8397L13.2929 4.29291ZM13 7.41422L6.16031 14.2539C6.01293 14.4013 5.91529 14.591 5.88102 14.7966L5.21655 18.7835L9.20339 18.119C9.40898 18.0847 9.59872 17.9871 9.7461 17.8397L16.5858 11L13 7.41422ZM18 9.5858L14.4142 6.00001L14.7071 5.70712C15.6973 4.71693 17.3027 4.71693 18.2929 5.70712C19.2831 6.69731 19.2831 8.30272 18.2929 9.29291L18 9.5858Z"
|
></path>
|
||||||
fill="currentColor"
|
</svg>
|
||||||
></path>
|
);
|
||||||
</svg>
|
});
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export default EditIcon;
|
export default EditIcon;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue