Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8089895
feat: add theming with config.yml
DeepanshKhurana Nov 22, 2024
ac42cd1
chore: add onClick = "select" to both app list and job list tables
DeepanshKhurana Nov 22, 2024
df18d7e
chore: revert config color to Appsilon Blue
DeepanshKhurana Nov 22, 2024
2250cfa
docs: update README with new FAQs about branding
DeepanshKhurana Nov 22, 2024
b33ef11
feat: add handling for logo in config.yml theming
DeepanshKhurana Nov 22, 2024
5543096
refactor: untangle main.R server
TymekAppsilon Mar 12, 2025
d6eb5ff
refactor: replace reactiveValues with plain reactives
TymekAppsilon Mar 12, 2025
c583c89
refactor: update checks
TymekAppsilon Mar 12, 2025
cc4ba3a
refactor: unnest renderUI
TymekAppsilon Mar 12, 2025
1bd9d78
test: add unit tests for general_utils.R
DeepanshKhurana Apr 13, 2025
d8cb1c0
feat: move branding outside ui/server
DeepanshKhurana Apr 13, 2025
f2d48df
chore: remove nolint
DeepanshKhurana Apr 13, 2025
d12bdcc
fix: add working default value + docstring improvement for generate_e…
DeepanshKhurana Apr 13, 2025
9e1a293
chore: move primary to the top in config.yml
DeepanshKhurana Apr 13, 2025
9937baa
chore: remove redundant default variable from generate_css_variables
DeepanshKhurana Apr 13, 2025
3a226a7
docs: add context for what the colors do in README.md
DeepanshKhurana Apr 13, 2025
8f88ddd
Merge remote-tracking branch 'upstream/feat/add-config-theming' into …
TymekAppsilon Apr 23, 2025
937c239
chore: enable box.linters
DeepanshKhurana Apr 13, 2025
70d1163
feat: add config for llm mode in config.yml
DeepanshKhurana Apr 13, 2025
7ff93c2
chore: update dependencies
DeepanshKhurana Apr 13, 2025
56a3dba
feat: add llm system prompt
DeepanshKhurana Apr 13, 2025
1b22202
fix: independent linting fixes
DeepanshKhurana Apr 13, 2025
ebe6d6b
feat: add llm utils using ellmer
DeepanshKhurana Apr 13, 2025
54a06fa
feat: integrate llm functionality
DeepanshKhurana Apr 13, 2025
86b1d69
feat: add llm styling
DeepanshKhurana Apr 13, 2025
3de3d6b
chore: disable llm mode by default
DeepanshKhurana Apr 13, 2025
b634e10
feat: add logic to validate llm providers
DeepanshKhurana Apr 13, 2025
687c311
chore: update R version + rebase
DeepanshKhurana Apr 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .lintr
Original file line number Diff line number Diff line change
@@ -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)
)
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ The LogAnalyzer open-source app is a simple, plug and play application developed
- I get `"Oops! Can't read apps from Posit Connect."` on the rightmost image?
- This may mean that the Posit Connect API's response did not send proper data.
- So far, one documented reason for this is that OAuth on Posit Connect instances may prevent the `/content` endpoint from sending app data.
- How do I rebrand the application?
- You can edit the branding in the `config.yml` file. You'll find the `colors` key which will build the CSS.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest expanding this with some description of what particular colors do.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! I've added a few points explaining how the colors work. It should at least indicate the user what colors affect what values.

- The three status colors and their highlights e.g. `red` and `red-highlight` color the logs for you.
- The `primary` color is the primary theme for your application. This also affects SVGs.
- The blacks, whites and greys fill in the rest of the UI elements such as text, separator et al.
- How do I recolor the SVGs?
- This requires some creativity. We recommend replacing the primary color hex which you can find in the `.svg` file as `fill="#hexcde"` to `PRIMARY`.
- We use this as a default value in the function that replaces it but you are welcome to use another value and modify the function.

# Credits

Expand Down
44 changes: 39 additions & 5 deletions app/logic/empty_state_utils.R
Original file line number Diff line number Diff line change
@@ -1,30 +1,64 @@
box::use(
base64enc[
base64encode
],
shiny[
div,
img,
p,
renderUI
],
)

#' @description Function to generate an empty state UI
#' @param text Text to display in the empty state
#' @param image_path Path to the image to display in the empty state
#' @param color Color to use for the image
#' @export
generate_empty_state_ui <- function(
text = "Select an application and a job to view logs",
image_path = "static/illustrations/empty_state.svg"
image_path = "static/illustrations/empty_state.svg",
color = "#0099f9"
) {
div(
class = "empty-state-container",
p(
class = "empty-state-text",
text
),
img(
src = image_path,
class = "empty-state-image",
replace_svg_fill(
color = color,
svg_path = image_path,
alt = text
)
)
}

#' Function to replace fill color in SVG
#' @param color Character. The color to replace
#' @param svg_path Character. The path to the SVG file
#' @param placeholder Character. The placeholder. Default is "PRIMARY"
#' @param alt Character. The alt text for the image
#' @return an image tag with the SVG content
replace_svg_fill <- function(
color,
svg_path = "",
placeholder = "PRIMARY",
alt = "",
class = "empty-state-image"
) {
svg_content <- readLines(svg_path)
svg_content <- paste(svg_content, collapse = "\n")
svg_content <- gsub(
placeholder,
color,
svg_content
)
img(
class = class,
src = paste0(
"data:image/svg+xml;base64,",
base64encode(charToRaw(svg_content))
),
alt = alt
)
}
27 changes: 27 additions & 0 deletions app/logic/general_utils.R
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
box::use(
purrr[
map_chr
],
)

#' Function to check if a string of log text has error keywords
#'
#' @param text Character. The log string
Expand Down Expand Up @@ -37,3 +43,24 @@ format_timestamp <- function(
format = to
)
}

#' Generate CSS variables from config.yml
#' @param config the config file
#' @return a string of CSS variables within :root {}
#' @export
generate_css_variables <- function(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it is a good moment to start adding unit tests? This function looks like an opportunity :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added tests for all the general_utils functions! :D

config
) {
css_lines <- map_chr(
names(config$colors),
function(name) {
color_value <- config$colors[[name]]
sprintf(" --%s: %s;", name, color_value)
}
)
paste0(
":root {\n",
paste(css_lines, collapse = "\n"),
"\n}"
)
}
128 changes: 128 additions & 0 deletions app/logic/llm_utils.R
Original file line number Diff line number Diff line change
@@ -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"
)
}
18 changes: 10 additions & 8 deletions app/logic/logs_utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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
}
Loading
Loading