Spaces:
Runtime error
Runtime error
Completely refactor the app for better shiny practices.
Browse files
app.R
CHANGED
@@ -25,99 +25,38 @@ ui <- page_fillable(
|
|
25 |
heights_equal = "row",
|
26 |
width = 1,
|
27 |
fillable = FALSE,
|
28 |
-
|
29 |
),
|
30 |
layout_column_wrap(
|
31 |
width = 1/2,
|
32 |
textInput("prompt", label = NULL, width="100%"),
|
33 |
-
|
34 |
)
|
35 |
)
|
36 |
|
37 |
server <- function(input, output, session) {
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
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 |
-
|
101 |
-
|
102 |
-
|
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 |
-
|
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 |
-
|
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 <-
|
133 |
-
promises::then(
|
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 |
-
ui = card(style="margin-bottom:5px;", card_body(
|
164 |
-
p(id = paste0("msg-",id), msg)
|
165 |
-
))
|
166 |
)
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
)
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|