feat: add "Retry All Errors" batch reanalyze button

Backend:
- POST /api/messages/reanalyze-batch — bulk reset error messages to pending
- messages.repository.reanalyzeErrorBatch() — scoped by guildId/channelId/messageIds
- messages.service.reanalyzeErrorBatch() — validation layer

Frontend:
- reanalyzeErrorBatch() API client function
- useMessages.reanalyzeAllErrors() — optimistic state + batch call
- MessagesPanel: destructive button visible when error count > 0
- Shows confirmation text with queued count

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-06-03 00:17:51 +07:00
co-authored by Claude Opus 4.8
parent d81aaf2b4a
commit 6da8a32c9b
7 changed files with 156 additions and 3 deletions
@@ -9,6 +9,7 @@ import {
handleGetMessagesByChannel,
handleListMessages,
} from "./messages.controller.js";
import { messagesService } from "./messages.service.js";
const logger = createChildLogger("messages.routes");
@@ -28,7 +29,30 @@ export function createMessagesRouter(): Router {
// (uses /detail/ prefix to avoid collision with :channelId route above)
router.get("/messages/detail/:id", handleGetMessageById);
// POST /api/messages/:id/reanalyze - Mark message for re-analysis
// POST /api/messages/reanalyze-batch — Bulk retry all errored messages
// MUST be registered BEFORE /messages/:id/reanalyze so "reanalyze-batch"
// is not captured as an :id param.
router.post(
"/messages/reanalyze-batch",
asyncHandler(async (req: Request, res: Response) => {
const { guildId, channelId, messageIds } = (req.body ?? {}) as {
guildId?: string;
channelId?: string;
messageIds?: string[];
};
const count = await messagesService.reanalyzeErrorBatch({
guildId,
channelId,
messageIds,
});
logger.info({ count, guildId, channelId }, "Batch reanalyze completed");
res.status(200).json({ ok: true, count });
}),
);
// POST /api/messages/:id/reanalyze - Mark single message for re-analysis
router.post(
"/messages/:id/reanalyze",
asyncHandler(async (req: Request, res: Response) => {