From f09d8f1d25195597ac19338327b3b36ef33519ae Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 20 Mar 2026 21:51:12 +0800 Subject: [PATCH] feat: add runway provider option in admin settings --- app/(admin)/settings/admin-settings-client.tsx | 7 +++++-- app/api/admin/ai-providers/route.ts | 4 ++++ lib/ai/provider-defaults.ts | 1 + lib/db/migrate.ts | 3 ++- lib/db/queries.ts | 4 ++-- lib/db/schema.ts | 2 +- 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index ee0b450b8..ca122b776 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -154,7 +154,7 @@ type ToolConfig = { type AiProvider = { id: string; name: string; - type: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "doubao-video" | "ruxa-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image" | "fal-image" | "openai-image" | "midjourney" | "fal-video" | "zhipu" | "tripo-3d" | "volcano-multimodal"; + type: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "doubao-video" | "ruxa-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image" | "fal-image" | "openai-image" | "midjourney" | "fal-video" | "zhipu" | "tripo-3d" | "runway" | "volcano-multimodal"; baseUrl: string; apiKey: string; isEnabled: boolean; @@ -465,6 +465,7 @@ const PROVIDER_TYPES = [ { value: "openai-image", label: "OpenAI 图片兼容" }, { value: "midjourney", label: "Midjourney" }, { value: "tripo-3d", label: "Tripo3D 模型生成" }, + { value: "runway", label: "Runway API" }, { value: "zhipu", label: "智普官方 4.7" }, { value: "volcano-multimodal", label: "火山引擎多模态" }, { value: "openai", label: "OpenAI 兼容" }, @@ -490,6 +491,7 @@ const PROVIDER_DEFAULT_BASE_URL: Record = { midjourney: "https://your-midjourney-api.example.com", "fal-video": "https://fal.run", "tripo-3d": "https://api.tripo3d.ai/v2/openapi", + runway: "https://api.dev.runwayml.com", zhipu: "https://open.bigmodel.cn/api/paas/v4", "volcano-multimodal": "https://ark.cn-beijing.volces.com/api/v3", openai: "https://api.openai.com/v1", @@ -1469,7 +1471,7 @@ export function AdminSettingsClient() { const [providerForm, setProviderForm] = useState({ id: "", name: "", - type: "openai" as "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "doubao-video" | "ruxa-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image" | "fal-image" | "openai-image" | "midjourney" | "fal-video" | "zhipu" | "tripo-3d" | "volcano-multimodal", + type: "openai" as "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "doubao-video" | "ruxa-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image" | "fal-image" | "openai-image" | "midjourney" | "fal-video" | "zhipu" | "tripo-3d" | "runway" | "volcano-multimodal", baseUrl: "", apiKey: "", isEnabled: true, @@ -4003,6 +4005,7 @@ export function AdminSettingsClient() { "openai": "bg-green-500", "zhipu": "bg-lime-500", "volcano-multimodal": "bg-fuchsia-500", + "runway": "bg-rose-500", "qianwen-video": "bg-cyan-500", "custom": "bg-gray-500", "local": "bg-yellow-500", diff --git a/app/api/admin/ai-providers/route.ts b/app/api/admin/ai-providers/route.ts index 9bd7efc23..108c7aff2 100644 --- a/app/api/admin/ai-providers/route.ts +++ b/app/api/admin/ai-providers/route.ts @@ -29,6 +29,7 @@ const providerTypes = [ "fal-video", "zhipu", "tripo-3d", + "runway", "volcano-multimodal", ] as const; type ProviderType = (typeof providerTypes)[number]; @@ -59,6 +60,7 @@ const RUXA_IMAGE_BASE_URL = "https://api.ruxa.ai"; const OPENAI_IMAGE_BASE_URL = "https://api.openai.com/v1"; const MIDJOURNEY_BASE_URL = "https://your-midjourney-api.example.com"; const TRIPO_3D_BASE_URL = "https://api.tripo3d.ai/v2/openapi"; +const RUNWAY_BASE_URL = "https://api.dev.runwayml.com"; function normalizeProviderTypeForStorage( type?: ProviderType @@ -175,6 +177,8 @@ export async function POST(request: Request) { ? data.baseUrl || MIDJOURNEY_BASE_URL : data.type === "tripo-3d" ? data.baseUrl || TRIPO_3D_BASE_URL + : data.type === "runway" + ? data.baseUrl || RUNWAY_BASE_URL : data.baseUrl, updatedBy: session.user.id, }); diff --git a/lib/ai/provider-defaults.ts b/lib/ai/provider-defaults.ts index 1adc4b206..84f788296 100644 --- a/lib/ai/provider-defaults.ts +++ b/lib/ai/provider-defaults.ts @@ -17,6 +17,7 @@ const PROVIDER_DEFAULT_BASE_URL: Partial> = { midjourney: "https://your-midjourney-api.example.com", "fal-video": "https://fal.run", "tripo-3d": "https://api.tripo3d.ai/v2/openapi", + runway: "https://api.dev.runwayml.com", }; export function getProviderDefaultBaseUrl(type: AiProvider["type"]): string | undefined { diff --git a/lib/db/migrate.ts b/lib/db/migrate.ts index fc63c22f8..b486089ce 100644 --- a/lib/db/migrate.ts +++ b/lib/db/migrate.ts @@ -32,6 +32,7 @@ const providerBaseUrlDefaults = [ { type: "midjourney", baseUrl: "https://your-midjourney-api.example.com" }, { type: "fal-video", baseUrl: "https://fal.run" }, { type: "tripo-3d", baseUrl: "https://api.tripo3d.ai/v2/openapi" }, + { type: "runway", baseUrl: "https://api.dev.runwayml.com" }, ] as const; function assertMigrationJournalIsConsistent() { @@ -91,7 +92,7 @@ const runMigrate = async () => { ADD CONSTRAINT "AiProvider_type_check" CHECK (type IN ( 'alibaba', 'openai', 'custom', 'local', 'gemini', - 'huanke-gemini', 'qianwen-video', 'doubao-video', 'ruxa-video', 'volcano-vision', 'volcano-video', 'alibaba-image', 'ruxa-image', 'fal-image', 'openai-image', 'midjourney', 'fal-video', 'zhipu', 'tripo-3d' + 'huanke-gemini', 'qianwen-video', 'doubao-video', 'ruxa-video', 'volcano-vision', 'volcano-video', 'alibaba-image', 'ruxa-image', 'fal-image', 'openai-image', 'midjourney', 'fal-video', 'zhipu', 'tripo-3d', 'runway' )); `); await db.execute(sql` diff --git a/lib/db/queries.ts b/lib/db/queries.ts index 719a2e32a..d0f1620af 100644 --- a/lib/db/queries.ts +++ b/lib/db/queries.ts @@ -3388,7 +3388,7 @@ export async function createAiProvider({ updatedBy, }: { name: string; - type: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "doubao-video" | "ruxa-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image" | "fal-image" | "openai-image" | "midjourney" | "fal-video" | "zhipu" | "tripo-3d"; + type: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "doubao-video" | "ruxa-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image" | "fal-image" | "openai-image" | "midjourney" | "fal-video" | "zhipu" | "tripo-3d" | "runway"; baseUrl: string; apiKey: string; isEnabled?: boolean; @@ -3426,7 +3426,7 @@ export async function updateAiProvider({ }: { id: string; name?: string; - type?: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "doubao-video" | "ruxa-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image" | "fal-image" | "openai-image" | "midjourney" | "fal-video" | "zhipu" | "tripo-3d"; + type?: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "doubao-video" | "ruxa-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image" | "fal-image" | "openai-image" | "midjourney" | "fal-video" | "zhipu" | "tripo-3d" | "runway"; baseUrl?: string; apiKey?: string; isEnabled?: boolean; diff --git a/lib/db/schema.ts b/lib/db/schema.ts index 21901a242..9c2a4c573 100644 --- a/lib/db/schema.ts +++ b/lib/db/schema.ts @@ -259,7 +259,7 @@ export type ToolConfig = InferSelectModel; export const aiProvider = pgTable("AiProvider", { id: uuid("id").primaryKey().notNull().defaultRandom(), name: varchar("name", { length: 128 }).notNull(), // 提供商名称,如 "阿里云通义千问" - type: varchar("type", { enum: ["alibaba", "openai", "custom", "local", "gemini", "huanke-gemini", "qianwen-video", "doubao-video", "ruxa-video", "volcano-vision", "volcano-video", "alibaba-image", "ruxa-image", "fal-image", "openai-image", "midjourney", "fal-video", "zhipu", "tripo-3d"] }) + type: varchar("type", { enum: ["alibaba", "openai", "custom", "local", "gemini", "huanke-gemini", "qianwen-video", "doubao-video", "ruxa-video", "volcano-vision", "volcano-video", "alibaba-image", "ruxa-image", "fal-image", "openai-image", "midjourney", "fal-video", "zhipu", "tripo-3d", "runway"] }) .notNull() .default("custom"), // 提供商类型 baseUrl: text("baseUrl").notNull(), // API 基础 URL