Spaces:
Sleeping
Sleeping
Mohammed AL Sarraj commited on
Commit Β·
e85a23d
1
Parent(s): 8806ae7
feat: add See Example sample outputs to all tools
Browse files- app/tools/changelog_ai/templates/changelog_ai/index.html +58 -0
- app/tools/doc_forge/templates/doc_forge/index.html +4 -0
- app/tools/git_narrator/templates/git_narrator/index.html +90 -0
- app/tools/schema_detective/templates/schema_detective/index.html +52 -0
- app/tools/sql_whisperer/templates/sql_whisperer/index.html +43 -0
- app/tools/test_forge/templates/test_forge/index.html +126 -0
- docforge.db +0 -0
- docforge.db-shm +0 -0
- docforge.db-wal +0 -0
app/tools/changelog_ai/templates/changelog_ai/index.html
CHANGED
|
@@ -156,6 +156,7 @@ refactor: clean up legacy charting utility"></textarea>
|
|
| 156 |
<span class="material-symbols-outlined text-5xl text-outline mb-4">auto_awesome</span>
|
| 157 |
<p class="text-sm font-medium">Paste your commits and click Generate</p>
|
| 158 |
<p class="text-xs text-outline mt-1">Set version, audience, and language, then generate</p>
|
|
|
|
| 159 |
</div>
|
| 160 |
|
| 161 |
<div id="loading-state" class="hidden flex-1 flex flex-col items-center justify-center gap-4">
|
|
@@ -715,6 +716,63 @@ refactor: clean up legacy charting utility"></textarea>
|
|
| 715 |
window.loadDemo = loadDemo;
|
| 716 |
|
| 717 |
document.getElementById('btn-try-demo').addEventListener('click', loadDemo);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 718 |
})();
|
| 719 |
</script>
|
| 720 |
{% endblock %}
|
|
|
|
| 156 |
<span class="material-symbols-outlined text-5xl text-outline mb-4">auto_awesome</span>
|
| 157 |
<p class="text-sm font-medium">Paste your commits and click Generate</p>
|
| 158 |
<p class="text-xs text-outline mt-1">Set version, audience, and language, then generate</p>
|
| 159 |
+
<button onclick="window._showChangelogExample()" class="mt-4" style="background:none;border:1px solid #00d97e;color:#00d97e;font-size:12px;padding:3px 10px;border-radius:6px;cursor:pointer;">See example ↗</button>
|
| 160 |
</div>
|
| 161 |
|
| 162 |
<div id="loading-state" class="hidden flex-1 flex flex-col items-center justify-center gap-4">
|
|
|
|
| 716 |
window.loadDemo = loadDemo;
|
| 717 |
|
| 718 |
document.getElementById('btn-try-demo').addEventListener('click', loadDemo);
|
| 719 |
+
|
| 720 |
+
// ββ Example feature ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 721 |
+
window._showChangelogExample = function() {
|
| 722 |
+
const exampleData = {
|
| 723 |
+
sections: [
|
| 724 |
+
{
|
| 725 |
+
type: 'features',
|
| 726 |
+
items: [
|
| 727 |
+
{ title: 'AI-powered auto-complete for search', description: 'Search bar now suggests results in real-time using a fine-tuned language model, reducing average search time by 60%.' },
|
| 728 |
+
{ title: 'Bulk export to CSV and Excel', description: 'Users can now export filtered datasets in both CSV and XLSX formats directly from any table view.' },
|
| 729 |
+
{ title: 'Dark mode toggle in user settings', description: 'A system-aware dark mode is now available under Settings > Appearance, with automatic switching based on OS preference.' }
|
| 730 |
+
]
|
| 731 |
+
},
|
| 732 |
+
{
|
| 733 |
+
type: 'fixes',
|
| 734 |
+
items: [
|
| 735 |
+
{ title: 'Dashboard charts not loading on Safari', description: 'Resolved a WebKit rendering issue that caused chart canvases to remain blank on Safari 17+.' },
|
| 736 |
+
{ title: 'Email notifications sent twice on password reset', description: 'Fixed a duplicate event trigger in the notification queue that caused two emails per reset request.' }
|
| 737 |
+
]
|
| 738 |
+
},
|
| 739 |
+
{
|
| 740 |
+
type: 'improvements',
|
| 741 |
+
items: [
|
| 742 |
+
{ title: 'Page load time reduced by 40%', description: 'Implemented route-level code splitting and lazy loading for all dashboard modules, bringing initial load under 1.2 seconds.' }
|
| 743 |
+
]
|
| 744 |
+
}
|
| 745 |
+
]
|
| 746 |
+
};
|
| 747 |
+
|
| 748 |
+
// Set version and date for the example
|
| 749 |
+
versionInput.value = 'v2.4.0';
|
| 750 |
+
dateInput.value = '2026-04-10';
|
| 751 |
+
lastResult = exampleData;
|
| 752 |
+
|
| 753 |
+
renderChangelog(exampleData);
|
| 754 |
+
showState('results');
|
| 755 |
+
btnDownloadPdf.classList.remove('hidden');
|
| 756 |
+
|
| 757 |
+
// Add clear example button
|
| 758 |
+
if (!document.getElementById('clear-example-btn')) {
|
| 759 |
+
const clearBtn = document.createElement('button');
|
| 760 |
+
clearBtn.id = 'clear-example-btn';
|
| 761 |
+
clearBtn.textContent = 'Clear example';
|
| 762 |
+
clearBtn.style.cssText = 'background:none;border:1px solid #94a3b8;color:#94a3b8;font-size:11px;padding:2px 8px;border-radius:5px;cursor:pointer;margin-top:8px;align-self:center;';
|
| 763 |
+
clearBtn.onclick = function() {
|
| 764 |
+
lastResult = null;
|
| 765 |
+
versionInput.value = '';
|
| 766 |
+
dateInput.valueAsDate = new Date();
|
| 767 |
+
showState('empty');
|
| 768 |
+
btnDownloadPdf.classList.add('hidden');
|
| 769 |
+
this.remove();
|
| 770 |
+
};
|
| 771 |
+
resultsState.appendChild(clearBtn);
|
| 772 |
+
}
|
| 773 |
+
};
|
| 774 |
+
|
| 775 |
+
document.getElementById('btn-try-demo').addEventListener('click', loadDemo);
|
| 776 |
})();
|
| 777 |
</script>
|
| 778 |
{% endblock %}
|
app/tools/doc_forge/templates/doc_forge/index.html
CHANGED
|
@@ -55,6 +55,10 @@
|
|
| 55 |
<div class="flex items-center gap-2"><span class="material-symbols-outlined text-sm">check_circle</span> Instant Results</div>
|
| 56 |
<div class="flex items-center gap-2"><span class="material-symbols-outlined text-sm">check_circle</span> Free & Open Source</div>
|
| 57 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
{# ββ Repo Preview Card (hidden until analyze) ββββββββββββββββββββββββββββββββ #}
|
| 60 |
<div id="repo-card" class="hidden w-full max-w-2xl mt-8 bg-surface-container-lowest rounded-xl p-6 text-left shadow-sm">
|
|
|
|
| 55 |
<div class="flex items-center gap-2"><span class="material-symbols-outlined text-sm">check_circle</span> Instant Results</div>
|
| 56 |
<div class="flex items-center gap-2"><span class="material-symbols-outlined text-sm">check_circle</span> Free & Open Source</div>
|
| 57 |
</div>
|
| 58 |
+
<a href="https://github.com/expressjs/express" target="_blank"
|
| 59 |
+
class="mt-4 inline-flex items-center gap-1" style="color:#00d97e;font-size:12px;text-decoration:none;border:1px solid #00d97e;padding:3px 10px;border-radius:6px;">
|
| 60 |
+
See example: expressjs/express ↗
|
| 61 |
+
</a>
|
| 62 |
|
| 63 |
{# ββ Repo Preview Card (hidden until analyze) ββββββββββββββββββββββββββββββββ #}
|
| 64 |
<div id="repo-card" class="hidden w-full max-w-2xl mt-8 bg-surface-container-lowest rounded-xl p-6 text-left shadow-sm">
|
app/tools/git_narrator/templates/git_narrator/index.html
CHANGED
|
@@ -120,6 +120,7 @@
|
|
| 120 |
<p class="text-xs text-gd-text-2 max-w-xs leading-relaxed">
|
| 121 |
Paste a GitHub URL or git log on the left. The AI writes your commit history as an editorial engineering report.
|
| 122 |
</p>
|
|
|
|
| 123 |
<div class="mt-5 flex flex-wrap justify-center gap-1.5">
|
| 124 |
{% for tag in ['Highlights', 'Tech Debt', 'Milestones', 'Contributors', 'Impact'] %}
|
| 125 |
<span class="pill" style="background:#1C2128; color:#484F58; border:1px solid rgba(240,246,252,0.06); font-size:9px;">{{ tag }}</span>
|
|
@@ -505,5 +506,94 @@ function buildCopyText(data) {
|
|
| 505 |
document.addEventListener('keydown', e => {
|
| 506 |
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') doNarrate();
|
| 507 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 508 |
</script>
|
| 509 |
{% endblock %}
|
|
|
|
| 120 |
<p class="text-xs text-gd-text-2 max-w-xs leading-relaxed">
|
| 121 |
Paste a GitHub URL or git log on the left. The AI writes your commit history as an editorial engineering report.
|
| 122 |
</p>
|
| 123 |
+
<button onclick="showNarratorExample()" class="mt-4" style="background:none;border:1px solid #00d97e;color:#00d97e;font-size:12px;padding:3px 10px;border-radius:6px;cursor:pointer;">See example ↗</button>
|
| 124 |
<div class="mt-5 flex flex-wrap justify-center gap-1.5">
|
| 125 |
{% for tag in ['Highlights', 'Tech Debt', 'Milestones', 'Contributors', 'Impact'] %}
|
| 126 |
<span class="pill" style="background:#1C2128; color:#484F58; border:1px solid rgba(240,246,252,0.06); font-size:9px;">{{ tag }}</span>
|
|
|
|
| 506 |
document.addEventListener('keydown', e => {
|
| 507 |
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') doNarrate();
|
| 508 |
});
|
| 509 |
+
|
| 510 |
+
function showNarratorExample() {
|
| 511 |
+
const exampleData = {
|
| 512 |
+
period_label: 'Mar 18 -- Apr 12, 2026',
|
| 513 |
+
summary_stats: {
|
| 514 |
+
total_commits: 47,
|
| 515 |
+
contributors: 4,
|
| 516 |
+
features: 8,
|
| 517 |
+
fixes: 12
|
| 518 |
+
},
|
| 519 |
+
commits: [
|
| 520 |
+
{ hash: 'a3f1b2c', message: 'feat: migrate payment gateway to Stripe V3 API', time: '2 days ago', author: 'sarah' },
|
| 521 |
+
{ hash: 'd4e5f6a', message: 'fix: resolve race condition in concurrent checkout', time: '3 days ago', author: 'david' },
|
| 522 |
+
{ hash: '7b8c9d0', message: 'refactor: extract payment validation into middleware', time: '4 days ago', author: 'sarah' },
|
| 523 |
+
{ hash: 'e1f2a3b', message: 'feat: add webhook handlers for payment events', time: '5 days ago', author: 'carlos' },
|
| 524 |
+
{ hash: 'c5d6e7f', message: 'fix: handle expired card tokens gracefully', time: '6 days ago', author: 'david' },
|
| 525 |
+
{ hash: 'f8a9b0c', message: 'test: add integration tests for refund flow', time: '1 week ago', author: 'mei' },
|
| 526 |
+
{ hash: '2d3e4f5', message: 'feat: implement partial refund support', time: '1 week ago', author: 'carlos' },
|
| 527 |
+
{ hash: '6a7b8c9', message: 'fix: correct currency conversion rounding errors', time: '2 weeks ago', author: 'mei' }
|
| 528 |
+
],
|
| 529 |
+
highlights: [
|
| 530 |
+
{
|
| 531 |
+
title: 'Payment Gateway Migration to Stripe V3',
|
| 532 |
+
narrative: 'The team executed a complete migration from the legacy payment processor to Stripe V3 API. This was the highest-risk change of the quarter, touching 23 files across the checkout, billing, and webhook subsystems. Sarah led the architecture while David handled the concurrency edge cases that surfaced during load testing.',
|
| 533 |
+
impact: 'Payment processing latency reduced by 340ms (avg), failure rate dropped from 2.1% to 0.3%',
|
| 534 |
+
key_commit: 'a3f1b2c feat: migrate payment gateway to Stripe V3 API\nd4e5f6a fix: resolve race condition in concurrent checkout\n7b8c9d0 refactor: extract payment validation into middleware'
|
| 535 |
+
},
|
| 536 |
+
{
|
| 537 |
+
title: 'Partial Refund Support',
|
| 538 |
+
narrative: 'A long-requested feature by the support team -- customers can now receive partial refunds without cancelling the entire order. Carlos implemented the core logic while Mei built out the integration test suite covering 15 edge cases including multi-currency scenarios.',
|
| 539 |
+
impact: 'Support ticket volume for refund issues projected to decrease by 40%'
|
| 540 |
+
}
|
| 541 |
+
],
|
| 542 |
+
tech_debt: [
|
| 543 |
+
{
|
| 544 |
+
icon: 'cleaning_services',
|
| 545 |
+
title: 'Legacy Payment Module Removed',
|
| 546 |
+
description: 'The old PaymentProcessorV1 class and its 1,200 lines of adapter code were safely removed after the Stripe V3 migration was verified in production for 2 weeks.'
|
| 547 |
+
},
|
| 548 |
+
{
|
| 549 |
+
icon: 'build',
|
| 550 |
+
title: 'Validation Middleware Extracted',
|
| 551 |
+
description: 'Payment validation logic that was duplicated across 4 route handlers has been consolidated into a single middleware, reducing code duplication by 60%.'
|
| 552 |
+
}
|
| 553 |
+
],
|
| 554 |
+
milestones: [
|
| 555 |
+
{
|
| 556 |
+
status: 'shipped',
|
| 557 |
+
title: 'Stripe V3 Payment Integration',
|
| 558 |
+
narrative: 'Full migration from legacy processor to Stripe V3, including webhook event handling, idempotent retries, and automatic currency conversion. Deployed to production on April 5.',
|
| 559 |
+
contributors: ['sarah', 'david']
|
| 560 |
+
},
|
| 561 |
+
{
|
| 562 |
+
status: 'shipped',
|
| 563 |
+
title: 'Partial Refund System',
|
| 564 |
+
narrative: 'End-to-end partial refund capability with audit trail, multi-currency support, and automated accounting entries.',
|
| 565 |
+
contributors: ['carlos', 'mei']
|
| 566 |
+
},
|
| 567 |
+
{
|
| 568 |
+
status: 'in-progress',
|
| 569 |
+
title: 'PCI DSS Compliance Audit Prep',
|
| 570 |
+
narrative: 'Preparing documentation and code artifacts for the upcoming annual PCI compliance review. Tokenization patterns have been validated, logging has been scrubbed of sensitive data.',
|
| 571 |
+
contributors: ['sarah', 'mei']
|
| 572 |
+
}
|
| 573 |
+
]
|
| 574 |
+
};
|
| 575 |
+
|
| 576 |
+
renderNarrative(exampleData);
|
| 577 |
+
setStatus('Example loaded');
|
| 578 |
+
|
| 579 |
+
// Add clear example button
|
| 580 |
+
if (!document.getElementById('clear-example-btn')) {
|
| 581 |
+
const clearBtn = document.createElement('button');
|
| 582 |
+
clearBtn.id = 'clear-example-btn';
|
| 583 |
+
clearBtn.textContent = 'Clear example';
|
| 584 |
+
clearBtn.style.cssText = 'background:none;border:1px solid #484F58;color:#484F58;font-size:11px;padding:2px 8px;border-radius:5px;cursor:pointer;margin-top:12px;align-self:center;display:flex;';
|
| 585 |
+
clearBtn.onclick = function() {
|
| 586 |
+
document.getElementById('narrative-output').classList.add('hidden');
|
| 587 |
+
document.getElementById('stats-area').classList.add('hidden');
|
| 588 |
+
document.getElementById('commits-area').classList.add('hidden');
|
| 589 |
+
document.getElementById('copy-btn').classList.add('hidden');
|
| 590 |
+
document.getElementById('period-chip').classList.add('hidden');
|
| 591 |
+
document.getElementById('empty-state').classList.remove('hidden');
|
| 592 |
+
setStatus('Ready');
|
| 593 |
+
this.remove();
|
| 594 |
+
};
|
| 595 |
+
document.getElementById('narrative-output').appendChild(clearBtn);
|
| 596 |
+
}
|
| 597 |
+
}
|
| 598 |
</script>
|
| 599 |
{% endblock %}
|
app/tools/schema_detective/templates/schema_detective/index.html
CHANGED
|
@@ -105,6 +105,7 @@
|
|
| 105 |
<span class="material-symbols-outlined text-5xl text-outline mb-4">policy</span>
|
| 106 |
<p class="text-sm font-medium">Paste your schema and click Run Audit</p>
|
| 107 |
<p class="text-xs text-outline mt-1">Detects missing PKs, bad indexes, naming issues, and more</p>
|
|
|
|
| 108 |
</div>
|
| 109 |
|
| 110 |
<!-- Loading state -->
|
|
@@ -490,6 +491,57 @@ CREATE TABLE prescriptions (
|
|
| 490 |
}
|
| 491 |
}
|
| 492 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 493 |
btnFix.addEventListener('click', async () => {
|
| 494 |
const schema = schemaInput.value.trim();
|
| 495 |
if (!schema) return;
|
|
|
|
| 105 |
<span class="material-symbols-outlined text-5xl text-outline mb-4">policy</span>
|
| 106 |
<p class="text-sm font-medium">Paste your schema and click Run Audit</p>
|
| 107 |
<p class="text-xs text-outline mt-1">Detects missing PKs, bad indexes, naming issues, and more</p>
|
| 108 |
+
<button onclick="window._showExample()" class="mt-4" style="background:none;border:1px solid #00d97e;color:#00d97e;font-size:12px;padding:3px 10px;border-radius:6px;cursor:pointer;">See example ↗</button>
|
| 109 |
</div>
|
| 110 |
|
| 111 |
<!-- Loading state -->
|
|
|
|
| 491 |
}
|
| 492 |
}
|
| 493 |
|
| 494 |
+
// ββ Example feature ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 495 |
+
window._showExample = function() {
|
| 496 |
+
const exampleData = {
|
| 497 |
+
health_score: 72,
|
| 498 |
+
summary: 'Your e-commerce database has a solid foundation but contains 3 findings that should be addressed. The orders table is missing an index on user_id which will cause slow lookups as data grows. A nullable foreign key in the reviews table risks orphaned records. Several columns use VARCHAR(255) when shorter limits would improve storage and validation.',
|
| 499 |
+
findings: [
|
| 500 |
+
{
|
| 501 |
+
severity: 'critical',
|
| 502 |
+
title: 'Missing index on orders.user_id',
|
| 503 |
+
description: 'The orders table references users via user_id but has no index on this column. Queries joining users to their orders will perform full table scans, degrading rapidly beyond 100K rows.',
|
| 504 |
+
suggestion: 'CREATE INDEX idx_orders_user_id ON orders(user_id);',
|
| 505 |
+
location: 'Table: orders, Column: user_id'
|
| 506 |
+
},
|
| 507 |
+
{
|
| 508 |
+
severity: 'warning',
|
| 509 |
+
title: 'Nullable foreign key in reviews table',
|
| 510 |
+
description: 'The reviews.product_id column allows NULL values despite being a foreign key to products. This can result in orphaned review records that reference no product.',
|
| 511 |
+
suggestion: 'ALTER TABLE reviews MODIFY product_id INT NOT NULL;',
|
| 512 |
+
location: 'Table: reviews, Column: product_id'
|
| 513 |
+
},
|
| 514 |
+
{
|
| 515 |
+
severity: 'advice',
|
| 516 |
+
title: 'Overuse of VARCHAR(255) in multiple tables',
|
| 517 |
+
description: 'Columns users.username, products.sku, and orders.status all use VARCHAR(255). Real-world data in these columns rarely exceeds 50 characters. Tighter limits improve storage efficiency and catch data entry errors.',
|
| 518 |
+
suggestion: 'ALTER TABLE users MODIFY username VARCHAR(64);\nALTER TABLE products MODIFY sku VARCHAR(32);\nALTER TABLE orders MODIFY status VARCHAR(30);',
|
| 519 |
+
location: 'Tables: users, products, orders'
|
| 520 |
+
}
|
| 521 |
+
]
|
| 522 |
+
};
|
| 523 |
+
_lastFindings = exampleData.findings;
|
| 524 |
+
renderResults(exampleData);
|
| 525 |
+
showState('results');
|
| 526 |
+
sbSummary.classList.remove('hidden');
|
| 527 |
+
|
| 528 |
+
// Add clear example button
|
| 529 |
+
if (!document.getElementById('clear-example-btn')) {
|
| 530 |
+
const clearBtn = document.createElement('button');
|
| 531 |
+
clearBtn.id = 'clear-example-btn';
|
| 532 |
+
clearBtn.textContent = 'Clear example';
|
| 533 |
+
clearBtn.style.cssText = 'background:none;border:1px solid #94a3b8;color:#94a3b8;font-size:11px;padding:2px 8px;border-radius:5px;cursor:pointer;margin:8px auto;display:block;';
|
| 534 |
+
clearBtn.onclick = function() {
|
| 535 |
+
showState('empty');
|
| 536 |
+
sbSummary.classList.add('hidden');
|
| 537 |
+
btnDownloadPdf.classList.add('hidden');
|
| 538 |
+
btnDownloadPdf.classList.remove('flex');
|
| 539 |
+
this.remove();
|
| 540 |
+
};
|
| 541 |
+
resultsState.appendChild(clearBtn);
|
| 542 |
+
}
|
| 543 |
+
};
|
| 544 |
+
|
| 545 |
btnFix.addEventListener('click', async () => {
|
| 546 |
const schema = schemaInput.value.trim();
|
| 547 |
if (!schema) return;
|
app/tools/sql_whisperer/templates/sql_whisperer/index.html
CHANGED
|
@@ -104,6 +104,7 @@ CREATE TABLE orders (
|
|
| 104 |
</div>
|
| 105 |
<p class="font-semibold text-sys-label text-sm mb-1.5" style="letter-spacing:-0.01em;">Output will appear here</p>
|
| 106 |
<p class="text-sys-label-3 text-xs max-w-[240px] leading-relaxed">Describe what you need in plain English, paste your schema, then hit Generate.</p>
|
|
|
|
| 107 |
<div class="mt-6 flex flex-wrap justify-center gap-1.5">
|
| 108 |
{% for tag in ['SELECT', 'JOIN', 'GROUP BY', 'CTE', 'Window Fn', 'Subquery'] %}
|
| 109 |
<span class="pill" style="background:rgba(116,116,128,0.08); color:#8E8E93; font-family:'SF Mono',monospace; font-size:10px;">{{ tag }}</span>
|
|
@@ -366,5 +367,47 @@ function clearAll() {
|
|
| 366 |
document.addEventListener('keydown', e => {
|
| 367 |
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') generateSQL();
|
| 368 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
</script>
|
| 370 |
{% endblock %}
|
|
|
|
| 104 |
</div>
|
| 105 |
<p class="font-semibold text-sys-label text-sm mb-1.5" style="letter-spacing:-0.01em;">Output will appear here</p>
|
| 106 |
<p class="text-sys-label-3 text-xs max-w-[240px] leading-relaxed">Describe what you need in plain English, paste your schema, then hit Generate.</p>
|
| 107 |
+
<button onclick="showSqlExample()" class="mt-4" style="background:none;border:1px solid #00d97e;color:#00d97e;font-size:12px;padding:3px 10px;border-radius:6px;cursor:pointer;">See example ↗</button>
|
| 108 |
<div class="mt-6 flex flex-wrap justify-center gap-1.5">
|
| 109 |
{% for tag in ['SELECT', 'JOIN', 'GROUP BY', 'CTE', 'Window Fn', 'Subquery'] %}
|
| 110 |
<span class="pill" style="background:rgba(116,116,128,0.08); color:#8E8E93; font-family:'SF Mono',monospace; font-size:10px;">{{ tag }}</span>
|
|
|
|
| 367 |
document.addEventListener('keydown', e => {
|
| 368 |
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') generateSQL();
|
| 369 |
});
|
| 370 |
+
|
| 371 |
+
function showSqlExample() {
|
| 372 |
+
const exampleData = {
|
| 373 |
+
sql: `SELECT\n c.customer_name,\n c.region,\n SUM(o.total_amount) AS revenue,\n COUNT(o.id) AS order_count\nFROM customers c\nJOIN orders o\n ON o.customer_id = c.id\nWHERE o.order_date >= DATE_TRUNC('quarter', CURRENT_DATE) - INTERVAL '3 months'\n AND o.order_date < DATE_TRUNC('quarter', CURRENT_DATE)\n AND o.status = 'completed'\nGROUP BY c.customer_name, c.region\nORDER BY revenue DESC\nLIMIT 10;`,
|
| 374 |
+
explanation: 'This query joins customers with their completed orders from last quarter, aggregates total revenue and order count per customer, then returns the top 10 sorted by revenue descending. DATE_TRUNC ensures quarter boundaries are calculated dynamically.',
|
| 375 |
+
warnings: [
|
| 376 |
+
'Ensure an index exists on orders(customer_id, order_date) for optimal join and filter performance.',
|
| 377 |
+
'If the orders table exceeds 10M rows, consider partitioning by order_date to reduce scan time.'
|
| 378 |
+
],
|
| 379 |
+
sample_result_shape: 'customer_name (VARCHAR), region (VARCHAR), revenue (DECIMAL), order_count (INTEGER)',
|
| 380 |
+
alternatives: [
|
| 381 |
+
{
|
| 382 |
+
label: 'CTE approach with window function',
|
| 383 |
+
trade_off: 'More readable for complex pipelines but slightly more overhead on small datasets.',
|
| 384 |
+
sql: `WITH quarterly_orders AS (\n SELECT\n customer_id,\n SUM(total_amount) AS revenue,\n COUNT(*) AS order_count,\n RANK() OVER (ORDER BY SUM(total_amount) DESC) AS rnk\n FROM orders\n WHERE order_date >= DATE_TRUNC('quarter', CURRENT_DATE) - INTERVAL '3 months'\n AND order_date < DATE_TRUNC('quarter', CURRENT_DATE)\n AND status = 'completed'\n GROUP BY customer_id\n)\nSELECT c.customer_name, c.region, q.revenue, q.order_count\nFROM quarterly_orders q\nJOIN customers c ON c.id = q.customer_id\nWHERE q.rnk <= 10\nORDER BY q.revenue DESC;`
|
| 385 |
+
},
|
| 386 |
+
{
|
| 387 |
+
label: 'Subquery with HAVING filter',
|
| 388 |
+
trade_off: 'Avoids window functions for databases that do not support them, but less flexible for tie-breaking.',
|
| 389 |
+
sql: `SELECT c.customer_name, c.region, sub.revenue\nFROM (\n SELECT customer_id, SUM(total_amount) AS revenue\n FROM orders\n WHERE status = 'completed'\n AND order_date >= DATE_TRUNC('quarter', CURRENT_DATE) - INTERVAL '3 months'\n GROUP BY customer_id\n ORDER BY revenue DESC\n LIMIT 10\n) sub\nJOIN customers c ON c.id = sub.customer_id\nORDER BY sub.revenue DESC;`
|
| 390 |
+
}
|
| 391 |
+
]
|
| 392 |
+
};
|
| 393 |
+
|
| 394 |
+
renderResults(exampleData, 'PostgreSQL');
|
| 395 |
+
setStatus('Example loaded');
|
| 396 |
+
|
| 397 |
+
// Add clear example button
|
| 398 |
+
if (!document.getElementById('clear-example-btn')) {
|
| 399 |
+
const clearBtn = document.createElement('button');
|
| 400 |
+
clearBtn.id = 'clear-example-btn';
|
| 401 |
+
clearBtn.textContent = 'Clear example';
|
| 402 |
+
clearBtn.style.cssText = 'background:none;border:1px solid #AEAEB2;color:#AEAEB2;font-size:11px;padding:2px 8px;border-radius:5px;cursor:pointer;margin-top:8px;align-self:center;';
|
| 403 |
+
clearBtn.onclick = function() {
|
| 404 |
+
document.getElementById('results-container').classList.add('hidden');
|
| 405 |
+
document.getElementById('empty-state').classList.remove('hidden');
|
| 406 |
+
setStatus('Ready');
|
| 407 |
+
this.remove();
|
| 408 |
+
};
|
| 409 |
+
document.getElementById('results-container').appendChild(clearBtn);
|
| 410 |
+
}
|
| 411 |
+
}
|
| 412 |
</script>
|
| 413 |
{% endblock %}
|
app/tools/test_forge/templates/test_forge/index.html
CHANGED
|
@@ -153,6 +153,7 @@
|
|
| 153 |
<span class="material-symbols-outlined text-4xl mb-3 text-outline">science</span>
|
| 154 |
<p class="text-sm font-medium">Paste your code and click Generate Tests</p>
|
| 155 |
<p class="text-xs text-outline mt-1">Ctrl+Enter to generate Β· 8 frameworks supported</p>
|
|
|
|
| 156 |
</div>
|
| 157 |
|
| 158 |
<!-- Loading state -->
|
|
@@ -459,6 +460,131 @@
|
|
| 459 |
statPanel.classList.add('hidden');
|
| 460 |
}
|
| 461 |
window.loadDemo = loadDemo;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 462 |
})();
|
| 463 |
</script>
|
| 464 |
{% endblock %}
|
|
|
|
| 153 |
<span class="material-symbols-outlined text-4xl mb-3 text-outline">science</span>
|
| 154 |
<p class="text-sm font-medium">Paste your code and click Generate Tests</p>
|
| 155 |
<p class="text-xs text-outline mt-1">Ctrl+Enter to generate Β· 8 frameworks supported</p>
|
| 156 |
+
<button onclick="window._showTestForgeExample()" class="mt-4" style="background:none;border:1px solid #00d97e;color:#00d97e;font-size:12px;padding:3px 10px;border-radius:6px;cursor:pointer;">See example ↗</button>
|
| 157 |
</div>
|
| 158 |
|
| 159 |
<!-- Loading state -->
|
|
|
|
| 460 |
statPanel.classList.add('hidden');
|
| 461 |
}
|
| 462 |
window.loadDemo = loadDemo;
|
| 463 |
+
|
| 464 |
+
// ββ Example feature ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 465 |
+
window._showTestForgeExample = function() {
|
| 466 |
+
const exampleCode = `import pytest
|
| 467 |
+
from unittest.mock import patch, MagicMock
|
| 468 |
+
from auth import AuthService, InvalidCredentialsError, AccountLockedError
|
| 469 |
+
|
| 470 |
+
|
| 471 |
+
class TestAuthServiceLogin:
|
| 472 |
+
"""Tests for the user authentication login flow."""
|
| 473 |
+
|
| 474 |
+
def test_login_valid_credentials(self):
|
| 475 |
+
svc = AuthService()
|
| 476 |
+
token = svc.login("alice@example.com", "Str0ng!Pass")
|
| 477 |
+
assert token is not None
|
| 478 |
+
assert len(token) == 64
|
| 479 |
+
|
| 480 |
+
def test_login_invalid_email_format(self):
|
| 481 |
+
svc = AuthService()
|
| 482 |
+
with pytest.raises(ValueError, match="Invalid email"):
|
| 483 |
+
svc.login("not-an-email", "password123")
|
| 484 |
+
|
| 485 |
+
def test_login_wrong_password(self):
|
| 486 |
+
svc = AuthService()
|
| 487 |
+
with pytest.raises(InvalidCredentialsError):
|
| 488 |
+
svc.login("alice@example.com", "wrongpass")
|
| 489 |
+
|
| 490 |
+
def test_login_account_locked_after_5_failures(self):
|
| 491 |
+
svc = AuthService()
|
| 492 |
+
for _ in range(5):
|
| 493 |
+
with pytest.raises(InvalidCredentialsError):
|
| 494 |
+
svc.login("alice@example.com", "wrongpass")
|
| 495 |
+
with pytest.raises(AccountLockedError):
|
| 496 |
+
svc.login("alice@example.com", "Str0ng!Pass")
|
| 497 |
+
|
| 498 |
+
def test_login_empty_password_rejected(self):
|
| 499 |
+
svc = AuthService()
|
| 500 |
+
with pytest.raises(ValueError, match="Password required"):
|
| 501 |
+
svc.login("alice@example.com", "")
|
| 502 |
+
|
| 503 |
+
def test_login_returns_unique_tokens(self):
|
| 504 |
+
svc = AuthService()
|
| 505 |
+
t1 = svc.login("alice@example.com", "Str0ng!Pass")
|
| 506 |
+
t2 = svc.login("alice@example.com", "Str0ng!Pass")
|
| 507 |
+
assert t1 != t2
|
| 508 |
+
|
| 509 |
+
|
| 510 |
+
class TestAuthServiceLogout:
|
| 511 |
+
"""Tests for session invalidation."""
|
| 512 |
+
|
| 513 |
+
def test_logout_invalidates_token(self):
|
| 514 |
+
svc = AuthService()
|
| 515 |
+
token = svc.login("alice@example.com", "Str0ng!Pass")
|
| 516 |
+
svc.logout(token)
|
| 517 |
+
assert svc.validate_token(token) is False
|
| 518 |
+
|
| 519 |
+
def test_logout_nonexistent_token_is_noop(self):
|
| 520 |
+
svc = AuthService()
|
| 521 |
+
svc.logout("nonexistent-token") # should not raise
|
| 522 |
+
|
| 523 |
+
|
| 524 |
+
class TestAuthServiceTokenValidation:
|
| 525 |
+
"""Tests for token validation."""
|
| 526 |
+
|
| 527 |
+
def test_valid_token_returns_user(self):
|
| 528 |
+
svc = AuthService()
|
| 529 |
+
token = svc.login("alice@example.com", "Str0ng!Pass")
|
| 530 |
+
assert svc.validate_token(token) is True
|
| 531 |
+
|
| 532 |
+
def test_expired_token_returns_false(self):
|
| 533 |
+
svc = AuthService()
|
| 534 |
+
token = svc.login("alice@example.com", "Str0ng!Pass")
|
| 535 |
+
svc._expire_token(token)
|
| 536 |
+
assert svc.validate_token(token) is False
|
| 537 |
+
|
| 538 |
+
def test_malformed_token_returns_false(self):
|
| 539 |
+
svc = AuthService()
|
| 540 |
+
assert svc.validate_token("") is False
|
| 541 |
+
assert svc.validate_token(None) is False
|
| 542 |
+
|
| 543 |
+
|
| 544 |
+
class TestPasswordReset:
|
| 545 |
+
"""Tests for password reset flow."""
|
| 546 |
+
|
| 547 |
+
@patch("auth.EmailService.send")
|
| 548 |
+
def test_reset_sends_email(self, mock_send):
|
| 549 |
+
svc = AuthService()
|
| 550 |
+
svc.request_password_reset("alice@example.com")
|
| 551 |
+
mock_send.assert_called_once()`;
|
| 552 |
+
|
| 553 |
+
generatedCode = exampleCode;
|
| 554 |
+
testOutput.textContent = exampleCode;
|
| 555 |
+
|
| 556 |
+
bentoTests.textContent = '12';
|
| 557 |
+
bentoCoverage.textContent = '85%';
|
| 558 |
+
bentoCoverageBar.style.width = '85%';
|
| 559 |
+
bentoEdge.textContent = '4';
|
| 560 |
+
|
| 561 |
+
statTestCount.textContent = '12';
|
| 562 |
+
statCoverage.textContent = '85%';
|
| 563 |
+
statCoverageBar.style.width = '85%';
|
| 564 |
+
statEdgeCases.textContent = '4';
|
| 565 |
+
statPanel.classList.remove('hidden');
|
| 566 |
+
|
| 567 |
+
notesText.textContent = 'Tests cover login, logout, token validation, and password reset. Edge cases include account lockout after 5 failures, empty passwords, expired tokens, and malformed inputs.';
|
| 568 |
+
notesRow.classList.remove('hidden');
|
| 569 |
+
|
| 570 |
+
showState('results');
|
| 571 |
+
|
| 572 |
+
// Add clear example button
|
| 573 |
+
if (!document.getElementById('clear-example-btn')) {
|
| 574 |
+
const clearBtn = document.createElement('button');
|
| 575 |
+
clearBtn.id = 'clear-example-btn';
|
| 576 |
+
clearBtn.textContent = 'Clear example';
|
| 577 |
+
clearBtn.style.cssText = 'background:none;border:1px solid #94a3b8;color:#94a3b8;font-size:11px;padding:2px 8px;border-radius:5px;cursor:pointer;position:absolute;top:8px;right:8px;z-index:10;';
|
| 578 |
+
clearBtn.onclick = function() {
|
| 579 |
+
generatedCode = '';
|
| 580 |
+
statPanel.classList.add('hidden');
|
| 581 |
+
showState('empty');
|
| 582 |
+
this.remove();
|
| 583 |
+
};
|
| 584 |
+
resultsState.style.position = 'relative';
|
| 585 |
+
resultsState.appendChild(clearBtn);
|
| 586 |
+
}
|
| 587 |
+
};
|
| 588 |
})();
|
| 589 |
</script>
|
| 590 |
{% endblock %}
|
docforge.db
CHANGED
|
Binary files a/docforge.db and b/docforge.db differ
|
|
|
docforge.db-shm
DELETED
|
Binary file (32.8 kB)
|
|
|
docforge.db-wal
DELETED
|
Binary file (33 kB)
|
|
|