Forge
csharpdeeb25a2
1#nullable enable
2
3namespace CascadeIDE.Features.Chat;
4
5/// <summary>Разрешение темы по заголовку или короткому id (для /topic open).</summary>
6public static class ChatSlashTopicResolver
7{
8 public static bool TryResolve(
9 string? query,
10 ChatSurfaceSnapshot snapshot,
11 Guid selectedThreadId,
12 out ChatThreadNode thread,
13 out string? error)
14 {
15 thread = default!;
16 error = null;
17
18 var threads = snapshot.State.Threads;
19 if (threads.Count == 0)
20 {
21 error = "Тем пока нет.";
22 return false;
23 }
24
25 if (string.IsNullOrWhiteSpace(query))
26 {
27 if (TryPickDefault(threads, selectedThreadId, snapshot.State.ActiveThreadId, out thread))
28 return true;
29
30 error = "Укажи тему: /topic open <заголовок или id>";
31 return false;
32 }
33
34 var q = query.Trim();
35 if (Guid.TryParse(q, out var fullId))
36 {
37 var byGuid = threads.FirstOrDefault(t => t.ThreadId == fullId);
38 if (byGuid is not null)
39 {
40 thread = byGuid;
41 return true;
42 }
43
44 error = "Тема не найдена по id: " + q;
45 return false;
46 }
47
48 if (q.Length >= 4 && q.All(Uri.IsHexDigit))
49 {
50 var byPrefix = threads
51 .Where(t => t.ThreadId.ToString("N").StartsWith(q, StringComparison.OrdinalIgnoreCase))
52 .ToList();
53 if (byPrefix.Count == 1)
54 {
55 thread = byPrefix[0];
56 return true;
57 }
58
59 if (byPrefix.Count > 1)
60 {
61 error = FormatAmbiguous(byPrefix.Select(t => t.Title));
62 return false;
63 }
64 }
65
66 var exact = threads
67 .Where(t => string.Equals(t.Title, q, StringComparison.OrdinalIgnoreCase))
68 .ToList();
69 if (exact.Count == 1)
70 {
71 thread = exact[0];
72 return true;
73 }
74
75 if (exact.Count > 1)
76 {
77 error = FormatAmbiguous(exact.Select(t => t.Title));
78 return false;
79 }
80
81 var contains = threads
82 .Where(t => t.Title.Contains(q, StringComparison.OrdinalIgnoreCase))
83 .ToList();
84 if (contains.Count == 1)
85 {
86 thread = contains[0];
87 return true;
88 }
89
90 if (contains.Count > 1)
91 {
92 error = FormatAmbiguous(contains.Select(t => t.Title));
93 return false;
94 }
95
96 error = "Тема не найдена: " + q;
97 return false;
98 }
99
100 private static bool TryPickDefault(
101 IReadOnlyList<ChatThreadNode> threads,
102 Guid selectedThreadId,
103 Guid activeThreadId,
104 out ChatThreadNode thread)
105 {
106 thread = default!;
107 if (selectedThreadId != Guid.Empty)
108 {
109 var selected = threads.FirstOrDefault(t => t.ThreadId == selectedThreadId);
110 if (selected is not null)
111 {
112 thread = selected;
113 return true;
114 }
115 }
116
117 if (activeThreadId != Guid.Empty)
118 {
119 var active = threads.FirstOrDefault(t => t.ThreadId == activeThreadId);
120 if (active is not null)
121 {
122 thread = active;
123 return true;
124 }
125 }
126
127 var main = threads.FirstOrDefault(t => t.IsMainThread) ?? threads[0];
128 thread = main;
129 return true;
130 }
131
132 private static string FormatAmbiguous(IEnumerable<string> titles)
133 {
134 var list = titles.ToList();
135 var shown = string.Join("; ", list.Take(5));
136 var suffix = list.Count > 5 ? "…" : "";
137 return "Несколько тем подходят: " + shown + suffix + ". Уточни заголовок или id.";
138 }
139}
140
View only · write via MCP/CIDE