feat(gtm): v2.5 - Hard Fact Extraction & UI

- Backend: Implemented secondary extraction phase for structured specs (JSON schema).

- Backend: Added strict normalization rules (min, cm, kg).

- Frontend: Added 'Phase1Data' interface update for specs.

- Frontend: Implemented new UI component for 'Technical Specifications' in Phase 1.

- Frontend: Updated header and sidebar to display 'v2.5' build marker.

- Docs: Updated architectural documentation.
This commit is contained in:
2026-01-06 19:36:42 +00:00
parent 2c5d50fd1a
commit 7a3e397037
6 changed files with 268 additions and 4 deletions

View File

@@ -1153,6 +1153,95 @@ const App: React.FC = () => {
</div>
</div>
{/* NEW: Hard Facts Specs Display */}
{state.phase1Result?.specs && 'metadata' in state.phase1Result.specs && (
<div className="p-6 rounded-xl border transition-colors bg-white border-slate-200 dark:bg-robo-800 dark:border-robo-700">
<h2 className="text-xl font-bold mb-4 flex items-center gap-2 text-slate-900 dark:text-white">
<Database className="text-blue-500" /> Technical Specifications (Hard Facts)
</h2>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
{/* Core Data */}
<div className="p-4 bg-slate-50 dark:bg-robo-900 rounded-lg border border-slate-200 dark:border-robo-700">
<h3 className="text-xs font-bold uppercase tracking-wider text-slate-500 mb-3">Core Data</h3>
<div className="space-y-2 text-sm">
<div className="flex justify-between border-b border-slate-200 dark:border-robo-800 pb-1">
<span className="text-slate-500">Model</span>
<span className="font-bold text-slate-800 dark:text-slate-200">{state.phase1Result.specs.metadata.brand} {state.phase1Result.specs.metadata.model_name}</span>
</div>
<div className="flex justify-between border-b border-slate-200 dark:border-robo-800 pb-1">
<span className="text-slate-500">Category</span>
<span className="capitalize text-slate-800 dark:text-slate-200">{state.phase1Result.specs.metadata.category}</span>
</div>
<div className="flex justify-between border-b border-slate-200 dark:border-robo-800 pb-1">
<span className="text-slate-500">Runtime</span>
<span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.core_specs.battery_runtime_min ? `${state.phase1Result.specs.core_specs.battery_runtime_min} min` : '-'}</span>
</div>
<div className="flex justify-between border-b border-slate-200 dark:border-robo-800 pb-1">
<span className="text-slate-500">Weight</span>
<span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.core_specs.weight_kg ? `${state.phase1Result.specs.core_specs.weight_kg} kg` : '-'}</span>
</div>
<div className="flex justify-between border-b border-slate-200 dark:border-robo-800 pb-1">
<span className="text-slate-500">Dimensions</span>
<span className="text-slate-800 dark:text-slate-200">
{state.phase1Result.specs.core_specs.dimensions_cm.l ? `${state.phase1Result.specs.core_specs.dimensions_cm.l}x${state.phase1Result.specs.core_specs.dimensions_cm.w}x${state.phase1Result.specs.core_specs.dimensions_cm.h} cm` : '-'}
</span>
</div>
<div className="flex justify-between">
<span className="text-slate-500">Navigation</span>
<span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.core_specs.navigation_type || '-'}</span>
</div>
</div>
</div>
{/* Layer Data */}
<div className="p-4 bg-slate-50 dark:bg-robo-900 rounded-lg border border-slate-200 dark:border-robo-700">
<h3 className="text-xs font-bold uppercase tracking-wider text-slate-500 mb-3">Performance Layer</h3>
{!state.phase1Result.specs.layers.cleaning && !state.phase1Result.specs.layers.service && !state.phase1Result.specs.layers.security && (
<div className="text-sm text-slate-400 italic">No specific layer data detected.</div>
)}
{state.phase1Result.specs.layers.cleaning && (
<div className="space-y-2 text-sm mb-4">
<div className="text-emerald-600 font-bold mb-1 flex items-center gap-1"><Sparkles size={12}/> Cleaning Mode</div>
<div className="flex justify-between"><span className="text-slate-500">Area Perf:</span> <span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.layers.cleaning.area_performance_sqm_h || '-'} m²/h</span></div>
<div className="flex justify-between"><span className="text-slate-500">Fresh Water:</span> <span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.layers.cleaning.fresh_water_l || '-'} L</span></div>
</div>
)}
{state.phase1Result.specs.layers.service && (
<div className="space-y-2 text-sm mb-4">
<div className="text-blue-600 font-bold mb-1 flex items-center gap-1"><Briefcase size={12}/> Service Mode</div>
<div className="flex justify-between"><span className="text-slate-500">Payload:</span> <span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.layers.service.max_payload_kg || '-'} kg</span></div>
<div className="flex justify-between"><span className="text-slate-500">Trays:</span> <span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.layers.service.number_of_trays || '-'}</span></div>
</div>
)}
{state.phase1Result.specs.layers.security && (
<div className="space-y-2 text-sm">
<div className="text-red-600 font-bold mb-1 flex items-center gap-1"><Shield size={12}/> Security Mode</div>
<div className="flex justify-between"><span className="text-slate-500">Night Vision:</span> <span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.layers.security.night_vision ? 'Yes' : 'No'}</span></div>
<div className="flex justify-between"><span className="text-slate-500">Cameras:</span> <span className="text-slate-800 dark:text-slate-200">{state.phase1Result.specs.layers.security.camera_types.join(', ') || '-'}</span></div>
</div>
)}
</div>
</div>
{/* Extended Features */}
{state.phase1Result.specs.extended_features.length > 0 && (
<div>
<h3 className="text-xs font-bold uppercase tracking-wider text-slate-500 mb-2">Extended Features</h3>
<div className="flex flex-wrap gap-2">
{state.phase1Result.specs.extended_features.map((feat, idx) => (
<span key={idx} className="px-2 py-1 bg-slate-100 dark:bg-robo-900 text-xs rounded border border-slate-200 dark:border-robo-700 text-slate-700 dark:text-slate-300">
<span className="font-bold">{feat.feature}:</span> {feat.value} {feat.unit}
</span>
))}
</div>
</div>
)}
</div>
)}
{state.phase1Result?.conflictCheck.hasConflict ? (
<div className="border p-6 rounded-xl flex gap-4 transition-colors
bg-red-50 border-red-200