Spaces:
Sleeping
Sleeping
Commit ·
fb8a7a9
1
Parent(s): eda0e3d
feat: editable plan card with field edit, ask agent, notes, save & continue
Browse files- web/index.html +155 -20
web/index.html
CHANGED
|
@@ -1879,44 +1879,179 @@ function syncWizardFromState() {
|
|
| 1879 |
|
| 1880 |
// ── PLAN CARD ─────────────────────────────────────────
|
| 1881 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1882 |
function renderPlanCard(plan) {
|
| 1883 |
-
|
| 1884 |
-
['Part', plan.part_name],
|
| 1885 |
-
['Material', plan.material],
|
| 1886 |
-
['Dimensions',
|
| 1887 |
-
['Features',
|
| 1888 |
-
['Constraints',
|
| 1889 |
-
['Axis', plan.axis_recommendation || 'Auto'],
|
| 1890 |
];
|
| 1891 |
-
let html = '<div class="plan-card" id="active-plan-card">';
|
| 1892 |
-
html += '<div class="plan-card-title">\u25c6 PLAN READY FOR REVIEW</div>';
|
| 1893 |
-
for (const [label, value] of fields) {
|
| 1894 |
-
html += '<div class="wizard-review-field"><span class="wizard-review-label">' + escapeHtml(label) + '</span><span class="wizard-review-value">' + escapeHtml(value || '\u2014') + '</span></div>';
|
| 1895 |
-
}
|
| 1896 |
if (plan.machining_notes && plan.machining_notes.length) {
|
| 1897 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1898 |
}
|
| 1899 |
-
html += '<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1900 |
html += '<div class="plan-card-actions">';
|
| 1901 |
-
html += '<button class="plan-card-btn plan-card-approve" onclick="approvePlanCard()">
|
| 1902 |
-
html += '<button class="plan-card-btn plan-card-
|
|
|
|
| 1903 |
html += '</div></div>';
|
| 1904 |
return html;
|
| 1905 |
}
|
| 1906 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1907 |
async function approvePlanCard() {
|
| 1908 |
-
|
| 1909 |
if (!plan) return;
|
|
|
|
|
|
|
| 1910 |
try {
|
| 1911 |
-
|
| 1912 |
method: 'POST',
|
| 1913 |
headers: { 'Content-Type': 'application/json' },
|
| 1914 |
body: JSON.stringify({ plan: plan, design_state: designState }),
|
| 1915 |
});
|
| 1916 |
-
|
| 1917 |
designState = data.design_state;
|
| 1918 |
saveState();
|
| 1919 |
-
|
| 1920 |
if (card) card.remove();
|
| 1921 |
await sendMessage('Generate the approved design');
|
| 1922 |
} catch (err) {
|
|
|
|
| 1879 |
|
| 1880 |
// ── PLAN CARD ─────────────────────────────────────────
|
| 1881 |
|
| 1882 |
+
var PLAN_FIELD_AGENTS = {
|
| 1883 |
+
part_name: 'design', material: 'engineering', dimensions: 'engineering',
|
| 1884 |
+
features: 'design', constraints: 'cnc', axis_recommendation: 'cnc',
|
| 1885 |
+
machining_notes: 'cnc',
|
| 1886 |
+
};
|
| 1887 |
+
var PLAN_FIELD_QUESTIONS = {
|
| 1888 |
+
part_name: 'What should this part be called?',
|
| 1889 |
+
material: 'What material do you recommend?',
|
| 1890 |
+
dimensions: 'What dimensions are appropriate?',
|
| 1891 |
+
features: 'What features should this part have?',
|
| 1892 |
+
constraints: 'What manufacturing constraints should we consider?',
|
| 1893 |
+
axis_recommendation: 'What axis strategy do you recommend?',
|
| 1894 |
+
machining_notes: 'Any machining notes to consider?',
|
| 1895 |
+
};
|
| 1896 |
+
|
| 1897 |
function renderPlanCard(plan) {
|
| 1898 |
+
var fields = [
|
| 1899 |
+
['Part', 'part_name', plan.part_name, 'text'],
|
| 1900 |
+
['Material', 'material', plan.material, 'text'],
|
| 1901 |
+
['Dimensions', 'dimensions', plan.dimensions, 'dimensions'],
|
| 1902 |
+
['Features', 'features', plan.features, 'list'],
|
| 1903 |
+
['Constraints', 'constraints', plan.constraints, 'list'],
|
| 1904 |
+
['Axis', 'axis_recommendation', plan.axis_recommendation || 'Auto', 'text'],
|
| 1905 |
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1906 |
if (plan.machining_notes && plan.machining_notes.length) {
|
| 1907 |
+
fields.push(['Notes', 'machining_notes', plan.machining_notes, 'list']);
|
| 1908 |
+
}
|
| 1909 |
+
var html = '<div class="plan-card" id="active-plan-card" data-original-score="' + (plan.confidence_score || 0) + '">';
|
| 1910 |
+
html += '<div class="plan-card-title">\u25c6 ' + t('planReady') + '</div>';
|
| 1911 |
+
for (var i = 0; i < fields.length; i++) {
|
| 1912 |
+
var label = fields[i][0], key = fields[i][1], value = fields[i][2], type = fields[i][3];
|
| 1913 |
+
html += '<div class="wizard-review-field" data-field="' + key + '">';
|
| 1914 |
+
html += '<span class="wizard-review-label">' + escapeHtml(label) + '</span>';
|
| 1915 |
+
html += '<span class="wizard-review-value" id="plan-val-' + key + '">';
|
| 1916 |
+
if (type === 'dimensions') {
|
| 1917 |
+
html += escapeHtml(Object.entries(value || {}).map(function(e) { return e[0] + '=' + e[1] + 'mm'; }).join(', ') || '\u2014');
|
| 1918 |
+
} else if (type === 'list') {
|
| 1919 |
+
html += escapeHtml((value || []).join(', ') || '\u2014');
|
| 1920 |
+
} else {
|
| 1921 |
+
html += escapeHtml(value || '\u2014');
|
| 1922 |
+
}
|
| 1923 |
+
html += '</span>';
|
| 1924 |
+
html += '<span class="plan-field-actions">';
|
| 1925 |
+
html += '<button class="plan-field-btn" onclick="toggleFieldEdit(\'' + key + '\', \'' + type + '\')" title="Edit">' + t('planEdit') + '</button>';
|
| 1926 |
+
html += '<button class="plan-field-btn" onclick="askAgentForField(\'' + key + '\')" title="Ask Agent">' + t('planAskAgent') + '</button>';
|
| 1927 |
+
html += '</span>';
|
| 1928 |
+
html += '</div>';
|
| 1929 |
}
|
| 1930 |
+
html += '<textarea class="plan-notes" id="plan-notes" placeholder="' + t('planNotesPlaceholder') + '">' + escapeHtml(plan.notes || '') + '</textarea>';
|
| 1931 |
+
var origScore = (plan.confidence_score || 0).toFixed(0);
|
| 1932 |
+
html += '<div class="plan-score-dual">';
|
| 1933 |
+
html += '<span>' + t('planScoreOriginal') + ': ' + origScore + '/8</span>';
|
| 1934 |
+
html += '<span>\u2192</span>';
|
| 1935 |
+
html += '<span class="plan-score-current score-ok" id="plan-current-score">' + t('planScoreCurrent') + ': ' + origScore + '/8</span>';
|
| 1936 |
+
html += '</div>';
|
| 1937 |
html += '<div class="plan-card-actions">';
|
| 1938 |
+
html += '<button class="plan-card-btn plan-card-approve" onclick="approvePlanCard()">' + t('planApprove') + '</button>';
|
| 1939 |
+
html += '<button class="plan-card-btn plan-card-save" onclick="savePlanAndContinue()">' + t('planSave') + '</button>';
|
| 1940 |
+
html += '<button class="plan-card-btn plan-card-reject" onclick="rejectPlanCard()">' + t('planReject') + '</button>';
|
| 1941 |
html += '</div></div>';
|
| 1942 |
return html;
|
| 1943 |
}
|
| 1944 |
|
| 1945 |
+
function toggleFieldEdit(fieldKey, fieldType) {
|
| 1946 |
+
var valEl = document.getElementById('plan-val-' + fieldKey);
|
| 1947 |
+
if (!valEl) return;
|
| 1948 |
+
var plan = designState.plan;
|
| 1949 |
+
if (!plan) return;
|
| 1950 |
+
|
| 1951 |
+
var existingInput = valEl.querySelector('input, .plan-dim-group');
|
| 1952 |
+
if (existingInput) {
|
| 1953 |
+
if (fieldType === 'dimensions') {
|
| 1954 |
+
var dimInputs = valEl.querySelectorAll('.plan-dim-input');
|
| 1955 |
+
var dims = {};
|
| 1956 |
+
dimInputs.forEach(function(inp) { if (inp.value) dims[inp.dataset.dim] = parseFloat(inp.value) || 0; });
|
| 1957 |
+
plan.dimensions = dims;
|
| 1958 |
+
valEl.textContent = Object.entries(dims).map(function(e) { return e[0] + '=' + e[1] + 'mm'; }).join(', ') || '\u2014';
|
| 1959 |
+
} else if (fieldType === 'list') {
|
| 1960 |
+
var raw = existingInput.value;
|
| 1961 |
+
plan[fieldKey] = raw ? raw.split(',').map(function(s) { return s.trim(); }).filter(Boolean) : [];
|
| 1962 |
+
valEl.textContent = plan[fieldKey].join(', ') || '\u2014';
|
| 1963 |
+
} else {
|
| 1964 |
+
plan[fieldKey] = existingInput.value;
|
| 1965 |
+
valEl.textContent = plan[fieldKey] || '\u2014';
|
| 1966 |
+
}
|
| 1967 |
+
recalcPlanScore();
|
| 1968 |
+
return;
|
| 1969 |
+
}
|
| 1970 |
+
|
| 1971 |
+
if (fieldType === 'dimensions') {
|
| 1972 |
+
var dims = plan.dimensions || {};
|
| 1973 |
+
var dimKeys = Object.keys(dims).length ? Object.keys(dims) : ['width', 'height', 'depth'];
|
| 1974 |
+
var groupHtml = '<span class="plan-dim-group">';
|
| 1975 |
+
dimKeys.forEach(function(dk) {
|
| 1976 |
+
groupHtml += '<span><span class="plan-dim-label">' + dk + '</span><input class="plan-dim-input" type="number" data-dim="' + dk + '" value="' + (dims[dk] || '') + '"></span>';
|
| 1977 |
+
});
|
| 1978 |
+
groupHtml += '</span>';
|
| 1979 |
+
valEl.innerHTML = groupHtml;
|
| 1980 |
+
} else if (fieldType === 'list') {
|
| 1981 |
+
var current = (plan[fieldKey] || []).join(', ');
|
| 1982 |
+
valEl.innerHTML = '<input class="plan-field-input" type="text" value="' + escapeHtml(current) + '">';
|
| 1983 |
+
} else {
|
| 1984 |
+
var current = plan[fieldKey] || '';
|
| 1985 |
+
valEl.innerHTML = '<input class="plan-field-input" type="text" value="' + escapeHtml(current) + '">';
|
| 1986 |
+
}
|
| 1987 |
+
var firstInput = valEl.querySelector('input');
|
| 1988 |
+
if (firstInput) firstInput.focus();
|
| 1989 |
+
}
|
| 1990 |
+
|
| 1991 |
+
function recalcPlanScore() {
|
| 1992 |
+
var plan = designState.plan;
|
| 1993 |
+
if (!plan) return;
|
| 1994 |
+
var s = 0;
|
| 1995 |
+
if (plan.material) s += 3;
|
| 1996 |
+
if (plan.part_name) s += 1;
|
| 1997 |
+
if (plan.description) s += 1;
|
| 1998 |
+
if (plan.axis_recommendation) s += 2;
|
| 1999 |
+
s += Math.min(Object.keys(plan.dimensions || {}).length, 4);
|
| 2000 |
+
s += Math.min((plan.features || []).length, 4);
|
| 2001 |
+
s += Math.min((plan.constraints || []).length, 2);
|
| 2002 |
+
var el = document.getElementById('plan-current-score');
|
| 2003 |
+
if (el) {
|
| 2004 |
+
el.textContent = t('planScoreCurrent') + ': ' + s.toFixed(0) + '/8';
|
| 2005 |
+
el.className = 'plan-score-current ' + (s >= 8 ? 'score-ok' : 'score-low');
|
| 2006 |
+
}
|
| 2007 |
+
}
|
| 2008 |
+
|
| 2009 |
+
function askAgentForField(fieldKey) {
|
| 2010 |
+
var plan = designState.plan;
|
| 2011 |
+
if (!plan) return;
|
| 2012 |
+
var agentId = PLAN_FIELD_AGENTS[fieldKey] || 'design';
|
| 2013 |
+
var question = PLAN_FIELD_QUESTIONS[fieldKey] || 'Can you help with this field?';
|
| 2014 |
+
var partCtx = plan.part_name ? ' for "' + plan.part_name + '"' : '';
|
| 2015 |
+
var msg = '@' + agentId + ' Regarding the plan' + partCtx + ': ' + question;
|
| 2016 |
+
var chatTab = document.querySelector('[data-tab="chat"]');
|
| 2017 |
+
if (chatTab) chatTab.click();
|
| 2018 |
+
sendMessage(msg);
|
| 2019 |
+
}
|
| 2020 |
+
|
| 2021 |
+
function savePlanAndContinue() {
|
| 2022 |
+
var plan = designState.plan;
|
| 2023 |
+
if (!plan) return;
|
| 2024 |
+
var notesEl = document.getElementById('plan-notes');
|
| 2025 |
+
if (notesEl) plan.notes = notesEl.value;
|
| 2026 |
+
designState.part_name = plan.part_name;
|
| 2027 |
+
designState.description = plan.description;
|
| 2028 |
+
designState.material = plan.material;
|
| 2029 |
+
designState.dimensions = Object.assign({}, plan.dimensions);
|
| 2030 |
+
designState.features = (plan.features || []).slice();
|
| 2031 |
+
designState.constraints = (plan.constraints || []).slice();
|
| 2032 |
+
designState.axis_recommendation = plan.axis_recommendation;
|
| 2033 |
+
designState.phase = 'exploring';
|
| 2034 |
+
designState.plan = null;
|
| 2035 |
+
saveState();
|
| 2036 |
+
var card = document.getElementById('active-plan-card');
|
| 2037 |
+
if (card) card.remove();
|
| 2038 |
+
}
|
| 2039 |
+
|
| 2040 |
async function approvePlanCard() {
|
| 2041 |
+
var plan = designState.plan;
|
| 2042 |
if (!plan) return;
|
| 2043 |
+
var notesEl = document.getElementById('plan-notes');
|
| 2044 |
+
if (notesEl) plan.notes = notesEl.value;
|
| 2045 |
try {
|
| 2046 |
+
var resp = await fetch('/api/plan/approve', {
|
| 2047 |
method: 'POST',
|
| 2048 |
headers: { 'Content-Type': 'application/json' },
|
| 2049 |
body: JSON.stringify({ plan: plan, design_state: designState }),
|
| 2050 |
});
|
| 2051 |
+
var data = await resp.json();
|
| 2052 |
designState = data.design_state;
|
| 2053 |
saveState();
|
| 2054 |
+
var card = document.getElementById('active-plan-card');
|
| 2055 |
if (card) card.remove();
|
| 2056 |
await sendMessage('Generate the approved design');
|
| 2057 |
} catch (err) {
|