Skip to content

Commit 1832c59

Browse files
committed
feat: add hybrid search command and implement related functionality
1 parent d5e12b7 commit 1832c59

File tree

7 files changed

+132
-8
lines changed

7 files changed

+132
-8
lines changed

errs/errors.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ var (
1111
ErrFileTooLarge = errors.New("file too large (>20MB)")
1212
ErrNoAvailableFile = errors.New("no available file")
1313

14-
ErrNilArtwork = errors.New("artwork is nil")
15-
ErrNotFoundArtworks = errors.New("artworks not found")
16-
ErrArtworkAlreadyExist = errors.New("artwork already exists")
17-
ErrSourceNotSupported = errors.New("source not supported")
18-
ErrIndexOOB = errors.New("index out of bounds")
19-
ErrFailedToFetchArtwork = errors.New("failed to fetch artwork")
14+
ErrNilArtwork = errors.New("artwork is nil")
15+
ErrNotFoundArtworks = errors.New("artworks not found")
16+
ErrArtworkAlreadyExist = errors.New("artwork already exists")
17+
ErrSourceNotSupported = errors.New("source not supported")
18+
ErrIndexOOB = errors.New("index out of bounds")
19+
ErrFailedToFetchArtwork = errors.New("failed to fetch artwork")
2020

2121
ErrStorageUnkown = errors.New("unknown storage")
2222

@@ -31,4 +31,6 @@ var (
3131
ErrChatIDNotSet = errors.New("chat id not set")
3232

3333
ErrAliasAlreadyUsed = errors.New("alias already used")
34+
35+
ErrNotEnabledHybridSearch = errors.New("hybrid search not enabled")
3436
)

service/artwork_search.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package service
2+
3+
import (
4+
"context"
5+
6+
"github.com/bytedance/sonic"
7+
"github.com/krau/ManyACG/adapter"
8+
"github.com/krau/ManyACG/common"
9+
"github.com/krau/ManyACG/config"
10+
"github.com/krau/ManyACG/dao"
11+
"github.com/krau/ManyACG/errs"
12+
"github.com/krau/ManyACG/types"
13+
"github.com/meilisearch/meilisearch-go"
14+
"go.mongodb.org/mongo-driver/bson/primitive"
15+
)
16+
17+
func HybridSearchArtworks(ctx context.Context, queryText string, hybridSemanticRatio float64, limit int64, options ...*types.AdapterOption) ([]*types.Artwork, error) {
18+
if common.MeilisearchClient == nil {
19+
return nil, errs.ErrNotEnabledHybridSearch
20+
}
21+
index := common.MeilisearchClient.Index(config.Cfg.Search.MeiliSearch.Index)
22+
resp, err := index.SearchWithContext(ctx, queryText, &meilisearch.SearchRequest{
23+
Limit: limit,
24+
Hybrid: &meilisearch.SearchRequestHybrid{
25+
SemanticRatio: hybridSemanticRatio,
26+
},
27+
})
28+
if err != nil {
29+
return nil, err
30+
}
31+
hits := resp.Hits
32+
artworkSearchDocs := make([]*types.ArtworkSearchDocument, 0, len(hits))
33+
hitsBytes, err := sonic.Marshal(hits)
34+
if err != nil {
35+
return nil, err
36+
}
37+
err = sonic.Unmarshal(hitsBytes, &artworkSearchDocs)
38+
if err != nil {
39+
return nil, err
40+
}
41+
artworkModels := make([]*types.ArtworkModel, 0, len(artworkSearchDocs))
42+
for _, doc := range artworkSearchDocs {
43+
objectID, err := primitive.ObjectIDFromHex(doc.ID)
44+
if err != nil {
45+
return nil, err
46+
}
47+
artworkModel, err := dao.GetArtworkByID(ctx, objectID)
48+
if err != nil {
49+
return nil, err
50+
}
51+
artworkModels = append(artworkModels, artworkModel)
52+
}
53+
return adapter.ConvertToArtworks(ctx, artworkModels, options...)
54+
}

service/change_stream.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,14 @@ func (m *artworkSyncManager) ProcessArtworkChangeEvent(event bson.M) {
6868
}()
6969
operationType := event["operationType"].(string)
7070
switch operationType {
71-
case "insert", "update":
71+
case "update":
7272
m.ProcessArtworkUpdateEvent(event)
7373
case "delete":
7474
m.ProcessArtworkDeleteEvent(event)
7575
case "replace":
7676
m.ProcessArtworkReplaceEvent(event)
77+
case "insert":
78+
// do nothing
7779
default:
7880
common.Logger.Debugf("unknown operation type: %s", operationType)
7981
}

telegram/bot.go

+4
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ var (
6363
Command: "help",
6464
Description: "食用指南",
6565
},
66+
{
67+
Command: "query",
68+
Description: "混合搜索作品",
69+
},
6670
}
6771

6872
AdminCommands = []telego.BotCommand{

telegram/handlers/handlers.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ func RegisterHandlers(hg *telegohandler.HandlerGroup) {
3030
mg.HandleMessageCtx(GetArtworkInfoCommand, telegohandler.CommandEqual("info"))
3131
mg.HandleMessageCtx(CalculatePicture, telegohandler.CommandEqual("hash"))
3232
mg.HandleMessageCtx(GetStats, telegohandler.CommandEqual("stats"))
33+
mg.HandleMessageCtx(HybridSearchArtworks, telegohandler.CommandEqual("query"))
3334

3435
mg.HandleMessageCtx(SetAdmin, telegohandler.CommandEqual("set_admin"))
3536
mg.HandleMessageCtx(DeleteArtwork, telegohandler.Or(telegohandler.CommandEqual("delete"), telegohandler.CommandEqual("del")))
3637
mg.HandleMessageCtx(ProcessPicturesHashAndSize, telegohandler.CommandEqual("process_pictures_hashsize"))
3738
mg.HandleMessageCtx(ProcessPicturesStorage, telegohandler.CommandEqual("process_pictures_storage"))
3839
mg.HandleMessageCtx(FixTwitterArtists, telegohandler.CommandEqual("fix_twitter_artists"))
39-
4040
mg.HandleMessageCtx(ToggleArtworkR18, telegohandler.CommandEqual("r18"))
4141
mg.HandleMessageCtx(SetArtworkTags, telegohandler.Or(telegohandler.CommandEqual("tags"), telegohandler.CommandEqual("addtags"), telegohandler.CommandEqual("deltags")))
4242
mg.HandleMessageCtx(EditArtworkTitle, telegohandler.CommandEqual("title"))

telegram/handlers/help.go

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ func Help(ctx context.Context, bot *telego.Bot, message telego.Message) {
2020
/hash - 计算图片信息
2121
/stats - 获取统计数据
2222
/files - 获取作品原图
23+
/query - 混合搜索作品
2324
`
2425
helpText += `
2526
随机图片相关功能中支持使用以下格式的参数:

telegram/handlers/random_picture.go telegram/handlers/query_artwork.go

+61
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import (
55
"errors"
66
"fmt"
77
"math/rand"
8+
"strconv"
89
"strings"
910

1011
"github.com/krau/ManyACG/adapter"
1112
"github.com/krau/ManyACG/common"
13+
"github.com/krau/ManyACG/config"
1214

1315
"github.com/krau/ManyACG/service"
1416
"github.com/krau/ManyACG/sources"
@@ -76,3 +78,62 @@ func RandomPicture(ctx context.Context, bot *telego.Bot, message telego.Message)
7678
}
7779
}
7880
}
81+
82+
func HybridSearchArtworks(ctx context.Context, bot *telego.Bot, message telego.Message) {
83+
if common.MeilisearchClient == nil {
84+
utils.ReplyMessage(bot, message, "未启用混合搜索功能")
85+
return
86+
}
87+
_, _, args := telegoutil.ParseCommand(message.Text)
88+
if len(args) == 0 {
89+
utils.ReplyMessage(bot, message, "使用方法: /query <搜索内容> [语义比例]\n语义比例为0-1的浮点数, 应位于参数列表最后, 越大越趋向于基于语义搜索, 若不提供, 使用默认值0.8")
90+
return
91+
}
92+
var hybridSemanticRatio float64
93+
var queryText string
94+
hybridSemanticRatio, err := strconv.ParseFloat(args[len(args)-1], 64)
95+
if err != nil {
96+
hybridSemanticRatio = 0.8
97+
queryText = strings.Join(args, " ")
98+
} else {
99+
if hybridSemanticRatio < 0 || hybridSemanticRatio > 1 {
100+
utils.ReplyMessage(bot, message, "参数错误: 语义比例应为0-1的浮点数")
101+
return
102+
}
103+
queryText = strings.Join(args[:len(args)-1], " ")
104+
}
105+
artworks, err := service.HybridSearchArtworks(ctx, queryText, hybridSemanticRatio, 10)
106+
if err != nil {
107+
common.Logger.Errorf("搜索失败: %s", err)
108+
utils.ReplyMessage(bot, message, "搜索失败, 请联系管理员检查搜索引擎设置与状态")
109+
return
110+
}
111+
if len(artworks) == 0 {
112+
utils.ReplyMessage(bot, message, "未找到相关图片")
113+
return
114+
}
115+
116+
if len(artworks) > 10 {
117+
artworks = artworks[:10]
118+
}
119+
120+
inputMedias := make([]telego.InputMedia, 0, len(artworks))
121+
for _, artwork := range artworks {
122+
picture := artwork.Pictures[0]
123+
var file telego.InputFile
124+
if picture.TelegramInfo != nil && picture.TelegramInfo.PhotoFileID != "" {
125+
file = telegoutil.FileFromID(picture.TelegramInfo.PhotoFileID)
126+
} else {
127+
photoURL := fmt.Sprintf("%s/?url=%s&w=2560&h=2560&we&output=jpg", config.Cfg.WSRVURL, picture.Original)
128+
file = telegoutil.FileFromURL(photoURL)
129+
}
130+
caption := fmt.Sprintf("<a href=\"%s\">%s</a>", artwork.SourceURL, common.EscapeHTML(artwork.Title))
131+
inputMedias = append(inputMedias, telegoutil.MediaPhoto(file).WithCaption(caption).WithParseMode(telego.ModeHTML))
132+
}
133+
mediaGroup := telegoutil.MediaGroup(message.Chat.ChatID(), inputMedias...)
134+
_, err = bot.SendMediaGroup(mediaGroup)
135+
if err != nil {
136+
common.Logger.Errorf("发送图片失败: %s", err)
137+
}
138+
139+
}

0 commit comments

Comments
 (0)