+ See how AI search cites any brand name or domain. +
++ Ask any prompt across ChatGPT, Claude, Gemini, and Perplexity + side-by-side. +
+{description}
++ {body} +
+{row.original.domain}
+ ) : null} + > + ), + }), + pagesHelper.accessor("platform", { + id: "platform", + header: () => Platform, + enableSorting: false, + cell: ({ getValue }) =>{row.original.question}
+ {row.original.brandsMentioned.length > 0 ? ( ++ Brands: {row.original.brandsMentioned.slice(0, 5).join(", ")} +
+ ) : null} + > + ), + }), + queriesHelper.accessor("platform", { + id: "platform", + header: () => Platform, + enableSorting: false, + cell: ({ getValue }) =>+ No cited pages returned. +
+ ); + } + + return+ No matching queries found. +
+ ); + } + + return| + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + | + ); + })} +
|---|
| + {flexRender(cell.column.columnDef.cell, cell.getContext())} + | + ); + })} +
+ {title} +
++ Platform +
+Refine results
+ {current.activeFilterCount > 0 ? ( + + {current.activeFilterCount} active + + ) : null} +{item.query}
+ )} + /> + ); +} diff --git a/src/client/features/ai-search/components/BrandLookupMentionTrendCard.tsx b/src/client/features/ai-search/components/BrandLookupMentionTrendCard.tsx new file mode 100644 index 0000000..38cd99b --- /dev/null +++ b/src/client/features/ai-search/components/BrandLookupMentionTrendCard.tsx @@ -0,0 +1,95 @@ +import { useMemo } from "react"; +import { + CartesianGrid, + Line, + LineChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from "recharts"; +import { formatCount } from "@/client/features/ai-search/platformLabels"; +import type { BrandLookupResult } from "@/types/schemas/ai-search"; + +type Props = { + result: BrandLookupResult; +}; + +export function BrandLookupMentionTrendCard({ result }: Props) { + const chartData = useMemo( + () => + result.monthlyVolume.map((entry) => ({ + label: `${entry.year}-${String(entry.month).padStart(2, "0")}`, + volume: entry.volume ?? 0, + })), + [result.monthlyVolume], + ); + + if (chartData.length === 0) { + return ( +{label}
++ {formatCount(payload[0].value)} mentions +
++ Note: {formatPlatformList(erroredPlatforms.map((p) => p.platform))}{" "} + {erroredPlatforms.length === 1 ? "was" : "were"} unavailable — some + mentions may be missing. +
+ ) : null} ++ Updated {formatRelative(result.fetchedAt)} +
+
+ {label}
+
+
+ {formatCount(total)} +
++ {validationError} +
+ ) : null} + ++ Est.{" "} + + ${BRAND_LOOKUP_DISPLAYED_COST_USD.toFixed(2)} + +
++ Model returned an empty response. +
+ ); + } + + const isCollapsed = needsCollapse && !expanded; + + return ( +
+ {text}
+
+ {children}
+ ), + ul: ({ children }: { children?: ReactNode }) => ( ++ {children} ++ ), + hr: () =>
{children};
+ }
+ return (
+
+ {children}
+
+ );
+ },
+ pre: ({ children }: { children?: ReactNode }) => (
+
+ {children}
+
+ ),
+ table: ({ children }: { children?: ReactNode }) => (
+ + {item.prompt} +
++ {item.models.map(formatModelLabel).join(", ")} +
+ > + )} + /> + ); +} diff --git a/src/client/features/ai-search/components/PromptExplorerLoadingState.tsx b/src/client/features/ai-search/components/PromptExplorerLoadingState.tsx new file mode 100644 index 0000000..9e1e39d --- /dev/null +++ b/src/client/features/ai-search/components/PromptExplorerLoadingState.tsx @@ -0,0 +1,32 @@ +type Props = { + modelCount: number; +}; + +export function PromptExplorerLoadingState({ modelCount }: Props) { + const count = Math.max(1, modelCount); + return ( ++ Cited sources ({modelResult.citations.length}) +
++ Related queries the model considered +
+{modelName}
+ ) : null}
+ {status === "error" ? (
+ Error
+ ) : null}
+ + {emptyMessage} +
+| + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + | + ))} +||||||
|---|---|---|---|---|---|---|
| {row.domain ?? "-"} | -{formatNumber(row.backlinks)} | -{formatNumber(row.referringPages)} | -{formatNumber(row.rank)} | -{formatDecimal(row.spamScore)} | -{formatCompactDate(row.firstSeen)} | -
-
-
- Broken links: {formatNumber(row.brokenBacklinks)}
-
- Broken pages: {formatNumber(row.brokenPages)}
-
- |
+ {table.getRowModel().rows.map((row) => (
+
| + {flexRender(cell.column.columnDef.cell, cell.getContext())} + | + ))}
| + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + | + ))} +||||
|---|---|---|---|---|
|
- {row.page ? (
- |
- {formatNumber(row.backlinks)} | -{formatNumber(row.referringDomains)} | -{formatNumber(row.rank)} | -{formatNumber(row.brokenBacklinks)} | + {table.getRowModel().rows.map((row) => ( +
| + {flexRender(cell.column.columnDef.cell, cell.getContext())} + | + ))}