Package not found. Please check the package name and try again.

completion-kit 0.5.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4a1592e3ab99397804845b4b200ec44ec78ba2575426aa34525ce1137b932fc
4
- data.tar.gz: baaed961b26171e4fac1410b587de872f5e0c28606d3b591232218406cca79d3
3
+ metadata.gz: 89766aed83665a19253ddf7f4fcbc99ad3e9bc8c17123d7f7d33acac36bf3096
4
+ data.tar.gz: fe71adba78f455a2ce36ef3649bb3e0c94ca77f7534fbe6377604bf6f66d36ab
5
5
  SHA512:
6
- metadata.gz: a0709c0819790bcb7246920fbaa6440debbafaa5872fa968f3d91acc1c3011c60c1682187d4a64102a81ead142ce2b0aa123503b13f9730967e20d3e1b156a94
7
- data.tar.gz: 6ea65ed6fec559e8b92c5f3cc46812425120a5a20681d527d7cb05c32dd39e22a6705b95df21f51657a0bf4e255cbc0e876df49e181578714218aaf921234754
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.
@@ -1 +1,2 @@
1
1
  //= link_directory ../stylesheets/completion_kit .css
2
+ //= link_directory ../javascripts/completion_kit .js
@@ -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
+ });
@@ -2986,6 +2986,10 @@ a.ck-metric-group-pill {
2986
2986
  white-space: nowrap;
2987
2987
  }
2988
2988
 
2989
+ .ck-runs-table__identity .tag-marks-row {
2990
+ padding-left: 1.2rem;
2991
+ }
2992
+
2989
2993
  .ck-runs-table__config-link {
2990
2994
  color: var(--ck-muted);
2991
2995
  text-decoration: none;
@@ -3559,6 +3563,8 @@ a.tag:hover, label.tag:hover { filter: brightness(1.12); }
3559
3563
  align-items: center;
3560
3564
  gap: 5px;
3561
3565
  padding: 2px 8px;
3566
+ min-height: 22px;
3567
+ box-sizing: border-box;
3562
3568
  border: 1px solid transparent;
3563
3569
  border-radius: 4px;
3564
3570
  font-family: var(--ck-mono);
@@ -3644,10 +3650,11 @@ a.tag:hover, label.tag:hover { filter: brightness(1.12); }
3644
3650
  border-color: var(--ck-line-strong);
3645
3651
  color: color-mix(in srgb, var(--mark-color) 80%, var(--ck-text));
3646
3652
  }
3647
- button.tag-mark, a.tag-mark {
3648
- border: 0;
3649
- background: color-mix(in srgb, var(--mark-color) 24%, transparent);
3653
+ button.tag-mark {
3654
+ font: inherit;
3650
3655
  cursor: pointer;
3656
+ }
3657
+ a.tag-mark {
3651
3658
  text-decoration: none;
3652
3659
  }
3653
3660
 
@@ -4,6 +4,7 @@
4
4
  <div class="ck-field">
5
5
  <%= form.label :name, "Tag name", class: "ck-label" %>
6
6
  <%= form.text_field :name,
7
+ id: "tag_name",
7
8
  class: ["ck-input", ("ck-input--error" if name_error)].compact.join(" "),
8
9
  placeholder: "e.g. production",
9
10
  autofocus: true,
@@ -9,6 +9,7 @@
9
9
  <%= favicon_link_tag "completion_kit/logo.svg", type: "image/svg+xml" %>
10
10
  <%= stylesheet_link_tag "completion_kit/application", media: "all" %>
11
11
  <%= javascript_include_tag "turbo", type: "module" %>
12
+ <%= javascript_include_tag "completion_kit/application", defer: true %>
12
13
  <%= action_cable_meta_tag %>
13
14
  </head>
14
15
  <body class="ck-app">
@@ -51,164 +52,5 @@
51
52
  <%= yield %>
52
53
  </div>
53
54
  </main>
54
- <script>
55
- document.addEventListener("turbo:load", function() {
56
- document.querySelectorAll("[data-local-time]").forEach(function(el) {
57
- var d = new Date(el.getAttribute("datetime"));
58
- el.textContent = d.toLocaleString(undefined, {year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"});
59
- });
60
- ckTickRelativeTimes();
61
- ckAutoFocusFirstError();
62
- });
63
-
64
- document.addEventListener("input", function(e) {
65
- if (!e.target || e.target.id !== "tag_name") return;
66
- var text = document.getElementById("tag-pill-text");
67
- if (!text) return;
68
- var v = e.target.value.trim().toLowerCase();
69
- text.textContent = v.length ? v : (text.dataset.placeholder || "");
70
- });
71
-
72
- function ckAutoFocusFirstError() {
73
- var fieldSelector = "input:not([type=hidden]):not([type=submit]):not([type=button]):not([type=reset]):not([type=file]), textarea, select";
74
- var marker = document.querySelector("form .ck-flash--alert, form [aria-invalid='true'], form .ck-field-error");
75
-
76
- var target;
77
- if (marker) {
78
- var form = marker.closest("form");
79
- target = form && (form.querySelector("[aria-invalid='true']") || form.querySelector(fieldSelector));
80
- } else if (/\/new(\/|$)/.test(window.location.pathname)) {
81
- target = document.querySelector("form " + fieldSelector);
82
- }
83
-
84
- if (!target || typeof target.focus !== "function") return;
85
- target.focus({ preventScroll: false });
86
- if (typeof target.setSelectionRange === "function" && typeof target.value === "string") {
87
- try { target.setSelectionRange(target.value.length, target.value.length); } catch (e) {}
88
- }
89
- }
90
-
91
- function ckRelativeTime(then) {
92
- var seconds = Math.round((Date.now() - then.getTime()) / 1000);
93
- if (seconds < 5) return "just now";
94
- if (seconds < 60) return "less than a minute";
95
- var minutes = Math.round(seconds / 60);
96
- if (minutes < 60) return minutes === 1 ? "1 minute" : minutes + " minutes";
97
- var hours = Math.round(minutes / 60);
98
- if (hours < 24) return hours === 1 ? "about 1 hour" : "about " + hours + " hours";
99
- var days = Math.round(hours / 24);
100
- if (days < 30) return days === 1 ? "1 day" : days + " days";
101
- var months = Math.round(days / 30);
102
- if (months < 12) return months === 1 ? "about 1 month" : "about " + months + " months";
103
- var years = Math.round(days / 365);
104
- return years === 1 ? "about 1 year" : "about " + years + " years";
105
- }
106
-
107
- function ckRelativeTimeCompact(then) {
108
- var seconds = Math.round((Date.now() - then.getTime()) / 1000);
109
- if (seconds < 60) return "now";
110
- var minutes = Math.round(seconds / 60);
111
- if (minutes < 60) return minutes + "m";
112
- var hours = Math.round(minutes / 60);
113
- if (hours < 24) return hours + "h";
114
- var days = Math.round(hours / 24);
115
- if (days < 30) return days + "d";
116
- var months = Math.round(days / 30);
117
- if (months < 12) return months + "mo";
118
- var years = Math.round(days / 365);
119
- return years + "y";
120
- }
121
-
122
- function ckTickRelativeTimes() {
123
- document.querySelectorAll("[data-relative-time]").forEach(function(el) {
124
- var then = new Date(el.getAttribute("datetime"));
125
- if (isNaN(then.getTime())) return;
126
- var verbose = el.getAttribute("data-relative-time") === "verbose";
127
- el.textContent = verbose ? ckRelativeTime(then) : ckRelativeTimeCompact(then);
128
- el.setAttribute("title", then.toLocaleString());
129
- });
130
- }
131
-
132
- if (!window.ckRelativeTimeInterval) {
133
- window.ckRelativeTimeInterval = setInterval(ckTickRelativeTimes, 30000);
134
- }
135
- document.addEventListener("turbo:before-stream-render", function() {
136
- requestAnimationFrame(ckTickRelativeTimes);
137
- });
138
-
139
- var ckCsvHoverTimer = null;
140
- var ckCsvHoverRow = null;
141
- document.addEventListener("mouseover", function(e) {
142
- var row = e.target.closest && e.target.closest(".ck-csv-table tbody tr");
143
- if (!row || row === ckCsvHoverRow) return;
144
- if (ckCsvHoverRow) ckCsvHoverRow.classList.remove("ck-csv-row--expanded");
145
- ckCsvHoverRow = row;
146
- clearTimeout(ckCsvHoverTimer);
147
- ckCsvHoverTimer = setTimeout(function() {
148
- if (ckCsvHoverRow === row) row.classList.add("ck-csv-row--expanded");
149
- }, 350);
150
- });
151
- document.addEventListener("mouseout", function(e) {
152
- var row = e.target.closest && e.target.closest(".ck-csv-table tbody tr");
153
- if (!row) return;
154
- var related = e.relatedTarget && e.relatedTarget.closest && e.relatedTarget.closest(".ck-csv-table tbody tr");
155
- if (related === row) return;
156
- clearTimeout(ckCsvHoverTimer);
157
- row.classList.remove("ck-csv-row--expanded");
158
- if (ckCsvHoverRow === row) ckCsvHoverRow = null;
159
- });
160
-
161
- var ckRefreshing = false;
162
- function ckRefreshModels() {
163
- if (ckRefreshing) return;
164
- ckRefreshing = true;
165
- var btn = document.querySelector('.ck-icon-btn[title="Refresh models"]');
166
- if (btn) btn.classList.add('ck-icon-btn--spinning');
167
- ckUpdateRefreshProgress();
168
- var csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute("content");
169
- fetch("/completion_kit/refresh_models", {
170
- method: "POST",
171
- headers: { "X-CSRF-Token": csrfToken }
172
- });
173
- }
174
-
175
- function ckUpdateRefreshProgress() {
176
- var status = document.getElementById('refresh-status');
177
- if (!status) return;
178
- var carriers = document.querySelectorAll('[data-refresh-progress-carriers] [id^="discovery_status_"]');
179
- var totalCurrent = 0, totalTotal = 0, anyDiscovering = false;
180
- carriers.forEach(function(node) {
181
- if (!node.querySelector('.ck-discovery-bar')) return;
182
- if (node.querySelector('.ck-discovery-bar--failed') || node.querySelector('.ck-discovery-bar--completed')) return;
183
- anyDiscovering = true;
184
- var match = node.textContent.match(/(\d+)\s*\/\s*(\d+)/);
185
- if (match) {
186
- totalCurrent += parseInt(match[1], 10);
187
- totalTotal += parseInt(match[2], 10);
188
- }
189
- });
190
- if (anyDiscovering || ckRefreshing) {
191
- if (totalTotal > 0) {
192
- status.textContent = 'Refreshing models\u2026 ' + totalCurrent + '/' + totalTotal;
193
- } else {
194
- status.textContent = 'Refreshing models\u2026';
195
- }
196
- }
197
- }
198
-
199
- document.addEventListener("turbo:before-stream-render", function(event) {
200
- var target = event.target.getAttribute("target");
201
- if (target && target.indexOf("discovery_status_") === 0) {
202
- requestAnimationFrame(ckUpdateRefreshProgress);
203
- }
204
- if (target === "prompt_llm_model" || target === "run_judge_model") {
205
- ckRefreshing = false;
206
- var btn = document.querySelector('.ck-icon-btn[title="Refresh models"]');
207
- if (btn) btn.classList.remove('ck-icon-btn--spinning');
208
- var status = document.getElementById('refresh-status');
209
- if (status) { status.textContent = 'Models updated.'; setTimeout(function() { status.textContent = '\u00a0'; }, 3000); }
210
- }
211
- });
212
- </script>
213
55
  </body>
214
56
  </html>
@@ -8,7 +8,11 @@ module CompletionKit
8
8
  paths.add "app/services", eager_load: true
9
9
 
10
10
  def self.register_assets(app)
11
- app.config.assets.precompile += %w( completion_kit/application.css completion_kit/logo.svg )
11
+ app.config.assets.precompile += %w(
12
+ completion_kit/application.css
13
+ completion_kit/application.js
14
+ completion_kit/logo.svg
15
+ )
12
16
  end
13
17
 
14
18
  initializer("completion_kit.assets") { |app| Engine.register_assets(app) }
@@ -1,3 +1,3 @@
1
1
  module CompletionKit
2
- VERSION = "0.5.0"
2
+ VERSION = "0.5.1"
3
3
  end
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.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Damien Bastin
@@ -228,6 +228,7 @@ files:
228
228
  - app/assets/config/completion_kit_manifest.js
229
229
  - app/assets/config/manifest.js
230
230
  - app/assets/images/completion_kit/logo.svg
231
+ - app/assets/javascripts/completion_kit/application.js
231
232
  - app/assets/stylesheets/completion_kit/application.css
232
233
  - app/controllers/completion_kit/api/v1/base_controller.rb
233
234
  - app/controllers/completion_kit/api/v1/datasets_controller.rb