Forge
csharpdeeb25a2
1#nullable enable
2
3using CascadeIDE.Features.Chat.AnchorPeek;
4using CascadeIDE.Features.Cockpit;
5using CommunityToolkit.Mvvm.ComponentModel;
6using CommunityToolkit.Mvvm.Input;
7
8namespace CascadeIDE.Features.Chat;
9
10public partial class ChatPanelViewModel
11{
12 /// <summary>CCL на Skia Intercom (не оверлей редактора).</summary>
13 public bool ShowIntercomCockpitCommandLine =>
14 Cockpit.Application.CockpitCommandLineHostPolicy.ShouldShowIntercomSkia(
15 IsCockpitCommandLineOpen,
16 CommandLineSession.ActiveHost);
17
18 internal void NotifyCockpitCommandLinePresentationChanged() =>
19 OnPropertyChanged(nameof(ShowIntercomCockpitCommandLine));
20 private readonly SlashCommandPreviewService _slashCommandPreviewService;
21 private readonly ICockpitCommandLineSession _cockpitCommandLineSession;
22
23 [ObservableProperty]
24 [NotifyPropertyChangedFor(nameof(IntercomSlashPreviewToolTip))]
25 private bool _isCockpitCommandLineOpen;
26
27 [ObservableProperty]
28 private string _cockpitCommandLineText = "/";
29
30 [ObservableProperty]
31 private string _commandLineSlashPreview = "";
32
33 [ObservableProperty]
34 private SlashCommandPreviewKind _commandLineSlashPreviewKind;
35
36 [ObservableProperty]
37 [NotifyPropertyChangedFor(nameof(IntercomSlashPreviewToolTip))]
38 private string? _commandLineSlashPreviewToolTip;
39
40 [ObservableProperty]
41 private string _composerSlashPreview = "";
42
43 [ObservableProperty]
44 private SlashCommandPreviewKind _composerSlashPreviewKind;
45
46 [ObservableProperty]
47 [NotifyPropertyChangedFor(nameof(IntercomSlashPreviewToolTip))]
48 private string? _composerSlashPreviewToolTip;
49
50 [ObservableProperty]
51 private int _cockpitCommandLineCaretIndex;
52
53 /// <summary>ToolTip на Intercom surface: CCL только при host=Intercom.</summary>
54 public string? IntercomSlashPreviewToolTip =>
55 ShowIntercomCockpitCommandLine ? CommandLineSlashPreviewToolTip : ComposerSlashPreviewToolTip;
56
57 public ICockpitCommandLineSession CommandLineSession => _cockpitCommandLineSession;
58
59 [RelayCommand]
60 private void OpenCockpitCommandLineUi() => OpenCockpitCommandLine("/");
61
62 public void OpenCockpitCommandLine(string initialText = "/")
63 {
64 var text = string.IsNullOrWhiteSpace(initialText) ? "/" : initialText;
65 IsCockpitCommandLineOpen = true;
66 CockpitCommandLineText = text;
67 CockpitCommandLineCaretIndex = text.Length;
68 UpdateSlashPreview(TciInputSurface.CockpitCommandLine);
69 UpdateSlashPreview(TciInputSurface.Composer);
70 RefreshCockpitCommandLineAutocomplete();
71 }
72
73 partial void OnIsCockpitCommandLineOpenChanged(bool value) =>
74 NotifyCockpitCommandLinePresentationChanged();
75
76 public void CloseCockpitCommandLine()
77 {
78 IsCockpitCommandLineOpen = false;
79 applySlashPreview(SlashCommandPreviewResult.Empty, TciInputSurface.CockpitCommandLine);
80 DismissChatSlashAutocomplete();
81 RefreshComposerAutocomplete();
82 UpdateSlashPreview(TciInputSurface.Composer);
83 }
84
85 partial void OnCockpitCommandLineTextChanged(string value)
86 {
87 UpdateSlashPreview(TciInputSurface.CockpitCommandLine);
88 if (IsCockpitCommandLineOpen)
89 RefreshCockpitCommandLineAutocomplete(value);
90 }
91
92 partial void OnCockpitCommandLineCaretIndexChanged(int value)
93 {
94 if (IsCockpitCommandLineOpen)
95 RefreshCockpitCommandLineAutocomplete();
96 }
97
98 /// <summary>Единая точка обновления slash-preview (P2).</summary>
99 public void UpdateSlashPreview(TciInputSurface surface, string? textOverride = null, int? caretOverride = null)
100 {
101 if (surface == TciInputSurface.Composer && IsCockpitCommandLineOpen)
102 {
103 applySlashPreview(SlashCommandPreviewResult.Empty, TciInputSurface.Composer);
104 return;
105 }
106
107 var preview = surface switch
108 {
109 TciInputSurface.CockpitCommandLine => _slashCommandPreviewService.Evaluate(
110 textOverride ?? CockpitCommandLineText),
111 TciInputSurface.Composer =>
112 _slashCommandPreviewService.EvaluateComposerAtCaret(
113 textOverride ?? ChatInput,
114 caretOverride ?? ChatComposerCaretIndex),
115 _ => SlashCommandPreviewResult.Empty,
116 };
117
118 applySlashPreview(preview, surface);
119 }
120
121 /// <summary>Slash preview под composer (линия по каретке).</summary>
122 public void RefreshComposerSlashPreview(string? inputOverride = null, int? caretOverride = null) =>
123 UpdateSlashPreview(TciInputSurface.Composer, inputOverride, caretOverride);
124
125 private void applySlashPreview(SlashCommandPreviewResult preview, TciInputSurface surface)
126 {
127 var text = preview.Text ?? "";
128 var tip = SlashCommandPreviewAccessibility.FormatToolTip(preview);
129 switch (surface)
130 {
131 case TciInputSurface.CockpitCommandLine:
132 CommandLineSlashPreview = text;
133 CommandLineSlashPreviewKind = preview.Kind;
134 CommandLineSlashPreviewToolTip = tip;
135 break;
136 case TciInputSurface.Composer:
137 ComposerSlashPreview = text;
138 ComposerSlashPreviewKind = preview.Kind;
139 ComposerSlashPreviewToolTip = tip;
140 break;
141 }
142 }
143
144 private bool tryBuildAnchorSlashPreview(string argsTail, out SlashCommandPreviewResult result)
145 {
146 if (TryResolveAnchorByShortId(argsTail, out var anchor, out _, out var error))
147 {
148 var kind = SlashCommandPreviewService.MapResolveOutcome(anchor.ResolveOutcome);
149 var label = anchor.DisplayLabel ?? anchor.MemberKey ?? anchor.File ?? "—";
150 var status = IntercomAnchorSlash.FormatOutcomeShort(anchor.ResolveOutcome);
151 var id = string.IsNullOrWhiteSpace(anchor.Id) ? "?" : anchor.Id;
152 var ordinalPrefix = AnchorPeekResolver.TryResolve(
153 argsTail,
154 BuildAnchorPeekResolveContext(),
155 out _,
156 out _,
157 out var ordinal,
158 out _)
159 ? $"#{ordinal} "
160 : "";
161 result = new($"{ordinalPrefix}a:{id} {label} {status}", kind);
162 return true;
163 }
164
165 result = new(error, SlashCommandPreviewKind.Error);
166 return true;
167 }
168
169 public async Task<bool> TryCommitCockpitCommandLineAsync(CancellationToken cancellationToken = default)
170 {
171 var raw = CockpitCommandLineText.Trim();
172 if (raw.Length == 0)
173 {
174 CloseCockpitCommandLine();
175 return true;
176 }
177
178 var result = await _slashCommandRunner.TryRunAsync(raw, cancellationToken).ConfigureAwait(false);
179 await UiScheduler.Default.InvokeAsync(() =>
180 {
181 if (!string.IsNullOrWhiteSpace(result.DetailText))
182 ClarificationStatusText = result.DetailText.Trim();
183 });
184
185 CloseCockpitCommandLine();
186 return result.Success;
187 }
188}
189
View only · write via MCP/CIDE