@saltcorn/bootstrap-prompt-theme 0.1.2 → 0.1.3

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 (3) hide show
  1. package/README.md +12 -0
  2. package/agent-skill.js +127 -8
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -30,6 +30,18 @@ This variant generates a lightweight CSS overlay by chatting with an LLM. Bootst
30
30
 
31
31
  The chat history is persisted across page reloads, so you can return to the configuration page and pick up where you left off.
32
32
 
33
+ ### Visual feedback
34
+
35
+ If the `@saltcorn/page-to-pdf` plugin is installed, the AI can capture a screenshot of a live page after each CSS change and use it to self-correct. To enable this, mention a page name in your message:
36
+
37
+ ```
38
+ You: A dark cyberpunk theme. Use the "dashboard" page for visual feedback.
39
+
40
+ AI: [applies CSS overlay, captures screenshot, refines if needed]
41
+ ```
42
+
43
+ The page name is remembered for the rest of the conversation — you only need to mention it once. The AI will tell you whether a screenshot was received and used for refinement, or explain if it failed.
44
+
33
45
  ### Example conversation
34
46
 
35
47
  ```
package/agent-skill.js CHANGED
@@ -2,8 +2,8 @@ const Plugin = require("@saltcorn/data/models/plugin");
2
2
  const { pre, code, div } = require("@saltcorn/markup/tags");
3
3
  const db = require("@saltcorn/data/db");
4
4
  const { getState } = require("@saltcorn/data/db/state");
5
- const { join } = require("path");
6
5
  const { writeFile, readdir, unlink } = require("fs").promises;
6
+ const { join } = require("path");
7
7
 
8
8
  const writeOverlayCSS = async (css, filename = `overlay.${Date.now()}.css`) => {
9
9
  const dest = join(__dirname, "public", filename);
@@ -68,10 +68,124 @@ LAYOUT CONFIG: Besides CSS, you can control structural layout options via set_la
68
68
  - in_card: true | false — wrap page body in a Bootstrap card
69
69
  Only call set_layout_config when you want to change structural layout, separate from CSS. Call both tools when a request requires both structural and CSS changes.
70
70
 
71
+ PAGE STRUCTURE: A typical rendered Saltcorn page looks like this (abridged). Use it to understand element hierarchy, class names, and selectors when writing CSS:
72
+ \`\`\`html
73
+ <html lang="en" data-bs-theme="light">
74
+ <head>
75
+ <meta charset="utf-8">
76
+ <meta name="viewport" content="width=device-width, initial-scale=1">
77
+ <link href="/plugins/public/bootstrap-prompt-theme/bootstrap.min.css" rel="stylesheet">
78
+ <link rel="stylesheet" href="/plugins/public/bootstrap-prompt-theme/sidebar-3.css">
79
+ <link rel="stylesheet" href="/plugins/public/bootstrap-prompt-theme/overlay.css">
80
+ <link href="/static_assets/.../saltcorn.css" rel="stylesheet">
81
+ <script>var _sc_globalCsrf = "<csrf-token>", _sc_version_tag = "...", _sc_lightmode = "light";</script>
82
+ </head>
83
+ <body id="page-top" class="page_<pagename>">
84
+ <div id="wrapper">
85
+ <nav class="navbar d-print-none navbar-expand-md navbar-light bg-light" id="mainNav">
86
+ <div class="container">
87
+ <a class="navbar-brand" href="/">Saltcorn</a>
88
+ <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive">
89
+ <span class="navbar-toggler-icon"></span>
90
+ </button>
91
+ <div class="collapse navbar-collapse" id="navbarResponsive">
92
+ <ul class="navbar-nav ms-auto my-2 my-lg-0">
93
+ <li class="nav-item"><a class="nav-link" href="/table">Tables</a></li>
94
+ <li class="nav-item dropdown">
95
+ <a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown">Settings</a>
96
+ <div class="dropdown-menu">
97
+ <a class="dropdown-item" href="/admin">About application</a>
98
+ <a class="dropdown-item" href="/plugins">Modules</a>
99
+ </div>
100
+ </li>
101
+ <li class="nav-item dropdown">
102
+ <a class="nav-link dropdown-toggle user-nav-section" href="#" data-bs-toggle="dropdown">User</a>
103
+ <div class="dropdown-menu dropdown-menu-end">
104
+ <a class="dropdown-item" href="/auth/settings">User settings</a>
105
+ <a class="dropdown-item" href="/auth/logout">Logout</a>
106
+ </div>
107
+ </li>
108
+ </ul>
109
+ </div>
110
+ </div>
111
+ </nav>
112
+ <div id="page-inner-content">
113
+ <section class="page-section pt-2">
114
+ <div class="container">
115
+ <!-- admin edit bar (admin only) -->
116
+ <div class="card p-1 mt-1 mb-3 d-print-none admin-edit-bar">
117
+ <div class="card-body p-1">...</div>
118
+ </div>
119
+ </div>
120
+ </section>
121
+ <section class="page-section">
122
+ <div class="container">
123
+ <!-- page content, e.g. a table view -->
124
+ <div class="table-responsive">
125
+ <table class="table table-sm table-valign-middle">
126
+ <thead><tr><th>Email</th><th>Role</th></tr></thead>
127
+ <tbody><tr><td>admin@foo.com</td><td>1</td></tr></tbody>
128
+ </table>
129
+ </div>
130
+ </div>
131
+ </section>
132
+ <section class="page-section">
133
+ <div class="container">
134
+ <div id="toasts-area" class="toast-container position-fixed top-0 end-0 p-2" style="z-index: 9999;"></div>
135
+ </div>
136
+ </section>
137
+ </div>
138
+ </div>
139
+ </body>
140
+ </html>
141
+ \`\`\`
142
+
71
143
  WORKFLOW:
72
144
  1. Call apply_css_overlay with the complete CSS — this is the only way to deliver CSS.
73
145
  2. Optionally call set_layout_config for structural layout changes.
74
- 3. After all tool calls, reply with one short sentence confirming what changed. Never include CSS in your text reply.`;
146
+ 3. After apply_css_overlay completes, the tool result JSON may contain a "screenshot" field — a base64-encoded PNG of the live page captured after the CSS was applied. Screenshots are only available when a reference page has been configured (via the "route" parameter) and the environment supports it; if the field is absent or null, skip to step 4 immediately. To read the image, interpret the "screenshot" value as a base64 PNG: decode it visually and assess the rendered page. The "route" parameter only needs to be passed the first time or when the user changes the reference page — the value is stored and reused automatically.
147
+ 4. If a screenshot is present, review it: check that colors, fonts, spacing, and contrast match the intended design. If there is a clear problem, call apply_css_overlay with a targeted correction and review the next screenshot. Repeat only while there are clear remaining issues — stop as soon as the result looks good or after at most 3 correction passes, whichever comes first.
148
+ 5. Once done, reply with one short sentence confirming what changed. Then report on the screenshot status — be specific, not generic:
149
+ - If a screenshot was received and used for refinement: say which page was used and that visual feedback was applied.
150
+ - If a route was configured but the screenshot field was absent or null: say that the screenshot for that page failed or returned no data.
151
+ - If no route was configured at all: add a short note that the user can specify a page name via the "route" parameter in their next request to enable visual feedback.
152
+ Never include CSS in your text reply.`;
153
+
154
+ const capturePageScreenshot = async (req) => {
155
+ const plugin = await findPlugin();
156
+ const pageName = plugin?.configuration?.screenshot_page;
157
+ if (!pageName) return null;
158
+ const action = getState().actions?.page_to_pdf;
159
+ if (!action) {
160
+ getState().log(
161
+ 5,
162
+ "bootstrap-prompt-theme: page_to_pdf action not available, skipping screenshot"
163
+ );
164
+ return null;
165
+ }
166
+ try {
167
+ const base =
168
+ req?.protocol && req?.get?.("host")
169
+ ? `${req.protocol}://${req.get("host")}`
170
+ : getState().getConfig("base_url", "").replace(/\/$/, "");
171
+ const result = await action.run({
172
+ req,
173
+ referrer: base + "/",
174
+ configuration: {
175
+ entity_type: "URL",
176
+ url: `${base}/page/${pageName}`,
177
+ format: "PNG",
178
+ },
179
+ });
180
+ return result?.download?.blob || null;
181
+ } catch (e) {
182
+ getState().log(
183
+ 4,
184
+ `bootstrap-prompt-theme: screenshot failed: ${e.message}`
185
+ );
186
+ return null;
187
+ }
188
+ };
75
189
 
76
190
  const findPlugin = async () => {
77
191
  return (
@@ -117,17 +231,17 @@ class GenerateBootstrapThemeSkill {
117
231
  code(css.slice(0, 300) + (css.length > 300 ? "\n..." : ""))
118
232
  );
119
233
  },
120
- process: async ({ css }) => {
234
+ process: async ({ css, route }, { req }) => {
121
235
  const filename = await writeOverlayCSS(css);
122
236
  const plugin = await findPlugin();
123
237
  if (plugin) {
124
- await savePluginConfig(plugin, {
125
- overlay_css: css,
126
- overlay_file: filename,
127
- });
238
+ const patch = { overlay_css: css, overlay_file: filename };
239
+ if (route) patch.screenshot_page = route;
240
+ await savePluginConfig(plugin, patch);
128
241
  await deleteOldOverlays(filename);
129
242
  }
130
- return { filename };
243
+ const screenshot = await capturePageScreenshot(req);
244
+ return { filename, screenshot };
131
245
  },
132
246
  renderToolResponse: async ({ filename }) => {
133
247
  return div(
@@ -148,6 +262,11 @@ class GenerateBootstrapThemeSkill {
148
262
  description:
149
263
  "Complete valid CSS to write as the overlay. Must start with :root { or a comment. No markdown, no code fences.",
150
264
  },
265
+ route: {
266
+ type: "string",
267
+ description:
268
+ "Saltcorn page name to use for visual feedback screenshots. Only pass this when the user has explicitly named a page. Do not guess or default to any page name. Once set, the value is remembered — only pass it again when the user specifies a different page.",
269
+ },
151
270
  },
152
271
  },
153
272
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saltcorn/bootstrap-prompt-theme",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Bootstrap 5 layout plugin with LLM-generated CSS overlay",
5
5
  "main": "index.js",
6
6
  "scripts": {