mirror of
https://github.com/usememos/memos.git
synced 2024-11-24 06:35:24 +03:00
feat: support set openai api host (#1292)
* Docker * feat: support set openai api host * fix css * fix eslint * use API in backend & put host check in plugin/openai * fix go-static-checks
This commit is contained in:
parent
fd99c5461c
commit
003161ea54
@ -19,4 +19,6 @@ 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"`
|
||||
}
|
||||
|
@ -29,6 +29,8 @@ const (
|
||||
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"
|
||||
)
|
||||
|
||||
// CustomizedProfile is the struct definition for SystemSettingCustomizedProfileName system setting item.
|
||||
@ -67,6 +69,8 @@ func (key SystemSettingName) String() string {
|
||||
return "storageServiceId"
|
||||
case SystemSettingOpenAIAPIKeyName:
|
||||
return "openAIApiKey"
|
||||
case SystemSettingOpenAIAPIHost:
|
||||
return "openAIApiHost"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@ -171,6 +175,12 @@ func (upsert SystemSettingUpsert) Validate() error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal system setting openai api key value")
|
||||
}
|
||||
} else if upsert.Name == SystemSettingOpenAIAPIHost {
|
||||
value := ""
|
||||
err := json.Unmarshal([]byte(upsert.Value), &value)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal system setting openai api host value")
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("invalid system setting name")
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -23,12 +24,20 @@ type ChatCompletionResponse struct {
|
||||
Choices []ChatCompletionChoice `json:"choices"`
|
||||
}
|
||||
|
||||
func PostChatCompletion(prompt string, apiKey string) (string, error) {
|
||||
func PostChatCompletion(prompt string, apiKey string, apiHost string) (string, error) {
|
||||
requestBody := strings.NewReader(`{
|
||||
"model": "gpt-3.5-turbo",
|
||||
"messages": [{"role": "user", "content": "` + prompt + `"}]
|
||||
}`)
|
||||
req, err := http.NewRequest("POST", "https://api.openai.com/v1/chat/completions", requestBody)
|
||||
if apiHost == "" {
|
||||
apiHost = "https://api.openai.com"
|
||||
}
|
||||
url, err := url.JoinPath(apiHost, "/v1/chat/completions")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, requestBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@ -18,7 +19,7 @@ type TextCompletionResponse struct {
|
||||
Choices []TextCompletionChoice `json:"choices"`
|
||||
}
|
||||
|
||||
func PostTextCompletion(prompt string, apiKey string) (string, error) {
|
||||
func PostTextCompletion(prompt string, apiKey string, apiHost string) (string, error) {
|
||||
requestBody := strings.NewReader(`{
|
||||
"prompt": "` + prompt + `",
|
||||
"temperature": 0.5,
|
||||
@ -26,7 +27,15 @@ func PostTextCompletion(prompt string, apiKey string) (string, error) {
|
||||
"n": 1,
|
||||
"stop": "."
|
||||
}`)
|
||||
req, err := http.NewRequest("POST", "https://api.openai.com/v1/completions", requestBody)
|
||||
if apiHost == "" {
|
||||
apiHost = "https://api.openai.com"
|
||||
}
|
||||
url, err := url.JoinPath(apiHost, "/v1/chat/completions")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, requestBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -20,6 +20,13 @@ func (s *Server) registerOpenAIRoutes(g *echo.Group) {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find openai api 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)
|
||||
@ -31,6 +38,14 @@ func (s *Server) registerOpenAIRoutes(g *echo.Group) {
|
||||
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)
|
||||
@ -39,7 +54,7 @@ func (s *Server) registerOpenAIRoutes(g *echo.Group) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Prompt is required")
|
||||
}
|
||||
|
||||
result, err := openai.PostChatCompletion(completionRequest.Prompt, openAIApiKey)
|
||||
result, err := openai.PostChatCompletion(completionRequest.Prompt, openAIApiKey, openAIApiHost)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to post chat completion").SetInternal(err)
|
||||
}
|
||||
@ -56,6 +71,13 @@ func (s *Server) registerOpenAIRoutes(g *echo.Group) {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find openai api 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)
|
||||
@ -67,6 +89,14 @@ func (s *Server) registerOpenAIRoutes(g *echo.Group) {
|
||||
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)
|
||||
@ -75,7 +105,7 @@ func (s *Server) registerOpenAIRoutes(g *echo.Group) {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Prompt is required")
|
||||
}
|
||||
|
||||
result, err := openai.PostTextCompletion(textCompletion.Prompt, openAIApiKey)
|
||||
result, err := openai.PostTextCompletion(textCompletion.Prompt, openAIApiKey, openAIApiHost)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to post text completion").SetInternal(err)
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
|
||||
ExternalURL: "",
|
||||
},
|
||||
StorageServiceID: 0,
|
||||
OpenAIAPIHost: "",
|
||||
}
|
||||
|
||||
systemSettingList, err := s.Store.FindSystemSettingList(ctx, &api.SystemSettingFind{})
|
||||
@ -100,6 +101,8 @@ func (s *Server) registerSystemRoutes(g *echo.Group) {
|
||||
}
|
||||
} else if systemSetting.Name == api.SystemSettingStorageServiceIDName {
|
||||
systemStatus.StorageServiceID = int(value.(float64))
|
||||
} else if systemSetting.Name == api.SystemSettingOpenAIAPIHost {
|
||||
systemStatus.OpenAIAPIHost = value.(string)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@ interface State {
|
||||
allowSignUp: boolean;
|
||||
disablePublicMemos: boolean;
|
||||
openAIApiKey: string;
|
||||
openAIApiHost: string;
|
||||
additionalStyle: string;
|
||||
additionalScript: string;
|
||||
}
|
||||
@ -36,6 +37,7 @@ const SystemSection = () => {
|
||||
allowSignUp: systemStatus.allowSignUp,
|
||||
additionalStyle: systemStatus.additionalStyle,
|
||||
openAIApiKey: "",
|
||||
openAIApiHost: systemStatus.openAIApiHost,
|
||||
additionalScript: systemStatus.additionalScript,
|
||||
disablePublicMemos: systemStatus.disablePublicMemos,
|
||||
});
|
||||
@ -52,6 +54,7 @@ const SystemSection = () => {
|
||||
allowSignUp: systemStatus.allowSignUp,
|
||||
additionalStyle: systemStatus.additionalStyle,
|
||||
openAIApiKey: "",
|
||||
openAIApiHost: systemStatus.openAIApiHost,
|
||||
additionalScript: systemStatus.additionalScript,
|
||||
disablePublicMemos: systemStatus.disablePublicMemos,
|
||||
});
|
||||
@ -103,6 +106,26 @@ const SystemSection = () => {
|
||||
toastHelper.success("OpenAI Api Key updated");
|
||||
};
|
||||
|
||||
const handleOpenAIApiHostChanged = (value: string) => {
|
||||
setState({
|
||||
...state,
|
||||
openAIApiHost: value,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSaveOpenAIApiHost = async () => {
|
||||
try {
|
||||
await api.upsertSystemSetting({
|
||||
name: "openAIApiHost",
|
||||
value: JSON.stringify(state.openAIApiHost),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
toastHelper.success("OpenAI Api Host updated");
|
||||
};
|
||||
|
||||
const handleAdditionalStyleChanged = (value: string) => {
|
||||
setState({
|
||||
...state,
|
||||
@ -195,6 +218,20 @@ const SystemSection = () => {
|
||||
value={state.openAIApiKey}
|
||||
onChange={(event) => handleOpenAIApiKeyChanged(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"
|
||||
sx={{
|
||||
fontFamily: "monospace",
|
||||
fontSize: "14px",
|
||||
}}
|
||||
placeholder="OpenAI Host. Default: https://api.openai.com"
|
||||
value={state.openAIApiHost}
|
||||
onChange={(event) => handleOpenAIApiHostChanged(event.target.value)}
|
||||
/>
|
||||
<Divider className="!mt-3 !my-4" />
|
||||
<div className="form-label">
|
||||
<span className="normal-text">{t("setting.system-section.additional-style")}</span>
|
||||
|
@ -22,6 +22,7 @@ export const initialGlobalState = async () => {
|
||||
appearance: "system",
|
||||
externalUrl: "",
|
||||
},
|
||||
openAIApiHost: "",
|
||||
} as SystemStatus,
|
||||
};
|
||||
|
||||
|
1
web/src/types/modules/system.d.ts
vendored
1
web/src/types/modules/system.d.ts
vendored
@ -23,6 +23,7 @@ interface SystemStatus {
|
||||
additionalScript: string;
|
||||
customizedProfile: CustomizedProfile;
|
||||
storageServiceId: number;
|
||||
openAIApiHost: string;
|
||||
}
|
||||
|
||||
interface SystemSetting {
|
||||
|
Loading…
Reference in New Issue
Block a user