@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.
- package/dist/chunks/{components-0hzentwq.js → components-799jpvhm.js} +4 -4
- package/dist/chunks/{extension-sections-qct0gt8f.js → extension-sections-qr3drkkw.js} +10 -10
- package/dist/chunks/{files-02zn5y24.js → files-j8a1t06k.js} +8 -8
- package/dist/chunks/{format-host-2c188n41.js → format-host-8jxwts2p.js} +2 -2
- package/dist/chunks/{git-panel-mbb0y0hz.js → git-panel-xtq5z81x.js} +6 -6
- package/dist/chunks/{layers-panel-9qekmqk1.js → layers-panel-ancn2wpq.js} +8 -8
- package/dist/chunks/{settings-modal-gj0hrah4.js → settings-modal-17ttr96g.js} +9 -9
- package/dist/chunks/{studio-asvf0j9e.js → studio-0xgs7xrs.js} +2 -2
- package/dist/chunks/{studio-d77qm3pk.js → studio-4d8hzcz5.js} +8 -8
- package/dist/chunks/{studio-hhj92gcr.js → studio-esrewxap.js} +2 -2
- package/dist/chunks/{studio-97qcgvdn.js → studio-gs6kqdt1.js} +2 -2
- package/dist/chunks/{studio-n9zmpxjg.js → studio-jm0z592w.js} +8 -8
- package/dist/chunks/{studio-j6gqyjf4.js → studio-m2t4mdg5.js} +8 -8
- package/dist/chunks/{studio-wd6ewdvd.js → studio-n4p11dfy.js} +6 -6
- package/dist/chunks/{studio-rq0vxb19.js → studio-sjh1wt1a.js} +2 -2
- package/dist/chunks/{studio-ygzav7z5.js → studio-v7jkjqng.js} +6 -6
- package/dist/chunks/{studio-zmy7241e.js → studio-yfcstdzy.js} +2 -2
- package/dist/chunks/{studio-568nx4t5.js → studio-zp7jncs8.js} +6 -6
- package/dist/chunks/{studio-568nx4t5.js.map → studio-zp7jncs8.js.map} +1 -1
- package/dist/studio.js +113 -82
- package/package.json +1 -1
- package/src/new-project/import-tab.ts +27 -9
- package/src/new-project/new-project-modal.ts +49 -17
- package/src/panels/ai-panel.ts +9 -76
- package/src/services/ai-models.ts +45 -1
- package/src/ui/ai-managed-connect.ts +107 -0
- /package/dist/chunks/{components-0hzentwq.js.map → components-799jpvhm.js.map} +0 -0
- /package/dist/chunks/{extension-sections-qct0gt8f.js.map → extension-sections-qr3drkkw.js.map} +0 -0
- /package/dist/chunks/{files-02zn5y24.js.map → files-j8a1t06k.js.map} +0 -0
- /package/dist/chunks/{format-host-2c188n41.js.map → format-host-8jxwts2p.js.map} +0 -0
- /package/dist/chunks/{git-panel-mbb0y0hz.js.map → git-panel-xtq5z81x.js.map} +0 -0
- /package/dist/chunks/{layers-panel-9qekmqk1.js.map → layers-panel-ancn2wpq.js.map} +0 -0
- /package/dist/chunks/{settings-modal-gj0hrah4.js.map → settings-modal-17ttr96g.js.map} +0 -0
- /package/dist/chunks/{studio-asvf0j9e.js.map → studio-0xgs7xrs.js.map} +0 -0
- /package/dist/chunks/{studio-d77qm3pk.js.map → studio-4d8hzcz5.js.map} +0 -0
- /package/dist/chunks/{studio-hhj92gcr.js.map → studio-esrewxap.js.map} +0 -0
- /package/dist/chunks/{studio-97qcgvdn.js.map → studio-gs6kqdt1.js.map} +0 -0
- /package/dist/chunks/{studio-n9zmpxjg.js.map → studio-jm0z592w.js.map} +0 -0
- /package/dist/chunks/{studio-j6gqyjf4.js.map → studio-m2t4mdg5.js.map} +0 -0
- /package/dist/chunks/{studio-wd6ewdvd.js.map → studio-n4p11dfy.js.map} +0 -0
- /package/dist/chunks/{studio-rq0vxb19.js.map → studio-sjh1wt1a.js.map} +0 -0
- /package/dist/chunks/{studio-ygzav7z5.js.map → studio-v7jkjqng.js.map} +0 -0
- /package/dist/chunks/{studio-zmy7241e.js.map → studio-yfcstdzy.js.map} +0 -0
package/src/panels/ai-panel.ts
CHANGED
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
* @license MIT
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { html, render as litRender
|
|
19
|
+
import { html, render as litRender } from "lit-html";
|
|
20
20
|
import type { TemplateResult } from "lit-html";
|
|
21
|
-
import { getPlatform } from "../platform";
|
|
22
21
|
import { effect, effectScope } from "../reactivity";
|
|
23
22
|
import { createDocumentAssistant } from "../services/document-assistant";
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
23
|
+
import { setOpenAiKey } from "../services/ai-settings";
|
|
24
|
+
import { hasAiCredentials } from "../services/ai-models";
|
|
26
25
|
import { createAiCredentialsForm } from "../ui/ai-credentials-form";
|
|
26
|
+
import { createManagedConnect } from "../ui/ai-managed-connect";
|
|
27
27
|
import { clearMarkdownCache } from "./ai-chat/chat-markdown";
|
|
28
28
|
import { renderChatHeader, renderMessageList } from "./ai-chat/chat-view";
|
|
29
29
|
import { createComposer } from "./ai-chat/composer";
|
|
@@ -38,23 +38,6 @@ let mounted = false;
|
|
|
38
38
|
/** Whether the OpenAI key form is showing (gate when no key, or re-edit via the gear). */
|
|
39
39
|
let keyEditing = false;
|
|
40
40
|
|
|
41
|
-
/**
|
|
42
|
-
* One-time proxy probe: managed platforms (cloud Workers AI) and env-keyed dev servers report
|
|
43
|
-
* `configured` from /models, unlocking the assistant without a locally stored key. Fired lazily on
|
|
44
|
-
* the first gated render.
|
|
45
|
-
*/
|
|
46
|
-
let proxyProbe: Promise<void> | null = null;
|
|
47
|
-
|
|
48
|
-
function ensureProxyProbe() {
|
|
49
|
-
proxyProbe ??= fetchAvailableModels({ force: true })
|
|
50
|
-
.catch(() => {
|
|
51
|
-
// Unreachable proxy — the key gate stays up.
|
|
52
|
-
})
|
|
53
|
-
.then(() => {
|
|
54
|
-
scheduleAiRender();
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
41
|
/** Which pane the panel shows once the key gate is passed. */
|
|
59
42
|
let view: "chat" | "sessions" = "chat";
|
|
60
43
|
|
|
@@ -171,56 +154,8 @@ function startEditApiKey() {
|
|
|
171
154
|
|
|
172
155
|
// ─── Managed Cloudflare connect (Workers AI) ─────────────────────────────────
|
|
173
156
|
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
${
|
|
216
|
-
cfConnectError
|
|
217
|
-
? html`<div class="ai-managed-connect-error">${cfConnectError}</div>`
|
|
218
|
-
: nothing
|
|
219
|
-
}
|
|
220
|
-
<div class="ai-managed-connect-divider">— or bring your own key —</div>
|
|
221
|
-
</div>
|
|
222
|
-
`;
|
|
223
|
-
}
|
|
157
|
+
/** Shared with the New Project modal's gates — see ui/ai-managed-connect.ts. */
|
|
158
|
+
const managedConnect = createManagedConnect({ requestRender: scheduleAiRender });
|
|
224
159
|
|
|
225
160
|
/**
|
|
226
161
|
* The credentials gate: on managed platforms a keyless "Connect Cloudflare" (Workers AI) option
|
|
@@ -229,9 +164,7 @@ function renderManagedConnect() {
|
|
|
229
164
|
function renderKeyGate() {
|
|
230
165
|
return html`
|
|
231
166
|
<div class="ai-tab-body">
|
|
232
|
-
<div class="ai-status-center">
|
|
233
|
-
${canOfferManagedConnect() ? renderManagedConnect() : nothing} ${credsForm.render()}
|
|
234
|
-
</div>
|
|
167
|
+
<div class="ai-status-center">${managedConnect.render()} ${credsForm.render()}</div>
|
|
235
168
|
</div>
|
|
236
169
|
`;
|
|
237
170
|
}
|
|
@@ -377,9 +310,9 @@ export function renderAiPanelTemplate(): TemplateResult {
|
|
|
377
310
|
/* The document assistant authenticates via the AI proxy. Gate the chat
|
|
378
311
|
behind the key form until a key is stored locally OR the proxy reports
|
|
379
312
|
itself configured (managed platforms, env-keyed dev servers). */
|
|
380
|
-
if (
|
|
313
|
+
if (!hasAiCredentials() || keyEditing) {
|
|
381
314
|
if (!keyEditing) {
|
|
382
|
-
|
|
315
|
+
managedConnect.ensureProbe();
|
|
383
316
|
}
|
|
384
317
|
return renderKeyGate();
|
|
385
318
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import type { AiModelsResponse } from "@jxsuite/protocol";
|
|
15
15
|
import { getPlatform } from "../platform";
|
|
16
|
-
import { getBaseUrl, getOpenAiKey } from "./ai-settings";
|
|
16
|
+
import { getBaseUrl, getOpenAiKey, hasOpenAiKey } from "./ai-settings";
|
|
17
17
|
|
|
18
18
|
export interface AiModel {
|
|
19
19
|
id: string;
|
|
@@ -25,12 +25,56 @@ let proxyConfigured = false;
|
|
|
25
25
|
let proxyManaged = false;
|
|
26
26
|
let proxyDefaultModel = "";
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* One-shot capability probe shared by every credentials gate, plus the hosts to repaint when it
|
|
30
|
+
* settles. Managed platforms (cloud Workers AI) and env-keyed dev servers report their state from
|
|
31
|
+
* /models, so the gate cannot know which paths to offer until this has run once.
|
|
32
|
+
*/
|
|
33
|
+
let proxyProbe: Promise<void> | null = null;
|
|
34
|
+
const probeListeners = new Set<() => void>();
|
|
35
|
+
|
|
28
36
|
/** Drop the cached model list (call after credentials/endpoint changes). */
|
|
29
37
|
export function invalidateModelCache() {
|
|
30
38
|
cache = null;
|
|
31
39
|
proxyConfigured = false;
|
|
32
40
|
proxyManaged = false;
|
|
33
41
|
proxyDefaultModel = "";
|
|
42
|
+
/* The probe's result IS the three flags above. Clearing them while keeping the settled promise
|
|
43
|
+
would strand every gate on a permanent "unconfigured, unmanaged" reading — ensureProxyProbe
|
|
44
|
+
would no-op forever and the managed option would vanish until a full reload. */
|
|
45
|
+
proxyProbe = null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Run the capability probe once, repainting `onSettled` when it lands. Idempotent: concurrent and
|
|
50
|
+
* repeated callers share the single in-flight fetch, and every registered host is notified. Probe
|
|
51
|
+
* failure is not surfaced — an unreachable proxy simply leaves the gate showing the key form.
|
|
52
|
+
*
|
|
53
|
+
* @param {() => void} [onSettled] - Host re-render scheduler, registered for this and later probes.
|
|
54
|
+
*/
|
|
55
|
+
export function ensureProxyProbe(onSettled?: () => void) {
|
|
56
|
+
if (onSettled) {
|
|
57
|
+
probeListeners.add(onSettled);
|
|
58
|
+
}
|
|
59
|
+
proxyProbe ??= fetchAvailableModels({ force: true })
|
|
60
|
+
.catch(() => {
|
|
61
|
+
// Unreachable proxy — the key gate stays up.
|
|
62
|
+
})
|
|
63
|
+
.then(() => {
|
|
64
|
+
for (const notify of probeListeners) {
|
|
65
|
+
notify();
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Whether the assistant can run at all: a key stored in this browser, or a backend that holds
|
|
72
|
+
* credentials itself (managed cloud platforms, env-keyed dev servers). Every credentials gate must
|
|
73
|
+
* use this rather than `hasOpenAiKey()` alone — gating on the local key locks managed-platform
|
|
74
|
+
* users out of features their own Cloudflare account is already paying for.
|
|
75
|
+
*/
|
|
76
|
+
export function hasAiCredentials(): boolean {
|
|
77
|
+
return hasOpenAiKey() || isProxyConfigured();
|
|
34
78
|
}
|
|
35
79
|
|
|
36
80
|
/**
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* Ai-managed-connect.ts — the keyless "Connect Cloudflare" (Workers AI) option for credentials
|
|
4
|
+
* gates.
|
|
5
|
+
*
|
|
6
|
+
* Companion to ai-credentials-form.ts: managed platforms broker Workers AI on the user's OWN
|
|
7
|
+
* Cloudflare account, so a gate that renders only the key form strands them — they have no key, and
|
|
8
|
+
* nothing on screen offers the hosted OAuth flow that would give them working AI. Both hosts that
|
|
9
|
+
* gate on credentials (the assistant sidebar and the New Project modal's Import/Agent tabs) embed
|
|
10
|
+
* this alongside the form so both real paths are always on offer.
|
|
11
|
+
*
|
|
12
|
+
* State is per-instance (closure-scoped) like the credentials form; the capability probe behind
|
|
13
|
+
* `ensureProbe` is shared module-wide by services/ai-models.ts.
|
|
14
|
+
*
|
|
15
|
+
* @docs studio/ai
|
|
16
|
+
* @license MIT
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { html, nothing } from "lit-html";
|
|
20
|
+
import type { TemplateResult } from "lit-html";
|
|
21
|
+
import { getPlatform, hasPlatform } from "../platform";
|
|
22
|
+
import {
|
|
23
|
+
ensureProxyProbe,
|
|
24
|
+
fetchAvailableModels,
|
|
25
|
+
isManagedProxy,
|
|
26
|
+
isProxyConfigured,
|
|
27
|
+
} from "../services/ai-models";
|
|
28
|
+
|
|
29
|
+
export interface ManagedConnectOptions {
|
|
30
|
+
/** Host re-render scheduler — called on connect start/finish and when the probe settles. */
|
|
31
|
+
requestRender: () => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ManagedConnect {
|
|
35
|
+
/** Whether the keyless path is available and worth offering right now. */
|
|
36
|
+
canOffer: () => boolean;
|
|
37
|
+
/** Fire the shared capability probe, repainting the host when it settles. */
|
|
38
|
+
ensureProbe: () => void;
|
|
39
|
+
/** The CTA block, or `nothing` when the platform cannot broker AI. */
|
|
40
|
+
render: () => TemplateResult | typeof nothing;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create a managed-connect controller bound to a host's render scheduler.
|
|
45
|
+
*
|
|
46
|
+
* @param {ManagedConnectOptions} opts
|
|
47
|
+
* @returns {ManagedConnect}
|
|
48
|
+
*/
|
|
49
|
+
export function createManagedConnect(opts: ManagedConnectOptions): ManagedConnect {
|
|
50
|
+
let busy = false;
|
|
51
|
+
let connectError = "";
|
|
52
|
+
|
|
53
|
+
/*
|
|
54
|
+
* Offer the keyless path when the proxy says managed-but-unconfigured and the platform can run
|
|
55
|
+
* the hosted OAuth flow (the PAL seam — desktop shells can implement cfConnect later). Guarded on
|
|
56
|
+
* hasPlatform() because gates can render from modules that load before registration.
|
|
57
|
+
*/
|
|
58
|
+
function canOffer(): boolean {
|
|
59
|
+
return (
|
|
60
|
+
isManagedProxy() && !isProxyConfigured() && hasPlatform() && Boolean(getPlatform().cfConnect)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function ensureProbe() {
|
|
65
|
+
ensureProxyProbe(opts.requestRender);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function connect() {
|
|
69
|
+
if (busy) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
busy = true;
|
|
73
|
+
connectError = "";
|
|
74
|
+
opts.requestRender();
|
|
75
|
+
try {
|
|
76
|
+
const connection = await getPlatform().cfConnect?.();
|
|
77
|
+
if (connection) {
|
|
78
|
+
// Re-probe: /models flips to configured once the connection lands, opening the gate.
|
|
79
|
+
await fetchAvailableModels({ force: true });
|
|
80
|
+
} else {
|
|
81
|
+
connectError = "Cloudflare connection was not completed.";
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
connectError = error instanceof Error ? error.message : String(error);
|
|
85
|
+
}
|
|
86
|
+
busy = false;
|
|
87
|
+
opts.requestRender();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function render(): TemplateResult | typeof nothing {
|
|
91
|
+
if (!canOffer()) {
|
|
92
|
+
return nothing;
|
|
93
|
+
}
|
|
94
|
+
return html`
|
|
95
|
+
<div class="ai-managed-connect">
|
|
96
|
+
<div>Use Workers AI on your own Cloudflare account — no API key needed.</div>
|
|
97
|
+
<sp-button size="s" ?disabled=${busy} @click=${() => void connect()}>
|
|
98
|
+
${busy ? "Connecting…" : "Connect Cloudflare"}
|
|
99
|
+
</sp-button>
|
|
100
|
+
${connectError ? html`<div class="ai-managed-connect-error">${connectError}</div>` : nothing}
|
|
101
|
+
<div class="ai-managed-connect-divider">— or bring your own key —</div>
|
|
102
|
+
</div>
|
|
103
|
+
`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { canOffer, ensureProbe, render };
|
|
107
|
+
}
|
|
File without changes
|
/package/dist/chunks/{extension-sections-qct0gt8f.js.map → extension-sections-qr3drkkw.js.map}
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|