mirror of
https://github.com/reconurge/flowsint.git
synced 2026-03-12 01:44:42 -05:00
feat: ask ai
This commit is contained in:
12
src/components/assistant.tsx
Normal file
12
src/components/assistant.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
import { GemIcon } from "lucide-react"
|
||||
import { useChatContext } from "./contexts/chatbot-context"
|
||||
|
||||
const Assistant = () => {
|
||||
const { handleOpenChat } = useChatContext()
|
||||
return (
|
||||
<Button onClick={() => handleOpenChat(null)} variant="ghost" size={"icon"} className="h-full w-12 rounded-none"><GemIcon className="h-6 w-6 text-primary" /></Button>
|
||||
)
|
||||
}
|
||||
export default Assistant
|
||||
@@ -6,147 +6,151 @@ import { useChat } from "@ai-sdk/react"
|
||||
import { Bot, User } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import ReactMarkdown from "react-markdown"
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { ScrollArea } from "../ui/scroll-area"
|
||||
|
||||
interface ChatContextType {
|
||||
open: boolean
|
||||
handleOpenChat: (content: any) => void
|
||||
open: boolean
|
||||
handleOpenChat: (content: any) => void
|
||||
}
|
||||
|
||||
const ChatContext = createContext<ChatContextType | undefined>(undefined)
|
||||
|
||||
interface ChatProviderProps {
|
||||
children: ReactNode
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export const ChatProvider: React.FC<ChatProviderProps> = ({ children }) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [context, setContext] = useState<any>(null)
|
||||
const { messages, input, handleInputChange, setInput, handleSubmit, error, isLoading } = useChat()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [context, setContext] = useState<any>(null)
|
||||
const { messages, input, handleInputChange, setInput, handleSubmit, error, isLoading } = useChat()
|
||||
|
||||
const handleOpenChat = (content: any) => {
|
||||
setContext(content)
|
||||
setInput(`What pattern can you extract from this person ?\n ${JSON.stringify(content)}`)
|
||||
setOpen(true)
|
||||
}
|
||||
const handleOpenChat = (content: any) => {
|
||||
setContext(content)
|
||||
if (content)
|
||||
setInput(`What pattern can you extract from this person ?\n ${JSON.stringify(content)}`)
|
||||
setOpen(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<ChatContext.Provider value={{ open, handleOpenChat }}>
|
||||
{children}
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent className={cn("w-full p-4", messages?.length > 0 ? "sm:max-w-[730px]" : "sm:max-w-[480px]")}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Ask AI</DialogTitle>
|
||||
<DialogDescription>Ask AI chat bot to help you understand patterns.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-3 overflow-y-auto w-full max-h-[60vh]">
|
||||
{messages.map((m) => (
|
||||
<div key={m.id} className="flex grow">
|
||||
<div
|
||||
className={cn(
|
||||
"flex gap-1 items-start w-full",
|
||||
m.role === "user" ? "flex-row-reverse justify-end" : "flex-row justify-start",
|
||||
)}
|
||||
>
|
||||
<Avatar className={cn("h-8 w-8")}>
|
||||
<AvatarFallback>
|
||||
{m.role === "user" ? (
|
||||
<User className="h-4 w-4" />
|
||||
) : isLoading ? (
|
||||
<LoadingDots />
|
||||
) : (
|
||||
<Bot className="h-4 w-4" />
|
||||
)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className={cn("flex flex-col gap-1 w-full", m.role === "user" ? "items-end" : "items-start")}>
|
||||
<span className={cn("text-sm font-bold", m.role === "user" ? "text-right" : "text-left")}>
|
||||
{isLoading && m.role !== "user" ? <LoadingDots /> : m.role === "user" ? "User" : "Chatbot"}
|
||||
</span>
|
||||
<Card className="max-w-[80%]">
|
||||
<CardContent className="p-3">
|
||||
<ReactMarkdown>{m.content}</ReactMarkdown>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{error && (
|
||||
<div className="flex grow">
|
||||
<div className="flex gap-1 items-start justify-start w-full">
|
||||
<Avatar className="h-8 w-8 bg-red-500">
|
||||
<AvatarFallback>
|
||||
<Bot className="h-4 w-4" />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex flex-col gap-1 items-start w-full">
|
||||
<span className="text-sm font-bold text-red-500">Error</span>
|
||||
<Card className="max-w-[80%]">
|
||||
<CardContent className="p-3 text-red-500">
|
||||
Oops, an error occurred. Make sure you provided a valid Mistral API key.
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<form onSubmit={handleSubmit} className="mt-4">
|
||||
<Input
|
||||
value={input}
|
||||
onChange={handleInputChange}
|
||||
placeholder="'What pattern can you extract from those relations ?'"
|
||||
className="w-full"
|
||||
/>
|
||||
<Button type="submit" className="mt-2">
|
||||
Send
|
||||
</Button>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</ChatContext.Provider>
|
||||
)
|
||||
return (
|
||||
<ChatContext.Provider value={{ open, handleOpenChat }}>
|
||||
{children}
|
||||
<Sheet open={open} onOpenChange={setOpen}>
|
||||
<SheetContent className={cn("w-full p-4 flex divide-y flex-col justify-between", messages?.length > 0 ? "sm:max-w-[730px]" : "sm:max-w-[480px]")}>
|
||||
<SheetHeader>
|
||||
<SheetTitle>Ask AI</SheetTitle>
|
||||
<SheetDescription>Ask AI chat bot to help you understand patterns.</SheetDescription>
|
||||
</SheetHeader>
|
||||
<ScrollArea className="flex flex-col gap-3 overflow-y-auto w-full grow">
|
||||
{messages.map((m) => (
|
||||
<div key={m.id} className="flex grow mb-2">
|
||||
<div
|
||||
className={cn(
|
||||
"flex gap-1 items-start w-full",
|
||||
m.role === "user" ? "flex-row-reverse justify-end" : "flex-row justify-start",
|
||||
)}
|
||||
>
|
||||
<Avatar className={cn("h-8 w-8")}>
|
||||
<AvatarFallback>
|
||||
{m.role === "user" ? (
|
||||
<User className="h-4 w-4" />
|
||||
) : isLoading ? (
|
||||
<LoadingDots />
|
||||
) : (
|
||||
<Bot className="h-4 w-4" />
|
||||
)}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className={cn("flex flex-col gap-1 w-full", m.role === "user" ? "items-end" : "items-start")}>
|
||||
<span className={cn("text-sm font-bold", m.role === "user" ? "text-right" : "text-left")}>
|
||||
{isLoading && m.role !== "user" ? <LoadingDots /> : m.role === "user" ? "User" : "Chatbot"}
|
||||
</span>
|
||||
<Card className="max-w-[80%]">
|
||||
<CardContent className="p-3">
|
||||
<ReactMarkdown>{m.content}</ReactMarkdown>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{error && (
|
||||
<div className="flex">
|
||||
<div className="flex gap-1 items-start justify-start w-full">
|
||||
<Avatar className="h-8 w-8 bg-red-500">
|
||||
<AvatarFallback>
|
||||
<Bot className="h-4 w-4" />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex flex-col gap-1 items-start w-full">
|
||||
<span className="text-sm font-bold text-red-500">Error</span>
|
||||
<Card className="max-w-[80%]">
|
||||
<CardContent className="p-3 text-red-500">
|
||||
Oops, an error occurred. Make sure you provided a valid Mistral API key.
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</ScrollArea>
|
||||
<form onSubmit={handleSubmit} className=" flex items-center gap-2">
|
||||
<Input
|
||||
value={input}
|
||||
onChange={handleInputChange}
|
||||
placeholder="'What pattern can you extract from those relations ?'"
|
||||
className="grow"
|
||||
/>
|
||||
<div>
|
||||
<Button type="submit">
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</ChatContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useChatContext = (): ChatContextType => {
|
||||
const context = useContext(ChatContext)
|
||||
if (!context) {
|
||||
throw new Error("useChatContext must be used within a ChatProvider")
|
||||
}
|
||||
return context
|
||||
const context = useContext(ChatContext)
|
||||
if (!context) {
|
||||
throw new Error("useChatContext must be used within a ChatProvider")
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
interface LoadingDotsProps {
|
||||
speed?: number
|
||||
text?: string
|
||||
speed?: number
|
||||
text?: string
|
||||
}
|
||||
|
||||
const LoadingDots: React.FC<LoadingDotsProps> = ({ speed = 200, text = "Thinking" }) => {
|
||||
const [dots, setDots] = useState("")
|
||||
const [dots, setDots] = useState("")
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setDots((prevDots) => {
|
||||
if (prevDots.length >= 3) {
|
||||
return ""
|
||||
}
|
||||
return prevDots + "."
|
||||
})
|
||||
}, speed)
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setDots((prevDots) => {
|
||||
if (prevDots.length >= 3) {
|
||||
return ""
|
||||
}
|
||||
return prevDots + "."
|
||||
})
|
||||
}, speed)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [speed])
|
||||
return () => clearInterval(interval)
|
||||
}, [speed])
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<span>{text}</span>
|
||||
<span className="w-8 text-left">{dots}</span>
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<span>{text}</span>
|
||||
<span className="w-8 text-left">{dots}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -95,7 +95,7 @@ export const InvestigationProvider: React.FC<InvestigationProviderProps> = ({ ch
|
||||
return (
|
||||
<InvestigationContext.Provider value={{ filters, setFilters, settings, setSettings, setOpenSettingsModal, investigation, isLoadingInvestigation, handleOpenIndividualModal, handleDeleteInvestigation, currentNode, setCurrentNode, panelOpen, setPanelOpen }}>
|
||||
{children}
|
||||
<Dialog open={openSettingsModal}>
|
||||
<Dialog open={openSettingsModal} onOpenChange={setOpenSettingsModal}>
|
||||
<DialogContent>
|
||||
<DialogTitle>Settings</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -119,8 +119,8 @@ export const InvestigationProvider: React.FC<InvestigationProviderProps> = ({ ch
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 justify-end">
|
||||
<DialogClose asChild onClick={() => setOpenSettingsModal(false)}>
|
||||
<Button variant="outline">
|
||||
<DialogClose asChild>
|
||||
<Button type="button" variant="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogClose>
|
||||
|
||||
@@ -189,7 +189,7 @@ export const NodeProvider: React.FC<NodeProviderProps> = (props: any) => {
|
||||
</Alert>}
|
||||
<div className="flex items-center gap-2 justify-end mt-4">
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline">
|
||||
<Button type="button" variant="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogClose>
|
||||
|
||||
@@ -4,13 +4,15 @@ import { createContext, useContext, type ReactNode, useState } from "react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Card } from "@/components/ui/card"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
Sheet,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetFooter,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet"
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert"
|
||||
import { InfoIcon } from "lucide-react"
|
||||
import { investigateValue } from "@/lib/actions/search"
|
||||
@@ -66,12 +68,12 @@ export const SearchProvider: React.FC<SearchProviderProps> = ({ children }) => {
|
||||
return (
|
||||
<SearchContext.Provider value={{ openSearchModal, handleOpenSearchModal }}>
|
||||
{children}
|
||||
<Dialog open={openSearchModal} onOpenChange={handleCloseModal}>
|
||||
<DialogContent className={results ? "sm:max-w-[950px]" : "sm:max-w-[450px]"}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>New search</DialogTitle>
|
||||
<DialogDescription>Make a new keyword associated research.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Sheet open={openSearchModal} onOpenChange={handleCloseModal}>
|
||||
<SheetContent className={results ? "sm:max-w-[950px]" : "sm:max-w-[450px]"}>
|
||||
<SheetHeader>
|
||||
<SheetTitle>New search</SheetTitle>
|
||||
<SheetDescription>Make a new keyword associated research.</SheetDescription>
|
||||
</SheetHeader>
|
||||
<form onSubmit={onSubmitNewSearch}>
|
||||
<div className="flex flex-col gap-3">
|
||||
<Card className="p-4">
|
||||
@@ -104,20 +106,22 @@ export const SearchProvider: React.FC<SearchProviderProps> = ({ children }) => {
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter className="mt-4">
|
||||
<Button variant="outline" onClick={handleCloseModal}>
|
||||
Close
|
||||
</Button>
|
||||
<SheetFooter className="mt-4">
|
||||
<SheetClose asChild>
|
||||
<Button type="button" variant="outline">
|
||||
Close
|
||||
</Button>
|
||||
</SheetClose>
|
||||
{!results && (
|
||||
<Button disabled={isLoading} type="submit">
|
||||
{isLoading ? "Saving..." : "Save"}
|
||||
</Button>
|
||||
)}
|
||||
</DialogFooter>
|
||||
</SheetFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</SearchContext.Provider>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</SearchContext.Provider >
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -140,18 +140,18 @@ const createStore = (initialNodes: AppNode[] = [], initialEdges: Edge[] = []) =>
|
||||
},
|
||||
onNodeClick: (_event: any, node: { id: string; }) => {
|
||||
set({ currentNode: node.id });
|
||||
get().resetNodeStyles()
|
||||
get().resetNodeStyles();
|
||||
},
|
||||
onPaneClick: (_envent: any) => {
|
||||
set({ currentNode: null });
|
||||
get().resetNodeStyles()
|
||||
get().resetNodeStyles();
|
||||
|
||||
},
|
||||
onLayout: (direction = 'TB', fitView: () => void) => {
|
||||
const { nodes, edges } = getLayoutedElements(get().nodes, get().edges, { direction });
|
||||
set({ nodes });
|
||||
set({ edges })
|
||||
fitView()
|
||||
set({ edges });
|
||||
fitView();
|
||||
},
|
||||
highlightPath: (selectedNode: Node | null) => {
|
||||
if (!selectedNode) {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useCallback } from "react"
|
||||
import { Copy } from "lucide-react"
|
||||
import { CopyIcon } from "lucide-react"
|
||||
import { useTimeout } from "usehooks-ts"
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"
|
||||
import { Button } from "./ui/button"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
interface CopyButtonProps {
|
||||
content: string
|
||||
@@ -34,8 +35,8 @@ export function CopyButton({ content, className, delay = 2000 }: CopyButtonProps
|
||||
return (
|
||||
<Tooltip open={isCopied}>
|
||||
<TooltipTrigger asChild>
|
||||
<Button className={className} size={"icon"} variant="ghost" onClick={handleCopy} aria-label="Copy content">
|
||||
<Copy className="h-3 w-3 opacity-50" />
|
||||
<Button className={cn("h-7 w-7", className)} size={"icon"} variant="ghost" onClick={handleCopy} aria-label="Copy content">
|
||||
<CopyIcon className="!h-3.5 !w-3.5 opacity-50" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
|
||||
@@ -3,32 +3,29 @@ import React from 'react'
|
||||
import { Investigation } from '@/types/investigation';
|
||||
import Link from 'next/link';
|
||||
import Image from 'next/image';
|
||||
import { Card } from '@/components/ui/card';
|
||||
const investigation = ({ investigation }: { investigation: Investigation }) => {
|
||||
return (
|
||||
<Link href={`/investigations/${investigation.id}`} >
|
||||
<div className="w-full h-full">
|
||||
<Card className='h-full'>
|
||||
<div className="group relative h-full overflow-hidden rounded-lg">
|
||||
<div className="aspect-[4/3] overflow-hidden">
|
||||
<Image
|
||||
src={"https://c6p3q0oludlschf8w.lite.vusercontent.net/placeholder.svg"}
|
||||
alt={investigation.title}
|
||||
width={400}
|
||||
height={300}
|
||||
className="h-full w-full object-cover transition-transform group-hover:scale-115 dark:invert"
|
||||
/>
|
||||
<div className="group relative h-full overflow-hidden">
|
||||
<div className="aspect-video overflow-hidden border">
|
||||
<Image
|
||||
src={"https://c6p3q0oludlschf8w.lite.vusercontent.net/placeholder.svg"}
|
||||
alt={investigation.title}
|
||||
width={400}
|
||||
height={300}
|
||||
className="h-full w-full object-cover transition-transform group-hover:scale-115 dark:invert"
|
||||
/>
|
||||
</div>
|
||||
<div className="p-4 h-full">
|
||||
<div>
|
||||
{investigation.title}
|
||||
</div>
|
||||
<div className="p-4 h-full">
|
||||
<div>
|
||||
{investigation.title}
|
||||
</div>
|
||||
<div>
|
||||
{investigation.description || <span className='italic'>No description provided.</span>}
|
||||
</div>
|
||||
<div className='opacity-60'>
|
||||
{investigation.description || <span className='italic'>No description provided.</span>}
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
|
||||
@@ -82,7 +82,7 @@ const SearchModal = ({ investigation_id }: { investigation_id: string }) => {
|
||||
<Button variant="outline" size="icon" onClick={() => setOpen(true)}>
|
||||
<Search className="h-4 w-4" />
|
||||
</Button>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogContent className="sm:max-w-[425px] min-h-none">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Search</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -99,13 +99,13 @@ const SearchModal = ({ investigation_id }: { investigation_id: string }) => {
|
||||
{error && <p className="text-red-500">An error occurred.</p>}
|
||||
{isLoading && <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>}
|
||||
{results?.length === 0 && <p>No results found for "{search}".</p>}
|
||||
<ScrollArea className="h-[60vh] w-full rounded-md border p-4">
|
||||
{results && results?.length !== 0 && <ScrollArea className="h-[60vh] w-full rounded-md border p-4">
|
||||
<ul className='space-y-2'>
|
||||
{!error && !isLoading && Array.isArray(results) && results?.map((item) => (
|
||||
<SearchItem key={item.id} item={item} />
|
||||
))}
|
||||
</ul>
|
||||
</ScrollArea>
|
||||
</ScrollArea>}
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@@ -18,10 +18,10 @@ export default function CaseSelector() {
|
||||
router.push(`/investigations/${value}`);
|
||||
};
|
||||
return (
|
||||
<div className="ml-2 flex items-center">
|
||||
<div className="flex items-center">
|
||||
{isLoading || isLoadingInvestigation ? <Skeleton className="h-8 w-40" /> :
|
||||
<Select onValueChange={handleSelectionChange} defaultValue={investigation?.id}>
|
||||
<SelectTrigger className="min-w-none w-full text-ellipsis truncate">
|
||||
<SelectTrigger className="min-w-none w-full shadow-none text-ellipsis truncate gap-1">
|
||||
<SelectValue defaultValue={investigation?.title || ""} placeholder="Select a fruit" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
||||
@@ -47,7 +47,7 @@ function FloatingEdge(props: any) {
|
||||
pointerEvents: 'all',
|
||||
opacity: style?.opacity || 1
|
||||
}}
|
||||
className="nodrag nopan !text-[.6rem] !px-1 !py-0">{label} {confidence_level &&
|
||||
className="nodrag nopan bg-primary/80 backdrop-blur leading-3.5 border border-primary !text-[.6rem] !px-1 !py-0">{label} {confidence_level &&
|
||||
<>{confidence_level}%</>}
|
||||
</Badge>}
|
||||
</EdgeLabelRenderer></>
|
||||
|
||||
@@ -27,7 +27,6 @@ import { Tooltip, TooltipContent } from '@/components/ui/tooltip';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { TooltipTrigger } from '@radix-ui/react-tooltip';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { getInvestigationData } from '@/lib/actions/investigations';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useInvestigationContext } from '@/components/contexts/investigation-provider';
|
||||
import { useFlowStore } from '../contexts/use-flow-store';
|
||||
@@ -141,7 +140,7 @@ const LayoutFlow = ({ theme }: { theme: ColorMode }) => {
|
||||
<NewActions addNodes={addNodes} />
|
||||
</div>
|
||||
{currentNode && (
|
||||
<Card>
|
||||
<Card className='p-3'>
|
||||
<>
|
||||
{getNode(currentNode)?.data?.label}
|
||||
</>
|
||||
|
||||
@@ -100,6 +100,7 @@ const IndividualModal = () => {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
type="button"
|
||||
onClick={() => setEditMode(!editMode)}
|
||||
aria-label={editMode ? "Cancel edit" : "Edit profile"}
|
||||
>
|
||||
@@ -113,8 +114,8 @@ const IndividualModal = () => {
|
||||
<AvatarFallback>{individual?.full_name?.[0] || "?"}</AvatarFallback>
|
||||
</Avatar>
|
||||
</div>
|
||||
<div className="flex-grow">
|
||||
<Tabs defaultValue="overview">
|
||||
<div className="flex-grow w-full">
|
||||
<Tabs defaultValue="overview" className="w-full">
|
||||
<TabsList className="overflow-x-auto">
|
||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||
<TabsTrigger value="social_account">
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useInvestigationContext } from '@/components/contexts/investigation-pro
|
||||
import { NavUser } from '@/components/user';
|
||||
import { Button } from '../ui/button';
|
||||
import { ScrollArea } from '../ui/scroll-area';
|
||||
import Assistant from '../assistant';
|
||||
|
||||
const InvestigationLayout = ({
|
||||
children,
|
||||
@@ -24,8 +25,7 @@ const InvestigationLayout = ({
|
||||
investigation_id: string
|
||||
user: any
|
||||
}) => {
|
||||
const { handleOpenChat } = useChatContext()
|
||||
const { panelOpen, setPanelOpen, investigation } = useInvestigationContext()
|
||||
const { panelOpen, setPanelOpen } = useInvestigationContext()
|
||||
return (
|
||||
<PanelGroup autoSaveId="conditional" className='h-screen w-screen flex' direction="horizontal">
|
||||
{panelOpen && <Panel id="left" order={1} className='h-screen' defaultSize={20} minSize={15}>
|
||||
@@ -33,7 +33,6 @@ const InvestigationLayout = ({
|
||||
<div className='w-full rounded-none shadow-none h-12 border-b flex items-center gap-1 flex-row justify-between p-2'>
|
||||
<Logo />
|
||||
<div className='flex gap-1'>
|
||||
<Button variant="outline" size="icon" onClick={() => handleOpenChat(investigation)} color="gray"><BotMessageSquareIcon className="h-4" /></Button>
|
||||
<SearchModal investigation_id={investigation_id} />
|
||||
<NewCase>
|
||||
<Button variant="outline" size="icon">
|
||||
@@ -55,14 +54,19 @@ const InvestigationLayout = ({
|
||||
<PanelResizeHandle />
|
||||
<Panel id="right" order={2} defaultSize={80} minSize={50} className='grow flex flex-col'>
|
||||
<div>
|
||||
<div className='w-full rounded-none shadow-none h-12 justify-between border-b flex flex-row items-center p-2'>
|
||||
<div className='flex gap-1'>
|
||||
<Button onClick={() => setPanelOpen(!panelOpen)} size="icon" variant="outline">
|
||||
<PanelRightIcon className='h-4 w-4' />
|
||||
</Button>
|
||||
<CaseSelector />
|
||||
<div className='w-full rounded-none shadow-none h-12 justify-between border-b flex flex-row items-center'>
|
||||
<div className='grow flex items-center justify-between p-2'>
|
||||
<div className='flex gap-1 p-2'>
|
||||
<Button onClick={() => setPanelOpen(!panelOpen)} size="icon" variant="outline">
|
||||
<PanelRightIcon className='h-4 w-4' />
|
||||
</Button>
|
||||
<CaseSelector />
|
||||
</div>
|
||||
<MoreMenu />
|
||||
</div>
|
||||
<div className='border-l h-full flex items-center'>
|
||||
<Assistant />
|
||||
</div>
|
||||
<MoreMenu />
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -44,7 +44,7 @@ function EmailNode({ data }: any) {
|
||||
<Badge variant="secondary" className="h-6 w-6 p-0 rounded-full">
|
||||
<LocateIcon className="h-4 w-4" />
|
||||
</Badge>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-sm">{data.label}</span>
|
||||
{settings.showCopyIcon && <CopyButton className="rounded-full h-7 w-7 text-xs" content={data.label} />}
|
||||
</div>
|
||||
|
||||
@@ -89,7 +89,7 @@ function Custom(props: any) {
|
||||
<AvatarFallback>{loading ? "..." : data.full_name[0]}</AvatarFallback>
|
||||
</Avatar>
|
||||
{settings.showNodeLabel && showContent && (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-sm">{data.full_name}</span>
|
||||
{settings.showCopyIcon && <CopyButton className="rounded-full" content={data.full_name} />}
|
||||
</div>
|
||||
|
||||
@@ -44,7 +44,7 @@ function IpNode({ data }: any) {
|
||||
<Badge variant="secondary" className="h-6 w-6 p-0 rounded-full">
|
||||
<LocateIcon className="h-4 w-4" />
|
||||
</Badge>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-sm">{data.label}</span>
|
||||
{settings.showCopyIcon && <CopyButton className="rounded-full h-7 w-7 text-xs" content={data.label} />}
|
||||
</div>
|
||||
|
||||
@@ -44,7 +44,7 @@ function PhoneNode({ data }: any) {
|
||||
<Badge variant="secondary" className="h-6 w-6 p-0 rounded-full">
|
||||
<PhoneIcon className="h-4 w-4" />
|
||||
</Badge>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-sm">{data.label}</span>
|
||||
{settings.showCopyIcon && <CopyButton className="rounded-full h-7 w-7 text-xs" content={data.label} />}
|
||||
</div>
|
||||
|
||||
@@ -44,7 +44,7 @@ function AddressNode({ data }: any) {
|
||||
<Badge variant="secondary" className="h-6 w-6 p-0 rounded-full">
|
||||
<MapPinIcon className="h-4 w-4" />
|
||||
</Badge>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-sm">{data.label}</span>
|
||||
{settings.showCopyIcon && <CopyButton className="rounded-full h-7 w-7 text-xs" content={data.label} />}
|
||||
</div>
|
||||
|
||||
@@ -49,7 +49,7 @@ function SocialNode({ data }: any) {
|
||||
{/* @ts-ignore */}
|
||||
{platformsIcons?.[data?.platform]?.icon}
|
||||
</Badge>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-sm">{data.username || data.profile_url}</span>
|
||||
{settings.showCopyIcon && <CopyButton className="rounded-full h-7 w-7 text-xs" content={data.username || data.profile_url} />}
|
||||
</div>
|
||||
|
||||
@@ -10,13 +10,13 @@ const buttonVariants = cva(
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary text-primary-foreground shadow-sm hover:bg-primary/90",
|
||||
"bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90",
|
||||
"bg-destructive text-destructive-foregroundhover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
||||
"bg-secondary text-secondary-foregroundhover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
|
||||
@@ -58,7 +58,7 @@ function SheetContent({
|
||||
<SheetPrimitive.Content
|
||||
data-slot="sheet-content"
|
||||
className={cn(
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out fixed z-50 flex flex-col gap-4 shadow-lg transition ease-in-out data-[state=closed]:duration-100 data-[state=open]:duration-300",
|
||||
side === "right" &&
|
||||
"data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
|
||||
side === "left" &&
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
--accent-foreground: hsl(0 0% 98%);
|
||||
--destructive: hsl(0 62.8% 30.6%);
|
||||
--destructive-foreground: hsl(0 0% 98%);
|
||||
--border: hsl(0 0% 14.9%);
|
||||
--border: hsl(0, 0%, 23%);
|
||||
--input: hsl(0 0% 14.9%);
|
||||
--ring: hsl(0 0% 83.1%);
|
||||
--chart-1: hsl(220 70% 50%);
|
||||
|
||||
Reference in New Issue
Block a user