Merge pull request #334 from jflo/feat/delete-insight
feat: add delete insight functionality
This commit is contained in:
commit
86329f406d
3 changed files with 132 additions and 29 deletions
|
|
@ -24,6 +24,16 @@ import {
|
|||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
|
|
@ -80,6 +90,8 @@ export function SourceDetailContent({
|
|||
const [isDownloadingFile, setIsDownloadingFile] = useState(false)
|
||||
const [fileAvailable, setFileAvailable] = useState<boolean | 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 () => {
|
||||
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) => {
|
||||
if (!source || title === source.title) return
|
||||
|
||||
|
|
@ -573,10 +603,18 @@ export function SourceDetailContent({
|
|||
<p className="mt-2 text-sm text-muted-foreground">
|
||||
{insight.content.slice(0, 180)}{insight.content.length > 180 ? '…' : ''}
|
||||
</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)}>
|
||||
View Insight
|
||||
</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>
|
||||
))}
|
||||
|
|
@ -743,7 +781,41 @@ export function SourceDetailContent({
|
|||
}
|
||||
}}
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'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 { Button } from '@/components/ui/button'
|
||||
import { FileText } from 'lucide-react'
|
||||
|
|
@ -19,6 +20,7 @@ interface SourceInsightDialogProps {
|
|||
created?: string
|
||||
source_id?: string
|
||||
}
|
||||
onDelete?: (insightId: string) => Promise<void>
|
||||
}
|
||||
|
||||
export function SourceInsightDialog({ open, onOpenChange, insight }: SourceInsightDialogProps) {
|
||||
|
|
@ -70,35 +72,60 @@ export function SourceInsightDialog({ open, onOpenChange, insight }: SourceInsig
|
|||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="flex-1 overflow-y-auto min-h-0">
|
||||
{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>,
|
||||
}}
|
||||
{showDeleteConfirm ? (
|
||||
<div className="flex flex-col items-center justify-center py-8 gap-4">
|
||||
<p className="text-center text-muted-foreground">
|
||||
Are you sure you want to delete this insight?<br />
|
||||
<span className="text-sm">This action cannot be undone.</span>
|
||||
</p>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowDeleteConfirm(false)}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{displayInsight.content}
|
||||
</ReactMarkdown>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleDelete}
|
||||
disabled={isDeleting}
|
||||
>
|
||||
{isDeleting ? 'Deleting...' : 'Delete'}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">No insight selected.</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex-1 overflow-y-auto min-h-0">
|
||||
{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>
|
||||
</Dialog>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -30,5 +30,9 @@ export const insightsApi = {
|
|||
data
|
||||
)
|
||||
return response.data
|
||||
},
|
||||
|
||||
delete: async (insightId: string) => {
|
||||
await apiClient.delete(`/insights/${insightId}`)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue