Changeset 0.17.0 (#75)
This commit is contained in:
@@ -33,7 +33,7 @@ func (s *KnowledgeBaseStore) Create(ctx context.Context, kb *models.KnowledgeBas
|
||||
func (s *KnowledgeBaseStore) GetByID(ctx context.Context, id string) (*models.KnowledgeBase, error) {
|
||||
kb, err := scanKB(DB.QueryRowContext(ctx, `
|
||||
SELECT id, name, description, scope, owner_id, team_id, embedding_config,
|
||||
document_count, chunk_count, total_bytes, status, created_at, updated_at
|
||||
document_count, chunk_count, total_bytes, status, discoverable, created_at, updated_at
|
||||
FROM knowledge_bases WHERE id = $1`, id))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -71,7 +71,7 @@ func (s *KnowledgeBaseStore) ListForUser(ctx context.Context, userID string, tea
|
||||
// User can see: global + their teams + personal + group-granted.
|
||||
q := `
|
||||
SELECT id, name, description, scope, owner_id, team_id, embedding_config,
|
||||
document_count, chunk_count, total_bytes, status, created_at, updated_at
|
||||
document_count, chunk_count, total_bytes, status, discoverable, created_at, updated_at
|
||||
FROM knowledge_bases
|
||||
WHERE scope = 'global'
|
||||
OR (scope = 'personal' AND owner_id = $1)`
|
||||
@@ -107,21 +107,21 @@ func (s *KnowledgeBaseStore) ListForUser(ctx context.Context, userID string, tea
|
||||
func (s *KnowledgeBaseStore) ListGlobal(ctx context.Context) ([]models.KnowledgeBase, error) {
|
||||
return queryKBs(ctx, `
|
||||
SELECT id, name, description, scope, owner_id, team_id, embedding_config,
|
||||
document_count, chunk_count, total_bytes, status, created_at, updated_at
|
||||
document_count, chunk_count, total_bytes, status, discoverable, created_at, updated_at
|
||||
FROM knowledge_bases WHERE scope = 'global' ORDER BY name`)
|
||||
}
|
||||
|
||||
func (s *KnowledgeBaseStore) ListForTeam(ctx context.Context, teamID string) ([]models.KnowledgeBase, error) {
|
||||
return queryKBs(ctx, `
|
||||
SELECT id, name, description, scope, owner_id, team_id, embedding_config,
|
||||
document_count, chunk_count, total_bytes, status, created_at, updated_at
|
||||
document_count, chunk_count, total_bytes, status, discoverable, created_at, updated_at
|
||||
FROM knowledge_bases WHERE team_id = $1 ORDER BY name`, teamID)
|
||||
}
|
||||
|
||||
func (s *KnowledgeBaseStore) ListPersonal(ctx context.Context, userID string) ([]models.KnowledgeBase, error) {
|
||||
return queryKBs(ctx, `
|
||||
SELECT id, name, description, scope, owner_id, team_id, embedding_config,
|
||||
document_count, chunk_count, total_bytes, status, created_at, updated_at
|
||||
document_count, chunk_count, total_bytes, status, discoverable, created_at, updated_at
|
||||
FROM knowledge_bases WHERE scope = 'personal' AND owner_id = $1 ORDER BY name`, userID)
|
||||
}
|
||||
|
||||
@@ -196,6 +196,12 @@ func (s *KnowledgeBaseStore) UpdateDocumentText(ctx context.Context, id string,
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *KnowledgeBaseStore) UpdateDocumentStorageKey(ctx context.Context, id string, storageKey string) error {
|
||||
_, err := DB.ExecContext(ctx, `
|
||||
UPDATE kb_documents SET storage_key = $2 WHERE id = $1`, id, storageKey)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *KnowledgeBaseStore) DeleteDocument(ctx context.Context, id string) (*models.KBDocument, error) {
|
||||
var doc models.KBDocument
|
||||
var errMsg sql.NullString
|
||||
@@ -385,6 +391,111 @@ func (s *KnowledgeBaseStore) UpdateStats(ctx context.Context, kbID string) error
|
||||
return err
|
||||
}
|
||||
|
||||
// ── Discoverable Management (v0.17.0) ────────────
|
||||
|
||||
func (s *KnowledgeBaseStore) SetDiscoverable(ctx context.Context, kbID string, discoverable bool) error {
|
||||
_, err := DB.ExecContext(ctx, `
|
||||
UPDATE knowledge_bases SET discoverable = $2, updated_at = now()
|
||||
WHERE id = $1`, kbID, discoverable)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetActiveKBIDsWithPersona returns KB IDs accessible for a channel, including
|
||||
// KBs bound to the active persona. Persona-bound KBs are included even if
|
||||
// they are not discoverable and not channel-linked.
|
||||
func (s *KnowledgeBaseStore) GetActiveKBIDsWithPersona(ctx context.Context, channelID string, userID string, teamIDs []string, personaID string) ([]string, error) {
|
||||
// Start with channel-linked KBs (existing behavior)
|
||||
q := `
|
||||
SELECT ckb.kb_id
|
||||
FROM channel_knowledge_bases ckb
|
||||
JOIN knowledge_bases kb ON ckb.kb_id = kb.id
|
||||
WHERE ckb.channel_id = $1 AND ckb.enabled = true
|
||||
AND (
|
||||
kb.scope = 'global'
|
||||
OR (kb.scope = 'personal' AND kb.owner_id = $2)`
|
||||
|
||||
args := []interface{}{channelID, userID}
|
||||
|
||||
if len(teamIDs) > 0 {
|
||||
q += fmt.Sprintf(` OR (kb.scope = 'team' AND kb.team_id = ANY($%d))`, len(args)+1)
|
||||
args = append(args, pq.Array(teamIDs))
|
||||
}
|
||||
|
||||
q += `)`
|
||||
|
||||
// UNION with persona-bound KBs (bypass discoverable check)
|
||||
if personaID != "" {
|
||||
q += fmt.Sprintf(`
|
||||
UNION
|
||||
SELECT pkb.kb_id
|
||||
FROM persona_knowledge_bases pkb
|
||||
JOIN knowledge_bases kb ON pkb.kb_id = kb.id
|
||||
WHERE pkb.persona_id = $%d
|
||||
AND kb.chunk_count > 0`, len(args)+1)
|
||||
args = append(args, personaID)
|
||||
}
|
||||
|
||||
rows, err := DB.QueryContext(ctx, q, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
seen := make(map[string]bool)
|
||||
var ids []string
|
||||
for rows.Next() {
|
||||
var id string
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !seen[id] {
|
||||
seen[id] = true
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
return ids, rows.Err()
|
||||
}
|
||||
|
||||
// ListDiscoverable returns KBs the user can see AND that are discoverable.
|
||||
// Used for the channel KB toggle popup when kb_direct_access is enabled.
|
||||
func (s *KnowledgeBaseStore) ListDiscoverable(ctx context.Context, userID string, teamIDs []string) ([]models.KnowledgeBase, error) {
|
||||
q := `
|
||||
SELECT id, name, description, scope, owner_id, team_id, embedding_config,
|
||||
document_count, chunk_count, total_bytes, status, discoverable, created_at, updated_at
|
||||
FROM knowledge_bases
|
||||
WHERE discoverable = true
|
||||
AND (
|
||||
scope = 'global'
|
||||
OR (scope = 'personal' AND owner_id = $1)`
|
||||
|
||||
args := []interface{}{userID}
|
||||
|
||||
if len(teamIDs) > 0 {
|
||||
q += fmt.Sprintf(` OR (scope = 'team' AND team_id = ANY($%d))`, len(args)+1)
|
||||
args = append(args, pq.Array(teamIDs))
|
||||
}
|
||||
|
||||
// Group-granted KBs
|
||||
q += fmt.Sprintf(`
|
||||
OR id IN (
|
||||
SELECT rg.resource_id FROM resource_grants rg
|
||||
WHERE rg.resource_type = 'knowledge_base'
|
||||
AND (
|
||||
rg.grant_scope = 'global'
|
||||
OR (rg.grant_scope = 'groups'
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM group_members gm
|
||||
WHERE gm.user_id = $%d
|
||||
AND gm.group_id = ANY(rg.granted_groups)
|
||||
))
|
||||
)
|
||||
)`, len(args)+1)
|
||||
args = append(args, userID)
|
||||
|
||||
q += `) ORDER BY name`
|
||||
return queryKBs(ctx, q, args...)
|
||||
}
|
||||
|
||||
// ── Scan Helpers ─────────────────────────────────
|
||||
|
||||
func scanKB(row *sql.Row) (*models.KnowledgeBase, error) {
|
||||
@@ -396,7 +507,7 @@ func scanKB(row *sql.Row) (*models.KnowledgeBase, error) {
|
||||
&kb.ID, &kb.Name, &kb.Description, &kb.Scope,
|
||||
&ownerID, &teamID, &embCfgJSON,
|
||||
&kb.DocumentCount, &kb.ChunkCount, &kb.TotalBytes,
|
||||
&kb.Status, &kb.CreatedAt, &kb.UpdatedAt,
|
||||
&kb.Status, &kb.Discoverable, &kb.CreatedAt, &kb.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -423,7 +534,7 @@ func queryKBs(ctx context.Context, query string, args ...interface{}) ([]models.
|
||||
&kb.ID, &kb.Name, &kb.Description, &kb.Scope,
|
||||
&ownerID, &teamID, &embCfgJSON,
|
||||
&kb.DocumentCount, &kb.ChunkCount, &kb.TotalBytes,
|
||||
&kb.Status, &kb.CreatedAt, &kb.UpdatedAt,
|
||||
&kb.Status, &kb.Discoverable, &kb.CreatedAt, &kb.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -320,3 +320,84 @@ func scanPersonas(rows *sql.Rows) ([]models.Persona, error) {
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
// ── Persona-KB Bindings (v0.17.0) ───────────
|
||||
|
||||
func (s *PersonaStore) SetKBs(ctx context.Context, personaID string, kbIDs []string, autoSearch map[string]bool) error {
|
||||
tx, err := DB.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
_, err = tx.ExecContext(ctx, "DELETE FROM persona_knowledge_bases WHERE persona_id = $1", personaID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, kbID := range kbIDs {
|
||||
auto := false
|
||||
if autoSearch != nil {
|
||||
auto = autoSearch[kbID]
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
INSERT INTO persona_knowledge_bases (persona_id, kb_id, auto_search)
|
||||
VALUES ($1, $2, $3)`,
|
||||
personaID, kbID, auto)
|
||||
if err != nil {
|
||||
return fmt.Errorf("persona KB %s: %w", kbID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (s *PersonaStore) GetKBs(ctx context.Context, personaID string) ([]models.PersonaKB, error) {
|
||||
rows, err := DB.QueryContext(ctx, `
|
||||
SELECT pkb.persona_id, pkb.kb_id, pkb.auto_search, pkb.added_at,
|
||||
kb.name AS kb_name, kb.document_count, kb.chunk_count
|
||||
FROM persona_knowledge_bases pkb
|
||||
JOIN knowledge_bases kb ON kb.id = pkb.kb_id
|
||||
WHERE pkb.persona_id = $1
|
||||
ORDER BY kb.name`, personaID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var result []models.PersonaKB
|
||||
for rows.Next() {
|
||||
var pkb models.PersonaKB
|
||||
if err := rows.Scan(&pkb.PersonaID, &pkb.KBID, &pkb.AutoSearch, &pkb.AddedAt,
|
||||
&pkb.KBName, &pkb.DocumentCount, &pkb.ChunkCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, pkb)
|
||||
}
|
||||
if result == nil {
|
||||
result = make([]models.PersonaKB, 0)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
func (s *PersonaStore) GetKBIDs(ctx context.Context, personaID string) ([]string, error) {
|
||||
rows, err := DB.QueryContext(ctx,
|
||||
"SELECT kb_id FROM persona_knowledge_bases WHERE persona_id = $1", personaID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var ids []string
|
||||
for rows.Next() {
|
||||
var id string
|
||||
if err := rows.Scan(&id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
if ids == nil {
|
||||
ids = make([]string, 0)
|
||||
}
|
||||
return ids, rows.Err()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user