completion-kit 0.4.8 → 0.5.1
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 +12 -0
- data/app/assets/config/completion_kit_manifest.js +1 -0
- data/app/assets/javascripts/completion_kit/application.js +157 -0
- data/app/assets/stylesheets/completion_kit/application.css +382 -0
- data/app/controllers/completion_kit/api/v1/datasets_controller.rb +2 -2
- data/app/controllers/completion_kit/api/v1/metric_groups_controller.rb +2 -2
- data/app/controllers/completion_kit/api/v1/metrics_controller.rb +3 -2
- data/app/controllers/completion_kit/api/v1/prompts_controller.rb +5 -4
- data/app/controllers/completion_kit/api/v1/runs_controller.rb +3 -2
- data/app/controllers/completion_kit/api/v1/tags_controller.rb +51 -0
- data/app/controllers/completion_kit/datasets_controller.rb +3 -2
- data/app/controllers/completion_kit/metric_groups_controller.rb +7 -6
- data/app/controllers/completion_kit/metrics_controller.rb +4 -2
- data/app/controllers/completion_kit/prompts_controller.rb +7 -4
- data/app/controllers/completion_kit/runs_controller.rb +4 -3
- data/app/controllers/completion_kit/tags_controller.rb +50 -0
- data/app/controllers/concerns/completion_kit/tag_filtering.rb +22 -0
- data/app/helpers/completion_kit/application_helper.rb +11 -0
- data/app/models/completion_kit/dataset.rb +5 -2
- data/app/models/completion_kit/metric.rb +4 -1
- data/app/models/completion_kit/metric_group.rb +4 -1
- data/app/models/completion_kit/prompt.rb +4 -1
- data/app/models/completion_kit/run.rb +3 -1
- data/app/models/completion_kit/tag.rb +39 -0
- data/app/models/completion_kit/tagging.rb +12 -0
- data/app/models/concerns/completion_kit/taggable.rb +24 -0
- data/app/services/completion_kit/mcp_dispatcher.rb +3 -1
- data/app/services/completion_kit/mcp_tools/datasets.rb +6 -4
- data/app/services/completion_kit/mcp_tools/metric_groups.rb +6 -2
- data/app/services/completion_kit/mcp_tools/metrics.rb +8 -4
- data/app/services/completion_kit/mcp_tools/prompts.rb +10 -5
- data/app/services/completion_kit/mcp_tools/runs.rb +7 -3
- data/app/services/completion_kit/mcp_tools/tags.rb +74 -0
- data/app/views/completion_kit/api_reference/index.html.erb +38 -0
- data/app/views/completion_kit/datasets/_form.html.erb +20 -1
- data/app/views/completion_kit/datasets/index.html.erb +17 -1
- data/app/views/completion_kit/datasets/show.html.erb +6 -0
- data/app/views/completion_kit/metric_groups/_form.html.erb +74 -19
- data/app/views/completion_kit/metric_groups/index.html.erb +30 -4
- data/app/views/completion_kit/metrics/_form.html.erb +19 -1
- data/app/views/completion_kit/metrics/index.html.erb +18 -2
- data/app/views/completion_kit/metrics/show.html.erb +6 -0
- data/app/views/completion_kit/prompts/_form.html.erb +20 -1
- data/app/views/completion_kit/prompts/index.html.erb +17 -1
- data/app/views/completion_kit/prompts/show.html.erb +6 -0
- data/app/views/completion_kit/provider_credentials/_form.html.erb +1 -1
- data/app/views/completion_kit/provider_credentials/index.html.erb +3 -1
- data/app/views/completion_kit/runs/_form.html.erb +25 -3
- data/app/views/completion_kit/runs/_row.html.erb +5 -0
- data/app/views/completion_kit/runs/index.html.erb +9 -0
- data/app/views/completion_kit/runs/show.html.erb +6 -0
- data/app/views/completion_kit/shared/_settings_nav.html.erb +9 -0
- data/app/views/completion_kit/tags/_filter_bar.html.erb +15 -0
- data/app/views/completion_kit/tags/_form.html.erb +40 -0
- data/app/views/completion_kit/tags/_marks.html.erb +3 -0
- data/app/views/completion_kit/tags/_picker.html.erb +20 -0
- data/app/views/completion_kit/tags/edit.html.erb +20 -0
- data/app/views/completion_kit/tags/index.html.erb +45 -0
- data/app/views/completion_kit/tags/new.html.erb +20 -0
- data/app/views/layouts/completion_kit/application.html.erb +11 -132
- data/config/routes.rb +2 -0
- data/db/migrate/20260509000001_create_completion_kit_tags.rb +10 -0
- data/db/migrate/20260509000002_create_completion_kit_taggings.rb +16 -0
- data/lib/completion_kit/engine.rb +5 -1
- data/lib/completion_kit/version.rb +1 -1
- metadata +19 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 89766aed83665a19253ddf7f4fcbc99ad3e9bc8c17123d7f7d33acac36bf3096
|
|
4
|
+
data.tar.gz: fe71adba78f455a2ce36ef3649bb3e0c94ca77f7534fbe6377604bf6f66d36ab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7dc10c0b796286e5c4535c761da1af4d49d4b1011477167849c8a398644cec30d310534971df8515ff5cda957596589d1277e6942c569b5c943a575a9c2a4e6c
|
|
7
|
+
data.tar.gz: a5a17559fc2032473aa6dd2946948cefd5b195f73dbd48e68c8ead4e322a469a1faca74e170168dad69dd947a88f2687675c007b41a92a91afbacaae752bc50d
|
data/README.md
CHANGED
|
@@ -64,6 +64,17 @@ bin/rails db:migrate
|
|
|
64
64
|
|
|
65
65
|
The engine mounts at `/completion_kit` in your app. CompletionKit's generate and judge flows enqueue Active Job jobs (`CompletionKit::GenerateRowJob`, `CompletionKit::JudgeReviewJob`, `CompletionKit::RunCompletionCheckJob`), so your host app needs an Active Job adapter that actually processes them — Solid Queue, Sidekiq, GoodJob, etc. The `:async` adapter is **not** suitable for production: it runs jobs in the web Puma's thread pool with no durability and no retry, and a long LLM call will block request handling.
|
|
66
66
|
|
|
67
|
+
### Host-app layout integration
|
|
68
|
+
|
|
69
|
+
If your host app overrides the engine layout (e.g. `layout "application"` on engine controllers, or rendering engine views inside your own shell), include both the engine's stylesheet and JavaScript in that layout:
|
|
70
|
+
|
|
71
|
+
```erb
|
|
72
|
+
<%= stylesheet_link_tag "completion_kit/application", media: "all" %>
|
|
73
|
+
<%= javascript_include_tag "completion_kit/application", defer: true %>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Without the JavaScript include, in-page behaviours silently fail: live tag-breadcrumb updates, relative-time ticking, CSV row hover-expand, model-refresh progress, focus-first-error, and local-time formatting.
|
|
77
|
+
|
|
67
78
|
## Providers
|
|
68
79
|
|
|
69
80
|
CompletionKit discovers available models from each provider's API automatically.
|
|
@@ -136,6 +147,7 @@ Only one mode can be active.
|
|
|
136
147
|
- **Response.** The model's output for one dataset row, with reviews attached.
|
|
137
148
|
- **Metric.** An evaluation dimension with a name, instruction, evaluation steps, and a 1-5 star scoring scale. The LLM judge uses this to score each response.
|
|
138
149
|
- **Metric Group.** A reusable group of metrics you can apply to a run as a set.
|
|
150
|
+
- **Tag.** A domain label you can attach to prompts, runs, metrics, and datasets. Auto-assigned from a 10-color palette. Filter any index page by tag (`?tag[]=...`).
|
|
139
151
|
- **Provider Credential.** An API key for a model provider. Encrypted at rest, never returned through the API.
|
|
140
152
|
|
|
141
153
|
## REST API
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
document.addEventListener("turbo:load", function() {
|
|
2
|
+
document.querySelectorAll("[data-local-time]").forEach(function(el) {
|
|
3
|
+
var d = new Date(el.getAttribute("datetime"));
|
|
4
|
+
el.textContent = d.toLocaleString(undefined, {year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"});
|
|
5
|
+
});
|
|
6
|
+
ckTickRelativeTimes();
|
|
7
|
+
ckAutoFocusFirstError();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
document.addEventListener("input", function(e) {
|
|
11
|
+
if (!e.target || e.target.id !== "tag_name") return;
|
|
12
|
+
var text = document.getElementById("tag-pill-text");
|
|
13
|
+
if (!text) return;
|
|
14
|
+
var v = e.target.value.trim().toLowerCase();
|
|
15
|
+
text.textContent = v.length ? v : (text.dataset.placeholder || "");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
function ckAutoFocusFirstError() {
|
|
19
|
+
var fieldSelector = "input:not([type=hidden]):not([type=submit]):not([type=button]):not([type=reset]):not([type=file]), textarea, select";
|
|
20
|
+
var marker = document.querySelector("form .ck-flash--alert, form [aria-invalid='true'], form .ck-field-error");
|
|
21
|
+
|
|
22
|
+
var target;
|
|
23
|
+
if (marker) {
|
|
24
|
+
var form = marker.closest("form");
|
|
25
|
+
target = form && (form.querySelector("[aria-invalid='true']") || form.querySelector(fieldSelector));
|
|
26
|
+
} else if (/\/new(\/|$)/.test(window.location.pathname)) {
|
|
27
|
+
target = document.querySelector("form " + fieldSelector);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!target || typeof target.focus !== "function") return;
|
|
31
|
+
target.focus({ preventScroll: false });
|
|
32
|
+
if (typeof target.setSelectionRange === "function" && typeof target.value === "string") {
|
|
33
|
+
try { target.setSelectionRange(target.value.length, target.value.length); } catch (e) {}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function ckRelativeTime(then) {
|
|
38
|
+
var seconds = Math.round((Date.now() - then.getTime()) / 1000);
|
|
39
|
+
if (seconds < 5) return "just now";
|
|
40
|
+
if (seconds < 60) return "less than a minute";
|
|
41
|
+
var minutes = Math.round(seconds / 60);
|
|
42
|
+
if (minutes < 60) return minutes === 1 ? "1 minute" : minutes + " minutes";
|
|
43
|
+
var hours = Math.round(minutes / 60);
|
|
44
|
+
if (hours < 24) return hours === 1 ? "about 1 hour" : "about " + hours + " hours";
|
|
45
|
+
var days = Math.round(hours / 24);
|
|
46
|
+
if (days < 30) return days === 1 ? "1 day" : days + " days";
|
|
47
|
+
var months = Math.round(days / 30);
|
|
48
|
+
if (months < 12) return months === 1 ? "about 1 month" : "about " + months + " months";
|
|
49
|
+
var years = Math.round(days / 365);
|
|
50
|
+
return years === 1 ? "about 1 year" : "about " + years + " years";
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function ckRelativeTimeCompact(then) {
|
|
54
|
+
var seconds = Math.round((Date.now() - then.getTime()) / 1000);
|
|
55
|
+
if (seconds < 60) return "now";
|
|
56
|
+
var minutes = Math.round(seconds / 60);
|
|
57
|
+
if (minutes < 60) return minutes + "m";
|
|
58
|
+
var hours = Math.round(minutes / 60);
|
|
59
|
+
if (hours < 24) return hours + "h";
|
|
60
|
+
var days = Math.round(hours / 24);
|
|
61
|
+
if (days < 30) return days + "d";
|
|
62
|
+
var months = Math.round(days / 30);
|
|
63
|
+
if (months < 12) return months + "mo";
|
|
64
|
+
var years = Math.round(days / 365);
|
|
65
|
+
return years + "y";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function ckTickRelativeTimes() {
|
|
69
|
+
document.querySelectorAll("[data-relative-time]").forEach(function(el) {
|
|
70
|
+
var then = new Date(el.getAttribute("datetime"));
|
|
71
|
+
if (isNaN(then.getTime())) return;
|
|
72
|
+
var verbose = el.getAttribute("data-relative-time") === "verbose";
|
|
73
|
+
el.textContent = verbose ? ckRelativeTime(then) : ckRelativeTimeCompact(then);
|
|
74
|
+
el.setAttribute("title", then.toLocaleString());
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!window.ckRelativeTimeInterval) {
|
|
79
|
+
window.ckRelativeTimeInterval = setInterval(ckTickRelativeTimes, 30000);
|
|
80
|
+
}
|
|
81
|
+
document.addEventListener("turbo:before-stream-render", function() {
|
|
82
|
+
requestAnimationFrame(ckTickRelativeTimes);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
var ckCsvHoverTimer = null;
|
|
86
|
+
var ckCsvHoverRow = null;
|
|
87
|
+
document.addEventListener("mouseover", function(e) {
|
|
88
|
+
var row = e.target.closest && e.target.closest(".ck-csv-table tbody tr");
|
|
89
|
+
if (!row || row === ckCsvHoverRow) return;
|
|
90
|
+
if (ckCsvHoverRow) ckCsvHoverRow.classList.remove("ck-csv-row--expanded");
|
|
91
|
+
ckCsvHoverRow = row;
|
|
92
|
+
clearTimeout(ckCsvHoverTimer);
|
|
93
|
+
ckCsvHoverTimer = setTimeout(function() {
|
|
94
|
+
if (ckCsvHoverRow === row) row.classList.add("ck-csv-row--expanded");
|
|
95
|
+
}, 350);
|
|
96
|
+
});
|
|
97
|
+
document.addEventListener("mouseout", function(e) {
|
|
98
|
+
var row = e.target.closest && e.target.closest(".ck-csv-table tbody tr");
|
|
99
|
+
if (!row) return;
|
|
100
|
+
var related = e.relatedTarget && e.relatedTarget.closest && e.relatedTarget.closest(".ck-csv-table tbody tr");
|
|
101
|
+
if (related === row) return;
|
|
102
|
+
clearTimeout(ckCsvHoverTimer);
|
|
103
|
+
row.classList.remove("ck-csv-row--expanded");
|
|
104
|
+
if (ckCsvHoverRow === row) ckCsvHoverRow = null;
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
var ckRefreshing = false;
|
|
108
|
+
function ckRefreshModels() {
|
|
109
|
+
if (ckRefreshing) return;
|
|
110
|
+
ckRefreshing = true;
|
|
111
|
+
var btn = document.querySelector('.ck-icon-btn[title="Refresh models"]');
|
|
112
|
+
if (btn) btn.classList.add('ck-icon-btn--spinning');
|
|
113
|
+
ckUpdateRefreshProgress();
|
|
114
|
+
var csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute("content");
|
|
115
|
+
fetch("/completion_kit/refresh_models", {
|
|
116
|
+
method: "POST",
|
|
117
|
+
headers: { "X-CSRF-Token": csrfToken }
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function ckUpdateRefreshProgress() {
|
|
122
|
+
var status = document.getElementById('refresh-status');
|
|
123
|
+
if (!status) return;
|
|
124
|
+
var carriers = document.querySelectorAll('[data-refresh-progress-carriers] [id^="discovery_status_"]');
|
|
125
|
+
var totalCurrent = 0, totalTotal = 0, anyDiscovering = false;
|
|
126
|
+
carriers.forEach(function(node) {
|
|
127
|
+
if (!node.querySelector('.ck-discovery-bar')) return;
|
|
128
|
+
if (node.querySelector('.ck-discovery-bar--failed') || node.querySelector('.ck-discovery-bar--completed')) return;
|
|
129
|
+
anyDiscovering = true;
|
|
130
|
+
var match = node.textContent.match(/(\d+)\s*\/\s*(\d+)/);
|
|
131
|
+
if (match) {
|
|
132
|
+
totalCurrent += parseInt(match[1], 10);
|
|
133
|
+
totalTotal += parseInt(match[2], 10);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
if (anyDiscovering || ckRefreshing) {
|
|
137
|
+
if (totalTotal > 0) {
|
|
138
|
+
status.textContent = 'Refreshing models… ' + totalCurrent + '/' + totalTotal;
|
|
139
|
+
} else {
|
|
140
|
+
status.textContent = 'Refreshing models…';
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
document.addEventListener("turbo:before-stream-render", function(event) {
|
|
146
|
+
var target = event.target.getAttribute("target");
|
|
147
|
+
if (target && target.indexOf("discovery_status_") === 0) {
|
|
148
|
+
requestAnimationFrame(ckUpdateRefreshProgress);
|
|
149
|
+
}
|
|
150
|
+
if (target === "prompt_llm_model" || target === "run_judge_model") {
|
|
151
|
+
ckRefreshing = false;
|
|
152
|
+
var btn = document.querySelector('.ck-icon-btn[title="Refresh models"]');
|
|
153
|
+
if (btn) btn.classList.remove('ck-icon-btn--spinning');
|
|
154
|
+
var status = document.getElementById('refresh-status');
|
|
155
|
+
if (status) { status.textContent = 'Models updated.'; setTimeout(function() { status.textContent = ' '; }, 3000); }
|
|
156
|
+
}
|
|
157
|
+
});
|
|
@@ -501,6 +501,11 @@ tr:hover .ck-chip--publish {
|
|
|
501
501
|
transform: translateY(-1px);
|
|
502
502
|
}
|
|
503
503
|
|
|
504
|
+
.ck-button:focus-visible {
|
|
505
|
+
outline: 2px solid var(--ck-accent);
|
|
506
|
+
outline-offset: 2px;
|
|
507
|
+
}
|
|
508
|
+
|
|
504
509
|
.ck-button--primary {
|
|
505
510
|
background: var(--ck-accent);
|
|
506
511
|
color: #080b14;
|
|
@@ -1531,6 +1536,11 @@ tr:hover .ck-chip--publish {
|
|
|
1531
1536
|
color: var(--ck-danger);
|
|
1532
1537
|
}
|
|
1533
1538
|
|
|
1539
|
+
.ck-icon-btn:focus-visible {
|
|
1540
|
+
outline: 2px solid var(--ck-accent);
|
|
1541
|
+
outline-offset: 2px;
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1534
1544
|
.ck-icon-btn--spinning svg {
|
|
1535
1545
|
animation: ck-spin 0.8s linear infinite;
|
|
1536
1546
|
}
|
|
@@ -2594,6 +2604,7 @@ select.ck-input {
|
|
|
2594
2604
|
align-items: center;
|
|
2595
2605
|
gap: 0.4rem;
|
|
2596
2606
|
padding: 0.32rem 0.7rem;
|
|
2607
|
+
white-space: nowrap;
|
|
2597
2608
|
background: transparent;
|
|
2598
2609
|
border: 1px solid var(--ck-line);
|
|
2599
2610
|
border-radius: 999px;
|
|
@@ -2842,6 +2853,72 @@ select.ck-input {
|
|
|
2842
2853
|
color: var(--ck-dim);
|
|
2843
2854
|
}
|
|
2844
2855
|
|
|
2856
|
+
.ck-clamp-2 {
|
|
2857
|
+
display: -webkit-box;
|
|
2858
|
+
-webkit-line-clamp: 2;
|
|
2859
|
+
-webkit-box-orient: vertical;
|
|
2860
|
+
overflow: hidden;
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
.ck-tags-table th:first-child,
|
|
2864
|
+
.ck-tags-table td:first-child {
|
|
2865
|
+
width: 35%;
|
|
2866
|
+
}
|
|
2867
|
+
.ck-tags-table th:nth-child(2),
|
|
2868
|
+
.ck-tags-table td:nth-child(2) {
|
|
2869
|
+
width: 60%;
|
|
2870
|
+
}
|
|
2871
|
+
.tag-mark.tag-mark--lg {
|
|
2872
|
+
padding: 4px 10px;
|
|
2873
|
+
font-size: 0.8rem;
|
|
2874
|
+
letter-spacing: 0.02em;
|
|
2875
|
+
border-radius: 5px;
|
|
2876
|
+
}
|
|
2877
|
+
.ck-tags-table__unused {
|
|
2878
|
+
color: var(--ck-dim);
|
|
2879
|
+
font-style: italic;
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
.ck-mg-members {
|
|
2883
|
+
display: flex;
|
|
2884
|
+
flex-wrap: wrap;
|
|
2885
|
+
gap: 0.3rem;
|
|
2886
|
+
}
|
|
2887
|
+
.ck-mg-member {
|
|
2888
|
+
display: inline-flex;
|
|
2889
|
+
align-items: center;
|
|
2890
|
+
padding: 2px 8px;
|
|
2891
|
+
background: var(--ck-surface-soft);
|
|
2892
|
+
border: 1px solid var(--ck-line);
|
|
2893
|
+
border-radius: 4px;
|
|
2894
|
+
font-family: var(--ck-mono);
|
|
2895
|
+
font-size: 11px;
|
|
2896
|
+
letter-spacing: 0.02em;
|
|
2897
|
+
color: var(--ck-muted);
|
|
2898
|
+
white-space: nowrap;
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
.ck-metric-tag-filter {
|
|
2902
|
+
display: flex;
|
|
2903
|
+
flex-wrap: wrap;
|
|
2904
|
+
align-items: center;
|
|
2905
|
+
gap: 8px;
|
|
2906
|
+
margin: 0.5rem 0 0.85rem;
|
|
2907
|
+
}
|
|
2908
|
+
.ck-metric-tag-filter__label {
|
|
2909
|
+
font-family: var(--ck-mono);
|
|
2910
|
+
font-size: 11px;
|
|
2911
|
+
letter-spacing: 0.14em;
|
|
2912
|
+
text-transform: uppercase;
|
|
2913
|
+
color: var(--ck-dim);
|
|
2914
|
+
margin-right: 0.3rem;
|
|
2915
|
+
}
|
|
2916
|
+
.ck-metric-tag-filter__chip {
|
|
2917
|
+
border-radius: 5px;
|
|
2918
|
+
font: inherit;
|
|
2919
|
+
cursor: pointer;
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2845
2922
|
.ck-metrics-table__groups {
|
|
2846
2923
|
display: flex;
|
|
2847
2924
|
flex-wrap: wrap;
|
|
@@ -2909,6 +2986,10 @@ a.ck-metric-group-pill {
|
|
|
2909
2986
|
white-space: nowrap;
|
|
2910
2987
|
}
|
|
2911
2988
|
|
|
2989
|
+
.ck-runs-table__identity .tag-marks-row {
|
|
2990
|
+
padding-left: 1.2rem;
|
|
2991
|
+
}
|
|
2992
|
+
|
|
2912
2993
|
.ck-runs-table__config-link {
|
|
2913
2994
|
color: var(--ck-muted);
|
|
2914
2995
|
text-decoration: none;
|
|
@@ -3396,3 +3477,304 @@ a.ck-metric-group-pill {
|
|
|
3396
3477
|
text-overflow: ellipsis;
|
|
3397
3478
|
}
|
|
3398
3479
|
|
|
3480
|
+
:root {
|
|
3481
|
+
--tag-crimson: #F24E1E;
|
|
3482
|
+
--tag-burnt-orange: #FF8A00;
|
|
3483
|
+
--tag-amber: #FFC700;
|
|
3484
|
+
--tag-mint: #14AE5C;
|
|
3485
|
+
--tag-deep-emerald: #00623F;
|
|
3486
|
+
--tag-electric-cyan: #00D1FF;
|
|
3487
|
+
--tag-cobalt-blue: #0D99FF;
|
|
3488
|
+
--tag-deep-indigo: #5551FF;
|
|
3489
|
+
--tag-amethyst: #9747FF;
|
|
3490
|
+
--tag-rose: #FF5CBE;
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
.tag {
|
|
3494
|
+
display: inline-flex;
|
|
3495
|
+
align-items: center;
|
|
3496
|
+
gap: 6px;
|
|
3497
|
+
padding: 4px 9px;
|
|
3498
|
+
min-height: calc(1.4em + 8px);
|
|
3499
|
+
border-radius: 5px;
|
|
3500
|
+
font-family: var(--ck-mono);
|
|
3501
|
+
font-size: 12.5px;
|
|
3502
|
+
font-weight: 500;
|
|
3503
|
+
letter-spacing: 0.01em;
|
|
3504
|
+
text-transform: lowercase;
|
|
3505
|
+
color: white;
|
|
3506
|
+
line-height: 1.4;
|
|
3507
|
+
white-space: nowrap;
|
|
3508
|
+
transition: filter 120ms ease;
|
|
3509
|
+
text-decoration: none;
|
|
3510
|
+
user-select: none;
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
.tag::before {
|
|
3514
|
+
content: "";
|
|
3515
|
+
width: 6px;
|
|
3516
|
+
height: 6px;
|
|
3517
|
+
border-radius: 50%;
|
|
3518
|
+
background: rgba(255, 255, 255, 0.85);
|
|
3519
|
+
flex-shrink: 0;
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3522
|
+
.tag > span:empty { display: none; }
|
|
3523
|
+
|
|
3524
|
+
a.tag, label.tag { cursor: pointer; }
|
|
3525
|
+
a.tag:hover, label.tag:hover { filter: brightness(1.12); }
|
|
3526
|
+
|
|
3527
|
+
|
|
3528
|
+
.tag-crimson { background-color: var(--tag-crimson); --current-tag-color: var(--tag-crimson); }
|
|
3529
|
+
.tag-burnt-orange { background-color: var(--tag-burnt-orange); --current-tag-color: var(--tag-burnt-orange); color: #2a1300; }
|
|
3530
|
+
.tag-burnt-orange::before { background: rgba(0, 0, 0, 0.55); }
|
|
3531
|
+
.tag-amber { background-color: var(--tag-amber); --current-tag-color: var(--tag-amber); color: #1a0f00; }
|
|
3532
|
+
.tag-amber::before { background: rgba(0, 0, 0, 0.55); }
|
|
3533
|
+
.tag-mint { background-color: var(--tag-mint); --current-tag-color: var(--tag-mint); }
|
|
3534
|
+
.tag-deep-emerald { background-color: var(--tag-deep-emerald); --current-tag-color: var(--tag-deep-emerald); }
|
|
3535
|
+
.tag-electric-cyan { background-color: var(--tag-electric-cyan); --current-tag-color: var(--tag-electric-cyan); color: #001620; }
|
|
3536
|
+
.tag-electric-cyan::before { background: rgba(0, 0, 0, 0.55); }
|
|
3537
|
+
.tag-cobalt-blue { background-color: var(--tag-cobalt-blue); --current-tag-color: var(--tag-cobalt-blue); }
|
|
3538
|
+
.tag-deep-indigo { background-color: var(--tag-deep-indigo); --current-tag-color: var(--tag-deep-indigo); }
|
|
3539
|
+
.tag-amethyst { background-color: var(--tag-amethyst); --current-tag-color: var(--tag-amethyst); }
|
|
3540
|
+
.tag-rose { background-color: var(--tag-rose); --current-tag-color: var(--tag-rose); color: #2a0017; }
|
|
3541
|
+
.tag-rose::before { background: rgba(0, 0, 0, 0.6); }
|
|
3542
|
+
|
|
3543
|
+
.tag-outline {
|
|
3544
|
+
background-color: transparent !important;
|
|
3545
|
+
border: 1px solid currentColor;
|
|
3546
|
+
box-shadow: none;
|
|
3547
|
+
}
|
|
3548
|
+
.tag-outline::before { background: currentColor; }
|
|
3549
|
+
.tag-outline.tag-crimson { color: var(--tag-crimson); }
|
|
3550
|
+
.tag-outline.tag-burnt-orange { color: var(--tag-burnt-orange); }
|
|
3551
|
+
.tag-outline.tag-amber { color: var(--tag-amber); }
|
|
3552
|
+
.tag-outline.tag-mint { color: var(--tag-mint); }
|
|
3553
|
+
.tag-outline.tag-deep-emerald { color: var(--tag-deep-emerald); }
|
|
3554
|
+
.tag-outline.tag-electric-cyan { color: var(--tag-electric-cyan); }
|
|
3555
|
+
.tag-outline.tag-cobalt-blue { color: var(--tag-cobalt-blue); }
|
|
3556
|
+
.tag-outline.tag-deep-indigo { color: var(--tag-deep-indigo); }
|
|
3557
|
+
.tag-outline.tag-amethyst { color: var(--tag-amethyst); }
|
|
3558
|
+
.tag-outline.tag-rose { color: var(--tag-rose); }
|
|
3559
|
+
|
|
3560
|
+
/* Inline mark — quiet "color dot + name" used on row metadata */
|
|
3561
|
+
.tag-mark {
|
|
3562
|
+
display: inline-flex;
|
|
3563
|
+
align-items: center;
|
|
3564
|
+
gap: 5px;
|
|
3565
|
+
padding: 2px 8px;
|
|
3566
|
+
min-height: 22px;
|
|
3567
|
+
box-sizing: border-box;
|
|
3568
|
+
border: 1px solid transparent;
|
|
3569
|
+
border-radius: 4px;
|
|
3570
|
+
font-family: var(--ck-mono);
|
|
3571
|
+
font-size: 11px;
|
|
3572
|
+
letter-spacing: 0.02em;
|
|
3573
|
+
text-transform: lowercase;
|
|
3574
|
+
white-space: nowrap;
|
|
3575
|
+
background: color-mix(in srgb, var(--mark-color) 24%, transparent);
|
|
3576
|
+
color: color-mix(in srgb, var(--mark-color) 88%, var(--ck-text));
|
|
3577
|
+
}
|
|
3578
|
+
|
|
3579
|
+
.tag-mark::before {
|
|
3580
|
+
content: "";
|
|
3581
|
+
display: inline-block;
|
|
3582
|
+
width: 0.95em;
|
|
3583
|
+
height: 0.95em;
|
|
3584
|
+
flex-shrink: 0;
|
|
3585
|
+
background-color: currentColor;
|
|
3586
|
+
-webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><path fill-rule='evenodd' clip-rule='evenodd' d='M3 4.5C3 3.67 3.67 3 4.5 3h5.379c.398 0 .779.158 1.06.44L18.06 10.56c.585.586.585 1.535 0 2.121l-5.379 5.379a1.5 1.5 0 0 1-2.121 0L3.44 10.94A1.5 1.5 0 0 1 3 9.879V4.5Zm3.5 1.75a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Z' fill='black'/></svg>") no-repeat center / contain;
|
|
3587
|
+
mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><path fill-rule='evenodd' clip-rule='evenodd' d='M3 4.5C3 3.67 3.67 3 4.5 3h5.379c.398 0 .779.158 1.06.44L18.06 10.56c.585.586.585 1.535 0 2.121l-5.379 5.379a1.5 1.5 0 0 1-2.121 0L3.44 10.94A1.5 1.5 0 0 1 3 9.879V4.5Zm3.5 1.75a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Z' fill='black'/></svg>") no-repeat center / contain;
|
|
3588
|
+
}
|
|
3589
|
+
.tag-mark + .tag-mark { margin-left: 0.4rem; }
|
|
3590
|
+
.tag-marks-row {
|
|
3591
|
+
display: flex;
|
|
3592
|
+
flex-wrap: wrap;
|
|
3593
|
+
align-items: center;
|
|
3594
|
+
margin-top: 0.35rem;
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
/* Picker — flat row of chips + inline input, no enclosing field */
|
|
3598
|
+
.ck-tag-picker {
|
|
3599
|
+
display: flex;
|
|
3600
|
+
flex-wrap: wrap;
|
|
3601
|
+
align-items: center;
|
|
3602
|
+
gap: 7px;
|
|
3603
|
+
}
|
|
3604
|
+
.ck-tag-picker__input {
|
|
3605
|
+
flex: 1;
|
|
3606
|
+
min-width: 160px;
|
|
3607
|
+
background: transparent;
|
|
3608
|
+
border: 0;
|
|
3609
|
+
padding: 2px 0;
|
|
3610
|
+
color: var(--ck-text);
|
|
3611
|
+
font-family: var(--ck-mono);
|
|
3612
|
+
font-size: 12.5px;
|
|
3613
|
+
letter-spacing: 0.01em;
|
|
3614
|
+
outline: none;
|
|
3615
|
+
}
|
|
3616
|
+
.ck-tag-picker__input::placeholder { color: var(--ck-dim); }
|
|
3617
|
+
|
|
3618
|
+
/* Picker chips: unchecked = neutral pill with a small tag-colored dot.
|
|
3619
|
+
Avoids looking "applied" simply because the tag color is on screen. */
|
|
3620
|
+
.ck-tag-picker label.tag-mark,
|
|
3621
|
+
.ck-tag-filter .tag-mark,
|
|
3622
|
+
.ck-metric-tag-filter .tag-mark {
|
|
3623
|
+
cursor: pointer;
|
|
3624
|
+
user-select: none;
|
|
3625
|
+
padding: 4px 10px;
|
|
3626
|
+
font-size: 0.8rem;
|
|
3627
|
+
transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
|
|
3628
|
+
}
|
|
3629
|
+
|
|
3630
|
+
/* Selected state — picker, filter bars, metric-tag-filter share one look */
|
|
3631
|
+
.ck-tag-picker label.tag-mark:has(input:checked),
|
|
3632
|
+
.ck-tag-filter .tag-mark:not(.tag-mark--off),
|
|
3633
|
+
.ck-metric-tag-filter .tag-mark:not(.tag-mark--off) {
|
|
3634
|
+
background: color-mix(in srgb, var(--mark-color) 38%, transparent);
|
|
3635
|
+
color: color-mix(in srgb, var(--mark-color) 95%, var(--ck-text));
|
|
3636
|
+
border-color: color-mix(in srgb, var(--mark-color) 50%, transparent);
|
|
3637
|
+
}
|
|
3638
|
+
|
|
3639
|
+
/* Unselected state — same shape, neutral outline + muted text */
|
|
3640
|
+
.ck-tag-picker label.tag-mark:has(input:not(:checked)),
|
|
3641
|
+
.ck-tag-filter .tag-mark.tag-mark--off,
|
|
3642
|
+
.ck-metric-tag-filter .tag-mark.tag-mark--off {
|
|
3643
|
+
background: transparent;
|
|
3644
|
+
border-color: var(--ck-line);
|
|
3645
|
+
color: var(--ck-muted);
|
|
3646
|
+
}
|
|
3647
|
+
.ck-tag-picker label.tag-mark:has(input:not(:checked)):hover,
|
|
3648
|
+
.ck-tag-filter .tag-mark.tag-mark--off:hover,
|
|
3649
|
+
.ck-metric-tag-filter .tag-mark.tag-mark--off:hover {
|
|
3650
|
+
border-color: var(--ck-line-strong);
|
|
3651
|
+
color: color-mix(in srgb, var(--mark-color) 80%, var(--ck-text));
|
|
3652
|
+
}
|
|
3653
|
+
button.tag-mark {
|
|
3654
|
+
font: inherit;
|
|
3655
|
+
cursor: pointer;
|
|
3656
|
+
}
|
|
3657
|
+
a.tag-mark {
|
|
3658
|
+
text-decoration: none;
|
|
3659
|
+
}
|
|
3660
|
+
|
|
3661
|
+
/* Top-nav settings menu (ported from completion-kit-cloud) */
|
|
3662
|
+
.ck-settings-menu {
|
|
3663
|
+
position: relative;
|
|
3664
|
+
}
|
|
3665
|
+
.ck-settings-menu__trigger {
|
|
3666
|
+
cursor: pointer;
|
|
3667
|
+
list-style: none;
|
|
3668
|
+
user-select: none;
|
|
3669
|
+
}
|
|
3670
|
+
.ck-settings-menu__trigger::-webkit-details-marker {
|
|
3671
|
+
display: none;
|
|
3672
|
+
}
|
|
3673
|
+
.ck-settings-menu__panel {
|
|
3674
|
+
position: absolute;
|
|
3675
|
+
right: 0;
|
|
3676
|
+
top: calc(100% + 0.5rem);
|
|
3677
|
+
padding: 0.5rem;
|
|
3678
|
+
background: var(--ck-surface);
|
|
3679
|
+
border: 1px solid var(--ck-line);
|
|
3680
|
+
border-radius: var(--ck-radius);
|
|
3681
|
+
z-index: 50;
|
|
3682
|
+
display: flex;
|
|
3683
|
+
flex-direction: column;
|
|
3684
|
+
gap: 0.1rem;
|
|
3685
|
+
white-space: nowrap;
|
|
3686
|
+
}
|
|
3687
|
+
.ck-settings-menu__item {
|
|
3688
|
+
display: block;
|
|
3689
|
+
padding: 0.5rem 0.65rem;
|
|
3690
|
+
border-radius: calc(var(--ck-radius) - 2px);
|
|
3691
|
+
font-family: var(--ck-mono);
|
|
3692
|
+
font-size: 0.78rem;
|
|
3693
|
+
letter-spacing: 0.04em;
|
|
3694
|
+
text-transform: uppercase;
|
|
3695
|
+
text-decoration: none;
|
|
3696
|
+
color: var(--ck-text);
|
|
3697
|
+
transition: background 0.15s, color 0.15s;
|
|
3698
|
+
}
|
|
3699
|
+
.ck-settings-menu__item:hover {
|
|
3700
|
+
background: var(--ck-surface-hover);
|
|
3701
|
+
}
|
|
3702
|
+
|
|
3703
|
+
/* Settings page kicker — small label above the page title on settings pages */
|
|
3704
|
+
.ck-settings-kicker {
|
|
3705
|
+
display: flex;
|
|
3706
|
+
align-items: center;
|
|
3707
|
+
gap: 0.45rem;
|
|
3708
|
+
flex-wrap: wrap;
|
|
3709
|
+
font-family: var(--ck-mono);
|
|
3710
|
+
font-size: 11px;
|
|
3711
|
+
letter-spacing: 0.2em;
|
|
3712
|
+
text-transform: uppercase;
|
|
3713
|
+
color: var(--ck-dim);
|
|
3714
|
+
margin: 0 0 0.85rem;
|
|
3715
|
+
}
|
|
3716
|
+
.ck-settings-kicker__sep {
|
|
3717
|
+
opacity: 0.55;
|
|
3718
|
+
}
|
|
3719
|
+
.ck-settings-kicker__link {
|
|
3720
|
+
color: var(--ck-muted);
|
|
3721
|
+
text-decoration: none;
|
|
3722
|
+
transition: color 120ms ease;
|
|
3723
|
+
}
|
|
3724
|
+
.ck-settings-kicker__link:hover { color: var(--ck-text); }
|
|
3725
|
+
|
|
3726
|
+
.tag-marks-row--header {
|
|
3727
|
+
margin-top: -0.5rem;
|
|
3728
|
+
margin-bottom: 1.25rem;
|
|
3729
|
+
}
|
|
3730
|
+
|
|
3731
|
+
|
|
3732
|
+
.ck-input--error {
|
|
3733
|
+
border-color: var(--ck-danger);
|
|
3734
|
+
background: rgba(248, 113, 113, 0.05);
|
|
3735
|
+
}
|
|
3736
|
+
.ck-input--error:focus {
|
|
3737
|
+
box-shadow: 0 0 0 3px rgba(248, 113, 113, 0.15);
|
|
3738
|
+
border-color: var(--ck-danger);
|
|
3739
|
+
}
|
|
3740
|
+
|
|
3741
|
+
.ck-field-error {
|
|
3742
|
+
margin: 0.35rem 0 0;
|
|
3743
|
+
color: var(--ck-danger);
|
|
3744
|
+
font-family: var(--ck-sans);
|
|
3745
|
+
font-size: 0.95rem;
|
|
3746
|
+
}
|
|
3747
|
+
|
|
3748
|
+
/* Filter bar — its own visual moment */
|
|
3749
|
+
.ck-tag-filter {
|
|
3750
|
+
display: flex;
|
|
3751
|
+
flex-wrap: wrap;
|
|
3752
|
+
align-items: center;
|
|
3753
|
+
gap: 8px;
|
|
3754
|
+
margin: 1.1rem 0 1.6rem;
|
|
3755
|
+
padding: 0.65rem 0.85rem;
|
|
3756
|
+
background: var(--ck-surface-soft);
|
|
3757
|
+
border: 1px solid var(--ck-line);
|
|
3758
|
+
border-radius: 6px;
|
|
3759
|
+
}
|
|
3760
|
+
.ck-tag-filter__label {
|
|
3761
|
+
font-family: var(--ck-mono);
|
|
3762
|
+
font-size: 12px;
|
|
3763
|
+
letter-spacing: 0.12em;
|
|
3764
|
+
text-transform: uppercase;
|
|
3765
|
+
color: var(--ck-dim);
|
|
3766
|
+
margin-right: 0.4rem;
|
|
3767
|
+
}
|
|
3768
|
+
.ck-tag-filter__clear {
|
|
3769
|
+
margin-left: auto;
|
|
3770
|
+
font-family: var(--ck-mono);
|
|
3771
|
+
font-size: 11.5px;
|
|
3772
|
+
letter-spacing: 0.08em;
|
|
3773
|
+
text-transform: uppercase;
|
|
3774
|
+
color: var(--ck-muted);
|
|
3775
|
+
text-decoration: none;
|
|
3776
|
+
transition: color 120ms ease;
|
|
3777
|
+
}
|
|
3778
|
+
.ck-tag-filter__clear:hover { color: var(--ck-accent); }
|
|
3779
|
+
|
|
3780
|
+
|
|
@@ -5,7 +5,7 @@ module CompletionKit
|
|
|
5
5
|
before_action :set_dataset, only: [:show, :update, :destroy]
|
|
6
6
|
|
|
7
7
|
def index
|
|
8
|
-
render json: Dataset.order(created_at: :desc)
|
|
8
|
+
render json: Dataset.includes(:tags).order(created_at: :desc)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def show
|
|
@@ -43,7 +43,7 @@ module CompletionKit
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def dataset_params
|
|
46
|
-
params.permit(:name, :csv_data)
|
|
46
|
+
params.permit(:name, :csv_data, tag_names: [])
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
end
|
|
@@ -5,7 +5,7 @@ module CompletionKit
|
|
|
5
5
|
before_action :set_metric_group, only: [:show, :update, :destroy]
|
|
6
6
|
|
|
7
7
|
def index
|
|
8
|
-
render json: MetricGroup.order(created_at: :desc)
|
|
8
|
+
render json: MetricGroup.includes(:tags).order(created_at: :desc)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def show
|
|
@@ -45,7 +45,7 @@ module CompletionKit
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
def metric_group_params
|
|
48
|
-
params.permit(:name, :description, metric_ids: [])
|
|
48
|
+
params.permit(:name, :description, metric_ids: [], tag_names: [])
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
end
|
|
@@ -5,7 +5,7 @@ module CompletionKit
|
|
|
5
5
|
before_action :set_metric, only: [:show, :update, :destroy]
|
|
6
6
|
|
|
7
7
|
def index
|
|
8
|
-
render json: Metric.order(created_at: :desc)
|
|
8
|
+
render json: Metric.includes(:tags).order(created_at: :desc)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def show
|
|
@@ -43,7 +43,8 @@ module CompletionKit
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def metric_params
|
|
46
|
-
params.permit(:name, :instruction,
|
|
46
|
+
params.permit(:name, :instruction,
|
|
47
|
+
rubric_bands: [:stars, :description], tag_names: [])
|
|
47
48
|
end
|
|
48
49
|
end
|
|
49
50
|
end
|