Forge
csharpdeeb25a2
1#nullable enable
2
3namespace CascadeIDE.Features.Chat;
4
5public sealed partial class ChatSurfaceIntentStage
6{
7 private static void ApplyForkHints(
8 in ChatSurfaceIntent intent,
9 Dictionary<Guid, Guid?> parentThreadByThread)
10 {
11 if (intent.ThreadForks is null)
12 return;
13
14 foreach (var fork in intent.ThreadForks)
15 {
16 if (fork.NewThreadId == Guid.Empty)
17 continue;
18 if (parentThreadByThread.ContainsKey(fork.NewThreadId))
19 continue;
20
21 parentThreadByThread[fork.NewThreadId] =
22 fork.PreviousThreadId == Guid.Empty ? null : fork.PreviousThreadId;
23 }
24 }
25
26 private static void MergeSyntheticThreads(
27 Dictionary<Guid, ChatThreadNode> threads,
28 in ChatSurfaceIntent intent,
29 IReadOnlyDictionary<Guid, Guid?> parentThreadByThread,
30 int orderBase)
31 {
32 var order = orderBase;
33 foreach (var threadId in CollectSyntheticThreadIds(intent))
34 {
35 if (threads.ContainsKey(threadId))
36 continue;
37
38 parentThreadByThread.TryGetValue(threadId, out var parentThreadId);
39 threads[threadId] = new ChatThreadNode(
40 threadId,
41 NodeIdForThread(threadId),
42 BuildSyntheticThreadTitle(threadId, in intent),
43 IsMainThread: threadId == intent.MainThreadId,
44 IsActive: threadId == intent.ActiveThreadId,
45 ParentThreadId: parentThreadId,
46 ForkedFromMessageId: null,
47 Depth: 0,
48 Order: order++);
49 }
50 }
51
52 private static IEnumerable<Guid> CollectSyntheticThreadIds(ChatSurfaceIntent intent)
53 {
54 if (intent.MainThreadId != Guid.Empty)
55 yield return intent.MainThreadId;
56
57 if (intent.ActiveThreadId != Guid.Empty)
58 yield return intent.ActiveThreadId;
59
60 if (intent.ThreadDisplayTitles is not null)
61 {
62 foreach (var threadId in intent.ThreadDisplayTitles.Keys)
63 yield return threadId;
64 }
65
66 if (intent.ThreadForks is not null)
67 {
68 foreach (var fork in intent.ThreadForks)
69 {
70 if (fork.NewThreadId != Guid.Empty)
71 yield return fork.NewThreadId;
72 }
73 }
74 }
75
76 private static bool HasSyntheticThreadSources(in ChatSurfaceIntent intent) =>
77 intent.MainThreadId != Guid.Empty
78 || intent.ActiveThreadId != Guid.Empty
79 || intent.ThreadDisplayTitles is { Count: > 0 }
80 || intent.ThreadForks is { Count: > 0 };
81
82 private static string BuildSyntheticThreadTitle(Guid threadId, in ChatSurfaceIntent intent)
83 {
84 if (intent.ThreadDisplayTitles is not null
85 && intent.ThreadDisplayTitles.TryGetValue(threadId, out var custom)
86 && !string.IsNullOrWhiteSpace(custom))
87 {
88 return TrimForTitle(custom);
89 }
90
91 if (threadId == intent.MainThreadId)
92 return "Основная тема";
93
94 if (!string.IsNullOrWhiteSpace(intent.ThreadBranchHint)
95 && threadId == intent.ActiveThreadId)
96 {
97 return TrimForTitle(intent.ThreadBranchHint);
98 }
99
100 return $"Ветка {threadId:N}"[..12];
101 }
102
103 private static ChatSurfaceState BuildStateWithoutMessages(in ChatSurfaceIntent intent)
104 {
105 var confirmations = new List<ChatConfirmationNode>();
106 if (intent.ActiveClarificationBatch is { } clarification)
107 {
108 var threadId = intent.ActiveThreadId != Guid.Empty ? intent.ActiveThreadId : intent.MainThreadId;
109 confirmations.Add(new ChatConfirmationNode(
110 NodeIdForClarification(clarification.Id),
111 threadId,
112 clarification.Id,
113 string.IsNullOrWhiteSpace(clarification.Title) ? "Уточнения к текущему шагу" : clarification.Title.Trim(),
114 BuildClarificationBody(clarification),
115 clarification.Items.Count,
116 IsActive: true,
117 IsResolved: false));
118 }
119
120 if (!HasSyntheticThreadSources(in intent))
121 {
122 return new ChatSurfaceState(
123 [],
124 [],
125 confirmations,
126 [],
127 intent.ActiveThreadId,
128 "Chat");
129 }
130
131 var parentThreadByThread = new Dictionary<Guid, Guid?>();
132 ApplyForkHints(in intent, parentThreadByThread);
133 var threads = new Dictionary<Guid, ChatThreadNode>();
134 MergeSyntheticThreads(threads, in intent, parentThreadByThread, orderBase: 0);
135
136 foreach (var threadId in threads.Keys.ToList())
137 {
138 threads[threadId] = threads[threadId] with
139 {
140 Depth = ComputeDepth(threadId, threads)
141 };
142 }
143
144 var orderedThreads = threads.Values.OrderBy(thread => thread.Order).ToList();
145 var activeThreadLabel = orderedThreads.FirstOrDefault(thread => thread.IsActive)?.Title
146 ?? orderedThreads.FirstOrDefault(thread => thread.IsMainThread)?.Title
147 ?? "Chat";
148
149 return new ChatSurfaceState(
150 orderedThreads,
151 [],
152 confirmations,
153 [],
154 intent.ActiveThreadId,
155 activeThreadLabel);
156 }
157}
158
View only · write via MCP/CIDE