add response versioning support in messages for regeneration improvements
This commit is contained in:
+53
-3
@@ -105,6 +105,14 @@ const preferredModelValidator = (body) => {
|
||||
export function createChatsRouter({repositories, chatService, modelService, config, chatRateLimiters}) {
|
||||
const router = express.Router();
|
||||
router.use(requireLogin);
|
||||
router.use(["/chats/:id/messages/by-index/:index", "/chats/:id/messages/:messageId"], (req, res, next) => {
|
||||
const chatId = Number(req.params.id);
|
||||
const messageId = req.params.messageId ? Number(req.params.messageId) : chatService.getChatMessageByIndex(chatId, req.params.index)?.id;
|
||||
if (["PUT", "DELETE", "POST"].includes(req.method) && chatService.isRegeneratingMessage(chatId, messageId)) {
|
||||
return res.status(409).json({error: "This response is being regenerated. Wait for it to finish."});
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
router.get("/models", async (req, res) => {
|
||||
try {
|
||||
@@ -475,7 +483,21 @@ export function createChatsRouter({repositories, chatService, modelService, conf
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/chats/:id/messages/:messageId/retry", ...chatRateLimiters, async (req, res) => {
|
||||
router.post("/chats/:id/messages/:messageId/versions/:index", (req, res) => {
|
||||
const chatId = Number(req.params.id), messageId = Number(req.params.messageId);
|
||||
if (!repositories.getChat(chatId, req.session.user)) return res.status(404).json({error: "Chat not found"});
|
||||
const message = repositories.getChatMessage(chatId, messageId);
|
||||
if (!message || message.role !== "bot") return res.status(404).json({error: "Assistant message not found"});
|
||||
if (chatService.isGenerating(chatId)) return res.status(409).json({error: "Wait for generation to finish"});
|
||||
const index = Number(req.params.index);
|
||||
const versions = repositories.listMessageVersions(messageId);
|
||||
if (!Number.isInteger(index) || index < 0 || index >= versions.length) return res.status(400).json({error: "Version is out of range"});
|
||||
repositories.saveMessageVersion(chatId, messageId, null, index);
|
||||
repositories.updateChatContextSummary(chatId, req.session.user, null, 0);
|
||||
res.json({message: chatService.formatChatMessage(repositories.getChatMessage(chatId, messageId))});
|
||||
});
|
||||
|
||||
router.post(["/chats/:id/messages/:messageId/retry", "/chats/:id/messages/:messageId/retry/stream"], ...chatRateLimiters, async (req, res) => {
|
||||
const chatId = Number(req.params.id);
|
||||
const messageId = Number(req.params.messageId);
|
||||
const chat = repositories.getChat(chatId, req.session.user);
|
||||
@@ -484,19 +506,45 @@ export function createChatsRouter({repositories, chatService, modelService, conf
|
||||
if (!targetMessage) return res.status(404).json({error: "Message not found"});
|
||||
if (targetMessage.role !== "bot") return res.status(400).json({error: "Only assistant messages can be regenerated"});
|
||||
|
||||
const streaming = req.path.endsWith("/stream");
|
||||
const controller = new AbortController();
|
||||
let finished = false;
|
||||
const sendEvent = (event, data) => {
|
||||
if (!res.destroyed) res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
||||
};
|
||||
if (streaming) {
|
||||
res.once("close", () => { if (!finished) controller.abort(); });
|
||||
res.writeHead(200, {"Content-Type": "text/event-stream", "Cache-Control": "no-cache, no-transform", Connection: "keep-alive", "X-Accel-Buffering": "no"});
|
||||
}
|
||||
try {
|
||||
const result = await chatService.retryMessage({
|
||||
user: req.session.user,
|
||||
chatId,
|
||||
selectedModel: String(req.body?.model || "qwen3:14b"),
|
||||
targetMessage,
|
||||
retryStyle: String(req.body?.style || "").trim() || null
|
||||
retryStyle: String(req.body?.style || "").trim() || null,
|
||||
webSearch: req.body?.webSearch === true,
|
||||
reasoning: req.body?.reasoning === true,
|
||||
signal: controller.signal,
|
||||
...(streaming ? {
|
||||
onChunk: (chunk, fullReply) => sendEvent("chunk", {chunk, fullReply}),
|
||||
onThinking: (chunk, fullThinking) => sendEvent("thinking", {chunk, fullThinking}),
|
||||
onStatus: status => sendEvent("status", status)
|
||||
} : {})
|
||||
});
|
||||
if (streaming) {
|
||||
sendEvent(result.error ? "error" : "done", result.error ? {error: result.error, searchState: result.searchState} : {...result, reply: result.message.content});
|
||||
return;
|
||||
}
|
||||
if (result.error) return res.status(result.status).json({error: result.error});
|
||||
return res.json(result);
|
||||
} catch (error) {
|
||||
const mapped = modelService.mapError(error);
|
||||
if (streaming) { sendEvent("error", mapped.body); return; }
|
||||
return res.status(mapped.status).json(mapped.body);
|
||||
} finally {
|
||||
finished = true;
|
||||
if (streaming) res.end();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -514,7 +562,9 @@ export function createChatsRouter({repositories, chatService, modelService, conf
|
||||
chatId,
|
||||
selectedModel: String(req.body?.model || "qwen3:14b"),
|
||||
targetMessage,
|
||||
retryStyle: String(req.body?.style || "").trim() || null
|
||||
retryStyle: String(req.body?.style || "").trim() || null,
|
||||
webSearch: req.body?.webSearch === true,
|
||||
reasoning: req.body?.reasoning === true
|
||||
});
|
||||
if (result.error) return res.status(result.status).json({error: result.error});
|
||||
return res.json(result);
|
||||
|
||||
Reference in New Issue
Block a user