From 0e42ff4b86eae4f3b8c5b1ac45d6fe7e70eb5160 Mon Sep 17 00:00:00 2001 From: Gary Date: Thu, 11 Jul 2024 22:40:38 +0800 Subject: [PATCH] feat(kb): add some kb metadata (#36) Because knowledgebase metadata needs 1. file # 2. tokens # 3. correct pipeline name This commit 1. add kb_uid in chunk table and set index on it for count performance. 2. modify pipeline name --- .gitignore | 1 + Dockerfile | 16 +- config/config.yaml | 2 +- go.mod | 2 +- go.sum | 4 +- .../000009_add_kb_uid_in_chunk_table.down.sql | 9 + .../000009_add_kb_uid_in_chunk_table.up.sql | 12 + ...eline_metadata_column_to_kb_table.down.sql | 0 ...ipeline_metadata_column_to_kb_table.up.sql | 10 - pkg/handler/knowledgebase.go | 100 ++- pkg/mock/repository_i_mock.gen.go | 634 ++++++++++++++++++ pkg/repository/chunk.go | 24 + pkg/repository/knowledgebasefile.go | 38 ++ pkg/worker/worker.go | 5 + 14 files changed, 809 insertions(+), 48 deletions(-) create mode 100644 pkg/db/migration/000009_add_kb_uid_in_chunk_table.down.sql create mode 100644 pkg/db/migration/000009_add_kb_uid_in_chunk_table.up.sql delete mode 100644 pkg/db/migration/000009_add_pipeline_metadata_column_to_kb_table.down.sql delete mode 100644 pkg/db/migration/000009_add_pipeline_metadata_column_to_kb_table.up.sql diff --git a/.gitignore b/.gitignore index 1f1f6a7..6a9a267 100644 --- a/.gitignore +++ b/.gitignore @@ -121,3 +121,4 @@ tmp test_.pdf test_.md test_pdf_base64.txt +.DS_Store diff --git a/Dockerfile b/Dockerfile index 403ac2f..cce3d88 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,12 +4,22 @@ FROM --platform=$TARGETPLATFORM golang:${GOLANG_VERSION} AS build WORKDIR /src COPY go.mod go.sum ./ -RUN go mod download + +RUN --mount=type=cache,target=/go/pkg/mod \ + go mod download COPY . . ARG SERVICE_NAME TARGETOS TARGETARCH -RUN --mount=target=. --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o /${SERVICE_NAME} ./cmd/main -RUN --mount=target=. --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o /${SERVICE_NAME}-migrate ./cmd/migration + +RUN --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o /${SERVICE_NAME} ./cmd/main + +# Build the migration tool +RUN --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -o /${SERVICE_NAME}-migrate ./cmd/migration + FROM golang:${GOLANG_VERSION} diff --git a/config/config.yaml b/config/config.yaml index 9567a4a..2b7f218 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -22,7 +22,7 @@ database: host: pg-sql port: 5432 name: artifact - version: 8 + version: 9 timezone: Etc/UTC pool: idleconnections: 5 diff --git a/go.mod b/go.mod index 82fce95..c53d20f 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 github.com/influxdata/influxdb-client-go/v2 v2.12.3 - github.com/instill-ai/protogen-go v0.3.3-alpha.0.20240708222431-209c7b3b6ff5 + github.com/instill-ai/protogen-go v0.3.3-alpha.0.20240711114323-e16b79bfb545 github.com/instill-ai/usage-client v0.3.0-alpha.0.20240319060111-4a3a39f2fd61 github.com/instill-ai/x v0.3.0-alpha.0.20231219052200-6230a89e386c github.com/knadh/koanf v1.5.0 diff --git a/go.sum b/go.sum index 9a96fa9..9fc8147 100644 --- a/go.sum +++ b/go.sum @@ -342,8 +342,8 @@ github.com/influxdata/influxdb-client-go/v2 v2.12.3 h1:28nRlNMRIV4QbtIUvxhWqaxn0 github.com/influxdata/influxdb-client-go/v2 v2.12.3/go.mod h1:IrrLUbCjjfkmRuaCiGQg4m2GbkaeJDcuWoxiWdQEbA0= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/instill-ai/protogen-go v0.3.3-alpha.0.20240708222431-209c7b3b6ff5 h1:CZkUKdzp4TkVbrwov82B341R8Prw2wdmZVzDOl9jVEk= -github.com/instill-ai/protogen-go v0.3.3-alpha.0.20240708222431-209c7b3b6ff5/go.mod h1:2blmpUwiTwxIDnrjIqT6FhR5ewshZZF554wzjXFvKpQ= +github.com/instill-ai/protogen-go v0.3.3-alpha.0.20240711114323-e16b79bfb545 h1:W79+lcBA8DyqGo2gnDSh9pCwyru0oGWEjQxCQf6DqVs= +github.com/instill-ai/protogen-go v0.3.3-alpha.0.20240711114323-e16b79bfb545/go.mod h1:2blmpUwiTwxIDnrjIqT6FhR5ewshZZF554wzjXFvKpQ= github.com/instill-ai/usage-client v0.3.0-alpha.0.20240319060111-4a3a39f2fd61 h1:smPTvmXDhn/QC7y/TPXyMTqbbRd0gvzmFgWBChwTfhE= github.com/instill-ai/usage-client v0.3.0-alpha.0.20240319060111-4a3a39f2fd61/go.mod h1:/TAHs4ybuylk5icuy+MQtHRc4XUnIyXzeNKxX9qDFhw= github.com/instill-ai/x v0.3.0-alpha.0.20231219052200-6230a89e386c h1:a2RVkpIV2QcrGnSHAou+t/L+vBsaIfFvk5inVg5Uh4s= diff --git a/pkg/db/migration/000009_add_kb_uid_in_chunk_table.down.sql b/pkg/db/migration/000009_add_kb_uid_in_chunk_table.down.sql new file mode 100644 index 0000000..1da1e5c --- /dev/null +++ b/pkg/db/migration/000009_add_kb_uid_in_chunk_table.down.sql @@ -0,0 +1,9 @@ +BEGIN; + +-- Drop the index +DROP INDEX IF EXISTS idx_text_chunk_kb_uid; + +-- Remove the column +ALTER TABLE text_chunk DROP COLUMN IF EXISTS kb_uid; + +COMMIT; diff --git a/pkg/db/migration/000009_add_kb_uid_in_chunk_table.up.sql b/pkg/db/migration/000009_add_kb_uid_in_chunk_table.up.sql new file mode 100644 index 0000000..aff5a48 --- /dev/null +++ b/pkg/db/migration/000009_add_kb_uid_in_chunk_table.up.sql @@ -0,0 +1,12 @@ +BEGIN; + +-- Add the new column kb_uid +ALTER TABLE text_chunk ADD COLUMN kb_uid UUID; + +-- Add a comment for the new column +COMMENT ON COLUMN text_chunk.kb_uid IS 'Knowledge Base unique identifier'; + +-- Create an index on the new column +CREATE INDEX idx_text_chunk_kb_uid ON text_chunk (kb_uid); + +COMMIT; diff --git a/pkg/db/migration/000009_add_pipeline_metadata_column_to_kb_table.down.sql b/pkg/db/migration/000009_add_pipeline_metadata_column_to_kb_table.down.sql deleted file mode 100644 index e69de29..0000000 diff --git a/pkg/db/migration/000009_add_pipeline_metadata_column_to_kb_table.up.sql b/pkg/db/migration/000009_add_pipeline_metadata_column_to_kb_table.up.sql deleted file mode 100644 index 860eb14..0000000 --- a/pkg/db/migration/000009_add_pipeline_metadata_column_to_kb_table.up.sql +++ /dev/null @@ -1,10 +0,0 @@ -BEGIN; - --- Add the new column 'pipelines' to the 'knowledge_base' table -ALTER TABLE knowledge_base -ADD COLUMN pipelines JSONB; - --- Add a comment for the new 'pipelines' column -COMMENT ON COLUMN knowledge_base.pipelines IS 'JSONB column to store pipeline configurations or data related to the knowledge base'; - -COMMIT; diff --git a/pkg/handler/knowledgebase.go b/pkg/handler/knowledgebase.go index 41a2f70..761ed4d 100644 --- a/pkg/handler/knowledgebase.go +++ b/pkg/handler/knowledgebase.go @@ -104,9 +104,13 @@ func (ph *PublicHandler) CreateKnowledgeBase(ctx context.Context, req *artifactp OwnerName: dbData.Owner, CreateTime: dbData.CreateTime.String(), UpdateTime: dbData.UpdateTime.String(), - ConvertingPipelines: []string{"leo/fake-pipeline-1", "leo/fake-pipeline-2"}, - SplittingPipelines: []string{"leo/fake-pipeline-3", "leo/fake-pipeline-4"}, - EmbeddingPipelines: []string{"leo/fake-pipeline-5", "leo/fake-pipeline-6"}, + ConvertingPipelines: []string{"preset/indexing-convert-pdf"}, + SplittingPipelines: []string{"preset/indexing-split-text", "preset/indexing-split-markdown"}, + EmbeddingPipelines: []string{"preset/indexing-embed"}, + DownstreamApps: []string{}, + TotalFiles: 0, + TotalTokens: 0, + UsedStorage: 0, }, }, nil } @@ -140,6 +144,21 @@ func (ph *PublicHandler) ListKnowledgeBases(ctx context.Context, req *artifactpb return nil, fmt.Errorf(ErrorListKnowledgeBasesMsg, err) } + kbUIDuuid := make([]uuid.UUID, len(dbData)) + for i, kb := range dbData { + kbUIDuuid[i] = kb.UID + } + + fileCounts, err := ph.service.Repository.GetCountFilesByListKnowledgeBaseUID(ctx, kbUIDuuid) + if err != nil { + log.Error("failed to get file counts", zap.Error(err)) + return nil, fmt.Errorf(ErrorListKnowledgeBasesMsg, err) + } + tokenCounts, err := ph.service.Repository.GetTotalTokensByListKBUIDs(ctx, kbUIDuuid) + if err != nil { + log.Error("failed to get token counts", zap.Error(err)) + return nil, fmt.Errorf(ErrorListKnowledgeBasesMsg, err) + } kbs := make([]*artifactpb.KnowledgeBase, len(dbData)) for i, kb := range dbData { kbs[i] = &artifactpb.KnowledgeBase{ @@ -150,10 +169,14 @@ func (ph *PublicHandler) ListKnowledgeBases(ctx context.Context, req *artifactpb CreateTime: kb.CreateTime.String(), UpdateTime: kb.UpdateTime.String(), OwnerName: kb.Owner, - ConvertingPipelines: []string{"leo/fake-pipeline-1", "leo/fake-pipeline-2"}, - SplittingPipelines: []string{"leo/fake-pipeline-3", "leo/fake-pipeline-4"}, - EmbeddingPipelines: []string{"leo/fake-pipeline-5", "leo/fake-pipeline-6"}, - // DownstreamApps: []string{"leo/fake-app-1", "leo/fake-app-2"}, + ConvertingPipelines: []string{"preset/indexing-convert-pdf"}, + SplittingPipelines: []string{"preset/indexing-split-text", "preset/indexing-split-markdown"}, + EmbeddingPipelines: []string{"preset/indexing-embed"}, + DownstreamApps: []string{}, + TotalFiles: uint32(fileCounts[kb.UID]), + TotalTokens: uint32(tokenCounts[kb.UID]), + // TODO: get used storage + UsedStorage: 0, } } return &artifactpb.ListKnowledgeBasesResponse{ @@ -187,7 +210,7 @@ func (ph *PublicHandler) UpdateKnowledgeBase(ctx context.Context, req *artifactp // TODO: ACL - check user's permission to update knowledge base _ = authUID // check if knowledge base exists - dbData, err := ph.service.Repository.UpdateKnowledgeBase( + kb, err := ph.service.Repository.UpdateKnowledgeBase( ctx, ownerUUID, repository.KnowledgeBase{ @@ -202,20 +225,34 @@ func (ph *PublicHandler) UpdateKnowledgeBase(ctx context.Context, req *artifactp log.Error("failed to update knowledge base", zap.Error(err)) return nil, err } + fileCounts, err := ph.service.Repository.GetCountFilesByListKnowledgeBaseUID(ctx, []uuid.UUID{kb.UID}) + if err != nil { + log.Error("failed to get file counts", zap.Error(err)) + return nil, fmt.Errorf(ErrorListKnowledgeBasesMsg, err) + } + tokenCounts, err := ph.service.Repository.GetTotalTokensByListKBUIDs(ctx, []uuid.UUID{kb.UID}) + if err != nil { + log.Error("failed to get token counts", zap.Error(err)) + return nil, fmt.Errorf(ErrorListKnowledgeBasesMsg, err) + } // populate response return &artifactpb.UpdateKnowledgeBaseResponse{ KnowledgeBase: &artifactpb.KnowledgeBase{ - Name: dbData.Name, - KbId: dbData.KbID, - Description: dbData.Description, - Tags: dbData.Tags, - CreateTime: dbData.CreateTime.String(), - UpdateTime: dbData.UpdateTime.String(), - OwnerName: dbData.Owner, - ConvertingPipelines: []string{"leo/fake-pipeline-1", "leo/fake-pipeline-2"}, - SplittingPipelines: []string{"leo/fake-pipeline-3", "leo/fake-pipeline-4"}, - EmbeddingPipelines: []string{"leo/fake-pipeline-5", "leo/fake-pipeline-6"}, - // DownstreamApps: []string{"leo/fake-app-1", "leo/fake-app-2"}, + Name: kb.Name, + KbId: kb.KbID, + Description: kb.Description, + Tags: kb.Tags, + CreateTime: kb.CreateTime.String(), + UpdateTime: kb.UpdateTime.String(), + OwnerName: kb.Owner, + ConvertingPipelines: []string{"preset/indexing-convert-pdf"}, + SplittingPipelines: []string{"preset/indexing-split-text", "preset/indexing-split-markdown"}, + EmbeddingPipelines: []string{"preset/indexing-embed"}, + DownstreamApps: []string{}, + TotalFiles: uint32(fileCounts[kb.UID]), + TotalTokens: uint32(tokenCounts[kb.UID]), + // TODO: get used storage + UsedStorage: 0, }, }, nil } @@ -244,21 +281,22 @@ func (ph *PublicHandler) DeleteKnowledgeBase(ctx context.Context, req *artifactp return nil, err } - // populate response return &artifactpb.DeleteKnowledgeBaseResponse{ KnowledgeBase: &artifactpb.KnowledgeBase{ - Name: deletedKb.Name, - KbId: deletedKb.KbID, - Description: deletedKb.Description, - Tags: deletedKb.Tags, - CreateTime: deletedKb.CreateTime.String(), - UpdateTime: deletedKb.UpdateTime.String(), - OwnerName: deletedKb.Owner, - ConvertingPipelines: []string{"leo/fake-pipeline-1", "leo/fake-pipeline-2"}, - SplittingPipelines: []string{"leo/fake-pipeline-3", "leo/fake-pipeline-4"}, - EmbeddingPipelines: []string{"leo/fake-pipeline-5", "leo/fake-pipeline-6"}, - // DownstreamApps: []string{"leo/fake-app-1", "leo/fake-app-2"} + Name: deletedKb.Name, + KbId: deletedKb.KbID, + Description: deletedKb.Description, + Tags: deletedKb.Tags, + CreateTime: deletedKb.CreateTime.String(), + UpdateTime: deletedKb.UpdateTime.String(), + OwnerName: deletedKb.Owner, + ConvertingPipelines: []string{}, + EmbeddingPipelines: []string{}, + DownstreamApps: []string{}, + TotalFiles: 0, + TotalTokens: 0, + UsedStorage: 0, }, }, nil } diff --git a/pkg/mock/repository_i_mock.gen.go b/pkg/mock/repository_i_mock.gen.go index 0b966cd..7cecfcb 100644 --- a/pkg/mock/repository_i_mock.gen.go +++ b/pkg/mock/repository_i_mock.gen.go @@ -104,6 +104,12 @@ type RepositoryIMock struct { beforeGetConvertedFileByFileUIDCounter uint64 GetConvertedFileByFileUIDMock mRepositoryIMockGetConvertedFileByFileUID + funcGetCountFilesByListKnowledgeBaseUID func(ctx context.Context, kbUIDs []uuid.UUID) (m1 map[uuid.UUID]int64, err error) + inspectFuncGetCountFilesByListKnowledgeBaseUID func(ctx context.Context, kbUIDs []uuid.UUID) + afterGetCountFilesByListKnowledgeBaseUIDCounter uint64 + beforeGetCountFilesByListKnowledgeBaseUIDCounter uint64 + GetCountFilesByListKnowledgeBaseUIDMock mRepositoryIMockGetCountFilesByListKnowledgeBaseUID + funcGetEmbeddingByUIDs func(ctx context.Context, embUIDs []uuid.UUID) (ea1 []mm_repository.Embedding, err error) inspectFuncGetEmbeddingByUIDs func(ctx context.Context, embUIDs []uuid.UUID) afterGetEmbeddingByUIDsCounter uint64 @@ -134,6 +140,12 @@ type RepositoryIMock struct { beforeGetTextChunksBySourceCounter uint64 GetTextChunksBySourceMock mRepositoryIMockGetTextChunksBySource + funcGetTotalTokensByListKBUIDs func(ctx context.Context, kbUIDs []uuid.UUID) (m1 map[uuid.UUID]int, err error) + inspectFuncGetTotalTokensByListKBUIDs func(ctx context.Context, kbUIDs []uuid.UUID) + afterGetTotalTokensByListKBUIDsCounter uint64 + beforeGetTotalTokensByListKBUIDsCounter uint64 + GetTotalTokensByListKBUIDsMock mRepositoryIMockGetTotalTokensByListKBUIDs + funcKnowledgeBaseFileTableName func() (s1 string) inspectFuncKnowledgeBaseFileTableName func() afterKnowledgeBaseFileTableNameCounter uint64 @@ -238,6 +250,9 @@ func NewRepositoryIMock(t minimock.Tester) *RepositoryIMock { m.GetConvertedFileByFileUIDMock = mRepositoryIMockGetConvertedFileByFileUID{mock: m} m.GetConvertedFileByFileUIDMock.callArgs = []*RepositoryIMockGetConvertedFileByFileUIDParams{} + m.GetCountFilesByListKnowledgeBaseUIDMock = mRepositoryIMockGetCountFilesByListKnowledgeBaseUID{mock: m} + m.GetCountFilesByListKnowledgeBaseUIDMock.callArgs = []*RepositoryIMockGetCountFilesByListKnowledgeBaseUIDParams{} + m.GetEmbeddingByUIDsMock = mRepositoryIMockGetEmbeddingByUIDs{mock: m} m.GetEmbeddingByUIDsMock.callArgs = []*RepositoryIMockGetEmbeddingByUIDsParams{} @@ -253,6 +268,9 @@ func NewRepositoryIMock(t minimock.Tester) *RepositoryIMock { m.GetTextChunksBySourceMock = mRepositoryIMockGetTextChunksBySource{mock: m} m.GetTextChunksBySourceMock.callArgs = []*RepositoryIMockGetTextChunksBySourceParams{} + m.GetTotalTokensByListKBUIDsMock = mRepositoryIMockGetTotalTokensByListKBUIDs{mock: m} + m.GetTotalTokensByListKBUIDsMock.callArgs = []*RepositoryIMockGetTotalTokensByListKBUIDsParams{} + m.KnowledgeBaseFileTableNameMock = mRepositoryIMockKnowledgeBaseFileTableName{mock: m} m.ListKnowledgeBaseFilesMock = mRepositoryIMockListKnowledgeBaseFiles{mock: m} @@ -4656,6 +4674,311 @@ func (m *RepositoryIMock) MinimockGetConvertedFileByFileUIDInspect() { } } +type mRepositoryIMockGetCountFilesByListKnowledgeBaseUID struct { + mock *RepositoryIMock + defaultExpectation *RepositoryIMockGetCountFilesByListKnowledgeBaseUIDExpectation + expectations []*RepositoryIMockGetCountFilesByListKnowledgeBaseUIDExpectation + + callArgs []*RepositoryIMockGetCountFilesByListKnowledgeBaseUIDParams + mutex sync.RWMutex + + expectedInvocations uint64 +} + +// RepositoryIMockGetCountFilesByListKnowledgeBaseUIDExpectation specifies expectation struct of the RepositoryI.GetCountFilesByListKnowledgeBaseUID +type RepositoryIMockGetCountFilesByListKnowledgeBaseUIDExpectation struct { + mock *RepositoryIMock + params *RepositoryIMockGetCountFilesByListKnowledgeBaseUIDParams + paramPtrs *RepositoryIMockGetCountFilesByListKnowledgeBaseUIDParamPtrs + results *RepositoryIMockGetCountFilesByListKnowledgeBaseUIDResults + Counter uint64 +} + +// RepositoryIMockGetCountFilesByListKnowledgeBaseUIDParams contains parameters of the RepositoryI.GetCountFilesByListKnowledgeBaseUID +type RepositoryIMockGetCountFilesByListKnowledgeBaseUIDParams struct { + ctx context.Context + kbUIDs []uuid.UUID +} + +// RepositoryIMockGetCountFilesByListKnowledgeBaseUIDParamPtrs contains pointers to parameters of the RepositoryI.GetCountFilesByListKnowledgeBaseUID +type RepositoryIMockGetCountFilesByListKnowledgeBaseUIDParamPtrs struct { + ctx *context.Context + kbUIDs *[]uuid.UUID +} + +// RepositoryIMockGetCountFilesByListKnowledgeBaseUIDResults contains results of the RepositoryI.GetCountFilesByListKnowledgeBaseUID +type RepositoryIMockGetCountFilesByListKnowledgeBaseUIDResults struct { + m1 map[uuid.UUID]int64 + err error +} + +// Expect sets up expected params for RepositoryI.GetCountFilesByListKnowledgeBaseUID +func (mmGetCountFilesByListKnowledgeBaseUID *mRepositoryIMockGetCountFilesByListKnowledgeBaseUID) Expect(ctx context.Context, kbUIDs []uuid.UUID) *mRepositoryIMockGetCountFilesByListKnowledgeBaseUID { + if mmGetCountFilesByListKnowledgeBaseUID.mock.funcGetCountFilesByListKnowledgeBaseUID != nil { + mmGetCountFilesByListKnowledgeBaseUID.mock.t.Fatalf("RepositoryIMock.GetCountFilesByListKnowledgeBaseUID mock is already set by Set") + } + + if mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation == nil { + mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation = &RepositoryIMockGetCountFilesByListKnowledgeBaseUIDExpectation{} + } + + if mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation.paramPtrs != nil { + mmGetCountFilesByListKnowledgeBaseUID.mock.t.Fatalf("RepositoryIMock.GetCountFilesByListKnowledgeBaseUID mock is already set by ExpectParams functions") + } + + mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation.params = &RepositoryIMockGetCountFilesByListKnowledgeBaseUIDParams{ctx, kbUIDs} + for _, e := range mmGetCountFilesByListKnowledgeBaseUID.expectations { + if minimock.Equal(e.params, mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation.params) { + mmGetCountFilesByListKnowledgeBaseUID.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation.params) + } + } + + return mmGetCountFilesByListKnowledgeBaseUID +} + +// ExpectCtxParam1 sets up expected param ctx for RepositoryI.GetCountFilesByListKnowledgeBaseUID +func (mmGetCountFilesByListKnowledgeBaseUID *mRepositoryIMockGetCountFilesByListKnowledgeBaseUID) ExpectCtxParam1(ctx context.Context) *mRepositoryIMockGetCountFilesByListKnowledgeBaseUID { + if mmGetCountFilesByListKnowledgeBaseUID.mock.funcGetCountFilesByListKnowledgeBaseUID != nil { + mmGetCountFilesByListKnowledgeBaseUID.mock.t.Fatalf("RepositoryIMock.GetCountFilesByListKnowledgeBaseUID mock is already set by Set") + } + + if mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation == nil { + mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation = &RepositoryIMockGetCountFilesByListKnowledgeBaseUIDExpectation{} + } + + if mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation.params != nil { + mmGetCountFilesByListKnowledgeBaseUID.mock.t.Fatalf("RepositoryIMock.GetCountFilesByListKnowledgeBaseUID mock is already set by Expect") + } + + if mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation.paramPtrs == nil { + mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation.paramPtrs = &RepositoryIMockGetCountFilesByListKnowledgeBaseUIDParamPtrs{} + } + mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation.paramPtrs.ctx = &ctx + + return mmGetCountFilesByListKnowledgeBaseUID +} + +// ExpectKbUIDsParam2 sets up expected param kbUIDs for RepositoryI.GetCountFilesByListKnowledgeBaseUID +func (mmGetCountFilesByListKnowledgeBaseUID *mRepositoryIMockGetCountFilesByListKnowledgeBaseUID) ExpectKbUIDsParam2(kbUIDs []uuid.UUID) *mRepositoryIMockGetCountFilesByListKnowledgeBaseUID { + if mmGetCountFilesByListKnowledgeBaseUID.mock.funcGetCountFilesByListKnowledgeBaseUID != nil { + mmGetCountFilesByListKnowledgeBaseUID.mock.t.Fatalf("RepositoryIMock.GetCountFilesByListKnowledgeBaseUID mock is already set by Set") + } + + if mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation == nil { + mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation = &RepositoryIMockGetCountFilesByListKnowledgeBaseUIDExpectation{} + } + + if mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation.params != nil { + mmGetCountFilesByListKnowledgeBaseUID.mock.t.Fatalf("RepositoryIMock.GetCountFilesByListKnowledgeBaseUID mock is already set by Expect") + } + + if mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation.paramPtrs == nil { + mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation.paramPtrs = &RepositoryIMockGetCountFilesByListKnowledgeBaseUIDParamPtrs{} + } + mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation.paramPtrs.kbUIDs = &kbUIDs + + return mmGetCountFilesByListKnowledgeBaseUID +} + +// Inspect accepts an inspector function that has same arguments as the RepositoryI.GetCountFilesByListKnowledgeBaseUID +func (mmGetCountFilesByListKnowledgeBaseUID *mRepositoryIMockGetCountFilesByListKnowledgeBaseUID) Inspect(f func(ctx context.Context, kbUIDs []uuid.UUID)) *mRepositoryIMockGetCountFilesByListKnowledgeBaseUID { + if mmGetCountFilesByListKnowledgeBaseUID.mock.inspectFuncGetCountFilesByListKnowledgeBaseUID != nil { + mmGetCountFilesByListKnowledgeBaseUID.mock.t.Fatalf("Inspect function is already set for RepositoryIMock.GetCountFilesByListKnowledgeBaseUID") + } + + mmGetCountFilesByListKnowledgeBaseUID.mock.inspectFuncGetCountFilesByListKnowledgeBaseUID = f + + return mmGetCountFilesByListKnowledgeBaseUID +} + +// Return sets up results that will be returned by RepositoryI.GetCountFilesByListKnowledgeBaseUID +func (mmGetCountFilesByListKnowledgeBaseUID *mRepositoryIMockGetCountFilesByListKnowledgeBaseUID) Return(m1 map[uuid.UUID]int64, err error) *RepositoryIMock { + if mmGetCountFilesByListKnowledgeBaseUID.mock.funcGetCountFilesByListKnowledgeBaseUID != nil { + mmGetCountFilesByListKnowledgeBaseUID.mock.t.Fatalf("RepositoryIMock.GetCountFilesByListKnowledgeBaseUID mock is already set by Set") + } + + if mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation == nil { + mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation = &RepositoryIMockGetCountFilesByListKnowledgeBaseUIDExpectation{mock: mmGetCountFilesByListKnowledgeBaseUID.mock} + } + mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation.results = &RepositoryIMockGetCountFilesByListKnowledgeBaseUIDResults{m1, err} + return mmGetCountFilesByListKnowledgeBaseUID.mock +} + +// Set uses given function f to mock the RepositoryI.GetCountFilesByListKnowledgeBaseUID method +func (mmGetCountFilesByListKnowledgeBaseUID *mRepositoryIMockGetCountFilesByListKnowledgeBaseUID) Set(f func(ctx context.Context, kbUIDs []uuid.UUID) (m1 map[uuid.UUID]int64, err error)) *RepositoryIMock { + if mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation != nil { + mmGetCountFilesByListKnowledgeBaseUID.mock.t.Fatalf("Default expectation is already set for the RepositoryI.GetCountFilesByListKnowledgeBaseUID method") + } + + if len(mmGetCountFilesByListKnowledgeBaseUID.expectations) > 0 { + mmGetCountFilesByListKnowledgeBaseUID.mock.t.Fatalf("Some expectations are already set for the RepositoryI.GetCountFilesByListKnowledgeBaseUID method") + } + + mmGetCountFilesByListKnowledgeBaseUID.mock.funcGetCountFilesByListKnowledgeBaseUID = f + return mmGetCountFilesByListKnowledgeBaseUID.mock +} + +// When sets expectation for the RepositoryI.GetCountFilesByListKnowledgeBaseUID which will trigger the result defined by the following +// Then helper +func (mmGetCountFilesByListKnowledgeBaseUID *mRepositoryIMockGetCountFilesByListKnowledgeBaseUID) When(ctx context.Context, kbUIDs []uuid.UUID) *RepositoryIMockGetCountFilesByListKnowledgeBaseUIDExpectation { + if mmGetCountFilesByListKnowledgeBaseUID.mock.funcGetCountFilesByListKnowledgeBaseUID != nil { + mmGetCountFilesByListKnowledgeBaseUID.mock.t.Fatalf("RepositoryIMock.GetCountFilesByListKnowledgeBaseUID mock is already set by Set") + } + + expectation := &RepositoryIMockGetCountFilesByListKnowledgeBaseUIDExpectation{ + mock: mmGetCountFilesByListKnowledgeBaseUID.mock, + params: &RepositoryIMockGetCountFilesByListKnowledgeBaseUIDParams{ctx, kbUIDs}, + } + mmGetCountFilesByListKnowledgeBaseUID.expectations = append(mmGetCountFilesByListKnowledgeBaseUID.expectations, expectation) + return expectation +} + +// Then sets up RepositoryI.GetCountFilesByListKnowledgeBaseUID return parameters for the expectation previously defined by the When method +func (e *RepositoryIMockGetCountFilesByListKnowledgeBaseUIDExpectation) Then(m1 map[uuid.UUID]int64, err error) *RepositoryIMock { + e.results = &RepositoryIMockGetCountFilesByListKnowledgeBaseUIDResults{m1, err} + return e.mock +} + +// Times sets number of times RepositoryI.GetCountFilesByListKnowledgeBaseUID should be invoked +func (mmGetCountFilesByListKnowledgeBaseUID *mRepositoryIMockGetCountFilesByListKnowledgeBaseUID) Times(n uint64) *mRepositoryIMockGetCountFilesByListKnowledgeBaseUID { + if n == 0 { + mmGetCountFilesByListKnowledgeBaseUID.mock.t.Fatalf("Times of RepositoryIMock.GetCountFilesByListKnowledgeBaseUID mock can not be zero") + } + mm_atomic.StoreUint64(&mmGetCountFilesByListKnowledgeBaseUID.expectedInvocations, n) + return mmGetCountFilesByListKnowledgeBaseUID +} + +func (mmGetCountFilesByListKnowledgeBaseUID *mRepositoryIMockGetCountFilesByListKnowledgeBaseUID) invocationsDone() bool { + if len(mmGetCountFilesByListKnowledgeBaseUID.expectations) == 0 && mmGetCountFilesByListKnowledgeBaseUID.defaultExpectation == nil && mmGetCountFilesByListKnowledgeBaseUID.mock.funcGetCountFilesByListKnowledgeBaseUID == nil { + return true + } + + totalInvocations := mm_atomic.LoadUint64(&mmGetCountFilesByListKnowledgeBaseUID.mock.afterGetCountFilesByListKnowledgeBaseUIDCounter) + expectedInvocations := mm_atomic.LoadUint64(&mmGetCountFilesByListKnowledgeBaseUID.expectedInvocations) + + return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) +} + +// GetCountFilesByListKnowledgeBaseUID implements repository.RepositoryI +func (mmGetCountFilesByListKnowledgeBaseUID *RepositoryIMock) GetCountFilesByListKnowledgeBaseUID(ctx context.Context, kbUIDs []uuid.UUID) (m1 map[uuid.UUID]int64, err error) { + mm_atomic.AddUint64(&mmGetCountFilesByListKnowledgeBaseUID.beforeGetCountFilesByListKnowledgeBaseUIDCounter, 1) + defer mm_atomic.AddUint64(&mmGetCountFilesByListKnowledgeBaseUID.afterGetCountFilesByListKnowledgeBaseUIDCounter, 1) + + if mmGetCountFilesByListKnowledgeBaseUID.inspectFuncGetCountFilesByListKnowledgeBaseUID != nil { + mmGetCountFilesByListKnowledgeBaseUID.inspectFuncGetCountFilesByListKnowledgeBaseUID(ctx, kbUIDs) + } + + mm_params := RepositoryIMockGetCountFilesByListKnowledgeBaseUIDParams{ctx, kbUIDs} + + // Record call args + mmGetCountFilesByListKnowledgeBaseUID.GetCountFilesByListKnowledgeBaseUIDMock.mutex.Lock() + mmGetCountFilesByListKnowledgeBaseUID.GetCountFilesByListKnowledgeBaseUIDMock.callArgs = append(mmGetCountFilesByListKnowledgeBaseUID.GetCountFilesByListKnowledgeBaseUIDMock.callArgs, &mm_params) + mmGetCountFilesByListKnowledgeBaseUID.GetCountFilesByListKnowledgeBaseUIDMock.mutex.Unlock() + + for _, e := range mmGetCountFilesByListKnowledgeBaseUID.GetCountFilesByListKnowledgeBaseUIDMock.expectations { + if minimock.Equal(*e.params, mm_params) { + mm_atomic.AddUint64(&e.Counter, 1) + return e.results.m1, e.results.err + } + } + + if mmGetCountFilesByListKnowledgeBaseUID.GetCountFilesByListKnowledgeBaseUIDMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmGetCountFilesByListKnowledgeBaseUID.GetCountFilesByListKnowledgeBaseUIDMock.defaultExpectation.Counter, 1) + mm_want := mmGetCountFilesByListKnowledgeBaseUID.GetCountFilesByListKnowledgeBaseUIDMock.defaultExpectation.params + mm_want_ptrs := mmGetCountFilesByListKnowledgeBaseUID.GetCountFilesByListKnowledgeBaseUIDMock.defaultExpectation.paramPtrs + + mm_got := RepositoryIMockGetCountFilesByListKnowledgeBaseUIDParams{ctx, kbUIDs} + + if mm_want_ptrs != nil { + + if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) { + mmGetCountFilesByListKnowledgeBaseUID.t.Errorf("RepositoryIMock.GetCountFilesByListKnowledgeBaseUID got unexpected parameter ctx, want: %#v, got: %#v%s\n", *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) + } + + if mm_want_ptrs.kbUIDs != nil && !minimock.Equal(*mm_want_ptrs.kbUIDs, mm_got.kbUIDs) { + mmGetCountFilesByListKnowledgeBaseUID.t.Errorf("RepositoryIMock.GetCountFilesByListKnowledgeBaseUID got unexpected parameter kbUIDs, want: %#v, got: %#v%s\n", *mm_want_ptrs.kbUIDs, mm_got.kbUIDs, minimock.Diff(*mm_want_ptrs.kbUIDs, mm_got.kbUIDs)) + } + + } else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { + mmGetCountFilesByListKnowledgeBaseUID.t.Errorf("RepositoryIMock.GetCountFilesByListKnowledgeBaseUID got unexpected parameters, want: %#v, got: %#v%s\n", *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + } + + mm_results := mmGetCountFilesByListKnowledgeBaseUID.GetCountFilesByListKnowledgeBaseUIDMock.defaultExpectation.results + if mm_results == nil { + mmGetCountFilesByListKnowledgeBaseUID.t.Fatal("No results are set for the RepositoryIMock.GetCountFilesByListKnowledgeBaseUID") + } + return (*mm_results).m1, (*mm_results).err + } + if mmGetCountFilesByListKnowledgeBaseUID.funcGetCountFilesByListKnowledgeBaseUID != nil { + return mmGetCountFilesByListKnowledgeBaseUID.funcGetCountFilesByListKnowledgeBaseUID(ctx, kbUIDs) + } + mmGetCountFilesByListKnowledgeBaseUID.t.Fatalf("Unexpected call to RepositoryIMock.GetCountFilesByListKnowledgeBaseUID. %v %v", ctx, kbUIDs) + return +} + +// GetCountFilesByListKnowledgeBaseUIDAfterCounter returns a count of finished RepositoryIMock.GetCountFilesByListKnowledgeBaseUID invocations +func (mmGetCountFilesByListKnowledgeBaseUID *RepositoryIMock) GetCountFilesByListKnowledgeBaseUIDAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmGetCountFilesByListKnowledgeBaseUID.afterGetCountFilesByListKnowledgeBaseUIDCounter) +} + +// GetCountFilesByListKnowledgeBaseUIDBeforeCounter returns a count of RepositoryIMock.GetCountFilesByListKnowledgeBaseUID invocations +func (mmGetCountFilesByListKnowledgeBaseUID *RepositoryIMock) GetCountFilesByListKnowledgeBaseUIDBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmGetCountFilesByListKnowledgeBaseUID.beforeGetCountFilesByListKnowledgeBaseUIDCounter) +} + +// Calls returns a list of arguments used in each call to RepositoryIMock.GetCountFilesByListKnowledgeBaseUID. +// The list is in the same order as the calls were made (i.e. recent calls have a higher index) +func (mmGetCountFilesByListKnowledgeBaseUID *mRepositoryIMockGetCountFilesByListKnowledgeBaseUID) Calls() []*RepositoryIMockGetCountFilesByListKnowledgeBaseUIDParams { + mmGetCountFilesByListKnowledgeBaseUID.mutex.RLock() + + argCopy := make([]*RepositoryIMockGetCountFilesByListKnowledgeBaseUIDParams, len(mmGetCountFilesByListKnowledgeBaseUID.callArgs)) + copy(argCopy, mmGetCountFilesByListKnowledgeBaseUID.callArgs) + + mmGetCountFilesByListKnowledgeBaseUID.mutex.RUnlock() + + return argCopy +} + +// MinimockGetCountFilesByListKnowledgeBaseUIDDone returns true if the count of the GetCountFilesByListKnowledgeBaseUID invocations corresponds +// the number of defined expectations +func (m *RepositoryIMock) MinimockGetCountFilesByListKnowledgeBaseUIDDone() bool { + for _, e := range m.GetCountFilesByListKnowledgeBaseUIDMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + return false + } + } + + return m.GetCountFilesByListKnowledgeBaseUIDMock.invocationsDone() +} + +// MinimockGetCountFilesByListKnowledgeBaseUIDInspect logs each unmet expectation +func (m *RepositoryIMock) MinimockGetCountFilesByListKnowledgeBaseUIDInspect() { + for _, e := range m.GetCountFilesByListKnowledgeBaseUIDMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + m.t.Errorf("Expected call to RepositoryIMock.GetCountFilesByListKnowledgeBaseUID with params: %#v", *e.params) + } + } + + afterGetCountFilesByListKnowledgeBaseUIDCounter := mm_atomic.LoadUint64(&m.afterGetCountFilesByListKnowledgeBaseUIDCounter) + // if default expectation was set then invocations count should be greater than zero + if m.GetCountFilesByListKnowledgeBaseUIDMock.defaultExpectation != nil && afterGetCountFilesByListKnowledgeBaseUIDCounter < 1 { + if m.GetCountFilesByListKnowledgeBaseUIDMock.defaultExpectation.params == nil { + m.t.Error("Expected call to RepositoryIMock.GetCountFilesByListKnowledgeBaseUID") + } else { + m.t.Errorf("Expected call to RepositoryIMock.GetCountFilesByListKnowledgeBaseUID with params: %#v", *m.GetCountFilesByListKnowledgeBaseUIDMock.defaultExpectation.params) + } + } + // if func was set then invocations count should be greater than zero + if m.funcGetCountFilesByListKnowledgeBaseUID != nil && afterGetCountFilesByListKnowledgeBaseUIDCounter < 1 { + m.t.Error("Expected call to RepositoryIMock.GetCountFilesByListKnowledgeBaseUID") + } + + if !m.GetCountFilesByListKnowledgeBaseUIDMock.invocationsDone() && afterGetCountFilesByListKnowledgeBaseUIDCounter > 0 { + m.t.Errorf("Expected %d calls to RepositoryIMock.GetCountFilesByListKnowledgeBaseUID but found %d calls", + mm_atomic.LoadUint64(&m.GetCountFilesByListKnowledgeBaseUIDMock.expectedInvocations), afterGetCountFilesByListKnowledgeBaseUIDCounter) + } +} + type mRepositoryIMockGetEmbeddingByUIDs struct { mock *RepositoryIMock defaultExpectation *RepositoryIMockGetEmbeddingByUIDsExpectation @@ -6208,6 +6531,311 @@ func (m *RepositoryIMock) MinimockGetTextChunksBySourceInspect() { } } +type mRepositoryIMockGetTotalTokensByListKBUIDs struct { + mock *RepositoryIMock + defaultExpectation *RepositoryIMockGetTotalTokensByListKBUIDsExpectation + expectations []*RepositoryIMockGetTotalTokensByListKBUIDsExpectation + + callArgs []*RepositoryIMockGetTotalTokensByListKBUIDsParams + mutex sync.RWMutex + + expectedInvocations uint64 +} + +// RepositoryIMockGetTotalTokensByListKBUIDsExpectation specifies expectation struct of the RepositoryI.GetTotalTokensByListKBUIDs +type RepositoryIMockGetTotalTokensByListKBUIDsExpectation struct { + mock *RepositoryIMock + params *RepositoryIMockGetTotalTokensByListKBUIDsParams + paramPtrs *RepositoryIMockGetTotalTokensByListKBUIDsParamPtrs + results *RepositoryIMockGetTotalTokensByListKBUIDsResults + Counter uint64 +} + +// RepositoryIMockGetTotalTokensByListKBUIDsParams contains parameters of the RepositoryI.GetTotalTokensByListKBUIDs +type RepositoryIMockGetTotalTokensByListKBUIDsParams struct { + ctx context.Context + kbUIDs []uuid.UUID +} + +// RepositoryIMockGetTotalTokensByListKBUIDsParamPtrs contains pointers to parameters of the RepositoryI.GetTotalTokensByListKBUIDs +type RepositoryIMockGetTotalTokensByListKBUIDsParamPtrs struct { + ctx *context.Context + kbUIDs *[]uuid.UUID +} + +// RepositoryIMockGetTotalTokensByListKBUIDsResults contains results of the RepositoryI.GetTotalTokensByListKBUIDs +type RepositoryIMockGetTotalTokensByListKBUIDsResults struct { + m1 map[uuid.UUID]int + err error +} + +// Expect sets up expected params for RepositoryI.GetTotalTokensByListKBUIDs +func (mmGetTotalTokensByListKBUIDs *mRepositoryIMockGetTotalTokensByListKBUIDs) Expect(ctx context.Context, kbUIDs []uuid.UUID) *mRepositoryIMockGetTotalTokensByListKBUIDs { + if mmGetTotalTokensByListKBUIDs.mock.funcGetTotalTokensByListKBUIDs != nil { + mmGetTotalTokensByListKBUIDs.mock.t.Fatalf("RepositoryIMock.GetTotalTokensByListKBUIDs mock is already set by Set") + } + + if mmGetTotalTokensByListKBUIDs.defaultExpectation == nil { + mmGetTotalTokensByListKBUIDs.defaultExpectation = &RepositoryIMockGetTotalTokensByListKBUIDsExpectation{} + } + + if mmGetTotalTokensByListKBUIDs.defaultExpectation.paramPtrs != nil { + mmGetTotalTokensByListKBUIDs.mock.t.Fatalf("RepositoryIMock.GetTotalTokensByListKBUIDs mock is already set by ExpectParams functions") + } + + mmGetTotalTokensByListKBUIDs.defaultExpectation.params = &RepositoryIMockGetTotalTokensByListKBUIDsParams{ctx, kbUIDs} + for _, e := range mmGetTotalTokensByListKBUIDs.expectations { + if minimock.Equal(e.params, mmGetTotalTokensByListKBUIDs.defaultExpectation.params) { + mmGetTotalTokensByListKBUIDs.mock.t.Fatalf("Expectation set by When has same params: %#v", *mmGetTotalTokensByListKBUIDs.defaultExpectation.params) + } + } + + return mmGetTotalTokensByListKBUIDs +} + +// ExpectCtxParam1 sets up expected param ctx for RepositoryI.GetTotalTokensByListKBUIDs +func (mmGetTotalTokensByListKBUIDs *mRepositoryIMockGetTotalTokensByListKBUIDs) ExpectCtxParam1(ctx context.Context) *mRepositoryIMockGetTotalTokensByListKBUIDs { + if mmGetTotalTokensByListKBUIDs.mock.funcGetTotalTokensByListKBUIDs != nil { + mmGetTotalTokensByListKBUIDs.mock.t.Fatalf("RepositoryIMock.GetTotalTokensByListKBUIDs mock is already set by Set") + } + + if mmGetTotalTokensByListKBUIDs.defaultExpectation == nil { + mmGetTotalTokensByListKBUIDs.defaultExpectation = &RepositoryIMockGetTotalTokensByListKBUIDsExpectation{} + } + + if mmGetTotalTokensByListKBUIDs.defaultExpectation.params != nil { + mmGetTotalTokensByListKBUIDs.mock.t.Fatalf("RepositoryIMock.GetTotalTokensByListKBUIDs mock is already set by Expect") + } + + if mmGetTotalTokensByListKBUIDs.defaultExpectation.paramPtrs == nil { + mmGetTotalTokensByListKBUIDs.defaultExpectation.paramPtrs = &RepositoryIMockGetTotalTokensByListKBUIDsParamPtrs{} + } + mmGetTotalTokensByListKBUIDs.defaultExpectation.paramPtrs.ctx = &ctx + + return mmGetTotalTokensByListKBUIDs +} + +// ExpectKbUIDsParam2 sets up expected param kbUIDs for RepositoryI.GetTotalTokensByListKBUIDs +func (mmGetTotalTokensByListKBUIDs *mRepositoryIMockGetTotalTokensByListKBUIDs) ExpectKbUIDsParam2(kbUIDs []uuid.UUID) *mRepositoryIMockGetTotalTokensByListKBUIDs { + if mmGetTotalTokensByListKBUIDs.mock.funcGetTotalTokensByListKBUIDs != nil { + mmGetTotalTokensByListKBUIDs.mock.t.Fatalf("RepositoryIMock.GetTotalTokensByListKBUIDs mock is already set by Set") + } + + if mmGetTotalTokensByListKBUIDs.defaultExpectation == nil { + mmGetTotalTokensByListKBUIDs.defaultExpectation = &RepositoryIMockGetTotalTokensByListKBUIDsExpectation{} + } + + if mmGetTotalTokensByListKBUIDs.defaultExpectation.params != nil { + mmGetTotalTokensByListKBUIDs.mock.t.Fatalf("RepositoryIMock.GetTotalTokensByListKBUIDs mock is already set by Expect") + } + + if mmGetTotalTokensByListKBUIDs.defaultExpectation.paramPtrs == nil { + mmGetTotalTokensByListKBUIDs.defaultExpectation.paramPtrs = &RepositoryIMockGetTotalTokensByListKBUIDsParamPtrs{} + } + mmGetTotalTokensByListKBUIDs.defaultExpectation.paramPtrs.kbUIDs = &kbUIDs + + return mmGetTotalTokensByListKBUIDs +} + +// Inspect accepts an inspector function that has same arguments as the RepositoryI.GetTotalTokensByListKBUIDs +func (mmGetTotalTokensByListKBUIDs *mRepositoryIMockGetTotalTokensByListKBUIDs) Inspect(f func(ctx context.Context, kbUIDs []uuid.UUID)) *mRepositoryIMockGetTotalTokensByListKBUIDs { + if mmGetTotalTokensByListKBUIDs.mock.inspectFuncGetTotalTokensByListKBUIDs != nil { + mmGetTotalTokensByListKBUIDs.mock.t.Fatalf("Inspect function is already set for RepositoryIMock.GetTotalTokensByListKBUIDs") + } + + mmGetTotalTokensByListKBUIDs.mock.inspectFuncGetTotalTokensByListKBUIDs = f + + return mmGetTotalTokensByListKBUIDs +} + +// Return sets up results that will be returned by RepositoryI.GetTotalTokensByListKBUIDs +func (mmGetTotalTokensByListKBUIDs *mRepositoryIMockGetTotalTokensByListKBUIDs) Return(m1 map[uuid.UUID]int, err error) *RepositoryIMock { + if mmGetTotalTokensByListKBUIDs.mock.funcGetTotalTokensByListKBUIDs != nil { + mmGetTotalTokensByListKBUIDs.mock.t.Fatalf("RepositoryIMock.GetTotalTokensByListKBUIDs mock is already set by Set") + } + + if mmGetTotalTokensByListKBUIDs.defaultExpectation == nil { + mmGetTotalTokensByListKBUIDs.defaultExpectation = &RepositoryIMockGetTotalTokensByListKBUIDsExpectation{mock: mmGetTotalTokensByListKBUIDs.mock} + } + mmGetTotalTokensByListKBUIDs.defaultExpectation.results = &RepositoryIMockGetTotalTokensByListKBUIDsResults{m1, err} + return mmGetTotalTokensByListKBUIDs.mock +} + +// Set uses given function f to mock the RepositoryI.GetTotalTokensByListKBUIDs method +func (mmGetTotalTokensByListKBUIDs *mRepositoryIMockGetTotalTokensByListKBUIDs) Set(f func(ctx context.Context, kbUIDs []uuid.UUID) (m1 map[uuid.UUID]int, err error)) *RepositoryIMock { + if mmGetTotalTokensByListKBUIDs.defaultExpectation != nil { + mmGetTotalTokensByListKBUIDs.mock.t.Fatalf("Default expectation is already set for the RepositoryI.GetTotalTokensByListKBUIDs method") + } + + if len(mmGetTotalTokensByListKBUIDs.expectations) > 0 { + mmGetTotalTokensByListKBUIDs.mock.t.Fatalf("Some expectations are already set for the RepositoryI.GetTotalTokensByListKBUIDs method") + } + + mmGetTotalTokensByListKBUIDs.mock.funcGetTotalTokensByListKBUIDs = f + return mmGetTotalTokensByListKBUIDs.mock +} + +// When sets expectation for the RepositoryI.GetTotalTokensByListKBUIDs which will trigger the result defined by the following +// Then helper +func (mmGetTotalTokensByListKBUIDs *mRepositoryIMockGetTotalTokensByListKBUIDs) When(ctx context.Context, kbUIDs []uuid.UUID) *RepositoryIMockGetTotalTokensByListKBUIDsExpectation { + if mmGetTotalTokensByListKBUIDs.mock.funcGetTotalTokensByListKBUIDs != nil { + mmGetTotalTokensByListKBUIDs.mock.t.Fatalf("RepositoryIMock.GetTotalTokensByListKBUIDs mock is already set by Set") + } + + expectation := &RepositoryIMockGetTotalTokensByListKBUIDsExpectation{ + mock: mmGetTotalTokensByListKBUIDs.mock, + params: &RepositoryIMockGetTotalTokensByListKBUIDsParams{ctx, kbUIDs}, + } + mmGetTotalTokensByListKBUIDs.expectations = append(mmGetTotalTokensByListKBUIDs.expectations, expectation) + return expectation +} + +// Then sets up RepositoryI.GetTotalTokensByListKBUIDs return parameters for the expectation previously defined by the When method +func (e *RepositoryIMockGetTotalTokensByListKBUIDsExpectation) Then(m1 map[uuid.UUID]int, err error) *RepositoryIMock { + e.results = &RepositoryIMockGetTotalTokensByListKBUIDsResults{m1, err} + return e.mock +} + +// Times sets number of times RepositoryI.GetTotalTokensByListKBUIDs should be invoked +func (mmGetTotalTokensByListKBUIDs *mRepositoryIMockGetTotalTokensByListKBUIDs) Times(n uint64) *mRepositoryIMockGetTotalTokensByListKBUIDs { + if n == 0 { + mmGetTotalTokensByListKBUIDs.mock.t.Fatalf("Times of RepositoryIMock.GetTotalTokensByListKBUIDs mock can not be zero") + } + mm_atomic.StoreUint64(&mmGetTotalTokensByListKBUIDs.expectedInvocations, n) + return mmGetTotalTokensByListKBUIDs +} + +func (mmGetTotalTokensByListKBUIDs *mRepositoryIMockGetTotalTokensByListKBUIDs) invocationsDone() bool { + if len(mmGetTotalTokensByListKBUIDs.expectations) == 0 && mmGetTotalTokensByListKBUIDs.defaultExpectation == nil && mmGetTotalTokensByListKBUIDs.mock.funcGetTotalTokensByListKBUIDs == nil { + return true + } + + totalInvocations := mm_atomic.LoadUint64(&mmGetTotalTokensByListKBUIDs.mock.afterGetTotalTokensByListKBUIDsCounter) + expectedInvocations := mm_atomic.LoadUint64(&mmGetTotalTokensByListKBUIDs.expectedInvocations) + + return totalInvocations > 0 && (expectedInvocations == 0 || expectedInvocations == totalInvocations) +} + +// GetTotalTokensByListKBUIDs implements repository.RepositoryI +func (mmGetTotalTokensByListKBUIDs *RepositoryIMock) GetTotalTokensByListKBUIDs(ctx context.Context, kbUIDs []uuid.UUID) (m1 map[uuid.UUID]int, err error) { + mm_atomic.AddUint64(&mmGetTotalTokensByListKBUIDs.beforeGetTotalTokensByListKBUIDsCounter, 1) + defer mm_atomic.AddUint64(&mmGetTotalTokensByListKBUIDs.afterGetTotalTokensByListKBUIDsCounter, 1) + + if mmGetTotalTokensByListKBUIDs.inspectFuncGetTotalTokensByListKBUIDs != nil { + mmGetTotalTokensByListKBUIDs.inspectFuncGetTotalTokensByListKBUIDs(ctx, kbUIDs) + } + + mm_params := RepositoryIMockGetTotalTokensByListKBUIDsParams{ctx, kbUIDs} + + // Record call args + mmGetTotalTokensByListKBUIDs.GetTotalTokensByListKBUIDsMock.mutex.Lock() + mmGetTotalTokensByListKBUIDs.GetTotalTokensByListKBUIDsMock.callArgs = append(mmGetTotalTokensByListKBUIDs.GetTotalTokensByListKBUIDsMock.callArgs, &mm_params) + mmGetTotalTokensByListKBUIDs.GetTotalTokensByListKBUIDsMock.mutex.Unlock() + + for _, e := range mmGetTotalTokensByListKBUIDs.GetTotalTokensByListKBUIDsMock.expectations { + if minimock.Equal(*e.params, mm_params) { + mm_atomic.AddUint64(&e.Counter, 1) + return e.results.m1, e.results.err + } + } + + if mmGetTotalTokensByListKBUIDs.GetTotalTokensByListKBUIDsMock.defaultExpectation != nil { + mm_atomic.AddUint64(&mmGetTotalTokensByListKBUIDs.GetTotalTokensByListKBUIDsMock.defaultExpectation.Counter, 1) + mm_want := mmGetTotalTokensByListKBUIDs.GetTotalTokensByListKBUIDsMock.defaultExpectation.params + mm_want_ptrs := mmGetTotalTokensByListKBUIDs.GetTotalTokensByListKBUIDsMock.defaultExpectation.paramPtrs + + mm_got := RepositoryIMockGetTotalTokensByListKBUIDsParams{ctx, kbUIDs} + + if mm_want_ptrs != nil { + + if mm_want_ptrs.ctx != nil && !minimock.Equal(*mm_want_ptrs.ctx, mm_got.ctx) { + mmGetTotalTokensByListKBUIDs.t.Errorf("RepositoryIMock.GetTotalTokensByListKBUIDs got unexpected parameter ctx, want: %#v, got: %#v%s\n", *mm_want_ptrs.ctx, mm_got.ctx, minimock.Diff(*mm_want_ptrs.ctx, mm_got.ctx)) + } + + if mm_want_ptrs.kbUIDs != nil && !minimock.Equal(*mm_want_ptrs.kbUIDs, mm_got.kbUIDs) { + mmGetTotalTokensByListKBUIDs.t.Errorf("RepositoryIMock.GetTotalTokensByListKBUIDs got unexpected parameter kbUIDs, want: %#v, got: %#v%s\n", *mm_want_ptrs.kbUIDs, mm_got.kbUIDs, minimock.Diff(*mm_want_ptrs.kbUIDs, mm_got.kbUIDs)) + } + + } else if mm_want != nil && !minimock.Equal(*mm_want, mm_got) { + mmGetTotalTokensByListKBUIDs.t.Errorf("RepositoryIMock.GetTotalTokensByListKBUIDs got unexpected parameters, want: %#v, got: %#v%s\n", *mm_want, mm_got, minimock.Diff(*mm_want, mm_got)) + } + + mm_results := mmGetTotalTokensByListKBUIDs.GetTotalTokensByListKBUIDsMock.defaultExpectation.results + if mm_results == nil { + mmGetTotalTokensByListKBUIDs.t.Fatal("No results are set for the RepositoryIMock.GetTotalTokensByListKBUIDs") + } + return (*mm_results).m1, (*mm_results).err + } + if mmGetTotalTokensByListKBUIDs.funcGetTotalTokensByListKBUIDs != nil { + return mmGetTotalTokensByListKBUIDs.funcGetTotalTokensByListKBUIDs(ctx, kbUIDs) + } + mmGetTotalTokensByListKBUIDs.t.Fatalf("Unexpected call to RepositoryIMock.GetTotalTokensByListKBUIDs. %v %v", ctx, kbUIDs) + return +} + +// GetTotalTokensByListKBUIDsAfterCounter returns a count of finished RepositoryIMock.GetTotalTokensByListKBUIDs invocations +func (mmGetTotalTokensByListKBUIDs *RepositoryIMock) GetTotalTokensByListKBUIDsAfterCounter() uint64 { + return mm_atomic.LoadUint64(&mmGetTotalTokensByListKBUIDs.afterGetTotalTokensByListKBUIDsCounter) +} + +// GetTotalTokensByListKBUIDsBeforeCounter returns a count of RepositoryIMock.GetTotalTokensByListKBUIDs invocations +func (mmGetTotalTokensByListKBUIDs *RepositoryIMock) GetTotalTokensByListKBUIDsBeforeCounter() uint64 { + return mm_atomic.LoadUint64(&mmGetTotalTokensByListKBUIDs.beforeGetTotalTokensByListKBUIDsCounter) +} + +// Calls returns a list of arguments used in each call to RepositoryIMock.GetTotalTokensByListKBUIDs. +// The list is in the same order as the calls were made (i.e. recent calls have a higher index) +func (mmGetTotalTokensByListKBUIDs *mRepositoryIMockGetTotalTokensByListKBUIDs) Calls() []*RepositoryIMockGetTotalTokensByListKBUIDsParams { + mmGetTotalTokensByListKBUIDs.mutex.RLock() + + argCopy := make([]*RepositoryIMockGetTotalTokensByListKBUIDsParams, len(mmGetTotalTokensByListKBUIDs.callArgs)) + copy(argCopy, mmGetTotalTokensByListKBUIDs.callArgs) + + mmGetTotalTokensByListKBUIDs.mutex.RUnlock() + + return argCopy +} + +// MinimockGetTotalTokensByListKBUIDsDone returns true if the count of the GetTotalTokensByListKBUIDs invocations corresponds +// the number of defined expectations +func (m *RepositoryIMock) MinimockGetTotalTokensByListKBUIDsDone() bool { + for _, e := range m.GetTotalTokensByListKBUIDsMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + return false + } + } + + return m.GetTotalTokensByListKBUIDsMock.invocationsDone() +} + +// MinimockGetTotalTokensByListKBUIDsInspect logs each unmet expectation +func (m *RepositoryIMock) MinimockGetTotalTokensByListKBUIDsInspect() { + for _, e := range m.GetTotalTokensByListKBUIDsMock.expectations { + if mm_atomic.LoadUint64(&e.Counter) < 1 { + m.t.Errorf("Expected call to RepositoryIMock.GetTotalTokensByListKBUIDs with params: %#v", *e.params) + } + } + + afterGetTotalTokensByListKBUIDsCounter := mm_atomic.LoadUint64(&m.afterGetTotalTokensByListKBUIDsCounter) + // if default expectation was set then invocations count should be greater than zero + if m.GetTotalTokensByListKBUIDsMock.defaultExpectation != nil && afterGetTotalTokensByListKBUIDsCounter < 1 { + if m.GetTotalTokensByListKBUIDsMock.defaultExpectation.params == nil { + m.t.Error("Expected call to RepositoryIMock.GetTotalTokensByListKBUIDs") + } else { + m.t.Errorf("Expected call to RepositoryIMock.GetTotalTokensByListKBUIDs with params: %#v", *m.GetTotalTokensByListKBUIDsMock.defaultExpectation.params) + } + } + // if func was set then invocations count should be greater than zero + if m.funcGetTotalTokensByListKBUIDs != nil && afterGetTotalTokensByListKBUIDsCounter < 1 { + m.t.Error("Expected call to RepositoryIMock.GetTotalTokensByListKBUIDs") + } + + if !m.GetTotalTokensByListKBUIDsMock.invocationsDone() && afterGetTotalTokensByListKBUIDsCounter > 0 { + m.t.Errorf("Expected %d calls to RepositoryIMock.GetTotalTokensByListKBUIDs but found %d calls", + mm_atomic.LoadUint64(&m.GetTotalTokensByListKBUIDsMock.expectedInvocations), afterGetTotalTokensByListKBUIDsCounter) + } +} + type mRepositoryIMockKnowledgeBaseFileTableName struct { mock *RepositoryIMock defaultExpectation *RepositoryIMockKnowledgeBaseFileTableNameExpectation @@ -8927,6 +9555,8 @@ func (m *RepositoryIMock) MinimockFinish() { m.MinimockGetConvertedFileByFileUIDInspect() + m.MinimockGetCountFilesByListKnowledgeBaseUIDInspect() + m.MinimockGetEmbeddingByUIDsInspect() m.MinimockGetIncompleteFileInspect() @@ -8937,6 +9567,8 @@ func (m *RepositoryIMock) MinimockFinish() { m.MinimockGetTextChunksBySourceInspect() + m.MinimockGetTotalTokensByListKBUIDsInspect() + m.MinimockKnowledgeBaseFileTableNameInspect() m.MinimockListKnowledgeBaseFilesInspect() @@ -8992,11 +9624,13 @@ func (m *RepositoryIMock) minimockDone() bool { m.MinimockDeleteKnowledgeBaseFileDone() && m.MinimockDeleteRepositoryTagDone() && m.MinimockGetConvertedFileByFileUIDDone() && + m.MinimockGetCountFilesByListKnowledgeBaseUIDDone() && m.MinimockGetEmbeddingByUIDsDone() && m.MinimockGetIncompleteFileDone() && m.MinimockGetKnowledgeBaseByOwnerAndIDDone() && m.MinimockGetRepositoryTagDone() && m.MinimockGetTextChunksBySourceDone() && + m.MinimockGetTotalTokensByListKBUIDsDone() && m.MinimockKnowledgeBaseFileTableNameDone() && m.MinimockListKnowledgeBaseFilesDone() && m.MinimockListKnowledgeBasesDone() && diff --git a/pkg/repository/chunk.go b/pkg/repository/chunk.go index eacbf56..c60c402 100644 --- a/pkg/repository/chunk.go +++ b/pkg/repository/chunk.go @@ -16,6 +16,7 @@ type TextChunkI interface { DeleteChunksBySource(ctx context.Context, sourceTable string, sourceUID uuid.UUID) error DeleteChunksByUIDs(ctx context.Context, chunkUIDs []uuid.UUID) error GetTextChunksBySource(ctx context.Context, sourceTable string, sourceUID uuid.UUID) ([]TextChunk, error) + GetTotalTokensByListKBUIDs(ctx context.Context, kbUIDs []uuid.UUID) (map[uuid.UUID]int, error) } // currently, we use minio to store the chunk but in the future, we may just get the content from the source @@ -33,6 +34,8 @@ type TextChunk struct { InOrder int `gorm:"column:in_order;not null" json:"order"` CreateTime *time.Time `gorm:"column:create_time;not null;default:CURRENT_TIMESTAMP" json:"create_time"` UpdateTime *time.Time `gorm:"column:update_time;not null;default:CURRENT_TIMESTAMP" json:"update_time"` + // KbUID is the knowledge base UID + KbUID uuid.UUID `gorm:"column:kb_uid;type:uuid" json:"kb_uid"` } type TextChunkColumns struct { @@ -158,3 +161,24 @@ func (r *Repository) GetTextChunksBySource(ctx context.Context, sourceTable stri } return chunks, nil } + +// GetTotalTokensByListKBUIDs returns the total tokens of the chunks by list of KBUIDs +func (r *Repository) GetTotalTokensByListKBUIDs(ctx context.Context, kbUIDs []uuid.UUID) (map[uuid.UUID]int, error) { + var totalTokens []struct { + KbUID uuid.UUID `gorm:"kb_uid"` + Tokens int `gorm:"tokens"` + } + if err := r.db.WithContext(ctx).Model(&TextChunk{}). + Select("kb_uid, SUM(tokens) as tokens"). + Where("kb_uid IN (?)", kbUIDs). + Group("kb_uid"). + Scan(&totalTokens).Error; err != nil { + return nil, err + } + + totalTokensMap := make(map[uuid.UUID]int) + for _, tt := range totalTokens { + totalTokensMap[tt.KbUID] = tt.Tokens + } + return totalTokensMap, nil +} diff --git a/pkg/repository/knowledgebasefile.go b/pkg/repository/knowledgebasefile.go index 02db390..b372b54 100644 --- a/pkg/repository/knowledgebasefile.go +++ b/pkg/repository/knowledgebasefile.go @@ -11,13 +11,22 @@ import ( ) type KnowledgeBaseFileI interface { + // KnowledgeBaseFileTableName returns the table name of the KnowledgeBaseFile KnowledgeBaseFileTableName() string + // CreateKnowledgeBaseFile creates a new knowledge base file CreateKnowledgeBaseFile(ctx context.Context, kb KnowledgeBaseFile, externalServiceCall func(FileUID string) error) (*KnowledgeBaseFile, error) + // ListKnowledgeBaseFiles lists the knowledge base files by owner UID, knowledge base UID, and page size ListKnowledgeBaseFiles(ctx context.Context, uid string, ownerUID string, kbUID string, pageSize int32, nextPageToken string, filesUID []string) ([]KnowledgeBaseFile, int, string, error) + // DeleteKnowledgeBaseFile deletes the knowledge base file by file UID DeleteKnowledgeBaseFile(ctx context.Context, fileUID string) error + // ProcessKnowledgeBaseFiles updates the process status of the files ProcessKnowledgeBaseFiles(ctx context.Context, fileUids []string) ([]KnowledgeBaseFile, error) + // GetIncompleteFile returns the files that are not yet processed GetIncompleteFile(ctx context.Context) []KnowledgeBaseFile + // UpdateKnowledgeBaseFile updates the data and retrieves the latest data UpdateKnowledgeBaseFile(ctx context.Context, fileUID string, updateMap map[string]interface{}) (*KnowledgeBaseFile, error) + // GetCountFilesByListKnowledgeBaseUID returns the number of files associated with the knowledge base UID + GetCountFilesByListKnowledgeBaseUID(ctx context.Context, kbUIDs []uuid.UUID) (map[uuid.UUID]int64, error) } type KnowledgeBaseFile struct { @@ -243,3 +252,32 @@ func (r *Repository) UpdateKnowledgeBaseFile(ctx context.Context, fileUID string return &updatedFile, nil } + +// CountFilesByListKnowledgeBaseUID returns the number of files associated with the knowledge base UID +func (r *Repository) GetCountFilesByListKnowledgeBaseUID(ctx context.Context, kbUIDs []uuid.UUID) (map[uuid.UUID]int64, error) { + var results []struct { + KnowledgeBaseUID uuid.UUID `gorm:"column:kb_uid"` + Count int64 `gorm:"column:count"` + } + + selectClause := fmt.Sprintf("%v, COUNT(*) as count", KnowledgeBaseFileColumn.KnowledgeBaseUID) + whereClause := fmt.Sprintf("%v IN ? AND %v IS NULL", KnowledgeBaseFileColumn.KnowledgeBaseUID, KnowledgeBaseFileColumn.DeleteTime) + + // Adjust the query to match the structure and requirements of your database and tables + err := r.db.Table(r.KnowledgeBaseFileTableName()). + Select(selectClause). + Where(whereClause, kbUIDs). + Group(KnowledgeBaseFileColumn.KnowledgeBaseUID). + Find(&results).Error + + if err != nil { + return nil, fmt.Errorf("error querying database: %w", err) + } + + counts := make(map[uuid.UUID]int64) + for _, result := range results { + counts[result.KnowledgeBaseUID] = result.Count + } + + return counts, nil +} diff --git a/pkg/worker/worker.go b/pkg/worker/worker.go index e15a9e5..0f44031 100644 --- a/pkg/worker/worker.go +++ b/pkg/worker/worker.go @@ -645,6 +645,10 @@ type chunk = struct { func (wp *fileToEmbWorkerPool) saveChunks(ctx context.Context, kbUID string, sourceTable string, sourceUID uuid.UUID, chunks []chunk) error { logger, _ := logger.GetZapLogger(ctx) textChunks := make([]*repository.TextChunk, len(chunks)) + + // turn kbuid to uuid no must parse + kbUIDuuid, _ := uuid.Parse(kbUID) + for i, c := range chunks { textChunks[i] = &repository.TextChunk{ SourceUID: sourceUID, @@ -655,6 +659,7 @@ func (wp *fileToEmbWorkerPool) saveChunks(ctx context.Context, kbUID string, sou Tokens: c.Tokens, Retrievable: true, InOrder: i, + KbUID: kbUIDuuid, } } _, err := wp.svc.Repository.DeleteAndCreateChunks(ctx, sourceTable, sourceUID, textChunks,