feat: update dependencies and improve dashboard functionality
Deploy to VPS / deploy (push) Failing after 1m43s
Deploy to VPS / deploy (push) Failing after 1m43s
- Added new dependencies for Next.js and lucide-react in pnpm-workspace.yaml. - Refactored DashboardPage component to improve readability and error handling. - Enhanced Header component to display error status with an alert icon. - Updated MobileTabBar and Sidebar components to use a centralized tabs definition. - Improved ChannelsView in dashboard-panel to handle channel fetching more cleanly. - Fixed ActiveSpeaker type to use camelCase for userId. - Updated MessagesPanel to handle guildId checks more gracefully. - Adjusted API calls in dashboard and messages to align with backend expectations. - Refined type definitions across various interfaces for consistency and clarity.
This commit is contained in:
@@ -19,7 +19,6 @@ import {
|
||||
} from "./batchProcessor.js";
|
||||
import { scheduleConversationAnalysis } from "./batchScheduler.js";
|
||||
import {
|
||||
_redisEventBroadcaster,
|
||||
broadcastAnalysisCompleted,
|
||||
conversationConsecutiveErrors,
|
||||
conversationDebounceTimers,
|
||||
|
||||
@@ -16,13 +16,13 @@ let activeCount = 0;
|
||||
let pendingCount = 0;
|
||||
|
||||
// Track queue state changes for logging
|
||||
function updateCounts(): void {
|
||||
function _updateCounts(): void {
|
||||
// p-limit exposes queueSize and activeCount via constructor internals,
|
||||
// but we track via our wrapper to avoid depending on internals.
|
||||
}
|
||||
|
||||
export async function withLlmConcurrency<T>(fn: () => Promise<T>): Promise<T> {
|
||||
const queuedAt = activeCount + pendingCount;
|
||||
const _queuedAt = activeCount + pendingCount;
|
||||
pendingCount++;
|
||||
logger.debug(
|
||||
{ activeCount, pendingCount, maxConcurrent: config.AI_LLM_MAX_CONCURRENT },
|
||||
|
||||
@@ -2,10 +2,7 @@ import { createChildLogger } from "@bete/shared/logger";
|
||||
import { and, desc, eq, sql } from "drizzle-orm";
|
||||
import { config } from "../../shared/config/config.js";
|
||||
import { getDatabase } from "../../shared/database/drizzle.js";
|
||||
import {
|
||||
channelCulturesTable,
|
||||
messagesTable,
|
||||
} from "../../shared/database/schema.js";
|
||||
import { messagesTable } from "../../shared/database/schema.js";
|
||||
import { updateChannelCulture } from "./channelCultureStore.js";
|
||||
import { llmChat } from "./llmClient.js";
|
||||
|
||||
|
||||
@@ -550,7 +550,7 @@ async function downloadMediaCandidate(
|
||||
imageMap: Map<string, MessageImagePart[]>,
|
||||
mediaAnalysisMap: Map<string, string[]>,
|
||||
): Promise<void> {
|
||||
const log = createChildLogger("mediaAnalysis");
|
||||
const _log = createChildLogger("mediaAnalysis");
|
||||
if ((imageMap.get(targetId)?.length ?? 0) >= 8) return;
|
||||
|
||||
if (candidate.customEmojiId || candidate.stickerName) {
|
||||
@@ -646,7 +646,7 @@ export async function prepareMediaMessage(
|
||||
target: MessageRecord,
|
||||
allAttachments: AttachmentRecord[] | undefined,
|
||||
): Promise<PreparedMediaMessage> {
|
||||
const log = createChildLogger("mediaAnalysis");
|
||||
const _log = createChildLogger("mediaAnalysis");
|
||||
const targetId = target.id;
|
||||
const imageMap = new Map<string, MessageImagePart[]>();
|
||||
const webTextMap = new Map<string, string[]>();
|
||||
|
||||
@@ -10,7 +10,6 @@ import { delay, retryWithBackoff } from "@bete/shared/utils";
|
||||
import type { ChatCompletion } from "openai/resources/chat/completions";
|
||||
import { config } from "../../shared/config/config.js";
|
||||
import { extractMessageMediaEvidence } from "../message-capture/messageMetadata.js";
|
||||
import { getMessageById } from "../message-capture/messageStore.js";
|
||||
import type {
|
||||
AnalysisResult,
|
||||
AttachmentRecord,
|
||||
@@ -18,15 +17,7 @@ import type {
|
||||
} from "../message-capture/types.js";
|
||||
import { getChannelCulture } from "./channelCultureStore.js";
|
||||
import { llmChat } from "./llmClient.js";
|
||||
import type {
|
||||
MessageImagePart,
|
||||
PreparedMediaMessage,
|
||||
} from "./mediaAnalysisClient.js";
|
||||
import {
|
||||
analyzeSingleMediaImage,
|
||||
hasMediaContent,
|
||||
prepareMediaMessage,
|
||||
} from "./mediaAnalysisClient.js";
|
||||
import { hasMediaContent, prepareMediaMessage } from "./mediaAnalysisClient.js";
|
||||
import {
|
||||
buildReferenceXml,
|
||||
escapeXml,
|
||||
@@ -816,7 +807,7 @@ export async function runSimpleTextFallback(
|
||||
const MAX_CONTENT_CHARS = 500;
|
||||
const truncatedContent =
|
||||
content.length > MAX_CONTENT_CHARS
|
||||
? content.slice(0, MAX_CONTENT_CHARS) + "..."
|
||||
? `${content.slice(0, MAX_CONTENT_CHARS)}...`
|
||||
: content;
|
||||
|
||||
let userProfileCtx = "";
|
||||
@@ -876,7 +867,7 @@ Jawab HANYA dengan satu kata: clean, warn, atau flagged`;
|
||||
let category = "";
|
||||
|
||||
if (status === "clean") {
|
||||
analysis = `${message.username ?? "user"}: ${content.length > 200 ? content.slice(0, 200) + "..." : content}. Percakapan normal, tidak ada pelanggaran.`;
|
||||
analysis = `${message.username ?? "user"}: ${content.length > 200 ? `${content.slice(0, 200)}...` : content}. Percakapan normal, tidak ada pelanggaran.`;
|
||||
} else {
|
||||
category = status === "flagged" ? "harassment" : "spam";
|
||||
const categoryOptions =
|
||||
@@ -958,7 +949,7 @@ Kategori: spam`;
|
||||
policyVersion: "default-simple-2026-06",
|
||||
evidence:
|
||||
status !== "clean"
|
||||
? [content.length > 120 ? content.slice(0, 120) + "..." : content]
|
||||
? [content.length > 120 ? `${content.slice(0, 120)}...` : content]
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -795,7 +795,7 @@ export function sanitizeAiContent(
|
||||
// 3. Cap length
|
||||
const capped =
|
||||
escaped.length > maxLen
|
||||
? escaped.slice(0, maxLen) + "…[truncated]"
|
||||
? `${escaped.slice(0, maxLen)}…[truncated]`
|
||||
: escaped;
|
||||
|
||||
// 4. Wrap in CDATA unless the caller opts out (e.g. plain-text contexts)
|
||||
|
||||
@@ -4,8 +4,6 @@ import { extractJson } from "./jsonExtractor.js";
|
||||
import { ModerationResponseSchema } from "./moderationSchemas.js";
|
||||
import {
|
||||
clampScore,
|
||||
DEFERRAL_ANALYSIS_PATTERN,
|
||||
DEFERRAL_EXCEPTION_PATTERN,
|
||||
deriveRecommendedAction,
|
||||
deriveSeverity,
|
||||
hasDeferralAnalysis,
|
||||
@@ -46,7 +44,7 @@ export function parseModerationResponse(
|
||||
let parsed: any;
|
||||
try {
|
||||
parsed = JSON.parse(content);
|
||||
} catch (e) {
|
||||
} catch (_e) {
|
||||
parsed = extractJson(content);
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ export function logModerationAnalysis(
|
||||
},
|
||||
parseErrors: string[] = [],
|
||||
): void {
|
||||
const response: ModerationAnalysisResponse = {
|
||||
const _response: ModerationAnalysisResponse = {
|
||||
messageIds,
|
||||
batchSize: messageIds.length,
|
||||
model,
|
||||
@@ -158,7 +158,7 @@ export function logCacheEvent(
|
||||
cacheKey: string,
|
||||
source: "text" | "media" | "sticker",
|
||||
): void {
|
||||
const event: CacheHitEvent = {
|
||||
const _event: CacheHitEvent = {
|
||||
type,
|
||||
cacheKey,
|
||||
source,
|
||||
@@ -260,7 +260,7 @@ export function logAnalysisSummary(
|
||||
duration_ms: durationMs,
|
||||
per_message_avg_ms: Math.round(durationMs / totalMessages),
|
||||
summary,
|
||||
success_rate: ((successCount / totalMessages) * 100).toFixed(1) + "%",
|
||||
success_rate: `${((successCount / totalMessages) * 100).toFixed(1)}%`,
|
||||
},
|
||||
`Analysis batch complete: ${successCount}/${totalMessages} successful in ${durationMs}ms`,
|
||||
);
|
||||
|
||||
@@ -86,7 +86,7 @@ export async function incrementTextCacheHit(text: string): Promise<void> {
|
||||
`UPDATE text_analysis_cache SET hit_count = hit_count + 1 WHERE text = $1`,
|
||||
[text],
|
||||
);
|
||||
} catch (error) {
|
||||
} catch (_error) {
|
||||
// Silent fail — this is just a counter, not critical
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { isIP } from "node:net";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { createAbortControllerWithTimeout } from "@bete/shared/utils";
|
||||
|
||||
const log = createChildLogger("urlFetcher");
|
||||
const _log = createChildLogger("urlFetcher");
|
||||
|
||||
export interface FetchedUrlContext {
|
||||
url: string;
|
||||
@@ -53,14 +53,14 @@ async function isSafeUrl(urlStr: string): Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
// If DNS fails, we can't fetch it anyway
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
} catch (_err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ function extractOgImage(html: string): string | null {
|
||||
const ogRegex =
|
||||
/<meta[^>]*(?:property|name)=["'](?:og:image|twitter:image)["'][^>]*content=["']([^"']+)["']/i;
|
||||
const match = html.match(ogRegex);
|
||||
if (match && match[1]) {
|
||||
if (match?.[1]) {
|
||||
// Unescape basic HTML entities
|
||||
return match[1].replace(/&/g, "&").replace(/"/g, '"');
|
||||
}
|
||||
@@ -79,7 +79,7 @@ function extractOgImage(html: string): string | null {
|
||||
const ogRegexRev =
|
||||
/<meta[^>]*content=["']([^"']+)["'][^>]*(?:property|name)=["'](?:og:image|twitter:image)["']/i;
|
||||
const matchRev = html.match(ogRegexRev);
|
||||
if (matchRev && matchRev[1]) {
|
||||
if (matchRev?.[1]) {
|
||||
return matchRev[1].replace(/&/g, "&").replace(/"/g, '"');
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ export async function fetchUrlSafely(
|
||||
// If it's HTML, try to find an og:image first (for Tenor/Giphy etc)
|
||||
if (contentType.startsWith("text/html")) {
|
||||
const ogImage = extractOgImage(text);
|
||||
if (ogImage && ogImage.startsWith("http")) {
|
||||
if (ogImage?.startsWith("http")) {
|
||||
// Fetch the og:image instead
|
||||
return fetchUrlSafely(ogImage, depth + 1);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,7 @@ import { createChildLogger } from "@bete/shared/logger";
|
||||
import { and, desc, eq, sql } from "drizzle-orm";
|
||||
import { config } from "../../shared/config/config.js";
|
||||
import { getDatabase } from "../../shared/database/drizzle.js";
|
||||
import {
|
||||
messagesTable,
|
||||
userProfilesTable,
|
||||
} from "../../shared/database/schema.js";
|
||||
import { messagesTable } from "../../shared/database/schema.js";
|
||||
import { llmChat } from "./llmClient.js";
|
||||
import { updateUserProfile } from "./userProfileStore.js";
|
||||
|
||||
@@ -48,7 +45,7 @@ async function learnUserProfile(
|
||||
for (const msg of recentMessages) {
|
||||
const ch = msg.channelId ?? "unknown";
|
||||
if (!channelGroups.has(ch)) channelGroups.set(ch, []);
|
||||
channelGroups.get(ch)!.push(msg);
|
||||
channelGroups.get(ch)?.push(msg);
|
||||
}
|
||||
|
||||
// Build messages text with channel context
|
||||
|
||||
@@ -9,10 +9,7 @@ import { createChildLogger } from "@bete/shared/logger";
|
||||
import type { Client } from "discord.js-selfbot-v13";
|
||||
import Redis from "ioredis";
|
||||
import { config } from "../../shared/config/config.js";
|
||||
import type {
|
||||
VoiceController,
|
||||
VoiceStatus,
|
||||
} from "../voice-recording/voiceController.js";
|
||||
import type { VoiceController } from "../voice-recording/voiceController.js";
|
||||
import { GuildHandler } from "./guild.handler.js";
|
||||
import {
|
||||
type CommandHandlerFn,
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
COMMAND_VOICE_DISCONNECT_GUILD,
|
||||
type CommandMessage,
|
||||
type CommandReply,
|
||||
} from "@bete/shared";
|
||||
import type { CommandMessage, CommandReply } from "@bete/shared";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import type { Client } from "discord.js-selfbot-v13";
|
||||
import { config } from "../../shared/config/config.js";
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import http from "node:http";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import type { AppConfig as GatewayConfig } from "../../shared/config/config.js";
|
||||
import { config } from "../../shared/config/config.js";
|
||||
|
||||
const logger = createChildLogger("gateway-metrics");
|
||||
@@ -82,7 +81,7 @@ function formatMetrics(): string {
|
||||
lines.push(`${fullName} ${metric.value}`);
|
||||
}
|
||||
|
||||
return lines.join("\n") + "\n";
|
||||
return `${lines.join("\n")}\n`;
|
||||
}
|
||||
|
||||
export function startMetricsServer(): void {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { decodeCursor, encodeCursor } from "@bete/shared";
|
||||
import { createChildLogger, type Logger } from "@bete/shared/logger";
|
||||
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
||||
import { getDatabase } from "../../shared/database/drizzle.js";
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
import { createChildLogger, type Logger } from "@bete/shared/logger";
|
||||
import {
|
||||
and,
|
||||
asc,
|
||||
desc,
|
||||
eq,
|
||||
inArray,
|
||||
isNull,
|
||||
or,
|
||||
type SQL,
|
||||
sql,
|
||||
} from "drizzle-orm";
|
||||
import { and, asc, desc, eq, inArray, isNull, or, sql } from "drizzle-orm";
|
||||
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
||||
import type * as schema from "../../shared/database/schema.js";
|
||||
import { messagesTable } from "../../shared/database/schema.js";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { decodeCursor, encodeCursor, pageResult } from "@bete/shared";
|
||||
import { decodeCursor, pageResult } from "@bete/shared";
|
||||
import { createChildLogger, type Logger } from "@bete/shared/logger";
|
||||
import { and, desc, eq, type SQL, sql } from "drizzle-orm";
|
||||
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { decodeCursor, encodeCursor, pageResult } from "@bete/shared";
|
||||
import { decodeCursor, pageResult } from "@bete/shared";
|
||||
import { createChildLogger, type Logger } from "@bete/shared/logger";
|
||||
import { and, desc, eq, type SQL, sql } from "drizzle-orm";
|
||||
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { decodeCursor, encodeCursor, pageResult } from "@bete/shared";
|
||||
import { decodeCursor, pageResult } from "@bete/shared";
|
||||
import { createChildLogger, type Logger } from "@bete/shared/logger";
|
||||
import { and, desc, eq, type SQL, sql } from "drizzle-orm";
|
||||
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import type fs from "node:fs";
|
||||
import type {
|
||||
AIRecommendedAction,
|
||||
AISeverity,
|
||||
AIStatus,
|
||||
AnalysisQueueStatus,
|
||||
AttachmentRecord,
|
||||
BroadcasterClient,
|
||||
MessageRecord,
|
||||
ModerationBroadcaster,
|
||||
RoleMetadata,
|
||||
UserMetadata,
|
||||
VoiceRecordingUploadData,
|
||||
} from "@bete/shared";
|
||||
|
||||
@@ -225,7 +225,7 @@ export function resolveMediaUrl(
|
||||
|
||||
// -- stderr (capture for diagnostics, capped at 4KB) ----------------------------------
|
||||
|
||||
const MAX_STDERR = 4096;
|
||||
const _MAX_STDERR = 4096;
|
||||
if (proc.stderr) {
|
||||
proc.stderr.on("data", (chunk: Buffer) => {
|
||||
stderrBuf += chunk.toString("utf8");
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { eq } from "drizzle-orm";
|
||||
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
|
||||
import { config } from "../../shared/config/config.js";
|
||||
import { getDatabase } from "../../shared/database/drizzle.js";
|
||||
import type * as schema from "../../shared/database/schema.js";
|
||||
import { muxerJobsTable } from "../../shared/database/schema.js";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Transform, type TransformCallback } from "node:stream";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
|
||||
const logger = createChildLogger("packet-filter");
|
||||
const _logger = createChildLogger("packet-filter");
|
||||
|
||||
/**
|
||||
* Transform stream to filter out audio packets that are too small.
|
||||
@@ -19,7 +19,7 @@ export class PacketFilter extends Transform {
|
||||
|
||||
_transform(
|
||||
chunk: Buffer,
|
||||
encoding: string,
|
||||
_encoding: string,
|
||||
callback: TransformCallback,
|
||||
): void {
|
||||
this.totalCount++;
|
||||
|
||||
@@ -16,7 +16,6 @@ import type { VoicePcmWsClient } from "../voice-pcm-ws/index.js";
|
||||
import {
|
||||
createRecordingSession,
|
||||
type RecordingSession,
|
||||
type SessionRecordingMetadata,
|
||||
} from "./recorder/sessionRecording.js";
|
||||
import { createSpeakingHandler } from "./recorder/speakingHandler.js";
|
||||
|
||||
@@ -141,7 +140,7 @@ export async function startRecording(
|
||||
activeSessions,
|
||||
recordingsDir,
|
||||
pcmSender: _pcmWsClient
|
||||
? (pcm, userId) => _pcmWsClient!.sendPcm(userId, pcm)
|
||||
? (pcm, userId) => _pcmWsClient?.sendPcm(userId, pcm)
|
||||
: undefined,
|
||||
});
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@ export class VoiceTransmitter {
|
||||
private readonly TRANSMIT_CHANNEL = BACKEND_VOICE_TRANSMIT;
|
||||
/** Queue for PCM chunks when backpressure is active */
|
||||
private backpressureQueue: Buffer[] = [];
|
||||
private draining = false;
|
||||
/** Serialise start/stop to prevent races between rapid toggle commands */
|
||||
private gate = Promise.resolve();
|
||||
/** Set true before sending SIGTERM so exit handler knows it's intentional */
|
||||
@@ -176,7 +175,7 @@ export class VoiceTransmitter {
|
||||
|
||||
logger.info("Voice transmitter started");
|
||||
} finally {
|
||||
release!();
|
||||
release?.();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +226,7 @@ export class VoiceTransmitter {
|
||||
discordPlayer.stop("browser-bridge");
|
||||
logger.info("Voice transmitter stopped");
|
||||
} finally {
|
||||
release!();
|
||||
release?.();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AppError } from "@bete/shared/errors";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { getVoiceConnection, type VoiceConnection } from "@discordjs/voice";
|
||||
import type { VoiceConnection } from "@discordjs/voice";
|
||||
import type { Client, Guild, VoiceChannel } from "discord.js-selfbot-v13";
|
||||
import { discordPlayer } from "./player.js";
|
||||
import { startRecording, stopRecording } from "./recorder.js";
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import type { AppConfig as GatewayConfig } from "../../shared/config/config.js";
|
||||
import { config } from "../../shared/config/config.js";
|
||||
|
||||
const logger = createChildLogger("webhook-notifier");
|
||||
|
||||
Reference in New Issue
Block a user