thumbgate 1.26.8 โ 1.27.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/.claude-plugin/plugin.json +1 -1
- package/.well-known/agentic-verify.txt +1 -0
- package/.well-known/llms.txt +2 -0
- package/.well-known/mcp/server-card.json +1 -1
- package/README.md +44 -31
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/gcp/dfcx-webhook-gate.js +295 -0
- package/adapters/mcp/server-stdio.js +41 -1
- package/adapters/opencode/opencode.json +1 -1
- package/bench/thumbgate-bench.json +2 -2
- package/bin/cli.js +184 -8
- package/bin/dashboard-cli.js +7 -0
- package/config/gate-classifier-routing.json +98 -0
- package/config/gate-templates.json +60 -0
- package/config/mcp-allowlists.json +8 -7
- package/config/model-candidates.json +71 -6
- package/package.json +28 -12
- package/public/about.html +162 -0
- package/public/chatgpt-app.html +330 -0
- package/public/codex-plugin.html +66 -14
- package/public/compare.html +2 -2
- package/public/dashboard.html +224 -36
- package/public/guide.html +2 -2
- package/public/index.html +122 -40
- package/public/learn.html +70 -0
- package/public/lessons.html +129 -6
- package/public/numbers.html +2 -2
- package/public/pricing.html +28 -23
- package/public/pro.html +3 -3
- package/scripts/agent-operations-planner.js +621 -0
- package/scripts/agent-reward-model.js +53 -1
- package/scripts/ai-component-inventory.js +367 -0
- package/scripts/classifier-routing.js +130 -0
- package/scripts/cli-schema.js +26 -0
- package/scripts/commercial-offer.js +10 -2
- package/scripts/dashboard-chat.js +199 -51
- package/scripts/feedback-sanitizer.js +105 -0
- package/scripts/gates-engine.js +301 -67
- package/scripts/hybrid-feedback-context.js +141 -7
- package/scripts/memory-scope-readiness.js +159 -0
- package/scripts/oss-pr-opportunity-scout.js +35 -5
- package/scripts/parallel-workflow-orchestrator.js +293 -0
- package/scripts/plausible-domain-config.js +86 -0
- package/scripts/plausible-server-events.js +4 -2
- package/scripts/proxy-pointer-rag-guardrails.js +42 -1
- package/scripts/qa-scenario-planner.js +136 -0
- package/scripts/rate-limiter.js +2 -2
- package/scripts/repeat-metric.js +28 -12
- package/scripts/secret-fixture-tokens.js +61 -0
- package/scripts/secret-scanner.js +44 -5
- package/scripts/security-scanner.js +80 -0
- package/scripts/seo-gsd.js +113 -0
- package/scripts/thumbgate-bench.js +16 -1
- package/scripts/tool-registry.js +37 -0
- package/scripts/workflow-sentinel.js +282 -54
- package/src/api/server.js +466 -60
- package/.claude-plugin/marketplace.json +0 -85
package/public/dashboard.html
CHANGED
|
@@ -263,14 +263,18 @@
|
|
|
263
263
|
<div class="panel" id="chatPanel" style="margin-bottom:20px;">
|
|
264
264
|
<div style="display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;margin-bottom:10px;">
|
|
265
265
|
<h2 style="margin:0;">๐ฌ Chat with your data</h2>
|
|
266
|
-
<span style="font-size:13px;color:var(--text-muted);">Ask about your
|
|
266
|
+
<span style="font-size:13px;color:var(--text-muted);">Ask about your gates, blocks, feedback, and lessons โ answered <strong>locally from your own data</strong>, no cloud. (An optional BYO model only kicks in for open-ended questions.)</span>
|
|
267
267
|
</div>
|
|
268
268
|
<div id="chatMessages" style="max-height:360px;overflow-y:auto;margin-bottom:12px;display:none;padding-right:4px;"></div>
|
|
269
269
|
<div style="display:flex;gap:8px;">
|
|
270
270
|
<input id="chatInput" class="auth-input" style="flex:1;" placeholder="e.g. What mistakes have we made, and how do we avoid them?" onkeydown="if(event.key==='Enter'){event.preventDefault();sendChat();}" />
|
|
271
271
|
<button class="btn" id="chatSend" onclick="sendChat()">Ask</button>
|
|
272
272
|
</div>
|
|
273
|
-
<div id="chatHint" style="font-size:12px;color:var(--text-muted);margin-top:8px;
|
|
273
|
+
<div id="chatHint" style="font-size:12px;color:var(--text-muted);margin-top:8px;display:flex;align-items:center;gap:8px;">
|
|
274
|
+
<span>Powered by local dashboard data. Optional Gemini/Perplexity keys only expand open-ended analysis.</span>
|
|
275
|
+
<input type="password" id="geminiKeyInput" placeholder="Optional GEMINI_API_KEY..." style="background:var(--bg-raised); border:1px solid var(--border); border-radius:4px; padding:4px 8px; color:var(--text); font-family:var(--mono); font-size:11px; flex:1; max-width: 250px;" onkeydown="if(event.key==='Enter'){event.preventDefault();saveGeminiKey();}" />
|
|
276
|
+
<button class="btn-outline" style="padding:4px 10px;font-size:11px;border-radius:4px;" onclick="saveGeminiKey()">Save</button>
|
|
277
|
+
</div>
|
|
274
278
|
</div>
|
|
275
279
|
|
|
276
280
|
<div class="panel" id="reviewDeltaPanel" style="margin-bottom:20px;">
|
|
@@ -304,7 +308,8 @@
|
|
|
304
308
|
<div class="tab active" onclick="switchTab('search')">๐ Search Memories</div>
|
|
305
309
|
<div class="tab" onclick="switchTab('gates')">๐ก๏ธ Active Gates</div>
|
|
306
310
|
<div class="tab" onclick="switchTab('team')">๐ฅ Team</div>
|
|
307
|
-
<div class="tab" onclick="switchTab('enterprise')">๐ข
|
|
311
|
+
<div class="tab" onclick="switchTab('enterprise')">๐ข Governed Data Chat</div>
|
|
312
|
+
<div class="tab" onclick="switchTab('ai-inventory')">๐งฌ AI Inventory</div>
|
|
308
313
|
<div class="tab" onclick="switchTab('generated')">๐งฉ Generated Views</div>
|
|
309
314
|
<div class="tab" onclick="switchTab('settings')">โ๏ธ Policy Origins</div>
|
|
310
315
|
<div class="tab" onclick="switchTab('templates')">๐งฑ Gate Templates</div>
|
|
@@ -411,25 +416,48 @@
|
|
|
411
416
|
<!-- ENTERPRISE CHAT TAB -->
|
|
412
417
|
<div class="tab-content" id="tab-enterprise">
|
|
413
418
|
<div class="templates-section">
|
|
414
|
-
<h2>
|
|
415
|
-
<p class="template-summary">Ask questions over local ThumbGate
|
|
419
|
+
<h2>Governed Data Chat</h2>
|
|
420
|
+
<p class="template-summary">Ask questions over your local ThumbGate data (lessons, raw feedback via LanceDB vectors, gate stats, receipts). Uses local retrieval plus your configured LLM, including open-source/local endpoints through <code>THUMBGATE_LOCAL_LLM_ENDPOINT</code>. Dialogflow is not the chatbot layer; DFCX/Vertex cards are optional deployment-readiness checks for buyers who run their own Google agent stack.</p>
|
|
416
421
|
<div class="enterprise-chat-layout">
|
|
417
422
|
<div class="panel">
|
|
418
423
|
<h3>Chat With Local ThumbGate Data</h3>
|
|
419
|
-
<textarea class="enterprise-chat-box" id="enterpriseChatPrompt" placeholder="Ask: What mistakes are recurring? Which gates blocked the most?
|
|
424
|
+
<textarea class="enterprise-chat-box" id="enterpriseChatPrompt" placeholder="Ask: What mistakes are recurring? Which gates blocked the most? Are we running local-only or cloud-integrated?"></textarea>
|
|
420
425
|
<div style="display:flex;gap:10px;align-items:center;margin-top:12px;flex-wrap:wrap;">
|
|
421
426
|
<button class="btn" id="enterpriseChatBtn" onclick="sendEnterpriseChat()">Ask ThumbGate</button>
|
|
422
427
|
<button class="btn-outline" onclick="setEnterprisePrompt('Which gates are blocking risky actions?')">Gates</button>
|
|
423
428
|
<button class="btn-outline" onclick="setEnterprisePrompt('What feedback mistakes keep repeating?')">Feedback</button>
|
|
424
|
-
<button class="btn-outline" onclick="setEnterprisePrompt('
|
|
429
|
+
<button class="btn-outline" onclick="setEnterprisePrompt('Are we running local-only or cloud-integrated?')">Runtime</button>
|
|
425
430
|
</div>
|
|
426
431
|
<div class="enterprise-answer" id="enterpriseChatAnswer">Connect your dashboard, then ask about local ThumbGate data.</div>
|
|
427
432
|
<div class="enterprise-source-list" id="enterpriseChatSources"></div>
|
|
428
433
|
</div>
|
|
429
434
|
<div class="panel">
|
|
430
|
-
<h3>
|
|
435
|
+
<h3>Runtime Readiness</h3>
|
|
431
436
|
<div class="inventory-tools" id="enterpriseStatusCards"><div class="loading">Loading enterprise status...</div></div>
|
|
432
|
-
<div class="template-summary" style="margin-top:14px;margin-bottom:0;">
|
|
437
|
+
<div class="template-summary" style="margin-top:14px;margin-bottom:0;">The chat itself is local RAG plus your LLM. Open-source/local models are the default enterprise path; DFCX/Vertex readiness is optional evidence for teams that already operate Google agents in their own tenancy.</div>
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
<!-- AI INVENTORY TAB -->
|
|
444
|
+
<div class="tab-content" id="tab-ai-inventory">
|
|
445
|
+
<div class="templates-section">
|
|
446
|
+
<h2>AI Component Inventory</h2>
|
|
447
|
+
<p class="template-summary">Enterprise evidence for AI/ML usage: provider SDKs, agent frameworks, vector databases, Vertex/Gemini/Dialogflow CX references, and local model artifacts. Export this as ML-BOM proof before claiming an AI system is production-ready.</p>
|
|
448
|
+
<div class="inventory-grid" id="aiInventorySummaryCards"><div class="loading">Scanning AI inventory...</div></div>
|
|
449
|
+
<div class="team-columns" style="margin-top:16px;">
|
|
450
|
+
<div class="panel">
|
|
451
|
+
<h3>Detected Components</h3>
|
|
452
|
+
<div class="inventory-tools" id="aiInventoryComponents"><div class="loading">Scanning components...</div></div>
|
|
453
|
+
</div>
|
|
454
|
+
<div class="panel">
|
|
455
|
+
<h3>Export Evidence</h3>
|
|
456
|
+
<div class="template-summary" style="margin-bottom:12px;">CLI export:</div>
|
|
457
|
+
<pre style="white-space:pre-wrap;background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:12px;font-size:12px;color:var(--text);">npx thumbgate ai-inventory --format=cyclonedx --output=.thumbgate/ai-mlbom.json</pre>
|
|
458
|
+
<div class="template-summary" style="margin-top:12px;">API export:</div>
|
|
459
|
+
<pre style="white-space:pre-wrap;background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:12px;font-size:12px;color:var(--text);">GET /v1/dashboard/ai-inventory?format=cyclonedx</pre>
|
|
460
|
+
<div id="aiInventoryExportNote" class="template-summary" style="margin-top:12px;margin-bottom:0;">Waiting for inventory evidence...</div>
|
|
433
461
|
</div>
|
|
434
462
|
</div>
|
|
435
463
|
</div>
|
|
@@ -681,8 +709,30 @@ let currentGeneratedView = 'team-review';
|
|
|
681
709
|
const BOOTSTRAP_API_KEY = __DASHBOARD_BOOTSTRAP_KEY__;
|
|
682
710
|
const LOCAL_PRO_BOOTSTRAP = __DASHBOARD_BOOTSTRAP_ENABLED__;
|
|
683
711
|
|
|
712
|
+
let ACTIVE_PROJECT_DIR = '';
|
|
713
|
+
try {
|
|
714
|
+
var urlParams = new URLSearchParams(window.location.search);
|
|
715
|
+
ACTIVE_PROJECT_DIR = urlParams.get('project') || '';
|
|
716
|
+
} catch (e) {}
|
|
717
|
+
|
|
718
|
+
function preserveProjectQueryParams() {
|
|
719
|
+
if (!ACTIVE_PROJECT_DIR) return;
|
|
720
|
+
var links = document.querySelectorAll('a[href^="/dashboard"], a[href^="/lessons"], a[href="/"]');
|
|
721
|
+
links.forEach(function(link) {
|
|
722
|
+
try {
|
|
723
|
+
var url = new URL(link.href, window.location.origin);
|
|
724
|
+
url.searchParams.set('project', ACTIVE_PROJECT_DIR);
|
|
725
|
+
link.href = url.pathname + url.search + url.hash;
|
|
726
|
+
} catch (e) {}
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
|
|
684
730
|
function getHeaders() {
|
|
685
|
-
|
|
731
|
+
var headers = { 'Authorization': 'Bearer ' + API_KEY, 'Content-Type': 'application/json' };
|
|
732
|
+
if (ACTIVE_PROJECT_DIR) {
|
|
733
|
+
headers['x-thumbgate-project-dir'] = ACTIVE_PROJECT_DIR;
|
|
734
|
+
}
|
|
735
|
+
return headers;
|
|
686
736
|
}
|
|
687
737
|
|
|
688
738
|
// --- Chat with your data ---------------------------------------------------
|
|
@@ -706,13 +756,32 @@ function chatRenderAnswer(a) {
|
|
|
706
756
|
.replace(/\[(\d+)\]/g, '<sup style="color:var(--cyan);font-weight:600;">[$1]</sup>')
|
|
707
757
|
.replace(/\n/g, '<br>');
|
|
708
758
|
}
|
|
709
|
-
function chatRenderSources(sources) {
|
|
759
|
+
function chatRenderSources(sources, answerText) {
|
|
710
760
|
if (!sources || !sources.length) return '';
|
|
761
|
+
var citedNums = new Set();
|
|
762
|
+
var regex = /\[(\d+)\]/g;
|
|
763
|
+
var match;
|
|
764
|
+
while ((match = regex.exec(answerText || '')) !== null) {
|
|
765
|
+
citedNums.add(parseInt(match[1], 10));
|
|
766
|
+
}
|
|
767
|
+
|
|
711
768
|
var items = sources.map(function (s, i) {
|
|
769
|
+
var num = i + 1;
|
|
770
|
+
var isCited = citedNums.has(num);
|
|
712
771
|
var label = chatEscape(String(s.title || s.id || '').slice(0, 64));
|
|
713
|
-
|
|
772
|
+
|
|
773
|
+
var bg = isCited ? 'var(--cyan-dim)' : 'var(--bg-raised)';
|
|
774
|
+
var color = isCited ? 'var(--cyan)' : 'var(--text-muted)';
|
|
775
|
+
var border = isCited ? '1px solid var(--cyan)' : '1px solid var(--border)';
|
|
776
|
+
var weight = isCited ? '600' : 'normal';
|
|
777
|
+
|
|
778
|
+
return '<span title="' + label + '" style="display:inline-block;font-size:11px;background:' + bg + ';color:' + color + ';border:' + border + ';font-weight:' + weight + ';padding:2px 7px;border-radius:5px;margin:4px 5px 0 0;">[' + num + '] ' + label + '</span>';
|
|
714
779
|
}).join('');
|
|
715
|
-
|
|
780
|
+
|
|
781
|
+
return '<div style="margin-top:12px;border-top:1px dashed var(--border);padding-top:10px;">' +
|
|
782
|
+
'<div style="font-size:11px;color:var(--text-muted);margin-bottom:6px;font-weight:600;">Database logs analyzed to answer your question:</div>' +
|
|
783
|
+
items +
|
|
784
|
+
'</div>';
|
|
716
785
|
}
|
|
717
786
|
async function sendChat() {
|
|
718
787
|
var input = document.getElementById('chatInput');
|
|
@@ -729,7 +798,10 @@ async function sendChat() {
|
|
|
729
798
|
var res = await fetch('/v1/chat', { method: 'POST', headers: getHeaders(), body: JSON.stringify({ question: q }) });
|
|
730
799
|
var data = await res.json();
|
|
731
800
|
if (data && data.ok) {
|
|
732
|
-
pending.innerHTML = chatRenderAnswer(data.answer) + chatRenderSources(data.sources);
|
|
801
|
+
pending.innerHTML = chatRenderAnswer(data.answer) + chatRenderSources(data.sources, data.answer);
|
|
802
|
+
} else if (data && data.error === 'gemini_error') {
|
|
803
|
+
pending.innerHTML = '<em style="color:#f87171;">Gemini rejected the key: ' + chatEscape(data.message || 'invalid') +
|
|
804
|
+
'<br>Fix: paste a valid key in the "Set GEMINI_API_KEY..." box below (get one at <a href="https://aistudio.google.com/app/apikey" target="_blank" style="color:#67e8f9;">AI Studio</a>) and click Save.</em>';
|
|
733
805
|
} else {
|
|
734
806
|
pending.innerHTML = '<em style="color:var(--text-muted);">' + chatEscape((data && data.message) || 'Chat is unavailable.') + '</em>';
|
|
735
807
|
}
|
|
@@ -741,6 +813,37 @@ async function sendChat() {
|
|
|
741
813
|
}
|
|
742
814
|
}
|
|
743
815
|
|
|
816
|
+
async function saveGeminiKey() {
|
|
817
|
+
var input = document.getElementById('geminiKeyInput');
|
|
818
|
+
var val = (input.value || '').trim();
|
|
819
|
+
if (!val) return;
|
|
820
|
+
var oldPlaceholder = input.placeholder;
|
|
821
|
+
input.disabled = true;
|
|
822
|
+
input.placeholder = 'Saving...';
|
|
823
|
+
try {
|
|
824
|
+
var res = await fetch('/v1/settings/gemini-key', {
|
|
825
|
+
method: 'POST',
|
|
826
|
+
headers: getHeaders(),
|
|
827
|
+
body: JSON.stringify({ key: val })
|
|
828
|
+
});
|
|
829
|
+
var data = await res.json();
|
|
830
|
+
if (data && data.ok) {
|
|
831
|
+
input.value = '';
|
|
832
|
+
input.placeholder = 'โ Key saved to .env';
|
|
833
|
+
setTimeout(function() {
|
|
834
|
+
document.getElementById('chatHint').innerHTML = '<span style="color:var(--green)">โ Key validated. Hybrid (Perplexity/Gemini) supported for chat with your data.</span>';
|
|
835
|
+
}, 2000);
|
|
836
|
+
} else {
|
|
837
|
+
input.placeholder = 'โ Failed: ' + (data.message || data.error || 'Unknown error');
|
|
838
|
+
}
|
|
839
|
+
} catch (e) {
|
|
840
|
+
input.placeholder = 'โ Network error';
|
|
841
|
+
} finally {
|
|
842
|
+
input.disabled = false;
|
|
843
|
+
setTimeout(function() { if (input.placeholder.startsWith('โ')) input.placeholder = oldPlaceholder; }, 3000);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
744
847
|
function hasBootstrapKey() {
|
|
745
848
|
return LOCAL_PRO_BOOTSTRAP && Boolean(BOOTSTRAP_API_KEY);
|
|
746
849
|
}
|
|
@@ -751,8 +854,8 @@ async function connect(options) {
|
|
|
751
854
|
API_KEY = String(opts.key || input.value || '').trim();
|
|
752
855
|
if (!API_KEY) return;
|
|
753
856
|
|
|
754
|
-
|
|
755
|
-
|
|
857
|
+
var isEnterprise = API_KEY.startsWith('tg_op_') || API_KEY.startsWith('tg_creator_');
|
|
858
|
+
var tierName = isEnterprise ? 'Enterprise' : 'Pro';
|
|
756
859
|
|
|
757
860
|
const status = document.getElementById('authStatus');
|
|
758
861
|
const btn = document.getElementById('connectBtn');
|
|
@@ -763,16 +866,33 @@ async function connect(options) {
|
|
|
763
866
|
const res = await fetch('/v1/feedback/stats', { headers: getHeaders() });
|
|
764
867
|
if (!res.ok) throw new Error('Invalid API key');
|
|
765
868
|
const data = await res.json();
|
|
869
|
+
|
|
870
|
+
if (data.tier) {
|
|
871
|
+
isEnterprise = (data.tier === 'Enterprise');
|
|
872
|
+
tierName = data.tier;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
if (data.geminiKeyStatus === 'validated' || data.geminiValidatedAt) {
|
|
876
|
+
var when = data.geminiValidatedAt ? ' (validated ' + new Date(data.geminiValidatedAt).toLocaleTimeString() + ')' : '';
|
|
877
|
+
document.getElementById('chatHint').innerHTML = '<span style="color:var(--green)">โ Gemini API key validated' + when + '. You can now chat with your data.</span>';
|
|
878
|
+
} else if (data.perplexityConfigured) {
|
|
879
|
+
document.getElementById('chatHint').innerHTML = '<span style="color:var(--green)">โ Perplexity hybrid (local-cloud) key present. Supports hybrid inference for chat with your data.</span>';
|
|
880
|
+
} else if (data.geminiConfigured) {
|
|
881
|
+
document.getElementById('chatHint').innerHTML = '<span style="color:#f59e0b">โ Gemini key present in .env (click Save below to validate it). Perplexity hybrid also supported for cost/privacy.</span>';
|
|
882
|
+
}
|
|
883
|
+
|
|
766
884
|
status.className = 'auth-status ok';
|
|
767
885
|
status.textContent = opts.localPro ? `โ Local ${tierName} connected` : 'โ Connected';
|
|
768
886
|
document.getElementById('dashboardContent').style.display = 'block';
|
|
769
887
|
if (opts.localPro) {
|
|
888
|
+
const localProActiveMessage = 'Local Pro is active on this machine. Your dashboard is using the saved license key automatically.';
|
|
889
|
+
const localEnterpriseActiveMessage = 'Local Enterprise is active on this machine. Your dashboard is using the saved license key automatically.';
|
|
770
890
|
input.value = 'local-license';
|
|
771
891
|
input.disabled = true;
|
|
772
892
|
input.placeholder = `Local ${tierName} auto-connected`;
|
|
773
893
|
btn.disabled = true;
|
|
774
894
|
document.getElementById('demoBtn').style.display = 'none';
|
|
775
|
-
document.getElementById('authHelp').textContent =
|
|
895
|
+
document.getElementById('authHelp').textContent = isEnterprise ? localEnterpriseActiveMessage : localProActiveMessage;
|
|
776
896
|
}
|
|
777
897
|
renderStats(data);
|
|
778
898
|
setSelectedCard('all');
|
|
@@ -971,11 +1091,11 @@ function switchTab(name) {
|
|
|
971
1091
|
/**
|
|
972
1092
|
* Resolve deep-link tab target from URL hash or query string.
|
|
973
1093
|
* Supports: /dashboard#insights, /dashboard?tab=gates, /dashboard#tab-export.
|
|
974
|
-
* Valid targets match tab-content ids (search, gates, team,
|
|
975
|
-
* settings, templates, insights, export).
|
|
1094
|
+
* Valid targets match tab-content ids (search, gates, team, enterprise,
|
|
1095
|
+
* ai-inventory, generated, settings, templates, insights, export).
|
|
976
1096
|
*/
|
|
977
1097
|
function getDeepLinkTab() {
|
|
978
|
-
var valid = ['search', 'gates', 'team', 'generated', 'settings', 'templates', 'insights', 'export'];
|
|
1098
|
+
var valid = ['search', 'gates', 'team', 'enterprise', 'ai-inventory', 'generated', 'settings', 'templates', 'insights', 'export'];
|
|
979
1099
|
var raw = (window.location.hash || '').replace(/^#/, '').replace(/^tab-/, '');
|
|
980
1100
|
if (!raw) {
|
|
981
1101
|
try {
|
|
@@ -1187,7 +1307,68 @@ function renderDashboardData(data) {
|
|
|
1187
1307
|
renderRegulatedProof(data.regulatedProof || {});
|
|
1188
1308
|
renderTemplates(data.templateLibrary || {});
|
|
1189
1309
|
renderInsights(data);
|
|
1190
|
-
|
|
1310
|
+
loadAiInventory();
|
|
1311
|
+
loadEnterpriseDataChatStatus();
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
async function loadAiInventory() {
|
|
1315
|
+
var summaryEl = document.getElementById('aiInventorySummaryCards');
|
|
1316
|
+
var componentsEl = document.getElementById('aiInventoryComponents');
|
|
1317
|
+
var exportEl = document.getElementById('aiInventoryExportNote');
|
|
1318
|
+
if (!summaryEl || !componentsEl) return;
|
|
1319
|
+
|
|
1320
|
+
try {
|
|
1321
|
+
var res = await fetch('/v1/dashboard/ai-inventory', { headers: getHeaders() });
|
|
1322
|
+
if (!res.ok) {
|
|
1323
|
+
var text = await res.text();
|
|
1324
|
+
throw new Error(text || 'Failed to load AI inventory');
|
|
1325
|
+
}
|
|
1326
|
+
var inventory = await res.json();
|
|
1327
|
+
renderAiInventory(inventory);
|
|
1328
|
+
} catch (e) {
|
|
1329
|
+
var message = e && e.message ? e.message : 'Failed to load AI inventory';
|
|
1330
|
+
setMessageState(summaryEl, 'empty', message);
|
|
1331
|
+
setMessageState(componentsEl, 'empty', message);
|
|
1332
|
+
if (exportEl) exportEl.textContent = message;
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
function renderAiInventory(inventory) {
|
|
1337
|
+
var summaryEl = document.getElementById('aiInventorySummaryCards');
|
|
1338
|
+
var componentsEl = document.getElementById('aiInventoryComponents');
|
|
1339
|
+
var exportEl = document.getElementById('aiInventoryExportNote');
|
|
1340
|
+
var components = Array.isArray(inventory.components) ? inventory.components : [];
|
|
1341
|
+
var byCategory = inventory.summary && inventory.summary.byCategory ? inventory.summary.byCategory : {};
|
|
1342
|
+
var modelArtifacts = byCategory.model_artifact || 0;
|
|
1343
|
+
var vectorDbs = byCategory.vector_database || 0;
|
|
1344
|
+
|
|
1345
|
+
summaryEl.innerHTML = [
|
|
1346
|
+
'<div class="team-card"><div class="team-kicker">Files scanned</div><div class="team-value">' + escHtml(String(inventory.filesScanned || 0)) + '</div><div class="team-note">source, manifests, model artifacts</div></div>',
|
|
1347
|
+
'<div class="team-card"><div class="team-kicker">AI components</div><div class="team-value">' + escHtml(String(inventory.componentCount || components.length)) + '</div><div class="team-note">inventory evidence items</div></div>',
|
|
1348
|
+
'<div class="team-card"><div class="team-kicker">Vector stores</div><div class="team-value">' + escHtml(String(vectorDbs)) + '</div><div class="team-note">retrieval governance surface</div></div>',
|
|
1349
|
+
'<div class="team-card"><div class="team-kicker">Model artifacts</div><div class="team-value">' + escHtml(String(modelArtifacts)) + '</div><div class="team-note">local ML-BOM artifacts</div></div>'
|
|
1350
|
+
].join('');
|
|
1351
|
+
|
|
1352
|
+
if (!components.length) {
|
|
1353
|
+
componentsEl.innerHTML = '<div class="empty">No AI/ML components detected in this project root.</div>';
|
|
1354
|
+
} else {
|
|
1355
|
+
componentsEl.innerHTML = components.map(function(item) {
|
|
1356
|
+
var evidence = Array.isArray(item.evidence) ? item.evidence.slice(0, 4) : [];
|
|
1357
|
+
var evidenceHtml = evidence.map(function(e) {
|
|
1358
|
+
var loc = e.line ? (e.file + ':' + e.line) : e.file;
|
|
1359
|
+
return '<li><code>' + escHtml(loc || 'unknown') + '</code> <span style="color:var(--text-muted);">[' + escHtml(e.kind || 'evidence') + ']</span></li>';
|
|
1360
|
+
}).join('');
|
|
1361
|
+
return '<div class="tool-card">' +
|
|
1362
|
+
'<div class="tool-title">' + escHtml(item.name || item.id || 'AI component') + '</div>' +
|
|
1363
|
+
'<div class="tool-meta">' + escHtml(item.category || 'unknown') + ' ยท ' + escHtml(item.ecosystem || 'unknown') + '</div>' +
|
|
1364
|
+
'<ul style="margin:8px 0 0 18px;padding:0;">' + evidenceHtml + '</ul>' +
|
|
1365
|
+
'</div>';
|
|
1366
|
+
}).join('');
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
if (exportEl) {
|
|
1370
|
+
exportEl.textContent = 'Ready: export ' + String(inventory.componentCount || components.length) + ' component(s) as CycloneDX ML-BOM evidence.';
|
|
1371
|
+
}
|
|
1191
1372
|
}
|
|
1192
1373
|
|
|
1193
1374
|
function renderGeneratedViewToolbar(spec) {
|
|
@@ -1632,24 +1813,29 @@ function renderEnterpriseStatus(status) {
|
|
|
1632
1813
|
if (!target) return;
|
|
1633
1814
|
var vertex = status && status.vertex ? status.vertex : {};
|
|
1634
1815
|
var dfcx = status && status.dfcx ? status.dfcx : {};
|
|
1816
|
+
var localLlm = status && status.chat ? status.chat.localLlmEndpointConfigured : false;
|
|
1817
|
+
var connectionMode = localLlm
|
|
1818
|
+
? 'Local/open-source LLM configured'
|
|
1819
|
+
: (dfcx.liveAgentConfigured ? 'Google agent guard configured' : (vertex.configured ? 'Vertex routing configured' : 'Local deterministic fallback'));
|
|
1635
1820
|
var rows = [
|
|
1636
|
-
{ name: '
|
|
1637
|
-
{ name: '
|
|
1638
|
-
{ name: '
|
|
1639
|
-
{ name: '
|
|
1821
|
+
{ name: 'Chat runtime', value: connectionMode, note: localLlm ? 'Using THUMBGATE_LOCAL_LLM_ENDPOINT for local/open-source inference.' : 'Local deterministic summaries work without cloud credentials; configure a local LLM for generated answers.' },
|
|
1822
|
+
{ name: 'Data boundary', value: 'Local by default', note: 'Dashboard answers are grounded in local ThumbGate data and do not require Google Cloud.' },
|
|
1823
|
+
{ name: 'Vertex adapter', value: vertex.configured ? 'Configured' : 'Optional', note: vertex.projectId ? ('Project: ' + vertex.projectId + ' ยท ' + (vertex.location || '')) : 'Only needed when the buyer chooses Google-hosted routing.' },
|
|
1824
|
+
{ name: 'Dialogflow guard adapter', value: dfcx.liveAgentConfigured ? 'Configured' : 'Optional', note: dfcx.verification || 'Only needed for customer-owned Dialogflow CX agent deployments.' },
|
|
1825
|
+
{ name: 'Legacy gcloud CX command', value: 'Unsupported', note: 'Do not use the old alpha gcloud CX command group; use REST API or console for DFCX proof.' }
|
|
1640
1826
|
];
|
|
1641
1827
|
target.innerHTML = rows.map(function(row) {
|
|
1642
1828
|
return '<div class="inventory-row"><div><div class="inventory-name">' + escHtml(row.name) + '</div><div class="inventory-subtitle">' + escHtml(row.note) + '</div></div><span class="remediation-action">' + escHtml(row.value) + '</span></div>';
|
|
1643
1829
|
}).join('');
|
|
1644
1830
|
}
|
|
1645
1831
|
|
|
1646
|
-
async function
|
|
1832
|
+
async function loadEnterpriseDataChatStatus() {
|
|
1647
1833
|
if (!API_KEY || isDemo) {
|
|
1648
1834
|
renderEnterpriseStatus({ vertex: {}, dfcx: { verification: 'Connect to load local status.' } });
|
|
1649
1835
|
return;
|
|
1650
1836
|
}
|
|
1651
1837
|
try {
|
|
1652
|
-
var res = await fetch('/v1/enterprise/
|
|
1838
|
+
var res = await fetch('/v1/enterprise/data-chat/status', { headers: getHeaders() });
|
|
1653
1839
|
if (!res.ok) throw new Error('status unavailable');
|
|
1654
1840
|
renderEnterpriseStatus(await res.json());
|
|
1655
1841
|
} catch (err) {
|
|
@@ -1679,26 +1865,27 @@ async function sendEnterpriseChat() {
|
|
|
1679
1865
|
}
|
|
1680
1866
|
button.disabled = true;
|
|
1681
1867
|
answer.className = 'enterprise-answer';
|
|
1682
|
-
answer.textContent = '
|
|
1868
|
+
answer.textContent = 'Local RAG (LanceDB + lessons) plus your LLM...';
|
|
1683
1869
|
sources.innerHTML = '';
|
|
1684
1870
|
try {
|
|
1685
|
-
var res = await fetch('/v1/
|
|
1871
|
+
var res = await fetch('/v1/chat', {
|
|
1686
1872
|
method: 'POST',
|
|
1687
1873
|
headers: getHeaders(),
|
|
1688
|
-
body: JSON.stringify({
|
|
1874
|
+
body: JSON.stringify({ question: prompt })
|
|
1689
1875
|
});
|
|
1690
1876
|
var data = await res.json();
|
|
1691
|
-
if (!res.ok) throw new Error(data.detail || data.error || 'chat failed');
|
|
1692
|
-
answer.className = 'enterprise-answer'
|
|
1693
|
-
answer.textContent = data.answer || 'No answer returned.';
|
|
1877
|
+
if (!res.ok) throw new Error(data.detail || data.error || data.message || 'chat failed');
|
|
1878
|
+
answer.className = 'enterprise-answer';
|
|
1879
|
+
answer.textContent = data.answer || data.message || 'No answer returned.';
|
|
1694
1880
|
var list = Array.isArray(data.sources) ? data.sources : [];
|
|
1695
1881
|
sources.innerHTML = list.map(function(source) {
|
|
1696
|
-
|
|
1882
|
+
var label = typeof source === 'string' ? source : (source && (source.title || source.id || ''));
|
|
1883
|
+
return '<span class="enterprise-source">' + escHtml(label) + '</span>';
|
|
1697
1884
|
}).join('');
|
|
1698
|
-
|
|
1885
|
+
loadEnterpriseDataChatStatus();
|
|
1699
1886
|
} catch (err) {
|
|
1700
1887
|
answer.className = 'enterprise-answer blocked';
|
|
1701
|
-
answer.textContent = err.message || '
|
|
1888
|
+
answer.textContent = err.message || 'Chat failed. Configure a local LLM endpoint or API key for RAG.';
|
|
1702
1889
|
} finally {
|
|
1703
1890
|
button.disabled = false;
|
|
1704
1891
|
}
|
|
@@ -1980,6 +2167,7 @@ function loadDemo() {
|
|
|
1980
2167
|
|
|
1981
2168
|
// Auto-load demo on first visit so visitors see the product immediately
|
|
1982
2169
|
window.addEventListener('DOMContentLoaded', function() {
|
|
2170
|
+
preserveProjectQueryParams();
|
|
1983
2171
|
if (hasBootstrapKey()) {
|
|
1984
2172
|
connect({ key: BOOTSTRAP_API_KEY, localPro: true });
|
|
1985
2173
|
return;
|
package/public/guide.html
CHANGED
|
@@ -330,7 +330,7 @@ npx thumbgate init --agent gemini</code></pre>
|
|
|
330
330
|
<tr><td>Auto-generates rules</td><td>Yes โ from repeated failures</td><td>No</td><td>No</td></tr>
|
|
331
331
|
<tr><td>Agent support</td><td>Claude Code, Codex, Gemini, Amp, Cursor, OpenCode</td><td>Claude Code, Cursor, Windsurf, Cline</td><td>Claude, Cursor</td></tr>
|
|
332
332
|
<tr><td>Install</td><td><code>npx thumbgate init</code></td><td><code>npx speclock setup</code></td><td>Cloud signup</td></tr>
|
|
333
|
-
<tr><td>Cost</td><td>Free (Pro $19/mo or $149/yr,
|
|
333
|
+
<tr><td>Cost</td><td>Free (Pro $19/mo or $149/yr, Enterprise custom pricing)</td><td>Free</td><td>Free tier + paid</td></tr>
|
|
334
334
|
</table>
|
|
335
335
|
|
|
336
336
|
<h2>Common Scenarios</h2>
|
|
@@ -379,7 +379,7 @@ npx thumbgate init --agent gemini</code></pre>
|
|
|
379
379
|
<a rel="nofollow noopener noreferrer" target="_blank" href="https://buy.stripe.com/00w14neyUcXA5pL5e33sI0e" class="cta cta-secondary">Pay $499 diagnostic</a>
|
|
380
380
|
<a rel="nofollow noopener noreferrer" target="_blank" href="https://buy.stripe.com/fZu9AT76saPsg4pbCr3sI0f" class="cta cta-secondary">Pay $1500 sprint</a>
|
|
381
381
|
<a href="https://thumbgate.ai/#workflow-sprint-intake" class="cta cta-secondary">Send workflow first</a>
|
|
382
|
-
<p style="color:var(--muted); font-size:0.85rem;">Free: 5 captures/day, 25 total captures, 3 active prevention rules, hook blocking. Pro: hosted sync, dashboard, recall, lesson search, unlimited captures/rules, DPO export.
|
|
382
|
+
<p style="color:var(--muted); font-size:0.85rem;">Free: 5 captures/day, 25 total captures, 3 active prevention rules, hook blocking. Pro: hosted sync, dashboard, recall, lesson search, unlimited captures/rules, DPO export. Enterprise: intake first, then custom pricing scoped to your rollout.</p>
|
|
383
383
|
|
|
384
384
|
</div>
|
|
385
385
|
</body>
|