Add a provider selection dropdown to the OpenAI settings, enabling users to choose from pre-configured options like "Azure" or "Custom." This streamlines setup and allows for more flexibility in configuring OpenAI API endpoints. The dropdown pre-populates base URLs and names based on the selected provider. The dropdown also automatically populates base URLs and names based on the selected provider, further simplifying the configuration process.
264 lines
8.4 KiB
TypeScript
264 lines
8.4 KiB
TypeScript
import { Form, Input, Modal, Table, message, Tooltip, Select } from "antd"
|
|
import { useState } from "react"
|
|
import { useTranslation } from "react-i18next"
|
|
import {
|
|
addOpenAICofig,
|
|
getAllOpenAIConfig,
|
|
deleteOpenAIConfig,
|
|
updateOpenAIConfig
|
|
} from "@/db/openai"
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"
|
|
import { Pencil, Trash2, RotateCwIcon } from "lucide-react"
|
|
import { OpenAIFetchModel } from "./openai-fetch-model"
|
|
import { OAI_API_PROVIDERS } from "@/utils/oai-api-providers"
|
|
|
|
export const OpenAIApp = () => {
|
|
const { t } = useTranslation("openai")
|
|
const [open, setOpen] = useState(false)
|
|
const [editingConfig, setEditingConfig] = useState(null)
|
|
const queryClient = useQueryClient()
|
|
const [form] = Form.useForm()
|
|
const [openaiId, setOpenaiId] = useState<string | null>(null)
|
|
const [openModelModal, setOpenModelModal] = useState(false)
|
|
|
|
const { data: configs, isLoading } = useQuery({
|
|
queryKey: ["openAIConfigs"],
|
|
queryFn: getAllOpenAIConfig
|
|
})
|
|
|
|
const addMutation = useMutation({
|
|
mutationFn: addOpenAICofig,
|
|
onSuccess: (data) => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["openAIConfigs"]
|
|
})
|
|
setOpen(false)
|
|
message.success(t("addSuccess"))
|
|
setOpenaiId(data)
|
|
setOpenModelModal(true)
|
|
}
|
|
})
|
|
|
|
const updateMutation = useMutation({
|
|
mutationFn: updateOpenAIConfig,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["openAIConfigs"]
|
|
})
|
|
setOpen(false)
|
|
message.success(t("updateSuccess"))
|
|
}
|
|
})
|
|
|
|
const deleteMutation = useMutation({
|
|
mutationFn: deleteOpenAIConfig,
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({
|
|
queryKey: ["openAIConfigs"]
|
|
})
|
|
message.success(t("deleteSuccess"))
|
|
}
|
|
})
|
|
|
|
const handleSubmit = (values: {
|
|
id?: string
|
|
name: string
|
|
baseUrl: string
|
|
apiKey: string
|
|
}) => {
|
|
if (editingConfig) {
|
|
updateMutation.mutate({ id: editingConfig.id, ...values })
|
|
} else {
|
|
addMutation.mutate(values)
|
|
}
|
|
}
|
|
|
|
const handleEdit = (record: any) => {
|
|
setEditingConfig(record)
|
|
setOpen(true)
|
|
form.setFieldsValue(record)
|
|
}
|
|
|
|
const handleDelete = (id: string) => {
|
|
deleteMutation.mutate(id)
|
|
}
|
|
|
|
return (
|
|
<div>
|
|
<div>
|
|
<div>
|
|
<h2 className="text-base font-semibold leading-7 text-gray-900 dark:text-white">
|
|
{t("heading")}
|
|
</h2>
|
|
<p className="mt-1 text-sm leading-6 text-gray-600 dark:text-gray-400">
|
|
{t("subheading")}
|
|
</p>
|
|
<div className="border border-b border-gray-200 dark:border-gray-600 mt-3 mb-6"></div>
|
|
</div>
|
|
<div className="mb-6">
|
|
<div className="-ml-4 -mt-2 flex flex-wrap items-center justify-end sm:flex-nowrap">
|
|
<div className="ml-4 mt-2 flex-shrink-0">
|
|
<button
|
|
onClick={() => {
|
|
setEditingConfig(null)
|
|
setOpen(true)
|
|
form.resetFields()
|
|
}}
|
|
className="inline-flex items-center rounded-md border border-transparent bg-black px-2 py-2 text-md font-medium leading-4 text-white shadow-sm hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-white dark:text-gray-800 dark:hover:bg-gray-100 dark:focus:ring-gray-500 dark:focus:ring-offset-gray-100 disabled:opacity-50">
|
|
{t("addBtn")}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<Table
|
|
columns={[
|
|
{
|
|
title: t("table.name"),
|
|
dataIndex: "name",
|
|
key: "name"
|
|
},
|
|
{
|
|
title: t("table.baseUrl"),
|
|
dataIndex: "baseUrl",
|
|
key: "baseUrl"
|
|
},
|
|
{
|
|
title: t("table.actions"),
|
|
key: "actions",
|
|
render: (_, record) => (
|
|
<div className="flex gap-4">
|
|
<Tooltip title={t("edit")}>
|
|
<button
|
|
className="text-gray-700 dark:text-gray-400"
|
|
onClick={() => handleEdit(record)}>
|
|
<Pencil className="size-4" />
|
|
</button>
|
|
</Tooltip>
|
|
|
|
<Tooltip title={t("refetch")}>
|
|
<button
|
|
className="text-gray-700 dark:text-gray-400"
|
|
onClick={() => {
|
|
setOpenModelModal(true)
|
|
setOpenaiId(record.id)
|
|
}}
|
|
disabled={!record.id}>
|
|
<RotateCwIcon className="size-4" />
|
|
</button>
|
|
</Tooltip>
|
|
<Tooltip title={t("delete")}>
|
|
<button
|
|
className="text-red-500 dark:text-red-400"
|
|
onClick={() => {
|
|
// add confirmation here
|
|
if (
|
|
confirm(
|
|
t("modal.deleteConfirm", {
|
|
name: record.name
|
|
})
|
|
)
|
|
) {
|
|
handleDelete(record.id)
|
|
}
|
|
}}>
|
|
<Trash2 className="size-4" />
|
|
</button>
|
|
</Tooltip>
|
|
</div>
|
|
)
|
|
}
|
|
]}
|
|
dataSource={configs}
|
|
loading={isLoading}
|
|
rowKey="id"
|
|
/>
|
|
|
|
<Modal
|
|
open={open}
|
|
title={editingConfig ? t("modal.titleEdit") : t("modal.titleAdd")}
|
|
onCancel={() => {
|
|
setOpen(false)
|
|
setEditingConfig(null)
|
|
form.resetFields()
|
|
}}
|
|
footer={null}>
|
|
{!editingConfig && (
|
|
<Select
|
|
defaultValue="custom"
|
|
onSelect={(e) => {
|
|
const value = OAI_API_PROVIDERS.find((item) => item.value === e)
|
|
form.setFieldsValue({
|
|
baseUrl: value?.baseUrl,
|
|
name: value?.label
|
|
})
|
|
}}
|
|
className="w-full !mb-4"
|
|
options={OAI_API_PROVIDERS}
|
|
/>
|
|
)}
|
|
<Form
|
|
form={form}
|
|
layout="vertical"
|
|
onFinish={handleSubmit}
|
|
initialValues={{ ...editingConfig }}>
|
|
<Form.Item
|
|
name="name"
|
|
label={t("modal.name.label")}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("modal.name.required")
|
|
}
|
|
]}>
|
|
<Input size="large" placeholder={t("modal.name.placeholder")} />
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
name="baseUrl"
|
|
label={t("modal.baseUrl.label")}
|
|
help={t("modal.baseUrl.help")}
|
|
rules={[
|
|
{
|
|
required: true,
|
|
message: t("modal.baseUrl.required")
|
|
}
|
|
]}>
|
|
<Input
|
|
size="large"
|
|
placeholder={t("modal.baseUrl.placeholder")}
|
|
/>
|
|
</Form.Item>
|
|
|
|
<Form.Item name="apiKey" label={t("modal.apiKey.label")}>
|
|
<Input.Password
|
|
size="large"
|
|
placeholder={t("modal.apiKey.placeholder")}
|
|
/>
|
|
</Form.Item>
|
|
|
|
<button
|
|
type="submit"
|
|
className="inline-flex justify-center w-full text-center mt-4 items-center rounded-md border border-transparent bg-black px-2 py-2 text-sm font-medium leading-4 text-white shadow-sm hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 dark:bg-white dark:text-gray-800 dark:hover:bg-gray-100 dark:focus:ring-gray-500 dark:focus:ring-offset-gray-100 disabled:opacity-50">
|
|
{editingConfig ? t("modal.update") : t("modal.submit")}
|
|
</button>
|
|
</Form>
|
|
</Modal>
|
|
|
|
<Modal
|
|
open={openModelModal}
|
|
title={t("modal.model.title")}
|
|
footer={null}
|
|
onCancel={() => setOpenModelModal(false)}>
|
|
{openaiId ? (
|
|
<OpenAIFetchModel
|
|
openaiId={openaiId}
|
|
setOpenModelModal={setOpenModelModal}
|
|
/>
|
|
) : null}
|
|
</Modal>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|