feat(coverage): 覆盖图节点用 lucide 图标 + 精简 label + 抽屉展示关联意图/事实

- 节点图标 emoji → 平台一致的 lucide 图标(白色 SVG data URI,iconSrc)
- 图内 label:服务显示 端口·标题·状态码(不再完整 URL),端点只显示 path
- 点节点抽屉新增「关联意图/关联事实/关联发现」:按 exploration_anchors
  反查本任务锚定该资产的节点
  - db AssetRefs(assetID);server GET /api/tasks/{id}/asset-refs?asset_id=X
  - types/api taskAssetRefs;抽屉按 node.asset_id 拉取渲染

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Autumn-27andClaude Opus 4.8 committed 2026-08-09 00:28:19 -04:00
1 parent 9c1591ad8e
commit e35c766e6a
5 files changed
+218 -14

No files matched your search

+35
View File
@@ -484,6 +484,7 @@ func (s *Server) Handler() http.Handler {
mux.HandleFunc("GET /api/tasks/{id}", s.getTask)
mux.HandleFunc("GET /api/tasks/{id}/coverage", s.taskCoverage)
mux.HandleFunc("GET /api/tasks/{id}/coverage-graph", s.taskCoverageGraph)
mux.HandleFunc("GET /api/tasks/{id}/asset-refs", s.taskAssetRefs)
mux.HandleFunc("GET /api/tasks/{id}/scope", s.taskScopeList)
mux.HandleFunc("POST /api/tasks/{id}/control", s.control)
mux.HandleFunc("POST /api/active", s.setActive)
@@ -1130,6 +1131,40 @@ func (s *Server) taskCoverageGraph(w http.ResponseWriter, r *http.Request) {
writeJSON(w, 200, g)
}
// taskAssetRefs returns the intents / facts / findings in this task anchored to a
// given asset id — powers the coverage-graph node drawer's「关联意图 / 关联事实」。
func (s *Server) taskAssetRefs(w http.ResponseWriter, r *http.Request) {
t, ok := s.m.Task(r.PathValue("id"))
if !ok {
writeErr(w, 404, "task not found")
return
}
assetID, _ := strconv.ParseInt(r.URL.Query().Get("asset_id"), 10, 64)
if assetID <= 0 {
writeErr(w, 400, "需要 asset_id")
return
}
refs, err := t.Store.AssetRefs(assetID)
if err != nil {
writeErr(w, 500, err.Error())
return
}
intents := []db.AssetRef{}
facts := []db.AssetRef{}
findings := []db.AssetRef{}
for _, ref := range refs {
switch ref.Kind {
case "intent":
intents = append(intents, ref)
case "fact":
facts = append(facts, ref)
case "finding":
findings = append(findings, ref)
}
}
writeJSON(w, 200, map[string]any{"intents": intents, "facts": facts, "findings": findings})
}
// taskScopeList returns a task's scope rows (coverage denominator sources).
func (s *Server) taskScopeList(w http.ResponseWriter, r *http.Request) {
t, ok := s.m.Task(r.PathValue("id"))