fix(moderation): remove manual reanalyze triggers — auto-recovery only

Manual per-message and batch reanalyze buttons/endpoints let anyone
re-queue arbitrary messages for LLM analysis, burning AI credits on
spam. Removed:
- FE: Reanalyze buttons in message list, search panel, and messages page
- FE: useReanalyze/useReanalyzeBatch hooks + messagesApi methods
- BE: POST /api/messages/:id/reanalyze and /reanalyze-batch endpoints
- BE: markForReanalysis/reanalyzeErrorBatch service+repository methods

Recovery of failed messages is fully automatic: the discord-gateway
startPendingAIAnalysisWorker retries 'pending' (batch path) and
'error/analysis_incomplete' (individual path) messages on
AI_ANALYSIS_RECOVERY_INTERVAL_MS.
This commit is contained in:
asepharyana
2026-08-04 15:37:56 +07:00
parent 88484f12a9
commit 2f51f94610
11 changed files with 6 additions and 243 deletions
@@ -1,6 +1,6 @@
"use client";
import { Flag, Image, Loader2, RefreshCw, Search } from "lucide-react";
import { Flag, Image, Loader2, Search } from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import { GlassCard } from "@/components/glass/card";
@@ -13,7 +13,6 @@ import { MessageList } from "@/components/messages/message-list";
import { SearchOverlay } from "@/components/messages/search-overlay";
import { EmptyState, ErrorState, LoadingSkeleton } from "@/components/shared";
import { GuildSelector } from "@/components/shared/guild-selector";
import { Button } from "@/components/ui/button";
import {
Select,
SelectContent,
@@ -28,8 +27,6 @@ import {
useMessages,
useMessagesHasMore,
useMessagesWsSync,
useReanalyze,
useReanalyzeBatch,
useReview,
useTextChannels,
} from "@/hooks";
@@ -74,8 +71,6 @@ export default function MessagesPage() {
const loadMoreMut = useLoadMore();
const { data: images } = useImages(guildId);
const { data: reviews } = useReview(selectedChannel || undefined);
const reanalyzeMut = useReanalyze();
const reanalyzeBatchMut = useReanalyzeBatch();
const {
message: detailMessage,
@@ -164,14 +159,6 @@ export default function MessagesPage() {
⌘K
</span>
</button>
<Button
variant="outline"
size="sm"
onClick={() => reanalyzeBatchMut.mutate(guildId)}
className="h-8 text-xs"
>
<RefreshCw className="mr-1 size-3" /> Reanalyze
</Button>
</div>
{/* ── Sub navigation ── */}
@@ -197,7 +184,6 @@ export default function MessagesPage() {
messages={currentMessages}
selectedId={detailId}
onSelect={setDetailId}
onReanalyze={(id) => reanalyzeMut.mutate(id)}
hasMore={cursorData?.hasMore}
onLoadMore={handleLoadMore}
isLoadingMore={loadMoreMut.isPending}
@@ -1,6 +1,6 @@
"use client";
import { Loader2, RefreshCw, Search, Sparkles } from "lucide-react";
import { Loader2, Search, Sparkles } from "lucide-react";
import { useCallback, useState } from "react";
import { EmptyState, LoadingSkeleton } from "@/components/shared";
@@ -10,14 +10,13 @@ import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Progress } from "@/components/ui/progress";
import { useMessageSearch, useReanalyze } from "@/hooks";
import { useMessageSearch } from "@/hooks";
import { renderMessageContent, safeParseJsonArray } from "@/lib/format";
import { cn } from "@/lib/utils";
export function SearchPanel() {
const [query, setQuery] = useState("");
const [enabled, setEnabled] = useState(false);
const reanalyzeMut = useReanalyze();
const { data: results, isValidating: isFetching } = useMessageSearch(
query,
@@ -134,14 +133,6 @@ export function SearchPanel() {
</span>
</div>
)}
<Button
variant="ghost"
size="xs"
onClick={() => reanalyzeMut.mutate(msg.id)}
>
<RefreshCw className="size-3 mr-1" />
Reanalyze
</Button>
</div>
</div>
</CardContent>
@@ -1,9 +1,8 @@
"use client";
import { Hash, RefreshCw } from "lucide-react";
import { Hash } from "lucide-react";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
import {
@@ -18,11 +17,9 @@ import { AiStatusBadge } from "./ai-status-badge";
export function MessageCard({
message: msg,
onClick,
onReanalyze,
}: {
message: MessageRecord;
onClick: (id: string) => void;
onReanalyze: (id: string) => void;
}) {
const severity = (
{
@@ -156,16 +153,6 @@ export function MessageCard({
</span>
</div>
)}
<Button
variant="ghost"
size="xs"
onClick={(e) => {
e.stopPropagation();
onReanalyze(msg.id);
}}
>
<RefreshCw className="size-3 mr-1" /> Reanalyze
</Button>
</div>
</div>
</CardContent>
@@ -9,7 +9,6 @@ interface MessageListProps {
messages: MessageRecord[];
selectedId: string | null;
onSelect: (id: string) => void;
onReanalyze?: (id: string) => void;
hasMore?: boolean;
onLoadMore?: () => void;
isLoadingMore?: boolean;
@@ -19,7 +18,6 @@ export function MessageList({
messages,
selectedId: _selectedId,
onSelect,
onReanalyze,
hasMore,
onLoadMore,
isLoadingMore,
@@ -27,12 +25,7 @@ export function MessageList({
return (
<>
{messages.map((msg) => (
<MessageCard
key={msg.id}
message={msg}
onClick={onSelect}
onReanalyze={(id) => onReanalyze?.(id)}
/>
<MessageCard key={msg.id} message={msg} onClick={onSelect} />
))}
{hasMore && (
<div className="flex justify-center py-4">
-2
View File
@@ -24,8 +24,6 @@ export {
useMessages,
useMessagesHasMore,
useMessagesWsSync,
useReanalyze,
useReanalyzeBatch,
useReview,
useTextChannels,
} from "./use-messages";
@@ -155,16 +155,6 @@ export function useMessageDetail(id: string | null) {
};
}
// ── Mutations ────────────────────────────────────
export function useReanalyze() {
return useAction((id: string) => messagesApi.reanalyze(id));
}
export function useReanalyzeBatch() {
return useAction((guildId: string) => messagesApi.reanalyzeBatch(guildId));
}
// ── Search ───────────────────────────────────────
export function useMessageSearch(query: string, enabled: boolean) {
@@ -65,15 +65,6 @@ export const messagesApi = {
);
},
reanalyze: (id: string) =>
api.post<{ ok: boolean }>(`/api/messages/${id}/reanalyze`, {}),
reanalyzeBatch: (guildId?: string, channelId?: string) =>
api.post<{ ok: boolean; count: number }>("/api/messages/reanalyze-batch", {
guildId,
channelId,
}),
search: (query: string, limit?: number) => {
const params = new URLSearchParams({ q: query });
if (limit) params.set("limit", String(limit));