completion-kit 0.20.3 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/javascripts/completion_kit/application.js +142 -0
- data/app/assets/stylesheets/completion_kit/application.css +124 -3
- data/app/controllers/completion_kit/metrics_controller.rb +10 -1
- data/app/controllers/completion_kit/runs_controller.rb +1 -1
- data/app/helpers/completion_kit/application_helper.rb +42 -0
- data/app/models/completion_kit/run.rb +3 -3
- data/app/services/completion_kit/mcp_tools/judges.rb +1 -1
- data/app/services/completion_kit/mcp_tools/prompts.rb +2 -2
- data/app/services/completion_kit/mcp_tools/runs.rb +1 -1
- data/app/views/completion_kit/api_reference/_body.html.erb +15 -1
- data/app/views/completion_kit/metrics/_check_spec.html.erb +5 -5
- data/app/views/completion_kit/metrics/_form.html.erb +38 -37
- data/app/views/completion_kit/runs/_form.html.erb +2 -2
- data/app/views/completion_kit/runs/_row.html.erb +1 -1
- data/app/views/completion_kit/runs/_status_header.html.erb +1 -1
- data/app/views/completion_kit/shared/_branded_select.html.erb +18 -0
- data/lib/completion_kit/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5db8eacad4c6487a87d970c4b942657ad5033b0647a846bdfba2a7c9548a54b0
|
|
4
|
+
data.tar.gz: 956456ed6c96d122283c6a437a91c062323153bee1e53ec32d10bae56bf2636c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e8b1f1e18e33a4875944bd779e107280b83acfc231cc4940b4f6fdcdd30d17d093c046fc0fb5836c2675c728681277006ccc54d9d0d72886ac09117d7c924c0d
|
|
7
|
+
data.tar.gz: c82ff9429c534d82ec3ca7b0acdcfa8ec1d50dfea091b4b04167a3dc65289b06bd3f4a5e2168654cbfb2a127fdd2fac69e77168847d899edd22b136e382e2560
|
|
@@ -210,6 +210,76 @@ document.addEventListener("click", function(e) {
|
|
|
210
210
|
});
|
|
211
211
|
});
|
|
212
212
|
|
|
213
|
+
var CK_CHECK_FIELDS = {
|
|
214
|
+
contains: ["value", "case_sensitive", "trim"],
|
|
215
|
+
not_contains: ["value", "case_sensitive", "trim"],
|
|
216
|
+
equals: ["value", "case_sensitive", "trim"],
|
|
217
|
+
regex: ["pattern", "case_sensitive", "multiline"],
|
|
218
|
+
valid_json: [],
|
|
219
|
+
json_path_equals: ["json_path", "expected"],
|
|
220
|
+
length_bounds: ["min", "max"],
|
|
221
|
+
no_refusal: []
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
function ckApplyCheckFields(scope) {
|
|
225
|
+
if (!scope) return;
|
|
226
|
+
var kindSelect = scope.querySelector('[name="metric[check_config][check_kind]"]');
|
|
227
|
+
if (!kindSelect) return;
|
|
228
|
+
var visible = CK_CHECK_FIELDS[kindSelect.value];
|
|
229
|
+
var targetSelect = scope.querySelector('[name="metric[check_config][target]"]');
|
|
230
|
+
var targetIsJsonPath = !!(targetSelect && targetSelect.value === "json_path");
|
|
231
|
+
scope.querySelectorAll("[data-ck-check-field]").forEach(function(field) {
|
|
232
|
+
var key = field.getAttribute("data-ck-check-field");
|
|
233
|
+
var show;
|
|
234
|
+
if (key === "target_path") {
|
|
235
|
+
show = targetIsJsonPath;
|
|
236
|
+
} else if (!visible) {
|
|
237
|
+
show = true;
|
|
238
|
+
} else {
|
|
239
|
+
show = visible.indexOf(key) !== -1;
|
|
240
|
+
}
|
|
241
|
+
field.hidden = !show;
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function ckApplyMetricType(group) {
|
|
246
|
+
var checked = group.querySelector('input[type="radio"]:checked');
|
|
247
|
+
if (!checked) return;
|
|
248
|
+
var value = checked.value;
|
|
249
|
+
var scope = group.closest("form") || document;
|
|
250
|
+
scope.querySelectorAll("[data-ck-metric-editor]").forEach(function(editor) {
|
|
251
|
+
var active = editor.getAttribute("data-ck-metric-editor") === value;
|
|
252
|
+
editor.hidden = !active;
|
|
253
|
+
editor.querySelectorAll("input, select, textarea").forEach(function(field) {
|
|
254
|
+
field.disabled = !active;
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
ckApplyCheckFields(scope);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
document.addEventListener("turbo:load", function() {
|
|
261
|
+
document.querySelectorAll("[data-ck-metric-type]").forEach(function(group) {
|
|
262
|
+
ckApplyMetricType(group);
|
|
263
|
+
});
|
|
264
|
+
document.querySelectorAll('[data-ck-metric-editor="check"]').forEach(function(editor) {
|
|
265
|
+
ckApplyCheckFields(editor);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
document.addEventListener("change", function(e) {
|
|
270
|
+
var target = e.target;
|
|
271
|
+
if (!target || !target.closest) return;
|
|
272
|
+
var group = target.closest("[data-ck-metric-type]");
|
|
273
|
+
if (group && target.type === "radio") {
|
|
274
|
+
ckApplyMetricType(group);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (target.name === "metric[check_config][check_kind]" || target.name === "metric[check_config][target]") {
|
|
278
|
+
var scope = target.closest('[data-ck-metric-editor="check"]') || target.closest("form");
|
|
279
|
+
ckApplyCheckFields(scope);
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
213
283
|
document.addEventListener("click", function(e) {
|
|
214
284
|
var btn = e.target.closest("[data-ck-apply]");
|
|
215
285
|
if (!btn) return;
|
|
@@ -226,3 +296,75 @@ document.addEventListener("click", function(e) {
|
|
|
226
296
|
btn.textContent = "Applied ✓";
|
|
227
297
|
field.focus({ preventScroll: true });
|
|
228
298
|
});
|
|
299
|
+
|
|
300
|
+
function ckSelectClose(sel) {
|
|
301
|
+
if (!sel.classList.contains("is-open")) return;
|
|
302
|
+
sel.classList.remove("is-open");
|
|
303
|
+
var menu = sel.querySelector("[data-ck-select-menu]");
|
|
304
|
+
var trigger = sel.querySelector("[data-ck-select-trigger]");
|
|
305
|
+
if (menu) menu.hidden = true;
|
|
306
|
+
if (trigger) trigger.setAttribute("aria-expanded", "false");
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function ckSelectOpen(sel) {
|
|
310
|
+
document.querySelectorAll("[data-ck-select].is-open").forEach(ckSelectClose);
|
|
311
|
+
sel.classList.add("is-open");
|
|
312
|
+
var menu = sel.querySelector("[data-ck-select-menu]");
|
|
313
|
+
var trigger = sel.querySelector("[data-ck-select-trigger]");
|
|
314
|
+
if (menu) menu.hidden = false;
|
|
315
|
+
if (trigger) trigger.setAttribute("aria-expanded", "true");
|
|
316
|
+
var current = menu.querySelector('[aria-selected="true"]') || menu.querySelector('[role="option"]');
|
|
317
|
+
if (current) current.focus();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function ckSelectChoose(sel, option) {
|
|
321
|
+
var input = sel.querySelector("[data-ck-select-value]");
|
|
322
|
+
var label = sel.querySelector("[data-ck-select-label]");
|
|
323
|
+
var text = option.querySelector("[data-ck-select-text]");
|
|
324
|
+
sel.querySelectorAll('[role="option"]').forEach(function(o) {
|
|
325
|
+
o.setAttribute("aria-selected", o === option ? "true" : "false");
|
|
326
|
+
});
|
|
327
|
+
input.value = option.getAttribute("data-value");
|
|
328
|
+
if (text) label.textContent = text.textContent;
|
|
329
|
+
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
330
|
+
ckSelectClose(sel);
|
|
331
|
+
var trigger = sel.querySelector("[data-ck-select-trigger]");
|
|
332
|
+
if (trigger) trigger.focus();
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
document.addEventListener("click", function(e) {
|
|
336
|
+
var trigger = e.target.closest("[data-ck-select-trigger]");
|
|
337
|
+
if (trigger) {
|
|
338
|
+
var sel = trigger.closest("[data-ck-select]");
|
|
339
|
+
if (sel.classList.contains("is-open")) { ckSelectClose(sel); } else { ckSelectOpen(sel); }
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
var option = e.target.closest('[data-ck-select] [role="option"]');
|
|
343
|
+
if (option) {
|
|
344
|
+
ckSelectChoose(option.closest("[data-ck-select]"), option);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
document.querySelectorAll("[data-ck-select].is-open").forEach(function(sel) {
|
|
348
|
+
if (!sel.contains(e.target)) ckSelectClose(sel);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
document.addEventListener("keydown", function(e) {
|
|
353
|
+
var sel = e.target.closest("[data-ck-select]");
|
|
354
|
+
if (!sel) return;
|
|
355
|
+
var trigger = sel.querySelector("[data-ck-select-trigger]");
|
|
356
|
+
var options = Array.prototype.slice.call(sel.querySelectorAll('[role="option"]'));
|
|
357
|
+
var open = sel.classList.contains("is-open");
|
|
358
|
+
if (e.target === trigger && !open) {
|
|
359
|
+
if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") { e.preventDefault(); ckSelectOpen(sel); }
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (!open) return;
|
|
363
|
+
var idx = options.indexOf(document.activeElement);
|
|
364
|
+
if (e.key === "ArrowDown") { e.preventDefault(); (options[idx + 1] || options[0]).focus(); }
|
|
365
|
+
else if (e.key === "ArrowUp") { e.preventDefault(); (options[idx - 1] || options[options.length - 1]).focus(); }
|
|
366
|
+
else if (e.key === "Home") { e.preventDefault(); options[0].focus(); }
|
|
367
|
+
else if (e.key === "End") { e.preventDefault(); options[options.length - 1].focus(); }
|
|
368
|
+
else if (e.key === "Enter" || e.key === " ") { e.preventDefault(); if (options[idx]) ckSelectChoose(sel, options[idx]); }
|
|
369
|
+
else if (e.key === "Escape") { e.preventDefault(); ckSelectClose(sel); trigger.focus(); }
|
|
370
|
+
});
|
|
@@ -1922,6 +1922,13 @@ label.ck-checkbox input {
|
|
|
1922
1922
|
cursor: pointer;
|
|
1923
1923
|
}
|
|
1924
1924
|
|
|
1925
|
+
.ck-radio-info {
|
|
1926
|
+
width: 16px;
|
|
1927
|
+
height: 16px;
|
|
1928
|
+
color: var(--ck-muted);
|
|
1929
|
+
cursor: help;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1925
1932
|
.ck-field-row {
|
|
1926
1933
|
display: flex;
|
|
1927
1934
|
gap: 1rem;
|
|
@@ -1974,6 +1981,117 @@ label.ck-checkbox input {
|
|
|
1974
1981
|
color: var(--ck-text);
|
|
1975
1982
|
}
|
|
1976
1983
|
|
|
1984
|
+
.ck-select {
|
|
1985
|
+
position: relative;
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
.ck-select__trigger {
|
|
1989
|
+
width: 100%;
|
|
1990
|
+
display: flex;
|
|
1991
|
+
align-items: center;
|
|
1992
|
+
justify-content: space-between;
|
|
1993
|
+
gap: 0.5rem;
|
|
1994
|
+
padding: 0.65rem 0.85rem;
|
|
1995
|
+
border: 1px solid var(--ck-line-strong);
|
|
1996
|
+
border-radius: var(--ck-radius);
|
|
1997
|
+
background: var(--ck-bg);
|
|
1998
|
+
color: var(--ck-text);
|
|
1999
|
+
font-family: var(--ck-mono);
|
|
2000
|
+
font-size: 0.9rem;
|
|
2001
|
+
text-align: left;
|
|
2002
|
+
cursor: pointer;
|
|
2003
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
.ck-select__label {
|
|
2007
|
+
overflow: hidden;
|
|
2008
|
+
text-overflow: ellipsis;
|
|
2009
|
+
white-space: nowrap;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
.ck-select__trigger:focus-visible,
|
|
2013
|
+
.ck-select.is-open .ck-select__trigger {
|
|
2014
|
+
outline: none;
|
|
2015
|
+
border-color: var(--ck-accent);
|
|
2016
|
+
box-shadow: 0 0 0 3px var(--ck-accent-soft);
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
.ck-select__chevron {
|
|
2020
|
+
display: inline-flex;
|
|
2021
|
+
flex-shrink: 0;
|
|
2022
|
+
color: var(--ck-muted);
|
|
2023
|
+
transition: transform 0.15s;
|
|
2024
|
+
}
|
|
2025
|
+
|
|
2026
|
+
.ck-select__chevron svg {
|
|
2027
|
+
width: 1rem;
|
|
2028
|
+
height: 1rem;
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
.ck-select.is-open .ck-select__chevron {
|
|
2032
|
+
transform: rotate(180deg);
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
.ck-select__menu {
|
|
2036
|
+
position: absolute;
|
|
2037
|
+
z-index: 30;
|
|
2038
|
+
left: 0;
|
|
2039
|
+
right: 0;
|
|
2040
|
+
top: calc(100% + 0.35rem);
|
|
2041
|
+
margin: 0;
|
|
2042
|
+
padding: 0.3rem;
|
|
2043
|
+
list-style: none;
|
|
2044
|
+
background: var(--ck-surface);
|
|
2045
|
+
border: 1px solid var(--ck-line-strong);
|
|
2046
|
+
border-radius: var(--ck-radius-lg);
|
|
2047
|
+
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.45);
|
|
2048
|
+
max-height: 18rem;
|
|
2049
|
+
overflow-y: auto;
|
|
2050
|
+
font-family: var(--ck-mono);
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
.ck-select__option {
|
|
2054
|
+
display: flex;
|
|
2055
|
+
align-items: center;
|
|
2056
|
+
gap: 0.5rem;
|
|
2057
|
+
padding: 0.5rem 0.6rem;
|
|
2058
|
+
border-radius: var(--ck-radius);
|
|
2059
|
+
color: var(--ck-text);
|
|
2060
|
+
font-size: 0.9rem;
|
|
2061
|
+
cursor: pointer;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
.ck-select__option:hover,
|
|
2065
|
+
.ck-select__option:focus-visible {
|
|
2066
|
+
outline: none;
|
|
2067
|
+
background: var(--ck-surface-hover);
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
.ck-select__option[aria-selected="true"] {
|
|
2071
|
+
color: var(--ck-accent);
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
.ck-select__tick {
|
|
2075
|
+
display: inline-flex;
|
|
2076
|
+
flex-shrink: 0;
|
|
2077
|
+
width: 1rem;
|
|
2078
|
+
color: var(--ck-accent);
|
|
2079
|
+
opacity: 0;
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
.ck-select__tick svg {
|
|
2083
|
+
width: 1rem;
|
|
2084
|
+
height: 1rem;
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
.ck-select__option[aria-selected="true"] .ck-select__tick {
|
|
2088
|
+
opacity: 1;
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
.ck-select__text {
|
|
2092
|
+
flex: 1;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
1977
2095
|
.ck-section-title {
|
|
1978
2096
|
font-size: 1.05rem;
|
|
1979
2097
|
font-weight: 600;
|
|
@@ -3238,7 +3356,8 @@ select.ck-input {
|
|
|
3238
3356
|
#ck-tab-metric-groups:checked ~ .ck-api-tabs__nav label[for="ck-tab-metric-groups"],
|
|
3239
3357
|
#ck-tab-agreements:checked ~ .ck-api-tabs__nav label[for="ck-tab-agreements"],
|
|
3240
3358
|
#ck-tab-tags:checked ~ .ck-api-tabs__nav label[for="ck-tab-tags"],
|
|
3241
|
-
#ck-tab-providers:checked ~ .ck-api-tabs__nav label[for="ck-tab-providers"]
|
|
3359
|
+
#ck-tab-providers:checked ~ .ck-api-tabs__nav label[for="ck-tab-providers"],
|
|
3360
|
+
#ck-tab-imports:checked ~ .ck-api-tabs__nav label[for="ck-tab-imports"] {
|
|
3242
3361
|
color: var(--ck-accent);
|
|
3243
3362
|
background: var(--ck-surface-soft);
|
|
3244
3363
|
border-left-color: var(--ck-accent);
|
|
@@ -3253,7 +3372,8 @@ select.ck-input {
|
|
|
3253
3372
|
#ck-tab-metric-groups:checked ~ .ck-api-tabs__panels .ck-api-tabs__panel:nth-child(7),
|
|
3254
3373
|
#ck-tab-agreements:checked ~ .ck-api-tabs__panels .ck-api-tabs__panel:nth-child(8),
|
|
3255
3374
|
#ck-tab-tags:checked ~ .ck-api-tabs__panels .ck-api-tabs__panel:nth-child(9),
|
|
3256
|
-
#ck-tab-providers:checked ~ .ck-api-tabs__panels .ck-api-tabs__panel:nth-child(10)
|
|
3375
|
+
#ck-tab-providers:checked ~ .ck-api-tabs__panels .ck-api-tabs__panel:nth-child(10),
|
|
3376
|
+
#ck-tab-imports:checked ~ .ck-api-tabs__panels .ck-api-tabs__panel:nth-child(11) {
|
|
3257
3377
|
display: block;
|
|
3258
3378
|
}
|
|
3259
3379
|
|
|
@@ -3295,7 +3415,8 @@ select.ck-input {
|
|
|
3295
3415
|
#ck-tab-metric-groups:checked ~ .ck-api-tabs__nav label[for="ck-tab-metric-groups"],
|
|
3296
3416
|
#ck-tab-agreements:checked ~ .ck-api-tabs__nav label[for="ck-tab-agreements"],
|
|
3297
3417
|
#ck-tab-tags:checked ~ .ck-api-tabs__nav label[for="ck-tab-tags"],
|
|
3298
|
-
#ck-tab-providers:checked ~ .ck-api-tabs__nav label[for="ck-tab-providers"]
|
|
3418
|
+
#ck-tab-providers:checked ~ .ck-api-tabs__nav label[for="ck-tab-providers"],
|
|
3419
|
+
#ck-tab-imports:checked ~ .ck-api-tabs__nav label[for="ck-tab-imports"] {
|
|
3299
3420
|
border-left-color: transparent;
|
|
3300
3421
|
border-bottom-color: var(--ck-accent);
|
|
3301
3422
|
}
|
|
@@ -68,7 +68,7 @@ module CompletionKit
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def create
|
|
71
|
-
@metric = Metric.new(
|
|
71
|
+
@metric = Metric.new(create_metric_params)
|
|
72
72
|
|
|
73
73
|
if @metric.save
|
|
74
74
|
redirect_to metric_path(@metric), notice: "Metric was successfully created."
|
|
@@ -235,6 +235,15 @@ module CompletionKit
|
|
|
235
235
|
@metric = Metric.find(params[:id])
|
|
236
236
|
end
|
|
237
237
|
|
|
238
|
+
def create_metric_params
|
|
239
|
+
attrs = metric_params
|
|
240
|
+
if attrs[:metric_type] == "check"
|
|
241
|
+
attrs.except(:instruction, :rubric_bands)
|
|
242
|
+
else
|
|
243
|
+
attrs.except(:check_config)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
238
247
|
def metric_params
|
|
239
248
|
permitted = params.require(:metric).permit(:name, :instruction, :metric_type,
|
|
240
249
|
rubric_bands: [:stars, :description],
|
|
@@ -122,7 +122,7 @@ module CompletionKit
|
|
|
122
122
|
|
|
123
123
|
def suggest
|
|
124
124
|
if @run.prompt.nil?
|
|
125
|
-
redirect_to run_path(@run), alert: "
|
|
125
|
+
redirect_to run_path(@run), alert: "A run that only scores existing outputs has no prompt to improve."
|
|
126
126
|
return
|
|
127
127
|
end
|
|
128
128
|
|
|
@@ -169,6 +169,48 @@ module CompletionKit
|
|
|
169
169
|
end
|
|
170
170
|
end
|
|
171
171
|
|
|
172
|
+
CHECK_KIND_LABELS = {
|
|
173
|
+
"contains" => "Contains a phrase",
|
|
174
|
+
"not_contains" => "Does not contain a phrase",
|
|
175
|
+
"equals" => "Equals exactly",
|
|
176
|
+
"regex" => "Matches a pattern",
|
|
177
|
+
"valid_json" => "Is valid JSON",
|
|
178
|
+
"json_path_equals" => "A JSON field equals a value",
|
|
179
|
+
"length_bounds" => "Length is within a range",
|
|
180
|
+
"no_refusal" => "Is not a refusal"
|
|
181
|
+
}.freeze
|
|
182
|
+
|
|
183
|
+
CHECK_TARGET_LABELS = {
|
|
184
|
+
"response_text" => "The response text",
|
|
185
|
+
"input_data" => "The input row",
|
|
186
|
+
"json_path" => "A value from the response JSON"
|
|
187
|
+
}.freeze
|
|
188
|
+
|
|
189
|
+
CHECK_FIELD_LABELS = {
|
|
190
|
+
"value" => "Text to look for",
|
|
191
|
+
"pattern" => "Pattern",
|
|
192
|
+
"json_path" => "JSON path",
|
|
193
|
+
"expected" => "Expected value",
|
|
194
|
+
"target_path" => "Path into the JSON",
|
|
195
|
+
"min" => "Shortest allowed",
|
|
196
|
+
"max" => "Longest allowed",
|
|
197
|
+
"case_sensitive" => "Case sensitive",
|
|
198
|
+
"multiline" => "Multiline",
|
|
199
|
+
"trim" => "Trim whitespace"
|
|
200
|
+
}.freeze
|
|
201
|
+
|
|
202
|
+
def ck_check_kind_label(kind)
|
|
203
|
+
CHECK_KIND_LABELS.fetch(kind.to_s) { kind.to_s.humanize }
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def ck_check_target_label(target)
|
|
207
|
+
CHECK_TARGET_LABELS.fetch(target.to_s) { target.to_s.humanize }
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def ck_check_field_label(key)
|
|
211
|
+
CHECK_FIELD_LABELS.fetch(key.to_s) { key.to_s.humanize }
|
|
212
|
+
end
|
|
213
|
+
|
|
172
214
|
def ck_result_change_badge(change)
|
|
173
215
|
case change
|
|
174
216
|
when "broke"
|
|
@@ -30,7 +30,7 @@ module CompletionKit
|
|
|
30
30
|
display_scoped.select(:id)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
# A
|
|
33
|
+
# A scoring-only run grades a pre-existing column on the dataset instead of
|
|
34
34
|
# generating new outputs. No prompt is attached; the response text is read
|
|
35
35
|
# from row[output_column]; no LLM generation happens.
|
|
36
36
|
def judge_only?
|
|
@@ -442,7 +442,7 @@ module CompletionKit
|
|
|
442
442
|
self.name = "#{prompt.name} — v#{prompt.version_number} ##{count}"
|
|
443
443
|
elsif dataset.present?
|
|
444
444
|
count = Run.where(prompt_id: nil, dataset_id: dataset.id).count + 1
|
|
445
|
-
self.name = "#{dataset.name}
|
|
445
|
+
self.name = "#{dataset.name} scoring ##{count}"
|
|
446
446
|
end
|
|
447
447
|
end
|
|
448
448
|
|
|
@@ -461,7 +461,7 @@ module CompletionKit
|
|
|
461
461
|
return if prompt.present?
|
|
462
462
|
|
|
463
463
|
if dataset.nil?
|
|
464
|
-
errors.add(:dataset_id, "is required
|
|
464
|
+
errors.add(:dataset_id, "is required when scoring existing outputs (no prompt)")
|
|
465
465
|
return
|
|
466
466
|
end
|
|
467
467
|
|
|
@@ -5,7 +5,7 @@ module CompletionKit
|
|
|
5
5
|
|
|
6
6
|
TOOLS = {
|
|
7
7
|
"judges_replay" => {
|
|
8
|
-
description: "Run the current judge against a dataset (
|
|
8
|
+
description: "Run the current judge against a dataset (scores existing outputs). Wraps runs_create with prompt_id omitted and output_column supplied. Re-judges existing dataset outputs so you can compare against human verdicts.",
|
|
9
9
|
inputSchema: {
|
|
10
10
|
type: "object",
|
|
11
11
|
properties: {
|
|
@@ -51,7 +51,7 @@ module CompletionKit
|
|
|
51
51
|
handler: :publish
|
|
52
52
|
},
|
|
53
53
|
"prompts_suggest_improvement" => {
|
|
54
|
-
description: "Suggest an improved version of a prompt, grounded in a run's test results and judge feedback. Analyzes the run's responses, scores, and reviews, then returns reasoning plus a rewritten template (preserving {{variables}}) and persists it as a Suggestion. Requires a run that has a prompt (not a
|
|
54
|
+
description: "Suggest an improved version of a prompt, grounded in a run's test results and judge feedback. Analyzes the run's responses, scores, and reviews, then returns reasoning plus a rewritten template (preserving {{variables}}) and persists it as a Suggestion. Requires a run that has a prompt (not a scoring-only run).",
|
|
55
55
|
inputSchema: {
|
|
56
56
|
type: "object",
|
|
57
57
|
properties: {run_id: {type: "integer", description: "The run whose results ground the improvement."}},
|
|
@@ -107,7 +107,7 @@ module CompletionKit
|
|
|
107
107
|
|
|
108
108
|
def self.suggest_improvement(args)
|
|
109
109
|
run = Run.find(args["run_id"])
|
|
110
|
-
return error_result("
|
|
110
|
+
return error_result("A run that only scores existing outputs has no prompt to improve.") if run.prompt.nil?
|
|
111
111
|
|
|
112
112
|
result = PromptImprovementService.new(run).suggest
|
|
113
113
|
return error_result("The model didn't return a usable rewrite.") if result["suggested_template"].blank?
|
|
@@ -15,7 +15,7 @@ module CompletionKit
|
|
|
15
15
|
handler: :get
|
|
16
16
|
},
|
|
17
17
|
"runs_create" => {
|
|
18
|
-
description: "Create a run. Omit prompt_id and provide output_column
|
|
18
|
+
description: "Create a run. Omit prompt_id and provide output_column to score existing outputs by grading a pre-existing dataset column instead of generating new ones.",
|
|
19
19
|
inputSchema: {
|
|
20
20
|
type: "object",
|
|
21
21
|
properties: {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
<input type="radio" name="ck-api-tab" id="ck-tab-agreements" class="ck-api-tabs__radio">
|
|
21
21
|
<input type="radio" name="ck-api-tab" id="ck-tab-tags" class="ck-api-tabs__radio">
|
|
22
22
|
<input type="radio" name="ck-api-tab" id="ck-tab-providers" class="ck-api-tabs__radio">
|
|
23
|
+
<input type="radio" name="ck-api-tab" id="ck-tab-imports" class="ck-api-tabs__radio">
|
|
23
24
|
|
|
24
25
|
<nav class="ck-api-tabs__nav">
|
|
25
26
|
<label for="ck-tab-mcp" class="ck-api-tabs__label">MCP <span class="ck-api-tabs__count"><%= CompletionKit::McpDispatcher.tool_definitions.size %></span></label>
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
<label for="ck-tab-agreements" class="ck-api-tabs__label">Agreements <span class="ck-api-tabs__count">3</span></label>
|
|
33
34
|
<label for="ck-tab-tags" class="ck-api-tabs__label">Tags <span class="ck-api-tabs__count">5</span></label>
|
|
34
35
|
<label for="ck-tab-providers" class="ck-api-tabs__label">Providers <span class="ck-api-tabs__count">5</span></label>
|
|
36
|
+
<label for="ck-tab-imports" class="ck-api-tabs__label">Imports <span class="ck-api-tabs__count">1</span></label>
|
|
35
37
|
</nav>
|
|
36
38
|
|
|
37
39
|
<div class="ck-api-tabs__panels">
|
|
@@ -124,7 +126,7 @@
|
|
|
124
126
|
<div class="ck-api-endpoint">
|
|
125
127
|
<p class="ck-api-method"><span class="ck-chip ck-chip--soft">POST</span> /api/v1/runs</p>
|
|
126
128
|
<p class="ck-meta-copy">Create a new run.</p>
|
|
127
|
-
<p class="ck-api-params"><strong>Optional:</strong> <code>name</code>, <code>prompt_id</code>, <code>dataset_id</code>, <code>metric_ids</code>, <code>judge_model</code>, <code>output_column</code> (
|
|
129
|
+
<p class="ck-api-params"><strong>Optional:</strong> <code>name</code>, <code>prompt_id</code>, <code>dataset_id</code>, <code>metric_ids</code>, <code>judge_model</code>, <code>output_column</code> (score existing outputs: omit <code>prompt_id</code> and grade a dataset column instead, default <code>actual_output</code>)</p>
|
|
128
130
|
<%= render "completion_kit/api_reference/example", base_url: base_url, token: token, real_token: real_token, cmd: "curl -X POST #{base_url}/api/v1/runs \\\n -H \"Authorization: Bearer #{token}\" \\\n -H \"Content-Type: application/json\" \\\n -d '{\"prompt_id\": 1, \"dataset_id\": 1, \"metric_ids\": [1, 2]}'" %>
|
|
129
131
|
</div>
|
|
130
132
|
<div class="ck-api-endpoint">
|
|
@@ -379,6 +381,18 @@
|
|
|
379
381
|
} %>
|
|
380
382
|
</div>
|
|
381
383
|
|
|
384
|
+
<div class="ck-api-tabs__panel">
|
|
385
|
+
<h2 class="ck-section-title">Imports</h2>
|
|
386
|
+
<p class="ck-copy">Bring an existing <a href="https://www.promptfoo.dev" class="ck-link">promptfoo</a> config into CompletionKit in one call. Prompts, the test dataset, assert-based metrics, and providers are created where they map cleanly and skipped with a reason where they don't.</p>
|
|
387
|
+
<div class="ck-api-endpoint">
|
|
388
|
+
<p class="ck-api-method"><span class="ck-chip ck-chip--soft">POST</span> /api/v1/imports/promptfoo</p>
|
|
389
|
+
<p class="ck-meta-copy">Import a promptfooconfig.yaml. Send the YAML as a <code>config</code> param, or POST the raw YAML as the request body. Returns 201 with a mapping summary, or 422 if the YAML cannot be parsed.</p>
|
|
390
|
+
<p class="ck-api-params"><strong>Request:</strong> <code>config</code> (the YAML text) or a raw YAML request body</p>
|
|
391
|
+
<p class="ck-api-params"><strong>Response 201:</strong> <code>prompts</code>, <code>dataset</code>, <code>metrics</code>, and <code>providers</code>, each listing what was <code>created</code> and what was <code>skipped</code> (with a reason)</p>
|
|
392
|
+
<%= render "completion_kit/api_reference/example", base_url: base_url, token: token, real_token: real_token, cmd: "curl -X POST #{base_url}/api/v1/imports/promptfoo \\\n -H \"Authorization: Bearer #{token}\" \\\n -H \"Content-Type: application/x-yaml\" \\\n --data-binary @promptfooconfig.yaml" %>
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
|
|
382
396
|
</div>
|
|
383
397
|
</div>
|
|
384
398
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
<% config = local_assigns.fetch(:config) %>
|
|
2
2
|
<dl class="ck-check-spec">
|
|
3
3
|
<div class="ck-check-spec__row">
|
|
4
|
-
<dt class="ck-check-spec__term">
|
|
5
|
-
<dd class="ck-check-spec__val"
|
|
4
|
+
<dt class="ck-check-spec__term">Rule</dt>
|
|
5
|
+
<dd class="ck-check-spec__val"><%= ck_check_kind_label(config["check_kind"]) %></dd>
|
|
6
6
|
</div>
|
|
7
7
|
<div class="ck-check-spec__row">
|
|
8
|
-
<dt class="ck-check-spec__term">
|
|
9
|
-
<dd class="ck-check-spec__val"
|
|
8
|
+
<dt class="ck-check-spec__term">Checks</dt>
|
|
9
|
+
<dd class="ck-check-spec__val"><%= ck_check_target_label(config["target"]) %></dd>
|
|
10
10
|
</div>
|
|
11
11
|
<% config.except("check_kind", "target").each do |key, value| %>
|
|
12
12
|
<div class="ck-check-spec__row">
|
|
13
|
-
<dt class="ck-check-spec__term"><%= key
|
|
13
|
+
<dt class="ck-check-spec__term"><%= ck_check_field_label(key) %></dt>
|
|
14
14
|
<dd class="ck-check-spec__val"><code class="ck-check-spec__code"><%= value %></code></dd>
|
|
15
15
|
</div>
|
|
16
16
|
<% end %>
|
|
@@ -75,20 +75,22 @@
|
|
|
75
75
|
<% else %>
|
|
76
76
|
<div class="ck-field" data-ck-metric-type>
|
|
77
77
|
<p class="ck-section-title">Metric type</p>
|
|
78
|
-
<p class="ck-hint">
|
|
78
|
+
<p class="ck-hint">The judge gives each response 1 to 5 stars against your rubric. A check just passes or fails, with no AI.</p>
|
|
79
79
|
<label class="ck-radio">
|
|
80
80
|
<%= form.radio_button :metric_type, "llm_judge", checked: !metric.check? %>
|
|
81
81
|
<span>LLM judge (1-5)</span>
|
|
82
|
+
<%= heroicon_tag "information-circle", variant: :outline, size: 16, class: "ck-radio-info", "aria-hidden": "true", title: "An AI reads each response and rates it 1 to 5 stars against your rubric, with a written reason. Best for subjective quality: tone, helpfulness, accuracy." %>
|
|
82
83
|
</label>
|
|
83
84
|
<label class="ck-radio">
|
|
84
85
|
<%= form.radio_button :metric_type, "check", checked: metric.check? %>
|
|
85
86
|
<span>Deterministic check</span>
|
|
87
|
+
<%= heroicon_tag "information-circle", variant: :outline, size: 16, class: "ck-radio-info", "aria-hidden": "true", title: "A rule that passes or fails instantly with no AI and no cost. Best for exact things: valid JSON, contains a phrase, no refusal." %>
|
|
86
88
|
</label>
|
|
87
89
|
</div>
|
|
88
90
|
<% end %>
|
|
89
91
|
|
|
90
92
|
<% if show_judge %>
|
|
91
|
-
<div class="ck-field ck-field--spacious" data-ck-metric-editor="llm_judge"
|
|
93
|
+
<div class="ck-field ck-field--spacious" data-ck-metric-editor="llm_judge" <%= "hidden" if metric.check? %>>
|
|
92
94
|
<p class="ck-section-title">Instruction</p>
|
|
93
95
|
<p class="ck-hint">What should the judge assess? This instruction is sent to the LLM judge when scoring outputs.</p>
|
|
94
96
|
<%= form.text_area :instruction, rows: 8, class: "ck-input ck-input--area", placeholder: "Evaluate whether the output...", **ck_field_aria(form, :instruction) %>
|
|
@@ -112,7 +114,7 @@
|
|
|
112
114
|
<% end %>
|
|
113
115
|
</div>
|
|
114
116
|
|
|
115
|
-
<div class="ck-field ck-field--spacious"
|
|
117
|
+
<div class="ck-field ck-field--spacious" data-ck-metric-editor="llm_judge" <%= "hidden" if metric.check? %>>
|
|
116
118
|
<p class="ck-section-title">Rubric<%= render "completion_kit/metrics/rubric_hint" %></p>
|
|
117
119
|
<p class="ck-hint">What each star rating means for this metric.</p>
|
|
118
120
|
|
|
@@ -155,78 +157,77 @@
|
|
|
155
157
|
|
|
156
158
|
<% if show_check %>
|
|
157
159
|
<% check = metric.check_config || {} %>
|
|
158
|
-
<div class="ck-field ck-field--spacious" data-ck-metric-editor="check"
|
|
160
|
+
<div class="ck-field ck-field--spacious" data-ck-metric-editor="check" <%= "hidden" unless metric.check? %>>
|
|
159
161
|
<p class="ck-section-title">Check</p>
|
|
160
|
-
<p class="ck-hint">A
|
|
162
|
+
<p class="ck-hint">A pass or fail rule with no AI and no cost. You only fill in the fields the chosen rule needs.</p>
|
|
161
163
|
|
|
162
164
|
<div class="ck-field">
|
|
163
|
-
<
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
165
|
+
<p class="ck-label" id="metric_check_kind_label">Rule</p>
|
|
166
|
+
<%= render "completion_kit/shared/branded_select",
|
|
167
|
+
name: "metric[check_config][check_kind]",
|
|
168
|
+
options: CompletionKit::Checks::Registry.kinds.map { |kind| [kind, ck_check_kind_label(kind)] },
|
|
169
|
+
selected: check["check_kind"] || CompletionKit::Checks::Registry.kinds.first,
|
|
170
|
+
labelledby: "metric_check_kind_label" %>
|
|
169
171
|
</div>
|
|
170
172
|
|
|
171
173
|
<div class="ck-field">
|
|
172
|
-
<
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
174
|
+
<p class="ck-label" id="metric_check_target_label">What to check</p>
|
|
175
|
+
<%= render "completion_kit/shared/branded_select",
|
|
176
|
+
name: "metric[check_config][target]",
|
|
177
|
+
options: CompletionKit::Checks::TargetResolver::TARGETS.map { |target| [target, ck_check_target_label(target)] },
|
|
178
|
+
selected: check["target"] || CompletionKit::Checks::TargetResolver::TARGETS.first,
|
|
179
|
+
labelledby: "metric_check_target_label" %>
|
|
178
180
|
</div>
|
|
179
181
|
|
|
180
|
-
<div class="ck-field">
|
|
181
|
-
<label class="ck-label" for="metric_check_target_path">
|
|
182
|
-
<p class="ck-hint">
|
|
182
|
+
<div class="ck-field" data-ck-check-field="target_path">
|
|
183
|
+
<label class="ck-label" for="metric_check_target_path">Path into the JSON</label>
|
|
184
|
+
<p class="ck-hint">Where to look inside the response, like data.items.0.name.</p>
|
|
183
185
|
<input type="text" name="metric[check_config][target_path]" id="metric_check_target_path" class="ck-input" value="<%= check["target_path"] %>">
|
|
184
186
|
</div>
|
|
185
187
|
|
|
186
|
-
<div class="ck-field">
|
|
187
|
-
<label class="ck-label" for="metric_check_value">
|
|
188
|
-
<p class="ck-hint">The substring or exact string for contains, not_contains, or equals.</p>
|
|
188
|
+
<div class="ck-field" data-ck-check-field="value">
|
|
189
|
+
<label class="ck-label" for="metric_check_value">Text to look for</label>
|
|
189
190
|
<input type="text" name="metric[check_config][value]" id="metric_check_value" class="ck-input" value="<%= check["value"] %>">
|
|
190
191
|
</div>
|
|
191
192
|
|
|
192
|
-
<div class="ck-field">
|
|
193
|
+
<div class="ck-field" data-ck-check-field="pattern">
|
|
193
194
|
<label class="ck-label" for="metric_check_pattern">Pattern</label>
|
|
194
|
-
<p class="ck-hint">A regular expression
|
|
195
|
+
<p class="ck-hint">A regular expression to match against.</p>
|
|
195
196
|
<input type="text" name="metric[check_config][pattern]" id="metric_check_pattern" class="ck-input" value="<%= check["pattern"] %>">
|
|
196
197
|
</div>
|
|
197
198
|
|
|
198
|
-
<div class="ck-field">
|
|
199
|
+
<div class="ck-field" data-ck-check-field="json_path">
|
|
199
200
|
<label class="ck-label" for="metric_check_json_path">JSON path</label>
|
|
200
|
-
<p class="ck-hint">
|
|
201
|
+
<p class="ck-hint">The path to the value inside the response, like data.status.</p>
|
|
201
202
|
<input type="text" name="metric[check_config][json_path]" id="metric_check_json_path" class="ck-input" value="<%= check["json_path"] %>">
|
|
202
203
|
</div>
|
|
203
204
|
|
|
204
|
-
<div class="ck-field">
|
|
205
|
-
<label class="ck-label" for="metric_check_expected">Expected</label>
|
|
206
|
-
<p class="ck-hint">The value
|
|
205
|
+
<div class="ck-field" data-ck-check-field="expected">
|
|
206
|
+
<label class="ck-label" for="metric_check_expected">Expected value</label>
|
|
207
|
+
<p class="ck-hint">The value it should equal.</p>
|
|
207
208
|
<input type="text" name="metric[check_config][expected]" id="metric_check_expected" class="ck-input" value="<%= check["expected"] %>">
|
|
208
209
|
</div>
|
|
209
210
|
|
|
210
211
|
<div class="ck-field-row">
|
|
211
|
-
<div class="ck-field">
|
|
212
|
-
<label class="ck-label" for="metric_check_min">
|
|
212
|
+
<div class="ck-field" data-ck-check-field="min">
|
|
213
|
+
<label class="ck-label" for="metric_check_min">Shortest allowed</label>
|
|
213
214
|
<input type="number" name="metric[check_config][min]" id="metric_check_min" class="ck-input" value="<%= check["min"] %>">
|
|
214
215
|
</div>
|
|
215
|
-
<div class="ck-field">
|
|
216
|
-
<label class="ck-label" for="metric_check_max">
|
|
216
|
+
<div class="ck-field" data-ck-check-field="max">
|
|
217
|
+
<label class="ck-label" for="metric_check_max">Longest allowed</label>
|
|
217
218
|
<input type="number" name="metric[check_config][max]" id="metric_check_max" class="ck-input" value="<%= check["max"] %>">
|
|
218
219
|
</div>
|
|
219
220
|
</div>
|
|
220
221
|
|
|
221
|
-
<label class="ck-checkbox">
|
|
222
|
+
<label class="ck-checkbox" data-ck-check-field="case_sensitive">
|
|
222
223
|
<input type="checkbox" name="metric[check_config][case_sensitive]" value="true"<%= " checked" if check["case_sensitive"] %>>
|
|
223
224
|
<span>Case sensitive</span>
|
|
224
225
|
</label>
|
|
225
|
-
<label class="ck-checkbox">
|
|
226
|
+
<label class="ck-checkbox" data-ck-check-field="multiline">
|
|
226
227
|
<input type="checkbox" name="metric[check_config][multiline]" value="true"<%= " checked" if check["multiline"] %>>
|
|
227
228
|
<span>Multiline</span>
|
|
228
229
|
</label>
|
|
229
|
-
<label class="ck-checkbox">
|
|
230
|
+
<label class="ck-checkbox" data-ck-check-field="trim">
|
|
230
231
|
<input type="checkbox" name="metric[check_config][trim]" value="true"<%= " checked" if check["trim"] %>>
|
|
231
232
|
<span>Trim whitespace</span>
|
|
232
233
|
</label>
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
<%= check_box_tag "run[judge_only]", "1", run.persisted? && run.judge_only?, id: "run_judge_only", class: "ck-checkbox" %>
|
|
23
23
|
<span class="ck-checkbox-label__box" aria-hidden="true"></span>
|
|
24
24
|
<span class="ck-checkbox-label__body">
|
|
25
|
-
<span class="ck-checkbox-label__text">
|
|
25
|
+
<span class="ck-checkbox-label__text">Score existing outputs</span>
|
|
26
26
|
<span class="ck-checkbox-label__hint">Grade an existing column on the dataset instead of running a prompt. Roughly half the LLM calls per row.</span>
|
|
27
27
|
</span>
|
|
28
28
|
</label>
|
|
@@ -263,7 +263,7 @@ function updateRunForm() {
|
|
|
263
263
|
}
|
|
264
264
|
} else if (!dataset) {
|
|
265
265
|
if (datasetField) datasetField.className = 'ck-field ck-field--info';
|
|
266
|
-
if (datasetHint) datasetHint.textContent = '
|
|
266
|
+
if (datasetHint) datasetHint.textContent = 'Skip generation and score responses you already have from a dataset column. Works with rubric metrics or deterministic checks.';
|
|
267
267
|
}
|
|
268
268
|
} else {
|
|
269
269
|
valid = prompt !== '';
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<%= link_to run.prompt.name, ck_prompt_path(run.prompt), class: "ck-runs-table__config-link", onclick: "event.stopPropagation();" %>
|
|
11
11
|
<span class="ck-runs-table__version">v<%= run.prompt.version_number %></span>
|
|
12
12
|
<% else %>
|
|
13
|
-
<span class="ck-runs-table__version">
|
|
13
|
+
<span class="ck-runs-table__version">Scoring only</span>
|
|
14
14
|
<% end %>
|
|
15
15
|
<% if run.dataset %>
|
|
16
16
|
<span class="ck-runs-table__sep">·</span>
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
<% if run.prompt %>
|
|
23
23
|
<p class="ck-meta-copy"><%= link_to run.prompt.display_name, prompt_path(run.prompt), class: "ck-link" %> <span class="ck-chip" style="text-transform: none;"><%= run.prompt.llm_model %></span></p>
|
|
24
24
|
<% else %>
|
|
25
|
-
<p class="ck-meta-copy">
|
|
25
|
+
<p class="ck-meta-copy">Scoring existing outputs, grading column <code><%= run.output_column.presence || "actual_output" %></code><% if run.dataset %> on <%= link_to run.dataset.name, dataset_path(run.dataset), class: "ck-link" %><% end %></p>
|
|
26
26
|
<% end %>
|
|
27
27
|
</div>
|
|
28
28
|
<%= render "completion_kit/runs/actions", run: run %>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<% chosen = options.find { |value, _label| value.to_s == selected.to_s } || options.first %>
|
|
2
|
+
<div class="ck-select" data-ck-select>
|
|
3
|
+
<input type="hidden" name="<%= name %>" value="<%= chosen && chosen.first %>" data-ck-select-value>
|
|
4
|
+
<button type="button" class="ck-select__trigger" data-ck-select-trigger
|
|
5
|
+
aria-haspopup="listbox" aria-expanded="false" aria-labelledby="<%= labelledby %>">
|
|
6
|
+
<span class="ck-select__label" data-ck-select-label><%= chosen && chosen.last %></span>
|
|
7
|
+
<span class="ck-select__chevron" aria-hidden="true"><%= heroicon_tag "chevron-down" %></span>
|
|
8
|
+
</button>
|
|
9
|
+
<ul class="ck-select__menu" role="listbox" hidden data-ck-select-menu aria-labelledby="<%= labelledby %>">
|
|
10
|
+
<% options.each do |value, label| %>
|
|
11
|
+
<li class="ck-select__option" role="option" tabindex="-1" data-value="<%= value %>"
|
|
12
|
+
aria-selected="<%= chosen && chosen.first.to_s == value.to_s ? "true" : "false" %>">
|
|
13
|
+
<span class="ck-select__tick" aria-hidden="true"><%= heroicon_tag "check" %></span>
|
|
14
|
+
<span class="ck-select__text" data-ck-select-text><%= label %></span>
|
|
15
|
+
</li>
|
|
16
|
+
<% end %>
|
|
17
|
+
</ul>
|
|
18
|
+
</div>
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: completion-kit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.21.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Damien Bastin
|
|
@@ -419,6 +419,7 @@ files:
|
|
|
419
419
|
- app/views/completion_kit/runs/index.html.erb
|
|
420
420
|
- app/views/completion_kit/runs/new.html.erb
|
|
421
421
|
- app/views/completion_kit/runs/show.html.erb
|
|
422
|
+
- app/views/completion_kit/shared/_branded_select.html.erb
|
|
422
423
|
- app/views/completion_kit/shared/_settings_nav.html.erb
|
|
423
424
|
- app/views/completion_kit/suggestions/_scoreboard.html.erb
|
|
424
425
|
- app/views/completion_kit/suggestions/_state.html.erb
|