From 77380929c2dc91b42716b1cf8f507ac0f84a7af7 Mon Sep 17 00:00:00 2001 From: jaewoong heo Date: Thu, 11 Dec 2025 13:52:22 +0900 Subject: [PATCH 1/3] docs: roxygen2 man update --- R/aiAssistant.R | 197 ++++++++++++++++++++++++++++++++++++------- man/aiAssistant.Rd | 95 ++++++++++++++++++--- man/aiAssistantUI.Rd | 72 ++++++++++++++-- 3 files changed, 315 insertions(+), 49 deletions(-) diff --git a/R/aiAssistant.R b/R/aiAssistant.R index af487fe5..6bb9bad3 100644 --- a/R/aiAssistant.R +++ b/R/aiAssistant.R @@ -1,18 +1,24 @@ #' @title aiAssistantUI: AI Assistant module UI #' @description AI-powered statistical analysis assistant module UI -#' @param id id +#' @param id Module's namespace ID. Used to create unique identifiers for UI elements. #' @param show_api_config If TRUE, shows API configuration UI. If FALSE, uses only env vars. Default: TRUE -#' @return AI Assistant module UI +#' @return Shiny UI tagList containing the AI Assistant interface with chat, code editor, and result panels #' @details Provides an interactive chat interface with AI for statistical analysis code generation #' @examples +#' \dontrun{ +#' # Setup: Add API key to .Renviron file +#' # usethis::edit_r_environ() +#' # Add line: ANTHROPIC_API_KEY=your_actual_key_here +#' # Save and restart R +#' #' library(shiny) #' library(DT) #' library(survival) #' +#' # Example 1: Basic usage with auto-generated variable structure #' ui <- fluidPage( -#' fluidRow( -#' column(12, aiAssistantUI("ai")) -#' ) +#' titlePanel("AI Statistical Assistant"), +#' aiAssistantUI("ai") #' ) #' #' server <- function(input, output, session) { @@ -22,10 +28,62 @@ #' callModule(aiAssistant, "ai", #' data = data, #' data_label = data.label, -#' data_varStruct = NULL, -#' api_key = Sys.getenv("ANTHROPIC_API_KEY") +#' data_varStruct = NULL # Auto-generates variable structure +#' ) +#' } +#' +#' shinyApp(ui, server) +#' +#' # Example 2: With custom variable structure and analysis context +#' ui2 <- fluidPage( +#' titlePanel("Survival Analysis Assistant"), +#' aiAssistantUI("ai") +#' ) +#' +#' server2 <- function(input, output, session) { +#' data <- reactive(colon) +#' data.label <- reactive(jstable::mk.lev(colon)) +#' +#' # Custom variable structure for survival analysis +#' var_struct <- reactive({ +#' list( +#' variable = names(colon), +#' Base = c("rx", "sex", "age", "obstruct", "nodes"), +#' Event = "status", +#' Time = "time" +#' ) +#' }) +#' +#' callModule(aiAssistant, "ai", +#' data = data, +#' data_label = data.label, +#' data_varStruct = var_struct, +#' analysis_context = reactive({ +#' "Colon cancer adjuvant chemotherapy trial (survival::colon). +#' Primary outcome: time to recurrence or death (status/time). +#' Treatment groups: Observation, Levamisole, Levamisole+5-FU." +#' }) #' ) #' } +#' +#' shinyApp(ui2, server2) +#' +#' # Example 3: Production deployment without API config UI +#' ui_prod <- fluidPage( +#' aiAssistantUI("ai", show_api_config = FALSE) +#' ) +#' +#' server_prod <- function(input, output, session) { +#' # Relies entirely on .Renviron configuration +#' callModule(aiAssistant, "ai", +#' data = reactive(mtcars), +#' data_label = reactive(jstable::mk.lev(mtcars)), +#' show_api_config = FALSE +#' ) +#' } +#' +#' shinyApp(ui_prod, server_prod) +#' } #' @rdname aiAssistantUI #' @export #' @import shiny @@ -622,24 +680,42 @@ aiAssistantUI <- function(id, show_api_config = TRUE) { #' @param input input #' @param output output #' @param session session -#' @param data Data (reactive) -#' @param data_label Data label (reactive) -#' @param data_varStruct Variable structure list of data, Default: NULL -#' @param api_key API key for AI service. If NULL, reads from provider-specific env var -#' @param stats_guide Optional custom statistical guide text. If NULL, uses default guide +#' @param data Data (reactive). Should return the current data set each time it is called. +#' @param data_label Data label (reactive). Typically created with `jstable::mk.lev()`. +#' @param data_varStruct Variable structure list of data (reactive or NULL). If NULL, automatically generates `list(variable = names(data()))`. Can also be a reactive returning a named list with elements like `Base`, `Event`, `Time`, etc. Default: NULL +#' @param api_key API key for AI service. If NULL, reads from provider-specific environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GOOGLE_API_KEY`) configured in `.Renviron` file +#' @param stats_guide Optional custom statistical guide text to override default guidelines. Can be a character string or reactive. If NULL, uses built-in statistical best practices guide. Useful for adding domain-specific statistical conventions or organizational standards. #' @param show_api_config If TRUE, shows API config UI. If FALSE, uses only env vars. Default: TRUE -#' @param analysis_context Optional reactive or list containing previous analysis results. AI can reference these when user asks follow-up questions. Default: NULL -#' @return AI Assistant module server -#' @details Provides interactive statistical analysis code generation using AI +#' @param analysis_context Optional character string, list, or reactive returning that information. Used to pass prior analysis context that the AI can reference in follow-up questions. +#' @return Server module (no explicit return value). Creates reactive outputs and observers for chat conversation history, generated code execution, analysis results (tables, plots, text), and export functionality. +#' @details +#' \itemize{ +#' \item `data` and `data_label` must be reactives; their values are re-evaluated every time the module needs data. +#' \item `data_varStruct` can be NULL (auto-generated) or a reactive returning a named list with elements like `variable`, `Base`, `Event`, `Time`, etc. This mirrors the structure used by other *jsmodule* components. +#' \item Generated code runs in a sandbox that only exposes the supplied data and allows the following packages: \code{jstable}, \code{jskm}, \code{jsmodule}, \code{survival}, \code{ggplot2}, \code{ggpubr}, \code{pROC}, \code{data.table}, \code{DT}, \code{gridExtra}, \code{GGally}, \code{forestploter}, \code{MatchIt}, \code{timeROC}. +#' \item API keys are resolved in the order: explicit `api_key` argument, UI input (if `show_api_config = TRUE`), provider-specific environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GOOGLE_API_KEY`). +#' \item To use environment variables for API keys, add them to your `.Renviron` file (use `usethis::edit_r_environ()` to open it) with lines like: \cr +#' `ANTHROPIC_API_KEY=your_key_here` \cr +#' `OPENAI_API_KEY=your_key_here` \cr +#' `GOOGLE_API_KEY=your_key_here` \cr +#' Then restart R session for changes to take effect. +#' \item `analysis_context` can be a static string/list or a reactive that returns a description of prior analyses (tables, plots, code snippets). The text is appended to the system prompt so the AI can reference earlier steps. +#' } #' @examples +#' \dontrun{ +#' # Setup: Add API key to .Renviron file +#' # usethis::edit_r_environ() +#' # Add line: ANTHROPIC_API_KEY=your_actual_key_here +#' # Save and restart R +#' #' library(shiny) #' library(DT) #' library(survival) #' +#' # Example 1: Basic usage with auto-generated variable structure #' ui <- fluidPage( -#' fluidRow( -#' column(12, aiAssistantUI("ai")) -#' ) +#' titlePanel("AI Statistical Assistant"), +#' aiAssistantUI("ai") #' ) #' #' server <- function(input, output, session) { @@ -649,10 +725,62 @@ aiAssistantUI <- function(id, show_api_config = TRUE) { #' callModule(aiAssistant, "ai", #' data = data, #' data_label = data.label, -#' data_varStruct = NULL, -#' api_key = Sys.getenv("ANTHROPIC_API_KEY") +#' data_varStruct = NULL # Auto-generates variable structure #' ) #' } +#' +#' shinyApp(ui, server) +#' +#' # Example 2: With custom variable structure and analysis context +#' ui2 <- fluidPage( +#' titlePanel("Survival Analysis Assistant"), +#' aiAssistantUI("ai") +#' ) +#' +#' server2 <- function(input, output, session) { +#' data <- reactive(colon) +#' data.label <- reactive(jstable::mk.lev(colon)) +#' +#' # Custom variable structure for survival analysis +#' var_struct <- reactive({ +#' list( +#' variable = names(colon), +#' Base = c("rx", "sex", "age", "obstruct", "nodes"), +#' Event = "status", +#' Time = "time" +#' ) +#' }) +#' +#' callModule(aiAssistant, "ai", +#' data = data, +#' data_label = data.label, +#' data_varStruct = var_struct, +#' analysis_context = reactive({ +#' "Colon cancer adjuvant chemotherapy trial (survival::colon). +#' Primary outcome: time to recurrence or death (status/time). +#' Treatment groups: Observation, Levamisole, Levamisole+5-FU." +#' }) +#' ) +#' } +#' +#' shinyApp(ui2, server2) +#' +#' # Example 3: Production deployment without API config UI +#' ui_prod <- fluidPage( +#' aiAssistantUI("ai", show_api_config = FALSE) +#' ) +#' +#' server_prod <- function(input, output, session) { +#' # Relies entirely on .Renviron configuration +#' callModule(aiAssistant, "ai", +#' data = reactive(mtcars), +#' data_label = reactive(jstable::mk.lev(mtcars)), +#' show_api_config = FALSE +#' ) +#' } +#' +#' shinyApp(ui_prod, server_prod) +#' } #' @rdname aiAssistant #' @export #' @import shiny @@ -678,6 +806,11 @@ aiAssistant <- function(input, output, session, data, data_label, "MatchIt", "timeROC" ) + # Initialize data_varStruct as reactive if not provided + if (is.null(data_varStruct)) { + data_varStruct <- reactive(list(variable = names(data()))) + } + # Reactive values for model list and settings available_models <- reactiveVal(list()) selected_model <- reactiveVal(NULL) @@ -1449,7 +1582,7 @@ aiAssistant <- function(input, output, session, data, data_label, req(data()) d <- data() dl <- if (!is.null(data_label)) data_label() else NULL - vs <- if (!is.null(data_varStruct)) data_varStruct else NULL + vs <- data_varStruct() factor_vars <- names(d)[sapply(d, is.factor)] numeric_vars <- names(d)[sapply(d, is.numeric)] @@ -1463,17 +1596,23 @@ aiAssistant <- function(input, output, session, data, data_label, if (length(numeric_vars) > 10) " ..." else "", "\n" ) - if (!is.null(vs)) { + # Add variable structure information if available + if (!is.null(vs$Base)) { context <- paste0(context, "\n## Variable Structure\n", - "- Base: ", paste(head(vs$Base, 5), collapse = ", "), " ...\n" + "- Base: ", paste(head(vs$Base, 5), collapse = ", "), + if (length(vs$Base) > 5) " ..." else "", "\n" ) - if (!is.null(vs$Event)) { - context <- paste0(context, "- Event: ", paste(vs$Event, collapse = ", "), "\n") - } - if (!is.null(vs$Time)) { - context <- paste0(context, "- Time: ", paste(vs$Time, collapse = ", "), "\n") - } + } + if (!is.null(vs$Event)) { + context <- paste0(context, + if (is.null(vs$Base)) "\n## Variable Structure\n" else "", + "- Event: ", paste(vs$Event, collapse = ", "), "\n") + } + if (!is.null(vs$Time)) { + context <- paste0(context, + if (is.null(vs$Base) && is.null(vs$Event)) "\n## Variable Structure\n" else "", + "- Time: ", paste(vs$Time, collapse = ", "), "\n") } if (!is.null(dl)) { diff --git a/man/aiAssistant.Rd b/man/aiAssistant.Rd index e4bb7d95..bf9c3812 100644 --- a/man/aiAssistant.Rd +++ b/man/aiAssistant.Rd @@ -24,38 +24,55 @@ aiAssistant( \item{session}{session} -\item{data}{Data (reactive)} +\item{data}{Data (reactive). Should return the current data set each time it is called.} -\item{data_label}{Data label (reactive)} +\item{data_label}{Data label (reactive). Typically created with `jstable::mk.lev()`.} -\item{data_varStruct}{Variable structure list of data, Default: NULL} +\item{data_varStruct}{Variable structure list of data (reactive or NULL). If NULL, automatically generates `list(variable = names(data()))`. Can also be a reactive returning a named list with elements like `Base`, `Event`, `Time`, etc. Default: NULL} -\item{api_key}{API key for AI service. If NULL, reads from provider-specific env var} +\item{api_key}{API key for AI service. If NULL, reads from provider-specific environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GOOGLE_API_KEY`) configured in `.Renviron` file} -\item{stats_guide}{Optional custom statistical guide text. If NULL, uses default guide} +\item{stats_guide}{Optional custom statistical guide text to override default guidelines. Can be a character string or reactive. If NULL, uses built-in statistical best practices guide. Useful for adding domain-specific statistical conventions or organizational standards.} \item{show_api_config}{If TRUE, shows API config UI. If FALSE, uses only env vars. Default: TRUE} -\item{analysis_context}{Optional reactive or list containing previous analysis results. AI can reference these when user asks follow-up questions. Default: NULL} +\item{analysis_context}{Optional character string, list, or reactive returning that information. Used to pass prior analysis context that the AI can reference in follow-up questions.} } \value{ -AI Assistant module server +Server module (no explicit return value). Creates reactive outputs and observers for chat conversation history, generated code execution, analysis results (tables, plots, text), and export functionality. } \description{ AI-powered statistical analysis assistant module server } \details{ -Provides interactive statistical analysis code generation using AI +\itemize{ + \item `data` and `data_label` must be reactives; their values are re-evaluated every time the module needs data. + \item `data_varStruct` can be NULL (auto-generated) or a reactive returning a named list with elements like `variable`, `Base`, `Event`, `Time`, etc. This mirrors the structure used by other *jsmodule* components. + \item Generated code runs in a sandbox that only exposes the supplied data and allows the following packages: \code{jstable}, \code{jskm}, \code{jsmodule}, \code{survival}, \code{ggplot2}, \code{ggpubr}, \code{pROC}, \code{data.table}, \code{DT}, \code{gridExtra}, \code{GGally}, \code{forestploter}, \code{MatchIt}, \code{timeROC}. + \item API keys are resolved in the order: explicit `api_key` argument, UI input (if `show_api_config = TRUE`), provider-specific environment variables (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, `GOOGLE_API_KEY`). + \item To use environment variables for API keys, add them to your `.Renviron` file (use `usethis::edit_r_environ()` to open it) with lines like: \cr + `ANTHROPIC_API_KEY=your_key_here` \cr + `OPENAI_API_KEY=your_key_here` \cr + `GOOGLE_API_KEY=your_key_here` \cr + Then restart R session for changes to take effect. + \item `analysis_context` can be a static string/list or a reactive that returns a description of prior analyses (tables, plots, code snippets). The text is appended to the system prompt so the AI can reference earlier steps. +} } \examples{ +\dontrun{ +# Setup: Add API key to .Renviron file +# usethis::edit_r_environ() +# Add line: ANTHROPIC_API_KEY=your_actual_key_here +# Save and restart R + library(shiny) library(DT) library(survival) +# Example 1: Basic usage with auto-generated variable structure ui <- fluidPage( - fluidRow( - column(12, aiAssistantUI("ai")) - ) + titlePanel("AI Statistical Assistant"), + aiAssistantUI("ai") ) server <- function(input, output, session) { @@ -65,8 +82,60 @@ server <- function(input, output, session) { callModule(aiAssistant, "ai", data = data, data_label = data.label, - data_varStruct = NULL, - api_key = Sys.getenv("ANTHROPIC_API_KEY") + data_varStruct = NULL # Auto-generates variable structure ) } + +shinyApp(ui, server) + +# Example 2: With custom variable structure and analysis context +ui2 <- fluidPage( + titlePanel("Survival Analysis Assistant"), + aiAssistantUI("ai") +) + +server2 <- function(input, output, session) { + data <- reactive(colon) + data.label <- reactive(jstable::mk.lev(colon)) + + # Custom variable structure for survival analysis + var_struct <- reactive({ + list( + variable = names(colon), + Base = c("rx", "sex", "age", "obstruct", "nodes"), + Event = "status", + Time = "time" + ) + }) + + callModule(aiAssistant, "ai", + data = data, + data_label = data.label, + data_varStruct = var_struct, + analysis_context = reactive({ + "Colon cancer adjuvant chemotherapy trial (survival::colon). + Primary outcome: time to recurrence or death (status/time). + Treatment groups: Observation, Levamisole, Levamisole+5-FU." + }) + ) +} + +shinyApp(ui2, server2) + +# Example 3: Production deployment without API config UI +ui_prod <- fluidPage( + aiAssistantUI("ai", show_api_config = FALSE) +) + +server_prod <- function(input, output, session) { + # Relies entirely on .Renviron configuration + callModule(aiAssistant, "ai", + data = reactive(mtcars), + data_label = reactive(jstable::mk.lev(mtcars)), + show_api_config = FALSE + ) +} + +shinyApp(ui_prod, server_prod) +} } diff --git a/man/aiAssistantUI.Rd b/man/aiAssistantUI.Rd index e26dc768..a39cd2c4 100644 --- a/man/aiAssistantUI.Rd +++ b/man/aiAssistantUI.Rd @@ -7,12 +7,12 @@ aiAssistantUI(id, show_api_config = TRUE) } \arguments{ -\item{id}{id} +\item{id}{Module's namespace ID. Used to create unique identifiers for UI elements.} \item{show_api_config}{If TRUE, shows API configuration UI. If FALSE, uses only env vars. Default: TRUE} } \value{ -AI Assistant module UI +Shiny UI tagList containing the AI Assistant interface with chat, code editor, and result panels } \description{ AI-powered statistical analysis assistant module UI @@ -21,14 +21,20 @@ AI-powered statistical analysis assistant module UI Provides an interactive chat interface with AI for statistical analysis code generation } \examples{ +\dontrun{ +# Setup: Add API key to .Renviron file +# usethis::edit_r_environ() +# Add line: ANTHROPIC_API_KEY=your_actual_key_here +# Save and restart R + library(shiny) library(DT) library(survival) +# Example 1: Basic usage with auto-generated variable structure ui <- fluidPage( - fluidRow( - column(12, aiAssistantUI("ai")) - ) + titlePanel("AI Statistical Assistant"), + aiAssistantUI("ai") ) server <- function(input, output, session) { @@ -38,8 +44,60 @@ server <- function(input, output, session) { callModule(aiAssistant, "ai", data = data, data_label = data.label, - data_varStruct = NULL, - api_key = Sys.getenv("ANTHROPIC_API_KEY") + data_varStruct = NULL # Auto-generates variable structure + ) +} + +shinyApp(ui, server) + +# Example 2: With custom variable structure and analysis context +ui2 <- fluidPage( + titlePanel("Survival Analysis Assistant"), + aiAssistantUI("ai") +) + +server2 <- function(input, output, session) { + data <- reactive(colon) + data.label <- reactive(jstable::mk.lev(colon)) + + # Custom variable structure for survival analysis + var_struct <- reactive({ + list( + variable = names(colon), + Base = c("rx", "sex", "age", "obstruct", "nodes"), + Event = "status", + Time = "time" + ) + }) + + callModule(aiAssistant, "ai", + data = data, + data_label = data.label, + data_varStruct = var_struct, + analysis_context = reactive({ + "Colon cancer adjuvant chemotherapy trial (survival::colon). + Primary outcome: time to recurrence or death (status/time). + Treatment groups: Observation, Levamisole, Levamisole+5-FU." + }) + ) +} + +shinyApp(ui2, server2) + +# Example 3: Production deployment without API config UI +ui_prod <- fluidPage( + aiAssistantUI("ai", show_api_config = FALSE) +) + +server_prod <- function(input, output, session) { + # Relies entirely on .Renviron configuration + callModule(aiAssistant, "ai", + data = reactive(mtcars), + data_label = reactive(jstable::mk.lev(mtcars)), + show_api_config = FALSE ) } + +shinyApp(ui_prod, server_prod) +} } From 7016d1b315ebdb66b78a5ee70c1f02e595d02bf1 Mon Sep 17 00:00:00 2001 From: jaewoong heo Date: Thu, 11 Dec 2025 14:23:29 +0900 Subject: [PATCH 2/3] fix: summary view port fix --- R/aiAssistant.R | 53 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/R/aiAssistant.R b/R/aiAssistant.R index 6bb9bad3..fb720c97 100644 --- a/R/aiAssistant.R +++ b/R/aiAssistant.R @@ -1317,6 +1317,13 @@ aiAssistant <- function(input, output, session, data, data_label, result_info$value <- res result_info$message <- sprintf("Generated %d plots successfully. All plots are displayed in the Results panel.", length(res)) + # Summary objects (lm, glm, coxph, etc.) - check before list processing + } else if (any(grepl("^summary\\.", class(res)))) { + result_info$type <- "text" + # Store as character vector (each line separate) for proper rendering + result_info$value <- list(capture.output(print(res))) + result_info$message <- "Summary statistics generated successfully." + # Multiple tables (list of data frames or matrices) } else if (is.list(res) && length(res) > 0 && all(sapply(res, function(x) is.data.frame(x) || is.matrix(x)))) { @@ -2738,19 +2745,33 @@ Please fix the code to ensure it returns a proper result that can be displayed a } else if (current_type == "table") { DTOutput(session$ns("result_table")) } else { - # Text result + # Text result (already captured as character vector) tags$div( style = "max-height: 400px; overflow-y: auto;", tags$pre( - style = "font-size: 11px; white-space: pre-wrap;", - paste(capture.output(print(current_item$value)), collapse = "\n") + style = "font-size: 11px; white-space: pre-wrap; background: #f8f9fa; padding: 10px; border-radius: 4px;", + paste(current_item$value, collapse = "\n") ) ) } )) } - # text + # text (result is a list containing character vectors) + if (rtype == "text") { + text_content <- if (is.list(result) && length(result) > 0) { + paste(result[[1]], collapse = "\n") + } else { + paste(capture.output(print(result)), collapse = "\n") + } + + return(tags$pre( + style = "max-height: 400px; overflow-y: auto; font-size: 11px; background: #f8f9fa; padding: 10px; border-radius: 4px; white-space: pre-wrap;", + text_content + )) + } + + # Fallback for other types return(tags$pre( style = "max-height: 400px; overflow-y: auto; font-size: 11px;", paste(capture.output(print(result)), collapse = "\n") @@ -3141,8 +3162,30 @@ Please fix the code to ensure it returns a proper result that can be displayed a content_lines <- c(content_lines, result_text) } else if (rtype == "plot") { content_lines <- c(content_lines, "[Plot result - cannot be displayed in text format]") + } else if (rtype == "text") { + # Text results (already captured as character vector) + if (is.list(result) && length(result) > 0) { + content_lines <- c(content_lines, result[[1]]) + } else { + result_text <- capture.output(print(result)) + content_lines <- c(content_lines, result_text) + } + } else if (rtype == "mixed") { + # Mixed results - process each item + for (i in seq_along(result)) { + item <- result[[i]] + content_lines <- c(content_lines, sprintf("--- Result %d/%d ---", i, length(result))) + if (item$type == "table") { + result_text <- capture.output(print(as.data.frame(item$value))) + content_lines <- c(content_lines, result_text, "") + } else if (item$type == "plot") { + content_lines <- c(content_lines, "[Plot - cannot be displayed in text format]", "") + } else if (item$type == "text") { + content_lines <- c(content_lines, item$value, "") + } + } } else { - # Other results (text, list, etc.) + # Other results (fallback) result_text <- capture.output(print(result)) content_lines <- c(content_lines, result_text) } From d282eb4792024d5cf2fea26d9b7892e717a69efb Mon Sep 17 00:00:00 2001 From: jaewoong heo Date: Thu, 11 Dec 2025 15:55:21 +0900 Subject: [PATCH 3/3] docs: add ai assistant module guide --- AI_ASSISTANT_MODULE_GUIDE.ko.md | 409 ++++++++++++++++++++++++++++++++ AI_ASSISTANT_MODULE_GUIDE.md | 409 ++++++++++++++++++++++++++++++++ 2 files changed, 818 insertions(+) create mode 100644 AI_ASSISTANT_MODULE_GUIDE.ko.md create mode 100644 AI_ASSISTANT_MODULE_GUIDE.md diff --git a/AI_ASSISTANT_MODULE_GUIDE.ko.md b/AI_ASSISTANT_MODULE_GUIDE.ko.md new file mode 100644 index 00000000..d19a2957 --- /dev/null +++ b/AI_ASSISTANT_MODULE_GUIDE.ko.md @@ -0,0 +1,409 @@ +# AI Assistant 모듈 + +jsmodule 패키지를 위한 AI 기반 통계 분석 코드 생성 모듈입니다. + +## 개요 + +AI Assistant 모듈은 통계 분석을 위한 R 코드를 생성하는 대화형 채팅 인터페이스를 제공합니다. jsmodule의 gadget들과 완벽하게 통합되며 여러 AI 제공자(Anthropic Claude, OpenAI GPT, Google Gemini)를 지원합니다. + +## 빠른 시작 + +### 1. API 키 설정 + +`.Renviron` 파일에 API 키를 추가하세요: + +```r +# .Renviron 파일 열기 +usethis::edit_r_environ() + +# 다음 중 하나를 추가: +ANTHROPIC_API_KEY=your_key_here +OPENAI_API_KEY=your_key_here +GOOGLE_API_KEY=your_key_here + +# 저장 후 R 세션 재시작 +``` + +### 2. 기본 사용법 + +#### 옵션 A: jsBasicGadget과 함께 사용 + +```r +library(jsmodule) + +# AI Assistant가 포함된 gadget 실행 +jsBasicGadget() + +# "AI Assistant" 탭으로 이동 +``` + +#### 옵션 B: 독립 실행형 Shiny 앱 + +```r +library(shiny) +library(jsmodule) +library(survival) + +ui <- fluidPage( + titlePanel("AI Statistical Assistant"), + aiAssistantUI("ai") +) + +server <- function(input, output, session) { + data <- reactive(colon) + data.label <- reactive(jstable::mk.lev(colon)) + + callModule(aiAssistant, "ai", + data = data, + data_label = data.label + ) +} + +shinyApp(ui, server) +``` + +## 주요 기능 + +### 코드 생성 +- 통계 분석 코드 (회귀분석, 생존분석, 기술통계) +- 시각화 코드 (ggplot2, jskm, forestplot) +- 테이블 생성 (jstable, DT) +- jsmodule 규칙 및 모범 사례 준수 + +### 다양한 AI 제공자 +- **Anthropic Claude** (기본값): claude-3-7-sonnet, claude-3-5-sonnet, claude-3-opus +- **OpenAI GPT**: gpt-4o, gpt-4-turbo, gpt-3.5-turbo +- **Google Gemini**: gemini-2.0-flash-exp, gemini-1.5-pro, gemini-1.5-flash + +### 내보내기 옵션 +- **Word (.docx)**: 서식이 적용된 테이블 +- **PowerPoint (.pptx)**: 편집 가능한 벡터 그래픽 플롯 +- **Excel (.xlsx)**: 데이터가 보존된 테이블 +- **R Script (.R)**: 완전히 재현 가능한 코드 + +### 안전 기능 +- 샌드박스 코드 실행 (허용된 패키지만 사용) +- 실행 전 코드 검토 및 편집 +- AI 지원 오류 수정 +- 파일 시스템 및 네트워크 접근 불가 + +## 중요 사항 + +### 데이터 접근 +- AI는 `data` 파라미터를 통해 제공된 데이터만 접근 가능 +- 생성된 코드에서 데이터는 `out`으로 참조됨 +- 파일 업로드 데이터는 자동으로 reactive하게 처리됨 + +### 허용된 패키지 +생성된 코드는 다음 패키지만 사용할 수 있습니다: +``` +jstable, jskm, jsmodule, survival, ggplot2, ggpubr, +pROC, data.table, DT, gridExtra, GGally, forestploter, +MatchIt, timeROC +``` + +### 변수 구조 +모듈은 변수 구조 정보를 자동으로 생성합니다: +- Factor 변수 +- Numeric 변수 +- 사용자 정의 구조 (`data_varStruct` 파라미터로 제공 시) + +### API 키 우선순위 +1. `callModule()`의 명시적 `api_key` 인자 +2. UI 입력 (`show_api_config = TRUE`인 경우) +3. 환경 변수 (`.Renviron` 파일) + +### API 설정 모드 + +`show_api_config` 파라미터는 API 키 관리 방식을 제어합니다: + +#### `show_api_config = TRUE` (기본값) +- **사용 사례**: 개발, 개인 사용, 또는 사용자가 자신의 API 키를 제공하는 경우 +- **동작**: + - UI에 Settings 패널 표시 + - 사용자가 AI 제공자와 모델 선택 가능 + - 사용자가 인터페이스에서 직접 API 키 입력 가능 + - UI에 입력된 API 키가 `.Renviron` 파일보다 우선 +- **보안 참고**: UI에 입력된 API 키는 브라우저 메모리에만 저장되며 디스크에 저장되지 않음 +- **권장 사항**: 로컬 개발 및 단일 사용자 애플리케이션에 적합 + +```r +# 개발 모드 - 사용자가 UI에서 설정 가능 +aiAssistantUI("ai", show_api_config = TRUE) # 기본값 + +callModule(aiAssistant, "ai", + data = data, + data_label = data.label, + show_api_config = TRUE +) +``` + +#### `show_api_config = FALSE` +- **사용 사례**: 프로덕션 배포, 공유 애플리케이션, 또는 사전 구성된 환경 +- **동작**: + - Settings 패널 완전히 숨김 + - `.Renviron` 파일 또는 명시적 `api_key` 인자만 사용 + - API 설정을 위한 UI 요소 없음 +- **보안 참고**: 사용자가 API 키를 보거나 수정하는 것을 방지 +- **권장 사항**: 공유 API 키를 사용하는 프로덕션 배포에 필수 + +```r +# 프로덕션 모드 - .Renviron에서만 API 키 읽기 +aiAssistantUI("ai", show_api_config = FALSE) + +callModule(aiAssistant, "ai", + data = data, + data_label = data.label, + show_api_config = FALSE +) +``` + +## 고급 사용법 + +### 사용자 정의 변수 구조 + +```r +server <- function(input, output, session) { + data <- reactive(lung) + data.label <- reactive(jstable::mk.lev(lung)) + + # 변수 역할 정의 + var_struct <- reactive({ + list( + variable = names(lung), + Base = c("age", "sex", "ph.ecog"), + Event = "status", + Time = "time" + ) + }) + + callModule(aiAssistant, "ai", + data = data, + data_label = data.label, + data_varStruct = var_struct + ) +} +``` + +### 분석 컨텍스트 + +AI 응답을 개선하기 위해 배경 정보를 제공하세요: + +```r +callModule(aiAssistant, "ai", + data = data, + data_label = data.label, + analysis_context = reactive({ + "NCCTG 폐암 임상시험 데이터. + 주요 결과: 사망까지의 시간 (status/time). + 수행능력 점수(ph.ecog)를 예측 변수로 중점 분석." + }) +) +``` + +### 프로덕션 배포 + +프로덕션 환경에서는 API 설정 UI를 숨기세요: + +```r +ui <- fluidPage( + aiAssistantUI("ai", show_api_config = FALSE) +) + +server <- function(input, output, session) { + callModule(aiAssistant, "ai", + data = data, + data_label = data.label, + show_api_config = FALSE # .Renviron만 사용 + ) +} +``` + +## 문제 해결 + +### API 키를 찾을 수 없음 +**문제**: "API key not configured" 오류 +**해결책**: +1. `.Renviron` 파일에 올바른 변수명이 있는지 확인 +2. `.Renviron` 편집 후 R 세션 재시작 +3. 키가 유효한지 확인 (터미널에서 테스트: `Sys.getenv("ANTHROPIC_API_KEY")`) + +### 코드 실행 오류 +**문제**: 생성된 코드가 실행 실패 +**해결책**: +1. 자동 수정을 위해 "Ask AI to Fix" 버튼 클릭 +2. 실행 전 에디터에서 코드 검토 +3. 데이터에 필요한 변수가 있는지 확인 +4. 패키지가 설치되어 있는지 확인 + +### Summary 결과가 너무 조각남 +**문제**: `summary()` 결과가 여러 조각으로 나뉨 +**해결책**: 최신 버전에서 수정되었습니다. jsmodule 패키지를 업데이트하세요. + +### 텍스트 출력에 이스케이프 시퀀스 표시 +**문제**: `\n`이 줄바꿈 대신 그대로 보임 +**해결책**: 최신 버전에서 수정되었습니다. jsmodule 패키지를 업데이트하세요. + +## 모범 사례 + +### 1. 구체적인 질문하기 +❌ 나쁨: "이 데이터를 분석해줘" +✅ 좋음: "wt.loss를 결과변수로, age, sex, ph.ecog를 예측변수로 선형회귀분석 수행" + +### 2. 생성된 코드 검토 +"Run Code" 클릭 전에 항상 에디터에서 코드를 검토하세요 + +### 3. 컨텍스트 제공 +`analysis_context` 파라미터를 사용하여 AI에게 데이터에 대한 배경 정보를 제공하세요 + +### 4. 적절한 모델 사용 +- 간단한 작업: 빠른 모델 사용 (Sonnet, GPT-4o) +- 복잡한 분석: 고급 모델 사용 (Opus, GPT-4) + +### 5. 반복적 개선 +처음부터 다시 시작하기보다 후속 질문으로 코드를 개선하세요 + +## 제한사항 + +1. **외부 데이터 접근 불가**: 파일을 읽거나 데이터베이스에 연결할 수 없음 +2. **제한된 패키지 범위**: 허용된 패키지만 사용 가능 +3. **컨텍스트 윈도우**: 매우 긴 대화는 초기화가 필요할 수 있음 +4. **시각화 미리보기**: 일부 복잡한 플롯은 즉시 렌더링되지 않을 수 있음 +5. **통계 전문성**: AI는 코드를 제공하지 통계 컨설팅을 제공하지 않음 + +## 예제 + +### 예제 1: 기술통계 +``` +Q: "치료군(rx)별 기저 특성을 비교하는 Table 1을 만들어줘" +``` + +### 예제 2: 생존분석 +``` +Q: "time과 status를 생존 결과로, age, sex, ph.ecog를 보정하여 + Cox 회귀분석 수행" +``` + +### 예제 3: 시각화 +``` +Q: "치료군별로 층화한 Kaplan-Meier plot을 risk table과 함께 그려줘" +``` + +### 예제 4: 모델 진단 +``` +Q: "wt.loss ~ age + sex + ph.ecog 선형모델에서 VIF로 다중공선성 확인" +``` + +## 보안 고려사항 + +### 코드 실행 보안 +- **샌드박스 실행**: 제한된 환경에서 코드 실행 +- **파일 접근 불가**: 시스템의 파일을 읽거나 쓸 수 없음 +- **네트워크 접근 불가**: 외부 API 호출 불가 (AI 제공자 API 제외) +- **패키지 화이트리스트**: 승인된 패키지만 허용 + +### API 키 보안 + +**⚠️ 중요: API 키 처리 방식** + +**API 키 사용 방법**: +- API 키는 환경 변수(`.Renviron`) 또는 UI 입력에서 읽음 +- UI에 입력된 경우, 키는 현재 R 세션 메모리에만 존재 +- API 호출은 `httr` 패키지를 사용하여 AI 제공자 API로 전달 + +**오픈소스입니다**: +- 모든 코드는 https://github.com/jinseob2kim/jsmodule 에서 공개되어 감사 가능 +- 숨겨진 API 키 저장이나 전송 없음 +- 코드를 직접 검토할 수 있습니다 + +✅ **이 모듈이 API 키로 하지 않는 것**: +- 어디에도 저장하지 않음 +- 로그에 기록하지 않음 +- AI 제공자 외에는 전송하지 않음 + +✅ **AI 제공자로 전송되는 것**: +- 사용자의 질문과 프롬프트 +- 데이터 구조 정보 (변수명, 타입, 요약 통계) +- 이전 대화 기록 +- 생성된 코드 (오류 수정용) + +**전송되지 않는 것**: +- 원시 데이터 값 (질문에 명시적으로 포함하지 않는 한) +- 파일 시스템 정보 + +#### 배포 유형별 모범 사례 + +**개인/데스크톱 사용** (권장): +```r +# .Renviron에 API 키 저장 (사용자 홈 디렉토리) +# 사용자 계정에만 비공개로 유지됩니다 +ANTHROPIC_API_KEY=your_key_here +``` + +**팀/공유 사용**: +- 각 팀원이 `.Renviron`에 자신의 API 키를 사용해야 함 +- 개별 설정을 허용하려면 `show_api_config = TRUE` 설정 +- 사용자 간 API 키를 공유하지 마세요 + +**공개 웹 애플리케이션**: +- ⚠️ **권장하지 않음**: `show_api_config = TRUE`로 공개 배포하지 마세요 +- 공개 배포가 필요한 경우 다음 대안을 고려하세요: + 1. 서버 측 API 프록시 구현 (커스텀 백엔드 필요) + 2. 접근을 제한하는 인증 사용 + 3. 엄격한 사용량 할당 및 모니터링 설정 + +#### API 키 저장 위치 + +1. **`.Renviron` 파일** (개인 사용 권장): + - 위치: `~/.Renviron` (사용자 홈 디렉토리) + - 보안: 사용자 계정만 접근 가능 + - 지속성: R 세션 재시작 후에도 유지 + +2. **UI 입력** (개발용만): + - 위치: 브라우저 메모리 (임시) + - 보안: 브라우저 탭을 닫으면 사라짐 + - 지속성: 없음 - 매 세션마다 재입력 필요 + +3. **`api_key` 인자** (고급 사용): + - 위치: R 스크립트 또는 코드 + - 보안: ⚠️ 피하세요 - 코드에 키가 노출됨 + - 지속성: 코드가 저장된 위치에 따라 다름 + +#### 규정 준수 고려사항 + +민감한 데이터로 작업하는 경우: +1. ✅ 데이터 구조 및 변수명이 AI 제공자로 전송됨 +2. ✅ 통계 요약이 전송될 수 있음 +3. ⚠️ 질문에 실제 데이터 값을 포함하지 마세요 +4. ⚠️ 조직의 AI 사용 정책을 검토하세요 +5. ⚠️ 분석 전 데이터 익명화를 고려하세요 + +### 권장 보안 설정 + +**최대 보안을 위해**: +```r +# 1. .Renviron에 API 키 저장 (코드에 절대 포함하지 않음) +usethis::edit_r_environ() +# 추가: ANTHROPIC_API_KEY=your_key + +# 2. 프로덕션에서는 show_api_config = FALSE 사용 +aiAssistantUI("ai", show_api_config = FALSE) + +# 3. .Renviron을 버전 관리에 커밋하지 않음 +# .gitignore에 추가: +# .Renviron +# .Renviron.local + +# 4. API 키를 정기적으로 교체 (90일마다 권장) + +# 5. 제공자의 대시보드를 통해 API 사용량 모니터링 +``` + +## 지원 + +이슈나 기능 요청은 다음에서 제출해주세요: +https://github.com/jinseob2kim/jsmodule/issues + +## 라이선스 + +jsmodule 패키지 라이선스와 동일합니다. diff --git a/AI_ASSISTANT_MODULE_GUIDE.md b/AI_ASSISTANT_MODULE_GUIDE.md new file mode 100644 index 00000000..c91d0e06 --- /dev/null +++ b/AI_ASSISTANT_MODULE_GUIDE.md @@ -0,0 +1,409 @@ +# AI Assistant Module + +AI-powered statistical analysis code generation module for jsmodule package. + +## Overview + +The AI Assistant module provides an interactive chat interface that generates R code for statistical analysis. It integrates seamlessly with jsmodule's gadgets and supports multiple AI providers (Anthropic Claude, OpenAI GPT, Google Gemini). + +## Quick Start + +### 1. API Key Setup + +Add your API key to `.Renviron` file: + +```r +# Open .Renviron file +usethis::edit_r_environ() + +# Add one of the following lines: +ANTHROPIC_API_KEY=your_key_here +OPENAI_API_KEY=your_key_here +GOOGLE_API_KEY=your_key_here + +# Save and restart R session +``` + +### 2. Basic Usage + +#### Option A: Use with jsBasicGadget + +```r +library(jsmodule) + +# Launch gadget with AI Assistant included +jsBasicGadget() + +# Navigate to "AI Assistant" tab +``` + +#### Option B: Standalone Shiny App + +```r +library(shiny) +library(jsmodule) +library(survival) + +ui <- fluidPage( + titlePanel("AI Statistical Assistant"), + aiAssistantUI("ai") +) + +server <- function(input, output, session) { + data <- reactive(colon) + data.label <- reactive(jstable::mk.lev(colon)) + + callModule(aiAssistant, "ai", + data = data, + data_label = data.label + ) +} + +shinyApp(ui, server) +``` + +## Features + +### Code Generation +- Statistical analysis code (regression, survival analysis, descriptive statistics) +- Visualization code (ggplot2, jskm, forestplot) +- Table generation (jstable, DT) +- Follows jsmodule conventions and best practices + +### Multiple AI Providers +- **Anthropic Claude** (default): claude-3-7-sonnet, claude-3-5-sonnet, claude-3-opus +- **OpenAI GPT**: gpt-4o, gpt-4-turbo, gpt-3.5-turbo +- **Google Gemini**: gemini-2.0-flash-exp, gemini-1.5-pro, gemini-1.5-flash + +### Export Options +- **Word (.docx)**: Tables with formatted layout +- **PowerPoint (.pptx)**: Plots as editable vector graphics +- **Excel (.xlsx)**: Tables with data preservation +- **R Script (.R)**: Complete reproducible code + +### Safety Features +- Sandboxed code execution (only allowed packages) +- Pre-execution code review and editing +- Error handling with AI-assisted fixes +- No file system or network access + +## Important Notes + +### Data Access +- The AI can only access data provided through the `data` parameter +- Data is referred to as `out` in generated code +- File upload data is automatically reactive + +### Allowed Packages +Generated code can only use these packages: +``` +jstable, jskm, jsmodule, survival, ggplot2, ggpubr, +pROC, data.table, DT, gridExtra, GGally, forestploter, +MatchIt, timeROC +``` + +### Variable Structure +The module automatically generates variable structure information: +- Factor variables +- Numeric variables +- Custom structures (if provided via `data_varStruct` parameter) + +### API Key Resolution Order +1. Explicit `api_key` argument in `callModule()` +2. UI input (if `show_api_config = TRUE`) +3. Environment variables (`.Renviron` file) + +### API Configuration Modes + +The `show_api_config` parameter controls how API keys are managed: + +#### `show_api_config = TRUE` (Default) +- **Use Case**: Development, personal use, or when users provide their own API keys +- **Behavior**: + - Shows Settings panel in the UI + - Users can select AI provider and model + - Users can enter API key directly in the interface + - API key input takes precedence over `.Renviron` file +- **Security Note**: API keys entered in UI are only stored in browser memory and never saved to disk +- **Recommendation**: Suitable for local development and single-user applications + +```r +# Development mode - users can configure in UI +aiAssistantUI("ai", show_api_config = TRUE) # Default + +callModule(aiAssistant, "ai", + data = data, + data_label = data.label, + show_api_config = TRUE +) +``` + +#### `show_api_config = FALSE` +- **Use Case**: Production deployment, shared applications, or pre-configured environments +- **Behavior**: + - Hides Settings panel completely + - Only uses `.Renviron` file or explicit `api_key` argument + - No UI elements for API configuration +- **Security Note**: Prevents users from seeing or modifying API keys +- **Recommendation**: Mandatory for production deployments with shared API keys + +```r +# Production mode - API key from .Renviron only +aiAssistantUI("ai", show_api_config = FALSE) + +callModule(aiAssistant, "ai", + data = data, + data_label = data.label, + show_api_config = FALSE +) +``` + +## Advanced Usage + +### Custom Variable Structure + +```r +server <- function(input, output, session) { + data <- reactive(lung) + data.label <- reactive(jstable::mk.lev(lung)) + + # Define custom variable roles + var_struct <- reactive({ + list( + variable = names(lung), + Base = c("age", "sex", "ph.ecog"), + Event = "status", + Time = "time" + ) + }) + + callModule(aiAssistant, "ai", + data = data, + data_label = data.label, + data_varStruct = var_struct + ) +} +``` + +### Analysis Context + +Provide background information to improve AI responses: + +```r +callModule(aiAssistant, "ai", + data = data, + data_label = data.label, + analysis_context = reactive({ + "NCCTG lung cancer trial data. + Primary outcome: time to death (status/time). + Focus on performance status (ph.ecog) as predictor." + }) +) +``` + +### Production Deployment + +Hide API configuration UI for production: + +```r +ui <- fluidPage( + aiAssistantUI("ai", show_api_config = FALSE) +) + +server <- function(input, output, session) { + callModule(aiAssistant, "ai", + data = data, + data_label = data.label, + show_api_config = FALSE # Use only .Renviron + ) +} +``` + +## Troubleshooting + +### API Key Not Found +**Problem**: "API key not configured" error +**Solution**: +1. Check `.Renviron` file has correct variable name +2. Restart R session after editing `.Renviron` +3. Verify key is valid (test in terminal: `Sys.getenv("ANTHROPIC_API_KEY")`) + +### Code Execution Errors +**Problem**: Generated code fails to execute +**Solution**: +1. Click "Ask AI to Fix" button for automatic correction +2. Review code in editor before execution +3. Check data has required variables +4. Verify packages are installed + +### Summary Results Too Fragmented +**Problem**: `summary()` results split into many pieces +**Solution**: This is now fixed in the latest version. Update jsmodule package. + +### Text Output Shows Escape Sequences +**Problem**: `\n` visible instead of line breaks +**Solution**: This is now fixed in the latest version. Update jsmodule package. + +## Best Practices + +### 1. Be Specific in Questions +❌ Bad: "analyze this data" +✅ Good: "perform linear regression with wt.loss as outcome and age, sex, ph.ecog as predictors" + +### 2. Review Generated Code +Always review code in the editor before clicking "Run Code" + +### 3. Provide Context +Use `analysis_context` parameter to give AI background about your data + +### 4. Use Appropriate Model +- Use faster models (Sonnet, GPT-4o) for simple tasks +- Use advanced models (Opus, GPT-4) for complex analyses + +### 5. Iterative Refinement +Ask follow-up questions to refine code rather than starting over + +## Limitations + +1. **No External Data Access**: Cannot read files or connect to databases +2. **Limited Package Scope**: Only allowed packages can be used +3. **Context Window**: Very long conversations may need to be cleared +4. **Visualization Preview**: Some complex plots may not render immediately +5. **Statistical Expertise**: AI provides code, not statistical consulting + +## Examples + +### Example 1: Descriptive Statistics +``` +Q: "Create a Table 1 comparing baseline characteristics by treatment group (rx)" +``` + +### Example 2: Survival Analysis +``` +Q: "Perform Cox regression with time and status as survival outcome, + adjusting for age, sex, and ph.ecog" +``` + +### Example 3: Visualization +``` +Q: "Create a Kaplan-Meier plot stratified by treatment group with risk table" +``` + +### Example 4: Model Diagnostics +``` +Q: "Check VIF for multicollinearity in the linear model with wt.loss ~ age + sex + ph.ecog" +``` + +## Security Considerations + +### Code Execution Security +- **Sandboxed Execution**: Code runs in restricted environment +- **No File Access**: Cannot read/write files on system +- **No Network Access**: Cannot make external API calls (except AI provider APIs) +- **Package Whitelist**: Only approved packages allowed + +### API Key Security + +**⚠️ IMPORTANT: API Key Handling** + +**How API Keys are Used**: +- API keys are read from environment variables (`.Renviron`) or UI input +- When entered in UI, keys exist only in the current R session memory +- API calls are made using the `httr` package to AI provider APIs + +**This is Open Source**: +- All code is publicly available and auditable at https://github.com/jinseob2kim/jsmodule +- No hidden API key storage or transmission +- You can review the code yourself + +✅ **What this module does NOT do with API keys**: +- Never saves them anywhere +- Never logs them +- Never transmits them except to the AI provider + +✅ **What IS sent to AI providers**: +- Your prompts and questions +- Data structure information (variable names, types, sample statistics) +- Previous conversation history +- Generated code (for error fixing) + +**NOT sent**: +- Raw data values (unless explicitly included in your question) +- File system information + +#### Best Practices by Deployment Type + +**For Personal/Desktop Use** (Recommended): +```r +# Store API key in .Renviron (user's home directory) +# This keeps the key private to your user account +ANTHROPIC_API_KEY=your_key_here +``` + +**For Team/Shared Use**: +- Each team member should use their own API key in `.Renviron` +- Set `show_api_config = TRUE` to allow individual configuration +- Do NOT share API keys between users + +**For Public Web Applications**: +- ⚠️ **NOT RECOMMENDED**: Do not deploy with `show_api_config = TRUE` publicly +- If you must deploy publicly, consider these alternatives: + 1. Implement server-side API proxy (requires custom backend) + 2. Use authentication to limit access + 3. Set strict usage quotas and monitoring + +#### API Key Storage Locations + +1. **`.Renviron` file** (Recommended for personal use): + - Location: `~/.Renviron` (user home directory) + - Security: Only accessible by your user account + - Persistence: Survives R session restarts + +2. **UI Input** (Development only): + - Location: Browser memory (temporary) + - Security: Lost when browser tab closes + - Persistence: No - must re-enter each session + +3. **`api_key` argument** (Advanced use): + - Location: R script or code + - Security: ⚠️ Avoid - keys visible in code + - Persistence: Depends on where code is stored + +#### Compliance Considerations + +If you're working with sensitive data: +1. ✅ Data structure and variable names are sent to AI provider +2. ✅ Statistical summaries may be sent +3. ⚠️ Avoid including actual data values in questions +4. ⚠️ Review your organization's AI usage policy +5. ⚠️ Consider data anonymization before analysis + +### Recommended Security Setup + +**For Maximum Security**: +```r +# 1. Store API key in .Renviron (never in code) +usethis::edit_r_environ() +# Add: ANTHROPIC_API_KEY=your_key + +# 2. Use show_api_config = FALSE in production +aiAssistantUI("ai", show_api_config = FALSE) + +# 3. Never commit .Renviron to version control +# Add to .gitignore: +# .Renviron +# .Renviron.local + +# 4. Rotate API keys regularly (every 90 days recommended) + +# 5. Monitor API usage through provider's dashboard +``` + +## Support + +For issues or feature requests, please file an issue at: +https://github.com/jinseob2kim/jsmodule/issues + +## License + +Same as jsmodule package license.