Compare commits

..

18 Commits

Author SHA1 Message Date
Michael Yang
77dc1a6d74 Merge pull request #74 from jmorganca/timings
Timings
2023-07-13 10:17:13 -07:00
Michael Yang
05e08d2310 return more info in generate response 2023-07-13 09:37:32 -07:00
Michael Yang
31590284a7 fix route 2023-07-12 19:21:49 -07:00
Michael Yang
f2863cc7f8 Merge pull request #76 from jmorganca/fix-pull
fix pull race
2023-07-12 19:21:13 -07:00
Jeffrey Morgan
4dd296e155 build app in publish script 2023-07-12 19:16:39 -07:00
Jeffrey Morgan
304f419429 update README.md API reference 2023-07-12 19:16:28 -07:00
Michael Yang
2666d3c206 fix pull race 2023-07-12 19:07:23 -07:00
Jeffrey Morgan
787d965331 web: disable signup button while submitting 2023-07-12 17:32:27 -07:00
Jeffrey Morgan
e6eee0732c web: fix npm build 2023-07-12 17:28:00 -07:00
Jeffrey Morgan
4c2b4589ac web: newsletter signup on download page 2023-07-12 17:26:20 -07:00
Michael Yang
5571ed5248 Merge pull request #73 from jmorganca/generate-eof
fix eof error in generate
2023-07-12 11:09:23 -07:00
Michael Yang
0944b01e7d pull fixes 2023-07-12 09:55:07 -07:00
Jeffrey Morgan
5028de2901 update vicuna model 2023-07-12 09:42:26 -07:00
Michael Yang
e1f0a0dc74 fix eof error in generate 2023-07-12 09:36:16 -07:00
Michael Yang
b227261f21 Merge pull request #71 from jmorganca/llama-errors
error checking new model
2023-07-12 09:20:33 -07:00
Jeffrey Morgan
c63f811909 return error if model fails to load 2023-07-11 20:32:26 -07:00
Jeffrey Morgan
7c71c10d4f fix compilation issue in Dockerfile, remove from README.md until ready 2023-07-11 19:51:08 -07:00
Michael Yang
c5f7eadd87 error checking new model 2023-07-11 17:07:41 -07:00
19 changed files with 516 additions and 102 deletions

1
.gitignore vendored
View File

@@ -2,6 +2,5 @@
.vscode
.env
.venv
*.spec
dist
ollama

View File

@@ -1,8 +1,6 @@
FROM golang:1.20
RUN apt-get update && apt-get install -y cmake
WORKDIR /go/src/github.com/jmorganca/ollama
COPY . .
RUN cmake -S llama -B llama/build && cmake --build llama/build
RUN CGO_ENABLED=1 go build -ldflags '-linkmode external -extldflags "-static"' .
FROM alpine

View File

@@ -18,7 +18,6 @@ Run large language models with `llama.cpp`.
- [Download](https://ollama.ai/download) for macOS
- Download for Windows (coming soon)
- Docker: `docker run -p 11434:11434 ollama/ollama`
You can also build the [binary from source](#building).
@@ -105,5 +104,5 @@ curl -X POST http://localhost:11343/api/pull -d '{"model": "orca"}'
Complete a prompt
```
curl -X POST http://localhost:11434/api/generate -d '{"model": "orca", "prompt": "hello!", "stream": true}'
curl -X POST http://localhost:11434/api/generate -d '{"model": "orca", "prompt": "hello!"}'
```

View File

@@ -1,6 +1,11 @@
package api
import "runtime"
import (
"fmt"
"os"
"runtime"
"time"
)
type PullRequest struct {
Model string `json:"model"`
@@ -20,7 +25,41 @@ type GenerateRequest struct {
}
type GenerateResponse struct {
Response string `json:"response"`
Model string `json:"model"`
CreatedAt time.Time `json:"created_at"`
Response string `json:"response,omitempty"`
Done bool `json:"done"`
TotalDuration time.Duration `json:"total_duration,omitempty"`
PromptEvalCount int `json:"prompt_eval_count,omitempty"`
PromptEvalDuration time.Duration `json:"prompt_eval_duration,omitempty"`
EvalCount int `json:"eval_count,omitempty"`
EvalDuration time.Duration `json:"eval_duration,omitempty"`
}
func (r *GenerateResponse) Summary() {
if r.TotalDuration > 0 {
fmt.Fprintf(os.Stderr, "total duration: %v\n", r.TotalDuration)
}
if r.PromptEvalCount > 0 {
fmt.Fprintf(os.Stderr, "prompt eval count: %d token(s)\n", r.PromptEvalCount)
}
if r.PromptEvalDuration > 0 {
fmt.Fprintf(os.Stderr, "prompt eval duration: %s\n", r.PromptEvalDuration)
fmt.Fprintf(os.Stderr, "prompt eval rate: %.2f tokens/s\n", float64(r.PromptEvalCount)/r.PromptEvalDuration.Seconds())
}
if r.EvalCount > 0 {
fmt.Fprintf(os.Stderr, "eval count: %d token(s)\n", r.EvalCount)
}
if r.EvalDuration > 0 {
fmt.Fprintf(os.Stderr, "eval duraiton: %s\n", r.EvalDuration)
fmt.Fprintf(os.Stderr, "eval rate: %.2f tokens/s\n", float64(r.EvalCount)/r.EvalDuration.Seconds())
}
}
type Options struct {

View File

@@ -59,7 +59,7 @@ func pull(model string) error {
&api.PullRequest{Model: model},
func(progress api.PullProgress) error {
if bar == nil {
if progress.Percent == 100 {
if progress.Percent >= 100 {
// already downloaded
return nil
}
@@ -72,21 +72,20 @@ func pull(model string) error {
)
}
func RunGenerate(_ *cobra.Command, args []string) error {
// join all args into a single prompt
prompt := strings.Join(args[1:], " ")
func RunGenerate(cmd *cobra.Command, args []string) error {
if len(args) > 1 {
return generate(args[0], prompt)
// join all args into a single prompt
return generate(cmd, args[0], strings.Join(args[1:], " "))
}
if term.IsTerminal(int(os.Stdin.Fd())) {
return generateInteractive(args[0])
return generateInteractive(cmd, args[0])
}
return generateBatch(args[0])
return generateBatch(cmd, args[0])
}
func generate(model, prompt string) error {
func generate(cmd *cobra.Command, model, prompt string) error {
if len(strings.TrimSpace(prompt)) > 0 {
client := api.NewClient()
@@ -109,12 +108,16 @@ func generate(model, prompt string) error {
}
}()
var latest api.GenerateResponse
request := api.GenerateRequest{Model: model, Prompt: prompt}
fn := func(resp api.GenerateResponse) error {
if !spinner.IsFinished() {
spinner.Finish()
}
latest = resp
fmt.Print(resp.Response)
return nil
}
@@ -125,16 +128,25 @@ func generate(model, prompt string) error {
fmt.Println()
fmt.Println()
verbose, err := cmd.Flags().GetBool("verbose")
if err != nil {
return err
}
if verbose {
latest.Summary()
}
}
return nil
}
func generateInteractive(model string) error {
func generateInteractive(cmd *cobra.Command, model string) error {
fmt.Print(">>> ")
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
if err := generate(model, scanner.Text()); err != nil {
if err := generate(cmd, model, scanner.Text()); err != nil {
return err
}
@@ -144,12 +156,12 @@ func generateInteractive(model string) error {
return nil
}
func generateBatch(model string) error {
func generateBatch(cmd *cobra.Command, model string) error {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
prompt := scanner.Text()
fmt.Printf(">>> %s\n", prompt)
if err := generate(model, prompt); err != nil {
if err := generate(cmd, model, prompt); err != nil {
return err
}
}
@@ -201,6 +213,8 @@ func NewCLI() *cobra.Command {
RunE: RunRun,
}
runCmd.Flags().Bool("verbose", false, "Show timings for response")
serveCmd := &cobra.Command{
Use: "serve",
Aliases: []string{"start"},

View File

@@ -1,3 +1,5 @@
// +build darwin
/**
* llama.cpp - git 5bf2a2771886ee86137e01dbc7492f78fb392066
*

View File

@@ -79,9 +79,11 @@ llama_token llama_sample(
import "C"
import (
"errors"
"fmt"
"io"
"os"
"strings"
"time"
"unsafe"
"github.com/jmorganca/ollama/api"
@@ -123,7 +125,14 @@ func New(model string, opts api.Options) (*llama, error) {
defer C.free(unsafe.Pointer(cModel))
llm.model = C.llama_load_model_from_file(cModel, params)
if llm.model == nil {
return nil, errors.New("failed to load model")
}
llm.ctx = C.llama_new_context_with_model(llm.model, params)
if llm.ctx == nil {
return nil, errors.New("failed to create context")
}
// warm up the model
bos := []C.llama_token{C.llama_token_bos()}
@@ -140,7 +149,7 @@ func (llm *llama) Close() {
C.llama_print_timings(llm.ctx)
}
func (llm *llama) Predict(prompt string, fn func(string)) error {
func (llm *llama) Predict(prompt string, fn func(api.GenerateResponse)) error {
if tokens := llm.tokenize(prompt); tokens != nil {
return llm.generate(tokens, fn)
}
@@ -169,7 +178,7 @@ func (llm *llama) detokenize(tokens ...C.llama_token) string {
return sb.String()
}
func (llm *llama) generate(tokens []C.llama_token, fn func(string)) error {
func (llm *llama) generate(input []C.llama_token, fn func(api.GenerateResponse)) error {
var opts C.struct_llama_sample_options
opts.repeat_penalty = C.float(llm.RepeatPenalty)
opts.frequency_penalty = C.float(llm.FrequencyPenalty)
@@ -183,38 +192,58 @@ func (llm *llama) generate(tokens []C.llama_token, fn func(string)) error {
opts.mirostat_tau = C.float(llm.MirostatTau)
opts.mirostat_eta = C.float(llm.MirostatEta)
pastTokens := deque[C.llama_token]{capacity: llm.RepeatLastN}
output := deque[C.llama_token]{capacity: llm.NumCtx}
for C.llama_get_kv_cache_token_count(llm.ctx) < C.int(llm.NumCtx) {
if retval := C.llama_eval(llm.ctx, unsafe.SliceData(tokens), C.int(len(tokens)), C.llama_get_kv_cache_token_count(llm.ctx), C.int(llm.NumThread)); retval != 0 {
if retval := C.llama_eval(llm.ctx, unsafe.SliceData(input), C.int(len(input)), C.llama_get_kv_cache_token_count(llm.ctx), C.int(llm.NumThread)); retval != 0 {
return errors.New("llama: eval")
}
token, err := llm.sample(pastTokens, &opts)
switch {
case err != nil:
token, err := llm.sample(output, &opts)
if errors.Is(err, io.EOF) {
break
} else if err != nil {
return err
case errors.Is(err, io.EOF):
return nil
}
fn(llm.detokenize(token))
// call the callback
fn(api.GenerateResponse{
Response: llm.detokenize(token),
})
tokens = []C.llama_token{token}
output.PushLeft(token)
pastTokens.PushLeft(token)
input = []C.llama_token{token}
}
dur := func(ms float64) time.Duration {
d, err := time.ParseDuration(fmt.Sprintf("%fms", ms))
if err != nil {
panic(err)
}
return d
}
timings := C.llama_get_timings(llm.ctx)
fn(api.GenerateResponse{
Done: true,
PromptEvalCount: int(timings.n_p_eval),
PromptEvalDuration: dur(float64(timings.t_p_eval_ms)),
EvalCount: int(timings.n_eval),
EvalDuration: dur(float64(timings.t_eval_ms)),
})
return nil
}
func (llm *llama) sample(pastTokens deque[C.llama_token], opts *C.struct_llama_sample_options) (C.llama_token, error) {
func (llm *llama) sample(output deque[C.llama_token], opts *C.struct_llama_sample_options) (C.llama_token, error) {
numVocab := int(C.llama_n_vocab(llm.ctx))
logits := unsafe.Slice(C.llama_get_logits(llm.ctx), numVocab)
candidates := make([]C.struct_llama_token_data, 0, numVocab)
for i := 0; i < numVocab; i++ {
candidates = append(candidates, C.llama_token_data{
candidates := deque[C.struct_llama_token_data]{capacity: numVocab}
for i := 0; i < candidates.Cap(); i++ {
candidates.PushLeft(C.struct_llama_token_data{
id: C.int(i),
logit: logits[i],
p: 0,
@@ -223,8 +252,8 @@ func (llm *llama) sample(pastTokens deque[C.llama_token], opts *C.struct_llama_s
token := C.llama_sample(
llm.ctx,
unsafe.SliceData(candidates), C.ulong(len(candidates)),
unsafe.SliceData(pastTokens.Data()), C.ulong(pastTokens.Len()),
unsafe.SliceData(candidates.Data()), C.ulong(candidates.Len()),
unsafe.SliceData(output.Data()), C.ulong(output.Len()),
opts)
if token != C.llama_token_eos() {
return token, nil

View File

@@ -25,14 +25,14 @@
},
{
"name": "vicuna",
"display_name": "Wizard Vicuna Uncensored",
"parameters": "13B",
"url": "https://huggingface.co/TheBloke/Wizard-Vicuna-13B-Uncensored-GGML/resolve/main/Wizard-Vicuna-13B-Uncensored.ggmlv3.q2_K.bin",
"short_description": "An uncensored model with no guardrails.",
"description": "This model is trained with a subset of the dataset - responses that contained alignment / moralizing were removed. The intent is to train a WizardLM that doesn't have alignment built-in, so that alignment (of any sort) can be added separately with for example with a RLHF LoRA.",
"display_name": "Vicuna",
"parameters": "7B",
"url": "https://huggingface.co/TheBloke/vicuna-7B-v1.3-GGML/resolve/main/vicuna-7b-v1.3.ggmlv3.q4_0.bin",
"short_description": "Vicuna is a chat assistant trained by fine-tuning LLaMA on user-shared conversations collected from ShareGPT.",
"description": "The primary use of Vicuna is research on large language models and chatbots. The primary intended users of the model are researchers and hobbyists in natural language processing, machine learning, and artificial intelligence.",
"published_by": "TheBloke",
"original_author": "ehartford",
"original_url": "https://huggingface.co/ehartford/Wizard-Vicuna-13B-Uncensored",
"license:": "GPL"
"original_author": "LMSYS",
"original_url": "https://huggingface.co/lmsys/vicuna-7b-v1.3",
"license:": "Non-commercial"
}
]
]

View File

@@ -12,13 +12,15 @@ ARCH=$(go env GOARCH)
go build .
npm --prefix app run make:sign
# Create a new tag if it doesn't exist.
if ! git rev-parse v$VERSION >/dev/null 2>&1; then
git tag v$VERSION
git push origin v$VERSION
fi
mkdir dist
mkdir -p dist
cp app/out/make/zip/${OS}/${ARCH}/Ollama-${OS}-${ARCH}-${VERSION}.zip dist/Ollama-${OS}-${ARCH}.zip
cp ./ollama dist/ollama-${OS}-${ARCH}

View File

@@ -76,22 +76,10 @@ func saveModel(model *Model, fn func(total, completed int64)) error {
return fmt.Errorf("failed to download model: %w", err)
}
// check if completed file exists
fi, err := os.Stat(model.FullName())
switch {
case errors.Is(err, os.ErrNotExist):
// noop, file doesn't exist so create it
case err != nil:
return fmt.Errorf("stat: %w", err)
default:
fn(fi.Size(), fi.Size())
return nil
}
var size int64
// completed file doesn't exist, check partial file
fi, err = os.Stat(model.TempFile())
fi, err := os.Stat(model.TempFile())
switch {
case errors.Is(err, os.ErrNotExist):
// noop, file doesn't exist so create it
@@ -119,25 +107,22 @@ func saveModel(model *Model, fn func(total, completed int64)) error {
}
defer out.Close()
totalSize, _ := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
remaining, _ := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
completed := size
totalBytes := size
totalSize += size
total := remaining + completed
for {
n, err := io.CopyN(out, resp.Body, 8192)
fn(total, completed)
if completed >= total {
return os.Rename(model.TempFile(), model.FullName())
}
n , err := io.CopyN(out, resp.Body, 8192)
if err != nil && !errors.Is(err, io.EOF) {
return err
}
if n == 0 {
break
}
totalBytes += n
fn(totalSize, totalBytes)
completed += n
}
fn(totalSize, totalSize)
return os.Rename(model.TempFile(), model.FullName())
}

View File

@@ -13,6 +13,7 @@ import (
"path"
"strings"
"text/template"
"time"
"github.com/gin-gonic/gin"
"github.com/lithammer/fuzzysearch/fuzzy"
@@ -35,6 +36,8 @@ func cacheDir() string {
}
func generate(c *gin.Context) {
start := time.Now()
req := api.GenerateRequest{
Options: api.DefaultOptions(),
}
@@ -81,8 +84,14 @@ func generate(c *gin.Context) {
}
defer llm.Close()
fn := func(s string) {
ch <- api.GenerateResponse{Response: s}
fn := func(r api.GenerateResponse) {
r.Model = req.Model
r.CreatedAt = time.Now().UTC()
if r.Done {
r.TotalDuration = time.Since(start)
}
ch <- r
}
if err := llm.Predict(req.Prompt, fn); err != nil {
@@ -105,6 +114,24 @@ func pull(c *gin.Context) {
return
}
// check if completed file exists
fi, err := os.Stat(remote.FullName())
switch {
case errors.Is(err, os.ErrNotExist):
// noop, file doesn't exist so create it
case err != nil:
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
default:
c.JSON(http.StatusOK, api.PullProgress{
Total: fi.Size(),
Completed: fi.Size(),
Percent: 100,
})
return
}
ch := make(chan any)
go stream(c, ch)
@@ -112,7 +139,7 @@ func pull(c *gin.Context) {
ch <- api.PullProgress{
Total: total,
Completed: completed,
Percent: float64(total) / float64(completed) * 100,
Percent: float64(completed) / float64(total) * 100,
}
}
@@ -129,7 +156,7 @@ func Serve(ln net.Listener) error {
c.String(http.StatusOK, "Ollama is running")
})
r.POST("api/pull", pull)
r.POST("/api/pull", pull)
r.POST("/api/generate", generate)
log.Printf("Listening on %s", ln.Addr())

View File

@@ -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 })
}

View File

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

View File

@@ -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>
)
}

View File

@@ -0,0 +1,51 @@
'use client'
import { useState } from 'react'
export default function Signup() {
const [email, setEmail] = useState('')
const [submitting, setSubmitting] = useState(false)
const [success, setSuccess] = useState(false)
return (
<form
onSubmit={async e => {
e.preventDefault()
setSubmitting(true)
await fetch('/api/signup', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
})
setSubmitting(false)
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'
disabled={submitting}
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

@@ -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>
)
}

View File

@@ -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>

243
web/package-lock.json generated
View File

@@ -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",

View File

@@ -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"
}
}