fix(web): render full document content, keep graph connected under categories
Two fixes to the new category taxonomy:
- Knowledge Base couldn't show a document/investigation/runbook's own
markdown body — knowledge_entities.content was never exposed by any
endpoint (GetEntityKnowledge answers "what knowledge references this
entity", not "what is this entity's content"). Add GET
/api/v1/knowledge/content/{id} and render it with the existing
marked+DOMPurify pipeline in a new Content section.
- The graph hid any edge whose other endpoint wasn't in the active
category, so nodes with only cross-category neighbors rendered as
disconnected dots. Queried the real relationship table: ~70% of infra
edges cross Fleet/Network/Services/Storage lines (compute+network+
software+storage+physical used to be one "infrastructure" layer).
EntityGraph now keeps 1-hop neighbors visible but dimmed instead of
hiding them, so the edges — and what they connect to — stay visible.
- categories.ts: `cognition` domain conflated true knowledge (document/
investigation/runbook, 58 entities) with operational telemetry
(execution/check/task/signal/approval/pattern/skill/classification/
feedback, 300+ entities with their own Operations/Signals/Learning
pages). Mapping the whole domain to Knowledge pulled in 245 execution
entities fanning out from ~17 compute nodes via `targets` edges — the
single biggest source of graph clutter. Knowledge now maps by type
(document/investigation/runbook only); the rest of cognition is
excluded from Knowledge Base browsing entirely.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/dtoro/oikos/internal/httpapi/gen"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
@@ -107,6 +108,43 @@ func (s *Server) serveRecentKnowledge(w http.ResponseWriter, req *http.Request)
|
||||
})
|
||||
}
|
||||
|
||||
// serveKnowledgeContent returns the full markdown body for a document/
|
||||
// investigation/runbook entity, by its own entity id or slug. Nothing else
|
||||
// exposes knowledge_entities.content — GetEntityKnowledge (below) answers a
|
||||
// different question ("what knowledge references THIS entity"), and
|
||||
// SearchKnowledge only returns a short ts_headline snippet. The KB detail
|
||||
// panel needs the entity's own full content when it IS a knowledge entity.
|
||||
func (s *Server) serveKnowledgeContent(w http.ResponseWriter, req *http.Request) {
|
||||
ctx := req.Context()
|
||||
idOrSlug := chi.URLParam(req, "id")
|
||||
|
||||
var title, content, source string
|
||||
var tags []string
|
||||
var updatedAt string
|
||||
err := s.pool.QueryRow(ctx, `
|
||||
SELECT ke.title, ke.content, COALESCE(ke.source,''), ke.tags, ke.updated_at::text
|
||||
FROM knowledge_entities ke
|
||||
JOIN entities e ON e.id = ke.entity_id
|
||||
WHERE e.slug = $1 OR e.id::text = $1`, idOrSlug).
|
||||
Scan(&title, &content, &source, &tags, &updatedAt)
|
||||
if err != nil {
|
||||
writeProblem(w, req, http.StatusNotFound, "no knowledge content for entity", "")
|
||||
return
|
||||
}
|
||||
if tags == nil {
|
||||
tags = []string{}
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]any{
|
||||
"title": title,
|
||||
"content": content,
|
||||
"source": source,
|
||||
"tags": tags,
|
||||
"updated_at": updatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) SearchKnowledge(ctx context.Context, request gen.SearchKnowledgeRequestObject) (gen.SearchKnowledgeResponseObject, error) {
|
||||
q := request.Params.Q
|
||||
limit := clampLimit(request.Params.Limit)
|
||||
|
||||
Reference in New Issue
Block a user