Improve audit pipeline and outreach review
This commit is contained in:
@@ -10,6 +10,7 @@ import { Badge } from "@/components/ui/badge";
|
||||
import { Globe } from "lucide-react";
|
||||
|
||||
type UsedSkill = {
|
||||
id?: string;
|
||||
name: string;
|
||||
purpose?: string;
|
||||
category?: string;
|
||||
@@ -17,6 +18,12 @@ type UsedSkill = {
|
||||
version?: string;
|
||||
};
|
||||
|
||||
type SkillSummary = {
|
||||
name: string;
|
||||
purpose: string;
|
||||
summary: string;
|
||||
};
|
||||
|
||||
type LeadContext = {
|
||||
_id: Id<"leads">;
|
||||
companyName?: string;
|
||||
@@ -35,9 +42,35 @@ type SkillAwareAudit = {
|
||||
createdAt?: number;
|
||||
updatedAt?: number;
|
||||
usedSkills?: UsedSkill[];
|
||||
skillSummaries?: SkillSummary[];
|
||||
internalSummary?: string | null;
|
||||
};
|
||||
|
||||
type AuditFindingEvidenceRef = {
|
||||
id: string;
|
||||
type:
|
||||
| "crawl_page"
|
||||
| "technical_check"
|
||||
| "screenshot"
|
||||
| "pagespeed"
|
||||
| "jina_excerpt"
|
||||
| "generation_stage";
|
||||
label: string;
|
||||
sourceUrl?: string;
|
||||
};
|
||||
|
||||
type AuditFinding = {
|
||||
_id: string;
|
||||
skillId: string;
|
||||
claim: string;
|
||||
recommendation: string;
|
||||
customerBenefit: string;
|
||||
severity: 1 | 2 | 3;
|
||||
confidence: number;
|
||||
evidenceRefs: AuditFindingEvidenceRef[];
|
||||
reviewStatus: "pending" | "accepted" | "rejected";
|
||||
};
|
||||
|
||||
type CheckedPageScreenshot = {
|
||||
id: Id<"_storage">;
|
||||
url: string;
|
||||
@@ -69,6 +102,7 @@ type CheckedPageEvidence = {
|
||||
type AuditDetailResult = {
|
||||
audit: SkillAwareAudit;
|
||||
lead: LeadContext | null;
|
||||
findings: AuditFinding[];
|
||||
sourceSummaries: {
|
||||
checkedPages: CheckedPageEvidence[];
|
||||
};
|
||||
@@ -121,6 +155,19 @@ function metaSignalText(page: CheckedPageEvidence) {
|
||||
return "Unbekannt";
|
||||
}
|
||||
|
||||
function evidenceTypeLabel(type: AuditFindingEvidenceRef["type"]) {
|
||||
const labels: Record<AuditFindingEvidenceRef["type"], string> = {
|
||||
crawl_page: "Crawl",
|
||||
technical_check: "Technik",
|
||||
screenshot: "Screenshot",
|
||||
pagespeed: "PageSpeed",
|
||||
jina_excerpt: "Reader",
|
||||
generation_stage: "KI-Stufe",
|
||||
};
|
||||
|
||||
return labels[type] ?? type;
|
||||
}
|
||||
|
||||
function leadSummary(lead: LeadContext | null | undefined) {
|
||||
if (!lead) {
|
||||
return "Kein Lead-Kontext gespeichert";
|
||||
@@ -156,7 +203,20 @@ export function AuditDetail({ id }: { id: string | Id<"audits"> }) {
|
||||
const audit = result?.audit;
|
||||
const lead = result?.lead;
|
||||
|
||||
const usedSkills = useMemo(() => audit?.usedSkills ?? [], [audit]);
|
||||
const usedSkills = useMemo(() => {
|
||||
const summaries = audit?.skillSummaries ?? [];
|
||||
return (audit?.usedSkills ?? []).map((skill) => {
|
||||
const summary = summaries.find(
|
||||
(candidate) => candidate.name === skill.name || candidate.name === skill.id,
|
||||
);
|
||||
return {
|
||||
...skill,
|
||||
purpose: summary?.purpose ?? skill.purpose,
|
||||
summary: summary?.summary,
|
||||
};
|
||||
});
|
||||
}, [audit]);
|
||||
const findings = useMemo(() => result?.findings ?? [], [result]);
|
||||
const checkedPageEvidence = useMemo(
|
||||
() => result?.sourceSummaries.checkedPages ?? [],
|
||||
[result],
|
||||
@@ -220,6 +280,56 @@ export function AuditDetail({ id }: { id: string | Id<"audits"> }) {
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Geprüfte Befunde</CardTitle>
|
||||
<CardDescription>
|
||||
Verifizierte Aussagen mit konkreten Belegen aus Crawl, Screenshots und Messungen.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{findings.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Noch keine verifizierten Befunde gespeichert.
|
||||
</p>
|
||||
) : (
|
||||
<ul className="grid gap-3">
|
||||
{findings.map((finding) => (
|
||||
<li className="rounded-md border p-3 text-sm" key={finding._id}>
|
||||
<div className="flex flex-wrap items-start justify-between gap-2">
|
||||
<div className="min-w-0">
|
||||
<p className="font-medium">{finding.claim}</p>
|
||||
<p className="mt-2 text-muted-foreground">
|
||||
{finding.customerBenefit}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
<Badge variant={finding.severity === 3 ? "secondary" : "outline"}>
|
||||
Priorität {finding.severity}
|
||||
</Badge>
|
||||
<Badge variant="outline">
|
||||
{Math.round(finding.confidence * 100)}% sicher
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<p className="mt-3">
|
||||
<span className="font-medium">Empfehlung: </span>
|
||||
{finding.recommendation}
|
||||
</p>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{finding.evidenceRefs.map((ref) => (
|
||||
<Badge variant="outline" key={ref.id}>
|
||||
Quelle: {evidenceTypeLabel(ref.type)} · {ref.label}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Geprüfte Seiten</CardTitle>
|
||||
@@ -322,6 +432,9 @@ export function AuditDetail({ id }: { id: string | Id<"audits"> }) {
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{skill.purpose ?? "Keine Zweckbeschreibung"}
|
||||
</p>
|
||||
{"summary" in skill && skill.summary ? (
|
||||
<p className="text-sm text-muted-foreground">{skill.summary}</p>
|
||||
) : null}
|
||||
<p className="mt-1 inline-flex flex-wrap items-center gap-1">
|
||||
{skill.category ? <Badge variant="outline">{skill.category}</Badge> : null}
|
||||
{skill.version ? <Badge variant="outline">{skill.version}</Badge> : null}
|
||||
|
||||
Reference in New Issue
Block a user