返回 DeepSeek-Reasonix
topic_state_catalog.go
根目录 / desktop / topic_state_catalog.go
1 package main
2
3 import (
4 "errors"
5 "strings"
6
7 "reasonix/internal/sessioncatalog"
8 )
9
10 // ListProjectTopics surfaces topic-state failures instead of hiding them as empty pages.
11 func (a *App) ListProjectTopics(req ProjectTopicPageRequest) (ProjectTopicPage, error) {
12 // Remote roots come from Serve and must never map to local metadata paths.
13 if strings.HasPrefix(strings.TrimSpace(req.WorkspaceRoot), "remote-project:") {
14 return ProjectTopicPage{Items: []ProjectNode{}}, nil
15 }
16 if err := topicStateReadable(topicTitleRoot(req.Scope, req.WorkspaceRoot)); err != nil {
17 return ProjectTopicPage{Items: []ProjectNode{}}, err
18 }
19 for attempt := 0; ; attempt++ {
20 page, err := a.unifiedProjectTopics(req)
21 var conflict *SessionOperationError
22 // Discovery can publish while the first snapshot is being assembled.
23 // Rebuild that snapshot, but never reinterpret an old pagination cursor.
24 if req.Cursor != "" || attempt == 2 || !errors.As(err, &conflict) || conflict.Code != "stale_cursor" {
25 return page, err
26 }
27 }
28 }
29
30 func (a *App) GetTopicSummary(key ProjectTopicKey) (ProjectNode, error) {
31 scope, workspaceRoot := normalizeDesktopTopicScope(key.Scope, key.WorkspaceRoot)
32 topicID := strings.TrimSpace(key.TopicID)
33 if topicID == "" {
34 return ProjectNode{Children: []ProjectNode{}}, nil
35 }
36 allowMetadataFallback := true
37 if catalog := a.sessionCatalog.Load(); catalog != nil {
38 availability := a.catalogWorkspaceAvailability(catalog, scope, workspaceRoot)
39 allowMetadataFallback = !availability.complete
40 if !availability.usable {
41 return a.metadataTopicSummary(scope, workspaceRoot, topicID), nil
42 }
43 ctx, cancel := a.catalogReadContext()
44 defer cancel()
45 topic, ok, err := catalog.GetTopic(ctx, sessioncatalog.TopicKey{
46 Scope: scope, WorkspaceRoot: workspaceRoot, TopicID: topicID,
47 })
48 if err != nil {
49 return ProjectNode{Children: []ProjectNode{}}, err
50 }
51 if ok {
52 topicOverlays, sessionOverlays := a.catalogRuntimeOverlays()
53 preferred, prefErr := catalog.PreferredOrdinarySessionPaths(ctx, scope, workspaceRoot)
54 if prefErr != nil {
55 preferred = nil
56 }
57 if node, visible := a.projectNodeFromCatalogTopic(topic, topicOverlays, sessionOverlays, preferred); visible {
58 return node, nil
59 }
60 }
61 }
62 if !allowMetadataFallback {
63 return ProjectNode{Children: []ProjectNode{}}, nil
64 }
65 return a.metadataTopicSummary(scope, workspaceRoot, topicID), nil
66 }
67
68 func (a *App) metadataTopicSummary(scope, workspaceRoot, topicID string) ProjectNode {
69 for _, node := range a.metadataProjectTopics(scope, workspaceRoot) {
70 if node.TopicID == topicID {
71 return node
72 }
73 }
74 return ProjectNode{Children: []ProjectNode{}}
75 }
76
76 lines GO