Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions packages/web/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ tbody {
color: var(--color-text-secondary);
}

.model-id-cell {
.id-cell {
display: flex;
align-items: center;
justify-content: space-between;
Expand All @@ -351,11 +351,11 @@ tbody {
transition: opacity 0.2s ease, color 0.2s ease;
}

.model-id-cell:hover .copy-button {
.id-cell:hover .copy-button {
opacity: 1;
}

.model-id-cell .copy-button svg {
.id-cell .copy-button svg {
display: block;
}

Expand All @@ -372,6 +372,11 @@ tbody {
color: var(--color-brand) !important;
}

/* Visual failure state when copy is not possible */
.copy-button.failed {
color: #e11 !important;
}

.modalities {
display: flex;
gap: 0.25rem;
Expand Down Expand Up @@ -546,4 +551,4 @@ dialog {
}
}

}
}
34 changes: 27 additions & 7 deletions packages/web/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,14 @@ search.addEventListener("keydown", (e) => {
});

///////////////////////////////////
// Handle Copy model ID function
// Handle Copy ID function
///////////////////////////////////
(window as any).copyModelId = async (
button: HTMLButtonElement,
modelId: string
) => {
// Generic copy function for model or provider IDs
// Copy text helper for buttons. This is now used by delegation below
async function copyText(button: HTMLButtonElement, id: string) {
try {
if (navigator.clipboard) {
await navigator.clipboard.writeText(modelId);
await navigator.clipboard.writeText(id);

// Switch to check icon
const copyIcon = button.querySelector(".copy-icon") as HTMLElement;
Expand All @@ -209,7 +208,28 @@ search.addEventListener("keydown", (e) => {
} catch (err) {
console.error("Failed to copy text: ", err);
}
};
}

// Attach to window for compatibility with any existing calling sites.
(window as any).copyText = copyText;

// Event delegation: catch clicks on copy buttons and handle them safely.
document.addEventListener("click", (e) => {
const target = e.target as Element | null;
if (!target) return;

const button = target.closest("button.copy-button") as HTMLButtonElement | null;
if (!button) return;

const id = button.dataset.copyId;
if (!id) return;

// Prevent accidental form submits/navigation
e.preventDefault();
copyText(button, id);
});

// NOTE: If you need to support older releases, update the calling sites accordingly.

///////////////////////////////////
// Initialize State from URL
Expand Down
103 changes: 55 additions & 48 deletions packages/web/src/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,59 @@ function renderProviderLogo(providerId: string) {
return <span dangerouslySetInnerHTML={{ __html: svgContent }} />;
}

// Render a reusable table cell for IDs (provider/model).
function renderIdCell(
id: string,
label: string,
className = "id-cell"
) {
const textClass = "id-text";
return (
<td>
<div class={className}>
<span class={textClass}>{id}</span>
<button
class="copy-button"
title={`Copy ${label} ID`}
aria-label={`Copy ${label} ID`}
data-copy-id={id}
>
<svg
class="copy-icon"
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
<path d="m4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
</svg>
<svg
class="check-icon"
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
style="display: none;"
>
<polyline points="20,6 9,17 4,12" />
</svg>
</button>
</div>
</td>
);
}

const getModalityIcon = (modality: string) => {
switch (modality) {
case "text":
Expand Down Expand Up @@ -363,54 +416,8 @@ export const Rendered = renderToString(
</td>
<td>{model.name}</td>
<td>{model.family ?? "-"}</td>
<td>{providerId}</td>
<td>
<div class="model-id-cell">
<span class="model-id-text">{modelId}</span>
<button
class="copy-button"
onclick={`copyModelId(this, '${modelId}')`}
>
<svg
class="copy-icon"
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect
width="14"
height="14"
x="8"
y="8"
rx="2"
ry="2"
/>
<path d="m4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
</svg>
<svg
class="check-icon"
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
style="display: none;"
>
<polyline points="20,6 9,17 4,12" />
</svg>
</button>
</div>
</td>
{renderIdCell(providerId, "Provider")}
{renderIdCell(modelId, "Model")}
<td>{model.tool_call ? "Yes" : "No"}</td>
<td>{model.reasoning ? "Yes" : "No"}</td>
<td>
Expand Down
25 changes: 25 additions & 0 deletions providers/openrouter/models/nvidia/nemotron-3-nano-30b-a3b.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name = "Nemotron 3 Nano 30B A3B"
family = "nemotron-3-nano"
release_date = "2025-12-10"
last_updated = "2025-12-10"
attachment = false
reasoning = true
temperature = true
tool_call = true
knowledge = "2025-12"
open_weights = true

[cost]
input = 0
output = 0

[limit]
context = 256_000
output = 256_000

[modalities]
input = ["text"]
output = ["text"]

[provider]
npm = "@openrouter/ai-sdk-provider"
25 changes: 25 additions & 0 deletions providers/openrouter/models/xiaomi/mimo-v2-flash.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name = "MiMo V2 Flash (free)"
family = "mimo-v2-flash"
release_date = "2025-12-10"
last_updated = "2025-12-10"
attachment = false
reasoning = true
temperature = true
tool_call = true
knowledge = "2025-12"
open_weights = true

[cost]
input = 0
output = 0

[limit]
context = 262_144
output = 262_144

[modalities]
input = ["text"]
output = ["text"]

[provider]
npm = "@openrouter/ai-sdk-provider"