mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 08:12:00 +02:00
🚀 feat: Assistants Streaming (#2159)
* chore: bump openai to 4.29.0 and npm audit fix * chore: remove unnecessary stream field from ContentData * feat: new enum and types for AssistantStreamEvent * refactor(AssistantService): remove stream field and add conversationId to text ContentData > - return `finalMessage` and `text` on run completion > - move `processMessages` to services/Threads to avoid circular dependencies with new stream handling > - refactor(processMessages/retrieveAndProcessFile): add new `client` field to differentiate new RunClient type * WIP: new assistants stream handling * chore: stores messages to StreamRunManager * chore: add additional typedefs * fix: pass req and openai to StreamRunManager * fix(AssistantService): pass openai as client to `retrieveAndProcessFile` * WIP: streaming tool i/o, handle in_progress and completed run steps * feat(assistants): process required actions with streaming enabled * chore: condense early return check for useSSE useEffect * chore: remove unnecessary comments and only handle completed tool calls when not function * feat: add TTL for assistants run abort cacheKey * feat: abort stream runs * fix(assistants): render streaming cursor * fix(assistants): hide edit icon as functionality is not supported * fix(textArea): handle pasting edge cases; first, when onChange events wouldn't fire; second, when textarea wouldn't resize * chore: memoize Conversations * chore(useTextarea): reverse args order * fix: load default capabilities when an azure is configured to support assistants, but `assistants` endpoint is not configured * fix(AssistantSelect): update form assistant model on assistant form select * fix(actions): handle azure strict validation for function names to fix crud for actions * chore: remove content data debug log as it fires in rapid succession * feat: improve UX for assistant errors mid-request * feat: add tool call localizations and replace any domain separators from azure action names * refactor(chat): error out tool calls without outputs during handleError * fix(ToolService): handle domain separators allowing Azure use of actions * refactor(StreamRunManager): types and throw Error if tool submission fails
This commit is contained in:
parent
ed64c76053
commit
f427ad792a
39 changed files with 1503 additions and 330 deletions
|
@ -66,7 +66,7 @@
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"nodejs-gpt": "^1.37.4",
|
"nodejs-gpt": "^1.37.4",
|
||||||
"nodemailer": "^6.9.4",
|
"nodemailer": "^6.9.4",
|
||||||
"openai": "^4.28.4",
|
"openai": "^4.29.0",
|
||||||
"openai-chat-tokens": "^0.2.8",
|
"openai-chat-tokens": "^0.2.8",
|
||||||
"openid-client": "^5.4.2",
|
"openid-client": "^5.4.2",
|
||||||
"passport": "^0.6.0",
|
"passport": "^0.6.0",
|
||||||
|
|
|
@ -4,9 +4,10 @@ const { checkMessageGaps, recordUsage } = require('~/server/services/Threads');
|
||||||
const { getConvo } = require('~/models/Conversation');
|
const { getConvo } = require('~/models/Conversation');
|
||||||
const getLogStores = require('~/cache/getLogStores');
|
const getLogStores = require('~/cache/getLogStores');
|
||||||
const { sendMessage } = require('~/server/utils');
|
const { sendMessage } = require('~/server/utils');
|
||||||
// const spendTokens = require('~/models/spendTokens');
|
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
|
const three_minutes = 1000 * 60 * 3;
|
||||||
|
|
||||||
async function abortRun(req, res) {
|
async function abortRun(req, res) {
|
||||||
res.setHeader('Content-Type', 'application/json');
|
res.setHeader('Content-Type', 'application/json');
|
||||||
const { abortKey } = req.body;
|
const { abortKey } = req.body;
|
||||||
|
@ -40,7 +41,7 @@ async function abortRun(req, res) {
|
||||||
const { openai } = await initializeClient({ req, res });
|
const { openai } = await initializeClient({ req, res });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await cache.set(cacheKey, 'cancelled');
|
await cache.set(cacheKey, 'cancelled', three_minutes);
|
||||||
const cancelledRun = await openai.beta.threads.runs.cancel(thread_id, run_id);
|
const cancelledRun = await openai.beta.threads.runs.cancel(thread_id, run_id);
|
||||||
logger.debug('[abortRun] Cancelled run:', cancelledRun);
|
logger.debug('[abortRun] Cancelled run:', cancelledRun);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
@ -2,9 +2,9 @@ const { v4 } = require('uuid');
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const { actionDelimiter } = require('librechat-data-provider');
|
const { actionDelimiter } = require('librechat-data-provider');
|
||||||
const { initializeClient } = require('~/server/services/Endpoints/assistants');
|
const { initializeClient } = require('~/server/services/Endpoints/assistants');
|
||||||
|
const { encryptMetadata, domainParser } = require('~/server/services/ActionService');
|
||||||
const { updateAction, getActions, deleteAction } = require('~/models/Action');
|
const { updateAction, getActions, deleteAction } = require('~/models/Action');
|
||||||
const { updateAssistant, getAssistant } = require('~/models/Assistant');
|
const { updateAssistant, getAssistant } = require('~/models/Assistant');
|
||||||
const { encryptMetadata } = require('~/server/services/ActionService');
|
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
@ -44,7 +44,10 @@ router.post('/:assistant_id', async (req, res) => {
|
||||||
|
|
||||||
let metadata = encryptMetadata(_metadata);
|
let metadata = encryptMetadata(_metadata);
|
||||||
|
|
||||||
const { domain } = metadata;
|
let { domain } = metadata;
|
||||||
|
/* Azure doesn't support periods in function names */
|
||||||
|
domain = domainParser(req, domain, true);
|
||||||
|
|
||||||
if (!domain) {
|
if (!domain) {
|
||||||
return res.status(400).json({ message: 'No domain provided' });
|
return res.status(400).json({ message: 'No domain provided' });
|
||||||
}
|
}
|
||||||
|
@ -141,9 +144,10 @@ router.post('/:assistant_id', async (req, res) => {
|
||||||
* @param {string} req.params.action_id - The ID of the action to delete.
|
* @param {string} req.params.action_id - The ID of the action to delete.
|
||||||
* @returns {Object} 200 - success response - application/json
|
* @returns {Object} 200 - success response - application/json
|
||||||
*/
|
*/
|
||||||
router.delete('/:assistant_id/:action_id', async (req, res) => {
|
router.delete('/:assistant_id/:action_id/:model', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { assistant_id, action_id } = req.params;
|
const { assistant_id, action_id, model } = req.params;
|
||||||
|
req.body.model = model;
|
||||||
|
|
||||||
/** @type {{ openai: OpenAI }} */
|
/** @type {{ openai: OpenAI }} */
|
||||||
const { openai } = await initializeClient({ req, res });
|
const { openai } = await initializeClient({ req, res });
|
||||||
|
@ -167,6 +171,8 @@ router.delete('/:assistant_id/:action_id', async (req, res) => {
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
domain = domainParser(req, domain, true);
|
||||||
|
|
||||||
const updatedTools = tools.filter(
|
const updatedTools = tools.filter(
|
||||||
(tool) => !(tool.function && tool.function.name.includes(domain)),
|
(tool) => !(tool.function && tool.function.name.includes(domain)),
|
||||||
);
|
);
|
||||||
|
|
|
@ -4,8 +4,10 @@ const {
|
||||||
Constants,
|
Constants,
|
||||||
RunStatus,
|
RunStatus,
|
||||||
CacheKeys,
|
CacheKeys,
|
||||||
|
ContentTypes,
|
||||||
EModelEndpoint,
|
EModelEndpoint,
|
||||||
ViolationTypes,
|
ViolationTypes,
|
||||||
|
AssistantStreamEvents,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
const {
|
const {
|
||||||
initThread,
|
initThread,
|
||||||
|
@ -18,8 +20,8 @@ const {
|
||||||
const { runAssistant, createOnTextProgress } = require('~/server/services/AssistantService');
|
const { runAssistant, createOnTextProgress } = require('~/server/services/AssistantService');
|
||||||
const { addTitle, initializeClient } = require('~/server/services/Endpoints/assistants');
|
const { addTitle, initializeClient } = require('~/server/services/Endpoints/assistants');
|
||||||
const { sendResponse, sendMessage, sleep, isEnabled, countTokens } = require('~/server/utils');
|
const { sendResponse, sendMessage, sleep, isEnabled, countTokens } = require('~/server/utils');
|
||||||
|
const { createRun, StreamRunManager } = require('~/server/services/Runs');
|
||||||
const { getTransactions } = require('~/models/Transaction');
|
const { getTransactions } = require('~/models/Transaction');
|
||||||
const { createRun } = require('~/server/services/Runs');
|
|
||||||
const checkBalance = require('~/models/checkBalance');
|
const checkBalance = require('~/models/checkBalance');
|
||||||
const { getConvo } = require('~/models/Conversation');
|
const { getConvo } = require('~/models/Conversation');
|
||||||
const getLogStores = require('~/cache/getLogStores');
|
const getLogStores = require('~/cache/getLogStores');
|
||||||
|
@ -38,6 +40,8 @@ const {
|
||||||
|
|
||||||
router.post('/abort', handleAbort());
|
router.post('/abort', handleAbort());
|
||||||
|
|
||||||
|
const ten_minutes = 1000 * 60 * 10;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @route POST /
|
* @route POST /
|
||||||
* @desc Chat with an assistant
|
* @desc Chat with an assistant
|
||||||
|
@ -147,7 +151,7 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res
|
||||||
return sendResponse(res, messageData, defaultErrorMessage);
|
return sendResponse(res, messageData, defaultErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
await sleep(3000);
|
await sleep(2000);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const status = await cache.get(cacheKey);
|
const status = await cache.get(cacheKey);
|
||||||
|
@ -187,6 +191,42 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res
|
||||||
latestMessageId: responseMessageId,
|
latestMessageId: responseMessageId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const errorContentPart = {
|
||||||
|
text: {
|
||||||
|
value:
|
||||||
|
error?.message ?? 'There was an error processing your request. Please try again later.',
|
||||||
|
},
|
||||||
|
type: ContentTypes.ERROR,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!Array.isArray(runMessages[runMessages.length - 1]?.content)) {
|
||||||
|
runMessages[runMessages.length - 1].content = [errorContentPart];
|
||||||
|
} else {
|
||||||
|
const contentParts = runMessages[runMessages.length - 1].content;
|
||||||
|
for (let i = 0; i < contentParts.length; i++) {
|
||||||
|
const currentPart = contentParts[i];
|
||||||
|
/** @type {CodeToolCall | RetrievalToolCall | FunctionToolCall | undefined} */
|
||||||
|
const toolCall = currentPart?.[ContentTypes.TOOL_CALL];
|
||||||
|
if (
|
||||||
|
toolCall &&
|
||||||
|
toolCall?.function &&
|
||||||
|
!(toolCall?.function?.output || toolCall?.function?.output?.length)
|
||||||
|
) {
|
||||||
|
contentParts[i] = {
|
||||||
|
...currentPart,
|
||||||
|
[ContentTypes.TOOL_CALL]: {
|
||||||
|
...toolCall,
|
||||||
|
function: {
|
||||||
|
...toolCall.function,
|
||||||
|
output: 'error processing tool',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runMessages[runMessages.length - 1].content.push(errorContentPart);
|
||||||
|
}
|
||||||
|
|
||||||
finalEvent = {
|
finalEvent = {
|
||||||
title: 'New Chat',
|
title: 'New Chat',
|
||||||
final: true,
|
final: true,
|
||||||
|
@ -358,53 +398,107 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res
|
||||||
body.instructions = instructions;
|
body.instructions = instructions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NOTE:
|
const sendInitialResponse = () => {
|
||||||
* By default, a Run will use the model and tools configuration specified in Assistant object,
|
sendMessage(res, {
|
||||||
* but you can override most of these when creating the Run for added flexibility:
|
sync: true,
|
||||||
*/
|
|
||||||
const run = await createRun({
|
|
||||||
openai,
|
|
||||||
thread_id,
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
|
|
||||||
run_id = run.id;
|
|
||||||
await cache.set(cacheKey, `${thread_id}:${run_id}`);
|
|
||||||
|
|
||||||
sendMessage(res, {
|
|
||||||
sync: true,
|
|
||||||
conversationId,
|
|
||||||
// messages: previousMessages,
|
|
||||||
requestMessage,
|
|
||||||
responseMessage: {
|
|
||||||
user: req.user.id,
|
|
||||||
messageId: openai.responseMessage.messageId,
|
|
||||||
parentMessageId: userMessageId,
|
|
||||||
conversationId,
|
conversationId,
|
||||||
assistant_id,
|
// messages: previousMessages,
|
||||||
thread_id,
|
requestMessage,
|
||||||
model: assistant_id,
|
responseMessage: {
|
||||||
},
|
user: req.user.id,
|
||||||
});
|
messageId: openai.responseMessage.messageId,
|
||||||
|
parentMessageId: userMessageId,
|
||||||
|
conversationId,
|
||||||
|
assistant_id,
|
||||||
|
thread_id,
|
||||||
|
model: assistant_id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// todo: retry logic
|
/** @type {RunResponse | typeof StreamRunManager | undefined} */
|
||||||
let response = await runAssistant({ openai, thread_id, run_id });
|
let response;
|
||||||
logger.debug('[/assistants/chat/] response', response);
|
|
||||||
|
|
||||||
if (response.run.status === RunStatus.IN_PROGRESS) {
|
const processRun = async (retry = false) => {
|
||||||
response = await runAssistant({
|
if (req.app.locals[EModelEndpoint.azureOpenAI]?.assistants) {
|
||||||
|
if (retry) {
|
||||||
|
response = await runAssistant({
|
||||||
|
openai,
|
||||||
|
thread_id,
|
||||||
|
run_id,
|
||||||
|
in_progress: openai.in_progress,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOTE:
|
||||||
|
* By default, a Run will use the model and tools configuration specified in Assistant object,
|
||||||
|
* but you can override most of these when creating the Run for added flexibility:
|
||||||
|
*/
|
||||||
|
const run = await createRun({
|
||||||
|
openai,
|
||||||
|
thread_id,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
run_id = run.id;
|
||||||
|
await cache.set(cacheKey, `${thread_id}:${run_id}`, ten_minutes);
|
||||||
|
sendInitialResponse();
|
||||||
|
|
||||||
|
// todo: retry logic
|
||||||
|
response = await runAssistant({ openai, thread_id, run_id });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {{[AssistantStreamEvents.ThreadRunCreated]: (event: ThreadRunCreated) => Promise<void>}} */
|
||||||
|
const handlers = {
|
||||||
|
[AssistantStreamEvents.ThreadRunCreated]: async (event) => {
|
||||||
|
await cache.set(cacheKey, `${thread_id}:${event.data.id}`, ten_minutes);
|
||||||
|
run_id = event.data.id;
|
||||||
|
sendInitialResponse();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const streamRunManager = new StreamRunManager({
|
||||||
|
req,
|
||||||
|
res,
|
||||||
openai,
|
openai,
|
||||||
thread_id,
|
thread_id,
|
||||||
run_id,
|
responseMessage: openai.responseMessage,
|
||||||
in_progress: openai.in_progress,
|
handlers,
|
||||||
|
// streamOptions: {
|
||||||
|
|
||||||
|
// },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await streamRunManager.runAssistant({
|
||||||
|
thread_id,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
response = streamRunManager;
|
||||||
|
};
|
||||||
|
|
||||||
|
await processRun();
|
||||||
|
logger.debug('[/assistants/chat/] response', {
|
||||||
|
run: response.run,
|
||||||
|
steps: response.steps,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.run.status === RunStatus.CANCELLED) {
|
||||||
|
logger.debug('[/assistants/chat/] Run cancelled, handled by `abortRun`');
|
||||||
|
return res.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.run.status === RunStatus.IN_PROGRESS) {
|
||||||
|
processRun(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
completedRun = response.run;
|
completedRun = response.run;
|
||||||
|
|
||||||
/** @type {ResponseMessage} */
|
/** @type {ResponseMessage} */
|
||||||
const responseMessage = {
|
const responseMessage = {
|
||||||
...openai.responseMessage,
|
...response.finalMessage,
|
||||||
parentMessageId: userMessageId,
|
parentMessageId: userMessageId,
|
||||||
conversationId,
|
conversationId,
|
||||||
user: req.user.id,
|
user: req.user.id,
|
||||||
|
@ -413,9 +507,6 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res
|
||||||
model: assistant_id,
|
model: assistant_id,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: token count from usage returned in run
|
|
||||||
// TODO: parse responses, save to db, send to user
|
|
||||||
|
|
||||||
sendMessage(res, {
|
sendMessage(res, {
|
||||||
title: 'New Chat',
|
title: 'New Chat',
|
||||||
final: true,
|
final: true,
|
||||||
|
@ -432,7 +523,7 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res
|
||||||
if (parentMessageId === Constants.NO_PARENT && !_thread_id) {
|
if (parentMessageId === Constants.NO_PARENT && !_thread_id) {
|
||||||
addTitle(req, {
|
addTitle(req, {
|
||||||
text,
|
text,
|
||||||
responseText: openai.responseText,
|
responseText: response.text,
|
||||||
conversationId,
|
conversationId,
|
||||||
client,
|
client,
|
||||||
});
|
});
|
||||||
|
@ -447,7 +538,7 @@ router.post('/', validateModel, buildEndpointOption, setHeaders, async (req, res
|
||||||
|
|
||||||
if (!response.run.usage) {
|
if (!response.run.usage) {
|
||||||
await sleep(3000);
|
await sleep(3000);
|
||||||
completedRun = await openai.beta.threads.runs.retrieve(thread_id, run.id);
|
completedRun = await openai.beta.threads.runs.retrieve(thread_id, response.run.id);
|
||||||
if (completedRun.usage) {
|
if (completedRun.usage) {
|
||||||
await recordUsage({
|
await recordUsage({
|
||||||
...completedRun.usage,
|
...completedRun.usage,
|
||||||
|
|
|
@ -1,8 +1,35 @@
|
||||||
const { AuthTypeEnum } = require('librechat-data-provider');
|
const { AuthTypeEnum, EModelEndpoint, actionDomainSeparator } = require('librechat-data-provider');
|
||||||
const { encryptV2, decryptV2 } = require('~/server/utils/crypto');
|
const { encryptV2, decryptV2 } = require('~/server/utils/crypto');
|
||||||
const { getActions } = require('~/models/Action');
|
const { getActions } = require('~/models/Action');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the domain for an action.
|
||||||
|
*
|
||||||
|
* Azure OpenAI Assistants API doesn't support periods in function
|
||||||
|
* names due to `[a-zA-Z0-9_-]*` Regex Validation.
|
||||||
|
*
|
||||||
|
* @param {Express.Request} req - Express Request object
|
||||||
|
* @param {string} domain - The domain for the actoin
|
||||||
|
* @param {boolean} inverse - If true, replaces periods with `actionDomainSeparator`
|
||||||
|
* @returns {string} The parsed domain
|
||||||
|
*/
|
||||||
|
function domainParser(req, domain, inverse = false) {
|
||||||
|
if (!domain) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!req.app.locals[EModelEndpoint.azureOpenAI]?.assistants) {
|
||||||
|
return domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inverse) {
|
||||||
|
return domain.replace(/\./g, actionDomainSeparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
return domain.replace(actionDomainSeparator, '.');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads action sets based on the user and assistant ID.
|
* Loads action sets based on the user and assistant ID.
|
||||||
*
|
*
|
||||||
|
@ -117,4 +144,5 @@ module.exports = {
|
||||||
createActionTool,
|
createActionTool,
|
||||||
encryptMetadata,
|
encryptMetadata,
|
||||||
decryptMetadata,
|
decryptMetadata,
|
||||||
|
domainParser,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
const {
|
const {
|
||||||
Constants,
|
Constants,
|
||||||
FileSources,
|
FileSources,
|
||||||
|
Capabilities,
|
||||||
EModelEndpoint,
|
EModelEndpoint,
|
||||||
defaultSocialLogins,
|
defaultSocialLogins,
|
||||||
validateAzureGroups,
|
validateAzureGroups,
|
||||||
|
@ -122,6 +123,13 @@ const AppService = async (app) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (azureConfiguration.assistants) {
|
||||||
|
endpointLocals[EModelEndpoint.assistants] = {
|
||||||
|
// Note: may need to add retrieval models here in the future
|
||||||
|
capabilities: [Capabilities.tools, Capabilities.actions, Capabilities.code_interpreter],
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config?.endpoints?.[EModelEndpoint.assistants]) {
|
if (config?.endpoints?.[EModelEndpoint.assistants]) {
|
||||||
|
@ -133,8 +141,11 @@ const AppService = async (app) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const prevConfig = endpointLocals[EModelEndpoint.assistants] ?? {};
|
||||||
|
|
||||||
/** @type {Partial<TAssistantEndpoint>} */
|
/** @type {Partial<TAssistantEndpoint>} */
|
||||||
endpointLocals[EModelEndpoint.assistants] = {
|
endpointLocals[EModelEndpoint.assistants] = {
|
||||||
|
...prevConfig,
|
||||||
retrievalModels: parsedConfig.retrievalModels,
|
retrievalModels: parsedConfig.retrievalModels,
|
||||||
disableBuilder: parsedConfig.disableBuilder,
|
disableBuilder: parsedConfig.disableBuilder,
|
||||||
pollIntervalMs: parsedConfig.pollIntervalMs,
|
pollIntervalMs: parsedConfig.pollIntervalMs,
|
||||||
|
|
|
@ -4,18 +4,17 @@ const {
|
||||||
StepTypes,
|
StepTypes,
|
||||||
RunStatus,
|
RunStatus,
|
||||||
StepStatus,
|
StepStatus,
|
||||||
FilePurpose,
|
|
||||||
ContentTypes,
|
ContentTypes,
|
||||||
ToolCallTypes,
|
ToolCallTypes,
|
||||||
imageExtRegex,
|
|
||||||
imageGenTools,
|
imageGenTools,
|
||||||
EModelEndpoint,
|
EModelEndpoint,
|
||||||
defaultOrderQuery,
|
defaultOrderQuery,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
const { retrieveAndProcessFile } = require('~/server/services/Files/process');
|
const { retrieveAndProcessFile } = require('~/server/services/Files/process');
|
||||||
const { RunManager, waitForRun } = require('~/server/services/Runs');
|
|
||||||
const { processRequiredActions } = require('~/server/services/ToolService');
|
const { processRequiredActions } = require('~/server/services/ToolService');
|
||||||
const { createOnProgress, sendMessage, sleep } = require('~/server/utils');
|
const { createOnProgress, sendMessage, sleep } = require('~/server/utils');
|
||||||
|
const { RunManager, waitForRun } = require('~/server/services/Runs');
|
||||||
|
const { processMessages } = require('~/server/services/Threads');
|
||||||
const { TextStream } = require('~/app/clients');
|
const { TextStream } = require('~/app/clients');
|
||||||
const { logger } = require('~/config');
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
|
@ -230,6 +229,7 @@ function createInProgressHandler(openai, thread_id, messages) {
|
||||||
const { file_id } = output.image;
|
const { file_id } = output.image;
|
||||||
const file = await retrieveAndProcessFile({
|
const file = await retrieveAndProcessFile({
|
||||||
openai,
|
openai,
|
||||||
|
client: openai,
|
||||||
file_id,
|
file_id,
|
||||||
basename: `${file_id}.png`,
|
basename: `${file_id}.png`,
|
||||||
});
|
});
|
||||||
|
@ -299,7 +299,7 @@ function createInProgressHandler(openai, thread_id, messages) {
|
||||||
openai.index++;
|
openai.index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await processMessages(openai, [message]);
|
const result = await processMessages({ openai, client: openai, messages: [message] });
|
||||||
openai.addContentData({
|
openai.addContentData({
|
||||||
[ContentTypes.TEXT]: { value: result.text },
|
[ContentTypes.TEXT]: { value: result.text },
|
||||||
type: ContentTypes.TEXT,
|
type: ContentTypes.TEXT,
|
||||||
|
@ -318,8 +318,8 @@ function createInProgressHandler(openai, thread_id, messages) {
|
||||||
res: openai.res,
|
res: openai.res,
|
||||||
index: messageIndex,
|
index: messageIndex,
|
||||||
messageId: openai.responseMessage.messageId,
|
messageId: openai.responseMessage.messageId,
|
||||||
|
conversationId: openai.responseMessage.conversationId,
|
||||||
type: ContentTypes.TEXT,
|
type: ContentTypes.TEXT,
|
||||||
stream: true,
|
|
||||||
thread_id,
|
thread_id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -416,7 +416,13 @@ async function runAssistant({
|
||||||
// const { messages: sortedMessages, text } = await processMessages(openai, messages);
|
// const { messages: sortedMessages, text } = await processMessages(openai, messages);
|
||||||
// return { run, steps, messages: sortedMessages, text };
|
// return { run, steps, messages: sortedMessages, text };
|
||||||
const sortedMessages = messages.sort((a, b) => a.created_at - b.created_at);
|
const sortedMessages = messages.sort((a, b) => a.created_at - b.created_at);
|
||||||
return { run, steps, messages: sortedMessages };
|
return {
|
||||||
|
run,
|
||||||
|
steps,
|
||||||
|
messages: sortedMessages,
|
||||||
|
finalMessage: openai.responseMessage,
|
||||||
|
text: openai.responseText,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { submit_tool_outputs } = run.required_action;
|
const { submit_tool_outputs } = run.required_action;
|
||||||
|
@ -447,98 +453,8 @@ async function runAssistant({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sorts, processes, and flattens messages to a single string.
|
|
||||||
*
|
|
||||||
* @param {OpenAIClient} openai - The OpenAI client instance.
|
|
||||||
* @param {ThreadMessage[]} messages - An array of messages.
|
|
||||||
* @returns {Promise<{messages: ThreadMessage[], text: string}>} The sorted messages and the flattened text.
|
|
||||||
*/
|
|
||||||
async function processMessages(openai, messages = []) {
|
|
||||||
const sorted = messages.sort((a, b) => a.created_at - b.created_at);
|
|
||||||
|
|
||||||
let text = '';
|
|
||||||
for (const message of sorted) {
|
|
||||||
message.files = [];
|
|
||||||
for (const content of message.content) {
|
|
||||||
const processImageFile =
|
|
||||||
content.type === 'image_file' && !openai.processedFileIds.has(content.image_file?.file_id);
|
|
||||||
if (processImageFile) {
|
|
||||||
const { file_id } = content.image_file;
|
|
||||||
|
|
||||||
const file = await retrieveAndProcessFile({ openai, file_id, basename: `${file_id}.png` });
|
|
||||||
openai.processedFileIds.add(file_id);
|
|
||||||
message.files.push(file);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
text += (content.text?.value ?? '') + ' ';
|
|
||||||
logger.debug('[processMessages] Processing message:', { value: text });
|
|
||||||
|
|
||||||
// Process annotations if they exist
|
|
||||||
if (!content.text?.annotations?.length) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug('[processMessages] Processing annotations:', content.text.annotations);
|
|
||||||
for (const annotation of content.text.annotations) {
|
|
||||||
logger.debug('Current annotation:', annotation);
|
|
||||||
let file;
|
|
||||||
const processFilePath =
|
|
||||||
annotation.file_path && !openai.processedFileIds.has(annotation.file_path?.file_id);
|
|
||||||
|
|
||||||
if (processFilePath) {
|
|
||||||
const basename = imageExtRegex.test(annotation.text)
|
|
||||||
? path.basename(annotation.text)
|
|
||||||
: null;
|
|
||||||
file = await retrieveAndProcessFile({
|
|
||||||
openai,
|
|
||||||
file_id: annotation.file_path.file_id,
|
|
||||||
basename,
|
|
||||||
});
|
|
||||||
openai.processedFileIds.add(annotation.file_path.file_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const processFileCitation =
|
|
||||||
annotation.file_citation &&
|
|
||||||
!openai.processedFileIds.has(annotation.file_citation?.file_id);
|
|
||||||
|
|
||||||
if (processFileCitation) {
|
|
||||||
file = await retrieveAndProcessFile({
|
|
||||||
openai,
|
|
||||||
file_id: annotation.file_citation.file_id,
|
|
||||||
unknownType: true,
|
|
||||||
});
|
|
||||||
openai.processedFileIds.add(annotation.file_citation.file_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!file && (annotation.file_path || annotation.file_citation)) {
|
|
||||||
const { file_id } = annotation.file_citation || annotation.file_path || {};
|
|
||||||
file = await retrieveAndProcessFile({ openai, file_id, unknownType: true });
|
|
||||||
openai.processedFileIds.add(file_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!file) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file.purpose && file.purpose === FilePurpose.Assistants) {
|
|
||||||
text = text.replace(annotation.text, file.filename);
|
|
||||||
} else if (file.filepath) {
|
|
||||||
text = text.replace(annotation.text, file.filepath);
|
|
||||||
}
|
|
||||||
|
|
||||||
message.files.push(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { messages: sorted, text };
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getResponse,
|
getResponse,
|
||||||
runAssistant,
|
runAssistant,
|
||||||
processMessages,
|
|
||||||
createOnTextProgress,
|
createOnTextProgress,
|
||||||
};
|
};
|
||||||
|
|
|
@ -338,19 +338,26 @@ const processFileUpload = async ({ req, res, file, metadata }) => {
|
||||||
* Retrieves and processes an OpenAI file based on its type.
|
* Retrieves and processes an OpenAI file based on its type.
|
||||||
*
|
*
|
||||||
* @param {Object} params - The params passed to the function.
|
* @param {Object} params - The params passed to the function.
|
||||||
* @param {OpenAIClient} params.openai - The params passed to the function.
|
* @param {OpenAIClient} params.openai - The OpenAI client instance.
|
||||||
|
* @param {RunClient} params.client - The LibreChat client instance: either refers to `openai` or `streamRunManager`.
|
||||||
* @param {string} params.file_id - The ID of the file to retrieve.
|
* @param {string} params.file_id - The ID of the file to retrieve.
|
||||||
* @param {string} params.basename - The basename of the file (if image); e.g., 'image.jpg'.
|
* @param {string} params.basename - The basename of the file (if image); e.g., 'image.jpg'.
|
||||||
* @param {boolean} [params.unknownType] - Whether the file type is unknown.
|
* @param {boolean} [params.unknownType] - Whether the file type is unknown.
|
||||||
* @returns {Promise<{file_id: string, filepath: string, source: string, bytes?: number, width?: number, height?: number} | null>}
|
* @returns {Promise<{file_id: string, filepath: string, source: string, bytes?: number, width?: number, height?: number} | null>}
|
||||||
* - Returns null if `file_id` is not defined; else, the file metadata if successfully retrieved and processed.
|
* - Returns null if `file_id` is not defined; else, the file metadata if successfully retrieved and processed.
|
||||||
*/
|
*/
|
||||||
async function retrieveAndProcessFile({ openai, file_id, basename: _basename, unknownType }) {
|
async function retrieveAndProcessFile({
|
||||||
|
openai,
|
||||||
|
client,
|
||||||
|
file_id,
|
||||||
|
basename: _basename,
|
||||||
|
unknownType,
|
||||||
|
}) {
|
||||||
if (!file_id) {
|
if (!file_id) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (openai.attachedFileIds?.has(file_id)) {
|
if (client.attachedFileIds?.has(file_id)) {
|
||||||
return {
|
return {
|
||||||
file_id,
|
file_id,
|
||||||
// filepath: TODO: local source filepath?,
|
// filepath: TODO: local source filepath?,
|
||||||
|
@ -416,7 +423,7 @@ async function retrieveAndProcessFile({ openai, file_id, basename: _basename, un
|
||||||
*/
|
*/
|
||||||
const processAsImage = async (dataBuffer, fileExt) => {
|
const processAsImage = async (dataBuffer, fileExt) => {
|
||||||
// Logic to process image files, convert to webp, etc.
|
// Logic to process image files, convert to webp, etc.
|
||||||
const _file = await convertToWebP(openai.req, dataBuffer, 'high', `${file_id}${fileExt}`);
|
const _file = await convertToWebP(client.req, dataBuffer, 'high', `${file_id}${fileExt}`);
|
||||||
const file = {
|
const file = {
|
||||||
..._file,
|
..._file,
|
||||||
type: 'image/webp',
|
type: 'image/webp',
|
||||||
|
|
618
api/server/services/Runs/StreamRunManager.js
Normal file
618
api/server/services/Runs/StreamRunManager.js
Normal file
|
@ -0,0 +1,618 @@
|
||||||
|
const path = require('path');
|
||||||
|
const {
|
||||||
|
StepTypes,
|
||||||
|
ContentTypes,
|
||||||
|
ToolCallTypes,
|
||||||
|
// StepStatus,
|
||||||
|
MessageContentTypes,
|
||||||
|
AssistantStreamEvents,
|
||||||
|
} = require('librechat-data-provider');
|
||||||
|
const { retrieveAndProcessFile } = require('~/server/services/Files/process');
|
||||||
|
const { processRequiredActions } = require('~/server/services/ToolService');
|
||||||
|
const { createOnProgress, sendMessage } = require('~/server/utils');
|
||||||
|
const { processMessages } = require('~/server/services/Threads');
|
||||||
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements the StreamRunManager functionality for managing the streaming
|
||||||
|
* and processing of run steps, messages, and tool calls within a thread.
|
||||||
|
* @implements {StreamRunManager}
|
||||||
|
*/
|
||||||
|
class StreamRunManager {
|
||||||
|
constructor(fields) {
|
||||||
|
this.index = 0;
|
||||||
|
/** @type {Map<string, RunStep>} */
|
||||||
|
this.steps = new Map();
|
||||||
|
|
||||||
|
/** @type {Map<string, number} */
|
||||||
|
this.mappedOrder = new Map();
|
||||||
|
/** @type {Map<string, StepToolCall} */
|
||||||
|
this.orderedRunSteps = new Map();
|
||||||
|
/** @type {Set<string>} */
|
||||||
|
this.processedFileIds = new Set();
|
||||||
|
/** @type {Map<string, (delta: ToolCallDelta | string) => Promise<void>} */
|
||||||
|
this.progressCallbacks = new Map();
|
||||||
|
/** @type {Run | null} */
|
||||||
|
this.run = null;
|
||||||
|
|
||||||
|
/** @type {Express.Request} */
|
||||||
|
this.req = fields.req;
|
||||||
|
/** @type {Express.Response} */
|
||||||
|
this.res = fields.res;
|
||||||
|
/** @type {OpenAI} */
|
||||||
|
this.openai = fields.openai;
|
||||||
|
/** @type {string} */
|
||||||
|
this.apiKey = this.openai.apiKey;
|
||||||
|
/** @type {string} */
|
||||||
|
this.thread_id = fields.thread_id;
|
||||||
|
/** @type {RunCreateAndStreamParams} */
|
||||||
|
this.initialRunBody = fields.runBody;
|
||||||
|
/**
|
||||||
|
* @type {Object.<AssistantStreamEvents, (event: AssistantStreamEvent) => Promise<void>>}
|
||||||
|
*/
|
||||||
|
this.clientHandlers = fields.handlers ?? {};
|
||||||
|
/** @type {OpenAIRequestOptions} */
|
||||||
|
this.streamOptions = fields.streamOptions ?? {};
|
||||||
|
/** @type {Partial<TMessage>} */
|
||||||
|
this.finalMessage = fields.responseMessage ?? {};
|
||||||
|
/** @type {ThreadMessage[]} */
|
||||||
|
this.messages = [];
|
||||||
|
/** @type {string} */
|
||||||
|
this.text = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Object.<AssistantStreamEvents, (event: AssistantStreamEvent) => Promise<void>>}
|
||||||
|
*/
|
||||||
|
this.handlers = {
|
||||||
|
[AssistantStreamEvents.ThreadCreated]: this.handleThreadCreated,
|
||||||
|
[AssistantStreamEvents.ThreadRunCreated]: this.handleRunEvent,
|
||||||
|
[AssistantStreamEvents.ThreadRunQueued]: this.handleRunEvent,
|
||||||
|
[AssistantStreamEvents.ThreadRunInProgress]: this.handleRunEvent,
|
||||||
|
[AssistantStreamEvents.ThreadRunRequiresAction]: this.handleRunEvent,
|
||||||
|
[AssistantStreamEvents.ThreadRunCompleted]: this.handleRunEvent,
|
||||||
|
[AssistantStreamEvents.ThreadRunFailed]: this.handleRunEvent,
|
||||||
|
[AssistantStreamEvents.ThreadRunCancelling]: this.handleRunEvent,
|
||||||
|
[AssistantStreamEvents.ThreadRunCancelled]: this.handleRunEvent,
|
||||||
|
[AssistantStreamEvents.ThreadRunExpired]: this.handleRunEvent,
|
||||||
|
[AssistantStreamEvents.ThreadRunStepCreated]: this.handleRunStepEvent,
|
||||||
|
[AssistantStreamEvents.ThreadRunStepInProgress]: this.handleRunStepEvent,
|
||||||
|
[AssistantStreamEvents.ThreadRunStepCompleted]: this.handleRunStepEvent,
|
||||||
|
[AssistantStreamEvents.ThreadRunStepFailed]: this.handleRunStepEvent,
|
||||||
|
[AssistantStreamEvents.ThreadRunStepCancelled]: this.handleRunStepEvent,
|
||||||
|
[AssistantStreamEvents.ThreadRunStepExpired]: this.handleRunStepEvent,
|
||||||
|
[AssistantStreamEvents.ThreadRunStepDelta]: this.handleRunStepDeltaEvent,
|
||||||
|
[AssistantStreamEvents.ThreadMessageCreated]: this.handleMessageEvent,
|
||||||
|
[AssistantStreamEvents.ThreadMessageInProgress]: this.handleMessageEvent,
|
||||||
|
[AssistantStreamEvents.ThreadMessageCompleted]: this.handleMessageEvent,
|
||||||
|
[AssistantStreamEvents.ThreadMessageIncomplete]: this.handleMessageEvent,
|
||||||
|
[AssistantStreamEvents.ThreadMessageDelta]: this.handleMessageDeltaEvent,
|
||||||
|
[AssistantStreamEvents.ErrorEvent]: this.handleErrorEvent,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* Sends the content data to the client via SSE.
|
||||||
|
*
|
||||||
|
* @param {StreamContentData} data
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async addContentData(data) {
|
||||||
|
const { type, index } = data;
|
||||||
|
this.finalMessage.content[index] = { type, [type]: data[type] };
|
||||||
|
|
||||||
|
if (type === ContentTypes.TEXT) {
|
||||||
|
this.text += data[type].value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentData = {
|
||||||
|
index,
|
||||||
|
type,
|
||||||
|
[type]: data[type],
|
||||||
|
thread_id: this.thread_id,
|
||||||
|
messageId: this.finalMessage.messageId,
|
||||||
|
conversationId: this.finalMessage.conversationId,
|
||||||
|
};
|
||||||
|
|
||||||
|
sendMessage(this.res, contentData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* <------------------ Main Event Handlers ------------------> */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the assistant and handle the events.
|
||||||
|
* @param {Object} params -
|
||||||
|
* The parameters for running the assistant.
|
||||||
|
* @param {string} params.thread_id - The thread id.
|
||||||
|
* @param {RunCreateAndStreamParams} params.body - The body of the run.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async runAssistant({ thread_id, body }) {
|
||||||
|
const streamRun = this.openai.beta.threads.runs.createAndStream(
|
||||||
|
thread_id,
|
||||||
|
body,
|
||||||
|
this.streamOptions,
|
||||||
|
);
|
||||||
|
for await (const event of streamRun) {
|
||||||
|
await this.handleEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the event.
|
||||||
|
* @param {AssistantStreamEvent} event - The stream event object.
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async handleEvent(event) {
|
||||||
|
const handler = this.handlers[event.event];
|
||||||
|
const clientHandler = this.clientHandlers[event.event];
|
||||||
|
|
||||||
|
if (clientHandler) {
|
||||||
|
await clientHandler.call(this, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handler) {
|
||||||
|
await handler.call(this, event);
|
||||||
|
} else {
|
||||||
|
logger.warn(`Unhandled event type: ${event.event}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle thread.created event
|
||||||
|
* @param {ThreadCreated} event -
|
||||||
|
* The thread.created event object.
|
||||||
|
*/
|
||||||
|
async handleThreadCreated(event) {
|
||||||
|
logger.debug('Thread created:', event.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Run Events
|
||||||
|
* @param {ThreadRunCreated | ThreadRunQueued | ThreadRunInProgress | ThreadRunRequiresAction | ThreadRunCompleted | ThreadRunFailed | ThreadRunCancelling | ThreadRunCancelled | ThreadRunExpired} event -
|
||||||
|
* The run event object.
|
||||||
|
*/
|
||||||
|
async handleRunEvent(event) {
|
||||||
|
this.run = event.data;
|
||||||
|
logger.debug('Run event:', this.run);
|
||||||
|
if (event.event === AssistantStreamEvents.ThreadRunRequiresAction) {
|
||||||
|
await this.onRunRequiresAction(event);
|
||||||
|
} else if (event.event === AssistantStreamEvents.ThreadRunCompleted) {
|
||||||
|
logger.debug('Run completed:', this.run);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Run Step Events
|
||||||
|
* @param {ThreadRunStepCreated | ThreadRunStepInProgress | ThreadRunStepCompleted | ThreadRunStepFailed | ThreadRunStepCancelled | ThreadRunStepExpired} event -
|
||||||
|
* The run step event object.
|
||||||
|
*/
|
||||||
|
async handleRunStepEvent(event) {
|
||||||
|
logger.debug('Run step event:', event.data);
|
||||||
|
|
||||||
|
const step = event.data;
|
||||||
|
this.steps.set(step.id, step);
|
||||||
|
|
||||||
|
if (event.event === AssistantStreamEvents.ThreadRunStepCreated) {
|
||||||
|
this.onRunStepCreated(event);
|
||||||
|
} else if (event.event === AssistantStreamEvents.ThreadRunStepCompleted) {
|
||||||
|
this.onRunStepCompleted(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* <------------------ Delta Events ------------------> */
|
||||||
|
|
||||||
|
/** @param {CodeImageOutput} */
|
||||||
|
async handleCodeImageOutput(output) {
|
||||||
|
if (this.processedFileIds.has(output.image?.file_id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { file_id } = output.image;
|
||||||
|
const file = await retrieveAndProcessFile({
|
||||||
|
openai: this.openai,
|
||||||
|
client: this,
|
||||||
|
file_id,
|
||||||
|
basename: `${file_id}.png`,
|
||||||
|
});
|
||||||
|
// toolCall.asset_pointer = file.filepath;
|
||||||
|
const prelimImage = {
|
||||||
|
file_id,
|
||||||
|
filename: path.basename(file.filepath),
|
||||||
|
filepath: file.filepath,
|
||||||
|
height: file.height,
|
||||||
|
width: file.width,
|
||||||
|
};
|
||||||
|
// check if every key has a value before adding to content
|
||||||
|
const prelimImageKeys = Object.keys(prelimImage);
|
||||||
|
const validImageFile = prelimImageKeys.every((key) => prelimImage[key]);
|
||||||
|
|
||||||
|
if (!validImageFile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = this.getStepIndex(file_id);
|
||||||
|
const image_file = {
|
||||||
|
[ContentTypes.IMAGE_FILE]: prelimImage,
|
||||||
|
type: ContentTypes.IMAGE_FILE,
|
||||||
|
index,
|
||||||
|
};
|
||||||
|
this.addContentData(image_file);
|
||||||
|
this.processedFileIds.add(file_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Tool Call Stream
|
||||||
|
* @param {number} index - The index of the tool call.
|
||||||
|
* @param {StepToolCall} toolCall -
|
||||||
|
* The current tool call object.
|
||||||
|
*/
|
||||||
|
createToolCallStream(index, toolCall) {
|
||||||
|
/** @type {StepToolCall} */
|
||||||
|
const state = toolCall;
|
||||||
|
const type = state.type;
|
||||||
|
const data = state[type];
|
||||||
|
|
||||||
|
/** @param {ToolCallDelta} */
|
||||||
|
const deltaHandler = async (delta) => {
|
||||||
|
for (const key in delta) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
||||||
|
logger.warn(`Unhandled tool call key "${key}", delta: `, delta);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(delta[key])) {
|
||||||
|
if (!Array.isArray(data[key])) {
|
||||||
|
data[key] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const d of delta[key]) {
|
||||||
|
if (typeof d === 'object' && !Object.prototype.hasOwnProperty.call(d, 'index')) {
|
||||||
|
logger.warn('Expected an object with an \'index\' for array updates but got:', d);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageOutput = type === ToolCallTypes.CODE_INTERPRETER && d?.type === 'image';
|
||||||
|
|
||||||
|
if (imageOutput) {
|
||||||
|
await this.handleCodeImageOutput(d);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { index, ...updateData } = d;
|
||||||
|
// Ensure the data at index is an object or undefined before assigning
|
||||||
|
if (typeof data[key][index] !== 'object' || data[key][index] === null) {
|
||||||
|
data[key][index] = {};
|
||||||
|
}
|
||||||
|
// Merge the updateData into data[key][index]
|
||||||
|
for (const updateKey in updateData) {
|
||||||
|
data[key][index][updateKey] = updateData[updateKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (typeof delta[key] === 'string' && typeof data[key] === 'string') {
|
||||||
|
// Concatenate strings
|
||||||
|
data[key] += delta[key];
|
||||||
|
} else if (
|
||||||
|
typeof delta[key] === 'object' &&
|
||||||
|
delta[key] !== null &&
|
||||||
|
!Array.isArray(delta[key])
|
||||||
|
) {
|
||||||
|
// Merge objects
|
||||||
|
data[key] = { ...data[key], ...delta[key] };
|
||||||
|
} else {
|
||||||
|
// Directly set the value for other types
|
||||||
|
data[key] = delta[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
state[type] = data;
|
||||||
|
|
||||||
|
this.addContentData({
|
||||||
|
[ContentTypes.TOOL_CALL]: toolCall,
|
||||||
|
type: ContentTypes.TOOL_CALL,
|
||||||
|
index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return deltaHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} stepId -
|
||||||
|
* @param {StepToolCall} toolCall -
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
handleNewToolCall(stepId, toolCall) {
|
||||||
|
const stepKey = this.generateToolCallKey(stepId, toolCall);
|
||||||
|
const index = this.getStepIndex(stepKey);
|
||||||
|
this.getStepIndex(toolCall.id, index);
|
||||||
|
toolCall.progress = 0.01;
|
||||||
|
this.orderedRunSteps.set(index, toolCall);
|
||||||
|
const progressCallback = this.createToolCallStream(index, toolCall);
|
||||||
|
this.progressCallbacks.set(stepKey, progressCallback);
|
||||||
|
|
||||||
|
this.addContentData({
|
||||||
|
[ContentTypes.TOOL_CALL]: toolCall,
|
||||||
|
type: ContentTypes.TOOL_CALL,
|
||||||
|
index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Completed Tool Call
|
||||||
|
* @param {string} stepId - The id of the step the tool_call is part of.
|
||||||
|
* @param {StepToolCall} toolCall - The tool call object.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
handleCompletedToolCall(stepId, toolCall) {
|
||||||
|
if (toolCall.type === ToolCallTypes.FUNCTION) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stepKey = this.generateToolCallKey(stepId, toolCall);
|
||||||
|
const index = this.getStepIndex(stepKey);
|
||||||
|
toolCall.progress = 1;
|
||||||
|
this.orderedRunSteps.set(index, toolCall);
|
||||||
|
this.addContentData({
|
||||||
|
[ContentTypes.TOOL_CALL]: toolCall,
|
||||||
|
type: ContentTypes.TOOL_CALL,
|
||||||
|
index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Run Step Delta Event
|
||||||
|
* @param {ThreadRunStepDelta} event -
|
||||||
|
* The run step delta event object.
|
||||||
|
*/
|
||||||
|
async handleRunStepDeltaEvent(event) {
|
||||||
|
const { delta, id: stepId } = event.data;
|
||||||
|
|
||||||
|
if (!delta.step_details) {
|
||||||
|
logger.warn('Undefined or unhandled run step delta:', delta);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {{ tool_calls: Array<ToolCallDeltaObject> }} */
|
||||||
|
const { tool_calls } = delta.step_details;
|
||||||
|
|
||||||
|
if (!tool_calls) {
|
||||||
|
logger.warn('Unhandled run step details', delta.step_details);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const toolCall of tool_calls) {
|
||||||
|
const stepKey = this.generateToolCallKey(stepId, toolCall);
|
||||||
|
|
||||||
|
if (!this.mappedOrder.has(stepKey)) {
|
||||||
|
this.handleNewToolCall(stepId, toolCall);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toolCallDelta = toolCall[toolCall.type];
|
||||||
|
const progressCallback = this.progressCallbacks.get(stepKey);
|
||||||
|
await progressCallback(toolCallDelta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Message Delta Event
|
||||||
|
* @param {ThreadMessageDelta} event -
|
||||||
|
* The Message Delta event object.
|
||||||
|
*/
|
||||||
|
async handleMessageDeltaEvent(event) {
|
||||||
|
const message = event.data;
|
||||||
|
const onProgress = this.progressCallbacks.get(message.id);
|
||||||
|
const content = message.delta.content?.[0];
|
||||||
|
|
||||||
|
if (content && content.type === MessageContentTypes.TEXT) {
|
||||||
|
onProgress(content.text.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Error Event
|
||||||
|
* @param {ErrorEvent} event -
|
||||||
|
* The Error event object.
|
||||||
|
*/
|
||||||
|
async handleErrorEvent(event) {
|
||||||
|
logger.error('Error event:', event.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* <------------------ Misc. Helpers ------------------> */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the step index for a given step key, creating a new index if it doesn't exist.
|
||||||
|
* @param {string} stepKey -
|
||||||
|
* The access key for the step. Either a message.id, tool_call key, or file_id.
|
||||||
|
* @param {number | undefined} [overrideIndex] - An override index to use an alternative stepKey.
|
||||||
|
* This is necessary due to the toolCall Id being unavailable in delta stream events.
|
||||||
|
* @returns {number | undefined} index - The index of the step; `undefined` if invalid key or using overrideIndex.
|
||||||
|
*/
|
||||||
|
getStepIndex(stepKey, overrideIndex) {
|
||||||
|
if (!stepKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isNaN(overrideIndex)) {
|
||||||
|
this.mappedOrder.set(stepKey, overrideIndex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let index = this.mappedOrder.get(stepKey);
|
||||||
|
|
||||||
|
if (index === undefined) {
|
||||||
|
index = this.index;
|
||||||
|
this.mappedOrder.set(stepKey, this.index);
|
||||||
|
this.index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate Tool Call Key
|
||||||
|
* @param {string} stepId - The id of the step the tool_call is part of.
|
||||||
|
* @param {StepToolCall} toolCall - The tool call object.
|
||||||
|
* @returns {string} key - The generated key for the tool call.
|
||||||
|
*/
|
||||||
|
generateToolCallKey(stepId, toolCall) {
|
||||||
|
return `${stepId}_tool_call_${toolCall.index}_${toolCall.type}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* <------------------ Run Event handlers ------------------> */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Run Events Requiring Action
|
||||||
|
* @param {ThreadRunRequiresAction} event -
|
||||||
|
* The run event object requiring action.
|
||||||
|
*/
|
||||||
|
async onRunRequiresAction(event) {
|
||||||
|
const run = event.data;
|
||||||
|
const { submit_tool_outputs } = run.required_action;
|
||||||
|
const actions = submit_tool_outputs.tool_calls.map((item) => {
|
||||||
|
const functionCall = item.function;
|
||||||
|
const args = JSON.parse(functionCall.arguments);
|
||||||
|
return {
|
||||||
|
tool: functionCall.name,
|
||||||
|
toolInput: args,
|
||||||
|
toolCallId: item.id,
|
||||||
|
run_id: run.id,
|
||||||
|
thread_id: this.thread_id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const { tool_outputs } = await processRequiredActions(this, actions);
|
||||||
|
/** @type {AssistantStream | undefined} */
|
||||||
|
let toolRun;
|
||||||
|
try {
|
||||||
|
toolRun = this.openai.beta.threads.runs.submitToolOutputsStream(
|
||||||
|
run.thread_id,
|
||||||
|
run.id,
|
||||||
|
{
|
||||||
|
tool_outputs,
|
||||||
|
stream: true,
|
||||||
|
},
|
||||||
|
this.streamOptions,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error submitting tool outputs:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const event of toolRun) {
|
||||||
|
await this.handleEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* <------------------ RunStep Event handlers ------------------> */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Run Step Created Events
|
||||||
|
* @param {ThreadRunStepCreated} event -
|
||||||
|
* The created run step event object.
|
||||||
|
*/
|
||||||
|
async onRunStepCreated(event) {
|
||||||
|
const step = event.data;
|
||||||
|
const isMessage = step.type === StepTypes.MESSAGE_CREATION;
|
||||||
|
|
||||||
|
if (isMessage) {
|
||||||
|
/** @type {MessageCreationStepDetails} */
|
||||||
|
const { message_creation } = step.step_details;
|
||||||
|
const stepKey = message_creation.message_id;
|
||||||
|
const index = this.getStepIndex(stepKey);
|
||||||
|
this.orderedRunSteps.set(index, message_creation);
|
||||||
|
// Create the Factory Function to stream the message
|
||||||
|
const { onProgress: progressCallback } = createOnProgress({
|
||||||
|
// todo: add option to save partialText to db
|
||||||
|
// onProgress: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
// This creates a function that attaches all of the parameters
|
||||||
|
// specified here to each SSE message generated by the TextStream
|
||||||
|
const onProgress = progressCallback({
|
||||||
|
index,
|
||||||
|
res: this.res,
|
||||||
|
messageId: this.finalMessage.messageId,
|
||||||
|
conversationId: this.finalMessage.conversationId,
|
||||||
|
thread_id: this.thread_id,
|
||||||
|
type: ContentTypes.TEXT,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.progressCallbacks.set(stepKey, onProgress);
|
||||||
|
this.orderedRunSteps.set(index, step);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (step.type !== StepTypes.TOOL_CALLS) {
|
||||||
|
logger.warn('Unhandled step creation type:', step.type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {{ tool_calls: StepToolCall[] }} */
|
||||||
|
const { tool_calls } = step.step_details;
|
||||||
|
for (const toolCall of tool_calls) {
|
||||||
|
this.handleNewToolCall(step.id, toolCall);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Run Step Completed Events
|
||||||
|
* @param {ThreadRunStepCompleted} event -
|
||||||
|
* The completed run step event object.
|
||||||
|
*/
|
||||||
|
async onRunStepCompleted(event) {
|
||||||
|
const step = event.data;
|
||||||
|
const isMessage = step.type === StepTypes.MESSAGE_CREATION;
|
||||||
|
|
||||||
|
if (isMessage) {
|
||||||
|
logger.warn('RunStep Message completion: to be handled by Message Event.', step);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {{ tool_calls: StepToolCall[] }} */
|
||||||
|
const { tool_calls } = step.step_details;
|
||||||
|
for (let i = 0; i < tool_calls.length; i++) {
|
||||||
|
const toolCall = tool_calls[i];
|
||||||
|
toolCall.index = i;
|
||||||
|
this.handleCompletedToolCall(step.id, toolCall);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* <------------------ Message Event handlers ------------------> */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Message Event
|
||||||
|
* @param {ThreadMessageCreated | ThreadMessageInProgress | ThreadMessageCompleted | ThreadMessageIncomplete} event -
|
||||||
|
* The Message event object.
|
||||||
|
*/
|
||||||
|
async handleMessageEvent(event) {
|
||||||
|
if (event.event === AssistantStreamEvents.ThreadMessageCompleted) {
|
||||||
|
this.messageCompleted(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Message Completed Events
|
||||||
|
* @param {ThreadMessageCompleted} event -
|
||||||
|
* The Completed Message event object.
|
||||||
|
*/
|
||||||
|
async messageCompleted(event) {
|
||||||
|
const message = event.data;
|
||||||
|
const result = await processMessages({
|
||||||
|
openai: this.openai,
|
||||||
|
client: this,
|
||||||
|
messages: [message],
|
||||||
|
});
|
||||||
|
const index = this.mappedOrder.get(message.id);
|
||||||
|
this.addContentData({
|
||||||
|
[ContentTypes.TEXT]: { value: result.text },
|
||||||
|
type: ContentTypes.TEXT,
|
||||||
|
index,
|
||||||
|
});
|
||||||
|
this.messages.push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = StreamRunManager;
|
|
@ -1,9 +1,11 @@
|
||||||
const handle = require('./handle');
|
const handle = require('./handle');
|
||||||
const methods = require('./methods');
|
const methods = require('./methods');
|
||||||
const RunManager = require('./RunManager');
|
const RunManager = require('./RunManager');
|
||||||
|
const StreamRunManager = require('./StreamRunManager');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
...handle,
|
...handle,
|
||||||
...methods,
|
...methods,
|
||||||
RunManager,
|
RunManager,
|
||||||
|
StreamRunManager,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
|
const path = require('path');
|
||||||
const { v4 } = require('uuid');
|
const { v4 } = require('uuid');
|
||||||
const {
|
const {
|
||||||
EModelEndpoint,
|
|
||||||
Constants,
|
Constants,
|
||||||
defaultOrderQuery,
|
FilePurpose,
|
||||||
ContentTypes,
|
ContentTypes,
|
||||||
|
imageExtRegex,
|
||||||
|
EModelEndpoint,
|
||||||
|
defaultOrderQuery,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
|
const { retrieveAndProcessFile } = require('~/server/services/Files/process');
|
||||||
const { recordMessage, getMessages } = require('~/models/Message');
|
const { recordMessage, getMessages } = require('~/models/Message');
|
||||||
const { saveConvo } = require('~/models/Conversation');
|
const { saveConvo } = require('~/models/Conversation');
|
||||||
const spendTokens = require('~/models/spendTokens');
|
const spendTokens = require('~/models/spendTokens');
|
||||||
const { countTokens } = require('~/server/utils');
|
const { countTokens } = require('~/server/utils');
|
||||||
|
const { logger } = require('~/config');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes a new thread or adds messages to an existing thread.
|
* Initializes a new thread or adds messages to an existing thread.
|
||||||
|
@ -484,9 +489,108 @@ const recordUsage = async ({ prompt_tokens, completion_tokens, model, user, conv
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts, processes, and flattens messages to a single string.
|
||||||
|
*
|
||||||
|
* @param {object} params - The OpenAI client instance.
|
||||||
|
* @param {OpenAIClient} params.openai - The OpenAI client instance.
|
||||||
|
* @param {RunClient} params.client - The LibreChat client that manages the run: either refers to `OpenAI` or `StreamRunManager`.
|
||||||
|
* @param {ThreadMessage[]} params.messages - An array of messages.
|
||||||
|
* @returns {Promise<{messages: ThreadMessage[], text: string}>} The sorted messages and the flattened text.
|
||||||
|
*/
|
||||||
|
async function processMessages({ openai, client, messages = [] }) {
|
||||||
|
const sorted = messages.sort((a, b) => a.created_at - b.created_at);
|
||||||
|
|
||||||
|
let text = '';
|
||||||
|
for (const message of sorted) {
|
||||||
|
message.files = [];
|
||||||
|
for (const content of message.content) {
|
||||||
|
const processImageFile =
|
||||||
|
content.type === 'image_file' && !client.processedFileIds.has(content.image_file?.file_id);
|
||||||
|
if (processImageFile) {
|
||||||
|
const { file_id } = content.image_file;
|
||||||
|
|
||||||
|
const file = await retrieveAndProcessFile({
|
||||||
|
openai,
|
||||||
|
client,
|
||||||
|
file_id,
|
||||||
|
basename: `${file_id}.png`,
|
||||||
|
});
|
||||||
|
client.processedFileIds.add(file_id);
|
||||||
|
message.files.push(file);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
text += (content.text?.value ?? '') + ' ';
|
||||||
|
logger.debug('[processMessages] Processing message:', { value: text });
|
||||||
|
|
||||||
|
// Process annotations if they exist
|
||||||
|
if (!content.text?.annotations?.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug('[processMessages] Processing annotations:', content.text.annotations);
|
||||||
|
for (const annotation of content.text.annotations) {
|
||||||
|
logger.debug('Current annotation:', annotation);
|
||||||
|
let file;
|
||||||
|
const processFilePath =
|
||||||
|
annotation.file_path && !client.processedFileIds.has(annotation.file_path?.file_id);
|
||||||
|
|
||||||
|
if (processFilePath) {
|
||||||
|
const basename = imageExtRegex.test(annotation.text)
|
||||||
|
? path.basename(annotation.text)
|
||||||
|
: null;
|
||||||
|
file = await retrieveAndProcessFile({
|
||||||
|
openai,
|
||||||
|
client,
|
||||||
|
file_id: annotation.file_path.file_id,
|
||||||
|
basename,
|
||||||
|
});
|
||||||
|
client.processedFileIds.add(annotation.file_path.file_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const processFileCitation =
|
||||||
|
annotation.file_citation &&
|
||||||
|
!client.processedFileIds.has(annotation.file_citation?.file_id);
|
||||||
|
|
||||||
|
if (processFileCitation) {
|
||||||
|
file = await retrieveAndProcessFile({
|
||||||
|
openai,
|
||||||
|
client,
|
||||||
|
file_id: annotation.file_citation.file_id,
|
||||||
|
unknownType: true,
|
||||||
|
});
|
||||||
|
client.processedFileIds.add(annotation.file_citation.file_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file && (annotation.file_path || annotation.file_citation)) {
|
||||||
|
const { file_id } = annotation.file_citation || annotation.file_path || {};
|
||||||
|
file = await retrieveAndProcessFile({ openai, client, file_id, unknownType: true });
|
||||||
|
client.processedFileIds.add(file_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.purpose && file.purpose === FilePurpose.Assistants) {
|
||||||
|
text = text.replace(annotation.text, file.filename);
|
||||||
|
} else if (file.filepath) {
|
||||||
|
text = text.replace(annotation.text, file.filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
message.files.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { messages: sorted, text };
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
initThread,
|
initThread,
|
||||||
recordUsage,
|
recordUsage,
|
||||||
|
processMessages,
|
||||||
saveUserMessage,
|
saveUserMessage,
|
||||||
checkMessageGaps,
|
checkMessageGaps,
|
||||||
addThreadMetadata,
|
addThreadMetadata,
|
||||||
|
|
|
@ -10,7 +10,7 @@ const {
|
||||||
validateAndParseOpenAPISpec,
|
validateAndParseOpenAPISpec,
|
||||||
actionDelimiter,
|
actionDelimiter,
|
||||||
} = require('librechat-data-provider');
|
} = require('librechat-data-provider');
|
||||||
const { loadActionSets, createActionTool } = require('./ActionService');
|
const { loadActionSets, createActionTool, domainParser } = require('./ActionService');
|
||||||
const { processFileURL } = require('~/server/services/Files/process');
|
const { processFileURL } = require('~/server/services/Files/process');
|
||||||
const { loadTools } = require('~/app/clients/tools/util');
|
const { loadTools } = require('~/app/clients/tools/util');
|
||||||
const { redactMessage } = require('~/config/parsers');
|
const { redactMessage } = require('~/config/parsers');
|
||||||
|
@ -112,26 +112,26 @@ function formatToOpenAIAssistantTool(tool) {
|
||||||
/**
|
/**
|
||||||
* Processes return required actions from run.
|
* Processes return required actions from run.
|
||||||
*
|
*
|
||||||
* @param {OpenAIClient} openai - OpenAI Client.
|
* @param {OpenAIClient} client - OpenAI or StreamRunManager Client.
|
||||||
* @param {RequiredAction[]} requiredActions - The required actions to submit outputs for.
|
* @param {RequiredAction[]} requiredActions - The required actions to submit outputs for.
|
||||||
* @returns {Promise<ToolOutputs>} The outputs of the tools.
|
* @returns {Promise<ToolOutputs>} The outputs of the tools.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
async function processRequiredActions(openai, requiredActions) {
|
async function processRequiredActions(client, requiredActions) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`[required actions] user: ${openai.req.user.id} | thread_id: ${requiredActions[0].thread_id} | run_id: ${requiredActions[0].run_id}`,
|
`[required actions] user: ${client.req.user.id} | thread_id: ${requiredActions[0].thread_id} | run_id: ${requiredActions[0].run_id}`,
|
||||||
requiredActions,
|
requiredActions,
|
||||||
);
|
);
|
||||||
const tools = requiredActions.map((action) => action.tool);
|
const tools = requiredActions.map((action) => action.tool);
|
||||||
const loadedTools = await loadTools({
|
const loadedTools = await loadTools({
|
||||||
user: openai.req.user.id,
|
user: client.req.user.id,
|
||||||
model: openai.req.body.model ?? 'gpt-3.5-turbo-1106',
|
model: client.req.body.model ?? 'gpt-3.5-turbo-1106',
|
||||||
tools,
|
tools,
|
||||||
functions: true,
|
functions: true,
|
||||||
options: {
|
options: {
|
||||||
processFileURL,
|
processFileURL,
|
||||||
openAIApiKey: openai.apiKey,
|
openAIApiKey: client.apiKey,
|
||||||
fileStrategy: openai.req.app.locals.fileStrategy,
|
fileStrategy: client.req.app.locals.fileStrategy,
|
||||||
returnMetadata: true,
|
returnMetadata: true,
|
||||||
},
|
},
|
||||||
skipSpecs: true,
|
skipSpecs: true,
|
||||||
|
@ -170,14 +170,14 @@ async function processRequiredActions(openai, requiredActions) {
|
||||||
action: isActionTool,
|
action: isActionTool,
|
||||||
};
|
};
|
||||||
|
|
||||||
const toolCallIndex = openai.mappedOrder.get(toolCall.id);
|
const toolCallIndex = client.mappedOrder.get(toolCall.id);
|
||||||
|
|
||||||
if (imageGenTools.has(currentAction.tool)) {
|
if (imageGenTools.has(currentAction.tool)) {
|
||||||
const imageOutput = output;
|
const imageOutput = output;
|
||||||
toolCall.function.output = `${currentAction.tool} displayed an image. All generated images are already plainly visible, so don't repeat the descriptions in detail. Do not list download links as they are available in the UI already. The user may download the images by clicking on them, but do not mention anything about downloading to the user.`;
|
toolCall.function.output = `${currentAction.tool} displayed an image. All generated images are already plainly visible, so don't repeat the descriptions in detail. Do not list download links as they are available in the UI already. The user may download the images by clicking on them, but do not mention anything about downloading to the user.`;
|
||||||
|
|
||||||
// Streams the "Finished" state of the tool call in the UI
|
// Streams the "Finished" state of the tool call in the UI
|
||||||
openai.addContentData({
|
client.addContentData({
|
||||||
[ContentTypes.TOOL_CALL]: toolCall,
|
[ContentTypes.TOOL_CALL]: toolCall,
|
||||||
index: toolCallIndex,
|
index: toolCallIndex,
|
||||||
type: ContentTypes.TOOL_CALL,
|
type: ContentTypes.TOOL_CALL,
|
||||||
|
@ -198,10 +198,10 @@ async function processRequiredActions(openai, requiredActions) {
|
||||||
index: toolCallIndex,
|
index: toolCallIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
openai.addContentData(image_file);
|
client.addContentData(image_file);
|
||||||
|
|
||||||
// Update the stored tool call
|
// Update the stored tool call
|
||||||
openai.seenToolCalls.set(toolCall.id, toolCall);
|
client.seenToolCalls && client.seenToolCalls.set(toolCall.id, toolCall);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tool_call_id: currentAction.toolCallId,
|
tool_call_id: currentAction.toolCallId,
|
||||||
|
@ -209,8 +209,8 @@ async function processRequiredActions(openai, requiredActions) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
openai.seenToolCalls.set(toolCall.id, toolCall);
|
client.seenToolCalls && client.seenToolCalls.set(toolCall.id, toolCall);
|
||||||
openai.addContentData({
|
client.addContentData({
|
||||||
[ContentTypes.TOOL_CALL]: toolCall,
|
[ContentTypes.TOOL_CALL]: toolCall,
|
||||||
index: toolCallIndex,
|
index: toolCallIndex,
|
||||||
type: ContentTypes.TOOL_CALL,
|
type: ContentTypes.TOOL_CALL,
|
||||||
|
@ -230,13 +230,13 @@ async function processRequiredActions(openai, requiredActions) {
|
||||||
if (!actionSets.length) {
|
if (!actionSets.length) {
|
||||||
actionSets =
|
actionSets =
|
||||||
(await loadActionSets({
|
(await loadActionSets({
|
||||||
user: openai.req.user.id,
|
user: client.req.user.id,
|
||||||
assistant_id: openai.req.body.assistant_id,
|
assistant_id: client.req.body.assistant_id,
|
||||||
})) ?? [];
|
})) ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const actionSet = actionSets.find((action) =>
|
const actionSet = actionSets.find((action) =>
|
||||||
currentAction.tool.includes(action.metadata.domain),
|
currentAction.tool.includes(domainParser(client.req, action.metadata.domain, true)),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!actionSet) {
|
if (!actionSet) {
|
||||||
|
@ -251,7 +251,7 @@ async function processRequiredActions(openai, requiredActions) {
|
||||||
const validationResult = validateAndParseOpenAPISpec(actionSet.metadata.raw_spec);
|
const validationResult = validateAndParseOpenAPISpec(actionSet.metadata.raw_spec);
|
||||||
if (!validationResult.spec) {
|
if (!validationResult.spec) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid spec: user: ${openai.req.user.id} | thread_id: ${requiredActions[0].thread_id} | run_id: ${requiredActions[0].run_id}`,
|
`Invalid spec: user: ${client.req.user.id} | thread_id: ${requiredActions[0].thread_id} | run_id: ${requiredActions[0].run_id}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const { requestBuilders } = openapiToFunction(validationResult.spec);
|
const { requestBuilders } = openapiToFunction(validationResult.spec);
|
||||||
|
@ -260,7 +260,7 @@ async function processRequiredActions(openai, requiredActions) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const functionName = currentAction.tool.replace(
|
const functionName = currentAction.tool.replace(
|
||||||
`${actionDelimiter}${actionSet.metadata.domain}`,
|
`${actionDelimiter}${domainParser(client.req, actionSet.metadata.domain, true)}`,
|
||||||
'',
|
'',
|
||||||
);
|
);
|
||||||
const requestBuilder = builders[functionName];
|
const requestBuilder = builders[functionName];
|
||||||
|
|
239
api/typedefs.js
239
api/typedefs.js
|
@ -8,6 +8,180 @@
|
||||||
* @memberof typedefs
|
* @memberof typedefs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports AssistantStreamEvent
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent} AssistantStreamEvent
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports AssistantStream
|
||||||
|
* @typedef {AsyncIterable<AssistantStreamEvent>} AssistantStream
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports RunCreateAndStreamParams
|
||||||
|
* @typedef {import('openai').OpenAI.Beta.Threads.RunCreateAndStreamParams} RunCreateAndStreamParams
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports OpenAIRequestOptions
|
||||||
|
* @typedef {import('openai').OpenAI.RequestOptions} OpenAIRequestOptions
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadCreated
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadCreated} ThreadCreated
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadRunCreated
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadRunCreated} ThreadRunCreated
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadRunQueued
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadRunQueued} ThreadRunQueued
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadRunInProgress
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadRunInProgress} ThreadRunInProgress
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadRunRequiresAction
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadRunRequiresAction} ThreadRunRequiresAction
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadRunCompleted
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadRunCompleted} ThreadRunCompleted
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadRunFailed
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadRunFailed} ThreadRunFailed
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadRunCancelling
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadRunCancelling} ThreadRunCancelling
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadRunCancelled
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadRunCancelled} ThreadRunCancelled
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadRunExpired
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadRunExpired} ThreadRunExpired
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadRunStepCreated
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadRunStepCreated} ThreadRunStepCreated
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadRunStepInProgress
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadRunStepInProgress} ThreadRunStepInProgress
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadRunStepDelta
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadRunStepDelta} ThreadRunStepDelta
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadRunStepCompleted
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadRunStepCompleted} ThreadRunStepCompleted
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadRunStepFailed
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadRunStepFailed} ThreadRunStepFailed
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadRunStepCancelled
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadRunStepCancelled} ThreadRunStepCancelled
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadRunStepExpired
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadRunStepExpired} ThreadRunStepExpired
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadMessageCreated
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadMessageCreated} ThreadMessageCreated
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadMessageInProgress
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadMessageInProgress} ThreadMessageInProgress
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadMessageDelta
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadMessageDelta} ThreadMessageDelta
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadMessageCompleted
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadMessageCompleted} ThreadMessageCompleted
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ThreadMessageIncomplete
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ThreadMessageIncomplete} ThreadMessageIncomplete
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ErrorEvent
|
||||||
|
* @typedef {import('openai').default.Beta.AssistantStreamEvent.ErrorEvent} ErrorEvent
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ToolCallDeltaObject
|
||||||
|
* @typedef {import('openai').default.Beta.Threads.Runs.Steps.ToolCallDeltaObject} ToolCallDeltaObject
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports ToolCallDelta
|
||||||
|
* @typedef {import('openai').default.Beta.Threads.Runs.Steps.ToolCallDelta} ToolCallDelta
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @exports Assistant
|
* @exports Assistant
|
||||||
* @typedef {import('librechat-data-provider').Assistant} Assistant
|
* @typedef {import('librechat-data-provider').Assistant} Assistant
|
||||||
|
@ -109,6 +283,18 @@
|
||||||
* @memberof typedefs
|
* @memberof typedefs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports TMessageContentParts
|
||||||
|
* @typedef {import('librechat-data-provider').TMessageContentParts} TMessageContentParts
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @exports StreamContentData
|
||||||
|
* @typedef {import('librechat-data-provider').StreamContentData} StreamContentData
|
||||||
|
* @memberof typedefs
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @exports ActionRequest
|
* @exports ActionRequest
|
||||||
* @typedef {import('librechat-data-provider').ActionRequest} ActionRequest
|
* @typedef {import('librechat-data-provider').ActionRequest} ActionRequest
|
||||||
|
@ -698,6 +884,8 @@
|
||||||
* @property {Run} run - The detailed information about the run.
|
* @property {Run} run - The detailed information about the run.
|
||||||
* @property {RunStep[]} steps - An array of steps taken during the run.
|
* @property {RunStep[]} steps - An array of steps taken during the run.
|
||||||
* @property {StepMessage[]} messages - An array of messages related to the run.
|
* @property {StepMessage[]} messages - An array of messages related to the run.
|
||||||
|
* @property {ResponseMessage} finalMessage - The final response message, with all content parts.
|
||||||
|
* @property {string} text - The final response text, accumulated from message parts
|
||||||
* @memberof typedefs
|
* @memberof typedefs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -726,7 +914,7 @@
|
||||||
// */
|
// */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} OpenAIClientType
|
* @typedef {Object} RunClient
|
||||||
*
|
*
|
||||||
* @property {Express.Request} req - The Express request object.
|
* @property {Express.Request} req - The Express request object.
|
||||||
* @property {Express.Response} res - The Express response object.
|
* @property {Express.Response} res - The Express response object.
|
||||||
|
@ -754,7 +942,7 @@
|
||||||
*
|
*
|
||||||
* @property {ResponseMessage} responseMessage - A message object for responses.
|
* @property {ResponseMessage} responseMessage - A message object for responses.
|
||||||
*
|
*
|
||||||
* @typedef {OpenAI & OpenAIClientType} OpenAIClient
|
* @typedef {OpenAI & RunClient} OpenAIClient
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -773,3 +961,50 @@
|
||||||
* @property {Object} [metadata] - Optional. Metadata for the run.
|
* @property {Object} [metadata] - Optional. Metadata for the run.
|
||||||
* @memberof typedefs
|
* @memberof typedefs
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} StreamRunManager
|
||||||
|
* Manages streaming and processing of run steps, messages, and tool calls within a thread.
|
||||||
|
*
|
||||||
|
* @property {number} index - Tracks the current index for step or message processing.
|
||||||
|
* @property {Map<string, any>} steps - Stores run steps by their IDs.
|
||||||
|
* @property {Map<string, number>} mappedOrder - Maps step or message IDs to their processing order index.
|
||||||
|
* @property {Map<number, any>} orderedRunSteps - Stores run steps in order of processing.
|
||||||
|
* @property {Set<string>} processedFileIds - Keeps track of file IDs that have been processed.
|
||||||
|
* @property {Map<string, Function>} progressCallbacks - Stores callbacks for reporting progress on step or message processing.
|
||||||
|
* @property {boolean} submittedToolOutputs - Indicates whether tool outputs have been submitted.
|
||||||
|
* @property {Object|null} run - Holds the current run object.
|
||||||
|
* @property {Object} req - The HTTP request object associated with the run.
|
||||||
|
* @property {Object} res - The HTTP response object for sending back data.
|
||||||
|
* @property {Object} openai - The OpenAI client instance.
|
||||||
|
* @property {string} apiKey - The API key used for OpenAI requests.
|
||||||
|
* @property {string} thread_id - The ID of the thread associated with the run.
|
||||||
|
* @property {Object} initialRunBody - The initial body of the run request.
|
||||||
|
* @property {Object.<string, Function>} clientHandlers - Custom handlers provided by the client.
|
||||||
|
* @property {Object} streamOptions - Options for streaming the run.
|
||||||
|
* @property {Object} finalMessage - The final message object to be constructed and sent.
|
||||||
|
* @property {Array} messages - An array of messages processed during the run.
|
||||||
|
* @property {string} text - Accumulated text from text content data.
|
||||||
|
* @property {Object.<string, Function>} handlers - Internal event handlers for different types of streaming events.
|
||||||
|
*
|
||||||
|
* @method addContentData Adds content data to the final message or sends it immediately depending on type.
|
||||||
|
* @method runAssistant Initializes and manages the streaming of a thread run.
|
||||||
|
* @method handleEvent Dispatches streaming events to the appropriate handlers.
|
||||||
|
* @method handleThreadCreated Handles the event when a thread is created.
|
||||||
|
* @method handleRunEvent Handles various run state events.
|
||||||
|
* @method handleRunStepEvent Handles events related to individual run steps.
|
||||||
|
* @method handleCodeImageOutput Processes and handles code-generated image outputs.
|
||||||
|
* @method createToolCallStream Initializes streaming for tool call outputs.
|
||||||
|
* @method handleNewToolCall Handles the creation of a new tool call within a run step.
|
||||||
|
* @method handleCompletedToolCall Handles the completion of tool call processing.
|
||||||
|
* @method handleRunStepDeltaEvent Handles updates (deltas) for run steps.
|
||||||
|
* @method handleMessageDeltaEvent Handles updates (deltas) for messages.
|
||||||
|
* @method handleErrorEvent Handles error events during streaming.
|
||||||
|
* @method getStepIndex Retrieves or assigns an index for a given step or message key.
|
||||||
|
* @method generateToolCallKey Generates a unique key for a tool call within a step.
|
||||||
|
* @method onRunRequiresAction Handles actions required by a run to proceed.
|
||||||
|
* @method onRunStepCreated Handles the creation of a new run step.
|
||||||
|
* @method onRunStepCompleted Handles the completion of a run step.
|
||||||
|
* @method handleMessageEvent Handles events related to messages within the run.
|
||||||
|
* @method messageCompleted Handles the completion of a message processing.
|
||||||
|
*/
|
||||||
|
|
|
@ -210,7 +210,7 @@ export type TAdditionalProps = {
|
||||||
|
|
||||||
export type TMessageContentProps = TInitialProps & TAdditionalProps;
|
export type TMessageContentProps = TInitialProps & TAdditionalProps;
|
||||||
|
|
||||||
export type TText = Pick<TInitialProps, 'text'>;
|
export type TText = Pick<TInitialProps, 'text'> & { className?: string };
|
||||||
export type TEditProps = Pick<TInitialProps, 'text' | 'isSubmitting'> &
|
export type TEditProps = Pick<TInitialProps, 'text' | 'isSubmitting'> &
|
||||||
Omit<TAdditionalProps, 'isCreatedByUser'>;
|
Omit<TAdditionalProps, 'isCreatedByUser'>;
|
||||||
export type TDisplayProps = TText &
|
export type TDisplayProps = TText &
|
||||||
|
|
|
@ -24,8 +24,18 @@ const ChatForm = ({ index = 0 }) => {
|
||||||
const [showStopButton, setShowStopButton] = useRecoilState(store.showStopButtonByIndex(index));
|
const [showStopButton, setShowStopButton] = useRecoilState(store.showStopButtonByIndex(index));
|
||||||
const { requiresKey } = useRequiresKey();
|
const { requiresKey } = useRequiresKey();
|
||||||
|
|
||||||
|
const methods = useForm<{ text: string }>({
|
||||||
|
defaultValues: { text: '' },
|
||||||
|
});
|
||||||
|
|
||||||
const { handlePaste, handleKeyUp, handleKeyDown, handleCompositionStart, handleCompositionEnd } =
|
const { handlePaste, handleKeyUp, handleKeyDown, handleCompositionStart, handleCompositionEnd } =
|
||||||
useTextarea({ textAreaRef, submitButtonRef, disabled: !!requiresKey });
|
useTextarea({
|
||||||
|
textAreaRef,
|
||||||
|
submitButtonRef,
|
||||||
|
disabled: !!requiresKey,
|
||||||
|
setValue: methods.setValue,
|
||||||
|
getValues: methods.getValues,
|
||||||
|
});
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ask,
|
ask,
|
||||||
|
@ -39,9 +49,6 @@ const ChatForm = ({ index = 0 }) => {
|
||||||
} = useChatContext();
|
} = useChatContext();
|
||||||
|
|
||||||
const assistantMap = useAssistantsMapContext();
|
const assistantMap = useAssistantsMapContext();
|
||||||
const methods = useForm<{ text: string }>({
|
|
||||||
defaultValues: { text: '' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const submitMessage = useCallback(
|
const submitMessage = useCallback(
|
||||||
(data?: { text: string }) => {
|
(data?: { text: string }) => {
|
||||||
|
|
|
@ -21,20 +21,20 @@ any) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{content.map((part: TMessageContentParts | undefined, idx: number) => {
|
{content
|
||||||
if (!part) {
|
.filter((part: TMessageContentParts | undefined) => part)
|
||||||
return null;
|
.map((part: TMessageContentParts | undefined, idx: number) => {
|
||||||
}
|
const showCursor = idx === content.length - 1 && isLast;
|
||||||
return (
|
return (
|
||||||
<Part
|
<Part
|
||||||
key={`display-${messageId}-${idx}`}
|
key={`display-${messageId}-${idx}`}
|
||||||
showCursor={idx === content.length - 1 && isLast}
|
showCursor={showCursor && isSubmitting}
|
||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
part={part}
|
part={part}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{!isSubmitting && unfinished && (
|
{!isSubmitting && unfinished && (
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<DelayedRender delay={250}>
|
<DelayedRender delay={250}>
|
||||||
|
|
|
@ -5,23 +5,21 @@ import FileContainer from '~/components/Chat/Input/Files/FileContainer';
|
||||||
import Plugin from '~/components/Messages/Content/Plugin';
|
import Plugin from '~/components/Messages/Content/Plugin';
|
||||||
import Error from '~/components/Messages/Content/Error';
|
import Error from '~/components/Messages/Content/Error';
|
||||||
import { DelayedRender } from '~/components/ui';
|
import { DelayedRender } from '~/components/ui';
|
||||||
import { useAuthContext } from '~/hooks';
|
|
||||||
import EditMessage from './EditMessage';
|
import EditMessage from './EditMessage';
|
||||||
import Container from './Container';
|
import Container from './Container';
|
||||||
import Markdown from './Markdown';
|
import Markdown from './Markdown';
|
||||||
import { cn } from '~/utils';
|
import { cn } from '~/utils';
|
||||||
import Image from './Image';
|
import Image from './Image';
|
||||||
|
|
||||||
export const ErrorMessage = ({ text }: TText) => {
|
export const ErrorMessage = ({ text, className = '' }: TText) => {
|
||||||
const { logout } = useAuthContext();
|
|
||||||
|
|
||||||
if (text.includes('ban')) {
|
|
||||||
logout();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<div className="rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200">
|
<div
|
||||||
|
className={cn(
|
||||||
|
'rounded-md border border-red-500 bg-red-500/10 px-3 py-2 text-sm text-gray-600 dark:text-gray-200',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
<Error text={text} />
|
<Error text={text} />
|
||||||
</div>
|
</div>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { ToolCallTypes, ContentTypes, imageGenTools } from 'librechat-data-provider';
|
import { ToolCallTypes, ContentTypes, imageGenTools } from 'librechat-data-provider';
|
||||||
import type { TMessageContentParts, TMessage } from 'librechat-data-provider';
|
import type { TMessageContentParts, TMessage } from 'librechat-data-provider';
|
||||||
import type { TDisplayProps } from '~/common';
|
import type { TDisplayProps } from '~/common';
|
||||||
|
import { ErrorMessage } from './MessageContent';
|
||||||
import RetrievalCall from './RetrievalCall';
|
import RetrievalCall from './RetrievalCall';
|
||||||
import CodeAnalyze from './CodeAnalyze';
|
import CodeAnalyze from './CodeAnalyze';
|
||||||
import Container from './Container';
|
import Container from './Container';
|
||||||
|
@ -17,6 +18,7 @@ const DisplayMessage = ({ text, isCreatedByUser = false, message, showCursor }:
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
showCursor && !!text?.length ? 'result-streaming' : '',
|
||||||
'markdown prose dark:prose-invert light w-full break-words',
|
'markdown prose dark:prose-invert light w-full break-words',
|
||||||
isCreatedByUser ? 'whitespace-pre-wrap dark:text-gray-20' : 'dark:text-gray-70',
|
isCreatedByUser ? 'whitespace-pre-wrap dark:text-gray-20' : 'dark:text-gray-70',
|
||||||
)}
|
)}
|
||||||
|
@ -44,7 +46,10 @@ export default function Part({
|
||||||
if (!part) {
|
if (!part) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (part.type === ContentTypes.TEXT) {
|
|
||||||
|
if (part.type === ContentTypes.ERROR) {
|
||||||
|
return <ErrorMessage text={part[ContentTypes.TEXT].value} className="my-2" />;
|
||||||
|
} else if (part.type === ContentTypes.TEXT) {
|
||||||
// Access the value property
|
// Access the value property
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// import { useState, useEffect } from 'react';
|
// import { useState, useEffect } from 'react';
|
||||||
import { actionDelimiter } from 'librechat-data-provider';
|
import { actionDelimiter, actionDomainSeparator } from 'librechat-data-provider';
|
||||||
import * as Popover from '@radix-ui/react-popover';
|
import * as Popover from '@radix-ui/react-popover';
|
||||||
|
import useLocalize from '~/hooks/useLocalize';
|
||||||
import ProgressCircle from './ProgressCircle';
|
import ProgressCircle from './ProgressCircle';
|
||||||
import InProgressCall from './InProgressCall';
|
import InProgressCall from './InProgressCall';
|
||||||
import CancelledIcon from './CancelledIcon';
|
import CancelledIcon from './CancelledIcon';
|
||||||
|
@ -24,12 +25,14 @@ export default function ToolCall({
|
||||||
args: string;
|
args: string;
|
||||||
output?: string | null;
|
output?: string | null;
|
||||||
}) {
|
}) {
|
||||||
|
const localize = useLocalize();
|
||||||
const progress = useProgress(initialProgress);
|
const progress = useProgress(initialProgress);
|
||||||
const radius = 56.08695652173913;
|
const radius = 56.08695652173913;
|
||||||
const circumference = 2 * Math.PI * radius;
|
const circumference = 2 * Math.PI * radius;
|
||||||
const offset = circumference - progress * circumference;
|
const offset = circumference - progress * circumference;
|
||||||
|
|
||||||
const [function_name, domain] = name.split(actionDelimiter);
|
const [function_name, _domain] = name.split(actionDelimiter);
|
||||||
|
const domain = _domain?.replaceAll(actionDomainSeparator, '.') ?? null;
|
||||||
const error = output?.toLowerCase()?.includes('error processing tool');
|
const error = output?.toLowerCase()?.includes('error processing tool');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -58,8 +61,12 @@ export default function ToolCall({
|
||||||
<ProgressText
|
<ProgressText
|
||||||
progress={progress}
|
progress={progress}
|
||||||
onClick={() => ({})}
|
onClick={() => ({})}
|
||||||
inProgressText={'Running action'}
|
inProgressText={localize('com_assistants_running_action')}
|
||||||
finishedText={domain ? `Talked to ${domain}` : `Ran ${function_name}`}
|
finishedText={
|
||||||
|
domain
|
||||||
|
? localize('com_assistants_completed_action', domain)
|
||||||
|
: localize('com_assistants_completed_function', function_name)
|
||||||
|
}
|
||||||
hasInput={!!args?.length}
|
hasInput={!!args?.length}
|
||||||
popover={true}
|
popover={true}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as Popover from '@radix-ui/react-popover';
|
import * as Popover from '@radix-ui/react-popover';
|
||||||
|
import useLocalize from '~/hooks/useLocalize';
|
||||||
|
|
||||||
export default function ToolPopover({
|
export default function ToolPopover({
|
||||||
input,
|
input,
|
||||||
|
@ -11,6 +12,7 @@ export default function ToolPopover({
|
||||||
output?: string | null;
|
output?: string | null;
|
||||||
domain?: string;
|
domain?: string;
|
||||||
}) {
|
}) {
|
||||||
|
const localize = useLocalize();
|
||||||
const formatText = (text: string) => {
|
const formatText = (text: string) => {
|
||||||
try {
|
try {
|
||||||
return JSON.stringify(JSON.parse(text), null, 2);
|
return JSON.stringify(JSON.parse(text), null, 2);
|
||||||
|
@ -31,7 +33,9 @@ export default function ToolPopover({
|
||||||
<div tabIndex={-1}>
|
<div tabIndex={-1}>
|
||||||
<div className="bg-token-surface-primary max-w-sm rounded-md p-2 shadow-[0_0_24px_0_rgba(0,0,0,0.05),inset_0_0.5px_0_0_rgba(0,0,0,0.05),0_2px_8px_0_rgba(0,0,0,0.05)]">
|
<div className="bg-token-surface-primary max-w-sm rounded-md p-2 shadow-[0_0_24px_0_rgba(0,0,0,0.05),inset_0_0.5px_0_0_rgba(0,0,0,0.05),0_2px_8px_0_rgba(0,0,0,0.05)]">
|
||||||
<div className="mb-2 text-sm font-medium dark:text-gray-100">
|
<div className="mb-2 text-sm font-medium dark:text-gray-100">
|
||||||
{domain ? 'Assistant sent this info to ' + domain : `Assistant used ${function_name}`}
|
{domain
|
||||||
|
? localize('com_assistants_domain_info', domain)
|
||||||
|
: localize('com_assistants_function_use', function_name)}
|
||||||
</div>
|
</div>
|
||||||
<div className="bg-token-surface-secondary text-token-text-primary dark rounded-md text-xs">
|
<div className="bg-token-surface-secondary text-token-text-primary dark rounded-md text-xs">
|
||||||
<div className="max-h-32 overflow-y-auto rounded-md p-2 dark:bg-gray-700">
|
<div className="max-h-32 overflow-y-auto rounded-md p-2 dark:bg-gray-700">
|
||||||
|
@ -40,7 +44,9 @@ export default function ToolPopover({
|
||||||
</div>
|
</div>
|
||||||
{output && (
|
{output && (
|
||||||
<>
|
<>
|
||||||
<div className="mb-2 mt-2 text-sm font-medium dark:text-gray-100">Result</div>
|
<div className="mb-2 mt-2 text-sm font-medium dark:text-gray-100">
|
||||||
|
{localize('com_ui_result')}
|
||||||
|
</div>
|
||||||
<div className="bg-token-surface-secondary text-token-text-primary dark rounded-md text-xs">
|
<div className="bg-token-surface-secondary text-token-text-primary dark rounded-md text-xs">
|
||||||
<div className="max-h-32 overflow-y-auto rounded-md p-2 dark:bg-gray-700">
|
<div className="max-h-32 overflow-y-auto rounded-md p-2 dark:bg-gray-700">
|
||||||
<code className="!whitespace-pre-wrap ">{formatText(output)}</code>
|
<code className="!whitespace-pre-wrap ">{formatText(output)}</code>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
import { EModelEndpoint } from 'librechat-data-provider';
|
||||||
import type { TConversation, TMessage } from 'librechat-data-provider';
|
import type { TConversation, TMessage } from 'librechat-data-provider';
|
||||||
import { Clipboard, CheckMark, EditIcon, RegenerateIcon, ContinueIcon } from '~/components/svg';
|
import { Clipboard, CheckMark, EditIcon, RegenerateIcon, ContinueIcon } from '~/components/svg';
|
||||||
import { useGenerationsByLatest, useLocalize } from '~/hooks';
|
import { useGenerationsByLatest, useLocalize } from '~/hooks';
|
||||||
|
@ -55,21 +56,23 @@ export default function HoverButtons({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="visible mt-0 flex justify-center gap-1 self-end text-gray-400 lg:justify-start">
|
<div className="visible mt-0 flex justify-center gap-1 self-end text-gray-400 lg:justify-start">
|
||||||
<button
|
{endpoint !== EModelEndpoint.assistants && (
|
||||||
className={cn(
|
<button
|
||||||
'hover-button rounded-md p-1 text-gray-400 hover:text-gray-900 dark:text-gray-400/70 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:group-hover:visible md:group-[.final-completion]:visible',
|
className={cn(
|
||||||
isCreatedByUser ? '' : 'active',
|
'hover-button rounded-md p-1 text-gray-400 hover:text-gray-900 dark:text-gray-400/70 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:group-hover:visible md:group-[.final-completion]:visible',
|
||||||
hideEditButton ? 'opacity-0' : '',
|
isCreatedByUser ? '' : 'active',
|
||||||
isEditing ? 'active bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-200' : '',
|
hideEditButton ? 'opacity-0' : '',
|
||||||
!isLast ? 'md:opacity-0 md:group-hover:opacity-100' : '',
|
isEditing ? 'active bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-200' : '',
|
||||||
)}
|
!isLast ? 'md:opacity-0 md:group-hover:opacity-100' : '',
|
||||||
onClick={onEdit}
|
)}
|
||||||
type="button"
|
onClick={onEdit}
|
||||||
title={localize('com_ui_edit')}
|
type="button"
|
||||||
disabled={hideEditButton}
|
title={localize('com_ui_edit')}
|
||||||
>
|
disabled={hideEditButton}
|
||||||
<EditIcon />
|
>
|
||||||
</button>
|
<EditIcon />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
className={cn(
|
className={cn(
|
||||||
'ml-0 flex items-center gap-1.5 rounded-md p-1 text-xs hover:text-gray-900 dark:text-gray-400/70 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:group-hover:visible md:group-[.final-completion]:visible',
|
'ml-0 flex items-center gap-1.5 rounded-md p-1 text-xs hover:text-gray-900 dark:text-gray-400/70 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:group-hover:visible md:group-[.final-completion]:visible',
|
||||||
|
@ -86,8 +89,10 @@ export default function HoverButtons({
|
||||||
</button>
|
</button>
|
||||||
{regenerateEnabled ? (
|
{regenerateEnabled ? (
|
||||||
<button
|
<button
|
||||||
className={cn("hover-button active rounded-md p-1 text-gray-400 hover:text-gray-900 dark:text-gray-400/70 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible md:group-[.final-completion]:visible",
|
className={cn(
|
||||||
!isLast ? 'md:opacity-0 md:group-hover:opacity-100' : '',)}
|
'hover-button active rounded-md p-1 text-gray-400 hover:text-gray-900 dark:text-gray-400/70 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible md:group-[.final-completion]:visible',
|
||||||
|
!isLast ? 'md:opacity-0 md:group-hover:opacity-100' : '',
|
||||||
|
)}
|
||||||
onClick={regenerate}
|
onClick={regenerate}
|
||||||
type="button"
|
type="button"
|
||||||
title={localize('com_ui_regenerate')}
|
title={localize('com_ui_regenerate')}
|
||||||
|
@ -97,8 +102,10 @@ export default function HoverButtons({
|
||||||
) : null}
|
) : null}
|
||||||
{continueSupported ? (
|
{continueSupported ? (
|
||||||
<button
|
<button
|
||||||
className={cn("hover-button active rounded-md p-1 hover:bg-gray-200 hover:text-gray-700 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible ",
|
className={cn(
|
||||||
!isLast ? 'md:opacity-0 md:group-hover:opacity-100' : '',)}
|
'hover-button active rounded-md p-1 hover:bg-gray-200 hover:text-gray-700 dark:text-gray-400/70 dark:hover:bg-gray-700 dark:hover:text-gray-200 disabled:dark:hover:text-gray-400 md:invisible md:group-hover:visible ',
|
||||||
|
!isLast ? 'md:opacity-0 md:group-hover:opacity-100' : '',
|
||||||
|
)}
|
||||||
onClick={handleContinue}
|
onClick={handleContinue}
|
||||||
type="button"
|
type="button"
|
||||||
title={localize('com_ui_continue')}
|
title={localize('com_ui_continue')}
|
||||||
|
|
|
@ -103,6 +103,7 @@ export default function Message(props: TMessageProps) {
|
||||||
copyToClipboard={copyToClipboard}
|
copyToClipboard={copyToClipboard}
|
||||||
handleContinue={handleContinue}
|
handleContinue={handleContinue}
|
||||||
latestMessage={latestMessage}
|
latestMessage={latestMessage}
|
||||||
|
isLast={isLast}
|
||||||
/>
|
/>
|
||||||
</SubRow>
|
</SubRow>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import { useMemo, memo } from 'react';
|
import { useMemo, memo } from 'react';
|
||||||
import { parseISO, isToday } from 'date-fns';
|
import { parseISO, isToday } from 'date-fns';
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
import { TConversation } from 'librechat-data-provider';
|
import { TConversation } from 'librechat-data-provider';
|
||||||
import { groupConversationsByDate } from '~/utils';
|
import { groupConversationsByDate } from '~/utils';
|
||||||
import Conversation from './Conversation';
|
|
||||||
import Convo from './Convo';
|
import Convo from './Convo';
|
||||||
|
|
||||||
const Conversations = ({
|
const Conversations = ({
|
||||||
|
@ -15,16 +13,14 @@ const Conversations = ({
|
||||||
moveToTop: () => void;
|
moveToTop: () => void;
|
||||||
toggleNav: () => void;
|
toggleNav: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const location = useLocation();
|
|
||||||
const { pathname } = location;
|
|
||||||
const ConvoItem = pathname.includes('chat') ? Conversation : Convo;
|
|
||||||
const groupedConversations = useMemo(
|
const groupedConversations = useMemo(
|
||||||
() => groupConversationsByDate(conversations),
|
() => groupConversationsByDate(conversations),
|
||||||
[conversations],
|
[conversations],
|
||||||
);
|
);
|
||||||
const firstTodayConvoId = conversations.find((convo) =>
|
const firstTodayConvoId = useMemo(
|
||||||
isToday(parseISO(convo.updatedAt)),
|
() => conversations.find((convo) => isToday(parseISO(convo.updatedAt)))?.conversationId,
|
||||||
)?.conversationId;
|
[conversations],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="text-token-text-primary flex flex-col gap-2 pb-2 text-sm">
|
<div className="text-token-text-primary flex flex-col gap-2 pb-2 text-sm">
|
||||||
|
@ -44,7 +40,7 @@ const Conversations = ({
|
||||||
{groupName}
|
{groupName}
|
||||||
</div>
|
</div>
|
||||||
{convos.map((convo, i) => (
|
{convos.map((convo, i) => (
|
||||||
<ConvoItem
|
<Convo
|
||||||
key={`${groupName}-${convo.conversationId}-${i}`}
|
key={`${groupName}-${convo.conversationId}-${i}`}
|
||||||
isLatestConvo={convo.conversationId === firstTodayConvoId}
|
isLatestConvo={convo.conversationId === firstTodayConvoId}
|
||||||
conversation={convo}
|
conversation={convo}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { useState, useRef, useEffect } from 'react';
|
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
import { useState, useRef, useMemo } from 'react';
|
||||||
import { EModelEndpoint } from 'librechat-data-provider';
|
import { EModelEndpoint } from 'librechat-data-provider';
|
||||||
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
|
import { useGetEndpointsQuery } from 'librechat-data-provider/react-query';
|
||||||
import type { MouseEvent, FocusEvent, KeyboardEvent } from 'react';
|
import type { MouseEvent, FocusEvent, KeyboardEvent } from 'react';
|
||||||
|
@ -17,7 +17,8 @@ import store from '~/store';
|
||||||
type KeyEvent = KeyboardEvent<HTMLInputElement>;
|
type KeyEvent = KeyboardEvent<HTMLInputElement>;
|
||||||
|
|
||||||
export default function Conversation({ conversation, retainView, toggleNav, isLatestConvo }) {
|
export default function Conversation({ conversation, retainView, toggleNav, isLatestConvo }) {
|
||||||
const { conversationId: currentConvoId } = useParams();
|
const params = useParams();
|
||||||
|
const currentConvoId = useMemo(() => params.conversationId, [params.conversationId]);
|
||||||
const updateConvoMutation = useUpdateConversationMutation(currentConvoId ?? '');
|
const updateConvoMutation = useUpdateConversationMutation(currentConvoId ?? '');
|
||||||
const activeConvos = useRecoilValue(store.allConversationsSelector);
|
const activeConvos = useRecoilValue(store.allConversationsSelector);
|
||||||
const { data: endpointsConfig } = useGetEndpointsQuery();
|
const { data: endpointsConfig } = useGetEndpointsQuery();
|
||||||
|
|
|
@ -14,9 +14,11 @@ import type {
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import type { ActionAuthForm } from '~/common';
|
import type { ActionAuthForm } from '~/common';
|
||||||
import type { Spec } from './ActionsTable';
|
import type { Spec } from './ActionsTable';
|
||||||
|
import { useAssistantsMapContext, useToastContext } from '~/Providers';
|
||||||
import { ActionsTable, columns } from './ActionsTable';
|
import { ActionsTable, columns } from './ActionsTable';
|
||||||
import { useUpdateAction } from '~/data-provider';
|
import { useUpdateAction } from '~/data-provider';
|
||||||
import { cn, removeFocusOutlines } from '~/utils';
|
import { cn, removeFocusOutlines } from '~/utils';
|
||||||
|
import useLocalize from '~/hooks/useLocalize';
|
||||||
import { Spinner } from '~/components/svg';
|
import { Spinner } from '~/components/svg';
|
||||||
|
|
||||||
const debouncedValidation = debounce(
|
const debouncedValidation = debounce(
|
||||||
|
@ -44,6 +46,9 @@ export default function ActionsInput({
|
||||||
setValidationResult(result);
|
setValidationResult(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const localize = useLocalize();
|
||||||
|
const { showToast } = useToastContext();
|
||||||
|
const assistantMap = useAssistantsMapContext();
|
||||||
const { handleSubmit, reset } = useFormContext<ActionAuthForm>();
|
const { handleSubmit, reset } = useFormContext<ActionAuthForm>();
|
||||||
const [validationResult, setValidationResult] = useState<null | ValidationResult>(null);
|
const [validationResult, setValidationResult] = useState<null | ValidationResult>(null);
|
||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
@ -81,9 +86,19 @@ export default function ActionsInput({
|
||||||
|
|
||||||
const updateAction = useUpdateAction({
|
const updateAction = useUpdateAction({
|
||||||
onSuccess(data) {
|
onSuccess(data) {
|
||||||
|
showToast({
|
||||||
|
message: localize('com_assistants_update_actions_success'),
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
reset();
|
reset();
|
||||||
setAction(data[2]);
|
setAction(data[2]);
|
||||||
},
|
},
|
||||||
|
onError(error) {
|
||||||
|
showToast({
|
||||||
|
message: (error as Error)?.message ?? localize('com_assistants_update_actions_error'),
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const saveAction = handleSubmit((authFormData) => {
|
const saveAction = handleSubmit((authFormData) => {
|
||||||
|
@ -158,6 +173,7 @@ export default function ActionsInput({
|
||||||
metadata,
|
metadata,
|
||||||
functions,
|
functions,
|
||||||
assistant_id,
|
assistant_id,
|
||||||
|
model: assistantMap[assistant_id].model,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -185,7 +201,8 @@ export default function ActionsInput({
|
||||||
onChange={(e) => console.log(e.target.value)}
|
onChange={(e) => console.log(e.target.value)}
|
||||||
className="border-token-border-medium h-8 min-w-[100px] rounded-lg border bg-transparent px-2 py-0 text-sm"
|
className="border-token-border-medium h-8 min-w-[100px] rounded-lg border bg-transparent px-2 py-0 text-sm"
|
||||||
>
|
>
|
||||||
<option value="label">Examples</option>
|
<option value="label">{localize('com_ui_examples')}</option>
|
||||||
|
{/* TODO: make these appear and function correctly */}
|
||||||
<option value="0">Weather (JSON)</option>
|
<option value="0">Weather (JSON)</option>
|
||||||
<option value="1">Pet Store (YAML)</option>
|
<option value="1">Pet Store (YAML)</option>
|
||||||
<option value="2">Blank Template</option>
|
<option value="2">Blank Template</option>
|
||||||
|
@ -218,7 +235,9 @@ export default function ActionsInput({
|
||||||
{!!data && (
|
{!!data && (
|
||||||
<div>
|
<div>
|
||||||
<div className="mb-1.5 flex items-center">
|
<div className="mb-1.5 flex items-center">
|
||||||
<label className="text-token-text-primary block font-medium">Available actions</label>
|
<label className="text-token-text-primary block font-medium">
|
||||||
|
{localize('com_assistants_available_actions')}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<ActionsTable columns={columns} data={data} />
|
<ActionsTable columns={columns} data={data} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -226,7 +245,9 @@ export default function ActionsInput({
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<div className="mb-1.5 flex items-center">
|
<div className="mb-1.5 flex items-center">
|
||||||
<span className="" data-state="closed">
|
<span className="" data-state="closed">
|
||||||
<label className="text-token-text-primary block font-medium">Privacy policy</label>
|
<label className="text-token-text-primary block font-medium">
|
||||||
|
{localize('com_ui_privacy_policy')}
|
||||||
|
</label>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-md border border-gray-300 px-3 py-2 shadow-none focus-within:border-gray-800 focus-within:ring-1 focus-within:ring-gray-800 dark:border-gray-700 dark:bg-gray-700 dark:focus-within:border-gray-500 dark:focus-within:ring-gray-500">
|
<div className="rounded-md border border-gray-300 px-3 py-2 shadow-none focus-within:border-gray-800 focus-within:ring-1 focus-within:ring-gray-800 dark:border-gray-700 dark:bg-gray-700 dark:focus-within:border-gray-500 dark:focus-within:ring-gray-500">
|
||||||
|
@ -252,13 +273,12 @@ export default function ActionsInput({
|
||||||
className="focus:shadow-outline mt-1 flex min-w-[100px] items-center justify-center rounded bg-green-500 px-4 py-2 font-semibold text-white hover:bg-green-400 focus:border-green-500 focus:outline-none focus:ring-0 disabled:bg-green-400"
|
className="focus:shadow-outline mt-1 flex min-w-[100px] items-center justify-center rounded bg-green-500 px-4 py-2 font-semibold text-white hover:bg-green-400 focus:border-green-500 focus:outline-none focus:ring-0 disabled:bg-green-400"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
{/* TODO: Add localization */}
|
|
||||||
{updateAction.isLoading ? (
|
{updateAction.isLoading ? (
|
||||||
<Spinner className="icon-md" />
|
<Spinner className="icon-md" />
|
||||||
) : action?.action_id ? (
|
) : action?.action_id ? (
|
||||||
'Update'
|
localize('com_ui_update')
|
||||||
) : (
|
) : (
|
||||||
'Create'
|
localize('com_ui_create')
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,9 +6,11 @@ import {
|
||||||
TokenExchangeMethodEnum,
|
TokenExchangeMethodEnum,
|
||||||
} from 'librechat-data-provider';
|
} from 'librechat-data-provider';
|
||||||
import type { AssistantPanelProps, ActionAuthForm } from '~/common';
|
import type { AssistantPanelProps, ActionAuthForm } from '~/common';
|
||||||
|
import { useAssistantsMapContext, useToastContext } from '~/Providers';
|
||||||
import { Dialog, DialogTrigger } from '~/components/ui';
|
import { Dialog, DialogTrigger } from '~/components/ui';
|
||||||
import { useDeleteAction } from '~/data-provider';
|
import { useDeleteAction } from '~/data-provider';
|
||||||
import { NewTrashIcon } from '~/components/svg';
|
import { NewTrashIcon } from '~/components/svg';
|
||||||
|
import useLocalize from '~/hooks/useLocalize';
|
||||||
import ActionsInput from './ActionsInput';
|
import ActionsInput from './ActionsInput';
|
||||||
import ActionsAuth from './ActionsAuth';
|
import ActionsAuth from './ActionsAuth';
|
||||||
import { Panel } from '~/common';
|
import { Panel } from '~/common';
|
||||||
|
@ -20,12 +22,25 @@ export default function ActionsPanel({
|
||||||
setActivePanel,
|
setActivePanel,
|
||||||
assistant_id,
|
assistant_id,
|
||||||
}: AssistantPanelProps) {
|
}: AssistantPanelProps) {
|
||||||
|
const localize = useLocalize();
|
||||||
|
const { showToast } = useToastContext();
|
||||||
|
const assistantMap = useAssistantsMapContext();
|
||||||
const [openAuthDialog, setOpenAuthDialog] = useState(false);
|
const [openAuthDialog, setOpenAuthDialog] = useState(false);
|
||||||
const deleteAction = useDeleteAction({
|
const deleteAction = useDeleteAction({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
showToast({
|
||||||
|
message: localize('com_assistants_delete_actions_success'),
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
setActivePanel(Panel.builder);
|
setActivePanel(Panel.builder);
|
||||||
setAction(undefined);
|
setAction(undefined);
|
||||||
},
|
},
|
||||||
|
onError(error) {
|
||||||
|
showToast({
|
||||||
|
message: (error as Error)?.message ?? localize('com_assistants_delete_actions_error'),
|
||||||
|
status: 'error',
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const methods = useForm<ActionAuthForm>({
|
const methods = useForm<ActionAuthForm>({
|
||||||
|
@ -115,6 +130,7 @@ export default function ActionsPanel({
|
||||||
const confirmed = confirm('Are you sure you want to delete this action?');
|
const confirmed = confirm('Are you sure you want to delete this action?');
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
deleteAction.mutate({
|
deleteAction.mutate({
|
||||||
|
model: assistantMap[assistant_id].model,
|
||||||
action_id: action.action_id,
|
action_id: action.action_id,
|
||||||
assistant_id,
|
assistant_id,
|
||||||
});
|
});
|
||||||
|
@ -129,8 +145,7 @@ export default function ActionsPanel({
|
||||||
)}
|
)}
|
||||||
<div className="text-xl font-medium">{(action ? 'Edit' : 'Add') + ' ' + 'actions'}</div>
|
<div className="text-xl font-medium">{(action ? 'Edit' : 'Add') + ' ' + 'actions'}</div>
|
||||||
<div className="text-token-text-tertiary text-sm">
|
<div className="text-token-text-tertiary text-sm">
|
||||||
{/* TODO: use App title */}
|
{localize('com_assistants_actions_info')}
|
||||||
Let your Assistant retrieve information or take actions outside of LibreChat.
|
|
||||||
</div>
|
</div>
|
||||||
{/* <div className="text-sm text-token-text-tertiary">
|
{/* <div className="text-sm text-token-text-tertiary">
|
||||||
<a href="https://help.openai.com/en/articles/8554397-creating-a-gpt" target="_blank" rel="noreferrer" className="font-medium">Learn more.</a>
|
<a href="https://help.openai.com/en/articles/8554397-creating-a-gpt" target="_blank" rel="noreferrer" className="font-medium">Learn more.</a>
|
||||||
|
@ -141,7 +156,7 @@ export default function ActionsPanel({
|
||||||
<div className="relative mb-6">
|
<div className="relative mb-6">
|
||||||
<div className="mb-1.5 flex items-center">
|
<div className="mb-1.5 flex items-center">
|
||||||
<label className="text-token-text-primary block font-medium">
|
<label className="text-token-text-primary block font-medium">
|
||||||
Authentication
|
{localize('com_ui_authentication')}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-token-border-medium flex rounded-lg border text-sm hover:cursor-pointer">
|
<div className="border-token-border-medium flex rounded-lg border text-sm hover:cursor-pointer">
|
||||||
|
|
|
@ -107,6 +107,7 @@ export default function AssistantSelect({
|
||||||
functions,
|
functions,
|
||||||
...actions,
|
...actions,
|
||||||
assistant: update,
|
assistant: update,
|
||||||
|
model: update.model,
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.entries(assistant).forEach(([name, value]) => {
|
Object.entries(assistant).forEach(([name, value]) => {
|
||||||
|
|
|
@ -489,7 +489,7 @@ export const useDeleteAction = (
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation([MutationKeys.deleteAction], {
|
return useMutation([MutationKeys.deleteAction], {
|
||||||
mutationFn: (variables: DeleteActionVariables) =>
|
mutationFn: (variables: DeleteActionVariables) =>
|
||||||
dataService.deleteAction(variables.assistant_id, variables.action_id),
|
dataService.deleteAction(variables.assistant_id, variables.action_id, variables.model),
|
||||||
|
|
||||||
onMutate: (variables) => options?.onMutate?.(variables),
|
onMutate: (variables) => options?.onMutate?.(variables),
|
||||||
onError: (error, variables, context) => options?.onError?.(error, variables, context),
|
onError: (error, variables, context) => options?.onError?.(error, variables, context),
|
||||||
|
|
|
@ -2,6 +2,7 @@ import debounce from 'lodash/debounce';
|
||||||
import React, { useEffect, useRef, useCallback } from 'react';
|
import React, { useEffect, useRef, useCallback } from 'react';
|
||||||
import { EModelEndpoint } from 'librechat-data-provider';
|
import { EModelEndpoint } from 'librechat-data-provider';
|
||||||
import type { TEndpointOption } from 'librechat-data-provider';
|
import type { TEndpointOption } from 'librechat-data-provider';
|
||||||
|
import type { UseFormSetValue } from 'react-hook-form';
|
||||||
import type { KeyboardEvent } from 'react';
|
import type { KeyboardEvent } from 'react';
|
||||||
import { useAssistantsMapContext } from '~/Providers/AssistantsMapContext';
|
import { useAssistantsMapContext } from '~/Providers/AssistantsMapContext';
|
||||||
import useGetSender from '~/hooks/Conversations/useGetSender';
|
import useGetSender from '~/hooks/Conversations/useGetSender';
|
||||||
|
@ -12,7 +13,6 @@ import useLocalize from '~/hooks/useLocalize';
|
||||||
type KeyEvent = KeyboardEvent<HTMLTextAreaElement>;
|
type KeyEvent = KeyboardEvent<HTMLTextAreaElement>;
|
||||||
|
|
||||||
function insertTextAtCursor(element: HTMLTextAreaElement, textToInsert: string) {
|
function insertTextAtCursor(element: HTMLTextAreaElement, textToInsert: string) {
|
||||||
// Focus the element to ensure the insertion point is updated
|
|
||||||
element.focus();
|
element.focus();
|
||||||
|
|
||||||
// Use the browser's built-in undoable actions if possible
|
// Use the browser's built-in undoable actions if possible
|
||||||
|
@ -31,6 +31,25 @@ function insertTextAtCursor(element: HTMLTextAreaElement, textToInsert: string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Necessary resize helper for edge cases where paste doesn't update the container height.
|
||||||
|
*
|
||||||
|
1) Resetting the height to 'auto' forces the component to recalculate height based on its current content
|
||||||
|
|
||||||
|
2) Forcing a reflow. Accessing offsetHeight will cause a reflow of the page,
|
||||||
|
ensuring that the reset height takes effect before resetting back to the scrollHeight.
|
||||||
|
This step is necessary because changes to the DOM do not instantly cause reflows.
|
||||||
|
|
||||||
|
3) Reseting back to scrollHeight reads and applies the ideal height for the current content dynamically
|
||||||
|
*/
|
||||||
|
const forceResize = (textAreaRef: React.RefObject<HTMLTextAreaElement>) => {
|
||||||
|
if (textAreaRef.current) {
|
||||||
|
textAreaRef.current.style.height = 'auto';
|
||||||
|
textAreaRef.current.offsetHeight;
|
||||||
|
textAreaRef.current.style.height = `${textAreaRef.current.scrollHeight}px`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const getAssistantName = ({
|
const getAssistantName = ({
|
||||||
name,
|
name,
|
||||||
localize,
|
localize,
|
||||||
|
@ -48,10 +67,14 @@ const getAssistantName = ({
|
||||||
export default function useTextarea({
|
export default function useTextarea({
|
||||||
textAreaRef,
|
textAreaRef,
|
||||||
submitButtonRef,
|
submitButtonRef,
|
||||||
|
setValue,
|
||||||
|
getValues,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
}: {
|
}: {
|
||||||
textAreaRef: React.RefObject<HTMLTextAreaElement>;
|
textAreaRef: React.RefObject<HTMLTextAreaElement>;
|
||||||
submitButtonRef: React.RefObject<HTMLButtonElement>;
|
submitButtonRef: React.RefObject<HTMLButtonElement>;
|
||||||
|
setValue: UseFormSetValue<{ text: string }>;
|
||||||
|
getValues: (field: string) => string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const assistantMap = useAssistantsMapContext();
|
const assistantMap = useAssistantsMapContext();
|
||||||
|
@ -205,6 +228,21 @@ export default function useTextarea({
|
||||||
isComposing.current = false;
|
isComposing.current = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Necessary handler to update form state when paste doesn't fire textArea input event */
|
||||||
|
const setPastedValue = useCallback(
|
||||||
|
(textArea: HTMLTextAreaElement, pastedData: string) => {
|
||||||
|
const currentTextValue = getValues('text') || '';
|
||||||
|
const { selectionStart, selectionEnd } = textArea;
|
||||||
|
const newValue =
|
||||||
|
currentTextValue.substring(0, selectionStart) +
|
||||||
|
pastedData +
|
||||||
|
currentTextValue.substring(selectionEnd);
|
||||||
|
|
||||||
|
setValue('text', newValue, { shouldValidate: true });
|
||||||
|
},
|
||||||
|
[getValues, setValue],
|
||||||
|
);
|
||||||
|
|
||||||
const handlePaste = useCallback(
|
const handlePaste = useCallback(
|
||||||
(e: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
(e: React.ClipboardEvent<HTMLTextAreaElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -214,7 +252,9 @@ export default function useTextarea({
|
||||||
}
|
}
|
||||||
|
|
||||||
const pastedData = e.clipboardData.getData('text/plain');
|
const pastedData = e.clipboardData.getData('text/plain');
|
||||||
|
setPastedValue(textArea, pastedData);
|
||||||
insertTextAtCursor(textArea, pastedData);
|
insertTextAtCursor(textArea, pastedData);
|
||||||
|
forceResize(textAreaRef);
|
||||||
|
|
||||||
if (e.clipboardData && e.clipboardData.files.length > 0) {
|
if (e.clipboardData && e.clipboardData.files.length > 0) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -229,7 +269,7 @@ export default function useTextarea({
|
||||||
handleFiles(timestampedFiles);
|
handleFiles(timestampedFiles);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[handleFiles, setFilesLoading, textAreaRef],
|
[handleFiles, setFilesLoading, setPastedValue, textAreaRef],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -22,7 +22,7 @@ export default function useContentHandler({ setMessages, getMessages }: TUseCont
|
||||||
const messageMap = useMemo(() => new Map<string, TMessage>(), []);
|
const messageMap = useMemo(() => new Map<string, TMessage>(), []);
|
||||||
return useCallback(
|
return useCallback(
|
||||||
({ data, submission }: TContentHandler) => {
|
({ data, submission }: TContentHandler) => {
|
||||||
const { type, messageId, thread_id, conversationId, index, stream } = data;
|
const { type, messageId, thread_id, conversationId, index } = data;
|
||||||
|
|
||||||
const _messages = getMessages();
|
const _messages = getMessages();
|
||||||
const messages =
|
const messages =
|
||||||
|
@ -46,8 +46,9 @@ export default function useContentHandler({ setMessages, getMessages }: TUseCont
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: handle streaming for non-text
|
// TODO: handle streaming for non-text
|
||||||
const part: ContentPart =
|
const part: ContentPart = data[ContentTypes.TEXT]
|
||||||
stream && data[ContentTypes.TEXT] ? { value: data[ContentTypes.TEXT] } : data[type];
|
? { value: data[ContentTypes.TEXT] }
|
||||||
|
: data[type];
|
||||||
|
|
||||||
/* spreading the content array to avoid mutation */
|
/* spreading the content array to avoid mutation */
|
||||||
response.content = [...(response.content ?? [])];
|
response.content = [...(response.content ?? [])];
|
||||||
|
|
|
@ -502,10 +502,7 @@ export default function useSSE(submission: TSubmission | null, index = 0) {
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (submission === null) {
|
if (submission === null || Object.keys(submission).length === 0) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (Object.keys(submission).length === 0) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,17 @@ export default {
|
||||||
com_assistants_actions: 'Actions',
|
com_assistants_actions: 'Actions',
|
||||||
com_assistants_add_tools: 'Add Tools',
|
com_assistants_add_tools: 'Add Tools',
|
||||||
com_assistants_add_actions: 'Add Actions',
|
com_assistants_add_actions: 'Add Actions',
|
||||||
|
com_assistants_available_actions: 'Available Actions',
|
||||||
|
com_assistants_running_action: 'Running action',
|
||||||
|
com_assistants_completed_action: 'Talked to {0}',
|
||||||
|
com_assistants_completed_function: 'Ran {0}',
|
||||||
|
com_assistants_function_use: 'Assistant used {0}',
|
||||||
|
com_assistants_domain_info: 'Assistant sent this info to {0}',
|
||||||
|
com_assistants_delete_actions_success: 'Successfully deleted Action from Assistant',
|
||||||
|
com_assistants_update_actions_success: 'Successfully created or updated Action',
|
||||||
|
com_assistants_update_actions_error: 'There was an error creating or updating the action.',
|
||||||
|
com_assistants_delete_actions_error: 'There was an error deleting the action.',
|
||||||
|
com_assistants_actions_info: 'Let your Assistant retrieve information or take actions via API\'s',
|
||||||
com_assistants_name_placeholder: 'Optional: The name of the assistant',
|
com_assistants_name_placeholder: 'Optional: The name of the assistant',
|
||||||
com_assistants_instructions_placeholder: 'The system instructions that the assistant uses',
|
com_assistants_instructions_placeholder: 'The system instructions that the assistant uses',
|
||||||
com_assistants_description_placeholder: 'Optional: Describe your Assistant here',
|
com_assistants_description_placeholder: 'Optional: Describe your Assistant here',
|
||||||
|
@ -61,6 +72,8 @@ export default {
|
||||||
com_ui_context: 'Context',
|
com_ui_context: 'Context',
|
||||||
com_ui_size: 'Size',
|
com_ui_size: 'Size',
|
||||||
com_ui_host: 'Host',
|
com_ui_host: 'Host',
|
||||||
|
com_ui_update: 'Update',
|
||||||
|
com_ui_authentication: 'Authentication',
|
||||||
com_ui_instructions: 'Instructions',
|
com_ui_instructions: 'Instructions',
|
||||||
com_ui_description: 'Description',
|
com_ui_description: 'Description',
|
||||||
com_ui_error: 'Error',
|
com_ui_error: 'Error',
|
||||||
|
@ -106,6 +119,7 @@ export default {
|
||||||
com_ui_chats: 'chats',
|
com_ui_chats: 'chats',
|
||||||
com_ui_avatar: 'Avatar',
|
com_ui_avatar: 'Avatar',
|
||||||
com_ui_unknown: 'Unknown',
|
com_ui_unknown: 'Unknown',
|
||||||
|
com_ui_result: 'Result',
|
||||||
com_ui_image_gen: 'Image Gen',
|
com_ui_image_gen: 'Image Gen',
|
||||||
com_ui_assistant: 'Assistant',
|
com_ui_assistant: 'Assistant',
|
||||||
com_ui_assistants: 'Assistants',
|
com_ui_assistants: 'Assistants',
|
||||||
|
|
134
package-lock.json
generated
134
package-lock.json
generated
|
@ -80,7 +80,7 @@
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.1",
|
||||||
"nodejs-gpt": "^1.37.4",
|
"nodejs-gpt": "^1.37.4",
|
||||||
"nodemailer": "^6.9.4",
|
"nodemailer": "^6.9.4",
|
||||||
"openai": "^4.28.4",
|
"openai": "^4.29.0",
|
||||||
"openai-chat-tokens": "^0.2.8",
|
"openai-chat-tokens": "^0.2.8",
|
||||||
"openid-client": "^5.4.2",
|
"openid-client": "^5.4.2",
|
||||||
"passport": "^0.6.0",
|
"passport": "^0.6.0",
|
||||||
|
@ -115,9 +115,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"api/node_modules/openai": {
|
"api/node_modules/openai": {
|
||||||
"version": "4.28.4",
|
"version": "4.29.0",
|
||||||
"resolved": "https://registry.npmjs.org/openai/-/openai-4.28.4.tgz",
|
"resolved": "https://registry.npmjs.org/openai/-/openai-4.29.0.tgz",
|
||||||
"integrity": "sha512-RNIwx4MT/F0zyizGcwS+bXKLzJ8QE9IOyigDG/ttnwB220d58bYjYFp0qjvGwEFBO6+pvFVIDABZPGDl46RFsg==",
|
"integrity": "sha512-ic6C681bSow1XQdKhADthM/OOKqNL05M1gCFLx1mRqLJ+yH49v6qnvaWQ76kwqI/IieCuVTXfRfTk3sz4cB45w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^18.11.18",
|
"@types/node": "^18.11.18",
|
||||||
"@types/node-fetch": "^2.6.4",
|
"@types/node-fetch": "^2.6.4",
|
||||||
|
@ -4895,9 +4895,9 @@
|
||||||
"integrity": "sha512-iRP+QKI2+oz3UAh4nPEq14CsEjrjD6a5+fuypjScisAh9kXKFvdJOZJDwk7kikLvWVLGEs9+kIUS4LPQV7VZVw=="
|
"integrity": "sha512-iRP+QKI2+oz3UAh4nPEq14CsEjrjD6a5+fuypjScisAh9kXKFvdJOZJDwk7kikLvWVLGEs9+kIUS4LPQV7VZVw=="
|
||||||
},
|
},
|
||||||
"node_modules/@firebase/app": {
|
"node_modules/@firebase/app": {
|
||||||
"version": "0.9.27",
|
"version": "0.9.29",
|
||||||
"resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.9.27.tgz",
|
"resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.9.29.tgz",
|
||||||
"integrity": "sha512-p2Dvl1ge4kRsyK5+wWcmdAIE9MSwZ0pDKAYB51LZgZuz6wciUZk4E1yAEdkfQlRxuHehn+Ol9WP5Qk2XQZiHGg==",
|
"integrity": "sha512-HbKTjfmILklasIu/ij6zKnFf3SgLYXkBDVN7leJfVGmohl+zA7Ig+eXM1ZkT1pyBJ8FTYR+mlOJer/lNEnUCtw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@firebase/component": "0.6.5",
|
"@firebase/component": "0.6.5",
|
||||||
"@firebase/logger": "0.4.0",
|
"@firebase/logger": "0.4.0",
|
||||||
|
@ -4947,11 +4947,11 @@
|
||||||
"integrity": "sha512-uwSUj32Mlubybw7tedRzR24RP8M8JUVR3NPiMk3/Z4bCmgEKTlQBwMXrehDAZ2wF+TsBq0SN1c6ema71U/JPyQ=="
|
"integrity": "sha512-uwSUj32Mlubybw7tedRzR24RP8M8JUVR3NPiMk3/Z4bCmgEKTlQBwMXrehDAZ2wF+TsBq0SN1c6ema71U/JPyQ=="
|
||||||
},
|
},
|
||||||
"node_modules/@firebase/app-compat": {
|
"node_modules/@firebase/app-compat": {
|
||||||
"version": "0.2.27",
|
"version": "0.2.29",
|
||||||
"resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.27.tgz",
|
"resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.29.tgz",
|
||||||
"integrity": "sha512-SYlqocfUDKPHR6MSFC8hree0BTiWFu5o8wbf6zFlYXyG41w7TcHp4wJi4H/EL5V6cM4kxwruXTJtqXX/fRAZtw==",
|
"integrity": "sha512-NqUdegXJfwphx9i/2bOE2CTZ55TC9bbDg+iwkxVShsPBJhD3CzQJkFhoDz4ccfbJaKZGsqjY3fisgX5kbDROnA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@firebase/app": "0.9.27",
|
"@firebase/app": "0.9.29",
|
||||||
"@firebase/component": "0.6.5",
|
"@firebase/component": "0.6.5",
|
||||||
"@firebase/logger": "0.4.0",
|
"@firebase/logger": "0.4.0",
|
||||||
"@firebase/util": "1.9.4",
|
"@firebase/util": "1.9.4",
|
||||||
|
@ -4964,15 +4964,15 @@
|
||||||
"integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q=="
|
"integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q=="
|
||||||
},
|
},
|
||||||
"node_modules/@firebase/auth": {
|
"node_modules/@firebase/auth": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.6.2.tgz",
|
||||||
"integrity": "sha512-Qhl35eJTV6BwvuueTPCY6x8kUlYyzALtjp/Ws0X3fw3AnjVVfuVb7oQ3Xh5VPVfMFhaIuUAd1KXwcAuIklkSDw==",
|
"integrity": "sha512-BFo/Nj1AAbKLbFiUyXCcnT/bSqMJicFOgdTAKzlXvCul7+eUE29vWmzd1g59O3iKAxvv3+fbQYjQVJpNTTHIyw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@firebase/component": "0.6.5",
|
"@firebase/component": "0.6.5",
|
||||||
"@firebase/logger": "0.4.0",
|
"@firebase/logger": "0.4.0",
|
||||||
"@firebase/util": "1.9.4",
|
"@firebase/util": "1.9.4",
|
||||||
"tslib": "^2.1.0",
|
"tslib": "^2.1.0",
|
||||||
"undici": "5.26.5"
|
"undici": "5.28.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@firebase/app": "0.x",
|
"@firebase/app": "0.x",
|
||||||
|
@ -4985,16 +4985,16 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@firebase/auth-compat": {
|
"node_modules/@firebase/auth-compat": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.4.tgz",
|
||||||
"integrity": "sha512-pRgje5BPCNR1vXyvGOVXwOHtv88A2WooXfklI8sV7/jWi03ExFqNfpJT26GUo/oD39NoKJ3Kt6rD5gVvdV7lMw==",
|
"integrity": "sha512-EtRVW9s0YsuJv3GnOGDoLUW3Pp9f3HcqWA2WK92E30Qa0FEVRwCSRLVQwn9td+SLVY3AP9gi/auC1q3osd4yCg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@firebase/auth": "1.6.0",
|
"@firebase/auth": "1.6.2",
|
||||||
"@firebase/auth-types": "0.12.0",
|
"@firebase/auth-types": "0.12.0",
|
||||||
"@firebase/component": "0.6.5",
|
"@firebase/component": "0.6.5",
|
||||||
"@firebase/util": "1.9.4",
|
"@firebase/util": "1.9.4",
|
||||||
"tslib": "^2.1.0",
|
"tslib": "^2.1.0",
|
||||||
"undici": "5.26.5"
|
"undici": "5.28.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@firebase/app-compat": "0.x"
|
"@firebase/app-compat": "0.x"
|
||||||
|
@ -5060,9 +5060,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@firebase/firestore": {
|
"node_modules/@firebase/firestore": {
|
||||||
"version": "4.4.2",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.5.0.tgz",
|
||||||
"integrity": "sha512-YaX6ypa/RzU6OkxzUQlpSxwhOIWdTraCNz7sMsbaSEjjl/pj/QvX6TqjkdWGzuBYh2S6rz7ErhDO0g39oZZw/g==",
|
"integrity": "sha512-rXS6v4HbsN6vZQlq2fLW1ZHb+J5SnS+8Zqb/McbKFIrGYjPUZo5CyO75mkgtlR1tCYAwCebaqoEWb6JHgZv/ww==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@firebase/component": "0.6.5",
|
"@firebase/component": "0.6.5",
|
||||||
"@firebase/logger": "0.4.0",
|
"@firebase/logger": "0.4.0",
|
||||||
|
@ -5071,7 +5071,7 @@
|
||||||
"@grpc/grpc-js": "~1.9.0",
|
"@grpc/grpc-js": "~1.9.0",
|
||||||
"@grpc/proto-loader": "^0.7.8",
|
"@grpc/proto-loader": "^0.7.8",
|
||||||
"tslib": "^2.1.0",
|
"tslib": "^2.1.0",
|
||||||
"undici": "5.26.5"
|
"undici": "5.28.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10.10.0"
|
"node": ">=10.10.0"
|
||||||
|
@ -5081,12 +5081,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@firebase/firestore-compat": {
|
"node_modules/@firebase/firestore-compat": {
|
||||||
"version": "0.3.25",
|
"version": "0.3.27",
|
||||||
"resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.25.tgz",
|
"resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.27.tgz",
|
||||||
"integrity": "sha512-+xI7WmsgZCBhMn/+uhDKcg+lsOUJ9FJyt5PGTzkFPbCsozWfeQZ7eVnfPh0rMkUOf0yIQ924RIe04gwvEIbcoQ==",
|
"integrity": "sha512-gY2q0fCDJvPg/IurZQbBM7MIVjxA1/LsvfgFOubUTrex5KTY9qm4/2V2R79eAs8Q+b4B8soDtlEjk6L8BW1Crw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@firebase/component": "0.6.5",
|
"@firebase/component": "0.6.5",
|
||||||
"@firebase/firestore": "4.4.2",
|
"@firebase/firestore": "4.5.0",
|
||||||
"@firebase/firestore-types": "3.0.0",
|
"@firebase/firestore-types": "3.0.0",
|
||||||
"@firebase/util": "1.9.4",
|
"@firebase/util": "1.9.4",
|
||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
|
@ -5105,9 +5105,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@firebase/functions": {
|
"node_modules/@firebase/functions": {
|
||||||
"version": "0.11.1",
|
"version": "0.11.2",
|
||||||
"resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.11.2.tgz",
|
||||||
"integrity": "sha512-3uUa1hB79Gmy6E1gHTfzoHeZolBeHc/I/n3+lOCDe6BOos9AHmzRjKygcFE/7VA2FJjitCE0K+OHI6+OuoY8fQ==",
|
"integrity": "sha512-2NULTYOZbu0rXczwfYdqQH0w1FmmYrKjTy1YPQSHLCAkMBdfewoKmVm4Lyo2vRn0H9ZndciLY7NszKDFt9MKCQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@firebase/app-check-interop-types": "0.3.0",
|
"@firebase/app-check-interop-types": "0.3.0",
|
||||||
"@firebase/auth-interop-types": "0.2.1",
|
"@firebase/auth-interop-types": "0.2.1",
|
||||||
|
@ -5115,19 +5115,19 @@
|
||||||
"@firebase/messaging-interop-types": "0.2.0",
|
"@firebase/messaging-interop-types": "0.2.0",
|
||||||
"@firebase/util": "1.9.4",
|
"@firebase/util": "1.9.4",
|
||||||
"tslib": "^2.1.0",
|
"tslib": "^2.1.0",
|
||||||
"undici": "5.26.5"
|
"undici": "5.28.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@firebase/app": "0.x"
|
"@firebase/app": "0.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@firebase/functions-compat": {
|
"node_modules/@firebase/functions-compat": {
|
||||||
"version": "0.3.7",
|
"version": "0.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.8.tgz",
|
||||||
"integrity": "sha512-uXe6Kmku5lNogp3OpPBcOJbSvnaCOn+YxS3zlXKNU6Q/NLwcvO3RY1zwYyctCos2RemEw3KEQ7YdzcECXjHWLw==",
|
"integrity": "sha512-VDHSw6UOu8RxfgAY/q8e+Jn+9Fh60Fc28yck0yfMsi2e0BiWgonIMWkFspFGGLgOJebTHl+hc+9v91rhzU6xlg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@firebase/component": "0.6.5",
|
"@firebase/component": "0.6.5",
|
||||||
"@firebase/functions": "0.11.1",
|
"@firebase/functions": "0.11.2",
|
||||||
"@firebase/functions-types": "0.6.0",
|
"@firebase/functions-types": "0.6.0",
|
||||||
"@firebase/util": "1.9.4",
|
"@firebase/util": "1.9.4",
|
||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
|
@ -5294,26 +5294,26 @@
|
||||||
"integrity": "sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA=="
|
"integrity": "sha512-RtEH4vdcbXZuZWRZbIRmQVBNsE7VDQpet2qFvq6vwKLBIQRQR5Kh58M4ok3A3US8Sr3rubYnaGqZSurCwI8uMA=="
|
||||||
},
|
},
|
||||||
"node_modules/@firebase/storage": {
|
"node_modules/@firebase/storage": {
|
||||||
"version": "0.12.1",
|
"version": "0.12.2",
|
||||||
"resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.12.2.tgz",
|
||||||
"integrity": "sha512-KJ5NV7FUh54TeTlEjdkTTX60ciCKOp9EqlbLnpdcXUYRJg0Z4810TXbilPc1z7fTIG4iPjtdi95bGE9n4dBX8A==",
|
"integrity": "sha512-MzanOBcxDx9oOwDaDPMuiYxd6CxcN1xZm+os5uNE3C1itbRKLhM9rzpODDKWzcbnHHFtXk3Q3lsK/d3Xa1WYYw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@firebase/component": "0.6.5",
|
"@firebase/component": "0.6.5",
|
||||||
"@firebase/util": "1.9.4",
|
"@firebase/util": "1.9.4",
|
||||||
"tslib": "^2.1.0",
|
"tslib": "^2.1.0",
|
||||||
"undici": "5.26.5"
|
"undici": "5.28.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@firebase/app": "0.x"
|
"@firebase/app": "0.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@firebase/storage-compat": {
|
"node_modules/@firebase/storage-compat": {
|
||||||
"version": "0.3.4",
|
"version": "0.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.5.tgz",
|
||||||
"integrity": "sha512-Y0m5e2gS/wB9Ioth2X/Sgz76vcxvqgQrCmfa9qwhss/N31kxY2Gks6Frv0nrE18AjVfcSmcfDitqUwxcMOTRSg==",
|
"integrity": "sha512-5dJXfY5NxCF5NAk4dLvJqC+m6cgcf0Fr29nrMHwhwI34pBheQq2PdRZqALsqZCES9dnHTuFNlqGQDpLr+Ph4rw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@firebase/component": "0.6.5",
|
"@firebase/component": "0.6.5",
|
||||||
"@firebase/storage": "0.12.1",
|
"@firebase/storage": "0.12.2",
|
||||||
"@firebase/storage-types": "0.8.0",
|
"@firebase/storage-types": "0.8.0",
|
||||||
"@firebase/util": "1.9.4",
|
"@firebase/util": "1.9.4",
|
||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
|
@ -14378,25 +14378,25 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/firebase": {
|
"node_modules/firebase": {
|
||||||
"version": "10.8.0",
|
"version": "10.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/firebase/-/firebase-10.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/firebase/-/firebase-10.9.0.tgz",
|
||||||
"integrity": "sha512-UJpC24vw8JFuHEOQyArBGKTUd7+kohLISCzHyn0M/prP0KOTx2io1eyLliEid330QqnWI7FOlPxoU97qecCSfQ==",
|
"integrity": "sha512-R8rDU3mg2dq0uPOoZ5Nc3BeZTbXxBPJS8HcZLtnV0f5/YrmpNsHngzmMHRVB+91T+ViJGVL/42dV23gS9w9ccw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@firebase/analytics": "0.10.1",
|
"@firebase/analytics": "0.10.1",
|
||||||
"@firebase/analytics-compat": "0.2.7",
|
"@firebase/analytics-compat": "0.2.7",
|
||||||
"@firebase/app": "0.9.27",
|
"@firebase/app": "0.9.29",
|
||||||
"@firebase/app-check": "0.8.2",
|
"@firebase/app-check": "0.8.2",
|
||||||
"@firebase/app-check-compat": "0.3.9",
|
"@firebase/app-check-compat": "0.3.9",
|
||||||
"@firebase/app-compat": "0.2.27",
|
"@firebase/app-compat": "0.2.29",
|
||||||
"@firebase/app-types": "0.9.0",
|
"@firebase/app-types": "0.9.0",
|
||||||
"@firebase/auth": "1.6.0",
|
"@firebase/auth": "1.6.2",
|
||||||
"@firebase/auth-compat": "0.5.2",
|
"@firebase/auth-compat": "0.5.4",
|
||||||
"@firebase/database": "1.0.3",
|
"@firebase/database": "1.0.3",
|
||||||
"@firebase/database-compat": "1.0.3",
|
"@firebase/database-compat": "1.0.3",
|
||||||
"@firebase/firestore": "4.4.2",
|
"@firebase/firestore": "4.5.0",
|
||||||
"@firebase/firestore-compat": "0.3.25",
|
"@firebase/firestore-compat": "0.3.27",
|
||||||
"@firebase/functions": "0.11.1",
|
"@firebase/functions": "0.11.2",
|
||||||
"@firebase/functions-compat": "0.3.7",
|
"@firebase/functions-compat": "0.3.8",
|
||||||
"@firebase/installations": "0.6.5",
|
"@firebase/installations": "0.6.5",
|
||||||
"@firebase/installations-compat": "0.2.5",
|
"@firebase/installations-compat": "0.2.5",
|
||||||
"@firebase/messaging": "0.12.6",
|
"@firebase/messaging": "0.12.6",
|
||||||
|
@ -14405,8 +14405,8 @@
|
||||||
"@firebase/performance-compat": "0.2.5",
|
"@firebase/performance-compat": "0.2.5",
|
||||||
"@firebase/remote-config": "0.4.5",
|
"@firebase/remote-config": "0.4.5",
|
||||||
"@firebase/remote-config-compat": "0.2.5",
|
"@firebase/remote-config-compat": "0.2.5",
|
||||||
"@firebase/storage": "0.12.1",
|
"@firebase/storage": "0.12.2",
|
||||||
"@firebase/storage-compat": "0.3.4",
|
"@firebase/storage-compat": "0.3.5",
|
||||||
"@firebase/util": "1.9.4"
|
"@firebase/util": "1.9.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -14456,9 +14456,9 @@
|
||||||
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
|
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
|
||||||
},
|
},
|
||||||
"node_modules/follow-redirects": {
|
"node_modules/follow-redirects": {
|
||||||
"version": "1.15.5",
|
"version": "1.15.6",
|
||||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||||
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
|
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "individual",
|
"type": "individual",
|
||||||
|
@ -17671,9 +17671,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jose": {
|
"node_modules/jose": {
|
||||||
"version": "4.15.4",
|
"version": "4.15.5",
|
||||||
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.4.tgz",
|
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz",
|
||||||
"integrity": "sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==",
|
"integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/panva"
|
"url": "https://github.com/sponsors/panva"
|
||||||
}
|
}
|
||||||
|
@ -26476,9 +26476,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "5.26.5",
|
"version": "5.28.3",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.26.5.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-5.28.3.tgz",
|
||||||
"integrity": "sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw==",
|
"integrity": "sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fastify/busboy": "^2.0.0"
|
"@fastify/busboy": "^2.0.0"
|
||||||
},
|
},
|
||||||
|
@ -27994,7 +27994,7 @@
|
||||||
},
|
},
|
||||||
"packages/data-provider": {
|
"packages/data-provider": {
|
||||||
"name": "librechat-data-provider",
|
"name": "librechat-data-provider",
|
||||||
"version": "0.4.7",
|
"version": "0.4.8",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "librechat-data-provider",
|
"name": "librechat-data-provider",
|
||||||
"version": "0.5.0",
|
"version": "0.5.1",
|
||||||
"description": "data services for librechat apps",
|
"description": "data services for librechat apps",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"module": "dist/index.es.js",
|
"module": "dist/index.es.js",
|
||||||
|
|
|
@ -527,3 +527,29 @@ export const defaultOrderQuery: {
|
||||||
} = {
|
} = {
|
||||||
order: 'asc',
|
order: 'asc',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum AssistantStreamEvents {
|
||||||
|
ThreadCreated = 'thread.created',
|
||||||
|
ThreadRunCreated = 'thread.run.created',
|
||||||
|
ThreadRunQueued = 'thread.run.queued',
|
||||||
|
ThreadRunInProgress = 'thread.run.in_progress',
|
||||||
|
ThreadRunRequiresAction = 'thread.run.requires_action',
|
||||||
|
ThreadRunCompleted = 'thread.run.completed',
|
||||||
|
ThreadRunFailed = 'thread.run.failed',
|
||||||
|
ThreadRunCancelling = 'thread.run.cancelling',
|
||||||
|
ThreadRunCancelled = 'thread.run.cancelled',
|
||||||
|
ThreadRunExpired = 'thread.run.expired',
|
||||||
|
ThreadRunStepCreated = 'thread.run.step.created',
|
||||||
|
ThreadRunStepInProgress = 'thread.run.step.in_progress',
|
||||||
|
ThreadRunStepCompleted = 'thread.run.step.completed',
|
||||||
|
ThreadRunStepFailed = 'thread.run.step.failed',
|
||||||
|
ThreadRunStepCancelled = 'thread.run.step.cancelled',
|
||||||
|
ThreadRunStepExpired = 'thread.run.step.expired',
|
||||||
|
ThreadRunStepDelta = 'thread.run.step.delta',
|
||||||
|
ThreadMessageCreated = 'thread.message.created',
|
||||||
|
ThreadMessageInProgress = 'thread.message.in_progress',
|
||||||
|
ThreadMessageCompleted = 'thread.message.completed',
|
||||||
|
ThreadMessageIncomplete = 'thread.message.incomplete',
|
||||||
|
ThreadMessageDelta = 'thread.message.delta',
|
||||||
|
ErrorEvent = 'error',
|
||||||
|
}
|
||||||
|
|
|
@ -275,5 +275,9 @@ export const listConversationsByQuery = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteAction = async (assistant_id: string, action_id: string): Promise<void> =>
|
export const deleteAction = async (
|
||||||
request.delete(endpoints.assistants(`actions/${assistant_id}/${action_id}`));
|
assistant_id: string,
|
||||||
|
action_id: string,
|
||||||
|
model: string,
|
||||||
|
): Promise<void> =>
|
||||||
|
request.delete(endpoints.assistants(`actions/${assistant_id}/${action_id}/${model}`));
|
||||||
|
|
|
@ -186,6 +186,7 @@ export enum ContentTypes {
|
||||||
TEXT = 'text',
|
TEXT = 'text',
|
||||||
TOOL_CALL = 'tool_call',
|
TOOL_CALL = 'tool_call',
|
||||||
IMAGE_FILE = 'image_file',
|
IMAGE_FILE = 'image_file',
|
||||||
|
ERROR = 'error',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum StepTypes {
|
export enum StepTypes {
|
||||||
|
@ -236,6 +237,7 @@ export type ContentPart = (CodeToolCall | RetrievalToolCall | FunctionToolCall |
|
||||||
PartMetadata;
|
PartMetadata;
|
||||||
|
|
||||||
export type TMessageContentParts =
|
export type TMessageContentParts =
|
||||||
|
| { type: ContentTypes.ERROR; text: Text & PartMetadata }
|
||||||
| { type: ContentTypes.TEXT; text: Text & PartMetadata }
|
| { type: ContentTypes.TEXT; text: Text & PartMetadata }
|
||||||
| {
|
| {
|
||||||
type: ContentTypes.TOOL_CALL;
|
type: ContentTypes.TOOL_CALL;
|
||||||
|
@ -243,16 +245,20 @@ export type TMessageContentParts =
|
||||||
}
|
}
|
||||||
| { type: ContentTypes.IMAGE_FILE; image_file: ImageFile & PartMetadata };
|
| { type: ContentTypes.IMAGE_FILE; image_file: ImageFile & PartMetadata };
|
||||||
|
|
||||||
export type TContentData = TMessageContentParts & {
|
export type StreamContentData = TMessageContentParts & {
|
||||||
|
index: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TContentData = StreamContentData & {
|
||||||
messageId: string;
|
messageId: string;
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
userMessageId: string;
|
userMessageId: string;
|
||||||
thread_id: string;
|
thread_id: string;
|
||||||
index: number;
|
|
||||||
stream?: boolean;
|
stream?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionDelimiter = '_action_';
|
export const actionDelimiter = '_action_';
|
||||||
|
export const actionDomainSeparator = '---';
|
||||||
|
|
||||||
export enum AuthTypeEnum {
|
export enum AuthTypeEnum {
|
||||||
ServiceHttp = 'service_http',
|
ServiceHttp = 'service_http',
|
||||||
|
|
|
@ -56,6 +56,7 @@ export type UpdateActionVariables = {
|
||||||
functions: FunctionTool[];
|
functions: FunctionTool[];
|
||||||
metadata: ActionMetadata;
|
metadata: ActionMetadata;
|
||||||
action_id?: string;
|
action_id?: string;
|
||||||
|
model: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UploadAssistantAvatarOptions = {
|
export type UploadAssistantAvatarOptions = {
|
||||||
|
@ -109,6 +110,7 @@ export type UpdateActionOptions = {
|
||||||
export type DeleteActionVariables = {
|
export type DeleteActionVariables = {
|
||||||
assistant_id: string;
|
assistant_id: string;
|
||||||
action_id: string;
|
action_id: string;
|
||||||
|
model: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DeleteActionOptions = {
|
export type DeleteActionOptions = {
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"lib": ["es2017", "dom"],
|
"lib": ["es2017", "dom", "ES2021.String"],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue