diff --git a/README.md b/README.md index ff550fd8b..05b785368 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ - **Chat with your library** - Ask questions about the current book or selected passages, with answers grounded in your reading context - **Find ideas semantically** - Hybrid vector retrieval + BM25 search helps you find concepts even when the exact keyword is missing - **Keep knowledge private** - Local embeddings and a local vector store keep your books, highlights, and notes offline-capable -- **Use your preferred model** - Connect OpenAI, Claude, Gemini, Ollama, DeepSeek, or custom-compatible providers +- **Use your preferred model** - Connect OpenAI, Claude, Gemini, Ollama, DeepSeek, OrcaRouter, or custom-compatible providers ## Why ReadAny? @@ -122,7 +122,7 @@ - **Intelligent Chat** - Ask questions about your books, AI knows your position, selected text, and highlights - **Semantic Search** - Beyond keywords, vector retrieval + BM25 hybrid search - **Instant Translation** - AI translation or DeepL, 19 languages supported -- **Multiple AI Providers** - OpenAI, Claude, Gemini, Ollama, DeepSeek +- **Multiple AI Providers** - OpenAI, Claude, Gemini, Ollama, DeepSeek, OrcaRouter - **Skills System** - Built-in skills (summarizer, concept explainer, character tracker, etc.) + create custom skills ### 📝 Annotation & Knowledge Management @@ -238,6 +238,7 @@ Mobile app source lives in [`packages/app-expo`](packages/app-expo). | Anthropic Claude | [console.anthropic.com](https://console.anthropic.com/) | | Google Gemini | [aistudio.google.com](https://aistudio.google.com/) | | Ollama / DeepSeek | Local or custom endpoint | +| OrcaRouter | [orcarouter.ai](https://www.orcarouter.ai/) | --- diff --git a/README_CN.md b/README_CN.md index 92179df04..723d1132a 100644 --- a/README_CN.md +++ b/README_CN.md @@ -41,7 +41,7 @@ - **和书库对话** - 围绕当前书籍、选中文本和阅读位置提问,答案基于你的阅读上下文生成 - **按语义找内容** - 向量检索 + BM25 混合搜索,不记得原文关键词也能找回相关概念 - **知识留在本地** - 本地 embeddings 与本地向量库,让书籍、高亮和笔记具备离线可用能力 -- **接入常用模型** - 支持 OpenAI、Claude、Gemini、Ollama、DeepSeek 以及兼容接口 +- **接入常用模型** - 支持 OpenAI、Claude、Gemini、Ollama、DeepSeek、OrcaRouter 以及兼容接口 ## 为什么选择 ReadAny? @@ -237,6 +237,7 @@ Expo Go。Expo Go 无法加载 ReadAny 当前依赖的原生模块和应用配 | Anthropic Claude | [console.anthropic.com](https://console.anthropic.com/) | | Google Gemini | [aistudio.google.com](https://aistudio.google.com/) | | Ollama / DeepSeek | 本地或自定义端点 | +| OrcaRouter | [orcarouter.ai](https://www.orcarouter.ai/) | --- diff --git a/packages/app-expo/src/components/onboarding/steps/AIPage.tsx b/packages/app-expo/src/components/onboarding/steps/AIPage.tsx index d16844d7b..c86c540a7 100644 --- a/packages/app-expo/src/components/onboarding/steps/AIPage.tsx +++ b/packages/app-expo/src/components/onboarding/steps/AIPage.tsx @@ -37,6 +37,7 @@ const PROVIDER_OPTIONS: { id: AIProviderType; name: string }[] = [ { id: "lmstudio", name: "LM Studio" }, { id: "openrouter", name: "OpenRouter" }, { id: "siliconflow", name: "SiliconFlow" }, + { id: "orcarouter", name: "OrcaRouter" }, { id: "custom", name: "Custom" }, ]; diff --git a/packages/app-expo/src/screens/settings/ai/EndpointEditor.tsx b/packages/app-expo/src/screens/settings/ai/EndpointEditor.tsx index 07b5e6472..498a15c0f 100644 --- a/packages/app-expo/src/screens/settings/ai/EndpointEditor.tsx +++ b/packages/app-expo/src/screens/settings/ai/EndpointEditor.tsx @@ -33,6 +33,7 @@ const PROVIDERS: { id: AIProviderType; label: string }[] = [ { id: "lmstudio", label: "LM Studio" }, { id: "openrouter", label: "OpenRouter" }, { id: "siliconflow", label: "SiliconFlow" }, + { id: "orcarouter", label: "OrcaRouter" }, { id: "moonshot", label: "Moonshot" }, { id: "zhipu", label: "智谱 GLM" }, { id: "aliyun", label: "阿里云通义" }, diff --git a/packages/app/src/components/onboarding/steps/AIPage.tsx b/packages/app/src/components/onboarding/steps/AIPage.tsx index 6e31f59d8..cbe781f6d 100644 --- a/packages/app/src/components/onboarding/steps/AIPage.tsx +++ b/packages/app/src/components/onboarding/steps/AIPage.tsx @@ -29,6 +29,7 @@ const PROVIDER_OPTIONS: { id: AIProviderType; name: string }[] = [ { id: "lmstudio", name: "LM Studio" }, { id: "openrouter", name: "OpenRouter" }, { id: "siliconflow", name: "SiliconFlow" }, + { id: "orcarouter", name: "OrcaRouter" }, { id: "custom", name: "Custom" }, ]; diff --git a/packages/app/src/components/settings/AISettings.tsx b/packages/app/src/components/settings/AISettings.tsx index f406c09af..9cead542c 100644 --- a/packages/app/src/components/settings/AISettings.tsx +++ b/packages/app/src/components/settings/AISettings.tsx @@ -42,6 +42,7 @@ function useProviderOptions(): { value: AIProviderType; label: string }[] { { value: "lmstudio", label: "LM Studio" }, { value: "openrouter", label: "OpenRouter" }, { value: "siliconflow", label: "SiliconFlow" }, + { value: "orcarouter", label: "OrcaRouter" }, { value: "moonshot", label: "Moonshot (Kimi)" }, { value: "zhipu", label: t("settings.ai_provider_zhipu") }, { value: "aliyun", label: t("settings.ai_provider_aliyun") }, diff --git a/packages/core/src/types/chat.ts b/packages/core/src/types/chat.ts index 22c1668ee..c83cb80b4 100644 --- a/packages/core/src/types/chat.ts +++ b/packages/core/src/types/chat.ts @@ -145,6 +145,7 @@ export type AIProviderType = | "mistral" | "perplexity" | "aihubmix" + | "orcarouter" | "custom"; export interface AIEndpoint { diff --git a/packages/core/src/utils/api.test.ts b/packages/core/src/utils/api.test.ts index a97fb4b45..a7d8b8b81 100644 --- a/packages/core/src/utils/api.test.ts +++ b/packages/core/src/utils/api.test.ts @@ -90,6 +90,15 @@ describe("AI API URL helpers", () => { expect(detectProviderFromUrl("https://api.atlascloud.ai/v1")).toBe("atlascloud"); }); + it("supports OrcaRouter as a named OpenAI-compatible provider", () => { + expect(getDefaultBaseUrl("orcarouter")).toBe("https://api.orcarouter.ai/v1"); + expect(resolveProviderBaseUrl("orcarouter")).toBe("https://api.orcarouter.ai/v1"); + expect(detectProviderFromUrl("https://api.orcarouter.ai/v1")).toBe("orcarouter"); + expect(buildProviderModelsUrl("orcarouter")).toBe( + "https://api.orcarouter.ai/v1/models", + ); + }); + describe("ensureUrlProtocol / scheme-less inputs", () => { it("prepends https:// when a remote URL has no scheme", () => { expect(ensureUrlProtocol("api.openai.com/v1")).toBe("https://api.openai.com/v1"); diff --git a/packages/core/src/utils/api.ts b/packages/core/src/utils/api.ts index 26d4d1142..021066bf7 100644 --- a/packages/core/src/utils/api.ts +++ b/packages/core/src/utils/api.ts @@ -97,6 +97,14 @@ export const PROVIDER_CONFIGS: Record = { placeholder: "https://api.siliconflow.cn", keyPlaceholder: "sk-...", }, + orcarouter: { + id: "orcarouter", + name: "OrcaRouter", + defaultBaseUrl: "https://api.orcarouter.ai/v1", + needsV1Suffix: false, + placeholder: "https://api.orcarouter.ai/v1", + keyPlaceholder: "sk-orca-...", + }, moonshot: { id: "moonshot", name: "Moonshot (Kimi)", @@ -566,6 +574,7 @@ export function detectProviderFromUrl(url: string): string { if (urlLower.includes("mistral.ai")) return "mistral"; if (urlLower.includes("perplexity.ai")) return "perplexity"; if (urlLower.includes("aihubmix.com")) return "aihubmix"; + if (urlLower.includes("api.orcarouter.ai")) return "orcarouter"; return "custom"; }