@interware/content-generator 0.7.1 → 0.8.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 (48) hide show
  1. package/dist/_chunks/{App-h4_txTsI.mjs → App-DD1MzaWm.mjs} +2 -2
  2. package/dist/_chunks/{App-Cwr5ra5m.js → App-DspaMfyW.js} +2 -2
  3. package/dist/_chunks/{ImageStudioTabs-B-mOwDfY.mjs → ImageStudioTabs-DwaLxRjl.mjs} +367 -61
  4. package/dist/_chunks/{ImageStudioTabs-CG4KTn1S.js → ImageStudioTabs-uXSBkFeu.js} +365 -59
  5. package/dist/_chunks/{Settings-BT-EqvXs.mjs → Settings-C6TbUrgU.mjs} +170 -2
  6. package/dist/_chunks/{Settings-D4NGBTk1.js → Settings-ps0o7-fC.js} +169 -1
  7. package/dist/_chunks/{StudioApp-xaEy-uIk.mjs → StudioApp-B5I1OfGw.mjs} +2 -2
  8. package/dist/_chunks/{StudioApp-_ViSAwCm.js → StudioApp-B7kGpqqo.js} +2 -2
  9. package/dist/_chunks/{index-NA9OuMGs.js → index-BudahdfX.js} +3 -3
  10. package/dist/_chunks/{index-D67xWUPz.mjs → index-zenHkX-p.mjs} +3 -3
  11. package/dist/admin/index.js +1 -1
  12. package/dist/admin/index.mjs +1 -1
  13. package/dist/admin/src/components/settings/DriveSettings.d.ts +77 -0
  14. package/dist/admin/src/features/drive/DriveTab.d.ts +42 -0
  15. package/dist/admin/src/features/image-studio/ImageStudioTabs.d.ts +3 -0
  16. package/dist/admin/src/features/library/FileCard.d.ts +5 -2
  17. package/dist/admin/src/features/library/driveSync.d.ts +41 -0
  18. package/dist/admin/src/features/templates/editor/ColorField.d.ts +4 -2
  19. package/dist/admin/src/features/templates/export/templateOrigin.d.ts +10 -0
  20. package/dist/admin/src/features/templates/model/palette.d.ts +2 -2
  21. package/dist/admin/src/lib/imageLimits.d.ts +9 -0
  22. package/dist/admin/src/lib/use-cases/drive/index.d.ts +3 -0
  23. package/dist/server/index.js +1414 -51
  24. package/dist/server/index.mjs +1412 -49
  25. package/dist/server/src/bootstrap.d.ts +2 -0
  26. package/dist/server/src/content-types/drive-link/index.d.ts +62 -0
  27. package/dist/server/src/content-types/drive-link/schema.d.ts +68 -0
  28. package/dist/server/src/content-types/drive-op/index.d.ts +46 -0
  29. package/dist/server/src/content-types/drive-op/schema.d.ts +49 -0
  30. package/dist/server/src/content-types/index.d.ts +106 -0
  31. package/dist/server/src/controllers/drive.d.ts +30 -0
  32. package/dist/server/src/controllers/index.d.ts +130 -0
  33. package/dist/server/src/drive/client.d.ts +32 -0
  34. package/dist/server/src/drive/clientConfig.d.ts +27 -0
  35. package/dist/server/src/drive/common.d.ts +6 -0
  36. package/dist/server/src/drive/fakes.d.ts +104 -0
  37. package/dist/server/src/drive/files.d.ts +136 -0
  38. package/dist/server/src/drive/lifecycles.d.ts +7 -0
  39. package/dist/server/src/drive/links.d.ts +23 -0
  40. package/dist/server/src/drive/model.d.ts +99 -0
  41. package/dist/server/src/drive/oauth.d.ts +47 -0
  42. package/dist/server/src/drive/pull.d.ts +23 -0
  43. package/dist/server/src/drive/strapiPorts.d.ts +10 -0
  44. package/dist/server/src/drive/sync.d.ts +40 -0
  45. package/dist/server/src/index.d.ts +294 -0
  46. package/dist/server/src/services/drive.d.ts +141 -0
  47. package/dist/server/src/services/index.d.ts +55 -0
  48. package/package.json +1 -1
@@ -2,7 +2,7 @@
2
2
  const jsxRuntime = require("react/jsx-runtime");
3
3
  const designSystem = require("@strapi/design-system");
4
4
  const reactRouterDom = require("react-router-dom");
5
- const index = require("./index-NA9OuMGs.js");
5
+ const index = require("./index-BudahdfX.js");
6
6
  const react = require("react");
7
7
  const icons = require("@strapi/icons");
8
8
  const styledComponents = require("styled-components");
@@ -740,6 +740,9 @@ const RemoveBackgroundButton = ({ src, onDone }) => {
740
740
  )
741
741
  ] });
742
742
  };
743
+ const IMAGE_MAX_MB = 30;
744
+ const IMAGE_MAX_BYTES = IMAGE_MAX_MB * 1024 * 1024;
745
+ const IMAGE_FORMATS_HINT = `PNG, JPG o WEBP de hasta ${IMAGE_MAX_MB} MB.`;
743
746
  const NameDialog = ({ open, title, label, initial = "", confirmLabel, validate, onClose, onConfirm }) => {
744
747
  const [name, setName] = react.useState(initial);
745
748
  const [error, setError] = react.useState(null);
@@ -1171,7 +1174,7 @@ const closestRatio = (width, height, ratios) => {
1171
1174
  };
1172
1175
  return ratios.reduce((best, r) => Math.abs(value(r) - target) < Math.abs(value(best) - target) ? r : best);
1173
1176
  };
1174
- const BASE = "/strapi-content-generator-gemini/image-studio";
1177
+ const BASE$2 = "/strapi-content-generator-gemini/image-studio";
1175
1178
  const toResult = ({ image, mimeType, model, grounding }) => ({
1176
1179
  src: `data:${mimeType};base64,${image}`,
1177
1180
  model,
@@ -1183,7 +1186,7 @@ const errorMessage = (err, fallback) => {
1183
1186
  };
1184
1187
  const createGeneratorApi = (client) => ({
1185
1188
  async models() {
1186
- const { data } = await client.get(`${BASE}/models`);
1189
+ const { data } = await client.get(`${BASE$2}/models`);
1187
1190
  return data;
1188
1191
  },
1189
1192
  async generate(p) {
@@ -1196,7 +1199,7 @@ const createGeneratorApi = (client) => ({
1196
1199
  form.append("allowPeople", String(p.allowPeople));
1197
1200
  form.append("grounding", String(p.grounding));
1198
1201
  p.references.forEach((f) => form.append("references", f, f.name));
1199
- const { data } = await client.post(`${BASE}/generate`, form);
1202
+ const { data } = await client.post(`${BASE$2}/generate`, form);
1200
1203
  return toResult(data);
1201
1204
  },
1202
1205
  async edit(p) {
@@ -1209,11 +1212,11 @@ const createGeneratorApi = (client) => ({
1209
1212
  p.references.forEach((f) => form.append("references", f, f.name));
1210
1213
  if (p.aspectRatio) form.append("aspectRatio", p.aspectRatio);
1211
1214
  form.append("image", new File([blob], `base.${blob.type.split("/")[1] ?? "png"}`, { type: blob.type }));
1212
- const { data } = await client.post(`${BASE}/edit`, form);
1215
+ const { data } = await client.post(`${BASE$2}/edit`, form);
1213
1216
  return toResult(data);
1214
1217
  },
1215
1218
  async expand(prompt) {
1216
- const { data } = await client.post(`${BASE}/expand`, { prompt });
1219
+ const { data } = await client.post(`${BASE$2}/expand`, { prompt });
1217
1220
  return data.prompt;
1218
1221
  }
1219
1222
  });
@@ -1253,7 +1256,10 @@ const EditEmpty = ({ disabled, onUpload, onOpenLibrary, onOpenRecent }) => {
1253
1256
  style: { width: "100%", maxWidth: 600, border: `2px dashed ${over ? c.primary600 : c.neutral200}` },
1254
1257
  children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "center", gap: 3, style: { textAlign: "center" }, children: [
1255
1258
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", tag: "h2", children: "Elige la imagen que quieres editar" }),
1256
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: "Arrástrala aquí, súbela o elígela de la biblioteca. PNG, JPG o WEBP de hasta 10 MB." }),
1259
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { textColor: "neutral600", children: [
1260
+ "Arrástrala aquí, súbela o elígela de la biblioteca. ",
1261
+ IMAGE_FORMATS_HINT
1262
+ ] }),
1257
1263
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, paddingTop: 2, children: [
1258
1264
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Upload, {}), disabled, onClick: () => input.current?.click(), children: "Subir imagen" }),
1259
1265
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Folder, {}), disabled, onClick: onOpenLibrary, children: "Elegir de la biblioteca" })
@@ -1328,7 +1334,7 @@ const ReferencePicker = ({
1328
1334
  onChange,
1329
1335
  onPickFromLibrary,
1330
1336
  label = "Imágenes de referencia",
1331
- hint = "PNG, JPG o WEBP de hasta 10 MB.",
1337
+ hint = IMAGE_FORMATS_HINT,
1332
1338
  noun = REFERENCES
1333
1339
  }) => {
1334
1340
  const input = react.useRef(null);
@@ -1522,8 +1528,8 @@ const GeneratorTab = () => {
1522
1528
  };
1523
1529
  const uploadBase = (file) => {
1524
1530
  if (!file) return;
1525
- if (!IMAGE_MIMES.includes(file.type) || file.size > 10 * 1024 * 1024) {
1526
- notify("warning", "Usa una imagen PNG, JPG o WEBP de hasta 10 MB.");
1531
+ if (!IMAGE_MIMES.includes(file.type) || file.size > IMAGE_MAX_BYTES) {
1532
+ notify("warning", `Usa una imagen ${IMAGE_FORMATS_HINT}`);
1527
1533
  return;
1528
1534
  }
1529
1535
  const reader = new FileReader();
@@ -1598,7 +1604,7 @@ const GeneratorTab = () => {
1598
1604
  ReferencePicker,
1599
1605
  {
1600
1606
  label: "Referencias de estilo",
1601
- hint: "Guían el estilo, no se copian. PNG, JPG o WEBP de hasta 10 MB.",
1607
+ hint: `Guían el estilo, no se copian. ${IMAGE_FORMATS_HINT}`,
1602
1608
  files: references,
1603
1609
  max: model?.maxReferences ?? 0,
1604
1610
  modelLabel: model?.label ?? "Este modelo",
@@ -1832,7 +1838,7 @@ const isImage = (f) => (f.mime ?? "").startsWith("image/");
1832
1838
  const canRemoveBackground = (f) => isImage(f) && f.mime !== "image/svg+xml";
1833
1839
  const isGenerated = (f) => (f.caption ?? "").startsWith("AI generated content");
1834
1840
  const formatSize = (kb = 0) => kb >= 1024 ? `${(kb / 1024).toFixed(1)} MB` : `${Math.round(kb)} KB`;
1835
- const FileCard = ({ file, onAction, busy }) => {
1841
+ const FileCard = ({ file, onAction, busy, drive }) => {
1836
1842
  const c = useThemeColors();
1837
1843
  const thumb2 = file.formats?.thumbnail?.url ?? file.formats?.small?.url ?? file.url;
1838
1844
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -1871,7 +1877,8 @@ const FileCard = ({ file, onAction, busy }) => {
1871
1877
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
1872
1878
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: formatSize(file.size) }),
1873
1879
  isGenerated(file) && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: "Generada" }),
1874
- busy && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { active: true, children: busy })
1880
+ busy && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { active: true, children: busy }),
1881
+ drive?.label && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { variant: drive.tone, title: drive.title, children: drive.label })
1875
1882
  ] })
1876
1883
  ] }),
1877
1884
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Menu.Root, { children: [
@@ -1883,6 +1890,8 @@ const FileCard = ({ file, onAction, busy }) => {
1883
1890
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Menu.Item, { onSelect: () => onAction("replace", file), children: "Reemplazar" }),
1884
1891
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Menu.Item, { onSelect: () => onAction("move", file), children: "Mover a…" }),
1885
1892
  canRemoveBackground(file) && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Menu.Item, { onSelect: () => onAction("removeBackground", file), disabled: !!busy, children: "Quitar fondo" }),
1893
+ drive?.action === "mark" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Menu.Item, { onSelect: () => onAction("syncDrive", file), children: "Sincronizar con Drive" }),
1894
+ drive?.action === "unmark" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Menu.Item, { onSelect: () => onAction("unsyncDrive", file), children: "Dejar de sincronizar" }),
1886
1895
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Menu.Item, { onSelect: () => onAction("delete", file), variant: "danger", children: "Eliminar" })
1887
1896
  ] })
1888
1897
  ] })
@@ -1891,10 +1900,255 @@ const FileCard = ({ file, onAction, busy }) => {
1891
1900
  }
1892
1901
  );
1893
1902
  };
1903
+ const BASE$1 = "/strapi-content-generator-gemini/drive";
1904
+ const FOLDER = "application/vnd.google-apps.folder";
1905
+ const NATIVE_TYPES = {
1906
+ "application/vnd.google-apps.document": "Documento de Google",
1907
+ "application/vnd.google-apps.spreadsheet": "Hoja de cálculo de Google",
1908
+ "application/vnd.google-apps.presentation": "Presentación de Google",
1909
+ "application/vnd.google-apps.form": "Formulario de Google",
1910
+ "application/vnd.google-apps.drawing": "Dibujo de Google",
1911
+ "application/vnd.google-apps.shortcut": "Acceso directo"
1912
+ };
1913
+ const typeLabel = (mime) => {
1914
+ if (mime === FOLDER) return "Carpeta";
1915
+ if (NATIVE_TYPES[mime]) return NATIVE_TYPES[mime];
1916
+ if (mime.startsWith("application/vnd.google-apps.")) return "Archivo de Google";
1917
+ if (mime === "application/pdf") return "PDF";
1918
+ if (mime.startsWith("image/")) return "Imagen";
1919
+ if (mime.startsWith("video/")) return "Video";
1920
+ if (mime.startsWith("audio/")) return "Audio";
1921
+ return "Archivo";
1922
+ };
1923
+ const TypeIcon = ({ mime }) => mime === FOLDER ? /* @__PURE__ */ jsxRuntime.jsx(icons.Folder, { width: 16, height: 16, "aria-hidden": true }) : mime === "application/pdf" ? /* @__PURE__ */ jsxRuntime.jsx(icons.FilePdf, { width: 16, height: 16, "aria-hidden": true }) : /* @__PURE__ */ jsxRuntime.jsx(icons.File, { width: 16, height: 16, "aria-hidden": true });
1924
+ const formatDate = (iso) => iso ? new Date(iso).toLocaleString("es-MX", { dateStyle: "medium", timeStyle: "short", timeZone: "America/Mexico_City" }) : "—";
1925
+ const confirmText = (item, count) => {
1926
+ const parts = [`«${item.name}» irá a la papelera de Drive, donde se puede recuperar durante 30 días.`];
1927
+ if (item.mimeType === FOLDER && count) {
1928
+ const total = count.files + count.folders;
1929
+ parts.push(
1930
+ total === 0 ? "La carpeta está vacía." : `Contiene ${count.truncated ? "más de " : ""}${total.toLocaleString("es-MX")} ${total === 1 ? "elemento" : "elementos"} (${count.files} archivos y ${count.folders} carpetas), que también se van.`
1931
+ );
1932
+ }
1933
+ if (item.synced || count?.synced) parts.push("Está sincronizado: también se borrará de la Biblioteca del CMS.");
1934
+ return parts.join(" ");
1935
+ };
1936
+ const serverMessage$1 = (error, fallback) => error?.response?.data?.error?.message ?? (error instanceof Error && error.message ? error.message : fallback);
1937
+ const useDriveBrowserApi = () => {
1938
+ const { get, del } = admin.useFetchClient();
1939
+ return react.useMemo(
1940
+ () => ({
1941
+ browse: async (folderId, pageToken) => {
1942
+ try {
1943
+ return (await get(`${BASE$1}/folders/${encodeURIComponent(folderId)}`, { params: pageToken ? { pageToken } : {} })).data;
1944
+ } catch (error) {
1945
+ throw new Error(serverMessage$1(error, "No se pudo leer Google Drive."));
1946
+ }
1947
+ },
1948
+ count: async (id) => (await get(`${BASE$1}/items/${encodeURIComponent(id)}/count`)).data,
1949
+ trash: async (id) => {
1950
+ try {
1951
+ await del(`${BASE$1}/items/${encodeURIComponent(id)}`);
1952
+ } catch (error) {
1953
+ throw new Error(serverMessage$1(error, "No se pudo mandar a la papelera."));
1954
+ }
1955
+ }
1956
+ }),
1957
+ [get, del]
1958
+ );
1959
+ };
1960
+ const DriveTab = ({ api: injected, notify }) => {
1961
+ const fallbackApi = useDriveBrowserApi();
1962
+ const api = injected ?? fallbackApi;
1963
+ const { toggleNotification } = admin.useNotification();
1964
+ const say = notify ?? ((type, message) => toggleNotification({ type, message }));
1965
+ const [folderId, setFolderId] = react.useState("root");
1966
+ const [page, setPage] = react.useState(null);
1967
+ const [error, setError] = react.useState(null);
1968
+ const [loading2, setLoading] = react.useState(true);
1969
+ const [loadingMore, setLoadingMore] = react.useState(false);
1970
+ const [doomed, setDoomed] = react.useState(null);
1971
+ const load = react.useCallback(async () => {
1972
+ setLoading(true);
1973
+ setError(null);
1974
+ try {
1975
+ setPage(await api.browse(folderId));
1976
+ } catch (err) {
1977
+ setError(err instanceof Error ? err.message : "No se pudo leer Google Drive.");
1978
+ } finally {
1979
+ setLoading(false);
1980
+ }
1981
+ }, [api, folderId]);
1982
+ react.useEffect(() => {
1983
+ load();
1984
+ }, [load]);
1985
+ const more = async () => {
1986
+ if (!page?.nextPageToken) return;
1987
+ setLoadingMore(true);
1988
+ try {
1989
+ const next = await api.browse(folderId, page.nextPageToken);
1990
+ setPage({ ...next, items: [...page.items, ...next.items] });
1991
+ } finally {
1992
+ setLoadingMore(false);
1993
+ }
1994
+ };
1995
+ const askDelete = async (item) => {
1996
+ setDoomed({ item, count: null });
1997
+ if (item.mimeType === FOLDER) {
1998
+ try {
1999
+ const count = await api.count(item.id);
2000
+ setDoomed((d) => d?.item.id === item.id ? { item, count } : d);
2001
+ } catch {
2002
+ }
2003
+ }
2004
+ };
2005
+ const confirmDelete = async () => {
2006
+ if (!doomed) return;
2007
+ const { item } = doomed;
2008
+ setDoomed(null);
2009
+ try {
2010
+ await api.trash(item.id);
2011
+ say("success", `«${item.name}» se mandó a la papelera de Drive.`);
2012
+ await load();
2013
+ } catch (err) {
2014
+ say("danger", err instanceof Error ? err.message : "No se pudo mandar a la papelera.");
2015
+ }
2016
+ };
2017
+ if (error) return /* @__PURE__ */ jsxRuntime.jsx(EmptyNote, { children: error });
2018
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
2019
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { "data-testid": "drive-breadcrumb", gap: 1, alignItems: "center", wrap: "wrap", paddingBottom: 4, children: [
2020
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "ghost", size: "S", onClick: () => setFolderId("root"), children: "Mi unidad" }),
2021
+ (page?.path ?? []).map((p, i, all) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, alignItems: "center", children: [
2022
+ /* @__PURE__ */ jsxRuntime.jsx(icons.ChevronRight, { width: 10, height: 10, "aria-hidden": true }),
2023
+ i === all.length - 1 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "bold", children: p.name }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "ghost", size: "S", onClick: () => setFolderId(p.id), children: p.name })
2024
+ ] }, p.id))
2025
+ ] }),
2026
+ loading2 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Cargando Google Drive…" }) }) : !page?.items.length ? /* @__PURE__ */ jsxRuntime.jsx(EmptyNote, { children: "Esta carpeta de Drive está vacía." }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2027
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 6, rowCount: page.items.length + 1, children: [
2028
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
2029
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Nombre" }) }),
2030
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Tipo" }) }),
2031
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Modificado" }) }),
2032
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Tamaño" }) }),
2033
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Estado" }) }),
2034
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.VisuallyHidden, { children: "Acciones" }) })
2035
+ ] }) }),
2036
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: page.items.map((item) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
2037
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { style: { maxWidth: 420, whiteSpace: "normal" }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
2038
+ /* @__PURE__ */ jsxRuntime.jsx(TypeIcon, { mime: item.mimeType }),
2039
+ item.mimeType === FOLDER ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.TextButton, { onClick: () => setFolderId(item.id), style: { textAlign: "left", height: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "semiBold", textColor: "neutral800", style: { overflowWrap: "anywhere" }, children: item.name }) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral800", style: { overflowWrap: "anywhere" }, children: item.name })
2040
+ ] }) }),
2041
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral700", children: typeLabel(item.mimeType) }) }),
2042
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral700", style: { whiteSpace: "nowrap" }, children: formatDate(item.modifiedTime) }) }),
2043
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral700", children: item.size === null ? "—" : formatSize(item.size / 1e3) }) }),
2044
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: item.synced && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { variant: "success", children: "Sincronizado" }) }),
2045
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, justifyContent: "end", children: [
2046
+ item.webViewLink && /* @__PURE__ */ jsxRuntime.jsx(
2047
+ designSystem.LinkButton,
2048
+ {
2049
+ variant: "tertiary",
2050
+ size: "S",
2051
+ href: item.webViewLink,
2052
+ target: "_blank",
2053
+ rel: "noreferrer noopener",
2054
+ endIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ExternalLink, {}),
2055
+ children: "Abrir en Drive"
2056
+ }
2057
+ ),
2058
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.IconButton, { label: `Eliminar ${item.name}`, variant: "ghost", onClick: () => askDelete(item), children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}) })
2059
+ ] }) })
2060
+ ] }, item.id)) })
2061
+ ] }),
2062
+ page.nextPageToken && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", paddingTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", onClick: more, loading: loadingMore, children: "Mostrar más" }) })
2063
+ ] }),
2064
+ /* @__PURE__ */ jsxRuntime.jsx(
2065
+ ConfirmDialog,
2066
+ {
2067
+ open: !!doomed,
2068
+ title: "¿Mandar a la papelera de Drive?",
2069
+ confirmLabel: "Mandar a la papelera",
2070
+ onClose: () => setDoomed(null),
2071
+ onConfirm: confirmDelete,
2072
+ children: doomed ? confirmText(doomed.item, doomed.count) : ""
2073
+ }
2074
+ )
2075
+ ] });
2076
+ };
2077
+ const BASE = "/strapi-content-generator-gemini/drive";
2078
+ const POLL_MS = 1e4;
2079
+ const LABELS = {
2080
+ synced: { label: "Drive", tone: "success" },
2081
+ pending: { label: "Drive: pendiente", tone: "neutral" },
2082
+ error: { label: "Drive: error", tone: "danger" },
2083
+ skipped: { label: "Drive: omitido", tone: "warning" }
2084
+ };
2085
+ const driveView = (links, key, parentInside) => {
2086
+ if (!links?.connected) return null;
2087
+ const item = links.items[key];
2088
+ if (item?.inside) {
2089
+ return { ...LABELS[item.status], title: item.error ?? void 0, action: item.root ? "unmark" : null };
2090
+ }
2091
+ if (parentInside) return { ...LABELS.pending, action: null };
2092
+ return { label: null, tone: "neutral", action: "mark" };
2093
+ };
2094
+ const serverMessage = (error, fallback) => error?.response?.data?.error?.message ?? fallback;
2095
+ const useDriveSync = (watching = false) => {
2096
+ const { get, post, del } = admin.useFetchClient();
2097
+ const [links, setLinks] = react.useState(null);
2098
+ const refresh = react.useCallback(async () => {
2099
+ try {
2100
+ const { data } = await get(`${BASE}/links`);
2101
+ setLinks(data);
2102
+ } catch {
2103
+ setLinks(null);
2104
+ }
2105
+ }, [get]);
2106
+ react.useEffect(() => {
2107
+ refresh();
2108
+ }, [refresh]);
2109
+ const busy = !!links?.connected && (watching || links.pending > 0 || Object.values(links.items).some((i) => i.status === "pending"));
2110
+ react.useEffect(() => {
2111
+ if (!busy) return;
2112
+ const timer = setInterval(refresh, POLL_MS);
2113
+ return () => clearInterval(timer);
2114
+ }, [busy, refresh]);
2115
+ react.useEffect(() => {
2116
+ const onVisible = () => document.visibilityState === "visible" && refresh();
2117
+ document.addEventListener("visibilitychange", onVisible);
2118
+ return () => document.removeEventListener("visibilitychange", onVisible);
2119
+ }, [refresh]);
2120
+ return react.useMemo(
2121
+ () => ({
2122
+ links,
2123
+ refresh,
2124
+ async mark(cmsType, cmsId) {
2125
+ try {
2126
+ await post(`${BASE}/links`, { cmsType, cmsId });
2127
+ } catch (error) {
2128
+ throw new Error(serverMessage(error, "No se pudo sincronizar con Drive."));
2129
+ } finally {
2130
+ await refresh();
2131
+ }
2132
+ },
2133
+ async unmark(cmsType, cmsId) {
2134
+ try {
2135
+ await del(`${BASE}/links/${cmsType}/${cmsId}`);
2136
+ } catch (error) {
2137
+ throw new Error(serverMessage(error, "No se pudo dejar de sincronizar."));
2138
+ } finally {
2139
+ await refresh();
2140
+ }
2141
+ }
2142
+ }),
2143
+ [links, refresh, post, del]
2144
+ );
2145
+ };
1894
2146
  const status = (err) => err?.response?.status ?? err?.status;
1895
2147
  const LibraryTab = () => {
1896
2148
  const c = useThemeColors();
1897
2149
  const { api, notify } = useLibraryApi();
2150
+ const [drivePending, setDrivePending] = react.useState(false);
2151
+ const drive = useDriveSync(drivePending);
1898
2152
  const [tree, setTree] = react.useState([]);
1899
2153
  const [counts, setCounts] = react.useState({});
1900
2154
  const [current, setCurrent] = react.useState(null);
@@ -1938,7 +2192,20 @@ const LibraryTab = () => {
1938
2192
  react.useEffect(() => {
1939
2193
  loadFiles();
1940
2194
  }, [loadFiles]);
1941
- const refresh = () => Promise.all([loadTree(), loadFiles()]);
2195
+ const refresh = () => Promise.all([loadTree(), loadFiles(), drive.refresh()]);
2196
+ const toggleDrive = async (mode, cmsType, cmsId) => {
2197
+ try {
2198
+ if (mode === "mark") {
2199
+ await drive.mark(cmsType, cmsId);
2200
+ notify("success", "Se sincronizará con Drive en unos segundos.");
2201
+ } else {
2202
+ await drive.unmark(cmsType, cmsId);
2203
+ notify("success", "Dejó de sincronizarse. No se borró nada en Drive ni aquí.");
2204
+ }
2205
+ } catch (err) {
2206
+ notify("danger", err instanceof Error ? err.message : "No se pudo completar la operación con Drive.");
2207
+ }
2208
+ };
1942
2209
  const run = async (action, ok) => {
1943
2210
  try {
1944
2211
  await action();
@@ -1995,12 +2262,24 @@ const LibraryTab = () => {
1995
2262
  if (action === "rename") return setDialog({ kind: "renameFile", file });
1996
2263
  if (action === "move") return setDialog({ kind: "moveFile", file });
1997
2264
  if (action === "delete") return setDialog({ kind: "deleteFile", file });
2265
+ if (action === "syncDrive") return toggleDrive("mark", "file", file.id);
2266
+ if (action === "unsyncDrive") return toggleDrive("unmark", "file", file.id);
1998
2267
  };
1999
2268
  const validateFolder = (parent, except) => (name) => cleanFolderName(name) !== except && nameTaken(tree, parent, name) ? "Ya existe una carpeta con ese nombre aquí." : null;
2269
+ const pendingShown = (() => {
2270
+ const inside = current !== null && !!drive.links?.items[`folder:${current}`]?.inside;
2271
+ const isPending = (key) => driveView(drive.links, key, inside)?.label === "Drive: pendiente";
2272
+ return files.some((f) => isPending(`file:${f.id}`)) || childrenOf(tree, current).some((f) => isPending(`folder:${f.id}`));
2273
+ })();
2274
+ react.useEffect(() => {
2275
+ setDrivePending(pendingShown);
2276
+ }, [pendingShown]);
2000
2277
  if (forbidden) {
2001
2278
  return /* @__PURE__ */ jsxRuntime.jsx(EmptyNote, { children: "Para usar la biblioteca necesitas permiso de la Media Library de Strapi. Pídelo a un administrador." });
2002
2279
  }
2003
2280
  const subfolders = childrenOf(tree, current);
2281
+ const currentInside = current !== null && !!drive.links?.items[`folder:${current}`]?.inside;
2282
+ const folderDrive = current !== null ? driveView(drive.links, `folder:${current}`, !!(folder && path.length > 1 && drive.links?.items[`folder:${path[path.length - 2].id}`]?.inside)) : null;
2004
2283
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "stretch", children: [
2005
2284
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "280px", shrink: 0, background: "neutral0", padding: 3, hasRadius: true, style: { maxHeight: "calc(100vh - 220px)", overflowY: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsx(
2006
2285
  FolderTree,
@@ -2047,6 +2326,9 @@ const LibraryTab = () => {
2047
2326
  ] }, f.id))
2048
2327
  ] }),
2049
2328
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
2329
+ folder && folderDrive?.label && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { variant: folderDrive.tone, title: folderDrive.title, children: folderDrive.label }),
2330
+ folder && folderDrive?.action === "mark" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", onClick: () => toggleDrive("mark", "folder", folder.id), children: "Sincronizar con Drive" }),
2331
+ folder && folderDrive?.action === "unmark" && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", onClick: () => toggleDrive("unmark", "folder", folder.id), children: "Dejar de sincronizar" }),
2050
2332
  folder && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2051
2333
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Pencil, {}), onClick: () => setDialog({ kind: "renameFolder" }), children: "Renombrar carpeta" }),
2052
2334
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "danger-light", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}), onClick: () => setDialog({ kind: "deleteFolder" }), children: "Eliminar carpeta" })
@@ -2071,11 +2353,15 @@ const LibraryTab = () => {
2071
2353
  children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
2072
2354
  /* @__PURE__ */ jsxRuntime.jsx(icons.Folder, { width: 16, height: 16, "aria-hidden": true }),
2073
2355
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { ellipsis: true, style: { flex: 1 }, children: f.name }),
2356
+ (() => {
2357
+ const view = driveView(drive.links, `folder:${f.id}`, currentInside);
2358
+ return view?.label ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { size: "S", variant: view.tone, title: view.title, children: view.label }) : null;
2359
+ })(),
2074
2360
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: counts[f.id]?.files ?? 0 })
2075
2361
  ] })
2076
2362
  }
2077
2363
  ) }, f.id)) }),
2078
- loading2 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Cargando archivos…" }) }) : files.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(EmptyNote, { children: subfolders.length ? "No hay archivos sueltos en esta carpeta." : "Carpeta vacía. Arrastra archivos aquí o usa «Subir archivos»." }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Root, { gap: 4, children: files.map((f) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 3, m: 4, s: 6, xs: 12, children: /* @__PURE__ */ jsxRuntime.jsx(FileCard, { file: f, onAction: onFileAction, busy: bgBusy[f.id] }) }, f.id)) }),
2364
+ loading2 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: "Cargando archivos…" }) }) : files.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(EmptyNote, { children: subfolders.length ? "No hay archivos sueltos en esta carpeta." : "Carpeta vacía. Arrastra archivos aquí o usa «Subir archivos»." }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Root, { gap: 4, children: files.map((f) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 3, m: 4, s: 6, xs: 12, children: /* @__PURE__ */ jsxRuntime.jsx(FileCard, { file: f, onAction: onFileAction, busy: bgBusy[f.id], drive: driveView(drive.links, `file:${f.id}`, currentInside) }) }, f.id)) }),
2079
2365
  dragOver && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { position: "absolute", justifyContent: "center", background: "primary100", hasRadius: true, style: { inset: 0, border: `2px dashed ${c.primary600}`, opacity: 0.95 }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "beta", textColor: "primary700", children: [
2080
2366
  "Suelta para subir a «",
2081
2367
  folder?.name ?? ROOT_LABEL,
@@ -2187,17 +2473,6 @@ const LibraryTab = () => {
2187
2473
  /* @__PURE__ */ jsxRuntime.jsx(RemoveBackgroundDialog, { open: !!bgTarget, src: bgTarget?.src ?? "", onClose: () => setBgTarget(null), onApply: saveNoBackground })
2188
2474
  ] });
2189
2475
  };
2190
- const PALETTE = [
2191
- { label: "Navy", value: "#1F2A44" },
2192
- { label: "Rojo", value: "#E4002B" },
2193
- { label: "Teal", value: "#00C7B1" },
2194
- { label: "Teal suave", value: "#92CFC7" },
2195
- { label: "Amarillo", value: "#FFC844" },
2196
- { label: "Amarillo suave", value: "#F8DF8D" },
2197
- { label: "Gris", value: "#5B6473" },
2198
- { label: "Papel", value: "#EEF1F5" },
2199
- { label: "Blanco", value: "#FFFFFF" }
2200
- ];
2201
2476
  const NAVY = "#1F2A44";
2202
2477
  const RED$1 = "#E4002B";
2203
2478
  const TEAL$1 = "#00C7B1";
@@ -2768,6 +3043,15 @@ const editorReducer = (state, action) => {
2768
3043
  }
2769
3044
  }
2770
3045
  };
3046
+ const ORIGIN_URL = "/strapi-content-generator-gemini/drive/origin";
3047
+ const recordTemplateOrigin = async (post, files, template) => {
3048
+ for (const file of files) {
3049
+ try {
3050
+ await post(ORIGIN_URL, { fileId: file.id, templateDocumentId: template.documentId ?? template.id, templateName: template.name });
3051
+ } catch {
3052
+ }
3053
+ }
3054
+ };
2771
3055
  const useEditorIO = () => {
2772
3056
  const { post } = admin.useFetchClient();
2773
3057
  const { toggleNotification } = admin.useNotification();
@@ -2800,7 +3084,8 @@ const useEditorIO = () => {
2800
3084
  if (!node) return false;
2801
3085
  try {
2802
3086
  const blob = await renderPiece(node, template, "png");
2803
- await api.saveBlob(blob, /\.png$/i.test(name) ? name : `${name}.png`, folderId);
3087
+ const saved = await api.saveBlob(blob, /\.png$/i.test(name) ? name : `${name}.png`, folderId);
3088
+ await recordTemplateOrigin((u, body) => post(u, body), saved, template);
2804
3089
  toggleNotification({ type: "success", message: "Pieza guardada en la biblioteca." });
2805
3090
  return true;
2806
3091
  } catch {
@@ -2808,7 +3093,7 @@ const useEditorIO = () => {
2808
3093
  return false;
2809
3094
  }
2810
3095
  },
2811
- [api, toggleNotification]
3096
+ [api, post, toggleNotification]
2812
3097
  );
2813
3098
  return { loadImage: loadImage2, exportAs, saveToLibrary };
2814
3099
  };
@@ -3937,44 +4222,63 @@ const SaveTemplateDialog = ({ open, defaultName, defaultFolder, folders, onClose
3937
4222
  ] })
3938
4223
  ] }) });
3939
4224
  };
4225
+ const parseHex = (raw) => {
4226
+ const hex = raw.trim().replace(/^#/, "");
4227
+ if (/^[0-9a-f]{3}$/i.test(hex)) return `#${[...hex].map((c) => c + c).join("")}`.toUpperCase();
4228
+ if (/^[0-9a-f]{6}$/i.test(hex)) return `#${hex}`.toUpperCase();
4229
+ return null;
4230
+ };
3940
4231
  const ColorField = ({ label, value, onChange }) => {
3941
4232
  const tc = useThemeColors();
4233
+ const current = parseHex(value ?? "") ?? "#000000";
4234
+ const [draft, setDraft] = react.useState(current);
4235
+ react.useEffect(() => setDraft((d) => parseHex(d) === current ? d : current), [current]);
4236
+ const apply = (text) => {
4237
+ const hex = parseHex(text);
4238
+ if (hex && hex !== current) onChange(hex);
4239
+ };
4240
+ const typeHex = (text) => {
4241
+ setDraft(text);
4242
+ if (text.trim().replace(/^#/, "").length === 6) apply(text);
4243
+ };
4244
+ const leave = () => {
4245
+ apply(draft);
4246
+ setDraft(parseHex(draft) ?? current);
4247
+ };
3942
4248
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 1, children: [
3943
4249
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", fontWeight: "bold", textColor: "neutral800", children: label }),
3944
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, wrap: "wrap", alignItems: "center", children: [
3945
- PALETTE.map((c) => {
3946
- const active = value?.toUpperCase() === c.value;
3947
- return /* @__PURE__ */ jsxRuntime.jsx(
3948
- designSystem.Box,
3949
- {
3950
- tag: "button",
3951
- color: "neutral800",
3952
- type: "button",
3953
- "aria-label": `${label}: ${c.label}`,
3954
- "aria-pressed": active,
3955
- title: c.label,
3956
- onClick: () => onChange(c.value),
3957
- style: {
3958
- width: 22,
3959
- height: 22,
3960
- borderRadius: 4,
3961
- cursor: "pointer",
3962
- background: c.value,
3963
- border: active ? `2px solid ${tc.primary600}` : `1px solid ${tc.neutral200}`,
3964
- padding: 0
3965
- }
3966
- },
3967
- c.value
3968
- );
3969
- }),
4250
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
3970
4251
  /* @__PURE__ */ jsxRuntime.jsx(
3971
4252
  "input",
3972
4253
  {
3973
4254
  type: "color",
3974
- "aria-label": `${label} personalizado`,
3975
- value: /^#[0-9a-f]{6}$/i.test(value ?? "") ? value : "#000000",
4255
+ "aria-label": `${label}: selector`,
4256
+ title: "Abrir selector de color",
4257
+ value: current.toLowerCase(),
3976
4258
  onChange: (e) => onChange(e.target.value.toUpperCase()),
3977
- style: { width: 26, height: 26, border: "none", padding: 0, background: "none", cursor: "pointer" }
4259
+ style: {
4260
+ flexShrink: 0,
4261
+ width: 32,
4262
+ height: 32,
4263
+ padding: 2,
4264
+ borderRadius: 4,
4265
+ cursor: "pointer",
4266
+ border: `1px solid ${tc.neutral200}`,
4267
+ background: tc.neutral0
4268
+ }
4269
+ }
4270
+ ),
4271
+ /* @__PURE__ */ jsxRuntime.jsx(
4272
+ designSystem.TextInput,
4273
+ {
4274
+ "aria-label": `${label}: hexadecimal`,
4275
+ size: "S",
4276
+ value: draft,
4277
+ placeholder: "#1F2A44",
4278
+ maxLength: 7,
4279
+ spellCheck: false,
4280
+ onChange: (e) => typeHex(e.target.value),
4281
+ onBlur: leave
3978
4282
  }
3979
4283
  )
3980
4284
  ] })
@@ -5234,7 +5538,8 @@ const TemplatesTab = () => {
5234
5538
  const STUDIO_TABS = [
5235
5539
  { value: "generator", label: "Generador" },
5236
5540
  { value: "templates", label: "Plantillas" },
5237
- { value: "library", label: "Imágenes y documentos" }
5541
+ { value: "library", label: "Imágenes y documentos" },
5542
+ { value: "drive", label: "Google Drive" }
5238
5543
  ];
5239
5544
  const PANEL = { height: "100%", overflowY: "auto" };
5240
5545
  const isStudioTab = (v) => STUDIO_TABS.some((t) => t.value === v);
@@ -5262,7 +5567,8 @@ const ImageStudioTabs = () => {
5262
5567
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { flex: "1", style: { minHeight: 0 }, children: [
5263
5568
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "generator", style: PANEL, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, children: /* @__PURE__ */ jsxRuntime.jsx(GeneratorTab, {}) }) }),
5264
5569
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "templates", style: PANEL, children: /* @__PURE__ */ jsxRuntime.jsx(TemplatesTab, {}) }),
5265
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "library", style: PANEL, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, children: /* @__PURE__ */ jsxRuntime.jsx(LibraryTab, {}) }) })
5570
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "library", style: PANEL, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, children: /* @__PURE__ */ jsxRuntime.jsx(LibraryTab, {}) }) }),
5571
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tabs.Content, { value: "drive", style: PANEL, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, children: tab === "drive" && /* @__PURE__ */ jsxRuntime.jsx(DriveTab, {}) }) })
5266
5572
  ] })
5267
5573
  ]
5268
5574
  }