@focus-reactive/payload-plugin-seo 1.5.0 → 1.6.1

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 (84) hide show
  1. package/README.md +198 -6
  2. package/dist/admin.css +97 -1
  3. package/dist/client-config/registry.d.ts +7 -0
  4. package/dist/client-config/registry.d.ts.map +1 -0
  5. package/dist/client-config/registry.js +16 -0
  6. package/dist/client-config/registry.js.map +1 -0
  7. package/dist/components/SeoField/Meter.d.ts +10 -0
  8. package/dist/components/SeoField/Meter.d.ts.map +1 -0
  9. package/dist/components/SeoField/Meter.js +59 -0
  10. package/dist/components/SeoField/Meter.js.map +1 -0
  11. package/dist/components/SeoField/icons.d.ts +5 -0
  12. package/dist/components/SeoField/icons.d.ts.map +1 -0
  13. package/dist/components/SeoField/icons.js +12 -0
  14. package/dist/components/SeoField/icons.js.map +1 -0
  15. package/dist/components/SeoField/index.d.ts +20 -0
  16. package/dist/components/SeoField/index.d.ts.map +1 -0
  17. package/dist/components/SeoField/index.js +118 -0
  18. package/dist/components/SeoField/index.js.map +1 -0
  19. package/dist/components/SeoField/useGenerate.d.ts +15 -0
  20. package/dist/components/SeoField/useGenerate.d.ts.map +1 -0
  21. package/dist/components/SeoField/useGenerate.js +102 -0
  22. package/dist/components/SeoField/useGenerate.js.map +1 -0
  23. package/dist/constants/generation.d.ts +13 -0
  24. package/dist/constants/generation.d.ts.map +1 -0
  25. package/dist/constants/generation.js +21 -0
  26. package/dist/constants/generation.js.map +1 -0
  27. package/dist/engine/helpers/title-progress.d.ts +2 -1
  28. package/dist/engine/helpers/title-progress.d.ts.map +1 -1
  29. package/dist/engine/helpers/title-progress.js +1 -1
  30. package/dist/engine/helpers/title-progress.js.map +1 -1
  31. package/dist/fields/index.d.ts +3 -0
  32. package/dist/fields/index.d.ts.map +1 -0
  33. package/dist/fields/index.js +5 -0
  34. package/dist/fields/index.js.map +1 -0
  35. package/dist/fields/onPublishHook.d.ts +8 -0
  36. package/dist/fields/onPublishHook.d.ts.map +1 -0
  37. package/dist/fields/onPublishHook.js +95 -0
  38. package/dist/fields/onPublishHook.js.map +1 -0
  39. package/dist/fields/seoTextField.d.ts +23 -0
  40. package/dist/fields/seoTextField.d.ts.map +1 -0
  41. package/dist/fields/seoTextField.js +43 -0
  42. package/dist/fields/seoTextField.js.map +1 -0
  43. package/dist/index.d.ts +3 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +3 -1
  46. package/dist/index.js.map +1 -1
  47. package/dist/measure/measure.d.ts +16 -0
  48. package/dist/measure/measure.d.ts.map +1 -0
  49. package/dist/measure/measure.js +38 -0
  50. package/dist/measure/measure.js.map +1 -0
  51. package/dist/providers/SeoClientConfigProvider.d.ts +8 -0
  52. package/dist/providers/SeoClientConfigProvider.d.ts.map +1 -0
  53. package/dist/providers/SeoClientConfigProvider.js +16 -0
  54. package/dist/providers/SeoClientConfigProvider.js.map +1 -0
  55. package/dist/server/generate/apiKey.d.ts +3 -0
  56. package/dist/server/generate/apiKey.d.ts.map +1 -0
  57. package/dist/server/generate/apiKey.js +11 -0
  58. package/dist/server/generate/apiKey.js.map +1 -0
  59. package/dist/server/generate/endpoint.d.ts +3 -0
  60. package/dist/server/generate/endpoint.d.ts.map +1 -0
  61. package/dist/server/generate/endpoint.js +47 -0
  62. package/dist/server/generate/endpoint.js.map +1 -0
  63. package/dist/server/generate/generateForField.d.ts +18 -0
  64. package/dist/server/generate/generateForField.d.ts.map +1 -0
  65. package/dist/server/generate/generateForField.js +41 -0
  66. package/dist/server/generate/generateForField.js.map +1 -0
  67. package/dist/server/generate/openai.d.ts +9 -0
  68. package/dist/server/generate/openai.d.ts.map +1 -0
  69. package/dist/server/generate/openai.js +43 -0
  70. package/dist/server/generate/openai.js.map +1 -0
  71. package/dist/server/generate/prompts.d.ts +19 -0
  72. package/dist/server/generate/prompts.d.ts.map +1 -0
  73. package/dist/server/generate/prompts.js +32 -0
  74. package/dist/server/generate/prompts.js.map +1 -0
  75. package/dist/server/generate/serverResolveDocs.d.ts +4 -0
  76. package/dist/server/generate/serverResolveDocs.d.ts.map +1 -0
  77. package/dist/server/generate/serverResolveDocs.js +38 -0
  78. package/dist/server/generate/serverResolveDocs.js.map +1 -0
  79. package/dist/types/config.d.ts +26 -0
  80. package/dist/types/config.d.ts.map +1 -1
  81. package/dist/utils/config/overrideAdmin.d.ts.map +1 -1
  82. package/dist/utils/config/overrideAdmin.js +27 -1
  83. package/dist/utils/config/overrideAdmin.js.map +1 -1
  84. package/package.json +13 -1
@@ -0,0 +1,7 @@
1
+ export interface SeoClientConfig {
2
+ enabled: boolean;
3
+ extractByCollection: Record<string, string>;
4
+ }
5
+ export declare function registerSeoClientConfig(config: SeoClientConfig): void;
6
+ export declare function getSeoClientConfig(): SeoClientConfig;
7
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/client-config/registry.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7C;AAOD,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAErE;AAED,wBAAgB,kBAAkB,IAAI,eAAe,CAIpD"}
@@ -0,0 +1,16 @@
1
+ const STORE_KEY = "__FR_SEO_CLIENT_CONFIG__";
2
+ const EMPTY = {
3
+ enabled: false,
4
+ extractByCollection: {}
5
+ };
6
+ function registerSeoClientConfig(config) {
7
+ globalThis[STORE_KEY] = config;
8
+ }
9
+ function getSeoClientConfig() {
10
+ return globalThis[STORE_KEY] ?? EMPTY;
11
+ }
12
+ export {
13
+ getSeoClientConfig,
14
+ registerSeoClientConfig
15
+ };
16
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/client-config/registry.ts"],"sourcesContent":["const STORE_KEY = \"__FR_SEO_CLIENT_CONFIG__\";\n\nexport interface SeoClientConfig {\n enabled: boolean;\n extractByCollection: Record<string, string>;\n}\n\nconst EMPTY: SeoClientConfig = {\n enabled: false,\n extractByCollection: {},\n};\n\nexport function registerSeoClientConfig(config: SeoClientConfig): void {\n (globalThis as Record<string, unknown>)[STORE_KEY] = config;\n}\n\nexport function getSeoClientConfig(): SeoClientConfig {\n return (\n ((globalThis as Record<string, unknown>)[STORE_KEY] as SeoClientConfig | undefined) ?? EMPTY\n );\n}\n"],"mappings":"AAAA,MAAM,YAAY;AAOlB,MAAM,QAAyB;AAAA,EAC7B,SAAS;AAAA,EACT,qBAAqB,CAAC;AACxB;AAEO,SAAS,wBAAwB,QAA+B;AACrE,EAAC,WAAuC,SAAS,IAAI;AACvD;AAEO,SAAS,qBAAsC;AACpD,SACI,WAAuC,SAAS,KAAqC;AAE3F;","names":[]}
@@ -0,0 +1,10 @@
1
+ import type { Measurement } from "../../measure/measure";
2
+ interface MeterProps {
3
+ measurement: Measurement;
4
+ hasText: boolean;
5
+ loading: boolean;
6
+ kindLabel: string;
7
+ }
8
+ export declare function Meter({ measurement, hasText, loading, kindLabel }: MeterProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=Meter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Meter.d.ts","sourceRoot":"","sources":["../../../src/components/SeoField/Meter.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAoBzD,UAAU,UAAU;IAClB,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,KAAK,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,2CA0C7E"}
@@ -0,0 +1,59 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Pill } from "../../ui/Pill";
4
+ import { cn } from "../../utils/style";
5
+ const STATUS_TO_VARIANT = {
6
+ good: "good",
7
+ short: "warn",
8
+ long: "bad"
9
+ };
10
+ const STATUS_LABEL = {
11
+ good: "Good",
12
+ short: "Too short",
13
+ long: "Too long"
14
+ };
15
+ const FILL_CLASS = {
16
+ good: "bg-seo-good",
17
+ short: "bg-seo-warn",
18
+ long: "bg-seo-bad"
19
+ };
20
+ function Meter({ measurement, hasText, loading, kindLabel }) {
21
+ const { value, min, max, status, unit } = measurement;
22
+ const readout = unit === "px" ? `${value} / ${max} px` : `${value} / ${max}`;
23
+ const pct = (n) => `${Math.min(100, Math.max(0, n / max * 100))}%`;
24
+ return /* @__PURE__ */ jsxs("div", { className: "mt-[11px] flex items-center gap-[12px]", children: [
25
+ /* @__PURE__ */ jsxs("div", { className: "relative h-[6px] flex-1 overflow-hidden rounded-full bg-neutral-100", children: [
26
+ /* @__PURE__ */ jsx(
27
+ "span",
28
+ {
29
+ className: "absolute inset-y-0 border-x border-seo-good/40 bg-seo-good/15",
30
+ style: {
31
+ left: pct(min),
32
+ right: `calc(100% - ${pct(max)})`
33
+ }
34
+ }
35
+ ),
36
+ hasText && !loading && /* @__PURE__ */ jsx(
37
+ "span",
38
+ {
39
+ className: cn("absolute inset-y-0 left-0 rounded-full", FILL_CLASS[status]),
40
+ style: { width: pct(value) }
41
+ }
42
+ ),
43
+ loading && /* @__PURE__ */ jsx("span", { className: "absolute inset-0 animate-pulse bg-neutral-200" })
44
+ ] }),
45
+ /* @__PURE__ */ jsx(
46
+ "span",
47
+ {
48
+ className: "whitespace-nowrap text-[12px] tabular-nums text-neutral-500",
49
+ style: { fontFamily: "ui-monospace, monospace" },
50
+ children: loading ? `\u2014 / ${max}` : readout
51
+ }
52
+ ),
53
+ hasText && !loading ? /* @__PURE__ */ jsx(Pill, { variant: STATUS_TO_VARIANT[status], children: STATUS_LABEL[status] }) : /* @__PURE__ */ jsx(Pill, { variant: "neutral", children: loading ? "Working\u2026" : `No ${kindLabel}` })
54
+ ] });
55
+ }
56
+ export {
57
+ Meter
58
+ };
59
+ //# sourceMappingURL=Meter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/SeoField/Meter.tsx"],"sourcesContent":["\"use client\";\n\nimport type { Measurement } from \"../../measure/measure\";\nimport { Pill } from \"../../ui/Pill\";\nimport { cn } from \"../../utils/style\";\n\nconst STATUS_TO_VARIANT = {\n good: \"good\",\n short: \"warn\",\n long: \"bad\",\n} as const;\nconst STATUS_LABEL = {\n good: \"Good\",\n short: \"Too short\",\n long: \"Too long\",\n} as const;\nconst FILL_CLASS = {\n good: \"bg-seo-good\",\n short: \"bg-seo-warn\",\n long: \"bg-seo-bad\",\n} as const;\n\ninterface MeterProps {\n measurement: Measurement;\n hasText: boolean;\n loading: boolean;\n kindLabel: string;\n}\n\nexport function Meter({ measurement, hasText, loading, kindLabel }: MeterProps) {\n const { value, min, max, status, unit } = measurement;\n\n const readout = unit === \"px\" ? `${value} / ${max} px` : `${value} / ${max}`;\n\n const pct = (n: number) => `${Math.min(100, Math.max(0, (n / max) * 100))}%`;\n\n return (\n <div className=\"mt-[11px] flex items-center gap-[12px]\">\n <div className=\"relative h-[6px] flex-1 overflow-hidden rounded-full bg-neutral-100\">\n <span\n className=\"absolute inset-y-0 border-x border-seo-good/40 bg-seo-good/15\"\n style={{\n left: pct(min),\n right: `calc(100% - ${pct(max)})`,\n }}\n />\n\n {hasText && !loading && (\n <span\n className={cn(\"absolute inset-y-0 left-0 rounded-full\", FILL_CLASS[status])}\n style={{ width: pct(value) }}\n />\n )}\n\n {loading && <span className=\"absolute inset-0 animate-pulse bg-neutral-200\" />}\n </div>\n\n <span\n className=\"whitespace-nowrap text-[12px] tabular-nums text-neutral-500\"\n style={{ fontFamily: \"ui-monospace, monospace\" }}\n >\n {loading ? `— / ${max}` : readout}\n </span>\n\n {hasText && !loading ? (\n <Pill variant={STATUS_TO_VARIANT[status]}>{STATUS_LABEL[status]}</Pill>\n ) : (\n <Pill variant=\"neutral\">{loading ? \"Working…\" : `No ${kindLabel}`}</Pill>\n )}\n </div>\n );\n}\n"],"mappings":";AAsCM,SACE,KADF;AAnCN,SAAS,YAAY;AACrB,SAAS,UAAU;AAEnB,MAAM,oBAAoB;AAAA,EACxB,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AACA,MAAM,eAAe;AAAA,EACnB,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AACA,MAAM,aAAa;AAAA,EACjB,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AASO,SAAS,MAAM,EAAE,aAAa,SAAS,SAAS,UAAU,GAAe;AAC9E,QAAM,EAAE,OAAO,KAAK,KAAK,QAAQ,KAAK,IAAI;AAE1C,QAAM,UAAU,SAAS,OAAO,GAAG,KAAK,MAAM,GAAG,QAAQ,GAAG,KAAK,MAAM,GAAG;AAE1E,QAAM,MAAM,CAAC,MAAc,GAAG,KAAK,IAAI,KAAK,KAAK,IAAI,GAAI,IAAI,MAAO,GAAG,CAAC,CAAC;AAEzE,SACE,qBAAC,SAAI,WAAU,0CACb;AAAA,yBAAC,SAAI,WAAU,uEACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,OAAO;AAAA,YACL,MAAM,IAAI,GAAG;AAAA,YACb,OAAO,eAAe,IAAI,GAAG,CAAC;AAAA,UAChC;AAAA;AAAA,MACF;AAAA,MAEC,WAAW,CAAC,WACX;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,GAAG,0CAA0C,WAAW,MAAM,CAAC;AAAA,UAC1E,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE;AAAA;AAAA,MAC7B;AAAA,MAGD,WAAW,oBAAC,UAAK,WAAU,iDAAgD;AAAA,OAC9E;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,OAAO,EAAE,YAAY,0BAA0B;AAAA,QAE9C,oBAAU,YAAO,GAAG,KAAK;AAAA;AAAA,IAC5B;AAAA,IAEC,WAAW,CAAC,UACX,oBAAC,QAAK,SAAS,kBAAkB,MAAM,GAAI,uBAAa,MAAM,GAAE,IAEhE,oBAAC,QAAK,SAAQ,WAAW,oBAAU,kBAAa,MAAM,SAAS,IAAG;AAAA,KAEtE;AAEJ;","names":[]}
@@ -0,0 +1,5 @@
1
+ export declare const GenerateIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
2
+ export declare const AutoPublishIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
3
+ export declare const ErrorIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
4
+ export declare const SpinnerIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
5
+ //# sourceMappingURL=icons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../../src/components/SeoField/icons.tsx"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,2IAAW,CAAC;AACrC,eAAO,MAAM,eAAe,2IAAe,CAAC;AAC5C,eAAO,MAAM,SAAS,2IAAc,CAAC;AACrC,eAAO,MAAM,WAAW,2IAAU,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { AlertCircle, Loader2, Sparkles, WandSparkles } from "lucide-react";
2
+ const GenerateIcon = Sparkles;
3
+ const AutoPublishIcon = WandSparkles;
4
+ const ErrorIcon = AlertCircle;
5
+ const SpinnerIcon = Loader2;
6
+ export {
7
+ AutoPublishIcon,
8
+ ErrorIcon,
9
+ GenerateIcon,
10
+ SpinnerIcon
11
+ };
12
+ //# sourceMappingURL=icons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/SeoField/icons.tsx"],"sourcesContent":["import { AlertCircle, Loader2, Sparkles, WandSparkles } from \"lucide-react\";\n\nexport const GenerateIcon = Sparkles;\nexport const AutoPublishIcon = WandSparkles;\nexport const ErrorIcon = AlertCircle;\nexport const SpinnerIcon = Loader2;\n"],"mappings":"AAAA,SAAS,aAAa,SAAS,UAAU,oBAAoB;AAEtD,MAAM,eAAe;AACrB,MAAM,kBAAkB;AACxB,MAAM,YAAY;AAClB,MAAM,cAAc;","names":[]}
@@ -0,0 +1,20 @@
1
+ import type { RangeOverride } from "../../measure/measure";
2
+ import type { SeoFieldKind } from "../../server/generate/prompts";
3
+ interface SeoFieldProps {
4
+ path: string;
5
+ field?: {
6
+ label?: Record<string, string> | string;
7
+ required?: boolean;
8
+ admin?: {
9
+ description?: Record<string, string> | string;
10
+ };
11
+ };
12
+ readOnly?: boolean;
13
+ kind: SeoFieldKind;
14
+ showButton?: boolean;
15
+ generateOnPublish?: boolean;
16
+ range?: RangeOverride;
17
+ }
18
+ export declare function SeoField(props: SeoFieldProps): import("react/jsx-runtime").JSX.Element;
19
+ export default SeoField;
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/SeoField/index.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAOlE,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE;QACN,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC;QACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,KAAK,CAAC,EAAE;YAAE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;SAAE,CAAC;KAC3D,CAAC;IACF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,YAAY,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAaD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,2CAkH5C;AAED,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,118 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { FieldLabel, TextareaInput, TextInput, useField } from "@payloadcms/ui";
4
+ import { getSeoClientConfig } from "../../client-config/registry";
5
+ import { measureDescription, measureTitle } from "../../measure/measure";
6
+ import { Tooltip, TooltipText, TooltipTitle } from "../../ui/Tooltip";
7
+ import { cn } from "../../utils/style";
8
+ import { AutoPublishIcon, ErrorIcon, GenerateIcon, SpinnerIcon } from "./icons";
9
+ import { Meter } from "./Meter";
10
+ import { useGenerate } from "./useGenerate";
11
+ const GEN_BTN_CLASS = "inline-flex items-center gap-[6px] rounded-rs border border-neutral-200 bg-transparent px-[9px] py-[4px] text-[12px] font-semibold text-neutral-800 transition-colors hover:border-neutral-300 hover:bg-neutral-100 disabled:cursor-default disabled:text-neutral-400 cursor-pointer";
12
+ function SeoField(props) {
13
+ const { path, field, readOnly, kind, showButton, generateOnPublish, range } = props;
14
+ const fieldState = useField({ path });
15
+ const { value, setValue, showError } = fieldState;
16
+ const injected = fieldState.customComponents ?? {};
17
+ const text = value ?? "";
18
+ const measurement = kind === "title" ? measureTitle(text, range) : measureDescription(text, range);
19
+ const kindLabel = kind === "title" ? "title" : "description";
20
+ const { generate, status, error } = useGenerate({ kind, measurement, setValue });
21
+ const enabled = getSeoClientConfig().enabled;
22
+ const loading = status === "loading";
23
+ const labelRow = /* @__PURE__ */ jsxs("div", { className: "flex min-h-[26px] items-center justify-between gap-[12px] pb-[5px] [&_.field-label]:pb-0!", children: [
24
+ injected.Label ?? /* @__PURE__ */ jsx(FieldLabel, { label: field?.label, path, required: field?.required }),
25
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-[8px]", children: [
26
+ status === "error" && /* @__PURE__ */ jsx(
27
+ Tooltip,
28
+ {
29
+ align: "end",
30
+ content: /* @__PURE__ */ jsxs(Fragment, { children: [
31
+ /* @__PURE__ */ jsx(TooltipTitle, { children: "Generation failed" }),
32
+ /* @__PURE__ */ jsx(TooltipText, { children: error ?? "Your text is unchanged \u2014 press Generate to try again." })
33
+ ] }),
34
+ side: "bottom",
35
+ children: /* @__PURE__ */ jsx(ErrorIcon, { "aria-label": "Generation failed", className: "size-[14px] text-seo-bad" })
36
+ }
37
+ ),
38
+ generateOnPublish && enabled && /* @__PURE__ */ jsx(
39
+ Tooltip,
40
+ {
41
+ align: "end",
42
+ content: /* @__PURE__ */ jsxs(Fragment, { children: [
43
+ /* @__PURE__ */ jsx(TooltipTitle, { children: "Generated automatically" }),
44
+ /* @__PURE__ */ jsxs(TooltipText, { children: [
45
+ "Left empty, this ",
46
+ kindLabel,
47
+ " is generated from the page content when you publish. Type a value to keep your own."
48
+ ] })
49
+ ] }),
50
+ side: "bottom",
51
+ children: /* @__PURE__ */ jsx(
52
+ AutoPublishIcon,
53
+ {
54
+ "aria-label": "Generated on publish",
55
+ className: "size-[14px] text-neutral-500"
56
+ }
57
+ )
58
+ }
59
+ ),
60
+ showButton && enabled && /* @__PURE__ */ jsxs(
61
+ "button",
62
+ {
63
+ className: GEN_BTN_CLASS,
64
+ disabled: loading || readOnly,
65
+ onClick: generate,
66
+ type: "button",
67
+ children: [
68
+ loading ? /* @__PURE__ */ jsx(SpinnerIcon, { className: "size-[14px] animate-spin" }) : /* @__PURE__ */ jsx(GenerateIcon, { className: "size-[14px]" }),
69
+ loading ? "Generating\u2026" : "Generate"
70
+ ]
71
+ }
72
+ )
73
+ ] })
74
+ ] });
75
+ const commonInputProps = {
76
+ AfterInput: injected.AfterInput,
77
+ BeforeInput: injected.BeforeInput,
78
+ Description: injected.Description,
79
+ Error: injected.Error,
80
+ Label: labelRow,
81
+ description: injected.Description ? void 0 : field?.admin?.description,
82
+ path,
83
+ readOnly: loading || readOnly,
84
+ showError,
85
+ value: text
86
+ };
87
+ return /* @__PURE__ */ jsxs("div", { className: cn("seo-generated-field", showError && "error"), children: [
88
+ kind === "description" ? /* @__PURE__ */ jsx(
89
+ TextareaInput,
90
+ {
91
+ ...commonInputProps,
92
+ onChange: (e) => setValue(e.target.value),
93
+ rows: 3
94
+ }
95
+ ) : /* @__PURE__ */ jsx(
96
+ TextInput,
97
+ {
98
+ ...commonInputProps,
99
+ onChange: (e) => setValue(e.target.value)
100
+ }
101
+ ),
102
+ /* @__PURE__ */ jsx(
103
+ Meter,
104
+ {
105
+ hasText: text.trim().length > 0,
106
+ kindLabel,
107
+ loading,
108
+ measurement
109
+ }
110
+ )
111
+ ] });
112
+ }
113
+ var SeoField_default = SeoField;
114
+ export {
115
+ SeoField,
116
+ SeoField_default as default
117
+ };
118
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/SeoField/index.tsx"],"sourcesContent":["\"use client\";\n\nimport { FieldLabel, TextareaInput, TextInput, useField } from \"@payloadcms/ui\";\nimport type { ChangeEvent, ReactNode } from \"react\";\nimport { getSeoClientConfig } from \"../../client-config/registry\";\nimport { measureDescription, measureTitle } from \"../../measure/measure\";\nimport type { RangeOverride } from \"../../measure/measure\";\nimport type { SeoFieldKind } from \"../../server/generate/prompts\";\nimport { Tooltip, TooltipText, TooltipTitle } from \"../../ui/Tooltip\";\nimport { cn } from \"../../utils/style\";\nimport { AutoPublishIcon, ErrorIcon, GenerateIcon, SpinnerIcon } from \"./icons\";\nimport { Meter } from \"./Meter\";\nimport { useGenerate } from \"./useGenerate\";\n\ninterface SeoFieldProps {\n path: string;\n field?: {\n label?: Record<string, string> | string;\n required?: boolean;\n admin?: { description?: Record<string, string> | string };\n };\n readOnly?: boolean;\n kind: SeoFieldKind;\n showButton?: boolean;\n generateOnPublish?: boolean;\n range?: RangeOverride;\n}\n\nconst GEN_BTN_CLASS =\n \"inline-flex items-center gap-[6px] rounded-rs border border-neutral-200 bg-transparent px-[9px] py-[4px] text-[12px] font-semibold text-neutral-800 transition-colors hover:border-neutral-300 hover:bg-neutral-100 disabled:cursor-default disabled:text-neutral-400 cursor-pointer\";\n\ninterface InjectedComponents {\n Label?: ReactNode;\n Description?: ReactNode;\n BeforeInput?: ReactNode;\n AfterInput?: ReactNode;\n Error?: ReactNode;\n}\n\nexport function SeoField(props: SeoFieldProps) {\n const { path, field, readOnly, kind, showButton, generateOnPublish, range } = props;\n const fieldState = useField<string>({ path });\n const { value, setValue, showError } = fieldState;\n const injected: InjectedComponents =\n (fieldState as { customComponents?: InjectedComponents }).customComponents ?? {};\n const text = value ?? \"\";\n const measurement =\n kind === \"title\" ? measureTitle(text, range) : measureDescription(text, range);\n const kindLabel = kind === \"title\" ? \"title\" : \"description\";\n\n const { generate, status, error } = useGenerate({ kind, measurement, setValue });\n const enabled = getSeoClientConfig().enabled;\n const loading = status === \"loading\";\n\n const labelRow = (\n <div className=\"flex min-h-[26px] items-center justify-between gap-[12px] pb-[5px] [&_.field-label]:pb-0!\">\n {injected.Label ?? <FieldLabel label={field?.label} path={path} required={field?.required} />}\n\n <span className=\"inline-flex items-center gap-[8px]\">\n {status === \"error\" && (\n <Tooltip\n align=\"end\"\n content={\n <>\n <TooltipTitle>Generation failed</TooltipTitle>\n <TooltipText>\n {error ?? \"Your text is unchanged — press Generate to try again.\"}\n </TooltipText>\n </>\n }\n side=\"bottom\"\n >\n <ErrorIcon aria-label=\"Generation failed\" className=\"size-[14px] text-seo-bad\" />\n </Tooltip>\n )}\n\n {generateOnPublish && enabled && (\n <Tooltip\n align=\"end\"\n content={\n <>\n <TooltipTitle>Generated automatically</TooltipTitle>\n <TooltipText>\n Left empty, this {kindLabel} is generated from the page content when you publish.\n Type a value to keep your own.\n </TooltipText>\n </>\n }\n side=\"bottom\"\n >\n <AutoPublishIcon\n aria-label=\"Generated on publish\"\n className=\"size-[14px] text-neutral-500\"\n />\n </Tooltip>\n )}\n\n {showButton && enabled && (\n <button\n className={GEN_BTN_CLASS}\n disabled={loading || readOnly}\n onClick={generate}\n type=\"button\"\n >\n {loading ? (\n <SpinnerIcon className=\"size-[14px] animate-spin\" />\n ) : (\n <GenerateIcon className=\"size-[14px]\" />\n )}\n\n {loading ? \"Generating…\" : \"Generate\"}\n </button>\n )}\n </span>\n </div>\n );\n\n const commonInputProps = {\n AfterInput: injected.AfterInput,\n BeforeInput: injected.BeforeInput,\n Description: injected.Description,\n Error: injected.Error,\n Label: labelRow,\n description: injected.Description ? undefined : field?.admin?.description,\n path,\n readOnly: loading || readOnly,\n showError,\n value: text,\n };\n\n return (\n <div className={cn(\"seo-generated-field\", showError && \"error\")}>\n {kind === \"description\" ? (\n <TextareaInput\n {...commonInputProps}\n onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setValue(e.target.value)}\n rows={3}\n />\n ) : (\n <TextInput\n {...commonInputProps}\n onChange={(e: ChangeEvent<HTMLInputElement>) => setValue(e.target.value)}\n />\n )}\n\n <Meter\n hasText={text.trim().length > 0}\n kindLabel={kindLabel}\n loading={loading}\n measurement={measurement}\n />\n </div>\n );\n}\n\nexport default SeoField;\n"],"mappings":"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","names":[]}
@@ -0,0 +1,15 @@
1
+ import type { Measurement } from "../../measure/measure";
2
+ import type { SeoFieldKind } from "../../server/generate/prompts";
3
+ export type GenerateStatus = "idle" | "loading" | "error";
4
+ interface UseGenerateProps {
5
+ kind: SeoFieldKind;
6
+ measurement: Measurement;
7
+ setValue: (v: string) => void;
8
+ }
9
+ export declare function useGenerate({ kind, measurement, setValue }: UseGenerateProps): {
10
+ generate: () => Promise<void>;
11
+ status: GenerateStatus;
12
+ error: string | null;
13
+ };
14
+ export {};
15
+ //# sourceMappingURL=useGenerate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGenerate.d.ts","sourceRoot":"","sources":["../../../src/components/SeoField/useGenerate.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAalE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAE1D,UAAU,gBAAgB;IACxB,IAAI,EAAE,YAAY,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC;IACzB,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,gBAAgB;;;;EA8E5E"}
@@ -0,0 +1,102 @@
1
+ "use client";
2
+ import { useAllFormFields, useConfig, useDocumentInfo, useLocale } from "@payloadcms/ui";
3
+ import { reduceFieldsToValues } from "payload/shared";
4
+ import { useCallback, useRef, useState } from "react";
5
+ import { getSeoClientConfig } from "../../client-config/registry";
6
+ import { GENERATE_ENDPOINT_PATH } from "../../constants/generation";
7
+ import { createResolveDocs } from "../../content/resolve/resolve-docs";
8
+ import { resolveContentExtractor } from "../../content/registry";
9
+ import {
10
+ compact,
11
+ heading,
12
+ html,
13
+ image,
14
+ link,
15
+ paragraph,
16
+ richText,
17
+ video
18
+ } from "../../content/schema/helpers";
19
+ import { serialize } from "../../content/schema/serialize";
20
+ const helpers = {
21
+ heading,
22
+ paragraph,
23
+ link,
24
+ image,
25
+ video,
26
+ html,
27
+ richText,
28
+ compact
29
+ };
30
+ function useGenerate({ kind, measurement, setValue }) {
31
+ const [formFields] = useAllFormFields();
32
+ const { config } = useConfig();
33
+ const { collectionSlug } = useDocumentInfo();
34
+ const locale = useLocale();
35
+ const [status, setStatus] = useState("idle");
36
+ const [error, setError] = useState(null);
37
+ const ref = useRef({
38
+ formFields,
39
+ collectionSlug,
40
+ locale,
41
+ apiRoute: config.routes.api
42
+ });
43
+ ref.current = {
44
+ formFields,
45
+ collectionSlug,
46
+ locale,
47
+ apiRoute: config.routes.api
48
+ };
49
+ const generate = useCallback(async () => {
50
+ const { formFields: ff, collectionSlug: slug, locale: loc, apiRoute } = ref.current;
51
+ const clientConfig = getSeoClientConfig();
52
+ const extractPath = slug ? clientConfig.extractByCollection[slug] : void 0;
53
+ const extractor = resolveContentExtractor(extractPath);
54
+ if (!extractor) {
55
+ setStatus("error");
56
+ setError("No content extractor is registered for this collection.");
57
+ return;
58
+ }
59
+ setStatus("loading");
60
+ setError(null);
61
+ try {
62
+ const values = reduceFieldsToValues(ff, true);
63
+ const localeCode = loc?.code;
64
+ const ir = await extractor(
65
+ values,
66
+ { locale: localeCode, apiRoute },
67
+ { resolveDocs: createResolveDocs(apiRoute, localeCode), helpers }
68
+ );
69
+ const contentHtml = serialize(ir);
70
+ const res = await fetch(`${apiRoute}${GENERATE_ENDPOINT_PATH}`, {
71
+ method: "POST",
72
+ credentials: "include",
73
+ headers: { "Content-Type": "application/json" },
74
+ body: JSON.stringify({
75
+ kind,
76
+ contentHtml,
77
+ locale: localeCode,
78
+ range: { min: measurement.min, max: measurement.max, unit: measurement.unit }
79
+ })
80
+ });
81
+ if (!res.ok)
82
+ throw new Error(`Request failed (${res.status})`);
83
+ const body = await res.json();
84
+ if (!body.text)
85
+ throw new Error("Empty response");
86
+ setValue(body.text);
87
+ setStatus("idle");
88
+ } catch (err) {
89
+ setStatus("error");
90
+ setError(err.message || "Generation failed");
91
+ }
92
+ }, [kind, measurement.min, measurement.max, measurement.unit, setValue]);
93
+ return {
94
+ generate,
95
+ status,
96
+ error
97
+ };
98
+ }
99
+ export {
100
+ useGenerate
101
+ };
102
+ //# sourceMappingURL=useGenerate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/SeoField/useGenerate.ts"],"sourcesContent":["\"use client\";\n\nimport { useAllFormFields, useConfig, useDocumentInfo, useLocale } from \"@payloadcms/ui\";\nimport { reduceFieldsToValues } from \"payload/shared\";\nimport { useCallback, useRef, useState } from \"react\";\nimport { getSeoClientConfig } from \"../../client-config/registry\";\nimport { GENERATE_ENDPOINT_PATH } from \"../../constants/generation\";\nimport { createResolveDocs } from \"../../content/resolve/resolve-docs\";\nimport { resolveContentExtractor } from \"../../content/registry\";\nimport {\n compact,\n heading,\n html,\n image,\n link,\n paragraph,\n richText,\n video,\n} from \"../../content/schema/helpers\";\nimport { serialize } from \"../../content/schema/serialize\";\nimport type { ContentHelpers } from \"../../types/config\";\nimport type { Measurement } from \"../../measure/measure\";\nimport type { SeoFieldKind } from \"../../server/generate/prompts\";\n\nconst helpers: ContentHelpers = {\n heading,\n paragraph,\n link,\n image,\n video,\n html,\n richText,\n compact,\n};\n\nexport type GenerateStatus = \"idle\" | \"loading\" | \"error\";\n\ninterface UseGenerateProps {\n kind: SeoFieldKind;\n measurement: Measurement;\n setValue: (v: string) => void;\n}\n\nexport function useGenerate({ kind, measurement, setValue }: UseGenerateProps) {\n const [formFields] = useAllFormFields();\n const { config } = useConfig();\n const { collectionSlug } = useDocumentInfo();\n const locale = useLocale();\n\n const [status, setStatus] = useState<GenerateStatus>(\"idle\");\n const [error, setError] = useState<string | null>(null);\n\n const ref = useRef({\n formFields,\n collectionSlug,\n locale,\n apiRoute: config.routes.api,\n });\n ref.current = {\n formFields,\n collectionSlug,\n locale,\n apiRoute: config.routes.api,\n };\n\n const generate = useCallback(async () => {\n const { formFields: ff, collectionSlug: slug, locale: loc, apiRoute } = ref.current;\n\n const clientConfig = getSeoClientConfig();\n const extractPath = slug ? clientConfig.extractByCollection[slug] : undefined;\n\n const extractor = resolveContentExtractor(extractPath);\n if (!extractor) {\n setStatus(\"error\");\n setError(\"No content extractor is registered for this collection.\");\n\n return;\n }\n\n setStatus(\"loading\");\n setError(null);\n\n try {\n const values = reduceFieldsToValues(ff, true) as Record<string, unknown>;\n const localeCode = loc?.code;\n const ir = await extractor(\n values,\n { locale: localeCode, apiRoute },\n { resolveDocs: createResolveDocs(apiRoute, localeCode), helpers }\n );\n const contentHtml = serialize(ir);\n\n const res = await fetch(`${apiRoute}${GENERATE_ENDPOINT_PATH}`, {\n method: \"POST\",\n credentials: \"include\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n kind,\n contentHtml,\n locale: localeCode,\n range: { min: measurement.min, max: measurement.max, unit: measurement.unit },\n }),\n });\n if (!res.ok) throw new Error(`Request failed (${res.status})`);\n\n const body = (await res.json()) as { text?: string };\n if (!body.text) throw new Error(\"Empty response\");\n\n setValue(body.text);\n setStatus(\"idle\");\n } catch (err) {\n setStatus(\"error\");\n setError((err as Error).message || \"Generation failed\");\n }\n }, [kind, measurement.min, measurement.max, measurement.unit, setValue]);\n\n return {\n generate,\n status,\n error,\n };\n}\n"],"mappings":";AAEA,SAAS,kBAAkB,WAAW,iBAAiB,iBAAiB;AACxE,SAAS,4BAA4B;AACrC,SAAS,aAAa,QAAQ,gBAAgB;AAC9C,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,yBAAyB;AAClC,SAAS,+BAA+B;AACxC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,iBAAiB;AAK1B,MAAM,UAA0B;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAUO,SAAS,YAAY,EAAE,MAAM,aAAa,SAAS,GAAqB;AAC7E,QAAM,CAAC,UAAU,IAAI,iBAAiB;AACtC,QAAM,EAAE,OAAO,IAAI,UAAU;AAC7B,QAAM,EAAE,eAAe,IAAI,gBAAgB;AAC3C,QAAM,SAAS,UAAU;AAEzB,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAyB,MAAM;AAC3D,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AAEtD,QAAM,MAAM,OAAO;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,OAAO,OAAO;AAAA,EAC1B,CAAC;AACD,MAAI,UAAU;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,OAAO,OAAO;AAAA,EAC1B;AAEA,QAAM,WAAW,YAAY,YAAY;AACvC,UAAM,EAAE,YAAY,IAAI,gBAAgB,MAAM,QAAQ,KAAK,SAAS,IAAI,IAAI;AAE5E,UAAM,eAAe,mBAAmB;AACxC,UAAM,cAAc,OAAO,aAAa,oBAAoB,IAAI,IAAI;AAEpE,UAAM,YAAY,wBAAwB,WAAW;AACrD,QAAI,CAAC,WAAW;AACd,gBAAU,OAAO;AACjB,eAAS,yDAAyD;AAElE;AAAA,IACF;AAEA,cAAU,SAAS;AACnB,aAAS,IAAI;AAEb,QAAI;AACF,YAAM,SAAS,qBAAqB,IAAI,IAAI;AAC5C,YAAM,aAAa,KAAK;AACxB,YAAM,KAAK,MAAM;AAAA,QACf;AAAA,QACA,EAAE,QAAQ,YAAY,SAAS;AAAA,QAC/B,EAAE,aAAa,kBAAkB,UAAU,UAAU,GAAG,QAAQ;AAAA,MAClE;AACA,YAAM,cAAc,UAAU,EAAE;AAEhC,YAAM,MAAM,MAAM,MAAM,GAAG,QAAQ,GAAG,sBAAsB,IAAI;AAAA,QAC9D,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACnB;AAAA,UACA;AAAA,UACA,QAAQ;AAAA,UACR,OAAO,EAAE,KAAK,YAAY,KAAK,KAAK,YAAY,KAAK,MAAM,YAAY,KAAK;AAAA,QAC9E,CAAC;AAAA,MACH,CAAC;AACD,UAAI,CAAC,IAAI;AAAI,cAAM,IAAI,MAAM,mBAAmB,IAAI,MAAM,GAAG;AAE7D,YAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,UAAI,CAAC,KAAK;AAAM,cAAM,IAAI,MAAM,gBAAgB;AAEhD,eAAS,KAAK,IAAI;AAClB,gBAAU,MAAM;AAAA,IAClB,SAAS,KAAK;AACZ,gBAAU,OAAO;AACjB,eAAU,IAAc,WAAW,mBAAmB;AAAA,IACxD;AAAA,EACF,GAAG,CAAC,MAAM,YAAY,KAAK,YAAY,KAAK,YAAY,MAAM,QAAQ,CAAC;AAEvE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,13 @@
1
+ export declare const GENERATE_ENDPOINT_PATH = "/seo/generate";
2
+ export declare const DEFAULT_MODEL = "gpt-4o-mini";
3
+ export declare const DEFAULT_MAX_CONTENT_CHARS = 6000;
4
+ export declare const AVG_GLYPH_PX = 8.5;
5
+ export declare const TITLE_RANGE: {
6
+ readonly min: 400;
7
+ readonly max: 600;
8
+ };
9
+ export declare const DESCRIPTION_RANGE: {
10
+ readonly min: 120;
11
+ readonly max: 156;
12
+ };
13
+ //# sourceMappingURL=generation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generation.d.ts","sourceRoot":"","sources":["../../src/constants/generation.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,kBAAkB,CAAC;AACtD,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAC3C,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,eAAO,MAAM,YAAY,MAAM,CAAC;AAEhC,eAAO,MAAM,WAAW;;;CAGd,CAAC;AAEX,eAAO,MAAM,iBAAiB;;;CAGpB,CAAC"}
@@ -0,0 +1,21 @@
1
+ const GENERATE_ENDPOINT_PATH = "/seo/generate";
2
+ const DEFAULT_MODEL = "gpt-4o-mini";
3
+ const DEFAULT_MAX_CONTENT_CHARS = 6e3;
4
+ const AVG_GLYPH_PX = 8.5;
5
+ const TITLE_RANGE = {
6
+ min: 400,
7
+ max: 600
8
+ };
9
+ const DESCRIPTION_RANGE = {
10
+ min: 120,
11
+ max: 156
12
+ };
13
+ export {
14
+ AVG_GLYPH_PX,
15
+ DEFAULT_MAX_CONTENT_CHARS,
16
+ DEFAULT_MODEL,
17
+ DESCRIPTION_RANGE,
18
+ GENERATE_ENDPOINT_PATH,
19
+ TITLE_RANGE
20
+ };
21
+ //# sourceMappingURL=generation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/constants/generation.ts"],"sourcesContent":["export const GENERATE_ENDPOINT_PATH = \"/seo/generate\";\nexport const DEFAULT_MODEL = \"gpt-4o-mini\";\nexport const DEFAULT_MAX_CONTENT_CHARS = 6000;\n\nexport const AVG_GLYPH_PX = 8.5;\n\nexport const TITLE_RANGE = {\n min: 400,\n max: 600,\n} as const;\n\nexport const DESCRIPTION_RANGE = {\n min: 120,\n max: 156,\n} as const;\n"],"mappings":"AAAO,MAAM,yBAAyB;AAC/B,MAAM,gBAAgB;AACtB,MAAM,4BAA4B;AAElC,MAAM,eAAe;AAErB,MAAM,cAAc;AAAA,EACzB,KAAK;AAAA,EACL,KAAK;AACP;AAEO,MAAM,oBAAoB;AAAA,EAC/B,KAAK;AAAA,EACL,KAAK;AACP;","names":[]}
@@ -1,5 +1,6 @@
1
1
  import type { LengthProgress } from "@yoast/search-metadata-previews/build/helpers/progress";
2
- export declare const AVG_GLYPH_PX = 8.5;
2
+ import { AVG_GLYPH_PX } from "../../constants/generation";
3
+ export { AVG_GLYPH_PX };
3
4
  export declare const TITLE_FALLBACK_MAX_PX = 600;
4
5
  export declare function getTitleProgressGuarded(title: string): LengthProgress;
5
6
  //# sourceMappingURL=title-progress.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"title-progress.d.ts","sourceRoot":"","sources":["../../../src/engine/helpers/title-progress.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wDAAwD,CAAC;AAE7F,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAarE"}
1
+ {"version":3,"file":"title-progress.d.ts","sourceRoot":"","sources":["../../../src/engine/helpers/title-progress.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wDAAwD,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE1D,OAAO,EAAE,YAAY,EAAE,CAAC;AACxB,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAarE"}
@@ -1,5 +1,5 @@
1
1
  import { getTitleProgress } from "@yoast/search-metadata-previews/build/helpers/progress";
2
- const AVG_GLYPH_PX = 8.5;
2
+ import { AVG_GLYPH_PX } from "../../constants/generation";
3
3
  const TITLE_FALLBACK_MAX_PX = 600;
4
4
  function getTitleProgressGuarded(title) {
5
5
  if (typeof document !== "undefined") {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/engine/helpers/title-progress.ts"],"sourcesContent":["import { getTitleProgress } from \"@yoast/search-metadata-previews/build/helpers/progress\";\nimport type { LengthProgress } from \"@yoast/search-metadata-previews/build/helpers/progress\";\n\nexport const AVG_GLYPH_PX = 8.5;\nexport const TITLE_FALLBACK_MAX_PX = 600;\n\nexport function getTitleProgressGuarded(title: string): LengthProgress {\n if (typeof document !== \"undefined\") {\n return getTitleProgress(title);\n }\n\n const actual = Math.round(title.length * AVG_GLYPH_PX);\n const score = actual <= TITLE_FALLBACK_MAX_PX ? 9 : 1;\n\n return {\n actual,\n max: TITLE_FALLBACK_MAX_PX,\n score,\n };\n}\n"],"mappings":"AAAA,SAAS,wBAAwB;AAG1B,MAAM,eAAe;AACrB,MAAM,wBAAwB;AAE9B,SAAS,wBAAwB,OAA+B;AACrE,MAAI,OAAO,aAAa,aAAa;AACnC,WAAO,iBAAiB,KAAK;AAAA,EAC/B;AAEA,QAAM,SAAS,KAAK,MAAM,MAAM,SAAS,YAAY;AACrD,QAAM,QAAQ,UAAU,wBAAwB,IAAI;AAEpD,SAAO;AAAA,IACL;AAAA,IACA,KAAK;AAAA,IACL;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/engine/helpers/title-progress.ts"],"sourcesContent":["import { getTitleProgress } from \"@yoast/search-metadata-previews/build/helpers/progress\";\nimport type { LengthProgress } from \"@yoast/search-metadata-previews/build/helpers/progress\";\nimport { AVG_GLYPH_PX } from \"../../constants/generation\";\n\nexport { AVG_GLYPH_PX };\nexport const TITLE_FALLBACK_MAX_PX = 600;\n\nexport function getTitleProgressGuarded(title: string): LengthProgress {\n if (typeof document !== \"undefined\") {\n return getTitleProgress(title);\n }\n\n const actual = Math.round(title.length * AVG_GLYPH_PX);\n const score = actual <= TITLE_FALLBACK_MAX_PX ? 9 : 1;\n\n return {\n actual,\n max: TITLE_FALLBACK_MAX_PX,\n score,\n };\n}\n"],"mappings":"AAAA,SAAS,wBAAwB;AAEjC,SAAS,oBAAoB;AAGtB,MAAM,wBAAwB;AAE9B,SAAS,wBAAwB,OAA+B;AACrE,MAAI,OAAO,aAAa,aAAa;AACnC,WAAO,iBAAiB,KAAK;AAAA,EAC/B;AAEA,QAAM,SAAS,KAAK,MAAM,MAAM,SAAS,YAAY;AACrD,QAAM,QAAQ,UAAU,wBAAwB,IAAI;AAEpD,SAAO;AAAA,IACL;AAAA,IACA,KAAK;AAAA,IACL;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,3 @@
1
+ export { seoTextField } from "./seoTextField";
2
+ export type { SeoTextFieldOptions } from "./seoTextField";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fields/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { seoTextField } from "./seoTextField";
2
+ export {
3
+ seoTextField
4
+ };
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/fields/index.ts"],"sourcesContent":["export { seoTextField } from \"./seoTextField\";\nexport type { SeoTextFieldOptions } from \"./seoTextField\";\n"],"mappings":"AAAA,SAAS,oBAAoB;","names":[]}
@@ -0,0 +1,8 @@
1
+ import type { FieldHook } from "payload";
2
+ import type { RangeOverride } from "../measure/measure";
3
+ import type { SeoFieldKind } from "../server/generate/prompts";
4
+ export declare function makeGenerateOnPublishHook(args: {
5
+ kind: SeoFieldKind;
6
+ range: RangeOverride | undefined;
7
+ }): FieldHook;
8
+ //# sourceMappingURL=onPublishHook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onPublishHook.d.ts","sourceRoot":"","sources":["../../src/fields/onPublishHook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAgBzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAIxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AA6B/D,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,aAAa,GAAG,SAAS,CAAC;CAClC,GAAG,SAAS,CAqDZ"}