@jxsuite/studio 0.37.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/studio.js +77654 -76269
- package/dist/studio.js.map +128 -119
- package/package.json +44 -44
- package/src/account-status.ts +39 -0
- package/src/browse/browse.ts +10 -14
- package/src/canvas/iframe-host.ts +1 -1
- package/src/editor/context-menu.ts +1 -1
- package/src/editor/repeater-scope.ts +8 -13
- package/src/files/files.ts +3 -0
- package/src/format/format-host.ts +63 -1
- package/src/new-project/add-repo-modal.ts +183 -0
- package/src/new-project/new-project-modal.ts +22 -3
- package/src/page-params.ts +34 -8
- package/src/panels/ai-chat/chat-markdown.ts +2 -2
- package/src/panels/ai-panel.ts +61 -4
- package/src/panels/data-grid.ts +619 -0
- package/src/panels/signals-panel.ts +102 -437
- package/src/panels/statusbar.ts +1 -1
- package/src/panels/welcome-screen.ts +50 -0
- package/src/platform-errors.ts +30 -0
- package/src/platforms/cloud.ts +172 -89
- package/src/platforms/devserver.ts +172 -0
- package/src/services/context-resolver.ts +73 -0
- package/src/services/data-service.ts +155 -0
- package/src/services/monaco-setup.ts +75 -26
- package/src/settings/contributed-section.ts +406 -0
- package/src/settings/extension-sections.ts +145 -0
- package/src/settings/schema-field-ui.ts +4 -2
- package/src/settings/settings-modal.ts +101 -42
- package/src/site-context.ts +10 -1
- package/src/studio.ts +24 -0
- package/src/tabs/transact.ts +1 -1
- package/src/types.ts +120 -1
- package/src/ui/form-controls.ts +322 -0
- package/src/ui/progress-modal.ts +2 -2
- package/src/ui/schema-form.ts +524 -0
- package/src/utils/studio-utils.ts +4 -3
- package/src/settings/content-types-editor.ts +0 -599
package/src/page-params.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { getPlatform } from "./platform";
|
|
14
|
+
import { loadExtensions } from "./format/format-host";
|
|
14
15
|
import type { JxMutableNode, JxPathsDef } from "@jxsuite/schema/types";
|
|
15
16
|
|
|
16
17
|
/** Param name → candidate values, in `$paths` declaration order. */
|
|
@@ -135,10 +136,15 @@ async function resolveParamValues(pathsDef: JxPathsDef): Promise<ParamValues> {
|
|
|
135
136
|
return out;
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
// Content type-based: { contentType: "blog", param: "slug", field: "id" } —
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
|
|
139
|
+
// Content type-based: { contentType: "blog", param: "slug", field: "id" } — an open extension
|
|
140
|
+
// Discriminator member on JxPathsDef, so narrow to string. Resolved through the backend's
|
|
141
|
+
// ContentCollection pipeline (the same one the canvas's ContentEntry uses), so every offered
|
|
142
|
+
// Value is guaranteed to resolve in preview.
|
|
143
|
+
if (
|
|
144
|
+
"contentType" in pathsDef &&
|
|
145
|
+
typeof pathsDef.contentType === "string" &&
|
|
146
|
+
pathsDef.contentType
|
|
147
|
+
) {
|
|
142
148
|
const param = pathsDef.param ?? "slug";
|
|
143
149
|
const field = pathsDef.field ?? "id";
|
|
144
150
|
const entries = (await resolveContentCollection(pathsDef.contentType)) as {
|
|
@@ -165,8 +171,9 @@ async function resolveParamValues(pathsDef: JxPathsDef): Promise<ParamValues> {
|
|
|
165
171
|
return out;
|
|
166
172
|
}
|
|
167
173
|
|
|
168
|
-
// Data file ref: { "$ref": "./data/products.json", param: "id", field: "sku" }
|
|
169
|
-
|
|
174
|
+
// Data file ref: { "$ref": "./data/products.json", param: "id", field: "sku" } — `$ref` also
|
|
175
|
+
// Reaches here as an open discriminator key, so narrow to a non-empty string.
|
|
176
|
+
if ("$ref" in pathsDef && typeof pathsDef.$ref === "string" && pathsDef.$ref) {
|
|
170
177
|
const param = pathsDef.param ?? "id";
|
|
171
178
|
const field = pathsDef.field ?? "id";
|
|
172
179
|
const content = await getPlatform().readFile(pathsDef.$ref.replace(/^\.\//, ""));
|
|
@@ -184,6 +191,25 @@ async function resolveParamValues(pathsDef: JxPathsDef): Promise<ParamValues> {
|
|
|
184
191
|
return {};
|
|
185
192
|
}
|
|
186
193
|
|
|
194
|
+
/**
|
|
195
|
+
* The `$src` of the ContentCollection class from the enabled extensions payload — whichever
|
|
196
|
+
* extension's manifest declares a class named `ContentCollection` (its backend-resolved descriptor
|
|
197
|
+
* path feeds `/__jx_resolve__` directly). Platforms without an extensions payload fall back to the
|
|
198
|
+
* historical parser-package specifier.
|
|
199
|
+
*
|
|
200
|
+
* @returns {Promise<string>}
|
|
201
|
+
*/
|
|
202
|
+
async function contentCollectionSrc(): Promise<string> {
|
|
203
|
+
const extensions = await loadExtensions();
|
|
204
|
+
for (const ext of extensions) {
|
|
205
|
+
const cls = ext.classes?.find((c) => c.name === "ContentCollection");
|
|
206
|
+
if (cls) {
|
|
207
|
+
return cls.path;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return "@jxsuite/parser/ContentCollection.class.json";
|
|
211
|
+
}
|
|
212
|
+
|
|
187
213
|
/**
|
|
188
214
|
* Resolve a ContentCollection for a content type via the PAL, falling back to a plain dev-proxy
|
|
189
215
|
* fetch on platforms that predate `resolveClass`.
|
|
@@ -191,10 +217,10 @@ async function resolveParamValues(pathsDef: JxPathsDef): Promise<ParamValues> {
|
|
|
191
217
|
* @param {string} contentType
|
|
192
218
|
* @returns {Promise<unknown>}
|
|
193
219
|
*/
|
|
194
|
-
function resolveContentCollection(contentType: string) {
|
|
220
|
+
async function resolveContentCollection(contentType: string) {
|
|
195
221
|
const body = {
|
|
196
222
|
$prototype: "ContentCollection",
|
|
197
|
-
$src:
|
|
223
|
+
$src: await contentCollectionSrc(),
|
|
198
224
|
contentType,
|
|
199
225
|
};
|
|
200
226
|
const platform = getPlatform();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Chat-markdown.js — memoized markdown rendering for assistant chat messages.
|
|
3
3
|
*
|
|
4
|
-
* Wraps @jxsuite/
|
|
4
|
+
* Wraps @jxsuite/markup/md-html (sanitized markdown → HTML) with a per-message cache
|
|
5
5
|
* keyed by message id + content length, so re-renders during streaming only re-parse
|
|
6
6
|
* the message that actually grew. The HTML goes through unsafeHTML, which is safe here
|
|
7
7
|
* because md-html sanitizes (raw HTML dropped, javascript: URLs stripped).
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { html } from "lit-html";
|
|
13
13
|
import type { TemplateResult } from "lit-html";
|
|
14
14
|
import { unsafeHTML } from "lit-html/directives/unsafe-html.js";
|
|
15
|
-
import { markdownToHtml } from "@jxsuite/
|
|
15
|
+
import { markdownToHtml } from "@jxsuite/markup/md-html";
|
|
16
16
|
|
|
17
17
|
const cache = new Map<string, { len: number; html: string }>();
|
|
18
18
|
|
package/src/panels/ai-panel.ts
CHANGED
|
@@ -16,12 +16,13 @@
|
|
|
16
16
|
* @license MIT
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { html, render as litRender } from "lit-html";
|
|
19
|
+
import { html, render as litRender, nothing } from "lit-html";
|
|
20
20
|
import type { TemplateResult } from "lit-html";
|
|
21
|
+
import { getPlatform } from "../platform";
|
|
21
22
|
import { effect, effectScope } from "../reactivity";
|
|
22
23
|
import { createDocumentAssistant } from "../services/document-assistant";
|
|
23
24
|
import { hasOpenAiKey } from "../services/ai-settings";
|
|
24
|
-
import { fetchAvailableModels, isProxyConfigured } from "../services/ai-models";
|
|
25
|
+
import { fetchAvailableModels, isManagedProxy, isProxyConfigured } from "../services/ai-models";
|
|
25
26
|
import { createAiCredentialsForm } from "../ui/ai-credentials-form";
|
|
26
27
|
import { clearMarkdownCache } from "./ai-chat/chat-markdown";
|
|
27
28
|
import { renderChatHeader, renderMessageList } from "./ai-chat/chat-view";
|
|
@@ -168,11 +169,67 @@ function startEditApiKey() {
|
|
|
168
169
|
credsForm.startEdit();
|
|
169
170
|
}
|
|
170
171
|
|
|
171
|
-
|
|
172
|
+
// ─── Managed Cloudflare connect (Workers AI) ─────────────────────────────────
|
|
173
|
+
|
|
174
|
+
let cfConnectBusy = false;
|
|
175
|
+
let cfConnectError = "";
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Managed platforms broker Workers AI on the user's own Cloudflare account: offer connecting it as
|
|
179
|
+
* the keyless alternative whenever the proxy says managed-but-unconfigured and the platform can run
|
|
180
|
+
* the hosted OAuth flow (the PAL seam — desktop shells can implement cfConnect later).
|
|
181
|
+
*/
|
|
182
|
+
function canOfferManagedConnect(): boolean {
|
|
183
|
+
return isManagedProxy() && !isProxyConfigured() && Boolean(getPlatform().cfConnect);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function connectCloudflareForAi() {
|
|
187
|
+
if (cfConnectBusy) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
cfConnectBusy = true;
|
|
191
|
+
cfConnectError = "";
|
|
192
|
+
scheduleAiRender();
|
|
193
|
+
try {
|
|
194
|
+
const connection = await getPlatform().cfConnect?.();
|
|
195
|
+
if (connection) {
|
|
196
|
+
// Re-probe: /models flips to configured once the connection lands, opening the gate.
|
|
197
|
+
await fetchAvailableModels({ force: true });
|
|
198
|
+
} else {
|
|
199
|
+
cfConnectError = "Cloudflare connection was not completed.";
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
cfConnectError = error instanceof Error ? error.message : String(error);
|
|
203
|
+
}
|
|
204
|
+
cfConnectBusy = false;
|
|
205
|
+
scheduleAiRender();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function renderManagedConnect() {
|
|
209
|
+
return html`
|
|
210
|
+
<div class="ai-managed-connect">
|
|
211
|
+
<div>Use Workers AI on your own Cloudflare account — no API key needed.</div>
|
|
212
|
+
<sp-button size="s" ?disabled=${cfConnectBusy} @click=${() => void connectCloudflareForAi()}>
|
|
213
|
+
${cfConnectBusy ? "Connecting…" : "Connect Cloudflare"}
|
|
214
|
+
</sp-button>
|
|
215
|
+
${cfConnectError
|
|
216
|
+
? html`<div class="ai-managed-connect-error">${cfConnectError}</div>`
|
|
217
|
+
: nothing}
|
|
218
|
+
<div class="ai-managed-connect-divider">— or bring your own key —</div>
|
|
219
|
+
</div>
|
|
220
|
+
`;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* The credentials gate: on managed platforms a keyless "Connect Cloudflare" (Workers AI) option
|
|
225
|
+
* sits above the OpenAI-compatible key form — both are real, working paths.
|
|
226
|
+
*/
|
|
172
227
|
function renderKeyGate() {
|
|
173
228
|
return html`
|
|
174
229
|
<div class="ai-tab-body">
|
|
175
|
-
<div class="ai-status-center"
|
|
230
|
+
<div class="ai-status-center">
|
|
231
|
+
${canOfferManagedConnect() ? renderManagedConnect() : nothing} ${credsForm.render()}
|
|
232
|
+
</div>
|
|
176
233
|
</div>
|
|
177
234
|
`;
|
|
178
235
|
}
|