File size: 6,353 Bytes
002cfd7
 
3a638e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d2fa5f2
3a638e2
 
d2fa5f2
 
5e99ac2
d2fa5f2
3a638e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
002cfd7
3a638e2
 
 
 
 
 
 
 
002cfd7
 
3a638e2
 
002cfd7
3a638e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
002cfd7
3a638e2
 
 
 
 
 
 
 
 
 
 
002cfd7
 
3a638e2
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
library(shiny)
library(bslib)
library(bs4Dash)
library(ellmer)
library(querychat)
library(gapminder)
library(highcharter)
library(reactable)
library(shinychat)

# get Gemini api key
api_key = Sys.getenv("GOOGLE_API_KEY")

# create data of the first 500 rows in gapminder
gdp_data <- gapminder


# initialize querychat with mtcars dataset and Gemini chat function
querychat_config <- querychat_init(gdp_data,
                                   create_chat_func = purrr::partial(ellmer::chat_google_gemini, model = "gemini-2.0-flash"),
                                   greeting = "")

# define the UI of the dashboard
ui <- tabsetPanel(
  id = "tabs",
  type = "pills",
  tabPanel(
    id = "query-tab",
    title = strong("Data Query"),
    icon = icon("chart-line",lib = "font-awesome",),
    page_sidebar(
      # hover bounce effect of value boxes
      tags$head(
        tags$style(HTML("
        .bounce-hover:hover {
          animation: bounce 0.6s infinite alternate;
        }

        .bounce-hover {
          box-shadow: 0 7px 7px grey;
        }
      
        @keyframes bounce {
          from {
            transform: translateY(0);
          }
          to {
            transform: translateY(-10px);
          }
        }
    "))
      ),
      sidebar = querychat_sidebar("chat",width = "25%"),
      # to change div background color on hover: onmouseover = "this.style.backgroundColor='mediumseagreen';", onmouseout="this.style.backgroundColor='DodgerBlue';". In style = "cursor: pointer;
      fluidRow(column(4,tags$div(class = "bounce-hover",valueBoxOutput("GDP"), style = "text-align:center; font-size: 20px; font-weight: bold;color:black;")),
               column(4,tags$div(class = "bounce-hover",valueBoxOutput("Population"), style = "text-align:center; font-size: 20px; font-weight: bold;")),
               column(4,tags$div(class = "bounce-hover",valueBoxOutput("LifeExp"),style = "text-align:center; font-size: 20px; font-weight: bold;"))),
      fluidRow(column(6,highchartOutput("line",width = "98%")),
               column(6,highchartOutput("column",width = "98%"))),
      fluidRow(reactable::reactableOutput("table",width = "100%"))
    )
  ),
  tabPanel(
    id = "chat-tab",
    title = strong("EconoBot"),
    icon = icon("robot",lib = "font-awesome"),
    h3(strong("🤖 EconoBot"),style="text-align:center;color:DodgerBlue;"),
    h5(strong("Answers your economic and financial questions"),style="text-align:center;color:#37474f;"),
    chat_ui("gemini_chat",placeholder = "Type your message here...")
  )
)

# define the server logic required to interact with the data 
server <- function(input, output) {

  # create a querychat object using the config from step 1.
  query_chat <- querychat_server("chat", querychat_config)
  
  output$table <- reactable::renderReactable({
    reactable::reactable(query_chat$df(),
                         bordered = T,
                         compact = T,
                         highlight = T,
                         striped = T,
                         searchable = T,
                         filterable = T,
                         defaultColDef = colDef(align = "center"),
                         theme = reactable::reactableTheme(
                           stripedColor = "#0091ea",
                           borderColor = "#0091ea",
                           borderWidth = 3
                         ))
  })
  
  output$line <- renderHighchart({
    req(query_chat$df())
    my_colors <- c("Africa" = "#1f77b4", "Asia" = "#ff7f0e", "Europe" = "#2ca02c",
                   "Americas" = "#d62728", "Oceania" = "#9467bd")
    
    hchart(query_chat$df(),
                 type = "line",
                 hcaes(x = year, y = pop, group = continent)) |>
      hc_colors(unname(my_colors)) |>
      hc_title(text = "Population Per Continent or Country")
  })
  
  output$column <- renderHighchart({
    req(query_chat$df())
    my_colors <- c("Africa" = "#1f77b4", "Asia" = "#ff7f0e", "Europe" = "#2ca02c",
                   "Americas" = "#d62728", "Oceania" = "#9467bd")
    
    hchart(query_chat$df(),
           type = "column",
           hcaes(x = year, y = gdpPercap, group = continent)) |>
      hc_colors(unname(my_colors)) |>
      hc_title(text = "GDP Per Capita Per Continent or Country")
  })
  
  output$GDP <- renderValueBox({
    req(query_chat$df())
    valueBox(
      value = round(mean(query_chat$df()$gdpPercap, na.rm = TRUE), 2),
      color = "success",
      icon = icon("dollar"),
      elevation = 4,
      width = 4,
      subtitle = " ",
      footer = "Average GDP Per Capita"
    )
  })
  
  output$Population <- renderValueBox({
    req(query_chat$df())
    valueBox(
      value = round(mean(query_chat$df()$pop, na.rm = TRUE), 0),
      color = "primary",
      icon = icon("people-group"),
      elevation = 4,
      width = 4,
      subtitle = " ",
      footer = "Average Population"
    )
  })
  
  output$LifeExp <- renderValueBox({
    req(query_chat$df())
    valueBox(
      value = round(mean(query_chat$df()$lifeExp, na.rm = TRUE), 2),
      color = "warning",
      icon = icon("person-walking"),
      elevation = 4,
      width = 4,
      subtitle = " ",
      footer = "Average Life Expectancy"
    )
  })
  
  # write logic for Gemini chat
  gemini <- chat_google_gemini(
    system_prompt = "You are a friendly and helpful economic and financial expert who only answers questions around country economy and finance. If a user asks a question that is not related to economics or finance, give the following polite response: 'I'm sorry, I can only provide economic and financial information'.
    Also, if a user asks for specific or personal financial advice, give the following polite response: 'I'm sorry, I cannot provide specific financial advice. Please consult a financial advisor for personalized guidance.'",
    model = "gemini-2.0-flash",
    api_key = api_key,
    echo = T
  )
  
  observeEvent(input$gemini_chat_user_input, {
    
    # input$gemini_chat_user_input is from the chat id in the UI
    gemini$chat_async(input$gemini_chat_user_input)$then(function(response) {
      chat_append("gemini_chat", response)
    })$catch(function(error) {
      warning("An error occurred during chat_async: ", error$message)
    })
  })
  
}

# Run the application 
shinyApp(ui = ui, server = server)