completion-kit 0.4.8 → 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 +18 -1
@@ -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.8"
2
+ VERSION = "0.5.0"
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.4.8
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Damien Bastin
@@ -237,6 +237,7 @@ files:
237
237
  - app/controllers/completion_kit/api/v1/provider_credentials_controller.rb
238
238
  - app/controllers/completion_kit/api/v1/responses_controller.rb
239
239
  - app/controllers/completion_kit/api/v1/runs_controller.rb
240
+ - app/controllers/completion_kit/api/v1/tags_controller.rb
240
241
  - app/controllers/completion_kit/api_reference_controller.rb
241
242
  - app/controllers/completion_kit/application_controller.rb
242
243
  - app/controllers/completion_kit/datasets_controller.rb
@@ -248,6 +249,8 @@ files:
248
249
  - app/controllers/completion_kit/responses_controller.rb
249
250
  - app/controllers/completion_kit/runs_controller.rb
250
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
251
254
  - app/helpers/completion_kit/application_helper.rb
252
255
  - app/jobs/completion_kit/application_job.rb
253
256
  - app/jobs/completion_kit/generate_row_job.rb
@@ -268,6 +271,9 @@ files:
268
271
  - app/models/completion_kit/run.rb
269
272
  - app/models/completion_kit/run_metric.rb
270
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
271
277
  - app/services/completion_kit/anthropic_client.rb
272
278
  - app/services/completion_kit/api_config.rb
273
279
  - app/services/completion_kit/csv_processor.rb
@@ -282,6 +288,7 @@ files:
282
288
  - app/services/completion_kit/mcp_tools/provider_credentials.rb
283
289
  - app/services/completion_kit/mcp_tools/responses.rb
284
290
  - app/services/completion_kit/mcp_tools/runs.rb
291
+ - app/services/completion_kit/mcp_tools/tags.rb
285
292
  - app/services/completion_kit/model_discovery_service.rb
286
293
  - app/services/completion_kit/ollama_client.rb
287
294
  - app/services/completion_kit/open_ai_client.rb
@@ -329,7 +336,15 @@ files:
329
336
  - app/views/completion_kit/runs/index.html.erb
330
337
  - app/views/completion_kit/runs/new.html.erb
331
338
  - app/views/completion_kit/runs/show.html.erb
339
+ - app/views/completion_kit/shared/_settings_nav.html.erb
332
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
333
348
  - app/views/layouts/completion_kit/application.html.erb
334
349
  - config/routes.rb
335
350
  - db/migrate/20260311000001_create_completion_kit_tables.rb
@@ -351,6 +366,8 @@ files:
351
366
  - db/migrate/20260501000005_collapse_run_status_and_add_failure_summary.rb
352
367
  - db/migrate/20260507000001_add_discovery_error_to_provider_credentials.rb
353
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
354
371
  - lib/completion-kit.rb
355
372
  - lib/completion_kit.rb
356
373
  - lib/completion_kit/concurrency_check.rb