Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions open-sse/config/providerModels.js
Original file line number Diff line number Diff line change
Expand Up @@ -345,8 +345,19 @@ export const PROVIDER_MODELS = {
{ id: "DeepSeek-V3.2", name: "DeepSeek-V3.2" },
],
"cloudflare-ai": [
{ id: "@cf/meta/llama-3.2-1b-instruct", name: "Llama 3.2 1B Instruct" },
{ id: "@cf/meta/llama-3.2-3b-instruct", name: "Llama 3.2 3B Instruct" },
{ id: "@cf/meta/llama-3.1-8b-instruct-fp8-fast", name: "Llama 3.1 8B Instruct FP8 Fast" },
{ id: "@cf/meta/llama-3.1-8b-instruct-awq", name: "Llama 3.1 8B Instruct AWQ" },
{ id: "@cf/mistralai/mistral-small-3.1-24b-instruct", name: "Mistral Small 3.1 24B Instruct" },
{ id: "@cf/meta/llama-3.1-70b-instruct-fp8-fast", name: "Llama 3.1 70B Instruct FP8 Fast" },
{ id: "@cf/meta/llama-3.3-70b-instruct-fp8-fast", name: "Llama 3.3 70B Instruct FP8 Fast" },
{ id: "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b", name: "DeepSeek R1 Distill Qwen 32B" },
{ id: "@cf/moonshotai/kimi-k2.5", name: "Kimi K2.5" },
{ id: "@cf/moonshotai/kimi-k2.6", name: "Kimi K2.6" },
{ id: "@cf/zai-org/glm-4.7-flash", name: "GLM 4.7 Flash" },
{ id: "@cf/qwen/qwq-32b", name: "QwQ 32B" },
{ id: "@cf/qwen/qwen2.5-coder-32b-instruct", name: "Qwen 2.5 Coder 32B Instruct" },
],
byteplus: [
{ id: "seed-2-0-pro-260328", name: "Seed 2.0 Pro" },
Expand Down
2 changes: 1 addition & 1 deletion open-sse/config/providers.js
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ export const PROVIDERS = {
format: "openai",
headers: {}
},
// Cloudflare Workers AI - {accountId} resolved from credentials.providerSpecificData.accountId
// Cloudflare AI - {accountId} resolved from credentials.providerSpecificData.accountId
"cloudflare-ai": {
baseUrl: "https://api.cloudflare.com/client/v4/accounts/{accountId}/ai/v1/chat/completions",
format: "openai"
Expand Down
3 changes: 3 additions & 0 deletions open-sse/services/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ const ALIAS_TO_PROVIDER_ID = {
"volcengine-ark": "volcengine-ark",
byteplus: "byteplus",
bpm: "byteplus",
cf: "cloudflare-ai",
cloudflare: "cloudflare-ai",
"cloudflare-ai": "cloudflare-ai",
cursor: "cursor",
vx: "vertex",
vertex: "vertex",
Expand Down
Binary file added public/providers/cloudflare-ai.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
)}
{isCloudflareAi && (
<div className="bg-sidebar/50 p-4 rounded-lg border border-accent/20">
<h3 className="font-semibold mb-3 text-sm">Cloudflare Workers AI</h3>
<h3 className="font-semibold mb-3 text-sm">Cloudflare AI</h3>
<Input
label="Account ID"
value={cloudflareData.accountId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,21 @@ function CompatibleModelRow({ modelId, fullModel, copied, onCopy, onDeleteAlias,
: undefined;

return (
<div className={`flex items-center gap-3 p-3 rounded-lg border ${borderColor} hover:bg-sidebar/50`}>
<div className={`flex min-w-0 items-center gap-3 rounded-lg border p-3 ${borderColor} hover:bg-sidebar/50`}>
<span
className="material-symbols-outlined text-base text-text-muted"
className="material-symbols-outlined shrink-0 text-base text-text-muted"
style={iconColor ? { color: iconColor } : undefined}
>
{testStatus === "ok" ? "check_circle" : testStatus === "error" ? "cancel" : "smart_toy"}
</span>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium truncate">{modelId}</p>
<div className="flex items-center gap-1 mt-1">
<code className="text-xs text-text-muted font-mono bg-sidebar px-1.5 py-0.5 rounded">{fullModel}</code>
<div className="relative group/btn">
<div className="min-w-0 flex-1 overflow-hidden">
<p className="truncate text-sm font-medium" title={modelId}>{modelId}</p>
<div className="mt-1 flex min-w-0 items-center gap-1">
<code className="min-w-0 max-w-full truncate rounded bg-sidebar px-1.5 py-0.5 font-mono text-xs text-text-muted" title={fullModel}>{fullModel}</code>
<div className="relative shrink-0 group/btn">
<button
onClick={() => onCopy(fullModel, `model-${modelId}`)}
className="p-0.5 hover:bg-sidebar rounded text-text-muted hover:text-primary"
className="rounded p-0.5 text-text-muted hover:bg-sidebar hover:text-primary"
>
<span className="material-symbols-outlined text-sm">
{copied === `model-${modelId}` ? "check" : "content_copy"}
Expand All @@ -42,11 +42,11 @@ function CompatibleModelRow({ modelId, fullModel, copied, onCopy, onDeleteAlias,
</span>
</div>
{onTest && (
<div className="relative group/btn">
<div className="relative shrink-0 group/btn">
<button
onClick={onTest}
disabled={isTesting}
className="p-0.5 hover:bg-sidebar rounded text-text-muted hover:text-primary transition-colors"
className="rounded p-0.5 text-text-muted transition-colors hover:bg-sidebar hover:text-primary"
>
<span className="material-symbols-outlined text-sm" style={isTesting ? { animation: "spin 1s linear infinite" } : undefined}>
{isTesting ? "progress_activity" : "science"}
Expand All @@ -61,7 +61,7 @@ function CompatibleModelRow({ modelId, fullModel, copied, onCopy, onDeleteAlias,
</div>
<button
onClick={onDeleteAlias}
className="p-1 hover:bg-red-50 rounded text-red-500"
className="shrink-0 rounded p-1 text-red-500 hover:bg-red-50"
title="Remove model"
>
<span className="material-symbols-outlined text-sm">delete</span>
Expand Down
109 changes: 52 additions & 57 deletions src/app/(dashboard)/dashboard/providers/[id]/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -464,10 +464,10 @@ export default function ProviderDetailPage() {
const isSelected = (connectionId) => selectedConnectionIds.includes(connectionId);

const connectionsList = (
<div className="flex flex-col divide-y divide-black/[0.03] dark:divide-white/[0.03]">
<div className="flex min-w-0 flex-col divide-y divide-black/[0.03] dark:divide-white/[0.03]">
{connections
.map((conn, index) => (
<div key={conn.id} className="flex items-stretch">
<div key={conn.id} className="flex min-w-0 items-stretch">
<div className="flex-1 min-w-0">
<ConnectionRow
connection={conn}
Expand Down Expand Up @@ -537,7 +537,7 @@ export default function ProviderDetailPage() {
<p className="text-xs text-text-muted">{bulkHint}</p>
<p className="text-xs text-text-muted">Selecting None will unbind selected connections from proxy pool.</p>

<div className="flex gap-2">
<div className="flex flex-col gap-2 sm:flex-row">
<Button onClick={handleBulkApplyProxyPool} fullWidth disabled={!canApplyBulkProxy}>
{bulkUpdatingProxy ? "Applying..." : "Apply"}
</Button>
Expand Down Expand Up @@ -656,7 +656,7 @@ export default function ProviderDetailPage() {
{/* Add model button — inline, same style as model chips */}
<button
onClick={() => setShowAddCustomModel(true)}
className="flex items-center gap-1.5 px-3 py-2 rounded-lg border border-dashed border-black/15 dark:border-white/15 text-xs text-text-muted hover:text-primary hover:border-primary/40 transition-colors"
className="flex w-full items-center justify-center gap-1.5 rounded-lg border border-dashed border-black/15 px-3 py-2 text-xs text-text-muted transition-colors hover:border-primary/40 hover:text-primary sm:w-auto"
>
<span className="material-symbols-outlined text-sm">add</span>
Add Model
Expand Down Expand Up @@ -728,19 +728,19 @@ export default function ProviderDetailPage() {
};

return (
<div className="flex flex-col gap-8">
<div className="flex min-w-0 flex-col gap-6 px-1 sm:gap-8 sm:px-0">
{/* Header */}
<div>
<div className="min-w-0">
<Link
href="/dashboard/providers"
className="inline-flex items-center gap-1 text-sm text-text-muted hover:text-primary transition-colors mb-4"
>
<span className="material-symbols-outlined text-lg">arrow_back</span>
Back to Providers
</Link>
<div className="flex items-center gap-4">
<div className="flex min-w-0 items-center gap-3 sm:gap-4">
<div
className="rounded-lg flex items-center justify-center"
className="flex size-12 shrink-0 items-center justify-center rounded-lg"
style={{ backgroundColor: `${providerInfo.color}15` }}
>
{headerImgError ? (
Expand All @@ -753,14 +753,14 @@ export default function ProviderDetailPage() {
alt={providerInfo.name}
width={48}
height={48}
className="object-contain rounded-lg max-w-[48px] max-h-[48px]"
className="max-h-12 max-w-12 rounded-lg object-contain"
sizes="48px"
onError={() => setHeaderImgError(true)}
/>
)}
</div>
<div>
<h1 className="text-3xl font-semibold tracking-tight">{providerInfo.name}</h1>
<div className="min-w-0">
<h1 className="truncate text-2xl font-semibold tracking-tight sm:text-3xl">{providerInfo.name}</h1>
<p className="text-text-muted">
{connections.length} connection{connections.length === 1 ? "" : "s"}
</p>
Expand All @@ -776,15 +776,15 @@ export default function ProviderDetailPage() {
)}

{providerInfo.notice && !providerInfo.deprecated && (
<div className="flex items-center gap-2 px-3 py-2 rounded-lg bg-blue-500/10 border border-blue-500/30">
<div className="flex flex-col gap-2 rounded-lg border border-blue-500/30 bg-blue-500/10 px-3 py-2 sm:flex-row sm:items-center">
<span className="material-symbols-outlined text-[16px] text-blue-500 shrink-0">info</span>
<p className="text-xs text-blue-600 dark:text-blue-400 leading-relaxed">{providerInfo.notice.text}</p>
<p className="min-w-0 flex-1 text-xs leading-relaxed text-blue-600 dark:text-blue-400">{providerInfo.notice.text}</p>
{providerInfo.notice.apiKeyUrl && (
<a
href={providerInfo.notice.apiKeyUrl}
target="_blank"
rel="noopener noreferrer"
className="text-xs font-medium text-white bg-blue-500 hover:bg-blue-600 px-2 py-0.5 rounded shrink-0 transition-colors"
className="inline-flex justify-center rounded bg-blue-500 px-2 py-1 text-xs font-medium text-white transition-colors hover:bg-blue-600 sm:py-0.5"
>
Get API Key →
</a>
Expand All @@ -794,20 +794,20 @@ export default function ProviderDetailPage() {

{isCompatible && providerNode && (
<Card>
<div className="flex items-center justify-between mb-4">
<div>
<div className="mb-4 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
<div className="min-w-0">
<h2 className="text-lg font-semibold">{isAnthropicCompatible ? "Anthropic Compatible Details" : "OpenAI Compatible Details"}</h2>
<p className="text-sm text-text-muted">
<p className="break-all text-sm text-text-muted">
{isAnthropicCompatible ? "Messages API" : (providerNode.apiType === "responses" ? "Responses API" : "Chat Completions")} · {(providerNode.baseUrl || "").replace(/\/$/, "")}/
{isAnthropicCompatible ? "messages" : (providerNode.apiType === "responses" ? "responses" : "chat/completions")}
</p>
</div>
<div className="flex items-center gap-2">
<div className="grid grid-cols-1 gap-2 sm:flex sm:items-center">
<Button
size="sm"
icon="add"
onClick={() => setShowAddApiKeyModal(true)}
disabled={connections.length > 0}
className="w-full sm:w-auto"
>
Add
</Button>
Expand All @@ -816,6 +816,7 @@ export default function ProviderDetailPage() {
variant="secondary"
icon="edit"
onClick={() => setShowEditNodeModal(true)}
className="w-full sm:w-auto"
>
Edit
</Button>
Expand All @@ -834,16 +835,12 @@ export default function ProviderDetailPage() {
console.log("Error deleting provider node:", error);
}
}}
className="w-full sm:w-auto"
>
Delete
</Button>
</div>
</div>
{connections.length > 0 && (
<p className="text-sm text-text-muted">
Only one connection is allowed per compatible node. Add another node if you need more connections.
</p>
)}
</Card>
)}

Expand All @@ -852,9 +849,9 @@ export default function ProviderDetailPage() {
<NoAuthProxyCard providerId={providerId} />
) : (
<Card>
<div className="flex items-center justify-between mb-4">
<div className="mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<h2 className="text-lg font-semibold">Connections</h2>
<div className="flex items-center gap-4">
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:gap-4">
{/* Thinking config */}
{/* {thinkingConfig && (
<div className="flex items-center gap-2">
Expand All @@ -871,7 +868,7 @@ export default function ProviderDetailPage() {
</div>
)} */}
{/* Round Robin toggle */}
<div className="flex items-center gap-2">
<div className="flex flex-wrap items-center gap-2">
<span className="text-xs text-text-muted font-medium">Round Robin</span>
<Toggle
checked={providerStrategy === "round-robin"}
Expand Down Expand Up @@ -901,52 +898,50 @@ export default function ProviderDetailPage() {
</div>
<p className="text-text-main font-medium mb-1">No connections yet</p>
<p className="text-sm text-text-muted mb-4">Add your first connection to get started</p>
{!isCompatible && (
<div className="flex gap-2 justify-center">
{providerId === "iflow" && (
<Button icon="cookie" variant="secondary" onClick={() => setShowIFlowCookieModal(true)}>
Cookie Auth
</Button>
)}
<Button icon="add" onClick={() => isOAuth ? setShowOAuthModal(true) : setShowAddApiKeyModal(true)}>
{providerId === "iflow" ? "OAuth" : "Add Connection"}
<div className="flex flex-col gap-2 justify-center sm:flex-row">
{providerId === "iflow" && (
<Button icon="cookie" variant="secondary" onClick={() => setShowIFlowCookieModal(true)}>
Cookie Auth
</Button>
</div>
)}
)}
<Button icon="add" onClick={() => isOAuth ? setShowOAuthModal(true) : setShowAddApiKeyModal(true)}>
{providerId === "iflow" ? "OAuth" : "Add Connection"}
</Button>
</div>
</div>
) : (
<>
{connectionsList}
{!isCompatible && (
<div className="flex gap-2 mt-4">
{providerId === "iflow" && (
<Button
size="sm"
icon="cookie"
variant="secondary"
onClick={() => setShowIFlowCookieModal(true)}
title="Add connection using browser cookie"
>
Cookie
</Button>
)}
<div className="mt-4 grid grid-cols-1 gap-2 sm:flex">
{providerId === "iflow" && (
<Button
size="sm"
icon="add"
onClick={() => isOAuth ? setShowOAuthModal(true) : setShowAddApiKeyModal(true)}
icon="cookie"
variant="secondary"
onClick={() => setShowIFlowCookieModal(true)}
title="Add connection using browser cookie"
className="w-full sm:w-auto"
>
Add
Cookie
</Button>
</div>
)}
)}
<Button
size="sm"
icon="add"
onClick={() => isOAuth ? setShowOAuthModal(true) : setShowAddApiKeyModal(true)}
className="w-full sm:w-auto"
>
Add
</Button>
</div>
</>
)}
</Card>
)}

{/* Models */}
<Card>
<div className="flex items-center justify-between mb-4">
<div className="mb-4 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<h2 className="text-lg font-semibold">
{"Available Models"}
</h2>
Expand Down
10 changes: 0 additions & 10 deletions src/app/api/providers/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,6 @@ export async function POST(request) {
return NextResponse.json({ error: "OpenAI Compatible node not found" }, { status: 404 });
}

const existingConnections = await getProviderConnections({ provider });
if (existingConnections.length > 0) {
return NextResponse.json({ error: "Only one connection is allowed for this OpenAI Compatible node" }, { status: 400 });
}

providerSpecificData = {
prefix: node.prefix,
apiType: node.apiType,
Expand All @@ -142,11 +137,6 @@ export async function POST(request) {
return NextResponse.json({ error: "Anthropic Compatible node not found" }, { status: 404 });
}

const existingConnections = await getProviderConnections({ provider });
if (existingConnections.length > 0) {
return NextResponse.json({ error: "Only one connection is allowed for this Anthropic Compatible node" }, { status: 400 });
}

providerSpecificData = {
prefix: node.prefix,
baseUrl: node.baseUrl,
Expand Down
1 change: 1 addition & 0 deletions src/app/api/providers/validate/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ export async function POST(request) {
break;
}


case "opencode-go": {
const res = await fetch("https://opencode.ai/zen/go/v1/chat/completions", {
method: "POST",
Expand Down
2 changes: 1 addition & 1 deletion src/shared/components/EditConnectionModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ export default function EditConnectionModal({ isOpen, connection, proxyPools, on

{isCloudflareAi && (
<div className="bg-sidebar/50 p-4 rounded-lg border border-accent/20">
<h3 className="font-semibold mb-3 text-sm">Cloudflare Workers AI</h3>
<h3 className="font-semibold mb-3 text-sm">Cloudflare AI</h3>
<Input
label="Account ID"
value={cloudflareData.accountId}
Expand Down
1 change: 1 addition & 0 deletions src/shared/constants/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export const PROVIDER_ENDPOINTS = {
"alicode-intl": "https://coding-intl.dashscope.aliyuncs.com/v1/chat/completions",
"volcengine-ark": "https://ark.cn-beijing.volces.com/api/coding/v3/chat/completions",
byteplus: "https://ark.ap-southeast.bytepluses.com/api/coding/v3/chat/completions",
cloudflare: "https://api.cloudflare.com/client/v4/accounts/{accountId}/ai/v1/chat/completions",
openai: "https://api.openai.com/v1/chat/completions",
anthropic: "https://api.anthropic.com/v1/messages",
gemini: "https://generativelanguage.googleapis.com/v1beta/models",
Expand Down
Loading