| 1 | <% content_for :title, "Models" %> |
| 2 | <% content_for :description, "Browse open-weights models on siGit — full model cards, LFS-backed files, and ready-to-pull weights." %> |
| 3 | |
| 4 | <div class="max-w-6xl mx-auto px-4 sm:px-6 py-8"> |
| 5 | |
| 6 | <div class="flex items-end justify-between gap-4 mb-6 flex-wrap"> |
| 7 | <div> |
| 8 | <h1 class="text-2xl font-semibold text-gray-100 flex items-center gap-2"> |
| 9 | <svg class="w-6 h-6 text-brand-500" viewBox="0 0 24 24" fill="none" |
| 10 | stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"> |
| 11 | <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/> |
| 12 | <path d="m3.27 6.96 8.73 5.05 8.73-5.05M12 22.08V12"/> |
| 13 | </svg> |
| 14 | Models |
| 15 | <span class="badge-gray text-sm align-middle"><%= number_with_delimiter(@models.size) %></span> |
| 16 | </h1> |
| 17 | <p class="text-sm text-gray-400 mt-1">Open-weights models hosted on siGit, ready to download and run.</p> |
| 18 | </div> |
| 19 | <% if signed_in? %> |
| 20 | <%= link_to new_repository_path(kind: "model"), class: "btn-primary text-xs py-2 px-4" do %> |
| 21 | <svg class="w-3.5 h-3.5" viewBox="0 0 16 16" fill="currentColor"><path d="M7.75 2a.75.75 0 0 1 .75.75V7h4.25a.75.75 0 0 1 0 1.5H8.5v4.25a.75.75 0 0 1-1.5 0V8.5H2.75a.75.75 0 0 1 0-1.5H7V2.75A.75.75 0 0 1 7.75 2Z"/></svg> |
| 22 | New model |
| 23 | <% end %> |
| 24 | <% end %> |
| 25 | </div> |
| 26 | |
| 27 | <div class="grid grid-cols-1 lg:grid-cols-4 gap-6"> |
| 28 | |
| 29 | <%# ── Filter sidebar ── %> |
| 30 | <aside class="lg:col-span-1 space-y-6"> |
| 31 | <% if @facets[:pipeline_tags].any? %> |
| 32 | <div> |
| 33 | <h2 class="text-xs uppercase tracking-wider text-gray-500 font-semibold mb-2">Tasks</h2> |
| 34 | <ul class="space-y-0.5"> |
| 35 | <% @facets[:pipeline_tags].each do |tag, count| %> |
| 36 | <li> |
| 37 | <%= link_to models_path(request.query_parameters.merge(pipeline_tag: (@pipeline_tag == tag ? nil : tag)).compact), |
| 38 | class: "flex items-center justify-between px-2 py-1 rounded text-sm #{@pipeline_tag == tag ? 'bg-brand-500/10 text-brand-400' : 'text-gray-400 hover:bg-surface-700'}" do %> |
| 39 | <span class="truncate"><%= pipeline_label(tag) %></span> |
| 40 | <span class="text-xs text-gray-500"><%= count %></span> |
| 41 | <% end %> |
| 42 | </li> |
| 43 | <% end %> |
| 44 | </ul> |
| 45 | </div> |
| 46 | <% end %> |
| 47 | |
| 48 | <% if @facets[:libraries].any? %> |
| 49 | <div> |
| 50 | <h2 class="text-xs uppercase tracking-wider text-gray-500 font-semibold mb-2">Libraries</h2> |
| 51 | <ul class="space-y-0.5"> |
| 52 | <% @facets[:libraries].each do |lib, count| %> |
| 53 | <li> |
| 54 | <%= link_to models_path(request.query_parameters.merge(library: (@library == lib ? nil : lib)).compact), |
| 55 | class: "flex items-center justify-between px-2 py-1 rounded text-sm #{@library == lib ? 'bg-brand-500/10 text-brand-400' : 'text-gray-400 hover:bg-surface-700'}" do %> |
| 56 | <span class="truncate"><%= lib %></span> |
| 57 | <span class="text-xs text-gray-500"><%= count %></span> |
| 58 | <% end %> |
| 59 | </li> |
| 60 | <% end %> |
| 61 | </ul> |
| 62 | </div> |
| 63 | <% end %> |
| 64 | |
| 65 | <% if @pipeline_tag || @library || @query.present? %> |
| 66 | <%= link_to "Clear filters", models_path, class: "text-xs text-brand-500 hover:underline" %> |
| 67 | <% end %> |
| 68 | </aside> |
| 69 | |
| 70 | <%# ── Results ── %> |
| 71 | <main class="lg:col-span-3"> |
| 72 | <div class="flex items-center gap-3 mb-4"> |
| 73 | <%= form_with url: models_path, method: :get, class: "flex-1" do %> |
| 74 | <% if @pipeline_tag %><%= hidden_field_tag :pipeline_tag, @pipeline_tag %><% end %> |
| 75 | <% if @library %><%= hidden_field_tag :library, @library %><% end %> |
| 76 | <%= hidden_field_tag :sort, @sort %> |
| 77 | <div class="relative"> |
| 78 | <svg class="w-4 h-4 text-gray-500 absolute left-3 top-1/2 -translate-y-1/2" viewBox="0 0 16 16" fill="currentColor"><path d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-1.06 1.06ZM11.5 7a4.5 4.5 0 1 0-9 0 4.5 4.5 0 0 0 9 0Z"/></svg> |
| 79 | <%= text_field_tag :q, @query, placeholder: "Search models by name or tag…", |
| 80 | class: "form-input pl-9", autocomplete: "off" %> |
| 81 | </div> |
| 82 | <% end %> |
| 83 | <%= form_with url: models_path, method: :get, class: "shrink-0", id: "sort-form" do %> |
| 84 | <% if @query.present? %><%= hidden_field_tag :q, @query %><% end %> |
| 85 | <% if @pipeline_tag %><%= hidden_field_tag :pipeline_tag, @pipeline_tag %><% end %> |
| 86 | <% if @library %><%= hidden_field_tag :library, @library %><% end %> |
| 87 | <%= select_tag :sort, |
| 88 | options_for_select([["Trending", "trending"], ["Most likes", "likes"], ["Most downloads", "downloads"], ["Recently updated", "recent"]], @sort), |
| 89 | class: "form-input py-2 pr-8 text-sm", onchange: "this.form.submit()" %> |
| 90 | <% end %> |
| 91 | </div> |
| 92 | |
| 93 | <% if @models.any? %> |
| 94 | <div class="card divide-y divide-surface-600"> |
| 95 | <% @models.each do |repo, card| %> |
| 96 | <%= link_to repository_path(repo.user.username, repo.name), |
| 97 | class: "block px-4 py-3 hover:bg-surface-600 transition-colors" do %> |
| 98 | <div class="flex items-center justify-between gap-3"> |
| 99 | <div class="min-w-0"> |
| 100 | <div class="flex items-center gap-2"> |
| 101 | <svg class="w-4 h-4 text-brand-500 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> |
| 102 | <path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/> |
| 103 | <path d="m3.27 6.96 8.73 5.05 8.73-5.05M12 22.08V12"/> |
| 104 | </svg> |
| 105 | <span class="font-medium text-gray-100 truncate"><%= repo.full_name %></span> |
| 106 | <% if repo.is_private %><span class="badge-gray">Private</span><% end %> |
| 107 | </div> |
| 108 | <div class="flex items-center gap-2 mt-1 text-xs text-gray-500 flex-wrap"> |
| 109 | <% if (label = pipeline_label(card.pipeline_tag)) %> |
| 110 | <span class="text-brand-400"><%= label %></span> |
| 111 | <span>·</span> |
| 112 | <% end %> |
| 113 | <% if card.library_name.present? %> |
| 114 | <span><%= card.library_name %></span> |
| 115 | <span>·</span> |
| 116 | <% end %> |
| 117 | <span>Updated <%= time_ago_in_words(repo.updated_at) %> ago</span> |
| 118 | </div> |
| 119 | </div> |
| 120 | <div class="flex items-center gap-4 text-xs text-gray-500 shrink-0"> |
| 121 | <span class="flex items-center gap-1" title="Downloads"> |
| 122 | <svg class="w-3.5 h-3.5" viewBox="0 0 16 16" fill="currentColor"><path d="M7.47 10.78a.75.75 0 0 0 1.06 0l3.75-3.75a.75.75 0 0 0-1.06-1.06L8.75 8.44V1.75a.75.75 0 0 0-1.5 0v6.69L4.78 5.97a.75.75 0 0 0-1.06 1.06ZM3.75 13a.75.75 0 0 0 0 1.5h8.5a.75.75 0 0 0 0-1.5Z"/></svg> |
| 123 | <%= humanize_count(repo.downloads_count) %> |
| 124 | </span> |
| 125 | <span class="flex items-center gap-1" title="Likes"> |
| 126 | <svg class="w-3.5 h-3.5" viewBox="0 0 16 16" fill="currentColor"><path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z"/></svg> |
| 127 | <%= humanize_count(repo.stars_count) %> |
| 128 | </span> |
| 129 | </div> |
| 130 | </div> |
| 131 | <% end %> |
| 132 | <% end %> |
| 133 | </div> |
| 134 | <% else %> |
| 135 | <div class="card px-6 py-16 text-center"> |
| 136 | <p class="text-sm text-gray-400 mb-1">No models found.</p> |
| 137 | <p class="text-xs text-gray-500">Try clearing filters or |
| 138 | <%= link_to "publish the first one", new_repository_path(kind: "model"), class: "text-brand-500 hover:underline" %>. |
| 139 | </p> |
| 140 | </div> |
| 141 | <% end %> |
| 142 | </main> |
| 143 | |
| 144 | </div> |
| 145 | </div> |