feat(dashboard): 'Open <host>' link in Add session cookie modal (#6268) (#6391)

'Open <host>' link in Add session cookie modal (#6268) (net +1/-0, tests OK). Integrated into release/v3.8.46.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-06 19:28:39 -03:00
committed by GitHub
parent c6a80071f1
commit b7ac5261a5
6 changed files with 128 additions and 2 deletions

View File

@@ -8,6 +8,7 @@
### ✨ New Features
- **feat(dashboard):** "Add session cookie" modal now shows a prominent **"Open host →"** link to the provider's own site ([#6268](https://github.com/diegosouzapw/OmniRoute/issues/6268)) — every `-web` cookie-session provider (chatgpt-web, claude-web, gemini-web, kimi-web, lmarena, qwen-web, m365-copilot-web, …) renders a one-click external link (opening the provider's login/home page in a new tab) so operators no longer tab away to retype the URL mid-setup. The host resolves from a pure, unit-tested `resolveWebProviderHost()` (prefers `WEB_COOKIE_PROVIDERS[id].website`, falls back to the registry `baseUrl` origin); non-web providers render exactly as before. Kilo's dup flag vs #6265 (modal-too-small-on-1080p) was a false positive — distinct concern. Regression guard: `tests/unit/resolve-web-provider-host.test.ts` (5). (thanks @chirag127)
- **feat(providers):** add **DigitalOcean** AI (serverless inference) as an OpenAI-compatible API-key provider — base `https://inference.do-ai.run/v1`, wired through the shared OpenAI-compatible registry with full model passthrough (`open-sse/config/providers/registry/digitalocean/`, `src/shared/constants/providers/apikey/inference-hosts.ts`). Regression guard: `tests/unit/digitalocean-provider.test.ts`. (thanks @newnol)
- **feat(providers):** add **Huancheng Public API** (`hcnsec`) as an OpenAI-compatible regional provider — Xinjiang Huancheng Cybersecurity's public LLM platform (base `https://api.hcnsec.cn/v1`, free credits via daily check-ins), wired through the shared OpenAI-compatible registry with full model passthrough (`open-sse/config/providers/registry/hcnsec/`, `src/shared/constants/providers/apikey/regional.ts`). Regression guard: `tests/unit/hcnsec-provider.test.ts`. (thanks @UnrealAryan)
- **feat(dashboard):** the web-session credential guide now shows an **"Open {host}" link** to the provider's sign-in site (derived from the provider `website` via `getProviderWebsiteHost`), so you can jump straight to the page where the cookie/session must be captured. Regression guard: `tests/unit/web-session-provider-link-6316.test.ts`. (thanks @jordansilly77-stack)

View File

@@ -2,7 +2,11 @@
import { useState, useEffect, useRef } from "react";
import { useTranslations } from "next-intl";
import { Button, Badge, Input, Modal, Toggle } from "@/shared/components";
import { providerAllowsOptionalApiKey, supportsBulkApiKey } from "@/shared/constants/providers";
import {
providerAllowsOptionalApiKey,
supportsBulkApiKey,
resolveWebProviderHost,
} from "@/shared/constants/providers";
import { parseBulkApiKeys } from "@/shared/utils/bulkApiKeyParser";
import { providerHasFreeModels } from "@/shared/utils/freeModels";
import {
@@ -87,6 +91,12 @@ export default function AddApiKeyModal({
const webSessionCredential = getWebSessionCredentialRequirement(provider);
const isNoAuthWebSessionCredential = webSessionCredential?.kind === "none";
const isWebSessionCredential = !!webSessionCredential && webSessionCredential.kind !== "none";
// #6268 — for web-session providers, resolve the provider's public site so the
// modal can offer a prominent "Open host →" link. Gated on webSessionCredential
// so non-web providers never render a link.
const webProviderHostLink = webSessionCredential
? resolveWebProviderHost(provider, defaultBaseUrl)
: null;
const providerDisplayName = providerName || provider || "";
const apiKeyOptional =
providerAllowsOptionalApiKey(provider) || Boolean(isNoAuthWebSessionCredential);
@@ -427,6 +437,21 @@ export default function AddApiKeyModal({
onClose={onClose}
>
<div className="flex flex-col gap-4">
{webProviderHostLink && (
<a
href={webProviderHostLink.url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center gap-1.5 rounded-lg border border-primary/30 bg-primary/10 px-3 py-2 text-sm font-medium text-primary transition-colors hover:bg-primary/20"
>
<span className="material-symbols-outlined text-[18px]" aria-hidden="true">
open_in_new
</span>
{providerText(t, "openWebProviderSite", "Open {host}", {
host: webProviderHostLink.host,
})}
</a>
)}
{bulkSupported && (
<div className="flex gap-1 border-b border-border">
<button

View File

@@ -4821,6 +4821,7 @@
"bedrockModelsDescription": "Amazon Bedrock models are scoped by AWS region. Import from /models or add Bedrock model IDs enabled in the selected region.",
"bedrockModelPlaceholder": "anthropic.claude-sonnet-4-6",
"addProviderSessionCookieTitle": "Add {provider} session cookie",
"openWebProviderSite": "Open {host}",
"addProviderWebTokenTitle": "Add {provider} web token",
"addProviderConnectionTitle": "Add {provider} connection",
"webTokenCredentialLabel": "Web session token",

View File

@@ -12,7 +12,9 @@ export interface ProviderRiskNoticeFields {
import { NOAUTH_PROVIDERS } from "./providers/noauth";
import { OAUTH_PROVIDERS } from "./providers/oauth";
import { WEB_COOKIE_PROVIDERS } from "./providers/web-cookie";
import { WEB_COOKIE_PROVIDERS, resolveWebProviderHost } from "./providers/web-cookie";
export { resolveWebProviderHost };
export type { WebProviderHostLink } from "./providers/web-cookie";
import { APIKEY_PROVIDERS } from "./providers/apikey";
import { LOCAL_PROVIDERS } from "./providers/local";
import { SEARCH_PROVIDERS } from "./providers/search";

View File

@@ -320,3 +320,45 @@ export const WEB_COOKIE_PROVIDERS = {
"Login at zenmux.ai, then export all cookies using EditThisCookie or Cookie-Editor and paste the full Cookie header string here. Refresh every ~30 days.",
},
};
/** Resolved public site for a web-session provider (href + display host). */
export interface WebProviderHostLink {
/** Full URL to open in a new tab (the provider's own `website`, or the origin
* derived from a registry baseUrl fallback). */
url: string;
/** Display host, e.g. `chatgpt.com` — used for the "Open host →" label. */
host: string;
}
/**
* Resolve the public website + display host for a web-session provider so the
* "Add session cookie" modal can render a prominent "Open host →" link.
*
* Primary source: `WEB_COOKIE_PROVIDERS[providerId].website`. When an entry has
* no `website` (or the provider is not in the catalog but the caller knows it is
* a web-session provider), the caller may pass its registry `baseUrl` as a
* fallback — only the origin is kept from it.
*
* Pure and React-free (unit-testable). Web-ness gating is the caller's
* responsibility: with no `fallbackBaseUrl`, a provider absent from
* `WEB_COOKIE_PROVIDERS` resolves to `null`.
*/
export function resolveWebProviderHost(
providerId: string | null | undefined,
fallbackBaseUrl?: string | null
): WebProviderHostLink | null {
if (!providerId) return null;
const entry = (WEB_COOKIE_PROVIDERS as Record<string, { website?: string }>)[providerId];
const website = entry?.website?.trim();
const fallback = fallbackBaseUrl?.trim();
const source = website || fallback;
if (!source) return null;
try {
const parsed = new URL(source);
// Keep the website URL verbatim (it may point at a specific path like
// `/chat`); for a registry baseUrl fallback, keep only the origin.
return { url: website ? source : parsed.origin, host: parsed.host };
} catch {
return null;
}
}

View File

@@ -0,0 +1,55 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import {
resolveWebProviderHost,
WEB_COOKIE_PROVIDERS,
} from "@/shared/constants/providers/web-cookie";
// Feature #6268 — "Open host →" link in the "Add session cookie" modal.
// Pure host-resolution helper backing the modal link.
test("known -web provider returns the host derived from its `website`", () => {
const link = resolveWebProviderHost("chatgpt-web");
assert.ok(link, "expected a resolved link for chatgpt-web");
assert.equal(link.host, "chatgpt.com");
assert.equal(link.url, "https://chatgpt.com");
});
test("website with a path keeps the full URL but exposes the bare host", () => {
// huggingchat's website is https://huggingface.co/chat
const link = resolveWebProviderHost("huggingchat");
assert.ok(link);
assert.equal(link.host, "huggingface.co");
assert.equal(link.url, "https://huggingface.co/chat");
});
test("provider with no `website` but a registry baseUrl returns the origin", () => {
// duckduckgo-web is a real web-session provider that is NOT in
// WEB_COOKIE_PROVIDERS and has no `website`; the caller supplies its registry
// baseUrl as a fallback, from which only the origin is kept.
assert.equal(
(WEB_COOKIE_PROVIDERS as Record<string, unknown>)["duckduckgo-web"],
undefined,
"test premise: duckduckgo-web must be absent from WEB_COOKIE_PROVIDERS"
);
const link = resolveWebProviderHost(
"duckduckgo-web",
"https://duckduckgo.com/duckchat/v1/chat"
);
assert.ok(link);
assert.equal(link.host, "duckduckgo.com");
assert.equal(link.url, "https://duckduckgo.com");
});
test("non-web / unknown provider returns null", () => {
assert.equal(resolveWebProviderHost("openai"), null);
assert.equal(resolveWebProviderHost("totally-made-up"), null);
assert.equal(resolveWebProviderHost(null), null);
assert.equal(resolveWebProviderHost(undefined), null);
assert.equal(resolveWebProviderHost(""), null);
});
test("unparseable fallback baseUrl yields null instead of throwing", () => {
assert.equal(resolveWebProviderHost("duckduckgo-web", "not a url"), null);
});