@jxsuite/studio 2.0.0 → 2.0.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 (43) hide show
  1. package/dist/chunks/{components-0hzentwq.js → components-799jpvhm.js} +4 -4
  2. package/dist/chunks/{extension-sections-qct0gt8f.js → extension-sections-qr3drkkw.js} +10 -10
  3. package/dist/chunks/{files-02zn5y24.js → files-j8a1t06k.js} +8 -8
  4. package/dist/chunks/{format-host-2c188n41.js → format-host-8jxwts2p.js} +2 -2
  5. package/dist/chunks/{git-panel-mbb0y0hz.js → git-panel-xtq5z81x.js} +6 -6
  6. package/dist/chunks/{layers-panel-9qekmqk1.js → layers-panel-ancn2wpq.js} +8 -8
  7. package/dist/chunks/{settings-modal-gj0hrah4.js → settings-modal-17ttr96g.js} +9 -9
  8. package/dist/chunks/{studio-asvf0j9e.js → studio-0xgs7xrs.js} +2 -2
  9. package/dist/chunks/{studio-d77qm3pk.js → studio-4d8hzcz5.js} +8 -8
  10. package/dist/chunks/{studio-hhj92gcr.js → studio-esrewxap.js} +2 -2
  11. package/dist/chunks/{studio-97qcgvdn.js → studio-gs6kqdt1.js} +2 -2
  12. package/dist/chunks/{studio-n9zmpxjg.js → studio-jm0z592w.js} +8 -8
  13. package/dist/chunks/{studio-j6gqyjf4.js → studio-m2t4mdg5.js} +8 -8
  14. package/dist/chunks/{studio-wd6ewdvd.js → studio-n4p11dfy.js} +6 -6
  15. package/dist/chunks/{studio-rq0vxb19.js → studio-sjh1wt1a.js} +2 -2
  16. package/dist/chunks/{studio-ygzav7z5.js → studio-v7jkjqng.js} +6 -6
  17. package/dist/chunks/{studio-zmy7241e.js → studio-yfcstdzy.js} +2 -2
  18. package/dist/chunks/{studio-568nx4t5.js → studio-zp7jncs8.js} +6 -6
  19. package/dist/chunks/{studio-568nx4t5.js.map → studio-zp7jncs8.js.map} +1 -1
  20. package/dist/studio.js +113 -82
  21. package/package.json +1 -1
  22. package/src/new-project/import-tab.ts +27 -9
  23. package/src/new-project/new-project-modal.ts +49 -17
  24. package/src/panels/ai-panel.ts +9 -76
  25. package/src/services/ai-models.ts +45 -1
  26. package/src/ui/ai-managed-connect.ts +107 -0
  27. /package/dist/chunks/{components-0hzentwq.js.map → components-799jpvhm.js.map} +0 -0
  28. /package/dist/chunks/{extension-sections-qct0gt8f.js.map → extension-sections-qr3drkkw.js.map} +0 -0
  29. /package/dist/chunks/{files-02zn5y24.js.map → files-j8a1t06k.js.map} +0 -0
  30. /package/dist/chunks/{format-host-2c188n41.js.map → format-host-8jxwts2p.js.map} +0 -0
  31. /package/dist/chunks/{git-panel-mbb0y0hz.js.map → git-panel-xtq5z81x.js.map} +0 -0
  32. /package/dist/chunks/{layers-panel-9qekmqk1.js.map → layers-panel-ancn2wpq.js.map} +0 -0
  33. /package/dist/chunks/{settings-modal-gj0hrah4.js.map → settings-modal-17ttr96g.js.map} +0 -0
  34. /package/dist/chunks/{studio-asvf0j9e.js.map → studio-0xgs7xrs.js.map} +0 -0
  35. /package/dist/chunks/{studio-d77qm3pk.js.map → studio-4d8hzcz5.js.map} +0 -0
  36. /package/dist/chunks/{studio-hhj92gcr.js.map → studio-esrewxap.js.map} +0 -0
  37. /package/dist/chunks/{studio-97qcgvdn.js.map → studio-gs6kqdt1.js.map} +0 -0
  38. /package/dist/chunks/{studio-n9zmpxjg.js.map → studio-jm0z592w.js.map} +0 -0
  39. /package/dist/chunks/{studio-j6gqyjf4.js.map → studio-m2t4mdg5.js.map} +0 -0
  40. /package/dist/chunks/{studio-wd6ewdvd.js.map → studio-n4p11dfy.js.map} +0 -0
  41. /package/dist/chunks/{studio-rq0vxb19.js.map → studio-sjh1wt1a.js.map} +0 -0
  42. /package/dist/chunks/{studio-ygzav7z5.js.map → studio-v7jkjqng.js.map} +0 -0
  43. /package/dist/chunks/{studio-zmy7241e.js.map → studio-yfcstdzy.js.map} +0 -0
@@ -9,6 +9,6 @@
9
9
  "/// <reference lib=\"dom\" />\n/**\n * Pull orchestration that keeps project sync from being blocked by Studio's own automated package\n * updates. The on-open @jxsuite update rewrites package.json and bun.lock without committing, so a\n * teammate pushing the same update makes a later `git pull` refuse to merge. Since those local\n * changes are machine-generated and reproducible, we discard them, pull, and re-run the package\n * sync (re-apply the update if the pulled project is still behind, or `bun install` if the pull\n * brought it). Detection is state-based (git status + content comparison against `@{u}`), never git\n * stderr parsing, so it works identically across backends and locales.\n */\n\nimport { errorMessage } from \"@jxsuite/schema/parse\";\nimport { getPlatform } from \"../platform\";\nimport { showConfirmDialog } from \"../ui/layers\";\nimport { showProgressModal } from \"../ui/progress-modal\";\nimport { statusMessage } from \"../panels/statusbar\";\nimport { applyJxsuiteUpdate, checkJxsuiteUpdate } from \"./jxsuite-update\";\nimport type { GitStatusResult } from \"../types\";\n\nconst PKG_PATHS = [\"package.json\", \"bun.lock\", \"bun.lockb\"];\n\n/** Untracked markers across backends: desktop porcelain v1 emits \"??\", server porcelain v2 \"U\". */\nconst UNTRACKED_STATUSES = new Set([\"??\", \"U\"]);\n\n/** Above this many dirty files, skip the per-file upstream comparison and leave the error as-is. */\nconst MAX_CONFLICT_SCAN = 25;\n\nconst JXSUITE_PREFIX = \"@jxsuite/\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction sortKeys(value: unknown): unknown {\n if (Array.isArray(value)) {\n return value.map((entry) => sortKeys(entry));\n }\n if (isRecord(value)) {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value).toSorted()) {\n out[key] = sortKeys(value[key]);\n }\n return out;\n }\n return value;\n}\n\n/** Replace every @jxsuite/* version in dependencies/devDependencies so they compare equal. */\nfunction maskJxsuiteVersions(pkg: Record<string, unknown>): Record<string, unknown> {\n const out = { ...pkg };\n for (const section of [\"dependencies\", \"devDependencies\"]) {\n const deps = out[section];\n if (!isRecord(deps)) {\n continue;\n }\n const masked = { ...deps };\n for (const name of Object.keys(masked)) {\n if (name.startsWith(JXSUITE_PREFIX)) {\n masked[name] = \"*\";\n }\n }\n out[section] = masked;\n }\n return out;\n}\n\n/**\n * True when the working package.json differs from the HEAD copy only in the version strings of\n * jxsuite-scoped entries — i.e. exactly what Studio's automated update writes. Added/removed deps\n * or any other edit means a human touched the file.\n */\nexport function isAutomatedPackageDiff(\n headText: string | null,\n workingText: string | null,\n): boolean {\n if (headText === null || workingText === null) {\n return false;\n }\n let head: unknown;\n let working: unknown;\n try {\n head = JSON.parse(headText);\n working = JSON.parse(workingText);\n } catch {\n return false;\n }\n if (!isRecord(head) || !isRecord(working)) {\n return false;\n }\n return (\n JSON.stringify(sortKeys(maskJxsuiteVersions(head))) ===\n JSON.stringify(sortKeys(maskJxsuiteVersions(working)))\n );\n}\n\nasync function tryShow(path: string, ref: string): Promise<string | null> {\n try {\n return await getPlatform().gitShow({ path, ref });\n } catch {\n return null;\n }\n}\n\nasync function tryRead(path: string): Promise<string | null> {\n try {\n return await getPlatform().readFile(path);\n } catch {\n return null;\n }\n}\n\nexport interface PackagePullPlan {\n /** Tracked dirty package files whose upstream (@{u}) content differs from HEAD. */\n discard: string[];\n /** Untracked package files that exist at @{u} (\"untracked would be overwritten\" case). */\n removeUntracked: string[];\n /** Whether the package.json working copy diverges from HEAD only by automated @jxsuite bumps. */\n automated: boolean;\n}\n\n/**\n * Decide which local package files stand in the way of a merge. Only files the upstream actually\n * changed can block the pull, so files whose @{u} content matches HEAD are left alone.\n */\nexport async function planPackageDiscard(status: GitStatusResult): Promise<PackagePullPlan> {\n const discard: string[] = [];\n const removeUntracked: string[] = [];\n let automated = true;\n for (const path of PKG_PATHS) {\n const entries = status.files.filter((f) => f.path === path);\n if (entries.length === 0) {\n continue;\n }\n const upstream = await tryShow(path, \"@{u}\");\n if (entries.every((f) => UNTRACKED_STATUSES.has(f.status))) {\n if (upstream !== null) {\n removeUntracked.push(path);\n }\n continue;\n }\n const head = await tryShow(path, \"HEAD\");\n if (upstream === null || upstream === head) {\n continue;\n }\n discard.push(path);\n if (path === \"package.json\") {\n automated &&= isAutomatedPackageDiff(head, await tryRead(path));\n }\n }\n return { automated, discard, removeUntracked };\n}\n\nfunction planIsEmpty(plan: PackagePullPlan): boolean {\n return plan.discard.length === 0 && plan.removeUntracked.length === 0;\n}\n\n/** True when a dirty non-package file also differs upstream — recovery can't unblock that pull. */\nasync function otherFilesConflict(status: GitStatusResult): Promise<boolean> {\n const paths = [\n ...new Set(status.files.filter((f) => !PKG_PATHS.includes(f.path)).map((f) => f.path)),\n ];\n if (paths.length > MAX_CONFLICT_SCAN) {\n return true;\n }\n for (const path of paths) {\n const upstream = await tryShow(path, \"@{u}\");\n if (upstream !== null && upstream !== (await tryShow(path, \"HEAD\"))) {\n return true;\n }\n }\n return false;\n}\n\nasync function applyDiscard(plan: PackagePullPlan): Promise<void> {\n const platform = getPlatform();\n if (plan.discard.length > 0) {\n // Unstage unconditionally: desktop porcelain v1 doesn't report a staged flag.\n // Restoring an unstaged tracked file is a no-op.\n await platform.gitUnstage(plan.discard);\n await platform.gitDiscard(plan.discard);\n }\n for (const path of plan.removeUntracked) {\n await platform.deleteFile(path);\n }\n}\n\n/**\n * Bring packages back in line after a pull that touched (or followed a discard of) package files:\n * re-apply the @jxsuite update if the pulled project is still behind, otherwise `bun install` so\n * node_modules matches the pulled lockfile. Never throws — a successful pull must not turn into an\n * error banner; failures surface through the progress modal.\n */\nasync function syncPackagesAfterPull(): Promise<void> {\n const platform = getPlatform();\n let check: Awaited<ReturnType<typeof checkJxsuiteUpdate>> = null;\n try {\n check = await checkJxsuiteUpdate();\n } catch {\n check = null;\n }\n if (check && platform.setPackageVersions) {\n // No re-prompt: the discarded local changes came from an update the user already accepted.\n await applyJxsuiteUpdate(check.outdated, check.target);\n return;\n }\n if (!platform.installDependencies) {\n return;\n }\n const progress = showProgressModal({\n status: \"Running bun install…\",\n title: \"Syncing dependencies\",\n });\n try {\n const result = await platform.installDependencies();\n if (result.ok) {\n progress.done();\n } else {\n progress.fail(result.log ?? \"bun install failed\");\n }\n } catch (error) {\n progress.fail(errorMessage(error));\n }\n}\n\n/**\n * Discard per plan, pull, and re-sync. A discard is always followed by a package re-sync — even\n * when the pull fails — so the project is never stranded without the update it had before.\n */\nasync function discardPullAndSync(plan: PackagePullPlan): Promise<void> {\n const platform = getPlatform();\n try {\n await applyDiscard(plan);\n } catch (error) {\n throw new Error(`Could not reset local package files: ${errorMessage(error)}`, {\n cause: error,\n });\n }\n try {\n await platform.gitPull();\n } catch (error) {\n await syncPackagesAfterPull();\n throw error;\n }\n statusMessage(\"Local package updates were superseded by pulled changes\");\n await syncPackagesAfterPull();\n}\n\nasync function recoverAfterFailedPull(original: unknown): Promise<void> {\n const platform = getPlatform();\n let status: GitStatusResult;\n try {\n status = await platform.gitStatus();\n } catch {\n throw original;\n }\n if (!(status.behind > 0)) {\n throw original;\n }\n const plan = await planPackageDiscard(status);\n if (planIsEmpty(plan) || (await otherFilesConflict(status))) {\n throw original;\n }\n if (!plan.automated) {\n const confirmed = await showConfirmDialog(\n \"Discard local package changes?\",\n \"package.json / bun.lock have local edits that conflict with incoming changes. Discard them, pull, and re-sync packages?\",\n { confirmLabel: \"Discard and pull\", destructive: true },\n );\n if (!confirmed) {\n throw original;\n }\n }\n await discardPullAndSync(plan);\n}\n\n/**\n * Snapshot the package files (aligned to PKG_PATHS). Lists the root once and reads only the files\n * that exist, so absent lockfiles (cloud projects don't commit one) never trigger a console 404\n * from a blind read. Listing failure falls back to probing all paths.\n */\n/**\n * Snapshot the package files (aligned to PKG_PATHS). Lists the root once and reads only the files\n * that exist, so absent lockfiles (cloud projects don't commit one) never trigger a console 404\n * from a blind read. Listing failure falls back to probing all paths.\n */\nasync function snapshotPackageFiles(): Promise<(string | null)[]> {\n let present: Set<string>;\n try {\n const entries = await getPlatform().listDirectory(\"\");\n present = new Set(entries.filter((e) => e.type === \"file\").map((e) => e.name));\n } catch {\n present = new Set(PKG_PATHS);\n }\n return Promise.all(\n PKG_PATHS.map((path) => (present.has(path) ? tryRead(path) : Promise.resolve(null))),\n );\n}\n\n/**\n * Pull with automatic recovery from conflicts caused by Studio's own package updates. Throws only\n * when the pull genuinely fails for reasons recovery can't (or shouldn't) fix.\n */\nexport async function pullWithPackageSync(): Promise<void> {\n const platform = getPlatform();\n const before = await snapshotPackageFiles();\n\n // Preemptive: fetch so `behind` is current, then reset automated package edits that would\n // Block the merge — manual package.json edits fall through to the plain pull + confirm path.\n let status: GitStatusResult | null = null;\n try {\n await platform.gitFetch();\n status = await platform.gitStatus();\n } catch {\n status = null; // Offline or status failure — the plain pull will report the real error\n }\n if (status && status.behind > 0) {\n const plan = await planPackageDiscard(status);\n if (!planIsEmpty(plan) && plan.automated) {\n await discardPullAndSync(plan);\n return;\n }\n }\n\n try {\n await platform.gitPull();\n } catch (error) {\n await recoverAfterFailedPull(error);\n return;\n }\n const after = await snapshotPackageFiles();\n if (before.some((content, i) => content !== after[i])) {\n await syncPackagesAfterPull();\n }\n}\n\n/**\n * Pull remote changes as soon as a project opens, ahead of the install check and update prompt, so\n * a session starts from the current remote state. Never throws and never blocks the open — failures\n * (offline, no upstream, unrelated conflicts) surface as a status message only.\n */\nexport async function autoSyncProjectOnOpen(): Promise<void> {\n const platform = getPlatform();\n let status: GitStatusResult;\n try {\n status = await platform.gitStatus();\n } catch {\n return;\n }\n if (!status?.isRepo || (status.remotes?.length ?? 0) === 0) {\n return;\n }\n statusMessage(\"Syncing project…\");\n try {\n await pullWithPackageSync();\n } catch (error) {\n statusMessage(`Sync skipped: ${errorMessage(error)}`);\n }\n}\n"
10
10
  ],
11
11
  "mappings": "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",
12
- "debugId": "62E0BBB309184E0B64756E2164756E21",
12
+ "debugId": "88C48B6EB01A9FE764756E2164756E21",
13
13
  "names": []
14
14
  }
package/dist/studio.js CHANGED
@@ -51,7 +51,7 @@ import {
51
51
  stringify1 as stringify2,
52
52
  stringifyPosition,
53
53
  whitespace
54
- } from "./chunks/studio-n9zmpxjg.js";
54
+ } from "./chunks/studio-jm0z592w.js";
55
55
  import {
56
56
  getCfToken,
57
57
  hydrateSettings,
@@ -109,7 +109,7 @@ import {
109
109
  import {
110
110
  registerFormControl,
111
111
  renderFieldRow
112
- } from "./chunks/studio-zmy7241e.js";
112
+ } from "./chunks/studio-yfcstdzy.js";
113
113
  import {
114
114
  addFieldFormTpl,
115
115
  detectFieldFormat,
@@ -122,7 +122,7 @@ import {
122
122
  schemaForType,
123
123
  schemeOfQuery,
124
124
  yamlDefault
125
- } from "./chunks/studio-j6gqyjf4.js";
125
+ } from "./chunks/studio-m2t4mdg5.js";
126
126
  import {
127
127
  ROW_KEY_FIELD,
128
128
  addRecentProject,
@@ -162,7 +162,7 @@ import {
162
162
  setupTreeKeyboard,
163
163
  startFsSync,
164
164
  trackRecentFile
165
- } from "./chunks/studio-d77qm3pk.js";
165
+ } from "./chunks/studio-4d8hzcz5.js";
166
166
  import {
167
167
  MEDIA_EXTENSIONS,
168
168
  UPLOAD_ACCEPT,
@@ -245,13 +245,13 @@ import {
245
245
  updateActivePanelHeaders,
246
246
  updateSiteConfig,
247
247
  uploadAssets
248
- } from "./chunks/studio-ygzav7z5.js";
248
+ } from "./chunks/studio-v7jkjqng.js";
249
249
  import {
250
250
  buildComponentInstance,
251
251
  componentRegistry,
252
252
  computeRelativePath,
253
253
  loadComponentRegistry
254
- } from "./chunks/studio-hhj92gcr.js";
254
+ } from "./chunks/studio-esrewxap.js";
255
255
  import {
256
256
  classMap,
257
257
  ifDefined,
@@ -262,7 +262,7 @@ import {
262
262
  platformSupportsClone,
263
263
  refreshGitStatus,
264
264
  renderGitPanel
265
- } from "./chunks/studio-wd6ewdvd.js";
265
+ } from "./chunks/studio-n4p11dfy.js";
266
266
  import {
267
267
  repeat
268
268
  } from "./chunks/studio-xq0t7n5t.js";
@@ -277,7 +277,7 @@ import {
277
277
  VERSION,
278
278
  autoSyncProjectOnOpen,
279
279
  maybePromptJxsuiteUpdate
280
- } from "./chunks/studio-568nx4t5.js";
280
+ } from "./chunks/studio-zp7jncs8.js";
281
281
  import {
282
282
  applyPanelCollapse,
283
283
  errorMessage,
@@ -295,7 +295,7 @@ import {
295
295
  showSaveDiscardDialog,
296
296
  statusMessage,
297
297
  view
298
- } from "./chunks/studio-asvf0j9e.js";
298
+ } from "./chunks/studio-0xgs7xrs.js";
299
299
  import {
300
300
  COMMON_SELECTORS,
301
301
  VOID_ELEMENTS,
@@ -392,7 +392,7 @@ import {
392
392
  updateUi,
393
393
  varDisplayName,
394
394
  workspace
395
- } from "./chunks/studio-rq0vxb19.js";
395
+ } from "./chunks/studio-sjh1wt1a.js";
396
396
  import {
397
397
  AsyncDirective,
398
398
  PartType,
@@ -416,7 +416,7 @@ import {
416
416
  loadedMonaco,
417
417
  refreshExtensionUi,
418
418
  refreshFormats
419
- } from "./chunks/studio-97qcgvdn.js";
419
+ } from "./chunks/studio-gs6kqdt1.js";
420
420
  import {
421
421
  getPlatform,
422
422
  hasPlatform,
@@ -19573,7 +19573,7 @@ function renderSiteLevelImports(renderLeftPanel) {
19573
19573
  const platform = getPlatform();
19574
19574
  await platform.removePackage(pkg);
19575
19575
  const updatedElements = siteElements.filter((e) => typeof e !== "string" || !e.startsWith(`${pkg}/`));
19576
- const { loadComponentRegistry: loadComponentRegistry2 } = await import("./chunks/components-0hzentwq.js");
19576
+ const { loadComponentRegistry: loadComponentRegistry2 } = await import("./chunks/components-799jpvhm.js");
19577
19577
  await loadComponentRegistry2();
19578
19578
  await updateSiteConfig({
19579
19579
  $elements: updatedElements
@@ -19643,7 +19643,7 @@ function renderSiteLevelImports(renderLeftPanel) {
19643
19643
  try {
19644
19644
  const platform = getPlatform();
19645
19645
  await platform.addPackage(name);
19646
- const { loadComponentRegistry: loadComponentRegistry2 } = await import("./chunks/components-0hzentwq.js");
19646
+ const { loadComponentRegistry: loadComponentRegistry2 } = await import("./chunks/components-799jpvhm.js");
19647
19647
  await loadComponentRegistry2();
19648
19648
  renderLeftPanel();
19649
19649
  } catch (error) {
@@ -19665,7 +19665,7 @@ function renderSiteLevelImports(renderLeftPanel) {
19665
19665
  try {
19666
19666
  const platform = getPlatform();
19667
19667
  await platform.addPackage(name);
19668
- const { loadComponentRegistry: loadComponentRegistry2 } = await import("./chunks/components-0hzentwq.js");
19668
+ const { loadComponentRegistry: loadComponentRegistry2 } = await import("./chunks/components-799jpvhm.js");
19669
19669
  await loadComponentRegistry2();
19670
19670
  renderLeftPanel();
19671
19671
  } catch (error) {
@@ -23908,11 +23908,27 @@ var cache2 = null;
23908
23908
  var proxyConfigured = false;
23909
23909
  var proxyManaged = false;
23910
23910
  var proxyDefaultModel = "";
23911
+ var proxyProbe = null;
23912
+ var probeListeners = new Set;
23911
23913
  function invalidateModelCache() {
23912
23914
  cache2 = null;
23913
23915
  proxyConfigured = false;
23914
23916
  proxyManaged = false;
23915
23917
  proxyDefaultModel = "";
23918
+ proxyProbe = null;
23919
+ }
23920
+ function ensureProxyProbe(onSettled) {
23921
+ if (onSettled) {
23922
+ probeListeners.add(onSettled);
23923
+ }
23924
+ proxyProbe ??= fetchAvailableModels({ force: true }).catch(() => {}).then(() => {
23925
+ for (const notify of probeListeners) {
23926
+ notify();
23927
+ }
23928
+ });
23929
+ }
23930
+ function hasAiCredentials() {
23931
+ return hasOpenAiKey() || isProxyConfigured();
23916
23932
  }
23917
23933
  function isProxyConfigured() {
23918
23934
  return proxyConfigured;
@@ -24074,6 +24090,54 @@ function createAiCredentialsForm(opts) {
24074
24090
  }
24075
24091
  return { render: render5, startEdit };
24076
24092
  }
24093
+
24094
+ // src/ui/ai-managed-connect.ts
24095
+ function createManagedConnect(opts) {
24096
+ let busy = false;
24097
+ let connectError = "";
24098
+ function canOffer() {
24099
+ return isManagedProxy() && !isProxyConfigured() && hasPlatform() && Boolean(getPlatform().cfConnect);
24100
+ }
24101
+ function ensureProbe() {
24102
+ ensureProxyProbe(opts.requestRender);
24103
+ }
24104
+ async function connect() {
24105
+ if (busy) {
24106
+ return;
24107
+ }
24108
+ busy = true;
24109
+ connectError = "";
24110
+ opts.requestRender();
24111
+ try {
24112
+ const connection = await getPlatform().cfConnect?.();
24113
+ if (connection) {
24114
+ await fetchAvailableModels({ force: true });
24115
+ } else {
24116
+ connectError = "Cloudflare connection was not completed.";
24117
+ }
24118
+ } catch (error) {
24119
+ connectError = error instanceof Error ? error.message : String(error);
24120
+ }
24121
+ busy = false;
24122
+ opts.requestRender();
24123
+ }
24124
+ function render5() {
24125
+ if (!canOffer()) {
24126
+ return nothing;
24127
+ }
24128
+ return html`
24129
+ <div class="ai-managed-connect">
24130
+ <div>Use Workers AI on your own Cloudflare account — no API key needed.</div>
24131
+ <sp-button size="s" ?disabled=${busy} @click=${() => void connect()}>
24132
+ ${busy ? "Connecting…" : "Connect Cloudflare"}
24133
+ </sp-button>
24134
+ ${connectError ? html`<div class="ai-managed-connect-error">${connectError}</div>` : nothing}
24135
+ <div class="ai-managed-connect-divider">— or bring your own key —</div>
24136
+ </div>
24137
+ `;
24138
+ }
24139
+ return { canOffer, ensureProbe, render: render5 };
24140
+ }
24077
24141
  // ../../node_modules/@ungap/structured-clone/esm/types.js
24078
24142
  var VOID = -1;
24079
24143
  var PRIMITIVE = 0;
@@ -36471,12 +36535,6 @@ function renderSessionsList(opts) {
36471
36535
  // src/panels/ai-panel.ts
36472
36536
  var mounted = false;
36473
36537
  var keyEditing = false;
36474
- var proxyProbe = null;
36475
- function ensureProxyProbe() {
36476
- proxyProbe ??= fetchAvailableModels({ force: true }).catch(() => {}).then(() => {
36477
- scheduleAiRender();
36478
- });
36479
- }
36480
36538
  var view2 = "chat";
36481
36539
  var assistant = createDocumentAssistant();
36482
36540
  globalThis.assistant = assistant;
@@ -36551,49 +36609,11 @@ function startEditApiKey() {
36551
36609
  keyEditing = true;
36552
36610
  credsForm.startEdit();
36553
36611
  }
36554
- var cfConnectBusy = false;
36555
- var cfConnectError = "";
36556
- function canOfferManagedConnect() {
36557
- return isManagedProxy() && !isProxyConfigured() && Boolean(getPlatform().cfConnect);
36558
- }
36559
- async function connectCloudflareForAi() {
36560
- if (cfConnectBusy) {
36561
- return;
36562
- }
36563
- cfConnectBusy = true;
36564
- cfConnectError = "";
36565
- scheduleAiRender();
36566
- try {
36567
- const connection = await getPlatform().cfConnect?.();
36568
- if (connection) {
36569
- await fetchAvailableModels({ force: true });
36570
- } else {
36571
- cfConnectError = "Cloudflare connection was not completed.";
36572
- }
36573
- } catch (error) {
36574
- cfConnectError = error instanceof Error ? error.message : String(error);
36575
- }
36576
- cfConnectBusy = false;
36577
- scheduleAiRender();
36578
- }
36579
- function renderManagedConnect() {
36580
- return html`
36581
- <div class="ai-managed-connect">
36582
- <div>Use Workers AI on your own Cloudflare account — no API key needed.</div>
36583
- <sp-button size="s" ?disabled=${cfConnectBusy} @click=${() => void connectCloudflareForAi()}>
36584
- ${cfConnectBusy ? "Connecting…" : "Connect Cloudflare"}
36585
- </sp-button>
36586
- ${cfConnectError ? html`<div class="ai-managed-connect-error">${cfConnectError}</div>` : nothing}
36587
- <div class="ai-managed-connect-divider">— or bring your own key —</div>
36588
- </div>
36589
- `;
36590
- }
36612
+ var managedConnect = createManagedConnect({ requestRender: scheduleAiRender });
36591
36613
  function renderKeyGate() {
36592
36614
  return html`
36593
36615
  <div class="ai-tab-body">
36594
- <div class="ai-status-center">
36595
- ${canOfferManagedConnect() ? renderManagedConnect() : nothing} ${credsForm.render()}
36596
- </div>
36616
+ <div class="ai-status-center">${managedConnect.render()} ${credsForm.render()}</div>
36597
36617
  </div>
36598
36618
  `;
36599
36619
  }
@@ -36678,9 +36698,9 @@ var composer = createComposer({
36678
36698
  requestRender: scheduleAiRender
36679
36699
  });
36680
36700
  function renderAiPanelTemplate() {
36681
- if (!hasOpenAiKey() && !isProxyConfigured() || keyEditing) {
36701
+ if (!hasAiCredentials() || keyEditing) {
36682
36702
  if (!keyEditing) {
36683
- ensureProxyProbe();
36703
+ managedConnect.ensureProbe();
36684
36704
  }
36685
36705
  return renderKeyGate();
36686
36706
  }
@@ -65568,13 +65588,13 @@ function renderImportStatus() {
65568
65588
  `;
65569
65589
  }
65570
65590
  function renderImportSource(ctx) {
65571
- if (!hasOpenAiKey()) {
65591
+ if (!ctx.aiGateOpen()) {
65572
65592
  return html`
65573
65593
  <div class="new-project-tab-intro">
65574
- Importing uses an AI-guided pipeline to clone an existing site. Add an OpenAI-compatible API
65575
- key to continue.
65594
+ Importing uses an AI-guided pipeline to clone an existing site.
65595
+ ${ctx.managedConnect.canOffer() ? "Connect Cloudflare, or add an OpenAI-compatible API key, to continue." : "Add an OpenAI-compatible API key to continue."}
65576
65596
  </div>
65577
- <div class="new-project-creds">${ctx.credsForm.render()}</div>
65597
+ <div class="new-project-creds">${ctx.managedConnect.render()} ${ctx.credsForm.render()}</div>
65578
65598
  `;
65579
65599
  }
65580
65600
  return html`
@@ -65669,21 +65689,30 @@ var _creating = false;
65669
65689
  var _starters = [];
65670
65690
  var _resolve2 = null;
65671
65691
  var _credsForm = null;
65692
+ function rerenderIfOpen() {
65693
+ if (_handle5) {
65694
+ renderModal3();
65695
+ }
65696
+ }
65672
65697
  function credsForm2() {
65673
65698
  _credsForm ??= createAiCredentialsForm({
65674
- onSaved: () => {
65675
- if (_handle5) {
65676
- renderModal3();
65677
- }
65678
- },
65679
- requestRender: () => {
65680
- if (_handle5) {
65681
- renderModal3();
65682
- }
65683
- }
65699
+ onSaved: rerenderIfOpen,
65700
+ requestRender: rerenderIfOpen
65684
65701
  });
65685
65702
  return _credsForm;
65686
65703
  }
65704
+ var _managedConnect = null;
65705
+ function managedConnect2() {
65706
+ _managedConnect ??= createManagedConnect({ requestRender: rerenderIfOpen });
65707
+ return _managedConnect;
65708
+ }
65709
+ function aiGateOpen() {
65710
+ if (hasAiCredentials()) {
65711
+ return true;
65712
+ }
65713
+ managedConnect2().ensureProbe();
65714
+ return false;
65715
+ }
65687
65716
  function openNewProjectModal(options) {
65688
65717
  if (_handle5) {
65689
65718
  return Promise.resolve(null);
@@ -65776,8 +65805,10 @@ function sourceLabel() {
65776
65805
  function renderModal3() {
65777
65806
  const platform = getPlatform();
65778
65807
  const importCtx = {
65808
+ aiGateOpen,
65779
65809
  credsForm: credsForm2(),
65780
65810
  form: _form2,
65811
+ managedConnect: managedConnect2(),
65781
65812
  onDone: finish2,
65782
65813
  rerender: renderModal3,
65783
65814
  resolveDestination: () => validateParams()
@@ -65968,13 +65999,13 @@ function renderModal3() {
65968
65999
  </div>
65969
66000
  ` : html`<div class="new-project-tab-intro">No starter sites are available.</div>`;
65970
66001
  const agentSourceTpl = () => {
65971
- if (!hasOpenAiKey()) {
66002
+ if (!aiGateOpen()) {
65972
66003
  return html`
65973
66004
  <div class="new-project-tab-intro">
65974
- The agent uses your AI provider to build the site. Add an OpenAI-compatible API key to
65975
- continue.
66005
+ The agent uses your AI provider to build the site.
66006
+ ${managedConnect2().canOffer() ? "Connect Cloudflare, or add an OpenAI-compatible API key, to continue." : "Add an OpenAI-compatible API key to continue."}
65976
66007
  </div>
65977
- <div class="new-project-creds">${credsForm2().render()}</div>
66008
+ <div class="new-project-creds">${managedConnect2().render()} ${credsForm2().render()}</div>
65978
66009
  `;
65979
66010
  }
65980
66011
  return html`
@@ -66080,7 +66111,7 @@ function renderModal3() {
66080
66111
  `;
66081
66112
  }
66082
66113
  if (_step === "source") {
66083
- const gated = (_tab === "import" || _tab === "agent") && !hasOpenAiKey();
66114
+ const gated = (_tab === "import" || _tab === "agent") && !aiGateOpen();
66084
66115
  return html`
66085
66116
  <sp-button variant="secondary" @click=${closeNewProjectModal}>Cancel</sp-button>
66086
66117
  ${gated ? "" : html`<sp-button variant="accent" @click=${goNext}>Next</sp-button>`}
@@ -73138,5 +73169,5 @@ export {
73138
73169
  hasUnsavedTabs
73139
73170
  };
73140
73171
 
73141
- //# debugId=176D3065B75AF29064756E2164756E21
73172
+ //# debugId=ADA5809F71C01E1464756E2164756E21
73142
73173
  //# sourceMappingURL=studio.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/studio",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Jx Studio — visual builder for Jx documents",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * The New Project modal's Import source step: an AI-guided clone of an existing site. Gated behind
3
- * the AI credentials form when no key is stored; otherwise a URL + crawl-options form. The
4
- * name/directory parameters live on the modal's shared Parameters step; the running pipeline
5
- * streams its progress into a log here and resolves the modal with the imported project.
3
+ * the AI credentials gate (key form + the keyless Cloudflare option) until AI is usable — a local
4
+ * key, or a backend holding its own; otherwise a URL + crawl-options form. The name/directory
5
+ * parameters live on the modal's shared Parameters step; the running pipeline streams its progress
6
+ * into a log here and resolves the modal with the imported project.
6
7
  */
7
8
 
8
9
  import { html } from "lit-html";
9
10
  import { getPlatform } from "../platform";
10
- import { getBaseUrl, getModel, getOpenAiKey, hasOpenAiKey } from "../services/ai-settings";
11
+ import { getBaseUrl, getModel, getOpenAiKey } from "../services/ai-settings";
11
12
  import { errorMessage } from "@jxsuite/schema/parse";
12
13
  import { destinationPath } from "./location-fields";
13
14
  import type { TemplateResult } from "lit-html";
@@ -20,6 +21,12 @@ export interface CredsFormLike {
20
21
  startEdit: () => void;
21
22
  }
22
23
 
24
+ /** Structural view of src/ui/ai-managed-connect's controller (the keyless option beside the form). */
25
+ export interface ManagedConnectLike {
26
+ canOffer: () => boolean;
27
+ render: () => unknown;
28
+ }
29
+
23
30
  export interface ImportTabCtx {
24
31
  /** The modal's shared name/directory state (the import destination slug). */
25
32
  form: { name: string; directory: string };
@@ -32,8 +39,15 @@ export interface ImportTabCtx {
32
39
  rerender: () => void;
33
40
  /** Called with the imported project; the modal resolves and closes. */
34
41
  onDone: (result: { root: string; config: ProjectConfig }) => void;
35
- /** The modal's shared credentials-form instance, rendered while no key is stored. */
42
+ /** The modal's shared credentials-form instance, rendered while the gate is closed. */
36
43
  credsForm: CredsFormLike;
44
+ /**
45
+ * Whether AI is usable — a local key OR a backend holding credentials of its own. Also fires the
46
+ * capability probe while closed, so a managed platform's Cloudflare option can appear.
47
+ */
48
+ aiGateOpen: () => boolean;
49
+ /** The modal's shared keyless "Connect Cloudflare" controller, rendered beside the form. */
50
+ managedConnect: ManagedConnectLike;
37
51
  }
38
52
 
39
53
  let _url = "";
@@ -253,13 +267,17 @@ export function renderImportStatus(): TemplateResult {
253
267
  }
254
268
 
255
269
  export function renderImportSource(ctx: ImportTabCtx): TemplateResult {
256
- if (!hasOpenAiKey()) {
270
+ if (!ctx.aiGateOpen()) {
257
271
  return html`
258
272
  <div class="new-project-tab-intro">
259
- Importing uses an AI-guided pipeline to clone an existing site. Add an OpenAI-compatible API
260
- key to continue.
273
+ Importing uses an AI-guided pipeline to clone an existing site.
274
+ ${
275
+ ctx.managedConnect.canOffer()
276
+ ? "Connect Cloudflare, or add an OpenAI-compatible API key, to continue."
277
+ : "Add an OpenAI-compatible API key to continue."
278
+ }
261
279
  </div>
262
- <div class="new-project-creds">${ctx.credsForm.render()}</div>
280
+ <div class="new-project-creds">${ctx.managedConnect.render()} ${ctx.credsForm.render()}</div>
263
281
  `;
264
282
  }
265
283
 
@@ -6,7 +6,10 @@
6
6
  * (colors, fonts, logo, breakpoints — a creation-time subset of the settings modal), prefilled from
7
7
  * the chosen source and customizable before anything is written.
8
8
  *
9
- * Import and Agent are gated behind the AI credentials form until a key is stored.
9
+ * Import and Agent need working AI, so both are gated until credentials exist — a key stored in
10
+ * this browser, or a backend that holds its own (managed cloud platforms, env-keyed dev servers).
11
+ * While gated they offer the same two paths as the assistant sidebar: connect Cloudflare for
12
+ * Workers AI, or bring a key.
10
13
  */
11
14
 
12
15
  import { html } from "lit-html";
@@ -14,9 +17,10 @@ import { errorMessage } from "@jxsuite/schema/parse";
14
17
  import { openModal } from "../ui/layers";
15
18
  import { getPlatform } from "../platform";
16
19
  import { installUrlOf } from "../platform-errors";
17
- import { hasOpenAiKey } from "../services/ai-settings";
20
+ import { hasAiCredentials } from "../services/ai-models";
18
21
  import { setPendingAgentPrompt } from "../services/agent-seed";
19
22
  import { createAiCredentialsForm } from "../ui/ai-credentials-form";
23
+ import { createManagedConnect } from "../ui/ai-managed-connect";
20
24
  import { PROJECT_TEMPLATES } from "./templates";
21
25
  import { collectDesign, renderDesignFields, resetDesignFields } from "./design-fields";
22
26
  import {
@@ -98,22 +102,44 @@ let _resolve: ((result: { root: string; config: ProjectConfig } | null) => void)
98
102
  // Before a platform is registered (the form fetches models through the platform on edit).
99
103
  let _credsForm: ReturnType<typeof createAiCredentialsForm> | null = null;
100
104
 
105
+ function rerenderIfOpen() {
106
+ if (_handle) {
107
+ renderModal();
108
+ }
109
+ }
110
+
101
111
  function credsForm() {
102
112
  _credsForm ??= createAiCredentialsForm({
103
- onSaved: () => {
104
- if (_handle) {
105
- renderModal();
106
- }
107
- },
108
- requestRender: () => {
109
- if (_handle) {
110
- renderModal();
111
- }
112
- },
113
+ onSaved: rerenderIfOpen,
114
+ requestRender: rerenderIfOpen,
113
115
  });
114
116
  return _credsForm;
115
117
  }
116
118
 
119
+ /**
120
+ * The keyless "Connect Cloudflare" option shown beside the key form on managed platforms — the same
121
+ * controller the assistant sidebar uses. Lazy for the same reason as the form above.
122
+ */
123
+ let _managedConnect: ReturnType<typeof createManagedConnect> | null = null;
124
+
125
+ function managedConnect() {
126
+ _managedConnect ??= createManagedConnect({ requestRender: rerenderIfOpen });
127
+ return _managedConnect;
128
+ }
129
+
130
+ /**
131
+ * The Import and Agent tabs need working AI, which a managed platform can supply without any local
132
+ * key. Probe on every gated render so the Cloudflare option appears as soon as the backend
133
+ * answers.
134
+ */
135
+ function aiGateOpen(): boolean {
136
+ if (hasAiCredentials()) {
137
+ return true;
138
+ }
139
+ managedConnect().ensureProbe();
140
+ return false;
141
+ }
142
+
117
143
  /**
118
144
  * Open the New Project modal. Returns a promise that resolves with the created project info (or
119
145
  * null if cancelled).
@@ -235,8 +261,10 @@ function sourceLabel(): string {
235
261
  function renderModal() {
236
262
  const platform = getPlatform();
237
263
  const importCtx: ImportTabCtx = {
264
+ aiGateOpen,
238
265
  credsForm: credsForm(),
239
266
  form: _form,
267
+ managedConnect: managedConnect(),
240
268
  onDone: finish,
241
269
  rerender: renderModal,
242
270
  resolveDestination: () => validateParams(),
@@ -469,13 +497,17 @@ function renderModal() {
469
497
  : html`<div class="new-project-tab-intro">No starter sites are available.</div>`;
470
498
 
471
499
  const agentSourceTpl = () => {
472
- if (!hasOpenAiKey()) {
500
+ if (!aiGateOpen()) {
473
501
  return html`
474
502
  <div class="new-project-tab-intro">
475
- The agent uses your AI provider to build the site. Add an OpenAI-compatible API key to
476
- continue.
503
+ The agent uses your AI provider to build the site.
504
+ ${
505
+ managedConnect().canOffer()
506
+ ? "Connect Cloudflare, or add an OpenAI-compatible API key, to continue."
507
+ : "Add an OpenAI-compatible API key to continue."
508
+ }
477
509
  </div>
478
- <div class="new-project-creds">${credsForm().render()}</div>
510
+ <div class="new-project-creds">${managedConnect().render()} ${credsForm().render()}</div>
479
511
  `;
480
512
  }
481
513
  return html`
@@ -597,7 +629,7 @@ function renderModal() {
597
629
  `;
598
630
  }
599
631
  if (_step === "source") {
600
- const gated = (_tab === "import" || _tab === "agent") && !hasOpenAiKey();
632
+ const gated = (_tab === "import" || _tab === "agent") && !aiGateOpen();
601
633
  return html`
602
634
  <sp-button variant="secondary" @click=${closeNewProjectModal}>Cancel</sp-button>
603
635
  ${gated ? "" : html`<sp-button variant="accent" @click=${goNext}>Next</sp-button>`}