diff --git a/.lintr b/.lintr index 63fade9..da7cb04 100644 --- a/.lintr +++ b/.lintr @@ -1,5 +1,5 @@ linters: linters_with_defaults( - line_length_linter = line_length_linter(100), - object_usage_linter = NULL # Does not work with `box::use()`. + defaults = box.linters::rhino_default_linters, + line_length_linter = line_length_linter(100) ) diff --git a/app/logic/empty_state_utils.R b/app/logic/empty_state_utils.R index 4419781..5718197 100644 --- a/app/logic/empty_state_utils.R +++ b/app/logic/empty_state_utils.R @@ -6,8 +6,6 @@ box::use( div, img, p, - renderUI, - tagAppendAttributes ], ) diff --git a/app/logic/general_utils.R b/app/logic/general_utils.R index 350eaa5..1f5fa47 100644 --- a/app/logic/general_utils.R +++ b/app/logic/general_utils.R @@ -1,7 +1,4 @@ box::use( - config[ - get - ], purrr[ map_chr ], diff --git a/app/logic/llm_utils.R b/app/logic/llm_utils.R new file mode 100644 index 0000000..482f1bb --- /dev/null +++ b/app/logic/llm_utils.R @@ -0,0 +1,128 @@ +box::use( + config[ + get + ], + ellmer, #nolint: we do use this package just in a separate notation [[ ]] + glue[ + glue + ], +) + +#' Check if LLM is enabled +#' +#' This function checks the LLM configuration and returns TRUE if enabled, FALSE otherwise. +#' @return Logical indicating if LLM is enabled +#' @export +is_llm_enabled <- function() { + get("llm")$enabled %||% FALSE +} + +#' Get valid LLM providers +#' @return A character vector of valid LLM providers +get_valid_providers <- function( +) { + ellmer_functions <- ls(ellmer)[ + grepl( + pattern = "^chat_", + x = ls(ellmer) + ) + ] + sub( + pattern = "^chat_", + replacement = "", + x = ellmer_functions + ) +} + +#' Check if the provider is valid +#' @param provider The LLM provider to check +#' @param valid_providers A character vector of valid LLM providers +#' @return Logical indicating if the provider is valid +verify_provider <- function( + provider, + valid_providers = get_valid_providers() +) { + if (!provider %in% valid_providers) { + stop( + glue( + "Invalid LLM provider '{provider}'. ", + "Valid providers are: {paste(valid_providers, collapse = ', ')}" + ) + ) + } + TRUE +} + +#' Get LLM configuration +#' +#' Returns the LLM configuration if LLM is enabled. Otherwise, throws an error. +#' @return A list containing the LLM configuration +get_llm_config <- function() { + if (!is_llm_enabled()) { + stop("Oops! LLM is not enabled in config.yml!") + } + verify_provider( + get("llm")$provider, + get_valid_providers() + ) + get("llm") +} + +#' Get the LLM function based on the provider +#' +#' Extracts the chat function dynamically from the `ellmer` module. +#' @param llm_config Optional configuration for the LLM +#' @return A function to create a chat object +get_llm_function <- function( + llm_config = get_llm_config() +) { + ellmer[[glue("chat_{llm_config$provider}")]] +} + +#' Create a chat object +#' +#' Uses the configured LLM provider and model to create a chat object. +#' @param llm_config Optional configuration for the LLM +#' @return A chat object +#' @export +create_chat_object <- function( + llm_config = get_llm_config() +) { + fun <- get_llm_function(llm_config) + fun( + api_key = llm_config$api_key, + model = llm_config$model, + system_prompt = llm_config$system_prompt, + seed = 42, + api_args = list( + temperature = 0 + ) + ) +} + +#' Invoke LLM help with the logs data.frame +#' @param logs_data A data frame containing log data +#' @return A response from the LLM +#' @export +get_llm_help <- function( + logs_data +) { + chat <- create_chat_object() + chat$chat( + concatenate_logs( + logs_data + ) + ) +} + +#' Concatenate logs +#' @param processed_logs A data frame containing log data +#' @return A string with concatenated log entries +concatenate_logs <- function( + processed_logs +) { + paste( + processed_logs$entries.data, + collapse = "\n" + ) +} diff --git a/app/logic/logs_utils.R b/app/logic/logs_utils.R index ebbec6e..c5aae50 100644 --- a/app/logic/logs_utils.R +++ b/app/logic/logs_utils.R @@ -18,19 +18,18 @@ process_log_data <- function( log_data ) { log_info <- strsplit(log_data, "_-_")[[1]] - status <- get_status_info(log_info[1], log_info[3]) div( - class = glue("log-entry {status[1]}-highlight"), + class = glue("log-entry {log_info[4]}-highlight"), icon( - name = status[2], + name = log_info[5], class = glue( - "log-status {status[1]}-text fa-solid" + "log-status {log_info[4]}-text fa-solid" ), ), div( class = "log-info-block", div( - class = glue("log-info {status[1]}-text"), + class = glue("log-info {log_info[4]}-text"), log_info[3] ), div( @@ -41,15 +40,18 @@ process_log_data <- function( ) } +#' @export get_status_info <- function( output_type, log_data ) { if (output_type == "stdout") { - c("green", "circle-info") + status_list <- list("green", "circle-info") } else if (output_type == "stderr" && check_text_error(log_data)) { - c("red", "circle-xmark") + status_list <- list("red", "circle-xmark") } else { - c("yellow", "circle-info") + status_list <- list("yellow", "circle-info") } + names(status_list) <- c("entries.status", "entries.icon") + status_list } diff --git a/app/main.R b/app/main.R index 9c3ea8d..62a0121 100644 --- a/app/main.R +++ b/app/main.R @@ -62,15 +62,25 @@ server <- function(id) { mod_header$server("header") - selected_app_ <- mod_app_table$server("app_table", app_list)$selected_app_ - - selected_job_ <- mod_job_list$server("job_list", selected_app_)$selected_job_ - - mod_logs$server("logs", selected_app_, selected_job_) + selected_app_ <- mod_app_table$server( + "app_table", + app_list + )$selected_app_ + + selected_job_ <- mod_job_list$server( + "job_list", + selected_app_ + )$selected_job_ + + mod_logs$server( + "logs", + selected_app_, + selected_job_ + ) output$job_list_pane <- shiny$renderUI({ if (!shiny$isTruthy(selected_app_()$guid)) { - return(NULL) + NULL } mod_job_list$ui(ns("job_list")) @@ -78,22 +88,18 @@ server <- function(id) { output$logs_pane <- shiny$renderUI({ if (!is.data.frame(app_list) || nrow(app_list) == 0) { - return( - generate_empty_state_ui( - text = "Oops! Can't read apps from Posit Connect.", - image_path = "app/static/illustrations/missing_apps.svg", - color = branding$colors$primary - ) + generate_empty_state_ui( + text = "Oops! Can't read apps from Posit Connect.", + image_path = "app/static/illustrations/missing_apps.svg", + color = branding$colors$primary ) } if (!shiny$isTruthy(selected_job_()$key)) { - return( - generate_empty_state_ui( - text = "Select an application and a job to view logs.", - image_path = "app/static/illustrations/empty_state.svg", - color = branding$colors$primary - ) + generate_empty_state_ui( + text = "Select an application and a job to view logs.", + image_path = "app/static/illustrations/empty_state.svg", + color = branding$colors$primary ) } diff --git a/app/static/css/app.min.css b/app/static/css/app.min.css index 79ee1dc..fca6d13 100644 --- a/app/static/css/app.min.css +++ b/app/static/css/app.min.css @@ -1 +1 @@ -@import"https://fonts.googleapis.com/css2?family=Maven+Pro:wght@400;600&display=swap";.red-text{color:var(--red)}.green-text{color:var(--green)}.yellow-text{color:var(--yellow)}.red-highlight{background-color:var(--red-highlight)}.green-highlight{background-color:var(--green-highlight)}.yellow-highlight{background-color:var(--yellow-highlight)}.red-text{color:var(--red)}.green-text{color:var(--green)}.yellow-text{color:var(--yellow)}.red-highlight{background-color:var(--red-highlight)}.green-highlight{background-color:var(--green-highlight)}.yellow-highlight{background-color:var(--yellow-highlight)}.logs .rt-td-inner{padding:0 !important}.logs>div{text-align:center}.logs>div .empty-state-container{margin-top:150px}.logs>div .empty-state-container .empty-state-image{width:50%}.logs>div .empty-state-container .empty-state-text{color:var(--grey-text);margin-bottom:40px}.logs-container{position:relative}.logs-container .log-entry{display:flex;align-items:center;gap:20px;padding:10px;margin:5px 10px}.logs-container .log-entry i{font-size:1.5em}.logs-container .log-entry .log-info-block{display:flex;flex-direction:column;gap:10px}.logs-container .log-entry .log-info-block .log-info{font-weight:600}.logs-container .log-entry .log-info-block .log-time{font-size:.75em}.logs-container .logs-download{position:absolute;z-index:2;right:0;margin:10px;background:0;border-radius:0;padding:5px 10px}.wrapper{background:none !important}.content-wrapper{background:var(--white);color:var(--black-text);height:90vh}.dashboard-body{display:flex;flex-direction:column;padding:0}.dashboard-body .dashboard-container{display:flex;flex-direction:row;height:100vh}.dashboard-body .reactable{background:rgba(0,0,0,0)}.dashboard-body .rt-search{width:80%;margin:10px 10px 20px;align-self:center;text-align:center;border-radius:0}.dashboard-body .rt-tr-header{display:none !important}.dashboard-body .rt-tr{align-items:center}.dashboard-body .rt-tr-selected{background:var(--selected-row)}.dashboard-body .app-table{width:30%;height:100%;overflow-y:auto}.dashboard-body .job-list{width:15%;height:100%;overflow-y:auto}.dashboard-body .logs{background:var(--white);width:55%;height:100%;overflow-y:auto}.app-entry{display:flex;flex-direction:column;width:100%}.app-entry .app-title{font-size:1.1em}.app-entry .app-link-icon{font-size:.5em;margin-left:10px;margin-bottom:10px}.app-entry .app-metadata{display:flex;flex-direction:column;gap:5px;color:var(--grey-text);font-size:.75em}.red-text{color:var(--red)}.green-text{color:var(--green)}.yellow-text{color:var(--yellow)}.red-highlight{background-color:var(--red-highlight)}.green-highlight{background-color:var(--green-highlight)}.yellow-highlight{background-color:var(--yellow-highlight)}.job-entry .job-key,.job-entry .job-start-time,.job-entry .job-end-time{font-size:.75em;color:var(--grey-text)}.header{display:flex;width:100%;align-items:center;justify-content:space-between;gap:10px;margin-bottom:20px}.header .header-section{display:flex;align-items:center;gap:10px}.header .left img{width:200px}.header .left h2{margin:0;margin-bottom:5px;margin-left:20px}.header .left .vertical-line{height:50px}.header .right .cta-button{background:var(--primary);color:#fff;padding:10px;border-radius:10px;margin:0 10px}*{font-family:"Maven Pro",sans-serif}body{overflow:hidden}.vertical-line{border-left:1px var(--grey2-border) solid;height:80%;align-self:center} +@import "https://fonts.googleapis.com/css2?family=Maven+Pro:wght@400;600&display=swap";.red-text{color:var(--red)}.green-text{color:var(--green)}.yellow-text{color:var(--yellow)}.red-highlight{background-color:var(--red-highlight)}.green-highlight{background-color:var(--green-highlight)}.yellow-highlight{background-color:var(--yellow-highlight)}.red-text{color:var(--red)}.green-text{color:var(--green)}.yellow-text{color:var(--yellow)}.red-highlight{background-color:var(--red-highlight)}.green-highlight{background-color:var(--green-highlight)}.yellow-highlight{background-color:var(--yellow-highlight)}.logs .rt-td-inner{padding:0 !important}.logs .rt-search{width:85% !important;align-self:flex-start !important}.logs>div{text-align:center}.logs>div .empty-state-container{margin-top:150px}.logs>div .empty-state-container .empty-state-image{width:50%}.logs>div .empty-state-container .empty-state-text{color:var(--grey-text);margin-bottom:40px}.logs-llm-placeholder img{width:50%}.logs-llm-modal .modal-dialog{text-align:justify}.logs-llm-modal .modal-dialog h2,.logs-llm-modal .modal-dialog h3{color:var(--primary)}.logs-llm-modal .modal-dialog .btn{background:var(--red-highlight);border-radius:0;border:1px solid var(--red)}.logs-llm-modal .modal-dialog .modal-content{border-radius:0}.logs-container{position:relative}.logs-container .log-entry{display:flex;align-items:center;gap:20px;padding:10px;margin:5px 10px}.logs-container .log-entry i{font-size:1.5em}.logs-container .log-entry .log-info-block{display:flex;flex-direction:column;gap:10px}.logs-container .log-entry .log-info-block .log-info{font-weight:600}.logs-container .log-entry .log-info-block .log-time{font-size:0.75em}.logs-container .logs-options{position:absolute;z-index:2;right:0;margin:10px}.logs-container .logs-options .logs-options-button{background:0;border-radius:0;padding:5px 10px}.wrapper{background:none !important}.content-wrapper{background:var(--white);color:var(--black-text);height:90vh}.dashboard-body{display:flex;flex-direction:column;padding:0}.dashboard-body .dashboard-container{display:flex;flex-direction:row;height:100vh}.dashboard-body .reactable{background:transparent}.dashboard-body .rt-search{width:80%;margin:10px 10px 20px;align-self:center;text-align:center;border-radius:0}.dashboard-body .rt-tr-header{display:none !important}.dashboard-body .rt-tr{align-items:center}.dashboard-body .rt-tr-selected{background:var(--selected-row)}.dashboard-body .app-table{width:30%;height:100%;overflow-y:auto}.dashboard-body .job-list{width:15%;height:100%;overflow-y:auto}.dashboard-body .logs{background:var(--white);width:55%;height:100%;overflow-y:auto}.app-entry{display:flex;flex-direction:column;width:100%}.app-entry .app-title{font-size:1.1em}.app-entry .app-link-icon{font-size:0.5em;margin-left:10px;margin-bottom:10px}.app-entry .app-metadata{display:flex;flex-direction:column;gap:5px;color:var(--grey-text);font-size:0.75em}.red-text{color:var(--red)}.green-text{color:var(--green)}.yellow-text{color:var(--yellow)}.red-highlight{background-color:var(--red-highlight)}.green-highlight{background-color:var(--green-highlight)}.yellow-highlight{background-color:var(--yellow-highlight)}.job-entry .job-key,.job-entry .job-start-time,.job-entry .job-end-time{font-size:0.75em;color:var(--grey-text)}.header{display:flex;width:100%;align-items:center;justify-content:space-between;gap:10px;margin-bottom:20px}.header .header-section{display:flex;align-items:center;gap:10px}.header .left img{width:200px}.header .left h2{margin:0;margin-bottom:5px;margin-left:20px}.header .left .vertical-line{height:50px}.header .right .cta-button{background:var(--primary);color:white;padding:10px;border-radius:10px;margin:0 10px}*{font-family:"Maven Pro", sans-serif}body{overflow:hidden}.vertical-line{border-left:1px var(--grey2-border) solid;height:80%;align-self:center} diff --git a/app/static/llm_system_prompt.txt b/app/static/llm_system_prompt.txt new file mode 100644 index 0000000..f191670 --- /dev/null +++ b/app/static/llm_system_prompt.txt @@ -0,0 +1,17 @@ +Context: You are an extremely sophisticated, R/Shiny log analyzer and debugger. +Task: You will be given only the problematic logs. Your job is to analyse them. +Result: Generate a div with formatting as given below. +Structure: +-
Caution: This help is AI generated. Verify the information before taking any action.
+- tags.
+- All code blocks and one-liners should be formatted properly using tags.
+- All emphasis should happen with and not _ or *.
+- All bold should happen with and not ** or __.
+- Return raw HTML. No \```html wrapper.
diff --git a/app/styles/_logs.scss b/app/styles/_logs.scss
index e0d9d50..1532853 100644
--- a/app/styles/_logs.scss
+++ b/app/styles/_logs.scss
@@ -5,6 +5,11 @@
padding: 0 !important;
}
+ .rt-search {
+ width: 85% !important;
+ align-self: flex-start !important;
+ }
+
> div {
text-align: center;
@@ -23,6 +28,27 @@
}
}
+.logs-llm-modal {
+ .modal-dialog {
+ text-align: justify;
+
+ h2,
+ h3 {
+ color: var(--primary);
+ }
+
+ .btn {
+ background: var(--red-highlight);
+ border-radius: 0;
+ border: 1px solid var(--red);
+ }
+
+ .modal-content {
+ border-radius: 0;
+ }
+ }
+}
+
.logs-container {
position: relative;
@@ -52,13 +78,16 @@
}
}
- .logs-download {
+ .logs-options {
position: absolute;
z-index: 2;
right: 0;
margin: 10px;
- background: 0;
- border-radius: 0;
- padding: 5px 10px;
+
+ .logs-options-button {
+ background: 0;
+ border-radius: 0;
+ padding: 5px 10px;
+ }
}
}
diff --git a/app/view/mod_header.R b/app/view/mod_header.R
index e6a860c..ae5891e 100644
--- a/app/view/mod_header.R
+++ b/app/view/mod_header.R
@@ -39,7 +39,7 @@ ui <- function(id) {
div(
class = "right header-section",
actionLink(
- "lets-talk",
+ ns("lets-talk"),
label = "Let's Talk",
class = "cta-button",
onclick = "window.open('https://appsilon.com/#contact', '_blank');"
diff --git a/app/view/mod_job_list.R b/app/view/mod_job_list.R
index 469156c..c1cc2e4 100644
--- a/app/view/mod_job_list.R
+++ b/app/view/mod_job_list.R
@@ -1,5 +1,4 @@
box::use(
- magrittr[`%>%`],
reactable[
colDef,
getReactableState,
diff --git a/app/view/mod_logs.R b/app/view/mod_logs.R
index 52b1a15..4644574 100644
--- a/app/view/mod_logs.R
+++ b/app/view/mod_logs.R
@@ -1,40 +1,47 @@
box::use(
- dplyr[mutate],
+ dplyr[
+ as_tibble,
+ bind_cols,
+ filter,
+ mutate,
+ tibble
+ ],
glue[glue],
magrittr[`%>%`],
+ purrr[
+ pmap_dfr
+ ],
reactable[
colDef,
reactable,
reactableOutput,
renderReactable
],
+ shiny,
shinycssloaders[withSpinner],
- shiny[
- div,
- downloadButton,
- downloadHandler,
- icon,
- moduleServer,
- NS,
- observeEvent,
- reactive,
- renderUI,
- req,
- uiOutput
- ],
)
box::use(
- app/logic/api_utils[download_job_logs, get_job_logs],
- app/logic/logs_utils[process_log_data],
+ app/logic/api_utils[
+ download_job_logs,
+ get_job_logs
+ ],
+ app/logic/llm_utils[
+ get_llm_help,
+ is_llm_enabled
+ ],
+ app/logic/logs_utils[
+ get_status_info,
+ process_log_data
+ ],
)
#' @export
ui <- function(id) {
- ns <- NS(id)
- div(
+ ns <- shiny$NS(id)
+ shiny$div(
class = "logs-container",
- uiOutput(
+ shiny$uiOutput(
ns("download_logs")
),
withSpinner(
@@ -48,12 +55,16 @@ ui <- function(id) {
}
#' @export
-server <- function(id, selected_app_, selected_job_) {
- moduleServer(id, function(input, output, session) {
+server <- function(
+ id,
+ selected_app_,
+ selected_job_
+) {
+ shiny$moduleServer(id, function(input, output, session) {
ns <- session$ns
- output$download <- downloadHandler(
+ output$download <- shiny$downloadHandler(
filename = function() {
glue(
"{selected_app_()$name}_{selected_job_()$id}.txt"
@@ -68,41 +79,68 @@ server <- function(id, selected_app_, selected_job_) {
}
)
- output$download_logs <- renderUI({
+ output$download_logs <- shiny$renderUI({
if (is.null(selected_job_()$key)) {
- return(NULL)
+ NULL
}
- downloadButton(
- outputId = ns("download"),
- label = NULL,
- icon = icon("download"),
- class = "logs-download"
+ shiny$div(
+ class = "logs-options",
+ if (is_llm_enabled()) {
+ shiny$actionButton(
+ inputId = ns("llm"),
+ label = NULL,
+ icon = shiny$icon("robot"),
+ class = "llm logs-options-button"
+ )
+ },
+ shiny$downloadButton(
+ outputId = ns("download"),
+ label = NULL,
+ icon = shiny$icon("download"),
+ class = "download logs-options-button"
+ )
)
})
- logs_data <- reactive({
- req(selected_job_()$key)
+ logs_data <- shiny$reactive({
+ shiny$req(selected_job_()$key)
get_job_logs(
selected_app_()$guid,
selected_job_()$key
)
})
- output$logs_table <- renderReactable({
-
- processed_logs <- logs_data() %>%
+ processed_logs <- shiny$reactive({
+ logs_data() %>%
+ pmap_dfr(
+ ~ {
+ get_status_info(..1, ..3) |>
+ as_tibble() |>
+ bind_cols(
+ tibble(
+ entries.source = ..1,
+ entries.timestamp = ..2,
+ entries.data = ..3
+ )
+ )
+ }
+ ) %>%
mutate(
log_line = paste(
entries.source,
entries.timestamp,
entries.data,
+ entries.status,
+ entries.icon,
sep = "_-_"
)
)
+ })
+ output$logs_table <- renderReactable({
reactable(
- data = processed_logs,
+ data = processed_logs(),
searchable = TRUE,
borderless = TRUE,
pagination = FALSE,
@@ -118,6 +156,12 @@ server <- function(id, selected_app_, selected_job_) {
entries.data = colDef(
show = FALSE
),
+ entries.status = colDef(
+ show = FALSE
+ ),
+ entries.icon = colDef(
+ show = FALSE
+ ),
log_line = colDef(
name = "Logs",
cell = function(log_data) {
@@ -128,5 +172,34 @@ server <- function(id, selected_app_, selected_job_) {
)
})
+ if (is_llm_enabled()) {
+ llm_result <- shiny$eventReactive(input$llm, {
+ shiny$req(processed_logs())
+ get_llm_help(
+ processed_logs() %>%
+ filter(
+ entries.status %in% c("red", "yellow")
+ )
+ )
+ })
+
+ shiny$observeEvent(llm_result(), {
+ shiny$removeModal()
+ shiny$showModal(
+ shiny$modalDialog(
+ easyClose = TRUE,
+ size = "m",
+ footer = shiny$modalButton(
+ shiny$icon(
+ "xmark",
+ class = "red-text"
+ )
+ ),
+ shiny$HTML(llm_result())
+ ) %>%
+ shiny$tagAppendAttributes(class = "logs-llm-modal")
+ )
+ })
+ }
})
}
diff --git a/config.yml b/config.yml
index 9407a26..462f155 100644
--- a/config.yml
+++ b/config.yml
@@ -1,6 +1,12 @@
default:
rhino_log_level: !expr Sys.getenv("RHINO_LOG_LEVEL", "INFO")
rhino_log_file: !expr Sys.getenv("RHINO_LOG_FILE", NA)
+ llm:
+ enabled: FALSE
+ provider: "openai"
+ model: "gpt-4o"
+ api_key: !expr Sys.getenv("LLM_API_KEY", NA)
+ system_prompt: !expr readLines("app/static/llm_system_prompt.txt") |> paste(collapse = " ")
app_role: "owner"
branding:
logo:
diff --git a/dependencies.R b/dependencies.R
index 03ed7ac..a2db989 100644
--- a/dependencies.R
+++ b/dependencies.R
@@ -1,5 +1,6 @@
# This file allows packrat (used by rsconnect during deployment) to pick up dependencies.
library(dplyr)
+library(ellmer)
library(httr2)
library(magrittr)
library(reactable)
diff --git a/renv.lock b/renv.lock
index a7b9d2a..ec8eafa 100644
--- a/renv.lock
+++ b/renv.lock
@@ -1,6 +1,6 @@
{
"R": {
- "Version": "4.4.1",
+ "Version": "4.5.0",
"Repositories": [
{
"Name": "CRAN",
@@ -61,7 +61,7 @@
},
"R.utils": {
"Package": "R.utils",
- "Version": "2.12.3",
+ "Version": "2.13.0",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
@@ -72,28 +72,39 @@
"tools",
"utils"
],
- "Hash": "3dc2829b790254bfba21e60965787651"
+ "Hash": "d32373d88da809f8974d5307481862b0"
},
"R6": {
"Package": "R6",
- "Version": "2.5.1",
+ "Version": "2.6.1",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R"
],
- "Hash": "470851b6d5d0ac559e9d01bb352b4021"
+ "Hash": "d4335fe7207f1c01ab8c41762f5840d4"
},
"Rcpp": {
"Package": "Rcpp",
- "Version": "1.0.13-1",
+ "Version": "1.0.14",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
"methods",
"utils"
],
- "Hash": "6b868847b365672d6c1677b1608da9ed"
+ "Hash": "e7bdd9ee90e96921ca8a0f1972d66682"
+ },
+ "S7": {
+ "Package": "S7",
+ "Version": "0.2.0",
+ "Source": "Repository",
+ "Repository": "RSPM",
+ "Requirements": [
+ "R",
+ "utils"
+ ],
+ "Hash": "5deb66b3ae702137e1f4162c11861e76"
},
"askpass": {
"Package": "askpass",
@@ -182,9 +193,9 @@
},
"bslib": {
"Package": "bslib",
- "Version": "0.8.0",
+ "Version": "0.9.0",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"base64enc",
@@ -200,7 +211,7 @@
"rlang",
"sass"
],
- "Hash": "b299c6741ca9746fb227debcb0f9fb6c"
+ "Hash": "70a6489cc254171fb9b4a7f130f44dca"
},
"cachem": {
"Package": "cachem",
@@ -228,14 +239,14 @@
},
"cli": {
"Package": "cli",
- "Version": "3.6.3",
+ "Version": "3.6.4",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
"R",
"utils"
],
- "Hash": "b21916dd77a27642b447374a5d30ecf3"
+ "Hash": "491c34d3d9dd0d2fe13d9f278bb90795"
},
"codetools": {
"Package": "codetools",
@@ -249,10 +260,10 @@
},
"commonmark": {
"Package": "commonmark",
- "Version": "1.9.2",
+ "Version": "1.9.5",
"Source": "Repository",
"Repository": "RSPM",
- "Hash": "14eb0596f987c71535d07c3aff814742"
+ "Hash": "4ac08754c8ed35996b7c343fbb22885a"
},
"config": {
"Package": "config",
@@ -264,6 +275,17 @@
],
"Hash": "8b7222e9d9eb5178eea545c0c4d33fc2"
},
+ "coro": {
+ "Package": "coro",
+ "Version": "1.1.0",
+ "Source": "Repository",
+ "Repository": "RSPM",
+ "Requirements": [
+ "R",
+ "rlang"
+ ],
+ "Hash": "4998c8836ff95c90993a4eb8d853df71"
+ },
"crayon": {
"Package": "crayon",
"Version": "1.5.3",
@@ -278,27 +300,13 @@
},
"curl": {
"Package": "curl",
- "Version": "6.0.1",
+ "Version": "6.2.2",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
"R"
],
- "Hash": "e8ba62486230951fcd2b881c5be23f96"
- },
- "cyclocomp": {
- "Package": "cyclocomp",
- "Version": "1.1.1",
- "Source": "Repository",
- "Repository": "CRAN",
- "Requirements": [
- "callr",
- "crayon",
- "desc",
- "remotes",
- "withr"
- ],
- "Hash": "cdc4a473222b0112d4df0bcfbed12d44"
+ "Hash": "e4f9e10b18f453a1b7eaf38247dad4fe"
},
"desc": {
"Package": "desc",
@@ -315,9 +323,9 @@
},
"diffobj": {
"Package": "diffobj",
- "Version": "0.3.5",
+ "Version": "0.3.6",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"crayon",
@@ -326,7 +334,7 @@
"tools",
"utils"
],
- "Hash": "bcaa8b95f8d7d01a5dedfd959ce88ab8"
+ "Hash": "e036ce354ab60e705ac5f40bac87e8cb"
},
"digest": {
"Package": "digest",
@@ -362,15 +370,35 @@
],
"Hash": "fedd9d00c2944ff00a0e2696ccf048ec"
},
+ "ellmer": {
+ "Package": "ellmer",
+ "Version": "0.1.1",
+ "Source": "Repository",
+ "Repository": "RSPM",
+ "Requirements": [
+ "R6",
+ "S7",
+ "cli",
+ "coro",
+ "glue",
+ "httr2",
+ "jsonlite",
+ "later",
+ "lifecycle",
+ "promises",
+ "rlang"
+ ],
+ "Hash": "754d3a8cbb25b05be2058892f579422f"
+ },
"evaluate": {
"Package": "evaluate",
- "Version": "1.0.1",
+ "Version": "1.0.3",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
"R"
],
- "Hash": "3fd29944b231036ad67c3edb32e02201"
+ "Hash": "e9651417729bbe7472e32b5027370e79"
},
"fansi": {
"Package": "fansi",
@@ -405,14 +433,14 @@
},
"fs": {
"Package": "fs",
- "Version": "1.6.5",
+ "Version": "1.6.6",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
"R",
"methods"
],
- "Hash": "7f48af39fa27711ea5fbd183b399920d"
+ "Hash": "7eb1e342eee7e0a7449c49cdaa526d39"
},
"generics": {
"Package": "generics",
@@ -480,7 +508,7 @@
},
"httpuv": {
"Package": "httpuv",
- "Version": "1.6.15",
+ "Version": "1.6.16",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
@@ -491,11 +519,11 @@
"promises",
"utils"
],
- "Hash": "d55aa087c47a63ead0f6fc10f8fa1ee0"
+ "Hash": "6c3c8728e40326de6529a5c46e377e5c"
},
"httr2": {
"Package": "httr2",
- "Version": "1.0.6",
+ "Version": "1.1.2",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
@@ -512,7 +540,7 @@
"vctrs",
"withr"
],
- "Hash": "3ef5d07ec78803475a94367d71b40c41"
+ "Hash": "ade531519694081d91036b509eb30594"
},
"jquerylib": {
"Package": "jquerylib",
@@ -526,17 +554,17 @@
},
"jsonlite": {
"Package": "jsonlite",
- "Version": "1.8.9",
+ "Version": "2.0.0",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
"methods"
],
- "Hash": "4e993b65c2c3ffbffce7bb3e2c6f832b"
+ "Hash": "b0776f526d36d8bd4a3344a88fe165c4"
},
"knitr": {
"Package": "knitr",
- "Version": "1.49",
+ "Version": "1.50",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
@@ -548,18 +576,18 @@
"xfun",
"yaml"
],
- "Hash": "9fcb189926d93c636dea94fbe4f44480"
+ "Hash": "5a07d8ec459d7b80bd4acca5f4a6e062"
},
"later": {
"Package": "later",
- "Version": "1.3.2",
+ "Version": "1.4.2",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
"Rcpp",
"rlang"
],
- "Hash": "a3e051d405326b8b0012377434c62b37"
+ "Hash": "9591aabef9cf988f05bde9324fd3ad99"
},
"lazyeval": {
"Package": "lazyeval",
@@ -586,14 +614,14 @@
},
"lintr": {
"Package": "lintr",
- "Version": "3.1.2",
+ "Version": "3.2.0",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
"R",
"backports",
+ "cli",
"codetools",
- "cyclocomp",
"digest",
"glue",
"knitr",
@@ -603,7 +631,7 @@
"xml2",
"xmlparsedata"
],
- "Hash": "08cff46381a242d44c0d8dd0aabd9f71"
+ "Hash": "45b44c7f1040cce8f34ec0a9c92d47f3"
},
"logger": {
"Package": "logger",
@@ -639,23 +667,23 @@
},
"mime": {
"Package": "mime",
- "Version": "0.12",
+ "Version": "0.13",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"tools"
],
- "Hash": "18e9c28c1d3ca1560ce30658b22ce104"
+ "Hash": "0ec19f34c72fab674d8f2b4b1c6410e1"
},
"openssl": {
"Package": "openssl",
- "Version": "2.2.2",
+ "Version": "2.3.2",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
"askpass"
],
- "Hash": "d413e0fef796c9401a4419485f709ca1"
+ "Hash": "bc54d87ebf858b28de18df4bca6528d3"
},
"packrat": {
"Package": "packrat",
@@ -671,12 +699,11 @@
},
"pillar": {
"Package": "pillar",
- "Version": "1.9.0",
+ "Version": "1.10.2",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"cli",
- "fansi",
"glue",
"lifecycle",
"rlang",
@@ -684,11 +711,11 @@
"utils",
"vctrs"
],
- "Hash": "15da5a8412f317beeee6175fbc76f4bb"
+ "Hash": "1098920a19b5cd5a15bacdc74a89979d"
},
"pkgbuild": {
"Package": "pkgbuild",
- "Version": "1.4.5",
+ "Version": "1.4.7",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
@@ -699,7 +726,7 @@
"desc",
"processx"
],
- "Hash": "30eaaab94db72652e72e3475c1b55278"
+ "Hash": "ae47817501cafc99f57586b6e5241134"
},
"pkgconfig": {
"Package": "pkgconfig",
@@ -742,7 +769,7 @@
},
"processx": {
"Package": "processx",
- "Version": "3.8.4",
+ "Version": "3.8.6",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
@@ -751,11 +778,11 @@
"ps",
"utils"
],
- "Hash": "0c90a7d71988856bad2a2a45dd871bb9"
+ "Hash": "720161b280b0a35f4d1490ead2fe81d0"
},
"promises": {
"Package": "promises",
- "Version": "1.3.0",
+ "Version": "1.3.2",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
@@ -767,24 +794,24 @@
"rlang",
"stats"
],
- "Hash": "434cd5388a3979e74be5c219bcd6e77d"
+ "Hash": "c84fd4f75ea1f5434735e08b7f50fbca"
},
"ps": {
"Package": "ps",
- "Version": "1.8.1",
+ "Version": "1.9.1",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
"R",
"utils"
],
- "Hash": "b4404b1de13758dea1c0484ad0d48563"
+ "Hash": "093688087b0bacce6ba2f661f36328e2"
},
"purrr": {
"Package": "purrr",
- "Version": "1.0.2",
+ "Version": "1.0.4",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"cli",
@@ -793,7 +820,7 @@
"rlang",
"vctrs"
],
- "Hash": "1cba04a4e9414bdefc9dcaa99649a8dc"
+ "Hash": "cc8b5d43f90551fa6df0a6be5d640a4f"
},
"rappdirs": {
"Package": "rappdirs",
@@ -830,20 +857,6 @@
],
"Hash": "6069eb2a6597963eae0605c1875ff14c"
},
- "remotes": {
- "Package": "remotes",
- "Version": "2.5.0",
- "Source": "Repository",
- "Repository": "RSPM",
- "Requirements": [
- "R",
- "methods",
- "stats",
- "tools",
- "utils"
- ],
- "Hash": "3ee025083e66f18db6cf27b56e23e141"
- },
"renv": {
"Package": "renv",
"Version": "1.0.3",
@@ -866,7 +879,7 @@
},
"rhino": {
"Package": "rhino",
- "Version": "1.10.1",
+ "Version": "1.11.0",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
@@ -874,6 +887,7 @@
"box",
"box.linters",
"box.lsp",
+ "callr",
"cli",
"config",
"fs",
@@ -891,18 +905,18 @@
"withr",
"yaml"
],
- "Hash": "cd58cf8f362b8cc8c82aea1de9468218"
+ "Hash": "65daa659ff338dcf4f1ff79ddecadb00"
},
"rlang": {
"Package": "rlang",
- "Version": "1.1.4",
+ "Version": "1.1.6",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
"R",
"utils"
],
- "Hash": "3eec01f8b1dee337674b2e34ab1f9bc1"
+ "Hash": "892124978869b74935dc3934c42bfe5a"
},
"rmarkdown": {
"Package": "rmarkdown",
@@ -939,7 +953,7 @@
},
"rsconnect": {
"Package": "rsconnect",
- "Version": "1.3.3",
+ "Version": "1.3.4",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
@@ -958,7 +972,7 @@
"tools",
"yaml"
],
- "Hash": "d466c98fdce812325feb4ad406c6ca4b"
+ "Hash": "315454d2f50d117ef58691d0bf50fe63"
},
"rstudioapi": {
"Package": "rstudioapi",
@@ -969,9 +983,9 @@
},
"sass": {
"Package": "sass",
- "Version": "0.4.9",
+ "Version": "0.4.10",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R6",
"fs",
@@ -979,13 +993,13 @@
"rappdirs",
"rlang"
],
- "Hash": "d53dbfddf695303ea4ad66f86e99b95d"
+ "Hash": "3fb78d066fb92299b1d13f6a7c9a90a8"
},
"shiny": {
"Package": "shiny",
- "Version": "1.9.1",
+ "Version": "1.10.0",
"Source": "Repository",
- "Repository": "CRAN",
+ "Repository": "RSPM",
"Requirements": [
"R",
"R6",
@@ -1012,7 +1026,7 @@
"withr",
"xtable"
],
- "Hash": "6a293995a66e12c48d13aa1f957d09c7"
+ "Hash": "4b4477baa9a939c5577e5ddb4bf01f28"
},
"shinycssloaders": {
"Package": "shinycssloaders",
@@ -1041,7 +1055,7 @@
},
"stringi": {
"Package": "stringi",
- "Version": "1.8.4",
+ "Version": "1.8.7",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
@@ -1050,7 +1064,7 @@
"tools",
"utils"
],
- "Hash": "39e1144fd75428983dc3f63aa53dfa91"
+ "Hash": "2b56088e23bdd58f89aebf43a0913457"
},
"stringr": {
"Package": "stringr",
@@ -1097,7 +1111,7 @@
},
"testthat": {
"Package": "testthat",
- "Version": "3.2.1.1",
+ "Version": "3.2.3",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
@@ -1122,7 +1136,7 @@
"waldo",
"withr"
],
- "Hash": "3f6e7e5e2220856ff865e4834766bf2b"
+ "Hash": "42f889439ccb14c55fc3d75c9c755056"
},
"tibble": {
"Package": "tibble",
@@ -1161,17 +1175,17 @@
},
"tinytex": {
"Package": "tinytex",
- "Version": "0.54",
+ "Version": "0.57",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
"xfun"
],
- "Hash": "3ec7e3ddcacc2d34a9046941222bf94d"
+ "Hash": "02d65e0c0415bf36a7ddc0d2ba50a840"
},
"treesitter": {
"Package": "treesitter",
- "Version": "0.1.0",
+ "Version": "0.2.0",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
@@ -1181,7 +1195,7 @@
"rlang",
"vctrs"
],
- "Hash": "22a579e4ac2ddd021df1791da7fd080f"
+ "Hash": "cc0472495bc995fabbb58e2de941c384"
},
"treesitter.r": {
"Package": "treesitter.r",
@@ -1246,7 +1260,7 @@
},
"xfun": {
"Package": "xfun",
- "Version": "0.49",
+ "Version": "0.52",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
@@ -1255,11 +1269,11 @@
"stats",
"tools"
],
- "Hash": "8687398773806cfff9401a2feca96298"
+ "Hash": "652ce36fe7d57688e6786819b09d9190"
},
"xml2": {
"Package": "xml2",
- "Version": "1.3.6",
+ "Version": "1.3.8",
"Source": "Repository",
"Repository": "RSPM",
"Requirements": [
@@ -1268,7 +1282,7 @@
"methods",
"rlang"
],
- "Hash": "1d0336142f4cd25d8d23cd3ba7a8fb61"
+ "Hash": "f5130b2f3d461964bac93cc618013231"
},
"xmlparsedata": {
"Package": "xmlparsedata",
diff --git a/tests/testthat/test-general_utils.R b/tests/testthat/test-general_utils.R
index 07c9e51..a298404 100644
--- a/tests/testthat/test-general_utils.R
+++ b/tests/testthat/test-general_utils.R
@@ -2,7 +2,6 @@ box::use(
testthat[
describe,
expect_identical,
- expect_error,
expect_true,
it
],