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