@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.
- package/README.md +12 -0
- package/agent-skill.js +127 -8
- 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
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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
|
},
|