Files
isphere-ai-bridge/internal/isphere/contacts.go
2026-07-10 09:39:18 +08:00

114 lines
2.6 KiB
Go

package isphere
import (
"sort"
"strings"
)
type Contact struct {
ContactID string
DisplayName string
Account string
Source string
Confidence float64
RawRef string
}
type SearchContactsQuery struct {
Query string
Limit int
}
type SearchContactsResult struct {
Contacts []Contact
}
func SearchContactsFromMessages(messages []Message, query SearchContactsQuery) SearchContactsResult {
unique := map[string]Contact{}
for _, message := range messages {
addMessageContact(unique, message.SenderID)
addMessageContact(unique, message.ReceiverID)
}
queryText := normalizedSearchText(query.Query)
contacts := make([]Contact, 0, len(unique))
for _, contact := range unique {
if contactMatchesQuery(contact, queryText) {
contacts = append(contacts, contact)
}
}
sort.Slice(contacts, func(i, j int) bool {
leftRank := contactSearchRank(contacts[i], queryText)
rightRank := contactSearchRank(contacts[j], queryText)
if leftRank != rightRank {
return leftRank < rightRank
}
leftID := strings.ToLower(contacts[i].ContactID)
rightID := strings.ToLower(contacts[j].ContactID)
if leftID != rightID {
return leftID < rightID
}
return contacts[i].ContactID < contacts[j].ContactID
})
limit := query.Limit
if limit <= 0 || limit > len(contacts) {
limit = len(contacts)
}
return SearchContactsResult{Contacts: contacts[:limit]}
}
func addMessageContact(unique map[string]Contact, id string) {
trimmed := strings.TrimSpace(id)
if trimmed == "" {
return
}
key := strings.ToLower(trimmed)
if _, exists := unique[key]; exists {
return
}
unique[key] = Contact{
ContactID: trimmed,
DisplayName: trimmed,
Account: trimmed,
Source: "local_readonly",
Confidence: 0.6,
RawRef: "message_jid",
}
}
func normalizedSearchText(value string) string {
return strings.ToLower(strings.TrimSpace(value))
}
func contactMatchesQuery(contact Contact, queryText string) bool {
if queryText == "" {
return true
}
return strings.Contains(strings.ToLower(contact.ContactID), queryText) ||
strings.Contains(strings.ToLower(contact.DisplayName), queryText) ||
strings.Contains(strings.ToLower(contact.Account), queryText)
}
func contactSearchRank(contact Contact, queryText string) int {
if queryText == "" {
return 0
}
values := []string{
strings.ToLower(contact.ContactID),
strings.ToLower(contact.DisplayName),
strings.ToLower(contact.Account),
}
for _, value := range values {
if value == queryText {
return 0
}
}
for _, value := range values {
if strings.HasPrefix(value, queryText) {
return 1
}
}
return 2
}