refactor: openAI config system setting (#1333)

pull/1334/head
boojack 2 years ago committed by GitHub
parent 7c92805aac
commit e5cbb8cd56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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"`
}

@ -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")

@ -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 != ""))
})
}

@ -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))
}
}

@ -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<OpenAIConfig>({
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 = () => {
<Divider className="!mt-3 !my-4" />
<div className="form-label">
<span className="normal-text">OpenAI API Key</span>
<Button onClick={handleSaveOpenAIApiKey}>{t("common.save")}</Button>
<Button onClick={handleSaveOpenAIConfig}>{t("common.save")}</Button>
</div>
<Input
className="w-full"
@ -215,12 +205,11 @@ const SystemSection = () => {
fontSize: "14px",
}}
placeholder="Write only"
value={state.openAIApiKey}
onChange={(event) => handleOpenAIApiKeyChanged(event.target.value)}
value={openAIConfig.key}
onChange={(event) => handleOpenAIConfigKeyChanged(event.target.value)}
/>
<div className="form-label mt-2">
<span className="normal-text">OpenAI API Host</span>
<Button onClick={handleSaveOpenAIApiHost}>{t("common.save")}</Button>
</div>
<Input
className="w-full"
@ -229,8 +218,8 @@ const SystemSection = () => {
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)}
/>
<Divider className="!mt-3 !my-4" />
<div className="form-label">

@ -10,6 +10,10 @@ export function getSystemStatus() {
return axios.get<ResponseObject<SystemStatus>>("/api/status");
}
export function getSystemSetting() {
return axios.get<ResponseObject<SystemSetting[]>>("/api/system/setting");
}
export function upsertSystemSetting(systemSetting: SystemSetting) {
return axios.post<ResponseObject<SystemSetting>>("/api/system/setting", systemSetting);
}

@ -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<SystemStatus>) => {
store.dispatch(
setGlobalState({
systemStatus: {
...state.systemStatus,
...systemStatus,
},
})
);
},
setLocale: (locale: Locale) => {
store.dispatch(setLocale(locale));
},

@ -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 {

Loading…
Cancel
Save