mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
'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:
committed by
GitHub
parent
c6a80071f1
commit
b7ac5261a5
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
55
tests/unit/resolve-web-provider-host.test.ts
Normal file
55
tests/unit/resolve-web-provider-host.test.ts
Normal 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);
|
||||
});
|
||||
Reference in New Issue
Block a user