Forge
csharpdeeb25a2
1using CascadeIDE.Features.Chat;
2using CascadeIDE.Models;
3using CascadeIDE.Models.Intercom;
4using CommunityToolkit.Mvvm.ComponentModel;
5
6namespace CascadeIDE.ViewModels;
7
8public sealed partial class ChatMessageViewModel : ObservableObject
9{
10 /// <summary>Стабильный идентификатор для лога событий и MCP (редактирование, ссылки).</summary>
11 public Guid MessageId { get; }
12
13 public string Role { get; }
14
15 /// <summary>Ветка диалога; сообщения с разными thread_id — параллельные линии в одной сессии.</summary>
16 public Guid ThreadId { get; private set; }
17
18 /// <summary>Родительское сообщение при ответной ветке; иначе null.</summary>
19 public Guid? ParentMessageId { get; }
20
21 [ObservableProperty]
22 private string _content;
23
24 public string? SlashCommandPath { get; private set; }
25
26 public string? SlashCommandArgs { get; private set; }
27
28 [ObservableProperty]
29 private ChatSlashCommandStatus? _slashCommandStatus;
30
31 public IReadOnlyList<AttachmentAnchor> Attachments { get; private set; } = [];
32
33 public SenderWorkspaceContext? SenderWorkspaceContext { get; private set; }
34
35 public IntercomMessageAudience Audience { get; private set; } = IntercomMessageAudience.Channel;
36
37 /// <summary>ADR 0116: normal | steer | follow_up.</summary>
38 public string? DeliveryMode { get; private set; }
39
40 public bool IsLocalSelfOnly => Audience == IntercomMessageAudience.SelfOnly;
41
42 public bool IsSlashCommand => SlashCommandStatus is not null;
43
44 public ChatMessageViewModel(
45 string role,
46 string content,
47 Guid? messageId = null,
48 Guid? threadId = null,
49 Guid? parentMessageId = null,
50 string? slashCommandPath = null,
51 string? slashCommandArgs = null,
52 ChatSlashCommandStatus? slashCommandStatus = null,
53 IReadOnlyList<AttachmentAnchor>? attachments = null,
54 SenderWorkspaceContext? senderWorkspaceContext = null,
55 IntercomMessageAudience audience = IntercomMessageAudience.Channel,
56 string? deliveryMode = null)
57 {
58 MessageId = messageId ?? Guid.NewGuid();
59 Role = role;
60 ThreadId = threadId ?? Guid.Empty;
61 ParentMessageId = parentMessageId;
62 _content = content;
63 SlashCommandPath = slashCommandPath;
64 SlashCommandArgs = slashCommandArgs;
65 _slashCommandStatus = slashCommandStatus;
66 Attachments = attachments ?? [];
67 SenderWorkspaceContext = senderWorkspaceContext;
68 Audience = audience;
69 DeliveryMode = string.IsNullOrWhiteSpace(deliveryMode)
70 ? null
71 : IntercomComposerDeliveryModes.Normalize(deliveryMode);
72 }
73
74 public void SetAttachments(IReadOnlyList<AttachmentAnchor> attachments, SenderWorkspaceContext? senderWorkspaceContext)
75 {
76 Attachments = attachments;
77 SenderWorkspaceContext = senderWorkspaceContext;
78 OnPropertyChanged(nameof(Attachments));
79 OnPropertyChanged(nameof(SenderWorkspaceContext));
80 }
81
82 public static ChatMessageViewModel CreateSlashCommand(
83 string slashPath,
84 string? args,
85 Guid? threadId = null,
86 IntercomMessageAudience audience = IntercomMessageAudience.Channel) =>
87 new(
88 "slash_command",
89 "",
90 threadId: threadId,
91 slashCommandPath: slashPath,
92 slashCommandArgs: args,
93 slashCommandStatus: ChatSlashCommandStatus.Running,
94 audience: audience);
95
96 /// <summary>Привязать слэш-сообщение к ветке после fork (<c>/topic create</c>, MCP).</summary>
97 public void AssignThread(Guid threadId)
98 {
99 if (threadId == Guid.Empty || ThreadId == threadId)
100 return;
101
102 ThreadId = threadId;
103 OnPropertyChanged(nameof(ThreadId));
104 }
105
106 public void ApplySlashCommandResult(in ChatSlashCommandRunResult result)
107 {
108 SlashCommandPath = result.SlashPath;
109 SlashCommandArgs = ChatSlashCommandPresentation.NormalizeArgsTail(result.ArgsTail);
110 SlashCommandStatus = result.Success
111 ? ChatSlashCommandStatus.Succeeded
112 : ChatSlashCommandStatus.Failed;
113 Content = result.DetailText ?? "";
114 OnPropertyChanged(nameof(SlashCommandPath));
115 OnPropertyChanged(nameof(SlashCommandArgs));
116 OnPropertyChanged(nameof(IsSlashCommand));
117 }
118}
119
View only · write via MCP/CIDE