Spaces:
Sleeping
Sleeping
Commit ·
f873b45
1
Parent(s): 737682f
feat: add Chat/Guided tab switcher, wizard, plan card, 3MF downloads, i18n
Browse files- web/index.html +506 -0
web/index.html
CHANGED
|
@@ -1171,6 +1171,151 @@
|
|
| 1171 |
animation: fade-in-up 0.3s ease-out both;
|
| 1172 |
}
|
| 1173 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1174 |
/* ---- RESPONSIVE ---- */
|
| 1175 |
|
| 1176 |
@media (max-width: 768px) {
|
|
@@ -1230,6 +1375,7 @@
|
|
| 1230 |
<div id="download-btns">
|
| 1231 |
<a class="dl-btn" id="dl-step" download>STEP</a>
|
| 1232 |
<a class="dl-btn" id="dl-stl" download>STL</a>
|
|
|
|
| 1233 |
<a id="dl-gcode" class="dl-btn" download style="display:none"><span class="dl-icon">↧</span> G-CODE</a>
|
| 1234 |
<a class="dl-btn" id="dl-report" download>REPORT</a>
|
| 1235 |
</div>
|
|
@@ -1257,6 +1403,11 @@
|
|
| 1257 |
<div id="chat-panel">
|
| 1258 |
<button id="chat-toggle" onclick="toggleChat()" title="Toggle chat panel">◀</button>
|
| 1259 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1260 |
<div class="chat-header">
|
| 1261 |
<div class="chat-header-left">
|
| 1262 |
<span class="chat-header-title" data-i18n="designChat">Design Chat</span>
|
|
@@ -1282,6 +1433,84 @@
|
|
| 1282 |
</div>
|
| 1283 |
</div>
|
| 1284 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1285 |
<div class="chat-input-area" style="position: relative;">
|
| 1286 |
<div id="mention-dropdown">
|
| 1287 |
<div class="mention-option" data-agent="design" onclick="insertMention('design')">
|
|
@@ -1372,6 +1601,255 @@ let mentionActive = false;
|
|
| 1372 |
let mentionIndex = 0;
|
| 1373 |
let currentLang = localStorage.getItem('neuralcad_lang') || 'en';
|
| 1374 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1375 |
// ── i18n ─────────────────────────────────────────────
|
| 1376 |
|
| 1377 |
const I18N = {
|
|
@@ -1406,6 +1884,11 @@ const I18N = {
|
|
| 1406 |
cadFailed: 'CAD execution failed: ',
|
| 1407 |
errorPrefix: 'Error: ',
|
| 1408 |
sendPreviewMsg: '@cad Generate a 3D preview based on our discussion',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1409 |
},
|
| 1410 |
'zh-TW': {
|
| 1411 |
subtitle: '\u591a\u4ee3\u7406\u8a2d\u8a08',
|
|
@@ -1438,6 +1921,11 @@ const I18N = {
|
|
| 1438 |
cadFailed: 'CAD \u57f7\u884c\u5931\u6557\uff1a',
|
| 1439 |
errorPrefix: '\u932f\u8aa4\uff1a',
|
| 1440 |
sendPreviewMsg: '@cad \u6839\u64da\u6211\u5011\u7684\u8a0e\u8ad6\u751f\u6210 3D \u9810\u89bd',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1441 |
},
|
| 1442 |
vi: {
|
| 1443 |
subtitle: 'Thi\u1ebft K\u1ebf \u0110a T\u00e1c T\u1eed',
|
|
@@ -1470,6 +1958,11 @@ const I18N = {
|
|
| 1470 |
cadFailed: 'Th\u1ef1c thi CAD th\u1ea5t b\u1ea1i: ',
|
| 1471 |
errorPrefix: 'L\u1ed7i: ',
|
| 1472 |
sendPreviewMsg: '@cad T\u1ea1o b\u1ea3n xem tr\u01b0\u1edbc 3D d\u1ef1a tr\u00ean cu\u1ed9c th\u1ea3o lu\u1eadn c\u1ee7a ch\u00fang ta',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1473 |
},
|
| 1474 |
};
|
| 1475 |
|
|
@@ -1959,6 +2452,17 @@ async function sendMessage(text) {
|
|
| 1959 |
}
|
| 1960 |
saveState();
|
| 1961 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1962 |
// If preview available, load 3D model
|
| 1963 |
if (data.preview && data.preview.success) {
|
| 1964 |
setViewerLoading(true, t('loadingModel'));
|
|
@@ -2261,6 +2765,7 @@ function updateDownloads(partName) {
|
|
| 2261 |
|
| 2262 |
document.getElementById('dl-step').href = '/api/models/' + partName + '.step';
|
| 2263 |
document.getElementById('dl-stl').href = '/api/models/' + partName + '.stl';
|
|
|
|
| 2264 |
document.getElementById('dl-report').href = '/api/models/' + partName + '_report.json';
|
| 2265 |
|
| 2266 |
const dlGcode = document.getElementById('dl-gcode');
|
|
@@ -2352,6 +2857,7 @@ function renderGallery() {
|
|
| 2352 |
html += '<div class="gallery-card-downloads">';
|
| 2353 |
html += '<a class="gallery-dl" href="/api/models/' + name + '.step" download>STEP</a>';
|
| 2354 |
html += '<a class="gallery-dl" href="/api/models/' + name + '.stl" download>STL</a>';
|
|
|
|
| 2355 |
html += '<a class="gallery-dl" href="/api/models/' + name + '.gcode" download>GCODE</a>';
|
| 2356 |
html += '</div>';
|
| 2357 |
html += '</div>';
|
|
|
|
| 1171 |
animation: fade-in-up 0.3s ease-out both;
|
| 1172 |
}
|
| 1173 |
|
| 1174 |
+
/* ---- TAB SWITCHER ---- */
|
| 1175 |
+
.chat-tabs {
|
| 1176 |
+
display: flex;
|
| 1177 |
+
border-bottom: 1px solid var(--border);
|
| 1178 |
+
background: var(--bg-panel);
|
| 1179 |
+
flex-shrink: 0;
|
| 1180 |
+
}
|
| 1181 |
+
.chat-tab {
|
| 1182 |
+
flex: 1;
|
| 1183 |
+
padding: 8px 0;
|
| 1184 |
+
text-align: center;
|
| 1185 |
+
font-family: var(--font-mono);
|
| 1186 |
+
font-size: 11px;
|
| 1187 |
+
font-weight: 500;
|
| 1188 |
+
color: var(--text-secondary);
|
| 1189 |
+
background: none;
|
| 1190 |
+
border: none;
|
| 1191 |
+
cursor: pointer;
|
| 1192 |
+
border-bottom: 2px solid transparent;
|
| 1193 |
+
transition: color 0.2s, border-color 0.2s;
|
| 1194 |
+
}
|
| 1195 |
+
.chat-tab:hover { color: var(--text-primary); }
|
| 1196 |
+
.chat-tab.active {
|
| 1197 |
+
color: var(--accent);
|
| 1198 |
+
border-bottom-color: var(--accent);
|
| 1199 |
+
}
|
| 1200 |
+
#guided-panel { display: none; overflow-y: auto; flex: 1; padding: 12px; }
|
| 1201 |
+
#guided-panel.active { display: flex; flex-direction: column; gap: 12px; }
|
| 1202 |
+
#chat-messages.hidden { display: none; }
|
| 1203 |
+
|
| 1204 |
+
/* ---- WIZARD STEPS ---- */
|
| 1205 |
+
.wizard-step {
|
| 1206 |
+
background: var(--bg-surface);
|
| 1207 |
+
border: 1px solid var(--border);
|
| 1208 |
+
border-radius: 8px;
|
| 1209 |
+
padding: 12px;
|
| 1210 |
+
}
|
| 1211 |
+
.wizard-step.completed { border-color: var(--success); }
|
| 1212 |
+
.wizard-step-header {
|
| 1213 |
+
display: flex; justify-content: space-between; align-items: center;
|
| 1214 |
+
margin-bottom: 8px;
|
| 1215 |
+
}
|
| 1216 |
+
.wizard-step-title {
|
| 1217 |
+
font-family: var(--font-mono); font-size: 11px;
|
| 1218 |
+
color: var(--text-secondary); font-weight: 600;
|
| 1219 |
+
}
|
| 1220 |
+
.wizard-step-check { color: var(--success); font-size: 14px; }
|
| 1221 |
+
.wizard-chips {
|
| 1222 |
+
display: flex; flex-wrap: wrap; gap: 6px;
|
| 1223 |
+
}
|
| 1224 |
+
.wizard-chip {
|
| 1225 |
+
padding: 5px 12px; border-radius: 14px;
|
| 1226 |
+
font-family: var(--font-mono); font-size: 11px;
|
| 1227 |
+
background: var(--bg-input); border: 1px solid var(--border);
|
| 1228 |
+
color: var(--text-primary); cursor: pointer;
|
| 1229 |
+
transition: border-color 0.15s, background 0.15s;
|
| 1230 |
+
}
|
| 1231 |
+
.wizard-chip:hover { border-color: var(--accent); }
|
| 1232 |
+
.wizard-chip.selected {
|
| 1233 |
+
border-color: var(--accent); background: var(--accent-glow);
|
| 1234 |
+
color: var(--accent);
|
| 1235 |
+
}
|
| 1236 |
+
.wizard-input {
|
| 1237 |
+
width: 100%; padding: 6px 10px; margin-top: 6px;
|
| 1238 |
+
background: var(--bg-input); border: 1px solid var(--border);
|
| 1239 |
+
border-radius: 6px; color: var(--text-primary);
|
| 1240 |
+
font-family: var(--font-mono); font-size: 12px;
|
| 1241 |
+
}
|
| 1242 |
+
.wizard-input:focus { outline: none; border-color: var(--accent); }
|
| 1243 |
+
.wizard-dim-row {
|
| 1244 |
+
display: flex; gap: 8px; align-items: center; margin-top: 6px;
|
| 1245 |
+
}
|
| 1246 |
+
.wizard-dim-label {
|
| 1247 |
+
font-family: var(--font-mono); font-size: 11px;
|
| 1248 |
+
color: var(--text-secondary); min-width: 50px;
|
| 1249 |
+
}
|
| 1250 |
+
.wizard-dim-input {
|
| 1251 |
+
width: 80px; padding: 4px 8px;
|
| 1252 |
+
background: var(--bg-input); border: 1px solid var(--border);
|
| 1253 |
+
border-radius: 4px; color: var(--text-primary);
|
| 1254 |
+
font-family: var(--font-mono); font-size: 12px;
|
| 1255 |
+
}
|
| 1256 |
+
.wizard-dim-unit {
|
| 1257 |
+
font-family: var(--font-mono); font-size: 10px;
|
| 1258 |
+
color: var(--text-muted);
|
| 1259 |
+
}
|
| 1260 |
+
.wizard-review-field {
|
| 1261 |
+
display: flex; justify-content: space-between; align-items: center;
|
| 1262 |
+
padding: 4px 0; border-bottom: 1px solid var(--border);
|
| 1263 |
+
}
|
| 1264 |
+
.wizard-review-label {
|
| 1265 |
+
font-family: var(--font-mono); font-size: 10px;
|
| 1266 |
+
color: var(--text-secondary);
|
| 1267 |
+
}
|
| 1268 |
+
.wizard-review-value {
|
| 1269 |
+
font-family: var(--font-mono); font-size: 11px;
|
| 1270 |
+
color: var(--text-primary);
|
| 1271 |
+
}
|
| 1272 |
+
.wizard-btn-row { display: flex; gap: 8px; margin-top: 10px; }
|
| 1273 |
+
.wizard-btn {
|
| 1274 |
+
flex: 1; padding: 8px; border-radius: 6px;
|
| 1275 |
+
font-family: var(--font-mono); font-size: 11px;
|
| 1276 |
+
font-weight: 600; cursor: pointer; border: 1px solid var(--border);
|
| 1277 |
+
transition: background 0.15s;
|
| 1278 |
+
}
|
| 1279 |
+
.wizard-btn-primary {
|
| 1280 |
+
background: var(--accent); color: var(--bg-void); border-color: var(--accent);
|
| 1281 |
+
}
|
| 1282 |
+
.wizard-btn-secondary {
|
| 1283 |
+
background: var(--bg-surface); color: var(--text-secondary);
|
| 1284 |
+
}
|
| 1285 |
+
|
| 1286 |
+
/* ---- PLAN CARD ---- */
|
| 1287 |
+
.plan-card {
|
| 1288 |
+
background: var(--bg-surface);
|
| 1289 |
+
border: 1px solid var(--accent);
|
| 1290 |
+
border-radius: 8px;
|
| 1291 |
+
padding: 14px;
|
| 1292 |
+
margin: 8px 0;
|
| 1293 |
+
}
|
| 1294 |
+
.plan-card-title {
|
| 1295 |
+
font-family: var(--font-mono);
|
| 1296 |
+
font-size: 11px;
|
| 1297 |
+
font-weight: 700;
|
| 1298 |
+
color: var(--accent);
|
| 1299 |
+
margin-bottom: 10px;
|
| 1300 |
+
}
|
| 1301 |
+
.plan-card .wizard-review-field { padding: 3px 0; }
|
| 1302 |
+
.plan-card-score {
|
| 1303 |
+
font-family: var(--font-mono); font-size: 11px;
|
| 1304 |
+
color: var(--text-secondary); margin-top: 8px;
|
| 1305 |
+
}
|
| 1306 |
+
.plan-card-actions { display: flex; gap: 8px; margin-top: 10px; }
|
| 1307 |
+
.plan-card-btn {
|
| 1308 |
+
flex: 1; padding: 7px; border-radius: 5px;
|
| 1309 |
+
font-family: var(--font-mono); font-size: 11px;
|
| 1310 |
+
font-weight: 600; cursor: pointer; border: 1px solid var(--border);
|
| 1311 |
+
}
|
| 1312 |
+
.plan-card-approve {
|
| 1313 |
+
background: var(--success); color: var(--bg-void); border-color: var(--success);
|
| 1314 |
+
}
|
| 1315 |
+
.plan-card-reject {
|
| 1316 |
+
background: var(--bg-surface); color: var(--text-secondary);
|
| 1317 |
+
}
|
| 1318 |
+
|
| 1319 |
/* ---- RESPONSIVE ---- */
|
| 1320 |
|
| 1321 |
@media (max-width: 768px) {
|
|
|
|
| 1375 |
<div id="download-btns">
|
| 1376 |
<a class="dl-btn" id="dl-step" download>STEP</a>
|
| 1377 |
<a class="dl-btn" id="dl-stl" download>STL</a>
|
| 1378 |
+
<a class="dl-btn" id="dl-3mf" download>3MF</a>
|
| 1379 |
<a id="dl-gcode" class="dl-btn" download style="display:none"><span class="dl-icon">↧</span> G-CODE</a>
|
| 1380 |
<a class="dl-btn" id="dl-report" download>REPORT</a>
|
| 1381 |
</div>
|
|
|
|
| 1403 |
<div id="chat-panel">
|
| 1404 |
<button id="chat-toggle" onclick="toggleChat()" title="Toggle chat panel">◀</button>
|
| 1405 |
|
| 1406 |
+
<div class="chat-tabs">
|
| 1407 |
+
<button class="chat-tab active" id="tab-chat" onclick="switchTab('chat')">Chat</button>
|
| 1408 |
+
<button class="chat-tab" id="tab-guided" onclick="switchTab('guided')">Guided</button>
|
| 1409 |
+
</div>
|
| 1410 |
+
|
| 1411 |
<div class="chat-header">
|
| 1412 |
<div class="chat-header-left">
|
| 1413 |
<span class="chat-header-title" data-i18n="designChat">Design Chat</span>
|
|
|
|
| 1433 |
</div>
|
| 1434 |
</div>
|
| 1435 |
|
| 1436 |
+
<div id="guided-panel">
|
| 1437 |
+
<div class="wizard-step" id="wiz-step-1">
|
| 1438 |
+
<div class="wizard-step-header"><span class="wizard-step-title">1. PART TYPE</span><span class="wizard-step-check" id="wiz-check-1"></span></div>
|
| 1439 |
+
<div class="wizard-chips">
|
| 1440 |
+
<button class="wizard-chip" onclick="wizSetPart('bracket','Mounting bracket')">Bracket</button>
|
| 1441 |
+
<button class="wizard-chip" onclick="wizSetPart('enclosure','Enclosure housing')">Enclosure</button>
|
| 1442 |
+
<button class="wizard-chip" onclick="wizSetPart('plate','Flat plate')">Plate</button>
|
| 1443 |
+
<button class="wizard-chip" onclick="wizSetPart('shaft','Cylindrical shaft')">Shaft</button>
|
| 1444 |
+
<button class="wizard-chip" onclick="wizSetPart('gear','Spur gear')">Gear</button>
|
| 1445 |
+
<button class="wizard-chip" onclick="wizSetPart('flange','Pipe flange')">Flange</button>
|
| 1446 |
+
</div>
|
| 1447 |
+
<input class="wizard-input" placeholder="Or type custom part name..." onchange="wizSetPart(this.value, this.value)">
|
| 1448 |
+
</div>
|
| 1449 |
+
<div class="wizard-step" id="wiz-step-2">
|
| 1450 |
+
<div class="wizard-step-header"><span class="wizard-step-title">2. MATERIAL</span><span class="wizard-step-check" id="wiz-check-2"></span></div>
|
| 1451 |
+
<div class="wizard-chips">
|
| 1452 |
+
<button class="wizard-chip" onclick="wizSetMaterial('aluminum 6061')">Aluminum 6061</button>
|
| 1453 |
+
<button class="wizard-chip" onclick="wizSetMaterial('aluminum 7075')">Aluminum 7075</button>
|
| 1454 |
+
<button class="wizard-chip" onclick="wizSetMaterial('stainless steel 304')">Steel 304</button>
|
| 1455 |
+
<button class="wizard-chip" onclick="wizSetMaterial('stainless steel 316')">Steel 316</button>
|
| 1456 |
+
<button class="wizard-chip" onclick="wizSetMaterial('brass')">Brass</button>
|
| 1457 |
+
<button class="wizard-chip" onclick="wizSetMaterial('titanium')">Titanium</button>
|
| 1458 |
+
<button class="wizard-chip" onclick="wizSetMaterial('nylon')">Nylon</button>
|
| 1459 |
+
<button class="wizard-chip" onclick="wizSetMaterial('delrin')">Delrin</button>
|
| 1460 |
+
</div>
|
| 1461 |
+
<input class="wizard-input" placeholder="Or type custom material..." onchange="wizSetMaterial(this.value)">
|
| 1462 |
+
</div>
|
| 1463 |
+
<div class="wizard-step" id="wiz-step-3">
|
| 1464 |
+
<div class="wizard-step-header"><span class="wizard-step-title">3. DIMENSIONS (mm)</span><span class="wizard-step-check" id="wiz-check-3"></span></div>
|
| 1465 |
+
<div class="wizard-dim-row"><span class="wizard-dim-label">Width</span><input class="wizard-dim-input" id="wiz-dim-width" type="number" onchange="wizSetDim('width', this.value)"><span class="wizard-dim-unit">mm</span></div>
|
| 1466 |
+
<div class="wizard-dim-row"><span class="wizard-dim-label">Height</span><input class="wizard-dim-input" id="wiz-dim-height" type="number" onchange="wizSetDim('height', this.value)"><span class="wizard-dim-unit">mm</span></div>
|
| 1467 |
+
<div class="wizard-dim-row"><span class="wizard-dim-label">Depth</span><input class="wizard-dim-input" id="wiz-dim-depth" type="number" onchange="wizSetDim('depth', this.value)"><span class="wizard-dim-unit">mm</span></div>
|
| 1468 |
+
</div>
|
| 1469 |
+
<div class="wizard-step" id="wiz-step-4">
|
| 1470 |
+
<div class="wizard-step-header"><span class="wizard-step-title">4. FEATURES</span><span class="wizard-step-check" id="wiz-check-4"></span></div>
|
| 1471 |
+
<div class="wizard-chips">
|
| 1472 |
+
<button class="wizard-chip" id="wiz-feat-holes" onclick="wizToggleFeature(this, 'holes')">Mounting Holes</button>
|
| 1473 |
+
<button class="wizard-chip" id="wiz-feat-fillets" onclick="wizToggleFeature(this, 'fillets')">Fillets</button>
|
| 1474 |
+
<button class="wizard-chip" id="wiz-feat-chamfers" onclick="wizToggleFeature(this, 'chamfers')">Chamfers</button>
|
| 1475 |
+
<button class="wizard-chip" id="wiz-feat-pockets" onclick="wizToggleFeature(this, 'pockets')">Pockets</button>
|
| 1476 |
+
<button class="wizard-chip" id="wiz-feat-slots" onclick="wizToggleFeature(this, 'slots')">Slots</button>
|
| 1477 |
+
</div>
|
| 1478 |
+
<div id="wiz-holes-config" style="display:none;margin-top:8px;">
|
| 1479 |
+
<div class="wizard-dim-row"><span class="wizard-dim-label">Count</span><input class="wizard-dim-input" id="wiz-hole-count" type="number" value="4" min="1" onchange="wizUpdateHoles()"></div>
|
| 1480 |
+
<div class="wizard-chips" style="margin-top:6px;">
|
| 1481 |
+
<button class="wizard-chip" id="wiz-hole-m3" onclick="wizSetHoleSize('M3')">M3</button>
|
| 1482 |
+
<button class="wizard-chip" id="wiz-hole-m4" onclick="wizSetHoleSize('M4')">M4</button>
|
| 1483 |
+
<button class="wizard-chip selected" id="wiz-hole-m6" onclick="wizSetHoleSize('M6')">M6</button>
|
| 1484 |
+
<button class="wizard-chip" id="wiz-hole-m8" onclick="wizSetHoleSize('M8')">M8</button>
|
| 1485 |
+
</div>
|
| 1486 |
+
</div>
|
| 1487 |
+
<input class="wizard-input" placeholder="Or type custom feature..." onkeydown="if(event.key==='Enter'){wizAddCustomFeature(this.value);this.value='';}">
|
| 1488 |
+
</div>
|
| 1489 |
+
<div class="wizard-step" id="wiz-step-5">
|
| 1490 |
+
<div class="wizard-step-header"><span class="wizard-step-title">5. CONSTRAINTS</span><span class="wizard-step-check" id="wiz-check-5"></span></div>
|
| 1491 |
+
<div class="wizard-dim-row"><span class="wizard-dim-label">Min wall</span><input class="wizard-dim-input" id="wiz-min-wall" type="number" value="3" step="0.5" onchange="wizUpdateConstraints()"><span class="wizard-dim-unit">mm</span></div>
|
| 1492 |
+
<div class="wizard-dim-row"><span class="wizard-dim-label">Max size</span><input class="wizard-dim-input" id="wiz-max-size" type="number" value="500" onchange="wizUpdateConstraints()"><span class="wizard-dim-unit">mm</span></div>
|
| 1493 |
+
</div>
|
| 1494 |
+
<div class="wizard-step" id="wiz-step-6">
|
| 1495 |
+
<div class="wizard-step-header"><span class="wizard-step-title">6. MACHINING</span><span class="wizard-step-check" id="wiz-check-6"></span></div>
|
| 1496 |
+
<div class="wizard-chips">
|
| 1497 |
+
<button class="wizard-chip" onclick="wizSetAxis('3-axis', this)">3-axis</button>
|
| 1498 |
+
<button class="wizard-chip" onclick="wizSetAxis('3+2-axis', this)">3+2-axis</button>
|
| 1499 |
+
<button class="wizard-chip" onclick="wizSetAxis('5-axis', this)">5-axis</button>
|
| 1500 |
+
<button class="wizard-chip" onclick="wizSetAxis('', this)">Auto</button>
|
| 1501 |
+
</div>
|
| 1502 |
+
</div>
|
| 1503 |
+
<div class="wizard-step" id="wiz-step-7">
|
| 1504 |
+
<div class="wizard-step-header"><span class="wizard-step-title">7. REVIEW</span><span class="wizard-step-check" id="wiz-check-7"></span></div>
|
| 1505 |
+
<div id="wiz-review-content"></div>
|
| 1506 |
+
<div id="wiz-score" style="font-family:var(--font-mono);font-size:11px;color:var(--text-secondary);margin-top:8px;"></div>
|
| 1507 |
+
<div class="wizard-btn-row">
|
| 1508 |
+
<button class="wizard-btn wizard-btn-primary" onclick="wizApprove()">Approve & Generate</button>
|
| 1509 |
+
<button class="wizard-btn wizard-btn-secondary" onclick="switchTab('chat')">Back to Chat</button>
|
| 1510 |
+
</div>
|
| 1511 |
+
</div>
|
| 1512 |
+
</div>
|
| 1513 |
+
|
| 1514 |
<div class="chat-input-area" style="position: relative;">
|
| 1515 |
<div id="mention-dropdown">
|
| 1516 |
<div class="mention-option" data-agent="design" onclick="insertMention('design')">
|
|
|
|
| 1601 |
let mentionIndex = 0;
|
| 1602 |
let currentLang = localStorage.getItem('neuralcad_lang') || 'en';
|
| 1603 |
|
| 1604 |
+
// ── WIZARD STATE ──────────────────────────────────────
|
| 1605 |
+
let activeTab = 'chat';
|
| 1606 |
+
let wizHoleSize = 'M6';
|
| 1607 |
+
let wizFeatures = new Set();
|
| 1608 |
+
|
| 1609 |
+
function switchTab(tab) {
|
| 1610 |
+
activeTab = tab;
|
| 1611 |
+
document.getElementById('tab-chat').classList.toggle('active', tab === 'chat');
|
| 1612 |
+
document.getElementById('tab-guided').classList.toggle('active', tab === 'guided');
|
| 1613 |
+
document.getElementById('chat-messages').classList.toggle('hidden', tab === 'guided');
|
| 1614 |
+
document.querySelector('.chat-header').style.display = tab === 'chat' ? 'flex' : 'none';
|
| 1615 |
+
document.getElementById('guided-panel').classList.toggle('active', tab === 'guided');
|
| 1616 |
+
if (tab === 'guided') syncWizardFromState();
|
| 1617 |
+
}
|
| 1618 |
+
|
| 1619 |
+
function wizSetPart(name, desc) {
|
| 1620 |
+
designState.part_name = name;
|
| 1621 |
+
designState.description = desc;
|
| 1622 |
+
wizMarkStep(1); saveState();
|
| 1623 |
+
document.querySelectorAll('#wiz-step-1 .wizard-chip').forEach(c => c.classList.remove('selected'));
|
| 1624 |
+
if (event && event.target) event.target.classList.add('selected');
|
| 1625 |
+
}
|
| 1626 |
+
|
| 1627 |
+
function wizSetMaterial(mat) {
|
| 1628 |
+
designState.material = mat;
|
| 1629 |
+
wizMarkStep(2); saveState();
|
| 1630 |
+
document.querySelectorAll('#wiz-step-2 .wizard-chip').forEach(c => c.classList.remove('selected'));
|
| 1631 |
+
if (event && event.target) event.target.classList.add('selected');
|
| 1632 |
+
}
|
| 1633 |
+
|
| 1634 |
+
function wizSetDim(name, val) {
|
| 1635 |
+
if (!designState.dimensions) designState.dimensions = {};
|
| 1636 |
+
const v = parseFloat(val);
|
| 1637 |
+
if (v > 0) designState.dimensions[name] = v;
|
| 1638 |
+
else delete designState.dimensions[name];
|
| 1639 |
+
wizMarkStep(3); saveState();
|
| 1640 |
+
}
|
| 1641 |
+
|
| 1642 |
+
function wizToggleFeature(el, feat) {
|
| 1643 |
+
if (wizFeatures.has(feat)) {
|
| 1644 |
+
wizFeatures.delete(feat);
|
| 1645 |
+
el.classList.remove('selected');
|
| 1646 |
+
} else {
|
| 1647 |
+
wizFeatures.add(feat);
|
| 1648 |
+
el.classList.add('selected');
|
| 1649 |
+
}
|
| 1650 |
+
if (feat === 'holes') {
|
| 1651 |
+
document.getElementById('wiz-holes-config').style.display = wizFeatures.has('holes') ? 'block' : 'none';
|
| 1652 |
+
}
|
| 1653 |
+
wizRebuildFeatures();
|
| 1654 |
+
wizMarkStep(4); saveState();
|
| 1655 |
+
}
|
| 1656 |
+
|
| 1657 |
+
function wizSetHoleSize(size) {
|
| 1658 |
+
wizHoleSize = size;
|
| 1659 |
+
document.querySelectorAll('#wiz-holes-config .wizard-chip').forEach(c => c.classList.remove('selected'));
|
| 1660 |
+
document.getElementById('wiz-hole-' + size.toLowerCase()).classList.add('selected');
|
| 1661 |
+
wizRebuildFeatures();
|
| 1662 |
+
saveState();
|
| 1663 |
+
}
|
| 1664 |
+
|
| 1665 |
+
function wizUpdateHoles() { wizRebuildFeatures(); saveState(); }
|
| 1666 |
+
|
| 1667 |
+
function wizRebuildFeatures() {
|
| 1668 |
+
if (!designState.features) designState.features = [];
|
| 1669 |
+
const custom = designState.features.filter(f => !['fillets','chamfers','pockets','slots'].includes(f) && !/^\d+x M\d+ holes$/.test(f));
|
| 1670 |
+
designState.features = [];
|
| 1671 |
+
if (wizFeatures.has('holes')) {
|
| 1672 |
+
const count = document.getElementById('wiz-hole-count')?.value || 4;
|
| 1673 |
+
designState.features.push(count + 'x ' + wizHoleSize + ' holes');
|
| 1674 |
+
}
|
| 1675 |
+
if (wizFeatures.has('fillets')) designState.features.push('fillets');
|
| 1676 |
+
if (wizFeatures.has('chamfers')) designState.features.push('chamfers');
|
| 1677 |
+
if (wizFeatures.has('pockets')) designState.features.push('pockets');
|
| 1678 |
+
if (wizFeatures.has('slots')) designState.features.push('slots');
|
| 1679 |
+
designState.features.push(...custom);
|
| 1680 |
+
}
|
| 1681 |
+
|
| 1682 |
+
function wizAddCustomFeature(val) {
|
| 1683 |
+
if (!val.trim()) return;
|
| 1684 |
+
if (!designState.features) designState.features = [];
|
| 1685 |
+
designState.features.push(val.trim());
|
| 1686 |
+
wizMarkStep(4); saveState();
|
| 1687 |
+
}
|
| 1688 |
+
|
| 1689 |
+
function wizUpdateConstraints() {
|
| 1690 |
+
designState.constraints = [];
|
| 1691 |
+
const wall = document.getElementById('wiz-min-wall')?.value;
|
| 1692 |
+
const size = document.getElementById('wiz-max-size')?.value;
|
| 1693 |
+
if (wall) designState.constraints.push('min wall ' + wall + 'mm');
|
| 1694 |
+
if (size && parseFloat(size) < 500) designState.constraints.push('max size ' + size + 'mm');
|
| 1695 |
+
wizMarkStep(5); saveState();
|
| 1696 |
+
}
|
| 1697 |
+
|
| 1698 |
+
function wizSetAxis(axis, el) {
|
| 1699 |
+
designState.axis_recommendation = axis;
|
| 1700 |
+
document.querySelectorAll('#wiz-step-6 .wizard-chip').forEach(c => c.classList.remove('selected'));
|
| 1701 |
+
if (el) el.classList.add('selected');
|
| 1702 |
+
wizMarkStep(6); saveState();
|
| 1703 |
+
}
|
| 1704 |
+
|
| 1705 |
+
function wizMarkStep(n) {
|
| 1706 |
+
const check = document.getElementById('wiz-check-' + n);
|
| 1707 |
+
const step = document.getElementById('wiz-step-' + n);
|
| 1708 |
+
if (check) check.textContent = '\u2713';
|
| 1709 |
+
if (step) step.classList.add('completed');
|
| 1710 |
+
if (n <= 6) wizUpdateReview();
|
| 1711 |
+
}
|
| 1712 |
+
|
| 1713 |
+
function wizUpdateReview() {
|
| 1714 |
+
const el = document.getElementById('wiz-review-content');
|
| 1715 |
+
if (!el) return;
|
| 1716 |
+
const fields = [
|
| 1717 |
+
['Part', designState.part_name || ''],
|
| 1718 |
+
['Material', designState.material || ''],
|
| 1719 |
+
['Dimensions', Object.entries(designState.dimensions || {}).map(([k,v]) => k + '=' + v + 'mm').join(', ') || ''],
|
| 1720 |
+
['Features', (designState.features || []).join(', ') || ''],
|
| 1721 |
+
['Constraints', (designState.constraints || []).join(', ') || ''],
|
| 1722 |
+
['Machining', designState.axis_recommendation || 'Auto'],
|
| 1723 |
+
];
|
| 1724 |
+
let html = '';
|
| 1725 |
+
for (const [label, value] of fields) {
|
| 1726 |
+
html += '<div class="wizard-review-field"><span class="wizard-review-label">' + label + '</span><span class="wizard-review-value">' + (value || '\u2014') + '</span></div>';
|
| 1727 |
+
}
|
| 1728 |
+
el.innerHTML = html;
|
| 1729 |
+
const score = wizComputeScore();
|
| 1730 |
+
const scoreEl = document.getElementById('wiz-score');
|
| 1731 |
+
if (scoreEl) scoreEl.textContent = 'Score: ' + score.toFixed(0) + '/8 ' + (score >= 8 ? '\u2713 Ready' : '\u2717 Need more info');
|
| 1732 |
+
}
|
| 1733 |
+
|
| 1734 |
+
function wizComputeScore() {
|
| 1735 |
+
let s = 0;
|
| 1736 |
+
if (designState.material) s += 3;
|
| 1737 |
+
if (designState.part_name) s += 1;
|
| 1738 |
+
if (designState.description) s += 1;
|
| 1739 |
+
if (designState.axis_recommendation) s += 2;
|
| 1740 |
+
s += Math.min(Object.keys(designState.dimensions || {}).length, 4);
|
| 1741 |
+
s += Math.min((designState.features || []).length, 4);
|
| 1742 |
+
s += Math.min((designState.constraints || []).length, 2);
|
| 1743 |
+
return s;
|
| 1744 |
+
}
|
| 1745 |
+
|
| 1746 |
+
async function wizApprove() {
|
| 1747 |
+
const plan = {
|
| 1748 |
+
part_name: designState.part_name || '',
|
| 1749 |
+
description: designState.description || '',
|
| 1750 |
+
material: designState.material || '',
|
| 1751 |
+
dimensions: designState.dimensions || {},
|
| 1752 |
+
features: designState.features || [],
|
| 1753 |
+
constraints: designState.constraints || [],
|
| 1754 |
+
axis_recommendation: designState.axis_recommendation || '',
|
| 1755 |
+
machining_notes: [],
|
| 1756 |
+
confidence_score: wizComputeScore(),
|
| 1757 |
+
};
|
| 1758 |
+
try {
|
| 1759 |
+
const resp = await fetch('/api/plan/approve', {
|
| 1760 |
+
method: 'POST',
|
| 1761 |
+
headers: { 'Content-Type': 'application/json' },
|
| 1762 |
+
body: JSON.stringify({ plan: plan, design_state: designState }),
|
| 1763 |
+
});
|
| 1764 |
+
const data = await resp.json();
|
| 1765 |
+
designState = data.design_state;
|
| 1766 |
+
saveState();
|
| 1767 |
+
switchTab('chat');
|
| 1768 |
+
await sendMessage('Generate the approved design');
|
| 1769 |
+
} catch (err) {
|
| 1770 |
+
console.error('Plan approve failed:', err);
|
| 1771 |
+
}
|
| 1772 |
+
}
|
| 1773 |
+
|
| 1774 |
+
function syncWizardFromState() {
|
| 1775 |
+
const dims = designState.dimensions || {};
|
| 1776 |
+
for (const key of ['width', 'height', 'depth']) {
|
| 1777 |
+
const el = document.getElementById('wiz-dim-' + key);
|
| 1778 |
+
if (el && dims[key]) el.value = dims[key];
|
| 1779 |
+
}
|
| 1780 |
+
if (designState.part_name) wizMarkStep(1);
|
| 1781 |
+
if (designState.material) wizMarkStep(2);
|
| 1782 |
+
if (Object.keys(dims).length > 0) wizMarkStep(3);
|
| 1783 |
+
if ((designState.features || []).length > 0) wizMarkStep(4);
|
| 1784 |
+
if ((designState.constraints || []).length > 0) wizMarkStep(5);
|
| 1785 |
+
if (designState.axis_recommendation) wizMarkStep(6);
|
| 1786 |
+
wizUpdateReview();
|
| 1787 |
+
}
|
| 1788 |
+
|
| 1789 |
+
// ── PLAN CARD ─────────────────────────────────────────
|
| 1790 |
+
|
| 1791 |
+
function renderPlanCard(plan) {
|
| 1792 |
+
const fields = [
|
| 1793 |
+
['Part', plan.part_name],
|
| 1794 |
+
['Material', plan.material],
|
| 1795 |
+
['Dimensions', Object.entries(plan.dimensions || {}).map(([k,v]) => k + '=' + v + 'mm').join(', ')],
|
| 1796 |
+
['Features', (plan.features || []).join(', ')],
|
| 1797 |
+
['Constraints', (plan.constraints || []).join(', ')],
|
| 1798 |
+
['Axis', plan.axis_recommendation || 'Auto'],
|
| 1799 |
+
];
|
| 1800 |
+
let html = '<div class="plan-card" id="active-plan-card">';
|
| 1801 |
+
html += '<div class="plan-card-title">\u25c6 PLAN READY FOR REVIEW</div>';
|
| 1802 |
+
for (const [label, value] of fields) {
|
| 1803 |
+
html += '<div class="wizard-review-field"><span class="wizard-review-label">' + label + '</span><span class="wizard-review-value">' + (value || '\u2014') + '</span></div>';
|
| 1804 |
+
}
|
| 1805 |
+
if (plan.machining_notes && plan.machining_notes.length) {
|
| 1806 |
+
html += '<div class="wizard-review-field"><span class="wizard-review-label">Notes</span><span class="wizard-review-value">' + plan.machining_notes.join('; ') + '</span></div>';
|
| 1807 |
+
}
|
| 1808 |
+
html += '<div class="plan-card-score">Score: ' + (plan.confidence_score || 0).toFixed(0) + '/8</div>';
|
| 1809 |
+
html += '<div class="plan-card-actions">';
|
| 1810 |
+
html += '<button class="plan-card-btn plan-card-approve" onclick="approvePlanCard()">Approve</button>';
|
| 1811 |
+
html += '<button class="plan-card-btn plan-card-reject" onclick="rejectPlanCard()">Reject</button>';
|
| 1812 |
+
html += '</div></div>';
|
| 1813 |
+
return html;
|
| 1814 |
+
}
|
| 1815 |
+
|
| 1816 |
+
async function approvePlanCard() {
|
| 1817 |
+
const plan = designState.plan;
|
| 1818 |
+
if (!plan) return;
|
| 1819 |
+
try {
|
| 1820 |
+
const resp = await fetch('/api/plan/approve', {
|
| 1821 |
+
method: 'POST',
|
| 1822 |
+
headers: { 'Content-Type': 'application/json' },
|
| 1823 |
+
body: JSON.stringify({ plan: plan, design_state: designState }),
|
| 1824 |
+
});
|
| 1825 |
+
const data = await resp.json();
|
| 1826 |
+
designState = data.design_state;
|
| 1827 |
+
saveState();
|
| 1828 |
+
const card = document.getElementById('active-plan-card');
|
| 1829 |
+
if (card) card.remove();
|
| 1830 |
+
await sendMessage('Generate the approved design');
|
| 1831 |
+
} catch (err) {
|
| 1832 |
+
console.error('Plan approve failed:', err);
|
| 1833 |
+
}
|
| 1834 |
+
}
|
| 1835 |
+
|
| 1836 |
+
async function rejectPlanCard() {
|
| 1837 |
+
try {
|
| 1838 |
+
const resp = await fetch('/api/plan/reject', {
|
| 1839 |
+
method: 'POST',
|
| 1840 |
+
headers: { 'Content-Type': 'application/json' },
|
| 1841 |
+
body: JSON.stringify({ design_state: designState }),
|
| 1842 |
+
});
|
| 1843 |
+
const data = await resp.json();
|
| 1844 |
+
designState = data.design_state;
|
| 1845 |
+
saveState();
|
| 1846 |
+
const card = document.getElementById('active-plan-card');
|
| 1847 |
+
if (card) card.remove();
|
| 1848 |
+
} catch (err) {
|
| 1849 |
+
console.error('Plan reject failed:', err);
|
| 1850 |
+
}
|
| 1851 |
+
}
|
| 1852 |
+
|
| 1853 |
// ── i18n ─────────────────────────────────────────────
|
| 1854 |
|
| 1855 |
const I18N = {
|
|
|
|
| 1884 |
cadFailed: 'CAD execution failed: ',
|
| 1885 |
errorPrefix: 'Error: ',
|
| 1886 |
sendPreviewMsg: '@cad Generate a 3D preview based on our discussion',
|
| 1887 |
+
tabChat: 'Chat',
|
| 1888 |
+
tabGuided: 'Guided',
|
| 1889 |
+
planReady: 'PLAN READY FOR REVIEW',
|
| 1890 |
+
planApprove: 'Approve',
|
| 1891 |
+
planReject: 'Reject',
|
| 1892 |
},
|
| 1893 |
'zh-TW': {
|
| 1894 |
subtitle: '\u591a\u4ee3\u7406\u8a2d\u8a08',
|
|
|
|
| 1921 |
cadFailed: 'CAD \u57f7\u884c\u5931\u6557\uff1a',
|
| 1922 |
errorPrefix: '\u932f\u8aa4\uff1a',
|
| 1923 |
sendPreviewMsg: '@cad \u6839\u64da\u6211\u5011\u7684\u8a0e\u8ad6\u751f\u6210 3D \u9810\u89bd',
|
| 1924 |
+
tabChat: '\u5c0d\u8a71',
|
| 1925 |
+
tabGuided: '\u5f15\u5c0e',
|
| 1926 |
+
planReady: '\u8a08\u756b\u5df2\u6e96\u5099\u5be9\u67e5',
|
| 1927 |
+
planApprove: '\u6279\u51c6',
|
| 1928 |
+
planReject: '\u62d2\u7d55',
|
| 1929 |
},
|
| 1930 |
vi: {
|
| 1931 |
subtitle: 'Thi\u1ebft K\u1ebf \u0110a T\u00e1c T\u1eed',
|
|
|
|
| 1958 |
cadFailed: 'Th\u1ef1c thi CAD th\u1ea5t b\u1ea1i: ',
|
| 1959 |
errorPrefix: 'L\u1ed7i: ',
|
| 1960 |
sendPreviewMsg: '@cad T\u1ea1o b\u1ea3n xem tr\u01b0\u1edbc 3D d\u1ef1a tr\u00ean cu\u1ed9c th\u1ea3o lu\u1eadn c\u1ee7a ch\u00fang ta',
|
| 1961 |
+
tabChat: 'Tr\u00f2 Chuy\u1ec7n',
|
| 1962 |
+
tabGuided: 'H\u01b0\u1edbng D\u1eabn',
|
| 1963 |
+
planReady: 'K\u1ebe HO\u1ea0CH S\u1eb4N S\u00c0NG',
|
| 1964 |
+
planApprove: 'Duy\u1ec7t',
|
| 1965 |
+
planReject: 'T\u1eeb Ch\u1ed1i',
|
| 1966 |
},
|
| 1967 |
};
|
| 1968 |
|
|
|
|
| 2452 |
}
|
| 2453 |
saveState();
|
| 2454 |
|
| 2455 |
+
// If phase transitioned to planning, show plan card
|
| 2456 |
+
if (designState.phase === 'planning' && designState.plan) {
|
| 2457 |
+
const old = document.getElementById('active-plan-card');
|
| 2458 |
+
if (old) old.remove();
|
| 2459 |
+
const msgs = document.getElementById('chat-messages');
|
| 2460 |
+
const cardDiv = document.createElement('div');
|
| 2461 |
+
cardDiv.innerHTML = renderPlanCard(designState.plan);
|
| 2462 |
+
msgs.appendChild(cardDiv.firstChild);
|
| 2463 |
+
msgs.scrollTop = msgs.scrollHeight;
|
| 2464 |
+
}
|
| 2465 |
+
|
| 2466 |
// If preview available, load 3D model
|
| 2467 |
if (data.preview && data.preview.success) {
|
| 2468 |
setViewerLoading(true, t('loadingModel'));
|
|
|
|
| 2765 |
|
| 2766 |
document.getElementById('dl-step').href = '/api/models/' + partName + '.step';
|
| 2767 |
document.getElementById('dl-stl').href = '/api/models/' + partName + '.stl';
|
| 2768 |
+
document.getElementById('dl-3mf').href = '/api/models/' + partName + '.3mf';
|
| 2769 |
document.getElementById('dl-report').href = '/api/models/' + partName + '_report.json';
|
| 2770 |
|
| 2771 |
const dlGcode = document.getElementById('dl-gcode');
|
|
|
|
| 2857 |
html += '<div class="gallery-card-downloads">';
|
| 2858 |
html += '<a class="gallery-dl" href="/api/models/' + name + '.step" download>STEP</a>';
|
| 2859 |
html += '<a class="gallery-dl" href="/api/models/' + name + '.stl" download>STL</a>';
|
| 2860 |
+
html += '<a class="gallery-dl" href="/api/models/' + name + '.3mf" download>3MF</a>';
|
| 2861 |
html += '<a class="gallery-dl" href="/api/models/' + name + '.gcode" download>GCODE</a>';
|
| 2862 |
html += '</div>';
|
| 2863 |
html += '</div>';
|