UPDATE: Chatbot module
All checks were successful
Build and Release / release (push) Successful in 2m13s

This commit is contained in:
2026-05-05 00:09:55 +07:00
parent 1998cf2ec0
commit a8f0597e59
33 changed files with 1042 additions and 65 deletions

View File

@@ -0,0 +1,60 @@
package controllers
import (
"context"
"history-api/internal/dtos/request"
"history-api/internal/dtos/response"
"history-api/internal/services"
"history-api/pkg/validator"
"time"
"github.com/gofiber/fiber/v3"
)
type ChatbotController struct {
chatbotService services.ChatbotService
}
func NewChatbotController(chatbotService services.ChatbotService) *ChatbotController {
return &ChatbotController{
chatbotService: chatbotService,
}
}
// Chat godoc
// @Summary Ask the AI chatbot
// @Description Ask a history question based on project context or global knowledge using RAG
// @Tags Chatbot
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param request body request.ChatbotDto true "Chatbot query"
// @Success 200 {object} response.CommonResponse{data=string} "Successful response with AI answer"
// @Failure 400 {object} response.CommonResponse "Invalid request"
// @Failure 500 {object} response.CommonResponse "Internal server error"
// @Router /chatbot/chat [post]
func (cx *ChatbotController) Chat(c fiber.Ctx) error {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
dto := &request.ChatbotDto{}
if err := validator.ValidateBodyDto(c, dto); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(response.CommonResponse{
Status: false,
Errors: err,
})
}
answer, err := cx.chatbotService.Chat(ctx, dto.ProjectID, dto.Question)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(response.CommonResponse{
Status: false,
Message: err.Error(),
})
}
return c.Status(fiber.StatusOK).JSON(response.CommonResponse{
Status: true,
Data: answer,
})
}

View File

@@ -0,0 +1,6 @@
package request
type ChatbotDto struct {
ProjectID *string `json:"project_id"`
Question string `json:"question" validate:"required"`
}

View File

@@ -83,7 +83,7 @@ type WikiSnapshot struct {
Source string `json:"source,omitempty" validate:"omitempty,oneof=inline ref"`
Operation string `json:"operation,omitempty" validate:"omitempty,oneof=create update delete reference"`
Title string `json:"title" validate:"required"`
Doc json.RawMessage `json:"doc,omitempty"`
Doc string `json:"doc,omitempty"`
UpdatedAt string `json:"updated_at,omitempty"`
}

View File

@@ -1,16 +1,15 @@
package response
import (
"encoding/json"
"time"
)
type WikiResponse struct {
ID string `json:"id"`
Title string `json:"title,omitempty"`
Content json.RawMessage `json:"content,omitempty"`
ProjectID string `json:"project_id"`
IsDeleted bool `json:"is_deleted,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
ID string `json:"id"`
Title string `json:"title,omitempty"`
Content string `json:"content,omitempty"`
ProjectID string `json:"project_id"`
IsDeleted bool `json:"is_deleted,omitempty"`
CreatedAt *time.Time `json:"created_at,omitempty"`
UpdatedAt *time.Time `json:"updated_at,omitempty"`
}

View File

@@ -8,6 +8,7 @@ import (
"encoding/json"
"github.com/jackc/pgx/v5/pgtype"
"github.com/pgvector/pgvector-go"
)
type Commit struct {
@@ -94,6 +95,18 @@ type ProjectMember struct {
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
type RagChunk struct {
ID pgtype.UUID `json:"id"`
SourceType string `json:"source_type"`
SourceID pgtype.UUID `json:"source_id"`
ProjectID pgtype.UUID `json:"project_id"`
ChunkIndex int32 `json:"chunk_index"`
Content string `json:"content"`
Embedding pgvector.Vector `json:"embedding"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
}
type Role struct {
ID pgtype.UUID `json:"id"`
Name string `json:"name"`
@@ -170,7 +183,7 @@ type Wiki struct {
ID pgtype.UUID `json:"id"`
ProjectID pgtype.UUID `json:"project_id"`
Title pgtype.Text `json:"title"`
Content []byte `json:"content"`
Content pgtype.Text `json:"content"`
IsDeleted bool `json:"is_deleted"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`

View File

@@ -0,0 +1,138 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: rag.sql
package sqlc
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
"github.com/pgvector/pgvector-go"
)
const createRagChunk = `-- name: CreateRagChunk :one
INSERT INTO rag_chunks (
id, source_type, source_id, project_id, chunk_index, content, embedding
) VALUES (
COALESCE($7::uuid, uuidv7()),
$1, $2, $3, $4, $5, $6
)
RETURNING id, source_type, source_id, project_id, chunk_index, content, embedding, created_at, updated_at
`
type CreateRagChunkParams struct {
SourceType string `json:"source_type"`
SourceID pgtype.UUID `json:"source_id"`
ProjectID pgtype.UUID `json:"project_id"`
ChunkIndex int32 `json:"chunk_index"`
Content string `json:"content"`
Embedding pgvector.Vector `json:"embedding"`
ID pgtype.UUID `json:"id"`
}
func (q *Queries) CreateRagChunk(ctx context.Context, arg CreateRagChunkParams) (RagChunk, error) {
row := q.db.QueryRow(ctx, createRagChunk,
arg.SourceType,
arg.SourceID,
arg.ProjectID,
arg.ChunkIndex,
arg.Content,
arg.Embedding,
arg.ID,
)
var i RagChunk
err := row.Scan(
&i.ID,
&i.SourceType,
&i.SourceID,
&i.ProjectID,
&i.ChunkIndex,
&i.Content,
&i.Embedding,
&i.CreatedAt,
&i.UpdatedAt,
)
return i, err
}
const deleteRagChunksBySourceIDs = `-- name: DeleteRagChunksBySourceIDs :exec
DELETE FROM rag_chunks
WHERE source_type = $1 AND source_id = ANY($2::uuid[])
`
type DeleteRagChunksBySourceIDsParams struct {
SourceType string `json:"source_type"`
Column2 []pgtype.UUID `json:"column_2"`
}
func (q *Queries) DeleteRagChunksBySourceIDs(ctx context.Context, arg DeleteRagChunksBySourceIDsParams) error {
_, err := q.db.Exec(ctx, deleteRagChunksBySourceIDs, arg.SourceType, arg.Column2)
return err
}
const searchRagChunks = `-- name: SearchRagChunks :many
SELECT
id, source_type, source_id, project_id, chunk_index, content,
(1 - (embedding <=> $1))::float8 AS similarity
FROM rag_chunks
WHERE 1=1
AND ($2::uuid IS NULL OR project_id = $2::uuid)
AND ($3::varchar IS NULL OR source_type = $3::varchar)
AND (1 - (embedding <=> $1))::float8 >= $4::float8
ORDER BY embedding <=> $1
LIMIT $5
`
type SearchRagChunksParams struct {
Embedding pgvector.Vector `json:"embedding"`
ProjectID pgtype.UUID `json:"project_id"`
SourceType pgtype.Text `json:"source_type"`
MatchThreshold float64 `json:"match_threshold"`
MatchCount int32 `json:"match_count"`
}
type SearchRagChunksRow struct {
ID pgtype.UUID `json:"id"`
SourceType string `json:"source_type"`
SourceID pgtype.UUID `json:"source_id"`
ProjectID pgtype.UUID `json:"project_id"`
ChunkIndex int32 `json:"chunk_index"`
Content string `json:"content"`
Similarity float64 `json:"similarity"`
}
func (q *Queries) SearchRagChunks(ctx context.Context, arg SearchRagChunksParams) ([]SearchRagChunksRow, error) {
rows, err := q.db.Query(ctx, searchRagChunks,
arg.Embedding,
arg.ProjectID,
arg.SourceType,
arg.MatchThreshold,
arg.MatchCount,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []SearchRagChunksRow{}
for rows.Next() {
var i SearchRagChunksRow
if err := rows.Scan(
&i.ID,
&i.SourceType,
&i.SourceID,
&i.ProjectID,
&i.ChunkIndex,
&i.Content,
&i.Similarity,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

View File

@@ -77,7 +77,7 @@ RETURNING id, project_id, title, content, is_deleted, created_at, updated_at
type CreateWikiParams struct {
Title pgtype.Text `json:"title"`
Content []byte `json:"content"`
Content pgtype.Text `json:"content"`
ProjectID pgtype.UUID `json:"project_id"`
ID pgtype.UUID `json:"id"`
}
@@ -312,7 +312,7 @@ RETURNING id, project_id, title, content, is_deleted, created_at, updated_at
type UpdateWikiParams struct {
Title pgtype.Text `json:"title"`
Content []byte `json:"content"`
Content pgtype.Text `json:"content"`
ProjectID pgtype.UUID `json:"project_id"`
ID pgtype.UUID `json:"id"`
}

17
internal/models/rag.go Normal file
View File

@@ -0,0 +1,17 @@
package models
import (
"time"
)
type RagChunk struct {
ID string `json:"id"`
SourceType string `json:"source_type"`
SourceID string `json:"source_id"`
ProjectID string `json:"project_id"`
ChunkIndex int32 `json:"chunk_index"`
Content string `json:"content"`
Similarity float64 `json:"similarity,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

View File

@@ -1,19 +1,18 @@
package models
import (
"encoding/json"
"history-api/internal/dtos/response"
"time"
)
type WikiEntity struct {
ID string `json:"id"`
Title string `json:"title"`
Content json.RawMessage `json:"content"`
ProjectID string `json:"project_id"`
IsDeleted bool `json:"is_deleted"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
ID string `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
ProjectID string `json:"project_id"`
IsDeleted bool `json:"is_deleted"`
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
}
func (w *WikiEntity) ToResponse() *response.WikiResponse {

View File

@@ -0,0 +1,94 @@
package repositories
import (
"context"
"history-api/internal/gen/sqlc"
"history-api/internal/models"
"history-api/pkg/cache"
"history-api/pkg/convert"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/pgvector/pgvector-go"
)
type RagRepository interface {
SaveChunk(ctx context.Context, sourceType string, sourceID string, projectID string, index int, content string, vector []float32) error
SearchSimilar(ctx context.Context, projectID *string, vector []float32, limit int, threshold float64) ([]*models.RagChunk, error)
DeleteBySourceIDs(ctx context.Context, sourceType string, sourceIDs []string) error
WithTx(tx pgx.Tx) RagRepository
}
type ragRepository struct {
q *sqlc.Queries
c cache.Cache
}
func NewRagRepository(db sqlc.DBTX, c cache.Cache) RagRepository {
return &ragRepository{q: sqlc.New(db), c: c}
}
func (r *ragRepository) WithTx(tx pgx.Tx) RagRepository {
return &ragRepository{q: r.q.WithTx(tx), c: r.c}
}
func (r *ragRepository) SaveChunk(ctx context.Context, sourceType string, sourceID string, projectID string, index int, content string, vector []float32) error {
pID, _ := convert.StringToUUID(projectID)
sID, _ := convert.StringToUUID(sourceID)
_, err := r.q.CreateRagChunk(ctx, sqlc.CreateRagChunkParams{
SourceType: sourceType,
SourceID: sID,
ProjectID: pID,
ChunkIndex: int32(index),
Content: content,
Embedding: pgvector.NewVector(vector),
})
return err
}
func (r *ragRepository) SearchSimilar(ctx context.Context, projectID *string, vector []float32, limit int, threshold float64) ([]*models.RagChunk, error) {
params := sqlc.SearchRagChunksParams{
Embedding: pgvector.NewVector(vector),
MatchThreshold: threshold,
MatchCount: int32(limit),
}
if projectID != nil && *projectID != "" {
pID, _ := convert.StringToUUID(*projectID)
params.ProjectID = pID
}
rows, err := r.q.SearchRagChunks(ctx, params)
if err != nil {
return nil, err
}
res := make([]*models.RagChunk, len(rows))
for i, row := range rows {
res[i] = &models.RagChunk{
ID: convert.UUIDToString(row.ID),
Content: row.Content,
Similarity: row.Similarity,
}
}
return res, nil
}
func (r *ragRepository) DeleteBySourceIDs(ctx context.Context, sourceType string, sourceIDs []string) error {
if len(sourceIDs) == 0 {
return nil
}
uids := make([]pgtype.UUID, 0, len(sourceIDs))
for _, id := range sourceIDs {
uid, err := convert.StringToUUID(id)
if err == nil {
uids = append(uids, uid)
}
}
return r.q.DeleteRagChunksBySourceIDs(ctx, sqlc.DeleteRagChunksBySourceIDsParams{
SourceType: sourceType,
Column2: uids,
})
}

View File

@@ -90,7 +90,7 @@ func (r *wikiRepository) getByIDsWithFallback(ctx context.Context, ids []string)
item := models.WikiEntity{
ID: convert.UUIDToString(row.ID),
Title: convert.TextToString(row.Title),
Content: json.RawMessage(row.Content),
Content: convert.TextToString(row.Content),
IsDeleted: row.IsDeleted,
ProjectID: convert.UUIDToString(row.ProjectID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
@@ -143,7 +143,7 @@ func (r *wikiRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.W
wiki = models.WikiEntity{
ID: convert.UUIDToString(row.ID),
Title: convert.TextToString(row.Title),
Content: json.RawMessage(row.Content),
Content: convert.TextToString(row.Content),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
@@ -172,7 +172,7 @@ func (r *wikiRepository) Search(ctx context.Context, params sqlc.SearchWikisPara
wiki := &models.WikiEntity{
ID: convert.UUIDToString(row.ID),
Title: convert.TextToString(row.Title),
Content: json.RawMessage(row.Content),
Content: convert.TextToString(row.Content),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
@@ -201,7 +201,7 @@ func (r *wikiRepository) Create(ctx context.Context, params sqlc.CreateWikiParam
wiki := models.WikiEntity{
ID: convert.UUIDToString(row.ID),
Title: convert.TextToString(row.Title),
Content: json.RawMessage(row.Content),
Content: convert.TextToString(row.Content),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
@@ -218,7 +218,7 @@ func (r *wikiRepository) Update(ctx context.Context, params sqlc.UpdateWikiParam
wiki := models.WikiEntity{
ID: convert.UUIDToString(row.ID),
Title: convert.TextToString(row.Title),
Content: json.RawMessage(row.Content),
Content: convert.TextToString(row.Content),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
@@ -272,7 +272,7 @@ func (r *wikiRepository) GetByProjectID(ctx context.Context, projectID pgtype.UU
wiki := &models.WikiEntity{
ID: convert.UUIDToString(row.ID),
Title: convert.TextToString(row.Title),
Content: json.RawMessage(row.Content),
Content: convert.TextToString(row.Content),
IsDeleted: row.IsDeleted,
ProjectID: convert.UUIDToString(row.ProjectID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),

View File

@@ -0,0 +1,16 @@
package routes
import (
"history-api/internal/controllers"
"history-api/internal/middlewares"
"history-api/internal/repositories"
"github.com/gofiber/fiber/v3"
)
func ChatbotRoutes(app *fiber.App, controller *controllers.ChatbotController, userRepo repositories.UserRepository) {
route := app.Group("/chatbot")
route.Use(middlewares.JwtAccess(userRepo))
route.Post("/chat", controller.Chat)
}

View File

@@ -0,0 +1,51 @@
package services
import (
"context"
"fmt"
"history-api/internal/repositories"
"history-api/pkg/ai"
)
type ChatbotService interface {
Chat(ctx context.Context, projectID *string, question string) (string, error)
}
type chatbotService struct {
repo repositories.RagRepository
ragUtils *ai.RagUtils
}
func NewChatbotService(repo repositories.RagRepository, ragUtils *ai.RagUtils) ChatbotService {
return &chatbotService{
repo: repo,
ragUtils: ragUtils,
}
}
func (s *chatbotService) Chat(ctx context.Context, projectID *string, question string) (string, error) {
qVector, err := s.ragUtils.EmbedQuery(ctx, question)
if err != nil {
return "", fmt.Errorf("failed to embed question: %w", err)
}
results, err := s.repo.SearchSimilar(ctx, projectID, qVector, 5, 0.65)
if err != nil {
return "", fmt.Errorf("failed to search similar content: %w", err)
}
contextStr := ""
for i, res := range results {
contextStr += fmt.Sprintf("[Document %d]: %s\n", i+1, res.Content)
}
prompt := fmt.Sprintf(`You are a helpful history assistant. Answer the question based ONLY on the provided context.
If the answer is not in the context, say "I don't have enough historical context to answer that."
Context:
%s
Question: %s
Answer:`, contextStr, question)
return s.ragUtils.GenerateResponse(ctx, prompt)
}

View File

@@ -9,6 +9,7 @@ import (
"history-api/internal/gen/sqlc"
"history-api/internal/models"
"history-api/internal/repositories"
"history-api/pkg/ai"
"history-api/pkg/cache"
"history-api/pkg/constants"
"history-api/pkg/convert"
@@ -36,6 +37,8 @@ type submissionService struct {
wikiRepo repositories.WikiRepository
geometryRepo repositories.GeometryRepository
entityRepo repositories.EntityRepository
ragRepo repositories.RagRepository
ragUtils *ai.RagUtils
db *pgxpool.Pool
c cache.Cache
}
@@ -48,6 +51,8 @@ func NewSubmissionService(
wikiRepo repositories.WikiRepository,
geometryRepo repositories.GeometryRepository,
entityRepo repositories.EntityRepository,
ragRepo repositories.RagRepository,
ragUtils *ai.RagUtils,
db *pgxpool.Pool,
c cache.Cache,
) SubmissionService {
@@ -59,6 +64,8 @@ func NewSubmissionService(
wikiRepo: wikiRepo,
geometryRepo: geometryRepo,
entityRepo: entityRepo,
ragRepo: ragRepo,
ragUtils: ragUtils,
db: db,
c: c,
}
@@ -127,6 +134,7 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
entityRepo := s.entityRepo.WithTx(tx)
geometryRepo := s.geometryRepo.WithTx(tx)
wikiRepo := s.wikiRepo.WithTx(tx)
ragRepo := s.ragRepo.WithTx(tx)
submissionUUID, err := convert.StringToUUID(submissionID)
if err != nil {
@@ -166,8 +174,11 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
return nil, fiber.NewError(fiber.StatusBadRequest, "Commit does not belong to project")
}
listDeleteEntities := make([]pgtype.UUID, 0)
listDeleteWikis := make([]pgtype.UUID, 0)
listDeleteGeometries := make([]pgtype.UUID, 0)
var snapshotData request.CommitSnapshot
if status == constants.StatusTypeApproved {
var snapshotData request.CommitSnapshot
err = json.Unmarshal(commit.SnapshotJson, &snapshotData)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to parse commit snapshot")
@@ -214,7 +225,6 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
persistCurrentItemIDs[item.ID] = struct{}{}
}
listDeleteEntities := make([]pgtype.UUID, 0)
for _, e := range currentEntity {
if _, ok := persistItemIDs[e.ID]; !ok {
itemUUID, err := convert.StringToUUID(e.ID)
@@ -226,7 +236,6 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
}
}
listDeleteGeometries := make([]pgtype.UUID, 0)
for _, g := range currentGeometry {
if _, ok := persistItemIDs[g.ID]; !ok {
itemUUID, err := convert.StringToUUID(g.ID)
@@ -238,7 +247,6 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
}
}
listDeleteWikis := make([]pgtype.UUID, 0)
for _, w := range currentWiki {
if _, ok := persistItemIDs[w.ID]; !ok {
itemUUID, err := convert.StringToUUID(w.ID)
@@ -274,6 +282,7 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
refEntityIDs = append(refEntityIDs, e.ID)
}
}
refEntities, _ := s.entityRepo.GetByIDs(ctx, refEntityIDs)
refEntityMap := make(map[string]bool)
for _, e := range refEntities {
@@ -444,7 +453,7 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
_, err := wikiRepo.Update(ctx, sqlc.UpdateWikiParams{
ID: wikiUUID,
Title: convert.StringToText(wiki.Title),
Content: wiki.Doc,
Content: convert.StringToText(wiki.Doc),
ProjectID: projectUUID,
})
if err != nil {
@@ -456,7 +465,7 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
_, err := wikiRepo.Create(ctx, sqlc.CreateWikiParams{
ID: wikiUUID,
Title: convert.StringToText(wiki.Title),
Content: wiki.Doc,
Content: convert.StringToText(wiki.Doc),
ProjectID: projectUUID,
})
if err != nil {
@@ -554,6 +563,58 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
}
}
if status == constants.StatusTypeApproved {
wikiDeleteIDs := make([]string, 0)
entityDeleteIDs := make([]string, 0)
for _, id := range listDeleteWikis {
wikiDeleteIDs = append(wikiDeleteIDs, convert.UUIDToString(id))
}
for _, id := range listDeleteEntities {
entityDeleteIDs = append(entityDeleteIDs, convert.UUIDToString(id))
}
for _, wiki := range snapshotData.Wikis {
if wiki.Operation == "delete" {
wikiDeleteIDs = append(wikiDeleteIDs, wiki.ID)
}
}
for _, entity := range snapshotData.Entities {
if entity.Operation == "delete" {
entityDeleteIDs = append(entityDeleteIDs, entity.ID)
}
}
_ = ragRepo.DeleteBySourceIDs(ctx, "wiki", wikiDeleteIDs)
_ = ragRepo.DeleteBySourceIDs(ctx, "entity", entityDeleteIDs)
for _, wiki := range snapshotData.Wikis {
if wiki.Source == "inline" {
cleanText := s.ragUtils.StripHTML(wiki.Title + "\n" + wiki.Doc)
chunks, vectors, err := s.ragUtils.PrepareChunks(ctx, cleanText)
if err == nil {
_ = ragRepo.DeleteBySourceIDs(ctx, "wiki", []string{wiki.ID})
for i, chunk := range chunks {
_ = ragRepo.SaveChunk(ctx, "wiki", wiki.ID, commit.ProjectID, i, chunk, vectors[i])
}
}
}
}
for _, entity := range snapshotData.Entities {
if entity.Source == "inline" {
cleanText := s.ragUtils.StripHTML(entity.Name + "\n" + entity.Description)
chunks, vectors, err := s.ragUtils.PrepareChunks(ctx, cleanText)
if err == nil {
_ = ragRepo.DeleteBySourceIDs(ctx, "entity", []string{entity.ID})
for i, chunk := range chunks {
_ = ragRepo.SaveChunk(ctx, "entity", entity.ID, commit.ProjectID, i, chunk, vectors[i])
}
}
}
}
}
arg := sqlc.UpdateSubmissionParams{
ID: submissionUUID,
Status: pgtype.Int2{Int16: status.Int16(), Valid: true},
@@ -563,12 +624,12 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
updatedSubmission, err := submissionRepo.Update(ctx, arg)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update submission status: " + err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update submission status: "+err.Error())
}
err = tx.Commit(ctx)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to commit transaction: " + err.Error())
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to commit transaction: "+err.Error())
}
if status == constants.StatusTypeApproved {
@@ -579,6 +640,7 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
_ = s.c.DelByPattern(bgCtx, "wiki:search*")
}()
}
_ = s.c.Del(ctx, fmt.Sprintf("project:id:%s", submission.ProjectID))
return updatedSubmission.ToResponse(), nil