Free / $5 Community Support)
If you’ve ever said “I wish AI answered like me,” this is the missing piece.
Most people don’t need a complex engineering team, a lab, or months of coding to start shaping how AI responds. What they need is a simple way to:
- write example questions people actually ask,
- write the best answers they want the AI to learn,
- export it in the correct format (JSONL),
- upload it to the training dashboard,
- and start getting responses that match their tone and structure.
That’s exactly why I built JacAI Training Builder.
This is a self-contained tool (web-based, no database required) that helps you create and export training files in the exact JSONL format used for fine-tuning/training workflows. It runs anywhere: your website folder, your laptop, or later as a desktop app.
Why this matters (especially for non-tech people)
I keep seeing the same problem: people want AI but they can’t get past setup complexity.
Even in my own circle, I ran into this with a client I call “Dickie.” He didn’t even know how to access ChatGPT or how to ask the kinds of questions that get useful answers. But once I showed him a tool where he can paste Q&A pairs and press “Download JSONL,” he immediately understood the value.
That’s the whole point: reduce the friction so regular people can build useful AI behavior without having to learn a developer workflow.
What JacAI Training Builder does
At its core, the tool has one job:
Turn your knowledge into a training dataset (JSONL) you can upload to your training dashboard.
You can:
- Add training examples one at a time (User prompt → Assistant answer)
- Add an optional System Prompt that gets attached to every training entry
- Bulk import many examples at once (batch mode)
- Preview the batch import so you can catch formatting mistakes before importing
- Export:
- JSONL (messages format) (recommended)
- JSONL (prompt/completion) (legacy / optional)
- Import existing JSONL back into the editor
- Save everything locally in your browser (no server needed)
Batch import formats supported
You can paste training pairs in either format:
Format A: ===PAIR=== blocks
- Most structured, best for big imports
Format B: Q: / A: pairs
- Fast and simple, great for everyday use
So whether you write like a power user or like a normal person taking notes, it still works.
Who this is for
This tool is built for people who want AI responses that match a specific style, industry, or mission, including:
- legal educators and self-help communities
- coaches and consultants
- small business owners who want a consistent “assistant voice”
- content creators who want AI that matches their brand tone
- social media managers building repeatable content systems
- teams that need standardized answers and scripts
You don’t have to be technical. If you can write a question and write an answer, you can build a dataset.
Free vs $5 community version
I’m releasing the starter build in two ways:
- Free DIY Build
- If you have the time and basic skills, you can build it yourself using the instructions below.
- $5 Community Support Version
- If you don’t want to spend time building, don’t have the tools, or just want to support what we’re building, you can grab the ready-to-run version for $5.
- Same tool, ready to upload and run.
The $5 isn’t about locking people out. It’s about supporting the community and the development path—because the next version (Pro) is going to be serious.
How to install (basic)
This tool is self-contained: it’s just HTML + CSS + JavaScript.
Option 1: Run on your website (cPanel / hosting)
- Create a folder like:
/training-builder/
- Upload the files into that folder
- Visit it in your browser and use it
Option 2: Run locally on your laptop
- Download the folder
- Open
index.htmlin your browser - Start creating training examples immediately
No database. No PHP. No logins. No dependencies.
How to use (basic workflow)
- Write a strong System Prompt (optional, but recommended)
- This defines the “voice” and boundaries of the AI
- Example: “Explain in simple steps, provide checklists, do not claim to be a lawyer, be direct and helpful.”
- Add training examples:
- user prompt = the question people ask
- assistant answer = the best response you want the AI to learn
- Use Batch Import if you have a lot of examples
- Export JSONL (messages format)
- This is the format most training dashboards accept.
- Upload the JSONL in your model training dashboard
- You will see a validation step. If your file is clean, training can begin.
What to include in a strong dataset
A lot of people think “more data” is the goal. It’s not.
Better examples beat more examples.
Good examples include:
- realistic user questions
- structured answers (steps, bullets, scripts)
- consistent tone
- disclaimers if needed (especially legal/medical/financial topics)
- edge cases (confusing situations, missing info, hostile user tone)
If you want AI to behave professionally, write examples like a professional.
Why I’m building this under JacAI
JacAI is the brand because it’s bigger than one tool.
This Training Builder is the “dataset engine.” Later, JacAI becomes a family of simplified AI agents (legal, content, business, automation). The common thread is the same:
We’re focused on the user relationship with AI — how to make powerful AI usable for people who aren’t technical.
For now, I’m launching the legal campaign first because the demand is real, and people need help organizing their cases and understanding what to do next. The Training Builder is one of the core tools behind that mission.
Get it (Free build or $5 support)
- If you want the tool right now and don’t want to build it: grab the $5 community version.
- If you want to build it yourself: follow the instructions on this page and you’re good.
Either way, you’ll be able to train AI to respond more like you—and that’s a major step beyond “generic chatbot answers.”
Next up: I’m building the Pro version (not public) with advanced features that make training cleaner, faster, and more valuable for businesses.
Pro version plan (private, not shown publicly)
The Pro build is where we add:
- multiple projects/datasets (Legal, SMM, etc.)
- dataset quality checks (duplicates, missing sections, too-short answers)
- PII warnings + one-click redaction
- tag/category labeling
- train/validation split export
- version snapshots (v1/v2/v3)
- dataset stats (counts, lengths, warnings)
If you’re serious about training AI the right way, that’s where it gets powerful.
Pro version scope (what I recommend you charge)
Two-tier lineup (your plan):
- Starter: Free / $5 community support
- Pro: Paid
For Pro pricing that matches “real value,” I’d position it like:
- $49 (intro)
- or $79–$99 (once you bundle the help pages + upgrade polish + project manager + quality checks)
If you later wrap it as an Electron desktop installer, the perceived value jumps.
Build Instructions
self-contained Training Builder that runs anywhere (web folder, WP plugin wrapper, Electron later) and only outputs JSONL for the user to upload into their own model dashboard, you can do it in 3 files:
index.htmlapp.jsstyle.css
No database, no server required. It stores drafts in localStorage, supports bulk paste, and exports NDJSON/JSONL in the fine-tune “messages” format.
File 1 — index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>JacAI Training Builder (Offline JSONL Export)</title>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<header class="topbar">
<div class="brand">
<div class="logo">J</div>
<div>
<div class="title">JacAI Training Builder</div>
<div class="subtitle">Offline tool → builds JSONL / NDJSON for fine-tuning</div>
</div>
</div>
<div class="top-actions">
<button id="btnExport" class="btn primary">Download JSONL</button>
<button id="btnExportRaw" class="btn">Download “prompt/completion” JSONL</button>
<button id="btnReset" class="btn danger">Reset</button>
</div>
</header>
<main class="wrap">
<section class="grid">
<div class="card">
<h2>System Prompt</h2>
<p class="muted">This is prepended to every example as the first message.</p>
<textarea id="systemPrompt" class="ta" rows="6" placeholder="Example: You are JacAI Legal Coach. You write clear, structured, educational answers. You do not claim to be an attorney. You give procedural guidance, checklists, and drafting help."></textarea>
<div class="row">
<label class="check">
<input type="checkbox" id="includeSystemPrompt" checked />
Include system prompt in export
</label>
<label class="check">
<input type="checkbox" id="prettyJsonl" />
Pretty-print JSON (larger files)
</label>
</div>
</div>
<div class="card">
<h2>Add Example</h2>
<div class="field">
<label>User prompt / fact pattern</label>
<textarea id="userPrompt" class="ta" rows="5" placeholder="Example: I received a notice of hearing for child support. What should I bring and how should I speak to the judge?"></textarea>
</div>
<div class="field">
<label>Assistant answer</label>
<textarea id="assistantAnswer" class="ta" rows="6" placeholder="Example: Here’s a structured checklist: 1) documents to bring, 2) what the hearing decides, 3) objections, 4) script, 5) next steps."></textarea>
</div>
<div class="row">
<button id="btnAdd" class="btn primary">Add</button>
<button id="btnUpdate" class="btn" disabled>Update Selected</button>
<button id="btnClearForm" class="btn">Clear</button>
</div>
<div class="hint">
Tip: You can also use <strong>Bulk Import</strong> (below) to paste multiple Q&A pairs at once.
</div>
</div>
<div class="card">
<h2>Bulk Import</h2>
<p class="muted">
Paste multiple examples. Format options:
<br><code>Q: ...</code> then <code>A: ...</code> (repeat), or
<br>separate blocks with <code>---</code>.
</p>
<textarea id="bulkInput" class="ta" rows="10" placeholder="Q: Question 1
A: Answer 1
---
Q: Question 2
A: Answer 2"></textarea>
<div class="row">
<button id="btnBulkParse" class="btn primary">Import Pairs</button>
<button id="btnBulkClear" class="btn">Clear Bulk</button>
</div>
</div>
<div class="card">
<h2>Import JSONL</h2>
<p class="muted">
Load an existing JSONL file back into the editor (messages format).
</p>
<input id="fileImport" type="file" accept=".jsonl,.ndjson,.txt,application/json" />
<div class="row">
<button id="btnImport" class="btn primary">Import File</button>
<button id="btnExportBackup" class="btn">Download Backup (.json)</button>
</div>
</div>
</section>
<section class="card table-card">
<div class="table-head">
<h2>Training Examples</h2>
<div class="table-actions">
<input id="search" class="search" placeholder="Search…" />
<button id="btnDeleteSelected" class="btn danger" disabled>Delete Selected</button>
</div>
</div>
<div id="msg" class="msg" hidden></div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th style="width:38px;"></th>
<th style="width:60px;">ID</th>
<th>User Prompt</th>
<th>Assistant Answer</th>
<th style="width:140px;">Actions</th>
</tr>
</thead>
<tbody id="rows"></tbody>
</table>
</div>
<div class="footer-note muted">
Stored locally in your browser (localStorage). No server required.
</div>
</section>
</main>
<footer class="foot">
<div class="muted">
Output formats:
<strong>messages JSONL</strong> (recommended) and optional <strong>prompt/completion JSONL</strong>.
</div>
</footer>
<script src="./app.js"></script>
</body>
</html>
File 2 — style.css
:root{
--bg:#020617;
--card:#0b1224;
--card2:#0a1020;
--border:#1f2a44;
--text:#e5e7eb;
--muted:#9aa4b2;
--accent:#38bdf8;
--good:#22c55e;
--warn:#f97316;
--bad:#ef4444;
}
*{box-sizing:border-box}
html,body{height:100%}
body{
margin:0;
font-family:system-ui,-apple-system,Segoe UI,Roboto,Arial,sans-serif;
background:radial-gradient(900px 400px at 10% 0%, rgba(56,189,248,.18), transparent 60%),
radial-gradient(900px 400px at 90% 0%, rgba(34,197,94,.14), transparent 60%),
var(--bg);
color:var(--text);
}
.topbar{
position:sticky;
top:0;
z-index:10;
background:rgba(2,6,23,.86);
backdrop-filter: blur(10px);
border-bottom:1px solid rgba(31,42,68,.65);
display:flex;
align-items:center;
justify-content:space-between;
padding:12px 16px;
gap:12px;
}
.brand{display:flex;align-items:center;gap:12px}
.logo{
width:38px;height:38px;border-radius:12px;
display:grid;place-items:center;
border:1px solid rgba(56,189,248,.45);
background:linear-gradient(135deg, rgba(56,189,248,.18), rgba(34,197,94,.12));
font-weight:900;
}
.title{font-weight:800;letter-spacing:.02em}
.subtitle{font-size:.85rem;color:var(--muted)}
.top-actions{display:flex;gap:8px;flex-wrap:wrap;justify-content:flex-end}
.wrap{max-width:1180px;margin:18px auto;padding:0 14px 28px}
.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}
@media (max-width: 980px){.grid{grid-template-columns:1fr}}
.card{
background:linear-gradient(180deg, rgba(11,18,36,.92), rgba(10,16,32,.92));
border:1px solid rgba(31,42,68,.75);
border-radius:16px;
padding:14px 14px;
box-shadow:0 18px 45px rgba(0,0,0,.45);
}
.card h2{margin:0 0 8px;font-size:1.05rem}
.muted{color:var(--muted);font-size:.92rem}
.field{margin-top:10px}
.field label{display:block;margin-bottom:6px;color:var(--muted);font-size:.86rem}
.ta{
width:100%;
padding:10px 10px;
border-radius:12px;
border:1px solid rgba(31,42,68,.9);
background:rgba(2,6,23,.65);
color:var(--text);
resize:vertical;
outline:none;
}
.ta:focus{border-color:rgba(56,189,248,.75); box-shadow:0 0 0 2px rgba(56,189,248,.12)}
.row{display:flex;gap:10px;align-items:center;margin-top:10px;flex-wrap:wrap}
.check{display:flex;gap:8px;align-items:center;color:var(--muted);font-size:.9rem}
.btn{
border-radius:999px;
border:1px solid rgba(56,189,248,.5);
background:rgba(15,23,42,.8);
color:var(--text);
padding:9px 14px;
cursor:pointer;
font-weight:700;
font-size:.9rem;
}
.btn:hover{border-color:rgba(56,189,248,.85)}
.btn:disabled{opacity:.5;cursor:not-allowed}
.primary{background:rgba(34,197,94,.9);border-color:rgba(34,197,94,.9);color:#052e1a}
.danger{background:rgba(239,68,68,.14);border-color:rgba(239,68,68,.65);color:#fecaca}
.hint{margin-top:10px;color:var(--muted);font-size:.9rem}
.table-card{margin-top:14px}
.table-head{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap}
.table-actions{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.search{
padding:9px 12px;border-radius:999px;
border:1px solid rgba(31,42,68,.9);
background:rgba(2,6,23,.65);
color:var(--text);
outline:none;
}
.search:focus{border-color:rgba(56,189,248,.75); box-shadow:0 0 0 2px rgba(56,189,248,.12)}
.table-wrap{overflow:auto;border-radius:12px;border:1px solid rgba(31,42,68,.7);margin-top:10px}
table{width:100%;border-collapse:collapse;min-width:880px}
th,td{padding:10px 10px;border-bottom:1px solid rgba(31,42,68,.55);vertical-align:top}
th{color:var(--muted);font-weight:800;font-size:.82rem;text-transform:uppercase;letter-spacing:.06em;background:rgba(2,6,23,.4)}
tbody tr:nth-child(even){background:rgba(2,6,23,.22)}
.cell-pre{white-space:pre-wrap}
.actions{display:flex;gap:8px;flex-wrap:wrap}
.smallbtn{
padding:7px 10px;border-radius:999px;
border:1px solid rgba(31,42,68,.9);
background:rgba(2,6,23,.55);
color:var(--text);
cursor:pointer;
font-weight:700;
font-size:.82rem;
}
.smallbtn:hover{border-color:rgba(56,189,248,.65)}
.msg{
margin-top:10px;
padding:10px 12px;
border-radius:12px;
border:1px solid rgba(31,42,68,.9);
background:rgba(2,6,23,.55);
font-size:.92rem;
}
.foot{
border-top:1px solid rgba(31,42,68,.65);
padding:14px 16px;
text-align:center;
background:rgba(2,6,23,.62);
}
.footer-note{margin-top:10px}
File 3 — app.js
(function () {
const LS_KEY = "jacai_training_builder_v1";
const $ = (id) => document.getElementById(id);
const state = {
systemPrompt: "",
includeSystemPrompt: true,
prettyJsonl: false,
items: [], // {id, user, assistant}
selectedId: null,
selectedRowIds: new Set(),
nextId: 1,
};
function nowStamp() {
const d = new Date();
const pad = (n) => String(n).padStart(2, "0");
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
}
function showMsg(text) {
const el = $("msg");
el.hidden = false;
el.textContent = text;
clearTimeout(showMsg._t);
showMsg._t = setTimeout(() => (el.hidden = true), 3500);
}
function save() {
const payload = {
...state,
selectedId: null,
selectedRowIds: Array.from(state.selectedRowIds),
};
localStorage.setItem(LS_KEY, JSON.stringify(payload));
}
function load() {
const raw = localStorage.getItem(LS_KEY);
if (!raw) return;
try {
const data = JSON.parse(raw);
state.systemPrompt = data.systemPrompt || "";
state.includeSystemPrompt = data.includeSystemPrompt !== false;
state.prettyJsonl = !!data.prettyJsonl;
state.items = Array.isArray(data.items) ? data.items : [];
state.nextId = data.nextId || (state.items.length ? Math.max(...state.items.map(i => i.id)) + 1 : 1);
state.selectedRowIds = new Set(Array.isArray(data.selectedRowIds) ? data.selectedRowIds : []);
} catch {
// ignore
}
}
function resetAll() {
if (!confirm("Reset everything? This clears local data.")) return;
localStorage.removeItem(LS_KEY);
location.reload();
}
function setSelected(id) {
state.selectedId = id;
const item = state.items.find((x) => x.id === id);
if (!item) return;
$("userPrompt").value = item.user;
$("assistantAnswer").value = item.assistant;
$("btnUpdate").disabled = false;
$("btnAdd").disabled = true;
}
function clearForm() {
state.selectedId = null;
$("userPrompt").value = "";
$("assistantAnswer").value = "";
$("btnUpdate").disabled = true;
$("btnAdd").disabled = false;
}
function addItem(user, assistant) {
const trimmedU = (user || "").trim();
const trimmedA = (assistant || "").trim();
if (!trimmedU || !trimmedA) return false;
state.items.unshift({
id: state.nextId++,
user: trimmedU,
assistant: trimmedA,
});
return true;
}
function updateSelected(user, assistant) {
const id = state.selectedId;
if (!id) return false;
const item = state.items.find((x) => x.id === id);
if (!item) return false;
const trimmedU = (user || "").trim();
const trimmedA = (assistant || "").trim();
if (!trimmedU || !trimmedA) return false;
item.user = trimmedU;
item.assistant = trimmedA;
return true;
}
function deleteOne(id) {
state.items = state.items.filter((x) => x.id !== id);
state.selectedRowIds.delete(id);
if (state.selectedId === id) clearForm();
}
function deleteSelectedRows() {
if (state.selectedRowIds.size === 0) return;
if (!confirm(`Delete ${state.selectedRowIds.size} selected example(s)?`)) return;
const ids = new Set(state.selectedRowIds);
state.items = state.items.filter((x) => !ids.has(x.id));
state.selectedRowIds.clear();
clearForm();
render();
save();
showMsg("Deleted selected items.");
}
function toggleRowSelected(id, checked) {
if (checked) state.selectedRowIds.add(id);
else state.selectedRowIds.delete(id);
$("btnDeleteSelected").disabled = state.selectedRowIds.size === 0;
save();
}
function downloadText(filename, text, mime = "text/plain;charset=utf-8") {
const blob = new Blob([text], { type: mime });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
a.remove();
setTimeout(() => URL.revokeObjectURL(url), 250);
}
function buildMessagesJSONL() {
const sys = $("systemPrompt").value || "";
const includeSys = $("includeSystemPrompt").checked;
const pretty = $("prettyJsonl").checked;
const rows = state.items
.slice()
.reverse()
.map((ex) => {
const messages = [];
if (includeSys && sys.trim()) {
messages.push({ role: "system", content: sys.trim() });
}
messages.push({ role: "user", content: ex.user });
messages.push({ role: "assistant", content: ex.assistant });
return { messages };
});
// NDJSON/JSONL: one JSON per line
return rows
.map((obj) => JSON.stringify(obj, null, pretty ? 2 : 0))
.join("\n") + "\n";
}
function buildPromptCompletionJSONL() {
// Some platforms/older flows accept prompt/completion JSONL
const pretty = $("prettyJsonl").checked;
const rows = state.items
.slice()
.reverse()
.map((ex) => ({
prompt: ex.user,
completion: ex.assistant,
}));
return rows
.map((obj) => JSON.stringify(obj, null, pretty ? 2 : 0))
.join("\n") + "\n";
}
function parseBulk(text) {
const input = (text || "").trim();
if (!input) return [];
// Split by --- blocks first
const blocks = input.split(/\n-{3,}\n/g).map((b) => b.trim()).filter(Boolean);
const pairs = [];
for (const block of blocks) {
// Try Q:/A: format
const qMatch = block.match(/(^|\n)\s*Q:\s*([\s\S]*?)(\n\s*A:\s*|$)/i);
const aMatch = block.match(/(^|\n)\s*A:\s*([\s\S]*)$/i);
if (qMatch && aMatch) {
const q = (qMatch[2] || "").trim();
const a = (aMatch[2] || "").trim();
if (q && a) pairs.push({ user: q, assistant: a });
continue;
}
// Fallback: first line question, rest answer
const lines = block.split("\n");
if (lines.length >= 2) {
const q = lines[0].trim();
const a = lines.slice(1).join("\n").trim();
if (q && a) pairs.push({ user: q, assistant: a });
}
}
return pairs;
}
async function importJSONLFile(file) {
const text = await file.text();
const lines = text.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
const imported = [];
let inferredSys = null;
for (const line of lines) {
let obj;
try {
obj = JSON.parse(line);
} catch {
continue;
}
// Accept: {messages:[{role,content},...]}
if (obj && Array.isArray(obj.messages)) {
const msgs = obj.messages;
const userMsg = msgs.find(m => m.role === "user");
const asstMsg = msgs.find(m => m.role === "assistant");
const sysMsg = msgs.find(m => m.role === "system");
if (!inferredSys && sysMsg && typeof sysMsg.content === "string") {
inferredSys = sysMsg.content;
}
if (userMsg?.content && asstMsg?.content) {
imported.push({ user: String(userMsg.content), assistant: String(asstMsg.content) });
}
}
// Accept: {prompt, completion}
if (typeof obj?.prompt === "string" && typeof obj?.completion === "string") {
imported.push({ user: obj.prompt, assistant: obj.completion });
}
}
if (inferredSys && !$("systemPrompt").value.trim()) {
$("systemPrompt").value = inferredSys;
showMsg("Imported system prompt from file.");
}
let added = 0;
for (const p of imported) {
if (addItem(p.user, p.assistant)) added++;
}
save();
render();
showMsg(`Imported ${added} example(s).`);
}
function exportBackupJSON() {
const payload = {
systemPrompt: $("systemPrompt").value || "",
includeSystemPrompt: $("includeSystemPrompt").checked,
prettyJsonl: $("prettyJsonl").checked,
items: state.items,
nextId: state.nextId,
exportedAt: new Date().toISOString(),
};
downloadText(`jacai_training_backup_${nowStamp()}.json`, JSON.stringify(payload, null, 2), "application/json;charset=utf-8");
}
function render() {
// sync state from UI
state.systemPrompt = $("systemPrompt").value;
state.includeSystemPrompt = $("includeSystemPrompt").checked;
state.prettyJsonl = $("prettyJsonl").checked;
const q = ($("search").value || "").toLowerCase().trim();
const rows = $("rows");
rows.innerHTML = "";
const filtered = state.items.filter((it) => {
if (!q) return true;
return (it.user + "\n" + it.assistant).toLowerCase().includes(q);
});
for (const it of filtered) {
const tr = document.createElement("tr");
const tdSel = document.createElement("td");
const cb = document.createElement("input");
cb.type = "checkbox";
cb.checked = state.selectedRowIds.has(it.id);
cb.addEventListener("change", () => toggleRowSelected(it.id, cb.checked));
tdSel.appendChild(cb);
const tdId = document.createElement("td");
tdId.textContent = String(it.id);
const tdU = document.createElement("td");
tdU.className = "cell-pre";
tdU.textContent = it.user;
const tdA = document.createElement("td");
tdA.className = "cell-pre";
tdA.textContent = it.assistant;
const tdAct = document.createElement("td");
const act = document.createElement("div");
act.className = "actions";
const bEdit = document.createElement("button");
bEdit.className = "smallbtn";
bEdit.textContent = "Edit";
bEdit.addEventListener("click", () => setSelected(it.id));
const bDel = document.createElement("button");
bDel.className = "smallbtn";
bDel.textContent = "Delete";
bDel.addEventListener("click", () => {
if (!confirm("Delete this example?")) return;
deleteOne(it.id);
save();
render();
showMsg("Deleted.");
});
act.appendChild(bEdit);
act.appendChild(bDel);
tdAct.appendChild(act);
tr.appendChild(tdSel);
tr.appendChild(tdId);
tr.appendChild(tdU);
tr.appendChild(tdA);
tr.appendChild(tdAct);
rows.appendChild(tr);
}
$("btnDeleteSelected").disabled = state.selectedRowIds.size === 0;
}
function bind() {
$("btnAdd").addEventListener("click", () => {
const ok = addItem($("userPrompt").value, $("assistantAnswer").value);
if (!ok) return showMsg("Both fields are required.");
clearForm();
save();
render();
showMsg("Added.");
});
$("btnUpdate").addEventListener("click", () => {
const ok = updateSelected($("userPrompt").value, $("assistantAnswer").value);
if (!ok) return showMsg("Select an item and complete both fields.");
clearForm();
save();
render();
showMsg("Updated.");
});
$("btnClearForm").addEventListener("click", () => clearForm());
$("btnBulkParse").addEventListener("click", () => {
const pairs = parseBulk($("bulkInput").value);
if (!pairs.length) return showMsg("No pairs detected.");
let added = 0;
for (const p of pairs) if (addItem(p.user, p.assistant)) added++;
save();
render();
showMsg(`Imported ${added} example(s).`);
});
$("btnBulkClear").addEventListener("click", () => ($("bulkInput").value = ""));
$("btnExport").addEventListener("click", () => {
if (!state.items.length) return showMsg("No examples to export.");
const out = buildMessagesJSONL();
downloadText(`jacai_train_messages_${nowStamp()}.jsonl`, out, "application/x-ndjson;charset=utf-8");
showMsg("Downloaded JSONL (messages).");
});
$("btnExportRaw").addEventListener("click", () => {
if (!state.items.length) return showMsg("No examples to export.");
const out = buildPromptCompletionJSONL();
downloadText(`jacai_train_prompt_completion_${nowStamp()}.jsonl`, out, "application/x-ndjson;charset=utf-8");
showMsg("Downloaded JSONL (prompt/completion).");
});
$("btnExportBackup").addEventListener("click", () => exportBackupJSON());
$("btnReset").addEventListener("click", () => resetAll());
$("btnDeleteSelected").addEventListener("click", () => deleteSelectedRows());
$("search").addEventListener("input", () => render());
// auto-save system prompt & toggles
["systemPrompt", "includeSystemPrompt", "prettyJsonl"].forEach((id) => {
$(id).addEventListener("input", () => {
save();
render();
});
$(id).addEventListener("change", () => {
save();
render();
});
});
$("btnImport").addEventListener("click", async () => {
const file = $("fileImport").files?.[0];
if (!file) return showMsg("Choose a file first.");
await importJSONLFile(file);
save();
});
}
// init
load();
$("systemPrompt").value = state.systemPrompt || "";
$("includeSystemPrompt").checked = state.includeSystemPrompt !== false;
$("prettyJsonl").checked = !!state.prettyJsonl;
$("btnUpdate").disabled = true;
bind();
render();
save();
})();
How to use
- Put these 3 files into a folder, e.g.
/training-builder/ - Open
index.htmlin a browser - Add Q/A pairs
- Click Download JSONL
- Upload that JSONL into your model provider dashboard
No dependencies.
explainer video
Train Your Own AI in 20 Minutes: The JacAI Training Builder
Support and buy now 5$ The video below shows an app I built with ChatGpt and is now a Upstart business. This video will show you how I use the Tool to train my JacAI legal A.I. Coach.
Leave a Reply