feat: replace corrections/tuner with dashboard module

Replace the corrections/adaptive-prompt-tuner feature with a new
dashboard module providing server stats and user profile overview.

Backend:
- Add dashboard module (routes, service, repository) with stats + user list + user detail endpoints
- Remove corrections module entirely
- Wire dashboard router in app.ts

Frontend:
- Add dashboard feature (DashboardStats, UserSummaryList, UserProfileDetail components + useDashboard hook)
- Remove tuner feature (CorrectionStats, CorrectionHistory, SubmitCorrection, useCorrections)
- Update API client from corrections → dashboard types/fns
- Rename tab 'tuner' → 'dashboard'
- Update MobileTabBar, Header, Sidebar links

Tests:
- Expand backend placeholder test with dashboard assertions
- Expand discord-gateway placeholder test with config/channel assertions

AI moderation:
- llmModerationClient: improve status/reply detection, expand safety categories, fix timer reset
- userProfileLearner: fix isReply refinement
- userProfileStore: add pending cache check
- messageMetadata: add crosspost type mapping
- migrate.ts: improve partial-index safety in schema push

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-06-13 11:24:42 +07:00
co-authored by Claude
parent e3249edb6c
commit 30f8d7cce3
32 changed files with 1409 additions and 1219 deletions
@@ -5,6 +5,7 @@ import type { ChatCompletion } from "openai/resources/chat/completions";
import { config } from "../../shared/config/config.js";
import { resizeImageForVision } from "../attachment-upload/imageResizer.js";
import { extractMessageMediaEvidence } from "../message-capture/messageMetadata.js";
import { getMessageById } from "../message-capture/messageStore.js";
import type {
AnalysisResult,
AttachmentRecord,
@@ -43,9 +44,8 @@ import {
upsertCachedMediaByPhash,
} from "./textCacheStore.js";
import { extractUrlsFromText, fetchUrlSafely } from "./urlFetcher.js";
import { initializeUserReputation } from "./userReputationStore.js";
import { getUserProfile } from "./userProfileStore.js";
import { getMessageById } from "../message-capture/messageStore.js";
import { initializeUserReputation } from "./userReputationStore.js";
export { sniffImageMimeType } from "./imageMimeSniffer.js";
export { extractJson } from "./jsonExtractor.js";
@@ -805,7 +805,10 @@ async function runTextOnlyBatch(
// Log channel culture & user profiles for debugging
if (channelCulture) {
log.debug({ channelId, culturePreview: channelCulture.slice(0, 120) }, "Injected channel culture into prompt");
log.debug(
{ channelId, culturePreview: channelCulture.slice(0, 120) },
"Injected channel culture into prompt",
);
}
// Run sub-batches sequentially to avoid rate limits
@@ -826,7 +829,9 @@ async function runTextOnlyBatch(
const profile = await getUserProfile(msg.user_id);
userProfiles.set(
msg.user_id,
profile ? `<user_profile>${profile.profile_summary}</user_profile>` : "",
profile
? `<user_profile>${profile.profile_summary}</user_profile>`
: "",
);
}
}
@@ -1150,7 +1155,10 @@ async function runMediaBatch(
// Log channel culture for debugging
if (channelCulture) {
log.debug({ channelId, culturePreview: channelCulture.slice(0, 120) }, "Injected channel culture into prompt (media path)");
log.debug(
{ channelId, culturePreview: channelCulture.slice(0, 120) },
"Injected channel culture into prompt (media path)",
);
}
const correctedExamples = await buildCorrectedFewShotExamples();
@@ -6,8 +6,8 @@ import {
messagesTable,
userProfilesTable,
} from "../../shared/database/schema.js";
import { updateUserProfile } from "./userProfileStore.js";
import { llmChat } from "./llmClient.js";
import { updateUserProfile } from "./userProfileStore.js";
const PROFILE_LEARNING_INTERVAL = 1000 * 60 * 60 * 12; // 12 hours
const log = createChildLogger("userProfileLearner");
@@ -55,8 +55,5 @@ export async function updateUserProfile(
},
});
logger.debug(
{ userId, guildId, profileSummary },
"User profile updated",
);
logger.debug({ userId, guildId, profileSummary }, "User profile updated");
}
@@ -237,7 +237,8 @@ export function getMessageMetadata(message: Message): RichMessageMetadata {
messageId: message.reference.messageId ?? null,
channelId: message.reference.channelId ?? null,
guildId: message.reference.guildId ?? null,
type: (message.reference.type as unknown as string | undefined) ?? null,
type:
(message.reference.type as unknown as string | undefined) ?? null,
}
: null,
isCrosspost: message.flags?.has(1 << 1) ?? false,