CallMeDaniel Claude Sonnet 4.6 commited on
Commit
fb8a7a9
·
1 Parent(s): eda0e3d

feat: editable plan card with field edit, ask agent, notes, save & continue

Browse files
Files changed (1) hide show
  1. 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
- const fields = [
1884
- ['Part', plan.part_name],
1885
- ['Material', plan.material],
1886
- ['Dimensions', Object.entries(plan.dimensions || {}).map(([k,v]) => k + '=' + v + 'mm').join(', ')],
1887
- ['Features', (plan.features || []).join(', ')],
1888
- ['Constraints', (plan.constraints || []).join(', ')],
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
- html += '<div class="wizard-review-field"><span class="wizard-review-label">Notes</span><span class="wizard-review-value">' + escapeHtml(plan.machining_notes.join('; ')) + '</span></div>';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1898
  }
1899
- html += '<div class="plan-card-score">Score: ' + (plan.confidence_score || 0).toFixed(0) + '/8</div>';
 
 
 
 
 
 
1900
  html += '<div class="plan-card-actions">';
1901
- html += '<button class="plan-card-btn plan-card-approve" onclick="approvePlanCard()">Approve</button>';
1902
- html += '<button class="plan-card-btn plan-card-reject" onclick="rejectPlanCard()">Reject</button>';
 
1903
  html += '</div></div>';
1904
  return html;
1905
  }
1906
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1907
  async function approvePlanCard() {
1908
- const plan = designState.plan;
1909
  if (!plan) return;
 
 
1910
  try {
1911
- const resp = await fetch('/api/plan/approve', {
1912
  method: 'POST',
1913
  headers: { 'Content-Type': 'application/json' },
1914
  body: JSON.stringify({ plan: plan, design_state: designState }),
1915
  });
1916
- const data = await resp.json();
1917
  designState = data.design_state;
1918
  saveState();
1919
- const card = document.getElementById('active-plan-card');
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) {