Add AI-powered evidence description generation
Introduce a background worker that automatically generates compliance-focused descriptions for uploaded evidence files using configurable LLM providers. Descriptions are surfaced across all interfaces: GraphQL API, MCP API, CLI, and the console UI. Key changes: - Multi-provider LLM config with per-agent settings (pointer types for Temperature/MaxTokens to preserve zero values) - Evidence description worker with bounded concurrency - EvidenceDescriptionStatus typed enum with PostgreSQL enum type - New `prb evidence` CLI commands (list, view, delete) - Evidence description displayed in console table and preview - Migration only marks evidences without files as completed Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
@@ -7,6 +7,7 @@ export const evidenceFileQuery = graphql`
|
||||
node(id: $evidenceId) {
|
||||
... on Evidence {
|
||||
id
|
||||
description
|
||||
file {
|
||||
mimeType
|
||||
fileName
|
||||
|
||||
@@ -125,39 +125,48 @@ function EvidencePreviewContent({
|
||||
);
|
||||
}
|
||||
|
||||
let preview;
|
||||
|
||||
if (evidence.file.mimeType?.startsWith("image/")) {
|
||||
return (
|
||||
preview = (
|
||||
<img
|
||||
src={evidence.file.downloadUrl}
|
||||
alt={evidence.file.fileName}
|
||||
className="max-h-[70vh] object-contain"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (evidence.file.mimeType?.includes("pdf")) {
|
||||
return (
|
||||
} else if (evidence.file.mimeType?.includes("pdf")) {
|
||||
preview = (
|
||||
<iframe
|
||||
src={evidence.file.downloadUrl}
|
||||
className="w-full h-[70vh]"
|
||||
title={evidence.file.fileName}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
preview = (
|
||||
<div className="flex flex-col items-center gap-2 justify-center">
|
||||
<IconWarning size={20} />
|
||||
<p className="text-txt-secondary text-center">
|
||||
{__("Preview not available for this file type")
|
||||
+ " "
|
||||
+ evidence.file.mimeType}
|
||||
</p>
|
||||
<Button asChild variant="secondary" icon={IconArrowInbox}>
|
||||
<a href={evidence.file.downloadUrl} target="_blank" rel="noreferrer">
|
||||
{__("Download File")}
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-2 justify-center">
|
||||
<IconWarning size={20} />
|
||||
<p className="text-txt-secondary text-center">
|
||||
{__("Preview not available for this file type")
|
||||
+ " "
|
||||
+ evidence.file.mimeType}
|
||||
</p>
|
||||
<Button asChild variant="secondary" icon={IconArrowInbox}>
|
||||
<a href={evidence.file.downloadUrl} target="_blank" rel="noreferrer">
|
||||
{__("Download File")}
|
||||
</a>
|
||||
</Button>
|
||||
<div className="space-y-4">
|
||||
{preview}
|
||||
{evidence.description && (
|
||||
<p className="text-txt-secondary text-sm">{evidence.description}</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fileSize, fileType, formatDate, sprintf } from "@probo/helpers";
|
||||
import { fileSize, formatDate, sprintf } from "@probo/helpers";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import {
|
||||
@@ -79,7 +79,7 @@ export const evidenceFragment = graphql`
|
||||
mimeType
|
||||
size
|
||||
}
|
||||
type
|
||||
description
|
||||
createdAt
|
||||
canDelete: permission(action: "core:evidence:delete")
|
||||
}
|
||||
@@ -123,8 +123,8 @@ export default function MeasureEvidencesTab() {
|
||||
<SortableTable {...pagination}>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{__("Evidence name")}</Th>
|
||||
<Th>{__("Type")}</Th>
|
||||
<Th>{__("Description")}</Th>
|
||||
<Th>{__("File type")}</Th>
|
||||
<Th>{__("File size")}</Th>
|
||||
<Th>{__("Created at")}</Th>
|
||||
<Th width={50}></Th>
|
||||
@@ -248,13 +248,12 @@ function EvidenceRow(props: {
|
||||
/>
|
||||
)}
|
||||
<Tr to={evidenceUrl}>
|
||||
<Td>{evidence.file?.fileName}</Td>
|
||||
<Td>
|
||||
{fileType(__, {
|
||||
type: evidence.type,
|
||||
mimeType: evidence.file?.mimeType || "",
|
||||
})}
|
||||
<span className="text-txt-secondary text-sm line-clamp-2">
|
||||
{evidence.description || "—"}
|
||||
</span>
|
||||
</Td>
|
||||
<Td>{evidence.file?.mimeType || "—"}</Td>
|
||||
<Td>{fileSize(__, evidence.file?.size || 0)}</Td>
|
||||
<Td>{formatDate(evidence.createdAt)}</Td>
|
||||
<Td noLink>
|
||||
|
||||
Reference in New Issue
Block a user