File size: 6,067 Bytes
9cee884 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | # Output builders: the dynamic inputs of the question (answer_rows,
# reference_rows) and the parts of the outputs that the server renders
# (spending meter, stats line, gallery, bundle files). The look is in
# www/theme.css. They use sr(), upload_input() and IMAGE_ACCEPT of R/ui.R,
# which R finds when a builder runs.
# The answers of a question: a name and a description for each answer, with
# buttons to add and remove answers. Yes / no has the fixed names true and
# false, so it has no name inputs and no buttons.
answer_rows <- function(question) {
answers <- question$answers
fixed <- question$type == "noul"
range <- answer_range(question$type)
rows <- purrr::map(seq_along(answers), \(i) {
shiny::div(
class = "answer-row",
if (fixed) {
shiny::span(class = "k", names(answers)[i])
} else {
shiny::textInput(paste0("answer_name_", i), sr(paste("Name of answer", i)), names(answers)[i])
},
shiny::textAreaInput(
paste0("answer_desc_", i), sr(paste("Description of answer", i)), answers[[i]], rows = 3
),
if (!fixed && length(answers) > range[1]) {
shiny::actionButton(
paste0("answer_remove_", i), shiny::span(`aria-hidden` = "true", "\u00d7"),
class = "btn-quiet btn-icon", `aria-label` = paste("Remove answer", i)
)
}
)
})
shiny::tagList(
shiny::div(
class = "answer-head", `aria-hidden` = "true",
shiny::span(if (fixed) "Answer" else "Name"), shiny::span("Description")
),
rows,
if (!fixed && length(answers) < range[2]) {
shiny::actionButton("answer_add", "Add answer", class = "btn-quiet sm")
}
)
}
# The id of the reference input of each answer: "ref_" and the clean name
ref_input_id <- function(answers) {
paste0("ref_", janitor::make_clean_names(answers, case = "none"))
}
# One row for each answer: its reference images and a file input. `srcs` is a
# named list of image addresses. A score question takes no references.
reference_rows <- function(question, srcs = list()) {
if (question$type == "score") return(shiny::tagList())
answers <- names(question$answers)
ids <- ref_input_id(answers)
purrr::map(seq_along(answers), \(i) {
thumbs <- purrr::imap(srcs[[answers[i]]] %||% character(), \(src, j) {
shiny::img(src = src, alt = sprintf("Reference %d of %s", j, answers[i]))
})
shiny::div(
class = "ref-row",
shiny::span(class = "k-plain", answers[i]),
shiny::div(
shiny::div(class = "thumbs", thumbs),
upload_input(
ids[i], sr(paste("References of", answers[i])), IMAGE_ACCEPT,
multiple = TRUE, button = "Add"
)
)
)
}) |>
shiny::tagList()
}
# The spending meter of the visit
spend_meter_ui <- function(spent, limit = LIMIT_SESSION) {
shiny::div(
class = "spend",
shiny::div(
class = "spend-row", shiny::span("This visit"),
shiny::span(class = "mono", sprintf("$%.2f of $%.2f", spent, limit))
),
shiny::div(
class = "meter", `aria-hidden` = "true",
shiny::tags$i(style = sprintf("width:%.0f%%", min(100, 100 * spent / limit)))
)
)
}
# The stats line of answer_stats(); `note` is a short tag such as "Saved run"
stats_ui <- function(stats, mode, note = NULL) {
item <- \(value, label) {
shiny::span(class = "stat", shiny::span(class = "v", value), shiny::span(class = "l", label))
}
shiny::div(
class = "stats",
if (!is.na(stats$auc)) item(sprintf("%.2f", stats$auc), "AUC"),
if (!is.na(stats$accuracy)) item(sprintf("%.2f", stats$accuracy), "accuracy"),
item(stats$n, if (mode == "image") "images" else "texts"),
item(sprintf("$%.3f", stats$cost), "cost"),
if (!is.null(note)) shiny::span(class = "chip", note)
)
}
# The gallery: one tile for each row of answers_table(), with the probability
# of the target as a number and a bar, the answer and the label. `src` holds
# the address of the image of each row.
gallery_ui <- function(table, target, src) {
tiles <- purrr::map(seq_len(nrow(table)), \(i) {
row <- table[i, ]
p <- row$p_target
caption <- c(
if (!is.na(row$answer)) paste("answer", row$answer),
if (!is.na(row$label)) paste("label", row$label)
)
shiny::tags$figure(
class = "tile",
shiny::img(src = src[i], alt = alt_text(row, target), loading = "lazy"),
shiny::tags$figcaption(
if (is.na(p)) {
shiny::div(class = "t", if (is.na(row$problem)) "No answer" else row$problem)
} else {
shiny::div(
class = "p", sprintf("%.2f", p),
shiny::span(
class = "bar", `aria-hidden` = "true",
shiny::tags$i(style = sprintf("width:%.0f%%", 100 * p))
)
)
},
shiny::div(class = "t", paste(caption, collapse = " \u00b7 "))
)
)
})
shiny::div(class = "gallery", tiles)
}
# The files of the bundle, with one line on each. `files` are the names in the
# zip file (a folder ends in "/").
BUNDLE_FILE_TEXT <- c(
"\\.R$" = "The code above.",
"-setup\\.json$" = "Model, question, references and image settings. Read it with read_setup().",
"-answers\\.csv$" = "Each input: answer, probabilities, label, tokens, seconds.",
"^README\\.md$" = "Versions, date, install, key and citation. Import reopens the setup here.",
"^references/?$" = "The reference images of the run.",
"^data\\.csv$" = "Your texts. The code reads them."
)
bundle_files_ui <- function(files, zip_name) {
describe <- \(f) {
hit <- purrr::detect_index(names(BUNDLE_FILE_TEXT), \(pattern) stringr::str_detect(f, pattern))
if (hit == 0) "" else BUNDLE_FILE_TEXT[[hit]]
}
shiny::tagList(
shiny::h3(
class = "eyebrow", shiny::tags$b(`aria-hidden` = "true", "\u203a"), " Bundle \u00b7 ", zip_name
),
shiny::tags$ul(
class = "files",
purrr::map(files, \(f) {
shiny::tags$li(class = "file", shiny::div(class = "n", f), shiny::div(class = "d", describe(f)))
})
)
)
}
|