@prenta/admin 1.11.0 → 1.13.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.
Files changed (75) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/__tests__/components/seo-editor-pane.render.test.js +15 -0
  3. package/dist/__tests__/components/seo-editor-pane.render.test.js.map +1 -1
  4. package/dist/__tests__/lib/seo-service-errors.test.js +87 -1
  5. package/dist/__tests__/lib/seo-service-errors.test.js.map +1 -1
  6. package/dist/__tests__/views/ai-settings.render.test.js +26 -8
  7. package/dist/__tests__/views/ai-settings.render.test.js.map +1 -1
  8. package/dist/__tests__/views/redirects-pane.render.test.js +20 -2
  9. package/dist/__tests__/views/redirects-pane.render.test.js.map +1 -1
  10. package/dist/__tests__/views/seo-settings.render.test.js +110 -5
  11. package/dist/__tests__/views/seo-settings.render.test.js.map +1 -1
  12. package/dist/components/seo/SeoCopilotDrawer.d.ts.map +1 -1
  13. package/dist/components/seo/SeoCopilotDrawer.js +4 -0
  14. package/dist/components/seo/SeoCopilotDrawer.js.map +1 -1
  15. package/dist/components/seo/SeoEditorPane.d.ts.map +1 -1
  16. package/dist/components/seo/SeoEditorPane.js +8 -1
  17. package/dist/components/seo/SeoEditorPane.js.map +1 -1
  18. package/dist/components/seo/SeoIssueFixDrawer.d.ts.map +1 -1
  19. package/dist/components/seo/SeoIssueFixDrawer.js +4 -0
  20. package/dist/components/seo/SeoIssueFixDrawer.js.map +1 -1
  21. package/dist/components/seo/SeoSearchStats.d.ts +4 -0
  22. package/dist/components/seo/SeoSearchStats.d.ts.map +1 -0
  23. package/dist/components/seo/SeoSearchStats.js +91 -0
  24. package/dist/components/seo/SeoSearchStats.js.map +1 -0
  25. package/dist/lib/seo-service.d.ts +60 -0
  26. package/dist/lib/seo-service.d.ts.map +1 -1
  27. package/dist/lib/seo-service.js +53 -1
  28. package/dist/lib/seo-service.js.map +1 -1
  29. package/dist/prenta-admin.css +1 -1
  30. package/dist/views/seo/OverviewTab.js +1 -1
  31. package/dist/views/seo/OverviewTab.js.map +1 -1
  32. package/dist/views/seo/RedirectsTab.d.ts.map +1 -1
  33. package/dist/views/seo/RedirectsTab.js +6 -0
  34. package/dist/views/seo/RedirectsTab.js.map +1 -1
  35. package/dist/views/settings/AISettingsTab.d.ts.map +1 -1
  36. package/dist/views/settings/AISettingsTab.js +1 -1
  37. package/dist/views/settings/AISettingsTab.js.map +1 -1
  38. package/dist/views/settings/AiFeaturesCard.d.ts +8 -3
  39. package/dist/views/settings/AiFeaturesCard.d.ts.map +1 -1
  40. package/dist/views/settings/AiFeaturesCard.js +7 -6
  41. package/dist/views/settings/AiFeaturesCard.js.map +1 -1
  42. package/dist/views/settings/SearchDataCard.d.ts +10 -0
  43. package/dist/views/settings/SearchDataCard.d.ts.map +1 -0
  44. package/dist/views/settings/SearchDataCard.js +126 -0
  45. package/dist/views/settings/SearchDataCard.js.map +1 -0
  46. package/dist/views/settings/SeoDigestCard.d.ts +9 -0
  47. package/dist/views/settings/SeoDigestCard.d.ts.map +1 -0
  48. package/dist/views/settings/SeoDigestCard.js +77 -0
  49. package/dist/views/settings/SeoDigestCard.js.map +1 -0
  50. package/dist/views/settings/SeoSettingsTab.d.ts.map +1 -1
  51. package/dist/views/settings/SeoSettingsTab.js +3 -1
  52. package/dist/views/settings/SeoSettingsTab.js.map +1 -1
  53. package/dist/views/settings/useAiSettings.d.ts +12 -0
  54. package/dist/views/settings/useAiSettings.d.ts.map +1 -1
  55. package/dist/views/settings/useAiSettings.js +29 -1
  56. package/dist/views/settings/useAiSettings.js.map +1 -1
  57. package/package.json +3 -3
  58. package/src/__tests__/components/seo-editor-pane.render.test.tsx +25 -3
  59. package/src/__tests__/lib/seo-service-errors.test.ts +111 -0
  60. package/src/__tests__/views/ai-settings.render.test.tsx +38 -8
  61. package/src/__tests__/views/redirects-pane.render.test.tsx +28 -2
  62. package/src/__tests__/views/seo-settings.render.test.tsx +156 -4
  63. package/src/components/seo/SeoCopilotDrawer.tsx +4 -0
  64. package/src/components/seo/SeoEditorPane.tsx +13 -0
  65. package/src/components/seo/SeoIssueFixDrawer.tsx +4 -0
  66. package/src/components/seo/SeoSearchStats.tsx +183 -0
  67. package/src/lib/seo-service.ts +107 -2
  68. package/src/views/seo/OverviewTab.tsx +1 -1
  69. package/src/views/seo/RedirectsTab.tsx +6 -0
  70. package/src/views/settings/AISettingsTab.tsx +1 -0
  71. package/src/views/settings/AiFeaturesCard.tsx +59 -2
  72. package/src/views/settings/SearchDataCard.tsx +390 -0
  73. package/src/views/settings/SeoDigestCard.tsx +164 -0
  74. package/src/views/settings/SeoSettingsTab.tsx +4 -0
  75. package/src/views/settings/useAiSettings.ts +71 -2
@@ -86,6 +86,22 @@ export interface AiFeatureView {
86
86
  modelId?: string
87
87
  /** Per-feature provider connection override. Usually derived from the model. */
88
88
  providerId?: string
89
+ /** AI output must be human-approved before it is applied. */
90
+ approvalRequired: boolean
91
+ /** Permit applying many AI results in one operation. */
92
+ allowBulkApply: boolean
93
+ /** Site-wide daily request cap; empty string = unlimited (string-buffered input). */
94
+ maxRequestsPerDay: string
95
+ }
96
+
97
+ /** Raw feature shape on the wire (`GET /ai/settings`): governance fields optional, cap numeric. */
98
+ type AiFeatureWire = Omit<
99
+ AiFeatureView,
100
+ 'approvalRequired' | 'allowBulkApply' | 'maxRequestsPerDay'
101
+ > & {
102
+ approvalRequired?: boolean
103
+ allowBulkApply?: boolean
104
+ maxRequestsPerDay?: number | null
89
105
  }
90
106
 
91
107
  export interface AiUsageView {
@@ -143,7 +159,7 @@ interface AiSettingsResponse {
143
159
  settings: {
144
160
  defaultProviderId?: string
145
161
  defaultModelId?: string
146
- features: AiFeatureView[]
162
+ features: AiFeatureWire[]
147
163
  budget: { monthlyTokenLimit?: number }
148
164
  brandVoice?: {
149
165
  enabled?: boolean
@@ -172,6 +188,12 @@ function formFromResponse(res: AiSettingsResponse): AiSettingsForm {
172
188
  ...f,
173
189
  modelId: f.modelId ?? '',
174
190
  providerId: f.providerId ?? '',
191
+ approvalRequired: (f as { approvalRequired?: boolean }).approvalRequired !== false,
192
+ allowBulkApply: (f as { allowBulkApply?: boolean }).allowBulkApply === true,
193
+ maxRequestsPerDay:
194
+ typeof (f as { maxRequestsPerDay?: number }).maxRequestsPerDay === 'number'
195
+ ? String((f as { maxRequestsPerDay?: number }).maxRequestsPerDay)
196
+ : '',
175
197
  })),
176
198
  monthlyTokenLimit:
177
199
  typeof res.settings.budget.monthlyTokenLimit === 'number'
@@ -241,6 +263,15 @@ export interface UseAiSettings {
241
263
  toggleFeature: (key: string, enabled: boolean) => void
242
264
  /** Set a per-feature model override. Empty string reverts to the default model. */
243
265
  setFeatureModel: (key: string, modelId: string) => void
266
+ /** Patch per-feature governance controls (approval, bulk apply, daily cap). */
267
+ setFeatureGovernance: (
268
+ key: string,
269
+ patch: Partial<{
270
+ approvalRequired: boolean
271
+ allowBulkApply: boolean
272
+ maxRequestsPerDay: string
273
+ }>,
274
+ ) => void
244
275
  /** Patch one or more brand voice fields. */
245
276
  setBrandVoice: (patch: Partial<BrandVoiceForm>) => void
246
277
  /** Whether saves should trust the markdown document or quick-edit fields. */
@@ -374,6 +405,34 @@ export function useAiSettings(canEdit: boolean): UseAiSettings {
374
405
  setSaveState('idle')
375
406
  }, [])
376
407
 
408
+ const setFeatureGovernance = useCallback(
409
+ (
410
+ key: string,
411
+ patch: Partial<{
412
+ approvalRequired: boolean
413
+ allowBulkApply: boolean
414
+ maxRequestsPerDay: string
415
+ }>,
416
+ ) => {
417
+ setForm((prev) => ({
418
+ ...prev,
419
+ features: prev.features.map((f) =>
420
+ f.key === key
421
+ ? {
422
+ ...f,
423
+ ...patch,
424
+ ...(patch.maxRequestsPerDay !== undefined
425
+ ? { maxRequestsPerDay: patch.maxRequestsPerDay.replace(/[^\d]/g, '') }
426
+ : {}),
427
+ }
428
+ : f,
429
+ ),
430
+ }))
431
+ setSaveState('idle')
432
+ },
433
+ [],
434
+ )
435
+
377
436
  const setBrandVoice = useCallback((patch: Partial<BrandVoiceForm>) => {
378
437
  setForm((prev) => ({ ...prev, brandVoice: { ...prev.brandVoice, ...patch } }))
379
438
  setSaveState('idle')
@@ -474,7 +533,13 @@ export function useAiSettings(canEdit: boolean): UseAiSettings {
474
533
  return true
475
534
  return form.features.some((f, i) => {
476
535
  const base = baseline.features[i]
477
- return f.enabled !== base?.enabled || (f.modelId ?? '') !== (base?.modelId ?? '')
536
+ return (
537
+ f.enabled !== base?.enabled ||
538
+ (f.modelId ?? '') !== (base?.modelId ?? '') ||
539
+ f.approvalRequired !== base?.approvalRequired ||
540
+ f.allowBulkApply !== base?.allowBulkApply ||
541
+ f.maxRequestsPerDay !== base?.maxRequestsPerDay
542
+ )
478
543
  })
479
544
  }, [form, baseline])
480
545
 
@@ -492,6 +557,9 @@ export function useAiSettings(canEdit: boolean): UseAiSettings {
492
557
  key: f.key,
493
558
  enabled: f.enabled,
494
559
  modelId: f.modelId ? f.modelId : null,
560
+ approvalRequired: f.approvalRequired,
561
+ allowBulkApply: f.allowBulkApply,
562
+ maxRequestsPerDay: f.maxRequestsPerDay === '' ? null : Number(f.maxRequestsPerDay),
495
563
  })),
496
564
  budget: {
497
565
  monthlyTokenLimit:
@@ -661,6 +729,7 @@ export function useAiSettings(canEdit: boolean): UseAiSettings {
661
729
  setMonthlyTokenLimit,
662
730
  toggleFeature,
663
731
  setFeatureModel,
732
+ setFeatureGovernance,
664
733
  setBrandVoice,
665
734
  analyzeBrandVoice,
666
735
  scoreBrandVoice,