feat(GODT-3121): adds KB suggestion scoring.

This commit is contained in:
Xavier Michelon 2023-12-06 16:17:53 +01:00
parent 3309137b80
commit b93c10ad47
4 changed files with 72 additions and 23 deletions

View File

@ -69,7 +69,7 @@ func NewShowMainWindowEvent() *StreamEvent {
func NewRequestKnowledgeBaseSuggestionsEvent(suggestions kb.ArticleList) *StreamEvent { func NewRequestKnowledgeBaseSuggestionsEvent(suggestions kb.ArticleList) *StreamEvent {
s := xslices.Map( s := xslices.Map(
suggestions, suggestions,
func(article kb.Article) *KnowledgeBaseSuggestion { func(article *kb.Article) *KnowledgeBaseSuggestion {
return &KnowledgeBaseSuggestion{Url: article.URL, Title: article.Title} return &KnowledgeBaseSuggestion{Url: article.URL, Title: article.Title}
}, },
) )

View File

@ -6,7 +6,10 @@
"keywords": [ "keywords": [
"start", "start",
"automatically", "automatically",
"login" "login",
"startup",
"start-up",
"boot"
] ]
}, },
{ {
@ -14,7 +17,11 @@
"url": "https://proton.me/support/bridge-automatic-update", "url": "https://proton.me/support/bridge-automatic-update",
"title": "Automatic Update and Bridge", "title": "Automatic Update and Bridge",
"keywords": [ "keywords": [
"TBD" "update",
"upgrade",
"restart",
"automatic",
"manual"
] ]
}, },
{ {
@ -22,7 +29,12 @@
"url": "https://proton.me/support/messages-encrypted-via-bridge", "url": "https://proton.me/support/messages-encrypted-via-bridge",
"title": "Are my messages encrypted via Proton Mail Bridge?", "title": "Are my messages encrypted via Proton Mail Bridge?",
"keywords": [ "keywords": [
"TBD" "encrypted",
"privacy",
"message",
"security",
"gpg",
"pgp"
] ]
}, },
{ {
@ -70,7 +82,7 @@
"url": "https://proton.me/support/update-required", "url": "https://proton.me/support/update-required",
"title": "Update required", "title": "Update required",
"keywords": [ "keywords": [
"TBD" "update", "upgrade", "restart", "reboot"
] ]
}, },
{ {
@ -78,7 +90,7 @@
"url": "https://proton.me/support/port-already-occupied-error", "url": "https://proton.me/support/port-already-occupied-error",
"title": "Port already occupied error", "title": "Port already occupied error",
"keywords": [ "keywords": [
"TBD" "Port", "occupied", "1143", "1025", "SMTP", "IMAP", "error"
] ]
}, },
{ {
@ -86,7 +98,7 @@
"url": "https://proton.me/support/clients-supported-bridge", "url": "https://proton.me/support/clients-supported-bridge",
"title": "Email clients supported by Proton Mail Bridge", "title": "Email clients supported by Proton Mail Bridge",
"keywords": [ "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", "url": "https://proton.me/support/imap-smtp-and-pop3-setup",
"title": "IMAP, SMTP, and POP3 setup", "title": "IMAP, SMTP, and POP3 setup",
"keywords": [ "keywords": [
"TBD" "IMAP", "SMTP", "setup"
] ]
}, },
{ {
@ -102,7 +114,7 @@
"url": "https://proton.me/support/protonmail-bridge-install", "url": "https://proton.me/support/protonmail-bridge-install",
"title": "How to install Proton Mail Bridge", "title": "How to install Proton Mail Bridge",
"keywords": [ "keywords": [
"TBD" "install", "setup", "installer"
] ]
}, },
{ {
@ -110,7 +122,7 @@
"url": "https://proton.me/support/bridge-for-linux", "url": "https://proton.me/support/bridge-for-linux",
"title": "Proton Mail Bridge for Linux", "title": "Proton Mail Bridge for Linux",
"keywords": [ "keywords": [
"TBD" "Linux", "Ubuntu", "Fedora", "Debian", "Unix", "deb", "rpm"
] ]
}, },
{ {
@ -118,7 +130,8 @@
"url": "https://proton.me/support/operating-systems-supported-bridge", "url": "https://proton.me/support/operating-systems-supported-bridge",
"title": "System requirements for Proton Mail Bridge", "title": "System requirements for Proton Mail Bridge",
"keywords": [ "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", "url": "https://proton.me/support/protonmail-bridge-configure-client",
"title": "How to configure your email client for Proton Mail Bridge", "title": "How to configure your email client for Proton Mail Bridge",
"keywords": [ "keywords": [
"TBD" "Client", "Outlook", "configure", "setup", "IMAP", "SMTP"
] ]
}, },
{ {
@ -134,7 +147,7 @@
"url": "https://proton.me/support/invalid-password-error-setting-email-client", "url": "https://proton.me/support/invalid-password-error-setting-email-client",
"title": "Invalid password error while setting up email client", "title": "Invalid password error while setting up email client",
"keywords": [ "keywords": [
"TBD" "password", "invalid", "error"
] ]
}, },
{ {
@ -142,7 +155,7 @@
"url": "https://proton.me/support/protonmail-bridge-clients-windows-outlook-2019", "url": "https://proton.me/support/protonmail-bridge-clients-windows-outlook-2019",
"title": "Proton Mail Bridge Microsoft Outlook for Windows 2019 setup guide", "title": "Proton Mail Bridge Microsoft Outlook for Windows 2019 setup guide",
"keywords": [ "keywords": [
"TBD" "Outlook", "2019", "setup", "configuration"
] ]
}, },
{ {

View File

@ -20,6 +20,11 @@ package kb
import ( import (
_ "embed" _ "embed"
"encoding/json" "encoding/json"
"regexp"
"strings"
"github.com/bradenaw/juniper/xslices"
"golang.org/x/exp/slices"
) )
//go:embed kbArticleList.json //go:embed kbArticleList.json
@ -31,9 +36,10 @@ type Article struct {
URL string `json:"url"` URL string `json:"url"`
Title string `json:"title"` Title string `json:"title"`
Keywords []string `json:"keywords"` Keywords []string `json:"keywords"`
Score int
} }
type ArticleList []Article type ArticleList []*Article
// GetArticleList returns the list of KB articles. // GetArticleList returns the list of KB articles.
func GetArticleList() (ArticleList, error) { 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. // 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() articles, err := GetArticleList()
if err != nil { if err != nil {
return ArticleList{}, err return ArticleList{}, err
} }
// note starting with go 1.21, we will be able to do: for _, article := range articles {
// return articles[:min(3, len(articles))] for _, keyword := range article.Keywords {
l := len(articles) if strings.Contains(userInput, strings.ToUpper(keyword)) {
if l > 3 { article.Score++
l = 3 }
}
} }
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, " "))
} }

View File

@ -18,6 +18,7 @@
package kb package kb
import ( import (
"fmt"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -39,7 +40,18 @@ func Test_ArticleList(t *testing.T) {
} }
func Test_GetSuggestions(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.NoError(t, err)
require.Empty(t, suggestions) 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"))
}