From b93c10ad4731d5d6d636dbc8c1a3085f13d9c1c2 Mon Sep 17 00:00:00 2001 From: Xavier Michelon Date: Wed, 6 Dec 2023 16:17:53 +0100 Subject: [PATCH] feat(GODT-3121): adds KB suggestion scoring. --- internal/frontend/grpc/event_factory.go | 2 +- internal/kb/kbArticleList.json | 39 ++++++++++++++++-------- internal/kb/suggester.go | 40 ++++++++++++++++++++----- internal/kb/suggester_test.go | 14 ++++++++- 4 files changed, 72 insertions(+), 23 deletions(-) diff --git a/internal/frontend/grpc/event_factory.go b/internal/frontend/grpc/event_factory.go index 3bebe34d..09fde1ef 100644 --- a/internal/frontend/grpc/event_factory.go +++ b/internal/frontend/grpc/event_factory.go @@ -69,7 +69,7 @@ func NewShowMainWindowEvent() *StreamEvent { func NewRequestKnowledgeBaseSuggestionsEvent(suggestions kb.ArticleList) *StreamEvent { s := xslices.Map( suggestions, - func(article kb.Article) *KnowledgeBaseSuggestion { + func(article *kb.Article) *KnowledgeBaseSuggestion { return &KnowledgeBaseSuggestion{Url: article.URL, Title: article.Title} }, ) diff --git a/internal/kb/kbArticleList.json b/internal/kb/kbArticleList.json index d9b819a0..e5f28006 100644 --- a/internal/kb/kbArticleList.json +++ b/internal/kb/kbArticleList.json @@ -6,7 +6,10 @@ "keywords": [ "start", "automatically", - "login" + "login", + "startup", + "start-up", + "boot" ] }, { @@ -14,7 +17,11 @@ "url": "https://proton.me/support/bridge-automatic-update", "title": "Automatic Update and Bridge", "keywords": [ - "TBD" + "update", + "upgrade", + "restart", + "automatic", + "manual" ] }, { @@ -22,7 +29,12 @@ "url": "https://proton.me/support/messages-encrypted-via-bridge", "title": "Are my messages encrypted via Proton Mail Bridge?", "keywords": [ - "TBD" + "encrypted", + "privacy", + "message", + "security", + "gpg", + "pgp" ] }, { @@ -70,7 +82,7 @@ "url": "https://proton.me/support/update-required", "title": "Update required", "keywords": [ - "TBD" + "update", "upgrade", "restart", "reboot" ] }, { @@ -78,7 +90,7 @@ "url": "https://proton.me/support/port-already-occupied-error", "title": "Port already occupied error", "keywords": [ - "TBD" + "Port", "occupied", "1143", "1025", "SMTP", "IMAP", "error" ] }, { @@ -86,7 +98,7 @@ "url": "https://proton.me/support/clients-supported-bridge", "title": "Email clients supported by Proton Mail Bridge", "keywords": [ - "TBD" + "client", "Outlook", "Thunderbird", "Apple Mail", "EM Client", "The Bat", "Eudora", "Postbox" ] }, { @@ -94,7 +106,7 @@ "url": "https://proton.me/support/imap-smtp-and-pop3-setup", "title": "IMAP, SMTP, and POP3 setup", "keywords": [ - "TBD" + "IMAP", "SMTP", "setup" ] }, { @@ -102,7 +114,7 @@ "url": "https://proton.me/support/protonmail-bridge-install", "title": "How to install Proton Mail Bridge", "keywords": [ - "TBD" + "install", "setup", "installer" ] }, { @@ -110,7 +122,7 @@ "url": "https://proton.me/support/bridge-for-linux", "title": "Proton Mail Bridge for Linux", "keywords": [ - "TBD" + "Linux", "Ubuntu", "Fedora", "Debian", "Unix", "deb", "rpm" ] }, { @@ -118,7 +130,8 @@ "url": "https://proton.me/support/operating-systems-supported-bridge", "title": "System requirements for Proton Mail Bridge", "keywords": [ - "TBD" + "requirement", "cpu", "memory", "Windows 7", "Windows XP", "Windows", "Windows 10", "Windows 11","Catalina", "Sonoma", "Ventura", + "Debian", "Ubuntu", "Fedora", "Redhat", "Big Sur", "Monterey" ] }, { @@ -126,7 +139,7 @@ "url": "https://proton.me/support/protonmail-bridge-configure-client", "title": "How to configure your email client for Proton Mail Bridge", "keywords": [ - "TBD" + "Client", "Outlook", "configure", "setup", "IMAP", "SMTP" ] }, { @@ -134,7 +147,7 @@ "url": "https://proton.me/support/invalid-password-error-setting-email-client", "title": "Invalid password error while setting up email client", "keywords": [ - "TBD" + "password", "invalid", "error" ] }, { @@ -142,7 +155,7 @@ "url": "https://proton.me/support/protonmail-bridge-clients-windows-outlook-2019", "title": "Proton Mail Bridge Microsoft Outlook for Windows 2019 setup guide", "keywords": [ - "TBD" + "Outlook", "2019", "setup", "configuration" ] }, { diff --git a/internal/kb/suggester.go b/internal/kb/suggester.go index 42b9af77..ce195acc 100644 --- a/internal/kb/suggester.go +++ b/internal/kb/suggester.go @@ -20,6 +20,11 @@ package kb import ( _ "embed" "encoding/json" + "regexp" + "strings" + + "github.com/bradenaw/juniper/xslices" + "golang.org/x/exp/slices" ) //go:embed kbArticleList.json @@ -31,9 +36,10 @@ type Article struct { URL string `json:"url"` Title string `json:"title"` Keywords []string `json:"keywords"` + Score int } -type ArticleList []Article +type ArticleList []*Article // GetArticleList returns the list of KB articles. func GetArticleList() (ArticleList, error) { @@ -44,17 +50,35 @@ func GetArticleList() (ArticleList, error) { } // GetSuggestions return a list of up to 3 suggestions for KB articles matching the given user input. -func GetSuggestions(_ string) (ArticleList, error) { +func GetSuggestions(userInput string) (ArticleList, error) { + userInput = strings.ToUpper(userInput) articles, err := GetArticleList() if err != nil { return ArticleList{}, err } - // note starting with go 1.21, we will be able to do: - // return articles[:min(3, len(articles))] - l := len(articles) - if l > 3 { - l = 3 + for _, article := range articles { + for _, keyword := range article.Keywords { + if strings.Contains(userInput, strings.ToUpper(keyword)) { + article.Score++ + } + } } - return articles[:l], nil + + articles = xslices.Filter(articles, func(article *Article) bool { return article.Score > 0 }) + slices.SortFunc(articles, func(lhs, rhs *Article) bool { return lhs.Score > rhs.Score }) + + if len(articles) > 3 { + return articles[:3], nil + } + + return articles, nil +} + +func simplifyUserInput(input string) string { + // replace any sequence not matching of the following with a single space: + // - letters in any language (accentuated or not) + // - numbers + // - the apostrophe character ' + return strings.TrimSpace(regexp.MustCompile(`[^\p{L}\p{N}']+`).ReplaceAllString(input, " ")) } diff --git a/internal/kb/suggester_test.go b/internal/kb/suggester_test.go index d6749c30..b3a8347e 100644 --- a/internal/kb/suggester_test.go +++ b/internal/kb/suggester_test.go @@ -18,6 +18,7 @@ package kb import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -39,7 +40,18 @@ func Test_ArticleList(t *testing.T) { } func Test_GetSuggestions(t *testing.T) { - suggestions, err := GetSuggestions("") + suggestions, err := GetSuggestions("Thunderbird is not working, error during password") + require.NoError(t, err) + require.True(t, len(suggestions) <= 3) + for _, article := range suggestions { + fmt.Printf("Score: %v - %#v\n", article.Score, article.Title) + } + + suggestions, err = GetSuggestions("Supercalifragilisticexpialidocious Sesquipedalian Worcestershire") require.NoError(t, err) require.Empty(t, suggestions) } + +func Test_simplifyUserInput(t *testing.T) { + require.Equal(t, "word1 ñóÄ don't déjà 33 pizza", simplifyUserInput(" \nword1 \n\tñóÄ don't\n\n\ndéjà, 33 pizza=🍕\n,\n")) +}