refactor: clean up code formatting and improve consistency across multiple components
Deploy to VPS / deploy (push) Failing after 1m59s
Deploy to VPS / deploy (push) Failing after 1m59s
This commit is contained in:
+2
-2
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.5.5/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/2.2.0/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
@@ -30,7 +30,6 @@
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"preset": "recommended",
|
||||
"style": {
|
||||
"noNonNullAssertion": "warn",
|
||||
"useNodejsImportProtocol": "warn"
|
||||
@@ -41,6 +40,7 @@
|
||||
"noArrayIndexKey": "warn"
|
||||
},
|
||||
"a11y": {
|
||||
"useSemanticElements": "off",
|
||||
"useButtonType": "off",
|
||||
"noAutofocus": "off"
|
||||
},
|
||||
|
||||
@@ -111,10 +111,8 @@ export default function AnalysisPage() {
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"text-[10px] px-1.5 py-0 h-4",
|
||||
msg.ai_status === "clean" &&
|
||||
"text-green-500",
|
||||
msg.ai_status === "flagged" &&
|
||||
"text-red-500",
|
||||
msg.ai_status === "clean" && "text-green-500",
|
||||
msg.ai_status === "flagged" && "text-red-500",
|
||||
)}
|
||||
>
|
||||
{msg.ai_status}
|
||||
@@ -127,17 +125,17 @@ export default function AnalysisPage() {
|
||||
{msg.ai_moderation_flags &&
|
||||
msg.ai_moderation_flags !== "[]" && (
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{safeParseJsonArray(
|
||||
msg.ai_moderation_flags,
|
||||
).map((flag) => (
|
||||
<Badge
|
||||
key={flag}
|
||||
variant="destructive"
|
||||
className="text-[10px] px-1.5 py-0 h-4"
|
||||
>
|
||||
{flag}
|
||||
</Badge>
|
||||
))}
|
||||
{safeParseJsonArray(msg.ai_moderation_flags).map(
|
||||
(flag) => (
|
||||
<Badge
|
||||
key={flag}
|
||||
variant="destructive"
|
||||
className="text-[10px] px-1.5 py-0 h-4"
|
||||
>
|
||||
{flag}
|
||||
</Badge>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { GuildSelector } from "@/components/shared/guild-selector";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
@@ -23,7 +23,6 @@ import { Input } from "@/components/ui/input";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { GuildSelector } from "@/components/shared/guild-selector";
|
||||
import { dashboardApi } from "@/lib/api";
|
||||
import { formatNumber } from "@/lib/format";
|
||||
import type {
|
||||
@@ -189,11 +188,7 @@ function StatsView() {
|
||||
value={stats.total_messages}
|
||||
icon={Hash}
|
||||
/>
|
||||
<StatCard
|
||||
label="Today"
|
||||
value={stats.today_messages}
|
||||
icon={Clock}
|
||||
/>
|
||||
<StatCard label="Today" value={stats.today_messages} icon={Clock} />
|
||||
<StatCard label="Users" value={stats.total_users} icon={Users} />
|
||||
<StatCard
|
||||
label="Active 24h"
|
||||
@@ -239,19 +234,15 @@ function StatsView() {
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{stats.top_channels.map((ch, i) => {
|
||||
{stats.top_channels.map((ch, _i) => {
|
||||
const maxCount = stats.top_channels[0].message_count;
|
||||
const pct =
|
||||
maxCount > 0
|
||||
? (ch.message_count / maxCount) * 100
|
||||
: 0;
|
||||
maxCount > 0 ? (ch.message_count / maxCount) * 100 : 0;
|
||||
return (
|
||||
<div key={ch.channel_id} className="space-y-1">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="truncate font-medium">
|
||||
#
|
||||
{ch.channel_name ??
|
||||
ch.channel_id.slice(0, 8)}
|
||||
#{ch.channel_name ?? ch.channel_id.slice(0, 8)}
|
||||
</span>
|
||||
<span className="text-muted-foreground tabular-nums">
|
||||
{formatNumber(ch.message_count)}
|
||||
@@ -279,9 +270,7 @@ function StatsView() {
|
||||
<div className="text-2xl font-bold tabular-nums">
|
||||
{stats.moderation_overview.pending}
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
Pending
|
||||
</div>
|
||||
<div className="text-xs text-muted-foreground">Pending</div>
|
||||
</div>
|
||||
<div className="rounded-lg bg-yellow-500/10 p-3 text-center space-y-1.5">
|
||||
<div className="text-2xl font-bold tabular-nums text-yellow-500">
|
||||
|
||||
@@ -5,8 +5,8 @@ import { Suspense } from "react";
|
||||
import { Header } from "@/components/layout/header";
|
||||
import { MobileTabBar } from "@/components/layout/mobile-tab-bar";
|
||||
import { Sidebar } from "@/components/layout/sidebar";
|
||||
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
|
||||
import { MascotChatbot } from "@/components/mascot/mascot-chatbot";
|
||||
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
|
||||
import { WsProvider } from "@/lib/ws/context";
|
||||
|
||||
function LoadingFallback() {
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Disc3,
|
||||
Music,
|
||||
Play,
|
||||
SkipForward,
|
||||
Square,
|
||||
Volume2,
|
||||
} from "lucide-react";
|
||||
import { Disc3, Music, Play, SkipForward, Square, Volume2 } from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { GuildSelector } from "@/components/shared/guild-selector";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -35,7 +35,6 @@ import {
|
||||
} from "@/components/ui/select";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { GuildSelector } from "@/components/shared/guild-selector";
|
||||
import { messagesApi, voiceApi } from "@/lib/api";
|
||||
import { formatBytes, safeParseJsonArray } from "@/lib/format";
|
||||
import type { AttachmentRecord, Channel, MessageRecord } from "@/lib/types";
|
||||
@@ -244,9 +243,7 @@ export default function MessagesPage() {
|
||||
<GuildSelector value={guildId} onChange={setGuildId} />
|
||||
<div className="flex flex-col items-center justify-center py-20 text-center">
|
||||
<AlertCircle className="size-10 text-destructive mb-3" />
|
||||
<p className="text-sm text-muted-foreground mb-4 max-w-sm">
|
||||
{error}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground mb-4 max-w-sm">{error}</p>
|
||||
<Button variant="outline" onClick={fetchMessages}>
|
||||
<RefreshCw className="size-4 mr-2" />
|
||||
Retry
|
||||
@@ -490,9 +487,7 @@ export default function MessagesPage() {
|
||||
{detailMessage.username}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{new Date(
|
||||
detailMessage.created_at,
|
||||
).toLocaleString()}
|
||||
{new Date(detailMessage.created_at).toLocaleString()}
|
||||
</span>
|
||||
{detailMessage.type === "deleted" && (
|
||||
<Badge variant="destructive" className="text-[10px]">
|
||||
@@ -787,18 +782,14 @@ function MessageCard({
|
||||
</p>
|
||||
)}
|
||||
|
||||
{msg.ai_confidence !== undefined &&
|
||||
msg.ai_confidence !== null && (
|
||||
<div className="flex items-center gap-2 max-w-40">
|
||||
<Progress
|
||||
value={msg.ai_confidence * 100}
|
||||
className="h-1.5"
|
||||
/>
|
||||
<span className="text-[11px] text-muted-foreground tabular-nums shrink-0">
|
||||
{(msg.ai_confidence * 100).toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{msg.ai_confidence !== undefined && msg.ai_confidence !== null && (
|
||||
<div className="flex items-center gap-2 max-w-40">
|
||||
<Progress value={msg.ai_confidence * 100} className="h-1.5" />
|
||||
<span className="text-[11px] text-muted-foreground tabular-nums shrink-0">
|
||||
{(msg.ai_confidence * 100).toFixed(0)}%
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex gap-1.5 pt-0.5">
|
||||
<Button
|
||||
|
||||
@@ -91,9 +91,7 @@ export default function RecordingsPage() {
|
||||
{rec.username}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{rec.channel_name ??
|
||||
rec.channel_id ??
|
||||
"Unknown channel"}
|
||||
{rec.channel_name ?? rec.channel_id ?? "Unknown channel"}
|
||||
{" — "}
|
||||
{new Date(rec.created_at).toLocaleString()}
|
||||
</p>
|
||||
@@ -108,9 +106,7 @@ export default function RecordingsPage() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() =>
|
||||
window.open(rec.download_url!, "_blank")
|
||||
}
|
||||
onClick={() => window.open(rec.download_url, "_blank")}
|
||||
>
|
||||
<Download className="size-4" />
|
||||
</Button>
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Moon,
|
||||
Server,
|
||||
Shield,
|
||||
Sun,
|
||||
Wifi,
|
||||
} from "lucide-react";
|
||||
import { Moon, Server, Shield, Sun, Wifi } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
@@ -85,9 +79,7 @@ export default function SettingsPage() {
|
||||
variant={statusConfig.variant}
|
||||
className="gap-1.5 px-2.5 py-1"
|
||||
>
|
||||
<span
|
||||
className={cn("size-1.5 rounded-full", statusConfig.dot)}
|
||||
/>
|
||||
<span className={cn("size-1.5 rounded-full", statusConfig.dot)} />
|
||||
{statusConfig.label}
|
||||
</Badge>
|
||||
</div>
|
||||
@@ -159,9 +151,7 @@ export default function SettingsPage() {
|
||||
<Separator />
|
||||
<ConfigRow
|
||||
label="Auto-Delete Flagged"
|
||||
value={
|
||||
config.autoDeleteFlaggedEnabled ? "Enabled" : "Disabled"
|
||||
}
|
||||
value={config.autoDeleteFlaggedEnabled ? "Enabled" : "Disabled"}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
@@ -197,13 +187,7 @@ export default function SettingsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
function ConfigRow({
|
||||
label,
|
||||
value,
|
||||
}: {
|
||||
label: string;
|
||||
value: string;
|
||||
}) {
|
||||
function ConfigRow({ label, value }: { label: string; value: string }) {
|
||||
return (
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<span className="text-muted-foreground">{label}</span>
|
||||
|
||||
@@ -31,9 +31,7 @@ export default function VoicePage() {
|
||||
|
||||
const [voiceStatus, setVoiceStatus] = useState<VoiceStatus | null>(null);
|
||||
const [speakers, setSpeakers] = useState<ActiveSpeaker[]>([]);
|
||||
const [guilds, setGuilds] = useState<
|
||||
Array<{ id: string; name: string }>
|
||||
>([]);
|
||||
const [guilds, setGuilds] = useState<Array<{ id: string; name: string }>>([]);
|
||||
const [voiceChannels, setVoiceChannels] = useState<
|
||||
Array<{ id: string; name: string }>
|
||||
>([]);
|
||||
@@ -74,9 +72,7 @@ export default function VoicePage() {
|
||||
const unsubSpeaker = ws.on("voice_active_user", (user) => {
|
||||
const speaker = user as ActiveSpeaker;
|
||||
setSpeakers((prev) => {
|
||||
const existing = prev.findIndex(
|
||||
(s) => s.userId === speaker.userId,
|
||||
);
|
||||
const existing = prev.findIndex((s) => s.userId === speaker.userId);
|
||||
if (existing >= 0) {
|
||||
const next = [...prev];
|
||||
next[existing] = speaker;
|
||||
@@ -224,9 +220,7 @@ export default function VoicePage() {
|
||||
) : (
|
||||
<Button
|
||||
onClick={handleConnect}
|
||||
disabled={
|
||||
voiceLoading || !selectedGuild || !selectedChannel
|
||||
}
|
||||
disabled={voiceLoading || !selectedGuild || !selectedChannel}
|
||||
>
|
||||
{voiceLoading ? (
|
||||
<Loader2 className="size-4 animate-spin mr-1.5" />
|
||||
@@ -286,9 +280,7 @@ export default function VoicePage() {
|
||||
setMicActive(checked);
|
||||
try {
|
||||
await voiceApi.sendCommand(
|
||||
checked
|
||||
? "voice:transmit:start"
|
||||
: "voice:transmit:stop",
|
||||
checked ? "voice:transmit:start" : "voice:transmit:stop",
|
||||
);
|
||||
} catch {
|
||||
setMicActive(!checked);
|
||||
|
||||
@@ -45,9 +45,7 @@ export function GuildSelector({
|
||||
.getGuilds()
|
||||
.then(setGuilds)
|
||||
.catch((err) =>
|
||||
setError(
|
||||
err instanceof Error ? err.message : "Failed to load guilds",
|
||||
),
|
||||
setError(err instanceof Error ? err.message : "Failed to load guilds"),
|
||||
)
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
@@ -17,9 +17,7 @@ export function formatBytes(bytes: number): string {
|
||||
/**
|
||||
* Safely parse a JSON string into an array.
|
||||
*/
|
||||
export function safeParseJsonArray(
|
||||
value: string | null | undefined,
|
||||
): string[] {
|
||||
export function safeParseJsonArray(value: string | null | undefined): string[] {
|
||||
if (!value) return [];
|
||||
try {
|
||||
const parsed = JSON.parse(value);
|
||||
|
||||
@@ -2,11 +2,11 @@ import {
|
||||
BarChart3,
|
||||
Headphones,
|
||||
LayoutDashboard,
|
||||
type LucideIcon,
|
||||
MessageSquare,
|
||||
Mic,
|
||||
Music,
|
||||
Search,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
export interface NavItem {
|
||||
|
||||
Reference in New Issue
Block a user