completion-kit 0.4.7 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -0
  3. data/app/assets/stylesheets/completion_kit/application.css +375 -0
  4. data/app/controllers/completion_kit/api/v1/datasets_controller.rb +2 -2
  5. data/app/controllers/completion_kit/api/v1/metric_groups_controller.rb +2 -2
  6. data/app/controllers/completion_kit/api/v1/metrics_controller.rb +3 -2
  7. data/app/controllers/completion_kit/api/v1/prompts_controller.rb +5 -4
  8. data/app/controllers/completion_kit/api/v1/runs_controller.rb +3 -2
  9. data/app/controllers/completion_kit/api/v1/tags_controller.rb +51 -0
  10. data/app/controllers/completion_kit/datasets_controller.rb +3 -2
  11. data/app/controllers/completion_kit/metric_groups_controller.rb +7 -6
  12. data/app/controllers/completion_kit/metrics_controller.rb +4 -2
  13. data/app/controllers/completion_kit/prompts_controller.rb +7 -4
  14. data/app/controllers/completion_kit/runs_controller.rb +4 -3
  15. data/app/controllers/completion_kit/tags_controller.rb +50 -0
  16. data/app/controllers/concerns/completion_kit/tag_filtering.rb +22 -0
  17. data/app/helpers/completion_kit/application_helper.rb +11 -0
  18. data/app/models/completion_kit/dataset.rb +5 -2
  19. data/app/models/completion_kit/metric.rb +4 -1
  20. data/app/models/completion_kit/metric_group.rb +4 -1
  21. data/app/models/completion_kit/prompt.rb +4 -1
  22. data/app/models/completion_kit/run.rb +3 -1
  23. data/app/models/completion_kit/tag.rb +39 -0
  24. data/app/models/completion_kit/tagging.rb +12 -0
  25. data/app/models/concerns/completion_kit/taggable.rb +24 -0
  26. data/app/services/completion_kit/mcp_dispatcher.rb +3 -1
  27. data/app/services/completion_kit/mcp_tools/datasets.rb +6 -4
  28. data/app/services/completion_kit/mcp_tools/metric_groups.rb +6 -2
  29. data/app/services/completion_kit/mcp_tools/metrics.rb +8 -4
  30. data/app/services/completion_kit/mcp_tools/prompts.rb +10 -5
  31. data/app/services/completion_kit/mcp_tools/runs.rb +7 -3
  32. data/app/services/completion_kit/mcp_tools/tags.rb +74 -0
  33. data/app/views/completion_kit/api_reference/index.html.erb +38 -0
  34. data/app/views/completion_kit/datasets/_form.html.erb +20 -1
  35. data/app/views/completion_kit/datasets/index.html.erb +17 -1
  36. data/app/views/completion_kit/datasets/show.html.erb +6 -0
  37. data/app/views/completion_kit/metric_groups/_form.html.erb +74 -19
  38. data/app/views/completion_kit/metric_groups/index.html.erb +30 -4
  39. data/app/views/completion_kit/metrics/_form.html.erb +19 -1
  40. data/app/views/completion_kit/metrics/index.html.erb +18 -2
  41. data/app/views/completion_kit/metrics/show.html.erb +6 -0
  42. data/app/views/completion_kit/prompts/_form.html.erb +20 -1
  43. data/app/views/completion_kit/prompts/index.html.erb +17 -1
  44. data/app/views/completion_kit/prompts/show.html.erb +6 -0
  45. data/app/views/completion_kit/provider_credentials/_form.html.erb +1 -1
  46. data/app/views/completion_kit/provider_credentials/index.html.erb +3 -1
  47. data/app/views/completion_kit/runs/_form.html.erb +25 -3
  48. data/app/views/completion_kit/runs/_row.html.erb +5 -0
  49. data/app/views/completion_kit/runs/index.html.erb +9 -0
  50. data/app/views/completion_kit/runs/show.html.erb +6 -0
  51. data/app/views/completion_kit/shared/_settings_nav.html.erb +9 -0
  52. data/app/views/completion_kit/tags/_filter_bar.html.erb +15 -0
  53. data/app/views/completion_kit/tags/_form.html.erb +39 -0
  54. data/app/views/completion_kit/tags/_marks.html.erb +3 -0
  55. data/app/views/completion_kit/tags/_picker.html.erb +20 -0
  56. data/app/views/completion_kit/tags/edit.html.erb +20 -0
  57. data/app/views/completion_kit/tags/index.html.erb +45 -0
  58. data/app/views/completion_kit/tags/new.html.erb +20 -0
  59. data/app/views/layouts/completion_kit/application.html.erb +38 -1
  60. data/config/routes.rb +2 -0
  61. data/db/migrate/20260509000001_create_completion_kit_tags.rb +10 -0
  62. data/db/migrate/20260509000002_create_completion_kit_taggings.rb +16 -0
  63. data/lib/completion_kit/version.rb +1 -1
  64. metadata +20 -6
@@ -0,0 +1,15 @@
1
+ <% if available.any? %>
2
+ <div class="ck-tag-filter">
3
+ <span class="ck-tag-filter__label">Filter by tag</span>
4
+ <% available.each do |tag| %>
5
+ <% on = selected.any? { |s| s.id == tag.id } %>
6
+ <%= link_to tag.name,
7
+ tag_filter_url(base_path, selected, tag),
8
+ class: ["tag-mark", ("tag-mark--off" unless on)].compact.join(" "),
9
+ style: "--mark-color: var(--tag-#{tag.color});" %>
10
+ <% end %>
11
+ <% if selected.any? %>
12
+ <%= link_to "× clear", base_path, class: "ck-tag-filter__clear" %>
13
+ <% end %>
14
+ </div>
15
+ <% end %>
@@ -0,0 +1,39 @@
1
+ <%= form_with(model: tag, local: true) do |form| %>
2
+ <div class="ck-card ck-form-card">
3
+ <% name_error = tag.errors[:name].first %>
4
+ <div class="ck-field">
5
+ <%= form.label :name, "Tag name", class: "ck-label" %>
6
+ <%= form.text_field :name,
7
+ class: ["ck-input", ("ck-input--error" if name_error)].compact.join(" "),
8
+ placeholder: "e.g. production",
9
+ autofocus: true,
10
+ "aria-invalid": name_error ? "true" : nil %>
11
+ <% if name_error %>
12
+ <p class="ck-field-error" role="alert"><%= name_error %></p>
13
+ <% elsif !tag.persisted? %>
14
+ <p class="ck-hint">Color is auto-assigned from a 10-color palette.</p>
15
+ <% end %>
16
+ </div>
17
+
18
+ <div class="ck-actions">
19
+ <% if tag.persisted? %>
20
+ <% applied_n = tag.taggings.count %>
21
+ <% confirm = if applied_n.zero?
22
+ "Delete \"#{tag.name}\"? It's not currently applied to anything."
23
+ else
24
+ "Delete \"#{tag.name}\"? It's currently applied to #{pluralize(applied_n, 'item')} — they'll lose this tag."
25
+ end %>
26
+ <%= button_to tag_path(tag), method: :delete,
27
+ form_class: "inline-block",
28
+ class: "ck-icon-btn",
29
+ title: "Delete tag",
30
+ "aria-label": "Delete tag",
31
+ data: { turbo_confirm: confirm } do %>
32
+ <%= heroicon_tag "trash", variant: :outline, size: 16, "aria-hidden": "true" %>
33
+ <% end %>
34
+ <% end %>
35
+ <%= link_to "Cancel", tags_path, class: ck_button_classes(:light, variant: :outline), tabindex: "0" %>
36
+ <%= form.submit(tag.persisted? ? "Save tag" : "Create tag", class: ck_button_classes(:dark)) %>
37
+ </div>
38
+ </div>
39
+ <% end %>
@@ -0,0 +1,3 @@
1
+ <% Array(tags).each do |tag| %>
2
+ <span class="tag-mark" style="--mark-color: var(--tag-<%= tag.color %>);"><%= tag.name %></span>
3
+ <% end %>
@@ -0,0 +1,20 @@
1
+ <div class="ck-field">
2
+ <% label_text = local_assigns[:label].presence || "#{param_namespace.to_s.titleize} tags" %>
3
+ <%= label_tag "#{param_namespace}_tags", label_text, class: "ck-label" %>
4
+ <% all_tags = CompletionKit::Tag.order(:name) %>
5
+ <% selected_ids = record.persisted? ? record.tags.pluck(:id) : [] %>
6
+ <div class="ck-tag-picker">
7
+ <% all_tags.each do |tag| %>
8
+ <% checked = selected_ids.include?(tag.id) %>
9
+ <label class="tag-mark" style="--mark-color: var(--tag-<%= tag.color %>);">
10
+ <%= check_box_tag "#{param_namespace}[tag_names][]", tag.name, checked, hidden: true %>
11
+ <%= tag.name %>
12
+ </label>
13
+ <% end %>
14
+ <%= text_field_tag "#{param_namespace}[tag_names][]", "",
15
+ id: "#{param_namespace}_tags",
16
+ class: "ck-tag-picker__input",
17
+ placeholder: all_tags.any? ? "+ create tag" : "+ create first tag",
18
+ autocomplete: "off" %>
19
+ </div>
20
+ </div>
@@ -0,0 +1,20 @@
1
+ <% original = @tag.name_was.presence || "tag" %>
2
+ <% display_name = @tag.name.presence || original %>
3
+ <% pill = content_tag(:span,
4
+ content_tag(:span, display_name, id: "tag-pill-text", data: { placeholder: original }),
5
+ id: "tag-breadcrumb-pill",
6
+ class: "tag-mark",
7
+ style: "--mark-color: var(--tag-#{@tag.color});") %>
8
+
9
+ <%= render "completion_kit/shared/settings_nav",
10
+ section_label: "Tags",
11
+ section_link: tags_path,
12
+ trail: pill %>
13
+
14
+ <section class="ck-page-header">
15
+ <div>
16
+ <h1 class="ck-title">Edit tag</h1>
17
+ </div>
18
+ </section>
19
+
20
+ <%= render "form", tag: @tag %>
@@ -0,0 +1,45 @@
1
+ <%= render "completion_kit/shared/settings_nav", section_label: "Tags" %>
2
+
3
+ <section class="ck-page-header">
4
+ <div>
5
+ <h1 class="ck-title">Tags</h1>
6
+ <p class="ck-lead">Domain labels for organizing metrics, prompts, runs, and datasets.</p>
7
+ </div>
8
+ <div class="ck-actions">
9
+ <%= link_to "New tag", new_tag_path, class: ck_button_classes(:dark) %>
10
+ </div>
11
+ </section>
12
+
13
+ <% if @tags.any? %>
14
+ <table class="ck-results-table ck-tags-table">
15
+ <thead>
16
+ <tr>
17
+ <th>Tag</th>
18
+ <th>Applied to</th>
19
+ <th></th>
20
+ </tr>
21
+ </thead>
22
+ <tbody>
23
+ <% @tags.each do |tag| %>
24
+ <% count = @tagging_counts.fetch(tag.id, 0) %>
25
+ <% by_type = @tagging_by_type.select { |(tid, _), _| tid == tag.id } %>
26
+ <% breakdown = by_type.map { |(_, type), n| pluralize(n, type.demodulize.titleize.downcase) }.join(" · ") %>
27
+ <tr onclick="window.location='<%= edit_tag_path(tag) %>'" style="cursor: pointer;">
28
+ <td><span class="tag-mark tag-mark--lg" style="--mark-color: var(--tag-<%= tag.color %>);"><%= tag.name %></span></td>
29
+ <td class="ck-meta-copy">
30
+ <% if count.zero? %>
31
+ <span class="ck-tags-table__unused">Not used yet</span>
32
+ <% else %>
33
+ <%= breakdown %>
34
+ <% end %>
35
+ </td>
36
+ <td class="ck-results-table__arrow">&rarr;</td>
37
+ </tr>
38
+ <% end %>
39
+ </tbody>
40
+ </table>
41
+ <% else %>
42
+ <div class="ck-empty">
43
+ <p>No tags yet. <%= link_to "Create your first tag", new_tag_path, class: "ck-link" %> to start organizing.</p>
44
+ </div>
45
+ <% end %>
@@ -0,0 +1,20 @@
1
+ <% display_name = @tag.name.presence.to_s %>
2
+ <% pill = content_tag(:span,
3
+ content_tag(:span, display_name, id: "tag-pill-text", data: { placeholder: "" }),
4
+ id: "tag-breadcrumb-pill",
5
+ class: "tag-mark",
6
+ style: "--mark-color: var(--tag-#{@tag.color});") %>
7
+
8
+ <%= render "completion_kit/shared/settings_nav",
9
+ section_label: "Tags",
10
+ section_link: tags_path,
11
+ trail: pill %>
12
+
13
+ <section class="ck-page-header">
14
+ <div>
15
+ <h1 class="ck-title">New tag</h1>
16
+ <p class="ck-lead">Tags help you filter metrics, prompts, runs, and datasets by domain.</p>
17
+ </div>
18
+ </section>
19
+
20
+ <%= render "form", tag: @tag %>
@@ -22,7 +22,16 @@
22
22
  <%= link_to "Metrics", metrics_path, class: request.path.start_with?(metrics_path) || request.path.start_with?(metric_groups_path) ? ck_button_classes(:dark) : ck_button_classes(:light, variant: :outline) %>
23
23
  <%= link_to "Datasets", datasets_path, class: active.(datasets_path) %>
24
24
  <%= link_to "Runs", runs_path, class: active.(runs_path) %>
25
- <%= link_to "Settings", provider_credentials_path, class: active.(provider_credentials_path) %>
25
+ <% settings_active = request.path.start_with?(provider_credentials_path) || request.path.start_with?(tags_path) %>
26
+ <details class="ck-settings-menu">
27
+ <summary class="<%= settings_active ? ck_button_classes(:dark) : ck_button_classes(:light, variant: :outline) %> ck-settings-menu__trigger" aria-label="Settings">
28
+ Settings
29
+ </summary>
30
+ <div class="ck-settings-menu__panel" role="menu">
31
+ <%= link_to "Providers", provider_credentials_path, class: "ck-settings-menu__item" %>
32
+ <%= link_to "Tags", tags_path, class: "ck-settings-menu__item" %>
33
+ </div>
34
+ </details>
26
35
  <%= link_to "API", api_reference_path, class: active.(api_reference_path) %>
27
36
  <% if main_app.respond_to?(:logout_path) %>
28
37
  <%= button_to "Log out", main_app.logout_path, method: :delete, class: ck_button_classes(:light, variant: :outline) %>
@@ -49,8 +58,36 @@ document.addEventListener("turbo:load", function() {
49
58
  el.textContent = d.toLocaleString(undefined, {year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"});
50
59
  });
51
60
  ckTickRelativeTimes();
61
+ ckAutoFocusFirstError();
52
62
  });
53
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
+
54
91
  function ckRelativeTime(then) {
55
92
  var seconds = Math.round((Date.now() - then.getTime()) / 1000);
56
93
  if (seconds < 5) return "just now";
data/config/routes.rb CHANGED
@@ -10,6 +10,7 @@ CompletionKit::Engine.routes.draw do
10
10
  resources :datasets
11
11
  resources :metrics
12
12
  resources :metric_groups
13
+ resources :tags
13
14
 
14
15
  resources :runs do
15
16
  member do
@@ -52,6 +53,7 @@ CompletionKit::Engine.routes.draw do
52
53
  resources :datasets
53
54
  resources :metrics
54
55
  resources :metric_groups
56
+ resources :tags
55
57
  resources :provider_credentials
56
58
  end
57
59
  end
@@ -0,0 +1,10 @@
1
+ class CreateCompletionKitTags < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :completion_kit_tags do |t|
4
+ t.string :name, null: false
5
+ t.string :color, null: false
6
+ t.timestamps
7
+ end
8
+ add_index :completion_kit_tags, :name, unique: true
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ class CreateCompletionKitTaggings < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :completion_kit_taggings do |t|
4
+ t.references :tag, null: false,
5
+ foreign_key: { to_table: :completion_kit_tags }
6
+ t.string :taggable_type, null: false
7
+ t.bigint :taggable_id, null: false
8
+ t.timestamps
9
+ end
10
+ add_index :completion_kit_taggings, [:taggable_type, :taggable_id]
11
+ add_index :completion_kit_taggings,
12
+ [:tag_id, :taggable_type, :taggable_id],
13
+ unique: true,
14
+ name: "idx_taggings_unique"
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
1
  module CompletionKit
2
- VERSION = "0.4.7"
2
+ VERSION = "0.5.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: completion-kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.7
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Damien Bastin
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-05-08 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rails
@@ -238,6 +237,7 @@ files:
238
237
  - app/controllers/completion_kit/api/v1/provider_credentials_controller.rb
239
238
  - app/controllers/completion_kit/api/v1/responses_controller.rb
240
239
  - app/controllers/completion_kit/api/v1/runs_controller.rb
240
+ - app/controllers/completion_kit/api/v1/tags_controller.rb
241
241
  - app/controllers/completion_kit/api_reference_controller.rb
242
242
  - app/controllers/completion_kit/application_controller.rb
243
243
  - app/controllers/completion_kit/datasets_controller.rb
@@ -249,6 +249,8 @@ files:
249
249
  - app/controllers/completion_kit/responses_controller.rb
250
250
  - app/controllers/completion_kit/runs_controller.rb
251
251
  - app/controllers/completion_kit/suggestions_controller.rb
252
+ - app/controllers/completion_kit/tags_controller.rb
253
+ - app/controllers/concerns/completion_kit/tag_filtering.rb
252
254
  - app/helpers/completion_kit/application_helper.rb
253
255
  - app/jobs/completion_kit/application_job.rb
254
256
  - app/jobs/completion_kit/generate_row_job.rb
@@ -269,6 +271,9 @@ files:
269
271
  - app/models/completion_kit/run.rb
270
272
  - app/models/completion_kit/run_metric.rb
271
273
  - app/models/completion_kit/suggestion.rb
274
+ - app/models/completion_kit/tag.rb
275
+ - app/models/completion_kit/tagging.rb
276
+ - app/models/concerns/completion_kit/taggable.rb
272
277
  - app/services/completion_kit/anthropic_client.rb
273
278
  - app/services/completion_kit/api_config.rb
274
279
  - app/services/completion_kit/csv_processor.rb
@@ -283,6 +288,7 @@ files:
283
288
  - app/services/completion_kit/mcp_tools/provider_credentials.rb
284
289
  - app/services/completion_kit/mcp_tools/responses.rb
285
290
  - app/services/completion_kit/mcp_tools/runs.rb
291
+ - app/services/completion_kit/mcp_tools/tags.rb
286
292
  - app/services/completion_kit/model_discovery_service.rb
287
293
  - app/services/completion_kit/ollama_client.rb
288
294
  - app/services/completion_kit/open_ai_client.rb
@@ -330,7 +336,15 @@ files:
330
336
  - app/views/completion_kit/runs/index.html.erb
331
337
  - app/views/completion_kit/runs/new.html.erb
332
338
  - app/views/completion_kit/runs/show.html.erb
339
+ - app/views/completion_kit/shared/_settings_nav.html.erb
333
340
  - app/views/completion_kit/suggestions/show.html.erb
341
+ - app/views/completion_kit/tags/_filter_bar.html.erb
342
+ - app/views/completion_kit/tags/_form.html.erb
343
+ - app/views/completion_kit/tags/_marks.html.erb
344
+ - app/views/completion_kit/tags/_picker.html.erb
345
+ - app/views/completion_kit/tags/edit.html.erb
346
+ - app/views/completion_kit/tags/index.html.erb
347
+ - app/views/completion_kit/tags/new.html.erb
334
348
  - app/views/layouts/completion_kit/application.html.erb
335
349
  - config/routes.rb
336
350
  - db/migrate/20260311000001_create_completion_kit_tables.rb
@@ -352,6 +366,8 @@ files:
352
366
  - db/migrate/20260501000005_collapse_run_status_and_add_failure_summary.rb
353
367
  - db/migrate/20260507000001_add_discovery_error_to_provider_credentials.rb
354
368
  - db/migrate/20260507150000_add_temperature_ignored_to_runs.rb
369
+ - db/migrate/20260509000001_create_completion_kit_tags.rb
370
+ - db/migrate/20260509000002_create_completion_kit_taggings.rb
355
371
  - lib/completion-kit.rb
356
372
  - lib/completion_kit.rb
357
373
  - lib/completion_kit/concurrency_check.rb
@@ -369,7 +385,6 @@ metadata:
369
385
  homepage_uri: https://github.com/homemade-software-inc/completion-kit
370
386
  source_code_uri: https://github.com/homemade-software-inc/completion-kit
371
387
  changelog_uri: https://github.com/homemade-software-inc/completion-kit/blob/main/CHANGELOG.md
372
- post_install_message:
373
388
  rdoc_options: []
374
389
  require_paths:
375
390
  - lib
@@ -384,8 +399,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
384
399
  - !ruby/object:Gem::Version
385
400
  version: '0'
386
401
  requirements: []
387
- rubygems_version: 3.5.16
388
- signing_key:
402
+ rubygems_version: 3.6.9
389
403
  specification_version: 4
390
404
  summary: Your prompts need tests too. Run them against real data, score outputs with
391
405
  an LLM judge, iterate until they work.