feat: Implement analysis job tracking with progress timeline and enhanced data source status management.
This commit is contained in:
@@ -9,6 +9,7 @@ import { Badge } from "@/components/ui/badge"
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useMutation } from "convex/react"
|
||||
import { Progress } from "@/components/ui/progress"
|
||||
|
||||
export default function Page() {
|
||||
const { selectedProjectId } = useProject()
|
||||
@@ -21,8 +22,13 @@ export default function Page() {
|
||||
api.projects.getSearchContext,
|
||||
selectedProjectId ? { projectId: selectedProjectId as any } : "skip"
|
||||
)
|
||||
const analysisJobs = useQuery(
|
||||
api.analysisJobs.listByProject,
|
||||
selectedProjectId ? { projectId: selectedProjectId as any } : "skip"
|
||||
)
|
||||
const updateDataSourceStatus = useMutation(api.dataSources.updateDataSourceStatus)
|
||||
const createAnalysis = useMutation(api.analyses.createAnalysis)
|
||||
const createAnalysisJob = useMutation(api.analysisJobs.create)
|
||||
const [reanalyzingId, setReanalyzingId] = useState<string | null>(null)
|
||||
const analysis = useQuery(
|
||||
api.analyses.getLatestByProject,
|
||||
@@ -75,10 +81,15 @@ export default function Page() {
|
||||
})
|
||||
|
||||
try {
|
||||
const jobId = await createAnalysisJob({
|
||||
projectId: selectedProjectId as any,
|
||||
dataSourceId: source._id,
|
||||
})
|
||||
|
||||
const response = await fetch("/api/analyze", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ url: source.url }),
|
||||
body: JSON.stringify({ url: source.url, jobId }),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
@@ -114,10 +125,10 @@ export default function Page() {
|
||||
<div className="flex flex-1 flex-col gap-6 p-4 lg:p-8">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<h1 className="text-2xl font-semibold">{analysis.productName}</h1>
|
||||
{selectedProject?.name && (
|
||||
<Badge variant="outline">{selectedProject.name}</Badge>
|
||||
)}
|
||||
<h1 className="text-2xl font-semibold">
|
||||
{selectedProject?.name || analysis.productName}
|
||||
</h1>
|
||||
<Badge variant="outline">{analysis.productName}</Badge>
|
||||
</div>
|
||||
<p className="text-muted-foreground">{analysis.tagline}</p>
|
||||
<p className="max-w-3xl text-sm text-muted-foreground">{analysis.description}</p>
|
||||
@@ -126,7 +137,15 @@ export default function Page() {
|
||||
{searchContext?.missingSources?.length > 0 && (
|
||||
<Alert>
|
||||
<AlertDescription>
|
||||
Some selected sources don't have analysis yet. Run onboarding or re-analyze them for best results.
|
||||
Some selected sources don't have analysis yet:{" "}
|
||||
{searchContext.missingSources
|
||||
.map((missing: any) =>
|
||||
dataSources?.find((source: any) => source._id === missing.sourceId)?.name ||
|
||||
dataSources?.find((source: any) => source._id === missing.sourceId)?.url ||
|
||||
missing.sourceId
|
||||
)
|
||||
.join(", ")}
|
||||
. Run onboarding or re-analyze them for best results.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
@@ -197,6 +216,56 @@ export default function Page() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{analysisJobs && analysisJobs.length > 0 && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-base">Analysis Jobs</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2 text-sm text-muted-foreground">
|
||||
{analysisJobs.slice(0, 5).map((job: any) => {
|
||||
const sourceName = dataSources?.find((source: any) => source._id === job.dataSourceId)?.name
|
||||
|| dataSources?.find((source: any) => source._id === job.dataSourceId)?.url
|
||||
|| "Unknown source"
|
||||
return (
|
||||
<div key={job._id} className="space-y-2 rounded-md border border-border p-3">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="truncate">
|
||||
<span className="font-medium text-foreground">{sourceName}</span>{" "}
|
||||
<span className="text-muted-foreground">
|
||||
({job.status})
|
||||
</span>
|
||||
</div>
|
||||
{job.status === "failed" && (
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
const source = dataSources?.find((s: any) => s._id === job.dataSourceId)
|
||||
if (source) void handleReanalyze(source)
|
||||
}}
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{(job.status === "running" || job.status === "pending") && (
|
||||
<div className="space-y-1">
|
||||
<Progress value={typeof job.progress === "number" ? job.progress : 10} />
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{typeof job.progress === "number" ? `${job.progress}% complete` : "Starting..."}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{job.status === "failed" && job.error && (
|
||||
<div className="text-xs text-destructive">{job.error}</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{searchContext?.context && (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
|
||||
Reference in New Issue
Block a user