feat: add support for Grok Web and Perplexity Web providers

This commit is contained in:
decolua committed 2026-04-22 11:58:53 +07:00
1 parent eeb2dc9e30
commit abb04c5366
17 files changed
+1583 -13

No files matched your search

@@ -4,9 +4,14 @@ import { useState } from "react";
import PropTypes from "prop-types";
import { Button, Badge, Input, Modal, Select } from "@/shared/components";
export default function AddApiKeyModal({ isOpen, provider, providerName, isCompatible, isAnthropic, proxyPools, onSave, onClose }) {
export default function AddApiKeyModal({ isOpen, provider, providerName, isCompatible, isAnthropic, authType, authHint, website, proxyPools, onSave, onClose }) {
const NONE_PROXY_POOL_VALUE = "__none__";
const isOllamaLocal = provider === "ollama-local";
const isCookie = authType === "cookie";
const credentialLabel = isCookie ? "Cookie Value" : "API Key";
const credentialPlaceholder = isCookie
? (provider === "grok-web" ? "sso=xxxxx... or just the raw value" : "eyJhbGciOi...")
: "";
const [formData, setFormData] = useState({
name: "",
@@ -87,7 +92,7 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
if (!provider) return null;
return (
<Modal isOpen={isOpen} title={`Add ${providerName || provider} API Key`} onClose={onClose}>
<Modal isOpen={isOpen} title={`Add ${providerName || provider} ${credentialLabel}`} onClose={onClose}>
<div className="flex flex-col gap-4">
<Input
label="Name"
@@ -114,10 +119,11 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
{!isOllamaLocal && (
<div className="flex gap-2">
<Input
label="API Key"
type="password"
label={credentialLabel}
type={isCookie ? "text" : "password"}
value={formData.apiKey}
onChange={(e) => setFormData({ ...formData, apiKey: e.target.value })}
placeholder={credentialPlaceholder}
className="flex-1"
/>
<div className="pt-6">
@@ -127,6 +133,19 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
</div>
</div>
)}
{isCookie && authHint && (
<p className="text-xs text-text-muted">
{authHint}
{website && (
<>
{" "}
<a href={website} target="_blank" rel="noopener noreferrer" className="text-primary underline">
Open {website.replace(/^https?:\/\//, "")}
</a>
</>
)}
</p>
)}
{isOllamaLocal && (
<p className="text-xs text-text-muted">
Leave blank to use <code>http://localhost:11434</code>. For remote Ollama, enter the full host URL (e.g. <code>http://192.168.1.10:11434</code>).
@@ -192,6 +211,9 @@ AddApiKeyModal.propTypes = {
providerName: PropTypes.string,
isCompatible: PropTypes.bool,
isAnthropic: PropTypes.bool,
authType: PropTypes.string,
authHint: PropTypes.string,
website: PropTypes.string,
proxyPools: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string,
@@ -5,7 +5,7 @@ import { useParams, useRouter } from "next/navigation";
import Link from "next/link";
import Image from "next/image";
import { Card, Button, Badge, Input, Modal, CardSkeleton, OAuthModal, KiroOAuthWrapper, CursorAuthModal, IFlowCookieModal, GitLabAuthModal, Toggle, Select, EditConnectionModal } from "@/shared/components";
import { OAUTH_PROVIDERS, APIKEY_PROVIDERS, FREE_PROVIDERS, FREE_TIER_PROVIDERS, getProviderAlias, isOpenAICompatibleProvider, isAnthropicCompatibleProvider, AI_PROVIDERS, THINKING_CONFIG } from "@/shared/constants/providers";
import { OAUTH_PROVIDERS, APIKEY_PROVIDERS, FREE_PROVIDERS, FREE_TIER_PROVIDERS, WEB_COOKIE_PROVIDERS, getProviderAlias, isOpenAICompatibleProvider, isAnthropicCompatibleProvider, AI_PROVIDERS, THINKING_CONFIG } from "@/shared/constants/providers";
import { getModelsByProviderId } from "@/shared/constants/models";
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
import { fetchSuggestedModels } from "@/shared/utils/providerModelsFetcher";
@@ -58,7 +58,7 @@ export default function ProviderDetailPage() {
baseUrl: providerNode.baseUrl,
type: providerNode.type,
}
: (OAUTH_PROVIDERS[providerId] || APIKEY_PROVIDERS[providerId] || FREE_PROVIDERS[providerId] || FREE_TIER_PROVIDERS[providerId]);
: (OAUTH_PROVIDERS[providerId] || APIKEY_PROVIDERS[providerId] || FREE_PROVIDERS[providerId] || FREE_TIER_PROVIDERS[providerId] || WEB_COOKIE_PROVIDERS[providerId]);
const isOAuth = !!OAUTH_PROVIDERS[providerId] || !!FREE_PROVIDERS[providerId];
const isFreeNoAuth = !!FREE_PROVIDERS[providerId]?.noAuth;
const models = getModelsByProviderId(providerId);
@@ -1012,6 +1012,9 @@ export default function ProviderDetailPage() {
providerName={providerInfo.name}
isCompatible={isCompatible}
isAnthropic={isAnthropicCompatible}
authType={providerInfo?.authType}
authHint={providerInfo?.authHint}
website={providerInfo?.website}
proxyPools={proxyPools}
onSave={handleSaveApiKey}
onClose={() => setShowAddApiKeyModal(false)}
@@ -17,6 +17,7 @@ import { OAUTH_PROVIDERS, APIKEY_PROVIDERS } from "@/shared/constants/config";
import {
FREE_PROVIDERS,
FREE_TIER_PROVIDERS,
WEB_COOKIE_PROVIDERS,
OPENAI_COMPATIBLE_PREFIX,
ANTHROPIC_COMPATIBLE_PREFIX,
} from "@/shared/constants/providers";
@@ -377,6 +378,27 @@ export default function ProvidersPage() {
</div>
</div>
{/* Web Cookie Providers — use browser subscription cookie instead of API key */}
{/* <div className="flex flex-col gap-4">
<div className="flex items-center justify-between">
<h2 className="text-xl font-semibold flex items-center gap-2">
Web Cookie Providers{" "}
</h2>
</div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
{Object.entries(WEB_COOKIE_PROVIDERS).map(([key, info]) => (
<ApiKeyProviderCard
key={key}
providerId={key}
provider={info}
stats={getProviderStats(key, "apikey")}
authType="apikey"
onToggle={(active) => handleToggleProvider(key, "apikey", active)}
/>
))}
</div>
</div> */}
{/* API Key Compatible Providers — dynamic (OpenAI/Anthropic compatible) */}
<div className="flex flex-col gap-4">
<div className="flex items-center justify-between">