diff --git a/api/system.go b/api/system.go index 927d742b..18c397ce 100644 --- a/api/system.go +++ b/api/system.go @@ -19,6 +19,4 @@ type SystemStatus struct { // Customized server profile, including server name and external url. CustomizedProfile CustomizedProfile `json:"customizedProfile"` StorageServiceID int `json:"storageServiceId"` - // OpenAI API Host - OpenAIAPIHost string `json:"openAIApiHost"` } diff --git a/api/system_setting.go b/api/system_setting.go index 2b271993..d2e039e1 100644 --- a/api/system_setting.go +++ b/api/system_setting.go @@ -27,10 +27,8 @@ const ( SystemSettingCustomizedProfileName SystemSettingName = "customizedProfile" // SystemSettingStorageServiceIDName is the key type of storage service ID. SystemSettingStorageServiceIDName SystemSettingName = "storageServiceId" - // SystemSettingOpenAIAPIKeyName is the key type of OpenAI API key. - SystemSettingOpenAIAPIKeyName SystemSettingName = "openAIApiKey" - // SystemSettingOpenAIAPIHost is the key type of OpenAI API path. - SystemSettingOpenAIAPIHost SystemSettingName = "openAIApiHost" + // SystemSettingOpenAIConfigName is the key type of OpenAI config. + SystemSettingOpenAIConfigName SystemSettingName = "openAIConfig" ) // CustomizedProfile is the struct definition for SystemSettingCustomizedProfileName system setting item. @@ -49,6 +47,11 @@ type CustomizedProfile struct { ExternalURL string `json:"externalUrl"` } +type OpenAIConfig struct { + Key string `json:"key"` + Host string `json:"host"` +} + func (key SystemSettingName) String() string { switch key { case SystemSettingServerID: @@ -67,24 +70,17 @@ func (key SystemSettingName) String() string { return "customizedProfile" case SystemSettingStorageServiceIDName: return "storageServiceId" - case SystemSettingOpenAIAPIKeyName: - return "openAIApiKey" - case SystemSettingOpenAIAPIHost: - return "openAIApiHost" + case SystemSettingOpenAIConfigName: + return "openAIConfig" } return "" } -var ( - SystemSettingAllowSignUpValue = []bool{true, false} - SystemSettingDisablePublicMemosValue = []bool{true, false} -) - type SystemSetting struct { - Name SystemSettingName + Name SystemSettingName `json:"name"` // Value is a JSON string with basic value. - Value string - Description string + Value string `json:"value"` + Description string `json:"description"` } type SystemSettingUpsert struct { @@ -102,35 +98,12 @@ func (upsert SystemSettingUpsert) Validate() error { if err != nil { return fmt.Errorf("failed to unmarshal system setting allow signup value") } - - invalid := true - for _, v := range SystemSettingAllowSignUpValue { - if value == v { - invalid = false - break - } - } - if invalid { - return fmt.Errorf("invalid system setting allow signup value") - } } else if upsert.Name == SystemSettingDisablePublicMemosName { value := false err := json.Unmarshal([]byte(upsert.Value), &value) if err != nil { return fmt.Errorf("failed to unmarshal system setting disable public memos value") } - - invalid := true - for _, v := range SystemSettingDisablePublicMemosValue { - if value == v { - invalid = false - break - } - } - - if invalid { - return fmt.Errorf("invalid system setting disable public memos value") - } } else if upsert.Name == SystemSettingAdditionalStyleName { value := "" err := json.Unmarshal([]byte(upsert.Value), &value) @@ -169,17 +142,11 @@ func (upsert SystemSettingUpsert) Validate() error { return fmt.Errorf("failed to unmarshal system setting storage service id value") } return nil - } else if upsert.Name == SystemSettingOpenAIAPIKeyName { - value := "" - err := json.Unmarshal([]byte(upsert.Value), &value) - if err != nil { - return fmt.Errorf("failed to unmarshal system setting openai api key value") - } - } else if upsert.Name == SystemSettingOpenAIAPIHost { - value := "" + } else if upsert.Name == SystemSettingOpenAIConfigName { + value := OpenAIConfig{} err := json.Unmarshal([]byte(upsert.Value), &value) if err != nil { - return fmt.Errorf("failed to unmarshal system setting openai api host value") + return fmt.Errorf("failed to unmarshal system setting openai api config value") } } else { return fmt.Errorf("invalid system setting name") diff --git a/server/openai.go b/server/openai.go index 114a78c6..17d52f61 100644 --- a/server/openai.go +++ b/server/openai.go @@ -13,39 +13,24 @@ import ( func (s *Server) registerOpenAIRoutes(g *echo.Group) { g.POST("/openai/chat-completion", func(c echo.Context) error { ctx := c.Request().Context() - openAIApiKeySetting, err := s.Store.FindSystemSetting(ctx, &api.SystemSettingFind{ - Name: api.SystemSettingOpenAIAPIKeyName, + openAIConfigSetting, err := s.Store.FindSystemSetting(ctx, &api.SystemSettingFind{ + Name: api.SystemSettingOpenAIConfigName, }) if err != nil && common.ErrorCode(err) != common.NotFound { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find openai api key").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find openai key").SetInternal(err) } - openAIApiHostSetting, err := s.Store.FindSystemSetting(ctx, &api.SystemSettingFind{ - Name: api.SystemSettingOpenAIAPIHost, - }) - if err != nil && common.ErrorCode(err) != common.NotFound { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find openai api host").SetInternal(err) - } - - openAIApiKey := "" - if openAIApiKeySetting != nil { - err = json.Unmarshal([]byte(openAIApiKeySetting.Value), &openAIApiKey) + openAIConfig := api.OpenAIConfig{} + if openAIConfigSetting != nil { + err = json.Unmarshal([]byte(openAIConfigSetting.Value), &openAIConfig) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal system setting value").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal openai system setting value").SetInternal(err) } } - if openAIApiKey == "" { + if openAIConfig.Key == "" { return echo.NewHTTPError(http.StatusBadRequest, "OpenAI API key not set") } - openAIApiHost := "" - if openAIApiHostSetting != nil { - err = json.Unmarshal([]byte(openAIApiHostSetting.Value), &openAIApiHost) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal system setting value").SetInternal(err) - } - } - completionRequest := api.OpenAICompletionRequest{} if err := json.NewDecoder(c.Request().Body).Decode(&completionRequest); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post chat completion request").SetInternal(err) @@ -54,7 +39,7 @@ func (s *Server) registerOpenAIRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusBadRequest, "Prompt is required") } - result, err := openai.PostChatCompletion(completionRequest.Prompt, openAIApiKey, openAIApiHost) + result, err := openai.PostChatCompletion(completionRequest.Prompt, openAIConfig.Key, openAIConfig.Host) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to post chat completion").SetInternal(err) } @@ -64,39 +49,24 @@ func (s *Server) registerOpenAIRoutes(g *echo.Group) { g.POST("/openai/text-completion", func(c echo.Context) error { ctx := c.Request().Context() - openAIApiKeySetting, err := s.Store.FindSystemSetting(ctx, &api.SystemSettingFind{ - Name: api.SystemSettingOpenAIAPIKeyName, - }) - if err != nil && common.ErrorCode(err) != common.NotFound { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find openai api key").SetInternal(err) - } - - openAIApiHostSetting, err := s.Store.FindSystemSetting(ctx, &api.SystemSettingFind{ - Name: api.SystemSettingOpenAIAPIHost, + openAIConfigSetting, err := s.Store.FindSystemSetting(ctx, &api.SystemSettingFind{ + Name: api.SystemSettingOpenAIConfigName, }) if err != nil && common.ErrorCode(err) != common.NotFound { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find openai api host").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find openai key").SetInternal(err) } - openAIApiKey := "" - if openAIApiKeySetting != nil { - err = json.Unmarshal([]byte(openAIApiKeySetting.Value), &openAIApiKey) + openAIConfig := api.OpenAIConfig{} + if openAIConfigSetting != nil { + err = json.Unmarshal([]byte(openAIConfigSetting.Value), &openAIConfig) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal system setting value").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal openai system setting value").SetInternal(err) } } - if openAIApiKey == "" { + if openAIConfig.Key == "" { return echo.NewHTTPError(http.StatusBadRequest, "OpenAI API key not set") } - openAIApiHost := "" - if openAIApiHostSetting != nil { - err = json.Unmarshal([]byte(openAIApiHostSetting.Value), &openAIApiHost) - if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal system setting value").SetInternal(err) - } - } - textCompletion := api.OpenAICompletionRequest{} if err := json.NewDecoder(c.Request().Body).Decode(&textCompletion); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post text completion request").SetInternal(err) @@ -105,7 +75,7 @@ func (s *Server) registerOpenAIRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusBadRequest, "Prompt is required") } - result, err := openai.PostTextCompletion(textCompletion.Prompt, openAIApiKey, openAIApiHost) + result, err := openai.PostTextCompletion(textCompletion.Prompt, openAIConfig.Key, openAIConfig.Host) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to post text completion").SetInternal(err) } @@ -115,21 +85,24 @@ func (s *Server) registerOpenAIRoutes(g *echo.Group) { g.GET("/openai/enabled", func(c echo.Context) error { ctx := c.Request().Context() - openAIApiKeySetting, err := s.Store.FindSystemSetting(ctx, &api.SystemSettingFind{ - Name: api.SystemSettingOpenAIAPIKeyName, + openAIConfigSetting, err := s.Store.FindSystemSetting(ctx, &api.SystemSettingFind{ + Name: api.SystemSettingOpenAIConfigName, }) if err != nil && common.ErrorCode(err) != common.NotFound { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find openai api key").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find openai key").SetInternal(err) } - openAIApiKey := "" - if openAIApiKeySetting != nil { - err = json.Unmarshal([]byte(openAIApiKeySetting.Value), &openAIApiKey) + openAIConfig := api.OpenAIConfig{} + if openAIConfigSetting != nil { + err = json.Unmarshal([]byte(openAIConfigSetting.Value), &openAIConfig) if err != nil { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal system setting value").SetInternal(err) + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal openai system setting value").SetInternal(err) } } + if openAIConfig.Key == "" { + return echo.NewHTTPError(http.StatusBadRequest, "OpenAI API key not set") + } - return c.JSON(http.StatusOK, composeResponse(openAIApiKey != "")) + return c.JSON(http.StatusOK, composeResponse(openAIConfig.Key != "")) }) } diff --git a/server/system.go b/server/system.go index f87a1557..0574ee6f 100644 --- a/server/system.go +++ b/server/system.go @@ -52,7 +52,6 @@ func (s *Server) registerSystemRoutes(g *echo.Group) { ExternalURL: "", }, StorageServiceID: 0, - OpenAIAPIHost: "", } systemSettingList, err := s.Store.FindSystemSettingList(ctx, &api.SystemSettingFind{}) @@ -60,49 +59,33 @@ func (s *Server) registerSystemRoutes(g *echo.Group) { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find system setting list").SetInternal(err) } for _, systemSetting := range systemSettingList { - if systemSetting.Name == api.SystemSettingServerID || systemSetting.Name == api.SystemSettingSecretSessionName || systemSetting.Name == api.SystemSettingOpenAIAPIKeyName { + if systemSetting.Name == api.SystemSettingServerID || systemSetting.Name == api.SystemSettingSecretSessionName || systemSetting.Name == api.SystemSettingOpenAIConfigName { continue } - var value interface{} - err := json.Unmarshal([]byte(systemSetting.Value), &value) + var baseValue interface{} + err := json.Unmarshal([]byte(systemSetting.Value), &baseValue) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal system setting value").SetInternal(err) } if systemSetting.Name == api.SystemSettingAllowSignUpName { - systemStatus.AllowSignUp = value.(bool) + systemStatus.AllowSignUp = baseValue.(bool) } else if systemSetting.Name == api.SystemSettingDisablePublicMemosName { - systemStatus.DisablePublicMemos = value.(bool) + systemStatus.DisablePublicMemos = baseValue.(bool) } else if systemSetting.Name == api.SystemSettingAdditionalStyleName { - systemStatus.AdditionalStyle = value.(string) + systemStatus.AdditionalStyle = baseValue.(string) } else if systemSetting.Name == api.SystemSettingAdditionalScriptName { - systemStatus.AdditionalScript = value.(string) + systemStatus.AdditionalScript = baseValue.(string) } else if systemSetting.Name == api.SystemSettingCustomizedProfileName { - valueMap := value.(map[string]interface{}) - systemStatus.CustomizedProfile = api.CustomizedProfile{} - if v := valueMap["name"]; v != nil { - systemStatus.CustomizedProfile.Name = v.(string) - } - if v := valueMap["logoUrl"]; v != nil { - systemStatus.CustomizedProfile.LogoURL = v.(string) - } - if v := valueMap["description"]; v != nil { - systemStatus.CustomizedProfile.Description = v.(string) - } - if v := valueMap["locale"]; v != nil { - systemStatus.CustomizedProfile.Locale = v.(string) - } - if v := valueMap["appearance"]; v != nil { - systemStatus.CustomizedProfile.Appearance = v.(string) - } - if v := valueMap["externalUrl"]; v != nil { - systemStatus.CustomizedProfile.ExternalURL = v.(string) + customizedProfile := api.CustomizedProfile{} + err := json.Unmarshal([]byte(systemSetting.Value), &customizedProfile) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal system setting customized profile value").SetInternal(err) } + systemStatus.CustomizedProfile = customizedProfile } else if systemSetting.Name == api.SystemSettingStorageServiceIDName { - systemStatus.StorageServiceID = int(value.(float64)) - } else if systemSetting.Name == api.SystemSettingOpenAIAPIHost { - systemStatus.OpenAIAPIHost = value.(string) + systemStatus.StorageServiceID = int(baseValue.(float64)) } } diff --git a/web/src/components/Settings/SystemSection.tsx b/web/src/components/Settings/SystemSection.tsx index b3459ed5..22af9036 100644 --- a/web/src/components/Settings/SystemSection.tsx +++ b/web/src/components/Settings/SystemSection.tsx @@ -5,16 +5,12 @@ import { Button, Divider, Input, Switch, Textarea } from "@mui/joy"; import { useGlobalStore } from "../../store/module"; import * as api from "../../helpers/api"; import showUpdateCustomizedProfileDialog from "../UpdateCustomizedProfileDialog"; -import { useAppDispatch } from "../../store"; -import { setGlobalState } from "../../store/reducer/global"; import "@/less/settings/system-section.less"; interface State { dbSize: number; allowSignUp: boolean; disablePublicMemos: boolean; - openAIApiKey: string; - openAIApiHost: string; additionalStyle: string; additionalScript: string; } @@ -36,13 +32,13 @@ const SystemSection = () => { dbSize: systemStatus.dbSize, allowSignUp: systemStatus.allowSignUp, additionalStyle: systemStatus.additionalStyle, - openAIApiKey: "", - openAIApiHost: systemStatus.openAIApiHost, additionalScript: systemStatus.additionalScript, disablePublicMemos: systemStatus.disablePublicMemos, }); - - const dispatch = useAppDispatch(); + const [openAIConfig, setOpenAIConfig] = useState({ + key: "", + host: "", + }); useEffect(() => { globalStore.fetchSystemStatus(); @@ -50,16 +46,24 @@ const SystemSection = () => { useEffect(() => { setState({ + ...state, dbSize: systemStatus.dbSize, allowSignUp: systemStatus.allowSignUp, additionalStyle: systemStatus.additionalStyle, - openAIApiKey: "", - openAIApiHost: systemStatus.openAIApiHost, additionalScript: systemStatus.additionalScript, disablePublicMemos: systemStatus.disablePublicMemos, }); }, [systemStatus]); + useEffect(() => { + api.getSystemSetting().then(({ data: { data: systemSettings } }) => { + const openAIConfigSetting = systemSettings.find((setting) => setting.name === "openAIConfig"); + if (openAIConfigSetting) { + setOpenAIConfig(JSON.parse(openAIConfigSetting.value)); + } + }); + }, []); + const handleAllowSignUpChanged = async (value: boolean) => { setState({ ...state, @@ -86,46 +90,33 @@ const SystemSection = () => { toast.success(t("message.succeed-vacuum-database")); }; - const handleOpenAIApiKeyChanged = (value: string) => { - setState({ - ...state, - openAIApiKey: value, + const handleOpenAIConfigKeyChanged = (value: string) => { + setOpenAIConfig({ + ...openAIConfig, + key: value, }); }; - const handleSaveOpenAIApiKey = async () => { + const handleSaveOpenAIConfig = async () => { try { await api.upsertSystemSetting({ - name: "openAIApiKey", - value: JSON.stringify(state.openAIApiKey), + name: "openAIConfig", + value: JSON.stringify(openAIConfig), }); } catch (error) { console.error(error); return; } - toast.success("OpenAI Api Key updated"); + toast.success("OpenAI Config updated"); }; - const handleOpenAIApiHostChanged = (value: string) => { - setState({ - ...state, - openAIApiHost: value, + const handleOpenAIConfigHostChanged = (value: string) => { + setOpenAIConfig({ + ...openAIConfig, + host: value, }); }; - const handleSaveOpenAIApiHost = async () => { - try { - await api.upsertSystemSetting({ - name: "openAIApiHost", - value: JSON.stringify(state.openAIApiHost), - }); - } catch (error) { - console.error(error); - return; - } - toast.success("OpenAI Api Host updated"); - }; - const handleAdditionalStyleChanged = (value: string) => { setState({ ...state, @@ -171,8 +162,7 @@ const SystemSection = () => { ...state, disablePublicMemos: value, }); - // Update global store immediately as MemoEditor/Selector is dependent on this value. - dispatch(setGlobalState({ systemStatus: { ...systemStatus, disablePublicMemos: value } })); + globalStore.setSystemStatus({ disablePublicMemos: value }); await api.upsertSystemSetting({ name: "disablePublicMemos", value: JSON.stringify(value), @@ -206,7 +196,7 @@ const SystemSection = () => {
OpenAI API Key - +
{ fontSize: "14px", }} placeholder="Write only" - value={state.openAIApiKey} - onChange={(event) => handleOpenAIApiKeyChanged(event.target.value)} + value={openAIConfig.key} + onChange={(event) => handleOpenAIConfigKeyChanged(event.target.value)} />
OpenAI API Host -
{ fontSize: "14px", }} placeholder="OpenAI Host. Default: https://api.openai.com" - value={state.openAIApiHost} - onChange={(event) => handleOpenAIApiHostChanged(event.target.value)} + value={openAIConfig.host} + onChange={(event) => handleOpenAIConfigHostChanged(event.target.value)} />
diff --git a/web/src/helpers/api.ts b/web/src/helpers/api.ts index 28602074..7b4dfa8e 100644 --- a/web/src/helpers/api.ts +++ b/web/src/helpers/api.ts @@ -10,6 +10,10 @@ export function getSystemStatus() { return axios.get>("/api/status"); } +export function getSystemSetting() { + return axios.get>("/api/system/setting"); +} + export function upsertSystemSetting(systemSetting: SystemSetting) { return axios.post>("/api/system/setting", systemSetting); } diff --git a/web/src/store/module/global.ts b/web/src/store/module/global.ts index 62aa965c..d59ec806 100644 --- a/web/src/store/module/global.ts +++ b/web/src/store/module/global.ts @@ -22,7 +22,6 @@ export const initialGlobalState = async () => { appearance: "system", externalUrl: "", }, - openAIApiHost: "", } as SystemStatus, }; @@ -75,6 +74,16 @@ export const useGlobalStore = () => { store.dispatch(setGlobalState({ systemStatus: systemStatus })); return systemStatus; }, + setSystemStatus: (systemStatus: Partial) => { + store.dispatch( + setGlobalState({ + systemStatus: { + ...state.systemStatus, + ...systemStatus, + }, + }) + ); + }, setLocale: (locale: Locale) => { store.dispatch(setLocale(locale)); }, diff --git a/web/src/types/modules/system.d.ts b/web/src/types/modules/system.d.ts index bc7b602b..8e95b107 100644 --- a/web/src/types/modules/system.d.ts +++ b/web/src/types/modules/system.d.ts @@ -12,6 +12,11 @@ interface CustomizedProfile { externalUrl: string; } +interface OpenAIConfig { + key: string; + host: string; +} + interface SystemStatus { host?: User; profile: Profile; @@ -23,7 +28,6 @@ interface SystemStatus { additionalScript: string; customizedProfile: CustomizedProfile; storageServiceId: number; - openAIApiHost: string; } interface SystemSetting {