feat(dahl): add manual API key option alongside auto-generated token (#9077)

Validated in local merge-train T5 (base49+contributors+pacocartones)
This commit is contained in:
PizzaV
2026-08-06 05:08:29 +02:00
committed by GitHub
parent ad82c81c38
commit 335829858b
4 changed files with 124 additions and 4 deletions

View File

@@ -93,6 +93,36 @@ export default function NoAuthProviderControls({
[blockedProviders, noAuthT, notify, providerAlias, providerId, providerName]
);
const handleManualApiKeyAdd = useCallback(
async (apiKey: string) => {
setSavingEnabled(true);
try {
const response = await fetch("/api/providers", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
provider: "dahl",
apiKey: apiKey.trim(),
name: "Dahl Manual",
priority: 1,
isActive: true,
testStatus: "unknown",
}),
});
const data = await response.json().catch(() => ({}));
if (!response.ok) {
throw new Error(data?.error?.message || data?.error || noAuthT("updateProviderFailed"));
}
notify.success(noAuthT("providerAdded", { provider: providerName }));
} catch (error) {
notify.error(error instanceof Error ? error.message : noAuthT("updateProviderFailed"));
} finally {
setSavingEnabled(false);
}
},
[noAuthT, notify, providerName]
);
const accountProviderName = ACCOUNT_PROVIDER_NAMES[providerId];
const host = providerProxy?.host;
const providerProxyControl = supportsNoAuthProviderProxy(providerId) ? (
@@ -129,6 +159,8 @@ export default function NoAuthProviderControls({
}
: undefined
}
showManualKeyInput={providerId === "dahl"}
onManualApiKeyAdd={providerId === "dahl" ? handleManualApiKeyAdd : undefined}
enabled={enabled}
savingEnabled={savingEnabled}
onEnabledChange={handleEnabledChange}

View File

@@ -19,6 +19,8 @@ interface NoAuthAccountCardProps {
savingEnabled?: boolean;
onEnabledChange?: (enabled: boolean) => void;
providerProxyControl?: ReactNode;
showManualKeyInput?: boolean;
onManualApiKeyAdd?: (apiKey: string) => Promise<void>;
}
interface Connection {
@@ -99,6 +101,7 @@ export default function NoAuthAccountCard({
savingEnabled = false,
onEnabledChange,
providerProxyControl,
onManualApiKeyAdd,
}: NoAuthAccountCardProps) {
const t = useTranslations("noAuthProvider");
const resolvedDescription = description || t("accountDescription");
@@ -116,6 +119,9 @@ export default function NoAuthAccountCard({
const [proxyUsername, setProxyUsername] = useState("");
const [proxyPassword, setProxyPassword] = useState("");
const [savingProxy, setSavingProxy] = useState(false);
const [manualApiKey, setManualApiKey] = useState("");
const [addingManualKey, setAddingManualKey] = useState(false);
const [showManualKeyInput, setShowManualKeyInput] = useState(false);
const popoverRef = useRef<HTMLDivElement>(null);
const fetchConnections = useCallback(async () => {
@@ -212,6 +218,23 @@ export default function NoAuthAccountCard({
}
};
const handleAddManualApiKey = async () => {
if (!manualApiKey.trim()) return;
setAddingManualKey(true);
try {
if (onManualApiKeyAdd) {
await onManualApiKeyAdd(manualApiKey.trim());
}
setManualApiKey("");
setShowManualKeyInput(false);
await fetchConnections();
} catch (err) {
console.error("Failed to add manual API key:", err);
} finally {
setAddingManualKey(false);
}
};
const handleRemoveAccount = async (accountId: string) => {
if (!conn) return;
const updated = allAccountIds.filter((id) => id !== accountId);
@@ -375,6 +398,45 @@ export default function NoAuthAccountCard({
<Button size="sm" icon="add" onClick={handleAddAccount} disabled={adding || !enabled}>
{adding ? t("adding") : resolvedAddLabel}
</Button>
{showManualKeyInput && (
<div className="flex items-center gap-2">
<input
type="password"
value={manualApiKey}
onChange={(e) => setManualApiKey(e.target.value)}
placeholder="Paste API key..."
className="rounded-md border border-black/10 bg-bg px-2 py-1 text-xs dark:border-white/10"
disabled={addingManualKey || !enabled}
/>
<Button
size="sm"
icon="add"
onClick={handleAddManualApiKey}
disabled={addingManualKey || !manualApiKey.trim() || !enabled}
>
{addingManualKey ? t("adding") : t("add")}
</Button>
<button
type="button"
onClick={() => {
setShowManualKeyInput(false);
setManualApiKey("");
}}
className="rounded p-1 text-text-muted hover:bg-black/5 dark:hover:bg-white/5"
>
<span className="material-symbols-outlined text-[16px]">close</span>
</button>
</div>
)}
{!showManualKeyInput && onManualApiKeyAdd && (
<button
type="button"
onClick={() => setShowManualKeyInput(true)}
className="rounded-md px-2 py-1 text-xs text-text-muted transition-colors hover:bg-black/5 hover:text-text-main dark:hover:bg-white/5"
>
{t("manualApiKey")}
</button>
)}
</div>
</div>
@@ -541,12 +603,14 @@ export default function NoAuthAccountCard({
)}
<div className="flex justify-end gap-2 pt-1">
<button
type="button"
onClick={() => setProxyAccountId(null)}
className="rounded-md px-3 py-1.5 text-xs text-text-muted transition-colors hover:bg-black/5 hover:text-text-main dark:hover:bg-white/5"
>
{t("cancel")}
</button>
<button
type="button"
onClick={handleSaveProxy}
disabled={savingProxy}
className="rounded-md bg-primary/10 px-3 py-1.5 text-xs text-primary transition-colors hover:bg-primary/20 disabled:opacity-50"

View File

@@ -234,14 +234,14 @@ export const APIKEY_PROVIDERS_GATEWAYS = {
textIcon: "DA",
website: "https://inference.dahl.global",
hasFree: true,
freeNote: "Free — MiniMax M2.7, Kimi K2.6. Click 'Add Account' to auto-generate a token.",
authHint: "Click 'Add Account' to auto-generate a token.",
apiHint: "No manual API key needed. Click 'Add Account' to auto-generate a token.",
freeNote: "Free — MiniMax M2.7, Kimi K2.6. Click 'Add Account' to auto-generate a token, or add your own API key.",
authHint: "Click 'Add Account' to auto-generate a token, or add a manual API key.",
apiHint: "Auto-generate a token or paste your own API key.",
apiKeyUrl: "https://inference.dahl.global/tokens",
passthroughModels: false,
managedAccount: true,
notice: {
text: "Dahl auto-generates tokens via https://inference.dahl.global/tokens. No signup needed. Rate limits apply.",
text: "Dahl auto-generates tokens via https://inference.dahl.global/tokens. No signup needed. Rate limits apply. You can also add your own API key.",
},
},
puter: {

View File

@@ -0,0 +1,24 @@
import test from "node:test";
import assert from "node:assert/strict";
import { APIKEY_PROVIDERS_GATEWAYS } from "../../src/shared/constants/providers/apikey/gateways.ts";
test("dahl freeNote mentions manual API key option", () => {
const dahl = APIKEY_PROVIDERS_GATEWAYS.dahl;
assert.ok(dahl.freeNote.includes("manual API key") || dahl.freeNote.includes("your own API key"), "dahl freeNote should mention manual API key option");
});
test("dahl authHint mentions manual API key option", () => {
const dahl = APIKEY_PROVIDERS_GATEWAYS.dahl;
assert.ok(dahl.authHint.includes("manual API key") || dahl.authHint.includes("your own API key"), "dahl authHint should mention manual API key option");
});
test("dahl apiHint mentions manual API key option", () => {
const dahl = APIKEY_PROVIDERS_GATEWAYS.dahl;
assert.ok(dahl.apiHint.includes("manual API key") || dahl.apiHint.includes("your own API key") || dahl.apiHint.includes("paste"), "dahl apiHint should mention manual API key option");
});
test("dahl notice text mentions manual API key option", () => {
const dahl = APIKEY_PROVIDERS_GATEWAYS.dahl;
assert.ok(dahl.notice.text.includes("manual API key") || dahl.notice.text.includes("your own API key"), "dahl notice should mention manual API key option");
});