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