feat: OpenAI settings page
Add a new settings page for OpenAI configuration, including a dedicated tab in the settings layout, translations, and routing.
This commit is contained in:
parent
2e97f6470d
commit
e2e3655c47
38
src/assets/locale/en/openai.json
Normal file
38
src/assets/locale/en/openai.json
Normal file
@ -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"
|
||||||
|
}
|
@ -6,12 +6,12 @@ import {
|
|||||||
BlocksIcon,
|
BlocksIcon,
|
||||||
InfoIcon,
|
InfoIcon,
|
||||||
CombineIcon,
|
CombineIcon,
|
||||||
ChromeIcon
|
ChromeIcon,
|
||||||
|
CloudCogIcon
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { Link, useLocation } from "react-router-dom"
|
import { Link, useLocation } from "react-router-dom"
|
||||||
import { OllamaIcon } from "../Icons/Ollama"
|
import { OllamaIcon } from "../Icons/Ollama"
|
||||||
import { Tag } from "antd"
|
|
||||||
import { BetaTag } from "../Common/Beta"
|
import { BetaTag } from "../Common/Beta"
|
||||||
|
|
||||||
function classNames(...classes: string[]) {
|
function classNames(...classes: string[]) {
|
||||||
@ -22,12 +22,11 @@ const LinkComponent = (item: {
|
|||||||
href: string
|
href: string
|
||||||
name: string | JSX.Element
|
name: string | JSX.Element
|
||||||
icon: any
|
icon: any
|
||||||
current: string,
|
current: string
|
||||||
beta?: boolean
|
beta?: boolean
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<li className="inline-flex items-center">
|
<li className="inline-flex items-center">
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
to={item.href}
|
to={item.href}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
@ -47,16 +46,14 @@ const LinkComponent = (item: {
|
|||||||
/>
|
/>
|
||||||
{item.name}
|
{item.name}
|
||||||
</Link>
|
</Link>
|
||||||
{
|
{item.beta && <BetaTag />}
|
||||||
item.beta && <BetaTag />
|
|
||||||
}
|
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SettingsLayout = ({ children }: { children: React.ReactNode }) => {
|
export const SettingsLayout = ({ children }: { children: React.ReactNode }) => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const { t } = useTranslation(["settings", "common"])
|
const { t } = useTranslation(["settings", "common", "openai"])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -93,6 +90,13 @@ export const SettingsLayout = ({ children }: { children: React.ReactNode }) => {
|
|||||||
beta
|
beta
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<LinkComponent
|
||||||
|
href="/settings/openai"
|
||||||
|
name={t("openai:settings")}
|
||||||
|
icon={CloudCogIcon}
|
||||||
|
current={location.pathname}
|
||||||
|
beta
|
||||||
|
/>
|
||||||
<LinkComponent
|
<LinkComponent
|
||||||
href="/settings/model"
|
href="/settings/model"
|
||||||
name={t("manageModels.title")}
|
name={t("manageModels.title")}
|
||||||
|
218
src/components/Option/Settings/openai.tsx
Normal file
218
src/components/Option/Settings/openai.tsx
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
import { Form, Input, Modal, Table, message, Tooltip } 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, Plus } from "lucide-react"
|
||||||
|
|
||||||
|
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 { 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 (
|
||||||
|
<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("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}>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
146
src/db/openai.ts
Normal file
146
src/db/openai.ts
Normal file
@ -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<OpenAIModelConfig[]> => {
|
||||||
|
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<void> => {
|
||||||
|
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<OpenAIModelConfig> => {
|
||||||
|
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<void> => {
|
||||||
|
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<void> => {
|
||||||
|
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)
|
||||||
|
}
|
@ -5,6 +5,7 @@ import sidepanel from "@/assets/locale/en/sidepanel.json";
|
|||||||
import settings from "@/assets/locale/en/settings.json";
|
import settings from "@/assets/locale/en/settings.json";
|
||||||
import knowledge from "@/assets/locale/en/knowledge.json";
|
import knowledge from "@/assets/locale/en/knowledge.json";
|
||||||
import chrome from "@/assets/locale/en/chrome.json";
|
import chrome from "@/assets/locale/en/chrome.json";
|
||||||
|
import openai from "@/assets/locale/en/openai.json";
|
||||||
|
|
||||||
export const en = {
|
export const en = {
|
||||||
option,
|
option,
|
||||||
@ -13,5 +14,6 @@ export const en = {
|
|||||||
sidepanel,
|
sidepanel,
|
||||||
settings,
|
settings,
|
||||||
knowledge,
|
knowledge,
|
||||||
chrome
|
chrome,
|
||||||
|
openai
|
||||||
}
|
}
|
@ -11,6 +11,7 @@ import SidepanelChat from "./sidepanel-chat"
|
|||||||
import SidepanelSettings from "./sidepanel-settings"
|
import SidepanelSettings from "./sidepanel-settings"
|
||||||
import OptionRagSettings from "./option-rag"
|
import OptionRagSettings from "./option-rag"
|
||||||
import OptionChrome from "./option-settings-chrome"
|
import OptionChrome from "./option-settings-chrome"
|
||||||
|
import OptionOpenAI from "./option-settings-openai"
|
||||||
|
|
||||||
export const OptionRoutingChrome = () => {
|
export const OptionRoutingChrome = () => {
|
||||||
return (
|
return (
|
||||||
@ -21,6 +22,7 @@ export const OptionRoutingChrome = () => {
|
|||||||
<Route path="/settings/prompt" element={<OptionPrompt />} />
|
<Route path="/settings/prompt" element={<OptionPrompt />} />
|
||||||
<Route path="/settings/ollama" element={<OptionOllamaSettings />} />
|
<Route path="/settings/ollama" element={<OptionOllamaSettings />} />
|
||||||
<Route path="/settings/chrome" element={<OptionChrome />} />
|
<Route path="/settings/chrome" element={<OptionChrome />} />
|
||||||
|
<Route path="/settings/openai" element={<OptionOpenAI />} />
|
||||||
<Route path="/settings/share" element={<OptionShare />} />
|
<Route path="/settings/share" element={<OptionShare />} />
|
||||||
<Route path="/settings/knowledge" element={<OptionKnowledgeBase />} />
|
<Route path="/settings/knowledge" element={<OptionKnowledgeBase />} />
|
||||||
<Route path="/settings/rag" element={<OptionRagSettings />} />
|
<Route path="/settings/rag" element={<OptionRagSettings />} />
|
||||||
|
@ -14,6 +14,7 @@ const OptionShare = lazy(() => import("./option-settings-share"))
|
|||||||
const OptionKnowledgeBase = lazy(() => import("./option-settings-knowledge"))
|
const OptionKnowledgeBase = lazy(() => import("./option-settings-knowledge"))
|
||||||
const OptionAbout = lazy(() => import("./option-settings-about"))
|
const OptionAbout = lazy(() => import("./option-settings-about"))
|
||||||
const OptionRagSettings = lazy(() => import("./option-rag"))
|
const OptionRagSettings = lazy(() => import("./option-rag"))
|
||||||
|
const OptionOpenAI = lazy(() => import("./option-settings-openai"))
|
||||||
|
|
||||||
export const OptionRoutingFirefox = () => {
|
export const OptionRoutingFirefox = () => {
|
||||||
return (
|
return (
|
||||||
@ -23,6 +24,7 @@ export const OptionRoutingFirefox = () => {
|
|||||||
<Route path="/settings/model" element={<OptionModal />} />
|
<Route path="/settings/model" element={<OptionModal />} />
|
||||||
<Route path="/settings/prompt" element={<OptionPrompt />} />
|
<Route path="/settings/prompt" element={<OptionPrompt />} />
|
||||||
<Route path="/settings/ollama" element={<OptionOllamaSettings />} />
|
<Route path="/settings/ollama" element={<OptionOllamaSettings />} />
|
||||||
|
<Route path="/settings/openai" element={<OptionOpenAI />} />
|
||||||
<Route path="/settings/share" element={<OptionShare />} />
|
<Route path="/settings/share" element={<OptionShare />} />
|
||||||
<Route path="/settings/knowledge" element={<OptionKnowledgeBase />} />
|
<Route path="/settings/knowledge" element={<OptionKnowledgeBase />} />
|
||||||
<Route path="/settings/about" element={<OptionAbout />} />
|
<Route path="/settings/about" element={<OptionAbout />} />
|
||||||
|
15
src/routes/option-settings-openai.tsx
Normal file
15
src/routes/option-settings-openai.tsx
Normal file
@ -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 (
|
||||||
|
<OptionLayout>
|
||||||
|
<SettingsLayout>
|
||||||
|
<OpenAIApp />
|
||||||
|
</SettingsLayout>
|
||||||
|
</OptionLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OptionOpenAI
|
Loading…
x
Reference in New Issue
Block a user