Changeset 0.17.0 (#75)
This commit is contained in:
@@ -147,9 +147,25 @@ func (h *KnowledgeBaseHandler) CreateKB(c *gin.Context) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "team_id required for team scope"})
|
||||
return
|
||||
}
|
||||
// TODO: verify user is team admin
|
||||
// Verify user is team admin (or system admin)
|
||||
role, _ := c.Get("role")
|
||||
if role != "admin" {
|
||||
var teamRole string
|
||||
err := database.DB.QueryRow(
|
||||
`SELECT role FROM team_members WHERE team_id = $1 AND user_id = $2`,
|
||||
req.TeamID, userID).Scan(&teamRole)
|
||||
if err != nil || teamRole != "admin" {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "team admin access required to create team knowledge bases"})
|
||||
return
|
||||
}
|
||||
}
|
||||
case "global":
|
||||
// TODO: verify user is admin
|
||||
// Verify user is system admin
|
||||
role, _ := c.Get("role")
|
||||
if role != "admin" {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "admin access required to create global knowledge bases"})
|
||||
return
|
||||
}
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "scope must be personal, team, or global"})
|
||||
return
|
||||
@@ -630,9 +646,7 @@ func (h *KnowledgeBaseHandler) getUserTeamIDs(c *gin.Context, userID string) []s
|
||||
|
||||
// updateDocStorageKey sets the storage_key on a document after upload.
|
||||
func (h *KnowledgeBaseHandler) updateDocStorageKey(c *gin.Context, docID, storageKey string) {
|
||||
database.DB.ExecContext(c.Request.Context(),
|
||||
`UPDATE kb_documents SET storage_key = $1 WHERE id = $2`,
|
||||
storageKey, docID)
|
||||
h.stores.KnowledgeBases.UpdateDocumentStorageKey(c.Request.Context(), docID, storageKey)
|
||||
}
|
||||
|
||||
// toKBResponse converts a model to the API response type.
|
||||
@@ -747,6 +761,46 @@ func (h *KnowledgeBaseHandler) SetChannelKBs(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"data": kbs})
|
||||
}
|
||||
|
||||
// ── Discoverable Management (v0.17.0) ────────────
|
||||
|
||||
// SetDiscoverable toggles KB visibility to users.
|
||||
func (h *KnowledgeBaseHandler) SetDiscoverable(c *gin.Context) {
|
||||
kbID := c.Param("id")
|
||||
|
||||
var req struct {
|
||||
Discoverable bool `json:"discoverable"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.stores.KnowledgeBases.SetDiscoverable(c.Request.Context(), kbID, req.Discoverable); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update discoverability"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"status": "ok"})
|
||||
}
|
||||
|
||||
// ListDiscoverableKBs returns KBs the user can see that are marked discoverable.
|
||||
// Used by the channel KB toggle popup when kb_direct_access is enabled.
|
||||
func (h *KnowledgeBaseHandler) ListDiscoverableKBs(c *gin.Context) {
|
||||
userID := getUserID(c)
|
||||
teamIDs := h.getUserTeamIDs(c, userID)
|
||||
|
||||
kbs, err := h.stores.KnowledgeBases.ListDiscoverable(c.Request.Context(), userID, teamIDs)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list knowledge bases"})
|
||||
return
|
||||
}
|
||||
if kbs == nil {
|
||||
kbs = []models.KnowledgeBase{}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": kbs})
|
||||
}
|
||||
|
||||
// userCanAccess checks if a user can access a KB based on scope.
|
||||
func (h *KnowledgeBaseHandler) userCanAccess(kb *models.KnowledgeBase, userID string, teamIDs []string) bool {
|
||||
switch kb.Scope {
|
||||
@@ -773,60 +827,57 @@ func (h *KnowledgeBaseHandler) userCanAccess(kb *models.KnowledgeBase, userID st
|
||||
// BuildKBHint returns a system prompt fragment listing active KBs for a
|
||||
// channel, or empty string if none are active. Called by the completion
|
||||
// handler to nudge the LLM to use kb_search.
|
||||
func BuildKBHint(ctx context.Context, stores store.Stores, channelID, userID string) string {
|
||||
func BuildKBHint(ctx context.Context, stores store.Stores, channelID, userID, personaID string) string {
|
||||
teamIDs, _ := stores.Teams.GetUserTeamIDs(ctx, userID)
|
||||
|
||||
kbIDs, err := stores.KnowledgeBases.GetActiveKBIDs(ctx, channelID, userID, teamIDs)
|
||||
if err != nil || len(kbIDs) == 0 {
|
||||
// Also check personal KBs — always available
|
||||
personalKBs, err := stores.KnowledgeBases.ListPersonal(ctx, userID)
|
||||
if err != nil || len(personalKBs) == 0 {
|
||||
return ""
|
||||
}
|
||||
// Only include personal KBs that have chunks
|
||||
hasContent := false
|
||||
for _, kb := range personalKBs {
|
||||
if kb.ChunkCount > 0 {
|
||||
hasContent = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasContent {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Collect KB details for the hint
|
||||
type kbInfo struct {
|
||||
Name string
|
||||
DocCount int
|
||||
}
|
||||
var kbs []kbInfo
|
||||
seen := make(map[string]bool)
|
||||
|
||||
// Persona-bound KBs (v0.17.0)
|
||||
if personaID != "" {
|
||||
personaKBs, err := stores.Personas.GetKBs(ctx, personaID)
|
||||
if err == nil {
|
||||
for _, pkb := range personaKBs {
|
||||
if pkb.ChunkCount > 0 {
|
||||
kbs = append(kbs, kbInfo{Name: pkb.KBName, DocCount: pkb.DocumentCount})
|
||||
seen[pkb.KBID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Channel-linked KBs
|
||||
channelKBs, err := stores.KnowledgeBases.GetChannelKBs(ctx, channelID)
|
||||
if err == nil {
|
||||
for _, ckb := range channelKBs {
|
||||
if ckb.Enabled && ckb.DocumentCount > 0 {
|
||||
if ckb.Enabled && ckb.DocumentCount > 0 && !seen[ckb.KBID] {
|
||||
kbs = append(kbs, kbInfo{Name: ckb.KBName, DocCount: ckb.DocumentCount})
|
||||
seen[ckb.KBID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Personal KBs (not already linked to channel)
|
||||
// Personal KBs (not already counted)
|
||||
personalKBs, err := stores.KnowledgeBases.ListPersonal(ctx, userID)
|
||||
if err == nil {
|
||||
linked := make(map[string]bool)
|
||||
for _, ckb := range channelKBs {
|
||||
linked[ckb.KBID] = true
|
||||
}
|
||||
for _, kb := range personalKBs {
|
||||
if !linked[kb.ID] && kb.ChunkCount > 0 {
|
||||
if !seen[kb.ID] && kb.ChunkCount > 0 {
|
||||
kbs = append(kbs, kbInfo{Name: kb.Name, DocCount: kb.DocumentCount})
|
||||
seen[kb.ID] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Group-granted KBs are resolved via GetActiveKBIDs — only add names
|
||||
// for KBs found through channel link or persona. The kb_search tool
|
||||
// handles the full resolution at search time.
|
||||
_ = teamIDs // used by GetActiveKBIDsWithPersona at search time
|
||||
|
||||
if len(kbs) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user