Add Cloudflare AI provider support and enhance connection management

- Introduced Cloudflare AI as a new provider with specific configurations in providerModels.js and providers.js.
- Updated DefaultExecutor to handle account ID resolution for Cloudflare AI connections.
- Enhanced AddApiKeyModal and EditConnectionModal to include account ID input for Cloudflare AI.
- Implemented validation for Cloudflare AI API key connections in testUtils.js and route.js.
- Updated UI components to reflect changes in provider management and connection handling.
This commit is contained in:
decolua committed 2026-04-28 11:07:39 +07:00
1 parent 111e78940a
commit 1bb621317d
18 files changed
+325 -71

No files matched your search

@@ -3,7 +3,7 @@
import { useParams, notFound, useRouter } from "next/navigation";
import Link from "next/link";
import { useState, useEffect } from "react";
import { Card, Badge, Button, AddCustomEmbeddingModal } from "@/shared/components";
import { Card, Badge, Button, AddCustomEmbeddingModal, NoAuthProxyCard, ProviderInfoCard } from "@/shared/components";
import ProviderIcon from "@/shared/components/ProviderIcon";
import { MEDIA_PROVIDER_KINDS, AI_PROVIDERS, getProviderAlias, isCustomEmbeddingProvider } from "@/shared/constants/providers";
import { getModelsByProviderId } from "@/shared/constants/models";
@@ -49,6 +49,12 @@ const KIND_EXAMPLE_CONFIG = {
defaultInput: "What is the latest news about AI?",
bodyKey: "query",
defaultResponse: `{\n "results": [\n { "title": "...", "url": "...", "snippet": "..." }\n ]\n}`,
extraFields: [
{ key: "search_type", label: "Type", type: "select", default: "web", options: ["web", "news"] },
{ key: "max_results", label: "Max results", type: "number", default: 5, min: 1, max: 100 },
{ key: "country", label: "Country", type: "text", default: "" },
{ key: "language", label: "Language", type: "text", default: "" },
],
},
webFetch: {
inputLabel: "URL",
@@ -56,6 +62,10 @@ const KIND_EXAMPLE_CONFIG = {
defaultInput: "https://example.com",
bodyKey: "url",
defaultResponse: `{\n "content": "...",\n "title": "...",\n "url": "..."\n}`,
extraFields: [
{ key: "format", label: "Format", type: "select", default: "markdown", options: ["markdown", "text", "html"] },
{ key: "max_characters", label: "Max chars", type: "number", default: 0, min: 0 },
],
},
image: {
inputLabel: "Prompt",
@@ -916,7 +926,8 @@ function GenericExampleCard({ providerId, kind }) {
const endpoint = useTunnel ? tunnelEndpoint : localEndpoint;
const apiPath = kindConfig.endpoint.path;
const modelFull = selectedModel ? `${providerAlias}/${selectedModel}` : "";
// For kinds without model concept (webSearch/webFetch), use providerAlias directly
const modelFull = kindModels.length === 0 ? providerAlias : (selectedModel ? `${providerAlias}/${selectedModel}` : "");
// Build request body with optional extra fields (only non-empty values)
const extraBodyFromFields = Object.entries(extraValues).reduce((acc, [k, v]) => {
@@ -1160,9 +1171,9 @@ function GenericExampleCard({ providerId, kind }) {
</Row>
)}
{/* Extra fields (filtered by model.params; if undefined → none shown) */}
{/* Extra fields — for kinds without model concept (webSearch/webFetch), show all; otherwise filter by model.params */}
{(exConfig.extraFields || [])
.filter((f) => Array.isArray(selectedModelObj?.params) && selectedModelObj.params.includes(f.key))
.filter((f) => kindModels.length === 0 || (Array.isArray(selectedModelObj?.params) && selectedModelObj.params.includes(f.key)))
.map((f) => (
<Row key={f.key} label={f.label}>
{f.type === "select" ? (
@@ -1175,6 +1186,14 @@ function GenericExampleCard({ providerId, kind }) {
<option key={opt} value={opt}>{opt === "" ? "(default)" : opt}</option>
))}
</select>
) : f.type === "text" ? (
<input
type="text"
value={extraValues[f.key] ?? ""}
placeholder={f.placeholder}
onChange={(e) => setExtraValues((s) => ({ ...s, [f.key]: e.target.value }))}
className="w-full px-3 py-1.5 text-sm border border-border rounded-lg bg-background focus:outline-none focus:border-primary"
/>
) : (
<input
type="number"
@@ -1413,23 +1432,13 @@ export default function MediaProviderDetailPage() {
{/* Connections */}
{!isCustom && provider.noAuth ? (
<Card>
<div className="flex items-center gap-3">
<div className="inline-flex items-center justify-center w-10 h-10 rounded-full bg-green-500/10 text-green-500">
<span className="material-symbols-outlined text-[20px]">lock_open</span>
</div>
<div>
<p className="text-sm font-medium">No authentication required</p>
<p className="text-xs text-text-muted">This provider is ready to use.</p>
</div>
</div>
</Card>
<NoAuthProxyCard providerId={id} />
) : (
<ConnectionsCard providerId={id} isOAuth={false} />
)}
{/* Models - only for non-tts kinds; custom uses prefix as alias */}
{kind !== "tts" && (
{/* Models - hidden for tts/webSearch/webFetch (provider IS the model); custom uses prefix as alias */}
{kind !== "tts" && kind !== "webSearch" && kind !== "webFetch" && (
<ModelsCard
providerId={id}
kindFilter={kind}
@@ -1437,6 +1446,14 @@ export default function MediaProviderDetailPage() {
/>
)}
{/* Provider Info — config-driven, only for providers with searchConfig/fetchConfig */}
{!isCustom && (provider.searchConfig || provider.fetchConfig) && (
<ProviderInfoCard
config={kind === "webFetch" ? provider.fetchConfig : provider.searchConfig}
title={`${kindConfig.label} Config`}
/>
)}
{/* Example — per kind */}
{kind === "embedding" && (
<EmbeddingExampleCard providerId={id} customAlias={customNode?.prefix} />