From f1fec1ab772ac0dcd5c6431a295356409f0e477c Mon Sep 17 00:00:00 2001 From: Mario Dian Date: Sat, 1 Feb 2025 15:35:42 +0800 Subject: [PATCH] Add Venice.ai --- .env.example | 5 +++ Dockerfile | 4 +- app/api/chat/messages/route.ts | 15 ++++++++ app/api/chat/messages/venice/route.ts | 36 ++++++++++++++++++ components/layout/model-select.tsx | 9 +++++ components/layout/settings-dialog.tsx | 4 ++ components/layout/settings-drawer.tsx | 4 ++ components/layout/settings/provider.tsx | 7 ++++ .../layout/settings/provider/venice.tsx | 35 +++++++++++++++++ config/provider/index.ts | 12 +++++- docker-compose.yml | 3 ++ public/img/Venice.png | Bin 0 -> 8436 bytes types/settings.ts | 7 ++++ 13 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 app/api/chat/messages/venice/route.ts create mode 100644 components/layout/settings/provider/venice.tsx create mode 100644 public/img/Venice.png diff --git a/.env.example b/.env.example index 2ba4fede..67a6b7c1 100644 --- a/.env.example +++ b/.env.example @@ -50,6 +50,11 @@ NEXT_PUBLIC_ACCESS_PERPLEXITY= PERPLEXITY_API_KEY= PERPLEXITY_ENDPOINT= +## Venice +NEXT_PUBLIC_ACCESS_VENICE= +VENICE_API_KEY= +VENICE_ENDPOINT= + # -------------- Search Engines -------------- ## Google diff --git a/Dockerfile b/Dockerfile index 925b32f7..987f7919 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,6 +41,8 @@ ENV AWS_ACCESS_KEY="" \ OPENAI_API_KEY="" \ OPENAI_API_ENDPOINT="" \ PERPLEXITY_API_KEY="" \ - PERPLEXITY_ENDPOINT="" + PERPLEXITY_ENDPOINT="" \ + VENICE_API_KEY="" \ + VENICE_ENDPOINT="" CMD ["pnpm", "start"] diff --git a/app/api/chat/messages/route.ts b/app/api/chat/messages/route.ts index 0090f6b4..68e4e00c 100644 --- a/app/api/chat/messages/route.ts +++ b/app/api/chat/messages/route.ts @@ -54,6 +54,11 @@ const perplexity = new OpenAI({ baseURL: process.env.PERPLEXITY_ENDPOINT ?? 'https://api.perplexity.ai/', }); +const venice = new OpenAI({ + apiKey: process.env.VENICE_API_KEY ?? '', + baseURL: process.env.VENICE_ENDPOINT ?? 'https://api.venice.ai/api/v1', +}); + export const runtime = 'edge'; export const dynamic = 'force-dynamic'; @@ -185,6 +190,16 @@ export async function POST(req: Request) { const output = OpenAIStream(response); return new StreamingTextResponse(output); } + case Provider.Venice: { + const response = await venice.chat.completions.create({ + model: config.model.model_id, + stream: true, + max_tokens: 4096, + messages, + }); + const output = OpenAIStream(response); + return new StreamingTextResponse(output); + } default: return new Response('Invalid Provider', { status: 400 }); } diff --git a/app/api/chat/messages/venice/route.ts b/app/api/chat/messages/venice/route.ts new file mode 100644 index 00000000..7f232c3d --- /dev/null +++ b/app/api/chat/messages/venice/route.ts @@ -0,0 +1,36 @@ +import { OpenAIStream, StreamingTextResponse } from 'ai'; +import OpenAI from 'openai'; + +import { ApiConfig } from '@/types/app'; + +export const runtime = 'edge'; + +export const dynamic = 'force-dynamic'; + +export async function POST(req: Request) { + const { + messages, + config, + stream, + }: { + messages: any[]; + config: ApiConfig; + stream: boolean; + } = await req.json(); + + const venice = new OpenAI({ + apiKey: config.provider?.apiKey ?? process.env.VENICE_API_KEY ?? '', + baseURL: config.provider?.endpoint ?? process.env.VENICE_ENDPOINT ?? 'https://api.venice.ai/api/v1', + }); + + const response = await venice.chat.completions.create({ + model: config.model.model_id, + stream: true, + max_tokens: 4096, + messages, + }); + + const output = OpenAIStream(response); + + return new StreamingTextResponse(output); +} diff --git a/components/layout/model-select.tsx b/components/layout/model-select.tsx index 07392297..71c1de08 100644 --- a/components/layout/model-select.tsx +++ b/components/layout/model-select.tsx @@ -30,6 +30,7 @@ import { model as HuggingFaceModel } from '@/config/provider/huggingface'; import { model as MistralModel } from '@/config/provider/mistral'; import { model as OpenAIModel } from '@/config/provider/openai'; import { model as PerplexityModel } from '@/config/provider/perplexity'; +import { model as VeniceModel } from '@/config/provider/venice'; import store from '@/hooks/store'; import { Model, SimpleModel } from '@/types/model'; import { ProviderSetting } from '@/types/settings'; @@ -162,6 +163,14 @@ export const ModelSelect = () => { currentProviderSettings={currentProviderSettings} configured={process.env['NEXT_PUBLIC_ACCESS_PERPLEXITY'] == 'true'} /> + { const [mistral, setMistral] = useState(currentProviderSettings?.Mistral || null); const [openAI, setOpenAI] = useState(currentProviderSettings?.OpenAI || null); const [perplexity, setPerplexity] = useState(currentProviderSettings?.Perplexity || null); + const [venice, setVenice] = useState(currentProviderSettings?.Venice || null); const [custom, setCustom] = useState(currentProviderSettings?.Custom || null); @@ -100,6 +101,7 @@ export const SettingsDialog = () => { Mistral: mistral!, OpenAI: openAI!, Perplexity: perplexity!, + Venice: venice!, Custom: custom!, }); @@ -203,6 +205,8 @@ export const SettingsDialog = () => { setMistral={setMistral} perplexity={perplexity} setPerplexity={setPerplexity} + venice={venice} + setVenice={setVenice} custom={custom} setCustom={setCustom} /> diff --git a/components/layout/settings-drawer.tsx b/components/layout/settings-drawer.tsx index e9a55688..6e6127f2 100644 --- a/components/layout/settings-drawer.tsx +++ b/components/layout/settings-drawer.tsx @@ -52,6 +52,7 @@ export const SettingsDrawer = () => { const [mistral, setMistral] = useState(currentProviderSettings?.Mistral || null); const [openAI, setOpenAI] = useState(currentProviderSettings?.OpenAI || null); const [perplexity, setPerplexity] = useState(currentProviderSettings?.Perplexity || null); + const [venice, setVenice] = useState(currentProviderSettings?.Venice || null); const [custom, setCustom] = useState(currentProviderSettings?.Custom || null); @@ -100,6 +101,7 @@ export const SettingsDrawer = () => { Mistral: mistral!, OpenAI: openAI!, Perplexity: perplexity!, + Venice: venice!, }); setCurrentSearchEngineSettings({ @@ -186,6 +188,8 @@ export const SettingsDrawer = () => { setMistral={setMistral} perplexity={perplexity} setPerplexity={setPerplexity} + venice={venice} + setVenice={setVenice} custom={custom} setCustom={setCustom} /> diff --git a/components/layout/settings/provider.tsx b/components/layout/settings/provider.tsx index 45656813..373aad3d 100644 --- a/components/layout/settings/provider.tsx +++ b/components/layout/settings/provider.tsx @@ -14,6 +14,7 @@ import { HuggingFaceProvider } from '@/components/layout/settings/provider/huggi import { MistralProvider } from '@/components/layout/settings/provider/mistral'; import { OpenAIProvider } from '@/components/layout/settings/provider/openai'; import { PerplexityProvider } from '@/components/layout/settings/provider/perplexity'; +import { VeniceProvider } from '@/components/layout/settings/provider/venice'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/custom/select'; import { Provider, Providers } from '@/config/provider'; import { ProviderSetting } from '@/types/settings'; @@ -43,6 +44,8 @@ export const ProviderSettings = ({ setOpenAI, perplexity, setPerplexity, + venice, + setVenice, custom, setCustom, @@ -71,6 +74,8 @@ export const ProviderSettings = ({ setOpenAI: (value: ProviderSetting['OpenAI'] | null) => void; perplexity: ProviderSetting['Perplexity'] | null; setPerplexity: (value: ProviderSetting['Perplexity'] | null) => void; + venice: ProviderSetting['Venice'] | null; + setVenice: (value: ProviderSetting['Venice'] | null) => void; custom: ProviderSetting['Custom'] | null; setCustom: (value: ProviderSetting['Custom'] | null) => void; @@ -103,6 +108,8 @@ export const ProviderSettings = ({ return ; case Provider.Perplexity: return ; + case Provider.Venice: + return ; case Provider.Custom: return ; default: diff --git a/components/layout/settings/provider/venice.tsx b/components/layout/settings/provider/venice.tsx new file mode 100644 index 00000000..d6842675 --- /dev/null +++ b/components/layout/settings/provider/venice.tsx @@ -0,0 +1,35 @@ +import { Input } from '@/components/ui/custom/input'; +import { ProviderSetting } from '@/types/settings'; + +export const VeniceProvider = ({ venice, setVenice }: { venice: ProviderSetting['Venice'] | null; setVenice: (value: ProviderSetting['Venice'] | null) => void }) => { + return ( +
+
+

Venice API Key

+ { + setVenice({ + apiKey: e.target.value, + }); + }} + /> +
+
+

Venice Endpoint

+ { + setVenice({ + endpoint: e.target.value, + }); + }} + /> +
+
+ ); +}; diff --git a/config/provider/index.ts b/config/provider/index.ts index a72dabd0..a3de64ee 100644 --- a/config/provider/index.ts +++ b/config/provider/index.ts @@ -9,6 +9,7 @@ import { HuggingFaceModelId, HuggingFaceModelName } from '@/config/provider/hugg import { MistralModelId, MistralModelName } from '@/config/provider/mistral'; import { OpenAIModelId, OpenAIModelName } from '@/config/provider/openai'; import { PerplexityModelId, PerplexityModelName } from '@/config/provider/perplexity'; +import { VeniceModelId, VeniceModelName } from '@/config/provider/venice'; export type AllModelId = | AmazonModelId @@ -21,7 +22,8 @@ export type AllModelId = | MistralModelId | GoogleModelId | OpenAIModelId - | PerplexityModelId; + | PerplexityModelId + | VeniceModelId; export type AllModelName = | AmazonModelName @@ -34,7 +36,8 @@ export type AllModelName = | MistralModelName | GoogleModelName | OpenAIModelName - | PerplexityModelName; + | PerplexityModelName + | VeniceModelName; export enum Provider { Amazon = 'Amazon', @@ -48,6 +51,7 @@ export enum Provider { HuggingFace = 'HuggingFace', Mistral = 'Mistral', Perplexity = 'Perplexity', + Venice = 'Venice', Custom = 'Custom', } @@ -100,6 +104,10 @@ export const Providers: { id: 'perplexity', name: Provider.Perplexity, }, + { + id: 'venice', + name: Provider.Venice, + }, { id: 'custom', name: Provider.Custom, diff --git a/docker-compose.yml b/docker-compose.yml index a39ccd18..5ac5a94a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,6 +34,9 @@ services: - PERPLEXITY_API_KEY= - PERPLEXITY_ENDPOINT= + + - VENICE_API_KEY= + - VENICE_ENDPOINT= restart: always ports: - 3000:3000 diff --git a/public/img/Venice.png b/public/img/Venice.png new file mode 100644 index 0000000000000000000000000000000000000000..473d98c3ba8ea2e39d074b98555bd09b5784b450 GIT binary patch literal 8436 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