completion-kit 0.26.2 → 0.26.4

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: 693a2c9c76ea04ca616b00a76556f2aeee15c38ca6290ef968d2b9c83eb23e83
4
- data.tar.gz: 6baf88740c80fe3102fff0f5d934f66357e74b62f3ac8da07b0c1d0069a3911d
3
+ metadata.gz: c296e624ff4313d1e4f4804cf902cce88ac7e5eee95deccefec8ff69241ff89d
4
+ data.tar.gz: 6f0c48e6efaa5fd65c23e07656687de56031f6c3634593d1a44cf9b40b560c11
5
5
  SHA512:
6
- metadata.gz: 3e5d34d55848f1480b01a5459025936c3d27e18f4b907a1dc98ca4081510dcbbe247fcdc88f05721a7452bf0d4adc0d78270c9ff08a9fba57dd616fc6b2c254b
7
- data.tar.gz: 4d12110ac0998d0f4640c89854f39ce2bdee3eae205910a4bc4a583eeda25ed1a2ac886d89da3cc185b6d4d42773d799101545c0c12ef56bd5b7cfc910bef106
6
+ metadata.gz: b4d0c20a2c591456cb9d7def96f8bd01ea143b66c3cbb678a10e6a68c5310e40a6bf8b69cc547b3c5e802905b31e4b741dcff5c2a6fb110d652ceb363a03548b
7
+ data.tar.gz: 58a03c4e699a33193dfe2cfaed23a3913cd0b1b2819a79beaca2559d38016c0c131e0cf0817b97a9c50651dc92a717d0146986d015065f588327cc63d075c7dc
@@ -290,6 +290,30 @@ document.addEventListener("change", function(e) {
290
290
  }
291
291
  });
292
292
 
293
+ function ckApplyProviderFields(scope) {
294
+ if (!scope) return;
295
+ var select = scope.querySelector("[data-ck-provider-select]");
296
+ if (!select) return;
297
+ var provider = select.value;
298
+ scope.querySelectorAll("[data-ck-provider-field]").forEach(function(field) {
299
+ var wanted = (field.getAttribute("data-ck-provider-field") || "").split(",");
300
+ field.hidden = wanted.indexOf(provider) === -1;
301
+ });
302
+ }
303
+
304
+ document.addEventListener("turbo:load", function() {
305
+ document.querySelectorAll("[data-ck-provider-select]").forEach(function(select) {
306
+ ckApplyProviderFields(select.closest("form") || document);
307
+ });
308
+ });
309
+
310
+ document.addEventListener("change", function(e) {
311
+ var target = e.target;
312
+ if (target && target.matches && target.matches("[data-ck-provider-select]")) {
313
+ ckApplyProviderFields(target.closest("form") || document);
314
+ }
315
+ });
316
+
293
317
  document.addEventListener("click", function(e) {
294
318
  var btn = e.target.closest("[data-ck-apply]");
295
319
  if (!btn) return;
@@ -378,3 +402,51 @@ document.addEventListener("keydown", function(e) {
378
402
  else if (e.key === "Enter" || e.key === " ") { e.preventDefault(); if (options[idx]) ckSelectChoose(sel, options[idx]); }
379
403
  else if (e.key === "Escape") { e.preventDefault(); ckSelectClose(sel); trigger.focus(); }
380
404
  });
405
+
406
+ function ckConfirmModal(message, formEl, submitter) {
407
+ var dialog = document.getElementById("ck-confirm-modal");
408
+ if (!dialog || typeof dialog.showModal !== "function") {
409
+ return Promise.resolve(window.confirm(message));
410
+ }
411
+ var messageEl = dialog.querySelector("#ck-confirm-message");
412
+ var acceptBtn = dialog.querySelector("[data-ck-confirm-accept]");
413
+ var cancelBtn = dialog.querySelector("[data-ck-confirm-cancel]");
414
+ if (messageEl) messageEl.textContent = message || "Are you sure?";
415
+ var label = (submitter && submitter.getAttribute("data-ck-confirm-label")) || "Confirm";
416
+ var tone = (submitter && submitter.getAttribute("data-ck-confirm-tone")) || "";
417
+ if (acceptBtn) {
418
+ acceptBtn.textContent = label;
419
+ acceptBtn.className = "ck-button " + (tone === "danger" ? "ck-button--danger" : "ck-button--primary");
420
+ }
421
+ return new Promise(function(resolve) {
422
+ function onClose() {
423
+ dialog.removeEventListener("close", onClose);
424
+ acceptBtn.removeEventListener("click", onAccept);
425
+ cancelBtn.removeEventListener("click", onCancel);
426
+ dialog.removeEventListener("click", onBackdrop);
427
+ resolve(dialog.returnValue === "accept");
428
+ }
429
+ function onAccept() { dialog.close("accept"); }
430
+ function onCancel() { dialog.close("cancel"); }
431
+ function onBackdrop(e) { if (e.target === dialog) dialog.close("cancel"); }
432
+ dialog.addEventListener("close", onClose);
433
+ acceptBtn.addEventListener("click", onAccept);
434
+ cancelBtn.addEventListener("click", onCancel);
435
+ dialog.addEventListener("click", onBackdrop);
436
+ dialog.returnValue = "";
437
+ dialog.showModal();
438
+ var focusTarget = (tone === "danger" ? cancelBtn : acceptBtn);
439
+ if (focusTarget) focusTarget.focus();
440
+ });
441
+ }
442
+
443
+ function ckInstallConfirm() {
444
+ if (!window.Turbo) return;
445
+ if (Turbo.config && Turbo.config.forms) {
446
+ Turbo.config.forms.confirm = ckConfirmModal;
447
+ } else if (typeof Turbo.setConfirmMethod === "function") {
448
+ Turbo.setConfirmMethod(ckConfirmModal);
449
+ }
450
+ }
451
+ document.addEventListener("turbo:load", ckInstallConfirm);
452
+ ckInstallConfirm();
@@ -2259,16 +2259,19 @@ label.ck-checkbox[hidden] {
2259
2259
  outline-offset: 2px;
2260
2260
  }
2261
2261
 
2262
- .ck-delete-action {
2263
- margin-top: 1rem;
2264
- display: flex;
2265
- flex-direction: column;
2266
- align-items: flex-start;
2267
- gap: 0.5rem;
2262
+ .ck-icon-btn:disabled {
2263
+ opacity: 0.4;
2264
+ cursor: not-allowed;
2268
2265
  }
2269
2266
 
2270
- .ck-delete-action .ck-field-hint {
2271
- margin: 0;
2267
+ .ck-icon-btn:disabled:hover {
2268
+ background: none;
2269
+ border-color: transparent;
2270
+ color: var(--ck-dim);
2271
+ }
2272
+
2273
+ .ck-delete-form {
2274
+ display: contents;
2272
2275
  }
2273
2276
 
2274
2277
  .ck-icon-btn--spinning svg {
@@ -2723,6 +2726,17 @@ select.ck-input {
2723
2726
  backdrop-filter: blur(4px);
2724
2727
  }
2725
2728
 
2729
+ .ck-modal--sm {
2730
+ max-width: min(92vw, 440px);
2731
+ }
2732
+
2733
+ .ck-confirm__message {
2734
+ margin: 0;
2735
+ color: var(--ck-muted);
2736
+ font-size: 0.95rem;
2737
+ line-height: 1.55;
2738
+ }
2739
+
2726
2740
  .ck-modal[open] {
2727
2741
  animation: ck-modal-in 0.18s ease-out;
2728
2742
  }
@@ -29,6 +29,21 @@ module CompletionKit
29
29
  "#{base} #{styles}"
30
30
  end
31
31
 
32
+ def ck_delete_trigger(form_id:, label:, confirm: nil, disabled: false, title: nil)
33
+ content_tag(
34
+ :button,
35
+ type: "submit",
36
+ form: (disabled ? nil : form_id),
37
+ class: "ck-icon-btn",
38
+ title: title || label,
39
+ "aria-label": label,
40
+ disabled: disabled,
41
+ data: (disabled ? {} : {turbo_confirm: confirm, ck_confirm_label: "Delete", ck_confirm_tone: "danger"})
42
+ ) do
43
+ heroicon_tag "trash", variant: :outline, size: 16, "aria-hidden": "true"
44
+ end
45
+ end
46
+
32
47
  def ck_badge_classes(kind)
33
48
  case kind.to_s
34
49
  when "high"
@@ -26,7 +26,6 @@ module CompletionKit
26
26
  validates :provider, presence: true, inclusion: { in: PROVIDERS }
27
27
  validates :provider, tenant_scoped_uniqueness: true
28
28
  validates :api_endpoint, presence: true, if: :azure_foundry?
29
- validates :api_version, presence: true, if: :azure_foundry?
30
29
  validate :api_endpoint_not_internal
31
30
 
32
31
  after_save :enqueue_discovery
@@ -49,7 +49,7 @@ module CompletionKit
49
49
  return [] unless configured?
50
50
  return [] unless ProviderEndpoint.safe?(api_endpoint)
51
51
 
52
- response = build_connection(azure_base_url).get("/openai/deployments?api-version=#{api_version}") do |req|
52
+ response = build_connection(azure_base_url).get(models_path) do |req|
53
53
  req.headers["api-key"] = api_key
54
54
  end
55
55
  return [] unless response.success?
@@ -67,7 +67,6 @@ module CompletionKit
67
67
  errors = []
68
68
  errors << "Azure endpoint is not configured" if api_endpoint.blank?
69
69
  errors << "Azure API key is not configured" if api_key.blank?
70
- errors << "Azure api-version is not configured" if api_version.blank?
71
70
  errors
72
71
  end
73
72
 
@@ -89,12 +88,21 @@ module CompletionKit
89
88
  api_endpoint.to_s.strip.delete_suffix("/")
90
89
  end
91
90
 
91
+ def v1_mode?
92
+ api_version.blank?
93
+ end
94
+
95
+ def models_path
96
+ v1_mode? ? "/openai/v1/models" : "/openai/deployments?api-version=#{api_version}"
97
+ end
98
+
92
99
  def post_chat(model:, prompt:, max_tokens:, temperature:)
93
100
  body = { messages: [{ role: "user", content: prompt }], max_tokens: max_tokens }
101
+ body[:model] = model if v1_mode?
94
102
  body[:temperature] = temperature unless temperature.nil?
95
103
 
96
104
  build_connection(azure_base_url, timeout: 30, open_timeout: 5).post do |req|
97
- req.url "/openai/deployments/#{model}/chat/completions?api-version=#{api_version}"
105
+ req.url(v1_mode? ? "/openai/v1/chat/completions" : "/openai/deployments/#{model}/chat/completions?api-version=#{api_version}")
98
106
  req.headers["Content-Type"] = "application/json"
99
107
  req.headers["api-key"] = api_key
100
108
  req.body = body.to_json
@@ -133,7 +133,7 @@ module CompletionKit
133
133
  def custom_endpoint_404_message
134
134
  message = "No OpenAI-compatible model list was found at #{custom_endpoint_host}/v1/models (404). Check that the base URL is correct."
135
135
  return message unless azure_custom_host?
136
- "#{message} This looks like an Azure endpoint; add it with the Azure AI Foundry provider, which uses an api-version and an api-key header."
136
+ "#{message} This looks like an Azure endpoint; add it with the Azure AI Foundry provider."
137
137
  end
138
138
 
139
139
  def with_detail(message, detail)
@@ -155,22 +155,31 @@ module CompletionKit
155
155
 
156
156
  def fetch_azure_foundry_models
157
157
  raise DiscoveryError, "An Azure endpoint URL is required." if @api_endpoint.blank?
158
- raise DiscoveryError, "An Azure api-version is required." if @api_version.blank?
159
158
 
160
- response = fetch_connection(azure_base_url).get("/openai/deployments?api-version=#{@api_version}") do |req|
159
+ response = fetch_connection(azure_base_url).get(azure_models_path) do |req|
161
160
  req.headers["api-key"] = @api_key
162
161
  end
163
162
  raise DiscoveryError, azure_error_message(response) unless response.success?
164
163
  JSON.parse(response.body).fetch("data", []).map { |e| { id: e["id"], display_name: e["id"] } }
165
164
  end
166
165
 
166
+ def azure_v1_mode?
167
+ @api_version.blank?
168
+ end
169
+
170
+ def azure_models_path
171
+ azure_v1_mode? ? "/openai/v1/models" : "/openai/deployments?api-version=#{@api_version}"
172
+ end
173
+
167
174
  def azure_base_url
168
175
  @api_endpoint.to_s.strip.delete_suffix("/")
169
176
  end
170
177
 
171
178
  def azure_error_message(response)
172
179
  detail = extract_provider_error_message(response.body)
173
- with_detail("Azure did not return a deployments list at /openai/deployments (#{response.status}). Check the endpoint base URL and api-version.", detail)
180
+ hint = azure_v1_mode? ? "Check the endpoint base URL." : "Check the endpoint base URL and api-version."
181
+ path = azure_v1_mode? ? "/openai/v1/models" : "/openai/deployments"
182
+ with_detail("Azure did not return a model list at #{path} (#{response.status}). #{hint}", detail)
174
183
  end
175
184
 
176
185
  def reconcile(discovered)
@@ -428,11 +437,13 @@ module CompletionKit
428
437
  f.request :retry, max: 1, interval: 0.5
429
438
  f.adapter Faraday.default_adapter
430
439
  end
440
+ body = { messages: [{ role: "user", content: input }], max_tokens: max_tokens }
441
+ body[:model] = model_id if azure_v1_mode?
431
442
  conn.post do |req|
432
- req.url "/openai/deployments/#{model_id}/chat/completions?api-version=#{@api_version}"
443
+ req.url(azure_v1_mode? ? "/openai/v1/chat/completions" : "/openai/deployments/#{model_id}/chat/completions?api-version=#{@api_version}")
433
444
  req.headers["Content-Type"] = "application/json"
434
445
  req.headers["api-key"] = @api_key
435
- req.body = { messages: [{ role: "user", content: input }], max_tokens: max_tokens }.to_json
446
+ req.body = body.to_json
436
447
  end
437
448
  end
438
449
  end
@@ -1,3 +1,11 @@
1
+ <% if dataset.persisted? %>
2
+ <% dataset_delete_id = "ck_delete_dataset_#{dataset.id}" %>
3
+ <% runs_n = dataset.runs.count %>
4
+ <% responses_n = CompletionKit::Response.where(run_id: dataset.runs.select(:id)).count %>
5
+ <% dataset_delete_confirm = runs_n.zero? ?
6
+ "Delete \"#{dataset.name}\"? It has no runs." :
7
+ "Delete \"#{dataset.name}\"? Cascades through #{pluralize(runs_n, 'run')} and #{pluralize(responses_n, 'response')} (and their reviews) that used this dataset." %>
8
+ <% end %>
1
9
  <%= form_with(model: dataset, local: true) do |form| %>
2
10
  <% if dataset.errors.any? %>
3
11
  <div class="ck-flash ck-flash--alert" role="alert">
@@ -31,6 +39,9 @@
31
39
  <%= render "completion_kit/tags/picker", record: dataset, param_namespace: :dataset %>
32
40
 
33
41
  <div class="ck-actions">
42
+ <% if dataset.persisted? %>
43
+ <%= ck_delete_trigger(form_id: dataset_delete_id, label: "Delete dataset", confirm: dataset_delete_confirm) %>
44
+ <% end %>
34
45
  <%= link_to "Cancel", datasets_path, class: ck_button_classes(:light, variant: :outline), tabindex: "0" %>
35
46
  <%= form.submit(dataset.persisted? ? "Save dataset" : "Create dataset", class: ck_button_classes(:dark)) %>
36
47
  </div>
@@ -66,12 +77,5 @@ ckDatasetHeaderPreview();
66
77
  <% end %>
67
78
 
68
79
  <% if dataset.persisted? %>
69
- <% runs_n = dataset.runs.count %>
70
- <% responses_n = CompletionKit::Response.where(run_id: dataset.runs.select(:id)).count %>
71
- <% confirm = if runs_n.zero?
72
- "Delete \"#{dataset.name}\"? It has no runs."
73
- else
74
- "Delete \"#{dataset.name}\"? Cascades through #{pluralize(runs_n, 'run')} and #{pluralize(responses_n, 'response')} (and their reviews) that used this dataset."
75
- end %>
76
- <%= render "completion_kit/shared/delete_action", path: dataset_path(dataset), label: "Delete dataset", confirm: confirm %>
80
+ <%= render "completion_kit/shared/delete_form", form_id: dataset_delete_id, path: dataset_path(dataset) %>
77
81
  <% end %>
@@ -1,3 +1,10 @@
1
+ <% if metric_group.persisted? %>
2
+ <% metric_group_delete_id = "ck_delete_metric_group_#{metric_group.id}" %>
3
+ <% members_n = metric_group.metrics.count %>
4
+ <% metric_group_delete_confirm = members_n.zero? ?
5
+ "Delete \"#{metric_group.name}\"? It has no members." :
6
+ "Delete \"#{metric_group.name}\"? Removes this grouping. #{pluralize(members_n, 'member metric')} #{members_n == 1 ? 'is' : 'are'} kept." %>
7
+ <% end %>
1
8
  <%= form_with(model: metric_group, url: metric_group.persisted? ? metric_group_path(metric_group) : metric_groups_path, local: true) do |form| %>
2
9
  <% name_error = metric_group.errors[:name].first %>
3
10
 
@@ -80,6 +87,9 @@
80
87
  </script>
81
88
 
82
89
  <div class="ck-actions">
90
+ <% if metric_group.persisted? %>
91
+ <%= ck_delete_trigger(form_id: metric_group_delete_id, label: "Delete metric group", confirm: metric_group_delete_confirm) %>
92
+ <% end %>
83
93
  <%= link_to "Cancel", metrics_path, class: ck_button_classes(:light, variant: :outline), tabindex: "0" %>
84
94
  <%= form.submit(metric_group.persisted? ? "Save metric group" : "Create metric group", class: ck_button_classes(:dark)) %>
85
95
  </div>
@@ -87,9 +97,5 @@
87
97
  <% end %>
88
98
 
89
99
  <% if metric_group.persisted? %>
90
- <% members_n = metric_group.metrics.count %>
91
- <% confirm = members_n.zero? ?
92
- "Delete \"#{metric_group.name}\"? It has no members." :
93
- "Delete \"#{metric_group.name}\"? Removes this grouping. #{pluralize(members_n, 'member metric')} #{members_n == 1 ? 'is' : 'are'} kept." %>
94
- <%= render "completion_kit/shared/delete_action", path: metric_group_path(metric_group), label: "Delete metric group", confirm: confirm %>
100
+ <%= render "completion_kit/shared/delete_form", form_id: metric_group_delete_id, path: metric_group_path(metric_group) %>
95
101
  <% end %>
@@ -3,6 +3,58 @@
3
3
  <% suggestion_bands = suggestion ? Array(suggestion.rubric_bands).each_with_object({}) { |b, h| h[b["stars"].to_i] = b["description"].to_s } : {} %>
4
4
  <% suggested_instruction = suggestion&.instruction.to_s %>
5
5
  <% instruction_changed = suggestion && suggested_instruction.present? && suggested_instruction != metric.instruction.to_s %>
6
+ <% if metric.persisted? %>
7
+ <% metric_delete_id = "ck_delete_metric_#{metric.id}" %>
8
+ <% groups_n = metric.metric_groups.count %>
9
+ <% reviews_n = metric.reviews.count %>
10
+ <% parts = [] %>
11
+ <% parts << "in #{pluralize(groups_n, 'metric group')} (removed from each)" if groups_n > 0 %>
12
+ <% parts << "scored in #{pluralize(reviews_n, 'review')} (scores kept, link cleared)" if reviews_n > 0 %>
13
+ <% metric_delete_confirm = parts.empty? ? "Delete \"#{metric.name}\"? It's not in use." : "Delete \"#{metric.name}\"? It's #{parts.to_sentence}." %>
14
+ <% end %>
15
+
16
+ <% if edit_draft %>
17
+ <% pub = local_assigns[:published_metric_version] %>
18
+ <% draft_instr_changed = pub && pub.instruction.to_s != edit_draft.instruction.to_s %>
19
+ <% draft_rubric_changed = pub && pub.rubric_bands != edit_draft.rubric_bands %>
20
+ <div class="ck-suggestion-banner" role="status">
21
+ <div class="ck-suggestion-banner__body">
22
+ <p class="ck-kicker">Draft pending</p>
23
+ <p class="ck-meta-copy">The form below shows your unpublished draft. Publish to replace the live<%= " instruction" if draft_instr_changed %><%= " and" if draft_instr_changed && draft_rubric_changed %><%= " rubric" if draft_rubric_changed %> for future runs, or keep editing.</p>
24
+ </div>
25
+ <div class="ck-suggestion-banner__actions">
26
+ <%= button_to "Discard draft", dismiss_suggestion_metric_path(metric, draft_id: edit_draft.id, back_to: "edit"),
27
+ method: :delete, form_class: "inline-block",
28
+ class: ck_button_classes(:light, variant: :outline),
29
+ data: { turbo_confirm: "Drop this draft?", ck_confirm_label: "Discard", ck_confirm_tone: "danger" } %>
30
+ <%= button_to "Publish this version", publish_draft_metric_path(metric, draft_id: edit_draft.id),
31
+ method: :post, form_class: "inline-block",
32
+ class: ck_button_classes(:dark) %>
33
+ </div>
34
+ </div>
35
+ <% end %>
36
+
37
+ <% if suggestion %>
38
+ <div class="ck-suggestion-banner" role="status">
39
+ <div class="ck-suggestion-banner__body">
40
+ <p class="ck-kicker ck-kicker--icon"><%= heroicon_tag "sparkles", variant: :outline, class: "ck-magic-icon", "aria-hidden": "true" %>Proposed changes</p>
41
+ <p class="ck-meta-copy">Based on human reviews, here are some proposed changes to the metric.</p>
42
+ </div>
43
+ <div class="ck-suggestion-banner__actions">
44
+ <%= button_to suggest_variants_metric_path(metric, back_to: "edit"),
45
+ method: :post, form_class: "inline-block", class: "ck-icon-btn",
46
+ title: "Try again", "aria-label": "Try again",
47
+ data: { turbo_confirm: "Replace these changes with fresh ones from the model?" } do %><%= heroicon_tag "arrow-path", variant: :outline, size: 16, "aria-hidden": "true" %><% end %>
48
+ <%= button_to dismiss_suggestion_metric_path(metric, draft_id: suggestion.id, back_to: "edit"),
49
+ method: :delete, form_class: "inline-block", class: "ck-icon-btn",
50
+ title: "Discard these changes", "aria-label": "Discard",
51
+ data: { turbo_confirm: "Drop these changes?", ck_confirm_label: "Discard", ck_confirm_tone: "danger" } do %><%= heroicon_tag "trash", variant: :outline, size: 16, "aria-hidden": "true" %><% end %>
52
+ <%= button_to "Apply all", publish_draft_metric_path(metric, draft_id: suggestion.id),
53
+ method: :post, form_class: "inline-block",
54
+ class: ck_button_classes(:dark) %>
55
+ </div>
56
+ </div>
57
+ <% end %>
6
58
 
7
59
  <%= form_with(model: metric, local: true) do |form| %>
8
60
  <% if metric.errors.any? %>
@@ -16,49 +68,6 @@
16
68
  </div>
17
69
  <% end %>
18
70
 
19
- <% if edit_draft %>
20
- <% pub = local_assigns[:published_metric_version] %>
21
- <% draft_instr_changed = pub && pub.instruction.to_s != edit_draft.instruction.to_s %>
22
- <% draft_rubric_changed = pub && pub.rubric_bands != edit_draft.rubric_bands %>
23
- <div class="ck-suggestion-banner" role="status">
24
- <div class="ck-suggestion-banner__body">
25
- <p class="ck-kicker">Draft pending</p>
26
- <p class="ck-meta-copy">The form below shows your unpublished draft. Publish to replace the live<%= " instruction" if draft_instr_changed %><%= " and" if draft_instr_changed && draft_rubric_changed %><%= " rubric" if draft_rubric_changed %> for future runs, or keep editing.</p>
27
- </div>
28
- <div class="ck-suggestion-banner__actions">
29
- <%= button_to "Discard draft", dismiss_suggestion_metric_path(metric, draft_id: edit_draft.id, back_to: "edit"),
30
- method: :delete, form_class: "inline-block",
31
- class: ck_button_classes(:light, variant: :outline),
32
- data: { turbo_confirm: "Drop this draft?" } %>
33
- <%= button_to "Publish this version", publish_draft_metric_path(metric, draft_id: edit_draft.id),
34
- method: :post, form_class: "inline-block",
35
- class: ck_button_classes(:dark) %>
36
- </div>
37
- </div>
38
- <% end %>
39
-
40
- <% if suggestion %>
41
- <div class="ck-suggestion-banner" role="status">
42
- <div class="ck-suggestion-banner__body">
43
- <p class="ck-kicker ck-kicker--icon"><%= heroicon_tag "sparkles", variant: :outline, class: "ck-magic-icon", "aria-hidden": "true" %>Proposed changes</p>
44
- <p class="ck-meta-copy">Based on human reviews, here are some proposed changes to the metric.</p>
45
- </div>
46
- <div class="ck-suggestion-banner__actions">
47
- <%= button_to suggest_variants_metric_path(metric, back_to: "edit"),
48
- method: :post, form_class: "inline-block", class: "ck-icon-btn",
49
- title: "Try again", "aria-label": "Try again",
50
- data: { turbo_confirm: "Replace these changes with fresh ones from the model?" } do %><%= heroicon_tag "arrow-path", variant: :outline, size: 16, "aria-hidden": "true" %><% end %>
51
- <%= button_to dismiss_suggestion_metric_path(metric, draft_id: suggestion.id, back_to: "edit"),
52
- method: :delete, form_class: "inline-block", class: "ck-icon-btn",
53
- title: "Discard these changes", "aria-label": "Discard",
54
- data: { turbo_confirm: "Drop these changes?" } do %><%= heroicon_tag "trash", variant: :outline, size: 16, "aria-hidden": "true" %><% end %>
55
- <%= button_to "Apply all", publish_draft_metric_path(metric, draft_id: suggestion.id),
56
- method: :post, form_class: "inline-block",
57
- class: ck_button_classes(:dark) %>
58
- </div>
59
- </div>
60
- <% end %>
61
-
62
71
  <div class="ck-card ck-form-card">
63
72
  <div class="ck-field">
64
73
  <%= form.label :name, "Metric name", class: "ck-label" %>
@@ -254,6 +263,9 @@
254
263
  <%= render "completion_kit/tags/picker", record: metric, param_namespace: :metric %>
255
264
 
256
265
  <div class="ck-actions">
266
+ <% if metric.persisted? %>
267
+ <%= ck_delete_trigger(form_id: metric_delete_id, label: "Delete metric", confirm: metric_delete_confirm) %>
268
+ <% end %>
257
269
  <%= link_to "Cancel", metrics_path, class: ck_button_classes(:light, variant: :outline), tabindex: "0" %>
258
270
  <%= form.submit(metric.persisted? ? "Save metric" : "Create metric", class: ck_button_classes(:dark)) %>
259
271
  </div>
@@ -261,11 +273,5 @@
261
273
  <% end %>
262
274
 
263
275
  <% if metric.persisted? %>
264
- <% groups_n = metric.metric_groups.count %>
265
- <% reviews_n = metric.reviews.count %>
266
- <% parts = [] %>
267
- <% parts << "in #{pluralize(groups_n, 'metric group')} (removed from each)" if groups_n > 0 %>
268
- <% parts << "scored in #{pluralize(reviews_n, 'review')} (scores kept, link cleared)" if reviews_n > 0 %>
269
- <% confirm = parts.empty? ? "Delete \"#{metric.name}\"? It's not in use." : "Delete \"#{metric.name}\"? It's #{parts.to_sentence}." %>
270
- <%= render "completion_kit/shared/delete_action", path: metric_path(metric), label: "Delete metric", confirm: confirm %>
276
+ <%= render "completion_kit/shared/delete_form", form_id: metric_delete_id, path: metric_path(metric) %>
271
277
  <% end %>
@@ -1,3 +1,11 @@
1
+ <% if prompt.persisted? %>
2
+ <% prompt_delete_id = "ck_delete_prompt_#{prompt.id}" %>
3
+ <% runs_n = prompt.runs.count %>
4
+ <% responses_n = prompt.responses.count %>
5
+ <% prompt_delete_confirm = runs_n.zero? ?
6
+ "Delete \"#{prompt.display_name}\"? This version has no runs." :
7
+ "Delete \"#{prompt.display_name}\"? Cascades through #{pluralize(runs_n, 'run')} and #{pluralize(responses_n, 'response')} (and their reviews). Other versions of this prompt are untouched." %>
8
+ <% end %>
1
9
  <%= form_with(model: prompt, local: true) do |form| %>
2
10
  <% if prompt.errors.any? %>
3
11
  <div class="ck-flash ck-flash--alert" role="alert">
@@ -58,6 +66,9 @@
58
66
  <%= render "completion_kit/tags/picker", record: prompt, param_namespace: :prompt %>
59
67
 
60
68
  <div class="ck-actions">
69
+ <% if prompt.persisted? %>
70
+ <%= ck_delete_trigger(form_id: prompt_delete_id, label: "Delete prompt", confirm: prompt_delete_confirm) %>
71
+ <% end %>
61
72
  <%= link_to "Cancel", prompts_path, class: ck_button_classes(:light, variant: :outline), tabindex: "0" %>
62
73
  <%= form.submit(prompt.persisted? ? "Save prompt" : "Create prompt", class: ck_button_classes(:dark), disabled: available.empty?) %>
63
74
  </div>
@@ -65,12 +76,5 @@
65
76
  <% end %>
66
77
 
67
78
  <% if prompt.persisted? %>
68
- <% runs_n = prompt.runs.count %>
69
- <% responses_n = prompt.responses.count %>
70
- <% confirm = if runs_n.zero?
71
- "Delete \"#{prompt.display_name}\"? This version has no runs."
72
- else
73
- "Delete \"#{prompt.display_name}\"? Cascades through #{pluralize(runs_n, 'run')} and #{pluralize(responses_n, 'response')} (and their reviews). Other versions of this prompt are untouched."
74
- end %>
75
- <%= render "completion_kit/shared/delete_action", path: prompt_path(prompt), label: "Delete prompt", confirm: confirm %>
79
+ <%= render "completion_kit/shared/delete_form", form_id: prompt_delete_id, path: prompt_path(prompt) %>
76
80
  <% end %>
@@ -1,3 +1,8 @@
1
+ <% if provider_credential.persisted? %>
2
+ <% pc_delete_id = "ck_delete_provider_#{provider_credential.id}" %>
3
+ <% pc_delete_blocked = provider_credential.in_use? %>
4
+ <% pc_delete_confirm = "Delete the #{provider_credential.display_provider} provider and its discovered models? This can't be undone." %>
5
+ <% end %>
1
6
  <%= form_with(model: provider_credential, local: true) do |form| %>
2
7
  <% if provider_credential.errors.any? %>
3
8
  <div class="ck-flash ck-flash--alert" role="alert">
@@ -13,7 +18,7 @@
13
18
  <div class="ck-card ck-form-card">
14
19
  <div class="ck-field">
15
20
  <%= form.label :provider, class: "ck-label" %>
16
- <%= form.select :provider, CompletionKit::ProviderCredential::PROVIDERS.map { |p| [CompletionKit::ProviderCredential::PROVIDER_LABELS[p] || p.titleize, p] }, {}, { class: "ck-input" } %>
21
+ <%= form.select :provider, CompletionKit::ProviderCredential::PROVIDERS.map { |p| [CompletionKit::ProviderCredential::PROVIDER_LABELS[p] || p.titleize, p] }, {}, { class: "ck-input", data: { ck_provider_select: true } } %>
17
22
  </div>
18
23
 
19
24
  <div class="ck-field">
@@ -28,26 +33,29 @@
28
33
  <%= ck_field_error(form, :api_endpoint) %>
29
34
  </div>
30
35
 
31
- <div class="ck-field">
36
+ <div class="ck-field" data-ck-provider-field="azure_foundry"<%= provider_credential.azure_foundry? ? "" : " hidden" %>>
32
37
  <%= form.label :api_version, "API version", class: "ck-label" %>
33
- <%= form.text_field :api_version, class: "ck-input", placeholder: "Only needed for Azure AI Foundry (e.g. 2024-10-21)", **ck_field_aria(form, :api_version) %>
38
+ <%= form.text_field :api_version, class: "ck-input", placeholder: "e.g. 2024-10-21", **ck_field_aria(form, :api_version) %>
34
39
  <%= ck_field_error(form, :api_version) %>
40
+ <p class="ck-field-hint">Optional. Leave blank to use Azure's v1 API, or set a dated version for the legacy API (which also lists your deployments during discovery).</p>
35
41
  </div>
36
42
 
37
43
  <div class="ck-actions">
44
+ <% if provider_credential.persisted? %>
45
+ <%= ck_delete_trigger(form_id: pc_delete_id, label: "Delete provider", confirm: pc_delete_confirm, disabled: pc_delete_blocked, title: (pc_delete_blocked ? provider_credential.in_use_message : "Delete provider")) %>
46
+ <% end %>
38
47
  <%= link_to "Cancel", provider_credentials_path, class: ck_button_classes(:light, variant: :outline), tabindex: "0" %>
39
48
  <%= form.submit(provider_credential.persisted? ? "Save provider" : "Create provider", class: ck_button_classes(:dark)) %>
40
49
  </div>
50
+ <% if provider_credential.persisted? && pc_delete_blocked %>
51
+ <p class="ck-field-hint"><%= provider_credential.in_use_message %></p>
52
+ <% end %>
41
53
 
42
54
  </div>
43
55
  <% end %>
44
56
 
45
- <% if provider_credential.persisted? %>
46
- <%= render "completion_kit/shared/delete_action",
47
- path: provider_credential_path(provider_credential),
48
- label: "Delete provider",
49
- confirm: "Delete the #{provider_credential.display_provider} provider and its discovered models? This can't be undone.",
50
- blocked_reason: (provider_credential.in_use_message if provider_credential.in_use?) %>
57
+ <% if provider_credential.persisted? && !pc_delete_blocked %>
58
+ <%= render "completion_kit/shared/delete_form", form_id: pc_delete_id, path: provider_credential_path(provider_credential) %>
51
59
  <% end %>
52
60
 
53
61
  <% if provider_credential.persisted? %>
@@ -1,6 +1,6 @@
1
1
  <div class="ck-actions" id="run_actions">
2
2
  <% running = run.status == "running" %>
3
- <%= button_to run_path(run), method: :delete, form_class: "inline-block", class: "ck-icon-btn", title: "Delete run", "aria-label": "Delete run", disabled: running, data: { turbo_confirm: "Delete this run and all its responses?" } do %><%= heroicon_tag "trash", variant: :outline, size: 16, "aria-hidden": "true" %><% end %>
3
+ <%= button_to run_path(run), method: :delete, form_class: "inline-block", class: "ck-icon-btn", title: "Delete run", "aria-label": "Delete run", disabled: running, data: { turbo_confirm: "Delete this run and all its responses?", ck_confirm_label: "Delete", ck_confirm_tone: "danger" } do %><%= heroicon_tag "trash", variant: :outline, size: 16, "aria-hidden": "true" %><% end %>
4
4
  <% if running %>
5
5
  <%= link_to "Edit", edit_run_path(run), class: ck_button_classes(:light, variant: :outline) + " disabled", "aria-disabled": "true", tabindex: "-1" %>
6
6
  <% else %>
@@ -0,0 +1,2 @@
1
+ <%# locals: (form_id:, path:) %>
2
+ <%= form_with url: path, method: :delete, id: form_id, class: "ck-delete-form" do %><% end %>
@@ -1,3 +1,10 @@
1
+ <% if tag.persisted? %>
2
+ <% tag_delete_id = "ck_delete_tag_#{tag.id}" %>
3
+ <% applied_n = tag.taggings.count %>
4
+ <% tag_delete_confirm = applied_n.zero? ?
5
+ "Delete \"#{tag.name}\"? It's not currently applied to anything." :
6
+ "Delete \"#{tag.name}\"? It's currently applied to #{pluralize(applied_n, 'item')}. They'll lose this tag." %>
7
+ <% end %>
1
8
  <%= form_with(model: tag, local: true) do |form| %>
2
9
  <div class="ck-card ck-form-card">
3
10
  <% name_error = tag.errors[:name].first %>
@@ -17,6 +24,9 @@
17
24
  </div>
18
25
 
19
26
  <div class="ck-actions">
27
+ <% if tag.persisted? %>
28
+ <%= ck_delete_trigger(form_id: tag_delete_id, label: "Delete tag", confirm: tag_delete_confirm) %>
29
+ <% end %>
20
30
  <%= link_to "Cancel", tags_path, class: ck_button_classes(:light, variant: :outline), tabindex: "0" %>
21
31
  <%= form.submit(tag.persisted? ? "Save tag" : "Create tag", class: ck_button_classes(:dark)) %>
22
32
  </div>
@@ -24,11 +34,5 @@
24
34
  <% end %>
25
35
 
26
36
  <% if tag.persisted? %>
27
- <% applied_n = tag.taggings.count %>
28
- <% confirm = if applied_n.zero?
29
- "Delete \"#{tag.name}\"? It's not currently applied to anything."
30
- else
31
- "Delete \"#{tag.name}\"? It's currently applied to #{pluralize(applied_n, 'item')}. They'll lose this tag."
32
- end %>
33
- <%= render "completion_kit/shared/delete_action", path: tag_path(tag), label: "Delete tag", confirm: confirm %>
37
+ <%= render "completion_kit/shared/delete_form", form_id: tag_delete_id, path: tag_path(tag) %>
34
38
  <% end %>
@@ -60,5 +60,22 @@
60
60
  <%= yield %>
61
61
  </div>
62
62
  </main>
63
+
64
+ <dialog id="ck-confirm-modal" class="ck-modal ck-modal--sm" role="alertdialog" aria-labelledby="ck-confirm-title" aria-describedby="ck-confirm-message">
65
+ <article class="ck-modal__panel" tabindex="-1">
66
+ <header class="ck-modal__header">
67
+ <div class="ck-modal__heading">
68
+ <h2 class="ck-modal__title" id="ck-confirm-title">Please confirm</h2>
69
+ </div>
70
+ </header>
71
+ <div class="ck-modal__body">
72
+ <p class="ck-confirm__message" id="ck-confirm-message"></p>
73
+ </div>
74
+ <div class="ck-modal__footer">
75
+ <button type="button" class="<%= ck_button_classes(:light, variant: :outline) %>" data-ck-confirm-cancel>Cancel</button>
76
+ <button type="button" class="<%= ck_button_classes(:dark) %>" data-ck-confirm-accept>Confirm</button>
77
+ </div>
78
+ </article>
79
+ </dialog>
63
80
  </body>
64
81
  </html>
@@ -1,3 +1,3 @@
1
1
  module CompletionKit
2
- VERSION = "0.26.2"
2
+ VERSION = "0.26.4"
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.26.2
4
+ version: 0.26.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Damien Bastin
@@ -421,7 +421,7 @@ files:
421
421
  - app/views/completion_kit/runs/new.html.erb
422
422
  - app/views/completion_kit/runs/show.html.erb
423
423
  - app/views/completion_kit/shared/_branded_select.html.erb
424
- - app/views/completion_kit/shared/_delete_action.html.erb
424
+ - app/views/completion_kit/shared/_delete_form.html.erb
425
425
  - app/views/completion_kit/shared/_settings_nav.html.erb
426
426
  - app/views/completion_kit/suggestions/_scoreboard.html.erb
427
427
  - app/views/completion_kit/suggestions/_state.html.erb
@@ -1,18 +0,0 @@
1
- <%# locals: (path:, label:, confirm:, blocked_reason: nil) %>
2
- <div class="ck-delete-action">
3
- <% if blocked_reason.present? %>
4
- <button type="button" class="<%= ck_button_classes(:red, variant: :outline) %>" disabled aria-disabled="true">
5
- <%= heroicon_tag "trash", variant: :outline, size: 15, "aria-hidden": "true" %>
6
- <%= label %>
7
- </button>
8
- <p class="ck-field-hint"><%= blocked_reason %></p>
9
- <% else %>
10
- <%= button_to path, method: :delete,
11
- form_class: "inline-block",
12
- class: ck_button_classes(:red, variant: :outline),
13
- data: { turbo_confirm: confirm } do %>
14
- <%= heroicon_tag "trash", variant: :outline, size: 15, "aria-hidden": "true" %>
15
- <%= label %>
16
- <% end %>
17
- <% end %>
18
- </div>