package v1 import ( "encoding/json" "net/http" "time" echosse "github.com/CorrectRoadH/echo-sse" "github.com/PullRequestInc/go-gpt3" "github.com/labstack/echo/v4" "github.com/usememos/memos/plugin/openai" "github.com/usememos/memos/store" ) func (s *APIV1Service) registerOpenAIRoutes(g *echo.Group) { g.POST("/openai/chat-completion", func(c echo.Context) error { ctx := c.Request().Context() openAIConfigSetting, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{ Name: SystemSettingOpenAIConfigName.String(), }) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find openai key").SetInternal(err) } openAIConfig := OpenAIConfig{} if openAIConfigSetting != nil { err = json.Unmarshal([]byte(openAIConfigSetting.Value), &openAIConfig) if err != nil { 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") } messages := []openai.ChatCompletionMessage{} if err := json.NewDecoder(c.Request().Body).Decode(&messages); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post chat completion request").SetInternal(err) } if len(messages) == 0 { return echo.NewHTTPError(http.StatusBadRequest, "No messages provided") } result, err := openai.PostChatCompletion(messages, openAIConfig.Key, openAIConfig.Host) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to post chat completion").SetInternal(err) } return c.JSON(http.StatusOK, result) }) g.POST("/openai/chat-streaming", func(c echo.Context) error { messages := []gpt3.ChatCompletionRequestMessage{} if err := json.NewDecoder(c.Request().Body).Decode(&messages); err != nil { return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post chat completion request").SetInternal(err) } if len(messages) == 0 { return echo.NewHTTPError(http.StatusBadRequest, "No messages provided") } ctx := c.Request().Context() openAIConfigSetting, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{ Name: SystemSettingOpenAIConfigName.String(), }) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find openai key").SetInternal(err) } openAIConfig := OpenAIConfig{} if openAIConfigSetting != nil { err = json.Unmarshal([]byte(openAIConfigSetting.Value), &openAIConfig) if err != nil { 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") } sse := echosse.NewSSEClint(c) // to do these things in server may not elegant. // But move it to openai plugin will break the simple. Because it is a streaming. We must use a channel to do it. // And we can think it is a forward proxy. So it in here is not a bad idea. client := gpt3.NewClient(openAIConfig.Key) err = client.ChatCompletionStream(ctx, gpt3.ChatCompletionRequest{ Model: gpt3.GPT3Dot5Turbo, Messages: messages, Stream: true, }, func(resp *gpt3.ChatCompletionStreamResponse) { // _ is for to pass the golangci-lint check _ = sse.SendEvent(resp.Choices[0].Delta.Content) // to delay 0.5 s time.Sleep(50 * time.Millisecond) // the delay is a very good way to make the chatbot more comfortable // otherwise the chatbot will reply too fast. Believe me it is not good.🤔 }) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to chat with OpenAI").SetInternal(err) } return nil }) g.GET("/openai/enabled", func(c echo.Context) error { ctx := c.Request().Context() openAIConfigSetting, err := s.Store.GetSystemSetting(ctx, &store.FindSystemSetting{ Name: SystemSettingOpenAIConfigName.String(), }) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "Failed to find openai key").SetInternal(err) } openAIConfig := OpenAIConfig{} if openAIConfigSetting != nil { err = json.Unmarshal([]byte(openAIConfigSetting.Value), &openAIConfig) if err != nil { 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, openAIConfig.Key != "") }) }