feat: Add OpenAI provider support

This commit is contained in:
n4ze3m 2024-10-12 18:28:29 +05:30
parent acce9b97f6
commit f1e40d5908
12 changed files with 227 additions and 27 deletions

View File

@ -1,5 +1,11 @@
import { ChromeIcon, CpuIcon } from "lucide-react"
import { OllamaIcon } from "../Icons/Ollama"
import { FireworksMonoIcon } from "../Icons/Fireworks"
import { GroqMonoIcon } from "../Icons/Groq"
import { LMStudioIcon } from "../Icons/LMStudio"
import { OpenAiIcon } from "../Icons/OpenAI"
import { TogtherMonoIcon } from "../Icons/Togther"
import { OpenRouterIcon } from "../Icons/OpenRouter"
export const ProviderIcons = ({
provider,
@ -13,6 +19,18 @@ export const ProviderIcons = ({
return <ChromeIcon className={className} />
case "custom":
return <CpuIcon className={className} />
case "fireworks":
return <FireworksMonoIcon className={className} />
case "groq":
return <GroqMonoIcon className={className} />
case "lmstudio":
return <LMStudioIcon className={className} />
case "openai":
return <OpenAiIcon className={className} />
case "together":
return <TogtherMonoIcon className={className} />
case "openrouter":
return <OpenRouterIcon className={className} />
default:
return <OllamaIcon className={className} />
}

View File

@ -0,0 +1,19 @@
import React from "react"
export const FireworksMonoIcon = React.forwardRef<
SVGSVGElement,
React.SVGProps<SVGSVGElement>
>((props, ref) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 638 315"
ref={ref}
{...props}>
<path
fill="#fff"
d="M318.563 221.755c-17.7 0-33.584-10.508-40.357-26.777L196.549 0h47.793l74.5 178.361L393.273 0h47.793L358.92 195.048c-6.808 16.199-22.657 26.707-40.357 26.707zM425.111 314.933c-17.63 0-33.444-10.439-40.287-26.567-6.877-16.269-3.317-34.842 9.112-47.445l148.721-150.64 18.572 43.813-136.153 137.654 194.071-1.082 18.573 43.813-212.574.524-.07-.07h.035zM0 314.408l18.573-43.813 194.07 1.082L76.525 133.988l18.573-43.813 148.721 150.641c12.428 12.568 16.024 31.21 9.111 47.444-6.842 16.164-22.727 26.567-40.287 26.567L.07 314.339l-.07.069z"></path>
</svg>
)
})

View File

@ -0,0 +1,18 @@
import React from "react"
export const GroqMonoIcon = React.forwardRef<
SVGSVGElement,
React.SVGProps<SVGSVGElement>
>((props, ref) => {
return (
<svg
fill="currentColor"
fillRule="evenodd"
ref={ref}
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
{...props}>
<path d="M12.036 2c-3.853-.035-7 3-7.036 6.781-.035 3.782 3.055 6.872 6.908 6.907h2.42v-2.566h-2.292c-2.407.028-4.38-1.866-4.408-4.23-.029-2.362 1.901-4.298 4.308-4.326h.1c2.407 0 4.358 1.915 4.365 4.278v6.305c0 2.342-1.944 4.25-4.323 4.279a4.375 4.375 0 01-3.033-1.252l-1.851 1.818A7 7 0 0012.029 22h.092c3.803-.056 6.858-3.083 6.879-6.816v-6.5C18.907 4.963 15.817 2 12.036 2z"></path>
</svg>
)
})

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,18 @@
import React from "react"
export const OpenAiIcon = React.forwardRef<
SVGSVGElement,
React.SVGProps<SVGSVGElement>
>((props, ref) => {
return (
<svg
fill="currentColor"
fillRule="evenodd"
ref={ref}
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
{...props}>
<path d="M21.55 10.004a5.416 5.416 0 00-.478-4.501c-1.217-2.09-3.662-3.166-6.05-2.66A5.59 5.59 0 0010.831 1C8.39.995 6.224 2.546 5.473 4.838A5.553 5.553 0 001.76 7.496a5.487 5.487 0 00.691 6.5 5.416 5.416 0 00.477 4.502c1.217 2.09 3.662 3.165 6.05 2.66A5.586 5.586 0 0013.168 23c2.443.006 4.61-1.546 5.361-3.84a5.553 5.553 0 003.715-2.66 5.488 5.488 0 00-.693-6.497v.001zm-8.381 11.558a4.199 4.199 0 01-2.675-.954c.034-.018.093-.05.132-.074l4.44-2.53a.71.71 0 00.364-.623v-6.176l1.877 1.069c.02.01.033.029.036.05v5.115c-.003 2.274-1.87 4.118-4.174 4.123zM4.192 17.78a4.059 4.059 0 01-.498-2.763c.032.02.09.055.131.078l4.44 2.53c.225.13.504.13.73 0l5.42-3.088v2.138a.068.068 0 01-.027.057L9.9 19.288c-1.999 1.136-4.552.46-5.707-1.51h-.001zM3.023 8.216A4.15 4.15 0 015.198 6.41l-.002.151v5.06a.711.711 0 00.364.624l5.42 3.087-1.876 1.07a.067.067 0 01-.063.005l-4.489-2.559c-1.995-1.14-2.679-3.658-1.53-5.63h.001zm15.417 3.54l-5.42-3.088L14.896 7.6a.067.067 0 01.063-.006l4.489 2.557c1.998 1.14 2.683 3.662 1.529 5.633a4.163 4.163 0 01-2.174 1.807V12.38a.71.71 0 00-.363-.623zm1.867-2.773a6.04 6.04 0 00-.132-.078l-4.44-2.53a.731.731 0 00-.729 0l-5.42 3.088V7.325a.068.068 0 01.027-.057L14.1 4.713c2-1.137 4.555-.46 5.707 1.513.487.833.664 1.809.499 2.757h.001zm-11.741 3.81l-1.877-1.068a.065.065 0 01-.036-.051V6.559c.001-2.277 1.873-4.122 4.181-4.12.976 0 1.92.338 2.671.954-.034.018-.092.05-.131.073l-4.44 2.53a.71.71 0 00-.365.623l-.003 6.173v.002zm1.02-2.168L12 9.25l2.414 1.375v2.75L12 14.75l-2.415-1.375v-2.75z"></path>
</svg>
)
})

View File

@ -0,0 +1,18 @@
import React from "react"
export const OpenRouterIcon = React.forwardRef<
SVGSVGElement,
React.SVGProps<SVGSVGElement>
>((props, ref) => {
return (
<svg
fill="currentColor"
fillRule="evenodd"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
ref={ref}
{...props}>
<path d="M16.804 1.957l7.22 4.105v.087L16.73 10.21l.017-2.117-.821-.03c-1.059-.028-1.611.002-2.268.11-1.064.175-2.038.577-3.147 1.352L8.345 11.03c-.284.195-.495.336-.68.455l-.515.322-.397.234.385.23.53.338c.476.314 1.17.796 2.701 1.866 1.11.775 2.083 1.177 3.147 1.352l.3.045c.694.091 1.375.094 2.825.033l.022-2.159 7.22 4.105v.087L16.589 22l.014-1.862-.635.022c-1.386.042-2.137.002-3.138-.162-1.694-.28-3.26-.926-4.881-2.059l-2.158-1.5a21.997 21.997 0 00-.755-.498l-.467-.28a55.927 55.927 0 00-.76-.43C2.908 14.73.563 14.116 0 14.116V9.888l.14.004c.564-.007 2.91-.622 3.809-1.124l1.016-.58.438-.274c.428-.28 1.072-.726 2.686-1.853 1.621-1.133 3.186-1.78 4.881-2.059 1.152-.19 1.974-.213 3.814-.138l.02-1.907z"></path>
</svg>
)
})

View File

@ -0,0 +1,23 @@
import React from "react"
export const TogtherMonoIcon = React.forwardRef<
SVGSVGElement,
React.SVGProps<SVGSVGElement>
>((props, ref) => {
return (
<svg
fill="currentColor"
fillRule="evenodd"
ref={ref}
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
{...props}>
<g>
<path
d="M17.385 11.23a4.615 4.615 0 100-9.23 4.615 4.615 0 000 9.23zm0 10.77a4.615 4.615 0 100-9.23 4.615 4.615 0 000 9.23zm-10.77 0a4.615 4.615 0 100-9.23 4.615 4.615 0 000 9.23z"
opacity=".2"></path>
<circle cx="6.615" cy="6.615" r="4.615"></circle>
</g>
</svg>
)
})

View File

@ -8,7 +8,7 @@ import {
updateOpenAIConfig
} from "@/db/openai"
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
import { Pencil, Trash2, RotateCwIcon } from "lucide-react"
import { Pencil, Trash2, RotateCwIcon, DownloadIcon } from "lucide-react"
import { OpenAIFetchModel } from "./openai-fetch-model"
import { OAI_API_PROVIDERS } from "@/utils/oai-api-providers"
@ -20,6 +20,7 @@ export const OpenAIApp = () => {
const [form] = Form.useForm()
const [openaiId, setOpenaiId] = useState<string | null>(null)
const [openModelModal, setOpenModelModal] = useState(false)
const [provider, setProvider] = useState("custom")
const { data: configs, isLoading } = useQuery({
queryKey: ["openAIConfigs"],
@ -69,8 +70,13 @@ export const OpenAIApp = () => {
if (editingConfig) {
updateMutation.mutate({ id: editingConfig.id, ...values })
} else {
addMutation.mutate(values)
addMutation.mutate({
...values,
provider
})
}
setProvider("custom")
}
const handleEdit = (record: any) => {
@ -144,7 +150,7 @@ export const OpenAIApp = () => {
setOpenaiId(record.id)
}}
disabled={!record.id}>
<RotateCwIcon className="size-4" />
<DownloadIcon className="size-4" />
</button>
</Tooltip>
<Tooltip title={t("delete")}>
@ -180,18 +186,20 @@ export const OpenAIApp = () => {
onCancel={() => {
setOpen(false)
setEditingConfig(null)
setProvider("custom")
form.resetFields()
}}
footer={null}>
{!editingConfig && (
<Select
defaultValue="custom"
value={provider}
onSelect={(e) => {
const value = OAI_API_PROVIDERS.find((item) => item.value === e)
form.setFieldsValue({
baseUrl: value?.baseUrl,
name: value?.label
})
setProvider(e)
}}
className="w-full !mb-4"
options={OAI_API_PROVIDERS}

View File

@ -1,4 +1,7 @@
import { getOpenAIConfigById as providerInfo } from "./openai"
import {
getAllOpenAIConfig,
getOpenAIConfigById as providerInfo
} from "./openai"
type Model = {
id: string
@ -16,11 +19,15 @@ export const generateID = () => {
}
export const removeModelSuffix = (id: string) => {
return id.replace(/_model-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{3,4}-[a-f0-9]{4}/, "")
return id.replace(
/_model-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{3,4}-[a-f0-9]{4}/,
""
)
}
export const isCustomModel = (model: string) => {
const customModelRegex = /_model-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{3,4}-[a-f0-9]{4}/
const customModelRegex =
/_model-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{3,4}-[a-f0-9]{4}/
return customModelRegex.test(model)
}
export class ModelDb {
@ -174,6 +181,17 @@ export const deleteModel = async (id: string) => {
await db.delete(id)
}
export const deleteAllModelsByProviderId = async (provider_id: string) => {
const db = new ModelDb()
const models = await db.getAll()
const modelsToDelete = models.filter(
(model) => model.provider_id === provider_id
)
for (const model of modelsToDelete) {
await db.delete(model.id)
}
}
export const isLookupExist = async (lookup: string) => {
const db = new ModelDb()
const models = await db.getAll()
@ -181,17 +199,19 @@ export const isLookupExist = async (lookup: string) => {
return model ? true : false
}
export const ollamaFormatAllCustomModels = async () => {
const allModles = await getAllCustomModels()
const allProviders = await getAllOpenAIConfig()
const ollamaModels = allModles.map((model) => {
return {
name: model.name,
model: model.id,
modified_at: "",
provider: "custom",
provider:
allProviders.find((provider) => provider.id === model.provider_id)
?.provider || "custom",
size: 0,
digest: "",
details: {

View File

@ -1,4 +1,5 @@
import { cleanUrl } from "@/libs/clean-url"
import { deleteAllModelsByProviderId } from "./models"
type OpenAIModelConfig = {
id: string
@ -93,7 +94,7 @@ export class OpenAIModelDb {
}
export const addOpenAICofig = async ({ name, baseUrl, apiKey }: { name: string, baseUrl: string, apiKey: string }) => {
export const addOpenAICofig = async ({ name, baseUrl, apiKey, provider }: { name: string, baseUrl: string, apiKey: string, provider?: string }) => {
const openaiDb = new OpenAIModelDb()
const id = generateID()
const config: OpenAIModelConfig = {
@ -102,7 +103,8 @@ export const addOpenAICofig = async ({ name, baseUrl, apiKey }: { name: string,
baseUrl: cleanUrl(baseUrl),
apiKey,
createdAt: Date.now(),
db_type: "openai"
db_type: "openai",
provider
}
await openaiDb.create(config)
return id
@ -117,13 +119,15 @@ export const getAllOpenAIConfig = async () => {
export const updateOpenAIConfig = async ({ id, name, baseUrl, apiKey }: { id: string, name: string, baseUrl: string, apiKey: string }) => {
const openaiDb = new OpenAIModelDb()
const oldData = await openaiDb.getById(id)
const config: OpenAIModelConfig = {
...oldData,
id,
name,
baseUrl: cleanUrl(baseUrl),
apiKey,
createdAt: Date.now(),
db_type: "openai"
db_type: "openai",
}
await openaiDb.update(config)
@ -135,6 +139,7 @@ export const updateOpenAIConfig = async ({ id, name, baseUrl, apiKey }: { id: st
export const deleteOpenAIConfig = async (id: string) => {
const openaiDb = new OpenAIModelDb()
await openaiDb.delete(id)
await deleteAllModelsByProviderId(id)
}

View File

@ -1,9 +1,12 @@
type Model = {
id: string
name?: string
display_name?: string
type: string
}
export const getAllOpenAIModels = async (baseUrl: string, apiKey?: string) => {
try {
const url = `${baseUrl}/models`
const headers = apiKey
? {
@ -19,7 +22,19 @@ export const getAllOpenAIModels = async (baseUrl: string, apiKey?: string) => {
return []
}
if (baseUrl === "https://api.together.xyz/v1") {
const data = (await res.json()) as Model[]
return data.map(model => ({
id: model.id,
name: model.display_name,
}))
}
const data = (await res.json()) as { data: Model[] }
return data.data
} catch (e) {
console.log(e)
return []
}
}

View File

@ -24,6 +24,11 @@ export const OAI_API_PROVIDERS = [
value: "together",
baseUrl: "https://api.together.xyz/v1"
},
{
label: "OpenRouter",
value: "openrouter",
baseUrl: "https://openrouter.ai/api/v1"
},
{
label: "Custsom",
value: "custom",