Explorar o código

web: newsletter signup on download page

Jeffrey Morgan hai 1 ano
pai
achega
4c2b4589ac

+ 17 - 0
web/app/api/signup/route.ts

@@ -0,0 +1,17 @@
+import { Analytics } from '@segment/analytics-node'
+import { v4 as uuid } from 'uuid'
+
+const analytics = new Analytics({ writeKey: process.env.TELEMETRY_WRITE_KEY || '<empty>' })
+
+export async function POST(req: Request) {
+  const { email } = await req.json()
+
+  analytics.identify({
+    anonymousId: uuid(),
+    traits: {
+      email,
+    },
+  })
+
+  return new Response(null, { status: 200 })
+}

+ 11 - 0
web/app/download/downloader.tsx

@@ -0,0 +1,11 @@
+'use client'
+
+import { useEffect } from 'react'
+
+export default function ({ url }: { url: string }) {
+  useEffect(() => {
+    window.location.href = url
+  }, [])
+
+  return null
+}

+ 24 - 9
web/app/download/page.tsx

@@ -1,18 +1,19 @@
-import { redirect } from 'next/navigation'
+import Downloader from './downloader'
+import Signup from './signup'
 
 export default async function Download() {
   const res = await fetch('https://api.github.com/repos/jmorganca/ollama/releases', { next: { revalidate: 60 } })
   const data = await res.json()
 
   if (data.length === 0) {
-    return new Response('not found', { status: 404 })
+    return null
   }
 
   const latest = data[0]
   const assets = latest.assets || []
 
   if (assets.length === 0) {
-    return new Response('not found', { status: 404 })
+    return null
   }
 
   // todo: get the correct asset for the current arch/os
@@ -21,12 +22,26 @@ export default async function Download() {
   )
 
   if (!asset) {
-    return new Response('not found', { status: 404 })
+    return null
   }
 
-  if (asset) {
-    redirect(asset.browser_download_url)
-  }
-
-  return null
+  return (
+    <main className='flex min-h-screen max-w-2xl flex-col p-4 lg:p-24 items-center mx-auto'>
+      <img src='/ollama.png' className='w-16 h-auto' />
+      <section className='my-12 text-center'>
+        <h2 className='my-2 max-w-md text-3xl tracking-tight'>Downloading Ollama</h2>
+        <h3 className='text-sm text-neutral-500'>
+          Problems downloading?{' '}
+          <a href={asset.browser_download_url} className='underline'>
+            Try again
+          </a>
+        </h3>
+        <Downloader url={asset.browser_download_url} />
+      </section>
+      <section className='max-w-sm flex flex-col w-full items-center border border-neutral-200 rounded-xl px-8 pt-8 pb-2'>
+        <p className='text-lg leading-tight text-center mb-6 max-w-[260px]'>Sign up for updates</p>
+        <Signup />
+      </section>
+    </main>
+  )
 }

+ 46 - 0
web/app/download/signup.tsx

@@ -0,0 +1,46 @@
+'use client'
+
+import { useState } from 'react'
+
+export default function () {
+  const [email, setEmail] = useState('')
+  const [success, setSuccess] = useState(false)
+
+  return (
+    <form
+      onSubmit={async e => {
+        e.preventDefault()
+
+        await fetch('/api/signup', {
+          method: 'POST',
+          headers: {
+            'Content-Type': 'application/json',
+          },
+          body: JSON.stringify({ email }),
+        })
+
+        setSuccess(true)
+        setEmail('')
+
+        return false
+      }}
+      className='flex self-stretch flex-col gap-3 h-32'
+    >
+      <input
+        required
+        autoFocus
+        value={email}
+        onChange={e => setEmail(e.target.value)}
+        type='email'
+        placeholder='your@email.com'
+        className='bg-neutral-100 rounded-lg px-4 py-2 focus:outline-none placeholder-neutral-500'
+      />
+      <input
+        type='submit'
+        value='Get updates'
+        className='bg-black text-white rounded-lg px-4 py-2 focus:outline-none cursor-pointer'
+      />
+      {success && <p className='text-center text-sm'>You&apos;re signed up for updates</p>}
+    </form>
+  )
+}

+ 1 - 1
web/app/layout.tsx

@@ -8,7 +8,7 @@ export const metadata = {
 export default function RootLayout({ children }: { children: React.ReactNode }) {
   return (
     <html lang='en'>
-      <body>{children}</body>
+      <body className='antialiased'>{children}</body>
     </html>
   )
 }

+ 2 - 2
web/app/page.tsx

@@ -5,7 +5,7 @@ import models from '../../models.json'
 export default async function Home() {
   return (
     <main className='flex min-h-screen max-w-2xl flex-col p-4 lg:p-24'>
-      <img src='/ollama.png' className='w-20 h-auto' />
+      <img src='/ollama.png' className='w-16 h-auto' />
       <section className='my-4'>
         <p className='my-3 max-w-md'>
           <a className='underline' href='https://github.com/jmorganca/ollama'>
@@ -14,7 +14,7 @@ export default async function Home() {
           is a tool for running large language models, currently for macOS with Windows and Linux coming soon.
           <br />
           <br />
-          <a href='/download' target='_blank'>
+          <a href='/download'>
             <button className='bg-black text-white text-sm py-2 px-3 rounded-lg flex items-center gap-2'>
               <AiFillApple className='h-auto w-5 relative -top-px' /> Download for macOS
             </button>

+ 232 - 11
web/package-lock.json

@@ -10,6 +10,7 @@
       "dependencies": {
         "@octokit/rest": "^19.0.13",
         "@octokit/types": "^11.0.0",
+        "@segment/analytics-node": "^1.0.0",
         "@types/node": "20.4.0",
         "@types/react": "18.2.14",
         "@types/react-dom": "18.2.6",
@@ -24,10 +25,14 @@
         "react-icons": "^4.10.1",
         "semver": "^7.5.3",
         "tailwindcss": "3.3.2",
-        "typescript": "5.1.6"
+        "typescript": "5.1.6",
+        "uuid": "^9.0.0"
       },
       "devDependencies": {
-        "@types/semver": "^7.5.0"
+        "@types/semver": "^7.5.0",
+        "@types/uuid": "^9.0.2",
+        "prettier": "^3.0.0",
+        "prettier-plugin-tailwindcss": "^0.4.0"
       }
     },
     "node_modules/@aashutoshrathi/word-wrap": {
@@ -190,6 +195,25 @@
       "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
       "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
     },
+    "node_modules/@lukeed/csprng": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz",
+      "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/@lukeed/uuid": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/@lukeed/uuid/-/uuid-2.0.1.tgz",
+      "integrity": "sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==",
+      "dependencies": {
+        "@lukeed/csprng": "^1.1.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/@next/env": {
       "version": "13.4.9",
       "resolved": "https://registry.npmjs.org/@next/env/-/env-13.4.9.tgz",
@@ -599,6 +623,31 @@
       "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.2.tgz",
       "integrity": "sha512-V+MvGwaHH03hYhY+k6Ef/xKd6RYlc4q8WBx+2ANmipHJcKuktNcI/NgEsJgdSUF6Lw32njT6OnrRsKYCdgHjYw=="
     },
+    "node_modules/@segment/analytics-core": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/@segment/analytics-core/-/analytics-core-1.3.0.tgz",
+      "integrity": "sha512-ujScWZH49NK1hYlp2/EMw45nOPEh+pmTydAnR6gSkRNucZD4fuinvpPL03rmFCw8ibaMuKLAdgPJfQ0gkLKZ5A==",
+      "dependencies": {
+        "@lukeed/uuid": "^2.0.0",
+        "dset": "^3.1.2",
+        "tslib": "^2.4.1"
+      }
+    },
+    "node_modules/@segment/analytics-node": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@segment/analytics-node/-/analytics-node-1.0.0.tgz",
+      "integrity": "sha512-UWFujSxRkRauZuMVF4MPOT5QPvX4i7kiC2QCsozHhltoTiR2SBWRI86cYO/JI/Uk7qKaOxxGFDkJarCyIP7uLA==",
+      "dependencies": {
+        "@lukeed/uuid": "^2.0.0",
+        "@segment/analytics-core": "1.3.0",
+        "buffer": "^6.0.3",
+        "node-fetch": "^2.6.7",
+        "tslib": "^2.4.1"
+      },
+      "engines": {
+        "node": ">=14"
+      }
+    },
     "node_modules/@swc/helpers": {
       "version": "0.5.1",
       "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.1.tgz",
@@ -651,6 +700,12 @@
       "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==",
       "dev": true
     },
+    "node_modules/@types/uuid": {
+      "version": "9.0.2",
+      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.2.tgz",
+      "integrity": "sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==",
+      "dev": true
+    },
     "node_modules/@typescript-eslint/parser": {
       "version": "5.60.1",
       "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.60.1.tgz",
@@ -991,6 +1046,25 @@
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
       "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
     },
+    "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"
+        }
+      ]
+    },
     "node_modules/before-after-hook": {
       "version": "2.2.3",
       "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
@@ -1074,6 +1148,29 @@
         "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
       }
     },
+    "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"
+        }
+      ],
+      "dependencies": {
+        "base64-js": "^1.3.1",
+        "ieee754": "^1.2.1"
+      }
+    },
     "node_modules/bundle-name": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz",
@@ -1390,6 +1487,14 @@
         "node": ">=6.0.0"
       }
     },
+    "node_modules/dset": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz",
+      "integrity": "sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==",
+      "engines": {
+        "node": ">=4"
+      }
+    },
     "node_modules/electron-to-chromium": {
       "version": "1.4.447",
       "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.447.tgz",
@@ -1747,9 +1852,9 @@
       }
     },
     "node_modules/eslint-plugin-import/node_modules/semver": {
-      "version": "6.3.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-      "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
       "bin": {
         "semver": "bin/semver.js"
       }
@@ -1784,9 +1889,9 @@
       }
     },
     "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": {
-      "version": "6.3.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-      "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
       "bin": {
         "semver": "bin/semver.js"
       }
@@ -1858,9 +1963,9 @@
       }
     },
     "node_modules/eslint-plugin-react/node_modules/semver": {
-      "version": "6.3.0",
-      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
-      "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
       "bin": {
         "semver": "bin/semver.js"
       }
@@ -2385,6 +2490,25 @@
         "node": ">=0.10.0"
       }
     },
+    "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"
+        }
+      ]
+    },
     "node_modules/ignore": {
       "version": "5.2.4",
       "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -3521,6 +3645,95 @@
         "node": ">= 0.8.0"
       }
     },
+    "node_modules/prettier": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz",
+      "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==",
+      "dev": true,
+      "bin": {
+        "prettier": "bin/prettier.cjs"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/prettier/prettier?sponsor=1"
+      }
+    },
+    "node_modules/prettier-plugin-tailwindcss": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.4.0.tgz",
+      "integrity": "sha512-Rna0sDPETA0KNhMHlN8wxKNgfSa8mTl2hPPAGxnbv6tUcHT6J4RQmQ8TLXyhB7Dm5Von4iHloBxTyClYM6wT0A==",
+      "dev": true,
+      "engines": {
+        "node": ">=12.17.0"
+      },
+      "peerDependencies": {
+        "@ianvs/prettier-plugin-sort-imports": "*",
+        "@prettier/plugin-pug": "*",
+        "@shopify/prettier-plugin-liquid": "*",
+        "@shufo/prettier-plugin-blade": "*",
+        "@trivago/prettier-plugin-sort-imports": "*",
+        "prettier": "^2.2 || ^3.0",
+        "prettier-plugin-astro": "*",
+        "prettier-plugin-css-order": "*",
+        "prettier-plugin-import-sort": "*",
+        "prettier-plugin-jsdoc": "*",
+        "prettier-plugin-marko": "*",
+        "prettier-plugin-organize-attributes": "*",
+        "prettier-plugin-organize-imports": "*",
+        "prettier-plugin-style-order": "*",
+        "prettier-plugin-svelte": "*",
+        "prettier-plugin-twig-melody": "*"
+      },
+      "peerDependenciesMeta": {
+        "@ianvs/prettier-plugin-sort-imports": {
+          "optional": true
+        },
+        "@prettier/plugin-pug": {
+          "optional": true
+        },
+        "@shopify/prettier-plugin-liquid": {
+          "optional": true
+        },
+        "@shufo/prettier-plugin-blade": {
+          "optional": true
+        },
+        "@trivago/prettier-plugin-sort-imports": {
+          "optional": true
+        },
+        "prettier-plugin-astro": {
+          "optional": true
+        },
+        "prettier-plugin-css-order": {
+          "optional": true
+        },
+        "prettier-plugin-import-sort": {
+          "optional": true
+        },
+        "prettier-plugin-jsdoc": {
+          "optional": true
+        },
+        "prettier-plugin-marko": {
+          "optional": true
+        },
+        "prettier-plugin-organize-attributes": {
+          "optional": true
+        },
+        "prettier-plugin-organize-imports": {
+          "optional": true
+        },
+        "prettier-plugin-style-order": {
+          "optional": true
+        },
+        "prettier-plugin-svelte": {
+          "optional": true
+        },
+        "prettier-plugin-twig-melody": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/prop-types": {
       "version": "15.8.1",
       "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -4360,6 +4573,14 @@
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
       "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
     },
+    "node_modules/uuid": {
+      "version": "9.0.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
+      "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
     "node_modules/watchpack": {
       "version": "2.4.0",
       "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",

+ 7 - 2
web/package.json

@@ -10,6 +10,7 @@
   "dependencies": {
     "@octokit/rest": "^19.0.13",
     "@octokit/types": "^11.0.0",
+    "@segment/analytics-node": "^1.0.0",
     "@types/node": "20.4.0",
     "@types/react": "18.2.14",
     "@types/react-dom": "18.2.6",
@@ -24,9 +25,13 @@
     "react-icons": "^4.10.1",
     "semver": "^7.5.3",
     "tailwindcss": "3.3.2",
-    "typescript": "5.1.6"
+    "typescript": "5.1.6",
+    "uuid": "^9.0.0"
   },
   "devDependencies": {
-    "@types/semver": "^7.5.0"
+    "@types/semver": "^7.5.0",
+    "@types/uuid": "^9.0.2",
+    "prettier": "^3.0.0",
+    "prettier-plugin-tailwindcss": "^0.4.0"
   }
 }