packages feed

ollama-haskell-0.1.2.0: src/Data/Ollama/Pull.hs

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings #-}

module Data.Ollama.Pull
  ( -- * Downloaded Models
    pull
  , pullOps
  ) where

import Data.Aeson
import Data.ByteString.Lazy.Char8 qualified as BSL
import Data.Maybe (fromMaybe)
import Data.Ollama.Common.Utils as CU
import Data.Text (Text)
import Data.Text qualified as T
import GHC.Generics
import GHC.Int (Int64)
import Network.HTTP.Client

-- TODO: Add Options parameter

-- | Configuration options for pulling a model.
data PullOps = PullOps
  { name :: Text
  -- ^ The name of the model to pull.
  , insecure :: Maybe Bool
  -- ^ Option to allow insecure connections.
  -- If set to 'Just True', the pull operation will allow insecure connections.
  , stream :: Maybe Bool
  -- ^ Option to enable streaming of the download.
  -- If set to 'Just True', the download will be streamed.
  }
  deriving (Show, Eq, Generic, ToJSON)

-- | Response data from a pull operation.
data PullResp = PullResp
  { status :: Text
  -- ^ The status of the pull operation, e.g., "success" or "failure".
  , digest :: Maybe Text
  -- ^ The digest of the model, if available.
  , total :: Maybe Int64
  -- ^ The total size of the model in bytes, if available.
  , completed :: Maybe Int64
  -- ^ The number of bytes completed, if available.
  }
  deriving (Show, Eq, Generic, FromJSON)

{- |
Pull a model with additional options for insecure connections and streaming.
This function interacts directly with the Ollama API to download the specified model.

Example:

> pullOps "myModel" (Just True) (Just True)

This will attempt to pull "myModel" with insecure connections allowed and enable streaming.
-}
pullOps ::
  -- | Model Name
  Text ->
  -- | Insecure
  Maybe Bool ->
  -- | Stream
  Maybe Bool ->
  IO ()
pullOps modelName mInsecure mStream = do
  let url = defaultOllamaUrl
  manager <- newManager defaultManagerSettings
  initialRequest <- parseRequest $ T.unpack (url <> "/api/pull")
  let reqBody =
        PullOps
          { name = modelName
          , insecure = mInsecure
          , stream = mStream
          }
      request =
        initialRequest
          { method = "POST"
          , requestBody = RequestBodyLBS $ encode reqBody
          }
  withResponse request manager $ \response -> do
    let go = do
          bs <- brRead $ responseBody response
          let eRes = decode (BSL.fromStrict bs) :: Maybe PullResp
          case eRes of
            Nothing -> putStrLn "Something went wrong"
            Just res -> do
              if status res /= "success"
                then do
                  let completed' = fromMaybe 0 (completed res)
                  let total' = fromMaybe 0 (total res)
                  putStrLn $ "Remaining bytes: " <> show (total' - completed')
                  go
                else do
                  putStrLn "Completed"
    go

{- |
Pull a model using default options. This simplifies the pull operation by
not requiring additional options.

Example:

> pull "myModel"

This will pull "myModel" using default settings (no insecure connections and no streaming).
-}
pull ::
  -- | Model Name
  Text ->
  IO ()
pull modelName = pullOps modelName Nothing Nothing