completion-kit 0.5.5 → 0.5.6
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/README.md +1 -1
- data/app/assets/javascripts/completion_kit/application.js +8 -4
- data/app/assets/stylesheets/completion_kit/application.css +193 -21
- data/app/controllers/completion_kit/prompts_controller.rb +1 -1
- data/app/helpers/completion_kit/application_helper.rb +58 -10
- data/app/models/completion_kit/provider_credential.rb +8 -0
- data/app/views/completion_kit/prompts/_form.html.erb +6 -1
- data/app/views/completion_kit/prompts/index.html.erb +7 -0
- data/app/views/completion_kit/prompts/show.html.erb +65 -8
- data/app/views/completion_kit/provider_credentials/_models_card.html.erb +25 -20
- data/app/views/completion_kit/provider_credentials/index.html.erb +1 -0
- data/app/views/completion_kit/runs/_form.html.erb +2 -1
- data/app/views/completion_kit/runs/show.html.erb +1 -1
- data/app/views/layouts/completion_kit/application.html.erb +3 -2
- data/lib/completion_kit/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bc297d90963b3ef6543ef3b2f14a94808c9a5a945047bd9ebb4b820fed1c9c9d
|
|
4
|
+
data.tar.gz: dc730069a2f27764f384068d2fab146e3cb402e24f3dfdeea33d700d50118d3a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c0fe08870fd298ca0ba0e5b850dc9992dbe8fc7cfebb81abb352d1543220c2925f195abd66113204f28ebfa2e48433d9ad389fc94f100ab1f8c9689edfdc6785
|
|
7
|
+
data.tar.gz: 1fc05c857f889ea0e461196471eca63d630c82a84d5447f870db9cd6bdfec9f539125d6189de27e3ee6cfc6969089105212f73e8a48485fecb8282ca886305a5
|
data/README.md
CHANGED
|
@@ -142,7 +142,7 @@ Only one mode can be active.
|
|
|
142
142
|
|
|
143
143
|
## Concepts
|
|
144
144
|
|
|
145
|
-
- **Prompt.** A versioned template with `{{variable}}` placeholders.
|
|
145
|
+
- **Prompt.** A versioned template with `{{variable}}` placeholders. Editing a prompt that's already been run creates a new version, so earlier results stay reproducible.
|
|
146
146
|
- **Dataset.** A CSV of real inputs. Each row becomes one test case.
|
|
147
147
|
- **Run.** One execution of a prompt against a dataset. Captures every input (model, temperature, metrics) and stores all outputs and scores.
|
|
148
148
|
- **Response.** The model's output for one dataset row, with reviews attached.
|
|
@@ -105,11 +105,16 @@ document.addEventListener("mouseout", function(e) {
|
|
|
105
105
|
});
|
|
106
106
|
|
|
107
107
|
var ckRefreshing = false;
|
|
108
|
+
function ckSetRefreshButtonsBusy(busy) {
|
|
109
|
+
document.querySelectorAll('.ck-icon-btn[title="Refresh models"]').forEach(function(btn) {
|
|
110
|
+
btn.classList.toggle('ck-icon-btn--spinning', busy);
|
|
111
|
+
btn.disabled = busy;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
108
114
|
function ckRefreshModels() {
|
|
109
115
|
if (ckRefreshing) return;
|
|
110
116
|
ckRefreshing = true;
|
|
111
|
-
|
|
112
|
-
if (btn) btn.classList.add('ck-icon-btn--spinning');
|
|
117
|
+
ckSetRefreshButtonsBusy(true);
|
|
113
118
|
ckUpdateRefreshProgress();
|
|
114
119
|
var csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute("content");
|
|
115
120
|
fetch("/completion_kit/refresh_models", {
|
|
@@ -149,8 +154,7 @@ document.addEventListener("turbo:before-stream-render", function(event) {
|
|
|
149
154
|
}
|
|
150
155
|
if (target === "prompt_llm_model" || target === "run_judge_model") {
|
|
151
156
|
ckRefreshing = false;
|
|
152
|
-
|
|
153
|
-
if (btn) btn.classList.remove('ck-icon-btn--spinning');
|
|
157
|
+
ckSetRefreshButtonsBusy(false);
|
|
154
158
|
var status = document.getElementById('refresh-status');
|
|
155
159
|
if (status) { status.textContent = 'Models updated.'; setTimeout(function() { status.textContent = ' '; }, 3000); }
|
|
156
160
|
}
|
|
@@ -1164,6 +1164,17 @@ tr:hover .ck-chip--publish {
|
|
|
1164
1164
|
cursor: help;
|
|
1165
1165
|
}
|
|
1166
1166
|
|
|
1167
|
+
.ck-model-table tr.ck-model-table__section td {
|
|
1168
|
+
padding: 0.85rem 0 0.3rem;
|
|
1169
|
+
font-family: var(--ck-mono);
|
|
1170
|
+
font-size: 0.66rem;
|
|
1171
|
+
font-weight: 600;
|
|
1172
|
+
letter-spacing: 0.16em;
|
|
1173
|
+
text-transform: uppercase;
|
|
1174
|
+
color: var(--ck-dim);
|
|
1175
|
+
}
|
|
1176
|
+
.ck-model-table tr.ck-model-table__section:first-child td { padding-top: 0; }
|
|
1177
|
+
|
|
1167
1178
|
.ck-model-list__summary {
|
|
1168
1179
|
display: flex;
|
|
1169
1180
|
align-items: center;
|
|
@@ -1278,6 +1289,102 @@ tr:hover .ck-chip--publish {
|
|
|
1278
1289
|
max-width: 36rem;
|
|
1279
1290
|
}
|
|
1280
1291
|
|
|
1292
|
+
.ck-version-note {
|
|
1293
|
+
margin: 0 0 1rem;
|
|
1294
|
+
max-width: 36rem;
|
|
1295
|
+
font-family: var(--ck-sans);
|
|
1296
|
+
font-size: 0.85rem;
|
|
1297
|
+
line-height: 1.5;
|
|
1298
|
+
color: var(--ck-muted);
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
.ck-version-change {
|
|
1302
|
+
margin: 0.5rem 0 0;
|
|
1303
|
+
display: flex;
|
|
1304
|
+
align-items: center;
|
|
1305
|
+
gap: 0.5rem;
|
|
1306
|
+
flex-wrap: wrap;
|
|
1307
|
+
font-family: var(--ck-mono);
|
|
1308
|
+
font-size: 0.8rem;
|
|
1309
|
+
}
|
|
1310
|
+
.ck-version-change__label {
|
|
1311
|
+
font-size: 0.66rem;
|
|
1312
|
+
font-weight: 600;
|
|
1313
|
+
letter-spacing: 0.12em;
|
|
1314
|
+
text-transform: uppercase;
|
|
1315
|
+
color: var(--ck-dim);
|
|
1316
|
+
}
|
|
1317
|
+
.ck-version-change__old {
|
|
1318
|
+
padding: 0.15em 0.5em;
|
|
1319
|
+
border-radius: var(--ck-radius);
|
|
1320
|
+
background: var(--ck-danger-soft);
|
|
1321
|
+
color: var(--ck-danger);
|
|
1322
|
+
text-decoration: line-through;
|
|
1323
|
+
}
|
|
1324
|
+
.ck-version-change__arrow { color: var(--ck-dim); }
|
|
1325
|
+
.ck-version-change__new {
|
|
1326
|
+
padding: 0.15em 0.5em;
|
|
1327
|
+
border-radius: var(--ck-radius);
|
|
1328
|
+
background: var(--ck-success-soft);
|
|
1329
|
+
color: var(--ck-success);
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
.ck-modal__body .ck-version-change + .ck-suggest-diff { margin-top: 0.9rem; }
|
|
1333
|
+
|
|
1334
|
+
.ck-cell-link {
|
|
1335
|
+
background: none;
|
|
1336
|
+
border: none;
|
|
1337
|
+
padding: 0;
|
|
1338
|
+
font-family: var(--ck-mono);
|
|
1339
|
+
font-size: 0.8rem;
|
|
1340
|
+
color: var(--ck-accent);
|
|
1341
|
+
cursor: pointer;
|
|
1342
|
+
text-decoration: underline;
|
|
1343
|
+
text-underline-offset: 2px;
|
|
1344
|
+
}
|
|
1345
|
+
.ck-cell-link:hover { color: var(--ck-accent-hover); }
|
|
1346
|
+
.ck-cell-link--delta {
|
|
1347
|
+
font-size: 1rem;
|
|
1348
|
+
font-weight: 700;
|
|
1349
|
+
text-decoration: none;
|
|
1350
|
+
color: var(--ck-dim);
|
|
1351
|
+
}
|
|
1352
|
+
.ck-cell-link--delta:hover { color: var(--ck-accent); }
|
|
1353
|
+
|
|
1354
|
+
/* Version-history table: cell that pairs the version label + publish control (left) with the diff trigger (right) */
|
|
1355
|
+
.ck-version-cell {
|
|
1356
|
+
display: flex;
|
|
1357
|
+
align-items: center;
|
|
1358
|
+
justify-content: space-between;
|
|
1359
|
+
gap: 0.75rem;
|
|
1360
|
+
}
|
|
1361
|
+
.ck-version-cell__label { display: flex; align-items: center; gap: 0.5rem; }
|
|
1362
|
+
|
|
1363
|
+
/* the version currently being viewed: faint accent wash (like the model chips)
|
|
1364
|
+
plus a bright accent border drawn on the cells (Safari can't box-shadow a <tr>) */
|
|
1365
|
+
.ck-results-table tbody tr.ck-results-table__row--active,
|
|
1366
|
+
.ck-results-table tbody tr.ck-results-table__row--active:hover {
|
|
1367
|
+
background: var(--ck-accent-soft);
|
|
1368
|
+
}
|
|
1369
|
+
.ck-results-table tbody tr.ck-results-table__row--active td {
|
|
1370
|
+
box-shadow: inset 0 2px 0 var(--ck-accent), inset 0 -2px 0 var(--ck-accent);
|
|
1371
|
+
}
|
|
1372
|
+
.ck-results-table tbody tr.ck-results-table__row--active td:first-child {
|
|
1373
|
+
box-shadow: inset 0 2px 0 var(--ck-accent), inset 0 -2px 0 var(--ck-accent), inset 2px 0 0 var(--ck-accent);
|
|
1374
|
+
}
|
|
1375
|
+
.ck-results-table tbody tr.ck-results-table__row--active td:last-child {
|
|
1376
|
+
box-shadow: inset 0 2px 0 var(--ck-accent), inset 0 -2px 0 var(--ck-accent), inset -2px 0 0 var(--ck-accent);
|
|
1377
|
+
}
|
|
1378
|
+
/* when it's the last row, round its bottom corners so the border isn't clipped
|
|
1379
|
+
by the table's rounded container */
|
|
1380
|
+
.ck-results-table tbody tr.ck-results-table__row--active:last-child td:first-child {
|
|
1381
|
+
border-bottom-left-radius: calc(var(--ck-radius-lg) - 1px);
|
|
1382
|
+
}
|
|
1383
|
+
.ck-results-table tbody tr.ck-results-table__row--active:last-child td:last-child {
|
|
1384
|
+
border-bottom-right-radius: calc(var(--ck-radius-lg) - 1px);
|
|
1385
|
+
}
|
|
1386
|
+
.ck-results-table tbody tr.ck-results-table__row--active strong { color: var(--ck-accent-hover); }
|
|
1387
|
+
|
|
1281
1388
|
.ck-field-hint {
|
|
1282
1389
|
font-family: var(--ck-sans);
|
|
1283
1390
|
font-size: 0.8rem;
|
|
@@ -1659,30 +1766,54 @@ tr:hover .ck-chip--publish {
|
|
|
1659
1766
|
color: var(--ck-text);
|
|
1660
1767
|
}
|
|
1661
1768
|
|
|
1769
|
+
@keyframes ck-flash-in {
|
|
1770
|
+
from { opacity: 0; transform: translateY(-4px); }
|
|
1771
|
+
to { opacity: 1; transform: translateY(0); }
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1662
1774
|
.ck-flash {
|
|
1663
|
-
margin-bottom:
|
|
1664
|
-
padding: 0.
|
|
1665
|
-
border-radius: var(--ck-radius);
|
|
1775
|
+
margin-bottom: 1.25rem;
|
|
1776
|
+
padding: 0.8rem 1rem;
|
|
1666
1777
|
border: 1px solid transparent;
|
|
1778
|
+
border-radius: var(--ck-radius);
|
|
1667
1779
|
font-family: var(--ck-sans);
|
|
1668
|
-
font-size: 0.
|
|
1780
|
+
font-size: 0.9rem;
|
|
1781
|
+
line-height: 1.6;
|
|
1782
|
+
animation: ck-flash-in 0.22s ease both;
|
|
1669
1783
|
}
|
|
1784
|
+
.ck-flash--notice { background: rgba(45, 212, 168, 0.08); border-color: rgba(45, 212, 168, 0.32); }
|
|
1785
|
+
.ck-flash--alert { background: rgba(248, 113, 113, 0.08); border-color: rgba(248, 113, 113, 0.32); }
|
|
1670
1786
|
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1787
|
+
/* layout flash: a mono status tag (the engine's kicker treatment) inline before the message */
|
|
1788
|
+
.ck-flash__label {
|
|
1789
|
+
margin-right: 0.6rem;
|
|
1790
|
+
font-family: var(--ck-mono);
|
|
1791
|
+
font-size: 0.7rem;
|
|
1792
|
+
font-weight: 600;
|
|
1793
|
+
letter-spacing: 0.14em;
|
|
1794
|
+
text-transform: uppercase;
|
|
1795
|
+
white-space: nowrap;
|
|
1675
1796
|
}
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1797
|
+
.ck-flash__label::before {
|
|
1798
|
+
content: "";
|
|
1799
|
+
display: inline-block;
|
|
1800
|
+
width: 6px;
|
|
1801
|
+
height: 6px;
|
|
1802
|
+
margin-right: 0.45rem;
|
|
1803
|
+
border-radius: 50%;
|
|
1804
|
+
background: currentColor;
|
|
1805
|
+
box-shadow: 0 0 7px currentColor;
|
|
1806
|
+
vertical-align: 0.12em;
|
|
1681
1807
|
}
|
|
1808
|
+
.ck-flash--notice .ck-flash__label { color: var(--ck-success); }
|
|
1809
|
+
.ck-flash--alert .ck-flash__label { color: var(--ck-danger); }
|
|
1810
|
+
.ck-flash__body { color: var(--ck-text); }
|
|
1682
1811
|
|
|
1683
|
-
.ck-
|
|
1684
|
-
|
|
1685
|
-
|
|
1812
|
+
/* form-error summary inside .ck-flash--alert: title sentence + ck-error-list */
|
|
1813
|
+
.ck-flash__title { margin: 0; font-weight: 600; color: var(--ck-danger); }
|
|
1814
|
+
|
|
1815
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1816
|
+
.ck-flash { animation: none; }
|
|
1686
1817
|
}
|
|
1687
1818
|
|
|
1688
1819
|
.ck-banner {
|
|
@@ -2012,6 +2143,10 @@ select.ck-input {
|
|
|
2012
2143
|
box-shadow: 0 30px 80px rgba(0, 0, 0, 0.55);
|
|
2013
2144
|
overflow: hidden;
|
|
2014
2145
|
}
|
|
2146
|
+
/* the panel is the dialog's autofocus target (so the close button isn't auto-focused);
|
|
2147
|
+
it's a container, not a control, so don't show a ring on it */
|
|
2148
|
+
.ck-modal__panel:focus,
|
|
2149
|
+
.ck-modal__panel:focus-visible { outline: none; }
|
|
2015
2150
|
|
|
2016
2151
|
.ck-modal__header {
|
|
2017
2152
|
display: flex;
|
|
@@ -2048,9 +2183,8 @@ select.ck-input {
|
|
|
2048
2183
|
|
|
2049
2184
|
.ck-modal__close {
|
|
2050
2185
|
appearance: none;
|
|
2186
|
+
position: relative;
|
|
2051
2187
|
display: inline-flex;
|
|
2052
|
-
align-items: center;
|
|
2053
|
-
justify-content: center;
|
|
2054
2188
|
width: 28px;
|
|
2055
2189
|
height: 28px;
|
|
2056
2190
|
padding: 0;
|
|
@@ -2059,12 +2193,25 @@ select.ck-input {
|
|
|
2059
2193
|
outline: none;
|
|
2060
2194
|
background: transparent;
|
|
2061
2195
|
color: var(--ck-dim);
|
|
2062
|
-
font-
|
|
2063
|
-
font-size: 1.5rem;
|
|
2064
|
-
line-height: 1;
|
|
2196
|
+
font-size: 0;
|
|
2065
2197
|
cursor: pointer;
|
|
2066
2198
|
transition: color 0.15s, background 0.15s;
|
|
2067
2199
|
}
|
|
2200
|
+
/* the "×" is drawn as two crossed bars centred on the button, so it (and the
|
|
2201
|
+
focus ring around the button) sit dead centre regardless of font metrics */
|
|
2202
|
+
.ck-modal__close::before,
|
|
2203
|
+
.ck-modal__close::after {
|
|
2204
|
+
content: "";
|
|
2205
|
+
position: absolute;
|
|
2206
|
+
top: 50%;
|
|
2207
|
+
left: 50%;
|
|
2208
|
+
width: 13px;
|
|
2209
|
+
height: 1.6px;
|
|
2210
|
+
border-radius: 1px;
|
|
2211
|
+
background: currentColor;
|
|
2212
|
+
}
|
|
2213
|
+
.ck-modal__close::before { transform: translate(-50%, -50%) rotate(45deg); }
|
|
2214
|
+
.ck-modal__close::after { transform: translate(-50%, -50%) rotate(-45deg); }
|
|
2068
2215
|
|
|
2069
2216
|
.ck-modal__close:hover {
|
|
2070
2217
|
color: var(--ck-text);
|
|
@@ -2083,6 +2230,8 @@ select.ck-input {
|
|
|
2083
2230
|
overflow: auto;
|
|
2084
2231
|
padding: 0 1.5rem;
|
|
2085
2232
|
}
|
|
2233
|
+
/* no footer? then the body needs its own bottom padding */
|
|
2234
|
+
.ck-modal__body:last-child { padding-bottom: 1.5rem; }
|
|
2086
2235
|
|
|
2087
2236
|
.ck-modal__footer {
|
|
2088
2237
|
display: flex;
|
|
@@ -2878,6 +3027,29 @@ select.ck-input {
|
|
|
2878
3027
|
color: var(--ck-dim);
|
|
2879
3028
|
}
|
|
2880
3029
|
|
|
3030
|
+
.ck-prompts-table__desc {
|
|
3031
|
+
margin: 0.3rem 0 0;
|
|
3032
|
+
font-family: var(--ck-sans);
|
|
3033
|
+
font-size: 0.82rem;
|
|
3034
|
+
font-weight: 400;
|
|
3035
|
+
line-height: 1.45;
|
|
3036
|
+
color: var(--ck-muted);
|
|
3037
|
+
white-space: normal;
|
|
3038
|
+
max-width: 42ch;
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
.ck-endpoint--compact {
|
|
3042
|
+
margin-top: 0.45rem;
|
|
3043
|
+
max-width: 26rem;
|
|
3044
|
+
}
|
|
3045
|
+
.ck-endpoint--compact .ck-endpoint__url {
|
|
3046
|
+
min-width: 0;
|
|
3047
|
+
white-space: nowrap;
|
|
3048
|
+
overflow: hidden;
|
|
3049
|
+
text-overflow: ellipsis;
|
|
3050
|
+
}
|
|
3051
|
+
.ck-endpoint--compact .ck-icon-btn { flex-shrink: 0; }
|
|
3052
|
+
|
|
2881
3053
|
.ck-clamp-2 {
|
|
2882
3054
|
display: -webkit-box;
|
|
2883
3055
|
-webkit-line-clamp: 2;
|
|
@@ -50,7 +50,7 @@ module CompletionKit
|
|
|
50
50
|
|
|
51
51
|
def publish
|
|
52
52
|
@prompt.publish!
|
|
53
|
-
redirect_to prompt_path(@prompt), notice: "#{@prompt.display_name} is now the
|
|
53
|
+
redirect_to prompt_path(@prompt), notice: "#{@prompt.display_name} is now the published version."
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
private
|
|
@@ -72,6 +72,41 @@ module CompletionKit
|
|
|
72
72
|
CompletionKit::ProviderCredential::PROVIDER_LABELS[provider.to_s] || provider.to_s.titleize
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
+
OPENAI_MODEL_FAMILY_ORDER = ["GPT-5", "GPT-4", "o-series", "GPT-3.5", "GPT-OSS", "Other"].freeze
|
|
76
|
+
|
|
77
|
+
def ck_openai_model_family(model_id)
|
|
78
|
+
id = model_id.to_s
|
|
79
|
+
return "GPT-5" if id.match?(/\Agpt-5/i)
|
|
80
|
+
return "GPT-4" if id.match?(/\Agpt-4/i)
|
|
81
|
+
return "GPT-3.5" if id.match?(/\Agpt-3/i)
|
|
82
|
+
return "GPT-OSS" if id.match?(/\Agpt-oss/i)
|
|
83
|
+
return "o-series" if id.match?(/\Ao\d/i)
|
|
84
|
+
"Other"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Groups a provider's models for the models-card table, mirroring how the
|
|
88
|
+
# dropdown sub-groups: OpenRouter clusters by upstream vendor (the part
|
|
89
|
+
# before "/"); OpenAI clusters by family; everyone else stays flat. Returns
|
|
90
|
+
# [[section_label_or_nil, [models]], ...]. A single section collapses to a
|
|
91
|
+
# nil label so we don't render a redundant header.
|
|
92
|
+
def ck_model_table_sections(models)
|
|
93
|
+
models = models.to_a
|
|
94
|
+
sections =
|
|
95
|
+
case models.first&.provider
|
|
96
|
+
when "openrouter"
|
|
97
|
+
models.group_by { |m| m.model_id.to_s.split("/", 2).first.delete_prefix("~") }
|
|
98
|
+
.sort_by { |label, _| label }
|
|
99
|
+
when "openai"
|
|
100
|
+
grouped = models.group_by { |m| ck_openai_model_family(m.model_id) }
|
|
101
|
+
ordered = OPENAI_MODEL_FAMILY_ORDER.filter_map { |label| [label, grouped[label]] if grouped[label] }
|
|
102
|
+
extras = (grouped.keys - OPENAI_MODEL_FAMILY_ORDER).sort.map { |label| [label, grouped[label]] }
|
|
103
|
+
ordered + extras
|
|
104
|
+
else
|
|
105
|
+
[[nil, models]]
|
|
106
|
+
end
|
|
107
|
+
sections.size <= 1 ? [[nil, models]] : sections
|
|
108
|
+
end
|
|
109
|
+
|
|
75
110
|
def ck_model_option_label(model)
|
|
76
111
|
return "#{model[:name]} (?)" if model.key?(:judging_confirmed) && !model[:judging_confirmed]
|
|
77
112
|
model[:name]
|
|
@@ -85,20 +120,33 @@ module CompletionKit
|
|
|
85
120
|
end
|
|
86
121
|
end
|
|
87
122
|
|
|
88
|
-
groups = models.group_by
|
|
89
|
-
|
|
90
|
-
upstream = m[:id].to_s.split("/", 2).first
|
|
91
|
-
"OpenRouter — #{upstream}"
|
|
92
|
-
else
|
|
93
|
-
ck_provider_label(m[:provider])
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
ordered_keys = groups.keys.sort_by { |label| [label.start_with?("OpenRouter") ? 1 : 0, label] }
|
|
123
|
+
groups = models.group_by { |m| ck_model_optgroup_label(m) }
|
|
124
|
+
ordered_keys = groups.keys.sort_by { |label| ck_model_optgroup_sort_key(label) }
|
|
98
125
|
grouped = ordered_keys.map { |label| [label, groups[label].map { |m| [ck_model_option_label(m), m[:id]] }] }
|
|
99
126
|
grouped_options_for_select(grouped, selected)
|
|
100
127
|
end
|
|
101
128
|
|
|
129
|
+
# Optgroup label for the model select — mirrors the provider models table:
|
|
130
|
+
# OpenRouter splits by upstream vendor, OpenAI splits by family, everyone
|
|
131
|
+
# else is a single group.
|
|
132
|
+
def ck_model_optgroup_label(model)
|
|
133
|
+
case model[:provider]
|
|
134
|
+
when "openrouter" then "OpenRouter — #{model[:id].to_s.split("/", 2).first.delete_prefix("~")}"
|
|
135
|
+
when "openai" then "OpenAI — #{ck_openai_model_family(model[:id])}"
|
|
136
|
+
else ck_provider_label(model[:provider])
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def ck_model_optgroup_sort_key(label)
|
|
141
|
+
if label.start_with?("OpenAI — ")
|
|
142
|
+
[0, OPENAI_MODEL_FAMILY_ORDER.index(label.delete_prefix("OpenAI — ")), label]
|
|
143
|
+
elsif label.start_with?("OpenRouter")
|
|
144
|
+
[2, 0, label]
|
|
145
|
+
else
|
|
146
|
+
[1, 0, label]
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
102
150
|
def ck_model_options_html(scope)
|
|
103
151
|
models = CompletionKit::ApiConfig.available_models(scope: scope)
|
|
104
152
|
return "" if models.empty?
|
|
@@ -47,6 +47,14 @@ module CompletionKit
|
|
|
47
47
|
false
|
|
48
48
|
end
|
|
49
49
|
|
|
50
|
+
def model_count
|
|
51
|
+
Model.where(provider: provider).active.count
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.discovery_in_progress?
|
|
55
|
+
where(discovery_status: "discovering").exists?
|
|
56
|
+
end
|
|
57
|
+
|
|
50
58
|
def prompt_count
|
|
51
59
|
model_ids = Model.where(provider: provider).pluck(:model_id)
|
|
52
60
|
return 0 if model_ids.empty?
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
</div>
|
|
11
11
|
<% end %>
|
|
12
12
|
|
|
13
|
+
<% if prompt.persisted? && prompt.runs.exists? %>
|
|
14
|
+
<p class="ck-version-note">Editing <%= prompt.version_label %> — it has runs, so saving creates a new version of this prompt.</p>
|
|
15
|
+
<% end %>
|
|
16
|
+
|
|
13
17
|
<div class="ck-card ck-form-card">
|
|
14
18
|
<div class="ck-field">
|
|
15
19
|
<%= form.label :name, "Name", class: "ck-label" %>
|
|
@@ -33,7 +37,8 @@
|
|
|
33
37
|
<% if available.any? %>
|
|
34
38
|
<div class="ck-select-with-action">
|
|
35
39
|
<%= form.select :llm_model, ck_grouped_models(available, prompt.llm_model), { include_blank: "— Select a model —" }, { class: "ck-input", id: "prompt_llm_model" } %>
|
|
36
|
-
|
|
40
|
+
<% ck_refreshing = CompletionKit::ProviderCredential.discovery_in_progress? %>
|
|
41
|
+
<button type="button" class="ck-icon-btn<%= ' ck-icon-btn--spinning' if ck_refreshing %>" title="Refresh models" <%= 'disabled' if ck_refreshing %> onclick="ckRefreshModels()"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" width="16" height="16"><path fill-rule="evenodd" d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.681.75.75 0 0 1-1.264-.808 6 6 0 0 1 9.44-.908l.84.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44.908l-.84-.84v1.68a.75.75 0 0 1-1.5 0V9.567a.75.75 0 0 1 .75-.75h3.182a.75.75 0 0 1 0 1.5h-1.37l.84.841a4.5 4.5 0 0 0 7.08-.681.75.75 0 0 1 1.024-.274Z" clip-rule="evenodd"/></svg></button>
|
|
37
42
|
</div>
|
|
38
43
|
<% else %>
|
|
39
44
|
<p class="ck-meta-copy">No models available. <%= link_to "Add a provider", provider_credentials_path, class: "ck-link" %> or click refresh after configuring a provider.</p>
|
|
@@ -30,6 +30,13 @@
|
|
|
30
30
|
<tr onclick="window.location='<%= prompt_path(prompt) %>'" style="cursor: pointer;">
|
|
31
31
|
<td>
|
|
32
32
|
<strong><%= prompt.name %></strong>
|
|
33
|
+
<% if prompt.description.present? %>
|
|
34
|
+
<p class="ck-prompts-table__desc"><%= truncate(prompt.description, length: 120) %></p>
|
|
35
|
+
<% end %>
|
|
36
|
+
<div class="ck-endpoint ck-endpoint--compact" onclick="event.stopPropagation()">
|
|
37
|
+
<code class="ck-endpoint__url" id="prompt_endpoint_<%= prompt.id %>"><%= api_v1_prompt_path(prompt.slug) %></code>
|
|
38
|
+
<button type="button" class="ck-icon-btn" title="Copy API path" onclick="event.stopPropagation();navigator.clipboard.writeText(document.getElementById('prompt_endpoint_<%= prompt.id %>').textContent)"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" width="13" height="13" aria-hidden="true"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"/><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"/></svg></button>
|
|
39
|
+
</div>
|
|
33
40
|
<% if prompt.tags.any? %>
|
|
34
41
|
<div class="tag-marks-row">
|
|
35
42
|
<%= render "completion_kit/tags/marks", tags: prompt.tags %>
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<div>
|
|
8
8
|
<div class="ck-inline">
|
|
9
9
|
<h1 class="ck-title"><%= @prompt.name %></h1>
|
|
10
|
+
<span class="ck-chip ck-chip--soft"><%= @prompt.version_label %></span>
|
|
10
11
|
<span class="ck-chip"><%= @prompt.llm_model %></span>
|
|
11
12
|
</div>
|
|
12
13
|
<% if @prompt.description.present? %>
|
|
@@ -47,7 +48,9 @@
|
|
|
47
48
|
<pre class="ck-code ck-code--dark"><%= @prompt.template %></pre>
|
|
48
49
|
</section>
|
|
49
50
|
|
|
50
|
-
<% versions = @prompt.family_versions %>
|
|
51
|
+
<% versions = @prompt.family_versions.includes(runs: { responses: :reviews }).to_a %>
|
|
52
|
+
<% predecessor_of = versions.index_with { |v| versions.detect { |o| o.version_number < v.version_number } } %>
|
|
53
|
+
<% version_changed = ->(v, pred) { pred && (pred.template != v.template || pred.llm_model != v.llm_model) } %>
|
|
51
54
|
<% if versions.size > 1 %>
|
|
52
55
|
<section class="ck-card--spaced">
|
|
53
56
|
<p class="ck-kicker">Versions</p>
|
|
@@ -56,28 +59,82 @@
|
|
|
56
59
|
<tr>
|
|
57
60
|
<th>Version</th>
|
|
58
61
|
<th>Model</th>
|
|
62
|
+
<th>Best score</th>
|
|
59
63
|
<th>Created</th>
|
|
60
|
-
<th></th>
|
|
61
64
|
</tr>
|
|
62
65
|
</thead>
|
|
63
66
|
<tbody>
|
|
64
67
|
<% versions.each do |v| %>
|
|
65
|
-
|
|
66
|
-
|
|
68
|
+
<% best_score = v.runs.map(&:avg_score).compact.max %>
|
|
69
|
+
<% pred = predecessor_of[v] %>
|
|
70
|
+
<tr class="<%= "ck-results-table__row--active" if v.id == @prompt.id %>" onclick="window.location='<%= prompt_path(v) %>'" style="cursor: pointer;">
|
|
71
|
+
<td>
|
|
72
|
+
<div class="ck-version-cell">
|
|
73
|
+
<div class="ck-version-cell__label" onclick="event.stopPropagation()">
|
|
74
|
+
<strong>v<%= v.version_number %></strong>
|
|
75
|
+
<% if v.current? %>
|
|
76
|
+
<span class="ck-chip">Published</span>
|
|
77
|
+
<% else %>
|
|
78
|
+
<%= button_to "Publish", publish_prompt_path(v), method: :post, class: "ck-chip ck-chip--publish", form_class: "inline-block" %>
|
|
79
|
+
<% end %>
|
|
80
|
+
</div>
|
|
81
|
+
<% if version_changed.call(v, pred) %>
|
|
82
|
+
<button type="button" class="ck-cell-link ck-cell-link--delta" title="What changed from v<%= pred.version_number %>" onclick="event.stopPropagation();document.getElementById('ck-vdiff-<%= v.id %>').showModal()">Δ</button>
|
|
83
|
+
<% end %>
|
|
84
|
+
</div>
|
|
85
|
+
</td>
|
|
67
86
|
<td><span class="ck-chip ck-chip--soft"><%= v.llm_model %></span></td>
|
|
68
|
-
<td class="ck-meta-copy"><time datetime="<%= v.created_at.iso8601 %>" data-local-time><%= v.created_at.utc.strftime("%b %-d, %Y at %-I:%M %p UTC") %></time></td>
|
|
69
87
|
<td>
|
|
70
|
-
<% if
|
|
71
|
-
<span class="
|
|
88
|
+
<% if best_score %>
|
|
89
|
+
<span class="<%= ck_badge_classes(ck_score_kind(best_score)) %>"><%= best_score %></span>
|
|
72
90
|
<% else %>
|
|
73
|
-
|
|
91
|
+
<span class="ck-prompts-table__dim">—</span>
|
|
74
92
|
<% end %>
|
|
75
93
|
</td>
|
|
94
|
+
<td class="ck-meta-copy"><time datetime="<%= v.created_at.iso8601 %>" data-local-time><%= v.created_at.utc.strftime("%b %-d, %Y at %-I:%M %p UTC") %></time></td>
|
|
76
95
|
</tr>
|
|
77
96
|
<% end %>
|
|
78
97
|
</tbody>
|
|
79
98
|
</table>
|
|
80
99
|
</section>
|
|
100
|
+
|
|
101
|
+
<% versions.each do |v| %>
|
|
102
|
+
<% pred = predecessor_of[v] %>
|
|
103
|
+
<% next unless version_changed.call(v, pred) %>
|
|
104
|
+
<dialog id="ck-vdiff-<%= v.id %>" class="ck-modal" onclick="if(event.target===this)this.close()">
|
|
105
|
+
<article class="ck-modal__panel" tabindex="-1" autofocus onclick="event.stopPropagation()">
|
|
106
|
+
<header class="ck-modal__header">
|
|
107
|
+
<div class="ck-modal__heading">
|
|
108
|
+
<h2 class="ck-modal__title">v<%= pred.version_number %> → v<%= v.version_number %></h2>
|
|
109
|
+
<span class="ck-modal__meta">What changed in <%= v.version_label %><% if v.current? %> (published)<% end %></span>
|
|
110
|
+
</div>
|
|
111
|
+
<button type="button" class="ck-modal__close" aria-label="Close" onclick="this.closest('dialog').close()">×</button>
|
|
112
|
+
</header>
|
|
113
|
+
<div class="ck-modal__body">
|
|
114
|
+
<% if pred.llm_model != v.llm_model %>
|
|
115
|
+
<p class="ck-version-change">
|
|
116
|
+
<span class="ck-version-change__label">Model</span>
|
|
117
|
+
<span class="ck-version-change__old"><%= pred.llm_model %></span>
|
|
118
|
+
<span class="ck-version-change__arrow" aria-hidden="true">→</span>
|
|
119
|
+
<span class="ck-version-change__new"><%= v.llm_model %></span>
|
|
120
|
+
</p>
|
|
121
|
+
<% end %>
|
|
122
|
+
<% if pred.template != v.template %>
|
|
123
|
+
<div class="ck-suggest-diff">
|
|
124
|
+
<div class="ck-suggest-diff__pane">
|
|
125
|
+
<div class="ck-suggest-diff__header"><span class="ck-suggest-diff__label ck-suggest-diff__label--before"><%= pred.version_label %></span></div>
|
|
126
|
+
<pre class="ck-suggest-diff__code"><%= ck_word_diff_old(pred.template, v.template) %></pre>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="ck-suggest-diff__pane">
|
|
129
|
+
<div class="ck-suggest-diff__header"><span class="ck-suggest-diff__label ck-suggest-diff__label--after"><%= v.version_label %></span></div>
|
|
130
|
+
<pre class="ck-suggest-diff__code"><%= ck_word_diff_new(pred.template, v.template) %></pre>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<% end %>
|
|
134
|
+
</div>
|
|
135
|
+
</article>
|
|
136
|
+
</dialog>
|
|
137
|
+
<% end %>
|
|
81
138
|
<% end %>
|
|
82
139
|
|
|
83
140
|
<% if @runs.any? %>
|
|
@@ -41,26 +41,31 @@
|
|
|
41
41
|
</tr>
|
|
42
42
|
</thead>
|
|
43
43
|
<tbody>
|
|
44
|
-
<% models.each do |
|
|
45
|
-
|
|
46
|
-
<
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
44
|
+
<% ck_model_table_sections(models).each do |section_label, section_models| %>
|
|
45
|
+
<% if section_label %>
|
|
46
|
+
<tr class="ck-model-table__section"><td colspan="3"><%= section_label %></td></tr>
|
|
47
|
+
<% end %>
|
|
48
|
+
<% section_models.each do |m| %>
|
|
49
|
+
<tr>
|
|
50
|
+
<td class="ck-model-table__name"><%= m.display_name || m.model_id %></td>
|
|
51
|
+
<td class="ck-model-table__cap">
|
|
52
|
+
<% if m.supports_generation %>
|
|
53
|
+
<span class="ck-model-table__tick" aria-label="Supports generation">✓</span>
|
|
54
|
+
<% else %>
|
|
55
|
+
<span class="ck-model-table__dash" aria-label="No generation support">—</span>
|
|
56
|
+
<% end %>
|
|
57
|
+
</td>
|
|
58
|
+
<td class="ck-model-table__cap">
|
|
59
|
+
<% if m.supports_judging %>
|
|
60
|
+
<span class="ck-model-table__tick" aria-label="Supports judging">✓</span>
|
|
61
|
+
<% elsif m.supports_judging.nil? %>
|
|
62
|
+
<span class="ck-model-table__unknown" aria-label="Untested as judge" title="Untested as a judge — selectable; a successful run confirms it">?</span>
|
|
63
|
+
<% else %>
|
|
64
|
+
<span class="ck-model-table__dash" aria-label="Not usable as judge">—</span>
|
|
65
|
+
<% end %>
|
|
66
|
+
</td>
|
|
67
|
+
</tr>
|
|
68
|
+
<% end %>
|
|
64
69
|
<% end %>
|
|
65
70
|
</tbody>
|
|
66
71
|
</table>
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
|
|
28
28
|
<div class="ck-provider-card__meta">
|
|
29
29
|
<span><%= provider_credential.api_endpoint.presence || default_endpoints[provider_credential.provider] %></span>
|
|
30
|
+
<span><%= provider_credential.model_count %> models</span>
|
|
30
31
|
<span><%= provider_credential.prompt_count %> prompts</span>
|
|
31
32
|
<span><%= provider_credential.judge_count %> judges</span>
|
|
32
33
|
<span><% if provider_credential.last_used_at %>Used <time data-relative-time datetime="<%= provider_credential.last_used_at.utc.iso8601 %>"><%= time_ago_in_words(provider_credential.last_used_at) %></time> ago<% else %>Never used<% end %></span>
|
|
@@ -74,7 +74,8 @@
|
|
|
74
74
|
<% if available.any? %>
|
|
75
75
|
<div class="ck-select-with-action">
|
|
76
76
|
<%= form.select :judge_model, ck_grouped_models(available, run.judge_model), { include_blank: "None" }, { class: "ck-input", id: "run_judge_model" } %>
|
|
77
|
-
|
|
77
|
+
<% ck_refreshing = CompletionKit::ProviderCredential.discovery_in_progress? %>
|
|
78
|
+
<button type="button" class="ck-icon-btn<%= ' ck-icon-btn--spinning' if ck_refreshing %>" title="Refresh models" <%= 'disabled' if ck_refreshing %> onclick="ckRefreshModels()"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" width="16" height="16"><path fill-rule="evenodd" d="M13.836 2.477a.75.75 0 0 1 .75.75v3.182a.75.75 0 0 1-.75.75h-3.182a.75.75 0 0 1 0-1.5h1.37l-.84-.841a4.5 4.5 0 0 0-7.08.681.75.75 0 0 1-1.264-.808 6 6 0 0 1 9.44-.908l.84.84V3.227a.75.75 0 0 1 .75-.75Zm-.911 7.5A.75.75 0 0 1 13.199 11a6 6 0 0 1-9.44.908l-.84-.84v1.68a.75.75 0 0 1-1.5 0V9.567a.75.75 0 0 1 .75-.75h3.182a.75.75 0 0 1 0 1.5h-1.37l.84.841a4.5 4.5 0 0 0 7.08-.681.75.75 0 0 1 1.024-.274Z" clip-rule="evenodd"/></svg></button>
|
|
78
79
|
</div>
|
|
79
80
|
<p class="ck-field-hint" id="judge-hint"></p>
|
|
80
81
|
<div hidden data-refresh-progress-carriers>
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
|
|
81
81
|
<% if @run.dataset %>
|
|
82
82
|
<dialog id="dataset-preview-<%= @run.id %>" class="ck-modal" onclick="if(event.target===this)this.close()">
|
|
83
|
-
<article class="ck-modal__panel" onclick="event.stopPropagation()">
|
|
83
|
+
<article class="ck-modal__panel" tabindex="-1" autofocus onclick="event.stopPropagation()">
|
|
84
84
|
<header class="ck-modal__header">
|
|
85
85
|
<div class="ck-modal__heading">
|
|
86
86
|
<h2 class="ck-modal__title"><%= @run.dataset.name %></h2>
|
|
@@ -45,8 +45,9 @@
|
|
|
45
45
|
<main class="ck-main">
|
|
46
46
|
<div class="ck-wrap">
|
|
47
47
|
<% flash.each do |type, message| %>
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
<% notice = type.to_s == "notice" %>
|
|
49
|
+
<div class="ck-flash <%= notice ? "ck-flash--notice" : "ck-flash--alert" %>" role="<%= notice ? "status" : "alert" %>">
|
|
50
|
+
<span class="ck-flash__label"><%= notice ? "Notice" : "Alert" %></span><span class="ck-flash__body"><%= message %></span>
|
|
50
51
|
</div>
|
|
51
52
|
<% end %>
|
|
52
53
|
|