Merge pull request #334 from jflo/feat/delete-insight

feat: add delete insight functionality
This commit is contained in:
Luis Novo 2025-12-19 22:51:18 -03:00 committed by GitHub
commit 86329f406d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 132 additions and 29 deletions

View file

@ -24,6 +24,16 @@ import {
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuTrigger, DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu' } from '@/components/ui/dropdown-menu'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog'
import { import {
Select, Select,
SelectContent, SelectContent,
@ -80,6 +90,8 @@ export function SourceDetailContent({
const [isDownloadingFile, setIsDownloadingFile] = useState(false) const [isDownloadingFile, setIsDownloadingFile] = useState(false)
const [fileAvailable, setFileAvailable] = useState<boolean | null>(null) const [fileAvailable, setFileAvailable] = useState<boolean | null>(null)
const [selectedInsight, setSelectedInsight] = useState<SourceInsightResponse | null>(null) const [selectedInsight, setSelectedInsight] = useState<SourceInsightResponse | null>(null)
const [insightToDelete, setInsightToDelete] = useState<string | null>(null)
const [deletingInsight, setDeletingInsight] = useState(false)
const fetchSource = useCallback(async () => { const fetchSource = useCallback(async () => {
try { try {
@ -152,6 +164,24 @@ export function SourceDetailContent({
} }
} }
const handleDeleteInsight = async (e?: React.MouseEvent) => {
e?.preventDefault()
if (!insightToDelete) return
try {
setDeletingInsight(true)
await insightsApi.delete(insightToDelete)
toast.success('Insight deleted successfully')
setInsightToDelete(null)
await fetchInsights()
} catch (err) {
console.error('Failed to delete insight:', err)
toast.error('Failed to delete insight')
} finally {
setDeletingInsight(false)
}
}
const handleUpdateTitle = async (title: string) => { const handleUpdateTitle = async (title: string) => {
if (!source || title === source.title) return if (!source || title === source.title) return
@ -573,10 +603,18 @@ export function SourceDetailContent({
<p className="mt-2 text-sm text-muted-foreground"> <p className="mt-2 text-sm text-muted-foreground">
{insight.content.slice(0, 180)}{insight.content.length > 180 ? '…' : ''} {insight.content.slice(0, 180)}{insight.content.length > 180 ? '…' : ''}
</p> </p>
<div className="mt-3 flex justify-end"> <div className="mt-3 flex justify-end gap-2">
<Button size="sm" variant="outline" onClick={() => setSelectedInsight(insight)}> <Button size="sm" variant="outline" onClick={() => setSelectedInsight(insight)}>
View Insight View Insight
</Button> </Button>
<Button
size="sm"
variant="outline"
onClick={() => setInsightToDelete(insight.id)}
className="text-destructive hover:text-destructive"
>
<Trash2 className="h-4 w-4" />
</Button>
</div> </div>
</div> </div>
))} ))}
@ -743,7 +781,41 @@ export function SourceDetailContent({
} }
}} }}
insight={selectedInsight ?? undefined} insight={selectedInsight ?? undefined}
onDelete={async (insightId) => {
try {
await insightsApi.delete(insightId)
toast.success('Insight deleted successfully')
setSelectedInsight(null)
await fetchInsights()
} catch (err) {
console.error('Failed to delete insight:', err)
toast.error('Failed to delete insight')
}
}}
/> />
<AlertDialog open={!!insightToDelete} onOpenChange={() => setInsightToDelete(null)}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Delete Insight?</AlertDialogTitle>
<AlertDialogDescription>
This action cannot be undone. This insight will be permanently deleted.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={deletingInsight}>Cancel</AlertDialogCancel>
<AlertDialogAction asChild>
<Button
onClick={handleDeleteInsight}
disabled={deletingInsight}
variant="destructive"
>
{deletingInsight ? 'Deleting...' : 'Delete'}
</Button>
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div> </div>
) )
} }

View file

@ -1,6 +1,7 @@
'use client' 'use client'
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { useState, useEffect } from 'react'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
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 { FileText } from 'lucide-react' import { FileText } from 'lucide-react'
@ -19,6 +20,7 @@ interface SourceInsightDialogProps {
created?: string created?: string
source_id?: string source_id?: string
} }
onDelete?: (insightId: string) => Promise<void>
} }
export function SourceInsightDialog({ open, onOpenChange, insight }: SourceInsightDialogProps) { export function SourceInsightDialog({ open, onOpenChange, insight }: SourceInsightDialogProps) {
@ -70,35 +72,60 @@ export function SourceInsightDialog({ open, onOpenChange, insight }: SourceInsig
</DialogTitle> </DialogTitle>
</DialogHeader> </DialogHeader>
<div className="flex-1 overflow-y-auto min-h-0"> {showDeleteConfirm ? (
{isLoading ? ( <div className="flex flex-col items-center justify-center py-8 gap-4">
<div className="flex items-center justify-center py-10"> <p className="text-center text-muted-foreground">
<span className="text-sm text-muted-foreground">Loading insight</span> Are you sure you want to delete this insight?<br />
</div> <span className="text-sm">This action cannot be undone.</span>
) : displayInsight ? ( </p>
<div className="prose prose-sm prose-neutral dark:prose-invert max-w-none"> <div className="flex gap-2">
<ReactMarkdown <Button
remarkPlugins={[remarkGfm]} variant="outline"
components={{ onClick={() => setShowDeleteConfirm(false)}
table: ({ children }) => ( disabled={isDeleting}
<div className="my-4 overflow-x-auto">
<table className="min-w-full border-collapse border border-border">{children}</table>
</div>
),
thead: ({ children }) => <thead className="bg-muted">{children}</thead>,
tbody: ({ children }) => <tbody>{children}</tbody>,
tr: ({ children }) => <tr className="border-b border-border">{children}</tr>,
th: ({ children }) => <th className="border border-border px-3 py-2 text-left font-semibold">{children}</th>,
td: ({ children }) => <td className="border border-border px-3 py-2">{children}</td>,
}}
> >
{displayInsight.content} Cancel
</ReactMarkdown> </Button>
<Button
variant="destructive"
onClick={handleDelete}
disabled={isDeleting}
>
{isDeleting ? 'Deleting...' : 'Delete'}
</Button>
</div> </div>
) : ( </div>
<p className="text-sm text-muted-foreground">No insight selected.</p> ) : (
)} <div className="flex-1 overflow-y-auto min-h-0">
</div> {isLoading ? (
<div className="flex items-center justify-center py-10">
<span className="text-sm text-muted-foreground">Loading insight</span>
</div>
) : displayInsight ? (
<div className="prose prose-sm prose-neutral dark:prose-invert max-w-none">
<ReactMarkdown
remarkPlugins={[remarkGfm]}
components={{
table: ({ children }) => (
<div className="my-4 overflow-x-auto">
<table className="min-w-full border-collapse border border-border">{children}</table>
</div>
),
thead: ({ children }) => <thead className="bg-muted">{children}</thead>,
tbody: ({ children }) => <tbody>{children}</tbody>,
tr: ({ children }) => <tr className="border-b border-border">{children}</tr>,
th: ({ children }) => <th className="border border-border px-3 py-2 text-left font-semibold">{children}</th>,
td: ({ children }) => <td className="border border-border px-3 py-2">{children}</td>,
}}
>
{displayInsight.content}
</ReactMarkdown>
</div>
) : (
<p className="text-sm text-muted-foreground">No insight selected.</p>
)}
</div>
)}
</DialogContent> </DialogContent>
</Dialog> </Dialog>
) )

View file

@ -30,5 +30,9 @@ export const insightsApi = {
data data
) )
return response.data return response.data
},
delete: async (insightId: string) => {
await apiClient.delete(`/insights/${insightId}`)
} }
} }