Implemented first version of CodeAgent monad.

This commit is contained in:
Martin Sosic 2023-06-13 16:09:48 +02:00
parent 65ec766ad6
commit 4401d4a89f
3 changed files with 68 additions and 6 deletions

View File

@ -0,0 +1,53 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Wasp.Cli.Command.AI.CodeAgent
( CodeAgent,
CodeAgentConfig (..),
runCodeAgent,
writeToLog,
writeToFile,
getFile,
getAllFiles,
)
where
import Control.Monad.IO.Class (MonadIO (liftIO))
import Control.Monad.Reader (MonadReader, ReaderT (runReaderT), asks)
import Control.Monad.State (MonadState, StateT (runStateT), gets)
import Data.List (find)
import Data.Text (Text)
import Wasp.OpenAI (OpenAIApiKey)
newtype CodeAgent a = CodeAgent {_unCodeAgent :: ReaderT CodeAgentConfig (StateT CodeAgentState IO) a}
deriving (Monad, Applicative, Functor, MonadIO, MonadReader CodeAgentConfig, MonadState CodeAgentState)
data CodeAgentConfig = CodeAgentConfig
{ _openAIApiKey :: !OpenAIApiKey,
_writeFile :: !(FilePath -> Text -> IO ()), -- TODO: Use StrongPath? Not clear which kind of path is it, rel, abs, ... .
_writeLog :: !(Text -> IO ())
}
runCodeAgent :: CodeAgent a -> CodeAgentConfig -> IO a
runCodeAgent codeAgent config =
fst <$> (_unCodeAgent codeAgent `runReaderT` config) `runStateT` initialState
where
initialState = CodeAgentState {_files = []}
writeToLog :: Text -> CodeAgent ()
writeToLog msg = asks _writeLog >>= liftIO . ($ msg)
writeToFile :: FilePath -> (Maybe Text -> Text) -> CodeAgent ()
writeToFile path updateContentFn = do
content <- updateContentFn <$> getFile path
asks _writeFile >>= liftIO . ($ content) . ($ path)
getFile :: FilePath -> CodeAgent (Maybe Text)
getFile path =
(snd <$>) . find ((== path) . fst) <$> getAllFiles
getAllFiles :: CodeAgent [(FilePath, Text)]
getAllFiles = gets _files
data CodeAgentState = CodeAgentState
{ _files :: ![(FilePath, Text)]
}

View File

@ -3,7 +3,6 @@ module Wasp.Cli.Command.AI.GenerateNewProject () where
-- TODO: Probably move this module out of here into general wasp lib.
import Wasp.OpenAI (OpenAIApiKey)
import qualified Wasp.OpenAI.ChatGPT as Chat
data NewProjectDetails = NewProjectDetails
{ _projectName :: !String,
@ -13,13 +12,11 @@ data NewProjectDetails = NewProjectDetails
data AuthProvider = Google
data FileDraft = FileDraft
{ path :: FilePath, -- TODO: Use StrongPath?
content :: Text
}
-- TODO: Create a monad stack transformer that contains openaiapikey + has state which holds files to generate?
-- Call it CodeGenerator. It can also send messages (via Chan) about its progress.
-- It doesn't even need to know how to send messages, it can just do that via IO somehow, and then we inject logic for that, be it Chan or something else.
-- But we define messages.
-- We could have two functions: writeFile, and updateFile. Or maybe just writeFile is enough, but it accepts previous content, if it has any.
-- TODO: Have generateNewProject accept Chan, to which it will stream its progress?
-- It could just stream its output instead of printing it to stdout, so calling function
@ -49,4 +46,5 @@ generateNewProject openAiKey newProjectDetails = do
-- TODO: Same as actions.
pages <- generatePages newProjectDetails plan entities queries actions
-- TODO: Similar as actions.
-- TODO: what about having additional step here that goes through all the files once again and fixes any stuff in them (Wasp, JS files)? REPL?
return ()

View File

@ -10,6 +10,7 @@ module Wasp.Util.IO
readFileStrict,
writeFile,
removeFile,
tryReadFile,
)
where
@ -17,6 +18,7 @@ import Control.Monad (filterM, when)
import Control.Monad.Extra (whenM)
import Data.Text (Text)
import qualified Data.Text.IO as T.IO
import qualified Data.Text.IO as Text.IO
import StrongPath (Abs, Dir, Dir', File, Path', Rel, basename, parseRelDir, parseRelFile, toFilePath, (</>))
import qualified StrongPath as SP
import qualified System.Directory as SD
@ -101,3 +103,12 @@ writeFile = P.writeFile . SP.fromAbsFile
removeFile :: Path' Abs (File f) -> IO ()
removeFile = SD.removeFile . SP.fromAbsFile
tryReadFile :: FilePath -> IO (Maybe Text)
tryReadFile fp =
(Just <$> Text.IO.readFile fp)
`catch` ( \e ->
if isDoesNotExistError e
then return Nothing
else throwIO e
)