Skip to content

Commit 39d620d

Browse files
committed
added example: Web Application For Using the Google Gemini APIs
1 parent 74b1d0f commit 39d620d

File tree

3 files changed

+200
-0
lines changed

3 files changed

+200
-0
lines changed

webchat/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# A Web Application For Using the Google Gemini APIs
2+
3+
Build and run using:
4+
5+
cabal run
6+
7+
Open a browser of:
8+
9+
http://localhost:3000

webchat/gemini-chat.cabal

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
cabal-version: 2.4
2+
name: gemini-chat
3+
version: 0.1.0.0
4+
executable gemini-chat
5+
main-is: Main.hs
6+
7+
-- Dependencies
8+
build-depends: base >= 4.7 && < 5
9+
, scotty
10+
, http-client >= 0.7
11+
, http-client-tls
12+
, http-types
13+
, aeson
14+
, text
15+
, vector
16+
, bytestring
17+
if os(darwin)
18+
ghc-options:
19+
ld-options:
20+
21+
-- Language extensions used in the code
22+
default-extensions:
23+
OverloadedStrings
24+
DeriveGeneric
25+
LambdaCase
26+
DeriveAnyClass
27+
28+
program-default-options
29+
hsc2hs: --with-hsc2hs=/opt/homebrew/bin/hsc2hs
30+
31+
-- Compiler flags
32+
ghc-options: -Wall -O2
33+

webchat/main.hs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import Web.Scotty
2+
import Network.HTTP.Client.TLS (tlsManagerSettings)
3+
import Network.HTTP.Client
4+
import Network.HTTP.Types.Status (status500)
5+
import qualified Data.ByteString.Char8 as BS
6+
import qualified Data.Text.Lazy as TL
7+
import qualified Data.Aeson as Aeson
8+
import Data.Aeson (FromJSON, ToJSON)
9+
import GHC.Generics
10+
import qualified Data.Text as T
11+
import System.IO (hFlush, stdout)
12+
import System.Environment (getEnv)
13+
import qualified Data.Vector as V
14+
15+
data GeminiRequest = GeminiRequest
16+
{ prompt :: String
17+
} deriving (Show, Generic, FromJSON, ToJSON)
18+
19+
data GeminiResponse = GeminiResponse
20+
  { candidates :: [Candidate]
21+
  , promptFeedback :: Maybe PromptFeedback
22+
  } deriving (Show, Generic, FromJSON, ToJSON)
23+
24+
data Candidate = Candidate
25+
  { content :: Content2
26+
  , finishReason :: Maybe String
27+
  , index :: Maybe Int
28+
  } deriving (Show, Generic, FromJSON, ToJSON)
29+
30+
data Content2 = Content2
31+
  { parts :: [Part]
32+
  , role :: Maybe String
33+
  } deriving (Show, Generic, FromJSON, ToJSON)
34+
35+
data Part = Part
36+
  { text :: String
37+
  } deriving (Show, Generic, FromJSON, ToJSON)
38+
39+
data PromptFeedback = PromptFeedback
40+
  { blockReason :: Maybe String
41+
  , safetyRatings :: Maybe [SafetyRating]
42+
  } deriving (Show, Generic, FromJSON, ToJSON)
43+
44+
data SafetyRating = SafetyRating
45+
  { category :: String
46+
  , probability :: String
47+
  } deriving (Show, Generic, FromJSON, ToJSON)
48+
49+
50+
main :: IO ()
51+
main = do
52+
apiKey <- getEnv "GOOGLE_API_KEY"
53+
scotty 3000 $ do
54+
get "/" $ do
55+
html $ TL.pack
56+
"<!DOCTYPE html>\
57+
\<html>\
58+
\<head>\
59+
\<title>Gemini Chat</title>\
60+
\</head>\
61+
\<body>\
62+
\ <h1>Gemini Chat</h1>\
63+
\ <form id='chat-form'>\
64+
\ <input type='text' id='prompt' name='prompt' placeholder='Enter your prompt'\
65+
\ style='width: 70%; padding: 10px; font-size: 16px;'>\
66+
\ <button type='submit'>Send</button>\
67+
\ </form><br/><br/><h4>Response:</h4>\
68+
\ <div id='response'></div>\
69+
\ <script>\
70+
\ const form = document.getElementById('chat-form');\
71+
\ const responseDiv = document.getElementById('response');\
72+
\ form.addEventListener('submit', async (event) => {\
73+
\ event.preventDefault();\
74+
\ const prompt = document.getElementById('prompt').value;\
75+
\ try {\
76+
\ const response = await fetch('/chat', {\
77+
\ method: 'POST',\
78+
\ headers: { 'Content-Type': 'application/json' },\
79+
\ body: JSON.stringify({ prompt: prompt })\
80+
\ });\
81+
\ const data = await response.json();\
82+
\ responseDiv.innerText = data.text;\
83+
\ } catch (error) {\
84+
\ console.error('Error:', error);\
85+
\ responseDiv.innerText = 'Error occurred while fetching response';\
86+
\ }\
87+
\ });\
88+
\ </script>\
89+
\</body>\
90+
\</html>"
91+
92+
    post "/chat" $ do
93+
      req <- jsonData :: ActionM GeminiRequest
94+
      liftIO $ putStrLn $ "Received request: " ++ show req
95+
      liftIO $ hFlush stdout
96+
97+
      manager <- liftIO $ newManager tlsManagerSettings
98+
99+
initialRequest <- liftIO $ parseRequest
100+
        "https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent"
101+
102+
let geminiRequestBody = Aeson.object
103+
            [ ("contents", Aeson.Array $ V.singleton $ Aeson.object
104+
                [ ("parts", Aeson.Array $ V.singleton $ Aeson.object
105+
                    [ ("text", Aeson.String $ T.pack $ prompt req)
106+
                    ]
107+
                  )
108+
                ]
109+
              )
110+
            , ("generationConfig", Aeson.object
111+
                [ ("temperature", Aeson.Number 0.1)
112+
                , ("maxOutputTokens", Aeson.Number 800)
113+
                ]
114+
              )
115+
            ]
116+
117+
let request2 = initialRequest
118+
            { requestHeaders =
119+
                [ ("Content-Type", "application/json")
120+
                , ("x-goog-api-key", BS.pack apiKey)
121+
                ]
122+
            , method = "POST"
123+
            , requestBody = RequestBodyLBS $ Aeson.encode geminiRequestBody
124+
            }
125+
126+
liftIO $ putStrLn $ "Request body: " ++ show (Aeson.encode geminiRequestBody)
127+
      liftIO $ hFlush stdout
128+
129+
response2 <- liftIO $ httpLbs request2 manager
130+
liftIO $ do
131+
        putStrLn $ "Response status: " ++ show (responseStatus response2)
132+
        putStrLn $ "Response headers: " ++ show (responseHeaders response2)
133+
        putStrLn $ "Raw response: " ++ show (responseBody response2)
134+
        hFlush stdout
135+
136+
let maybeGeminiResponse = Aeson.decode (responseBody response2) :: Maybe GeminiResponse
137+
138+
liftIO $ putStrLn $ "Parsed response: " ++ show maybeGeminiResponse -- Debug print
139+
liftIO $ hFlush stdout
140+
141+
case maybeGeminiResponse of
142+
        Just geminiResponse -> do
143+
          case candidates geminiResponse of
144+
            (candidate:_) -> do
145+
              case parts (content candidate) of
146+
                (part:_) -> do
147+
                  liftIO $ putStrLn $ "Sending response: " ++ show part
148+
                  liftIO $ hFlush stdout
149+
                  json part
150+
                [] -> do
151+
                  liftIO $ putStrLn "No parts in response"
152+
                  status status500 >> Web.Scotty.text "No content in response"
153+
            [] -> do
154+
              liftIO $ putStrLn "No candidates in response"
155+
              status status500 >> Web.Scotty.text "No candidates in response"
156+
        Nothing -> do
157+
          liftIO $ putStrLn "Failed to parse response"
158+
          status status500 >> Web.Scotty.text "Failed to parse Gemini response"

0 commit comments

Comments
 (0)