Compare commits

..

1 Commits

Author SHA1 Message Date
Patrick Devine
5c26b81a2f skip files in the list if we can't get the correct model path 2023-07-18 12:37:51 -07:00
18 changed files with 257 additions and 251 deletions

108
README.md
View File

@@ -1,65 +1,75 @@
<div align="center">
<picture>
<source media="(prefers-color-scheme: dark)" height="200px" srcset="https://github.com/jmorganca/ollama/assets/3325447/318048d2-b2dd-459c-925a-ac8449d5f02c">
<img alt="logo" height="200px" src="https://github.com/jmorganca/ollama/assets/3325447/c7d6e15f-7f4d-4776-b568-c084afa297c2">
</picture>
</div>
![ollama](https://github.com/jmorganca/ollama/assets/251292/961f99bb-251a-4eec-897d-1ba99997ad0f)
# Ollama
Create, run, and share self-contained large language models (LLMs). Ollama bundles a models weights, configuration, prompts, and more into self-contained packages that run anywhere.
Run large language models with `llama.cpp`.
> Note: Ollama is in early preview. Please report any issues you find.
> Note: certain models that can be run with Ollama are intended for research and/or non-commercial use only.
## Download
### Features
- [Download](https://ollama.ai/download) for macOS on Apple Silicon (Intel coming soon)
- Download for Windows and Linux (coming soon)
- Build [from source](#building)
- Download and run popular large language models
- Switch between multiple models on the fly
- Hardware acceleration where available (Metal, CUDA)
- Fast inference server written in Go, powered by [llama.cpp](https://github.com/ggerganov/llama.cpp)
- REST API to use with your application (python, typescript SDKs coming soon)
## Examples
## Install
### Quickstart
- [Download](https://ollama.ai/download) for macOS with Apple Silicon (Intel coming soon)
- Download for Windows (coming soon)
You can also build the [binary from source](#building).
## Quickstart
Run a fast and simple model.
```
ollama run llama2
>>> hi
Hello! How can I help you today?
ollama run orca
```
### Creating a custom model
## Example models
Create a `Modelfile`:
### 💬 Chat
Have a conversation.
```
FROM llama2
PROMPT """
You are Mario from Super Mario Bros. Answer as Mario, the assistant, only.
User: {{ .Prompt }}
Mario:
"""
ollama run vicuna "Why is the sky blue?"
```
Next, create and run the model:
### 🗺️ Instructions
Get a helping hand.
```
ollama create mario -f ./Modelfile
ollama run mario
>>> hi
Hello! It's your friend Mario.
ollama run orca "Write an email to my boss."
```
## Model library
### 🔎 Ask questions about documents
Ollama includes a library of open-source, pre-trained models. More models are coming soon.
Send the contents of a document and ask questions about it.
| Model | Parameters | Size | Download |
| ----------- | ---------- | ----- | ------------------------- |
| Llama2 | 7B | 3.8GB | `ollama pull llama2` |
| Orca Mini | 3B | 1.9GB | `ollama pull orca` |
| Vicuna | 7B | 3.8GB | `ollama pull vicuna` |
| Nous-Hermes | 13B | 7.3GB | `ollama pull nous-hermes` |
```
ollama run nous-hermes "$(cat input.txt)", please summarize this story
```
### 📖 Storytelling
Venture into the unknown.
```
ollama run nous-hermes "Once upon a time"
```
## Advanced usage
### Run a local model
```
ollama run ~/Downloads/vicuna-7b-v1.3.ggmlv3.q4_1.bin
```
## Building
@@ -76,5 +86,23 @@ To run it start the server:
Finally, run a model!
```
./ollama run llama2
./ollama run ~/Downloads/vicuna-7b-v1.3.ggmlv3.q4_1.bin
```
## API Reference
### `POST /api/pull`
Download a model
```
curl -X POST http://localhost:11343/api/pull -d '{"model": "orca"}'
```
### `POST /api/generate`
Complete a prompt
```
curl -X POST http://localhost:11434/api/generate -d '{"model": "orca", "prompt": "hello!"}'
```

View File

@@ -160,11 +160,11 @@ func (c *Client) Generate(ctx context.Context, req *GenerateRequest, fn Generate
})
}
type PullProgressFunc func(ProgressResponse) error
type PullProgressFunc func(PullProgress) error
func (c *Client) Pull(ctx context.Context, req *PullRequest, fn PullProgressFunc) error {
return c.stream(ctx, http.MethodPost, "/api/pull", req, func(bts []byte) error {
var resp ProgressResponse
var resp PullProgress
if err := json.Unmarshal(bts, &resp); err != nil {
return err
}
@@ -173,11 +173,11 @@ func (c *Client) Pull(ctx context.Context, req *PullRequest, fn PullProgressFunc
})
}
type PushProgressFunc func(ProgressResponse) error
type PushProgressFunc func(PushProgress) error
func (c *Client) Push(ctx context.Context, req *PushRequest, fn PushProgressFunc) error {
return c.stream(ctx, http.MethodPost, "/api/push", req, func(bts []byte) error {
var resp ProgressResponse
var resp PushProgress
if err := json.Unmarshal(bts, &resp); err != nil {
return err
}

View File

@@ -43,11 +43,12 @@ type PullRequest struct {
Password string `json:"password"`
}
type ProgressResponse struct {
type PullProgress struct {
Status string `json:"status"`
Digest string `json:"digest,omitempty"`
Total int `json:"total,omitempty"`
Completed int `json:"completed,omitempty"`
Percent float64 `json:"percent,omitempty"`
}
type PushRequest struct {
@@ -56,6 +57,14 @@ type PushRequest struct {
Password string `json:"password"`
}
type PushProgress struct {
Status string `json:"status"`
Digest string `json:"digest,omitempty"`
Total int `json:"total,omitempty"`
Completed int `json:"completed,omitempty"`
Percent float64 `json:"percent,omitempty"`
}
type ListResponse struct {
Models []ListResponseModel `json:"models"`
}

View File

@@ -19,7 +19,7 @@ export default function () {
const [step, setStep] = useState<Step>(Step.WELCOME)
const [commandCopied, setCommandCopied] = useState<boolean>(false)
const command = 'ollama run llama2'
const command = 'ollama run orca'
return (
<div className='drag'>
@@ -77,11 +77,7 @@ export default function () {
{command}
</pre>
<button
className={`no-drag absolute right-[5px] px-2 py-2 ${
commandCopied
? 'text-gray-900 opacity-100 hover:cursor-auto'
: 'text-gray-200 opacity-50 hover:cursor-pointer'
} hover:font-bold hover:text-gray-900 group-hover:opacity-100`}
className={`no-drag absolute right-[5px] px-2 py-2 ${commandCopied ? 'text-gray-900 opacity-100 hover:cursor-auto' : 'text-gray-200 opacity-50 hover:cursor-pointer'} hover:text-gray-900 hover:font-bold group-hover:opacity-100`}
onClick={() => {
copy(command)
setCommandCopied(true)
@@ -89,15 +85,13 @@ export default function () {
}}
>
{commandCopied ? (
<CheckIcon className='h-4 w-4 font-bold text-gray-500' />
<CheckIcon className='h-4 w-4 text-gray-500 font-bold' />
) : (
<DocumentDuplicateIcon className='h-4 w-4 text-gray-500' />
)}
</button>
</div>
<p className='mx-auto my-4 w-[70%] text-xs text-gray-400'>
Run this command in your favorite terminal.
</p>
<p className='mx-auto my-4 w-[70%] text-xs text-gray-400'>Run this command in your favorite terminal.</p>
</div>
<button
onClick={() => {

View File

@@ -9,7 +9,6 @@ import (
"net"
"net/http"
"os"
"path/filepath"
"strings"
"time"
@@ -26,11 +25,6 @@ import (
func create(cmd *cobra.Command, args []string) error {
filename, _ := cmd.Flags().GetString("file")
filename, err := filepath.Abs(filename)
if err != nil {
return err
}
client := api.NewClient()
var spinner *Spinner
@@ -89,7 +83,7 @@ func push(cmd *cobra.Command, args []string) error {
client := api.NewClient()
request := api.PushRequest{Name: args[0]}
fn := func(resp api.ProgressResponse) error {
fn := func(resp api.PushProgress) error {
fmt.Println(resp.Status)
return nil
}
@@ -135,23 +129,25 @@ func RunPull(cmd *cobra.Command, args []string) error {
func pull(model string) error {
client := api.NewClient()
var currentDigest string
var bar *progressbar.ProgressBar
currentLayer := ""
request := api.PullRequest{Name: model}
fn := func(resp api.ProgressResponse) error {
if resp.Digest != currentDigest && resp.Digest != "" {
currentDigest = resp.Digest
fn := func(resp api.PullProgress) error {
if resp.Digest != currentLayer && resp.Digest != "" {
if currentLayer != "" {
fmt.Println()
}
currentLayer = resp.Digest
layerStr := resp.Digest[7:23] + "..."
bar = progressbar.DefaultBytes(
int64(resp.Total),
fmt.Sprintf("pulling %s...", resp.Digest[7:19]),
"pulling "+layerStr,
)
bar.Set(resp.Completed)
} else if resp.Digest == currentDigest && resp.Digest != "" {
} else if resp.Digest == currentLayer && resp.Digest != "" {
bar.Set(resp.Completed)
} else {
currentDigest = ""
currentLayer = ""
fmt.Println(resp.Status)
}
return nil

View File

@@ -1,15 +0,0 @@
# Examples
This directory contains examples that can be created and run with `ollama`.
To create a model:
```
ollama create example -f <example file>
```
To run a model:
```
ollama run example
```

View File

@@ -1,7 +0,0 @@
FROM llama2
PARAMETER temperature 1
PROMPT """
System: You are Mario from super mario bros, acting as an assistant.
User: {{ .Prompt }}
Assistant:
"""

15
examples/python/README.md Normal file
View File

@@ -0,0 +1,15 @@
# Python
This is a simple example of calling the Ollama api from a python app.
First, download a model:
```
curl -L https://huggingface.co/TheBloke/orca_mini_3B-GGML/resolve/main/orca-mini-3b.ggmlv3.q4_1.bin -o orca.bin
```
Then run it using the example script. You'll need to have Ollama running on your machine.
```
python3 main.py orca.bin
```

32
examples/python/main.py Normal file
View File

@@ -0,0 +1,32 @@
import http.client
import json
import os
import sys
if len(sys.argv) < 2:
print("Usage: python main.py <model file>")
sys.exit(1)
conn = http.client.HTTPConnection('localhost', 11434)
headers = { 'Content-Type': 'application/json' }
# generate text from the model
conn.request("POST", "/api/generate", json.dumps({
'model': os.path.join(os.getcwd(), sys.argv[1]),
'prompt': 'write me a short story',
'stream': True
}), headers)
response = conn.getresponse()
def parse_generate(data):
for event in data.decode('utf-8').split("\n"):
if not event:
continue
yield event
if response.status == 200:
for chunk in response:
for event in parse_generate(chunk):
print(json.loads(event)['response'], end="", flush=True)

View File

@@ -3,6 +3,7 @@ package server
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
@@ -41,9 +42,10 @@ type Layer struct {
Size int `json:"size"`
}
type LayerReader struct {
type LayerWithBuffer struct {
Layer
io.Reader
Buffer *bytes.Buffer
}
type ConfigV2 struct {
@@ -159,7 +161,7 @@ func CreateModel(name string, mf io.Reader, fn func(status string)) error {
return err
}
var layers []*LayerReader
var layers []*LayerWithBuffer
params := make(map[string]string)
for _, c := range commands {
@@ -272,7 +274,7 @@ func CreateModel(name string, mf io.Reader, fn func(status string)) error {
return nil
}
func removeLayerFromLayers(layers []*LayerReader, mediaType string) []*LayerReader {
func removeLayerFromLayers(layers []*LayerWithBuffer, mediaType string) []*LayerWithBuffer {
j := 0
for _, l := range layers {
if l.MediaType != mediaType {
@@ -283,7 +285,7 @@ func removeLayerFromLayers(layers []*LayerReader, mediaType string) []*LayerRead
return layers[:j]
}
func SaveLayers(layers []*LayerReader, fn func(status string), force bool) error {
func SaveLayers(layers []*LayerWithBuffer, fn func(status string), force bool) error {
// Write each of the layers to disk
for _, layer := range layers {
fp, err := GetBlobsPath(layer.Digest)
@@ -301,10 +303,10 @@ func SaveLayers(layers []*LayerReader, fn func(status string), force bool) error
}
defer out.Close()
if _, err = io.Copy(out, layer.Reader); err != nil {
_, err = io.Copy(out, layer.Buffer)
if err != nil {
return err
}
} else {
fn(fmt.Sprintf("using already created layer %s", layer.Digest))
}
@@ -313,7 +315,7 @@ func SaveLayers(layers []*LayerReader, fn func(status string), force bool) error
return nil
}
func CreateManifest(name string, cfg *LayerReader, layers []*Layer) error {
func CreateManifest(name string, cfg *LayerWithBuffer, layers []*Layer) error {
mp := ParseModelPath(name)
manifest := ManifestV2{
@@ -339,7 +341,7 @@ func CreateManifest(name string, cfg *LayerReader, layers []*Layer) error {
return os.WriteFile(fp, manifestJSON, 0o644)
}
func GetLayerWithBufferFromLayer(layer *Layer) (*LayerReader, error) {
func GetLayerWithBufferFromLayer(layer *Layer) (*LayerWithBuffer, error) {
fp, err := GetBlobsPath(layer.Digest)
if err != nil {
return nil, err
@@ -359,7 +361,7 @@ func GetLayerWithBufferFromLayer(layer *Layer) (*LayerReader, error) {
return newLayer, nil
}
func paramsToReader(params map[string]string) (io.ReadSeeker, error) {
func paramsToReader(params map[string]string) (io.Reader, error) {
opts := api.DefaultOptions()
typeOpts := reflect.TypeOf(opts)
@@ -417,7 +419,7 @@ func paramsToReader(params map[string]string) (io.ReadSeeker, error) {
return bytes.NewReader(bts), nil
}
func getLayerDigests(layers []*LayerReader) ([]string, error) {
func getLayerDigests(layers []*LayerWithBuffer) ([]string, error) {
var digests []string
for _, l := range layers {
if l.Digest == "" {
@@ -429,30 +431,34 @@ func getLayerDigests(layers []*LayerReader) ([]string, error) {
}
// CreateLayer creates a Layer object from a given file
func CreateLayer(f io.ReadSeeker) (*LayerReader, error) {
digest, size := GetSHA256Digest(f)
f.Seek(0, 0)
func CreateLayer(f io.Reader) (*LayerWithBuffer, error) {
buf := new(bytes.Buffer)
_, err := io.Copy(buf, f)
if err != nil {
return nil, err
}
layer := &LayerReader{
digest, size := GetSHA256Digest(buf)
layer := &LayerWithBuffer{
Layer: Layer{
MediaType: "application/vnd.docker.image.rootfs.diff.tar",
Digest: digest,
Size: size,
},
Reader: f,
Buffer: buf,
}
return layer, nil
}
func PushModel(name, username, password string, fn func(api.ProgressResponse)) error {
func PushModel(name, username, password string, fn func(status, digest string, Total, Completed int, Percent float64)) error {
mp := ParseModelPath(name)
fn(api.ProgressResponse{Status: "retrieving manifest"})
fn("retrieving manifest", "", 0, 0, 0)
manifest, err := GetManifest(mp)
if err != nil {
fn(api.ProgressResponse{Status: "couldn't retrieve manifest"})
fn("couldn't retrieve manifest", "", 0, 0, 0)
return err
}
@@ -474,21 +480,11 @@ func PushModel(name, username, password string, fn func(api.ProgressResponse)) e
if exists {
completed += layer.Size
fn(api.ProgressResponse{
Status: "using existing layer",
Digest: layer.Digest,
Total: total,
Completed: completed,
})
fn("using existing layer", layer.Digest, total, completed, float64(completed)/float64(total))
continue
}
fn(api.ProgressResponse{
Status: "starting upload",
Digest: layer.Digest,
Total: total,
Completed: completed,
})
fn("starting upload", layer.Digest, total, completed, float64(completed)/float64(total))
location, err := startUpload(mp, username, password)
if err != nil {
@@ -502,19 +498,10 @@ func PushModel(name, username, password string, fn func(api.ProgressResponse)) e
return err
}
completed += layer.Size
fn(api.ProgressResponse{
Status: "upload complete",
Digest: layer.Digest,
Total: total,
Completed: completed,
})
fn("upload complete", layer.Digest, total, completed, float64(completed)/float64(total))
}
fn(api.ProgressResponse{
Status: "pushing manifest",
Total: total,
Completed: completed,
})
fn("pushing manifest", "", total, completed, float64(completed/total))
url := fmt.Sprintf("%s://%s/v2/%s/manifests/%s", mp.ProtocolScheme, mp.Registry, mp.GetNamespaceRepository(), mp.Tag)
headers := map[string]string{
"Content-Type": "application/vnd.docker.distribution.manifest.v2+json",
@@ -537,19 +524,15 @@ func PushModel(name, username, password string, fn func(api.ProgressResponse)) e
return fmt.Errorf("registry responded with code %d: %v", resp.StatusCode, string(body))
}
fn(api.ProgressResponse{
Status: "success",
Total: total,
Completed: completed,
})
fn("success", "", total, completed, 1.0)
return nil
}
func PullModel(name, username, password string, fn func(api.ProgressResponse)) error {
func PullModel(name, username, password string, fn func(status, digest string, Total, Completed int, Percent float64)) error {
mp := ParseModelPath(name)
fn(api.ProgressResponse{Status: "pulling manifest"})
fn("pulling manifest", "", 0, 0, 0)
manifest, err := pullModelManifest(mp, username, password)
if err != nil {
@@ -567,15 +550,16 @@ func PullModel(name, username, password string, fn func(api.ProgressResponse)) e
total += manifest.Config.Size
for _, layer := range layers {
fn("starting download", layer.Digest, total, completed, float64(completed)/float64(total))
if err := downloadBlob(mp, layer.Digest, username, password, fn); err != nil {
fn(api.ProgressResponse{Status: fmt.Sprintf("error downloading: %v", err), Digest: layer.Digest})
fn(fmt.Sprintf("error downloading: %v", err), layer.Digest, 0, 0, 0)
return err
}
completed += layer.Size
fn("download complete", layer.Digest, total, completed, float64(completed)/float64(total))
}
fn(api.ProgressResponse{Status: "writing manifest"})
fn("writing manifest", "", total, completed, 1.0)
manifestJSON, err := json.Marshal(manifest)
if err != nil {
@@ -593,7 +577,7 @@ func PullModel(name, username, password string, fn func(api.ProgressResponse)) e
return err
}
fn(api.ProgressResponse{Status: "success"})
fn("success", "", total, completed, 1.0)
return nil
}
@@ -625,7 +609,7 @@ func pullModelManifest(mp ModelPath, username, password string) (*ManifestV2, er
return m, err
}
func createConfigLayer(layers []string) (*LayerReader, error) {
func createConfigLayer(layers []string) (*LayerWithBuffer, error) {
// TODO change architecture and OS
config := ConfigV2{
Architecture: "arm64",
@@ -644,26 +628,22 @@ func createConfigLayer(layers []string) (*LayerReader, error) {
buf := bytes.NewBuffer(configJSON)
digest, size := GetSHA256Digest(buf)
layer := &LayerReader{
layer := &LayerWithBuffer{
Layer: Layer{
MediaType: "application/vnd.docker.container.image.v1+json",
Digest: digest,
Size: size,
},
Reader: buf,
Buffer: buf,
}
return layer, nil
}
// GetSHA256Digest returns the SHA256 hash of a given buffer and returns it, and the size of buffer
func GetSHA256Digest(r io.Reader) (string, int) {
h := sha256.New()
n, err := io.Copy(h, r)
if err != nil {
log.Fatal(err)
}
return fmt.Sprintf("sha256:%x", h.Sum(nil)), int(n)
func GetSHA256Digest(data *bytes.Buffer) (string, int) {
layerBytes := data.Bytes()
hash := sha256.Sum256(layerBytes)
return "sha256:" + hex.EncodeToString(hash[:]), len(layerBytes)
}
func startUpload(mp ModelPath, username string, password string) (string, error) {
@@ -745,20 +725,16 @@ func uploadBlob(location string, layer *Layer, username string, password string)
return nil
}
func downloadBlob(mp ModelPath, digest string, username, password string, fn func(api.ProgressResponse)) error {
func downloadBlob(mp ModelPath, digest string, username, password string, fn func(status, digest string, Total, Completed int, Percent float64)) error {
fp, err := GetBlobsPath(digest)
if err != nil {
return err
}
if fi, _ := os.Stat(fp); fi != nil {
_, err = os.Stat(fp)
if !os.IsNotExist(err) {
// we already have the file, so return
fn(api.ProgressResponse{
Digest: digest,
Total: int(fi.Size()),
Completed: int(fi.Size()),
})
log.Printf("already have %s\n", digest)
return nil
}
@@ -807,21 +783,10 @@ func downloadBlob(mp ModelPath, digest string, username, password string, fn fun
total := remaining + completed
for {
fn(api.ProgressResponse{
Status: fmt.Sprintf("downloading %s", digest),
Digest: digest,
Total: int(total),
Completed: int(completed),
})
fn(fmt.Sprintf("Downloading %s", digest), digest, int(total), int(completed), float64(completed)/float64(total))
if completed >= total {
if err := os.Rename(fp+"-partial", fp); err != nil {
fn(api.ProgressResponse{
Status: fmt.Sprintf("error renaming file: %v", err),
Digest: digest,
Total: int(total),
Completed: int(completed),
})
fn(fmt.Sprintf("error renaming file: %v", err), digest, int(total), int(completed), 1)
return err
}

View File

@@ -101,10 +101,15 @@ func pull(c *gin.Context) {
ch := make(chan any)
go func() {
defer close(ch)
fn := func(r api.ProgressResponse) {
ch <- r
fn := func(status, digest string, total, completed int, percent float64) {
ch <- api.PullProgress{
Status: status,
Digest: digest,
Total: total,
Completed: completed,
Percent: percent,
}
}
if err := PullModel(req.Name, req.Username, req.Password, fn); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -124,10 +129,15 @@ func push(c *gin.Context) {
ch := make(chan any)
go func() {
defer close(ch)
fn := func(r api.ProgressResponse) {
ch <- r
fn := func(status, digest string, total, completed int, percent float64) {
ch <- api.PushProgress{
Status: status,
Digest: digest,
Total: total,
Completed: completed,
Percent: percent,
}
}
if err := PushModel(req.Name, req.Username, req.Password, fn); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
@@ -185,8 +195,7 @@ func list(c *gin.Context) {
if !info.IsDir() {
fi, err := os.Stat(path)
if err != nil {
log.Printf("skipping file: %s", fp)
return nil
return err
}
path := path[len(fp)+1:]
slashIndex := strings.LastIndex(path, "/")

View File

@@ -1,4 +1,3 @@
import Header from '../header'
import Downloader from './downloader'
import Signup from './signup'
@@ -27,19 +26,22 @@ export default async function Download() {
}
return (
<>
<Header />
<main className='flex min-h-screen max-w-6xl flex-col py-20 px-16 lg:p-32 items-center mx-auto'>
<img src='/ollama.png' className='w-16 h-auto' />
<section className='mt-12 mb-8 text-center'>
<h2 className='my-2 max-w-md text-3xl tracking-tight'>Downloading...</h2>
<h3 className='text-base text-neutral-500 mt-12 max-w-[16rem]'>
While Ollama downloads, sign up to get notified of new updates.
</h3>
<Downloader url={asset.browser_download_url} />
</section>
<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 />
</main>
</>
</section>
</main>
)
}

View File

@@ -28,7 +28,7 @@ export default function Signup() {
return false
}}
className='flex self-stretch flex-col gap-3 h-32 md:mx-40 lg:mx-72'
className='flex self-stretch flex-col gap-3 h-32'
>
<input
required
@@ -37,13 +37,13 @@ export default function Signup() {
onChange={e => setEmail(e.target.value)}
type='email'
placeholder='your@email.com'
className='border border-neutral-200 rounded-lg px-4 py-2 focus:outline-none placeholder-neutral-300'
className='bg-neutral-100 rounded-lg px-4 py-2 focus:outline-none placeholder-neutral-500'
/>
<input
type='submit'
value='Get updates'
disabled={submitting}
className='bg-black text-white disabled:text-neutral-200 disabled:bg-neutral-700 rounded-full px-4 py-2 focus:outline-none cursor-pointer'
className='bg-black text-white disabled:text-neutral-200 disabled:bg-neutral-700 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>

View File

@@ -1,24 +0,0 @@
const navigation = [
{ name: 'Discord', href: 'https://discord.gg/MrfB5FbNWN' },
{ name: 'GitHub', href: 'https://github.com/jmorganca/ollama' },
{ name: 'Download', href: '/download' },
]
export default function Header() {
return (
<header className='absolute inset-x-0 top-0 z-50'>
<nav className='mx-auto flex items-center justify-between px-10 py-4'>
<a className='flex-1 font-bold' href='/'>
Ollama
</a>
<div className='flex space-x-8'>
{navigation.map(item => (
<a key={item.name} href={item.href} className='text-sm leading-6 text-gray-900'>
{item.name}
</a>
))}
</div>
</nav>
</header>
)
}

View File

@@ -1,32 +1,34 @@
import { AiFillApple } from 'react-icons/ai'
import models from '../../models.json'
import Header from './header'
export default async function Home() {
return (
<>
<Header />
<main className='flex min-h-screen max-w-6xl flex-col py-20 px-16 md:p-32 items-center mx-auto'>
<img src='/ollama.png' className='w-16 h-auto' />
<section className='my-12 text-center'>
<div className='flex flex-col space-y-2'>
<h2 className='md:max-w-[18rem] mx-auto my-2 text-3xl tracking-tight'>Portable large language models</h2>
<h3 className='md:max-w-xs mx-auto text-base text-neutral-500'>
Bundle a models weights, configuration, prompts, data and more into self-contained packages that run anywhere.
</h3>
<main className='flex min-h-screen max-w-2xl flex-col p-4 lg:p-24'>
<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'>
Ollama
</a>{' '}
is a tool for running large language models, currently for macOS with Windows and Linux coming soon.
<br />
<br />
<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>
</a>
</p>
</section>
<section className='my-4'>
<h2 className='mb-4 text-lg'>Example models you can try running:</h2>
{models.map(m => (
<div className='my-2 grid font-mono' key={m.name}>
<code className='py-0.5'>ollama run {m.name}</code>
</div>
<div className='mx-auto flex flex-col space-y-4 mt-12'>
<a href='/download' className='md:mx-10 lg:mx-14 bg-black text-white rounded-full px-4 py-2 focus:outline-none cursor-pointer'>
Download
</a>
<p className='text-neutral-500 text-sm '>
Available for macOS with Apple Silicon <br />
Windows & Linux support coming soon.
</p>
</div>
</section>
</main>
</>
))}
</section>
</main>
)
}