@consilioweb/payload-seo-analyzer 1.8.1 → 1.9.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/dist/client.js CHANGED
@@ -1052,6 +1052,8 @@ var fr = {
1052
1052
  },
1053
1053
  seoView: {
1054
1054
  loadingAudit: "Chargement de l'audit SEO...",
1055
+ buildingAudit: "G\xE9n\xE9ration de l'audit SEO en cours\u2026 (calcul\xE9 en arri\xE8re-plan pour ne pas surcharger le serveur, cela peut prendre un moment sur un gros site)",
1056
+ buildTimeout: "La g\xE9n\xE9ration de l'audit prend plus de temps que pr\xE9vu. R\xE9essayez dans quelques instants.",
1055
1057
  errorSaving: "Erreur lors de la sauvegarde",
1056
1058
  auditTitle: "Audit SEO",
1057
1059
  pagesAnalyzed: "pages analys\xE9es",
@@ -1494,7 +1496,19 @@ var fr = {
1494
1496
  generateMeta: "G\xE9n\xE9rer les meta",
1495
1497
  metaTitle: "Meta Title",
1496
1498
  metaDescription: "Meta Description",
1497
- emptyValue: "(vide)"
1499
+ emptyValue: "(vide)",
1500
+ optimizeWithAi: "Optimiser avec l'IA",
1501
+ optimizeIntro: "L'IA analyse la page et propose des meta optimis\xE9es (titre, description, mot-cl\xE9). V\xE9rifiez puis appliquez.",
1502
+ optimizeRunning: "Analyse en cours\u2026",
1503
+ applyAll: "Appliquer",
1504
+ applied: "Appliqu\xE9",
1505
+ whyChanges: "Pourquoi ces changements",
1506
+ labelCurrent: "Actuel",
1507
+ labelSuggested: "Sugg\xE9r\xE9",
1508
+ labelFocusKeyword: "Mot-cl\xE9 cible",
1509
+ heuristicNote: "Suggestions heuristiques (cl\xE9 API Claude non configur\xE9e).",
1510
+ applySaveHint: "Champs remplis \u2014 pensez \xE0 enregistrer le document.",
1511
+ noMetaChange: "Aucun changement propos\xE9."
1498
1512
  },
1499
1513
  scoreHistory: {
1500
1514
  loading: "Chargement de l'historique...",
@@ -1630,6 +1644,8 @@ var en = {
1630
1644
  },
1631
1645
  seoView: {
1632
1646
  loadingAudit: "Loading SEO audit...",
1647
+ buildingAudit: "Building the SEO audit\u2026 (computed in the background to avoid overloading the server \u2014 this can take a moment on a large site)",
1648
+ buildTimeout: "The audit is taking longer than expected. Please try again in a moment.",
1633
1649
  errorSaving: "Error during save",
1634
1650
  auditTitle: "SEO Audit",
1635
1651
  pagesAnalyzed: "pages analyzed",
@@ -2072,7 +2088,19 @@ var en = {
2072
2088
  generateMeta: "Generate meta",
2073
2089
  metaTitle: "Meta Title",
2074
2090
  metaDescription: "Meta Description",
2075
- emptyValue: "(empty)"
2091
+ emptyValue: "(empty)",
2092
+ optimizeWithAi: "Optimize with AI",
2093
+ optimizeIntro: "AI analyzes the page and proposes optimized meta tags (title, description, keyword). Review, then apply.",
2094
+ optimizeRunning: "Analyzing\u2026",
2095
+ applyAll: "Apply",
2096
+ applied: "Applied",
2097
+ whyChanges: "Why these changes",
2098
+ labelCurrent: "Current",
2099
+ labelSuggested: "Suggested",
2100
+ labelFocusKeyword: "Focus keyword",
2101
+ heuristicNote: "Heuristic suggestions (Claude API key not configured).",
2102
+ applySaveHint: "Fields filled \u2014 remember to save the document.",
2103
+ noMetaChange: "No changes proposed."
2076
2104
  },
2077
2105
  scoreHistory: {
2078
2106
  loading: "Loading history...",
@@ -6478,9 +6506,11 @@ var C2 = {
6478
6506
  white: "#fff",
6479
6507
  green: "#22c55e",
6480
6508
  red: "#ef4444",
6509
+ bg: "#fafafa",
6481
6510
  textPrimary: "var(--theme-text, #1a1a1a)",
6482
6511
  textSecondary: "var(--theme-elevation-600, #6b7280)",
6483
6512
  border: "var(--theme-border-color, #000)",
6513
+ inputBg: "var(--theme-input-bg, #fff)",
6484
6514
  surfaceBg: "var(--theme-elevation-0, #fff)",
6485
6515
  surface50: "var(--theme-elevation-50, #f9fafb)"
6486
6516
  };
@@ -6980,10 +7010,48 @@ function useInternalLinkSuggestions(documentId, collection, textContent) {
6980
7010
  }, [textContent, documentId, collection]);
6981
7011
  return { suggestions, loading };
6982
7012
  }
7013
+ function AiDiffRow({
7014
+ label,
7015
+ current,
7016
+ suggested,
7017
+ labelCurrent,
7018
+ labelSuggested,
7019
+ emptyValue,
7020
+ C: colors
7021
+ }) {
7022
+ return /* @__PURE__ */ jsxs("div", { style: { marginBottom: 10 }, children: [
7023
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 10, fontWeight: 700, color: colors.textSecondary, textTransform: "uppercase", marginBottom: 4 }, children: label }),
7024
+ /* @__PURE__ */ jsxs("div", { style: { fontSize: 11, color: colors.textSecondary, marginBottom: 3 }, children: [
7025
+ /* @__PURE__ */ jsxs("span", { style: { fontWeight: 700 }, children: [
7026
+ labelCurrent,
7027
+ ": "
7028
+ ] }),
7029
+ /* @__PURE__ */ jsx("span", { style: { textDecoration: current ? "line-through" : "none", opacity: 0.7 }, children: current || emptyValue })
7030
+ ] }),
7031
+ /* @__PURE__ */ jsxs(
7032
+ "div",
7033
+ {
7034
+ style: {
7035
+ padding: "6px 10px",
7036
+ borderRadius: 6,
7037
+ border: `1px solid var(--theme-elevation-200, #e5e7eb)`,
7038
+ backgroundColor: colors.surface50,
7039
+ fontSize: 12,
7040
+ color: colors.textPrimary,
7041
+ lineHeight: 1.5
7042
+ },
7043
+ children: [
7044
+ /* @__PURE__ */ jsx("span", { style: { fontSize: 10, fontWeight: 700, color: colors.green, textTransform: "uppercase", marginRight: 6 }, children: labelSuggested }),
7045
+ suggested || emptyValue
7046
+ ]
7047
+ }
7048
+ )
7049
+ ] });
7050
+ }
6983
7051
  var SeoAnalyzer = () => {
6984
7052
  const locale = useSeoLocale();
6985
7053
  const t = getDashboardT(locale);
6986
- const [formFields] = useAllFormFields();
7054
+ const [formFields, dispatchFields] = useAllFormFields();
6987
7055
  const initialScoreRef = useRef(null);
6988
7056
  const [suggestionsOpen, setSuggestionsOpen] = useState(true);
6989
7057
  const [cannibalizationExpanded, setCannibalizationExpanded] = useState(false);
@@ -6992,6 +7060,10 @@ var SeoAnalyzer = () => {
6992
7060
  const [aiGenerating, setAiGenerating] = useState(false);
6993
7061
  const [aiResult, setAiResult] = useState(null);
6994
7062
  const [aiCopied, setAiCopied] = useState(null);
7063
+ const [aiOptimizing, setAiOptimizing] = useState(false);
7064
+ const [aiOptimizeResult, setAiOptimizeResult] = useState(null);
7065
+ const [aiOptimizeApplied, setAiOptimizeApplied] = useState(false);
7066
+ const [aiOptimizeError, setAiOptimizeError] = useState(false);
6995
7067
  const getFieldValue = useCallback(
6996
7068
  (path) => {
6997
7069
  if (!formFields) return void 0;
@@ -7719,6 +7791,145 @@ var SeoAnalyzer = () => {
7719
7791
  }
7720
7792
  )
7721
7793
  ] }),
7794
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: 12 }, children: [
7795
+ /* @__PURE__ */ jsx(
7796
+ "button",
7797
+ {
7798
+ type: "button",
7799
+ disabled: aiOptimizing || !documentId,
7800
+ title: !documentId ? t.seoAnalyzer.applySaveHint : void 0,
7801
+ onClick: async () => {
7802
+ setAiOptimizing(true);
7803
+ setAiOptimizeResult(null);
7804
+ setAiOptimizeApplied(false);
7805
+ setAiOptimizeError(false);
7806
+ try {
7807
+ const res = await fetch("/api/seo-plugin/ai-optimize", {
7808
+ method: "POST",
7809
+ credentials: "include",
7810
+ headers: { "Content-Type": "application/json" },
7811
+ body: JSON.stringify({ collection: currentCollection, id: documentId })
7812
+ });
7813
+ if (res.ok) {
7814
+ setAiOptimizeResult(await res.json());
7815
+ } else {
7816
+ setAiOptimizeError(true);
7817
+ }
7818
+ } catch {
7819
+ setAiOptimizeError(true);
7820
+ }
7821
+ setAiOptimizing(false);
7822
+ },
7823
+ style: {
7824
+ display: "flex",
7825
+ alignItems: "center",
7826
+ gap: 6,
7827
+ width: "100%",
7828
+ padding: "10px 14px",
7829
+ borderRadius: 8,
7830
+ border: `2px solid ${C2.border}`,
7831
+ backgroundColor: "#7c3aed",
7832
+ color: "#fff",
7833
+ fontWeight: 800,
7834
+ fontSize: 12,
7835
+ cursor: aiOptimizing || !documentId ? "not-allowed" : "pointer",
7836
+ opacity: aiOptimizing || !documentId ? 0.6 : 1,
7837
+ textTransform: "uppercase",
7838
+ letterSpacing: "0.04em",
7839
+ justifyContent: "center",
7840
+ boxShadow: "2px 2px 0 0 var(--theme-border-color, rgba(0,0,0,1))"
7841
+ },
7842
+ children: aiOptimizing ? t.seoAnalyzer.optimizeRunning : `\u2728 ${t.seoAnalyzer.optimizeWithAi}`
7843
+ }
7844
+ ),
7845
+ aiOptimizeError && /* @__PURE__ */ jsx("div", { style: { marginTop: 8, padding: "8px 12px", borderRadius: 6, fontSize: 11, color: C2.white, backgroundColor: C2.red }, children: t.common.loadingError }),
7846
+ aiOptimizeResult && /* @__PURE__ */ jsxs(
7847
+ "div",
7848
+ {
7849
+ style: {
7850
+ marginTop: 8,
7851
+ padding: "12px 14px",
7852
+ borderRadius: 8,
7853
+ border: `2px solid ${C2.border}`,
7854
+ backgroundColor: C2.surfaceBg
7855
+ },
7856
+ children: [
7857
+ aiOptimizeResult.method === "heuristic" && /* @__PURE__ */ jsx("div", { style: { fontSize: 10, color: C2.textSecondary, marginBottom: 8, fontStyle: "italic" }, children: t.seoAnalyzer.heuristicNote }),
7858
+ /* @__PURE__ */ jsx(
7859
+ AiDiffRow,
7860
+ {
7861
+ label: t.seoAnalyzer.metaTitle,
7862
+ current: aiOptimizeResult.current.metaTitle,
7863
+ suggested: aiOptimizeResult.suggestions.metaTitle,
7864
+ labelCurrent: t.seoAnalyzer.labelCurrent,
7865
+ labelSuggested: t.seoAnalyzer.labelSuggested,
7866
+ emptyValue: t.seoAnalyzer.emptyValue,
7867
+ C: C2
7868
+ }
7869
+ ),
7870
+ /* @__PURE__ */ jsx(
7871
+ AiDiffRow,
7872
+ {
7873
+ label: t.seoAnalyzer.metaDescription,
7874
+ current: aiOptimizeResult.current.metaDescription,
7875
+ suggested: aiOptimizeResult.suggestions.metaDescription,
7876
+ labelCurrent: t.seoAnalyzer.labelCurrent,
7877
+ labelSuggested: t.seoAnalyzer.labelSuggested,
7878
+ emptyValue: t.seoAnalyzer.emptyValue,
7879
+ C: C2
7880
+ }
7881
+ ),
7882
+ aiOptimizeResult.suggestions.focusKeyword && aiOptimizeResult.suggestions.focusKeyword !== aiOptimizeResult.current.focusKeyword && /* @__PURE__ */ jsx(
7883
+ AiDiffRow,
7884
+ {
7885
+ label: t.seoAnalyzer.labelFocusKeyword,
7886
+ current: aiOptimizeResult.current.focusKeyword,
7887
+ suggested: aiOptimizeResult.suggestions.focusKeyword,
7888
+ labelCurrent: t.seoAnalyzer.labelCurrent,
7889
+ labelSuggested: t.seoAnalyzer.labelSuggested,
7890
+ emptyValue: t.seoAnalyzer.emptyValue,
7891
+ C: C2
7892
+ }
7893
+ ),
7894
+ aiOptimizeResult.suggestions.rationale.length > 0 && /* @__PURE__ */ jsxs("div", { style: { marginTop: 4, marginBottom: 10 }, children: [
7895
+ /* @__PURE__ */ jsx("div", { style: { fontSize: 10, fontWeight: 700, color: C2.textSecondary, textTransform: "uppercase", marginBottom: 4 }, children: t.seoAnalyzer.whyChanges }),
7896
+ /* @__PURE__ */ jsx("ul", { style: { margin: 0, paddingLeft: 16, fontSize: 11, color: C2.textPrimary, lineHeight: 1.5 }, children: aiOptimizeResult.suggestions.rationale.map((r, i) => /* @__PURE__ */ jsx("li", { children: r }, i)) })
7897
+ ] }),
7898
+ /* @__PURE__ */ jsx(
7899
+ "button",
7900
+ {
7901
+ type: "button",
7902
+ disabled: aiOptimizeApplied,
7903
+ onClick: () => {
7904
+ const sug = aiOptimizeResult.suggestions;
7905
+ if (sug.metaTitle) dispatchFields({ type: "UPDATE", path: "meta.title", value: sug.metaTitle });
7906
+ if (sug.metaDescription) dispatchFields({ type: "UPDATE", path: "meta.description", value: sug.metaDescription });
7907
+ if (sug.focusKeyword && sug.focusKeyword !== aiOptimizeResult.current.focusKeyword) {
7908
+ dispatchFields({ type: "UPDATE", path: "focusKeyword", value: sug.focusKeyword });
7909
+ }
7910
+ setAiOptimizeApplied(true);
7911
+ },
7912
+ style: {
7913
+ width: "100%",
7914
+ padding: "9px 14px",
7915
+ borderRadius: 6,
7916
+ border: `2px solid ${C2.border}`,
7917
+ backgroundColor: aiOptimizeApplied ? C2.green : C2.cyan,
7918
+ color: aiOptimizeApplied ? C2.white : C2.black,
7919
+ fontWeight: 800,
7920
+ fontSize: 11,
7921
+ textTransform: "uppercase",
7922
+ letterSpacing: "0.04em",
7923
+ cursor: aiOptimizeApplied ? "default" : "pointer"
7924
+ },
7925
+ children: aiOptimizeApplied ? `\u2713 ${t.seoAnalyzer.applied}` : t.seoAnalyzer.applyAll
7926
+ }
7927
+ ),
7928
+ aiOptimizeApplied && /* @__PURE__ */ jsx("div", { style: { marginTop: 6, fontSize: 10, color: C2.textSecondary, textAlign: "center" }, children: t.seoAnalyzer.applySaveHint })
7929
+ ]
7930
+ }
7931
+ )
7932
+ ] }),
7722
7933
  suggestions.length > 0 && /* @__PURE__ */ jsxs(
7723
7934
  "div",
7724
7935
  {
@@ -9058,6 +9269,7 @@ function SeoView() {
9058
9269
  const [items, setItems] = useState([]);
9059
9270
  const [stats, setStats] = useState(null);
9060
9271
  const [loading, setLoading] = useState(true);
9272
+ const [building, setBuilding] = useState(false);
9061
9273
  const [error, setError] = useState(null);
9062
9274
  const [filter, setFilter] = useState("all");
9063
9275
  const [scoreFilter, setScoreFilter] = useState("all");
@@ -9074,9 +9286,22 @@ function SeoView() {
9074
9286
  const fetchAudit = useCallback(async (forceRefresh = false) => {
9075
9287
  setLoading(true);
9076
9288
  setError(null);
9289
+ setBuilding(false);
9077
9290
  try {
9078
- const url = forceRefresh ? "/api/seo-plugin/audit?nocache=1" : "/api/seo-plugin/audit";
9079
- const res = await fetch(url, { credentials: "include", cache: "no-store" });
9291
+ const base = "/api/seo-plugin/audit";
9292
+ let res = await fetch(forceRefresh ? `${base}?nocache=1` : base, {
9293
+ credentials: "include",
9294
+ cache: "no-store"
9295
+ });
9296
+ let attempts = 0;
9297
+ const MAX_POLLS = 90;
9298
+ while (res.status === 202 && attempts < MAX_POLLS) {
9299
+ setBuilding(true);
9300
+ await new Promise((r) => setTimeout(r, 3e3));
9301
+ res = await fetch(base, { credentials: "include", cache: "no-store" });
9302
+ attempts++;
9303
+ }
9304
+ if (res.status === 202) throw new Error(t.seoView.buildTimeout);
9080
9305
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
9081
9306
  const data = await res.json();
9082
9307
  setItems(data.results || []);
@@ -9084,6 +9309,7 @@ function SeoView() {
9084
9309
  } catch (e) {
9085
9310
  setError(e instanceof Error ? e.message : t.common.loadingError);
9086
9311
  }
9312
+ setBuilding(false);
9087
9313
  setLoading(false);
9088
9314
  }, [t]);
9089
9315
  useEffect(() => {
@@ -9525,7 +9751,7 @@ function SeoView() {
9525
9751
  fontSize: 14,
9526
9752
  fontFamily: "var(--font-body, system-ui)"
9527
9753
  },
9528
- children: t.seoView.loadingAudit
9754
+ children: building ? t.seoView.buildingAudit : t.seoView.loadingAudit
9529
9755
  }
9530
9756
  );
9531
9757
  }