Skip to content
Closed
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
16 changes: 16 additions & 0 deletions console/frontend/_tests_/alias-loader.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Minimal ESM loader that maps the `@/` path alias to `<cwd>/src/`, so unit tests
// can import modules that reference the Vite/tsconfig `@/*` alias (which ts-node's
// default ESM resolver does not apply). Test-only; not part of the app runtime.
import { pathToFileURL } from 'node:url';
import { fileURLToPath } from 'node:url';

const cwd = process.cwd();

export async function resolve(specifier, context, nextResolve) {
if (specifier.startsWith('@/')) {
const rel = specifier.slice(2);
const url = pathToFileURL(`${cwd}/src/${rel}.ts`).href;
return nextResolve(url, context);
}
return nextResolve(specifier, context);
}
44 changes: 44 additions & 0 deletions console/frontend/_tests_/model-provider-orcarouter.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
mapProviderToVendor,
getSpecificProviderOptions,
} from '../src/pages/model-management/utils/provider-group.ts';
import { ModelProviderType } from '../src/types/model.ts';

// OrcaRouter is registered as a named provider in the Official Providers catalog.
test('ModelProviderType includes ORCAROUTER', () => {
assert.ok('ORCAROUTER' in ModelProviderType);
assert.equal(ModelProviderType.ORCAROUTER, 'orcarouter');
});

// OrcaRouter exposes an OpenAI-compatible chat completions endpoint, so it must
// map to the OpenAI vendor group (openai-compatible).
test('orcarouter maps to the OpenAI-compatible vendor', () => {
assert.equal(
mapProviderToVendor(ModelProviderType.ORCAROUTER),
ModelProviderType.OPENAI
);
});

// It must appear as a selectable provider option on the Official Providers page.
test('getSpecificProviderOptions includes the OrcaRouter option', () => {
const specificOptions = getSpecificProviderOptions();
const orcaOption = specificOptions.find(
option => option.value === ModelProviderType.ORCAROUTER
);
assert.ok(orcaOption, 'getSpecificProviderOptions should include OrcaRouter');
assert.equal(orcaOption.label, 'OrcaRouter');
});

// Other OpenAI-compatible providers must keep mapping to the OpenAI vendor.
test('existing OpenAI-compatible providers still map to the OpenAI vendor', () => {
assert.equal(
mapProviderToVendor(ModelProviderType.DEEPSEEK),
ModelProviderType.OPENAI
);
assert.equal(
mapProviderToVendor(ModelProviderType.MINIMAX),
ModelProviderType.OPENAI
);
});
2 changes: 1 addition & 1 deletion console/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"scripts": {
"dev": "vite --mode development --host",
"test": "vite --mode test --host localhost",
"test:unit": "node --test --loader ts-node/esm",
"test:unit": "node --test --loader ts-node/esm --loader ./_tests_/alias-loader.mjs",
"build": "vite build --mode production",
"build:dev": "vite build --mode development",
"build-dev": "vite build --mode development",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions console/frontend/src/locales/en-En/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ const translation = {
providerMoonshot: 'Moonshot',
providerChatGPT: 'ChatGPT',
providerDoubao: 'Doubao',
providerOrcaRouter: 'OrcaRouter',
providerHintOpenAI:
'Use an OpenAI-compatible endpoint such as /v1/chat/completions.',
providerHintMiniMax:
Expand Down Expand Up @@ -213,6 +214,8 @@ const translation = {
'Connect Doubao official models for enterprise assistant, conversation and tool orchestration scenarios.',
providerCardDeepSeekDesc:
'Connect DeepSeek-V3, DeepSeek-R1 and other models for general generation, reasoning and workflow Q&A.',
providerCardOrcaRouterDesc:
'Connect OrcaRouter, a gateway-level zero-trust LLM routing gateway, to route chat across leading models with a single API key.',
thinkingCapability: 'Thinking Capability',
enableThinkingCapability: 'Enable thinking content output',
multimodalCapability: 'Multimodal Capability',
Expand Down
3 changes: 3 additions & 0 deletions console/frontend/src/locales/zh-ZH/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ const translation = {
providerMoonshot: '月之暗面',
providerChatGPT: 'ChatGPT',
providerDoubao: '豆包',
providerOrcaRouter: 'OrcaRouter',
providerHintOpenAI: '使用 OpenAI 接口地址,例如 /v1/chat/completions。',
providerHintMiniMax:
'使用 MiniMax 官方接口地址,并填写 MiniMax-M3 等模型名称。',
Expand Down Expand Up @@ -207,6 +208,8 @@ const translation = {
'接入豆包官方模型,适合企业助手、对话生成与工具编排场景。',
providerCardDeepSeekDesc:
'接入 DeepSeek-V3、DeepSeek-R1 等模型,适合通用生成、推理与工作流问答。',
providerCardOrcaRouterDesc:
'接入 OrcaRouter 网关级零信任 LLM 路由网关,使用一个 API Key 路由到多个主流模型。',
thinkingCapability: '思维能力',
enableThinkingCapability: '启用思考内容输出',
multimodalCapability: '多模态能力',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const ProviderLogoGlyph: React.FC<{ provider: ModelProviderType }> = ({
[ModelProviderType.QWEN]: chatgptIcon, // 使用ChatGPT图标,因为也是OpenAI兼容
[ModelProviderType.MOONSHOT]: chatgptIcon, // 使用ChatGPT图标,因为也是OpenAI兼容
[ModelProviderType.DOUBAO]: chatgptIcon, // 使用ChatGPT图标,因为也是OpenAI兼容
[ModelProviderType.ORCAROUTER]: chatgptIcon, // 使用ChatGPT图标,因为也是OpenAI兼容
};

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,8 @@ const ModelBasicForm = ({
currentProvider === ModelProviderType.MOONSHOT ||
currentProvider === ModelProviderType.DOUBAO ||
currentProvider === ModelProviderType.DEEPSEEK ||
currentProvider === ModelProviderType.MINIMAX;
currentProvider === ModelProviderType.MINIMAX ||
currentProvider === ModelProviderType.ORCAROUTER;

const providerHint = isAnthropicProvider
? t('model.providerHintAnthropic')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import zhipuIcon from '@/assets/imgs/modelManage/providers/custom/zhipu.svg';
import qwenIcon from '@/assets/imgs/modelManage/providers/custom/qwen.svg';
import moonshotIcon from '@/assets/imgs/modelManage/providers/custom/moonshot.svg';
import doubaoIcon from '@/assets/imgs/modelManage/providers/custom/doubao.svg';
import orcaRouterIcon from '@/assets/imgs/modelManage/providers/custom/orcarouter.svg';

interface OfficialProviderCard {
provider: ModelProviderType;
Expand All @@ -45,6 +46,7 @@ const ProviderLogoGlyph: React.FC<{ provider: ModelProviderType }> = ({
[ModelProviderType.QWEN]: qwenIcon,
[ModelProviderType.MOONSHOT]: moonshotIcon,
[ModelProviderType.DOUBAO]: doubaoIcon,
[ModelProviderType.ORCAROUTER]: orcaRouterIcon,
};

return (
Expand Down Expand Up @@ -157,6 +159,14 @@ const OfficialModelContent: React.FC = () => {
accentClass: 'from-[#EEF2FF] via-[#F7F8FF] to-white',
endpoint: 'https://api.deepseek.com/v1',
},
{
provider: ModelProviderType.ORCAROUTER,
title: 'OrcaRouter',
subtitle: 'orcarouter/auto',
description: t('model.providerCardOrcaRouterDesc'),
accentClass: 'from-[#F0F9FF] via-[#F8FCFF] to-white',
endpoint: 'https://api.orcarouter.ai/v1',
},
],
[t]
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export function mapProviderToVendor(provider?: string | null): string {
ModelProviderType.QWEN,
ModelProviderType.MOONSHOT,
ModelProviderType.DOUBAO,
ModelProviderType.ORCAROUTER,
];

if (openaiCompatibleProviders.includes(provider as ModelProviderType)) {
Expand Down Expand Up @@ -93,6 +94,7 @@ export function getSpecificProviderOptions(): Array<{
{ label: 'Moonshot', value: ModelProviderType.MOONSHOT },
{ label: 'Doubao (ByteDance)', value: ModelProviderType.DOUBAO },
{ label: 'DeepSeek', value: ModelProviderType.DEEPSEEK },
{ label: 'OrcaRouter', value: ModelProviderType.ORCAROUTER },
];
}

Expand Down
3 changes: 3 additions & 0 deletions console/frontend/src/pages/model-management/utils/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export function getModelProviderLabel(provider?: string | null): string {
if (normalizedProvider === ModelProviderType.DEEPSEEK) {
return i18next.t('model.providerDeepSeek');
}
if (normalizedProvider === ModelProviderType.ORCAROUTER) {
return i18next.t('model.providerOrcaRouter');
}
if (normalizedProvider === ModelProviderType.ANTHROPIC) {
return i18next.t('model.providerAnthropic');
}
Expand Down
1 change: 1 addition & 0 deletions console/frontend/src/types/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export enum ModelProviderType {
MOONSHOT = 'moonshot',
CHATGPT = 'chatgpt',
DOUBAO = 'doubao',
ORCAROUTER = 'orcarouter',
}

export enum LocalModelStatus {
Expand Down