@jxsuite/studio 2.4.3 → 4.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/README.md +5 -5
- package/dist/chunks/Overlay.dev-723bv0df.js +23 -0
- package/dist/chunks/cf-settings-45fgyx8y.js +21 -0
- package/dist/chunks/{components-z6cnkyy1.js → components-zmx10t62.js} +6 -5
- package/dist/chunks/content-source-t88vb2dr.js +49 -0
- package/dist/chunks/{convert-to-repeater-k10dvcc7.js → convert-to-repeater-dhw1nrz9.js} +27 -18
- package/dist/chunks/entry-commands-6z6bhxg0.js +46 -0
- package/dist/chunks/{extension-sections-0ada0xcw.js → extension-sections-3ydgjbdy.js} +32 -23
- package/dist/chunks/file-ops-rskmhxyq.js +48 -0
- package/dist/chunks/files-0cratsr0.js +92 -0
- package/dist/chunks/{format-host-wewba4nq.js → format-host-tgcmfs4p.js} +3 -3
- package/dist/chunks/fs-events-hrrg328g.js +21 -0
- package/dist/chunks/{git-panel-2dh1aw50.js → git-panel-0yywyk76.js} +21 -15
- package/dist/chunks/{jx-validate-jj9rnt4n.js → jx-validate-8q2g05x1.js} +6 -4
- package/dist/chunks/layers-panel-z3tqe5cd.js +67 -0
- package/dist/chunks/layers-q75tj2mf.js +45 -0
- package/dist/chunks/lit-html-j055mkh8.js +22 -0
- package/dist/chunks/monaco-buffer-dqw0xxpx.js +25 -0
- package/dist/chunks/{monaco-setup-a5wkmbgh.js → monaco-setup-rws7k4br.js} +4 -4
- package/dist/chunks/notify-yjm79n4s.js +30 -0
- package/dist/chunks/parse-24d0gay3.js +19 -0
- package/dist/chunks/platform-ba1j1gbh.js +16 -0
- package/dist/chunks/publish-panel-54jvhskq.js +28 -0
- package/dist/chunks/quick-search-cgfmjwq5.js +65 -0
- package/dist/chunks/references-3qx8fkyp.js +29 -0
- package/dist/chunks/right-panel-cqp8bc4v.js +46 -0
- package/dist/chunks/sp-overlay.dev-yztmtrs1.js +18 -0
- package/dist/chunks/sp-popover.dev-n2etmqdp.js +9 -0
- package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +15 -0
- package/dist/chunks/sp-tooltip.dev-xcvpnqr0.js +15 -0
- package/dist/chunks/{sp-tray.dev-64v4pxw8.js → sp-tray.dev-tkbscm06.js} +5 -5
- package/dist/chunks/{studio-f5yyvqxv.js → studio-1hbqnt19.js} +37 -31
- package/dist/chunks/studio-2zkz8evn.js +3163 -0
- package/dist/chunks/{studio-0b6fs05q.js → studio-4pqqxbrt.js} +23 -15
- package/dist/chunks/{studio-kvfx9z2c.js → studio-4rzyrtjz.js} +70 -31
- package/dist/chunks/{studio-ef8t53vc.js → studio-5q69gfag.js} +60 -56
- package/dist/chunks/{studio-v2c5pmjj.js → studio-5rywrkxz.js} +565 -133
- package/dist/chunks/{studio-6kv3apa9.js → studio-68n67094.js} +25 -9
- package/dist/chunks/{studio-0rqqhrdy.js → studio-7nte2hj4.js} +2 -2
- package/dist/chunks/{studio-hyn3vhwr.js → studio-8swrjbny.js} +31 -18
- package/dist/chunks/studio-92ab2jqe.js +282 -0
- package/dist/chunks/{studio-sas7mt3k.js → studio-9ab8gqsm.js} +2 -2
- package/dist/chunks/{studio-28xc8ws4.js → studio-9c4k4vhv.js} +14 -10
- package/dist/chunks/studio-9n4mrmnb.js +184 -0
- package/dist/chunks/{studio-tr20m8g8.js → studio-9r6j5ev1.js} +37 -2
- package/dist/chunks/{studio-aweyr4v1.js → studio-ahjfdy6e.js} +9 -7
- package/dist/chunks/{studio-s519zc99.js → studio-bgxt93ga.js} +3 -3
- package/dist/chunks/{studio-26sv44br.js → studio-cb12yz9w.js} +8543 -8784
- package/dist/chunks/studio-cft9r4r7.js +119 -0
- package/dist/chunks/{studio-rqemfhz6.js → studio-eenfkt4e.js} +6 -6
- package/dist/chunks/studio-eesa1myv.js +498 -0
- package/dist/chunks/{studio-ay8wcr2b.js → studio-eqq1w638.js} +7 -9
- package/dist/chunks/{studio-ke244pam.js → studio-ex8zyp98.js} +51 -25
- package/dist/chunks/studio-gv8vvshc.js +118 -0
- package/dist/chunks/studio-hbtp0eh1.js +1315 -0
- package/dist/chunks/{studio-cz2j1c0g.js → studio-htsf0w6m.js} +75 -123
- package/dist/chunks/{studio-pyrw8h0w.js → studio-kjjsjak4.js} +528 -212
- package/dist/chunks/{studio-5x5774ak.js → studio-kryvsw6j.js} +3 -3
- package/dist/chunks/studio-kyh9avvv.js +28 -0
- package/dist/chunks/{studio-jnakhg87.js → studio-kyk2v92a.js} +14 -14
- package/dist/chunks/{studio-twsafr1e.js → studio-m3hwn7y1.js} +3 -3
- package/dist/chunks/{studio-tzx3xmvx.js → studio-mmv72a27.js} +8 -8
- package/dist/chunks/{studio-vhk5fmfv.js → studio-n8b9wqwy.js} +5 -5
- package/dist/chunks/{studio-cjm7fdjf.js → studio-p4ngc046.js} +8 -104
- package/dist/chunks/{studio-sccge2wf.js → studio-p8j86wag.js} +2 -2
- package/dist/chunks/{studio-v1va0vhd.js → studio-pdfjntbk.js} +75 -139
- package/dist/chunks/studio-q43a5y9h.js +394 -0
- package/dist/chunks/studio-q7v10cg2.js +106 -0
- package/dist/chunks/{studio-2w11grav.js → studio-qq6jym04.js} +3 -3
- package/dist/chunks/studio-r1r7afxx.js +1066 -0
- package/dist/chunks/{studio-3fk1ac9z.js → studio-rdx8jcxy.js} +4 -4
- package/dist/chunks/{studio-4mqb9a0j.js → studio-rn9ta2mz.js} +4 -4
- package/dist/chunks/studio-sf7zjgv1.js +91 -0
- package/dist/chunks/studio-t7qb9db8.js +665 -0
- package/dist/chunks/studio-tmc5emrw.js +8 -0
- package/dist/chunks/{studio-wdt9y92a.js → studio-txwkm6p9.js} +2 -2
- package/dist/chunks/{studio-dgj7gq44.js → studio-v38hmvx1.js} +7 -7
- package/dist/chunks/{studio-7ejemhej.js → studio-vadzgkrz.js} +6 -6
- package/dist/chunks/studio-wcy8trfx.js +263 -0
- package/dist/chunks/{studio-hb7c46jy.js → studio-wx5609xd.js} +21 -15
- package/dist/chunks/studio-xrapxwwc.js +1539 -0
- package/dist/chunks/{studio-ws6ef6js.js → studio-yxrkyv8a.js} +41 -29
- package/dist/chunks/{studio-xm3dbxvn.js → studio-zczps70q.js} +2 -2
- package/dist/chunks/workspace-xpres40v.js +89 -0
- package/dist/iframe-entry.js +450 -48
- package/dist/studio.js +2455 -1761
- package/package.json +16 -15
- package/src/browse/library-layouts.ts +3 -3
- package/src/browse/library-pane.ts +59 -5
- package/src/browse/library-preview.ts +19 -3
- package/src/canvas/asset-refs.ts +119 -0
- package/src/canvas/asset-resolve.ts +256 -0
- package/src/canvas/canvas-live-render.ts +23 -15
- package/src/canvas/canvas-render.ts +23 -1
- package/src/canvas/iframe-entry.ts +1 -0
- package/src/canvas/iframe-host.ts +4 -0
- package/src/canvas/iframe-patch.ts +47 -21
- package/src/canvas/iframe-protocol.ts +9 -0
- package/src/canvas/iframe-render.ts +50 -16
- package/src/commands/app-commands.ts +4 -0
- package/src/commands/command-args.ts +29 -0
- package/src/commands/context.ts +15 -1
- package/src/commands/defaults.ts +29 -1
- package/src/commands/levels.ts +16 -0
- package/src/commands/live-context.ts +3 -0
- package/src/content/collection-match.ts +267 -0
- package/src/content/entry-commands.ts +38 -29
- package/src/content/entry-fields.ts +6 -0
- package/src/editor/file-drop-action.ts +2 -1
- package/src/editor/shortcuts.ts +5 -0
- package/src/files/file-ops.ts +10 -42
- package/src/files/files.ts +487 -61
- package/src/files/fs-events.ts +9 -0
- package/src/files/gitignore.ts +432 -0
- package/src/files/media-paths.ts +37 -14
- package/src/files/media-upload.ts +47 -6
- package/src/files/serialize-document.ts +60 -0
- package/src/format/convert-file.ts +511 -0
- package/src/format/format-choices.ts +199 -0
- package/src/grid/grid-idle.ts +45 -0
- package/src/grid/grid-view.ts +35 -0
- package/src/grid/schema-columns.ts +2 -1
- package/src/grid/sources/content-source.ts +24 -3
- package/src/media/media-open.ts +58 -0
- package/src/media/media-pane.ts +310 -0
- package/src/new-project/import-tab.ts +333 -160
- package/src/new-project/new-project-modal.ts +27 -34
- package/src/packages/ensure-deps.ts +14 -7
- package/src/packages/jxsuite-update.ts +11 -11
- package/src/page-params.ts +10 -46
- package/src/panels/activity-bar.ts +45 -16
- package/src/panels/ai-chat/attached-context.ts +6 -1
- package/src/panels/ai-chat/chat-view.ts +255 -13
- package/src/panels/ai-chat/composer.ts +21 -80
- package/src/panels/ai-panel.ts +116 -2
- package/src/panels/frontmatter-fields.ts +6 -2
- package/src/panels/navigator-panels.ts +7 -6
- package/src/panels/properties-panel.ts +8 -2
- package/src/panels/seo-modal.ts +5 -6
- package/src/panels/settings-menu.ts +799 -0
- package/src/panels/signals-panel.ts +2 -1
- package/src/panels/toolbar.ts +135 -15
- package/src/platforms/cloud.ts +34 -3
- package/src/platforms/devserver.ts +72 -34
- package/src/preview/preview-overlay.ts +226 -0
- package/src/services/ai-ask.ts +247 -0
- package/src/services/ai-import-tools.ts +494 -0
- package/src/services/ai-models.ts +101 -11
- package/src/services/ai-project-tools.ts +20 -26
- package/src/services/ai-settings.ts +69 -71
- package/src/services/ai-system-prompt.ts +88 -2
- package/src/services/ai-turn-signal.ts +70 -0
- package/src/services/cf-settings.ts +19 -37
- package/src/services/connection.ts +74 -0
- package/src/services/context-manager.ts +89 -0
- package/src/services/document-assistant.ts +49 -7
- package/src/services/idle.ts +6 -1
- package/src/services/import-client.ts +33 -6
- package/src/services/import-run.ts +158 -0
- package/src/services/import-seed.ts +120 -0
- package/src/services/jx-validate.ts +58 -6
- package/src/services/project-adoption.ts +71 -0
- package/src/services/references.ts +0 -0
- package/src/services/settings/definitions.ts +181 -0
- package/src/services/settings/kernel.ts +335 -0
- package/src/services/settings/write-queue.ts +116 -0
- package/src/services/tool-executor.ts +34 -2
- package/src/settings/contexts-section.ts +45 -3
- package/src/settings/dependencies-editor.ts +43 -23
- package/src/settings/general-settings.ts +30 -12
- package/src/settings/preferences-accounts.ts +6 -19
- package/src/settings/preferences-dialog.ts +18 -41
- package/src/settings/preferences-keymap.ts +8 -18
- package/src/settings/preferences-sections.ts +57 -0
- package/src/settings/schema-field-ui.ts +2 -2
- package/src/settings/settings-document.ts +65 -9
- package/src/shell.ts +6 -14
- package/src/studio.ts +65 -7
- package/src/tabs/transact.ts +42 -19
- package/src/types.ts +164 -8
- package/src/ui/ai-credentials-form.ts +52 -27
- package/src/ui/ai-model-picker.ts +162 -0
- package/src/ui/choice-field.ts +72 -0
- package/src/ui/layers.ts +83 -5
- package/src/ui/media-picker.ts +6 -11
- package/src/utils/studio-utils.ts +43 -31
- package/styles/forced-colors.css +7 -0
- package/styles/overlays.css +256 -0
- package/styles/panels.css +13 -1
- package/styles/shell.css +111 -0
- package/dist/chunks/Overlay.dev-d2ajy6sj.js +0 -23
- package/dist/chunks/cf-settings-g5fbf2zp.js +0 -17
- package/dist/chunks/content-source-k9724vcv.js +0 -39
- package/dist/chunks/files-yew27sn9.js +0 -69
- package/dist/chunks/layers-panel-qnsghwwd.js +0 -59
- package/dist/chunks/publish-panel-a0pdft5w.js +0 -21
- package/dist/chunks/quick-search-9avyc4pc.js +0 -54
- package/dist/chunks/right-panel-kcnhtc5j.js +0 -38
- package/dist/chunks/sp-overlay.dev-s18hymn9.js +0 -18
- package/dist/chunks/sp-popover.dev-dj9x7e7a.js +0 -9
- package/dist/chunks/sp-progress-circle.dev-0zxcyz3r.js +0 -15
- package/dist/chunks/sp-tooltip.dev-5c6d1kjz.js +0 -15
- package/dist/chunks/studio-a7pzveq1.js +0 -110
- package/dist/chunks/studio-c31yb4t6.js +0 -55
- package/dist/chunks/studio-ra7ar50f.js +0 -6682
- package/src/canvas/content-assets.ts +0 -263
- package/src/canvas/site-style-css.ts +0 -121
- package/src/services/settings-store.ts +0 -99
|
@@ -1,21 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The New Project modal's Import source step: an AI-guided clone of an existing site. Gated behind
|
|
3
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
|
|
5
|
-
* parameters live on the modal's shared Parameters step
|
|
6
|
-
*
|
|
4
|
+
* key, or a backend holding its own; otherwise a URL, crawl options, a model and a brief. The
|
|
5
|
+
* name/directory parameters live on the modal's shared Parameters step.
|
|
6
|
+
*
|
|
7
|
+
* **This form no longer runs the import.** It gathers a brief and hands it to the assistant, which
|
|
8
|
+
* runs the pipeline as a tool call. The wizard used to own the run, and the consequence was that a
|
|
9
|
+
* successful import destroyed its own account of itself: every phase line and every warning lived
|
|
10
|
+
* in a log that vanished with the modal at the moment it handed off. In the transcript the run
|
|
11
|
+
* survives, the model can read what the crawl actually found, and it can stop and ask about
|
|
12
|
+
* anything the pipeline had to guess at.
|
|
7
13
|
*/
|
|
8
14
|
|
|
9
|
-
import { html } from "lit-html";
|
|
15
|
+
import { html, nothing } from "lit-html";
|
|
16
|
+
import { live } from "lit/directives/live.js";
|
|
10
17
|
import { getPlatform } from "../platform";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { notify } from "../services/notify";
|
|
14
|
-
import { errorMessage } from "@jxsuite/schema/parse";
|
|
18
|
+
import { preferredModel } from "../services/ai-models";
|
|
19
|
+
import { createModelPicker } from "../ui/ai-model-picker";
|
|
15
20
|
import { destinationPath } from "./location-fields";
|
|
16
21
|
import type { TemplateResult } from "lit-html";
|
|
17
|
-
import type {
|
|
18
|
-
|
|
22
|
+
import type { CreateProjectDestination, ImportBreakpointPolicy } from "../types";
|
|
23
|
+
|
|
24
|
+
import { setPendingImportBrief } from "../services/import-seed";
|
|
25
|
+
import type { ImportBrief } from "../services/import-seed";
|
|
26
|
+
|
|
27
|
+
/** The three answers the Breakpoints control offers. */
|
|
28
|
+
type BreakpointMode = "all" | "limit" | "explicit";
|
|
29
|
+
|
|
30
|
+
/** How a kept width matches one the site declared. */
|
|
31
|
+
type BreakpointRounding = "nearest" | "down" | "up";
|
|
32
|
+
|
|
33
|
+
/** The most breakpoints the control will offer to keep — `@jxsuite/import`'s own ceiling. */
|
|
34
|
+
const MAX_BREAKPOINTS = 12;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The deepest crawl the pipeline accepts.
|
|
38
|
+
*
|
|
39
|
+
* The field said 2 while `import_site` and the endpoint both clamped to 5, so the wizard could not
|
|
40
|
+
* ask for a depth the backend was willing to run.
|
|
41
|
+
*/
|
|
42
|
+
const MAX_DEPTH = 5;
|
|
19
43
|
|
|
20
44
|
/** Structural view of src/ui/ai-credentials-form's controller (what this tab renders when gated). */
|
|
21
45
|
export interface CredsFormLike {
|
|
@@ -39,8 +63,11 @@ export interface ImportTabCtx {
|
|
|
39
63
|
*/
|
|
40
64
|
resolveDestination: () => CreateProjectDestination | null;
|
|
41
65
|
rerender: () => void;
|
|
42
|
-
/**
|
|
43
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Called with the brief the form gathered. The modal closes WITHOUT a project — the assistant
|
|
68
|
+
* runs the import as a tool call and creates it.
|
|
69
|
+
*/
|
|
70
|
+
onHandoff: (brief: ImportBrief) => void;
|
|
44
71
|
/** The modal's shared credentials-form instance, rendered while the gate is closed. */
|
|
45
72
|
credsForm: CredsFormLike;
|
|
46
73
|
/**
|
|
@@ -55,11 +82,55 @@ export interface ImportTabCtx {
|
|
|
55
82
|
let _url = "";
|
|
56
83
|
let _depth = 1;
|
|
57
84
|
let _maxPages = 20;
|
|
85
|
+
/**
|
|
86
|
+
* How many of the site's breakpoints the project keeps, and how a kept width matches a declared
|
|
87
|
+
* one.
|
|
88
|
+
*
|
|
89
|
+
* Three by default, and that default is the point of the control. A real site declares as many
|
|
90
|
+
* breakpoints as it has accumulated frameworks — nine is ordinary — and the importer used to keep
|
|
91
|
+
* every one, so an imported project opened with nine canvas sizes and nine columns in every style
|
|
92
|
+
* editor. Nobody authors against nine, and nobody chose these nine.
|
|
93
|
+
*/
|
|
94
|
+
let _breakpointMode: BreakpointMode = "limit";
|
|
95
|
+
let _breakpointCount = 3;
|
|
96
|
+
let _breakpointWidths = "";
|
|
97
|
+
let _breakpointRounding: BreakpointRounding = "nearest";
|
|
58
98
|
let _aiNaming = true;
|
|
59
|
-
|
|
60
|
-
|
|
99
|
+
/**
|
|
100
|
+
* The model this import will run its AI passes on. Empty means "whatever the assistant would use",
|
|
101
|
+
* which is what the tab did silently before there was a picker.
|
|
102
|
+
*
|
|
103
|
+
* A DRAFT, not the `jx.ai.model` preference: choosing a model for one import must not retarget the
|
|
104
|
+
* assistant for every later conversation. `startImport` falls back to {@link preferredModel} so an
|
|
105
|
+
* untouched picker behaves exactly as the tab did before.
|
|
106
|
+
*/
|
|
107
|
+
let _model = "";
|
|
108
|
+
/** What the user wants done with the site once it is imported. Handed to the assistant. */
|
|
109
|
+
let _prompt = "";
|
|
110
|
+
/**
|
|
111
|
+
* Build the result and screenshot-diff it against the original.
|
|
112
|
+
*
|
|
113
|
+
* Off by default because it roughly doubles the run: a full compile plus a second browser pass.
|
|
114
|
+
* Worth offering because it produces the only finding that says how WELL the clone came out — every
|
|
115
|
+
* other one is a count of things that were skipped, and a page at 61% fidelity is a question the
|
|
116
|
+
* assistant can put to a person.
|
|
117
|
+
*/
|
|
118
|
+
let _verify = false;
|
|
119
|
+
/**
|
|
120
|
+
* The average fidelity the clone has to reach before the run counts as having matched the original.
|
|
121
|
+
*
|
|
122
|
+
* A floor, not a quality target: a faithful import of a complicated site lands well under 100 for
|
|
123
|
+
* reasons no importer can fix (a rotating hero, a font rendering a hair differently). What it
|
|
124
|
+
* catches is the other case — the clone that came out at 8% and reported success anyway. Same
|
|
125
|
+
* default as `jx-import --min-fidelity`, so the two surfaces answer the same question the same
|
|
126
|
+
* way.
|
|
127
|
+
*
|
|
128
|
+
* Failing it is a FINDING here rather than a failure: the project is written and opened either way,
|
|
129
|
+
* and the assistant reports that the bar was missed. The CLI turns the same number into an exit
|
|
130
|
+
* code because a script in a pipeline has nobody to tell.
|
|
131
|
+
*/
|
|
132
|
+
let _minFidelity = 25;
|
|
61
133
|
let _errorMsg = "";
|
|
62
|
-
let _abort: AbortController | null = null;
|
|
63
134
|
let _dirManual = false;
|
|
64
135
|
|
|
65
136
|
/** Reset the tab's state when the modal opens. */
|
|
@@ -67,14 +138,69 @@ export function resetImportTab() {
|
|
|
67
138
|
_url = "";
|
|
68
139
|
_depth = 1;
|
|
69
140
|
_maxPages = 20;
|
|
141
|
+
_breakpointMode = "limit";
|
|
142
|
+
_breakpointCount = 3;
|
|
143
|
+
_breakpointWidths = "";
|
|
144
|
+
_breakpointRounding = "nearest";
|
|
70
145
|
_aiNaming = true;
|
|
71
|
-
|
|
72
|
-
|
|
146
|
+
_model = "";
|
|
147
|
+
_prompt = "";
|
|
148
|
+
_verify = false;
|
|
149
|
+
_minFidelity = 25;
|
|
73
150
|
_errorMsg = "";
|
|
74
|
-
_abort = null;
|
|
75
151
|
_dirManual = false;
|
|
76
152
|
}
|
|
77
153
|
|
|
154
|
+
/**
|
|
155
|
+
* The tab's model picker, and the scheduler it repaints through.
|
|
156
|
+
*
|
|
157
|
+
* Created once and kept: `createModelPicker` holds the in-flight fetch and the failed-connection
|
|
158
|
+
* record, and a picker rebuilt per render would re-fetch the catalogue on every keystroke. The
|
|
159
|
+
* scheduler is a SLOT rather than a captured closure because `ImportTabCtx` is rebuilt per render
|
|
160
|
+
* (see `importCtxFor`), so the picker must reach whichever one is current.
|
|
161
|
+
*/
|
|
162
|
+
let _rerender: (() => void) | null = null;
|
|
163
|
+
let _picker: ReturnType<typeof createModelPicker> | null = null;
|
|
164
|
+
|
|
165
|
+
function modelPicker() {
|
|
166
|
+
_picker ??= createModelPicker({
|
|
167
|
+
className: "new-project-import-model",
|
|
168
|
+
getModel: () => _model || preferredModel(),
|
|
169
|
+
// A draft, deliberately not `setModel` — see `_model`.
|
|
170
|
+
onChange: (id) => {
|
|
171
|
+
_model = id;
|
|
172
|
+
},
|
|
173
|
+
requestRender: () => _rerender?.(),
|
|
174
|
+
});
|
|
175
|
+
return _picker;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The policy the three controls describe.
|
|
180
|
+
*
|
|
181
|
+
* Exported so a test can read what the form would send without driving the assistant, for the same
|
|
182
|
+
* reason {@link importBriefFor} is.
|
|
183
|
+
*
|
|
184
|
+
* @returns {ImportBreakpointPolicy}
|
|
185
|
+
*/
|
|
186
|
+
export function breakpointPolicyFromForm(): ImportBreakpointPolicy {
|
|
187
|
+
if (_breakpointMode === "all") {
|
|
188
|
+
return { mode: "all" };
|
|
189
|
+
}
|
|
190
|
+
if (_breakpointMode === "explicit") {
|
|
191
|
+
const widths = _breakpointWidths
|
|
192
|
+
.split(",")
|
|
193
|
+
.map((part) => Math.trunc(Number(part.trim())))
|
|
194
|
+
.filter((width) => Number.isFinite(width) && width > 0);
|
|
195
|
+
/* A half-typed width list is not an instruction to keep nothing. Until it parses into at least
|
|
196
|
+
one width, the form still means "three, evenly spaced" — the value the field started from. */
|
|
197
|
+
if (widths.length > 0) {
|
|
198
|
+
return { mode: "explicit", rounding: _breakpointRounding, widths };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return { count: _breakpointCount, mode: "limit", rounding: _breakpointRounding };
|
|
202
|
+
}
|
|
203
|
+
|
|
78
204
|
function slugOf(name: string): string {
|
|
79
205
|
return name
|
|
80
206
|
.toLowerCase()
|
|
@@ -82,19 +208,6 @@ function slugOf(name: string): string {
|
|
|
82
208
|
.replaceAll(/^-|-$/g, "");
|
|
83
209
|
}
|
|
84
210
|
|
|
85
|
-
/** True while an import stream is active (the modal disables tab switching and close). */
|
|
86
|
-
export function isImportRunning(): boolean {
|
|
87
|
-
return _status === "running";
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** Abort the running import (the footer's Cancel Import button). */
|
|
91
|
-
export function cancelImport(ctx: ImportTabCtx) {
|
|
92
|
-
_abort?.abort();
|
|
93
|
-
_abort = null;
|
|
94
|
-
_status = "idle";
|
|
95
|
-
ctx.rerender();
|
|
96
|
-
}
|
|
97
|
-
|
|
98
211
|
/**
|
|
99
212
|
* Validate the source step (the URL) before advancing to the Parameters step. Sets the inline error
|
|
100
213
|
* and returns false when invalid.
|
|
@@ -115,89 +228,77 @@ export function validateImportSource(ctx: ImportTabCtx): boolean {
|
|
|
115
228
|
return true;
|
|
116
229
|
}
|
|
117
230
|
|
|
118
|
-
/**
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
231
|
+
/**
|
|
232
|
+
* The brief this form has gathered, or null when something is still missing (the modal has already
|
|
233
|
+
* rendered the reason inline).
|
|
234
|
+
*
|
|
235
|
+
* Exported so a test can read what the form would hand over without driving the assistant.
|
|
236
|
+
*
|
|
237
|
+
* @param {ImportTabCtx} ctx
|
|
238
|
+
* @returns {ImportBrief | null}
|
|
239
|
+
*/
|
|
240
|
+
export function importBriefFor(ctx: ImportTabCtx): ImportBrief | null {
|
|
124
241
|
let parsed: URL;
|
|
125
242
|
try {
|
|
126
243
|
parsed = new URL(_url.trim());
|
|
127
244
|
} catch {
|
|
128
245
|
_errorMsg = "Enter a valid URL (e.g. https://example.com)";
|
|
129
246
|
ctx.rerender();
|
|
130
|
-
return;
|
|
247
|
+
return null;
|
|
131
248
|
}
|
|
132
249
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
133
250
|
_errorMsg = "The URL must start with http:// or https://";
|
|
134
251
|
ctx.rerender();
|
|
135
|
-
return;
|
|
252
|
+
return null;
|
|
136
253
|
}
|
|
137
254
|
const destination = ctx.resolveDestination();
|
|
138
255
|
if (!destination || destination.kind !== "path") {
|
|
139
|
-
return;
|
|
256
|
+
return null;
|
|
140
257
|
}
|
|
258
|
+
return {
|
|
259
|
+
aiComponents: _aiNaming,
|
|
260
|
+
breakpoints: breakpointPolicyFromForm(),
|
|
261
|
+
depth: _depth,
|
|
262
|
+
directory: destinationPath(destination, ctx.form.directory),
|
|
263
|
+
maxPages: _maxPages,
|
|
264
|
+
model: _model,
|
|
265
|
+
name: ctx.form.name.trim(),
|
|
266
|
+
prompt: _prompt.trim(),
|
|
267
|
+
minFidelity: _minFidelity,
|
|
268
|
+
url: parsed.href,
|
|
269
|
+
verify: _verify,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
141
272
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
directory: destinationPath(destination, ctx.form.directory),
|
|
157
|
-
depth: _depth,
|
|
158
|
-
maxPages: _maxPages,
|
|
159
|
-
aiComponents: _aiNaming,
|
|
160
|
-
...(key ? { apiKey: key } : {}),
|
|
161
|
-
...(baseUrl ? { baseUrl } : {}),
|
|
162
|
-
...(model ? { model } : {}),
|
|
163
|
-
},
|
|
164
|
-
(evt) => {
|
|
165
|
-
_log = [..._log, evt];
|
|
166
|
-
// The log is destroyed the moment a successful import hands off, so the one line the client
|
|
167
|
-
// Emits about lines it could not read is re-posted where it will outlive that.
|
|
168
|
-
if (evt.phase === IMPORT_WARNING_PHASE) {
|
|
169
|
-
notify.warn(evt.message);
|
|
170
|
-
}
|
|
171
|
-
ctx.rerender();
|
|
172
|
-
requestAnimationFrame(() => {
|
|
173
|
-
const el = document.querySelector(".new-project-import-log");
|
|
174
|
-
if (el) {
|
|
175
|
-
el.scrollTop = el.scrollHeight;
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
},
|
|
179
|
-
_abort.signal,
|
|
180
|
-
);
|
|
181
|
-
_status = "idle";
|
|
182
|
-
_abort = null;
|
|
183
|
-
ctx.onDone(result);
|
|
184
|
-
} catch (error) {
|
|
185
|
-
if (_status !== "running") {
|
|
186
|
-
return; // Cancelled by the user — cancelImport already reset the tab.
|
|
187
|
-
}
|
|
188
|
-
_abort = null;
|
|
189
|
-
_status = "error";
|
|
190
|
-
_errorMsg = errorMessage(error);
|
|
191
|
-
ctx.rerender();
|
|
273
|
+
/**
|
|
274
|
+
* Hand the brief to the assistant and close the wizard. Wired to the modal footer's primary button.
|
|
275
|
+
*
|
|
276
|
+
* **The wizard does not run the import any more.** It used to, and the run's whole account of
|
|
277
|
+
* itself — every phase line, every warning — was destroyed the moment it succeeded and the modal
|
|
278
|
+
* handed off. It is a tool call now (`services/ai-import-tools.ts`), so the progress lives in the
|
|
279
|
+
* transcript, the model can read what the crawl actually found, and it can stop and ask about
|
|
280
|
+
* anything the pipeline had to guess at.
|
|
281
|
+
*
|
|
282
|
+
* @param {ImportTabCtx} ctx
|
|
283
|
+
*/
|
|
284
|
+
export function handoffImport(ctx: ImportTabCtx) {
|
|
285
|
+
if (!getPlatform().importSite) {
|
|
286
|
+
return;
|
|
192
287
|
}
|
|
288
|
+
const brief = importBriefFor(ctx);
|
|
289
|
+
if (!brief) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
/* Stored HERE, by the form that gathered it, rather than inside the assistant's hand-off: two
|
|
293
|
+
readers need it — the turn that gets composed now, and `import_site` minutes later for the
|
|
294
|
+
destination — and the producer is the one place both can be sure it was written. */
|
|
295
|
+
setPendingImportBrief(brief);
|
|
296
|
+
ctx.onHandoff(brief);
|
|
193
297
|
}
|
|
194
298
|
|
|
195
|
-
/** The footer's primary-button label
|
|
299
|
+
/** The footer's primary-button label. One state: the wizard hands off and closes. */
|
|
196
300
|
export function importButtonLabel(): string {
|
|
197
|
-
|
|
198
|
-
return "Importing…";
|
|
199
|
-
}
|
|
200
|
-
return _status === "error" ? "Retry Import" : "Import Site";
|
|
301
|
+
return "Import Site";
|
|
201
302
|
}
|
|
202
303
|
|
|
203
304
|
function onUrlInput(ctx: ImportTabCtx) {
|
|
@@ -221,59 +322,96 @@ function onUrlInput(ctx: ImportTabCtx) {
|
|
|
221
322
|
};
|
|
222
323
|
}
|
|
223
324
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
)}
|
|
241
|
-
</div>
|
|
242
|
-
`;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/** The streaming progress view shown (in place of the Parameters step) while a run is active. */
|
|
246
|
-
export function renderImportProgress(): TemplateResult {
|
|
247
|
-
return renderProgress();
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/** Inline error + retained log, rendered on the Parameters step after a failed run. */
|
|
251
|
-
export function renderImportStatus(): TemplateResult {
|
|
252
|
-
if (_status !== "error") {
|
|
253
|
-
return html``;
|
|
254
|
-
}
|
|
325
|
+
/**
|
|
326
|
+
* The Breakpoints control: how many of the site's own breakpoints the project ends up with.
|
|
327
|
+
*
|
|
328
|
+
* Three controls rather than one because they answer different questions and only some of them
|
|
329
|
+
* apply at a time — a count is meaningless once you have named the widths, and both are meaningless
|
|
330
|
+
* when you are keeping everything. The rounding picker stays for the two that use it: it decides
|
|
331
|
+
* which DECLARED width backs a kept one, and the styles flip where the site says they do.
|
|
332
|
+
*
|
|
333
|
+
* @param {ImportTabCtx} ctx
|
|
334
|
+
* @returns {TemplateResult}
|
|
335
|
+
*/
|
|
336
|
+
function breakpointsField(ctx: ImportTabCtx): TemplateResult {
|
|
337
|
+
const onMode = (e: Event) => {
|
|
338
|
+
_breakpointMode = (e.target as HTMLInputElement).value as BreakpointMode;
|
|
339
|
+
ctx.rerender();
|
|
340
|
+
};
|
|
255
341
|
return html`
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
342
|
+
<label class="new-project-field">
|
|
343
|
+
<span class="new-project-label">Breakpoints</span>
|
|
344
|
+
<div class="new-project-import-breakpoints">
|
|
345
|
+
<sp-picker
|
|
346
|
+
size="m"
|
|
347
|
+
class="new-project-breakpoint-mode"
|
|
348
|
+
label="Breakpoints"
|
|
349
|
+
.value=${live(_breakpointMode)}
|
|
350
|
+
@change=${onMode}
|
|
351
|
+
>
|
|
352
|
+
<sp-menu-item value="limit">Limit to</sp-menu-item>
|
|
353
|
+
<sp-menu-item value="explicit">Custom widths</sp-menu-item>
|
|
354
|
+
<sp-menu-item value="all">Keep all</sp-menu-item>
|
|
355
|
+
</sp-picker>
|
|
356
|
+
${
|
|
357
|
+
_breakpointMode === "limit"
|
|
358
|
+
? html`<sp-number-field
|
|
359
|
+
class="new-project-breakpoint-count"
|
|
360
|
+
min="1"
|
|
361
|
+
max=${MAX_BREAKPOINTS}
|
|
362
|
+
.value=${live(_breakpointCount)}
|
|
363
|
+
@change=${(e: Event) => {
|
|
364
|
+
_breakpointCount = Math.trunc(Number((e.target as HTMLInputElement).value)) || 3;
|
|
365
|
+
}}
|
|
366
|
+
></sp-number-field>`
|
|
367
|
+
: nothing
|
|
368
|
+
}
|
|
369
|
+
${
|
|
370
|
+
_breakpointMode === "explicit"
|
|
371
|
+
? html`<sp-textfield
|
|
372
|
+
class="new-project-breakpoint-widths"
|
|
373
|
+
placeholder="640, 1024, 1440"
|
|
374
|
+
.value=${live(_breakpointWidths)}
|
|
375
|
+
@input=${(e: Event) => {
|
|
376
|
+
_breakpointWidths = (e.target as HTMLInputElement).value;
|
|
377
|
+
}}
|
|
378
|
+
></sp-textfield>`
|
|
379
|
+
: nothing
|
|
380
|
+
}
|
|
381
|
+
${
|
|
382
|
+
_breakpointMode === "all"
|
|
383
|
+
? nothing
|
|
384
|
+
: html`<sp-picker
|
|
385
|
+
size="m"
|
|
386
|
+
class="new-project-breakpoint-rounding"
|
|
387
|
+
label="Rounding"
|
|
388
|
+
.value=${live(_breakpointRounding)}
|
|
389
|
+
@change=${(e: Event) => {
|
|
390
|
+
_breakpointRounding = (e.target as HTMLInputElement).value as BreakpointRounding;
|
|
391
|
+
}}
|
|
392
|
+
>
|
|
393
|
+
<sp-menu-item value="nearest">nearest</sp-menu-item>
|
|
394
|
+
<sp-menu-item value="down">round down</sp-menu-item>
|
|
395
|
+
<sp-menu-item value="up">round up</sp-menu-item>
|
|
396
|
+
</sp-picker>`
|
|
397
|
+
}
|
|
398
|
+
</div>
|
|
399
|
+
<span class="new-project-hint">
|
|
400
|
+
${
|
|
401
|
+
_breakpointMode === "all"
|
|
402
|
+
? "Every breakpoint the site declares becomes a canvas size. Real sites often declare nine."
|
|
403
|
+
: _breakpointMode === "explicit"
|
|
404
|
+
? "The project gets these widths, each backed by the declared width nearest it."
|
|
405
|
+
: "Evenly spaced across the widths the site declares — the narrowest, the widest, and the ones between."
|
|
406
|
+
}
|
|
407
|
+
</span>
|
|
408
|
+
</label>
|
|
273
409
|
`;
|
|
274
410
|
}
|
|
275
411
|
|
|
276
412
|
export function renderImportSource(ctx: ImportTabCtx): TemplateResult {
|
|
413
|
+
// `ctx` is rebuilt per render, so the picker's scheduler has to be re-pointed at the current one.
|
|
414
|
+
_rerender = ctx.rerender;
|
|
277
415
|
if (!ctx.aiGateOpen()) {
|
|
278
416
|
return html`
|
|
279
417
|
<div class="new-project-tab-intro">
|
|
@@ -296,7 +434,7 @@ export function renderImportSource(ctx: ImportTabCtx): TemplateResult {
|
|
|
296
434
|
<span class="new-project-label">Site URL *</span>
|
|
297
435
|
<sp-textfield
|
|
298
436
|
placeholder="https://example.com"
|
|
299
|
-
.value=${_url}
|
|
437
|
+
.value=${live(_url)}
|
|
300
438
|
@input=${onUrlInput(ctx)}
|
|
301
439
|
style="width: 100%"
|
|
302
440
|
></sp-textfield>
|
|
@@ -306,8 +444,8 @@ export function renderImportSource(ctx: ImportTabCtx): TemplateResult {
|
|
|
306
444
|
<span class="new-project-label">Crawl Depth</span>
|
|
307
445
|
<sp-number-field
|
|
308
446
|
min="0"
|
|
309
|
-
max
|
|
310
|
-
.value=${_depth}
|
|
447
|
+
max=${MAX_DEPTH}
|
|
448
|
+
.value=${live(_depth)}
|
|
311
449
|
@change=${(e: Event) => {
|
|
312
450
|
_depth = Math.trunc(Number((e.target as HTMLInputElement).value)) || 0;
|
|
313
451
|
}}
|
|
@@ -318,37 +456,72 @@ export function renderImportSource(ctx: ImportTabCtx): TemplateResult {
|
|
|
318
456
|
<sp-number-field
|
|
319
457
|
min="1"
|
|
320
458
|
max="100"
|
|
321
|
-
.value=${_maxPages}
|
|
459
|
+
.value=${live(_maxPages)}
|
|
322
460
|
@change=${(e: Event) => {
|
|
323
461
|
_maxPages = Math.trunc(Number((e.target as HTMLInputElement).value)) || 1;
|
|
324
462
|
}}
|
|
325
463
|
></sp-number-field>
|
|
326
464
|
</label>
|
|
327
465
|
</div>
|
|
466
|
+
${breakpointsField(ctx)}
|
|
328
467
|
<sp-switch
|
|
329
|
-
|
|
468
|
+
.checked=${live(_aiNaming)}
|
|
330
469
|
@change=${(e: Event) => {
|
|
331
470
|
_aiNaming = (e.target as HTMLInputElement).checked;
|
|
332
471
|
}}
|
|
333
472
|
>
|
|
334
473
|
AI component naming
|
|
335
474
|
</sp-switch>
|
|
336
|
-
|
|
475
|
+
<sp-switch
|
|
476
|
+
.checked=${live(_verify)}
|
|
477
|
+
@change=${(e: Event) => {
|
|
478
|
+
_verify = (e.target as HTMLInputElement).checked;
|
|
479
|
+
// The bar below it only means anything while the check runs, so it appears with it.
|
|
480
|
+
ctx.rerender();
|
|
481
|
+
}}
|
|
482
|
+
>
|
|
483
|
+
Check fidelity against the original (slower)
|
|
484
|
+
</sp-switch>
|
|
337
485
|
${
|
|
338
|
-
|
|
486
|
+
_verify
|
|
339
487
|
? html`
|
|
340
|
-
<
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
488
|
+
<label class="new-project-field">
|
|
489
|
+
<span class="new-project-label">Minimum fidelity</span>
|
|
490
|
+
<sp-number-field
|
|
491
|
+
class="new-project-min-fidelity"
|
|
492
|
+
min="0"
|
|
493
|
+
max="100"
|
|
494
|
+
.value=${live(_minFidelity)}
|
|
495
|
+
@change=${(e: Event) => {
|
|
496
|
+
const raw = Math.trunc(Number((e.target as HTMLInputElement).value));
|
|
497
|
+
_minFidelity = Number.isNaN(raw) ? 0 : Math.min(100, Math.max(0, raw));
|
|
498
|
+
}}
|
|
499
|
+
></sp-number-field>
|
|
500
|
+
<span class="new-project-hint">
|
|
501
|
+
Below this average, the assistant reports that the clone did not match the original.
|
|
502
|
+
A floor rather than a target: a faithful import of a complicated site still lands
|
|
503
|
+
well under 100. Set 0 to report the score without judging it.
|
|
504
|
+
</span>
|
|
505
|
+
</label>
|
|
350
506
|
`
|
|
351
507
|
: ""
|
|
352
508
|
}
|
|
509
|
+
<label class="new-project-field">
|
|
510
|
+
<span class="new-project-label">Model</span>
|
|
511
|
+
${modelPicker().render()}
|
|
512
|
+
</label>
|
|
513
|
+
<label class="new-project-field">
|
|
514
|
+
<span class="new-project-label">What should the assistant do with it?</span>
|
|
515
|
+
<sp-textfield
|
|
516
|
+
multiline
|
|
517
|
+
class="new-project-import-prompt"
|
|
518
|
+
placeholder="Keep the layout but modernise the typography, and turn the news list into a content collection…"
|
|
519
|
+
.value=${live(_prompt)}
|
|
520
|
+
@input=${(e: Event) => {
|
|
521
|
+
_prompt = (e.target as HTMLInputElement).value;
|
|
522
|
+
}}
|
|
523
|
+
></sp-textfield>
|
|
524
|
+
</label>
|
|
525
|
+
${_errorMsg ? html`<div class="new-project-error">${_errorMsg}</div>` : ""}
|
|
353
526
|
`;
|
|
354
527
|
}
|