# quickgen_chart_ui.R library(shinyjs) library(shinyAce) ## ==================== 右下角浮框 ==================== ui_chart_progress <- tags$div( id = "chart_progress_box", style = "display:none; position:fixed; bottom:15px; right:15px; width:220px; z-index:9999; background:#f5f5f5; color:#333; border:1px solid #337ab7; border-radius:4px; padding:10px 15px; box-shadow:0 2px 8px rgba(0,0,0,.25);", tags$strong(i18n$t("AI generating...")), tags$div(class = "progress", tags$div(class = "progress-bar progress-bar-striped active", style = "width:100%")) ) ## ======== 警告弹窗======== ui_chart_warn <- tags$div( id = "chart_warn_box", style = "display:none; position:fixed; bottom:15px; right:15px; width:220px; z-index:9999; background:#fff3cd; color:#856404; border:1px solid #ffeaa7; border-radius:4px; padding:10px 15px; box-shadow:0 2px 8px rgba(0,0,0,.25);", tags$strong(i18n$t("Warning:Please enter a request related to descriptive statistics or visualization.")) ) ## ==================== 统一入口 ==================== output$quickgen_chart <- renderUI({ stat_tab_panel( menu = i18n$t("Oneclick generation > AI assistance"), tool = i18n$t("AI generates descriptive statistics(chart)"), tool_ui = "chart_main_ui", output_panels = tabPanel( title = i18n$t("Chart Assistant"), value = "chart_panel", uiOutput("chart_result_area") ) ) }) ## ==================== 右侧 Chart 面板 ==================== output$chart_main_ui <- renderUI({ tagList( useShinyjs(), ui_chart_progress, ui_chart_warn, wellPanel( i18n$t("Describe your analysis request"), returnTextAreaInput("chart_prompt", label = NULL, placeholder = i18n$t("e. g. Please help me generate a cross frequency histogram of sex and smoker"), rows = 4, value = state_init("chart_prompt", "")), fluidRow( column(6, uiOutput("ui_chart_submit")), column(6, uiOutput("chart_loading")) ) ), wellPanel( i18n$t("Generated R code"), uiOutput("chart_r_code_block"), fluidRow( column(6, actionButton("chart_run_code", i18n$t("Run code"), icon = icon("play"), class = "btn-success")), column(6, actionButton("chart_edit_code", i18n$t("Edit code"), icon = icon("edit"), class = "btn-default")) ) ), help_and_report( modal_title = i18n$t("AI generates descriptive statistics(chart)"), fun_name = "quickgen_chart", help_file = inclMD(file.path(getOption("radiant.path.quickgen"), "app/tools/help/quickgen_chart.md")), lic = "by-sa" ) ) }) ## ==================== 控件渲染 ==================== output$ui_chart_submit <- renderUI({ req(input$dataset) actionButton("chart_submit", i18n$t("Send"), icon = icon("magic"), class = "btn-primary") }) output$chart_loading <- renderUI({ if (isTRUE(r_values$chart_loading)) tags$div(class = "progress", tags$div(class = "progress-bar progress-bar-striped active", style = "width:100%", i18n$t("Calling chart generation model..."))) }) ## ==================== reactiveValues ==================== r_values <- reactiveValues( chart_r_code = "", chart_result_type = "text", chart_result_ready = FALSE, chart_loading = FALSE ) ## ==================== 生成代码 ==================== observeEvent(input$chart_submit, { if (is.empty(input$chart_prompt)) return(showNotification(i18n$t("Please enter an analysis request"), type = "error")) r_values$chart_loading <- TRUE shinyjs::show("chart_progress_box") # 显示右下角进度框 on.exit({ r_values$chart_loading <- FALSE shinyjs::hide("chart_progress_box") # 无论成功失败都隐藏 }) res <- try(do.call(chart_generate, list(prompt = input$chart_prompt, dataset = input$dataset, envir = r_data)), silent = TRUE) if (inherits(res, "try-error")) { showNotification(paste(i18n$t("Chart API error:"), res), type = "error") return() } r_values$chart_r_code <- res$r_code r_values$chart_result_type <- res$type r_values$chart_result_ready <- FALSE r_values$auto_run <- res$auto_run if (res$type == "empty") { shinyjs::show("chart_warn_box") shinyjs::delay(3000, shinyjs::hide("chart_warn_box")) return() } if (isTRUE(r_values$auto_run)) shinyjs::click("chart_run_code") }) ## ==================== 显示 R 代码 ==================== output$chart_r_code_block <- renderUI({ codes <- r_values$chart_r_code tags$pre(codes, style = "background:#f5f5f5; padding:10px; border-radius:4px; font-family:monospace; white-space:pre-wrap; min-height:100px;") }) ## ==================== 运行代码 ==================== observeEvent(input$chart_run_code, { shinyjs::hide("chart_progress_box") if (is.empty(r_values$chart_r_code)) return() if (trimws(r_values$chart_r_code) == "" || identical(r_values$chart_result_type, "empty")) { shinyjs::show("chart_warn_box") shinyjs::delay(3000, shinyjs::hide("chart_warn_box")) return() } tryCatch({ result <- do.call(chart_run_code, list(r_code = r_values$chart_r_code, envir = r_data)) r_data$chart_temp_result <- result if (inherits(result, "gg") || inherits(result, "ggplot")) { r_values$chart_result_type <- "plot" output$chart_result_plot <- renderPlot(print(result)) } else if (is.data.frame(result) || is.matrix(result)) { r_values$chart_result_type <- "table" output$chart_result_table <- DT::renderDataTable( DT::datatable(result, options = list(scrollX = TRUE, pageLength = 10)), server = FALSE ) } else { r_values$chart_result_type <- "text" output$chart_result_text <- renderText(capture.output(print(result))) } r_values$chart_result_ready <- TRUE }, error = function(e) { r_values$chart_result_type <- "error" output$chart_result_error <- renderText(paste0(i18n$t("Error: "), e$message)) r_values$chart_result_ready <- TRUE showNotification(paste0(i18n$t("Run code error: "), e$message), type = "error", duration = NULL) }) }, ignoreInit = TRUE) ## ======== 结果展示区======== output$chart_result_area <- renderUI({ req(r_values$chart_result_ready) tagList( conditionalPanel( condition = "output.chart_result_type == 'plot'", download_link("dlp_chart_plot"), br(), plotOutput("chart_result_plot", width = "100%", height = "500px") ), conditionalPanel( condition = "output.chart_result_type == 'table'", download_link("dlp_chart_table"), br(), DT::dataTableOutput("chart_result_table") ), conditionalPanel( condition = "output.chart_result_type == 'text' || output.chart_result_type == 'error'", verbatimTextOutput("chart_result_text") ) ) }) output$chart_result_type <- reactive({ r_values$chart_result_type }) outputOptions(output, "chart_result_type", suspendWhenHidden = FALSE) ## ==================== 编辑代码模态框 ==================== observeEvent(input$chart_edit_code, { showModal( modalDialog( title = i18n$t("Edit R Code"), size = "l", footer = tagList( actionButton("chart_save_code", i18n$t("Save Changes"), class = "btn-primary"), modalButton(i18n$t("Cancel")) ), aceEditor( "chart_code_editor", mode = "r", theme = getOption("radiant.ace_theme", "tomorrow"), wordWrap = TRUE, value = r_values$chart_r_code, placeholder = i18n$t("Edit the generated R code here..."), vimKeyBinding = getOption("radiant.ace_vim.keys", FALSE), tabSize = getOption("radiant.ace_tabSize", 2), useSoftTabs = getOption("radiant.ace_useSoftTabs", TRUE), showInvisibles = getOption("radiant.ace_showInvisibles", FALSE), autoScrollEditorIntoView = TRUE, minLines = 15, maxLines = 30 ) ) ) }) ## ==================== 保存代码 ==================== observeEvent(input$chart_save_code, { r_values$chart_r_code <- input$chart_code_editor r_values$auto_run <- FALSE removeModal() }) ## ==================== PNG 下载处理器 ==================== dlp_chart_plot <- function(path) { result <- r_data$chart_temp_result if (inherits(result, "gg") || inherits(result, "ggplot")) { png(path, width = 800, height = 500, res = 96) print(result) dev.off() } else { png(path, width = 400, height = 400) plot(1, type = "n", axes = FALSE, xlab = "", ylab = "") text(1, 1, "No plot available", cex = 1.5) dev.off() } } download_handler( id = "dlp_chart_plot", fun = dlp_chart_plot, fn = function() paste0("plot_", Sys.Date()), type = "png", caption = i18n$t("Save chart-generated plot") ) # ======== 表格 CSV 下载处理器 ======== dlp_chart_table <- function(path) { result <- r_data$chart_temp_result if (is.data.frame(result)) { df <- result } else if (is.table(result)) { df <- as.data.frame(result, stringsAsFactors = FALSE) } else if (is.matrix(result)) { df <- as.data.frame(result, stringsAsFactors = FALSE) if (!is.null(rownames(result))) { df <- cbind(row_name = rownames(result), df, row.names = NULL) } } else { df <- data.frame(msg = "No valid table available") } write.csv(df, file = path, row.names = FALSE, fileEncoding = "UTF-8") } download_handler( id = "dlp_chart_table", fun = dlp_chart_table, fn = function() paste0("table_", Sys.Date()), type = "csv", caption = i18n$t("Save chart-generated table") ) ## ==================== 报告 / 截图 ==================== chart_report <- function() {} observeEvent(input$chart_report, chart_report()) observeEvent(input$chart_screenshot, radiant_screenshot_modal("modal_chart_screenshot")) observeEvent(input$modal_chart_screenshot, { chart_report(); removeModal() })