added docker and nextjs app
Browse files- .env.example +6 -0
- .gitignore +42 -0
- AGENTS.md +5 -0
- CLAUDE.md +1 -0
- README.md +36 -11
- app/analyze/page.tsx +123 -0
- app/favicon.ico +0 -0
- app/globals.css +160 -0
- app/layout.tsx +33 -0
- app/page.tsx +23 -0
- components.json +25 -0
- components/analyze/analysis-result.tsx +117 -0
- components/analyze/analyze-form.tsx +78 -0
- components/analyze/loading-state.tsx +21 -0
- components/analyze/status-badge.tsx +40 -0
- components/analyze/upload-zone.tsx +128 -0
- components/landing/cta.tsx +45 -0
- components/landing/dashboard-preview.tsx +232 -0
- components/landing/features.tsx +85 -0
- components/landing/footer.tsx +66 -0
- components/landing/hero.tsx +96 -0
- components/landing/navbar.tsx +31 -0
- components/landing/workflow.tsx +100 -0
- components/ui/badge.tsx +36 -0
- components/ui/button.tsx +67 -0
- components/ui/card.tsx +76 -0
- components/ui/input.tsx +21 -0
- components/ui/label.tsx +24 -0
- dockerfile +10 -0
- eslint.config.mjs +18 -0
- hooks/use-analyze.ts +103 -0
- lib/api.ts +53 -0
- lib/types.ts +20 -0
- lib/utils.ts +6 -0
- next.config.js +5 -0
- next.config.ts +7 -0
- package-lock.json +0 -0
- package.json +40 -0
- postcss.config.mjs +7 -0
- public/file.svg +1 -0
- public/globe.svg +1 -0
- public/next.svg +1 -0
- public/vercel.svg +1 -0
- public/window.svg +1 -0
- tsconfig.json +34 -0
.env.example
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Manufacturing Agent - Frontend Configuration
|
| 2 |
+
|
| 3 |
+
# Backend API URL for analyze endpoint
|
| 4 |
+
# Local development: http://localhost:8080
|
| 5 |
+
# Production: Update with your backend deployment URL
|
| 6 |
+
NEXT_PUBLIC_API_URL=http://localhost:8080
|
.gitignore
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
| 2 |
+
|
| 3 |
+
# dependencies
|
| 4 |
+
/node_modules
|
| 5 |
+
/.pnp
|
| 6 |
+
.pnp.*
|
| 7 |
+
.yarn/*
|
| 8 |
+
!.yarn/patches
|
| 9 |
+
!.yarn/plugins
|
| 10 |
+
!.yarn/releases
|
| 11 |
+
!.yarn/versions
|
| 12 |
+
|
| 13 |
+
# testing
|
| 14 |
+
/coverage
|
| 15 |
+
|
| 16 |
+
# next.js
|
| 17 |
+
/.next/
|
| 18 |
+
/out/
|
| 19 |
+
|
| 20 |
+
# production
|
| 21 |
+
/build
|
| 22 |
+
|
| 23 |
+
# misc
|
| 24 |
+
.DS_Store
|
| 25 |
+
*.pem
|
| 26 |
+
|
| 27 |
+
# debug
|
| 28 |
+
npm-debug.log*
|
| 29 |
+
yarn-debug.log*
|
| 30 |
+
yarn-error.log*
|
| 31 |
+
.pnpm-debug.log*
|
| 32 |
+
|
| 33 |
+
# env files (can opt-in for committing if needed)
|
| 34 |
+
.env
|
| 35 |
+
.env.local
|
| 36 |
+
|
| 37 |
+
# vercel
|
| 38 |
+
.vercel
|
| 39 |
+
|
| 40 |
+
# typescript
|
| 41 |
+
*.tsbuildinfo
|
| 42 |
+
next-env.d.ts
|
AGENTS.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!-- BEGIN:nextjs-agent-rules -->
|
| 2 |
+
# This is NOT the Next.js you know
|
| 3 |
+
|
| 4 |
+
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
| 5 |
+
<!-- END:nextjs-agent-rules -->
|
CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
@AGENTS.md
|
README.md
CHANGED
|
@@ -1,11 +1,36 @@
|
|
| 1 |
-
---
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
|
| 2 |
+
|
| 3 |
+
## Getting Started
|
| 4 |
+
|
| 5 |
+
First, run the development server:
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
npm run dev
|
| 9 |
+
# or
|
| 10 |
+
yarn dev
|
| 11 |
+
# or
|
| 12 |
+
pnpm dev
|
| 13 |
+
# or
|
| 14 |
+
bun dev
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
| 18 |
+
|
| 19 |
+
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
| 20 |
+
|
| 21 |
+
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
| 22 |
+
|
| 23 |
+
## Learn More
|
| 24 |
+
|
| 25 |
+
To learn more about Next.js, take a look at the following resources:
|
| 26 |
+
|
| 27 |
+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
| 28 |
+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
| 29 |
+
|
| 30 |
+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
| 31 |
+
|
| 32 |
+
## Deploy on Vercel
|
| 33 |
+
|
| 34 |
+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
| 35 |
+
|
| 36 |
+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
app/analyze/page.tsx
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useState } from "react";
|
| 4 |
+
import { useAnalyze } from "@/hooks/use-analyze";
|
| 5 |
+
import { UploadZone } from "@/components/analyze/upload-zone";
|
| 6 |
+
import { AnalyzeForm } from "@/components/analyze/analyze-form";
|
| 7 |
+
import { LoadingState } from "@/components/analyze/loading-state";
|
| 8 |
+
import { AnalysisResult } from "@/components/analyze/analysis-result";
|
| 9 |
+
import { Button } from "@/components/ui/button";
|
| 10 |
+
import { Navbar } from "@/components/landing/navbar";
|
| 11 |
+
import { Footer } from "@/components/landing/footer";
|
| 12 |
+
import { AlertCircle, ArrowRight } from "lucide-react";
|
| 13 |
+
|
| 14 |
+
export default function AnalyzePage() {
|
| 15 |
+
const {
|
| 16 |
+
form,
|
| 17 |
+
selectedFile,
|
| 18 |
+
handleFileSelect,
|
| 19 |
+
handleFileRemove,
|
| 20 |
+
onSubmit,
|
| 21 |
+
result,
|
| 22 |
+
error,
|
| 23 |
+
isLoading,
|
| 24 |
+
resetAnalysis,
|
| 25 |
+
} = useAnalyze();
|
| 26 |
+
|
| 27 |
+
const [isDragging, setIsDragging] = useState(false);
|
| 28 |
+
|
| 29 |
+
if (result) {
|
| 30 |
+
return (
|
| 31 |
+
<>
|
| 32 |
+
<Navbar />
|
| 33 |
+
<div className="min-h-screen bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950 pt-20 pb-16 px-4">
|
| 34 |
+
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
| 35 |
+
<div className="absolute top-20 left-1/4 w-96 h-96 bg-blue-500/10 rounded-full blur-3xl"></div>
|
| 36 |
+
<div className="absolute bottom-20 right-1/4 w-80 h-80 bg-cyan-500/10 rounded-full blur-3xl"></div>
|
| 37 |
+
</div>
|
| 38 |
+
<div className="max-w-5xl mx-auto relative z-10">
|
| 39 |
+
<AnalysisResult data={result} />
|
| 40 |
+
<div className="mt-10 flex gap-4 justify-center">
|
| 41 |
+
<Button
|
| 42 |
+
onClick={resetAnalysis}
|
| 43 |
+
className="px-8 py-3 bg-gradient-to-r from-blue-600 to-cyan-500 hover:from-blue-700 hover:to-cyan-600 text-white rounded-lg font-semibold transition-all active:scale-95 shadow-lg"
|
| 44 |
+
>
|
| 45 |
+
<ArrowRight className="w-4 h-4 mr-2" />
|
| 46 |
+
Analyze Another File
|
| 47 |
+
</Button>
|
| 48 |
+
</div>
|
| 49 |
+
</div>
|
| 50 |
+
</div>
|
| 51 |
+
<Footer />
|
| 52 |
+
</>
|
| 53 |
+
);
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
return (
|
| 57 |
+
<>
|
| 58 |
+
<Navbar />
|
| 59 |
+
<div className="min-h-screen bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950 pt-20 pb-16 px-4 sm:px-6 lg:px-8">
|
| 60 |
+
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
| 61 |
+
<div className="absolute top-20 left-1/4 w-96 h-96 bg-blue-500/10 rounded-full blur-3xl"></div>
|
| 62 |
+
<div className="absolute bottom-20 right-1/4 w-80 h-80 bg-cyan-500/10 rounded-full blur-3xl"></div>
|
| 63 |
+
</div>
|
| 64 |
+
|
| 65 |
+
<div className="max-w-5xl mx-auto relative z-10">
|
| 66 |
+
<div className="mb-16 text-center">
|
| 67 |
+
<div className="inline-block mb-4 px-3 py-1 bg-blue-500/10 border border-blue-500/30 rounded-full">
|
| 68 |
+
<span className="text-xs font-semibold text-blue-400 uppercase tracking-widest">Precision Analysis</span>
|
| 69 |
+
</div>
|
| 70 |
+
<h1 className="text-5xl sm:text-6xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-cyan-300 mb-4 tracking-tight">
|
| 71 |
+
Manufacturability Check
|
| 72 |
+
</h1>
|
| 73 |
+
<p className="text-lg text-slate-400 max-w-2xl mx-auto leading-relaxed">
|
| 74 |
+
Upload your STEP file and specify parameters to instantly verify manufacturing feasibility
|
| 75 |
+
</p>
|
| 76 |
+
</div>
|
| 77 |
+
|
| 78 |
+
<div className="grid lg:grid-cols-3 gap-8 items-start">
|
| 79 |
+
<div className="lg:col-span-2 space-y-6">
|
| 80 |
+
<div className="bg-gradient-to-br from-slate-800/50 to-slate-900/50 border border-slate-700/50 rounded-2xl p-8 backdrop-blur-sm hover:border-blue-500/30 transition-all duration-300">
|
| 81 |
+
<label className="text-xs font-semibold uppercase tracking-widest text-slate-300 mb-4 block">📁 Upload STEP File</label>
|
| 82 |
+
<UploadZone
|
| 83 |
+
file={selectedFile}
|
| 84 |
+
onFileSelect={handleFileSelect}
|
| 85 |
+
onFileRemove={handleFileRemove}
|
| 86 |
+
isDragging={isDragging}
|
| 87 |
+
onDragEnter={() => setIsDragging(true)}
|
| 88 |
+
onDragLeave={() => setIsDragging(false)}
|
| 89 |
+
isDisabled={isLoading}
|
| 90 |
+
/>
|
| 91 |
+
</div>
|
| 92 |
+
|
| 93 |
+
{isLoading && <LoadingState />}
|
| 94 |
+
|
| 95 |
+
{error && (
|
| 96 |
+
<div className="border border-red-500/30 rounded-xl p-5 bg-red-950/20 backdrop-blur-sm flex gap-4">
|
| 97 |
+
<AlertCircle className="w-5 h-5 text-red-400 flex-shrink-0 mt-1" />
|
| 98 |
+
<div className="flex-1">
|
| 99 |
+
<p className="text-sm font-semibold text-red-200">Analysis Failed</p>
|
| 100 |
+
<p className="text-sm text-red-300 mt-1">{error}</p>
|
| 101 |
+
</div>
|
| 102 |
+
</div>
|
| 103 |
+
)}
|
| 104 |
+
</div>
|
| 105 |
+
|
| 106 |
+
<div className="lg:col-span-1">
|
| 107 |
+
<div className="sticky top-24 bg-gradient-to-br from-slate-800/50 to-slate-900/50 border border-slate-700/50 rounded-2xl p-6 backdrop-blur-sm">
|
| 108 |
+
<label className="text-xs font-semibold uppercase tracking-widest text-slate-300 mb-4 block">⚙️ Parameters</label>
|
| 109 |
+
<AnalyzeForm
|
| 110 |
+
form={form}
|
| 111 |
+
onSubmit={onSubmit}
|
| 112 |
+
isLoading={isLoading}
|
| 113 |
+
isDisabled={!selectedFile}
|
| 114 |
+
/>
|
| 115 |
+
</div>
|
| 116 |
+
</div>
|
| 117 |
+
</div>
|
| 118 |
+
</div>
|
| 119 |
+
</div>
|
| 120 |
+
<Footer />
|
| 121 |
+
</>
|
| 122 |
+
);
|
| 123 |
+
}
|
app/favicon.ico
ADDED
|
|
app/globals.css
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import "tailwindcss";
|
| 2 |
+
@import "tw-animate-css";
|
| 3 |
+
@import "shadcn/tailwind.css";
|
| 4 |
+
|
| 5 |
+
@custom-variant dark (&:is(.dark *));
|
| 6 |
+
|
| 7 |
+
@theme inline {
|
| 8 |
+
--color-background: var(--background);
|
| 9 |
+
--color-foreground: var(--foreground);
|
| 10 |
+
--font-sans: var(--font-sans);
|
| 11 |
+
--font-mono: var(--font-geist-mono);
|
| 12 |
+
--font-heading: var(--font-sans);
|
| 13 |
+
--color-sidebar-ring: var(--sidebar-ring);
|
| 14 |
+
--color-sidebar-border: var(--sidebar-border);
|
| 15 |
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
| 16 |
+
--color-sidebar-accent: var(--sidebar-accent);
|
| 17 |
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
| 18 |
+
--color-sidebar-primary: var(--sidebar-primary);
|
| 19 |
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
| 20 |
+
--color-sidebar: var(--sidebar);
|
| 21 |
+
--color-chart-5: var(--chart-5);
|
| 22 |
+
--color-chart-4: var(--chart-4);
|
| 23 |
+
--color-chart-3: var(--chart-3);
|
| 24 |
+
--color-chart-2: var(--chart-2);
|
| 25 |
+
--color-chart-1: var(--chart-1);
|
| 26 |
+
--color-ring: var(--ring);
|
| 27 |
+
--color-input: var(--input);
|
| 28 |
+
--color-border: var(--border);
|
| 29 |
+
--color-destructive: var(--destructive);
|
| 30 |
+
--color-accent-foreground: var(--accent-foreground);
|
| 31 |
+
--color-accent: var(--accent);
|
| 32 |
+
--color-muted-foreground: var(--muted-foreground);
|
| 33 |
+
--color-muted: var(--muted);
|
| 34 |
+
--color-secondary-foreground: var(--secondary-foreground);
|
| 35 |
+
--color-secondary: var(--secondary);
|
| 36 |
+
--color-primary-foreground: var(--primary-foreground);
|
| 37 |
+
--color-primary: var(--primary);
|
| 38 |
+
--color-popover-foreground: var(--popover-foreground);
|
| 39 |
+
--color-popover: var(--popover);
|
| 40 |
+
--color-card-foreground: var(--card-foreground);
|
| 41 |
+
--color-card: var(--card);
|
| 42 |
+
--radius-sm: calc(var(--radius) * 0.6);
|
| 43 |
+
--radius-md: calc(var(--radius) * 0.8);
|
| 44 |
+
--radius-lg: var(--radius);
|
| 45 |
+
--radius-xl: calc(var(--radius) * 1.4);
|
| 46 |
+
--radius-2xl: calc(var(--radius) * 1.8);
|
| 47 |
+
--radius-3xl: calc(var(--radius) * 2.2);
|
| 48 |
+
--radius-4xl: calc(var(--radius) * 2.6);
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
:root {
|
| 52 |
+
--background: oklch(1 0 0);
|
| 53 |
+
--foreground: oklch(0.145 0 0);
|
| 54 |
+
--card: oklch(1 0 0);
|
| 55 |
+
--card-foreground: oklch(0.145 0 0);
|
| 56 |
+
--popover: oklch(1 0 0);
|
| 57 |
+
--popover-foreground: oklch(0.145 0 0);
|
| 58 |
+
--primary: oklch(0.205 0 0);
|
| 59 |
+
--primary-foreground: oklch(0.985 0 0);
|
| 60 |
+
--secondary: oklch(0.97 0 0);
|
| 61 |
+
--secondary-foreground: oklch(0.205 0 0);
|
| 62 |
+
--muted: oklch(0.97 0 0);
|
| 63 |
+
--muted-foreground: oklch(0.556 0 0);
|
| 64 |
+
--accent: oklch(0.97 0 0);
|
| 65 |
+
--accent-foreground: oklch(0.205 0 0);
|
| 66 |
+
--destructive: oklch(0.577 0.245 27.325);
|
| 67 |
+
--border: oklch(0.922 0 0);
|
| 68 |
+
--input: oklch(0.922 0 0);
|
| 69 |
+
--ring: oklch(0.708 0 0);
|
| 70 |
+
--chart-1: oklch(0.87 0 0);
|
| 71 |
+
--chart-2: oklch(0.556 0 0);
|
| 72 |
+
--chart-3: oklch(0.439 0 0);
|
| 73 |
+
--chart-4: oklch(0.371 0 0);
|
| 74 |
+
--chart-5: oklch(0.269 0 0);
|
| 75 |
+
--radius: 0.625rem;
|
| 76 |
+
--sidebar: oklch(0.985 0 0);
|
| 77 |
+
--sidebar-foreground: oklch(0.145 0 0);
|
| 78 |
+
--sidebar-primary: oklch(0.205 0 0);
|
| 79 |
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
| 80 |
+
--sidebar-accent: oklch(0.97 0 0);
|
| 81 |
+
--sidebar-accent-foreground: oklch(0.205 0 0);
|
| 82 |
+
--sidebar-border: oklch(0.922 0 0);
|
| 83 |
+
--sidebar-ring: oklch(0.708 0 0);
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
.dark {
|
| 87 |
+
--background: oklch(0.145 0 0);
|
| 88 |
+
--foreground: oklch(0.985 0 0);
|
| 89 |
+
--card: oklch(0.205 0 0);
|
| 90 |
+
--card-foreground: oklch(0.985 0 0);
|
| 91 |
+
--popover: oklch(0.205 0 0);
|
| 92 |
+
--popover-foreground: oklch(0.985 0 0);
|
| 93 |
+
--primary: oklch(0.922 0 0);
|
| 94 |
+
--primary-foreground: oklch(0.205 0 0);
|
| 95 |
+
--secondary: oklch(0.269 0 0);
|
| 96 |
+
--secondary-foreground: oklch(0.985 0 0);
|
| 97 |
+
--muted: oklch(0.269 0 0);
|
| 98 |
+
--muted-foreground: oklch(0.708 0 0);
|
| 99 |
+
--accent: oklch(0.269 0 0);
|
| 100 |
+
--accent-foreground: oklch(0.985 0 0);
|
| 101 |
+
--destructive: oklch(0.704 0.191 22.216);
|
| 102 |
+
--border: oklch(1 0 0 / 10%);
|
| 103 |
+
--input: oklch(1 0 0 / 15%);
|
| 104 |
+
--ring: oklch(0.556 0 0);
|
| 105 |
+
--chart-1: oklch(0.87 0 0);
|
| 106 |
+
--chart-2: oklch(0.556 0 0);
|
| 107 |
+
--chart-3: oklch(0.439 0 0);
|
| 108 |
+
--chart-4: oklch(0.371 0 0);
|
| 109 |
+
--chart-5: oklch(0.269 0 0);
|
| 110 |
+
--sidebar: oklch(0.205 0 0);
|
| 111 |
+
--sidebar-foreground: oklch(0.985 0 0);
|
| 112 |
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
| 113 |
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
| 114 |
+
--sidebar-accent: oklch(0.269 0 0);
|
| 115 |
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
| 116 |
+
--sidebar-border: oklch(1 0 0 / 10%);
|
| 117 |
+
--sidebar-ring: oklch(0.556 0 0);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
@layer base {
|
| 121 |
+
html {
|
| 122 |
+
@apply font-sans scroll-smooth;
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
* {
|
| 126 |
+
@apply border-border outline-ring/50;
|
| 127 |
+
}
|
| 128 |
+
|
| 129 |
+
body {
|
| 130 |
+
@apply bg-background text-foreground;
|
| 131 |
+
}
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
/* Smooth transitions for interactive elements */
|
| 135 |
+
@layer components {
|
| 136 |
+
button,
|
| 137 |
+
a,
|
| 138 |
+
input,
|
| 139 |
+
select,
|
| 140 |
+
textarea {
|
| 141 |
+
@apply transition-all duration-300 ease-in-out;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
/* Specific smooth transitions for buttons */
|
| 145 |
+
button {
|
| 146 |
+
@apply active:scale-95 focus:outline-none focus:ring-2 focus:ring-offset-2;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
/* Link hover effects */
|
| 150 |
+
a {
|
| 151 |
+
@apply hover:text-cyan-300;
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
/* Form element transitions */
|
| 155 |
+
input,
|
| 156 |
+
textarea,
|
| 157 |
+
select {
|
| 158 |
+
@apply focus:ring-2 focus:ring-cyan-500/50 focus:border-cyan-400;
|
| 159 |
+
}
|
| 160 |
+
}
|
app/layout.tsx
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { Metadata } from "next";
|
| 2 |
+
import { Geist, Geist_Mono } from "next/font/google";
|
| 3 |
+
import "./globals.css";
|
| 4 |
+
|
| 5 |
+
const geistSans = Geist({
|
| 6 |
+
variable: "--font-geist-sans",
|
| 7 |
+
subsets: ["latin"],
|
| 8 |
+
});
|
| 9 |
+
|
| 10 |
+
const geistMono = Geist_Mono({
|
| 11 |
+
variable: "--font-geist-mono",
|
| 12 |
+
subsets: ["latin"],
|
| 13 |
+
});
|
| 14 |
+
|
| 15 |
+
export const metadata: Metadata = {
|
| 16 |
+
title: "Create Next App",
|
| 17 |
+
description: "Generated by create next app",
|
| 18 |
+
};
|
| 19 |
+
|
| 20 |
+
export default function RootLayout({
|
| 21 |
+
children,
|
| 22 |
+
}: Readonly<{
|
| 23 |
+
children: React.ReactNode;
|
| 24 |
+
}>) {
|
| 25 |
+
return (
|
| 26 |
+
<html
|
| 27 |
+
lang="en"
|
| 28 |
+
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased dark`}
|
| 29 |
+
>
|
| 30 |
+
<body className="min-h-full flex flex-col">{children}</body>
|
| 31 |
+
</html>
|
| 32 |
+
);
|
| 33 |
+
}
|
app/page.tsx
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Navbar } from "@/components/landing/navbar";
|
| 2 |
+
import { Hero } from "@/components/landing/hero";
|
| 3 |
+
import { Features } from "@/components/landing/features";
|
| 4 |
+
import { Workflow } from "@/components/landing/workflow";
|
| 5 |
+
import { DashboardPreview } from "@/components/landing/dashboard-preview";
|
| 6 |
+
import { CTA } from "@/components/landing/cta";
|
| 7 |
+
import { Footer } from "@/components/landing/footer";
|
| 8 |
+
|
| 9 |
+
export default function Home() {
|
| 10 |
+
return (
|
| 11 |
+
<div className="flex flex-col min-h-screen">
|
| 12 |
+
<Navbar />
|
| 13 |
+
<main className="flex-1">
|
| 14 |
+
<Hero />
|
| 15 |
+
<Features />
|
| 16 |
+
<Workflow />
|
| 17 |
+
<DashboardPreview />
|
| 18 |
+
<CTA />
|
| 19 |
+
</main>
|
| 20 |
+
<Footer />
|
| 21 |
+
</div>
|
| 22 |
+
);
|
| 23 |
+
}
|
components.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"$schema": "https://ui.shadcn.com/schema.json",
|
| 3 |
+
"style": "radix-nova",
|
| 4 |
+
"rsc": true,
|
| 5 |
+
"tsx": true,
|
| 6 |
+
"tailwind": {
|
| 7 |
+
"config": "",
|
| 8 |
+
"css": "app/globals.css",
|
| 9 |
+
"baseColor": "neutral",
|
| 10 |
+
"cssVariables": true,
|
| 11 |
+
"prefix": ""
|
| 12 |
+
},
|
| 13 |
+
"iconLibrary": "lucide",
|
| 14 |
+
"rtl": false,
|
| 15 |
+
"aliases": {
|
| 16 |
+
"components": "@/components",
|
| 17 |
+
"utils": "@/lib/utils",
|
| 18 |
+
"ui": "@/components/ui",
|
| 19 |
+
"lib": "@/lib",
|
| 20 |
+
"hooks": "@/hooks"
|
| 21 |
+
},
|
| 22 |
+
"menuColor": "default",
|
| 23 |
+
"menuAccent": "subtle",
|
| 24 |
+
"registries": {}
|
| 25 |
+
}
|
components/analyze/analysis-result.tsx
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { AnalysisResponse } from "@/lib/types";
|
| 4 |
+
import { StatusBadge } from "./status-badge";
|
| 5 |
+
import { Wrench, Lightbulb, AlertCircle, Zap } from "lucide-react";
|
| 6 |
+
|
| 7 |
+
interface AnalysisResultProps {
|
| 8 |
+
data: AnalysisResponse;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
export function AnalysisResult({ data }: AnalysisResultProps) {
|
| 12 |
+
return (
|
| 13 |
+
<div className="space-y-8 animate-in fade-in duration-500">
|
| 14 |
+
<div className="border-b border-gray-700 pb-6 space-y-3">
|
| 15 |
+
<div className="flex items-start justify-between gap-4">
|
| 16 |
+
<div className="space-y-1 flex-1">
|
| 17 |
+
<h2 className="text-2xl font-mono font-bold text-gray-100 uppercase tracking-wide">
|
| 18 |
+
ANALYSIS COMPLETE
|
| 19 |
+
</h2>
|
| 20 |
+
<p className="text-xs text-gray-500 font-mono">
|
| 21 |
+
{new Date().toLocaleString()}
|
| 22 |
+
</p>
|
| 23 |
+
</div>
|
| 24 |
+
<StatusBadge decision={data.decision} />
|
| 25 |
+
</div>
|
| 26 |
+
</div>
|
| 27 |
+
|
| 28 |
+
<div className="bg-gray-900/50 border border-gray-700/50 rounded p-4 space-y-2">
|
| 29 |
+
<p className="text-sm text-gray-200 leading-relaxed font-mono">
|
| 30 |
+
{data.summary}
|
| 31 |
+
</p>
|
| 32 |
+
</div>
|
| 33 |
+
|
| 34 |
+
<div className="grid md:grid-cols-2 gap-6">
|
| 35 |
+
{data.operations && data.operations.length > 0 && (
|
| 36 |
+
<div className="border border-gray-700/50 rounded p-4 space-y-3">
|
| 37 |
+
<h3 className="text-xs font-mono uppercase tracking-wider text-blue-400 font-semibold flex items-center gap-2">
|
| 38 |
+
<Wrench className="w-4 h-4" />
|
| 39 |
+
REQUIRED OPERATIONS
|
| 40 |
+
</h3>
|
| 41 |
+
<ul className="space-y-2">
|
| 42 |
+
{data.operations.map((op, idx) => (
|
| 43 |
+
<li
|
| 44 |
+
key={idx}
|
| 45 |
+
className="text-sm text-gray-300 font-mono flex items-start gap-2"
|
| 46 |
+
>
|
| 47 |
+
<span className="mt-1.5 w-1.5 h-1.5 rounded-full bg-blue-500 flex-shrink-0" />
|
| 48 |
+
<span>{op}</span>
|
| 49 |
+
</li>
|
| 50 |
+
))}
|
| 51 |
+
</ul>
|
| 52 |
+
</div>
|
| 53 |
+
)}
|
| 54 |
+
|
| 55 |
+
{data.required_tools && data.required_tools.length > 0 && (
|
| 56 |
+
<div className="border border-gray-700/50 rounded p-4 space-y-3">
|
| 57 |
+
<h3 className="text-xs font-mono uppercase tracking-wider text-green-400 font-semibold flex items-center gap-2">
|
| 58 |
+
<Zap className="w-4 h-4" />
|
| 59 |
+
AVAILABLE TOOLS
|
| 60 |
+
</h3>
|
| 61 |
+
<ul className="space-y-2">
|
| 62 |
+
{data.required_tools.map((tool, idx) => (
|
| 63 |
+
<li
|
| 64 |
+
key={idx}
|
| 65 |
+
className="text-sm text-gray-300 font-mono flex items-start gap-2"
|
| 66 |
+
>
|
| 67 |
+
<span className="mt-1.5 w-1.5 h-1.5 rounded-full bg-green-500 flex-shrink-0" />
|
| 68 |
+
<span>{tool}</span>
|
| 69 |
+
</li>
|
| 70 |
+
))}
|
| 71 |
+
</ul>
|
| 72 |
+
</div>
|
| 73 |
+
)}
|
| 74 |
+
</div>
|
| 75 |
+
|
| 76 |
+
{data.missing_tools && data.missing_tools.length > 0 && (
|
| 77 |
+
<div className="border border-red-700/50 bg-red-950/20 rounded p-4 space-y-3">
|
| 78 |
+
<h3 className="text-xs font-mono uppercase tracking-wider text-red-400 font-semibold flex items-center gap-2">
|
| 79 |
+
<AlertCircle className="w-4 h-4" />
|
| 80 |
+
MISSING TOOLS
|
| 81 |
+
</h3>
|
| 82 |
+
<ul className="space-y-2">
|
| 83 |
+
{data.missing_tools.map((tool, idx) => (
|
| 84 |
+
<li
|
| 85 |
+
key={idx}
|
| 86 |
+
className="text-sm text-red-300 font-mono flex items-start gap-2"
|
| 87 |
+
>
|
| 88 |
+
<span className="mt-1.5 w-1.5 h-1.5 rounded-full bg-red-500 flex-shrink-0" />
|
| 89 |
+
<span>{tool}</span>
|
| 90 |
+
</li>
|
| 91 |
+
))}
|
| 92 |
+
</ul>
|
| 93 |
+
</div>
|
| 94 |
+
)}
|
| 95 |
+
|
| 96 |
+
{data.recommendations && data.recommendations.length > 0 && (
|
| 97 |
+
<div className="border border-blue-700/50 bg-blue-950/20 rounded p-4 space-y-3">
|
| 98 |
+
<h3 className="text-xs font-mono uppercase tracking-wider text-blue-400 font-semibold flex items-center gap-2">
|
| 99 |
+
<Lightbulb className="w-4 h-4" />
|
| 100 |
+
RECOMMENDATIONS
|
| 101 |
+
</h3>
|
| 102 |
+
<ul className="space-y-2">
|
| 103 |
+
{data.recommendations.map((rec, idx) => (
|
| 104 |
+
<li
|
| 105 |
+
key={idx}
|
| 106 |
+
className="text-sm text-blue-300 font-mono flex items-start gap-2"
|
| 107 |
+
>
|
| 108 |
+
<span className="mt-1.5 w-1.5 h-1.5 rounded-full bg-blue-500 flex-shrink-0" />
|
| 109 |
+
<span>{rec}</span>
|
| 110 |
+
</li>
|
| 111 |
+
))}
|
| 112 |
+
</ul>
|
| 113 |
+
</div>
|
| 114 |
+
)}
|
| 115 |
+
</div>
|
| 116 |
+
);
|
| 117 |
+
}
|
components/analyze/analyze-form.tsx
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { Input } from "@/components/ui/input";
|
| 4 |
+
import { Label } from "@/components/ui/label";
|
| 5 |
+
import { Button } from "@/components/ui/button";
|
| 6 |
+
import { UseFormReturn, FieldValues } from "react-hook-form";
|
| 7 |
+
import { Loader2 } from "lucide-react";
|
| 8 |
+
|
| 9 |
+
interface AnalyzeFormProps {
|
| 10 |
+
form: UseFormReturn<any>;
|
| 11 |
+
onSubmit: () => Promise<void>;
|
| 12 |
+
isLoading: boolean;
|
| 13 |
+
isDisabled: boolean;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
export function AnalyzeForm({
|
| 17 |
+
form,
|
| 18 |
+
onSubmit,
|
| 19 |
+
isLoading,
|
| 20 |
+
isDisabled,
|
| 21 |
+
}: AnalyzeFormProps) {
|
| 22 |
+
const { register, formState } = form;
|
| 23 |
+
|
| 24 |
+
return (
|
| 25 |
+
<form onSubmit={onSubmit} className="space-y-4">
|
| 26 |
+
<div className="space-y-2">
|
| 27 |
+
<Label className="text-xs font-semibold uppercase tracking-wider text-slate-300">Material</Label>
|
| 28 |
+
<Input
|
| 29 |
+
{...register("material")}
|
| 30 |
+
placeholder="Steel 304, Aluminium 6061"
|
| 31 |
+
disabled={isLoading || isDisabled}
|
| 32 |
+
className="text-sm bg-slate-700/50 border-slate-600 text-slate-100 placeholder:text-slate-500 focus:border-blue-400 focus:ring-blue-500/30 transition-all duration-300 hover:border-slate-500"
|
| 33 |
+
/>
|
| 34 |
+
{formState.errors.material && (
|
| 35 |
+
<p className="text-xs text-red-400">{formState.errors.material.message as string}</p>
|
| 36 |
+
)}
|
| 37 |
+
</div>
|
| 38 |
+
|
| 39 |
+
<div className="space-y-2">
|
| 40 |
+
<Label className="text-xs font-semibold uppercase tracking-wider text-slate-300">Tolerance</Label>
|
| 41 |
+
<Input
|
| 42 |
+
{...register("tolerance")}
|
| 43 |
+
placeholder="±0.02mm, ±0.05mm"
|
| 44 |
+
disabled={isLoading || isDisabled}
|
| 45 |
+
className="text-sm bg-slate-700/50 border-slate-600 text-slate-100 placeholder:text-slate-500 focus:border-blue-400 focus:ring-blue-500/30 transition-all duration-300 hover:border-slate-500"
|
| 46 |
+
/>
|
| 47 |
+
{formState.errors.tolerance && (
|
| 48 |
+
<p className="text-xs text-red-400">{formState.errors.tolerance.message as string}</p>
|
| 49 |
+
)}
|
| 50 |
+
</div>
|
| 51 |
+
|
| 52 |
+
<div className="space-y-2">
|
| 53 |
+
<Label className="text-xs font-semibold uppercase tracking-wider text-slate-300">Threads (Optional)</Label>
|
| 54 |
+
<Input
|
| 55 |
+
{...register("threads")}
|
| 56 |
+
placeholder="M8x1.25, M10x1.5"
|
| 57 |
+
disabled={isLoading || isDisabled}
|
| 58 |
+
className="text-sm bg-slate-700/50 border-slate-600 text-slate-100 placeholder:text-slate-500 focus:border-blue-400 focus:ring-blue-500/30 transition-all duration-300 hover:border-slate-500"
|
| 59 |
+
/>
|
| 60 |
+
</div>
|
| 61 |
+
|
| 62 |
+
<Button
|
| 63 |
+
type="submit"
|
| 64 |
+
disabled={isLoading || isDisabled}
|
| 65 |
+
className="w-full h-11 font-semibold text-sm bg-gradient-to-r from-blue-600 to-cyan-500 hover:from-blue-700 hover:to-cyan-600 text-white rounded-lg transition-all duration-300 disabled:opacity-50 disabled:cursor-not-allowed active:scale-95 hover:scale-105 hover:shadow-lg hover:shadow-blue-500/50 shadow-lg"
|
| 66 |
+
>
|
| 67 |
+
{isLoading ? (
|
| 68 |
+
<>
|
| 69 |
+
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
|
| 70 |
+
Analyzing...
|
| 71 |
+
</>
|
| 72 |
+
) : (
|
| 73 |
+
"Analyze Now"
|
| 74 |
+
)}
|
| 75 |
+
</Button>
|
| 76 |
+
</form>
|
| 77 |
+
);
|
| 78 |
+
}
|
components/analyze/loading-state.tsx
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { Loader2 } from "lucide-react";
|
| 4 |
+
|
| 5 |
+
export function LoadingState() {
|
| 6 |
+
return (
|
| 7 |
+
<div className="flex flex-col items-center justify-center py-16 gap-6 border-2 border-dashed border-gray-600 rounded-lg bg-gray-950/50 p-12">
|
| 8 |
+
<div className="p-4 rounded-lg bg-blue-950/30 border border-blue-700/30">
|
| 9 |
+
<Loader2 className="w-8 h-8 text-blue-400 animate-spin" />
|
| 10 |
+
</div>
|
| 11 |
+
<div className="text-center space-y-2">
|
| 12 |
+
<p className="text-sm font-mono font-semibold text-gray-200 uppercase tracking-wide">
|
| 13 |
+
ANALYZING MANUFACTURABILITY...
|
| 14 |
+
</p>
|
| 15 |
+
<p className="text-xs text-gray-500 font-mono">
|
| 16 |
+
Processing CAD geometry and checking tool compatibility
|
| 17 |
+
</p>
|
| 18 |
+
</div>
|
| 19 |
+
</div>
|
| 20 |
+
);
|
| 21 |
+
}
|
components/analyze/status-badge.tsx
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { DecisionType } from "@/lib/types";
|
| 4 |
+
import { Check, X, AlertCircle } from "lucide-react";
|
| 5 |
+
|
| 6 |
+
interface StatusBadgeProps {
|
| 7 |
+
decision: DecisionType;
|
| 8 |
+
}
|
| 9 |
+
|
| 10 |
+
export function StatusBadge({ decision }: StatusBadgeProps) {
|
| 11 |
+
const config = {
|
| 12 |
+
YES: {
|
| 13 |
+
bg: "bg-green-950/40 border-green-700/50",
|
| 14 |
+
text: "text-green-400",
|
| 15 |
+
icon: <Check className="w-5 h-5" />,
|
| 16 |
+
label: "MANUFACTURABLE",
|
| 17 |
+
},
|
| 18 |
+
NO: {
|
| 19 |
+
bg: "bg-red-950/40 border-red-700/50",
|
| 20 |
+
text: "text-red-400",
|
| 21 |
+
icon: <X className="w-5 h-5" />,
|
| 22 |
+
label: "NOT MANUFACTURABLE",
|
| 23 |
+
},
|
| 24 |
+
CONDITIONAL: {
|
| 25 |
+
bg: "bg-yellow-950/40 border-yellow-700/50",
|
| 26 |
+
text: "text-yellow-400",
|
| 27 |
+
icon: <AlertCircle className="w-5 h-5" />,
|
| 28 |
+
label: "CONDITIONALLY MANUFACTURABLE",
|
| 29 |
+
},
|
| 30 |
+
}[decision];
|
| 31 |
+
|
| 32 |
+
return (
|
| 33 |
+
<div
|
| 34 |
+
className={`inline-flex items-center gap-3 px-4 py-3 rounded border font-mono text-sm font-semibold uppercase tracking-wide ${config.bg}`}
|
| 35 |
+
>
|
| 36 |
+
<span className={config.text}>{config.icon}</span>
|
| 37 |
+
<span className={config.text}>{config.label}</span>
|
| 38 |
+
</div>
|
| 39 |
+
);
|
| 40 |
+
}
|
components/analyze/upload-zone.tsx
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useRef, useState } from "react";
|
| 4 |
+
import { Upload, X } from "lucide-react";
|
| 5 |
+
import { cn } from "@/lib/utils";
|
| 6 |
+
|
| 7 |
+
interface UploadZoneProps {
|
| 8 |
+
file: File | null;
|
| 9 |
+
onFileSelect: (file: File) => void;
|
| 10 |
+
onFileRemove: () => void;
|
| 11 |
+
isDragging: boolean;
|
| 12 |
+
onDragEnter: () => void;
|
| 13 |
+
onDragLeave: () => void;
|
| 14 |
+
isDisabled?: boolean;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
export function UploadZone({
|
| 18 |
+
file,
|
| 19 |
+
onFileSelect,
|
| 20 |
+
onFileRemove,
|
| 21 |
+
isDragging,
|
| 22 |
+
onDragEnter,
|
| 23 |
+
onDragLeave,
|
| 24 |
+
isDisabled,
|
| 25 |
+
}: UploadZoneProps) {
|
| 26 |
+
const inputRef = useRef<HTMLInputElement>(null);
|
| 27 |
+
|
| 28 |
+
const handleDragOver = (e: React.DragEvent) => {
|
| 29 |
+
e.preventDefault();
|
| 30 |
+
};
|
| 31 |
+
|
| 32 |
+
const handleDrop = (e: React.DragEvent) => {
|
| 33 |
+
e.preventDefault();
|
| 34 |
+
onDragLeave();
|
| 35 |
+
|
| 36 |
+
const droppedFile = e.dataTransfer.files?.[0];
|
| 37 |
+
if (droppedFile) {
|
| 38 |
+
onFileSelect(droppedFile);
|
| 39 |
+
}
|
| 40 |
+
};
|
| 41 |
+
|
| 42 |
+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
| 43 |
+
const selectedFile = e.currentTarget.files?.[0];
|
| 44 |
+
if (selectedFile) {
|
| 45 |
+
onFileSelect(selectedFile);
|
| 46 |
+
}
|
| 47 |
+
};
|
| 48 |
+
|
| 49 |
+
const handleClick = () => {
|
| 50 |
+
if (!isDisabled) {
|
| 51 |
+
inputRef.current?.click();
|
| 52 |
+
}
|
| 53 |
+
};
|
| 54 |
+
|
| 55 |
+
if (file) {
|
| 56 |
+
return (
|
| 57 |
+
<div className="border border-blue-500/30 rounded-xl p-5 bg-gradient-to-r from-blue-950/30 to-cyan-950/30 backdrop-blur-sm transition-all duration-300">
|
| 58 |
+
<div className="flex items-center justify-between gap-4">
|
| 59 |
+
<div className="flex items-center gap-3 flex-1">
|
| 60 |
+
<div className="p-3 rounded-lg bg-gradient-to-br from-blue-500/20 to-cyan-500/20 border border-blue-500/40 transition-all duration-300">
|
| 61 |
+
<Upload className="w-5 h-5 text-blue-300" />
|
| 62 |
+
</div>
|
| 63 |
+
<div className="min-w-0 flex-1">
|
| 64 |
+
<p className="text-sm font-semibold text-blue-100 truncate">{file.name}</p>
|
| 65 |
+
<p className="text-xs text-blue-400/70">{(file.size / 1024).toFixed(2)} KB</p>
|
| 66 |
+
</div>
|
| 67 |
+
</div>
|
| 68 |
+
<button
|
| 69 |
+
onClick={onFileRemove}
|
| 70 |
+
disabled={isDisabled}
|
| 71 |
+
className="p-2 hover:bg-red-500/20 rounded-lg transition-all duration-300 disabled:opacity-50 hover:scale-110 active:scale-95"
|
| 72 |
+
type="button"
|
| 73 |
+
>
|
| 74 |
+
<X className="w-4 h-4 text-red-400" />
|
| 75 |
+
</button>
|
| 76 |
+
</div>
|
| 77 |
+
</div>
|
| 78 |
+
);
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
return (
|
| 82 |
+
<div
|
| 83 |
+
onDragOver={handleDragOver}
|
| 84 |
+
onDrop={handleDrop}
|
| 85 |
+
onDragEnter={onDragEnter}
|
| 86 |
+
onDragLeave={onDragLeave}
|
| 87 |
+
onClick={handleClick}
|
| 88 |
+
className={cn(
|
| 89 |
+
"relative border-2 border-dashed rounded-2xl p-12 transition-all duration-300 cursor-pointer",
|
| 90 |
+
isDragging
|
| 91 |
+
? "border-blue-400 bg-blue-950/40 scale-105"
|
| 92 |
+
: "border-slate-600 hover:border-slate-500 bg-slate-950/40 hover:bg-slate-950/60 hover:scale-102",
|
| 93 |
+
isDisabled && "opacity-50 cursor-not-allowed"
|
| 94 |
+
)}
|
| 95 |
+
>
|
| 96 |
+
<input
|
| 97 |
+
ref={inputRef}
|
| 98 |
+
type="file"
|
| 99 |
+
accept=".step,.stp"
|
| 100 |
+
onChange={handleInputChange}
|
| 101 |
+
disabled={isDisabled}
|
| 102 |
+
className="hidden"
|
| 103 |
+
/>
|
| 104 |
+
|
| 105 |
+
<div className="flex flex-col items-center justify-center gap-4 text-center">
|
| 106 |
+
<div className="p-4 rounded-xl bg-gradient-to-br from-blue-500/20 to-cyan-500/20 border border-blue-500/30">
|
| 107 |
+
<Upload className="w-8 h-8 text-blue-400" />
|
| 108 |
+
</div>
|
| 109 |
+
<div>
|
| 110 |
+
<p className="text-base font-semibold text-slate-100">Drag & Drop CAD Files Here</p>
|
| 111 |
+
<p className="text-sm text-slate-400 mt-1">Supported: STEP, STP (Max 50MB)</p>
|
| 112 |
+
</div>
|
| 113 |
+
<button
|
| 114 |
+
type="button"
|
| 115 |
+
disabled={isDisabled}
|
| 116 |
+
className={cn(
|
| 117 |
+
"px-6 py-2.5 text-sm font-semibold rounded-lg transition-all duration-300",
|
| 118 |
+
isDisabled
|
| 119 |
+
? "bg-slate-700 text-slate-400 cursor-not-allowed"
|
| 120 |
+
: "bg-gradient-to-r from-blue-600 to-cyan-500 hover:from-blue-700 hover:to-cyan-600 text-white shadow-lg hover:shadow-blue-500/50 active:scale-95 hover:scale-105"
|
| 121 |
+
)}
|
| 122 |
+
>
|
| 123 |
+
Select Files
|
| 124 |
+
</button>
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
+
);
|
| 128 |
+
}
|
components/landing/cta.tsx
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from "react";
|
| 2 |
+
import Link from "next/link";
|
| 3 |
+
import { Upload, ArrowRight } from "lucide-react";
|
| 4 |
+
|
| 5 |
+
export function CTA() {
|
| 6 |
+
return (
|
| 7 |
+
<section className="py-32 relative overflow-hidden bg-gradient-to-b from-slate-950 via-slate-900 to-slate-950 border-t border-slate-800">
|
| 8 |
+
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
| 9 |
+
<div className="absolute top-1/4 left-0 w-96 h-96 bg-blue-600/15 rounded-full blur-3xl"></div>
|
| 10 |
+
<div className="absolute bottom-0 right-1/3 w-80 h-80 bg-cyan-600/15 rounded-full blur-3xl"></div>
|
| 11 |
+
</div>
|
| 12 |
+
|
| 13 |
+
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
| 14 |
+
<div className="max-w-4xl mx-auto">
|
| 15 |
+
<div className="bg-gradient-to-br from-slate-800/50 to-slate-900/50 border border-slate-700/50 rounded-2xl p-12 sm:p-16 text-center backdrop-blur-sm">
|
| 16 |
+
<h2 className="text-4xl sm:text-5xl lg:text-6xl font-bold tracking-tight mb-6 text-transparent bg-clip-text bg-gradient-to-r from-blue-300 to-cyan-300">
|
| 17 |
+
Ready to optimize your CNC workflow?
|
| 18 |
+
</h2>
|
| 19 |
+
<p className="text-xl text-slate-300 leading-relaxed mb-10 max-w-2xl mx-auto">
|
| 20 |
+
Join leading machine shops eliminating quoting delays, reducing errors, and maximizing shop floor efficiency with AI-powered manufacturability analysis.
|
| 21 |
+
</p>
|
| 22 |
+
|
| 23 |
+
<div className="flex flex-col sm:flex-row gap-4 justify-center mb-8">
|
| 24 |
+
<Link
|
| 25 |
+
href="/analyze"
|
| 26 |
+
className="inline-flex items-center justify-center gap-2 px-8 py-4 rounded-lg bg-gradient-to-r from-blue-600 to-cyan-500 hover:from-blue-700 hover:to-cyan-600 text-white font-semibold shadow-lg hover:shadow-blue-500/50 transition-all active:scale-95"
|
| 27 |
+
>
|
| 28 |
+
<Upload className="w-5 h-5" />
|
| 29 |
+
Start Free Analysis
|
| 30 |
+
</Link>
|
| 31 |
+
<button className="inline-flex items-center justify-center gap-2 px-8 py-4 rounded-lg border border-slate-600 hover:border-blue-500 text-slate-200 font-semibold hover:bg-slate-800/50 transition-all">
|
| 32 |
+
Learn More
|
| 33 |
+
<ArrowRight className="w-4 h-4" />
|
| 34 |
+
</button>
|
| 35 |
+
</div>
|
| 36 |
+
|
| 37 |
+
<p className="text-sm text-slate-400">
|
| 38 |
+
Supports STEP, STP, and DXF files up to 50MB • 2-second analysis • No credit card required
|
| 39 |
+
</p>
|
| 40 |
+
</div>
|
| 41 |
+
</div>
|
| 42 |
+
</div>
|
| 43 |
+
</section>
|
| 44 |
+
);
|
| 45 |
+
}
|
components/landing/dashboard-preview.tsx
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import React from "react";
|
| 4 |
+
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
| 5 |
+
import { Badge } from "@/components/ui/badge";
|
| 6 |
+
import { AlertCircle, CheckCircle, FileText, Info, Wrench } from "lucide-react";
|
| 7 |
+
import { motion } from "framer-motion";
|
| 8 |
+
|
| 9 |
+
export function DashboardPreview() {
|
| 10 |
+
return (
|
| 11 |
+
<section id="dashboard" className="py-24 relative bg-gradient-to-b from-slate-900/30 to-slate-950/50 border-y border-border/50">
|
| 12 |
+
{/* Decorative blurs */}
|
| 13 |
+
<div className="absolute top-0 right-1/4 w-96 h-96 bg-cyan-500/10 rounded-full blur-3xl -z-10"></div>
|
| 14 |
+
<div className="absolute bottom-0 left-1/4 w-96 h-96 bg-blue-500/10 rounded-full blur-3xl -z-10"></div>
|
| 15 |
+
|
| 16 |
+
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
| 17 |
+
<div className="text-center max-w-3xl mx-auto mb-16">
|
| 18 |
+
<motion.div
|
| 19 |
+
initial={{ opacity: 0, y: 20 }}
|
| 20 |
+
whileInView={{ opacity: 1, y: 0 }}
|
| 21 |
+
viewport={{ once: true }}
|
| 22 |
+
transition={{ duration: 0.5 }}
|
| 23 |
+
>
|
| 24 |
+
<h2 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl mb-4">
|
| 25 |
+
<span className="bg-gradient-to-r from-blue-300 via-cyan-300 to-blue-300 bg-clip-text text-transparent">
|
| 26 |
+
Enterprise Grade Analysis
|
| 27 |
+
</span>
|
| 28 |
+
</h2>
|
| 29 |
+
</motion.div>
|
| 30 |
+
<motion.p
|
| 31 |
+
className="text-lg text-muted-foreground"
|
| 32 |
+
initial={{ opacity: 0, y: 20 }}
|
| 33 |
+
whileInView={{ opacity: 1, y: 0 }}
|
| 34 |
+
viewport={{ once: true }}
|
| 35 |
+
transition={{ duration: 0.5, delay: 0.1 }}
|
| 36 |
+
>
|
| 37 |
+
A comprehensive dashboard providing deep insights into manufacturing feasibility, risk factors, and tool requirements.
|
| 38 |
+
</motion.p>
|
| 39 |
+
</div>
|
| 40 |
+
|
| 41 |
+
<motion.div
|
| 42 |
+
className="max-w-5xl mx-auto rounded-xl border border-cyan-500/20 bg-gradient-to-br from-slate-900/80 to-slate-950/80 shadow-2xl overflow-hidden backdrop-blur-sm"
|
| 43 |
+
initial={{ opacity: 0, y: 40, scale: 0.95 }}
|
| 44 |
+
whileInView={{ opacity: 1, y: 0, scale: 1 }}
|
| 45 |
+
viewport={{ once: true }}
|
| 46 |
+
transition={{ duration: 0.7 }}
|
| 47 |
+
>
|
| 48 |
+
{/* Dashboard Header with gradient */}
|
| 49 |
+
<div className="border-b border-cyan-500/20 bg-gradient-to-r from-slate-900/50 to-slate-950/50 px-6 py-4 flex items-center justify-between backdrop-blur-sm">
|
| 50 |
+
<div className="flex items-center gap-4">
|
| 51 |
+
<h3 className="font-semibold font-mono text-sm text-cyan-300">PART-A92-REV3</h3>
|
| 52 |
+
<Badge className="bg-gradient-to-r from-green-500 to-emerald-500 text-white border-0 rounded-sm shadow-lg">
|
| 53 |
+
<CheckCircle className="w-3 h-3 mr-1" /> Manufacturable
|
| 54 |
+
</Badge>
|
| 55 |
+
</div>
|
| 56 |
+
<div className="flex items-center gap-2">
|
| 57 |
+
<span className="text-xs text-cyan-400/70 font-mono">EST. CYCLE TIME: 4m 12s</span>
|
| 58 |
+
</div>
|
| 59 |
+
</div>
|
| 60 |
+
|
| 61 |
+
<div className="p-6 grid grid-cols-1 lg:grid-cols-3 gap-6">
|
| 62 |
+
{/* Left Column: Drawing Viewer & Agent Reasoning */}
|
| 63 |
+
<div className="lg:col-span-2 flex flex-col gap-6">
|
| 64 |
+
<Card className="flex-1 bg-gradient-to-br from-slate-850/60 to-slate-900/40 border-cyan-500/20 shadow-none">
|
| 65 |
+
<CardHeader className="py-4 px-5 border-b border-cyan-500/20">
|
| 66 |
+
<CardTitle className="text-sm flex items-center font-medium text-cyan-300">
|
| 67 |
+
<FileText className="w-4 h-4 mr-2 text-cyan-400" />
|
| 68 |
+
3D model Analysis
|
| 69 |
+
</CardTitle>
|
| 70 |
+
</CardHeader>
|
| 71 |
+
<CardContent className="p-0 h-64 relative bg-card flex items-center justify-center overflow-hidden">
|
| 72 |
+
{/* Blueprint grid with gradient */}
|
| 73 |
+
<div className="absolute inset-0 opacity-[0.02] bg-[linear-gradient(to_right,#06b6d4_1px,transparent_1px),linear-gradient(to_bottom,#06b6d4_1px,transparent_1px)] bg-[size:20px_20px]"></div>
|
| 74 |
+
<div className="relative border-2 border-cyan-500/40 w-3/4 h-3/4 flex items-center justify-center rounded-lg bg-slate-950/50">
|
| 75 |
+
<span className="font-mono text-xs text-cyan-400/60">Interactive CAD Viewer</span>
|
| 76 |
+
<motion.div
|
| 77 |
+
className="absolute top-1/4 left-1/4 w-4 h-4 rounded-full border border-red-400/60 bg-red-500/20"
|
| 78 |
+
animate={{ scale: [1, 1.2, 1] }}
|
| 79 |
+
transition={{ duration: 2, repeat: Infinity }}
|
| 80 |
+
/>
|
| 81 |
+
<div className="absolute top-1/2 right-1/3 w-4 h-4 rounded-full border border-yellow-400/60 bg-yellow-500/20"></div>
|
| 82 |
+
</div>
|
| 83 |
+
</CardContent>
|
| 84 |
+
</Card>
|
| 85 |
+
|
| 86 |
+
<Card className="bg-gradient-to-br from-slate-850/60 to-slate-900/40 border-cyan-500/20 shadow-none">
|
| 87 |
+
<CardHeader className="py-4 px-5 border-b border-cyan-500/20">
|
| 88 |
+
<CardTitle className="text-sm flex items-center font-medium text-cyan-300">
|
| 89 |
+
<BrainIcon className="w-4 h-4 mr-2 text-cyan-400" />
|
| 90 |
+
Agent Reasoning Panel
|
| 91 |
+
</CardTitle>
|
| 92 |
+
</CardHeader>
|
| 93 |
+
<CardContent className="p-4">
|
| 94 |
+
<div className="space-y-3 font-mono text-xs text-cyan-300/80">
|
| 95 |
+
<motion.div
|
| 96 |
+
className="flex gap-2"
|
| 97 |
+
initial={{ opacity: 0, x: -10 }}
|
| 98 |
+
whileInView={{ opacity: 1, x: 0 }}
|
| 99 |
+
viewport={{ once: true }}
|
| 100 |
+
transition={{ delay: 0 }}
|
| 101 |
+
>
|
| 102 |
+
<span className="text-cyan-400 flex-shrink-0">{">"}</span>
|
| 103 |
+
<p>Analyzed 14 geometric features and 8 dimensional tolerances.</p>
|
| 104 |
+
</motion.div>
|
| 105 |
+
<motion.div
|
| 106 |
+
className="flex gap-2"
|
| 107 |
+
initial={{ opacity: 0, x: -10 }}
|
| 108 |
+
whileInView={{ opacity: 1, x: 0 }}
|
| 109 |
+
viewport={{ once: true }}
|
| 110 |
+
transition={{ delay: 0.1 }}
|
| 111 |
+
>
|
| 112 |
+
<span className="text-cyan-400 flex-shrink-0">{">"}</span>
|
| 113 |
+
<p>Identified required operations: Facing, Roughing, Finishing, Tapping (M6x1.0).</p>
|
| 114 |
+
</motion.div>
|
| 115 |
+
<motion.div
|
| 116 |
+
className="flex gap-2"
|
| 117 |
+
initial={{ opacity: 0, x: -10 }}
|
| 118 |
+
whileInView={{ opacity: 1, x: 0 }}
|
| 119 |
+
viewport={{ once: true }}
|
| 120 |
+
transition={{ delay: 0.2 }}
|
| 121 |
+
>
|
| 122 |
+
<span className="text-cyan-400 flex-shrink-0">{">"}</span>
|
| 123 |
+
<p>Tolerance ±0.01mm on ID fits within machine HAAS-VF2 capability.</p>
|
| 124 |
+
</motion.div>
|
| 125 |
+
<motion.div
|
| 126 |
+
className="flex gap-2 text-green-400"
|
| 127 |
+
initial={{ opacity: 0, x: -10 }}
|
| 128 |
+
whileInView={{ opacity: 1, x: 0 }}
|
| 129 |
+
viewport={{ once: true }}
|
| 130 |
+
transition={{ delay: 0.3 }}
|
| 131 |
+
>
|
| 132 |
+
<span className="text-green-400 flex-shrink-0">{">"}</span>
|
| 133 |
+
<p>Conclusion: Part can be manufactured using standard shop tooling.</p>
|
| 134 |
+
</motion.div>
|
| 135 |
+
</div>
|
| 136 |
+
</CardContent>
|
| 137 |
+
</Card>
|
| 138 |
+
</div>
|
| 139 |
+
|
| 140 |
+
{/* Right Column: Risk Flags & Tools */}
|
| 141 |
+
<div className="flex flex-col gap-6">
|
| 142 |
+
<Card className="bg-gradient-to-br from-slate-850/60 to-slate-900/40 border-cyan-500/20 shadow-none">
|
| 143 |
+
<CardHeader className="py-4 px-5 border-b border-cyan-500/20">
|
| 144 |
+
<CardTitle className="text-sm flex items-center font-medium text-cyan-300">
|
| 145 |
+
<AlertCircle className="w-4 h-4 mr-2 text-cyan-400" />
|
| 146 |
+
Risk Flags
|
| 147 |
+
</CardTitle>
|
| 148 |
+
</CardHeader>
|
| 149 |
+
<CardContent className="p-4 space-y-3">
|
| 150 |
+
<div className="flex items-start gap-3 p-3 rounded-md bg-yellow-500/10 border border-yellow-500/30">
|
| 151 |
+
<AlertCircle className="w-4 h-4 text-yellow-400 mt-0.5 flex-shrink-0" />
|
| 152 |
+
<div>
|
| 153 |
+
<h4 className="text-xs font-semibold text-yellow-100">Deep Hole Drilling</h4>
|
| 154 |
+
<p className="text-[10px] text-yellow-200/70 mt-1">L/D ratio is 6:1. Peck drilling recommended.</p>
|
| 155 |
+
</div>
|
| 156 |
+
</div>
|
| 157 |
+
<div className="flex items-start gap-3 p-3 rounded-md bg-blue-500/10 border border-blue-500/30">
|
| 158 |
+
<Info className="w-4 h-4 text-blue-400 mt-0.5 flex-shrink-0" />
|
| 159 |
+
<div>
|
| 160 |
+
<h4 className="text-xs font-semibold text-blue-100">Thin Wall</h4>
|
| 161 |
+
<p className="text-[10px] text-blue-200/70 mt-1">Wall thickness 1.5mm. Watch for chatter.</p>
|
| 162 |
+
</div>
|
| 163 |
+
</div>
|
| 164 |
+
</CardContent>
|
| 165 |
+
</Card>
|
| 166 |
+
|
| 167 |
+
<Card className="flex-1 bg-gradient-to-br from-slate-850/60 to-slate-900/40 border-cyan-500/20 shadow-none">
|
| 168 |
+
<CardHeader className="py-4 px-5 border-b border-cyan-500/20">
|
| 169 |
+
<CardTitle className="text-sm flex items-center font-medium text-cyan-300">
|
| 170 |
+
<Wrench className="w-4 h-4 mr-2 text-cyan-400" />
|
| 171 |
+
Required Tools
|
| 172 |
+
</CardTitle>
|
| 173 |
+
</CardHeader>
|
| 174 |
+
<CardContent className="p-0">
|
| 175 |
+
<div className="divide-y divide-cyan-500/20 text-xs">
|
| 176 |
+
<div className="flex items-center justify-between p-3 px-5 hover:bg-cyan-500/10 transition-colors">
|
| 177 |
+
<span className="font-mono text-cyan-400/60">T01</span>
|
| 178 |
+
<span className="text-cyan-200">Face Mill 50mm</span>
|
| 179 |
+
<CheckCircle className="w-3 h-3 text-green-400" />
|
| 180 |
+
</div>
|
| 181 |
+
<div className="flex items-center justify-between p-3 px-5 hover:bg-cyan-500/10 transition-colors">
|
| 182 |
+
<span className="font-mono text-cyan-400/60">T04</span>
|
| 183 |
+
<span className="text-cyan-200">End Mill 10mm</span>
|
| 184 |
+
<CheckCircle className="w-3 h-3 text-green-400" />
|
| 185 |
+
</div>
|
| 186 |
+
<div className="flex items-center justify-between p-3 px-5 hover:bg-cyan-500/10 transition-colors">
|
| 187 |
+
<span className="font-mono text-cyan-400/60">T12</span>
|
| 188 |
+
<span className="text-cyan-200">Drill 5.0mm</span>
|
| 189 |
+
<CheckCircle className="w-3 h-3 text-green-400" />
|
| 190 |
+
</div>
|
| 191 |
+
<div className="flex items-center justify-between p-3 px-5 hover:bg-cyan-500/10 transition-colors">
|
| 192 |
+
<span className="font-mono text-cyan-400/60">T15</span>
|
| 193 |
+
<span className="text-cyan-200">Tap M6x1.0</span>
|
| 194 |
+
<CheckCircle className="w-3 h-3 text-green-400" />
|
| 195 |
+
</div>
|
| 196 |
+
</div>
|
| 197 |
+
</CardContent>
|
| 198 |
+
</Card>
|
| 199 |
+
</div>
|
| 200 |
+
</div>
|
| 201 |
+
</motion.div>
|
| 202 |
+
</div>
|
| 203 |
+
</section>
|
| 204 |
+
);
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
function BrainIcon(props: React.SVGProps<SVGSVGElement>) {
|
| 208 |
+
return (
|
| 209 |
+
<svg
|
| 210 |
+
{...props}
|
| 211 |
+
xmlns="http://www.w3.org/2000/svg"
|
| 212 |
+
width="24"
|
| 213 |
+
height="24"
|
| 214 |
+
viewBox="0 0 24 24"
|
| 215 |
+
fill="none"
|
| 216 |
+
stroke="currentColor"
|
| 217 |
+
strokeWidth="2"
|
| 218 |
+
strokeLinecap="round"
|
| 219 |
+
strokeLinejoin="round"
|
| 220 |
+
>
|
| 221 |
+
<path d="M12 5a3 3 0 1 0-5.997.125 4 4 0 0 0-2.526 5.77 4 4 0 0 0 .556 6.588A4 4 0 1 0 12 18Z" />
|
| 222 |
+
<path d="M12 5a3 3 0 1 1 5.997.125 4 4 0 0 1 2.526 5.77 4 4 0 0 1-.556 6.588A4 4 0 1 1 12 18Z" />
|
| 223 |
+
<path d="M15 13a4.5 4.5 0 0 1-3-4 4.5 4.5 0 0 1-3 4" />
|
| 224 |
+
<path d="M17.599 6.5a3 3 0 0 0 .399-1.375" />
|
| 225 |
+
<path d="M6.002 5.125A3 3 0 0 0 6.401 6.5" />
|
| 226 |
+
<path d="M3.477 10.896a4 4 0 0 1 .585-.396" />
|
| 227 |
+
<path d="M19.938 10.5a4 4 0 0 1 .585.396" />
|
| 228 |
+
<path d="M6 18a4 4 0 0 1-1.967-.516" />
|
| 229 |
+
<path d="M19.967 17.484A4 4 0 0 1 18 18" />
|
| 230 |
+
</svg>
|
| 231 |
+
);
|
| 232 |
+
}
|
components/landing/features.tsx
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import React from "react";
|
| 4 |
+
import { FileSearch, Settings2, Wrench, Cpu, AlertTriangle, FileText } from "lucide-react";
|
| 5 |
+
import { motion } from "framer-motion";
|
| 6 |
+
|
| 7 |
+
const features = [
|
| 8 |
+
{
|
| 9 |
+
title: "3D Parsing",
|
| 10 |
+
description: "Instantly parse complex 3D CAD models using AI agents.",
|
| 11 |
+
icon: FileSearch,
|
| 12 |
+
},
|
| 13 |
+
{
|
| 14 |
+
title: "CNC Operation Detection",
|
| 15 |
+
description: "Automatically identify required machining operations like turning, milling, drilling, and tapping.",
|
| 16 |
+
icon: Settings2,
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
title: "Tool Matching",
|
| 20 |
+
description: "Map detected features to available shop floor tooling to ensure manufacturability without custom tools.",
|
| 21 |
+
icon: Wrench,
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
title: "Machine Capability Analysis",
|
| 25 |
+
description: "Evaluate if part dimensions, tolerances, and operations fit within your specific machine envelope.",
|
| 26 |
+
icon: Cpu,
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
title: "Risk Detection",
|
| 30 |
+
description: "Identify high-risk geometric features, tight tolerances, or impossible-to-machine internal geometries.",
|
| 31 |
+
icon: AlertTriangle,
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
title: "Automated Reports",
|
| 35 |
+
description: "Generate comprehensive DFM (Design for Manufacturing) reports instantly for client feedback.",
|
| 36 |
+
icon: FileText,
|
| 37 |
+
},
|
| 38 |
+
];
|
| 39 |
+
|
| 40 |
+
export function Features() {
|
| 41 |
+
return (
|
| 42 |
+
<section id="features" className="py-32 bg-gradient-to-b from-slate-900 via-slate-950 to-slate-900 relative overflow-hidden border-t border-slate-800">
|
| 43 |
+
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
| 44 |
+
<div className="absolute bottom-0 right-1/4 w-96 h-96 bg-cyan-600/10 rounded-full blur-3xl"></div>
|
| 45 |
+
</div>
|
| 46 |
+
|
| 47 |
+
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
| 48 |
+
<div className="text-center max-w-3xl mx-auto mb-20">
|
| 49 |
+
<h2 className="text-4xl sm:text-5xl font-bold tracking-tight mb-6 text-transparent bg-clip-text bg-gradient-to-r from-blue-300 to-cyan-300">
|
| 50 |
+
Industrial-Grade AI Capabilities
|
| 51 |
+
</h2>
|
| 52 |
+
<p className="text-lg text-slate-300 leading-relaxed">
|
| 53 |
+
Comprehensive manufacturability analysis tools designed for advanced machine shops
|
| 54 |
+
</p>
|
| 55 |
+
</div>
|
| 56 |
+
|
| 57 |
+
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
|
| 58 |
+
{features.map((feature, index) => {
|
| 59 |
+
const Icon = feature.icon;
|
| 60 |
+
return (
|
| 61 |
+
<motion.div
|
| 62 |
+
key={index}
|
| 63 |
+
initial={{ opacity: 0, y: 20 }}
|
| 64 |
+
whileInView={{ opacity: 1, y: 0 }}
|
| 65 |
+
viewport={{ once: true }}
|
| 66 |
+
transition={{ duration: 0.5, delay: index * 0.1 }}
|
| 67 |
+
>
|
| 68 |
+
<div className="group relative h-full">
|
| 69 |
+
<div className="absolute inset-0 bg-gradient-to-r from-blue-500/10 to-cyan-500/10 rounded-xl blur opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
| 70 |
+
<div className="relative bg-gradient-to-br from-slate-800/50 to-slate-900/50 border border-slate-700/50 group-hover:border-blue-500/50 rounded-xl p-8 transition-all duration-300 h-full">
|
| 71 |
+
<div className="h-12 w-12 rounded-lg bg-gradient-to-br from-blue-500/20 to-cyan-500/20 border border-blue-500/30 flex items-center justify-center mb-4 group-hover:from-blue-500/30 group-hover:to-cyan-500/30 transition-colors">
|
| 72 |
+
<Icon className="h-6 w-6 text-blue-400" />
|
| 73 |
+
</div>
|
| 74 |
+
<h3 className="text-xl font-semibold text-slate-100 mb-3">{feature.title}</h3>
|
| 75 |
+
<p className="text-slate-400 leading-relaxed">{feature.description}</p>
|
| 76 |
+
</div>
|
| 77 |
+
</div>
|
| 78 |
+
</motion.div>
|
| 79 |
+
);
|
| 80 |
+
})}
|
| 81 |
+
</div>
|
| 82 |
+
</div>
|
| 83 |
+
</section>
|
| 84 |
+
);
|
| 85 |
+
}
|
components/landing/footer.tsx
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from "react";
|
| 2 |
+
import Link from "next/link";
|
| 3 |
+
import { Layers } from "lucide-react";
|
| 4 |
+
|
| 5 |
+
export function Footer() {
|
| 6 |
+
return (
|
| 7 |
+
<footer className="border-t border-cyan-500/20 bg-gradient-to-b from-slate-950 to-slate-900 relative overflow-hidden">
|
| 8 |
+
{/* Decorative glow */}
|
| 9 |
+
<div className="absolute bottom-0 right-0 w-96 h-96 bg-cyan-500/5 rounded-full blur-3xl -z-10"></div>
|
| 10 |
+
|
| 11 |
+
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
| 12 |
+
<div className="grid grid-cols-1 md:grid-cols-4 gap-12 md:gap-8 py-16">
|
| 13 |
+
<div className="md:col-span-1">
|
| 14 |
+
<Link href="/" className="flex items-center space-x-2 mb-4 group">
|
| 15 |
+
<div className="p-2 rounded-lg bg-gradient-to-br from-blue-500 to-cyan-500 group-hover:shadow-lg group-hover:shadow-cyan-500/50 transition-shadow">
|
| 16 |
+
<Layers className="h-5 w-5 text-white" />
|
| 17 |
+
</div>
|
| 18 |
+
<span className="font-bold text-lg tracking-tight bg-gradient-to-r from-blue-300 to-cyan-300 bg-clip-text text-transparent">MachinaCheck</span>
|
| 19 |
+
</Link>
|
| 20 |
+
<p className="text-sm text-slate-400 leading-relaxed max-w-xs">
|
| 21 |
+
AI-powered CNC manufacturability assessment platform for advanced machine shops.
|
| 22 |
+
</p>
|
| 23 |
+
</div>
|
| 24 |
+
|
| 25 |
+
<div>
|
| 26 |
+
<h4 className="font-semibold text-slate-200 mb-4">Product</h4>
|
| 27 |
+
<ul className="space-y-3 text-sm text-slate-400">
|
| 28 |
+
<li><Link href="#" className="hover:text-cyan-300 transition-colors duration-200">Features</Link></li>
|
| 29 |
+
<li><Link href="#" className="hover:text-cyan-300 transition-colors duration-200">Workflow</Link></li>
|
| 30 |
+
<li><Link href="#" className="hover:text-cyan-300 transition-colors duration-200">Integrations</Link></li>
|
| 31 |
+
<li><Link href="#" className="hover:text-cyan-300 transition-colors duration-200">Pricing</Link></li>
|
| 32 |
+
</ul>
|
| 33 |
+
</div>
|
| 34 |
+
|
| 35 |
+
<div>
|
| 36 |
+
<h4 className="font-semibold text-slate-200 mb-4">Resources</h4>
|
| 37 |
+
<ul className="space-y-3 text-sm text-slate-400">
|
| 38 |
+
<li><Link href="#" className="hover:text-cyan-300 transition-colors duration-200">Documentation</Link></li>
|
| 39 |
+
<li><Link href="#" className="hover:text-cyan-300 transition-colors duration-200">API Reference</Link></li>
|
| 40 |
+
<li><Link href="#" className="hover:text-cyan-300 transition-colors duration-200">Blog</Link></li>
|
| 41 |
+
<li><Link href="#" className="hover:text-cyan-300 transition-colors duration-200">GitHub</Link></li>
|
| 42 |
+
</ul>
|
| 43 |
+
</div>
|
| 44 |
+
|
| 45 |
+
<div>
|
| 46 |
+
<h4 className="font-semibold text-slate-200 mb-4">Company</h4>
|
| 47 |
+
<ul className="space-y-3 text-sm text-slate-400">
|
| 48 |
+
<li><Link href="#" className="hover:text-cyan-300 transition-colors duration-200">About</Link></li>
|
| 49 |
+
<li><Link href="#" className="hover:text-cyan-300 transition-colors duration-200">Contact</Link></li>
|
| 50 |
+
<li><Link href="#" className="hover:text-cyan-300 transition-colors duration-200">Privacy Policy</Link></li>
|
| 51 |
+
<li><Link href="#" className="hover:text-cyan-300 transition-colors duration-200">Terms of Service</Link></li>
|
| 52 |
+
</ul>
|
| 53 |
+
</div>
|
| 54 |
+
</div>
|
| 55 |
+
|
| 56 |
+
<div className="py-8 border-t border-cyan-500/20 flex flex-col md:flex-row items-center justify-between text-xs text-slate-500">
|
| 57 |
+
<p>© {new Date().getFullYear()} MachinaCheck Inc. All rights reserved.</p>
|
| 58 |
+
<div className="flex space-x-6 mt-4 md:mt-0 font-mono">
|
| 59 |
+
<span className="text-cyan-400/60">SYS.STATUS: <span className="text-green-400">ONLINE</span></span>
|
| 60 |
+
<span className="text-slate-600">v2.0.4</span>
|
| 61 |
+
</div>
|
| 62 |
+
</div>
|
| 63 |
+
</div>
|
| 64 |
+
</footer>
|
| 65 |
+
);
|
| 66 |
+
}
|
components/landing/hero.tsx
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import React from "react";
|
| 4 |
+
import Link from "next/link";
|
| 5 |
+
import { ArrowRight, Upload } from "lucide-react";
|
| 6 |
+
import { motion } from "framer-motion";
|
| 7 |
+
|
| 8 |
+
export function Hero() {
|
| 9 |
+
return (
|
| 10 |
+
<section className="relative overflow-hidden pt-32 pb-24 lg:pt-48 lg:pb-32 bg-gradient-to-b from-slate-900 via-slate-950 to-slate-900">
|
| 11 |
+
{/* Animated gradient blobs */}
|
| 12 |
+
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
| 13 |
+
<div className="absolute top-0 left-1/4 w-96 h-96 bg-blue-600/20 rounded-full blur-3xl animate-pulse"></div>
|
| 14 |
+
<div className="absolute top-1/3 right-0 w-80 h-80 bg-cyan-600/20 rounded-full blur-3xl animate-pulse delay-1000"></div>
|
| 15 |
+
</div>
|
| 16 |
+
|
| 17 |
+
<div className="container mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
| 18 |
+
<div className="max-w-4xl mx-auto text-center">
|
| 19 |
+
<motion.div
|
| 20 |
+
initial={{ opacity: 0, y: 20 }}
|
| 21 |
+
animate={{ opacity: 1, y: 0 }}
|
| 22 |
+
transition={{ duration: 0.5 }}
|
| 23 |
+
>
|
| 24 |
+
<div className="inline-flex items-center gap-2 rounded-full border border-blue-500/30 bg-blue-950/50 px-4 py-2 mb-8">
|
| 25 |
+
<span className="flex h-2 w-2 rounded-full bg-blue-400 animate-pulse"></span>
|
| 26 |
+
<span className="text-sm font-medium text-blue-300">Powered by Multimodal AI</span>
|
| 27 |
+
</div>
|
| 28 |
+
</motion.div>
|
| 29 |
+
|
| 30 |
+
<motion.h1
|
| 31 |
+
className="text-5xl sm:text-6xl lg:text-7xl font-bold tracking-tight mb-6 text-transparent bg-clip-text bg-gradient-to-r from-blue-300 via-blue-200 to-cyan-300"
|
| 32 |
+
initial={{ opacity: 0, y: 20 }}
|
| 33 |
+
animate={{ opacity: 1, y: 0 }}
|
| 34 |
+
transition={{ duration: 0.5, delay: 0.1 }}
|
| 35 |
+
>
|
| 36 |
+
AI-Powered CNC<br />
|
| 37 |
+
Manufacturability
|
| 38 |
+
</motion.h1>
|
| 39 |
+
|
| 40 |
+
<motion.p
|
| 41 |
+
className="text-xl sm:text-2xl text-slate-300 mb-12 leading-relaxed max-w-2xl mx-auto"
|
| 42 |
+
initial={{ opacity: 0, y: 20 }}
|
| 43 |
+
animate={{ opacity: 1, y: 0 }}
|
| 44 |
+
transition={{ duration: 0.5, delay: 0.2 }}
|
| 45 |
+
>
|
| 46 |
+
Upload 3D model determine machining feasibility using AI agents
|
| 47 |
+
</motion.p>
|
| 48 |
+
|
| 49 |
+
<motion.div
|
| 50 |
+
className="flex flex-col sm:flex-row gap-4 justify-center"
|
| 51 |
+
initial={{ opacity: 0, y: 20 }}
|
| 52 |
+
animate={{ opacity: 1, y: 0 }}
|
| 53 |
+
transition={{ duration: 0.5, delay: 0.3 }}
|
| 54 |
+
>
|
| 55 |
+
<Link
|
| 56 |
+
href="/analyze"
|
| 57 |
+
className="inline-flex items-center justify-center gap-2 px-8 py-4 rounded-lg bg-gradient-to-r from-blue-600 to-cyan-500 hover:from-blue-700 hover:to-cyan-600 text-white font-semibold shadow-lg hover:shadow-blue-500/50 transition-all active:scale-95"
|
| 58 |
+
>
|
| 59 |
+
<Upload className="h-5 w-5" />
|
| 60 |
+
Start Analysis
|
| 61 |
+
</Link>
|
| 62 |
+
<button className="inline-flex items-center justify-center gap-2 px-8 py-4 rounded-lg border border-slate-600 hover:border-blue-500 text-slate-200 font-semibold transition-all hover:bg-slate-800/50">
|
| 63 |
+
View Documentation
|
| 64 |
+
<ArrowRight className="h-4 w-4" />
|
| 65 |
+
</button>
|
| 66 |
+
</motion.div>
|
| 67 |
+
|
| 68 |
+
<motion.div
|
| 69 |
+
className="mt-20 relative group"
|
| 70 |
+
initial={{ opacity: 0, scale: 0.95 }}
|
| 71 |
+
animate={{ opacity: 1, scale: 1 }}
|
| 72 |
+
transition={{ duration: 0.7, delay: 0.4 }}
|
| 73 |
+
>
|
| 74 |
+
<div className="absolute inset-0 bg-gradient-to-r from-blue-500/20 to-cyan-500/20 rounded-2xl blur-2xl group-hover:blur-3xl transition-all"></div>
|
| 75 |
+
<div className="relative bg-gradient-to-br from-slate-800/50 to-slate-900/50 border border-slate-700/50 rounded-2xl p-8 backdrop-blur-sm">
|
| 76 |
+
<div className="grid grid-cols-3 gap-6">
|
| 77 |
+
<div className="text-center">
|
| 78 |
+
<div className="text-3xl font-bold text-blue-400 mb-2">10,000+</div>
|
| 79 |
+
<div className="text-sm text-slate-400">Parts Analyzed</div>
|
| 80 |
+
</div>
|
| 81 |
+
<div className="text-center border-l border-r border-slate-700">
|
| 82 |
+
<div className="text-3xl font-bold text-cyan-400 mb-2">99.8%</div>
|
| 83 |
+
<div className="text-sm text-slate-400">Accuracy Rate</div>
|
| 84 |
+
</div>
|
| 85 |
+
<div className="text-center">
|
| 86 |
+
<div className="text-3xl font-bold text-blue-400 mb-2"><2s</div>
|
| 87 |
+
<div className="text-sm text-slate-400">Analysis Time</div>
|
| 88 |
+
</div>
|
| 89 |
+
</div>
|
| 90 |
+
</div>
|
| 91 |
+
</motion.div>
|
| 92 |
+
</div>
|
| 93 |
+
</div>
|
| 94 |
+
</section>
|
| 95 |
+
);
|
| 96 |
+
}
|
components/landing/navbar.tsx
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import React from "react";
|
| 2 |
+
import Link from "next/link";
|
| 3 |
+
import { Layers } from "lucide-react";
|
| 4 |
+
|
| 5 |
+
export function Navbar() {
|
| 6 |
+
return (
|
| 7 |
+
<header className="sticky top-0 z-50 w-full border-b border-slate-800 bg-slate-950/80 backdrop-blur-xl supports-[backdrop-filter]:bg-slate-950/60">
|
| 8 |
+
<div className="container mx-auto px-4 sm:px-6 lg:px-8 flex h-16 items-center justify-between">
|
| 9 |
+
<Link href="/" className="flex items-center space-x-3 group">
|
| 10 |
+
<div className="p-2 rounded-lg bg-gradient-to-br from-blue-600 to-cyan-500 group-hover:from-blue-700 group-hover:to-cyan-600 transition-all">
|
| 11 |
+
<Layers className="h-5 w-5 text-white" />
|
| 12 |
+
</div>
|
| 13 |
+
<span className="font-bold text-lg tracking-tight text-white">MachinaCheck</span>
|
| 14 |
+
</Link>
|
| 15 |
+
|
| 16 |
+
<nav className="hidden md:flex items-center gap-8 text-sm font-medium">
|
| 17 |
+
<Link href="#features" className="text-slate-300 hover:text-blue-400 transition-colors">Features</Link>
|
| 18 |
+
<Link href="#workflow" className="text-slate-300 hover:text-blue-400 transition-colors">Workflow</Link>
|
| 19 |
+
<Link href="#dashboard" className="text-slate-300 hover:text-blue-400 transition-colors">Docs</Link>
|
| 20 |
+
</nav>
|
| 21 |
+
|
| 22 |
+
<Link
|
| 23 |
+
href="/analyze"
|
| 24 |
+
className="hidden sm:inline-flex px-6 py-2.5 bg-gradient-to-r from-blue-600 to-cyan-500 hover:from-blue-700 hover:to-cyan-600 text-white font-semibold text-sm rounded-lg transition-all shadow-lg hover:shadow-blue-500/50 active:scale-95"
|
| 25 |
+
>
|
| 26 |
+
Analyze Now
|
| 27 |
+
</Link>
|
| 28 |
+
</div>
|
| 29 |
+
</header>
|
| 30 |
+
);
|
| 31 |
+
}
|
components/landing/workflow.tsx
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import React from "react";
|
| 4 |
+
import { Upload, BrainCircuit, Activity, Wrench, CheckCircle2, FileOutput } from "lucide-react";
|
| 5 |
+
import { motion } from "framer-motion";
|
| 6 |
+
|
| 7 |
+
const steps = [
|
| 8 |
+
{ name: "Upload 3D model", icon: Upload, gradient: "from-blue-500 to-blue-600" },
|
| 9 |
+
{ name: "AI Parsing", icon: BrainCircuit, gradient: "from-cyan-500 to-blue-500" },
|
| 10 |
+
{ name: "Operations Detection", icon: Activity, gradient: "from-cyan-400 to-cyan-500" },
|
| 11 |
+
{ name: "Tool Matching", icon: Wrench, gradient: "from-blue-400 to-cyan-400" },
|
| 12 |
+
{ name: "Feasibility Decision", icon: CheckCircle2, gradient: "from-cyan-500 to-blue-500" },
|
| 13 |
+
{ name: "Report Generation", icon: FileOutput, gradient: "from-blue-600 to-cyan-500" },
|
| 14 |
+
];
|
| 15 |
+
|
| 16 |
+
export function Workflow() {
|
| 17 |
+
return (
|
| 18 |
+
<section id="workflow" className="py-24 relative bg-gradient-to-b from-slate-950/50 to-slate-900/30">
|
| 19 |
+
{/* Decorative blurs */}
|
| 20 |
+
<div className="absolute top-0 left-1/4 w-96 h-96 bg-blue-500/10 rounded-full blur-3xl -z-10"></div>
|
| 21 |
+
<div className="absolute bottom-0 right-1/4 w-96 h-96 bg-cyan-500/10 rounded-full blur-3xl -z-10"></div>
|
| 22 |
+
|
| 23 |
+
<div className="container mx-auto px-4 sm:px-6 lg:px-8">
|
| 24 |
+
<div className="text-center max-w-3xl mx-auto mb-20">
|
| 25 |
+
<motion.div
|
| 26 |
+
initial={{ opacity: 0, y: 20 }}
|
| 27 |
+
whileInView={{ opacity: 1, y: 0 }}
|
| 28 |
+
viewport={{ once: true }}
|
| 29 |
+
transition={{ duration: 0.5 }}
|
| 30 |
+
>
|
| 31 |
+
<h2 className="text-3xl font-bold tracking-tight text-foreground sm:text-4xl mb-4">
|
| 32 |
+
<span className="bg-gradient-to-r from-blue-300 via-cyan-300 to-blue-300 bg-clip-text text-transparent">
|
| 33 |
+
Automated Assessment Pipeline
|
| 34 |
+
</span>
|
| 35 |
+
</h2>
|
| 36 |
+
</motion.div>
|
| 37 |
+
<motion.p
|
| 38 |
+
className="text-lg text-muted-foreground"
|
| 39 |
+
initial={{ opacity: 0, y: 20 }}
|
| 40 |
+
whileInView={{ opacity: 1, y: 0 }}
|
| 41 |
+
viewport={{ once: true }}
|
| 42 |
+
transition={{ duration: 0.5, delay: 0.1 }}
|
| 43 |
+
>
|
| 44 |
+
A continuous, deterministic workflow that transforms raw CAD design into actionable manufacturing intelligence within seconds.
|
| 45 |
+
</motion.p>
|
| 46 |
+
</div>
|
| 47 |
+
|
| 48 |
+
<div className="relative max-w-5xl mx-auto">
|
| 49 |
+
{/* Animated connecting line */}
|
| 50 |
+
<motion.div
|
| 51 |
+
className="hidden lg:block absolute top-1/2 left-0 right-0 h-0.5 -translate-y-1/2 z-0"
|
| 52 |
+
initial={{ scaleX: 0, opacity: 0 }}
|
| 53 |
+
whileInView={{ scaleX: 1, opacity: 1 }}
|
| 54 |
+
viewport={{ once: true }}
|
| 55 |
+
transition={{ duration: 0.8, delay: 0.2 }}
|
| 56 |
+
style={{ transformOrigin: "left" }}
|
| 57 |
+
>
|
| 58 |
+
<div className="w-full h-full bg-gradient-to-r from-transparent via-blue-500/50 to-transparent"></div>
|
| 59 |
+
</motion.div>
|
| 60 |
+
|
| 61 |
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-6 relative z-10">
|
| 62 |
+
{steps.map((step, index) => {
|
| 63 |
+
const Icon = step.icon;
|
| 64 |
+
return (
|
| 65 |
+
<motion.div
|
| 66 |
+
key={index}
|
| 67 |
+
className="flex flex-col items-center"
|
| 68 |
+
initial={{ opacity: 0, y: 20 }}
|
| 69 |
+
whileInView={{ opacity: 1, y: 0 }}
|
| 70 |
+
viewport={{ once: true }}
|
| 71 |
+
transition={{ duration: 0.5, delay: index * 0.1 }}
|
| 72 |
+
>
|
| 73 |
+
{/* Icon container with gradient glow */}
|
| 74 |
+
<motion.div
|
| 75 |
+
className={`h-16 w-16 rounded-xl bg-gradient-to-br ${step.gradient} flex items-center justify-center shadow-lg mb-4 relative z-10 cursor-pointer group`}
|
| 76 |
+
whileHover={{ scale: 1.1, boxShadow: "0 0 30px rgba(34, 211, 238, 0.5)" }}
|
| 77 |
+
transition={{ type: "spring", stiffness: 300, damping: 10 }}
|
| 78 |
+
>
|
| 79 |
+
<div className="absolute inset-0 rounded-xl bg-white/10 opacity-0 group-hover:opacity-20 transition-opacity"></div>
|
| 80 |
+
<Icon className="h-6 w-6 text-white relative z-10" />
|
| 81 |
+
|
| 82 |
+
{/* Glow effect on hover */}
|
| 83 |
+
<div className="absolute inset-0 rounded-xl bg-gradient-to-br opacity-0 group-hover:opacity-30 group-hover:blur-lg -z-10 transition-opacity" style={{ backgroundImage: `linear-gradient(to bottom right, rgba(34, 211, 238, 0.6), rgba(59, 130, 246, 0.6))` }}></div>
|
| 84 |
+
</motion.div>
|
| 85 |
+
|
| 86 |
+
<h3 className="text-sm font-semibold text-foreground text-center">
|
| 87 |
+
{step.name}
|
| 88 |
+
</h3>
|
| 89 |
+
<div className="text-xs text-muted-foreground mt-1 font-mono">
|
| 90 |
+
Step 0{index + 1}
|
| 91 |
+
</div>
|
| 92 |
+
</motion.div>
|
| 93 |
+
);
|
| 94 |
+
})}
|
| 95 |
+
</div>
|
| 96 |
+
</div>
|
| 97 |
+
</div>
|
| 98 |
+
</section>
|
| 99 |
+
);
|
| 100 |
+
}
|
components/ui/badge.tsx
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
| 3 |
+
|
| 4 |
+
import { cn } from "@/lib/utils"
|
| 5 |
+
|
| 6 |
+
const badgeVariants = cva(
|
| 7 |
+
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
| 8 |
+
{
|
| 9 |
+
variants: {
|
| 10 |
+
variant: {
|
| 11 |
+
default:
|
| 12 |
+
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
| 13 |
+
secondary:
|
| 14 |
+
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
| 15 |
+
destructive:
|
| 16 |
+
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
| 17 |
+
outline: "text-foreground",
|
| 18 |
+
},
|
| 19 |
+
},
|
| 20 |
+
defaultVariants: {
|
| 21 |
+
variant: "default",
|
| 22 |
+
},
|
| 23 |
+
}
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
export interface BadgeProps
|
| 27 |
+
extends React.HTMLAttributes<HTMLDivElement>,
|
| 28 |
+
VariantProps<typeof badgeVariants> {}
|
| 29 |
+
|
| 30 |
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
| 31 |
+
return (
|
| 32 |
+
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
| 33 |
+
)
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
export { Badge, badgeVariants }
|
components/ui/button.tsx
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
| 3 |
+
import { Slot } from "radix-ui"
|
| 4 |
+
|
| 5 |
+
import { cn } from "@/lib/utils"
|
| 6 |
+
|
| 7 |
+
const buttonVariants = cva(
|
| 8 |
+
"group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
| 9 |
+
{
|
| 10 |
+
variants: {
|
| 11 |
+
variant: {
|
| 12 |
+
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
| 13 |
+
outline:
|
| 14 |
+
"border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
|
| 15 |
+
secondary:
|
| 16 |
+
"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
| 17 |
+
ghost:
|
| 18 |
+
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
| 19 |
+
destructive:
|
| 20 |
+
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
| 21 |
+
link: "text-primary underline-offset-4 hover:underline",
|
| 22 |
+
},
|
| 23 |
+
size: {
|
| 24 |
+
default:
|
| 25 |
+
"h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
| 26 |
+
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
|
| 27 |
+
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
|
| 28 |
+
lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
| 29 |
+
icon: "size-8",
|
| 30 |
+
"icon-xs":
|
| 31 |
+
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
|
| 32 |
+
"icon-sm":
|
| 33 |
+
"size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
|
| 34 |
+
"icon-lg": "size-9",
|
| 35 |
+
},
|
| 36 |
+
},
|
| 37 |
+
defaultVariants: {
|
| 38 |
+
variant: "default",
|
| 39 |
+
size: "default",
|
| 40 |
+
},
|
| 41 |
+
}
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
function Button({
|
| 45 |
+
className,
|
| 46 |
+
variant = "default",
|
| 47 |
+
size = "default",
|
| 48 |
+
asChild = false,
|
| 49 |
+
...props
|
| 50 |
+
}: React.ComponentProps<"button"> &
|
| 51 |
+
VariantProps<typeof buttonVariants> & {
|
| 52 |
+
asChild?: boolean
|
| 53 |
+
}) {
|
| 54 |
+
const Comp = asChild ? Slot.Root : "button"
|
| 55 |
+
|
| 56 |
+
return (
|
| 57 |
+
<Comp
|
| 58 |
+
data-slot="button"
|
| 59 |
+
data-variant={variant}
|
| 60 |
+
data-size={size}
|
| 61 |
+
className={cn(buttonVariants({ variant, size, className }))}
|
| 62 |
+
{...props}
|
| 63 |
+
/>
|
| 64 |
+
)
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
export { Button, buttonVariants }
|
components/ui/card.tsx
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
|
| 3 |
+
import { cn } from "@/lib/utils"
|
| 4 |
+
|
| 5 |
+
const Card = React.forwardRef<
|
| 6 |
+
HTMLDivElement,
|
| 7 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 8 |
+
>(({ className, ...props }, ref) => (
|
| 9 |
+
<div
|
| 10 |
+
ref={ref}
|
| 11 |
+
className={cn(
|
| 12 |
+
"rounded-xl border bg-card text-card-foreground shadow",
|
| 13 |
+
className
|
| 14 |
+
)}
|
| 15 |
+
{...props}
|
| 16 |
+
/>
|
| 17 |
+
))
|
| 18 |
+
Card.displayName = "Card"
|
| 19 |
+
|
| 20 |
+
const CardHeader = React.forwardRef<
|
| 21 |
+
HTMLDivElement,
|
| 22 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 23 |
+
>(({ className, ...props }, ref) => (
|
| 24 |
+
<div
|
| 25 |
+
ref={ref}
|
| 26 |
+
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
| 27 |
+
{...props}
|
| 28 |
+
/>
|
| 29 |
+
))
|
| 30 |
+
CardHeader.displayName = "CardHeader"
|
| 31 |
+
|
| 32 |
+
const CardTitle = React.forwardRef<
|
| 33 |
+
HTMLParagraphElement,
|
| 34 |
+
React.HTMLAttributes<HTMLHeadingElement>
|
| 35 |
+
>(({ className, ...props }, ref) => (
|
| 36 |
+
<h3
|
| 37 |
+
ref={ref}
|
| 38 |
+
className={cn("font-semibold leading-none tracking-tight", className)}
|
| 39 |
+
{...props}
|
| 40 |
+
/>
|
| 41 |
+
))
|
| 42 |
+
CardTitle.displayName = "CardTitle"
|
| 43 |
+
|
| 44 |
+
const CardDescription = React.forwardRef<
|
| 45 |
+
HTMLParagraphElement,
|
| 46 |
+
React.HTMLAttributes<HTMLParagraphElement>
|
| 47 |
+
>(({ className, ...props }, ref) => (
|
| 48 |
+
<p
|
| 49 |
+
ref={ref}
|
| 50 |
+
className={cn("text-sm text-muted-foreground", className)}
|
| 51 |
+
{...props}
|
| 52 |
+
/>
|
| 53 |
+
))
|
| 54 |
+
CardDescription.displayName = "CardDescription"
|
| 55 |
+
|
| 56 |
+
const CardContent = React.forwardRef<
|
| 57 |
+
HTMLDivElement,
|
| 58 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 59 |
+
>(({ className, ...props }, ref) => (
|
| 60 |
+
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
| 61 |
+
))
|
| 62 |
+
CardContent.displayName = "CardContent"
|
| 63 |
+
|
| 64 |
+
const CardFooter = React.forwardRef<
|
| 65 |
+
HTMLDivElement,
|
| 66 |
+
React.HTMLAttributes<HTMLDivElement>
|
| 67 |
+
>(({ className, ...props }, ref) => (
|
| 68 |
+
<div
|
| 69 |
+
ref={ref}
|
| 70 |
+
className={cn("flex items-center p-6 pt-0", className)}
|
| 71 |
+
{...props}
|
| 72 |
+
/>
|
| 73 |
+
))
|
| 74 |
+
CardFooter.displayName = "CardFooter"
|
| 75 |
+
|
| 76 |
+
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
components/ui/input.tsx
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
|
| 3 |
+
import { cn } from "@/lib/utils"
|
| 4 |
+
|
| 5 |
+
const Input = React.forwardRef<
|
| 6 |
+
HTMLInputElement,
|
| 7 |
+
React.ComponentProps<"input">
|
| 8 |
+
>(({ className, type, ...props }, ref) => (
|
| 9 |
+
<input
|
| 10 |
+
type={type}
|
| 11 |
+
className={cn(
|
| 12 |
+
"flex h-9 w-full rounded-lg border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm pointer-events-auto cursor-text",
|
| 13 |
+
className
|
| 14 |
+
)}
|
| 15 |
+
ref={ref}
|
| 16 |
+
{...props}
|
| 17 |
+
/>
|
| 18 |
+
))
|
| 19 |
+
Input.displayName = "Input"
|
| 20 |
+
|
| 21 |
+
export { Input }
|
components/ui/label.tsx
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as React from "react"
|
| 2 |
+
import * as LabelPrimitive from "@radix-ui/react-label"
|
| 3 |
+
import { cva, type VariantProps } from "class-variance-authority"
|
| 4 |
+
|
| 5 |
+
import { cn } from "@/lib/utils"
|
| 6 |
+
|
| 7 |
+
const labelVariants = cva(
|
| 8 |
+
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
const Label = React.forwardRef<
|
| 12 |
+
React.ElementRef<typeof LabelPrimitive.Root>,
|
| 13 |
+
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
| 14 |
+
VariantProps<typeof labelVariants>
|
| 15 |
+
>(({ className, ...props }, ref) => (
|
| 16 |
+
<LabelPrimitive.Root
|
| 17 |
+
ref={ref}
|
| 18 |
+
className={cn(labelVariants(), className)}
|
| 19 |
+
{...props}
|
| 20 |
+
/>
|
| 21 |
+
))
|
| 22 |
+
Label.displayName = LabelPrimitive.Root.displayName
|
| 23 |
+
|
| 24 |
+
export { Label }
|
dockerfile
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM node:18-alpine
|
| 2 |
+
WORKDIR /app
|
| 3 |
+
COPY package*.json ./
|
| 4 |
+
RUN npm install
|
| 5 |
+
COPY . .
|
| 6 |
+
ENV NEXT_PUBLIC_API_URL=https://your-ngrok-url.ngrok-free.app
|
| 7 |
+
RUN npm run build
|
| 8 |
+
EXPOSE 3000
|
| 9 |
+
ENV PORT 3000
|
| 10 |
+
CMD ["npm", "start"]
|
eslint.config.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig, globalIgnores } from "eslint/config";
|
| 2 |
+
import nextVitals from "eslint-config-next/core-web-vitals";
|
| 3 |
+
import nextTs from "eslint-config-next/typescript";
|
| 4 |
+
|
| 5 |
+
const eslintConfig = defineConfig([
|
| 6 |
+
...nextVitals,
|
| 7 |
+
...nextTs,
|
| 8 |
+
// Override default ignores of eslint-config-next.
|
| 9 |
+
globalIgnores([
|
| 10 |
+
// Default ignores of eslint-config-next:
|
| 11 |
+
".next/**",
|
| 12 |
+
"out/**",
|
| 13 |
+
"build/**",
|
| 14 |
+
"next-env.d.ts",
|
| 15 |
+
]),
|
| 16 |
+
]);
|
| 17 |
+
|
| 18 |
+
export default eslintConfig;
|
hooks/use-analyze.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useState, useCallback } from "react";
|
| 4 |
+
import { useForm } from "react-hook-form";
|
| 5 |
+
import { zodResolver } from "@hookform/resolvers/zod";
|
| 6 |
+
import { z } from "zod";
|
| 7 |
+
import { analyzeStep } from "@/lib/api";
|
| 8 |
+
import { AnalysisResponse, AnalyzeFormInputs } from "@/lib/types";
|
| 9 |
+
|
| 10 |
+
const AnalyzeFormSchema = z.object({
|
| 11 |
+
material: z.string().min(1, "Material is required"),
|
| 12 |
+
tolerance: z.string().min(1, "Tolerance is required"),
|
| 13 |
+
threads: z.string(),
|
| 14 |
+
});
|
| 15 |
+
|
| 16 |
+
type FormSchemaType = z.infer<typeof AnalyzeFormSchema>;
|
| 17 |
+
|
| 18 |
+
export function useAnalyze() {
|
| 19 |
+
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
| 20 |
+
const [result, setResult] = useState<AnalysisResponse | null>(null);
|
| 21 |
+
const [error, setError] = useState<string | null>(null);
|
| 22 |
+
const [isLoading, setIsLoading] = useState(false);
|
| 23 |
+
|
| 24 |
+
const form = useForm<FormSchemaType>({
|
| 25 |
+
resolver: zodResolver(AnalyzeFormSchema),
|
| 26 |
+
defaultValues: {
|
| 27 |
+
material: "",
|
| 28 |
+
tolerance: "",
|
| 29 |
+
threads: "",
|
| 30 |
+
},
|
| 31 |
+
});
|
| 32 |
+
|
| 33 |
+
const handleFileSelect = useCallback((file: File) => {
|
| 34 |
+
const validExtensions = [".step", ".stp"];
|
| 35 |
+
const hasValidExtension = validExtensions.some((ext) =>
|
| 36 |
+
file.name.toLowerCase().endsWith(ext)
|
| 37 |
+
);
|
| 38 |
+
|
| 39 |
+
if (!hasValidExtension) {
|
| 40 |
+
setError("Invalid file type. Please upload a .step or .stp file.");
|
| 41 |
+
setSelectedFile(null);
|
| 42 |
+
return;
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
setSelectedFile(file);
|
| 46 |
+
setError(null);
|
| 47 |
+
}, []);
|
| 48 |
+
|
| 49 |
+
const handleFileRemove = useCallback(() => {
|
| 50 |
+
setSelectedFile(null);
|
| 51 |
+
setError(null);
|
| 52 |
+
}, []);
|
| 53 |
+
|
| 54 |
+
const onSubmit = async (data: FormSchemaType) => {
|
| 55 |
+
if (!selectedFile) {
|
| 56 |
+
setError("Please select a STEP file to analyze.");
|
| 57 |
+
return;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
setIsLoading(true);
|
| 61 |
+
setError(null);
|
| 62 |
+
setResult(null);
|
| 63 |
+
|
| 64 |
+
try {
|
| 65 |
+
const formData = new FormData();
|
| 66 |
+
formData.append("file", selectedFile);
|
| 67 |
+
formData.append("material", data.material);
|
| 68 |
+
formData.append("tolerance", data.tolerance);
|
| 69 |
+
if (data.threads) {
|
| 70 |
+
formData.append("threads", data.threads);
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
const response = await analyzeStep(formData);
|
| 74 |
+
setResult(response);
|
| 75 |
+
} catch (err) {
|
| 76 |
+
const message =
|
| 77 |
+
err instanceof Error ? err.message : "Failed to analyze file";
|
| 78 |
+
setError(message);
|
| 79 |
+
setResult(null);
|
| 80 |
+
} finally {
|
| 81 |
+
setIsLoading(false);
|
| 82 |
+
}
|
| 83 |
+
};
|
| 84 |
+
|
| 85 |
+
const resetAnalysis = useCallback(() => {
|
| 86 |
+
setSelectedFile(null);
|
| 87 |
+
setResult(null);
|
| 88 |
+
setError(null);
|
| 89 |
+
form.reset();
|
| 90 |
+
}, [form]);
|
| 91 |
+
|
| 92 |
+
return {
|
| 93 |
+
form,
|
| 94 |
+
selectedFile,
|
| 95 |
+
handleFileSelect,
|
| 96 |
+
handleFileRemove,
|
| 97 |
+
onSubmit: form.handleSubmit(onSubmit),
|
| 98 |
+
result,
|
| 99 |
+
error,
|
| 100 |
+
isLoading,
|
| 101 |
+
resetAnalysis,
|
| 102 |
+
};
|
| 103 |
+
}
|
lib/api.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import axios, { AxiosInstance } from "axios";
|
| 2 |
+
import { AnalysisResponse, DecisionType } from "./types";
|
| 3 |
+
|
| 4 |
+
const API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080";
|
| 5 |
+
|
| 6 |
+
const client: AxiosInstance = axios.create({
|
| 7 |
+
baseURL: API_URL,
|
| 8 |
+
timeout: 120000,
|
| 9 |
+
headers: {
|
| 10 |
+
"Content-Type": "multipart/form-data",
|
| 11 |
+
},
|
| 12 |
+
});
|
| 13 |
+
|
| 14 |
+
function formatTool(t: { type: string; diameter_mm?: number; min_diameter_mm?: number; material?: string }): string {
|
| 15 |
+
const size = t.diameter_mm ? `${t.diameter_mm}mm` : t.min_diameter_mm ? `min ${t.min_diameter_mm}mm` : "";
|
| 16 |
+
return [t.type, size, t.material].filter(Boolean).join(" ");
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
| 20 |
+
function transformResponse(raw: any): AnalysisResponse {
|
| 21 |
+
const decisionStr: DecisionType =
|
| 22 |
+
typeof raw.decision === "object" ? raw.decision?.decision : raw.decision;
|
| 23 |
+
|
| 24 |
+
return {
|
| 25 |
+
decision: decisionStr,
|
| 26 |
+
summary: raw.report?.summary ?? raw.summary ?? "",
|
| 27 |
+
operations: raw.classification?.required_operations ?? raw.operations ?? [],
|
| 28 |
+
required_tools: (raw.match_result?.tools_available ?? raw.classification?.required_tools ?? []).map(formatTool),
|
| 29 |
+
missing_tools: (raw.match_result?.tools_missing ?? raw.missing_tools ?? []).map(formatTool),
|
| 30 |
+
recommendations: raw.report?.action_required ?? raw.recommendations ?? [],
|
| 31 |
+
status: raw.status,
|
| 32 |
+
features: raw.features,
|
| 33 |
+
classification: raw.classification,
|
| 34 |
+
match_result: raw.match_result,
|
| 35 |
+
};
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
export async function analyzeStep(formData: FormData): Promise<AnalysisResponse> {
|
| 39 |
+
try {
|
| 40 |
+
const response = await client.post("/analyze", formData);
|
| 41 |
+
return transformResponse(response.data);
|
| 42 |
+
} catch (error) {
|
| 43 |
+
if (axios.isAxiosError(error)) {
|
| 44 |
+
const message =
|
| 45 |
+
error.response?.data?.detail ||
|
| 46 |
+
error.response?.data?.message ||
|
| 47 |
+
error.message ||
|
| 48 |
+
"Failed to analyze file. Please try again.";
|
| 49 |
+
throw new Error(message);
|
| 50 |
+
}
|
| 51 |
+
throw error;
|
| 52 |
+
}
|
| 53 |
+
}
|
lib/types.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export type DecisionType = "YES" | "NO" | "CONDITIONAL";
|
| 2 |
+
|
| 3 |
+
export interface AnalysisResponse {
|
| 4 |
+
decision: DecisionType;
|
| 5 |
+
summary: string;
|
| 6 |
+
operations: string[];
|
| 7 |
+
required_tools: string[];
|
| 8 |
+
missing_tools?: string[];
|
| 9 |
+
recommendations?: string[];
|
| 10 |
+
status: string;
|
| 11 |
+
features?: Record<string, unknown>;
|
| 12 |
+
classification?: Record<string, unknown>;
|
| 13 |
+
match_result?: Record<string, unknown>;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
export interface AnalyzeFormInputs {
|
| 17 |
+
material: string;
|
| 18 |
+
tolerance: string;
|
| 19 |
+
threads?: string;
|
| 20 |
+
}
|
lib/utils.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { clsx, type ClassValue } from "clsx"
|
| 2 |
+
import { twMerge } from "tailwind-merge"
|
| 3 |
+
|
| 4 |
+
export function cn(...inputs: ClassValue[]) {
|
| 5 |
+
return twMerge(clsx(inputs))
|
| 6 |
+
}
|
next.config.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/** @type {import('next').NextConfig} */
|
| 2 |
+
const nextConfig = {
|
| 3 |
+
output: 'standalone',
|
| 4 |
+
}
|
| 5 |
+
module.exports = nextConfig
|
next.config.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { NextConfig } from "next";
|
| 2 |
+
|
| 3 |
+
const nextConfig: NextConfig = {
|
| 4 |
+
output: 'standalone',
|
| 5 |
+
};
|
| 6 |
+
|
| 7 |
+
export default nextConfig;
|
package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "frontend",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"private": true,
|
| 5 |
+
"scripts": {
|
| 6 |
+
"dev": "next dev",
|
| 7 |
+
"build": "next build",
|
| 8 |
+
"start": "next start",
|
| 9 |
+
"lint": "eslint"
|
| 10 |
+
},
|
| 11 |
+
"dependencies": {
|
| 12 |
+
"@hookform/resolvers": "^5.2.2",
|
| 13 |
+
"@tanstack/react-query": "^5.100.9",
|
| 14 |
+
"axios": "^1.16.0",
|
| 15 |
+
"class-variance-authority": "^0.7.1",
|
| 16 |
+
"clsx": "^2.1.1",
|
| 17 |
+
"framer-motion": "^12.38.0",
|
| 18 |
+
"lucide-react": "^1.14.0",
|
| 19 |
+
"next": "16.2.6",
|
| 20 |
+
"radix-ui": "^1.4.3",
|
| 21 |
+
"react": "19.2.4",
|
| 22 |
+
"react-dom": "19.2.4",
|
| 23 |
+
"react-hook-form": "^7.75.0",
|
| 24 |
+
"shadcn": "^4.7.0",
|
| 25 |
+
"tailwind-merge": "^3.5.0",
|
| 26 |
+
"tw-animate-css": "^1.4.0",
|
| 27 |
+
"zod": "^4.4.3",
|
| 28 |
+
"zustand": "^5.0.13"
|
| 29 |
+
},
|
| 30 |
+
"devDependencies": {
|
| 31 |
+
"@tailwindcss/postcss": "^4",
|
| 32 |
+
"@types/node": "^20",
|
| 33 |
+
"@types/react": "^19",
|
| 34 |
+
"@types/react-dom": "^19",
|
| 35 |
+
"eslint": "^9",
|
| 36 |
+
"eslint-config-next": "16.2.6",
|
| 37 |
+
"tailwindcss": "^4",
|
| 38 |
+
"typescript": "^5"
|
| 39 |
+
}
|
| 40 |
+
}
|
postcss.config.mjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const config = {
|
| 2 |
+
plugins: {
|
| 3 |
+
"@tailwindcss/postcss": {},
|
| 4 |
+
},
|
| 5 |
+
};
|
| 6 |
+
|
| 7 |
+
export default config;
|
public/file.svg
ADDED
|
|
public/globe.svg
ADDED
|
|
public/next.svg
ADDED
|
|
public/vercel.svg
ADDED
|
|
public/window.svg
ADDED
|
|
tsconfig.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"target": "ES2017",
|
| 4 |
+
"lib": ["dom", "dom.iterable", "esnext"],
|
| 5 |
+
"allowJs": true,
|
| 6 |
+
"skipLibCheck": true,
|
| 7 |
+
"strict": true,
|
| 8 |
+
"noEmit": true,
|
| 9 |
+
"esModuleInterop": true,
|
| 10 |
+
"module": "esnext",
|
| 11 |
+
"moduleResolution": "bundler",
|
| 12 |
+
"resolveJsonModule": true,
|
| 13 |
+
"isolatedModules": true,
|
| 14 |
+
"jsx": "react-jsx",
|
| 15 |
+
"incremental": true,
|
| 16 |
+
"plugins": [
|
| 17 |
+
{
|
| 18 |
+
"name": "next"
|
| 19 |
+
}
|
| 20 |
+
],
|
| 21 |
+
"paths": {
|
| 22 |
+
"@/*": ["./*"]
|
| 23 |
+
}
|
| 24 |
+
},
|
| 25 |
+
"include": [
|
| 26 |
+
"next-env.d.ts",
|
| 27 |
+
"**/*.ts",
|
| 28 |
+
"**/*.tsx",
|
| 29 |
+
".next/types/**/*.ts",
|
| 30 |
+
".next/dev/types/**/*.ts",
|
| 31 |
+
"**/*.mts"
|
| 32 |
+
],
|
| 33 |
+
"exclude": ["node_modules"]
|
| 34 |
+
}
|