mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-23 23:52:18 +03:00
Compare commits
1 Commits
fix/codex-
...
feat/cheap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9a3e29d33 |
108
src/app/(dashboard)/dashboard/CheaperInferenceSponsorBanner.tsx
Normal file
108
src/app/(dashboard)/dashboard/CheaperInferenceSponsorBanner.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
"use client";
|
||||
|
||||
import { useSyncExternalStore } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import ProviderIcon from "@/shared/components/ProviderIcon";
|
||||
|
||||
// Branded short link through our own link.omniroute.online shortener, so the
|
||||
// click lands in our Kutt metrics. Points at cheaperinference.com?utm_source=omniroute
|
||||
// (the URL in README.md's Open Source Friends section). Keep in sync with the
|
||||
// `cheaper` slug on the shortener.
|
||||
const CHEAPER_INFERENCE_URL = "https://link.omniroute.online/cheaper";
|
||||
|
||||
// Cheaper Inference brand green (#31f889). White text on it fails contrast, so
|
||||
// the CTA pairs it with the dark ink from the provider's color token (colors.ts:
|
||||
// cheaperinference.text = #04170d). Hex values stay in sync with that token.
|
||||
|
||||
const DISMISS_STORAGE_KEY = "omniroute-cheaperinference-sponsor-banner-dismissed-v1";
|
||||
// Same-tab signal for the dismiss button, since writing localStorage doesn't
|
||||
// fire a "storage" event in the tab that wrote it.
|
||||
const DISMISS_EVENT = "omniroute:cheaperinference-sponsor-banner-dismissed";
|
||||
|
||||
function isNotDismissed(): boolean {
|
||||
try {
|
||||
return !localStorage.getItem(DISMISS_STORAGE_KEY);
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function subscribe(callback: () => void) {
|
||||
window.addEventListener(DISMISS_EVENT, callback);
|
||||
return () => window.removeEventListener(DISMISS_EVENT, callback);
|
||||
}
|
||||
|
||||
// SSR has no localStorage, so the server always renders the banner visible;
|
||||
// useSyncExternalStore reconciles that against the real client-side value
|
||||
// right after hydration, mirroring KimiSponsorBanner's pattern.
|
||||
function getServerSnapshot() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismissable banner announcing the Cheaper Inference OmniRoute partnership on
|
||||
* the dashboard home page — same size/shape as KimiSponsorBanner, no version
|
||||
* gate (durable partnership, not a time-boxed offer). The logomark reuses
|
||||
* <ProviderIcon providerId="cheaperinference" .../>.
|
||||
*/
|
||||
export default function CheaperInferenceSponsorBanner() {
|
||||
const t = useTranslations("cheaperInferenceSponsorBanner");
|
||||
const visible = useSyncExternalStore(subscribe, isNotDismissed, getServerSnapshot);
|
||||
|
||||
if (!visible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const dismiss = () => {
|
||||
try {
|
||||
localStorage.setItem(DISMISS_STORAGE_KEY, "true");
|
||||
} catch {
|
||||
// ignore — worst case the banner reappears next visit
|
||||
}
|
||||
window.dispatchEvent(new Event(DISMISS_EVENT));
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
role="complementary"
|
||||
aria-label={t("title")}
|
||||
className="mb-4 flex flex-col gap-3 rounded-lg border border-[#31f889]/30 bg-[#31f889]/5 px-4 py-3 dark:bg-[#31f889]/10 sm:flex-row sm:items-center sm:justify-between"
|
||||
>
|
||||
<div className="flex min-w-0 items-start gap-3">
|
||||
<div className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-[#31f889]/10">
|
||||
<ProviderIcon providerId="cheaperinference" size={24} type="color" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-semibold text-text-main">{t("title")}</p>
|
||||
<p className="mt-0.5 text-xs text-text-muted">{t("description")}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 items-center gap-3 self-end sm:self-auto">
|
||||
<div className="flex flex-col items-end gap-0.5">
|
||||
<a
|
||||
href={CHEAPER_INFERENCE_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title={t("partnerLinkNote")}
|
||||
className="inline-flex items-center gap-1.5 whitespace-nowrap rounded-lg bg-[#31f889] px-3 py-1.5 text-xs font-semibold text-[#04170d] transition-colors hover:brightness-110"
|
||||
>
|
||||
{t("cta")}
|
||||
<span className="material-symbols-outlined text-[14px]" aria-hidden="true">
|
||||
open_in_new
|
||||
</span>
|
||||
</a>
|
||||
<span className="text-[9px] text-text-muted/70">{t("partnerLinkNote")}</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={dismiss}
|
||||
aria-label={t("dismissAriaLabel")}
|
||||
className="text-text-muted transition-colors hover:text-text-main"
|
||||
>
|
||||
<span className="material-symbols-outlined text-[18px]">close</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,7 +6,9 @@ import { useTranslations } from "next-intl";
|
||||
// Marketplace listing is the primary CTA; Open VSX (Cursor/Windsurf/VSCodium/etc.)
|
||||
// is called out via secondaryNote instead of a second button, to keep this banner
|
||||
// the same size as KimiSponsorBanner.
|
||||
const MARKETPLACE_URL = "https://marketplace.visualstudio.com/items?itemName=diegosouzapw.omnicopilot";
|
||||
// Branded short link through our own link.omniroute.online shortener (the `vsx`
|
||||
// slug), so the click lands in our Kutt metrics.
|
||||
const MARKETPLACE_URL = "https://link.omniroute.online/vsx";
|
||||
|
||||
const DISMISS_STORAGE_KEY = "omniroute-vscode-copilot-banner-dismissed-v1";
|
||||
// Same-tab signal for the dismiss button, since writing localStorage doesn't
|
||||
|
||||
@@ -4,6 +4,7 @@ import { getSettings } from "@/lib/localDb";
|
||||
import HomePageClient from "../dashboard/HomePageClient";
|
||||
import BootstrapBanner from "../dashboard/BootstrapBanner";
|
||||
import KimiSponsorBanner from "../dashboard/KimiSponsorBanner";
|
||||
import CheaperInferenceSponsorBanner from "../dashboard/CheaperInferenceSponsorBanner";
|
||||
import VscodeCopilotBanner from "../dashboard/VscodeCopilotBanner";
|
||||
import NewsBanner from "../dashboard/NewsBanner";
|
||||
|
||||
@@ -20,6 +21,7 @@ export default async function HomePage() {
|
||||
<>
|
||||
{isBootstrapped && <BootstrapBanner />}
|
||||
<KimiSponsorBanner />
|
||||
<CheaperInferenceSponsorBanner />
|
||||
<VscodeCopilotBanner />
|
||||
<NewsBanner />
|
||||
<HomePageClient machineId={machineId} />
|
||||
|
||||
@@ -13869,5 +13869,12 @@
|
||||
"toolsMismatch": "Provider does not support tool calling",
|
||||
"structuredOutputMismatch": "Provider does not support structured output",
|
||||
"contextWindowMismatch": "Request exceeds provider context window"
|
||||
},
|
||||
"cheaperInferenceSponsorBanner": {
|
||||
"title": "Cheaper Inference is an OmniRoute Open Source Friend",
|
||||
"description": "A cost-ranked gateway reselling dozens of frontier models behind one OpenAI-compatible endpoint — routing each request to the cheapest eligible provider, never above list price.",
|
||||
"cta": "Get an API Key",
|
||||
"partnerLinkNote": "Partner link",
|
||||
"dismissAriaLabel": "Dismiss"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13845,5 +13845,12 @@
|
||||
"toolsMismatch": "Provider does not support tool calling",
|
||||
"structuredOutputMismatch": "Provider does not support structured output",
|
||||
"contextWindowMismatch": "Request exceeds provider context window"
|
||||
},
|
||||
"cheaperInferenceSponsorBanner": {
|
||||
"title": "A Cheaper Inference é uma Amiga do Código Aberto do OmniRoute",
|
||||
"description": "Um gateway com custo ordenado que revende dezenas de modelos de fronteira num único endpoint compatível com OpenAI — roteando cada requisição ao provedor elegível mais barato, nunca acima do preço de tabela.",
|
||||
"cta": "Obter uma Chave de API",
|
||||
"partnerLinkNote": "Link de parceiro",
|
||||
"dismissAriaLabel": "Dispensar"
|
||||
}
|
||||
}
|
||||
|
||||
92
tests/unit/ui/cheaperInferenceSponsorBanner.test.tsx
Normal file
92
tests/unit/ui/cheaperInferenceSponsorBanner.test.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
// @vitest-environment jsdom
|
||||
/**
|
||||
* CheaperInferenceSponsorBanner — render gate (localStorage dismissal), CTA
|
||||
* pointing at our link.omniroute.online branded short link, and discreet
|
||||
* partner-link note. Mirrors kimiSponsorBanner.test.tsx, minus the version gate
|
||||
* (this banner is a durable partnership, not a time-boxed offer).
|
||||
*/
|
||||
import React from "react";
|
||||
import { act } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const STORAGE_KEY = "omniroute-cheaperinference-sponsor-banner-dismissed-v1";
|
||||
const DISMISS_EVENT = "omniroute:cheaperinference-sponsor-banner-dismissed";
|
||||
const SHORT_URL = "https://link.omniroute.online/cheaper";
|
||||
|
||||
vi.mock("next-intl", () => ({ useTranslations: () => (k: string) => k }));
|
||||
vi.mock("@/shared/components/ProviderIcon", () => ({ default: () => null }));
|
||||
|
||||
async function renderBanner(): Promise<HTMLDivElement> {
|
||||
vi.resetModules();
|
||||
const { default: CheaperInferenceSponsorBanner } =
|
||||
await import("../../../src/app/(dashboard)/dashboard/CheaperInferenceSponsorBanner");
|
||||
|
||||
const container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
const root = createRoot(container);
|
||||
act(() => {
|
||||
root.render(<CheaperInferenceSponsorBanner />);
|
||||
});
|
||||
return container;
|
||||
}
|
||||
|
||||
describe("CheaperInferenceSponsorBanner", () => {
|
||||
beforeEach(() => {
|
||||
(
|
||||
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
|
||||
).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = "";
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
});
|
||||
|
||||
it("renders with the CTA pointing at the branded short link", async () => {
|
||||
const container = await renderBanner();
|
||||
expect(container.textContent).toContain("title");
|
||||
expect(container.textContent).toContain("cta");
|
||||
const link = container.querySelector("a[href]");
|
||||
expect(link).not.toBeNull();
|
||||
expect(link?.getAttribute("href")).toBe(SHORT_URL);
|
||||
expect(link?.getAttribute("target")).toBe("_blank");
|
||||
expect(link?.getAttribute("rel")).toContain("noopener");
|
||||
});
|
||||
|
||||
it("shows the discreet partner-link note near the CTA", async () => {
|
||||
const container = await renderBanner();
|
||||
expect(container.textContent).toContain("partnerLinkNote");
|
||||
const link = container.querySelector("a[href]");
|
||||
expect(link?.getAttribute("title")).toBe("partnerLinkNote");
|
||||
});
|
||||
|
||||
it("hides after dismissal and stays hidden on re-render", async () => {
|
||||
const first = await renderBanner();
|
||||
const button = first.querySelector("button");
|
||||
expect(button).not.toBeNull();
|
||||
act(() => {
|
||||
button?.click();
|
||||
});
|
||||
expect(localStorage.getItem(STORAGE_KEY)).toBe("true");
|
||||
expect(first.textContent).not.toContain("title");
|
||||
|
||||
// a fresh render (simulating a later visit) stays hidden
|
||||
const second = await renderBanner();
|
||||
expect(second.textContent).not.toContain("title");
|
||||
});
|
||||
|
||||
it("re-renders visible again only after the key is cleared", async () => {
|
||||
const first = await renderBanner();
|
||||
const button = first.querySelector("button");
|
||||
act(() => {
|
||||
button?.click();
|
||||
});
|
||||
expect(localStorage.getItem(STORAGE_KEY)).toBe("true");
|
||||
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
const second = await renderBanner();
|
||||
expect(second.textContent).toContain("title");
|
||||
});
|
||||
});
|
||||
@@ -11,7 +11,7 @@ import { createRoot } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const STORAGE_KEY = "omniroute-vscode-copilot-banner-dismissed-v1";
|
||||
const MARKETPLACE_URL = "https://marketplace.visualstudio.com/items?itemName=diegosouzapw.omnicopilot";
|
||||
const MARKETPLACE_URL = "https://link.omniroute.online/vsx";
|
||||
|
||||
vi.mock("next-intl", () => ({ useTranslations: () => (k: string) => k }));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user