-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathprompt-builder.html
More file actions
390 lines (358 loc) · 20.8 KB
/
Copy pathprompt-builder.html
File metadata and controls
390 lines (358 loc) · 20.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ASPPY Prompt Builder</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #f0f2f5; color: #1d2a38; line-height: 1.5; }
.wrap { max-width: 820px; margin: 0 auto; padding: 2rem 1.25rem 4rem; }
h1 { font-size: 1.6rem; font-weight: 800; letter-spacing: -0.02em; margin-bottom: 0.25rem; }
.subtitle { color: #6c757d; font-size: 0.95rem; margin-bottom: 2rem; }
.card { background: #fff; border-radius: 12px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); padding: 1.5rem; margin-bottom: 1.25rem; }
.card h2 { font-size: 1rem; font-weight: 700; margin-bottom: 0.75rem; }
.card p { font-size: 0.85rem; color: #6c757d; margin-bottom: 1rem; }
label { display: block; font-size: 0.8rem; font-weight: 600; margin-bottom: 0.3rem; color: #495057; }
textarea, input[type="text"] { width: 100%; border: 1px solid #ced4da; border-radius: 6px; padding: 0.6rem 0.75rem; font-size: 0.875rem; font-family: inherit; resize: vertical; }
textarea:focus, input[type="text"]:focus { outline: none; border-color: #0d6efd; box-shadow: 0 0 0 3px rgba(13,110,253,0.15); }
.radio-group { display: flex; gap: 0.75rem; margin-bottom: 0.75rem; }
.radio-group label { display: flex; align-items: center; gap: 0.4rem; font-weight: 500; font-size: 0.875rem; cursor: pointer; padding: 0.5rem 1rem; border: 2px solid #dee2e6; border-radius: 8px; transition: all 0.15s; }
.radio-group input { display: none; }
.radio-group input:checked + label, .radio-group label.sel { border-color: #0d6efd; background: rgba(13,110,253,0.06); color: #0d6efd; }
.check-row { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem; }
.check-row input[type="checkbox"] { width: 16px; height: 16px; accent-color: #0d6efd; }
.check-row label { margin-bottom: 0; font-weight: 500; font-size: 0.85rem; cursor: pointer; }
.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
.btn { display: inline-block; padding: 0.65rem 1.5rem; border: none; border-radius: 8px; font-size: 0.9rem; font-weight: 600; cursor: pointer; transition: all 0.15s; }
.btn-primary { background: #0d6efd; color: #fff; }
.btn-primary:hover { background: #0b5ed7; }
.btn-outline { background: none; border: 2px solid #dee2e6; color: #495057; }
.btn-outline:hover { border-color: #adb5bd; }
.btn-row { display: flex; gap: 0.75rem; margin-top: 1rem; flex-wrap: wrap; }
#output-wrap { display: none; }
#output-wrap.show { display: block; }
#output { width: 100%; min-height: 300px; font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; font-size: 0.78rem; line-height: 1.45; background: #1e1e2e; color: #cdd6f4; border: none; border-radius: 8px; padding: 1rem; resize: vertical; }
.tag { display: inline-block; font-size: 0.7rem; font-weight: 600; padding: 0.15rem 0.5rem; border-radius: 4px; background: #e9ecef; color: #495057; margin-left: 0.5rem; vertical-align: middle; }
.copied { position: fixed; top: 1rem; right: 1rem; background: #198754; color: #fff; padding: 0.5rem 1.25rem; border-radius: 8px; font-size: 0.85rem; font-weight: 600; opacity: 0; transition: opacity 0.3s; pointer-events: none; z-index: 99; }
.copied.show { opacity: 1; }
.explain { background: #f8f9fa; border-left: 3px solid #0d6efd; border-radius: 0 8px 8px 0; padding: 1rem 1.25rem; margin-bottom: 1.5rem; }
.explain h2 { font-size: 0.95rem; font-weight: 700; margin-bottom: 0.5rem; color: #0d6efd; }
.explain p { font-size: 0.84rem; color: #495057; margin-bottom: 0.5rem; }
.explain p:last-child { margin-bottom: 0; }
@media (max-width: 600px) { .row2 { grid-template-columns: 1fr; } .radio-group { flex-direction: column; } }
</style>
</head>
<body>
<div class="wrap">
<h1>ASPPY Prompt Builder</h1>
<p class="subtitle">Generate a complete project prompt for AI coding agents</p>
<div class="explain">
<h2>What is this?</h2>
<p>This tool builds a structured JSON prompt that contains your project description together with every technical rule from the ASPPY readme. You paste the JSON output into a coding agent like OpenCode or Claude Code as your <strong>first message</strong>.</p>
<p><strong>Why it works well:</strong> AI coding agents make most of their mistakes early, when they lack context about the framework. A single structured prompt front-loads the hard rules, path conventions, MVC structure, known pitfalls (like the VBScript subscript error), and your project requirements into one message. The agent reads everything before writing a single line of code, so it builds the correct file structure, uses the right include paths, and avoids the common errors from the start. No back-and-forth corrections, no wasted tokens.</p>
</div>
<div class="card">
<h2>Project Description</h2>
<p>Describe what you want to build. Be specific about features, pages, and data.</p>
<textarea id="description" rows="8" placeholder="Example: Build a project management app with task boards, user assignments, due dates, and a Kanban view. Include login with sample users. Tasks should belong to projects and have statuses: todo, in-progress, done."></textarea>
</div>
<div class="card">
<h2>App Name <span class="tag">optional</span></h2>
<input type="text" id="appname" placeholder="e.g. TaskBoard, Invoicer, HelpDesk">
</div>
<div class="card">
<h2>CSS Framework</h2>
<div class="radio-group" id="css-group">
<input type="radio" name="css" id="css-bootstrap" value="bootstrap" checked>
<label for="css-bootstrap">Bootstrap 5</label>
<input type="radio" name="css" id="css-tailwind" value="tailwind">
<label for="css-tailwind">Tailwind CSS</label>
</div>
</div>
<div class="card">
<h2>Application Language</h2>
<p>Choose the language for all UI labels, buttons, messages, and content in the application.</p>
<div class="row2" style="margin-bottom:0.75rem">
<div>
<label for="lang-select">Primary language</label>
<select id="lang-select" style="width:100%;border:1px solid #ced4da;border-radius:6px;padding:0.6rem 0.75rem;font-size:0.875rem;font-family:inherit;background:#fff;">
<option value="English" selected>English</option>
<option value="Chinese (Simplified)">Chinese (Simplified)</option>
<option value="Chinese (Traditional)">Chinese (Traditional)</option>
<option value="Spanish">Spanish</option>
<option value="Hindi">Hindi</option>
<option value="Arabic">Arabic</option>
<option value="Portuguese">Portuguese</option>
<option value="French">French</option>
<option value="German">German</option>
<option value="Japanese">Japanese</option>
<option value="Russian">Russian</option>
<option value="Korean">Korean</option>
<option value="Italian">Italian</option>
<option value="Dutch">Dutch</option>
<option value="Turkish">Turkish</option>
<option value="Polish">Polish</option>
<option value="Indonesian">Indonesian</option>
<option value="Thai">Thai</option>
<option value="Vietnamese">Vietnamese</option>
<option value="Swedish">Swedish</option>
</select>
</div>
<div>
<div class="check-row" style="margin-top:1.65rem">
<input type="checkbox" id="multilingual">
<label for="multilingual">Multi-lingual application</label>
</div>
<div style="font-size:0.78rem;color:#6c757d;margin-top:0.15rem">English is always the default language. Select additional languages below.</div>
</div>
</div>
<div id="multi-langs" style="display:none;border-top:1px solid #eee;padding-top:0.75rem;margin-top:0.5rem">
<label style="margin-bottom:0.5rem">Additional languages (English is always included)</label>
<div class="row2" id="multi-lang-checks"></div>
</div>
</div>
<div class="card">
<h2>Features</h2>
<div class="row2">
<div class="check-row"><input type="checkbox" id="feat-auth" checked><label for="feat-auth">Login / authentication</label></div>
<div class="check-row"><input type="checkbox" id="feat-sample" checked><label for="feat-sample">Sample / seed data</label></div>
<div class="check-row"><input type="checkbox" id="feat-crud" checked><label for="feat-crud">Full CRUD operations</label></div>
<div class="check-row"><input type="checkbox" id="feat-search"><label for="feat-search">Search / filtering</label></div>
<div class="check-row"><input type="checkbox" id="feat-upload"><label for="feat-upload">File uploads</label></div>
<div class="check-row"><input type="checkbox" id="feat-dash"><label for="feat-dash">Dashboard with stats</label></div>
</div>
</div>
<div class="card">
<h2>Additional Instructions <span class="tag">optional</span></h2>
<p>Anything else the agent should know: tone, specific libraries, special requirements.</p>
<textarea id="extra" rows="3" placeholder="e.g. Use a dark color scheme. Include pagination on list pages."></textarea>
</div>
<div class="btn-row">
<button class="btn btn-primary" onclick="generate()">Generate Prompt</button>
<button class="btn btn-outline" onclick="copyOutput()">Copy to Clipboard</button>
</div>
<div id="output-wrap">
<div class="card" style="margin-top:1.25rem; padding:0; overflow:hidden;">
<textarea id="output" readonly></textarea>
</div>
</div>
</div>
<div class="copied" id="copied-toast">Copied to clipboard</div>
<script>
// radio styling
document.querySelectorAll('.radio-group').forEach(function(g){
g.querySelectorAll('input[type=radio]').forEach(function(r){
r.addEventListener('change', function(){
g.querySelectorAll('label').forEach(function(l){ l.classList.remove('sel'); });
if(r.checked) r.nextElementSibling.classList.add('sel');
});
if(r.checked) r.nextElementSibling.classList.add('sel');
});
});
// multi-lingual toggle
var allLangs = ['Chinese (Simplified)','Chinese (Traditional)','Spanish','Hindi','Arabic','Portuguese','French','German','Japanese','Russian','Korean','Italian','Dutch','Turkish','Polish','Indonesian','Thai','Vietnamese','Swedish'];
var multiBox = document.getElementById('multi-langs');
var multiChecks = document.getElementById('multi-lang-checks');
allLangs.forEach(function(lang){
var id = 'ml-' + lang.replace(/[^a-zA-Z]/g,'').toLowerCase();
var d = document.createElement('div');
d.className = 'check-row';
d.innerHTML = '<input type="checkbox" id="'+id+'" data-lang="'+lang+'"><label for="'+id+'">'+lang+'</label>';
multiChecks.appendChild(d);
});
document.getElementById('multilingual').addEventListener('change', function(){
multiBox.style.display = this.checked ? 'block' : 'none';
if(!this.checked){
multiChecks.querySelectorAll('input[type=checkbox]').forEach(function(c){ c.checked = false; });
}
});
function val(id){ return document.getElementById(id).value.trim(); }
function chk(id){ return document.getElementById(id).checked; }
function css(){ return document.querySelector('input[name="css"]:checked').value; }
function generate(){
var desc = val('description');
if(!desc){
document.getElementById('description').focus();
return;
}
var features = [];
if(chk('feat-auth')) features.push('Secure login with session-based authentication and bcrypt password hashing');
if(chk('feat-sample')) features.push('Sample seed data inserted via a one-time /setup route');
if(chk('feat-crud')) features.push('Full CRUD (create, read, update, delete) for all entities');
if(chk('feat-search')) features.push('Search and filtering on list pages');
if(chk('feat-upload')) features.push('File upload support using www/uploads');
if(chk('feat-dash')) features.push('Dashboard page with summary statistics');
var prompt = {
"project_description": desc
};
if(val('appname')) prompt["app_name"] = val('appname');
prompt["css_framework"] = css() === 'tailwind' ? 'Tailwind CSS' : 'Bootstrap 5 (latest CDN)';
var isMulti = chk('multilingual');
var primaryLang = document.getElementById('lang-select').value;
if(isMulti){
var extraLangs = [];
document.querySelectorAll('#multi-lang-checks input[type=checkbox]:checked').forEach(function(c){
extraLangs.push(c.getAttribute('data-lang'));
});
var allSelected = ['English'];
if(primaryLang !== 'English' && allSelected.indexOf(primaryLang) === -1) allSelected.push(primaryLang);
extraLangs.forEach(function(l){ if(allSelected.indexOf(l) === -1) allSelected.push(l); });
prompt["language"] = {
"mode": "multi-lingual",
"default_language": "English",
"supported_languages": allSelected,
"instructions": "Build the application with a language switching mechanism. English is the default. All UI labels, buttons, messages, validation errors, and page titles must be translatable. Store translations in a central location (e.g. a dictionary/lookup structure or database table). Do not hardcode user-facing strings directly in views."
};
} else {
prompt["language"] = primaryLang;
}
if(features.length) prompt["requested_features"] = features;
if(val('extra')) prompt["additional_instructions"] = val('extra');
prompt["platform"] = {
"name": "ASPPY",
"language": "VBScript (Classic ASP syntax)",
"database": "SQLite via ADODB",
"server_command": "python -m ASPPY.server 0.0.0.0 5000 www",
"working_directory": "www",
"starter_template": "Copy www_starter into www before building"
};
prompt["hard_rules"] = [
"Build inside www",
"Do not modify ASPPY\\*.py",
"Do not modify www_starter\\*.*",
"Use ASPPY only, do not add IIS requirements",
"Run dev server on port 5000",
"After editing any included .asp file, restart the ASPPY server process to clear the in-memory ASP compilation cache"
];
prompt["mvc_rules"] = {
"router": "www/default.asp is the front controller and router",
"entry_point": "www/index.asp includes default.asp",
"routing": "Use Request.Path for routing via RouteParts() and RouteIs()",
"separation": [
"default.asp dispatches routes only, no SQL or HTML rendering",
"Controllers live in www/asp/controllers/",
"Models live in www/asp/models/",
"Views live in www/asp/views/",
"Controllers prepare data and include a view; they do not build full pages with Response.Write"
],
"layout": "Pick one layout ownership model: either default.asp or each controller owns RenderHeader/RenderFooter, not both",
"error_handling": [
"Unknown routes must return Response.Status = '404 Not Found'",
"Missing .asp files must stay real 404s",
"Do not bypass MVC by linking directly to /asp/*.asp files"
]
};
prompt["critical_vbscript_pitfall"] = {
"error": "Subscript out of range",
"cause": "VBScript And does NOT short-circuit. All operands are evaluated even if the first is False.",
"broken_example": "If UBound(parts) = 2 And parts(0) = \"contacts\" And IsNumeric(parts(1)) And parts(2) = \"edit\" Then",
"explanation": "parts(2) is evaluated even when the array has 0 or 1 elements, causing a crash.",
"workaround": "Use nested If blocks. Only access parts(N) inside an If partCount >= N guard.",
"correct_pattern": [
"Dim parts, partCount",
"parts = RouteParts()",
"partCount = UBound(parts)",
"",
"If partCount = 1 Then",
" If parts(0) = \"contacts\" Then",
" If IsNumeric(parts(1)) Then",
" Call ContactsShow(parts(1))",
" End If",
" End If",
"End If",
"",
"If partCount = 2 Then",
" If parts(0) = \"contacts\" Then",
" If IsNumeric(parts(1)) Then",
" Select Case parts(2)",
" Case \"edit\"",
" Call ContactsEdit(parts(1))",
" Case \"delete\"",
" Call ContactsDelete(parts(1))",
" End Select",
" End If",
" End If",
"End If"
]
};
prompt["path_rules"] = {
"three_path_types": [
"Route path: /posts/hello (for Request.Path routing)",
"Include path: db.asp or ../asp/db.asp (relative to the file containing the include)",
"Server.MapPath() path: data/app.db (relative to the executing ASP file, never use leading slash)"
],
"mappath_warning": "Server.MapPath is relative to the executing file. The same string points to different physical folders in different files. Never guess, never hardcode physical paths.",
"common_includes": {
"default.asp -> asp/helpers.asp": "<!--#include file=\"asp/helpers.asp\"-->",
"controllers/x.asp -> models/x.asp": "<!--#include file=\"../models/x.asp\"-->",
"models/x.asp -> db.asp": "<!--#include file=\"../db.asp\"-->"
}
};
prompt["file_structure"] = {
"layout": [
"www/default.asp - front controller / router",
"www/index.asp - includes default.asp",
"www/asp/ - helpers, db.asp, layout.asp, setup.asp",
"www/asp/controllers/ - one file per resource",
"www/asp/models/ - one file per resource",
"www/asp/views/ - subdirectory per resource",
"www/assets/ - CSS, JS, images",
"www/data/ - SQLite database (app.db)",
"www/uploads/ - user-uploaded files"
],
"include_strategy": "Include all models and controllers from default.asp to avoid duplicate function definitions. Individual model and controller files should NOT re-include shared files like db.asp."
};
prompt["common_mistakes_to_avoid"] = [
"Do not redirect index.asp to /",
"Do not write output before Response.Redirect",
"Do not concatenate raw request values directly into SQL, use SqlEscape()",
"Do not use IIf() for nullable SQL values",
"Do not use On Error Resume Next in routing or page setup",
"Do not create tables or run migrations on every request, use a dedicated /setup route",
"Do not mix ASP server-side storage with browser localStorage for the same feature",
"Validate numeric IDs with IsNumeric() and CInt() before using them",
"HTML-encode user output with Html() or Server.HTMLEncode()"
];
prompt["development_workflow"] = [
"1. Copy www_starter into www",
"2. Create the database schema and seed data in asp/setup.asp",
"3. Build models, then controllers, then views",
"4. Wire all includes in default.asp (models first, then controllers)",
"5. Add routes to default.asp using nested If blocks for dynamic segments",
"6. Start server: python -m ASPPY.server 0.0.0.0 5000 www",
"7. Run /setup once to create tables and insert sample data",
"8. Test all routes including edge cases and 404s",
"9. Report any issues encountered during development"
];
prompt["final_checklist"] = [
"App uses MVC model only",
"default.asp only dispatches routes",
"index.asp does not create a redirect loop",
"Extensionless routes work through Request.Path",
"Dynamic routes verified: /resource/1, /resource/1/edit, /resource/1/delete",
"Missing .asp files return real 404",
"All includes use correct relative paths",
"Layout rendering owned by one place only",
"Data files in www/data are read and written from the same location",
"Assets in www/assets, uploads in www/uploads",
"Pages load without ASPPY runtime errors"
];
var json = JSON.stringify(prompt, null, 2);
document.getElementById('output').value = json;
document.getElementById('output-wrap').classList.add('show');
document.getElementById('output').scrollIntoView({ behavior: 'smooth', block: 'start' });
}
function copyOutput(){
var el = document.getElementById('output');
if(!el.value){ generate(); }
if(!el.value) return;
navigator.clipboard.writeText(el.value).then(function(){
var t = document.getElementById('copied-toast');
t.classList.add('show');
setTimeout(function(){ t.classList.remove('show'); }, 1800);
});
}
</script>
</body>
</html>