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)))
      })
    )
  )
}