dfalbel commited on
Commit
640a26a
β€’
1 Parent(s): 13c1f55

Completely refactor the app for better shiny practices.

Browse files
Files changed (1) hide show
  1. app.R +177 -117
app.R CHANGED
@@ -25,99 +25,38 @@ ui <- page_fillable(
25
  heights_equal = "row",
26
  width = 1,
27
  fillable = FALSE,
28
- card_body(id = "messages", gap = 5, fillable = FALSE)
29
  ),
30
  layout_column_wrap(
31
  width = 1/2,
32
  textInput("prompt", label = NULL, width="100%"),
33
- actionButton("send", "Send", width = "100%")
34
  )
35
  )
36
 
37
  server <- function(input, output, session) {
38
- idxs <- reactiveVal()
39
- n_tokens <- reactiveVal(value = 0)
40
- msg_id <- reactiveVal(value = 0)
41
-
42
- observeEvent(input$send, {
43
- if (is.null(input$prompt) || input$prompt == "") {
44
- return()
45
- }
46
- shinyjs::disable("send")
47
- updateActionButton(inputId = "send", label = "Waiting for model...")
48
- insert_message(msg_id, as.character(glue::glue("πŸ€—: {input$prompt}")))
49
-
50
- if (is.null(idxs())) {
51
- current_idxs <- sess$tok$encode(system_prompt)$ids
52
- } else {
53
- current_idxs <- idxs()
54
- }
55
-
56
- new_idxs <- paste0("<|USER|>", input$prompt, "<|ASSISTANT|>")
57
- new_idxs <- sess$tok$encode(new_idxs)$ids
58
-
59
- # we modify the prompt to trigger the 'next_token' reactive
60
- idxs(c(current_idxs, new_idxs))
61
- })
62
-
63
- next_token <- eventReactive(idxs(), ignoreInit = TRUE, {
64
- idxs() %>%
65
- sess$generate() %>%
66
- promises::then(
67
- onFulfilled = function(x) {x},
68
- onRejected = function(x) {
69
- insert_message(msg_id, paste0("😭 Error generating token.", as.character(x)))
70
- updateActionButton(inputId = "send", label = "Failing generation. Contact admin.")
71
- NULL
72
- }
73
- )
74
- })
75
-
76
- observeEvent(next_token(), {
77
- tok <- next_token()
78
- n_tokens(n_tokens() + 1)
79
- tok %>% promises::then(function(tok) {
80
- tok_dec <- sess$tok$decode(tok)
81
- if (n_tokens() == 1) {
82
- insert_message(msg_id, paste0("πŸ€–: ", tok_dec), append = FALSE)
83
- } else {
84
- insert_message(msg_id, tok_dec, append = TRUE)
85
- }
86
-
87
- if ((!tok %in% c(50278L, 50279L, 50277L, 1L, 0L)) &&
88
- n_tokens() < max_n_tokens) {
89
- idxs(c(idxs(), tok))
90
- } else {
91
- shinyjs::enable("send")
92
- updateActionButton(inputId = "send", label = "Send")
93
- n_tokens(0)
94
- }
95
- })
96
- })
97
-
98
  # Observer used at app startup time to allow using the 'Send' button once the
99
  # model has been loaded.
100
- model_loaded <- reactiveVal()
101
- event_reload <- reactiveVal(val = 0)
102
- observeEvent(event_reload(), ignoreNULL=FALSE, {
103
-
104
- # the model is already loaded, we just make sure the send button is enabled
105
  if (!is.null(sess$is_loaded) && sess$is_loaded) {
106
- shinyjs::enable("send")
107
- updateActionButton(inputId = "send", label = "Send")
108
  return()
109
  }
110
 
111
- # the model isn't loaded, this we disable the send button and
112
- # show that we are loading the model
113
- shinyjs::disable("send")
114
- updateActionButton(inputId = "send", label = "Loading the model...")
115
-
116
  # the model isn't loaded and no task is trying to load it, so we start a new
117
  # task to load it
118
  if (is.null(sess$is_loaded)) {
119
  cat("Started loading model ....", "\n")
120
- model_loaded(sess$load_model(repo))
121
  sess$is_loaded <- FALSE # not yet loaded, but loading
122
  } else {
123
  # the model is loading, but this is handled by another session. We should
@@ -129,51 +68,172 @@ server <- function(input, output, session) {
129
 
130
  # this runs for the cases where sess$is_loaded was NULL
131
  # ie there was no model currently loading.
132
- m <- model_loaded() %>%
133
- promises::then(onFulfilled = function(x) {
134
- cat("Model has been loaded!", "\n")
135
- shinyjs::enable("send")
136
- updateActionButton(inputId = "send", label = "Send")
137
- sess$is_loaded <- TRUE
138
- TRUE
139
- }, onRejected = function(x) {
140
- shinyjs::disable("send")
141
- insert_message(msg_id, paste0("😭 Error loading the model:\n", as.character(x)))
142
- sess$is_loaded <- NULL # means failure!
143
- sess$sess <- NULL
144
- if (event_reload() < 10) {
145
- Sys.sleep(5)
146
- event_reload(event_reload() + 1)
147
- }
148
- FALSE
149
- })
150
- model_loaded(m)
 
 
 
 
 
 
 
151
  })
152
- }
153
-
154
- insert_message <- function(message_id, msg, append = FALSE) {
155
- if (!append) {
156
- id <- message_id() + 1
157
- message_id(id)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
- insertUI(
160
- "#messages",
161
- "beforeEnd",
162
- immediate = TRUE,
163
- ui = card(style="margin-bottom:5px;", card_body(
164
- p(id = paste0("msg-",id), msg)
165
- ))
166
  )
167
- } else {
168
- id <- message_id()
169
- shinyjs::runjs(glue::glue(
170
- "document.getElementById('msg-{id}').textContent += '{msg}'"
171
- ))
172
- }
173
- # scroll to bottom
174
- shinyjs::runjs("var elem = document.getElementById('messages'); elem.scrollTop = elem.scrollHeight;")
175
- id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  }
177
 
178
-
179
  shinyApp(ui, server)
 
25
  heights_equal = "row",
26
  width = 1,
27
  fillable = FALSE,
28
+ uiOutput("messages")
29
  ),
30
  layout_column_wrap(
31
  width = 1/2,
32
  textInput("prompt", label = NULL, width="100%"),
33
+ uiOutput("sendButton")
34
  )
35
  )
36
 
37
  server <- function(input, output, session) {
38
+ # context for the observers that load the model in the background session
39
+ # it also handles reloads
40
+ loading <- reactiveValues(
41
+ model = NULL,
42
+ reload = NULL
43
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  # Observer used at app startup time to allow using the 'Send' button once the
45
  # model has been loaded.
46
+ observeEvent(loading$reload, ignoreInit = FALSE, ignoreNULL = FALSE, priority = 0, {
47
+
48
+ # the model is already loaded, we just make sure that we propagate this
49
+ # by setting generating to FALSE
 
50
  if (!is.null(sess$is_loaded) && sess$is_loaded) {
51
+ context$generating <- FALSE
 
52
  return()
53
  }
54
 
 
 
 
 
 
55
  # the model isn't loaded and no task is trying to load it, so we start a new
56
  # task to load it
57
  if (is.null(sess$is_loaded)) {
58
  cat("Started loading model ....", "\n")
59
+ loading$model <- sess$load_model(repo)
60
  sess$is_loaded <- FALSE # not yet loaded, but loading
61
  } else {
62
  # the model is loading, but this is handled by another session. We should
 
68
 
69
  # this runs for the cases where sess$is_loaded was NULL
70
  # ie there was no model currently loading.
71
+ m <- loading$model %>%
72
+ promises::then(
73
+ onFulfilled = function(x) {
74
+ cat("Model has been loaded!", "\n")
75
+ context$generating <- FALSE
76
+ sess$is_loaded <- TRUE
77
+ TRUE
78
+ },
79
+ onRejected = function(x) {
80
+ context$generating <- "error"
81
+ msg <- list(
82
+ role = "error",
83
+ content = paste0("Error loading the model:\n", as.character(x))
84
+ )
85
+ context$messages <- append(context$messages, list(msg))
86
+
87
+ # setup for retry!
88
+ sess$is_loaded <- NULL # means failure!
89
+ sess$sess <- NULL
90
+ if (loading$reload < 10) {
91
+ Sys.sleep(5)
92
+ loading$reload <- loading$reload + 1
93
+ }
94
+ FALSE
95
+ })
96
+ loading$model <- m
97
  })
98
+
99
+
100
+ # context for generating messages
101
+ context <- reactiveValues(
102
+ generating = "loading", # a flag indicating if we are still generating tokens
103
+ idxs = NULL, # the current sequence of tokens
104
+ n_tokens = 0, # number of tokens already generated
105
+ messages = list()
106
+ )
107
+
108
+ observeEvent(input$send, ignoreInit = TRUE, {
109
+ # the is the observer for send message action button that triggers the rest
110
+ # of the reactions.
111
+ # if the prompt is empty, there's nothing to do
112
+ if (is.null(input$prompt) || input$prompt == "") {
113
+ return()
114
+ }
115
+
116
+ # the user clicked 'send' and the prompt is not empty:
117
+ # we will enter in generation mode
118
+ context$generating <- TRUE
119
 
120
+ # we add the user message into the messages list
121
+ context$messages <- append(
122
+ context$messages,
123
+ list(list(role = "user", content = input$prompt))
 
 
 
124
  )
125
+ # ... and the start of the assistant message
126
+ context$messages <- append(
127
+ context$messages,
128
+ list(list(role = "assistant", content = ""))
129
+ )
130
+
131
+ # we also update the idxs context value with the newly added prompt
132
+ # in case, this is the first send call, we also need to add the system
133
+ # prompt
134
+ if (is.null(context$idxs)) {
135
+ context$idxs <- sess$tok$encode(system_prompt)$ids
136
+ }
137
+
138
+ # we now append the prompt. the prompt is wrapped around special tokens
139
+ # for generation:
140
+ prompt <- paste0("<|USER|>", input$prompt, "<|ASSISTANT|>")
141
+ context$idxs <- c(context$idxs, sess$tok$encode(prompt)$ids)
142
+ cat("Tokens in context: ", length(context$idxs), "\n")
143
+ })
144
+
145
+ observeEvent(context$generating, priority = 10, {
146
+ # this controls the state of the send button.
147
+ # if generating is TRUE we want it to be disabled, otherwise it's enabled
148
+ # if generating is `NULL`, then the model is not yet loaded
149
+ btn <- if (is.null(context$generating) || context$generating == "loading") {
150
+ btn <- list(class = "btn-secondary disabled", label = "Loading model ...")
151
+ insertUI(
152
+ "#sendButton",
153
+ ui = actionButton("send", width = "100%", label = btn$label, class = btn$class),
154
+ immediate = TRUE
155
+ )
156
+ btn
157
+ } else if (context$generating == "error") {
158
+ list(class = "btn-secondary disabled", label = "Generating error ...")
159
+ } else if (context$generating) {
160
+ list(class = "btn-secondary disabled", label = "Generating response ...")
161
+ } else {
162
+ list(class = "btn-primary", label = "Send")
163
+ }
164
+
165
+ output$sendButton <- renderUI({
166
+ actionButton("send", width = "100%", label = btn$label, class = btn$class)
167
+ })
168
+ })
169
+
170
+ observeEvent(context$messages, priority = 10, {
171
+ # this observer generates and updates the messages list
172
+ msg_cards <- context$messages %>%
173
+ lapply(function(msg) {
174
+ emoji <- if (msg$role == "user") "πŸ€—" else "πŸ€–"
175
+ card(style="margin-bottom:5px;", card_body(
176
+ p(paste0(emoji, ":", msg$content))
177
+ ))
178
+ })
179
+
180
+ output$messages <- renderUI({
181
+ rlang::exec(card_body, !!!msg_cards, gap = 5, fillable = FALSE)
182
+ })
183
+ })
184
+
185
+ observeEvent(context$idxs, {
186
+ # this observer is responsible for actually generating text by calling the
187
+ # model that is loaded in `sess`. it takes the context$idxs to generate new
188
+ # text and updates it once it's done. It also appends the last message with
189
+ # the newly generated token
190
+ context$idxs %>%
191
+ sess$generate() %>%
192
+ promises::then(
193
+ onFulfilled = function(id) {
194
+ if (id %in% c(50278L, 50279L, 50277L, 1L, 0L)) {
195
+ context$generating <- FALSE
196
+ context$n_tokens <- 0
197
+ return() # special tokens that stop generation.
198
+ }
199
+
200
+ # update last message with the newly generated token
201
+ messages <- context$messages
202
+ new_msg <- paste0(
203
+ messages[[length(messages)]]$content,
204
+ sess$tok$decode(id)
205
+ )
206
+
207
+ messages[[length(messages)]]$content <- new_msg
208
+ context$messages <- messages
209
+
210
+ # update the token counter
211
+ context$n_tokens <- context$n_tokens + 1
212
+
213
+ if (context$n_tokens > max_n_tokens) {
214
+ context$generating <- FALSE
215
+ context$n_tokens <- 0
216
+ return() # we already generated enough tokens
217
+ }
218
+
219
+ context$idxs <- c(context$idxs, id)
220
+ },
221
+ onRejected = function(x) {
222
+ # if there was a generation error, we post a message in the message
223
+ # list with a error role
224
+ msg <- list(
225
+ role = "error",
226
+ content = paste0("Error generating token.", x)
227
+ )
228
+ context$messages <- append(context$messages, list(msg))
229
+
230
+ # we also say that we are no longer generating, by setting another
231
+ # value for the `generating` "flag"
232
+ context$generating <- "error"
233
+ }
234
+ )
235
+ NULL
236
+ })
237
  }
238
 
 
239
  shinyApp(ui, server)