nvti commited on
Commit
3331c1a
·
1 Parent(s): b7db872

fix(report): show scan result insights

Browse files
Files changed (2) hide show
  1. web/app.js +57 -1
  2. web/styles.css +31 -0
web/app.js CHANGED
@@ -141,10 +141,11 @@ function finalProgressState(result) {
141
 
142
  function ProgressPanel({ steps }) {
143
  if (!steps.length) return null;
 
144
  return h(
145
  "section",
146
  { className: "progress-panel", "aria-live": "polite" },
147
- h("h3", null, "Your scan is moving"),
148
  h(
149
  "ol",
150
  { className: "progress-list" },
@@ -160,6 +161,60 @@ function ProgressPanel({ steps }) {
160
  );
161
  }
162
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  function SummaryTab({ result }) {
164
  if (!result) {
165
  return h(
@@ -922,6 +977,7 @@ function App() {
922
  result?.annotated_video_url
923
  ? null
924
  : h(ProgressPanel, { steps: progressSteps }),
 
925
  ),
926
  ),
927
  h(ReportPanel, { result, activeTab, onTabChange: setActiveTab }),
 
141
 
142
  function ProgressPanel({ steps }) {
143
  if (!steps.length) return null;
144
+ const isComplete = steps.every((step) => step.status === "done");
145
  return h(
146
  "section",
147
  { className: "progress-panel", "aria-live": "polite" },
148
+ h("h3", null, isComplete ? "Scan results are ready" : "Your scan is moving"),
149
  h(
150
  "ol",
151
  { className: "progress-list" },
 
161
  );
162
  }
163
 
164
+ function ReviewInsights({ result }) {
165
+ if (!result) return null;
166
+ const report = result.report;
167
+ const warnings = report.video_manifest?.quality_warnings || [];
168
+ const exercise = label(report.exercise?.exercise || "movement");
169
+ const repCount = report.reps?.reps?.length || 0;
170
+ const issues = report.issue_markers?.issues || [];
171
+ const confidence = percent(report.exercise?.confidence);
172
+ const issueText = issues.length
173
+ ? `${issues.length} coaching moment${issues.length === 1 ? "" : "s"}`
174
+ : "No clear form issues";
175
+
176
+ return h(
177
+ "div",
178
+ { className: "scan-insights", "aria-label": "Scan results" },
179
+ h(
180
+ "article",
181
+ { className: "scan-insight" },
182
+ h("span", null, "Movement"),
183
+ h("strong", null, exercise),
184
+ h("small", null, `confidence ${confidence}`),
185
+ ),
186
+ h(
187
+ "article",
188
+ { className: "scan-insight" },
189
+ h("span", null, "Reps counted"),
190
+ h("strong", null, String(repCount)),
191
+ h("small", null, repCount === 1 ? "clean rep detected" : "reps detected"),
192
+ ),
193
+ h(
194
+ "article",
195
+ { className: "scan-insight" },
196
+ h("span", null, "Coach review"),
197
+ h("strong", null, issueText),
198
+ h(
199
+ "small",
200
+ null,
201
+ issues.length ? "tap Issues for clips" : "nothing major stood out",
202
+ ),
203
+ ),
204
+ h(
205
+ "article",
206
+ { className: "scan-insight" },
207
+ h("span", null, "Video quality"),
208
+ h("strong", null, warnings.length ? "Needs a quick note" : "Looks good"),
209
+ h(
210
+ "small",
211
+ null,
212
+ warnings.length ? warnings.map(label).join(", ") : "clear enough to coach",
213
+ ),
214
+ ),
215
+ );
216
+ }
217
+
218
  function SummaryTab({ result }) {
219
  if (!result) {
220
  return h(
 
977
  result?.annotated_video_url
978
  ? null
979
  : h(ProgressPanel, { steps: progressSteps }),
980
+ h(ReviewInsights, { result }),
981
  ),
982
  ),
983
  h(ReportPanel, { result, activeTab, onTabChange: setActiveTab }),
web/styles.css CHANGED
@@ -399,6 +399,36 @@ input[type="text"]:focus,
399
  box-shadow: 0 0 0 5px rgba(190, 104, 75, 0.14);
400
  }
401
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  .result-stage {
403
  display: grid;
404
  min-height: 470px;
@@ -768,6 +798,7 @@ td {
768
  .note-grid,
769
  .stat-grid,
770
  .metric-grid,
 
771
  .rep-grid,
772
  .issue-card-grid {
773
  grid-template-columns: 1fr;
 
399
  box-shadow: 0 0 0 5px rgba(190, 104, 75, 0.14);
400
  }
401
 
402
+ .scan-insights {
403
+ display: grid;
404
+ grid-template-columns: repeat(4, minmax(0, 1fr));
405
+ gap: 10px;
406
+ margin-top: 14px;
407
+ }
408
+
409
+ .scan-insight {
410
+ min-width: 0;
411
+ border: 1px solid rgba(37, 44, 31, 0.12);
412
+ border-radius: 8px;
413
+ padding: 12px;
414
+ background: rgba(255, 253, 247, 0.74);
415
+ }
416
+
417
+ .scan-insight span,
418
+ .scan-insight small {
419
+ display: block;
420
+ color: var(--muted);
421
+ font-size: 0.8rem;
422
+ }
423
+
424
+ .scan-insight strong {
425
+ display: block;
426
+ overflow-wrap: anywhere;
427
+ margin: 4px 0;
428
+ color: var(--forest-deep);
429
+ font-size: 1.04rem;
430
+ }
431
+
432
  .result-stage {
433
  display: grid;
434
  min-height: 470px;
 
798
  .note-grid,
799
  .stat-grid,
800
  .metric-grid,
801
+ .scan-insights,
802
  .rep-grid,
803
  .issue-card-grid {
804
  grid-template-columns: 1fr;