From eb9680395aa896abfe670ac083887bd87f145ff4 Mon Sep 17 00:00:00 2001 From: songtianlun Date: Thu, 3 Jul 2025 00:27:11 +0800 Subject: [PATCH] use openweb ui --- package-lock.json | 176 +-------------------------- package.json | 3 +- public/manifest.json | 21 ++++ public/robots.txt | 5 + src/app/api/compare/route.ts | 44 ++++--- src/app/globals.css | 38 ++++++ src/app/layout.tsx | 83 ++++++++++++- src/app/page.tsx | 25 +++- src/app/sitemap.ts | 14 +++ src/components/ComparisonForm.tsx | 148 ++++++++++++++++------ src/components/ComparisonHistory.tsx | 139 +++++++++++++++++++++ src/components/ComparisonResults.tsx | 12 +- src/utils/historyStorage.ts | 54 ++++++++ 13 files changed, 522 insertions(+), 240 deletions(-) create mode 100644 public/manifest.json create mode 100644 public/robots.txt create mode 100644 src/app/sitemap.ts create mode 100644 src/components/ComparisonHistory.tsx create mode 100644 src/utils/historyStorage.ts diff --git a/package-lock.json b/package-lock.json index 7ccf63f..01e4a6b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-markdown": "^10.1.0", - "remark-gfm": "^4.0.1", - "replicate": "^1.0.1" + "remark-gfm": "^4.0.1" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -1935,19 +1934,6 @@ "win32" ] }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "license": "MIT", - "optional": true, - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -2251,27 +2237,6 @@ "dev": true, "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true - }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -2296,31 +2261,6 @@ "node": ">=8" } }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -3419,26 +3359,6 @@ "node": ">=0.10.0" } }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.8.x" - } - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -3911,27 +3831,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause", - "optional": true - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -6309,16 +6208,6 @@ "node": ">= 0.8.0" } }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.6.0" - } - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -6427,23 +6316,6 @@ "react": ">=18" } }, - "node_modules/readable-stream": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", - "license": "MIT", - "optional": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -6554,21 +6426,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/replicate": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replicate/-/replicate-1.0.1.tgz", - "integrity": "sha512-EY+rK1YR5bKHcM9pd6WyaIbv6m2aRIvHfHDh51j/LahlHTLKemTYXF6ptif2sLa+YospupAsIoxw8Ndt5nI3vg==", - "license": "Apache-2.0", - "engines": { - "git": ">=2.11.0", - "node": ">=18.0.0", - "npm": ">=7.19.0", - "yarn": ">=1.7.0" - }, - "optionalDependencies": { - "readable-stream": ">=4.0.0" - } - }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -6665,27 +6522,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "optional": true - }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -6988,16 +6824,6 @@ "node": ">=10.0.0" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "optional": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", diff --git a/package.json b/package.json index dd1a8dc..0b611db 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-markdown": "^10.1.0", - "remark-gfm": "^4.0.1", - "replicate": "^1.0.1" + "remark-gfm": "^4.0.1" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..52eef33 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,21 @@ +{ + "name": "Anything vs Anything - AI Comparison Tool", + "short_name": "Anything vs Anything", + "description": "Compare any two things with AI-powered analysis", + "start_url": "/", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#3B82F6", + "icons": [ + { + "src": "/favicon.ico", + "sizes": "48x48", + "type": "image/x-icon" + }, + { + "src": "/apple-touch-icon.png", + "sizes": "180x180", + "type": "image/png" + } + ] +} \ No newline at end of file diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..40bd373 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,5 @@ +User-agent: * +Allow: / +Disallow: /api/ + +Sitemap: https://anything-vs-anything.com/sitemap.xml \ No newline at end of file diff --git a/src/app/api/compare/route.ts b/src/app/api/compare/route.ts index 0e9fb84..c69b8d0 100644 --- a/src/app/api/compare/route.ts +++ b/src/app/api/compare/route.ts @@ -1,9 +1,4 @@ import { NextRequest, NextResponse } from 'next/server'; -import Replicate from 'replicate'; - -const replicate = new Replicate({ - auth: process.env.REPLICATE_API_TOKEN, -}); export async function POST(request: NextRequest) { try { @@ -31,22 +26,37 @@ Please provide a comprehensive comparison in a markdown table format with the fo The table should have three columns: Aspect, ${item1}, ${item2}`; - const output = await replicate.run( - process.env.REPLICATE_MODEL || "meta/llama-2-70b-chat:02e509c789964a7ea8736978a43525956ef40397be9033abf9fd2badfe68c9e3", - { - input: { - prompt: prompt, - max_new_tokens: 1000, - temperature: 0.7, - }, - } - ); + const response = await fetch('https://openrouter.ai/api/v1/chat/completions', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`, + 'Content-Type': 'application/json', + 'HTTP-Referer': process.env.NEXT_PUBLIC_SITE_URL || 'https://anything-vs-anything.com', + 'X-Title': 'Anything vs Anything', + }, + body: JSON.stringify({ + model: process.env.OPENROUTER_MODEL || 'anthropic/claude-3.5-sonnet', + messages: [ + { + role: 'user', + content: prompt + } + ], + temperature: 0.7, + max_tokens: 2000, + }), + }); - const comparison = Array.isArray(output) ? output.join('') : output; + if (!response.ok) { + throw new Error(`OpenRouter API error: ${response.status}`); + } + + const data = await response.json(); + const comparison = data.choices[0]?.message?.content || 'No comparison generated'; return NextResponse.json({ comparison }); } catch (error) { - console.error('Error calling Replicate API:', error); + console.error('Error calling OpenRouter API:', error); return NextResponse.json( { error: 'Failed to generate comparison' }, { status: 500 } diff --git a/src/app/globals.css b/src/app/globals.css index 5a728c6..ee5a046 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -25,6 +25,44 @@ body { font-family: Arial, Helvetica, sans-serif; } +/* Mobile optimizations */ +@media (max-width: 640px) { + .prose { + font-size: 0.875rem; + line-height: 1.5; + } + + .prose table { + font-size: 0.75rem; + } + + .prose table th, + .prose table td { + padding: 0.5rem 0.25rem; + } +} + +/* Loading animation keyframes */ +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +.animate-pulse { + animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; +} + +/* Smooth transitions */ +* { + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + .prose table { width: 100%; margin: 1rem 0; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f7fa87e..0043798 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -13,8 +13,57 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Anything vs Anything - AI-Powered Comparison Tool", + description: "Compare any two things with our AI-powered analysis tool. Get detailed comparisons of products, services, concepts, and more. Free online comparison generator with intelligent insights.", + keywords: "comparison tool, AI comparison, product comparison, service comparison, vs tool, compare anything, analysis tool, decision making", + authors: [{ name: "Anything vs Anything" }], + creator: "Anything vs Anything", + publisher: "Anything vs Anything", + formatDetection: { + email: false, + address: false, + telephone: false, + }, + metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || 'https://anything-vs-anything.com'), + alternates: { + canonical: '/', + }, + openGraph: { + title: "Anything vs Anything - AI-Powered Comparison Tool", + description: "Compare any two things with our AI-powered analysis tool. Get detailed comparisons of products, services, concepts, and more.", + url: '/', + siteName: "Anything vs Anything", + locale: 'en_US', + type: 'website', + images: [ + { + url: '/og-image.png', + width: 1200, + height: 630, + alt: 'Anything vs Anything - AI Comparison Tool', + }, + ], + }, + twitter: { + card: 'summary_large_image', + title: "Anything vs Anything - AI-Powered Comparison Tool", + description: "Compare any two things with our AI-powered analysis tool. Get detailed comparisons of products, services, concepts, and more.", + images: ['/og-image.png'], + }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + 'max-video-preview': -1, + 'max-image-preview': 'large', + 'max-snippet': -1, + }, + }, + verification: { + google: process.env.GOOGLE_SITE_VERIFICATION, + }, }; export default function RootLayout({ @@ -24,6 +73,36 @@ export default function RootLayout({ }>) { return ( + + + + + + +