Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .cursor/mcp.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"mcpServers": {
"supabaselocal": {
"url": " http://127.0.0.1:55321/mcp"
}
"mcpServers": {
"supabaselocal": {
"url": " http://127.0.0.1:55321/mcp"
}
}
}
}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ yarn-error.log*

# env files (can opt-in for committing if needed)
.env*
notenv

# vercel
.vercel
Expand Down
357 changes: 207 additions & 150 deletions app/(dashboard)/ai-chat/[id]/chat-client.tsx

Large diffs are not rendered by default.

9 changes: 6 additions & 3 deletions app/(dashboard)/ai-chat/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { loadChat } from "@/lib/chat-store";
import ChatClient from "./chat-client";

export default async function ChatPage({ params }: { params: Promise<{ id: string }> }) {
export default async function ChatPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const messages = await loadChat(id);

return <ChatClient key={id} id={id} initialMessages={messages} />;
}

13 changes: 8 additions & 5 deletions app/(dashboard)/ai-chat/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useEffect, useRef } from "react";
import { useRouter } from "next/navigation";
import { useEffect, useRef } from "react";
import { Spinner } from "@/components/ui/shadcn-io/spinner";

export default function NewChatPage() {
Expand All @@ -17,15 +17,15 @@ export default function NewChatPage() {
const controller = new AbortController();

// Create a new chat and redirect to it
fetch("/api/chat/new", {
fetch("/api/chat/new", {
method: "POST",
signal: controller.signal
signal: controller.signal,
})
.then((r) => r.json())
.then(({ id }) => router.push(`/ai-chat/${id}`))
.catch((error) => {
// Ignore AbortError - this is expected when component unmounts
if (error.name === 'AbortError') {
if (error.name === "AbortError") {
return;
}
console.error("Error creating new chat:", error);
Expand All @@ -40,7 +40,10 @@ export default function NewChatPage() {

return (
<div className="flex flex-col min-h-screen bg-background">
<div className="flex items-center justify-center" style={{ height: "70vh" }}>
<div
className="flex items-center justify-center"
style={{ height: "70vh" }}
>
<div className="text-center space-y-4">
<Spinner variant="ring" size={48} className="mx-auto" />
<p className="text-muted-foreground">Creating new chat...</p>
Expand Down
106 changes: 65 additions & 41 deletions app/(dashboard)/database/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
"use client"
"use client";

import { useState } from "react"
import { SiteHeader } from "@/components/site-header"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { RefreshCw, Database, AlertTriangle } from "lucide-react"
import { AlertTriangle, Database, RefreshCw } from "lucide-react";
import { useState } from "react";
import { SiteHeader } from "@/components/site-header";
import {
AlertDialog,
AlertDialogAction,
Expand All @@ -16,36 +13,48 @@ import {
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
} from "@/components/ui/alert-dialog";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";

export default function DatabasePage() {
const [syncing, setSyncing] = useState(false)
const [syncResult, setSyncResult] = useState<{ events: number; analysis: number } | null>(null)
const [syncing, setSyncing] = useState(false);
const [syncResult, setSyncResult] = useState<{
events: number;
analysis: number;
} | null>(null);

const handleReindex = async () => {
setSyncing(true)
setSyncResult(null)
setSyncing(true);
setSyncResult(null);

try {
const response = await fetch('/api/search/sync?type=all', {
method: 'POST'
})
const data = await response.json()
const response = await fetch("/api/search/sync?type=all", {
method: "POST",
});

const data = await response.json();

if (response.ok) {
setSyncResult(data.counts)
setSyncResult(data.counts);
} else {
console.error('Reindex error:', data.error)
alert(`Reindex failed: ${data.error}`)
console.error("Reindex error:", data.error);
alert(`Reindex failed: ${data.error}`);
}
} catch (error) {
console.error('Reindex failed:', error)
alert('Reindex failed. Check console for details.')
console.error("Reindex failed:", error);
alert("Reindex failed. Check console for details.");
} finally {
setSyncing(false)
setSyncing(false);
}
}
};

return (
<div className="flex flex-1 flex-col">
Expand All @@ -54,7 +63,9 @@ export default function DatabasePage() {
<Card>
<CardHeader>
<CardTitle>Database</CardTitle>
<CardDescription>Explore Events, Entities, and more.</CardDescription>
<CardDescription>
Explore Events, Entities, and more.
</CardDescription>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">Database UI coming soon.</p>
Expand All @@ -74,7 +85,9 @@ export default function DatabasePage() {
Dev/Admin Only
</Badge>
</CardTitle>
<CardDescription>Administrative operations for managing search indices</CardDescription>
<CardDescription>
Administrative operations for managing search indices
</CardDescription>
</div>
</div>
</CardHeader>
Expand All @@ -85,31 +98,38 @@ export default function DatabasePage() {
<div className="flex-1">
<h3 className="font-semibold mb-1">Reindex Search Data</h3>
<p className="text-sm text-muted-foreground mb-3">
Clear and rebuild the Elasticsearch index with all events and analysis results from the database.
This will sync all existing data to make it searchable.
Clear and rebuild the Elasticsearch index with all events
and analysis results from the database. This will sync all
existing data to make it searchable.
</p>
</div>
</div>

<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="outline"
<Button
variant="outline"
disabled={syncing}
className="w-fit"
>
<RefreshCw className={`h-4 w-4 mr-2 ${syncing ? 'animate-spin' : ''}`} />
{syncing ? 'Reindexing...' : 'Reindex All Search Data'}
<RefreshCw
className={`h-4 w-4 mr-2 ${syncing ? "animate-spin" : ""}`}
/>
{syncing ? "Reindexing..." : "Reindex All Search Data"}
</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Reindex Search Data?</AlertDialogTitle>
<AlertDialogDescription>
This will reindex all events and analysis results to Elasticsearch.
This operation may take a few moments depending on the amount of data.
<br /><br />
<strong>This is safe to run and won't delete any database data.</strong>
This will reindex all events and analysis results to
Elasticsearch. This operation may take a few moments
depending on the amount of data.
<br />
<br />
<strong>
This is safe to run and won't delete any database data.
</strong>
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
Expand All @@ -127,8 +147,12 @@ export default function DatabasePage() {
✓ Reindex completed successfully
</p>
<div className="mt-2 flex gap-4 text-xs text-muted-foreground">
<span>Events: <strong>{syncResult.events}</strong></span>
<span>Analysis: <strong>{syncResult.analysis}</strong></span>
<span>
Events: <strong>{syncResult.events}</strong>
</span>
<span>
Analysis: <strong>{syncResult.analysis}</strong>
</span>
</div>
</div>
)}
Expand All @@ -137,5 +161,5 @@ export default function DatabasePage() {
</Card>
</div>
</div>
)
);
}
54 changes: 33 additions & 21 deletions app/(dashboard)/debug/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
"use client";

import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { toast } from "sonner";
import { useRouter } from "next/navigation";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";

export default function DebugPage() {
const router = useRouter();

const showRegularToast = () => {
toast("Regular Toast", {
description: "This is a regular Sonner toast notification with JetBrains Mono font.",
description:
"This is a regular Sonner toast notification with JetBrains Mono font.",
});
};

Expand Down Expand Up @@ -54,16 +61,16 @@ export default function DebugPage() {
<div className="font-semibold">Shoplifting Incident</div>
</div>
<div className="text-sm text-muted-foreground line-clamp-2">
A person wearing a blue jacket is seen taking a product from the shelf and concealing it without paying.
A person wearing a blue jacket is seen taking a product from the shelf
and concealing it without paying.
</div>
<div className="flex items-center justify-between mt-1">
<span className="text-xs text-muted-foreground">
0:05
</span>
<span className="text-xs text-muted-foreground">0:05</span>
<button
onClick={() => {
toast.info("View Video clicked!", {
description: "This would navigate to the video at the specified timestamp.",
description:
"This would navigate to the video at the specified timestamp.",
});
}}
className="inline-flex items-center px-3 py-1 rounded-md text-xs font-medium bg-primary text-primary-foreground hover:bg-primary/90 transition-colors"
Expand All @@ -74,7 +81,7 @@ export default function DebugPage() {
</div>,
{
duration: 8000,
}
},
);
};

Expand All @@ -91,13 +98,12 @@ export default function DebugPage() {
Individual loitering near high-value merchandise for extended period.
</div>
<div className="flex items-center justify-between mt-1">
<span className="text-xs text-muted-foreground">
2:34
</span>
<span className="text-xs text-muted-foreground">2:34</span>
<button
onClick={() => {
toast.info("View Video clicked!", {
description: "This would navigate to the video at the specified timestamp.",
description:
"This would navigate to the video at the specified timestamp.",
});
}}
className="inline-flex items-center px-3 py-1 rounded-md text-xs font-medium bg-primary text-primary-foreground hover:bg-primary/90 transition-colors"
Expand All @@ -108,7 +114,7 @@ export default function DebugPage() {
</div>,
{
duration: 8000,
}
},
);
};

Expand All @@ -127,7 +133,8 @@ export default function DebugPage() {
<CardHeader>
<CardTitle>Sonner Toast Notifications</CardTitle>
<CardDescription>
Test different types of toast notifications with JetBrains Mono font
Test different types of toast notifications with JetBrains Mono
font
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
Expand Down Expand Up @@ -156,18 +163,24 @@ export default function DebugPage() {
</div>

<div className="border-t pt-4">
<h3 className="text-sm font-semibold mb-3">Critical Event Toasts</h3>
<h3 className="text-sm font-semibold mb-3">
Critical Event Toasts
</h3>
<div className="flex flex-wrap gap-2">
<Button onClick={showCriticalEventToast} variant="destructive">
Critical Event (High)
</Button>
<Button onClick={showCriticalEventMediumSeverity} variant="default">
<Button
onClick={showCriticalEventMediumSeverity}
variant="default"
>
Critical Event (Medium)
</Button>
</div>
<p className="text-xs text-muted-foreground mt-2">
These toasts simulate the critical event notifications with badge on the left of title
and "View Video" button in the bottom right.
These toasts simulate the critical event notifications with
badge on the left of title and "View Video" button in the bottom
right.
</p>
</div>
</CardContent>
Expand All @@ -191,4 +204,3 @@ export default function DebugPage() {
</div>
);
}

Loading
Loading