mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-09-22 06:00:56 +02:00

* ✨ feat: Implement Show Thinking feature; refactor: testing thinking render optimizations * ✨ feat: Refactor Thinking component styles and enhance Markdown rendering * chore: add back removed code, revert type changes * chore: Add back resetCounter effect to Markdown component for improved code block indexing * chore: bump @librechat/agents and google langchain packages * WIP: reasoning type updates * WIP: first pass, reasoning content blocks * chore: revert code * chore: bump @librechat/agents * refactor: optimize reasoning tag handling * style: ul indent padding * feat: add Reasoning component to handle reasoning display * feat: first pass, content reasoning part styling * refactor: add content placeholder for endpoints using new stream handler * refactor: only cache messages when requesting stream audio * fix: circular dep. * fix: add default param * refactor: tts, only request after message stream, fix chrome autoplay * style: update label for submitting state and add localization for 'Thinking...' * fix: improve global audio pause logic and reset active run ID * fix: handle artifact edge cases * fix: remove unnecessary console log from artifact update test * feat: add support for continued message handling with new streaming method --------- Co-authored-by: Marco Beretta <81851188+berry-13@users.noreply.github.com>
189 lines
6.2 KiB
JavaScript
189 lines
6.2 KiB
JavaScript
const express = require('express');
|
|
const { ContentTypes } = require('librechat-data-provider');
|
|
const {
|
|
saveConvo,
|
|
saveMessage,
|
|
getMessage,
|
|
getMessages,
|
|
updateMessage,
|
|
deleteMessages,
|
|
} = require('~/models');
|
|
const { findAllArtifacts, replaceArtifactContent } = require('~/server/services/Artifacts/update');
|
|
const { requireJwtAuth, validateMessageReq } = require('~/server/middleware');
|
|
const { countTokens } = require('~/server/utils');
|
|
const { logger } = require('~/config');
|
|
|
|
const router = express.Router();
|
|
router.use(requireJwtAuth);
|
|
|
|
router.post('/artifact/:messageId', async (req, res) => {
|
|
try {
|
|
const { messageId } = req.params;
|
|
const { index, original, updated } = req.body;
|
|
|
|
if (typeof index !== 'number' || index < 0 || original == null || updated == null) {
|
|
return res.status(400).json({ error: 'Invalid request parameters' });
|
|
}
|
|
|
|
const message = await getMessage({ user: req.user.id, messageId });
|
|
if (!message) {
|
|
return res.status(404).json({ error: 'Message not found' });
|
|
}
|
|
|
|
const artifacts = findAllArtifacts(message);
|
|
if (index >= artifacts.length) {
|
|
return res.status(400).json({ error: 'Artifact index out of bounds' });
|
|
}
|
|
|
|
const targetArtifact = artifacts[index];
|
|
let updatedText = null;
|
|
|
|
if (targetArtifact.source === 'content') {
|
|
const part = message.content[targetArtifact.partIndex];
|
|
updatedText = replaceArtifactContent(part.text, targetArtifact, original, updated);
|
|
if (updatedText) {
|
|
part.text = updatedText;
|
|
}
|
|
} else {
|
|
updatedText = replaceArtifactContent(message.text, targetArtifact, original, updated);
|
|
if (updatedText) {
|
|
message.text = updatedText;
|
|
}
|
|
}
|
|
|
|
if (!updatedText) {
|
|
return res.status(400).json({ error: 'Original content not found in target artifact' });
|
|
}
|
|
|
|
const savedMessage = await saveMessage(
|
|
req,
|
|
{
|
|
messageId,
|
|
conversationId: message.conversationId,
|
|
text: message.text,
|
|
content: message.content,
|
|
user: req.user.id,
|
|
},
|
|
{ context: 'POST /api/messages/artifact/:messageId' },
|
|
);
|
|
|
|
res.status(200).json({
|
|
conversationId: savedMessage.conversationId,
|
|
content: savedMessage.content,
|
|
text: savedMessage.text,
|
|
});
|
|
} catch (error) {
|
|
logger.error('Error editing artifact:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
/* Note: It's necessary to add `validateMessageReq` within route definition for correct params */
|
|
router.get('/:conversationId', validateMessageReq, async (req, res) => {
|
|
try {
|
|
const { conversationId } = req.params;
|
|
const messages = await getMessages({ conversationId }, '-_id -__v -user');
|
|
res.status(200).json(messages);
|
|
} catch (error) {
|
|
logger.error('Error fetching messages:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
router.post('/:conversationId', validateMessageReq, async (req, res) => {
|
|
try {
|
|
const message = req.body;
|
|
const savedMessage = await saveMessage(
|
|
req,
|
|
{ ...message, user: req.user.id },
|
|
{ context: 'POST /api/messages/:conversationId' },
|
|
);
|
|
if (!savedMessage) {
|
|
return res.status(400).json({ error: 'Message not saved' });
|
|
}
|
|
await saveConvo(req, savedMessage, { context: 'POST /api/messages/:conversationId' });
|
|
res.status(201).json(savedMessage);
|
|
} catch (error) {
|
|
logger.error('Error saving message:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
router.get('/:conversationId/:messageId', validateMessageReq, async (req, res) => {
|
|
try {
|
|
const { conversationId, messageId } = req.params;
|
|
const message = await getMessages({ conversationId, messageId }, '-_id -__v -user');
|
|
if (!message) {
|
|
return res.status(404).json({ error: 'Message not found' });
|
|
}
|
|
res.status(200).json(message);
|
|
} catch (error) {
|
|
logger.error('Error fetching message:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
router.put('/:conversationId/:messageId', validateMessageReq, async (req, res) => {
|
|
try {
|
|
const { conversationId, messageId } = req.params;
|
|
const { text, index, model } = req.body;
|
|
|
|
if (index === undefined) {
|
|
const tokenCount = await countTokens(text, model);
|
|
const result = await updateMessage(req, { messageId, text, tokenCount });
|
|
return res.status(200).json(result);
|
|
}
|
|
|
|
if (typeof index !== 'number' || index < 0) {
|
|
return res.status(400).json({ error: 'Invalid index' });
|
|
}
|
|
|
|
const message = (await getMessages({ conversationId, messageId }, 'content tokenCount'))?.[0];
|
|
if (!message) {
|
|
return res.status(404).json({ error: 'Message not found' });
|
|
}
|
|
|
|
const existingContent = message.content;
|
|
if (!Array.isArray(existingContent) || index >= existingContent.length) {
|
|
return res.status(400).json({ error: 'Invalid index' });
|
|
}
|
|
|
|
const updatedContent = [...existingContent];
|
|
if (!updatedContent[index]) {
|
|
return res.status(400).json({ error: 'Content part not found' });
|
|
}
|
|
|
|
if (updatedContent[index].type !== ContentTypes.TEXT) {
|
|
return res.status(400).json({ error: 'Cannot update non-text content' });
|
|
}
|
|
|
|
const oldText = updatedContent[index].text;
|
|
updatedContent[index] = { type: ContentTypes.TEXT, text };
|
|
|
|
let tokenCount = message.tokenCount;
|
|
if (tokenCount !== undefined) {
|
|
const oldTokenCount = await countTokens(oldText, model);
|
|
const newTokenCount = await countTokens(text, model);
|
|
tokenCount = Math.max(0, tokenCount - oldTokenCount) + newTokenCount;
|
|
}
|
|
|
|
const result = await updateMessage(req, { messageId, content: updatedContent, tokenCount });
|
|
return res.status(200).json(result);
|
|
} catch (error) {
|
|
logger.error('Error updating message:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
router.delete('/:conversationId/:messageId', validateMessageReq, async (req, res) => {
|
|
try {
|
|
const { messageId } = req.params;
|
|
await deleteMessages({ messageId });
|
|
res.status(204).send();
|
|
} catch (error) {
|
|
logger.error('Error deleting message:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|