From b3b2c2579070b32e579eddb7f62b7f592720a7c3 Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Fri, 3 Nov 2023 16:42:02 -0500 Subject: [PATCH 01/38] fix false negative with api keys containing "-" --- R/check_api.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/check_api.R b/R/check_api.R index 41389da6..0b67710e 100644 --- a/R/check_api.R +++ b/R/check_api.R @@ -64,7 +64,7 @@ check_api_key <- function(api_key) { invisible(FALSE) } - if (stringr::str_detect(api_key, "^[a-zA-Z0-9-]{30,60}$")) { + if (stringr::str_detect(api_key, "^([a-zA-Z0-9]|-){30,60}$")) { invisible(TRUE) } else { set_key_instructions From e441d8ac8732149632e700f5dd9259e5bb21e73f Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Fri, 3 Nov 2023 17:04:34 -0500 Subject: [PATCH 02/38] remove false negatives in test snapshots --- R/check_api.R | 17 ++++++------ tests/testthat/_snaps/check_api.md | 42 ------------------------------ tests/testthat/test-check_api.R | 1 - 3 files changed, 8 insertions(+), 52 deletions(-) diff --git a/R/check_api.R b/R/check_api.R index 0b67710e..3b0107a4 100644 --- a/R/check_api.R +++ b/R/check_api.R @@ -51,23 +51,22 @@ check_api_connection <- function(api_key, verbose = FALSE) { #' success message is printed. If the API key is not in the correct format, #' an error message is printed and the function aborts. check_api_key <- function(api_key) { - set_key_instructions <- - cli_inform( - c( - "!" = "OPENAI_API_KEY is not valid.", - "i" = "Generate a key at {.url https://platform.openai.com/account/api-keys}", - "i" = "Set the key in your .Renviron file {.run usethis::edit_r_environ()}" - ) + key_instructions <- + c( + "!" = "OPENAI_API_KEY is not valid.", + "i" = "Generate a key at {.url https://platform.openai.com/account/api-keys}", + "i" = "Set the key in your .Renviron file {.run usethis::edit_r_environ()}" ) + if (is.null(api_key)) { - set_key_instructions + cli::cli_inform(key_instructions) invisible(FALSE) } if (stringr::str_detect(api_key, "^([a-zA-Z0-9]|-){30,60}$")) { invisible(TRUE) } else { - set_key_instructions + cli::cli_inform(key_instructions) invisible(FALSE) } } diff --git a/tests/testthat/_snaps/check_api.md b/tests/testthat/_snaps/check_api.md index 4ec28344..ae1546b3 100644 --- a/tests/testthat/_snaps/check_api.md +++ b/tests/testthat/_snaps/check_api.md @@ -3,9 +3,6 @@ Code check_api() Message - ! OPENAI_API_KEY is not valid. - i Generate a key at - i Set the key in your .Renviron file `usethis::edit_r_environ()` x API key found but call was unsuccessful. i Attempted to use API key: 38a5****************************2d60 @@ -31,55 +28,27 @@ Code check_api() - Message - ! OPENAI_API_KEY is not valid. - i Generate a key at - i Set the key in your .Renviron file `usethis::edit_r_environ()` --- Code check_api() - Message - ! OPENAI_API_KEY is not valid. - i Generate a key at - i Set the key in your .Renviron file `usethis::edit_r_environ()` - ---- - - Code - check_api() - Message - ! OPENAI_API_KEY is not valid. - i Generate a key at - i Set the key in your .Renviron file `usethis::edit_r_environ()` # API checking works, assumes OPENAI_API_KEY is set Code check_api() - Message - ! OPENAI_API_KEY is not valid. - i Generate a key at - i Set the key in your .Renviron file `usethis::edit_r_environ()` --- Code check_api() - Message - ! OPENAI_API_KEY is not valid. - i Generate a key at - i Set the key in your .Renviron file `usethis::edit_r_environ()` --- Code check_api() Message - ! OPENAI_API_KEY is not valid. - i Generate a key at - i Set the key in your .Renviron file `usethis::edit_r_environ()` x API key found but call was unsuccessful. i Attempted to use API key: 38a5****************************2d60 @@ -87,10 +56,6 @@ Code check_api_key(sample_key) - Message - ! OPENAI_API_KEY is not valid. - i Generate a key at - i Set the key in your .Renviron file `usethis::edit_r_environ()` --- @@ -115,9 +80,6 @@ Code check_api_connection(sample_key) Message - ! OPENAI_API_KEY is not valid. - i Generate a key at - i Set the key in your .Renviron file `usethis::edit_r_environ()` x API key found but call was unsuccessful. i Attempted to use API key: 38a5****************************2d60 @@ -134,8 +96,4 @@ Code check_api_connection(Sys.getenv("OPENAI_API_KEY")) - Message - ! OPENAI_API_KEY is not valid. - i Generate a key at - i Set the key in your .Renviron file `usethis::edit_r_environ()` diff --git a/tests/testthat/test-check_api.R b/tests/testthat/test-check_api.R index 055ad6c9..128bdd32 100644 --- a/tests/testthat/test-check_api.R +++ b/tests/testthat/test-check_api.R @@ -24,7 +24,6 @@ test_that("API checking works on CI", { withr::local_options(gptstudio.valid_api = FALSE) withr::local_envvar("OPENAI_API_KEY" = sample_key) expect_snapshot(check_api()) - expect_snapshot(check_api()) withr::local_envvar("OPENAI_API_KEY" = sample_key2) expect_snapshot(check_api()) }) From 7f4f31ae523983435d675796ba5c0f41d257de2e Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Fri, 3 Nov 2023 17:05:53 -0500 Subject: [PATCH 03/38] copy settings ui to sidebar panel --- R/mod_app.R | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/R/mod_app.R b/R/mod_app.R index 41879de2..20b252d2 100644 --- a/R/mod_app.R +++ b/R/mod_app.R @@ -14,14 +14,24 @@ mod_app_ui <- function(id, ide_colors = get_ide_theme_info()) { bslib::page_fluid( theme = create_chat_app_theme(ide_colors), title = "ChatGPT from gptstudio", - class = "vh-100 p-3 m-0", + class = "vh-100 p-0 m-0", html_dependencies(), - div( - class = "row justify-content-center h-100", + + bslib::layout_sidebar( + class = "vh-100", + sidebar = bslib::sidebar( + open = "closed", + width = 300, + + mod_settings_ui(id = ns(id), translator = translator) + ), div( - class = "col h-100", - style = htmltools::css(`max-width` = "800px"), - mod_chat_ui(ns("chat"), translator) + class = "row justify-content-center h-100", + div( + class = "col h-100", + style = htmltools::css(`max-width` = "800px"), + mod_chat_ui(ns("chat"), translator) + ) ) ) ) @@ -69,7 +79,7 @@ create_chat_app_theme <- function(ide_colors = get_ide_theme_info()) { version = 5, bg = ide_colors$bg, fg = ide_colors$fg, - font_scale = 0.9 + font_scale = 0.9, ) } From 77309c4d29608b4442f2ec4c092151897b5a2798 Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Fri, 3 Nov 2023 17:06:27 -0500 Subject: [PATCH 04/38] separate settings in accordion --- R/mod_settings.R | 61 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/R/mod_settings.R b/R/mod_settings.R index f878b407..94d0e141 100644 --- a/R/mod_settings.R +++ b/R/mod_settings.R @@ -5,8 +5,14 @@ mod_settings_ui <- function(id, translator = create_translator()) { stringr::str_remove(pattern = "gptstudio_request_perform.gptstudio_request_") %>% purrr::discard(~ .x == "gptstudio_request_perform.default") - tagList( - fluidRow( + preferences <- bslib::accordion( + open = FALSE, + multiple = FALSE, + + bslib::accordion_panel( + title = "Assistant behavior", + icon = fontawesome::fa("robot"), + selectInput( inputId = ns("task"), label = translator$t("Task"), @@ -14,13 +20,6 @@ mod_settings_ui <- function(id, translator = create_translator()) { width = "200px", selected = getOption("gptstudio.task") ), - selectInput( - inputId = ns("language"), - label = translator$t("Language"), - choices = c("en", "es", "de"), - width = "200px", - selected = getOption("gptstudio.language") - ), selectInput( inputId = ns("style"), label = translator$t("Programming Style"), @@ -35,6 +34,16 @@ mod_settings_ui <- function(id, translator = create_translator()) { selected = getOption("gptstudio.skill"), width = "200px" ), + textAreaInput( + inputId = ns("custom_prompt"), + label = translator$t("Custom Prompt"), + value = getOption("gptstudio.custom_prompt")) + ), + + bslib::accordion_panel( + title = "API service", + icon = fontawesome::fa("server"), + selectInput( inputId = ns("service"), label = translator$t("Select API Service"), @@ -56,16 +65,32 @@ mod_settings_ui <- function(id, translator = create_translator()) { choiceValues = c(TRUE, FALSE), inline = TRUE, width = "200px", - ), - textAreaInput( - inputId = ns("custom_prompt"), - label = translator$t("Custom Prompt"), - value = getOption("gptstudio.custom_prompt")) + ) ), - column(width = 12, align = "right", - actionButton(ns("save_default"), "Save as Default", - icon = icon("save"), - width = "200px") + + bslib::accordion_panel( + title = "Other settings", + icon = fontawesome::fa("sliders"), + + selectInput( + inputId = ns("language"), + label = translator$t("Language"), + choices = c("en", "es", "de"), + width = "200px", + selected = getOption("gptstudio.language") + ) + ) + ) + + tagList( + tags$h2("Settings"), + + preferences, + + actionButton( + inputId = ns("save_default"), + label = "Save as Default", + icon = icon("save") ) ) } From 4b5a55de2116ddba3dcee3b75eaf95dbae954ad4 Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Fri, 3 Nov 2023 17:51:50 -0500 Subject: [PATCH 05/38] add return value for openai models --- R/openai_api_calls.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/openai_api_calls.R b/R/openai_api_calls.R index 23343dd1..d3bd4021 100644 --- a/R/openai_api_calls.R +++ b/R/openai_api_calls.R @@ -125,6 +125,7 @@ get_available_models <- function(service) { models <- models[stringr::str_detect(models, "gpt-3.5|gpt-4")] idx <- which(models == "gpt-3.5-turbo") models <- c(models[idx], models[-idx]) + return(models) } else if (service == "huggingface") { c("gpt2", "tiiuae/falcon-7b-instruct", "bigcode/starcoderplus") } else if (service == "anthropic") { From b286c8f06246c3a589b5e979c813500efd039c7a Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Fri, 3 Nov 2023 18:15:19 -0500 Subject: [PATCH 06/38] pass settings to chat --- R/mod_app.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/mod_app.R b/R/mod_app.R index 20b252d2..8e746209 100644 --- a/R/mod_app.R +++ b/R/mod_app.R @@ -45,7 +45,8 @@ mod_app_ui <- function(id, ide_colors = get_ide_theme_info()) { #' mod_app_server <- function(id, ide_colors = get_ide_theme_info()) { moduleServer(id, function(input, output, session) { - mod_chat_server("chat", ide_colors) + settings <- mod_settings_server("settings") + mod_chat_server("chat", ide_colors, translator = NULL, settings = settings) }) } From 4cd3b794c35328970312e86b975e739963c9284c Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Fri, 3 Nov 2023 18:16:02 -0500 Subject: [PATCH 07/38] stilize sidebar and add space for history --- R/mod_app.R | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/R/mod_app.R b/R/mod_app.R index 8e746209..9f6b0fe9 100644 --- a/R/mod_app.R +++ b/R/mod_app.R @@ -22,8 +22,24 @@ mod_app_ui <- function(id, ide_colors = get_ide_theme_info()) { sidebar = bslib::sidebar( open = "closed", width = 300, + class = "p-0", + padding = "0.5rem", - mod_settings_ui(id = ns(id), translator = translator) + bslib::navset_pill( + selected = "settings", + + bslib::nav_panel( + title = "History", + value = "history", + + ), + bslib::nav_panel( + title = "Settings", + value = "settings", + class = "px-0 py-2", + mod_settings_ui(id = ns(id), translator = translator) + ) + ) ), div( class = "row justify-content-center h-100", From 5065a09019ebdc1284b9f880de2569c003d196ec Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Fri, 3 Nov 2023 18:16:45 -0500 Subject: [PATCH 08/38] use settings from outside mod_chat --- R/mod_chat.R | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/R/mod_chat.R b/R/mod_chat.R index a1bccada..21789a1a 100644 --- a/R/mod_chat.R +++ b/R/mod_chat.R @@ -68,7 +68,8 @@ mod_chat_ui <- function(id, translator = create_translator()) { #' mod_chat_server <- function(id, ide_colors = get_ide_theme_info(), - translator = create_translator()) { + translator = create_translator(), + settings) { # This is where changes will focus moduleServer(id, function(input, output, session) { @@ -81,8 +82,6 @@ mod_chat_server <- function(id, rv$reset_welcome_message <- 0L rv$reset_streaming_message <- 0L - settings <- mod_settings_server("settings") - # UI outputs ---- output$welcome <- renderWelcomeMessage({ @@ -149,16 +148,7 @@ mod_chat_server <- function(id, observe({ - showModal( - modalDialog( - title = "Settings", - easyClose = TRUE, - footer = modalButton("Save"), - size = "l", - - mod_settings_ui(ns("settings"), translator = translator) - - )) + showNotification("Settings are now on the sidebar panel!") }) %>% bindEvent(input$settings) }) From fff79b383858c3f121501574f2e5d98fc8e80f1c Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Fri, 3 Nov 2023 18:37:45 -0500 Subject: [PATCH 09/38] fix fetching of models --- R/mod_settings.R | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/R/mod_settings.R b/R/mod_settings.R index 94d0e141..b1ec4440 100644 --- a/R/mod_settings.R +++ b/R/mod_settings.R @@ -68,8 +68,10 @@ mod_settings_ui <- function(id, translator = create_translator()) { ) ), + + bslib::accordion_panel( - title = "Other settings", + title = "UI options", icon = fontawesome::fa("sliders"), selectInput( @@ -83,14 +85,13 @@ mod_settings_ui <- function(id, translator = create_translator()) { ) tagList( - tags$h2("Settings"), - preferences, actionButton( inputId = ns("save_default"), label = "Save as Default", - icon = icon("save") + icon = icon("save"), + class = "mt-3" ) ) } @@ -103,13 +104,18 @@ mod_settings_server <- function(id) { purrr::discard(~ .x == "gptstudio_request_perform.default") observe({ + msg <- glue::glue("Fetching models for {input$service} service...") + showNotification(ui = msg, type = "message", session = session) + models <- get_available_models(input$service) + showNotification(ui = "Got models!", type = "message", session = session) + updateSelectInput( session = session, inputId = "model", choices = models, - selected = getOption("gptstudio.model") + selected = models[1] ) }) %>% bindEvent(input$service) From bf0e28dab514e3d674e563281aa468e93be41dd1 Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Fri, 3 Nov 2023 18:38:12 -0500 Subject: [PATCH 10/38] fix module communication --- R/mod_app.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/mod_app.R b/R/mod_app.R index 9f6b0fe9..236dfec2 100644 --- a/R/mod_app.R +++ b/R/mod_app.R @@ -37,7 +37,7 @@ mod_app_ui <- function(id, ide_colors = get_ide_theme_info()) { title = "Settings", value = "settings", class = "px-0 py-2", - mod_settings_ui(id = ns(id), translator = translator) + mod_settings_ui(id = ns("settings"), translator = translator) ) ) ), From 5b565eece217656ed95e6f0f4881de9609462ff9 Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Fri, 3 Nov 2023 18:45:20 -0500 Subject: [PATCH 11/38] provide feedback when no models available --- R/mod_settings.R | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/R/mod_settings.R b/R/mod_settings.R index b1ec4440..4065e95a 100644 --- a/R/mod_settings.R +++ b/R/mod_settings.R @@ -109,14 +109,26 @@ mod_settings_server <- function(id) { models <- get_available_models(input$service) - showNotification(ui = "Got models!", type = "message", session = session) - - updateSelectInput( - session = session, - inputId = "model", - choices = models, - selected = models[1] - ) + if (length(models) > 0) { + showNotification(ui = "Got models!", type = "message", session = session) + + updateSelectInput( + session = session, + inputId = "model", + choices = models, + selected = models[1] + ) + + } else { + showNotification(ui = "No models available", type = "error", session = session) + + updateSelectInput( + session = session, + inputId = "model", + choices = character(), + selected = NULL + ) + } }) %>% bindEvent(input$service) From 06191860ee5ed499f830faafcc43d329a9fdf4a9 Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Fri, 3 Nov 2023 18:51:25 -0500 Subject: [PATCH 12/38] notify when defaults are updated --- R/mod_settings.R | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/R/mod_settings.R b/R/mod_settings.R index 4065e95a..426c99dc 100644 --- a/R/mod_settings.R +++ b/R/mod_settings.R @@ -105,12 +105,12 @@ mod_settings_server <- function(id) { observe({ msg <- glue::glue("Fetching models for {input$service} service...") - showNotification(ui = msg, type = "message", session = session) + showNotification(ui = msg, type = "message",duration = 3, session = session) models <- get_available_models(input$service) if (length(models) > 0) { - showNotification(ui = "Got models!", type = "message", session = session) + showNotification(ui = "Got models!", duration = 3, type = "message", session = session) updateSelectInput( session = session, @@ -120,7 +120,7 @@ mod_settings_server <- function(id) { ) } else { - showNotification(ui = "No models available", type = "error", session = session) + showNotification(ui = "No models available", duration = 3, type = "error", session = session) updateSelectInput( session = session, @@ -143,6 +143,8 @@ mod_settings_server <- function(id) { custom_prompt = input$custom_prompt, stream = input$stream ) + + showNotification("Defaults updated", duration = 3, type = "message", session = session) }) %>% bindEvent(input$save_default) ## Module output ---- From feb6414cbf8958001e3bcf887ed0683326b12640 Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Fri, 3 Nov 2023 19:13:54 -0500 Subject: [PATCH 13/38] draft functions to read/write chat history --- R/app_history.R | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 R/app_history.R diff --git a/R/app_history.R b/R/app_history.R new file mode 100644 index 00000000..da2e5c23 --- /dev/null +++ b/R/app_history.R @@ -0,0 +1,15 @@ +write_chat_history <- function(chat_history) { + dir_path <- tools::R_user_dir("gptstudio", which = "history") + if (!dir.exists(dir_path)) dir.create(dir_path) + + file_path <- file.path(dir_path, "history.json") + jsonlite::write_json(x = chat_history, path = file_path) +} + +read_chat_history <- function() { + dir_path <- tools::R_user_dir("gptstudio", which = "history") + file_path <- file.path(dir_path, "history.json") + + if(!file.exists(file_path)) return(list()) + jsonlite::read_json(file_path) +} From af7d8db750f29d450b284d10aea1ee66f8acf989 Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Tue, 7 Nov 2023 08:53:21 -0500 Subject: [PATCH 14/38] draft mod_history --- R/mod_app.R | 3 +++ R/{app_history.R => mod_history.R} | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+) rename R/{app_history.R => mod_history.R} (62%) diff --git a/R/mod_app.R b/R/mod_app.R index 236dfec2..90e48e2b 100644 --- a/R/mod_app.R +++ b/R/mod_app.R @@ -31,6 +31,8 @@ mod_app_ui <- function(id, ide_colors = get_ide_theme_info()) { bslib::nav_panel( title = "History", value = "history", + class = "px-0 py-2", + mod_history_ui(id = ns("history")) ), bslib::nav_panel( @@ -62,6 +64,7 @@ mod_app_ui <- function(id, ide_colors = get_ide_theme_info()) { mod_app_server <- function(id, ide_colors = get_ide_theme_info()) { moduleServer(id, function(input, output, session) { settings <- mod_settings_server("settings") + mod_history_server("history") mod_chat_server("chat", ide_colors, translator = NULL, settings = settings) }) } diff --git a/R/app_history.R b/R/mod_history.R similarity index 62% rename from R/app_history.R rename to R/mod_history.R index da2e5c23..3daf0284 100644 --- a/R/app_history.R +++ b/R/mod_history.R @@ -1,3 +1,25 @@ +mod_history_ui <- function(id) { + ns <- NS(id) + tagList( + tags$div( + style = htmltools::css("background-color" = "#C8C8C8", width = "100px", height = "100px") + ) + ) +} + +mod_history_server <- function(id) { + moduleServer( + id, + function(input, output, session) { + + } + ) +} + + + + + write_chat_history <- function(chat_history) { dir_path <- tools::R_user_dir("gptstudio", which = "history") if (!dir.exists(dir_path)) dir.create(dir_path) From e83461f9fb48e7eef87a9a6ee54bd7674ba4a75f Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Tue, 7 Nov 2023 10:32:12 -0500 Subject: [PATCH 15/38] fix retrieving of available openai chat models --- R/openai_api_calls.R | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/R/openai_api_calls.R b/R/openai_api_calls.R index d3bd4021..01eef5f3 100644 --- a/R/openai_api_calls.R +++ b/R/openai_api_calls.R @@ -121,8 +121,14 @@ get_available_models <- function(service) { httr2::req_perform() %>% httr2::resp_body_json() %>% purrr::pluck("data") %>% - purrr::map_chr("root") - models <- models[stringr::str_detect(models, "gpt-3.5|gpt-4")] + purrr::map_chr("id") + + models <- models %>% + stringr::str_subset("^gpt") %>% + stringr::str_subset("instruct", negate = TRUE) %>% + stringr::str_subset("vision", negate = TRUE) %>% + sort() + idx <- which(models == "gpt-3.5-turbo") models <- c(models[idx], models[-idx]) return(models) From 59e65b9f715861d2586b26adaddf59f19af2e8cb Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Tue, 7 Nov 2023 11:10:52 -0500 Subject: [PATCH 16/38] first draft of chat element for history --- R/mod_app.R | 2 +- R/mod_history.R | 30 +++++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/R/mod_app.R b/R/mod_app.R index 90e48e2b..f5ed5b59 100644 --- a/R/mod_app.R +++ b/R/mod_app.R @@ -26,7 +26,7 @@ mod_app_ui <- function(id, ide_colors = get_ide_theme_info()) { padding = "0.5rem", bslib::navset_pill( - selected = "settings", + selected = "history", bslib::nav_panel( title = "History", diff --git a/R/mod_history.R b/R/mod_history.R index 3daf0284..693ebfab 100644 --- a/R/mod_history.R +++ b/R/mod_history.R @@ -1,9 +1,9 @@ mod_history_ui <- function(id) { ns <- NS(id) tagList( - tags$div( - style = htmltools::css("background-color" = "#C8C8C8", width = "100px", height = "100px") - ) + actionButton(ns("new"), "New chat", icon = shiny::icon("plus")), + actionButton(ns("delete_all"), "Delete All", icon = shiny::icon("trash")), + 1:6 |> lapply(chat_element) ) } @@ -35,3 +35,27 @@ read_chat_history <- function() { if(!file.exists(file_path)) return(list()) jsonlite::read_json(file_path) } + +chat_element <- function(id = ids::random_id(), label = "This is the title. Sometimes the title can be very very long") { + chat_title <- tags$div( + class = "flex-grow-1 text-truncate", + fontawesome::fa("message"), + label + ) %>% + bslib::tooltip(label, placement = "right") + + edit_btn <- fontawesome::fa("pen-to-square", margin_left = "0.4em") %>% + bslib::tooltip("Edit title", placement = "left") + + delete_btn <- fontawesome::fa("trash-can", margin_left = "0.4em") %>% + bslib::tooltip("Delete this chat", placement = "right") + + tags$div( + id = id, + class = "p-2 d-flex align-items-center", + + chat_title, + edit_btn, + delete_btn + ) +} From eaeb6c08f85e1514eeafd72b7b0e21444d306e8c Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Tue, 7 Nov 2023 15:28:53 -0500 Subject: [PATCH 17/38] create mod_sidebar for navigation of settings and history --- R/mod_app.R | 25 ++++--------------- R/mod_history.R | 46 +++++++++++++++++++++++++++++------ R/mod_settings.R | 62 +++++++++++++++++++++++++++++++++--------------- R/mod_sidebar.R | 46 +++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 47 deletions(-) create mode 100644 R/mod_sidebar.R diff --git a/R/mod_app.R b/R/mod_app.R index f5ed5b59..2e13374a 100644 --- a/R/mod_app.R +++ b/R/mod_app.R @@ -20,28 +20,12 @@ mod_app_ui <- function(id, ide_colors = get_ide_theme_info()) { bslib::layout_sidebar( class = "vh-100", sidebar = bslib::sidebar( - open = "closed", + open = "open", width = 300, class = "p-0", padding = "0.5rem", - bslib::navset_pill( - selected = "history", - - bslib::nav_panel( - title = "History", - value = "history", - class = "px-0 py-2", - mod_history_ui(id = ns("history")) - - ), - bslib::nav_panel( - title = "Settings", - value = "settings", - class = "px-0 py-2", - mod_settings_ui(id = ns("settings"), translator = translator) - ) - ) + mod_sidebar_ui(ns("sidebar"), translator) ), div( class = "row justify-content-center h-100", @@ -63,9 +47,8 @@ mod_app_ui <- function(id, ide_colors = get_ide_theme_info()) { #' mod_app_server <- function(id, ide_colors = get_ide_theme_info()) { moduleServer(id, function(input, output, session) { - settings <- mod_settings_server("settings") - mod_history_server("history") - mod_chat_server("chat", ide_colors, translator = NULL, settings = settings) + sidebar <- mod_sidebar_server("sidebar") + mod_chat_server("chat", ide_colors, translator = NULL, settings = sidebar$settings) }) } diff --git a/R/mod_history.R b/R/mod_history.R index 693ebfab..9bd48b1e 100644 --- a/R/mod_history.R +++ b/R/mod_history.R @@ -1,17 +1,49 @@ mod_history_ui <- function(id) { ns <- NS(id) + + btn_new_chat <- actionButton( + inputId = ns("new"), + label = "New chat", + icon = shiny::icon("plus"), + class = "flex-grow-1 me-2" + ) + + btn_delete_all <- actionButton( + inputId = ns("delete_all"), + label = fontawesome::fa("trash"), + class = "me-2" + ) %>% + bslib::tooltip("Delete all chats") + + btn_settings <- actionButton( + inputId = ns("settings"), + label = fontawesome::fa("gear") + ) %>% + bslib::tooltip("Settings") + tagList( - actionButton(ns("new"), "New chat", icon = shiny::icon("plus")), - actionButton(ns("delete_all"), "Delete All", icon = shiny::icon("trash")), - 1:6 |> lapply(chat_element) + tags$div( + class = "d-flex mb-1", + btn_new_chat, + btn_delete_all, + btn_settings, + ), + 1:40 |> lapply(chat_element) ) } mod_history_server <- function(id) { - moduleServer( - id, - function(input, output, session) { + moduleServer(id, function(input, output, session) { + rv <- reactiveValues() + rv$selected_settings <- 0L + + observe({ + rv$selected_settings <- rv$selected_settings + 1L + }) %>% + bindEvent(input$settings) + # return value + rv } ) } @@ -52,7 +84,7 @@ chat_element <- function(id = ids::random_id(), label = "This is the title. Some tags$div( id = id, - class = "p-2 d-flex align-items-center", + class = "px-2 py-1 mt-2 d-flex align-items-center", chat_title, edit_btn, diff --git a/R/mod_settings.R b/R/mod_settings.R index 426c99dc..1d1a95b7 100644 --- a/R/mod_settings.R +++ b/R/mod_settings.R @@ -84,21 +84,43 @@ mod_settings_ui <- function(id, translator = create_translator()) { ) ) + btn_to_history <- actionButton( + inputId = ns("to_history"), + label = fontawesome::fa("arrow-left-long"), + class = "mb-3" + ) %>% + bslib::tooltip("Back to history") + + btn_save_as_default <- actionButton( + inputId = ns("save_default"), + label = fontawesome::fa("floppy-disk"), + class = "mb-3" + ) %>% + bslib::tooltip("Save as default") + + btn_save_in_session <- actionButton( + inputId = ns("save_session"), + label = fontawesome::fa("bookmark"), + class = "mb-3" + ) %>% + bslib::tooltip("Save for this session") + tagList( - preferences, + btn_to_history, + btn_save_in_session, + btn_save_as_default, + + preferences - actionButton( - inputId = ns("save_default"), - label = "Save as Default", - icon = icon("save"), - class = "mt-3" - ) ) } mod_settings_server <- function(id) { moduleServer(id, function(input, output, session) { + rv <- reactiveValues() + rv$selected_history <- 0L + api_services <- utils::methods("gptstudio_request_perform") %>% stringr::str_remove(pattern = "gptstudio_request_perform.gptstudio_request_") %>% purrr::discard(~ .x == "gptstudio_request_perform.default") @@ -147,24 +169,26 @@ mod_settings_server <- function(id) { showNotification("Defaults updated", duration = 3, type = "message", session = session) }) %>% bindEvent(input$save_default) - ## Module output ---- + observe({ + rv$selected_history <- rv$selected_history + 1L + }) %>% + bindEvent(input$to_history) - module_output <- reactiveValues() observe({ - module_output$task <- input$task %||% getOption("gptstudio.task") - module_output$skill <- input$skill %||% getOption("gptstudio.skill") - module_output$style <- input$style %||% getOption("gptstudio.code_style") - module_output$model <- input$model %||% getOption("gptstudio.model") - module_output$service <- input$service %||% getOption("gptstudio.service") - module_output$stream <- as.logical(input$stream %||% getOption("gptstudio.stream")) - module_output$custom_prompt <- input$custom_prompt %||% getOption("gptstudio.custom_prompt") + rv$task <- input$task %||% getOption("gptstudio.task") + rv$skill <- input$skill %||% getOption("gptstudio.skill") + rv$style <- input$style %||% getOption("gptstudio.code_style") + rv$model <- input$model %||% getOption("gptstudio.model") + rv$service <- input$service %||% getOption("gptstudio.service") + rv$stream <- as.logical(input$stream %||% getOption("gptstudio.stream")) + rv$custom_prompt <- input$custom_prompt %||% getOption("gptstudio.custom_prompt") }) %>% - bindEvent(input$task, input$skill, input$style, input$model, - input$service, input$stream, input$custom_prompt, ignoreNULL = FALSE) + bindEvent(input$save_session, ignoreNULL = FALSE) - module_output + ## Module output ---- + rv }) } diff --git a/R/mod_sidebar.R b/R/mod_sidebar.R new file mode 100644 index 00000000..ba983b0e --- /dev/null +++ b/R/mod_sidebar.R @@ -0,0 +1,46 @@ +mod_sidebar_ui <- function(id, translator = create_translator()) { + ns <- NS(id) + tagList( + bslib::navset_hidden( + id = ns("panel"), + selected = "history", + + bslib::nav_panel_hidden( + value = "history", + class = "px-0 py-2", + mod_history_ui(id = ns("history")) + + ), + bslib::nav_panel_hidden( + value = "settings", + class = "px-0 py-2", + mod_settings_ui(id = ns("settings"), translator = translator) + ) + ) + ) +} + +mod_sidebar_server <- function(id) { + moduleServer( + id, + function(input, output, session) { + settings <- mod_settings_server("settings") + history <- mod_history_server("history") + + observe({ + bslib::nav_select("panel", selected = "settings", session = session) + }) %>% + bindEvent(history$selected_settings, ignoreInit = TRUE) + + observe({ + bslib::nav_select("panel", selected = "history", session = session) + }) %>% + bindEvent(settings$selected_history, ignoreInit = TRUE) + + list( + settings = settings, + history = history + ) + } + ) +} From e0ed0859037751767ffbcf9e27bf0ee737c8e325 Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Tue, 7 Nov 2023 16:15:21 -0500 Subject: [PATCH 18/38] generate new chat by pressing "New chat" or by saving new settings --- R/mod_app.R | 8 +++++++- R/mod_chat.R | 21 ++++----------------- R/mod_history.R | 8 +++++++- R/mod_settings.R | 13 ++++++++++++- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/R/mod_app.R b/R/mod_app.R index 2e13374a..00852629 100644 --- a/R/mod_app.R +++ b/R/mod_app.R @@ -48,7 +48,13 @@ mod_app_ui <- function(id, ide_colors = get_ide_theme_info()) { mod_app_server <- function(id, ide_colors = get_ide_theme_info()) { moduleServer(id, function(input, output, session) { sidebar <- mod_sidebar_server("sidebar") - mod_chat_server("chat", ide_colors, translator = NULL, settings = sidebar$settings) + mod_chat_server( + id = "chat", + ide_colors = ide_colors, + translator = NULL, + settings = sidebar$settings, + history = sidebar$history + ) }) } diff --git a/R/mod_chat.R b/R/mod_chat.R index 21789a1a..2415ec42 100644 --- a/R/mod_chat.R +++ b/R/mod_chat.R @@ -42,16 +42,7 @@ mod_chat_ui <- function(id, translator = create_translator()) { inputId = ns("chat"), label = icon("fas fa-paper-plane"), class = "w-100 btn-primary p-1 chat-send-btn" - ), - actionButton( - inputId = ns("clear_history"), - label = icon("eraser"), - class = "w-100 btn-primary mt-2 p-1" - ), - actionButton( - inputId = ns("settings"), - label = icon("gear"), - class = "w-100 btn-primary mt-2 p-1") + ) ) ) ) @@ -69,7 +60,8 @@ mod_chat_ui <- function(id, translator = create_translator()) { mod_chat_server <- function(id, ide_colors = get_ide_theme_info(), translator = create_translator(), - settings) { + settings, + history) { # This is where changes will focus moduleServer(id, function(input, output, session) { @@ -110,7 +102,7 @@ mod_chat_server <- function(id, rv$chat_history <- list() rv$reset_welcome_message <- rv$reset_welcome_message + 1L }) %>% - bindEvent(input$clear_history) + bindEvent(history$create_new_chat, settings$create_new_chat) observe({ @@ -146,10 +138,5 @@ mod_chat_server <- function(id, }) %>% bindEvent(input$chat) - - observe({ - showNotification("Settings are now on the sidebar panel!") - }) %>% bindEvent(input$settings) - }) } diff --git a/R/mod_history.R b/R/mod_history.R index 9bd48b1e..734c75b4 100644 --- a/R/mod_history.R +++ b/R/mod_history.R @@ -2,7 +2,7 @@ mod_history_ui <- function(id) { ns <- NS(id) btn_new_chat <- actionButton( - inputId = ns("new"), + inputId = ns("new_chat"), label = "New chat", icon = shiny::icon("plus"), class = "flex-grow-1 me-2" @@ -36,12 +36,18 @@ mod_history_server <- function(id) { moduleServer(id, function(input, output, session) { rv <- reactiveValues() rv$selected_settings <- 0L + rv$create_new_chat <- 0L observe({ rv$selected_settings <- rv$selected_settings + 1L }) %>% bindEvent(input$settings) + observe({ + rv$create_new_chat <- rv$create_new_chat + 1L + }) %>% + bindEvent(input$new_chat) + # return value rv } diff --git a/R/mod_settings.R b/R/mod_settings.R index 1d1a95b7..2f2ee188 100644 --- a/R/mod_settings.R +++ b/R/mod_settings.R @@ -120,6 +120,8 @@ mod_settings_server <- function(id) { rv <- reactiveValues() rv$selected_history <- 0L + rv$modify_session_settings <- 0L + rv$create_new_chat <- 0L api_services <- utils::methods("gptstudio_request_perform") %>% stringr::str_remove(pattern = "gptstudio_request_perform.gptstudio_request_") %>% @@ -166,6 +168,8 @@ mod_settings_server <- function(id) { stream = input$stream ) + rv$modify_session_settings <- rv$modify_session_settings + 1L + showNotification("Defaults updated", duration = 3, type = "message", session = session) }) %>% bindEvent(input$save_default) @@ -174,6 +178,11 @@ mod_settings_server <- function(id) { }) %>% bindEvent(input$to_history) + observe({ + rv$modify_session_settings <- rv$modify_session_settings + 1L + }) %>% + bindEvent(input$save_session, ignoreNULL = FALSE) + observe({ rv$task <- input$task %||% getOption("gptstudio.task") @@ -183,8 +192,10 @@ mod_settings_server <- function(id) { rv$service <- input$service %||% getOption("gptstudio.service") rv$stream <- as.logical(input$stream %||% getOption("gptstudio.stream")) rv$custom_prompt <- input$custom_prompt %||% getOption("gptstudio.custom_prompt") + + rv$create_new_chat <- rv$create_new_chat + 1L }) %>% - bindEvent(input$save_session, ignoreNULL = FALSE) + bindEvent(rv$modify_session_settings) ## Module output ---- From 960eceacbaeb4974da8e863cacc9ffb5bd30988f Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Tue, 7 Nov 2023 16:34:13 -0500 Subject: [PATCH 19/38] ask confirmation before updating settings --- R/mod_settings.R | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/R/mod_settings.R b/R/mod_settings.R index 2f2ee188..be4ef587 100644 --- a/R/mod_settings.R +++ b/R/mod_settings.R @@ -118,6 +118,8 @@ mod_settings_ui <- function(id, translator = create_translator()) { mod_settings_server <- function(id) { moduleServer(id, function(input, output, session) { + ns <- session$ns + rv <- reactiveValues() rv$selected_history <- 0L rv$modify_session_settings <- 0L @@ -156,6 +158,27 @@ mod_settings_server <- function(id) { }) %>% bindEvent(input$service) + + observe({ + rv$selected_history <- rv$selected_history + 1L + }) %>% + bindEvent(input$to_history) + + + observe({ + showModal(modalDialog( + tags$p("These settings will persist for all your future chat sessions."), + tags$p("After changing the settings a new chat will be created."), + + footer = tagList( + modalButton("Cancel"), + actionButton(ns("confirm_default"), "Ok") + ) + )) + }) %>% + bindEvent(input$save_default) + + observe({ save_user_config( code_style = input$style, @@ -170,18 +193,30 @@ mod_settings_server <- function(id) { rv$modify_session_settings <- rv$modify_session_settings + 1L + removeModal(session) + showNotification("Defaults updated", duration = 3, type = "message", session = session) - }) %>% bindEvent(input$save_default) + }) %>% bindEvent(input$confirm_default) + observe({ - rv$selected_history <- rv$selected_history + 1L + showModal(modalDialog( + + tags$p("After changing the settings a new chat will be created."), + + footer = tagList( + modalButton("Cancel"), + actionButton(ns("confirm_session"), "Ok") + ) + )) }) %>% - bindEvent(input$to_history) + bindEvent(input$save_session) observe({ rv$modify_session_settings <- rv$modify_session_settings + 1L + removeModal(session) }) %>% - bindEvent(input$save_session, ignoreNULL = FALSE) + bindEvent(input$confirm_session, ignoreNULL = FALSE) observe({ From 790e9463dccd72c46c3708bfd6c40bb0d1d9e87c Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Tue, 7 Nov 2023 17:06:00 -0500 Subject: [PATCH 20/38] draft position absolute for send chat btn --- R/mod_chat.R | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/R/mod_chat.R b/R/mod_chat.R index 2415ec42..e3d55b0f 100644 --- a/R/mod_chat.R +++ b/R/mod_chat.R @@ -22,22 +22,25 @@ mod_chat_ui <- function(id, translator = create_translator()) { div( class = "mt-auto", htmltools::div( - class = "d-flex p-3", + # class = "d-flex p-3", + class = "position-relative", div( - class = "flex-grow-1 pe-3", + # class = "flex-grow-1 pe-3", + # class = "position-absolute", text_area_input_wrapper( inputId = ns("chat_input"), label = NULL, width = "100%", placeholder = translator$t("Write your prompt here"), value = "", - resize = "vertical", - rows = 5, + resize = "none", + # rows = 1, textarea_class = "chat-prompt" ) ), div( - style = htmltools::css(width = "50px"), + # style = htmltools::css(width = "50px"), + class = "position-absolute", actionButton( inputId = ns("chat"), label = icon("fas fa-paper-plane"), From df6dce357699ef6183cad7e29180e33bbc57b32b Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Wed, 8 Nov 2023 09:18:58 -0500 Subject: [PATCH 21/38] set position of send chat btn and add margins to text area input --- R/mod_chat.R | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/R/mod_chat.R b/R/mod_chat.R index e3d55b0f..4a959fc1 100644 --- a/R/mod_chat.R +++ b/R/mod_chat.R @@ -21,12 +21,16 @@ mod_chat_ui <- function(id, translator = create_translator()) { ), div( class = "mt-auto", + style = css( + "margin-left" = "40px", + "margin-right" = "40px" + ), htmltools::div( - # class = "d-flex p-3", class = "position-relative", + style = css( + "width" = "100%" + ), div( - # class = "flex-grow-1 pe-3", - # class = "position-absolute", text_area_input_wrapper( inputId = ns("chat_input"), label = NULL, @@ -34,13 +38,11 @@ mod_chat_ui <- function(id, translator = create_translator()) { placeholder = translator$t("Write your prompt here"), value = "", resize = "none", - # rows = 1, textarea_class = "chat-prompt" ) ), div( - # style = htmltools::css(width = "50px"), - class = "position-absolute", + class = "position-absolute top-50 end-0 translate-middle", actionButton( inputId = ns("chat"), label = icon("fas fa-paper-plane"), From 72f08621b545c5b64f7e7fe14c376980ac4f059f Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Wed, 8 Nov 2023 15:54:46 -0500 Subject: [PATCH 22/38] put sidebar toggle button at the top and style it --- inst/assets/css/mod_app.css | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/inst/assets/css/mod_app.css b/inst/assets/css/mod_app.css index 3eea50ec..9d490208 100644 --- a/inst/assets/css/mod_app.css +++ b/inst/assets/css/mod_app.css @@ -19,3 +19,22 @@ pre code { display: block; } + + +/* Change sidebar icon styles */ +.bslib-sidebar-layout>.collapse-toggle { + top: 10px; + bottom: auto !important; + background-color: transparent !important; + border: none !important; + right: -1.6rem !important; +} + +.bslib-sidebar-layout.sidebar-collapsed>.collapse-toggle { + right: -1.6rem !important; +} + +.bslib-sidebar-layout > .collapse-toggle > .collapse-icon { + width: 2rem !important; + height: 2rem !important; +} From 268efce0cb5c30886dc2dead5087baa2929f37a8 Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Wed, 8 Nov 2023 16:05:20 -0500 Subject: [PATCH 23/38] give better position to sidebar toggle btn when the container is smaller --- inst/assets/css/mod_app.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/inst/assets/css/mod_app.css b/inst/assets/css/mod_app.css index 9d490208..76342de1 100644 --- a/inst/assets/css/mod_app.css +++ b/inst/assets/css/mod_app.css @@ -38,3 +38,9 @@ pre code { width: 2rem !important; height: 2rem !important; } + +@media (max-width: 575.98px) { + .bslib-sidebar-layout>.collapse-toggle, .bslib-sidebar-layout.sidebar-collapsed>.collapse-toggle { + right: 1.6rem !important; + } +} From 5808f8b6001e0f1f1a2f793b00c10f70a02eb2fe Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Wed, 8 Nov 2023 16:57:44 -0500 Subject: [PATCH 24/38] start saving and reading chat history file --- R/mod_chat.R | 12 ++++++++++++ R/mod_history.R | 36 ++++++++++++++++++++++++------------ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/R/mod_chat.R b/R/mod_chat.R index 4a959fc1..0285b3c7 100644 --- a/R/mod_chat.R +++ b/R/mod_chat.R @@ -104,6 +104,18 @@ mod_chat_server <- function(id, # Observers ---- observe({ + all_chats <- read_chat_history() + + chat_to_append <- list( + id = ids::random_id(), + title = "Some random title while we figure out how to automate it", + time_written = Sys.time(), + messages = rv$chat_history + ) + + all_chats <- c(all_chats, list(chat_to_append)) + write_chat_history(all_chats) + rv$chat_history <- list() rv$reset_welcome_message <- rv$reset_welcome_message + 1L }) %>% diff --git a/R/mod_history.R b/R/mod_history.R index 734c75b4..40579373 100644 --- a/R/mod_history.R +++ b/R/mod_history.R @@ -1,5 +1,8 @@ mod_history_ui <- function(id) { ns <- NS(id) + chat_history_messages <- read_chat_history() + + print(chat_history_messages) btn_new_chat <- actionButton( inputId = ns("new_chat"), @@ -28,7 +31,8 @@ mod_history_ui <- function(id) { btn_delete_all, btn_settings, ), - 1:40 |> lapply(chat_element) + chat_history_messages %>% + purrr::map(~chat_element(id = .x$id, title = .x$title)) ) } @@ -57,30 +61,38 @@ mod_history_server <- function(id) { +chat_history_path <- function() { + dir <- tools::R_user_dir("gptstudio", which = "data") + file <- file.path(dir, "history.json") + + list(dir = dir, file = file) +} write_chat_history <- function(chat_history) { - dir_path <- tools::R_user_dir("gptstudio", which = "history") - if (!dir.exists(dir_path)) dir.create(dir_path) + history_path <- chat_history_path() + if (!dir.exists(history_path$dir)) dir.create(history_path$dir) - file_path <- file.path(dir_path, "history.json") - jsonlite::write_json(x = chat_history, path = file_path) + chat_history %>% + purrr::keep(~!rlang::is_empty(.x$messages)) %>% + jsonlite::write_json(path = history_path$file, auto_unbox = TRUE) } read_chat_history <- function() { - dir_path <- tools::R_user_dir("gptstudio", which = "history") - file_path <- file.path(dir_path, "history.json") + history_path <- chat_history_path() - if(!file.exists(file_path)) return(list()) - jsonlite::read_json(file_path) + if(!file.exists(history_path$file)) return(list()) + jsonlite::read_json(history_path$file) } -chat_element <- function(id = ids::random_id(), label = "This is the title. Sometimes the title can be very very long") { +chat_element <- function( + id = ids::random_id(), + title = "This is the title. Sometimes the title can be very very long") { chat_title <- tags$div( class = "flex-grow-1 text-truncate", fontawesome::fa("message"), - label + title ) %>% - bslib::tooltip(label, placement = "right") + bslib::tooltip(title, placement = "right") edit_btn <- fontawesome::fa("pen-to-square", margin_left = "0.4em") %>% bslib::tooltip("Edit title", placement = "left") From e10526010eb671bbce47232b0e44407cb4d3fe63 Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Thu, 9 Nov 2023 14:47:19 -0500 Subject: [PATCH 25/38] read saved chat history when clicking its title --- R/mod_app.R | 4 ++-- R/mod_chat.R | 2 +- R/mod_history.R | 33 +++++++++++++++++++++++++-------- R/mod_sidebar.R | 2 +- inst/assets/js/conversation.js | 6 ++++++ 5 files changed, 35 insertions(+), 12 deletions(-) create mode 100644 inst/assets/js/conversation.js diff --git a/R/mod_app.R b/R/mod_app.R index 00852629..b35ef6be 100644 --- a/R/mod_app.R +++ b/R/mod_app.R @@ -128,10 +128,10 @@ get_ide_theme_info <- function() { html_dependencies <- function() { htmltools::htmlDependency( - name = "gptstudio-assets", version = "0.2.0", + name = "gptstudio-assets", version = "0.4.0", package = "gptstudio", src = "assets", - script = c("js/copyToClipboard.js", "js/shiftEnter.js"), + script = c("js/copyToClipboard.js", "js/shiftEnter.js", "js/conversation.js"), stylesheet = c("css/mod_app.css") ) } diff --git a/R/mod_chat.R b/R/mod_chat.R index 0285b3c7..ea680689 100644 --- a/R/mod_chat.R +++ b/R/mod_chat.R @@ -119,7 +119,7 @@ mod_chat_server <- function(id, rv$chat_history <- list() rv$reset_welcome_message <- rv$reset_welcome_message + 1L }) %>% - bindEvent(history$create_new_chat, settings$create_new_chat) + bindEvent(history$create_new_chat) observe({ diff --git a/R/mod_history.R b/R/mod_history.R index 40579373..e22e4b63 100644 --- a/R/mod_history.R +++ b/R/mod_history.R @@ -32,15 +32,16 @@ mod_history_ui <- function(id) { btn_settings, ), chat_history_messages %>% - purrr::map(~chat_element(id = .x$id, title = .x$title)) + purrr::map(~conversation(id = .x$id, title = .x$title, ns = ns)) ) } -mod_history_server <- function(id) { +mod_history_server <- function(id, settings) { moduleServer(id, function(input, output, session) { rv <- reactiveValues() rv$selected_settings <- 0L rv$create_new_chat <- 0L + rv$chat_history <- list() observe({ rv$selected_settings <- rv$selected_settings + 1L @@ -50,7 +51,17 @@ mod_history_server <- function(id) { observe({ rv$create_new_chat <- rv$create_new_chat + 1L }) %>% - bindEvent(input$new_chat) + bindEvent(input$new_chat, settings$create_new_chat) + + observe({ + all_chats <- read_chat_history() + rv$chat_history <- all_chats %>% + purrr::keep(~.x$id == input$conversation_id) %>% + purrr::pluck(1L, "messages") + + str(rv$chat_history) + }) %>% + bindEvent(input$conversation_id) # return value rv @@ -84,11 +95,17 @@ read_chat_history <- function() { jsonlite::read_json(history_path$file) } -chat_element <- function( +ns_safe <- function(id, ns = NULL) if (is.null(ns)) id else ns(id) + +conversation <- function( id = ids::random_id(), - title = "This is the title. Sometimes the title can be very very long") { - chat_title <- tags$div( - class = "flex-grow-1 text-truncate", + title = "This is the title. Sometimes the title can be very very long", + ns = NULL) { + + conversation_title <- tags$div( + class = "multi-click-input flex-grow-1 text-truncate", + `shiny-input-id` = ns_safe("conversation_id", ns), + value = id, fontawesome::fa("message"), title ) %>% @@ -104,7 +121,7 @@ chat_element <- function( id = id, class = "px-2 py-1 mt-2 d-flex align-items-center", - chat_title, + conversation_title, edit_btn, delete_btn ) diff --git a/R/mod_sidebar.R b/R/mod_sidebar.R index ba983b0e..7598167d 100644 --- a/R/mod_sidebar.R +++ b/R/mod_sidebar.R @@ -25,7 +25,7 @@ mod_sidebar_server <- function(id) { id, function(input, output, session) { settings <- mod_settings_server("settings") - history <- mod_history_server("history") + history <- mod_history_server("history", settings) observe({ bslib::nav_select("panel", selected = "settings", session = session) diff --git a/inst/assets/js/conversation.js b/inst/assets/js/conversation.js new file mode 100644 index 00000000..33d3e635 --- /dev/null +++ b/inst/assets/js/conversation.js @@ -0,0 +1,6 @@ +$(document).on('click', ".multi-click-input", function(event) { + let shinyInputId = $(this).attr("shiny-input-id") + let value = $(this).attr("value") + + Shiny.setInputValue(shinyInputId, value, {priority: "event"}); +}) From dc0524cd228deb8a9408d0fd3ddeec9cf23310ee Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Thu, 9 Nov 2023 15:03:30 -0500 Subject: [PATCH 26/38] fix requiring valid confirmation to modify settings --- R/mod_settings.R | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/R/mod_settings.R b/R/mod_settings.R index be4ef587..bbac4d1b 100644 --- a/R/mod_settings.R +++ b/R/mod_settings.R @@ -180,6 +180,8 @@ mod_settings_server <- function(id) { observe({ + if (!isTRUE(input$confirm_default)) return() + save_user_config( code_style = input$style, skill = input$skill, @@ -213,6 +215,8 @@ mod_settings_server <- function(id) { bindEvent(input$save_session) observe({ + if (!isTRUE(input$confirm_session)) return() + rv$modify_session_settings <- rv$modify_session_settings + 1L removeModal(session) }) %>% From 59ed800abfd3f6839cf87d8262099c06542aae47 Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Thu, 9 Nov 2023 15:07:00 -0500 Subject: [PATCH 27/38] write history to disk in mod_history --- R/mod_chat.R | 20 +++----------------- R/mod_history.R | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/R/mod_chat.R b/R/mod_chat.R index ea680689..7ffee320 100644 --- a/R/mod_chat.R +++ b/R/mod_chat.R @@ -75,7 +75,6 @@ mod_chat_server <- function(id, ns <- session$ns rv <- reactiveValues() - rv$chat_history <- list() rv$reset_welcome_message <- 0L rv$reset_streaming_message <- 0L @@ -88,7 +87,7 @@ mod_chat_server <- function(id, output$history <- renderUI({ - rv$chat_history %>% + history$chat_history %>% style_chat_history(ide_colors = ide_colors) }) @@ -104,19 +103,6 @@ mod_chat_server <- function(id, # Observers ---- observe({ - all_chats <- read_chat_history() - - chat_to_append <- list( - id = ids::random_id(), - title = "Some random title while we figure out how to automate it", - time_written = Sys.time(), - messages = rv$chat_history - ) - - all_chats <- c(all_chats, list(chat_to_append)) - write_chat_history(all_chats) - - rv$chat_history <- list() rv$reset_welcome_message <- rv$reset_welcome_message + 1L }) %>% bindEvent(history$create_new_chat) @@ -128,7 +114,7 @@ mod_chat_server <- function(id, service = settings$service, model = settings$model, prompt = input$chat_input, - history = rv$chat_history, + history = history$chat_history, stream = settings$stream ) %>% gptstudio_skeleton_build( @@ -144,7 +130,7 @@ mod_chat_server <- function(id, ) %>% gptstudio_response_process() - rv$chat_history <- response$history + history$chat_history <- response$history if (settings$stream) { rv$reset_streaming_message <- rv$reset_streaming_message + 1L diff --git a/R/mod_history.R b/R/mod_history.R index e22e4b63..e8929726 100644 --- a/R/mod_history.R +++ b/R/mod_history.R @@ -49,6 +49,20 @@ mod_history_server <- function(id, settings) { bindEvent(input$settings) observe({ + all_chats <- read_chat_history() + + chat_to_append <- list( + id = ids::random_id(), + title = "Some random title while we figure out how to automate it", + last_modified = Sys.time(), + messages = rv$chat_history + ) + + all_chats <- c(all_chats, list(chat_to_append)) + write_chat_history(all_chats) + + rv$chat_history <- list() + rv$create_new_chat <- rv$create_new_chat + 1L }) %>% bindEvent(input$new_chat, settings$create_new_chat) @@ -58,8 +72,6 @@ mod_history_server <- function(id, settings) { rv$chat_history <- all_chats %>% purrr::keep(~.x$id == input$conversation_id) %>% purrr::pluck(1L, "messages") - - str(rv$chat_history) }) %>% bindEvent(input$conversation_id) From d0b85145389a9894044267f3ecef255b7b96a4ce Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Thu, 9 Nov 2023 16:09:53 -0500 Subject: [PATCH 28/38] append new chat at the start of conversation history --- R/mod_history.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/mod_history.R b/R/mod_history.R index e8929726..3569c88f 100644 --- a/R/mod_history.R +++ b/R/mod_history.R @@ -58,7 +58,7 @@ mod_history_server <- function(id, settings) { messages = rv$chat_history ) - all_chats <- c(all_chats, list(chat_to_append)) + all_chats <- c(list(chat_to_append), all_chats) write_chat_history(all_chats) rv$chat_history <- list() From efdb9cc5f4f2fe305cd71932e4d66fe5f095461c Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Sun, 12 Nov 2023 10:39:42 -0500 Subject: [PATCH 29/38] create user dir recursively --- R/mod_history.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/mod_history.R b/R/mod_history.R index 3569c88f..b185b26b 100644 --- a/R/mod_history.R +++ b/R/mod_history.R @@ -93,7 +93,7 @@ chat_history_path <- function() { write_chat_history <- function(chat_history) { history_path <- chat_history_path() - if (!dir.exists(history_path$dir)) dir.create(history_path$dir) + if (!dir.exists(history_path$dir)) dir.create(history_path$dir, recursive = TRUE) chat_history %>% purrr::keep(~!rlang::is_empty(.x$messages)) %>% From f53383a00576fb605a46a81e71e3875e49445cfb Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Sun, 12 Nov 2023 11:12:59 -0500 Subject: [PATCH 30/38] rename chat_history to conversation_history to better differentiate between them --- R/mod_history.R | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/R/mod_history.R b/R/mod_history.R index b185b26b..dea15a8a 100644 --- a/R/mod_history.R +++ b/R/mod_history.R @@ -1,8 +1,6 @@ mod_history_ui <- function(id) { ns <- NS(id) - chat_history_messages <- read_chat_history() - - print(chat_history_messages) + conversation_history <- read_conversation_history() btn_new_chat <- actionButton( inputId = ns("new_chat"), @@ -31,7 +29,7 @@ mod_history_ui <- function(id) { btn_delete_all, btn_settings, ), - chat_history_messages %>% + conversation_history %>% purrr::map(~conversation(id = .x$id, title = .x$title, ns = ns)) ) } @@ -49,7 +47,7 @@ mod_history_server <- function(id, settings) { bindEvent(input$settings) observe({ - all_chats <- read_chat_history() + conversation_history <- read_conversation_history() chat_to_append <- list( id = ids::random_id(), @@ -58,8 +56,8 @@ mod_history_server <- function(id, settings) { messages = rv$chat_history ) - all_chats <- c(list(chat_to_append), all_chats) - write_chat_history(all_chats) + conversation_history <- c(list(chat_to_append), conversation_history) + write_conversation_history(conversation_history) rv$chat_history <- list() @@ -68,8 +66,8 @@ mod_history_server <- function(id, settings) { bindEvent(input$new_chat, settings$create_new_chat) observe({ - all_chats <- read_chat_history() - rv$chat_history <- all_chats %>% + conversation_history <- read_conversation_history() + rv$chat_history <- conversation_history %>% purrr::keep(~.x$id == input$conversation_id) %>% purrr::pluck(1L, "messages") }) %>% @@ -84,27 +82,27 @@ mod_history_server <- function(id, settings) { -chat_history_path <- function() { +conversation_history_path <- function() { dir <- tools::R_user_dir("gptstudio", which = "data") - file <- file.path(dir, "history.json") + file <- file.path(dir, "conversation_history.json") list(dir = dir, file = file) } -write_chat_history <- function(chat_history) { - history_path <- chat_history_path() - if (!dir.exists(history_path$dir)) dir.create(history_path$dir, recursive = TRUE) +write_conversation_history <- function(conversation_history) { + path <- conversation_history_path() + if (!dir.exists(path$dir)) dir.create(path$dir, recursive = TRUE) - chat_history %>% + conversation_history %>% purrr::keep(~!rlang::is_empty(.x$messages)) %>% - jsonlite::write_json(path = history_path$file, auto_unbox = TRUE) + jsonlite::write_json(path = path$file, auto_unbox = TRUE) } -read_chat_history <- function() { - history_path <- chat_history_path() +read_conversation_history <- function() { + path <- conversation_history_path() - if(!file.exists(history_path$file)) return(list()) - jsonlite::read_json(history_path$file) + if(!file.exists(path$file)) return(list()) + jsonlite::read_json(path$file) } ns_safe <- function(id, ns = NULL) if (is.null(ns)) id else ns(id) From b87002e8262eb35cb6ae3806304b64fd70765d64 Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Sun, 12 Nov 2023 12:06:21 -0500 Subject: [PATCH 31/38] render conversation history dynamically --- R/mod_history.R | 45 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/R/mod_history.R b/R/mod_history.R index dea15a8a..448bad5a 100644 --- a/R/mod_history.R +++ b/R/mod_history.R @@ -29,39 +29,40 @@ mod_history_ui <- function(id) { btn_delete_all, btn_settings, ), - conversation_history %>% - purrr::map(~conversation(id = .x$id, title = .x$title, ns = ns)) + uiOutput(ns("conversation_history")) ) } mod_history_server <- function(id, settings) { moduleServer(id, function(input, output, session) { + ns <- session$ns + rv <- reactiveValues() rv$selected_settings <- 0L rv$create_new_chat <- 0L + rv$reload_conversation_history <- 0L rv$chat_history <- list() + output$conversation_history <- renderUI({ + read_conversation_history() %>% + purrr::map(~conversation(id = .x$id, title = .x$title, ns = ns)) + }) %>% + bindEvent(rv$reload_conversation_history) + observe({ rv$selected_settings <- rv$selected_settings + 1L }) %>% bindEvent(input$settings) observe({ - conversation_history <- read_conversation_history() - - chat_to_append <- list( - id = ids::random_id(), + append_to_conversation_history( title = "Some random title while we figure out how to automate it", - last_modified = Sys.time(), messages = rv$chat_history ) - conversation_history <- c(list(chat_to_append), conversation_history) - write_conversation_history(conversation_history) - rv$chat_history <- list() - rv$create_new_chat <- rv$create_new_chat + 1L + rv$reload_conversation_history <- rv$reload_conversation_history + 1L }) %>% bindEvent(input$new_chat, settings$create_new_chat) @@ -73,6 +74,14 @@ mod_history_server <- function(id, settings) { }) %>% bindEvent(input$conversation_id) + observe({ + conversation_history_file <- conversation_history_path()$file + file.remove(conversation_history_file) + showNotification("Deleted all conversations", type = "warning", duration = 3, session = session) + rv$reload_conversation_history <- rv$reload_conversation_history + 1L + }) %>% + bindEvent(input$delete_all) + # return value rv } @@ -105,6 +114,20 @@ read_conversation_history <- function() { jsonlite::read_json(path$file) } +append_to_conversation_history <- function(title = "Some title", messages = list()) { + conversation_history <- read_conversation_history() + + chat_to_append <- list( + id = ids::random_id(), + title = title, + last_modified = Sys.time(), + messages = messages + ) + + conversation_history <- c(list(chat_to_append), conversation_history) + write_conversation_history(conversation_history) +} + ns_safe <- function(id, ns = NULL) if (is.null(ns)) id else ns(id) conversation <- function( From 61bfa929e0e96725eb5555072c728cbeb2e4a98e Mon Sep 17 00:00:00 2001 From: Samuel Calderon Date: Sun, 12 Nov 2023 12:36:50 -0500 Subject: [PATCH 32/38] overwrite selected conversation when saving, instead of creating a new one --- R/mod_history.R | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/R/mod_history.R b/R/mod_history.R index 448bad5a..1f9cdac7 100644 --- a/R/mod_history.R +++ b/R/mod_history.R @@ -41,6 +41,7 @@ mod_history_server <- function(id, settings) { rv$selected_settings <- 0L rv$create_new_chat <- 0L rv$reload_conversation_history <- 0L + rv$conversation_id <- ids::random_id() rv$chat_history <- list() output$conversation_history <- renderUI({ @@ -56,11 +57,13 @@ mod_history_server <- function(id, settings) { observe({ append_to_conversation_history( + id = rv$conversation_id, title = "Some random title while we figure out how to automate it", messages = rv$chat_history ) rv$chat_history <- list() + rv$conversation_id <- ids::random_id() rv$reload_conversation_history <- rv$reload_conversation_history + 1L }) %>% @@ -68,9 +71,12 @@ mod_history_server <- function(id, settings) { observe({ conversation_history <- read_conversation_history() - rv$chat_history <- conversation_history %>% + selected_conversation <- conversation_history %>% purrr::keep(~.x$id == input$conversation_id) %>% - purrr::pluck(1L, "messages") + purrr::pluck(1L) + + rv$chat_history <- selected_conversation$messages + rv$conversation_id <- selected_conversation$id }) %>% bindEvent(input$conversation_id) @@ -114,11 +120,15 @@ read_conversation_history <- function() { jsonlite::read_json(path$file) } -append_to_conversation_history <- function(title = "Some title", messages = list()) { - conversation_history <- read_conversation_history() +append_to_conversation_history <- function(id = ids::random_id(), + title = "Some title", + messages = list()) { + + conversation_history <- read_conversation_history() %>% + purrr::discard(~.x$id == id) chat_to_append <- list( - id = ids::random_id(), + id = id, title = title, last_modified = Sys.time(), messages = messages From 549f3731e6b7e43bffef821a3ae3a691ec7691c4 Mon Sep 17 00:00:00 2001 From: calderonsamuel Date: Tue, 14 Nov 2023 20:40:49 -0500 Subject: [PATCH 33/38] make tooltips work only on hover --- R/mod_history.R | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/R/mod_history.R b/R/mod_history.R index 1f9cdac7..c77389ae 100644 --- a/R/mod_history.R +++ b/R/mod_history.R @@ -152,13 +152,17 @@ conversation <- function( fontawesome::fa("message"), title ) %>% - bslib::tooltip(title, placement = "right") + tooltip_on_hover(title, placement = "right") - edit_btn <- fontawesome::fa("pen-to-square", margin_left = "0.4em") %>% - bslib::tooltip("Edit title", placement = "left") + edit_btn <- tags$span( + fontawesome::fa("pen-to-square", margin_left = "0.4em") + ) %>% + tooltip_on_hover("Edit title", placement = "left") - delete_btn <- fontawesome::fa("trash-can", margin_left = "0.4em") %>% - bslib::tooltip("Delete this chat", placement = "right") + delete_btn <- tags$span( + fontawesome::fa("trash-can", margin_left = "0.4em") + ) %>% + tooltip_on_hover("Delete this chat", placement = "right") tags$div( id = id, @@ -169,3 +173,5 @@ conversation <- function( delete_btn ) } + +tooltip_on_hover <- purrr::partial(bslib::tooltip, options = list(trigger = "hover")) From 003399978dad3e8d8fb27507dcc69b4b56f77984 Mon Sep 17 00:00:00 2001 From: calderonsamuel Date: Thu, 16 Nov 2023 00:57:31 -0500 Subject: [PATCH 34/38] avoid translator errors --- R/mod_settings.R | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/R/mod_settings.R b/R/mod_settings.R index bbac4d1b..5d1d3bc1 100644 --- a/R/mod_settings.R +++ b/R/mod_settings.R @@ -29,7 +29,8 @@ mod_settings_ui <- function(id, translator = create_translator()) { ), selectInput( inputId = ns("skill"), - label = translator$t("Programming Skill"), + label = "Programming Skill", # TODO: update translator + # label = translator$t("Programming Skill"), choices = c("beginner", "intermediate", "advanced", "genius"), selected = getOption("gptstudio.skill"), width = "200px" @@ -76,7 +77,8 @@ mod_settings_ui <- function(id, translator = create_translator()) { selectInput( inputId = ns("language"), - label = translator$t("Language"), + # label = translator$t("Language"), # TODO: update translator + label = "Language", choices = c("en", "es", "de"), width = "200px", selected = getOption("gptstudio.language") @@ -180,7 +182,7 @@ mod_settings_server <- function(id) { observe({ - if (!isTRUE(input$confirm_default)) return() + if (!isTruthy(input$confirm_default)) return() save_user_config( code_style = input$style, @@ -215,12 +217,12 @@ mod_settings_server <- function(id) { bindEvent(input$save_session) observe({ - if (!isTRUE(input$confirm_session)) return() + if (!isTruthy(input$confirm_session)) return() rv$modify_session_settings <- rv$modify_session_settings + 1L removeModal(session) }) %>% - bindEvent(input$confirm_session, ignoreNULL = FALSE) + bindEvent(input$confirm_session) observe({ From 20fd90ba6be889705fd62966b31aa2f5858f9a32 Mon Sep 17 00:00:00 2001 From: calderonsamuel Date: Thu, 16 Nov 2023 00:58:40 -0500 Subject: [PATCH 35/38] delete all, delete single chat, and edit title are now possible and they ask for user confirmation --- R/mod_history.R | 125 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 111 insertions(+), 14 deletions(-) diff --git a/R/mod_history.R b/R/mod_history.R index c77389ae..29d707df 100644 --- a/R/mod_history.R +++ b/R/mod_history.R @@ -39,16 +39,17 @@ mod_history_server <- function(id, settings) { rv <- reactiveValues() rv$selected_settings <- 0L - rv$create_new_chat <- 0L rv$reload_conversation_history <- 0L - rv$conversation_id <- ids::random_id() + rv$selected_conversation <- NULL # list rv$chat_history <- list() + conversation_history <- reactive(read_conversation_history()) %>% + bindEvent(rv$reload_conversation_history) + output$conversation_history <- renderUI({ - read_conversation_history() %>% + conversation_history() %>% purrr::map(~conversation(id = .x$id, title = .x$title, ns = ns)) - }) %>% - bindEvent(rv$reload_conversation_history) + }) observe({ rv$selected_settings <- rv$selected_settings + 1L @@ -57,13 +58,13 @@ mod_history_server <- function(id, settings) { observe({ append_to_conversation_history( - id = rv$conversation_id, - title = "Some random title while we figure out how to automate it", + id = rv$selected_conversation$id %||% ids::random_id(), + title = rv$selected_conversation$title %||% "Placeholder title", messages = rv$chat_history ) rv$chat_history <- list() - rv$conversation_id <- ids::random_id() + rv$selected_conversation <- NULL rv$reload_conversation_history <- rv$reload_conversation_history + 1L }) %>% @@ -71,22 +72,111 @@ mod_history_server <- function(id, settings) { observe({ conversation_history <- read_conversation_history() - selected_conversation <- conversation_history %>% + rv$selected_conversation <- conversation_history() %>% purrr::keep(~.x$id == input$conversation_id) %>% purrr::pluck(1L) - rv$chat_history <- selected_conversation$messages - rv$conversation_id <- selected_conversation$id + rv$chat_history <- rv$selected_conversation$messages }) %>% bindEvent(input$conversation_id) + observe({ + showModal(modalDialog( + tags$p("Are you sure?"), + footer = tagList( + modalButton("Cancel"), + actionButton(ns("confirm_delete_all"), "Ok") + ) + )) + }) %>% + bindEvent(input$delete_all) + observe({ conversation_history_file <- conversation_history_path()$file file.remove(conversation_history_file) + removeModal(session) + showNotification("Deleted all conversations", type = "warning", duration = 3, session = session) rv$reload_conversation_history <- rv$reload_conversation_history + 1L }) %>% - bindEvent(input$delete_all) + bindEvent(input$confirm_delete_all) + + observe({ + rv$selected_conversation <- conversation_history() %>% + purrr::keep(~.x$id == input$conversation_to_edit) %>% + purrr::pluck(1L) + + showModal(modalDialog( + textAreaInput( + inputId = ns("new_title"), + label = "New title", + value = rv$selected_conversation$title, + width = "100%" + ), + + footer = tagList( + modalButton("Cancel"), + actionButton(ns("confirm_new_title"), "Ok") + ) + )) + }) %>% + bindEvent(input$conversation_to_edit) + + observe({ + if(!isTruthy(input$confirm_new_title)) return() + + append_to_conversation_history( + id = rv$selected_conversation$id, + title = input$new_title, + messages = rv$selected_conversation$messages + ) + + rv$selected_conversation <- NULL + + rv$reload_conversation_history <- rv$reload_conversation_history + 1L + + removeModal(session) + + }) %>% + bindEvent(input$confirm_new_title) + + observe({ + rv$selected_conversation <- conversation_history() %>% + purrr::keep(~.x$id == input$conversation_to_delete) %>% + purrr::pluck(1L) + + msg <- glue::glue("Confirm deletion of conversation: {rv$selected_conversation$title}") + + showModal(modalDialog( + tags$p(msg), + footer = tagList( + modalButton("Cancel"), + actionButton(ns("confirm_single_delete"), "Ok") + ) + )) + }) %>% + bindEvent(input$conversation_to_delete) + + observe({ + if(!isTruthy(input$confirm_single_delete)) return() + + conversation_history() %>% + purrr::discard(~.x$id == rv$selected_conversation$id) %>% + write_conversation_history() + + + + rv$selected_conversation <- NULL + + rv$reload_conversation_history <- rv$reload_conversation_history + 1L + + removeModal(session) + showNotification("Deleted!", duration = 3, type = "message", session = session) + + }) %>% + bindEvent(input$confirm_single_delete) + + # return value rv @@ -155,12 +245,19 @@ conversation <- function( tooltip_on_hover(title, placement = "right") edit_btn <- tags$span( - fontawesome::fa("pen-to-square", margin_left = "0.4em") + fontawesome::fa("pen-to-square", margin_left = "0.4em"), + class = "multi-click-input", + `shiny-input-id` = ns_safe("conversation_to_edit", ns), + value = id + ) %>% tooltip_on_hover("Edit title", placement = "left") delete_btn <- tags$span( - fontawesome::fa("trash-can", margin_left = "0.4em") + fontawesome::fa("trash-can", margin_left = "0.4em"), + class = "multi-click-input", + `shiny-input-id` = ns_safe("conversation_to_delete", ns), + value = id ) %>% tooltip_on_hover("Delete this chat", placement = "right") From c3b377cd2cda5e8dd0fb1e13e56cc54001136708 Mon Sep 17 00:00:00 2001 From: calderonsamuel Date: Thu, 16 Nov 2023 01:11:26 -0500 Subject: [PATCH 36/38] address check() problems --- DESCRIPTION | 2 ++ R/mod_chat.R | 1 + man/mod_chat_server.Rd | 6 +++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index db00bd88..fcca7d19 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -26,11 +26,13 @@ Imports: cli, colorspace, curl, + fontawesome, glue, grDevices, htmltools, htmlwidgets, httr2, + ids, jsonlite, magrittr, purrr, diff --git a/R/mod_chat.R b/R/mod_chat.R index 7ffee320..afea110a 100644 --- a/R/mod_chat.R +++ b/R/mod_chat.R @@ -60,6 +60,7 @@ mod_chat_ui <- function(id, translator = create_translator()) { #' #' @param id id of the module #' @param translator Translator from `shiny.i18n::Translator` +#' @param settings,history Reactive values from the settings and history module #' @inheritParams run_chatgpt_app #' mod_chat_server <- function(id, diff --git a/man/mod_chat_server.Rd b/man/mod_chat_server.Rd index 1788260e..6ec78ce9 100644 --- a/man/mod_chat_server.Rd +++ b/man/mod_chat_server.Rd @@ -7,7 +7,9 @@ mod_chat_server( id, ide_colors = get_ide_theme_info(), - translator = create_translator() + translator = create_translator(), + settings, + history ) } \arguments{ @@ -16,6 +18,8 @@ mod_chat_server( \item{ide_colors}{List containing the colors of the IDE theme.} \item{translator}{Translator from \code{shiny.i18n::Translator}} + +\item{settings, history}{Reactive values from the settings and history module} } \description{ Chat server From d33cea290ac21485ae64ca52b0de93f829e14d20 Mon Sep 17 00:00:00 2001 From: calderonsamuel Date: Thu, 16 Nov 2023 09:50:11 -0500 Subject: [PATCH 37/38] minimal ui changes --- R/mod_app.R | 2 +- R/mod_chat.R | 3 ++- R/welcomeMessage.R | 15 --------------- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/R/mod_app.R b/R/mod_app.R index b35ef6be..981ed23a 100644 --- a/R/mod_app.R +++ b/R/mod_app.R @@ -20,7 +20,7 @@ mod_app_ui <- function(id, ide_colors = get_ide_theme_info()) { bslib::layout_sidebar( class = "vh-100", sidebar = bslib::sidebar( - open = "open", + open = "closed", width = 300, class = "p-0", padding = "0.5rem", diff --git a/R/mod_chat.R b/R/mod_chat.R index afea110a..63bd1ee4 100644 --- a/R/mod_chat.R +++ b/R/mod_chat.R @@ -47,7 +47,8 @@ mod_chat_ui <- function(id, translator = create_translator()) { inputId = ns("chat"), label = icon("fas fa-paper-plane"), class = "w-100 btn-primary p-1 chat-send-btn" - ) + ) %>% + bslib::tooltip("Send (click or Enter)") ) ) ) diff --git a/R/welcomeMessage.R b/R/welcomeMessage.R index 38734082..96ae34fe 100644 --- a/R/welcomeMessage.R +++ b/R/welcomeMessage.R @@ -97,25 +97,10 @@ chat_message_default <- function(translator = create_translator()) { ) %>% purrr::map_chr(~ translator$t(.x)) - paperplane <- icon("fas fa-paper-plane") %>% as.character() - eraser <- icon("eraser") - gear <- icon("gear") - - explain_btns <- c( - "In this chat you can:\n\n", - "- Send me a prompt ({paperplane} or Enter key)\n", - "- Clear the current chat history ({eraser})\n", - "- Change the settings ({gear})\n" - ) %>% - purrr::map_chr(~ translator$t(.x)) %>% - glue::glue_collapse() %>% - glue::glue() - # nolint end content <- c( "{sample(welcome_messages, 1)}\n\n", - "{explain_btns}\n\n", translator$t("Type anything to start our conversation.") ) %>% glue::glue_collapse() %>% From d686f34f97284286bb2912c4150f2067ad2be0f6 Mon Sep 17 00:00:00 2001 From: calderonsamuel Date: Thu, 16 Nov 2023 10:09:23 -0500 Subject: [PATCH 38/38] update NEWS --- NEWS.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/NEWS.md b/NEWS.md index 552f2732..7525214b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,15 @@ # gptstudio (development version) +## UI updates + +- The chat app has now a sidebar where users can see their conversation history, start new chats and change the settings. Because of this, the chat interface has more room for showing messages. +- All saved chats are created with a placeholder title that the user can edit later. +- We have a shorter welcome message, but we have added lots of tooltips to help with navigation. + +## Internal + - Reverted back to use an R6 class for OpenAI streaming. This doesn't affect how the users interact with the addins. +- Fixed a bug in retrieval of OpenAI models # gptstudio 0.3.1