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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7c2de74d874b4b8ea5952f574bd922360399b2287d0cf87a3ef05bf11e4c50cd
4
- data.tar.gz: 3a949c1db4c91dd7d2b3ef353ac50a4c3a0d16210695adbd9b4e98a7c43157c1
3
+ metadata.gz: e4a1592e3ab99397804845b4b200ec44ec78ba2575426aa34525ce1137b932fc
4
+ data.tar.gz: baaed961b26171e4fac1410b587de872f5e0c28606d3b591232218406cca79d3
5
5
  SHA512:
6
- metadata.gz: 4212a7bef6dbf0302631e4a70cbe9823656687ecee26003aaa940c10aff39c81c94402b2dbb9820259b11bde7ce6bcb22662b4749a9a8631884635fd88df18f9
7
- data.tar.gz: 7cba484ac695ebeaa5956de196208bfa6c68d51d47e46726d2804a12db5df16f82acd9632bab3aac5538bce4a5bc977e65b5137d4dea214c0584541d03d50c8f
6
+ metadata.gz: a0709c0819790bcb7246920fbaa6440debbafaa5872fa968f3d91acc1c3011c60c1682187d4a64102a81ead142ce2b0aa123503b13f9730967e20d3e1b156a94
7
+ data.tar.gz: 6ea65ed6fec559e8b92c5f3cc46812425120a5a20681d527d7cb05c32dd39e22a6705b95df21f51657a0bf4e255cbc0e876df49e181578714218aaf921234754
data/README.md CHANGED
@@ -136,6 +136,7 @@ Only one mode can be active.
136
136
  - **Response.** The model's output for one dataset row, with reviews attached.
137
137
  - **Metric.** An evaluation dimension with a name, instruction, evaluation steps, and a 1-5 star scoring scale. The LLM judge uses this to score each response.
138
138
  - **Metric Group.** A reusable group of metrics you can apply to a run as a set.
139
+ - **Tag.** A domain label you can attach to prompts, runs, metrics, and datasets. Auto-assigned from a 10-color palette. Filter any index page by tag (`?tag[]=...`).
139
140
  - **Provider Credential.** An API key for a model provider. Encrypted at rest, never returned through the API.
140
141
 
141
142
  ## REST API
@@ -501,6 +501,11 @@ tr:hover .ck-chip--publish {
501
501
  transform: translateY(-1px);
502
502
  }
503
503
 
504
+ .ck-button:focus-visible {
505
+ outline: 2px solid var(--ck-accent);
506
+ outline-offset: 2px;
507
+ }
508
+
504
509
  .ck-button--primary {
505
510
  background: var(--ck-accent);
506
511
  color: #080b14;
@@ -1531,6 +1536,11 @@ tr:hover .ck-chip--publish {
1531
1536
  color: var(--ck-danger);
1532
1537
  }
1533
1538
 
1539
+ .ck-icon-btn:focus-visible {
1540
+ outline: 2px solid var(--ck-accent);
1541
+ outline-offset: 2px;
1542
+ }
1543
+
1534
1544
  .ck-icon-btn--spinning svg {
1535
1545
  animation: ck-spin 0.8s linear infinite;
1536
1546
  }
@@ -2594,6 +2604,7 @@ select.ck-input {
2594
2604
  align-items: center;
2595
2605
  gap: 0.4rem;
2596
2606
  padding: 0.32rem 0.7rem;
2607
+ white-space: nowrap;
2597
2608
  background: transparent;
2598
2609
  border: 1px solid var(--ck-line);
2599
2610
  border-radius: 999px;
@@ -2842,6 +2853,72 @@ select.ck-input {
2842
2853
  color: var(--ck-dim);
2843
2854
  }
2844
2855
 
2856
+ .ck-clamp-2 {
2857
+ display: -webkit-box;
2858
+ -webkit-line-clamp: 2;
2859
+ -webkit-box-orient: vertical;
2860
+ overflow: hidden;
2861
+ }
2862
+
2863
+ .ck-tags-table th:first-child,
2864
+ .ck-tags-table td:first-child {
2865
+ width: 35%;
2866
+ }
2867
+ .ck-tags-table th:nth-child(2),
2868
+ .ck-tags-table td:nth-child(2) {
2869
+ width: 60%;
2870
+ }
2871
+ .tag-mark.tag-mark--lg {
2872
+ padding: 4px 10px;
2873
+ font-size: 0.8rem;
2874
+ letter-spacing: 0.02em;
2875
+ border-radius: 5px;
2876
+ }
2877
+ .ck-tags-table__unused {
2878
+ color: var(--ck-dim);
2879
+ font-style: italic;
2880
+ }
2881
+
2882
+ .ck-mg-members {
2883
+ display: flex;
2884
+ flex-wrap: wrap;
2885
+ gap: 0.3rem;
2886
+ }
2887
+ .ck-mg-member {
2888
+ display: inline-flex;
2889
+ align-items: center;
2890
+ padding: 2px 8px;
2891
+ background: var(--ck-surface-soft);
2892
+ border: 1px solid var(--ck-line);
2893
+ border-radius: 4px;
2894
+ font-family: var(--ck-mono);
2895
+ font-size: 11px;
2896
+ letter-spacing: 0.02em;
2897
+ color: var(--ck-muted);
2898
+ white-space: nowrap;
2899
+ }
2900
+
2901
+ .ck-metric-tag-filter {
2902
+ display: flex;
2903
+ flex-wrap: wrap;
2904
+ align-items: center;
2905
+ gap: 8px;
2906
+ margin: 0.5rem 0 0.85rem;
2907
+ }
2908
+ .ck-metric-tag-filter__label {
2909
+ font-family: var(--ck-mono);
2910
+ font-size: 11px;
2911
+ letter-spacing: 0.14em;
2912
+ text-transform: uppercase;
2913
+ color: var(--ck-dim);
2914
+ margin-right: 0.3rem;
2915
+ }
2916
+ .ck-metric-tag-filter__chip {
2917
+ border-radius: 5px;
2918
+ font: inherit;
2919
+ cursor: pointer;
2920
+ }
2921
+
2845
2922
  .ck-metrics-table__groups {
2846
2923
  display: flex;
2847
2924
  flex-wrap: wrap;
@@ -3396,3 +3473,301 @@ a.ck-metric-group-pill {
3396
3473
  text-overflow: ellipsis;
3397
3474
  }
3398
3475
 
3476
+ :root {
3477
+ --tag-crimson: #F24E1E;
3478
+ --tag-burnt-orange: #FF8A00;
3479
+ --tag-amber: #FFC700;
3480
+ --tag-mint: #14AE5C;
3481
+ --tag-deep-emerald: #00623F;
3482
+ --tag-electric-cyan: #00D1FF;
3483
+ --tag-cobalt-blue: #0D99FF;
3484
+ --tag-deep-indigo: #5551FF;
3485
+ --tag-amethyst: #9747FF;
3486
+ --tag-rose: #FF5CBE;
3487
+ }
3488
+
3489
+ .tag {
3490
+ display: inline-flex;
3491
+ align-items: center;
3492
+ gap: 6px;
3493
+ padding: 4px 9px;
3494
+ min-height: calc(1.4em + 8px);
3495
+ border-radius: 5px;
3496
+ font-family: var(--ck-mono);
3497
+ font-size: 12.5px;
3498
+ font-weight: 500;
3499
+ letter-spacing: 0.01em;
3500
+ text-transform: lowercase;
3501
+ color: white;
3502
+ line-height: 1.4;
3503
+ white-space: nowrap;
3504
+ transition: filter 120ms ease;
3505
+ text-decoration: none;
3506
+ user-select: none;
3507
+ }
3508
+
3509
+ .tag::before {
3510
+ content: "";
3511
+ width: 6px;
3512
+ height: 6px;
3513
+ border-radius: 50%;
3514
+ background: rgba(255, 255, 255, 0.85);
3515
+ flex-shrink: 0;
3516
+ }
3517
+
3518
+ .tag > span:empty { display: none; }
3519
+
3520
+ a.tag, label.tag { cursor: pointer; }
3521
+ a.tag:hover, label.tag:hover { filter: brightness(1.12); }
3522
+
3523
+
3524
+ .tag-crimson { background-color: var(--tag-crimson); --current-tag-color: var(--tag-crimson); }
3525
+ .tag-burnt-orange { background-color: var(--tag-burnt-orange); --current-tag-color: var(--tag-burnt-orange); color: #2a1300; }
3526
+ .tag-burnt-orange::before { background: rgba(0, 0, 0, 0.55); }
3527
+ .tag-amber { background-color: var(--tag-amber); --current-tag-color: var(--tag-amber); color: #1a0f00; }
3528
+ .tag-amber::before { background: rgba(0, 0, 0, 0.55); }
3529
+ .tag-mint { background-color: var(--tag-mint); --current-tag-color: var(--tag-mint); }
3530
+ .tag-deep-emerald { background-color: var(--tag-deep-emerald); --current-tag-color: var(--tag-deep-emerald); }
3531
+ .tag-electric-cyan { background-color: var(--tag-electric-cyan); --current-tag-color: var(--tag-electric-cyan); color: #001620; }
3532
+ .tag-electric-cyan::before { background: rgba(0, 0, 0, 0.55); }
3533
+ .tag-cobalt-blue { background-color: var(--tag-cobalt-blue); --current-tag-color: var(--tag-cobalt-blue); }
3534
+ .tag-deep-indigo { background-color: var(--tag-deep-indigo); --current-tag-color: var(--tag-deep-indigo); }
3535
+ .tag-amethyst { background-color: var(--tag-amethyst); --current-tag-color: var(--tag-amethyst); }
3536
+ .tag-rose { background-color: var(--tag-rose); --current-tag-color: var(--tag-rose); color: #2a0017; }
3537
+ .tag-rose::before { background: rgba(0, 0, 0, 0.6); }
3538
+
3539
+ .tag-outline {
3540
+ background-color: transparent !important;
3541
+ border: 1px solid currentColor;
3542
+ box-shadow: none;
3543
+ }
3544
+ .tag-outline::before { background: currentColor; }
3545
+ .tag-outline.tag-crimson { color: var(--tag-crimson); }
3546
+ .tag-outline.tag-burnt-orange { color: var(--tag-burnt-orange); }
3547
+ .tag-outline.tag-amber { color: var(--tag-amber); }
3548
+ .tag-outline.tag-mint { color: var(--tag-mint); }
3549
+ .tag-outline.tag-deep-emerald { color: var(--tag-deep-emerald); }
3550
+ .tag-outline.tag-electric-cyan { color: var(--tag-electric-cyan); }
3551
+ .tag-outline.tag-cobalt-blue { color: var(--tag-cobalt-blue); }
3552
+ .tag-outline.tag-deep-indigo { color: var(--tag-deep-indigo); }
3553
+ .tag-outline.tag-amethyst { color: var(--tag-amethyst); }
3554
+ .tag-outline.tag-rose { color: var(--tag-rose); }
3555
+
3556
+ /* Inline mark — quiet "color dot + name" used on row metadata */
3557
+ .tag-mark {
3558
+ display: inline-flex;
3559
+ align-items: center;
3560
+ gap: 5px;
3561
+ padding: 2px 8px;
3562
+ border: 1px solid transparent;
3563
+ border-radius: 4px;
3564
+ font-family: var(--ck-mono);
3565
+ font-size: 11px;
3566
+ letter-spacing: 0.02em;
3567
+ text-transform: lowercase;
3568
+ white-space: nowrap;
3569
+ background: color-mix(in srgb, var(--mark-color) 24%, transparent);
3570
+ color: color-mix(in srgb, var(--mark-color) 88%, var(--ck-text));
3571
+ }
3572
+
3573
+ .tag-mark::before {
3574
+ content: "";
3575
+ display: inline-block;
3576
+ width: 0.95em;
3577
+ height: 0.95em;
3578
+ flex-shrink: 0;
3579
+ background-color: currentColor;
3580
+ -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><path fill-rule='evenodd' clip-rule='evenodd' d='M3 4.5C3 3.67 3.67 3 4.5 3h5.379c.398 0 .779.158 1.06.44L18.06 10.56c.585.586.585 1.535 0 2.121l-5.379 5.379a1.5 1.5 0 0 1-2.121 0L3.44 10.94A1.5 1.5 0 0 1 3 9.879V4.5Zm3.5 1.75a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Z' fill='black'/></svg>") no-repeat center / contain;
3581
+ mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'><path fill-rule='evenodd' clip-rule='evenodd' d='M3 4.5C3 3.67 3.67 3 4.5 3h5.379c.398 0 .779.158 1.06.44L18.06 10.56c.585.586.585 1.535 0 2.121l-5.379 5.379a1.5 1.5 0 0 1-2.121 0L3.44 10.94A1.5 1.5 0 0 1 3 9.879V4.5Zm3.5 1.75a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5Z' fill='black'/></svg>") no-repeat center / contain;
3582
+ }
3583
+ .tag-mark + .tag-mark { margin-left: 0.4rem; }
3584
+ .tag-marks-row {
3585
+ display: flex;
3586
+ flex-wrap: wrap;
3587
+ align-items: center;
3588
+ margin-top: 0.35rem;
3589
+ }
3590
+
3591
+ /* Picker — flat row of chips + inline input, no enclosing field */
3592
+ .ck-tag-picker {
3593
+ display: flex;
3594
+ flex-wrap: wrap;
3595
+ align-items: center;
3596
+ gap: 7px;
3597
+ }
3598
+ .ck-tag-picker__input {
3599
+ flex: 1;
3600
+ min-width: 160px;
3601
+ background: transparent;
3602
+ border: 0;
3603
+ padding: 2px 0;
3604
+ color: var(--ck-text);
3605
+ font-family: var(--ck-mono);
3606
+ font-size: 12.5px;
3607
+ letter-spacing: 0.01em;
3608
+ outline: none;
3609
+ }
3610
+ .ck-tag-picker__input::placeholder { color: var(--ck-dim); }
3611
+
3612
+ /* Picker chips: unchecked = neutral pill with a small tag-colored dot.
3613
+ Avoids looking "applied" simply because the tag color is on screen. */
3614
+ .ck-tag-picker label.tag-mark,
3615
+ .ck-tag-filter .tag-mark,
3616
+ .ck-metric-tag-filter .tag-mark {
3617
+ cursor: pointer;
3618
+ user-select: none;
3619
+ padding: 4px 10px;
3620
+ font-size: 0.8rem;
3621
+ transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
3622
+ }
3623
+
3624
+ /* Selected state — picker, filter bars, metric-tag-filter share one look */
3625
+ .ck-tag-picker label.tag-mark:has(input:checked),
3626
+ .ck-tag-filter .tag-mark:not(.tag-mark--off),
3627
+ .ck-metric-tag-filter .tag-mark:not(.tag-mark--off) {
3628
+ background: color-mix(in srgb, var(--mark-color) 38%, transparent);
3629
+ color: color-mix(in srgb, var(--mark-color) 95%, var(--ck-text));
3630
+ border-color: color-mix(in srgb, var(--mark-color) 50%, transparent);
3631
+ }
3632
+
3633
+ /* Unselected state — same shape, neutral outline + muted text */
3634
+ .ck-tag-picker label.tag-mark:has(input:not(:checked)),
3635
+ .ck-tag-filter .tag-mark.tag-mark--off,
3636
+ .ck-metric-tag-filter .tag-mark.tag-mark--off {
3637
+ background: transparent;
3638
+ border-color: var(--ck-line);
3639
+ color: var(--ck-muted);
3640
+ }
3641
+ .ck-tag-picker label.tag-mark:has(input:not(:checked)):hover,
3642
+ .ck-tag-filter .tag-mark.tag-mark--off:hover,
3643
+ .ck-metric-tag-filter .tag-mark.tag-mark--off:hover {
3644
+ border-color: var(--ck-line-strong);
3645
+ color: color-mix(in srgb, var(--mark-color) 80%, var(--ck-text));
3646
+ }
3647
+ button.tag-mark, a.tag-mark {
3648
+ border: 0;
3649
+ background: color-mix(in srgb, var(--mark-color) 24%, transparent);
3650
+ cursor: pointer;
3651
+ text-decoration: none;
3652
+ }
3653
+
3654
+ /* Top-nav settings menu (ported from completion-kit-cloud) */
3655
+ .ck-settings-menu {
3656
+ position: relative;
3657
+ }
3658
+ .ck-settings-menu__trigger {
3659
+ cursor: pointer;
3660
+ list-style: none;
3661
+ user-select: none;
3662
+ }
3663
+ .ck-settings-menu__trigger::-webkit-details-marker {
3664
+ display: none;
3665
+ }
3666
+ .ck-settings-menu__panel {
3667
+ position: absolute;
3668
+ right: 0;
3669
+ top: calc(100% + 0.5rem);
3670
+ padding: 0.5rem;
3671
+ background: var(--ck-surface);
3672
+ border: 1px solid var(--ck-line);
3673
+ border-radius: var(--ck-radius);
3674
+ z-index: 50;
3675
+ display: flex;
3676
+ flex-direction: column;
3677
+ gap: 0.1rem;
3678
+ white-space: nowrap;
3679
+ }
3680
+ .ck-settings-menu__item {
3681
+ display: block;
3682
+ padding: 0.5rem 0.65rem;
3683
+ border-radius: calc(var(--ck-radius) - 2px);
3684
+ font-family: var(--ck-mono);
3685
+ font-size: 0.78rem;
3686
+ letter-spacing: 0.04em;
3687
+ text-transform: uppercase;
3688
+ text-decoration: none;
3689
+ color: var(--ck-text);
3690
+ transition: background 0.15s, color 0.15s;
3691
+ }
3692
+ .ck-settings-menu__item:hover {
3693
+ background: var(--ck-surface-hover);
3694
+ }
3695
+
3696
+ /* Settings page kicker — small label above the page title on settings pages */
3697
+ .ck-settings-kicker {
3698
+ display: flex;
3699
+ align-items: center;
3700
+ gap: 0.45rem;
3701
+ flex-wrap: wrap;
3702
+ font-family: var(--ck-mono);
3703
+ font-size: 11px;
3704
+ letter-spacing: 0.2em;
3705
+ text-transform: uppercase;
3706
+ color: var(--ck-dim);
3707
+ margin: 0 0 0.85rem;
3708
+ }
3709
+ .ck-settings-kicker__sep {
3710
+ opacity: 0.55;
3711
+ }
3712
+ .ck-settings-kicker__link {
3713
+ color: var(--ck-muted);
3714
+ text-decoration: none;
3715
+ transition: color 120ms ease;
3716
+ }
3717
+ .ck-settings-kicker__link:hover { color: var(--ck-text); }
3718
+
3719
+ .tag-marks-row--header {
3720
+ margin-top: -0.5rem;
3721
+ margin-bottom: 1.25rem;
3722
+ }
3723
+
3724
+
3725
+ .ck-input--error {
3726
+ border-color: var(--ck-danger);
3727
+ background: rgba(248, 113, 113, 0.05);
3728
+ }
3729
+ .ck-input--error:focus {
3730
+ box-shadow: 0 0 0 3px rgba(248, 113, 113, 0.15);
3731
+ border-color: var(--ck-danger);
3732
+ }
3733
+
3734
+ .ck-field-error {
3735
+ margin: 0.35rem 0 0;
3736
+ color: var(--ck-danger);
3737
+ font-family: var(--ck-sans);
3738
+ font-size: 0.95rem;
3739
+ }
3740
+
3741
+ /* Filter bar — its own visual moment */
3742
+ .ck-tag-filter {
3743
+ display: flex;
3744
+ flex-wrap: wrap;
3745
+ align-items: center;
3746
+ gap: 8px;
3747
+ margin: 1.1rem 0 1.6rem;
3748
+ padding: 0.65rem 0.85rem;
3749
+ background: var(--ck-surface-soft);
3750
+ border: 1px solid var(--ck-line);
3751
+ border-radius: 6px;
3752
+ }
3753
+ .ck-tag-filter__label {
3754
+ font-family: var(--ck-mono);
3755
+ font-size: 12px;
3756
+ letter-spacing: 0.12em;
3757
+ text-transform: uppercase;
3758
+ color: var(--ck-dim);
3759
+ margin-right: 0.4rem;
3760
+ }
3761
+ .ck-tag-filter__clear {
3762
+ margin-left: auto;
3763
+ font-family: var(--ck-mono);
3764
+ font-size: 11.5px;
3765
+ letter-spacing: 0.08em;
3766
+ text-transform: uppercase;
3767
+ color: var(--ck-muted);
3768
+ text-decoration: none;
3769
+ transition: color 120ms ease;
3770
+ }
3771
+ .ck-tag-filter__clear:hover { color: var(--ck-accent); }
3772
+
3773
+
@@ -5,7 +5,7 @@ module CompletionKit
5
5
  before_action :set_dataset, only: [:show, :update, :destroy]
6
6
 
7
7
  def index
8
- render json: Dataset.order(created_at: :desc)
8
+ render json: Dataset.includes(:tags).order(created_at: :desc)
9
9
  end
10
10
 
11
11
  def show
@@ -43,7 +43,7 @@ module CompletionKit
43
43
  end
44
44
 
45
45
  def dataset_params
46
- params.permit(:name, :csv_data)
46
+ params.permit(:name, :csv_data, tag_names: [])
47
47
  end
48
48
  end
49
49
  end
@@ -5,7 +5,7 @@ module CompletionKit
5
5
  before_action :set_metric_group, only: [:show, :update, :destroy]
6
6
 
7
7
  def index
8
- render json: MetricGroup.order(created_at: :desc)
8
+ render json: MetricGroup.includes(:tags).order(created_at: :desc)
9
9
  end
10
10
 
11
11
  def show
@@ -45,7 +45,7 @@ module CompletionKit
45
45
  end
46
46
 
47
47
  def metric_group_params
48
- params.permit(:name, :description, metric_ids: [])
48
+ params.permit(:name, :description, metric_ids: [], tag_names: [])
49
49
  end
50
50
  end
51
51
  end
@@ -5,7 +5,7 @@ module CompletionKit
5
5
  before_action :set_metric, only: [:show, :update, :destroy]
6
6
 
7
7
  def index
8
- render json: Metric.order(created_at: :desc)
8
+ render json: Metric.includes(:tags).order(created_at: :desc)
9
9
  end
10
10
 
11
11
  def show
@@ -43,7 +43,8 @@ module CompletionKit
43
43
  end
44
44
 
45
45
  def metric_params
46
- params.permit(:name, :instruction, rubric_bands: [:stars, :description])
46
+ params.permit(:name, :instruction,
47
+ rubric_bands: [:stars, :description], tag_names: [])
47
48
  end
48
49
  end
49
50
  end
@@ -5,7 +5,7 @@ module CompletionKit
5
5
  before_action :set_prompt, only: [:show, :update, :destroy, :publish]
6
6
 
7
7
  def index
8
- render json: Prompt.order(created_at: :desc)
8
+ render json: Prompt.includes(:tags).order(created_at: :desc)
9
9
  end
10
10
 
11
11
  def show
@@ -23,9 +23,10 @@ module CompletionKit
23
23
 
24
24
  def update
25
25
  if @prompt.runs.exists?
26
- new_prompt = @prompt.clone_as_new_version(prompt_params.to_h)
26
+ new_prompt = @prompt.clone_as_new_version(prompt_params.except(:tag_names).to_h)
27
27
  new_prompt.publish!
28
- render json: new_prompt
28
+ new_prompt.update!(tag_names: prompt_params[:tag_names]) if prompt_params.key?(:tag_names)
29
+ render json: new_prompt.reload
29
30
  elsif @prompt.update(prompt_params)
30
31
  render json: @prompt
31
32
  else
@@ -56,7 +57,7 @@ module CompletionKit
56
57
  end
57
58
 
58
59
  def prompt_params
59
- params.permit(:name, :description, :template, :llm_model)
60
+ params.permit(:name, :description, :template, :llm_model, tag_names: [])
60
61
  end
61
62
  end
62
63
  end
@@ -5,7 +5,7 @@ module CompletionKit
5
5
  before_action :set_run, only: [:show, :update, :destroy, :generate, :retry_failures]
6
6
 
7
7
  def index
8
- render json: Run.order(created_at: :desc)
8
+ render json: Run.includes(:tags).order(created_at: :desc)
9
9
  end
10
10
 
11
11
  def show
@@ -76,7 +76,8 @@ module CompletionKit
76
76
  end
77
77
 
78
78
  def run_params
79
- params.permit(:name, :prompt_id, :dataset_id, :judge_model, :temperature, metric_ids: [])
79
+ params.permit(:name, :prompt_id, :dataset_id, :judge_model, :temperature,
80
+ metric_ids: [], tag_names: [])
80
81
  end
81
82
  end
82
83
  end
@@ -0,0 +1,51 @@
1
+ module CompletionKit
2
+ module Api
3
+ module V1
4
+ class TagsController < BaseController
5
+ before_action :set_tag, only: [:show, :update, :destroy]
6
+
7
+ def index
8
+ render json: Tag.order(:name)
9
+ end
10
+
11
+ def show
12
+ render json: @tag
13
+ end
14
+
15
+ def create
16
+ tag = Tag.new(tag_params)
17
+ if tag.save
18
+ render json: tag, status: :created
19
+ else
20
+ render json: {errors: tag.errors}, status: :unprocessable_entity
21
+ end
22
+ end
23
+
24
+ def update
25
+ if @tag.update(tag_params)
26
+ render json: @tag
27
+ else
28
+ render json: {errors: @tag.errors}, status: :unprocessable_entity
29
+ end
30
+ end
31
+
32
+ def destroy
33
+ @tag.destroy!
34
+ head :no_content
35
+ end
36
+
37
+ private
38
+
39
+ def set_tag
40
+ @tag = Tag.find(params[:id])
41
+ rescue ActiveRecord::RecordNotFound
42
+ not_found
43
+ end
44
+
45
+ def tag_params
46
+ params.permit(:name)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,9 +1,10 @@
1
1
  module CompletionKit
2
2
  class DatasetsController < ApplicationController
3
+ include CompletionKit::TagFiltering
3
4
  before_action :set_dataset, only: [:show, :edit, :update, :destroy]
4
5
 
5
6
  def index
6
- @datasets = Dataset.includes(:runs).order(created_at: :desc)
7
+ @datasets = apply_tag_filter(Dataset.includes(:runs, :tags).order(created_at: :desc))
7
8
  end
8
9
 
9
10
  def show
@@ -47,7 +48,7 @@ module CompletionKit
47
48
  end
48
49
 
49
50
  def dataset_params
50
- params.require(:dataset).permit(:name, :csv_data)
51
+ params.require(:dataset).permit(:name, :csv_data, tag_names: [])
51
52
  end
52
53
  end
53
54
  end
@@ -1,9 +1,10 @@
1
1
  module CompletionKit
2
2
  class MetricGroupsController < ApplicationController
3
+ include CompletionKit::TagFiltering
3
4
  before_action :set_metric_group, only: [:show, :edit, :update, :destroy]
4
5
 
5
6
  def index
6
- @metric_groups = MetricGroup.includes(:metrics).order(:name)
7
+ @metric_groups = apply_tag_filter(MetricGroup.includes(:metrics, :tags).order(:name))
7
8
  end
8
9
 
9
10
  def show
@@ -11,16 +12,16 @@ module CompletionKit
11
12
 
12
13
  def new
13
14
  @metric_group = MetricGroup.new
14
- @metrics = Metric.order(:name)
15
+ @metrics = Metric.includes(:tags).order(:name)
15
16
  end
16
17
 
17
18
  def edit
18
- @metrics = Metric.order(:name)
19
+ @metrics = Metric.includes(:tags).order(:name)
19
20
  end
20
21
 
21
22
  def create
22
23
  @metric_group = MetricGroup.new(metric_group_params.except(:metric_ids))
23
- @metrics = Metric.order(:name)
24
+ @metrics = Metric.includes(:tags).order(:name)
24
25
 
25
26
  if @metric_group.save
26
27
  @metric_group.replace_metrics!(metric_group_params[:metric_ids])
@@ -31,7 +32,7 @@ module CompletionKit
31
32
  end
32
33
 
33
34
  def update
34
- @metrics = Metric.order(:name)
35
+ @metrics = Metric.includes(:tags).order(:name)
35
36
 
36
37
  if @metric_group.update(metric_group_params.except(:metric_ids))
37
38
  @metric_group.replace_metrics!(metric_group_params[:metric_ids])
@@ -53,7 +54,7 @@ module CompletionKit
53
54
  end
54
55
 
55
56
  def metric_group_params
56
- params.require(:metric_group).permit(:name, :description, metric_ids: [])
57
+ params.require(:metric_group).permit(:name, :description, metric_ids: [], tag_names: [])
57
58
  end
58
59
  end
59
60
  end
@@ -1,9 +1,10 @@
1
1
  module CompletionKit
2
2
  class MetricsController < ApplicationController
3
+ include CompletionKit::TagFiltering
3
4
  before_action :set_metric, only: [:show, :edit, :update, :destroy]
4
5
 
5
6
  def index
6
- @metrics = Metric.includes(:metric_groups).order(:name)
7
+ @metrics = apply_tag_filter(Metric.includes(:metric_groups, :tags).order(:name))
7
8
  end
8
9
 
9
10
  def show
@@ -46,7 +47,8 @@ module CompletionKit
46
47
  end
47
48
 
48
49
  def metric_params
49
- params.require(:metric).permit(:name, :instruction, rubric_bands: [:stars, :description])
50
+ params.require(:metric).permit(:name, :instruction,
51
+ rubric_bands: [:stars, :description], tag_names: [])
50
52
  end
51
53
  end
52
54
  end