cyclecad 0.2.2 → 0.2.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/API-BUILD-MANIFEST.txt +339 -0
- package/API-SERVER.md +535 -0
- package/Architecture-Deck.pptx +0 -0
- package/CLAUDE.md +172 -11
- package/CLI-BUILD-SUMMARY.md +504 -0
- package/CLI-INDEX.md +356 -0
- package/CLI-README.md +466 -0
- package/COLLABORATION-INTEGRATION-GUIDE.md +325 -0
- package/CONNECTED_FABS_GUIDE.md +612 -0
- package/CONNECTED_FABS_README.md +310 -0
- package/DELIVERABLES.md +343 -0
- package/DFM-ANALYZER-INTEGRATION.md +368 -0
- package/DFM-QUICK-START.js +253 -0
- package/Dockerfile +69 -0
- package/IMPLEMENTATION.md +327 -0
- package/LICENSE +31 -0
- package/MARKETPLACE_QUICK_REFERENCE.txt +294 -0
- package/MCP-INDEX.md +264 -0
- package/QUICKSTART-API.md +388 -0
- package/QUICKSTART-CLI.md +211 -0
- package/QUICKSTART-MCP.md +196 -0
- package/README-MCP.md +208 -0
- package/TEST-TOKEN-ENGINE.md +319 -0
- package/TOKEN-ENGINE-SUMMARY.md +266 -0
- package/TOKENS-README.md +263 -0
- package/TOOLS-REFERENCE.md +254 -0
- package/app/index.html +168 -3
- package/app/js/TOKEN-INTEGRATION.md +391 -0
- package/app/js/agent-api.js +3 -3
- package/app/js/ai-copilot.js +1435 -0
- package/app/js/cam-pipeline.js +840 -0
- package/app/js/collaboration-ui.js +995 -0
- package/app/js/collaboration.js +1116 -0
- package/app/js/connected-fabs-example.js +404 -0
- package/app/js/connected-fabs.js +1449 -0
- package/app/js/dfm-analyzer.js +1760 -0
- package/app/js/marketplace.js +1994 -0
- package/app/js/material-library.js +2115 -0
- package/app/js/token-dashboard.js +563 -0
- package/app/js/token-engine.js +743 -0
- package/app/test-agent.html +1801 -0
- package/bin/cyclecad-cli.js +662 -0
- package/bin/cyclecad-mcp +2 -0
- package/bin/server.js +242 -0
- package/cycleCAD-Architecture.pptx +0 -0
- package/cycleCAD-Investor-Deck.pptx +0 -0
- package/demo-mcp.sh +60 -0
- package/docs/API-SERVER-SUMMARY.md +375 -0
- package/docs/API-SERVER.md +667 -0
- package/docs/CAM-EXAMPLES.md +344 -0
- package/docs/CAM-INTEGRATION.md +612 -0
- package/docs/CAM-QUICK-REFERENCE.md +199 -0
- package/docs/CLI-INTEGRATION.md +510 -0
- package/docs/CLI.md +872 -0
- package/docs/MARKETPLACE-API-SCHEMA.json +564 -0
- package/docs/MARKETPLACE-INTEGRATION.md +467 -0
- package/docs/MARKETPLACE-SETUP.html +439 -0
- package/docs/MCP-SERVER.md +403 -0
- package/examples/api-client-example.js +488 -0
- package/examples/api-client-example.py +359 -0
- package/examples/batch-manufacturing.txt +28 -0
- package/examples/batch-simple.txt +26 -0
- package/model-marketplace.html +1273 -0
- package/package.json +14 -3
- package/server/api-server.js +1120 -0
- package/server/mcp-server.js +1161 -0
- package/test-api-server.js +432 -0
- package/test-mcp.js +198 -0
- package/~$cycleCAD-Investor-Deck.pptx +0 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* token-dashboard.js — Token Engine UI Dashboard for cycleCAD
|
|
3
|
+
*
|
|
4
|
+
* Provides a rich UI for:
|
|
5
|
+
* - Balance display with tier badge
|
|
6
|
+
* - Recent transactions
|
|
7
|
+
* - Monthly usage chart
|
|
8
|
+
* - Tier information and upgrade buttons
|
|
9
|
+
* - Purchase tokens dialog
|
|
10
|
+
* - Operation price estimator
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export function initTokenDashboard() {
|
|
14
|
+
// Create panel HTML
|
|
15
|
+
const panelHTML = `
|
|
16
|
+
<div id="tab-tokens" style="display: none; padding: 12px; overflow-y: auto;">
|
|
17
|
+
<!-- Balance Card -->
|
|
18
|
+
<div style="background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 6px; padding: 12px; margin-bottom: 12px;">
|
|
19
|
+
<div style="font-size: 11px; color: var(--text-secondary); text-transform: uppercase; margin-bottom: 6px;">Balance</div>
|
|
20
|
+
<div style="display: flex; align-items: baseline; gap: 8px; margin-bottom: 8px;">
|
|
21
|
+
<div id="token-balance-value" style="font-size: 28px; font-weight: bold; color: var(--accent-blue);">1,000</div>
|
|
22
|
+
<div style="color: var(--text-secondary); font-size: 13px;">tokens</div>
|
|
23
|
+
<div id="token-tier-badge" style="margin-left: auto; padding: 2px 8px; border-radius: 3px; background: rgba(152,152,152,0.2); color: #999; font-size: 10px; font-weight: 600; text-transform: uppercase;">FREE</div>
|
|
24
|
+
</div>
|
|
25
|
+
<div style="font-size: 11px; color: var(--text-secondary);">
|
|
26
|
+
<span id="token-monthly">0</span> / <span id="token-monthly-max">1,000</span> this month
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<!-- Quick Actions -->
|
|
31
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-bottom: 12px;">
|
|
32
|
+
<button id="btn-estimate-op" style="
|
|
33
|
+
padding: 8px; border: 1px solid var(--border-color); background: var(--bg-tertiary);
|
|
34
|
+
border-radius: 4px; color: var(--text-primary); font-size: 11px; cursor: pointer;
|
|
35
|
+
transition: all 150ms; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
36
|
+
" onmouseover="this.style.background='var(--bg-primary)'" onmouseout="this.style.background='var(--bg-tertiary)'">
|
|
37
|
+
📊 Estimate Price
|
|
38
|
+
</button>
|
|
39
|
+
<button id="btn-buy-tokens" style="
|
|
40
|
+
padding: 8px; border: 1px solid var(--accent-blue); background: rgba(88,166,255,0.1);
|
|
41
|
+
border-radius: 4px; color: var(--accent-blue); font-size: 11px; cursor: pointer;
|
|
42
|
+
transition: all 150ms; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 500;
|
|
43
|
+
" onmouseover="this.style.background='rgba(88,166,255,0.2)'" onmouseout="this.style.background='rgba(88,166,255,0.1)'">
|
|
44
|
+
💳 Buy Tokens
|
|
45
|
+
</button>
|
|
46
|
+
</div>
|
|
47
|
+
|
|
48
|
+
<!-- Tier Info -->
|
|
49
|
+
<div style="background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 6px; padding: 10px; margin-bottom: 12px; font-size: 11px;">
|
|
50
|
+
<div style="color: var(--text-secondary); margin-bottom: 6px; font-weight: 500;">YOUR TIER</div>
|
|
51
|
+
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
|
|
52
|
+
<div>
|
|
53
|
+
<div style="color: var(--text-muted); font-size: 10px;">Monthly Allowance</div>
|
|
54
|
+
<div style="font-weight: 600; margin-top: 2px;"><span id="token-allowance">1,000</span> tokens</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div>
|
|
57
|
+
<div style="color: var(--text-muted); font-size: 10px;">Creator Royalty</div>
|
|
58
|
+
<div style="font-weight: 600; margin-top: 2px;" id="token-royalty">70%</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
<button id="btn-upgrade-tier" style="
|
|
62
|
+
margin-top: 8px; width: 100%; padding: 6px; border: 1px solid var(--accent-green);
|
|
63
|
+
background: rgba(63,185,80,0.1); border-radius: 4px; color: var(--accent-green);
|
|
64
|
+
font-size: 11px; font-weight: 500; cursor: pointer; transition: all 150ms;
|
|
65
|
+
" onmouseover="this.style.background='rgba(63,185,80,0.2)'" onmouseout="this.style.background='rgba(63,185,80,0.1)'">
|
|
66
|
+
Upgrade to PRO (€49/mo)
|
|
67
|
+
</button>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<!-- Recent Transactions -->
|
|
71
|
+
<div style="margin-bottom: 12px;">
|
|
72
|
+
<div style="font-size: 11px; color: var(--text-secondary); text-transform: uppercase; margin-bottom: 6px; font-weight: 500;">Recent Activity</div>
|
|
73
|
+
<div id="token-activity-list" style="background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 4px; max-height: 120px; overflow-y: auto;">
|
|
74
|
+
<div style="padding: 12px; color: var(--text-secondary); text-align: center; font-size: 11px;">No transactions yet</div>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<!-- Usage by Operation -->
|
|
79
|
+
<div style="margin-bottom: 12px;">
|
|
80
|
+
<div style="font-size: 11px; color: var(--text-secondary); text-transform: uppercase; margin-bottom: 6px; font-weight: 500;">Top Operations (This Month)</div>
|
|
81
|
+
<div id="token-usage-list" style="background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 4px; max-height: 100px; overflow-y: auto;">
|
|
82
|
+
<div style="padding: 12px; color: var(--text-secondary); text-align: center; font-size: 11px;">No operations yet</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<!-- Footer -->
|
|
87
|
+
<div style="border-top: 1px solid var(--border-color); padding-top: 8px; text-align: center;">
|
|
88
|
+
<button id="btn-token-history" style="
|
|
89
|
+
background: none; border: none; color: var(--accent-blue); cursor: pointer; font-size: 11px;
|
|
90
|
+
text-decoration: underline; opacity: 0.7; transition: opacity 150ms;
|
|
91
|
+
" onmouseover="this.style.opacity='1'" onmouseout="this.style.opacity='0.7'">
|
|
92
|
+
View full history
|
|
93
|
+
</button>
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
`;
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
html: panelHTML,
|
|
100
|
+
init: initTokenDashboardEvents,
|
|
101
|
+
update: updateTokenDashboard,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Initialize event handlers for token dashboard
|
|
107
|
+
*/
|
|
108
|
+
function initTokenDashboardEvents() {
|
|
109
|
+
// Estimate price button
|
|
110
|
+
document.getElementById('btn-estimate-op')?.addEventListener('click', () => {
|
|
111
|
+
showEstimateDialog();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Buy tokens button
|
|
115
|
+
document.getElementById('btn-buy-tokens')?.addEventListener('click', () => {
|
|
116
|
+
showPurchaseDialog();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Upgrade tier button
|
|
120
|
+
document.getElementById('btn-upgrade-tier')?.addEventListener('click', () => {
|
|
121
|
+
showUpgradeDialog();
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// History button
|
|
125
|
+
document.getElementById('btn-token-history')?.addEventListener('click', () => {
|
|
126
|
+
showHistoryModal();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Subscribe to token engine events
|
|
130
|
+
if (window.cycleCAD?.tokens) {
|
|
131
|
+
window.cycleCAD.tokens.on('token-spent', () => updateTokenDashboard());
|
|
132
|
+
window.cycleCAD.tokens.on('token-added', () => updateTokenDashboard());
|
|
133
|
+
window.cycleCAD.tokens.on('month-reset', () => updateTokenDashboard());
|
|
134
|
+
window.cycleCAD.tokens.on('tier-changed', () => updateTokenDashboard());
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
updateTokenDashboard();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Update dashboard display
|
|
142
|
+
*/
|
|
143
|
+
function updateTokenDashboard() {
|
|
144
|
+
if (!window.cycleCAD?.tokens) return;
|
|
145
|
+
|
|
146
|
+
const info = window.cycleCAD.tokens.getBalanceInfo();
|
|
147
|
+
const history = window.cycleCAD.tokens.getTransactionHistory({ limit: 5 });
|
|
148
|
+
const usage = window.cycleCAD.tokens.getUsageByOperation();
|
|
149
|
+
|
|
150
|
+
// Update balance
|
|
151
|
+
document.getElementById('token-balance-value').textContent = formatNumber(info.balance);
|
|
152
|
+
document.getElementById('token-tier-badge').textContent = info.tier;
|
|
153
|
+
document.getElementById('token-tier-badge').style.background = info.tierColor + '22';
|
|
154
|
+
document.getElementById('token-tier-badge').style.color = info.tierColor;
|
|
155
|
+
|
|
156
|
+
// Update monthly usage
|
|
157
|
+
document.getElementById('token-monthly').textContent = formatNumber(info.usedThisMonth);
|
|
158
|
+
document.getElementById('token-monthly-max').textContent = formatNumber(info.monthlyAllowance);
|
|
159
|
+
|
|
160
|
+
// Update tier info
|
|
161
|
+
document.getElementById('token-allowance').textContent = formatNumber(info.monthlyAllowance);
|
|
162
|
+
document.getElementById('token-royalty').textContent = info.creatorRoyalty;
|
|
163
|
+
|
|
164
|
+
// Update recent activity
|
|
165
|
+
const activityList = document.getElementById('token-activity-list');
|
|
166
|
+
if (history.length > 0) {
|
|
167
|
+
activityList.innerHTML = history.map(t => `
|
|
168
|
+
<div style="
|
|
169
|
+
padding: 6px 8px; border-bottom: 1px solid var(--border-color);
|
|
170
|
+
display: flex; justify-content: space-between; align-items: center; font-size: 10px;
|
|
171
|
+
">
|
|
172
|
+
<div>
|
|
173
|
+
<div style="color: var(--text-primary);">${escapeHtml(t.operation)}</div>
|
|
174
|
+
<div style="color: var(--text-muted); margin-top: 2px;">${new Date(t.timestamp).toLocaleDateString()}</div>
|
|
175
|
+
</div>
|
|
176
|
+
<div style="text-align: right; font-weight: 500;">
|
|
177
|
+
<div style="color: ${t.type === 'debit' ? 'var(--accent-red)' : 'var(--accent-green)';};">
|
|
178
|
+
${t.type === 'debit' ? '-' : '+'}${t.amount}
|
|
179
|
+
</div>
|
|
180
|
+
<div style="color: var(--text-secondary); margin-top: 2px;">Balance: ${formatNumber(t.balance_after)}</div>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
`).join('');
|
|
184
|
+
} else {
|
|
185
|
+
activityList.innerHTML = '<div style="padding: 12px; color: var(--text-secondary); text-align: center; font-size: 11px;">No transactions yet</div>';
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Update top operations
|
|
189
|
+
const usageList = document.getElementById('token-usage-list');
|
|
190
|
+
const topOps = Object.entries(usage)
|
|
191
|
+
.sort(([, a], [, b]) => b.totalTokens - a.totalTokens)
|
|
192
|
+
.slice(0, 5);
|
|
193
|
+
|
|
194
|
+
if (topOps.length > 0) {
|
|
195
|
+
usageList.innerHTML = topOps.map(([op, stats]) => `
|
|
196
|
+
<div style="
|
|
197
|
+
padding: 6px 8px; border-bottom: 1px solid var(--border-color);
|
|
198
|
+
display: flex; justify-content: space-between; align-items: center; font-size: 10px;
|
|
199
|
+
">
|
|
200
|
+
<div style="color: var(--text-primary);">${escapeHtml(op)}</div>
|
|
201
|
+
<div style="text-align: right;">
|
|
202
|
+
<div style="font-weight: 600;">${stats.totalTokens} tokens</div>
|
|
203
|
+
<div style="color: var(--text-secondary);">${stats.count}x</div>
|
|
204
|
+
</div>
|
|
205
|
+
</div>
|
|
206
|
+
`).join('');
|
|
207
|
+
} else {
|
|
208
|
+
usageList.innerHTML = '<div style="padding: 12px; color: var(--text-secondary); text-align: center; font-size: 11px;">No operations yet</div>';
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ============================================================================
|
|
213
|
+
// Dialog Functions
|
|
214
|
+
// ============================================================================
|
|
215
|
+
|
|
216
|
+
function showEstimateDialog() {
|
|
217
|
+
const backdrop = document.getElementById('dialog-backdrop');
|
|
218
|
+
if (!backdrop) return;
|
|
219
|
+
|
|
220
|
+
const operations = [
|
|
221
|
+
{ id: 'model.export.stl', name: 'Export as STL', price: 2 },
|
|
222
|
+
{ id: 'model.export.step', name: 'Export as STEP', price: 10 },
|
|
223
|
+
{ id: 'model.export.gltf', name: 'Export as glTF', price: 3 },
|
|
224
|
+
{ id: 'ai.identify.part', name: 'AI Part Identifier', price: 5 },
|
|
225
|
+
{ id: 'ai.design_review', name: 'AI Design Review', price: 15 },
|
|
226
|
+
{ id: 'cam.slice.model', name: 'Slice for 3D Print', price: 20 },
|
|
227
|
+
{ id: 'analysis.weight', name: 'Weight Estimation', price: 2 },
|
|
228
|
+
{ id: 'analysis.cost', name: 'Cost Analysis', price: 5 },
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
const dialog = document.createElement('div');
|
|
232
|
+
dialog.className = 'operation-dialog';
|
|
233
|
+
dialog.style.zIndex = '10001';
|
|
234
|
+
dialog.innerHTML = `
|
|
235
|
+
<div class="dialog-header">
|
|
236
|
+
<div class="dialog-title">Estimate Operation Cost</div>
|
|
237
|
+
<div class="dialog-close-btn" onclick="this.closest('.operation-dialog').remove()">✕</div>
|
|
238
|
+
</div>
|
|
239
|
+
<div class="dialog-content">
|
|
240
|
+
<div class="dialog-form-group">
|
|
241
|
+
<label class="dialog-label">Operation</label>
|
|
242
|
+
<select class="dialog-select" id="estimate-operation" onchange="updateEstimatePreview()">
|
|
243
|
+
${operations.map(op => `<option value="${op.id}">${op.name}</option>`).join('')}
|
|
244
|
+
</select>
|
|
245
|
+
</div>
|
|
246
|
+
<div class="dialog-form-group">
|
|
247
|
+
<label class="dialog-label">Batch Size</label>
|
|
248
|
+
<input type="number" class="dialog-input" id="estimate-batch" value="1" min="1" onchange="updateEstimatePreview()">
|
|
249
|
+
</div>
|
|
250
|
+
<div style="background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 4px; padding: 10px; margin-top: 10px;">
|
|
251
|
+
<div style="font-size: 11px; color: var(--text-secondary); margin-bottom: 4px;">ESTIMATED COST</div>
|
|
252
|
+
<div style="font-size: 20px; font-weight: bold; color: var(--accent-blue); margin-bottom: 4px;">
|
|
253
|
+
<span id="estimate-price">2</span> tokens
|
|
254
|
+
</div>
|
|
255
|
+
<div style="font-size: 10px; color: var(--text-secondary);">
|
|
256
|
+
€<span id="estimate-euros">0.02</span> · <span id="estimate-discount">no discount</span>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
<div class="dialog-footer">
|
|
261
|
+
<button class="dialog-button secondary" onclick="this.closest('.operation-dialog').remove()">Close</button>
|
|
262
|
+
</div>
|
|
263
|
+
`;
|
|
264
|
+
|
|
265
|
+
document.body.appendChild(dialog);
|
|
266
|
+
backdrop.style.display = 'block';
|
|
267
|
+
|
|
268
|
+
// Initial preview
|
|
269
|
+
window.updateEstimatePreview = () => {
|
|
270
|
+
const opId = document.getElementById('estimate-operation')?.value;
|
|
271
|
+
const batchSize = parseInt(document.getElementById('estimate-batch')?.value || 1);
|
|
272
|
+
if (!window.cycleCAD?.tokens || !opId) return;
|
|
273
|
+
|
|
274
|
+
const estimate = window.cycleCAD.tokens.estimateOperation(opId, { batchSize });
|
|
275
|
+
document.getElementById('estimate-price').textContent = estimate.finalPrice;
|
|
276
|
+
document.getElementById('estimate-euros').textContent = (estimate.finalPrice / 100).toFixed(2);
|
|
277
|
+
document.getElementById('estimate-discount').textContent =
|
|
278
|
+
estimate.batchDiscount > 0 ? `${estimate.batchDiscount}% batch discount` : 'no discount';
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
window.updateEstimatePreview();
|
|
282
|
+
|
|
283
|
+
// Close on backdrop click
|
|
284
|
+
backdrop.onclick = (e) => {
|
|
285
|
+
if (e.target === backdrop) {
|
|
286
|
+
backdrop.style.display = 'none';
|
|
287
|
+
dialog.remove();
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function showPurchaseDialog() {
|
|
293
|
+
const backdrop = document.getElementById('dialog-backdrop');
|
|
294
|
+
if (!backdrop) return;
|
|
295
|
+
|
|
296
|
+
const presets = [
|
|
297
|
+
{ tokens: 1000, euros: 10, label: '1,000 tokens' },
|
|
298
|
+
{ tokens: 5000, euros: 50, label: '5,000 tokens (save €0.50)' },
|
|
299
|
+
{ tokens: 10000, euros: 90, label: '10,000 tokens (save €10)' },
|
|
300
|
+
];
|
|
301
|
+
|
|
302
|
+
const dialog = document.createElement('div');
|
|
303
|
+
dialog.className = 'operation-dialog';
|
|
304
|
+
dialog.style.zIndex = '10001';
|
|
305
|
+
dialog.innerHTML = `
|
|
306
|
+
<div class="dialog-header">
|
|
307
|
+
<div class="dialog-title">Purchase Tokens</div>
|
|
308
|
+
<div class="dialog-close-btn" onclick="this.closest('.operation-dialog').remove()">✕</div>
|
|
309
|
+
</div>
|
|
310
|
+
<div class="dialog-content">
|
|
311
|
+
<div class="dialog-form-group">
|
|
312
|
+
<label class="dialog-label">Choose Package</label>
|
|
313
|
+
<div style="display: grid; grid-template-columns: 1fr; gap: 6px;">
|
|
314
|
+
${presets.map((p, i) => `
|
|
315
|
+
<button type="button" style="
|
|
316
|
+
padding: 10px; border: 1px solid var(--border-color); background: var(--bg-tertiary);
|
|
317
|
+
border-radius: 4px; color: var(--text-primary); cursor: pointer; text-align: left;
|
|
318
|
+
transition: all 150ms;
|
|
319
|
+
" onmouseover="this.style.background='rgba(88,166,255,0.1)'; this.style.borderColor='var(--accent-blue)'"
|
|
320
|
+
onmouseout="this.style.background='var(--bg-tertiary)'; this.style.borderColor='var(--border-color)'"
|
|
321
|
+
onclick="selectTokenPackage(${p.tokens})">
|
|
322
|
+
<div style="font-weight: 500; margin-bottom: 2px;">${p.label}</div>
|
|
323
|
+
<div style="font-size: 11px; color: var(--text-secondary);">€${p.euros.toFixed(2)}</div>
|
|
324
|
+
</button>
|
|
325
|
+
`).join('')}
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
<div class="dialog-form-group">
|
|
329
|
+
<label class="dialog-label">Custom Amount</label>
|
|
330
|
+
<input type="number" class="dialog-input" id="custom-tokens" placeholder="100" min="10" value="100">
|
|
331
|
+
</div>
|
|
332
|
+
<div style="background: var(--bg-tertiary); border: 1px solid var(--border-color); border-radius: 4px; padding: 10px; margin-top: 10px;">
|
|
333
|
+
<div style="display: flex; justify-content: space-between; margin-bottom: 6px;">
|
|
334
|
+
<span style="color: var(--text-secondary);">Total:</span>
|
|
335
|
+
<span id="purchase-total" style="font-weight: 600; font-size: 14px;">€1.00</span>
|
|
336
|
+
</div>
|
|
337
|
+
</div>
|
|
338
|
+
<div style="font-size: 10px; color: var(--text-secondary); margin-top: 10px; line-height: 1.6;">
|
|
339
|
+
Conversion: 1 token = €0.01 · Payment via Stripe
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
<div class="dialog-footer">
|
|
343
|
+
<button class="dialog-button secondary" onclick="this.closest('.operation-dialog').remove()">Cancel</button>
|
|
344
|
+
<button class="dialog-button primary" onclick="completePurchaseFlow()">Proceed to Checkout</button>
|
|
345
|
+
</div>
|
|
346
|
+
`;
|
|
347
|
+
|
|
348
|
+
document.body.appendChild(dialog);
|
|
349
|
+
backdrop.style.display = 'block';
|
|
350
|
+
|
|
351
|
+
window.selectTokenPackage = (tokens) => {
|
|
352
|
+
document.getElementById('custom-tokens').value = tokens;
|
|
353
|
+
updatePurchasePreview();
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
window.updatePurchasePreview = () => {
|
|
357
|
+
const tokens = parseInt(document.getElementById('custom-tokens')?.value || 100);
|
|
358
|
+
const euros = (tokens / 100).toFixed(2);
|
|
359
|
+
document.getElementById('purchase-total').textContent = `€${euros}`;
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
window.completePurchaseFlow = () => {
|
|
363
|
+
const tokens = parseInt(document.getElementById('custom-tokens')?.value || 100);
|
|
364
|
+
if (window.cycleCAD?.tokens) {
|
|
365
|
+
const session = window.cycleCAD.tokens.purchaseTokens(tokens, 'stripe');
|
|
366
|
+
alert(`Purchase initiated!\nTokens: ${tokens}\nURL: ${session.stripeCheckoutUrl}\n\n(Demo mode — would open Stripe checkout)`);
|
|
367
|
+
}
|
|
368
|
+
dialog.remove();
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// Close on backdrop click
|
|
372
|
+
backdrop.onclick = (e) => {
|
|
373
|
+
if (e.target === backdrop) {
|
|
374
|
+
backdrop.style.display = 'none';
|
|
375
|
+
dialog.remove();
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
updatePurchasePreview();
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function showUpgradeDialog() {
|
|
383
|
+
const backdrop = document.getElementById('dialog-backdrop');
|
|
384
|
+
if (!backdrop) return;
|
|
385
|
+
|
|
386
|
+
const tiers = [
|
|
387
|
+
{
|
|
388
|
+
name: 'FREE',
|
|
389
|
+
price: 'Free',
|
|
390
|
+
tokens: 1000,
|
|
391
|
+
royalty: '70%',
|
|
392
|
+
features: ['1,000 tokens/month', 'Basic exports', 'Community support'],
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
name: 'PRO',
|
|
396
|
+
price: '€49/mo',
|
|
397
|
+
tokens: 10000,
|
|
398
|
+
royalty: '80%',
|
|
399
|
+
features: ['10,000 tokens/month', 'STEP import/export', 'AI design review', 'Priority support'],
|
|
400
|
+
highlight: true,
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
name: 'ENTERPRISE',
|
|
404
|
+
price: '€299/mo',
|
|
405
|
+
tokens: 100000,
|
|
406
|
+
royalty: '90%',
|
|
407
|
+
features: ['100,000 tokens/month', 'Unlimited STEP', 'Advanced analytics', 'SLA & support', 'Custom integrations'],
|
|
408
|
+
},
|
|
409
|
+
];
|
|
410
|
+
|
|
411
|
+
const dialog = document.createElement('div');
|
|
412
|
+
dialog.className = 'operation-dialog';
|
|
413
|
+
dialog.style.zIndex = '10001';
|
|
414
|
+
dialog.style.maxWidth = '600px';
|
|
415
|
+
dialog.innerHTML = `
|
|
416
|
+
<div class="dialog-header">
|
|
417
|
+
<div class="dialog-title">Upgrade to Higher Tier</div>
|
|
418
|
+
<div class="dialog-close-btn" onclick="this.closest('.operation-dialog').remove()">✕</div>
|
|
419
|
+
</div>
|
|
420
|
+
<div class="dialog-content" style="max-height: 400px; overflow-y: auto;">
|
|
421
|
+
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 8px;">
|
|
422
|
+
${tiers.map(tier => `
|
|
423
|
+
<div style="
|
|
424
|
+
border: 2px solid ${tier.highlight ? 'var(--accent-blue)' : 'var(--border-color)'};
|
|
425
|
+
border-radius: 6px; padding: 12px; background: ${tier.highlight ? 'rgba(88,166,255,0.05)' : 'var(--bg-tertiary)'};
|
|
426
|
+
position: relative;
|
|
427
|
+
">
|
|
428
|
+
${tier.highlight ? '<div style="position: absolute; top: -10px; right: 12px; background: var(--accent-blue); color: white; padding: 2px 8px; font-size: 9px; border-radius: 3px; font-weight: 600;">POPULAR</div>' : ''}
|
|
429
|
+
<div style="font-weight: 600; margin-bottom: 4px; font-size: 12px;">${tier.name}</div>
|
|
430
|
+
<div style="font-size: 14px; font-weight: bold; color: var(--accent-blue); margin-bottom: 8px;">${tier.price}</div>
|
|
431
|
+
<div style="font-size: 10px; color: var(--text-secondary); margin-bottom: 6px;">
|
|
432
|
+
<div>${tier.tokens.toLocaleString()} tokens/month</div>
|
|
433
|
+
<div>${tier.royalty} creator royalty</div>
|
|
434
|
+
</div>
|
|
435
|
+
<div style="border-top: 1px solid var(--border-color); padding-top: 8px;">
|
|
436
|
+
${tier.features.map(f => `<div style="font-size: 9px; color: var(--text-secondary); margin-bottom: 3px;">✓ ${f}</div>`).join('')}
|
|
437
|
+
</div>
|
|
438
|
+
<button style="
|
|
439
|
+
margin-top: 8px; width: 100%; padding: 6px; border: 1px solid ${tier.highlight ? 'var(--accent-blue)' : 'var(--border-color)'};
|
|
440
|
+
background: ${tier.highlight ? 'rgba(88,166,255,0.1)' : 'var(--bg-primary)'}; border-radius: 3px;
|
|
441
|
+
color: ${tier.highlight ? 'var(--accent-blue)' : 'var(--text-primary)'}; font-size: 10px; cursor: pointer;
|
|
442
|
+
transition: all 150ms;
|
|
443
|
+
" onclick="upgradeTo('${tier.name}')"
|
|
444
|
+
onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'">
|
|
445
|
+
${tier.name === 'FREE' ? 'Current' : 'Upgrade'}
|
|
446
|
+
</button>
|
|
447
|
+
</div>
|
|
448
|
+
`).join('')}
|
|
449
|
+
</div>
|
|
450
|
+
</div>
|
|
451
|
+
<div class="dialog-footer">
|
|
452
|
+
<button class="dialog-button secondary" onclick="this.closest('.operation-dialog').remove()">Cancel</button>
|
|
453
|
+
</div>
|
|
454
|
+
`;
|
|
455
|
+
|
|
456
|
+
document.body.appendChild(dialog);
|
|
457
|
+
backdrop.style.display = 'block';
|
|
458
|
+
|
|
459
|
+
window.upgradeTo = (tier) => {
|
|
460
|
+
if (window.cycleCAD?.tokens) {
|
|
461
|
+
window.cycleCAD.tokens.setTier(tier);
|
|
462
|
+
alert(`Upgraded to ${tier} tier! Refreshing dashboard...`);
|
|
463
|
+
updateTokenDashboard();
|
|
464
|
+
dialog.remove();
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
backdrop.onclick = (e) => {
|
|
469
|
+
if (e.target === backdrop) {
|
|
470
|
+
backdrop.style.display = 'none';
|
|
471
|
+
dialog.remove();
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function showHistoryModal() {
|
|
477
|
+
if (!window.cycleCAD?.tokens) return;
|
|
478
|
+
|
|
479
|
+
const history = window.cycleCAD.tokens.getTransactionHistory({ limit: 100 });
|
|
480
|
+
const backdrop = document.getElementById('dialog-backdrop');
|
|
481
|
+
if (!backdrop) return;
|
|
482
|
+
|
|
483
|
+
const dialog = document.createElement('div');
|
|
484
|
+
dialog.className = 'operation-dialog';
|
|
485
|
+
dialog.style.zIndex = '10001';
|
|
486
|
+
dialog.style.maxWidth = '500px';
|
|
487
|
+
dialog.innerHTML = `
|
|
488
|
+
<div class="dialog-header">
|
|
489
|
+
<div class="dialog-title">Token Transaction History</div>
|
|
490
|
+
<div class="dialog-close-btn" onclick="this.closest('.operation-dialog').remove()">✕</div>
|
|
491
|
+
</div>
|
|
492
|
+
<div class="dialog-content" style="max-height: 400px; overflow-y: auto;">
|
|
493
|
+
<table style="width: 100%; border-collapse: collapse; font-size: 11px;">
|
|
494
|
+
<thead>
|
|
495
|
+
<tr style="border-bottom: 1px solid var(--border-color);">
|
|
496
|
+
<th style="text-align: left; padding: 6px; color: var(--text-secondary); font-weight: 500;">Date</th>
|
|
497
|
+
<th style="text-align: left; padding: 6px; color: var(--text-secondary); font-weight: 500;">Operation</th>
|
|
498
|
+
<th style="text-align: right; padding: 6px; color: var(--text-secondary); font-weight: 500;">Amount</th>
|
|
499
|
+
<th style="text-align: right; padding: 6px; color: var(--text-secondary); font-weight: 500;">Balance</th>
|
|
500
|
+
</tr>
|
|
501
|
+
</thead>
|
|
502
|
+
<tbody>
|
|
503
|
+
${history.map(t => `
|
|
504
|
+
<tr style="border-bottom: 1px solid var(--border-color);">
|
|
505
|
+
<td style="padding: 6px; color: var(--text-secondary);">${new Date(t.timestamp).toLocaleDateString()}</td>
|
|
506
|
+
<td style="padding: 6px; color: var(--text-primary);">${escapeHtml(t.operation)}</td>
|
|
507
|
+
<td style="text-align: right; padding: 6px; color: ${t.type === 'debit' ? 'var(--accent-red)' : 'var(--accent-green)'}; font-weight: 500;">${t.type === 'debit' ? '-' : '+'}${t.amount}</td>
|
|
508
|
+
<td style="text-align: right; padding: 6px; color: var(--text-secondary);">${formatNumber(t.balance_after)}</td>
|
|
509
|
+
</tr>
|
|
510
|
+
`).join('')}
|
|
511
|
+
</tbody>
|
|
512
|
+
</table>
|
|
513
|
+
</div>
|
|
514
|
+
<div class="dialog-footer">
|
|
515
|
+
<button class="dialog-button secondary" onclick="this.closest('.operation-dialog').remove()">Close</button>
|
|
516
|
+
<button class="dialog-button primary" onclick="exportTokenHistory()">Export CSV</button>
|
|
517
|
+
</div>
|
|
518
|
+
`;
|
|
519
|
+
|
|
520
|
+
document.body.appendChild(dialog);
|
|
521
|
+
backdrop.style.display = 'block';
|
|
522
|
+
|
|
523
|
+
window.exportTokenHistory = () => {
|
|
524
|
+
const csv = [
|
|
525
|
+
['Date', 'Operation', 'Type', 'Amount', 'Balance'],
|
|
526
|
+
...history.map(t => [
|
|
527
|
+
new Date(t.timestamp).toLocaleDateString(),
|
|
528
|
+
t.operation,
|
|
529
|
+
t.type,
|
|
530
|
+
t.amount,
|
|
531
|
+
t.balance_after,
|
|
532
|
+
]),
|
|
533
|
+
].map(row => row.join(',')).join('\n');
|
|
534
|
+
|
|
535
|
+
const blob = new Blob([csv], { type: 'text/csv' });
|
|
536
|
+
const url = URL.createObjectURL(blob);
|
|
537
|
+
const a = document.createElement('a');
|
|
538
|
+
a.href = url;
|
|
539
|
+
a.download = `cyclecad-tokens-${new Date().toISOString().slice(0, 10)}.csv`;
|
|
540
|
+
a.click();
|
|
541
|
+
URL.revokeObjectURL(url);
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
backdrop.onclick = (e) => {
|
|
545
|
+
if (e.target === backdrop) {
|
|
546
|
+
backdrop.style.display = 'none';
|
|
547
|
+
dialog.remove();
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// ============================================================================
|
|
553
|
+
// Utility Functions
|
|
554
|
+
// ============================================================================
|
|
555
|
+
|
|
556
|
+
function formatNumber(num) {
|
|
557
|
+
return num.toLocaleString();
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function escapeHtml(text) {
|
|
561
|
+
const map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
562
|
+
return text.replace(/[&<>"']/g, m => map[m]);
|
|
563
|
+
}
|