feat(web): 任务详情页新增探索链路播报板
按创建时间分页平铺展示探索节点,作为探索链路图的时间轴视图, 复用同一份 exploration_nodes 数据,不影响链路图。 - 后端 NodesPage/NodesByIDs/EdgesTouching:分页下推 SQL, 支持 kind/state 过滤、payload/origin 搜索、正倒序, 并补出当页节点的相邻边与对端节点 - 新增 GET /api/exploration/nodes(仅本任务节点,不含继承) - 播报板:时间轴分行、按天分组、类型快筛、搜索防抖、 自动刷新、直播位未读提示、展开看上下游与 payload - 补 mock,NEXT_PUBLIC_MOCK=1 下可无后端预览
This commit is contained in:
1 parent
13ce2ea6e3
commit
c03e008d7b
7 files changed
+1053
-37
No files matched your search
@@ -858,6 +858,7 @@ func (s *Server) Handler() http.Handler {
|
||||
mux.HandleFunc("DELETE /api/exploration/findings/{id}", s.deleteFinding)
|
||||
mux.HandleFunc("GET /api/exploration/intents", s.intents)
|
||||
mux.HandleFunc("GET /api/exploration/graph", s.explorationGraph)
|
||||
mux.HandleFunc("GET /api/exploration/nodes", s.explorationNodes)
|
||||
mux.HandleFunc("GET /api/exploration/activity", s.activity)
|
||||
mux.HandleFunc("GET /api/exploration/activity/history", s.activityHistory)
|
||||
mux.HandleFunc("GET /api/exploration/main-sessions", s.mainSessions)
|
||||
@@ -2576,6 +2577,90 @@ func (s *Server) explorationGraph(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, 200, map[string]any{"nodes": taskNodeDTOs(nodes), "edges": edgeDTOs(edges)})
|
||||
}
|
||||
|
||||
// explorationNodes serves the 播报板: this task's own exploration nodes as a
|
||||
// paged time series (newest first unless ?order=asc), filterable by kind/state
|
||||
// and a payload substring. Inherited nodes are deliberately out of scope — the
|
||||
// board reports what this task is doing right now, and paging across the source
|
||||
// tasks' stores would make the cursor meaningless.
|
||||
// Response also carries the edges touching the page plus the neighbour nodes
|
||||
// they point at, so each row can state where it came from and what it produced.
|
||||
func (s *Server) explorationNodes(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
page := atoiDefault(q.Get("page"), 1)
|
||||
size := atoiDefault(q.Get("size"), 20)
|
||||
t := s.m.ResolveTask(q.Get("task"))
|
||||
if t == nil {
|
||||
writeJSON(w, 200, map[string]any{
|
||||
"items": []any{}, "total": 0, "page": page, "size": size,
|
||||
"edges": []any{}, "refs": map[string]any{},
|
||||
})
|
||||
return
|
||||
}
|
||||
filter := db.NodeFilter{
|
||||
Kinds: csvValues(q.Get("kind")),
|
||||
States: csvValues(q.Get("state")),
|
||||
Query: q.Get("q"),
|
||||
Asc: q.Get("order") == "asc",
|
||||
}
|
||||
nodes, total, err := t.Store.NodesPage(filter, page, size)
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
ids := make([]int64, 0, len(nodes))
|
||||
onPage := make(map[int64]bool, len(nodes))
|
||||
for _, n := range nodes {
|
||||
ids = append(ids, n.ID)
|
||||
onPage[n.ID] = true
|
||||
}
|
||||
edges, err := t.Store.EdgesTouching(ids)
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
neighbourSet := map[int64]bool{}
|
||||
for _, e := range edges {
|
||||
if !onPage[e.From] {
|
||||
neighbourSet[e.From] = true
|
||||
}
|
||||
if !onPage[e.To] {
|
||||
neighbourSet[e.To] = true
|
||||
}
|
||||
}
|
||||
neighbourIDs := make([]int64, 0, len(neighbourSet))
|
||||
for id := range neighbourSet {
|
||||
neighbourIDs = append(neighbourIDs, id)
|
||||
}
|
||||
neighbours, err := t.Store.NodesByIDs(neighbourIDs)
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
refs := make(map[string]TaskNodeDTO, len(neighbours))
|
||||
for _, n := range neighbours {
|
||||
refs[i64s(n.ID)] = taskNodeDTO(n)
|
||||
}
|
||||
writeJSON(w, 200, map[string]any{
|
||||
"items": taskNodeDTOs(nodes),
|
||||
"total": total,
|
||||
"page": page,
|
||||
"size": size,
|
||||
"edges": edgeDTOs(edges),
|
||||
"refs": refs,
|
||||
})
|
||||
}
|
||||
|
||||
// csvValues splits a comma-separated query parameter, dropping empty entries.
|
||||
func csvValues(s string) []string {
|
||||
out := []string{}
|
||||
for _, part := range strings.Split(s, ",") {
|
||||
if v := strings.TrimSpace(part); v != "" {
|
||||
out = append(out, v)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// activity returns the worker execution step log (incremental via ?since=seq).
|
||||
func (s *Server) activity(w http.ResponseWriter, r *http.Request) {
|
||||
t := s.m.ResolveTask(r.URL.Query().Get("task"))
|
||||
|
||||
Reference in new issue
Block a user