diff --git a/src/assets/locale/en/openai.json b/src/assets/locale/en/openai.json new file mode 100644 index 0000000..e9babc3 --- /dev/null +++ b/src/assets/locale/en/openai.json @@ -0,0 +1,38 @@ +{ + "settings": "OpenAI API Settings", + "heading": "OpenAI API Settings", + "subheading": "Manage and configure your OpenAI API Compatible providers here.", + "addBtn": "Add Provider", + "table": { + "name": "Provider Name", + "baseUrl": "Base URL", + "actions": "Action" + }, + "modal": { + "titleAdd": "Add New Provider", + "name": { + "label": "Provider Name", + "required": "Provider name is required.", + "placeholder": "Enter provider name" + }, + "baseUrl": { + "label": "Base URL", + "help": "The base URL of the OpenAI API provider. eg (http://loocalhost:8080/v1)", + "required": "Base URL is required.", + "placeholder": "Enter base URL" + }, + "apiKey": { + "label": "API Key", + "required": "API Key is required.", + "placeholder": "Enter API Key" + }, + "submit": "Submit", + "update": "Update", + "deleteConfirm": "Are you sure you want to delete this provider?" + }, + "addSuccess": "Provider added successfully.", + "deleteSuccess": "Provider deleted successfully.", + "updateSuccess": "Provider updated successfully.", + "delete": "Delete", + "edit": "Edit" +} \ No newline at end of file diff --git a/src/components/Layouts/SettingsOptionLayout.tsx b/src/components/Layouts/SettingsOptionLayout.tsx index ce96a52..6365381 100644 --- a/src/components/Layouts/SettingsOptionLayout.tsx +++ b/src/components/Layouts/SettingsOptionLayout.tsx @@ -6,12 +6,12 @@ import { BlocksIcon, InfoIcon, CombineIcon, - ChromeIcon + ChromeIcon, + CloudCogIcon } from "lucide-react" import { useTranslation } from "react-i18next" import { Link, useLocation } from "react-router-dom" import { OllamaIcon } from "../Icons/Ollama" -import { Tag } from "antd" import { BetaTag } from "../Common/Beta" function classNames(...classes: string[]) { @@ -22,12 +22,11 @@ const LinkComponent = (item: { href: string name: string | JSX.Element icon: any - current: string, + current: string beta?: boolean }) => { return (
  • - {item.name} - { - item.beta && - } + {item.beta && }
  • ) } export const SettingsLayout = ({ children }: { children: React.ReactNode }) => { const location = useLocation() - const { t } = useTranslation(["settings", "common"]) + const { t } = useTranslation(["settings", "common", "openai"]) return ( <> @@ -93,6 +90,13 @@ export const SettingsLayout = ({ children }: { children: React.ReactNode }) => { beta /> )} + { + const { t } = useTranslation("openai") + const [open, setOpen] = useState(false) + const [editingConfig, setEditingConfig] = useState(null) + const queryClient = useQueryClient() + const [form] = Form.useForm() + + const { data: configs, isLoading } = useQuery({ + queryKey: ["openAIConfigs"], + queryFn: getAllOpenAIConfig + }) + + const addMutation = useMutation({ + mutationFn: addOpenAICofig, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["openAIConfigs"] + }) + setOpen(false) + message.success(t("addSuccess")) + } + }) + + 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 ( +
    +
    +
    +

    + {t("heading")} +

    +

    + {t("subheading")} +

    +
    +
    +
    +
    +
    + +
    +
    +
    + + ( +
    + + + + + + +
    + ) + } + ]} + dataSource={configs} + loading={isLoading} + rowKey="id" + /> + + { + setOpen(false) + setEditingConfig(null) + form.resetFields() + }} + footer={null}> +
    + + + + + + + + + + + + + + +
    + + + ) +} diff --git a/src/db/openai.ts b/src/db/openai.ts new file mode 100644 index 0000000..501ecfd --- /dev/null +++ b/src/db/openai.ts @@ -0,0 +1,146 @@ +type OpenAIModelConfig = { + id: string + name: string + baseUrl: string + apiKey?: string + createdAt: number +} +export const generateID = () => { + return "openai-xxxx-xxx-xxxx".replace(/[x]/g, () => { + const r = Math.floor(Math.random() * 16) + return r.toString(16) + }) +} + +export class OpenAIModelDb { + db: chrome.storage.StorageArea + + + constructor() { + this.db = chrome.storage.local + } + + + getAll = async (): Promise => { + return new Promise((resolve, reject) => { + this.db.get(null, (result) => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError) + } else { + const data = Object.keys(result).map((key) => result[key]) + resolve(data) + } + }) + }) + } + + + create = async (config: OpenAIModelConfig): Promise => { + return new Promise((resolve, reject) => { + this.db.set({ [config.id]: config }, () => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError) + } else { + resolve() + } + }) + }) + } + + + getById = async (id: string): Promise => { + return new Promise((resolve, reject) => { + this.db.get(id, (result) => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError) + } else { + resolve(result[id]) + } + }) + }) + } + + + update = async (config: OpenAIModelConfig): Promise => { + return new Promise((resolve, reject) => { + this.db.set({ [config.id]: config }, () => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError) + } else { + resolve() + } + }) + }) + } + + + delete = async (id: string): Promise => { + return new Promise((resolve, reject) => { + this.db.remove(id, () => { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError) + } else { + resolve() + } + }) + }) + } + +} + + +export const addOpenAICofig = async ({ name, baseUrl, apiKey }: { name: string, baseUrl: string, apiKey: string }) => { + const openaiDb = new OpenAIModelDb() + const id = generateID() + const config: OpenAIModelConfig = { + id, + name, + baseUrl, + apiKey, + createdAt: Date.now() + } + await openaiDb.create(config) + return id +} + + +export const getAllOpenAIConfig = async () => { + const openaiDb = new OpenAIModelDb() + const configs = await openaiDb.getAll() + return configs +} + +export const updateOpenAIConfig = async ({ id, name, baseUrl, apiKey }: { id: string, name: string, baseUrl: string, apiKey: string }) => { + const openaiDb = new OpenAIModelDb() + const config: OpenAIModelConfig = { + id, + name, + baseUrl, + apiKey, + createdAt: Date.now() + } + + await openaiDb.update(config) + + return config +} + + +export const deleteOpenAIConfig = async (id: string) => { + const openaiDb = new OpenAIModelDb() + await openaiDb.delete(id) +} + + +export const updateOpenAIConfigApiKey = async (id: string, { name, baseUrl, apiKey }: { name: string, baseUrl: string, apiKey: string }) => { + const openaiDb = new OpenAIModelDb() + const config: OpenAIModelConfig = { + id, + name, + baseUrl, + apiKey, + createdAt: Date.now() + } + + await openaiDb.update(config) +} \ No newline at end of file diff --git a/src/i18n/lang/en.ts b/src/i18n/lang/en.ts index dcb0dc6..0eb1b8a 100644 --- a/src/i18n/lang/en.ts +++ b/src/i18n/lang/en.ts @@ -5,6 +5,7 @@ import sidepanel from "@/assets/locale/en/sidepanel.json"; import settings from "@/assets/locale/en/settings.json"; import knowledge from "@/assets/locale/en/knowledge.json"; import chrome from "@/assets/locale/en/chrome.json"; +import openai from "@/assets/locale/en/openai.json"; export const en = { option, @@ -13,5 +14,6 @@ export const en = { sidepanel, settings, knowledge, - chrome + chrome, + openai } \ No newline at end of file diff --git a/src/routes/chrome.tsx b/src/routes/chrome.tsx index 4e78ba0..647bb33 100644 --- a/src/routes/chrome.tsx +++ b/src/routes/chrome.tsx @@ -11,6 +11,7 @@ import SidepanelChat from "./sidepanel-chat" import SidepanelSettings from "./sidepanel-settings" import OptionRagSettings from "./option-rag" import OptionChrome from "./option-settings-chrome" +import OptionOpenAI from "./option-settings-openai" export const OptionRoutingChrome = () => { return ( @@ -21,6 +22,7 @@ export const OptionRoutingChrome = () => { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/routes/firefox.tsx b/src/routes/firefox.tsx index 40264f4..901e584 100644 --- a/src/routes/firefox.tsx +++ b/src/routes/firefox.tsx @@ -14,6 +14,7 @@ const OptionShare = lazy(() => import("./option-settings-share")) const OptionKnowledgeBase = lazy(() => import("./option-settings-knowledge")) const OptionAbout = lazy(() => import("./option-settings-about")) const OptionRagSettings = lazy(() => import("./option-rag")) +const OptionOpenAI = lazy(() => import("./option-settings-openai")) export const OptionRoutingFirefox = () => { return ( @@ -23,6 +24,7 @@ export const OptionRoutingFirefox = () => { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/routes/option-settings-openai.tsx b/src/routes/option-settings-openai.tsx new file mode 100644 index 0000000..3ddbc4f --- /dev/null +++ b/src/routes/option-settings-openai.tsx @@ -0,0 +1,15 @@ +import { SettingsLayout } from "~/components/Layouts/SettingsOptionLayout" +import OptionLayout from "~/components/Layouts/Layout" +import { OpenAIApp } from "@/components/Option/Settings/openai" + +const OptionOpenAI = () => { + return ( + + + + + + ) +} + +export default OptionOpenAI