diff --git a/src/app/(dashboard)/dashboard/media-providers/[kind]/[id]/page.js b/src/app/(dashboard)/dashboard/media-providers/[kind]/[id]/page.js new file mode 100644 index 0000000..110a766 --- /dev/null +++ b/src/app/(dashboard)/dashboard/media-providers/[kind]/[id]/page.js @@ -0,0 +1,93 @@ +"use client"; + +import { useParams, notFound } from "next/navigation"; +import Link from "next/link"; +import Image from "next/image"; +import { useState } from "react"; +import { Card, Badge } from "@/shared/components"; +import { MEDIA_PROVIDER_KINDS, AI_PROVIDERS } from "@/shared/constants/providers"; +import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard"; +import ConnectionsCard from "@/app/(dashboard)/dashboard/providers/components/ConnectionsCard"; +import ModelsCard from "@/app/(dashboard)/dashboard/providers/components/ModelsCard"; + +export default function MediaProviderDetailPage() { + const { kind, id } = useParams(); + const { copied, copy } = useCopyToClipboard(); + const [headerImgError, setHeaderImgError] = useState(false); + + const kindConfig = MEDIA_PROVIDER_KINDS.find((k) => k.id === kind); + if (!kindConfig) return notFound(); + + const provider = AI_PROVIDERS[id]; + if (!provider) return notFound(); + + const kinds = provider.serviceKinds ?? ["llm"]; + if (!kinds.includes(kind)) return notFound(); + + const endpointText = `${kindConfig.endpoint.method} ${kindConfig.endpoint.path}`; + + return ( +
{kindConfig.label} Endpoint
+{kindConfig.endpoint.path}
+
+ Endpoint
+{kindConfig.endpoint.path}
+
+ {provider.name}
+{(provider.serviceKinds ?? ["llm"]).join(", ")}
+{displayName}
+{maskedProxyUrl}}
+ {noProxyText && no_proxy: {noProxyText}}
+ No connections yet
+ +{fullModel}
+ {onTest && (
+ {testError}
} + +diff --git a/src/shared/constants/providers.js b/src/shared/constants/providers.js index 6ae85ce..9f94c63 100644 --- a/src/shared/constants/providers.js +++ b/src/shared/constants/providers.js @@ -40,9 +40,9 @@ export const APIKEY_PROVIDERS = { "minimax-cn": { id: "minimax-cn", alias: "minimax-cn", name: "Minimax (China)", icon: "memory", color: "#DC2626", textIcon: "MC", website: "https://www.minimaxi.com" }, alicode: { id: "alicode", alias: "alicode", name: "Alibaba", icon: "cloud", color: "#FF6A00", textIcon: "ALi" }, "alicode-intl": { id: "alicode-intl", alias: "alicode-intl", name: "Alibaba Intl", icon: "cloud", color: "#FF6A00", textIcon: "ALi" }, - openai: { id: "openai", alias: "openai", name: "OpenAI", icon: "auto_awesome", color: "#10A37F", textIcon: "OA", website: "https://platform.openai.com" }, - anthropic: { id: "anthropic", alias: "anthropic", name: "Anthropic", icon: "smart_toy", color: "#D97757", textIcon: "AN", website: "https://console.anthropic.com" }, - gemini: { id: "gemini", alias: "gemini", name: "Gemini", icon: "diamond", color: "#4285F4", textIcon: "GE", website: "https://ai.google.dev" }, + openai: { id: "openai", alias: "openai", name: "OpenAI", icon: "auto_awesome", color: "#10A37F", textIcon: "OA", website: "https://platform.openai.com", serviceKinds: ["llm", "embedding", "tts"] }, + anthropic: { id: "anthropic", alias: "anthropic", name: "Anthropic", icon: "smart_toy", color: "#D97757", textIcon: "AN", website: "https://console.anthropic.com", serviceKinds: ["llm"] }, + gemini: { id: "gemini", alias: "gemini", name: "Gemini", icon: "diamond", color: "#4285F4", textIcon: "GE", website: "https://ai.google.dev", serviceKinds: ["llm", "embedding"] }, deepseek: { id: "deepseek", alias: "ds", name: "DeepSeek", icon: "bolt", color: "#4D6BFE", textIcon: "DS", website: "https://deepseek.com" }, groq: { id: "groq", alias: "groq", name: "Groq", icon: "speed", color: "#F55036", textIcon: "GQ", website: "https://groq.com" }, xai: { id: "xai", alias: "xai", name: "xAI (Grok)", icon: "auto_awesome", color: "#1DA1F2", textIcon: "XA", website: "https://x.ai" }, @@ -55,14 +55,30 @@ export const APIKEY_PROVIDERS = { nebius: { id: "nebius", alias: "nebius", name: "Nebius AI", icon: "cloud", color: "#6C5CE7", textIcon: "NB", website: "https://nebius.com" }, siliconflow: { id: "siliconflow", alias: "siliconflow", name: "SiliconFlow", icon: "cloud_queue", color: "#5B6EF5", textIcon: "SF", website: "https://cloud.siliconflow.com" }, hyperbolic: { id: "hyperbolic", alias: "hyp", name: "Hyperbolic", icon: "bolt", color: "#00D4FF", textIcon: "HY", website: "https://hyperbolic.xyz" }, - deepgram: { id: "deepgram", alias: "dg", name: "Deepgram", icon: "mic", color: "#13EF93", textIcon: "DG", website: "https://deepgram.com" }, - assemblyai: { id: "assemblyai", alias: "aai", name: "AssemblyAI", icon: "record_voice_over", color: "#0062FF", textIcon: "AA", website: "https://assemblyai.com" }, - nanobanana: { id: "nanobanana", alias: "nb", name: "NanoBanana", icon: "image", color: "#FFD700", textIcon: "NB", website: "https://nanobananaapi.ai" }, + deepgram: { id: "deepgram", alias: "dg", name: "Deepgram", icon: "mic", color: "#13EF93", textIcon: "DG", website: "https://deepgram.com", serviceKinds: ["stt"] }, + assemblyai: { id: "assemblyai", alias: "aai", name: "AssemblyAI", icon: "record_voice_over", color: "#0062FF", textIcon: "AA", website: "https://assemblyai.com", serviceKinds: ["stt"] }, + nanobanana: { id: "nanobanana", alias: "nb", name: "NanoBanana", icon: "image", color: "#FFD700", textIcon: "NB", website: "https://nanobananaapi.ai", serviceKinds: ["image"] }, + elevenlabs: { id: "elevenlabs", alias: "el", name: "ElevenLabs", icon: "record_voice_over", color: "#6C47FF", textIcon: "EL", website: "https://elevenlabs.io", serviceKinds: ["tts"] }, + cartesia: { id: "cartesia", alias: "cartesia", name: "Cartesia", icon: "spatial_audio", color: "#FF4F8B", textIcon: "CA", website: "https://cartesia.ai", serviceKinds: ["tts"] }, + playht: { id: "playht", alias: "playht", name: "PlayHT", icon: "play_circle", color: "#00B4D8", textIcon: "PH", website: "https://play.ht", serviceKinds: ["tts"] }, + sdwebui: { id: "sdwebui", alias: "sdwebui", name: "SD WebUI", icon: "brush", color: "#FF7043", textIcon: "SD", website: "https://github.com/AUTOMATIC1111/stable-diffusion-webui", serviceKinds: ["image"] }, + comfyui: { id: "comfyui", alias: "comfyui", name: "ComfyUI", icon: "account_tree", color: "#4CAF50", textIcon: "CF", website: "https://github.com/comfyanonymous/ComfyUI", serviceKinds: ["image"] }, + huggingface: { id: "huggingface", alias: "hf", name: "HuggingFace", icon: "face", color: "#FFD21E", textIcon: "HF", website: "https://huggingface.co", serviceKinds: ["embedding", "image", "tts"] }, chutes: { id: "chutes", alias: "ch", name: "Chutes AI", icon: "water_drop", color: "#ffffffff", textIcon: "CH", website: "https://chutes.ai" }, "ollama-local": { id: "ollama-local", alias: "ollama-local", name: "Ollama Local", icon: "cloud", color: "#ffffffff", textIcon: "OL", website: "https://ollama.com" }, "vertex-partner": { id: "vertex-partner", alias: "vxp", name: "Vertex Partner", icon: "cloud", color: "#34A853", textIcon: "VP", website: "https://cloud.google.com/vertex-ai/generative-ai/docs/partner-models/use-partner-models" }, }; +// Media provider kinds — each kind maps to a route and endpoint config +export const MEDIA_PROVIDER_KINDS = [ + { id: "embedding", label: "Embedding", icon: "data_array", endpoint: { method: "POST", path: "/v1/embeddings" } }, + { id: "image", label: "Image", icon: "image", endpoint: { method: "POST", path: "/v1/images/generations" } }, + { id: "tts", label: "TTS", icon: "record_voice_over", endpoint: { method: "POST", path: "/v1/audio/speech" } }, + { id: "stt", label: "STT", icon: "mic", endpoint: { method: "POST", path: "/v1/audio/transcriptions" } }, + { id: "video", label: "Video", icon: "movie", endpoint: { method: "POST", path: "/v1/video/generations" } }, + { id: "music", label: "Music", icon: "music_note", endpoint: { method: "POST", path: "/v1/audio/music" } }, +]; + export const OPENAI_COMPATIBLE_PREFIX = "openai-compatible-"; export const ANTHROPIC_COMPATIBLE_PREFIX = "anthropic-compatible-"; @@ -117,6 +133,15 @@ export const ID_TO_ALIAS = Object.values(AI_PROVIDERS).reduce((acc, p) => { return acc; }, {}); +// Helper: Get providers by service kind (e.g. "tts", "embedding", "image") +// Providers without serviceKinds default to ["llm"] +export function getProvidersByKind(kind) { + return Object.values(AI_PROVIDERS).filter((p) => { + const kinds = p.serviceKinds ?? ["llm"]; + return kinds.includes(kind); + }); +} + // Providers that support usage/quota API export const USAGE_SUPPORTED_PROVIDERS = [ "claude",