2026-04-09 10:01:23 +08:00
|
|
|
package services
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"log/slog"
|
|
|
|
|
"time"
|
|
|
|
|
|
2026-08-21 00:41:07 +08:00
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/ai/rag"
|
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/models"
|
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/pkg/dto"
|
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/pkg/dto/request"
|
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/pkg/enums"
|
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/pkg/errorsx"
|
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/pkg/utils"
|
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/repositories"
|
|
|
|
|
|
|
|
|
|
"code.tczkiot.com/wlw/ai-agent/internal/pkg/httpx/params"
|
2026-05-31 18:43:48 +08:00
|
|
|
|
2026-04-09 10:01:23 +08:00
|
|
|
"github.com/mlogclub/simple/sqls"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var KnowledgeFAQService = newKnowledgeFAQService()
|
|
|
|
|
|
|
|
|
|
func newKnowledgeFAQService() *knowledgeFAQService {
|
|
|
|
|
return &knowledgeFAQService{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type knowledgeFAQService struct{}
|
|
|
|
|
|
|
|
|
|
func (s *knowledgeFAQService) Get(id int64) *models.KnowledgeFAQ {
|
|
|
|
|
return repositories.KnowledgeFAQRepository.Get(sqls.DB(), id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *knowledgeFAQService) FindPageByCnd(cnd *sqls.Cnd) (list []models.KnowledgeFAQ, paging *sqls.Paging) {
|
|
|
|
|
return repositories.KnowledgeFAQRepository.FindPageByCnd(sqls.DB(), cnd)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *knowledgeFAQService) FindPageByParams(queryParams *params.QueryParams) (list []models.KnowledgeFAQ, paging *sqls.Paging) {
|
|
|
|
|
return repositories.KnowledgeFAQRepository.FindPageByParams(sqls.DB(), queryParams)
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-02 15:37:50 +08:00
|
|
|
func (s *knowledgeFAQService) Count(cnd *sqls.Cnd) int64 {
|
|
|
|
|
return repositories.KnowledgeFAQRepository.Count(sqls.DB(), cnd)
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 10:01:23 +08:00
|
|
|
func (s *knowledgeFAQService) CreateKnowledgeFAQ(req request.CreateKnowledgeFAQRequest, operator *dto.AuthPrincipal) (*models.KnowledgeFAQ, error) {
|
|
|
|
|
if operator == nil {
|
2026-06-02 20:51:13 +08:00
|
|
|
return nil, errorsx.UnauthorizedI18n("error.auth.expired")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
kb, err := s.requireFAQKnowledgeBase(req.KnowledgeBaseID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-06-02 15:37:50 +08:00
|
|
|
if _, err := KnowledgeDirectoryService.RequireUsableDirectory(req.KnowledgeBaseID, req.DirectoryID); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-04-09 10:01:23 +08:00
|
|
|
item, err := s.buildKnowledgeFAQModel(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
item.Status = kb.Status
|
|
|
|
|
item.IndexStatus = enums.KnowledgeDocumentIndexStatusPending
|
|
|
|
|
item.IndexError = ""
|
|
|
|
|
item.IndexedAt = nil
|
|
|
|
|
item.AuditFields = utils.BuildAuditFields(operator)
|
|
|
|
|
if err := repositories.KnowledgeFAQRepository.Create(sqls.DB(), item); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if err := rag.Index.IndexFAQByID(context.Background(), item.ID); err != nil {
|
|
|
|
|
slog.Error("failed to index created knowledge faq", "faq_id", item.ID, "error", err)
|
|
|
|
|
return item, nil
|
|
|
|
|
}
|
|
|
|
|
return s.Get(item.ID), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *knowledgeFAQService) UpdateKnowledgeFAQ(req request.UpdateKnowledgeFAQRequest, operator *dto.AuthPrincipal) error {
|
|
|
|
|
if operator == nil {
|
2026-06-02 20:51:13 +08:00
|
|
|
return errorsx.UnauthorizedI18n("error.auth.expired")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
current := s.Get(req.ID)
|
|
|
|
|
if current == nil {
|
2026-06-02 20:51:13 +08:00
|
|
|
return errorsx.InvalidParamI18n("error.e0025")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
if _, err := s.requireFAQKnowledgeBase(req.KnowledgeBaseID); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-06-02 15:37:50 +08:00
|
|
|
if _, err := KnowledgeDirectoryService.RequireUsableDirectory(req.KnowledgeBaseID, req.DirectoryID); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-04-09 10:01:23 +08:00
|
|
|
item, err := s.buildKnowledgeFAQModel(req.CreateKnowledgeFAQRequest)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if err := repositories.KnowledgeFAQRepository.Updates(sqls.DB(), req.ID, map[string]any{
|
|
|
|
|
"knowledge_base_id": item.KnowledgeBaseID,
|
2026-06-02 15:37:50 +08:00
|
|
|
"directory_id": item.DirectoryID,
|
2026-04-09 10:01:23 +08:00
|
|
|
"question": item.Question,
|
|
|
|
|
"answer": item.Answer,
|
|
|
|
|
"similar_questions": item.SimilarQuestions,
|
|
|
|
|
"index_status": enums.KnowledgeDocumentIndexStatusPending,
|
|
|
|
|
"indexed_at": nil,
|
|
|
|
|
"index_error": "",
|
|
|
|
|
"remark": item.Remark,
|
|
|
|
|
"update_user_id": operator.UserID,
|
|
|
|
|
"update_user_name": operator.Username,
|
|
|
|
|
"updated_at": time.Now(),
|
|
|
|
|
}); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
return rag.Index.IndexFAQByID(context.Background(), req.ID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *knowledgeFAQService) DeleteKnowledgeFAQ(id int64) error {
|
|
|
|
|
current := s.Get(id)
|
|
|
|
|
if current == nil {
|
2026-06-02 20:51:13 +08:00
|
|
|
return errorsx.InvalidParamI18n("error.e0025")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
2026-06-02 11:35:46 +08:00
|
|
|
if err := repositories.KnowledgeFAQRepository.Delete(sqls.DB(), id); err != nil {
|
2026-04-09 10:01:23 +08:00
|
|
|
return err
|
|
|
|
|
}
|
2026-06-02 11:35:46 +08:00
|
|
|
return rag.Index.RemoveFAQIndex(context.Background(), id)
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
|
2026-06-02 16:13:08 +08:00
|
|
|
func (s *knowledgeFAQService) BatchMoveKnowledgeFAQs(req request.BatchMoveKnowledgeFAQRequest, operator *dto.AuthPrincipal) error {
|
|
|
|
|
if operator == nil {
|
2026-06-02 20:51:13 +08:00
|
|
|
return errorsx.UnauthorizedI18n("error.auth.expired")
|
2026-06-02 16:13:08 +08:00
|
|
|
}
|
|
|
|
|
ids := uniquePositiveIDs(req.IDs)
|
|
|
|
|
if len(ids) == 0 {
|
2026-06-02 20:51:13 +08:00
|
|
|
return errorsx.InvalidParamI18n("error.e0332")
|
2026-06-02 16:13:08 +08:00
|
|
|
}
|
|
|
|
|
if _, err := s.requireFAQKnowledgeBase(req.KnowledgeBaseID); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if _, err := KnowledgeDirectoryService.RequireUsableDirectory(req.KnowledgeBaseID, req.DirectoryID); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
for _, id := range ids {
|
|
|
|
|
current := s.Get(id)
|
|
|
|
|
if current == nil {
|
2026-06-02 20:51:13 +08:00
|
|
|
return errorsx.InvalidParamI18n("error.e0025")
|
2026-06-02 16:13:08 +08:00
|
|
|
}
|
|
|
|
|
if current.KnowledgeBaseID != req.KnowledgeBaseID {
|
2026-06-02 20:51:13 +08:00
|
|
|
return errorsx.InvalidParamI18n("error.e0138")
|
2026-06-02 16:13:08 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
now := time.Now()
|
|
|
|
|
if err := sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
|
|
|
|
for _, id := range ids {
|
|
|
|
|
if err := repositories.KnowledgeFAQRepository.Updates(ctx.Tx, id, map[string]any{
|
|
|
|
|
"directory_id": req.DirectoryID,
|
|
|
|
|
"index_status": enums.KnowledgeDocumentIndexStatusPending,
|
|
|
|
|
"indexed_at": nil,
|
|
|
|
|
"index_error": "",
|
|
|
|
|
"update_user_id": operator.UserID,
|
|
|
|
|
"update_user_name": operator.Username,
|
|
|
|
|
"updated_at": now,
|
|
|
|
|
}); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
for _, id := range ids {
|
|
|
|
|
if err := rag.Index.IndexFAQByID(context.Background(), id); err != nil {
|
|
|
|
|
slog.Error("failed to reindex moved knowledge faq", "faq_id", id, "error", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *knowledgeFAQService) BatchDeleteKnowledgeFAQs(req request.BatchDeleteKnowledgeFAQRequest) error {
|
|
|
|
|
ids := uniquePositiveIDs(req.IDs)
|
|
|
|
|
if len(ids) == 0 {
|
2026-06-02 20:51:13 +08:00
|
|
|
return errorsx.InvalidParamI18n("error.e0330")
|
2026-06-02 16:13:08 +08:00
|
|
|
}
|
|
|
|
|
for _, id := range ids {
|
|
|
|
|
if err := s.DeleteKnowledgeFAQ(id); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 10:01:23 +08:00
|
|
|
func (s *knowledgeFAQService) buildKnowledgeFAQModel(req request.CreateKnowledgeFAQRequest) (*models.KnowledgeFAQ, error) {
|
|
|
|
|
if req.KnowledgeBaseID <= 0 {
|
2026-06-02 20:51:13 +08:00
|
|
|
return nil, errorsx.InvalidParamI18n("error.e0283")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
if req.Question == "" {
|
2026-06-02 20:51:13 +08:00
|
|
|
return nil, errorsx.InvalidParamI18n("error.e0340")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
if req.Answer == "" {
|
2026-06-02 20:51:13 +08:00
|
|
|
return nil, errorsx.InvalidParamI18n("error.e0292")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
similarQuestions, err := json.Marshal(normalizeSimilarQuestions(req.SimilarQuestions))
|
|
|
|
|
if err != nil {
|
2026-06-02 20:51:13 +08:00
|
|
|
return nil, errorsx.InvalidParamI18n("error.e0280")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
return &models.KnowledgeFAQ{
|
|
|
|
|
KnowledgeBaseID: req.KnowledgeBaseID,
|
2026-06-02 15:37:50 +08:00
|
|
|
DirectoryID: req.DirectoryID,
|
2026-04-09 10:01:23 +08:00
|
|
|
Question: req.Question,
|
|
|
|
|
Answer: req.Answer,
|
|
|
|
|
SimilarQuestions: string(similarQuestions),
|
|
|
|
|
Remark: req.Remark,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *knowledgeFAQService) requireFAQKnowledgeBase(knowledgeBaseID int64) (*models.KnowledgeBase, error) {
|
|
|
|
|
kb := KnowledgeBaseService.Get(knowledgeBaseID)
|
|
|
|
|
if kb == nil {
|
2026-06-02 20:51:13 +08:00
|
|
|
return nil, errorsx.InvalidParamI18n("error.e0283")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
if kb.KnowledgeType != "faq" {
|
2026-06-02 20:51:13 +08:00
|
|
|
return nil, errorsx.InvalidParamI18n("error.e0199")
|
2026-04-09 10:01:23 +08:00
|
|
|
}
|
|
|
|
|
return kb, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func normalizeSimilarQuestions(values []string) []string {
|
|
|
|
|
items := make([]string, 0, len(values))
|
|
|
|
|
seen := make(map[string]struct{}, len(values))
|
|
|
|
|
for _, item := range values {
|
|
|
|
|
value := item
|
|
|
|
|
if value == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if _, ok := seen[value]; ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
seen[value] = struct{}{}
|
|
|
|
|
items = append(items, value)
|
|
|
|
|
}
|
|
|
|
|
return items
|
|
|
|
|
}
|