@prenta/admin 1.15.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/__tests__/components/faq-proposal-preview.render.test.d.ts +2 -0
- package/dist/__tests__/components/faq-proposal-preview.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/faq-proposal-preview.render.test.js +97 -0
- package/dist/__tests__/components/faq-proposal-preview.render.test.js.map +1 -0
- package/dist/__tests__/components/seo-issue-fix-panel.render.test.js +37 -11
- package/dist/__tests__/components/seo-issue-fix-panel.render.test.js.map +1 -1
- package/dist/__tests__/lib/seo-service-errors.test.js +3 -0
- package/dist/__tests__/lib/seo-service-errors.test.js.map +1 -1
- package/dist/__tests__/lib/seo-service-geo.test.d.ts +2 -0
- package/dist/__tests__/lib/seo-service-geo.test.d.ts.map +1 -0
- package/dist/__tests__/lib/seo-service-geo.test.js +103 -0
- package/dist/__tests__/lib/seo-service-geo.test.js.map +1 -0
- package/dist/__tests__/lib/seo-service-proposals.test.js +24 -0
- package/dist/__tests__/lib/seo-service-proposals.test.js.map +1 -1
- package/dist/__tests__/router/seo-tab-for-path.test.js +4 -0
- package/dist/__tests__/router/seo-tab-for-path.test.js.map +1 -1
- package/dist/__tests__/views/seo-ai-visibility.render.test.d.ts +2 -0
- package/dist/__tests__/views/seo-ai-visibility.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/seo-ai-visibility.render.test.js +244 -0
- package/dist/__tests__/views/seo-ai-visibility.render.test.js.map +1 -0
- package/dist/__tests__/views/seo-geo-settings-card.render.test.d.ts +2 -0
- package/dist/__tests__/views/seo-geo-settings-card.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/seo-geo-settings-card.render.test.js +227 -0
- package/dist/__tests__/views/seo-geo-settings-card.render.test.js.map +1 -0
- package/dist/__tests__/views/seo-proposals-tab.render.test.js +277 -0
- package/dist/__tests__/views/seo-proposals-tab.render.test.js.map +1 -1
- package/dist/__tests__/views/seo-settings.render.test.js +16 -4
- package/dist/__tests__/views/seo-settings.render.test.js.map +1 -1
- package/dist/__tests__/views/seo-technical-tab.render.test.js +13 -0
- package/dist/__tests__/views/seo-technical-tab.render.test.js.map +1 -1
- package/dist/components/seo/FaqProposalPreview.d.ts +39 -0
- package/dist/components/seo/FaqProposalPreview.d.ts.map +1 -0
- package/dist/components/seo/FaqProposalPreview.js +62 -0
- package/dist/components/seo/FaqProposalPreview.js.map +1 -0
- package/dist/components/seo/SeoIssueFixPanel.d.ts.map +1 -1
- package/dist/components/seo/SeoIssueFixPanel.js +7 -2
- package/dist/components/seo/SeoIssueFixPanel.js.map +1 -1
- package/dist/lib/seo-service.d.ts +155 -2
- package/dist/lib/seo-service.d.ts.map +1 -1
- package/dist/lib/seo-service.js +42 -2
- package/dist/lib/seo-service.js.map +1 -1
- package/dist/prenta-admin.css +1 -1
- package/dist/views/SEO.d.ts +5 -1
- package/dist/views/SEO.d.ts.map +1 -1
- package/dist/views/SEO.js +12 -7
- package/dist/views/SEO.js.map +1 -1
- package/dist/views/seo/AiVisibilityTab.d.ts +4 -0
- package/dist/views/seo/AiVisibilityTab.d.ts.map +1 -0
- package/dist/views/seo/AiVisibilityTab.js +165 -0
- package/dist/views/seo/AiVisibilityTab.js.map +1 -0
- package/dist/views/seo/ProposalsTab.d.ts.map +1 -1
- package/dist/views/seo/ProposalsTab.js +160 -7
- package/dist/views/seo/ProposalsTab.js.map +1 -1
- package/dist/views/seo/TechnicalTab.d.ts +3 -1
- package/dist/views/seo/TechnicalTab.d.ts.map +1 -1
- package/dist/views/seo/TechnicalTab.js +4 -4
- package/dist/views/seo/TechnicalTab.js.map +1 -1
- package/dist/views/settings/SeoAutopilotCard.d.ts.map +1 -1
- package/dist/views/settings/SeoAutopilotCard.js +6 -0
- package/dist/views/settings/SeoAutopilotCard.js.map +1 -1
- package/dist/views/settings/SeoGeoSettingsCard.d.ts +12 -0
- package/dist/views/settings/SeoGeoSettingsCard.d.ts.map +1 -0
- package/dist/views/settings/SeoGeoSettingsCard.js +178 -0
- package/dist/views/settings/SeoGeoSettingsCard.js.map +1 -0
- package/dist/views/settings/SeoSettingsTab.d.ts.map +1 -1
- package/dist/views/settings/SeoSettingsTab.js +2 -1
- package/dist/views/settings/SeoSettingsTab.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/components/faq-proposal-preview.render.test.tsx +137 -0
- package/src/__tests__/components/seo-issue-fix-panel.render.test.tsx +42 -11
- package/src/__tests__/lib/seo-service-errors.test.ts +4 -0
- package/src/__tests__/lib/seo-service-geo.test.ts +118 -0
- package/src/__tests__/lib/seo-service-proposals.test.ts +30 -0
- package/src/__tests__/router/seo-tab-for-path.test.ts +5 -0
- package/src/__tests__/views/seo-ai-visibility.render.test.tsx +279 -0
- package/src/__tests__/views/seo-geo-settings-card.render.test.tsx +253 -0
- package/src/__tests__/views/seo-proposals-tab.render.test.tsx +358 -0
- package/src/__tests__/views/seo-settings.render.test.tsx +28 -7
- package/src/__tests__/views/seo-technical-tab.render.test.tsx +15 -0
- package/src/components/seo/FaqProposalPreview.tsx +134 -0
- package/src/components/seo/SeoIssueFixPanel.tsx +7 -2
- package/src/lib/seo-service.ts +162 -1
- package/src/views/SEO.tsx +15 -6
- package/src/views/seo/AiVisibilityTab.tsx +616 -0
- package/src/views/seo/ProposalsTab.tsx +279 -13
- package/src/views/seo/TechnicalTab.tsx +27 -8
- package/src/views/settings/SeoAutopilotCard.tsx +6 -0
- package/src/views/settings/SeoGeoSettingsCard.tsx +310 -0
- package/src/views/settings/SeoSettingsTab.tsx +2 -0
package/dist/views/SEO.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
/**
|
|
4
|
-
* SEO Operations Center —
|
|
5
|
-
* Links / Technical / Redirects / Audit / Proposals). Tab +
|
|
6
|
-
* driven by the `?tab=` query string so audits, issues,
|
|
7
|
-
* and content editors are all linkable. All data flows
|
|
8
|
-
* `lib/seo-service.ts`.
|
|
4
|
+
* SEO Operations Center — eight-tab workflow surface (Overview / Content /
|
|
5
|
+
* Links / Technical / AI visibility / Redirects / Audit / Proposals). Tab +
|
|
6
|
+
* deep-link state is driven by the `?tab=` query string so audits, issues,
|
|
7
|
+
* redirects, proposals, and content editors are all linkable. All data flows
|
|
8
|
+
* through `lib/seo-service.ts`.
|
|
9
9
|
*/
|
|
10
10
|
import * as Tabs from '@radix-ui/react-tabs';
|
|
11
|
-
import { LayoutDashboard, FileText, Settings2, ArrowRightLeft, ClipboardCheck, RefreshCw, Bot, Link2, Inbox, } from 'lucide-react';
|
|
11
|
+
import { LayoutDashboard, FileText, Settings2, ArrowRightLeft, ClipboardCheck, RefreshCw, Bot, Link2, Inbox, Radar, } from 'lucide-react';
|
|
12
12
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
13
13
|
import { toast } from 'sonner';
|
|
14
14
|
import { parseRouteQuery } from '../router/index.js';
|
|
@@ -25,11 +25,13 @@ import { RedirectsTab } from './seo/RedirectsTab.js';
|
|
|
25
25
|
import { AuditTab } from './seo/AuditTab.js';
|
|
26
26
|
import { LinksTab } from './seo/LinksTab.js';
|
|
27
27
|
import { ProposalsTab } from './seo/ProposalsTab.js';
|
|
28
|
+
import { AiVisibilityTab } from './seo/AiVisibilityTab.js';
|
|
28
29
|
const TABS = [
|
|
29
30
|
{ id: 'overview', label: 'Overview', icon: LayoutDashboard },
|
|
30
31
|
{ id: 'content', label: 'Content SEO', icon: FileText },
|
|
31
32
|
{ id: 'links', label: 'Links', icon: Link2 },
|
|
32
33
|
{ id: 'technical', label: 'Technical', icon: Settings2 },
|
|
34
|
+
{ id: 'ai-visibility', label: 'AI visibility', icon: Radar },
|
|
33
35
|
{ id: 'redirects', label: 'Redirects', icon: ArrowRightLeft },
|
|
34
36
|
{ id: 'audit', label: 'Audit', icon: ClipboardCheck },
|
|
35
37
|
{ id: 'proposals', label: 'Proposals', icon: Inbox },
|
|
@@ -54,6 +56,7 @@ export const SEO_DEEP_PATHS = [
|
|
|
54
56
|
'/seo/links',
|
|
55
57
|
'/seo/audit',
|
|
56
58
|
'/seo/proposals',
|
|
59
|
+
'/seo/ai-visibility',
|
|
57
60
|
'/seo',
|
|
58
61
|
];
|
|
59
62
|
/** Map an SEO deep path to the tab it should open, or null if it isn't one. */
|
|
@@ -75,6 +78,8 @@ export function seoTabForPath(pathname) {
|
|
|
75
78
|
return 'audit';
|
|
76
79
|
case '/seo/proposals':
|
|
77
80
|
return 'proposals';
|
|
81
|
+
case '/seo/ai-visibility':
|
|
82
|
+
return 'ai-visibility';
|
|
78
83
|
case '/seo':
|
|
79
84
|
return 'overview';
|
|
80
85
|
default:
|
|
@@ -162,6 +167,6 @@ export function SEO({ onNavigate, initialTab = 'overview' }) {
|
|
|
162
167
|
? 'bg-muted text-muted-foreground'
|
|
163
168
|
: 'bg-destructive/10 text-destructive';
|
|
164
169
|
return (_jsxs(Tabs.Trigger, { value: t.id, className: "text-muted-foreground hover:text-foreground data-[state=active]:text-foreground data-[state=active]:border-primary focus-visible:ring-ring -mb-px flex items-center gap-2 border-b-2 border-transparent px-3 py-2.5 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none", children: [_jsx(Icon, { className: "h-4 w-4", "aria-hidden": true }), t.label, badge > 0 && (_jsx("span", { className: `${badgeClass} ml-1 rounded-full px-1.5 py-0.5 text-xs font-medium tabular-nums`, children: badge }))] }, t.id));
|
|
165
|
-
}) }), _jsx("div", { className: "pt-6", children: _jsxs(ErrorBoundary, { children: [_jsx(Tabs.Content, { value: "overview", tabIndex: -1, children: activeTab === 'overview' && (_jsx(OverviewTab, { resource: overviewResource, onOpenIssue: (issueId) => onNavigate?.(`/seo?tab=audit&issueId=${issueId}`) })) }), _jsx(Tabs.Content, { value: "content", tabIndex: -1, children: activeTab === 'content' && (_jsx(ContentTab, { initialEntityType: deepLink.entityType, initialEntityId: deepLink.entityId, onNavigate: onNavigate })) }), _jsx(Tabs.Content, { value: "links", tabIndex: -1, children: activeTab === 'links' && _jsx(LinksTab, { onNavigate: onNavigate, planInfo: planInfo }) }), _jsx(Tabs.Content, { value: "technical", tabIndex: -1, children: activeTab === 'technical' && _jsx(TechnicalTab, {}) }), _jsx(Tabs.Content, { value: "redirects", tabIndex: -1, children: activeTab === 'redirects' && _jsx(RedirectsTab, { onNavigate: onNavigate }) }), _jsx(Tabs.Content, { value: "audit", tabIndex: -1, children: activeTab === 'audit' && (_jsx(AuditTab, { refetchKey: refetchKey, initialIssueId: deepLink.issueId, onNavigate: onNavigate, planInfo: planInfo })) }), _jsx(Tabs.Content, { value: "proposals", tabIndex: -1, children: activeTab === 'proposals' && (_jsx(ProposalsTab, { planInfo: planInfo, onChanged: () => setRefetchKey((k) => k + 1), onNavigate: onNavigate })) })] }) })] })] }));
|
|
170
|
+
}) }), _jsx("div", { className: "pt-6", children: _jsxs(ErrorBoundary, { children: [_jsx(Tabs.Content, { value: "overview", tabIndex: -1, children: activeTab === 'overview' && (_jsx(OverviewTab, { resource: overviewResource, onOpenIssue: (issueId) => onNavigate?.(`/seo?tab=audit&issueId=${issueId}`) })) }), _jsx(Tabs.Content, { value: "content", tabIndex: -1, children: activeTab === 'content' && (_jsx(ContentTab, { initialEntityType: deepLink.entityType, initialEntityId: deepLink.entityId, onNavigate: onNavigate })) }), _jsx(Tabs.Content, { value: "links", tabIndex: -1, children: activeTab === 'links' && _jsx(LinksTab, { onNavigate: onNavigate, planInfo: planInfo }) }), _jsx(Tabs.Content, { value: "technical", tabIndex: -1, children: activeTab === 'technical' && _jsx(TechnicalTab, { onNavigate: onNavigate }) }), _jsx(Tabs.Content, { value: "ai-visibility", tabIndex: -1, children: activeTab === 'ai-visibility' && _jsx(AiVisibilityTab, { onNavigate: onNavigate }) }), _jsx(Tabs.Content, { value: "redirects", tabIndex: -1, children: activeTab === 'redirects' && _jsx(RedirectsTab, { onNavigate: onNavigate }) }), _jsx(Tabs.Content, { value: "audit", tabIndex: -1, children: activeTab === 'audit' && (_jsx(AuditTab, { refetchKey: refetchKey, initialIssueId: deepLink.issueId, onNavigate: onNavigate, planInfo: planInfo })) }), _jsx(Tabs.Content, { value: "proposals", tabIndex: -1, children: activeTab === 'proposals' && (_jsx(ProposalsTab, { planInfo: planInfo, onChanged: () => setRefetchKey((k) => k + 1), onNavigate: onNavigate })) })] }) })] })] }));
|
|
166
171
|
}
|
|
167
172
|
//# sourceMappingURL=SEO.js.map
|
package/dist/views/SEO.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SEO.js","sourceRoot":"","sources":["../../src/views/SEO.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;;;GAMG;AACH,OAAO,KAAK,IAAI,MAAM,sBAAsB,CAAA;AAC5C,OAAO,EACL,eAAe,EACf,QAAQ,EACR,SAAS,EACT,cAAc,EACd,cAAc,EACd,SAAS,EACT,GAAG,EACH,KAAK,EACL,KAAK,GACN,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACjE,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAA;AAC9D,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACrE,OAAO,EAAE,aAAa,EAAiB,MAAM,gBAAgB,CAAA;AAC7D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAA;AACrF,OAAO,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAA;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAA;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"SEO.js","sourceRoot":"","sources":["../../src/views/SEO.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AiVisibilityTab.d.ts","sourceRoot":"","sources":["../../../src/views/seo/AiVisibilityTab.tsx"],"names":[],"mappings":"AAqfA,wBAAgB,eAAe,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,kDAkHtF"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* SEO → AI visibility (spec 2026-09-08 §6): daily AI/search crawler trend,
|
|
5
|
+
* AI-engine referral visits, and the pages behind both. Data is self-reported
|
|
6
|
+
* by the site middleware via `POST /geo/beacon`; the tab says so. Everything
|
|
7
|
+
* flows through `fetchSeoGeo` in `lib/seo-service.ts`.
|
|
8
|
+
*/
|
|
9
|
+
import { useMemo, useState } from 'react';
|
|
10
|
+
import { Bar, BarChart, CartesianGrid, Legend, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts';
|
|
11
|
+
import { Bot, ExternalLink, Minus, RefreshCw, ShieldAlert, ShieldCheck, ShieldOff, TrendingDown, TrendingUp, } from 'lucide-react';
|
|
12
|
+
import { fetchSeoGeo, } from '../../lib/seo-service.js';
|
|
13
|
+
import { sourceEditPath } from '../../lib/collection-routes.js';
|
|
14
|
+
import { SectionCard, SeoEmptyState, SeoErrorState, SeoLoading, btnSecondary, } from '../../components/seo/primitives.js';
|
|
15
|
+
import { relativeAge } from './relative-age.js';
|
|
16
|
+
import { useSeoResource } from './useSeoResource.js';
|
|
17
|
+
const WINDOWS = [
|
|
18
|
+
{ days: 7, label: '7 days' },
|
|
19
|
+
{ days: 28, label: '28 days' },
|
|
20
|
+
{ days: 90, label: '90 days' },
|
|
21
|
+
];
|
|
22
|
+
const CATEGORY_LABEL = {
|
|
23
|
+
training: 'AI training',
|
|
24
|
+
retrieval: 'AI retrieval',
|
|
25
|
+
search: 'Search',
|
|
26
|
+
};
|
|
27
|
+
/** Bots drawn as their own stacked series; the rest fold into "Other". */
|
|
28
|
+
const CHART_SERIES = 5;
|
|
29
|
+
/**
|
|
30
|
+
* recharts paints SVG `fill`s, which Tailwind utilities cannot reach, so the
|
|
31
|
+
* chart tokens are referenced as CSS variables. They are the theme's own
|
|
32
|
+
* `--chart-*` series colours and follow dark mode.
|
|
33
|
+
*/
|
|
34
|
+
const CHART_TOKENS = [
|
|
35
|
+
'var(--chart-1)',
|
|
36
|
+
'var(--chart-2)',
|
|
37
|
+
'var(--chart-3)',
|
|
38
|
+
'var(--chart-4)',
|
|
39
|
+
'var(--chart-5)',
|
|
40
|
+
];
|
|
41
|
+
const CHART_OTHER_TOKEN = 'var(--muted-foreground)';
|
|
42
|
+
const CHART_TOOLTIP_STYLE = {
|
|
43
|
+
background: 'var(--card)',
|
|
44
|
+
border: '1px solid var(--border)',
|
|
45
|
+
borderRadius: 'var(--radius-md)',
|
|
46
|
+
color: 'var(--foreground)',
|
|
47
|
+
fontSize: 12,
|
|
48
|
+
};
|
|
49
|
+
const SEO_SETTINGS_PATH = '/settings?tab=seo';
|
|
50
|
+
const EM_DASH = '—';
|
|
51
|
+
const th = 'py-2 pr-3 font-medium';
|
|
52
|
+
const thRight = 'py-2 pr-3 text-right font-medium';
|
|
53
|
+
const headRow = 'border-border text-muted-foreground border-b text-left';
|
|
54
|
+
const tdNum = 'text-foreground py-2 pr-3 text-right tabular-nums';
|
|
55
|
+
// ─── Cells ───────────────────────────────────────────────────────────────
|
|
56
|
+
/** Percentage change vs the prior window; `null` prev means "no full baseline". */
|
|
57
|
+
function Delta({ current, prev }) {
|
|
58
|
+
if (prev === null) {
|
|
59
|
+
return (_jsxs("span", { className: "text-muted-foreground", title: "No full prior window yet", children: [_jsx("span", { "aria-hidden": true, children: EM_DASH }), _jsx("span", { className: "sr-only", children: "No full prior window yet" })] }));
|
|
60
|
+
}
|
|
61
|
+
if (prev === 0 && current > 0)
|
|
62
|
+
return _jsx("span", { className: "text-muted-foreground", children: "new" });
|
|
63
|
+
const pct = prev === 0 ? 0 : Math.round(((current - prev) / prev) * 100);
|
|
64
|
+
const Icon = pct > 0 ? TrendingUp : pct < 0 ? TrendingDown : Minus;
|
|
65
|
+
const tone = pct > 0 ? 'text-success' : pct < 0 ? 'text-destructive' : 'text-muted-foreground';
|
|
66
|
+
return (_jsxs("span", { className: `inline-flex items-center gap-1 tabular-nums ${tone}`, children: [_jsx(Icon, { className: "h-4 w-4", "aria-hidden": true }), pct > 0 ? '+' : '', pct, "%"] }));
|
|
67
|
+
}
|
|
68
|
+
function PolicyLabel({ policy }) {
|
|
69
|
+
switch (policy) {
|
|
70
|
+
case 'allowed':
|
|
71
|
+
return (_jsxs("span", { className: "text-muted-foreground inline-flex items-center gap-1", children: [_jsx(ShieldCheck, { className: "h-4 w-4", "aria-hidden": true }), " Allowed"] }));
|
|
72
|
+
case 'blocked':
|
|
73
|
+
return (_jsxs("span", { className: "text-muted-foreground inline-flex items-center gap-1", children: [_jsx(ShieldOff, { className: "h-4 w-4", "aria-hidden": true }), " Blocked"] }));
|
|
74
|
+
case 'blocked_still_crawling':
|
|
75
|
+
return (_jsxs("span", { className: "text-destructive inline-flex items-center gap-1", children: [_jsx(ShieldAlert, { className: "h-4 w-4", "aria-hidden": true }), " Blocked \u2014 still crawling"] }));
|
|
76
|
+
default: {
|
|
77
|
+
const _exhaustive = policy;
|
|
78
|
+
return _exhaustive;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* A path, promoted to an "open in editor" button only when the server joined
|
|
84
|
+
* it to a document *and* the host gave us a navigator.
|
|
85
|
+
*/
|
|
86
|
+
function PathCell({ path, entityId, entityTitle, collection, onNavigate, }) {
|
|
87
|
+
if (!entityId || !onNavigate)
|
|
88
|
+
return _jsx("span", { className: "text-foreground break-all", children: path });
|
|
89
|
+
return (_jsxs("button", { type: "button", className: "text-foreground hover:text-primary focus-visible:ring-ring inline-flex items-center gap-1 rounded-sm text-left break-all underline-offset-2 hover:underline focus-visible:ring-2 focus-visible:outline-none", onClick: () => onNavigate(sourceEditPath(collection, entityId)), "aria-label": `Open ${entityTitle ?? path} in editor`, children: [path, _jsx(ExternalLink, { className: "h-3.5 w-3.5 shrink-0", "aria-hidden": true })] }));
|
|
90
|
+
}
|
|
91
|
+
// ─── Controls ────────────────────────────────────────────────────────────
|
|
92
|
+
/**
|
|
93
|
+
* Segmented window picker — the same `role="group"` + `aria-pressed` toggle
|
|
94
|
+
* pattern as the Audit / Content filters. Never disabled while loading:
|
|
95
|
+
* `useSeoResource` discards stale responses, and disabling the focused
|
|
96
|
+
* button would drop keyboard focus to `<body>` mid-request.
|
|
97
|
+
*/
|
|
98
|
+
function WindowSwitch({ value, onChange, }) {
|
|
99
|
+
return (_jsx("div", { role: "group", "aria-label": "Window", className: "border-border inline-flex rounded-md border p-0.5", children: WINDOWS.map((w) => (_jsx("button", { type: "button", "aria-pressed": value === w.days, className: `focus-visible:ring-ring rounded-sm px-3 py-1 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none ${value === w.days
|
|
100
|
+
? 'bg-primary text-primary-foreground'
|
|
101
|
+
: 'text-muted-foreground hover:bg-accent hover:text-foreground'}`, onClick: () => onChange(w.days), children: w.label }, w.days))) }));
|
|
102
|
+
}
|
|
103
|
+
// ─── Chart ───────────────────────────────────────────────────────────────
|
|
104
|
+
function CrawlChart({ series, bots, }) {
|
|
105
|
+
const { top, data, hasOther } = useMemo(() => {
|
|
106
|
+
const top = bots.slice(0, CHART_SERIES).map((b) => b.bot);
|
|
107
|
+
const hasOther = bots.length > CHART_SERIES;
|
|
108
|
+
const data = series.map((day) => {
|
|
109
|
+
const row = { date: day.date.slice(5) };
|
|
110
|
+
let other = 0;
|
|
111
|
+
for (const [bot, hits] of Object.entries(day.hits)) {
|
|
112
|
+
if (top.includes(bot))
|
|
113
|
+
row[bot] = hits;
|
|
114
|
+
else
|
|
115
|
+
other += hits;
|
|
116
|
+
}
|
|
117
|
+
if (hasOther)
|
|
118
|
+
row.Other = other;
|
|
119
|
+
return row;
|
|
120
|
+
});
|
|
121
|
+
return { top, data, hasOther };
|
|
122
|
+
}, [series, bots]);
|
|
123
|
+
return (
|
|
124
|
+
// The Crawlers table below carries the same numbers for assistive tech.
|
|
125
|
+
_jsx("div", { className: "h-56 w-full", role: "img", "aria-label": "Daily crawler hits by bot", children: _jsx(ResponsiveContainer, { width: "100%", height: "100%", children: _jsxs(BarChart, { data: data, children: [_jsx(CartesianGrid, { strokeDasharray: "3 3", stroke: "var(--border)" }), _jsx(XAxis, { dataKey: "date", tick: { fontSize: 12, fill: 'var(--muted-foreground)' } }), _jsx(YAxis, { allowDecimals: false, tick: { fontSize: 12, fill: 'var(--muted-foreground)' }, width: 32 }), _jsx(Tooltip, { contentStyle: CHART_TOOLTIP_STYLE, cursor: { fill: 'var(--accent)' } }), _jsx(Legend, { wrapperStyle: { fontSize: 12 } }), top.map((bot, i) => (_jsx(Bar, { dataKey: bot, stackId: "hits", fill: CHART_TOKENS[i % CHART_TOKENS.length] }, bot))), hasOther && _jsx(Bar, { dataKey: "Other", stackId: "hits", fill: CHART_OTHER_TOKEN })] }) }) }));
|
|
126
|
+
}
|
|
127
|
+
// ─── Sections ────────────────────────────────────────────────────────────
|
|
128
|
+
function CrawlSection({ crawl }) {
|
|
129
|
+
return (_jsx(SectionCard, { title: "Crawl trend", description: "Daily hits by AI training, AI retrieval, and search crawlers (Googlebot, Bingbot \u2014 counted so AI answer surfaces backed by web search are visible; the AI robots policy never blocks these).", children: _jsxs("div", { className: "space-y-4", children: [_jsxs("dl", { role: "group", "aria-label": "Crawl summary", className: "grid grid-cols-2 gap-3 text-sm sm:grid-cols-3", children: [_jsxs("div", { children: [_jsx("dt", { className: "text-muted-foreground", children: "Crawler hits" }), _jsx("dd", { className: "text-foreground font-medium tabular-nums", children: crawl.total.hits })] }), _jsxs("div", { children: [_jsx("dt", { className: "text-muted-foreground", children: "\u0394 vs prior" }), _jsx("dd", { children: _jsx(Delta, { current: crawl.total.hits, prev: crawl.total.prevHits }) })] }), _jsxs("div", { children: [_jsx("dt", { className: "text-muted-foreground", children: "Distinct bots" }), _jsx("dd", { className: "text-foreground font-medium tabular-nums", children: crawl.bots.length })] })] }), _jsx(CrawlChart, { series: crawl.series, bots: crawl.bots }), _jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full min-w-[640px] text-sm", "aria-label": "Crawlers", children: [_jsx("thead", { children: _jsxs("tr", { className: headRow, children: [_jsx("th", { scope: "col", className: th, children: "Bot" }), _jsx("th", { scope: "col", className: th, children: "Type" }), _jsx("th", { scope: "col", className: thRight, children: "Hits" }), _jsx("th", { scope: "col", className: thRight, children: "\u0394 vs prior" }), _jsx("th", { scope: "col", className: th, children: "Last seen" }), _jsx("th", { scope: "col", className: th, children: "Policy" })] }) }), _jsx("tbody", { className: "divide-border divide-y", children: crawl.bots.map((b) => (_jsxs("tr", { children: [_jsx("td", { className: "text-foreground py-2 pr-3 font-medium", children: b.bot }), _jsx("td", { className: "text-muted-foreground py-2 pr-3", children: CATEGORY_LABEL[b.category] }), _jsx("td", { className: tdNum, children: b.hits }), _jsx("td", { className: "py-2 pr-3 text-right", children: _jsx(Delta, { current: b.hits, prev: b.prevHits }) }), _jsx("td", { className: "text-muted-foreground py-2 pr-3 tabular-nums", children: b.lastSeenAt ?? EM_DASH }), _jsx("td", { className: "py-2", children: _jsx(PolicyLabel, { policy: b.policy }) })] }, b.bot))) })] }) })] }) }));
|
|
130
|
+
}
|
|
131
|
+
function ReferralsSection({ referrals, onNavigate, }) {
|
|
132
|
+
return (_jsx(SectionCard, { title: "AI referrals", description: "Visits that arrived from an answer engine (Referer or utm_source).", children: _jsxs("div", { className: "space-y-4", children: [_jsxs("dl", { role: "group", "aria-label": "Referral summary", className: "grid grid-cols-2 gap-3 text-sm sm:grid-cols-3", children: [_jsxs("div", { children: [_jsx("dt", { className: "text-muted-foreground", children: "Visits" }), _jsx("dd", { className: "text-foreground font-medium tabular-nums", children: referrals.total.visits })] }), _jsxs("div", { children: [_jsx("dt", { className: "text-muted-foreground", children: "\u0394 vs prior" }), _jsx("dd", { children: _jsx(Delta, { current: referrals.total.visits, prev: referrals.total.prevVisits }) })] })] }), referrals.engines.length > 0 && (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full min-w-[480px] text-sm", "aria-label": "Engines", children: [_jsx("thead", { children: _jsxs("tr", { className: headRow, children: [_jsx("th", { scope: "col", className: th, children: "Engine" }), _jsx("th", { scope: "col", className: thRight, children: "Visits" }), _jsx("th", { scope: "col", className: thRight, children: "\u0394 vs prior" })] }) }), _jsx("tbody", { className: "divide-border divide-y", children: referrals.engines.map((e) => (_jsxs("tr", { children: [_jsx("td", { className: "text-foreground py-2 pr-3 font-medium", children: e.engine }), _jsx("td", { className: tdNum, children: e.visits }), _jsx("td", { className: "py-2 pr-3 text-right", children: _jsx(Delta, { current: e.visits, prev: e.prevVisits }) })] }, e.engine))) })] }) })), referrals.topPaths.length > 0 && (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full min-w-[640px] text-sm", "aria-label": "Top cited pages", children: [_jsx("thead", { children: _jsxs("tr", { className: headRow, children: [_jsx("th", { scope: "col", className: th, children: "Path" }), _jsx("th", { scope: "col", className: thRight, children: "Visits" }), _jsx("th", { scope: "col", className: th, children: "Engines" })] }) }), _jsx("tbody", { className: "divide-border divide-y", children: referrals.topPaths.map((p) => (_jsxs("tr", { children: [_jsx("td", { className: "py-2 pr-3", children: _jsx(PathCell, { path: p.path, entityId: p.entityId, entityTitle: p.entityTitle, collection: p.collection, onNavigate: onNavigate }) }), _jsx("td", { className: tdNum, children: p.visits }), _jsx("td", { className: "text-muted-foreground py-2", children: p.engines.join(', ') })] }, p.path))) })] }) })), referrals.otherVisits > 0 && (_jsxs("p", { className: "text-muted-foreground text-sm", children: [referrals.otherVisits, " visits on paths beyond the daily cap."] }))] }) }));
|
|
133
|
+
}
|
|
134
|
+
function TopCrawledSection({ crawl, onNavigate, }) {
|
|
135
|
+
return (_jsxs(SectionCard, { title: "Top crawled pages", description: "Paths most fetched by AI and search crawlers in this window.", children: [_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full min-w-[640px] text-sm", "aria-label": "Top crawled pages", children: [_jsx("thead", { children: _jsxs("tr", { className: headRow, children: [_jsx("th", { scope: "col", className: th, children: "Path" }), _jsx("th", { scope: "col", className: thRight, children: "Hits" }), _jsx("th", { scope: "col", className: th, children: "Bots" })] }) }), _jsx("tbody", { className: "divide-border divide-y", children: crawl.topPaths.map((p) => (_jsxs("tr", { children: [_jsx("td", { className: "py-2 pr-3", children: _jsx(PathCell, { path: p.path, entityId: p.entityId, entityTitle: p.entityTitle, collection: p.collection, onNavigate: onNavigate }) }), _jsx("td", { className: tdNum, children: p.hits }), _jsx("td", { className: "text-muted-foreground py-2", children: p.bots.join(', ') })] }, p.path))) })] }) }), crawl.otherHits > 0 && (_jsxs("p", { className: "text-muted-foreground mt-3 text-sm", children: [crawl.otherHits, " hits on paths beyond the daily cap."] }))] }));
|
|
136
|
+
}
|
|
137
|
+
// ─── Tab ─────────────────────────────────────────────────────────────────
|
|
138
|
+
export function AiVisibilityTab({ onNavigate }) {
|
|
139
|
+
const [days, setDays] = useState(28);
|
|
140
|
+
const { data, loading, error, refetch } = useSeoResource(() => fetchSeoGeo(days), [days]);
|
|
141
|
+
const header = (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx(WindowSwitch, { value: days, onChange: setDays }), data?.firstSeenAt && (_jsxs("span", { className: "text-muted-foreground text-sm", children: ["First seen ", relativeAge(data.firstSeenAt) || data.firstSeenAt] }))] }), _jsxs("button", { type: "button", className: btnSecondary, onClick: () => refetch(), disabled: loading, children: [_jsx(RefreshCw, { className: `h-4 w-4 ${loading ? 'motion-safe:animate-spin' : ''}`, "aria-hidden": true }), "Refresh"] })] }));
|
|
142
|
+
if (loading && !data) {
|
|
143
|
+
return (_jsxs("div", { className: "space-y-4", children: [header, _jsx(SeoLoading, { label: "Loading AI visibility\u2026" })] }));
|
|
144
|
+
}
|
|
145
|
+
if (error) {
|
|
146
|
+
return (_jsxs("div", { className: "space-y-4", children: [header, _jsx(SeoErrorState, { message: error, onRetry: refetch })] }));
|
|
147
|
+
}
|
|
148
|
+
if (!data)
|
|
149
|
+
return null;
|
|
150
|
+
// Both setup-ish empty states send the editor to Settings → SEO (the kill
|
|
151
|
+
// switch and the middleware snippet live on the same card).
|
|
152
|
+
const settingsAction = (label) => onNavigate && (_jsx("button", { type: "button", className: btnSecondary, onClick: () => onNavigate(SEO_SETTINGS_PATH), children: label }));
|
|
153
|
+
if (!data.enabled) {
|
|
154
|
+
// `unavailable` = the tables are not in the schema; the setting cannot
|
|
155
|
+
// fix that, so no settings action is offered for it.
|
|
156
|
+
const unavailable = data.disabledReason === 'unavailable';
|
|
157
|
+
return (_jsxs("div", { className: "space-y-4", children: [header, unavailable ? (_jsx(SeoEmptyState, { icon: _jsx(Bot, { size: 24 }), title: "GEO tables not migrated", description: "Run `prisma migrate deploy` (migration `0018_geo_daily`) on the CMS database, then reload." })) : (_jsx(SeoEmptyState, { icon: _jsx(Bot, { size: 24 }), title: "Measurement is off", description: "Turn on AI visibility measurement in Settings \u2192 SEO to start recording AI crawler hits and AI-engine referrals.", action: settingsAction('Open SEO settings') }))] }));
|
|
158
|
+
}
|
|
159
|
+
if (data.firstSeenAt === null) {
|
|
160
|
+
return (_jsxs("div", { className: "space-y-4", children: [header, _jsx(SeoEmptyState, { icon: _jsx(Bot, { size: 24 }), title: "The site middleware is not reporting yet", description: "Add observeGeoTraffic to your site's proxy.ts (Next) or use createAstroRedirectMiddleware (Astro). Counts appear here as soon as the first AI or search crawler visits.", action: settingsAction('Show middleware snippet') })] }));
|
|
161
|
+
}
|
|
162
|
+
const hasRows = data.crawl.bots.length > 0 || data.referrals.engines.length > 0;
|
|
163
|
+
return (_jsxs("div", { className: "space-y-4", children: [header, _jsx("p", { className: "text-muted-foreground text-sm", children: "Counts are self-reported by your middleware; they show real bot names on real paths but are not audited traffic." }), !hasRows ? (_jsx(SeoEmptyState, { icon: _jsx(Bot, { size: 24 }), title: "No AI crawler or referral activity in this window", description: "Try a longer window." })) : (_jsxs(_Fragment, { children: [_jsx(CrawlSection, { crawl: data.crawl }), _jsx(ReferralsSection, { referrals: data.referrals, onNavigate: onNavigate }), data.crawl.topPaths.length > 0 && (_jsx(TopCrawledSection, { crawl: data.crawl, onNavigate: onNavigate }))] }))] }));
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=AiVisibilityTab.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AiVisibilityTab.js","sourceRoot":"","sources":["../../../src/views/seo/AiVisibilityTab.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProposalsTab.d.ts","sourceRoot":"","sources":["../../../src/views/seo/ProposalsTab.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProposalsTab.d.ts","sourceRoot":"","sources":["../../../src/views/seo/ProposalsTab.tsx"],"names":[],"mappings":"AAqCA,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAwNjE,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,SAAS,EACT,UAAU,GACX,EAAE;IACD,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAA;IACzB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACpC,2CAkiBA"}
|
|
@@ -5,16 +5,19 @@ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-run
|
|
|
5
5
|
* in one review surface. Batch approve honours server governance
|
|
6
6
|
* (`allowBulkApply`); the UI only pre-disables the button using the flags the
|
|
7
7
|
* list payload exposes. Generation is human-triggered, capped per pass, and
|
|
8
|
-
* loops client-side while the server reports `remaining > 0`.
|
|
8
|
+
* loops client-side while the server reports `remaining > 0`. FAQ
|
|
9
|
+
* (`add-section`) rows expand into a per-question preview and approve one at
|
|
10
|
+
* a time through `apply-fix`; in a batch they apply the whole drafted section.
|
|
9
11
|
*/
|
|
10
|
-
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
12
|
+
import { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
|
|
11
13
|
import { AlertTriangle, ArrowRightLeft, Bot, Check, ChevronDown, Inbox, Loader2, Sparkles, Square, X, } from 'lucide-react';
|
|
12
14
|
import { toast } from 'sonner';
|
|
13
|
-
import { bulkApplySeoProposals, dismissRedirectSuggestion, dismissSeoIssueFix, fetchSeoProposals, generateSeoProposals, } from '../../lib/seo-service.js';
|
|
15
|
+
import { applySeoIssueFix, bulkApplySeoProposals, dismissRedirectSuggestion, dismissSeoIssueFix, fetchSeoProposals, generateSeoProposals, } from '../../lib/seo-service.js';
|
|
14
16
|
import { hasPlanFeature } from '../../lib/plan.js';
|
|
15
17
|
import { brandAlignmentLabel, brandAlignmentTone } from '../../lib/brand-voice-client.js';
|
|
16
18
|
import { PlanUpgradeCallout } from '../../components/PlanUpgradeCallout.js';
|
|
17
19
|
import { ChangePreview } from '../../components/seo/ChangePreview.js';
|
|
20
|
+
import { FaqProposalPreview, faqDraftBlocker, faqSignalLine, } from '../../components/seo/FaqProposalPreview.js';
|
|
18
21
|
import { SectionCard, SeoEmptyState, SeoErrorState, SeoLoading, SeoStatusBadge, btnPrimary, btnSecondary, } from '../../components/seo/primitives.js';
|
|
19
22
|
import { ConfirmDialog } from '../../components/ui/ConfirmDialog.js';
|
|
20
23
|
import { AutopilotAppliedSection } from './AutopilotAppliedSection.js';
|
|
@@ -39,6 +42,64 @@ function toBulkItem(p) {
|
|
|
39
42
|
? { kind: 'redirect', id: p.suggestionId ?? '' }
|
|
40
43
|
: { kind: 'issue-fix', id: p.issueId ?? '', fingerprint: p.fingerprint ?? undefined };
|
|
41
44
|
}
|
|
45
|
+
/** An `add-section` (FAQ) proposal; `p.section` may still be `null` when the cache was unreadable. */
|
|
46
|
+
function isFaqProposal(p) {
|
|
47
|
+
return p.fixStrategy === 'add-section';
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Drafts are keyed by the drafted content, not just the issue: `fingerprint`
|
|
51
|
+
* tracks the page (`updatedAt`), so dismiss + regenerate on an unchanged page
|
|
52
|
+
* keeps the same id and fingerprint while the questions change. Folding the
|
|
53
|
+
* questions (and `generatedAt`) into the key makes a regenerated proposal
|
|
54
|
+
* start clean instead of inheriting stale ticks or a stale heading edit.
|
|
55
|
+
*/
|
|
56
|
+
function faqDraftKey(p) {
|
|
57
|
+
const questions = p.section?.content.items.map((i) => i.question).join('\u0001') ?? '';
|
|
58
|
+
return `${p.id}:${p.generatedAt ?? ''}:${questions}`;
|
|
59
|
+
}
|
|
60
|
+
function initialFaqDraft(p) {
|
|
61
|
+
const section = p.section;
|
|
62
|
+
return {
|
|
63
|
+
selected: section ? section.content.items.map(() => true) : [],
|
|
64
|
+
heading: section?.content.heading ?? '',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The row's stored draft when it still fits the section (one `selected` flag
|
|
69
|
+
* per item), otherwise the untouched default. Every reader *and* writer goes
|
|
70
|
+
* through this so a mismatched draft is replaced rather than mutated.
|
|
71
|
+
*/
|
|
72
|
+
function currentFaqDraft(drafts, p) {
|
|
73
|
+
const draft = drafts[faqDraftKey(p)];
|
|
74
|
+
if (draft && draft.selected.length === (p.section?.content.items.length ?? 0))
|
|
75
|
+
return draft;
|
|
76
|
+
return initialFaqDraft(p);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Body of the single-row `apply-fix` call. `includeItems` only when the
|
|
80
|
+
* reviewer dropped a question; `headingOverride` only when the trimmed heading
|
|
81
|
+
* is non-empty and differs from the draft — so an untouched row sends exactly
|
|
82
|
+
* `{ fingerprint }`, the same request the Audit tab makes.
|
|
83
|
+
*/
|
|
84
|
+
function faqApplyOptions(p, draft) {
|
|
85
|
+
const opts = {};
|
|
86
|
+
const includeItems = draft.selected.flatMap((on, i) => (on ? [i] : []));
|
|
87
|
+
if (includeItems.length < draft.selected.length)
|
|
88
|
+
opts.includeItems = includeItems;
|
|
89
|
+
const heading = draft.heading.trim();
|
|
90
|
+
if (heading !== '' && heading !== p.section?.content.heading)
|
|
91
|
+
opts.headingOverride = heading;
|
|
92
|
+
return opts;
|
|
93
|
+
}
|
|
94
|
+
/** Change-column text for a FAQ row: question count plus the strongest signal. */
|
|
95
|
+
function faqSummary(p) {
|
|
96
|
+
const signal = faqSignalLine(p.signals);
|
|
97
|
+
if (!p.section)
|
|
98
|
+
return signal ?? p.justification;
|
|
99
|
+
const count = plural(p.section.content.items.length, 'question');
|
|
100
|
+
return signal ? `${count} · ${signal}` : count;
|
|
101
|
+
}
|
|
102
|
+
const FAQ_UNREADABLE_HINT = 'The drafted section could not be read from the cache. Dismiss this proposal and generate again to get a fresh draft.';
|
|
42
103
|
function BrandScoreBadge({ score }) {
|
|
43
104
|
return (_jsxs("span", { className: "border-border bg-muted/40 inline-flex items-center gap-1.5 rounded-md border px-2 py-0.5 text-xs", "aria-label": `Brand voice score ${score} out of 100`, children: [_jsxs("span", { className: `font-medium ${brandAlignmentTone(score)}`, children: [score, "/100"] }), _jsx("span", { className: "text-muted-foreground", children: brandAlignmentLabel(score) })] }));
|
|
44
105
|
}
|
|
@@ -96,6 +157,8 @@ export function ProposalsTab({ planInfo, onChanged, onNavigate, }) {
|
|
|
96
157
|
const [confirmGenerate, setConfirmGenerate] = useState(false);
|
|
97
158
|
const [mutation, setMutation] = useState(null);
|
|
98
159
|
const [pendingItems, setPendingItems] = useState([]);
|
|
160
|
+
/** Per-row FAQ review edits, created lazily on first interaction (see `faqDraftKey`). */
|
|
161
|
+
const [faqDrafts, setFaqDrafts] = useState({});
|
|
99
162
|
const [generating, setGenerating] = useState(null);
|
|
100
163
|
/** Bumped after every mutation so the autopilot ledger below refetches with the inbox. */
|
|
101
164
|
const [refreshKey, setRefreshKey] = useState(0);
|
|
@@ -215,6 +278,71 @@ export function ProposalsTab({ planInfo, onChanged, onNavigate, }) {
|
|
|
215
278
|
endMutation();
|
|
216
279
|
}
|
|
217
280
|
}, [afterMutation, beginMutation, endMutation]);
|
|
281
|
+
/** The row's current draft, or the untouched default when the reviewer has not edited it. */
|
|
282
|
+
const faqDraftFor = useCallback((p) => currentFaqDraft(faqDrafts, p), [faqDrafts]);
|
|
283
|
+
const toggleFaqItem = useCallback((p, index) => {
|
|
284
|
+
setFaqDrafts((cur) => {
|
|
285
|
+
const base = currentFaqDraft(cur, p);
|
|
286
|
+
const selected = base.selected.map((on, i) => (i === index ? !on : on));
|
|
287
|
+
return { ...cur, [faqDraftKey(p)]: { ...base, selected } };
|
|
288
|
+
});
|
|
289
|
+
}, []);
|
|
290
|
+
const setFaqHeading = useCallback((p, heading) => {
|
|
291
|
+
setFaqDrafts((cur) => ({ ...cur, [faqDraftKey(p)]: { ...currentFaqDraft(cur, p), heading } }));
|
|
292
|
+
}, []);
|
|
293
|
+
// Drop drafts whose proposal is gone or was regenerated, so the map cannot grow across refetches.
|
|
294
|
+
useEffect(() => {
|
|
295
|
+
const liveKeys = new Set(proposals.filter(isFaqProposal).map(faqDraftKey));
|
|
296
|
+
setFaqDrafts((cur) => {
|
|
297
|
+
const stale = Object.keys(cur).filter((key) => !liveKeys.has(key));
|
|
298
|
+
if (stale.length === 0)
|
|
299
|
+
return cur;
|
|
300
|
+
const next = { ...cur };
|
|
301
|
+
for (const key of stale)
|
|
302
|
+
delete next[key];
|
|
303
|
+
return next;
|
|
304
|
+
});
|
|
305
|
+
}, [proposals]);
|
|
306
|
+
/**
|
|
307
|
+
* Single-row approve for a FAQ proposal. Goes through `apply-fix` (not
|
|
308
|
+
* bulk-apply) so the reviewer's per-question selection and heading edit are
|
|
309
|
+
* honoured; the bulk path applies the whole drafted section unchanged.
|
|
310
|
+
*/
|
|
311
|
+
const handleApproveFaq = useCallback(async (p) => {
|
|
312
|
+
if (!p.issueId || !p.fingerprint || !p.section)
|
|
313
|
+
return;
|
|
314
|
+
const draft = faqDraftFor(p);
|
|
315
|
+
if (faqDraftBlocker(draft.selected) !== null)
|
|
316
|
+
return;
|
|
317
|
+
if (!beginMutation('apply'))
|
|
318
|
+
return;
|
|
319
|
+
try {
|
|
320
|
+
const res = await applySeoIssueFix(p.issueId, p.fingerprint, faqApplyOptions(p, draft));
|
|
321
|
+
if (!mountedRef.current)
|
|
322
|
+
return;
|
|
323
|
+
if (res.error) {
|
|
324
|
+
toast.error(res.error);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
toast.success('FAQ section added.');
|
|
328
|
+
setFaqDrafts((cur) => {
|
|
329
|
+
const key = faqDraftKey(p);
|
|
330
|
+
if (!(key in cur))
|
|
331
|
+
return cur;
|
|
332
|
+
const rest = { ...cur };
|
|
333
|
+
delete rest[key];
|
|
334
|
+
return rest;
|
|
335
|
+
});
|
|
336
|
+
afterMutation();
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
if (mountedRef.current)
|
|
340
|
+
toast.error(errorMessage(err));
|
|
341
|
+
}
|
|
342
|
+
finally {
|
|
343
|
+
endMutation();
|
|
344
|
+
}
|
|
345
|
+
}, [afterMutation, beginMutation, endMutation, faqDraftFor]);
|
|
218
346
|
const handleDismissSelected = useCallback(async () => {
|
|
219
347
|
const targets = selectedProposals;
|
|
220
348
|
if (targets.length === 0)
|
|
@@ -319,16 +447,41 @@ export function ProposalsTab({ planInfo, onChanged, onNavigate, }) {
|
|
|
319
447
|
const headerAction = autopilotChip || generateAction ? (_jsxs("div", { className: "flex flex-wrap items-center justify-end gap-2", children: [autopilotChip, generateAction] })) : undefined;
|
|
320
448
|
return (_jsxs("div", { className: "space-y-6", children: [!inlineApplyEnabled ? (_jsx(PlanUpgradeCallout, { feature: "seo.inlineApply", upgradeUrl: planInfo?.upgradeUrl })) : null, _jsx(SectionCard, { title: "Proposals", description: "Review, approve, or dismiss what the AI proposes across issue fixes and 404 redirects.", action: headerAction, children: loading ? (_jsx(SeoLoading, {})) : error ? (_jsx(SeoErrorState, { message: error, onRetry: refetch })) : proposals.length === 0 ? (_jsx(SeoEmptyState, { icon: _jsx(Inbox, { size: 24 }), title: "No proposals yet", description: "Generate proposals to draft fixes for open, fixable issues, or run 404 recovery from the Redirects tab." })) : (_jsxs("div", { className: "space-y-3", children: [pendingItems.length > 0 && (_jsxs("div", { className: "border-border bg-muted/40 flex flex-wrap items-center gap-2 rounded-lg border px-3 py-2 text-sm", role: "status", "aria-live": "polite", children: [_jsxs("span", { className: "text-foreground", children: [plural(pendingItems.length, 'proposal'), " not reached within the time budget."] }), _jsxs("button", { type: "button", className: "text-primary hover:bg-accent focus-visible:ring-ring rounded-md px-2 py-1 font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:opacity-60", onClick: () => void runApply(pendingItems), disabled: busy, children: ["Apply remaining (", pendingItems.length, ")"] }), _jsx("button", { type: "button", className: "text-muted-foreground hover:text-foreground focus-visible:ring-ring ml-auto rounded-md px-2 py-1 font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none", onClick: () => setPendingItems([]), children: "Dismiss" })] })), selected.size > 0 && (_jsxs("div", { className: "border-border bg-accent/40 flex flex-wrap items-center gap-2 rounded-lg border px-3 py-2 text-sm", role: "region", "aria-label": "Bulk actions", children: [_jsxs("span", { className: "text-foreground font-medium", children: [selected.size, " selected"] }), _jsx("span", { className: "bg-border mx-1 h-4 w-px", "aria-hidden": true }), _jsx("button", { type: "button", className: "text-foreground hover:bg-accent focus-visible:ring-ring rounded-md px-2 py-1 font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:opacity-60", onClick: () => setConfirmApply(true), disabled: busy || bulkBlockedReason !== null, children: "Approve selected" }), _jsx("button", { type: "button", className: "text-foreground hover:bg-accent focus-visible:ring-ring rounded-md px-2 py-1 font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none disabled:opacity-60", onClick: () => void handleDismissSelected(), disabled: busy, children: "Dismiss selected" }), _jsx("button", { type: "button", className: "text-muted-foreground hover:text-foreground focus-visible:ring-ring ml-auto rounded-md px-2 py-1 font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none", onClick: () => setSelected(new Set()), children: "Clear" }), bulkBlockedReason && (_jsxs("p", { className: "text-muted-foreground basis-full text-sm", role: "status", children: ["Applying several ", bulkBlockedReason, " proposals at once needs \u201CAllow bulk apply\u201D.", onNavigate ? (_jsx("button", { type: "button", onClick: () => onNavigate(AI_SETTINGS_PATH), className: "text-primary focus-visible:ring-ring ml-1 rounded-sm font-medium hover:underline focus-visible:ring-2 focus-visible:outline-none", children: "Open Settings \u2192 AI" })) : null] }))] })), _jsx("div", { className: "border-border overflow-x-auto rounded-md border", children: _jsxs("table", { className: "w-full text-sm", "aria-label": "AI proposals", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-border bg-muted/40 text-muted-foreground border-b text-left text-xs", children: [_jsx("th", { scope: "col", className: "w-10 px-3 py-2.5", children: _jsx("input", { type: "checkbox", checked: allSelected, onChange: toggleSelectAll, disabled: live.length === 0, "aria-label": "Select all proposals", className: "border-border text-primary focus-visible:ring-ring h-4 w-4 rounded" }) }), _jsx("th", { scope: "col", className: "px-3 py-2.5 font-medium", children: "Proposal" }), _jsx("th", { scope: "col", className: "px-3 py-2.5 font-medium", children: "Change" }), _jsx("th", { scope: "col", className: "px-3 py-2.5 font-medium", children: "Signals" }), _jsx("th", { scope: "col", className: "px-3 py-2.5 font-medium", children: "Age" }), _jsx("th", { scope: "col", className: "px-3 py-2.5", children: _jsx("span", { className: "sr-only", children: "Actions" }) })] }) }), _jsx("tbody", { className: "divide-border divide-y", children: proposals.map((p) => {
|
|
321
449
|
const first = p.changes[0];
|
|
450
|
+
const canApply = inlineApplyEnabled || p.kind === 'redirect';
|
|
451
|
+
if (isFaqProposal(p)) {
|
|
452
|
+
const draft = faqDraftFor(p);
|
|
453
|
+
const blocker = p.section
|
|
454
|
+
? faqDraftBlocker(draft.selected)
|
|
455
|
+
: FAQ_UNREADABLE_HINT;
|
|
456
|
+
return (_jsx(ProposalRow, { proposal: p, selected: selected.has(p.id), expanded: expanded.has(p.id), summary: faqSummary(p), onToggleSelect: () => toggleSelect(p.id), onToggleExpanded: () => toggleExpanded(p.id), onApprove: () => void handleApproveFaq(p), onDismiss: () => void handleDismissOne(p), busy: busy, canApply: canApply, approveBlocker: blocker, details: _jsx(FaqProposalDetails, { proposal: p, draft: draft, blocker: blocker, busy: busy, canApply: canApply, onToggle: (i) => toggleFaqItem(p, i), onHeadingChange: (h) => setFaqHeading(p, h), onApprove: () => void handleApproveFaq(p) }) }, p.id));
|
|
457
|
+
}
|
|
322
458
|
return (_jsx(ProposalRow, { proposal: p, selected: selected.has(p.id), expanded: expanded.has(p.id), summary: first
|
|
323
459
|
? `${String(first.before ?? '—')} → ${String(first.after ?? '—')}`
|
|
324
|
-
: p.justification, onToggleSelect: () => toggleSelect(p.id), onToggleExpanded: () => toggleExpanded(p.id), onApprove: () => void runApply([toBulkItem(p)]), onDismiss: () => void handleDismissOne(p), busy: busy, canApply:
|
|
460
|
+
: p.justification, onToggleSelect: () => toggleSelect(p.id), onToggleExpanded: () => toggleExpanded(p.id), onApprove: () => void runApply([toBulkItem(p)]), onDismiss: () => void handleDismissOne(p), busy: busy, canApply: canApply }, p.id));
|
|
325
461
|
}) })] }) })] })) }), _jsx(AutopilotAppliedSection, { onNavigate: onNavigate, refreshKey: refreshKey, onChanged: handleLedgerChanged }), _jsx(ConfirmDialog, { open: confirmApply, onClose: () => setConfirmApply(false), onConfirm: () => void runApply(selectedProposals.map(toBulkItem)), title: `Apply ${plural(selectedProposals.length, 'proposal')}?`, description: "Approved fixes write SEO fields, content links, or redirects immediately and mark their issues resolved. Each document keeps a version you can restore.", confirmLabel: `Apply ${plural(selectedProposals.length, 'proposal')}` }), _jsx(ConfirmDialog, { open: confirmGenerate, onClose: () => setConfirmGenerate(false), onConfirm: () => void handleGenerate(), title: `Generate proposals for ${plural(eligible, 'eligible issue')}?`, description: `Runs in passes of ${GENERATE_BATCH_SIZE}. AI tokens are spent for each issue; you can stop between passes.`, confirmLabel: "Generate" })] }));
|
|
326
462
|
}
|
|
327
|
-
function ProposalRow({ proposal: p, selected, expanded, summary, onToggleSelect, onToggleExpanded, onApprove, onDismiss, busy, canApply, }) {
|
|
328
|
-
|
|
463
|
+
function ProposalRow({ proposal: p, selected, expanded, summary, onToggleSelect, onToggleExpanded, onApprove, onDismiss, busy, canApply, approveBlocker = null, details, }) {
|
|
464
|
+
const approveHintId = `${useId()}-approve-hint`;
|
|
465
|
+
return (_jsxs(_Fragment, { children: [_jsxs("tr", { className: `${selected ? 'bg-accent/30' : ''} hover:bg-accent/20 transition-colors`, children: [_jsx("td", { className: "px-3 py-3 align-top", children: p.stale ? null : (_jsx("input", { type: "checkbox", checked: selected, onChange: onToggleSelect, "aria-label": `Select proposal ${p.title}`, className: "border-border text-primary focus-visible:ring-ring h-4 w-4 rounded" })) }), _jsxs("td", { className: "px-3 py-3 align-top", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("span", { className: "text-foreground font-medium", children: p.title }), isFaqProposal(p) ? _jsx(SeoStatusBadge, { label: "FAQ section", tone: "neutral" }) : null, p.severity ? (_jsx(SeoStatusBadge, { label: p.severity, tone: p.severity === 'critical'
|
|
329
466
|
? 'critical'
|
|
330
467
|
: p.severity === 'warning'
|
|
331
468
|
? 'fair'
|
|
332
|
-
: 'neutral' })) : null, p.stale ? _jsx(SeoStatusBadge, { label: "Stale \u2014 regenerate", tone: "poor" }) : null] }), p.entity.title || p.entity.url ? (_jsxs("p", { className: "text-muted-foreground mt-0.5 text-xs", children: [p.entity.title ?? '', p.entity.title && p.entity.url ? ' · ' : '', p.entity.url ?? ''] })) : null] }), _jsxs("td", { className: "px-3 py-3 align-top", children: [_jsx("p", { className: "text-foreground max-w-md truncate", children: summary }), _jsxs("button", { type: "button", className: "text-primary hover:bg-accent focus-visible:ring-ring mt-1 inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs focus-visible:ring-2 focus-visible:outline-none", onClick: onToggleExpanded, "aria-expanded": expanded, "aria-label": `${expanded ? 'Hide' : 'Show'} changes for ${p.title}`, children: [expanded ? 'Hide changes' : 'Show changes', _jsx(ChevronDown, { className: `h-3 w-3 motion-safe:transition-transform ${expanded ? 'rotate-180' : ''}`, "aria-hidden": true })] })] }), _jsx("td", { className: "px-3 py-3 align-top", children: _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(SourceBadge, { source: p.source }), typeof p.confidence === 'number' ? (_jsx(SeoStatusBadge, { label: `${Math.round(p.confidence * 100)}%`, tone: confidenceTone(p.confidence) })) : null, typeof p.brandAlignment === 'number' ? (_jsx(BrandScoreBadge, { score: p.brandAlignment })) : null] }) }), _jsx("td", { className: "text-muted-foreground px-3 py-3 align-top text-xs whitespace-nowrap", children: relativeAge(p.generatedAt) }), _jsx("td", { className: "px-3 py-3 align-top", children: _jsxs("div", { className: "flex items-center justify-end gap-1", children: [!p.stale && canApply ? (_jsx("button", { type: "button", onClick: onApprove, disabled: busy, className: "text-success hover:bg-accent focus-visible:ring-ring rounded-md p-1.5 focus-visible:ring-2 focus-visible:outline-none disabled:opacity-60", "aria-label": `Approve ${p.title}`, children: _jsx(Check, { className: "h-4 w-4", "aria-hidden": true }) })) : null, _jsx("button", { type: "button", onClick: onDismiss, disabled: busy, className: "text-muted-foreground hover:bg-accent focus-visible:ring-ring rounded-md p-1.5 focus-visible:ring-2 focus-visible:outline-none disabled:opacity-60", "aria-label": `Dismiss ${p.title}`, children: _jsx(X, { className: "h-4 w-4", "aria-hidden": true }) })] }) })] }), expanded ? (_jsx("tr", { children: _jsx("td", { colSpan: 6, className: "bg-muted/30 px-4 py-4", children: _jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-foreground text-sm", children: p.justification }), _jsx(ChangePreview, { changes: p.changes })] }) }) })) : null] }));
|
|
469
|
+
: 'neutral' })) : null, p.stale ? _jsx(SeoStatusBadge, { label: "Stale \u2014 regenerate", tone: "poor" }) : null] }), p.entity.title || p.entity.url ? (_jsxs("p", { className: "text-muted-foreground mt-0.5 text-xs", children: [p.entity.title ?? '', p.entity.title && p.entity.url ? ' · ' : '', p.entity.url ?? ''] })) : null] }), _jsxs("td", { className: "px-3 py-3 align-top", children: [_jsx("p", { className: "text-foreground max-w-md truncate", children: summary }), _jsxs("button", { type: "button", className: "text-primary hover:bg-accent focus-visible:ring-ring mt-1 inline-flex items-center gap-1 rounded-md px-1.5 py-0.5 text-xs focus-visible:ring-2 focus-visible:outline-none", onClick: onToggleExpanded, "aria-expanded": expanded, "aria-label": `${expanded ? 'Hide' : 'Show'} changes for ${p.title}`, children: [expanded ? 'Hide changes' : 'Show changes', _jsx(ChevronDown, { className: `h-3 w-3 motion-safe:transition-transform ${expanded ? 'rotate-180' : ''}`, "aria-hidden": true })] })] }), _jsx("td", { className: "px-3 py-3 align-top", children: _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(SourceBadge, { source: p.source }), typeof p.confidence === 'number' ? (_jsx(SeoStatusBadge, { label: `${Math.round(p.confidence * 100)}%`, tone: confidenceTone(p.confidence) })) : null, typeof p.brandAlignment === 'number' ? (_jsx(BrandScoreBadge, { score: p.brandAlignment })) : null] }) }), _jsx("td", { className: "text-muted-foreground px-3 py-3 align-top text-xs whitespace-nowrap", children: relativeAge(p.generatedAt) }), _jsx("td", { className: "px-3 py-3 align-top", children: _jsxs("div", { className: "flex items-center justify-end gap-1", children: [!p.stale && canApply ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: onApprove, disabled: busy || approveBlocker !== null, className: "text-success hover:bg-accent focus-visible:ring-ring rounded-md p-1.5 focus-visible:ring-2 focus-visible:outline-none disabled:opacity-60", "aria-label": `Approve ${p.title}`, title: approveBlocker ?? undefined, "aria-describedby": approveBlocker ? approveHintId : undefined, children: _jsx(Check, { className: "h-4 w-4", "aria-hidden": true }) }), approveBlocker ? (_jsx("span", { id: approveHintId, className: "sr-only", children: approveBlocker })) : null] })) : null, _jsx("button", { type: "button", onClick: onDismiss, disabled: busy, className: "text-muted-foreground hover:bg-accent focus-visible:ring-ring rounded-md p-1.5 focus-visible:ring-2 focus-visible:outline-none disabled:opacity-60", "aria-label": `Dismiss ${p.title}`, children: _jsx(X, { className: "h-4 w-4", "aria-hidden": true }) })] }) })] }), expanded ? (_jsx("tr", { children: _jsx("td", { colSpan: 6, className: "bg-muted/30 px-4 py-4", children: _jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-foreground text-sm", children: p.justification }), details ?? _jsx(ChangePreview, { changes: p.changes })] }) }) })) : null] }));
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Expanded body of a FAQ row: the per-question preview plus a reviewed
|
|
473
|
+
* Approve button. When core could not read the cached section the row shows
|
|
474
|
+
* the recovery hint instead of a preview and offers no approve at all.
|
|
475
|
+
*/
|
|
476
|
+
function FaqProposalDetails({ proposal: p, draft, blocker, busy, canApply, onToggle, onHeadingChange, onApprove, }) {
|
|
477
|
+
const section = p.section;
|
|
478
|
+
if (!section) {
|
|
479
|
+
return (_jsxs("p", { className: "text-muted-foreground flex items-start gap-2 text-sm", children: [_jsx(AlertTriangle, { className: "text-warning mt-0.5 h-4 w-4 shrink-0", "aria-hidden": true }), _jsx("span", { children: FAQ_UNREADABLE_HINT })] }));
|
|
480
|
+
}
|
|
481
|
+
const items = section.content.items.map((item, i) => ({
|
|
482
|
+
...item,
|
|
483
|
+
sourceQuote: p.grounding?.[i]?.sourceQuote || undefined,
|
|
484
|
+
}));
|
|
485
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsx(FaqProposalPreview, { heading: section.content.heading, items: items, signals: p.signals, selected: draft.selected, onToggle: onToggle, headingValue: draft.heading, onHeadingChange: onHeadingChange, disabled: busy }), !p.stale && canApply ? (_jsxs("button", { type: "button", className: btnPrimary, onClick: onApprove, disabled: busy || blocker !== null, children: [_jsx(Check, { className: "h-4 w-4", "aria-hidden": true }), "Approve"] })) : null] }));
|
|
333
486
|
}
|
|
334
487
|
//# sourceMappingURL=ProposalsTab.js.map
|