mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-25 19:15:18 +02:00
Initial formatting using biome
This commit is contained in:
parent
4c8ff48155
commit
758603b275
156 changed files with 23825 additions and 29508 deletions
|
|
@ -1,267 +1,290 @@
|
|||
'use client';
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { AppSidebar } from '@/components/sidebar/app-sidebar';
|
||||
import { useEffect, useState } from "react";
|
||||
import { AppSidebar } from "@/components/sidebar/app-sidebar";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { apiClient } from '@/lib/api'; // Import the API client
|
||||
import { apiClient } from "@/lib/api"; // Import the API client
|
||||
|
||||
interface Chat {
|
||||
created_at: string;
|
||||
id: number;
|
||||
type: string;
|
||||
title: string;
|
||||
messages: string[];
|
||||
search_space_id: number;
|
||||
created_at: string;
|
||||
id: number;
|
||||
type: string;
|
||||
title: string;
|
||||
messages: string[];
|
||||
search_space_id: number;
|
||||
}
|
||||
|
||||
interface SearchSpace {
|
||||
created_at: string;
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
user_id: string;
|
||||
created_at: string;
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
user_id: string;
|
||||
}
|
||||
|
||||
interface AppSidebarProviderProps {
|
||||
searchSpaceId: string;
|
||||
navSecondary: {
|
||||
title: string;
|
||||
url: string;
|
||||
icon: string;
|
||||
}[];
|
||||
navMain: {
|
||||
title: string;
|
||||
url: string;
|
||||
icon: string;
|
||||
isActive?: boolean;
|
||||
items?: {
|
||||
title: string;
|
||||
url: string;
|
||||
}[];
|
||||
}[];
|
||||
searchSpaceId: string;
|
||||
navSecondary: {
|
||||
title: string;
|
||||
url: string;
|
||||
icon: string;
|
||||
}[];
|
||||
navMain: {
|
||||
title: string;
|
||||
url: string;
|
||||
icon: string;
|
||||
isActive?: boolean;
|
||||
items?: {
|
||||
title: string;
|
||||
url: string;
|
||||
}[];
|
||||
}[];
|
||||
}
|
||||
|
||||
export function AppSidebarProvider({
|
||||
searchSpaceId,
|
||||
navSecondary,
|
||||
navMain
|
||||
searchSpaceId,
|
||||
navSecondary,
|
||||
navMain,
|
||||
}: AppSidebarProviderProps) {
|
||||
const [recentChats, setRecentChats] = useState<{ name: string; url: string; icon: string; id: number; search_space_id: number; actions: { name: string; icon: string; onClick: () => void }[] }[]>([]);
|
||||
const [searchSpace, setSearchSpace] = useState<SearchSpace | null>(null);
|
||||
const [isLoadingChats, setIsLoadingChats] = useState(true);
|
||||
const [isLoadingSearchSpace, setIsLoadingSearchSpace] = useState(true);
|
||||
const [chatError, setChatError] = useState<string | null>(null);
|
||||
const [searchSpaceError, setSearchSpaceError] = useState<string | null>(null);
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const [chatToDelete, setChatToDelete] = useState<{ id: number, name: string } | null>(null);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
const [recentChats, setRecentChats] = useState<
|
||||
{
|
||||
name: string;
|
||||
url: string;
|
||||
icon: string;
|
||||
id: number;
|
||||
search_space_id: number;
|
||||
actions: { name: string; icon: string; onClick: () => void }[];
|
||||
}[]
|
||||
>([]);
|
||||
const [searchSpace, setSearchSpace] = useState<SearchSpace | null>(null);
|
||||
const [isLoadingChats, setIsLoadingChats] = useState(true);
|
||||
const [isLoadingSearchSpace, setIsLoadingSearchSpace] = useState(true);
|
||||
const [chatError, setChatError] = useState<string | null>(null);
|
||||
const [searchSpaceError, setSearchSpaceError] = useState<string | null>(null);
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
const [chatToDelete, setChatToDelete] = useState<{ id: number; name: string } | null>(null);
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [isClient, setIsClient] = useState(false);
|
||||
|
||||
// Set isClient to true when component mounts on the client
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
// Set isClient to true when component mounts on the client
|
||||
useEffect(() => {
|
||||
setIsClient(true);
|
||||
}, []);
|
||||
|
||||
// Fetch recent chats
|
||||
useEffect(() => {
|
||||
const fetchRecentChats = async () => {
|
||||
try {
|
||||
// Only run on client-side
|
||||
if (typeof window === 'undefined') return;
|
||||
// Fetch recent chats
|
||||
useEffect(() => {
|
||||
const fetchRecentChats = async () => {
|
||||
try {
|
||||
// Only run on client-side
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
try {
|
||||
// Use the API client instead of direct fetch - filter by current search space ID
|
||||
const chats: Chat[] = await apiClient.get<Chat[]>(`api/v1/chats/?limit=5&skip=0&search_space_id=${searchSpaceId}`);
|
||||
try {
|
||||
// Use the API client instead of direct fetch - filter by current search space ID
|
||||
const chats: Chat[] = await apiClient.get<Chat[]>(
|
||||
`api/v1/chats/?limit=5&skip=0&search_space_id=${searchSpaceId}`
|
||||
);
|
||||
|
||||
// Sort chats by created_at in descending order (newest first)
|
||||
const sortedChats = chats.sort((a, b) =>
|
||||
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
||||
);
|
||||
// console.log("sortedChats", sortedChats);
|
||||
// Transform API response to the format expected by AppSidebar
|
||||
const formattedChats = sortedChats.map(chat => ({
|
||||
name: chat.title || `Chat ${chat.id}`, // Fallback if title is empty
|
||||
url: `/dashboard/${chat.search_space_id}/researcher/${chat.id}`,
|
||||
icon: 'MessageCircleMore',
|
||||
id: chat.id,
|
||||
search_space_id: chat.search_space_id,
|
||||
actions: [
|
||||
{
|
||||
name: 'View Details',
|
||||
icon: 'ExternalLink',
|
||||
onClick: () => {
|
||||
window.location.href = `/dashboard/${chat.search_space_id}/researcher/${chat.id}`;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
icon: 'Trash2',
|
||||
onClick: () => {
|
||||
setChatToDelete({ id: chat.id, name: chat.title || `Chat ${chat.id}` });
|
||||
setShowDeleteDialog(true);
|
||||
}
|
||||
}
|
||||
]
|
||||
}));
|
||||
// Sort chats by created_at in descending order (newest first)
|
||||
const sortedChats = chats.sort(
|
||||
(a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
|
||||
);
|
||||
// console.log("sortedChats", sortedChats);
|
||||
// Transform API response to the format expected by AppSidebar
|
||||
const formattedChats = sortedChats.map((chat) => ({
|
||||
name: chat.title || `Chat ${chat.id}`, // Fallback if title is empty
|
||||
url: `/dashboard/${chat.search_space_id}/researcher/${chat.id}`,
|
||||
icon: "MessageCircleMore",
|
||||
id: chat.id,
|
||||
search_space_id: chat.search_space_id,
|
||||
actions: [
|
||||
{
|
||||
name: "View Details",
|
||||
icon: "ExternalLink",
|
||||
onClick: () => {
|
||||
window.location.href = `/dashboard/${chat.search_space_id}/researcher/${chat.id}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Delete",
|
||||
icon: "Trash2",
|
||||
onClick: () => {
|
||||
setChatToDelete({ id: chat.id, name: chat.title || `Chat ${chat.id}` });
|
||||
setShowDeleteDialog(true);
|
||||
},
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
setRecentChats(formattedChats);
|
||||
setChatError(null);
|
||||
} catch (error) {
|
||||
console.error('Error fetching chats:', error);
|
||||
setChatError(error instanceof Error ? error.message : 'Unknown error occurred');
|
||||
// Provide empty array to ensure UI still renders
|
||||
setRecentChats([]);
|
||||
} finally {
|
||||
setIsLoadingChats(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in fetchRecentChats:', error);
|
||||
setIsLoadingChats(false);
|
||||
}
|
||||
};
|
||||
setRecentChats(formattedChats);
|
||||
setChatError(null);
|
||||
} catch (error) {
|
||||
console.error("Error fetching chats:", error);
|
||||
setChatError(error instanceof Error ? error.message : "Unknown error occurred");
|
||||
// Provide empty array to ensure UI still renders
|
||||
setRecentChats([]);
|
||||
} finally {
|
||||
setIsLoadingChats(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error in fetchRecentChats:", error);
|
||||
setIsLoadingChats(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchRecentChats();
|
||||
fetchRecentChats();
|
||||
|
||||
// Set up a refresh interval (every 5 minutes)
|
||||
const intervalId = setInterval(fetchRecentChats, 5 * 60 * 1000);
|
||||
// Set up a refresh interval (every 5 minutes)
|
||||
const intervalId = setInterval(fetchRecentChats, 5 * 60 * 1000);
|
||||
|
||||
// Clean up interval on component unmount
|
||||
return () => clearInterval(intervalId);
|
||||
}, [searchSpaceId]);
|
||||
// Clean up interval on component unmount
|
||||
return () => clearInterval(intervalId);
|
||||
}, [searchSpaceId]);
|
||||
|
||||
// Handle delete chat
|
||||
const handleDeleteChat = async () => {
|
||||
if (!chatToDelete) return;
|
||||
// Handle delete chat
|
||||
const handleDeleteChat = async () => {
|
||||
if (!chatToDelete) return;
|
||||
|
||||
try {
|
||||
setIsDeleting(true);
|
||||
try {
|
||||
setIsDeleting(true);
|
||||
|
||||
// Use the API client instead of direct fetch
|
||||
await apiClient.delete(`api/v1/chats/${chatToDelete.id}`);
|
||||
// Use the API client instead of direct fetch
|
||||
await apiClient.delete(`api/v1/chats/${chatToDelete.id}`);
|
||||
|
||||
// Close dialog and refresh chats
|
||||
setRecentChats(recentChats.filter(chat => chat.id !== chatToDelete.id));
|
||||
// Close dialog and refresh chats
|
||||
setRecentChats(recentChats.filter((chat) => chat.id !== chatToDelete.id));
|
||||
} catch (error) {
|
||||
console.error("Error deleting chat:", error);
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
setShowDeleteDialog(false);
|
||||
setChatToDelete(null);
|
||||
}
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error deleting chat:', error);
|
||||
} finally {
|
||||
setIsDeleting(false);
|
||||
setShowDeleteDialog(false);
|
||||
setChatToDelete(null);
|
||||
}
|
||||
};
|
||||
// Fetch search space details
|
||||
useEffect(() => {
|
||||
const fetchSearchSpace = async () => {
|
||||
try {
|
||||
// Only run on client-side
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
// Fetch search space details
|
||||
useEffect(() => {
|
||||
const fetchSearchSpace = async () => {
|
||||
try {
|
||||
// Only run on client-side
|
||||
if (typeof window === 'undefined') return;
|
||||
try {
|
||||
// Use the API client instead of direct fetch
|
||||
const data: SearchSpace = await apiClient.get<SearchSpace>(
|
||||
`api/v1/searchspaces/${searchSpaceId}`
|
||||
);
|
||||
setSearchSpace(data);
|
||||
setSearchSpaceError(null);
|
||||
} catch (error) {
|
||||
console.error("Error fetching search space:", error);
|
||||
setSearchSpaceError(error instanceof Error ? error.message : "Unknown error occurred");
|
||||
} finally {
|
||||
setIsLoadingSearchSpace(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error in fetchSearchSpace:", error);
|
||||
setIsLoadingSearchSpace(false);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
// Use the API client instead of direct fetch
|
||||
const data: SearchSpace = await apiClient.get<SearchSpace>(`api/v1/searchspaces/${searchSpaceId}`);
|
||||
setSearchSpace(data);
|
||||
setSearchSpaceError(null);
|
||||
} catch (error) {
|
||||
console.error('Error fetching search space:', error);
|
||||
setSearchSpaceError(error instanceof Error ? error.message : 'Unknown error occurred');
|
||||
} finally {
|
||||
setIsLoadingSearchSpace(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in fetchSearchSpace:', error);
|
||||
setIsLoadingSearchSpace(false);
|
||||
}
|
||||
};
|
||||
fetchSearchSpace();
|
||||
}, [searchSpaceId]);
|
||||
|
||||
fetchSearchSpace();
|
||||
}, [searchSpaceId]);
|
||||
// Create a fallback chat if there's an error or no chats
|
||||
const fallbackChats =
|
||||
chatError || (!isLoadingChats && recentChats.length === 0)
|
||||
? [
|
||||
{
|
||||
name: chatError ? "Error loading chats" : "No recent chats",
|
||||
url: "#",
|
||||
icon: chatError ? "AlertCircle" : "MessageCircleMore",
|
||||
id: 0,
|
||||
search_space_id: Number(searchSpaceId),
|
||||
actions: [],
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
// Create a fallback chat if there's an error or no chats
|
||||
const fallbackChats = chatError || (!isLoadingChats && recentChats.length === 0)
|
||||
? [{
|
||||
name: chatError ? "Error loading chats" : "No recent chats",
|
||||
url: "#",
|
||||
icon: chatError ? "AlertCircle" : "MessageCircleMore",
|
||||
id: 0,
|
||||
search_space_id: Number(searchSpaceId),
|
||||
actions: []
|
||||
}]
|
||||
: [];
|
||||
// Use fallback chats if there's an error or no chats
|
||||
const displayChats = recentChats.length > 0 ? recentChats : fallbackChats;
|
||||
|
||||
// Use fallback chats if there's an error or no chats
|
||||
const displayChats = recentChats.length > 0 ? recentChats : fallbackChats;
|
||||
// Update the first item in navSecondary to show the search space name
|
||||
const updatedNavSecondary = [...navSecondary];
|
||||
if (updatedNavSecondary.length > 0 && isClient) {
|
||||
updatedNavSecondary[0] = {
|
||||
...updatedNavSecondary[0],
|
||||
title:
|
||||
searchSpace?.name ||
|
||||
(isLoadingSearchSpace
|
||||
? "Loading..."
|
||||
: searchSpaceError
|
||||
? "Error loading search space"
|
||||
: "Unknown Search Space"),
|
||||
};
|
||||
}
|
||||
|
||||
// Update the first item in navSecondary to show the search space name
|
||||
const updatedNavSecondary = [...navSecondary];
|
||||
if (updatedNavSecondary.length > 0 && isClient) {
|
||||
updatedNavSecondary[0] = {
|
||||
...updatedNavSecondary[0],
|
||||
title: searchSpace?.name || (isLoadingSearchSpace ? 'Loading...' : searchSpaceError ? 'Error loading search space' : 'Unknown Search Space'),
|
||||
};
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<AppSidebar
|
||||
navSecondary={updatedNavSecondary}
|
||||
navMain={navMain}
|
||||
RecentChats={isClient ? displayChats : []}
|
||||
/>
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppSidebar
|
||||
navSecondary={updatedNavSecondary}
|
||||
navMain={navMain}
|
||||
RecentChats={isClient ? displayChats : []}
|
||||
/>
|
||||
|
||||
{/* Delete Confirmation Dialog - Only render on client */}
|
||||
{isClient && (
|
||||
<Dialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Trash2 className="h-5 w-5 text-destructive" />
|
||||
<span>Delete Chat</span>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete <span className="font-medium">{chatToDelete?.name}</span>? This action cannot be undone.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="flex gap-2 sm:justify-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowDeleteDialog(false)}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleDeleteChat}
|
||||
disabled={isDeleting}
|
||||
className="gap-2"
|
||||
>
|
||||
{isDeleting ? (
|
||||
<>
|
||||
<span className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
||||
Deleting...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
Delete
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
{/* Delete Confirmation Dialog - Only render on client */}
|
||||
{isClient && (
|
||||
<Dialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
<Trash2 className="h-5 w-5 text-destructive" />
|
||||
<span>Delete Chat</span>
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete{" "}
|
||||
<span className="font-medium">{chatToDelete?.name}</span>? This action cannot be
|
||||
undone.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="flex gap-2 sm:justify-end">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowDeleteDialog(false)}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleDeleteChat}
|
||||
disabled={isDeleting}
|
||||
className="gap-2"
|
||||
>
|
||||
{isDeleting ? (
|
||||
<>
|
||||
<span className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
||||
Deleting...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
Delete
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,233 +1,235 @@
|
|||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import * as React from "react";
|
||||
import {
|
||||
BookOpen,
|
||||
Cable,
|
||||
FileStack,
|
||||
Undo2,
|
||||
MessageCircleMore,
|
||||
Settings2,
|
||||
SquareLibrary,
|
||||
SquareTerminal,
|
||||
AlertCircle,
|
||||
Info,
|
||||
ExternalLink,
|
||||
Trash2,
|
||||
Podcast,
|
||||
type LucideIcon,
|
||||
FileText,
|
||||
} from "lucide-react"
|
||||
BookOpen,
|
||||
Cable,
|
||||
FileStack,
|
||||
Undo2,
|
||||
MessageCircleMore,
|
||||
Settings2,
|
||||
SquareLibrary,
|
||||
SquareTerminal,
|
||||
AlertCircle,
|
||||
Info,
|
||||
ExternalLink,
|
||||
Trash2,
|
||||
Podcast,
|
||||
type LucideIcon,
|
||||
FileText,
|
||||
} from "lucide-react";
|
||||
|
||||
import { Logo } from "@/components/Logo";
|
||||
import { NavMain } from "@/components/sidebar/nav-main"
|
||||
import { NavProjects } from "@/components/sidebar/nav-projects"
|
||||
import { NavSecondary } from "@/components/sidebar/nav-secondary"
|
||||
import { NavMain } from "@/components/sidebar/nav-main";
|
||||
import { NavProjects } from "@/components/sidebar/nav-projects";
|
||||
import { NavSecondary } from "@/components/sidebar/nav-secondary";
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar"
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
SidebarFooter,
|
||||
SidebarHeader,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
|
||||
// Map of icon names to their components
|
||||
export const iconMap: Record<string, LucideIcon> = {
|
||||
BookOpen,
|
||||
Cable,
|
||||
FileStack,
|
||||
Undo2,
|
||||
MessageCircleMore,
|
||||
Settings2,
|
||||
SquareLibrary,
|
||||
SquareTerminal,
|
||||
AlertCircle,
|
||||
Info,
|
||||
ExternalLink,
|
||||
Trash2,
|
||||
Podcast,
|
||||
FileText
|
||||
}
|
||||
BookOpen,
|
||||
Cable,
|
||||
FileStack,
|
||||
Undo2,
|
||||
MessageCircleMore,
|
||||
Settings2,
|
||||
SquareLibrary,
|
||||
SquareTerminal,
|
||||
AlertCircle,
|
||||
Info,
|
||||
ExternalLink,
|
||||
Trash2,
|
||||
Podcast,
|
||||
FileText,
|
||||
};
|
||||
|
||||
const defaultData = {
|
||||
user: {
|
||||
name: "Surf",
|
||||
email: "m@example.com",
|
||||
avatar: "/icon-128.png",
|
||||
},
|
||||
navMain: [
|
||||
{
|
||||
title: "Researcher",
|
||||
url: "#",
|
||||
icon: "SquareTerminal",
|
||||
isActive: true,
|
||||
items: [],
|
||||
},
|
||||
user: {
|
||||
name: "Surf",
|
||||
email: "m@example.com",
|
||||
avatar: "/icon-128.png",
|
||||
},
|
||||
navMain: [
|
||||
{
|
||||
title: "Researcher",
|
||||
url: "#",
|
||||
icon: "SquareTerminal",
|
||||
isActive: true,
|
||||
items: [],
|
||||
},
|
||||
|
||||
{
|
||||
title: "Documents",
|
||||
url: "#",
|
||||
icon: "FileStack",
|
||||
items: [
|
||||
{
|
||||
title: "Upload Documents",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Manage Documents",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Connectors",
|
||||
url: "#",
|
||||
icon: "Cable",
|
||||
items: [
|
||||
{
|
||||
title: "Add Connector",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Manage Connectors",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Research Synthesizer's",
|
||||
url: "#",
|
||||
icon: "SquareLibrary",
|
||||
items: [
|
||||
{
|
||||
title: "Podcast Creator",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Presentation Creator",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
navSecondary: [
|
||||
{
|
||||
title: "SEARCH SPACE",
|
||||
url: "#",
|
||||
icon: "LifeBuoy",
|
||||
},
|
||||
],
|
||||
RecentChats: [
|
||||
{
|
||||
name: "Design Engineering",
|
||||
url: "#",
|
||||
icon: "MessageCircleMore",
|
||||
id: 1001,
|
||||
},
|
||||
{
|
||||
name: "Sales & Marketing",
|
||||
url: "#",
|
||||
icon: "MessageCircleMore",
|
||||
id: 1002,
|
||||
},
|
||||
{
|
||||
name: "Travel",
|
||||
url: "#",
|
||||
icon: "MessageCircleMore",
|
||||
id: 1003,
|
||||
},
|
||||
],
|
||||
}
|
||||
{
|
||||
title: "Documents",
|
||||
url: "#",
|
||||
icon: "FileStack",
|
||||
items: [
|
||||
{
|
||||
title: "Upload Documents",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Manage Documents",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Connectors",
|
||||
url: "#",
|
||||
icon: "Cable",
|
||||
items: [
|
||||
{
|
||||
title: "Add Connector",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Manage Connectors",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Research Synthesizer's",
|
||||
url: "#",
|
||||
icon: "SquareLibrary",
|
||||
items: [
|
||||
{
|
||||
title: "Podcast Creator",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Presentation Creator",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
navSecondary: [
|
||||
{
|
||||
title: "SEARCH SPACE",
|
||||
url: "#",
|
||||
icon: "LifeBuoy",
|
||||
},
|
||||
],
|
||||
RecentChats: [
|
||||
{
|
||||
name: "Design Engineering",
|
||||
url: "#",
|
||||
icon: "MessageCircleMore",
|
||||
id: 1001,
|
||||
},
|
||||
{
|
||||
name: "Sales & Marketing",
|
||||
url: "#",
|
||||
icon: "MessageCircleMore",
|
||||
id: 1002,
|
||||
},
|
||||
{
|
||||
name: "Travel",
|
||||
url: "#",
|
||||
icon: "MessageCircleMore",
|
||||
id: 1003,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
interface AppSidebarProps extends React.ComponentProps<typeof Sidebar> {
|
||||
navMain?: {
|
||||
title: string
|
||||
url: string
|
||||
icon: string
|
||||
isActive?: boolean
|
||||
items?: {
|
||||
title: string
|
||||
url: string
|
||||
}[]
|
||||
}[]
|
||||
navSecondary?: {
|
||||
title: string
|
||||
url: string
|
||||
icon: string // Changed to string (icon name)
|
||||
}[]
|
||||
RecentChats?: {
|
||||
name: string
|
||||
url: string
|
||||
icon: string // Changed to string (icon name)
|
||||
id?: number
|
||||
search_space_id?: number
|
||||
actions?: {
|
||||
name: string
|
||||
icon: string
|
||||
onClick: () => void
|
||||
}[]
|
||||
}[]
|
||||
navMain?: {
|
||||
title: string;
|
||||
url: string;
|
||||
icon: string;
|
||||
isActive?: boolean;
|
||||
items?: {
|
||||
title: string;
|
||||
url: string;
|
||||
}[];
|
||||
}[];
|
||||
navSecondary?: {
|
||||
title: string;
|
||||
url: string;
|
||||
icon: string; // Changed to string (icon name)
|
||||
}[];
|
||||
RecentChats?: {
|
||||
name: string;
|
||||
url: string;
|
||||
icon: string; // Changed to string (icon name)
|
||||
id?: number;
|
||||
search_space_id?: number;
|
||||
actions?: {
|
||||
name: string;
|
||||
icon: string;
|
||||
onClick: () => void;
|
||||
}[];
|
||||
}[];
|
||||
}
|
||||
|
||||
export function AppSidebar({
|
||||
navMain = defaultData.navMain,
|
||||
navSecondary = defaultData.navSecondary,
|
||||
RecentChats = defaultData.RecentChats,
|
||||
...props
|
||||
export function AppSidebar({
|
||||
navMain = defaultData.navMain,
|
||||
navSecondary = defaultData.navSecondary,
|
||||
RecentChats = defaultData.RecentChats,
|
||||
...props
|
||||
}: AppSidebarProps) {
|
||||
// Process navMain to resolve icon names to components
|
||||
const processedNavMain = React.useMemo(() => {
|
||||
return navMain.map(item => ({
|
||||
...item,
|
||||
icon: iconMap[item.icon] || SquareTerminal // Fallback to SquareTerminal if icon not found
|
||||
}))
|
||||
}, [navMain])
|
||||
// Process navMain to resolve icon names to components
|
||||
const processedNavMain = React.useMemo(() => {
|
||||
return navMain.map((item) => ({
|
||||
...item,
|
||||
icon: iconMap[item.icon] || SquareTerminal, // Fallback to SquareTerminal if icon not found
|
||||
}));
|
||||
}, [navMain]);
|
||||
|
||||
// Process navSecondary to resolve icon names to components
|
||||
const processedNavSecondary = React.useMemo(() => {
|
||||
return navSecondary.map(item => ({
|
||||
...item,
|
||||
icon: iconMap[item.icon] || Undo2 // Fallback to Undo2 if icon not found
|
||||
}))
|
||||
}, [navSecondary])
|
||||
// Process navSecondary to resolve icon names to components
|
||||
const processedNavSecondary = React.useMemo(() => {
|
||||
return navSecondary.map((item) => ({
|
||||
...item,
|
||||
icon: iconMap[item.icon] || Undo2, // Fallback to Undo2 if icon not found
|
||||
}));
|
||||
}, [navSecondary]);
|
||||
|
||||
// Process RecentChats to resolve icon names to components
|
||||
const processedRecentChats = React.useMemo(() => {
|
||||
return RecentChats?.map(item => ({
|
||||
...item,
|
||||
icon: iconMap[item.icon] || MessageCircleMore // Fallback to MessageCircleMore if icon not found
|
||||
})) || [];
|
||||
}, [RecentChats])
|
||||
// Process RecentChats to resolve icon names to components
|
||||
const processedRecentChats = React.useMemo(() => {
|
||||
return (
|
||||
RecentChats?.map((item) => ({
|
||||
...item,
|
||||
icon: iconMap[item.icon] || MessageCircleMore, // Fallback to MessageCircleMore if icon not found
|
||||
})) || []
|
||||
);
|
||||
}, [RecentChats]);
|
||||
|
||||
return (
|
||||
<Sidebar variant="inset" {...props}>
|
||||
<SidebarHeader>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton size="lg" asChild>
|
||||
<div>
|
||||
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
|
||||
<Logo className="rounded-lg" />
|
||||
</div>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">SurfSense</span>
|
||||
<span className="truncate text-xs">beta v0.0.7</span>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<NavMain items={processedNavMain} />
|
||||
{processedRecentChats.length > 0 && <NavProjects chats={processedRecentChats} />}
|
||||
<NavSecondary items={processedNavSecondary} className="mt-auto" />
|
||||
</SidebarContent>
|
||||
{/* <SidebarFooter>
|
||||
return (
|
||||
<Sidebar variant="inset" {...props}>
|
||||
<SidebarHeader>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton size="lg" asChild>
|
||||
<div>
|
||||
<div className="bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg">
|
||||
<Logo className="rounded-lg" />
|
||||
</div>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">SurfSense</span>
|
||||
<span className="truncate text-xs">beta v0.0.7</span>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<NavMain items={processedNavMain} />
|
||||
{processedRecentChats.length > 0 && <NavProjects chats={processedRecentChats} />}
|
||||
<NavSecondary items={processedNavSecondary} className="mt-auto" />
|
||||
</SidebarContent>
|
||||
{/* <SidebarFooter>
|
||||
footer
|
||||
</SidebarFooter> */}
|
||||
</Sidebar>
|
||||
)
|
||||
</Sidebar>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,78 +1,74 @@
|
|||
"use client"
|
||||
"use client";
|
||||
|
||||
import { ChevronRight, type LucideIcon } from "lucide-react"
|
||||
import { ChevronRight, type LucideIcon } from "lucide-react";
|
||||
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible"
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
SidebarMenuSubButton,
|
||||
SidebarMenuSubItem,
|
||||
} from "@/components/ui/sidebar"
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarMenuSub,
|
||||
SidebarMenuSubButton,
|
||||
SidebarMenuSubItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
|
||||
export function NavMain({
|
||||
items,
|
||||
items,
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon: LucideIcon
|
||||
isActive?: boolean
|
||||
items?: {
|
||||
title: string
|
||||
url: string
|
||||
}[]
|
||||
}[]
|
||||
items: {
|
||||
title: string;
|
||||
url: string;
|
||||
icon: LucideIcon;
|
||||
isActive?: boolean;
|
||||
items?: {
|
||||
title: string;
|
||||
url: string;
|
||||
}[];
|
||||
}[];
|
||||
}) {
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{items.map((item, index) => (
|
||||
<Collapsible key={`${item.title}-${index}`} asChild defaultOpen={item.isActive}>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip={item.title}>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
{item.items?.length ? (
|
||||
<>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuAction className="data-[state=open]:rotate-90">
|
||||
<ChevronRight />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</SidebarMenuAction>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{item.items?.map((subItem, subIndex) => (
|
||||
<SidebarMenuSubItem key={`${subItem.title}-${subIndex}`}>
|
||||
<SidebarMenuSubButton asChild>
|
||||
<a href={subItem.url}>
|
||||
<span>{subItem.title}</span>
|
||||
</a>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</>
|
||||
) : null}
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
)
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{items.map((item, index) => (
|
||||
<Collapsible key={`${item.title}-${index}`} asChild defaultOpen={item.isActive}>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild tooltip={item.title}>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
{item.items?.length ? (
|
||||
<>
|
||||
<CollapsibleTrigger asChild>
|
||||
<SidebarMenuAction className="data-[state=open]:rotate-90">
|
||||
<ChevronRight />
|
||||
<span className="sr-only">Toggle</span>
|
||||
</SidebarMenuAction>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<SidebarMenuSub>
|
||||
{item.items?.map((subItem, subIndex) => (
|
||||
<SidebarMenuSubItem key={`${subItem.title}-${subIndex}`}>
|
||||
<SidebarMenuSubButton asChild>
|
||||
<a href={subItem.url}>
|
||||
<span>{subItem.title}</span>
|
||||
</a>
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
))}
|
||||
</SidebarMenuSub>
|
||||
</CollapsibleContent>
|
||||
</>
|
||||
) : null}
|
||||
</SidebarMenuItem>
|
||||
</Collapsible>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,122 +1,118 @@
|
|||
"use client"
|
||||
"use client";
|
||||
|
||||
import { ExternalLink, Folder, MoreHorizontal, Share, Trash2, type LucideIcon } from "lucide-react";
|
||||
|
||||
import {
|
||||
ExternalLink,
|
||||
Folder,
|
||||
MoreHorizontal,
|
||||
Share,
|
||||
Trash2,
|
||||
type LucideIcon,
|
||||
} from "lucide-react"
|
||||
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/components/ui/sidebar"
|
||||
import { useRouter } from "next/navigation"
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
// Map of icon names to their components
|
||||
const actionIconMap: Record<string, LucideIcon> = {
|
||||
ExternalLink,
|
||||
Folder,
|
||||
Share,
|
||||
Trash2,
|
||||
MoreHorizontal
|
||||
}
|
||||
ExternalLink,
|
||||
Folder,
|
||||
Share,
|
||||
Trash2,
|
||||
MoreHorizontal,
|
||||
};
|
||||
|
||||
interface ChatAction {
|
||||
name: string;
|
||||
icon: string;
|
||||
onClick: () => void;
|
||||
name: string;
|
||||
icon: string;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export function NavProjects({
|
||||
chats,
|
||||
chats,
|
||||
}: {
|
||||
chats: {
|
||||
name: string
|
||||
url: string
|
||||
icon: LucideIcon
|
||||
id?: number
|
||||
search_space_id?: number
|
||||
actions?: ChatAction[]
|
||||
}[]
|
||||
chats: {
|
||||
name: string;
|
||||
url: string;
|
||||
icon: LucideIcon;
|
||||
id?: number;
|
||||
search_space_id?: number;
|
||||
actions?: ChatAction[];
|
||||
}[];
|
||||
}) {
|
||||
const { isMobile } = useSidebar()
|
||||
const router = useRouter()
|
||||
|
||||
const searchSpaceId = chats[0]?.search_space_id || ""
|
||||
const { isMobile } = useSidebar();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
||||
<SidebarGroupLabel>Recent Chats</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{chats.map((item, index) => (
|
||||
<SidebarMenuItem key={item.id ? `chat-${item.id}` : `chat-${item.name}-${index}`}>
|
||||
<SidebarMenuButton>
|
||||
<item.icon />
|
||||
<span>{item.name}</span>
|
||||
</SidebarMenuButton>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuAction showOnHover>
|
||||
<MoreHorizontal />
|
||||
<span className="sr-only">More</span>
|
||||
</SidebarMenuAction>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-48"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
align={isMobile ? "end" : "start"}
|
||||
>
|
||||
{item.actions ? (
|
||||
// Use the actions provided by the item
|
||||
item.actions.map((action, actionIndex) => {
|
||||
const ActionIcon = actionIconMap[action.icon] || Folder;
|
||||
return (
|
||||
<DropdownMenuItem key={`${action.name}-${actionIndex}`} onClick={action.onClick}>
|
||||
<ActionIcon className="text-muted-foreground" />
|
||||
<span>{action.name}</span>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
// Default actions if none provided
|
||||
<>
|
||||
<DropdownMenuItem>
|
||||
<Folder className="text-muted-foreground" />
|
||||
<span>View Chat</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<Trash2 className="text-muted-foreground" />
|
||||
<span>Delete Chat</span>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton onClick={() => router.push(`/dashboard/${searchSpaceId}/chats`)}>
|
||||
<MoreHorizontal />
|
||||
<span>View All Chats</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
)
|
||||
const searchSpaceId = chats[0]?.search_space_id || "";
|
||||
|
||||
return (
|
||||
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
||||
<SidebarGroupLabel>Recent Chats</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{chats.map((item, index) => (
|
||||
<SidebarMenuItem key={item.id ? `chat-${item.id}` : `chat-${item.name}-${index}`}>
|
||||
<SidebarMenuButton>
|
||||
<item.icon />
|
||||
<span>{item.name}</span>
|
||||
</SidebarMenuButton>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuAction showOnHover>
|
||||
<MoreHorizontal />
|
||||
<span className="sr-only">More</span>
|
||||
</SidebarMenuAction>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-48"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
align={isMobile ? "end" : "start"}
|
||||
>
|
||||
{item.actions ? (
|
||||
// Use the actions provided by the item
|
||||
item.actions.map((action, actionIndex) => {
|
||||
const ActionIcon = actionIconMap[action.icon] || Folder;
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={`${action.name}-${actionIndex}`}
|
||||
onClick={action.onClick}
|
||||
>
|
||||
<ActionIcon className="text-muted-foreground" />
|
||||
<span>{action.name}</span>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
// Default actions if none provided
|
||||
<>
|
||||
<DropdownMenuItem>
|
||||
<Folder className="text-muted-foreground" />
|
||||
<span>View Chat</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<Trash2 className="text-muted-foreground" />
|
||||
<span>Delete Chat</span>
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton onClick={() => router.push(`/dashboard/${searchSpaceId}/chats`)}>
|
||||
<MoreHorizontal />
|
||||
<span>View All Chats</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,42 +1,41 @@
|
|||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as React from "react"
|
||||
import { type LucideIcon } from "lucide-react"
|
||||
import type * as React from "react";
|
||||
import type { LucideIcon } from "lucide-react";
|
||||
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarGroupLabel,
|
||||
} from "@/components/ui/sidebar"
|
||||
|
||||
SidebarGroup,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
SidebarGroupLabel,
|
||||
} from "@/components/ui/sidebar";
|
||||
|
||||
export function NavSecondary({
|
||||
items,
|
||||
...props
|
||||
items,
|
||||
...props
|
||||
}: {
|
||||
items: {
|
||||
title: string
|
||||
url: string
|
||||
icon: LucideIcon
|
||||
}[]
|
||||
items: {
|
||||
title: string;
|
||||
url: string;
|
||||
icon: LucideIcon;
|
||||
}[];
|
||||
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
||||
return (
|
||||
<SidebarGroup {...props}>
|
||||
<SidebarGroupLabel>SearchSpace</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{items.map((item, index) => (
|
||||
<SidebarMenuItem key={`${item.title}-${index}`}>
|
||||
<SidebarMenuButton asChild size="sm">
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
)
|
||||
return (
|
||||
<SidebarGroup {...props}>
|
||||
<SidebarGroupLabel>SearchSpace</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{items.map((item, index) => (
|
||||
<SidebarMenuItem key={`${item.title}-${index}`}>
|
||||
<SidebarMenuButton asChild size="sm">
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,110 +1,103 @@
|
|||
"use client"
|
||||
"use client";
|
||||
|
||||
import {
|
||||
BadgeCheck,
|
||||
ChevronsUpDown,
|
||||
LogOut,
|
||||
Settings,
|
||||
} from "lucide-react"
|
||||
import { BadgeCheck, ChevronsUpDown, LogOut, Settings } from "lucide-react";
|
||||
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/components/ui/avatar"
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import {
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/components/ui/sidebar"
|
||||
import { useRouter, useParams } from "next/navigation"
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/components/ui/sidebar";
|
||||
import { useRouter, useParams } from "next/navigation";
|
||||
|
||||
export function NavUser({
|
||||
user,
|
||||
user,
|
||||
}: {
|
||||
user: {
|
||||
name: string
|
||||
email: string
|
||||
avatar: string
|
||||
}
|
||||
user: {
|
||||
name: string;
|
||||
email: string;
|
||||
avatar: string;
|
||||
};
|
||||
}) {
|
||||
const { isMobile } = useSidebar()
|
||||
const router = useRouter()
|
||||
const { search_space_id } = useParams()
|
||||
const { isMobile } = useSidebar();
|
||||
const router = useRouter();
|
||||
const { search_space_id } = useParams();
|
||||
|
||||
const handleLogout = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.removeItem('surfsense_bearer_token');
|
||||
router.push('/');
|
||||
}
|
||||
};
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="truncate text-xs">{user.email}</span>
|
||||
</div>
|
||||
<ChevronsUpDown className="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="truncate text-xs">{user.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem onClick={() => router.push(`/dashboard/${search_space_id}/api-key`)}>
|
||||
<BadgeCheck />
|
||||
API Key
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => router.push(`/settings`)}>
|
||||
<Settings />
|
||||
Settings
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleLogout}>
|
||||
<LogOut />
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
)
|
||||
const handleLogout = () => {
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.removeItem("surfsense_bearer_token");
|
||||
router.push("/");
|
||||
}
|
||||
};
|
||||
return (
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuButton
|
||||
size="lg"
|
||||
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
||||
>
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="truncate text-xs">{user.email}</span>
|
||||
</div>
|
||||
<ChevronsUpDown className="ml-auto size-4" />
|
||||
</SidebarMenuButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
align="end"
|
||||
sideOffset={4}
|
||||
>
|
||||
<DropdownMenuLabel className="p-0 font-normal">
|
||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
||||
<Avatar className="h-8 w-8 rounded-lg">
|
||||
<AvatarImage src={user.avatar} alt={user.name} />
|
||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||
<span className="truncate font-medium">{user.name}</span>
|
||||
<span className="truncate text-xs">{user.email}</span>
|
||||
</div>
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
onClick={() => router.push(`/dashboard/${search_space_id}/api-key`)}
|
||||
>
|
||||
<BadgeCheck />
|
||||
API Key
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={() => router.push(`/settings`)}>
|
||||
<Settings />
|
||||
Settings
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleLogout}>
|
||||
<LogOut />
|
||||
Log out
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue