b2605abd6a
- Added `EnableWebSearch` and `KnowledgeGroupIDs` fields to `KolGenerationSubmitRequest`. - Updated `Submit` method in `KolGenerationService` to handle new request fields. - Integrated web search and knowledge group validation based on card configuration. - Introduced caching mechanisms in `KolPackageService`, `KolPromptService`, and `KolSubscriptionAdminService` to improve performance. - Implemented knowledge context resolution in `KolGenerationWorker` to enrich prompts with relevant knowledge snippets. - Added utility functions for handling card configuration in both backend and frontend. - Created tests for knowledge extraction and rendering to ensure accuracy and reliability.
594 lines
20 KiB
Go
594 lines
20 KiB
Go
package app
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"github.com/geo-platform/tenant-api/internal/shared/auth"
|
|
sharedcache "github.com/geo-platform/tenant-api/internal/shared/cache"
|
|
"github.com/geo-platform/tenant-api/internal/shared/response"
|
|
"github.com/geo-platform/tenant-api/internal/tenant/repository"
|
|
)
|
|
|
|
var (
|
|
errKolPackageNotFound = response.ErrNotFound(40461, "kol_package_not_found", "package not found")
|
|
ErrPackageNotOwned = response.ErrForbidden(40361, "kol_package_not_owned", "package is not owned by current KOL")
|
|
ErrPromptNotFound = response.ErrNotFound(40462, "kol_prompt_not_found", "prompt not found")
|
|
ErrPromptNotOwned = response.ErrForbidden(40362, "kol_prompt_not_owned", "prompt is not owned by current KOL")
|
|
errKolPromptContentEmpty = response.ErrConflict(40961, "kol_prompt_content_missing", "prompt content is required before publish or activate")
|
|
)
|
|
|
|
type CreatePromptInput struct {
|
|
PackageID int64 `json:"-"`
|
|
Name string `json:"name" binding:"required"`
|
|
PlatformHint *string `json:"platform_hint"`
|
|
}
|
|
|
|
type UpdateKolPromptInput struct {
|
|
Name string `json:"name" binding:"required"`
|
|
PlatformHint *string `json:"platform_hint"`
|
|
SortOrder *int `json:"sort_order"`
|
|
}
|
|
|
|
type PublishPromptInput struct {
|
|
Name string `json:"name" binding:"required"`
|
|
PlatformHint *string `json:"platform_hint"`
|
|
SortOrder *int `json:"sort_order"`
|
|
Content string `json:"content" binding:"required"`
|
|
Schema KolSchemaJSON `json:"schema" binding:"required"`
|
|
CardConfig map[string]interface{} `json:"card_config"`
|
|
}
|
|
|
|
type KolPromptDetailResponse struct {
|
|
ID int64 `json:"id"`
|
|
PackageID int64 `json:"package_id"`
|
|
Name string `json:"name"`
|
|
PlatformHint *string `json:"platform_hint"`
|
|
Status string `json:"status"`
|
|
SortOrder int `json:"sort_order"`
|
|
PublishedRevisionNo *int `json:"published_revision_no"`
|
|
DraftRevisionNo *int `json:"draft_revision_no"`
|
|
LatestSchemaJSON *KolSchemaJSON `json:"latest_schema_json"`
|
|
LatestCardConfigJSON map[string]interface{} `json:"latest_card_config_json"`
|
|
LatestPromptContent *string `json:"latest_prompt_content"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type KolPromptService struct {
|
|
pool *pgxpool.Pool
|
|
profileSvc *KolProfileService
|
|
pkgRepo repository.KolPackageRepository
|
|
promptRepo repository.KolPromptRepository
|
|
subRepo repository.KolSubscriptionRepository
|
|
asset *KolPromptAsset
|
|
cache sharedcache.Cache
|
|
}
|
|
|
|
func NewKolPromptService(
|
|
pool *pgxpool.Pool,
|
|
profileSvc *KolProfileService,
|
|
pkgRepo repository.KolPackageRepository,
|
|
promptRepo repository.KolPromptRepository,
|
|
subRepo repository.KolSubscriptionRepository,
|
|
asset *KolPromptAsset,
|
|
) *KolPromptService {
|
|
return &KolPromptService{
|
|
pool: pool,
|
|
profileSvc: profileSvc,
|
|
pkgRepo: pkgRepo,
|
|
promptRepo: promptRepo,
|
|
subRepo: subRepo,
|
|
asset: asset,
|
|
}
|
|
}
|
|
|
|
func (s *KolPromptService) WithCache(c sharedcache.Cache) *KolPromptService {
|
|
s.cache = c
|
|
return s
|
|
}
|
|
|
|
func (s *KolPromptService) CreatePrompt(ctx context.Context, actor auth.Actor, input CreatePromptInput) (*KolPromptDetailResponse, error) {
|
|
if strings.TrimSpace(input.Name) == "" {
|
|
return nil, response.ErrBadRequest(40063, "kol_prompt_name_required", "prompt name is required")
|
|
}
|
|
|
|
if _, _, err := s.loadOwnedPackage(ctx, actor, input.PackageID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prompt, err := s.promptRepo.Create(ctx, repository.CreateKolPromptInput{
|
|
TenantID: actor.TenantID,
|
|
PackageID: input.PackageID,
|
|
Name: strings.TrimSpace(input.Name),
|
|
PlatformHint: normalizeKolOptionalString(input.PlatformHint),
|
|
SortOrder: 0,
|
|
CreatedBy: actor.UserID,
|
|
})
|
|
if err != nil {
|
|
return nil, response.ErrInternal(50070, "kol_prompt_create_failed", err.Error())
|
|
}
|
|
|
|
InvalidateKolPromptCaches(ctx, s.cache)
|
|
return s.buildPromptDetail(ctx, prompt, false)
|
|
}
|
|
|
|
func (s *KolPromptService) ListByPackage(ctx context.Context, actor auth.Actor, packageID int64) ([]KolPromptDetailResponse, error) {
|
|
if _, _, err := s.loadOwnedPackage(ctx, actor, packageID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
prompts, err := s.promptRepo.ListByPackage(ctx, actor.TenantID, packageID)
|
|
if err != nil {
|
|
return nil, response.ErrInternal(50071, "kol_prompt_query_failed", err.Error())
|
|
}
|
|
|
|
result := make([]KolPromptDetailResponse, 0, len(prompts))
|
|
for _, prompt := range prompts {
|
|
item, err := s.buildPromptDetail(ctx, &prompt, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result = append(result, *item)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func (s *KolPromptService) GetForOwner(ctx context.Context, actor auth.Actor, promptID int64) (*KolPromptDetailResponse, error) {
|
|
_, prompt, _, err := s.loadOwnedPrompt(ctx, actor, promptID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return s.buildPromptDetail(ctx, prompt, true)
|
|
}
|
|
|
|
func (s *KolPromptService) UpdateMetadata(ctx context.Context, actor auth.Actor, promptID int64, input UpdateKolPromptInput) (*KolPromptDetailResponse, error) {
|
|
_, prompt, _, err := s.loadOwnedPrompt(ctx, actor, promptID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
name := strings.TrimSpace(input.Name)
|
|
if name == "" {
|
|
return nil, response.ErrBadRequest(40063, "kol_prompt_name_required", "prompt name is required")
|
|
}
|
|
|
|
sortOrder := prompt.SortOrder
|
|
if input.SortOrder != nil {
|
|
sortOrder = *input.SortOrder
|
|
}
|
|
|
|
if err := s.promptRepo.UpdateMetadata(ctx, repository.UpdateKolPromptMetadataInput{
|
|
ID: promptID,
|
|
TenantID: actor.TenantID,
|
|
Name: name,
|
|
PlatformHint: normalizeKolOptionalString(input.PlatformHint),
|
|
SortOrder: sortOrder,
|
|
UpdatedBy: actor.UserID,
|
|
}); err != nil {
|
|
return nil, response.ErrInternal(50072, "kol_prompt_update_failed", err.Error())
|
|
}
|
|
|
|
updated, err := s.promptRepo.GetByID(ctx, actor.TenantID, promptID)
|
|
if err != nil {
|
|
return nil, response.ErrInternal(50071, "kol_prompt_query_failed", err.Error())
|
|
}
|
|
if updated == nil {
|
|
return nil, ErrPromptNotFound
|
|
}
|
|
|
|
InvalidateKolPromptCaches(ctx, s.cache)
|
|
return s.buildPromptDetail(ctx, updated, false)
|
|
}
|
|
|
|
func (s *KolPromptService) Save(ctx context.Context, actor auth.Actor, promptID int64, input PublishPromptInput) (*KolPromptDetailResponse, error) {
|
|
_, prompt, _, err := s.loadOwnedPrompt(ctx, actor, promptID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
name := strings.TrimSpace(input.Name)
|
|
if name == "" {
|
|
return nil, response.ErrBadRequest(40063, "kol_prompt_name_required", "prompt name is required")
|
|
}
|
|
if strings.TrimSpace(input.Content) == "" {
|
|
return nil, response.ErrBadRequest(40064, "kol_prompt_content_required", "prompt content is required")
|
|
}
|
|
if err := ValidateSchema(input.Schema); err != nil {
|
|
return nil, response.ErrBadRequest(40065, "kol_prompt_schema_invalid", err.Error())
|
|
}
|
|
|
|
sortOrder := prompt.SortOrder
|
|
if input.SortOrder != nil {
|
|
sortOrder = *input.SortOrder
|
|
}
|
|
|
|
const currentRevisionNo = 1
|
|
|
|
assetKey, err := s.asset.Put(ctx, actor.TenantID, promptID, currentRevisionNo, input.Content)
|
|
if err != nil {
|
|
return nil, response.ErrInternal(50074, "kol_prompt_asset_store_failed", err.Error())
|
|
}
|
|
|
|
schemaJSON, err := JSONEncodeSchema(input.Schema)
|
|
if err != nil {
|
|
return nil, response.ErrBadRequest(40065, "kol_prompt_schema_invalid", err.Error())
|
|
}
|
|
|
|
cardConfigJSON, err := json.Marshal(normalizeKolCardConfig(input.CardConfig))
|
|
if err != nil {
|
|
return nil, response.ErrBadRequest(40066, "kol_prompt_card_config_invalid", "card_config must be valid json")
|
|
}
|
|
|
|
publishedRevisionNo := prompt.PublishedRevisionNo
|
|
if prompt.Status == "active" {
|
|
revNo := currentRevisionNo
|
|
publishedRevisionNo = &revNo
|
|
}
|
|
|
|
if err := s.promptRepo.Save(ctx, repository.SaveKolPromptInput{
|
|
ID: promptID,
|
|
TenantID: actor.TenantID,
|
|
Name: name,
|
|
PlatformHint: normalizeKolOptionalString(input.PlatformHint),
|
|
SortOrder: sortOrder,
|
|
PromptAssetKey: assetKey,
|
|
SchemaJSON: schemaJSON,
|
|
CardConfigJSON: cardConfigJSON,
|
|
Status: prompt.Status,
|
|
PublishedRevisionNo: publishedRevisionNo,
|
|
UpdatedBy: actor.UserID,
|
|
}); err != nil {
|
|
return nil, response.ErrInternal(50072, "kol_prompt_update_failed", err.Error())
|
|
}
|
|
|
|
updated, err := s.promptRepo.GetByID(ctx, actor.TenantID, promptID)
|
|
if err != nil {
|
|
return nil, response.ErrInternal(50071, "kol_prompt_query_failed", err.Error())
|
|
}
|
|
if updated == nil {
|
|
return nil, ErrPromptNotFound
|
|
}
|
|
|
|
InvalidateKolPromptCaches(ctx, s.cache)
|
|
return s.buildPromptDetail(ctx, updated, true)
|
|
}
|
|
|
|
func (s *KolPromptService) Publish(ctx context.Context, actor auth.Actor, promptID int64, input PublishPromptInput) (*KolPromptDetailResponse, error) {
|
|
_, prompt, pkg, err := s.loadOwnedPrompt(ctx, actor, promptID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
name := strings.TrimSpace(input.Name)
|
|
if name == "" {
|
|
return nil, response.ErrBadRequest(40063, "kol_prompt_name_required", "prompt name is required")
|
|
}
|
|
if strings.TrimSpace(input.Content) == "" {
|
|
return nil, response.ErrBadRequest(40064, "kol_prompt_content_required", "prompt content is required")
|
|
}
|
|
if err := ValidateSchema(input.Schema); err != nil {
|
|
return nil, response.ErrBadRequest(40065, "kol_prompt_schema_invalid", err.Error())
|
|
}
|
|
|
|
sortOrder := prompt.SortOrder
|
|
if input.SortOrder != nil {
|
|
sortOrder = *input.SortOrder
|
|
}
|
|
|
|
const publishedRevisionNo = 1
|
|
|
|
assetKey, err := s.asset.Put(ctx, actor.TenantID, promptID, publishedRevisionNo, input.Content)
|
|
if err != nil {
|
|
return nil, response.ErrInternal(50074, "kol_prompt_asset_store_failed", err.Error())
|
|
}
|
|
|
|
schemaJSON, err := JSONEncodeSchema(input.Schema)
|
|
if err != nil {
|
|
return nil, response.ErrBadRequest(40065, "kol_prompt_schema_invalid", err.Error())
|
|
}
|
|
|
|
cardConfigJSON, err := json.Marshal(normalizeKolCardConfig(input.CardConfig))
|
|
if err != nil {
|
|
return nil, response.ErrBadRequest(40066, "kol_prompt_card_config_invalid", "card_config must be valid json")
|
|
}
|
|
|
|
tx, err := s.pool.Begin(ctx)
|
|
if err != nil {
|
|
return nil, response.ErrInternal(50079, "kol_prompt_publish_tx_failed", "failed to begin prompt publish transaction")
|
|
}
|
|
defer tx.Rollback(ctx)
|
|
|
|
promptTx := repository.NewKolPromptRepository(tx)
|
|
subTx := repository.NewKolSubscriptionRepository(tx)
|
|
|
|
if err := promptTx.Publish(ctx, repository.PublishKolPromptInput{
|
|
ID: promptID,
|
|
TenantID: actor.TenantID,
|
|
Name: name,
|
|
PlatformHint: normalizeKolOptionalString(input.PlatformHint),
|
|
SortOrder: sortOrder,
|
|
PromptAssetKey: assetKey,
|
|
SchemaJSON: schemaJSON,
|
|
CardConfigJSON: cardConfigJSON,
|
|
PublishedRevisionNo: publishedRevisionNo,
|
|
UpdatedBy: actor.UserID,
|
|
}); err != nil {
|
|
return nil, response.ErrInternal(50080, "kol_prompt_publish_failed", err.Error())
|
|
}
|
|
|
|
if err := s.grantPromptToActiveSubscribers(ctx, subTx, pkg.ID, promptID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return nil, response.ErrInternal(50081, "kol_prompt_publish_commit_failed", err.Error())
|
|
}
|
|
|
|
updated, err := s.promptRepo.GetByID(ctx, actor.TenantID, promptID)
|
|
if err != nil {
|
|
return nil, response.ErrInternal(50071, "kol_prompt_query_failed", err.Error())
|
|
}
|
|
if updated == nil {
|
|
return nil, ErrPromptNotFound
|
|
}
|
|
|
|
InvalidateKolPromptCaches(ctx, s.cache)
|
|
return s.buildPromptDetail(ctx, updated, true)
|
|
}
|
|
|
|
func (s *KolPromptService) Activate(ctx context.Context, actor auth.Actor, promptID int64) (*KolPromptDetailResponse, error) {
|
|
_, prompt, pkg, err := s.loadOwnedPrompt(ctx, actor, promptID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tx, err := s.pool.Begin(ctx)
|
|
if err != nil {
|
|
return nil, response.ErrInternal(50088, "kol_prompt_activate_tx_failed", "failed to begin prompt activate transaction")
|
|
}
|
|
defer tx.Rollback(ctx)
|
|
|
|
promptTx := repository.NewKolPromptRepository(tx)
|
|
subTx := repository.NewKolSubscriptionRepository(tx)
|
|
|
|
switch {
|
|
case prompt.PromptAssetKey != nil && strings.TrimSpace(*prompt.PromptAssetKey) != "":
|
|
if err := promptTx.SetPublishedRevision(ctx, actor.TenantID, promptID, 1, actor.UserID); err != nil {
|
|
return nil, response.ErrInternal(50090, "kol_prompt_activate_failed", err.Error())
|
|
}
|
|
default:
|
|
return nil, errKolPromptContentEmpty
|
|
}
|
|
|
|
if err := s.grantPromptToActiveSubscribers(ctx, subTx, pkg.ID, promptID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := tx.Commit(ctx); err != nil {
|
|
return nil, response.ErrInternal(50091, "kol_prompt_activate_commit_failed", "failed to commit prompt activate transaction")
|
|
}
|
|
|
|
updated, err := s.promptRepo.GetByID(ctx, actor.TenantID, promptID)
|
|
if err != nil {
|
|
return nil, response.ErrInternal(50071, "kol_prompt_query_failed", err.Error())
|
|
}
|
|
if updated == nil {
|
|
return nil, ErrPromptNotFound
|
|
}
|
|
|
|
InvalidateKolPromptCaches(ctx, s.cache)
|
|
return s.buildPromptDetail(ctx, updated, false)
|
|
}
|
|
|
|
func (s *KolPromptService) Archive(ctx context.Context, actor auth.Actor, promptID int64) (*KolPromptDetailResponse, error) {
|
|
if _, _, _, err := s.loadOwnedPrompt(ctx, actor, promptID); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := s.promptRepo.UpdateStatus(ctx, actor.TenantID, promptID, "archived", actor.UserID); err != nil {
|
|
return nil, response.ErrInternal(50092, "kol_prompt_archive_failed", err.Error())
|
|
}
|
|
|
|
updated, err := s.promptRepo.GetByID(ctx, actor.TenantID, promptID)
|
|
if err != nil {
|
|
return nil, response.ErrInternal(50071, "kol_prompt_query_failed", err.Error())
|
|
}
|
|
if updated == nil {
|
|
return nil, ErrPromptNotFound
|
|
}
|
|
|
|
InvalidateKolPromptCaches(ctx, s.cache)
|
|
return s.buildPromptDetail(ctx, updated, false)
|
|
}
|
|
|
|
func (s *KolPromptService) Delete(ctx context.Context, actor auth.Actor, promptID int64) error {
|
|
if _, _, _, err := s.loadOwnedPrompt(ctx, actor, promptID); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := s.promptRepo.SoftDelete(ctx, actor.TenantID, promptID); err != nil {
|
|
return response.ErrInternal(50082, "kol_prompt_delete_failed", err.Error())
|
|
}
|
|
|
|
InvalidateKolPromptCaches(ctx, s.cache)
|
|
return nil
|
|
}
|
|
|
|
func (s *KolPromptService) loadOwnedPackage(ctx context.Context, actor auth.Actor, packageID int64) (*repository.KolProfile, *repository.KolPackage, error) {
|
|
profile, err := s.profileSvc.RequireActiveKol(ctx, actor)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
pkg, err := s.pkgRepo.GetByID(ctx, actor.TenantID, packageID)
|
|
if err != nil {
|
|
return nil, nil, response.ErrInternal(50062, "kol_package_query_failed", err.Error())
|
|
}
|
|
if pkg == nil {
|
|
return nil, nil, errKolPackageNotFound
|
|
}
|
|
if pkg.KolProfileID != profile.ID {
|
|
return nil, nil, ErrPackageNotOwned
|
|
}
|
|
|
|
return profile, pkg, nil
|
|
}
|
|
|
|
func (s *KolPromptService) loadOwnedPrompt(ctx context.Context, actor auth.Actor, promptID int64) (*repository.KolProfile, *repository.KolPrompt, *repository.KolPackage, error) {
|
|
profile, err := s.profileSvc.RequireActiveKol(ctx, actor)
|
|
if err != nil {
|
|
return nil, nil, nil, err
|
|
}
|
|
|
|
prompt, err := s.promptRepo.GetByID(ctx, actor.TenantID, promptID)
|
|
if err != nil {
|
|
return nil, nil, nil, response.ErrInternal(50071, "kol_prompt_query_failed", err.Error())
|
|
}
|
|
if prompt == nil {
|
|
return nil, nil, nil, ErrPromptNotFound
|
|
}
|
|
|
|
pkg, err := s.pkgRepo.GetByID(ctx, actor.TenantID, prompt.PackageID)
|
|
if err != nil {
|
|
return nil, nil, nil, response.ErrInternal(50062, "kol_package_query_failed", err.Error())
|
|
}
|
|
if pkg == nil {
|
|
return nil, nil, nil, errKolPackageNotFound
|
|
}
|
|
if pkg.KolProfileID != profile.ID {
|
|
return nil, nil, nil, ErrPromptNotOwned
|
|
}
|
|
|
|
return profile, prompt, pkg, nil
|
|
}
|
|
|
|
func (s *KolPromptService) buildPromptDetail(ctx context.Context, prompt *repository.KolPrompt, includeContent bool) (*KolPromptDetailResponse, error) {
|
|
detail := &KolPromptDetailResponse{
|
|
ID: prompt.ID,
|
|
PackageID: prompt.PackageID,
|
|
Name: prompt.Name,
|
|
PlatformHint: prompt.PlatformHint,
|
|
Status: prompt.Status,
|
|
SortOrder: prompt.SortOrder,
|
|
PublishedRevisionNo: prompt.PublishedRevisionNo,
|
|
DraftRevisionNo: prompt.DraftRevisionNo,
|
|
CreatedAt: prompt.CreatedAt,
|
|
UpdatedAt: prompt.UpdatedAt,
|
|
}
|
|
|
|
if len(prompt.SchemaJSON) == 0 && len(prompt.CardConfigJSON) == 0 && (prompt.PromptAssetKey == nil || strings.TrimSpace(*prompt.PromptAssetKey) == "") {
|
|
return detail, nil
|
|
}
|
|
|
|
schema, err := decodeKolSchema(prompt.SchemaJSON)
|
|
if err != nil {
|
|
return nil, response.ErrInternal(50085, "kol_prompt_schema_decode_failed", err.Error())
|
|
}
|
|
cardConfig, err := decodeKolCardConfig(prompt.CardConfigJSON)
|
|
if err != nil {
|
|
return nil, response.ErrInternal(50086, "kol_prompt_card_config_decode_failed", err.Error())
|
|
}
|
|
|
|
detail.LatestSchemaJSON = schema
|
|
detail.LatestCardConfigJSON = cardConfig
|
|
|
|
if includeContent && prompt.PromptAssetKey != nil && strings.TrimSpace(*prompt.PromptAssetKey) != "" {
|
|
content, err := s.asset.Get(ctx, *prompt.PromptAssetKey)
|
|
if err != nil {
|
|
return nil, response.ErrInternal(50087, "kol_prompt_asset_load_failed", err.Error())
|
|
}
|
|
detail.LatestPromptContent = &content
|
|
}
|
|
|
|
return detail, nil
|
|
}
|
|
|
|
func (s *KolPromptService) grantPromptToActiveSubscribers(
|
|
ctx context.Context,
|
|
subRepo repository.KolSubscriptionRepository,
|
|
packageID, promptID int64,
|
|
) error {
|
|
subscribers, err := subRepo.ListActiveSubscribersForPackage(ctx, packageID)
|
|
if err != nil {
|
|
return response.ErrInternal(50083, "kol_subscription_query_failed", err.Error())
|
|
}
|
|
for _, sub := range subscribers {
|
|
if err := subRepo.CreateAccessRow(ctx, repository.CreateAccessRowInput{
|
|
TenantID: sub.TenantID,
|
|
SubscriptionID: sub.ID,
|
|
PackageID: packageID,
|
|
PromptID: promptID,
|
|
}); err != nil {
|
|
return response.ErrInternal(50084, "kol_subscription_access_create_failed", err.Error())
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func normalizeKolCardConfig(cardConfig map[string]interface{}) map[string]interface{} {
|
|
normalized := map[string]interface{}{
|
|
"allow_web_search": false,
|
|
"allow_user_knowledge": true,
|
|
}
|
|
for key, value := range cardConfig {
|
|
normalized[key] = value
|
|
}
|
|
normalized["allow_web_search"] = kolCardConfigBoolValue(cardConfig["allow_web_search"], false)
|
|
normalized["allow_user_knowledge"] = kolCardConfigBoolValue(cardConfig["allow_user_knowledge"], true)
|
|
return normalized
|
|
}
|
|
|
|
func kolCardConfigBoolValue(value interface{}, defaultValue bool) bool {
|
|
switch typed := value.(type) {
|
|
case bool:
|
|
return typed
|
|
case *bool:
|
|
if typed != nil {
|
|
return *typed
|
|
}
|
|
}
|
|
return defaultValue
|
|
}
|
|
|
|
func kolCardConfigAllowsWebSearch(cardConfig map[string]interface{}) bool {
|
|
return kolCardConfigBoolValue(cardConfig["allow_web_search"], false)
|
|
}
|
|
|
|
func kolCardConfigAllowsUserKnowledge(cardConfig map[string]interface{}) bool {
|
|
return kolCardConfigBoolValue(cardConfig["allow_user_knowledge"], true)
|
|
}
|
|
|
|
func decodeKolSchema(raw []byte) (*KolSchemaJSON, error) {
|
|
schema := normalizeKolSchema(KolSchemaJSON{})
|
|
trimmed := bytes.TrimSpace(raw)
|
|
if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
|
|
return &schema, nil
|
|
}
|
|
if err := json.Unmarshal(trimmed, &schema); err != nil {
|
|
return nil, err
|
|
}
|
|
schema = normalizeKolSchema(schema)
|
|
return &schema, nil
|
|
}
|
|
|
|
func decodeKolCardConfig(raw []byte) (map[string]interface{}, error) {
|
|
trimmed := bytes.TrimSpace(raw)
|
|
if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) {
|
|
return normalizeKolCardConfig(nil), nil
|
|
}
|
|
|
|
var cardConfig map[string]interface{}
|
|
if err := json.Unmarshal(trimmed, &cardConfig); err != nil {
|
|
return nil, err
|
|
}
|
|
return normalizeKolCardConfig(cardConfig), nil
|
|
}
|