123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382 |
- using System.Linq;
- using UnityEditor;
- using UnityEngine;
- using LLM.Editor.Core;
- using System.Threading;
- using LLM.Editor.Client;
- using LLM.Editor.Helper;
- using LLM.Editor.Commands;
- using LLM.Editor.Analysis;
- using System.Threading.Tasks;
- using System.Collections.Generic;
- namespace LLM.Editor.GUI
- {
- public class MCPWindow : EditorWindow
- {
- private string _promptText = "The grenade launcher should hit the target.";
- private Vector2 _scrollPos;
- private bool _isRequestInProgress;
-
- private readonly List<Object> _stagedContextObjects = new();
- private List<string> _requestedRoles = new();
- private bool _isAnalysisContextRequested;
- private ILlmApiClient _apiClient;
- private CancellationTokenSource _cancellationTokenSource;
- private List<string> _currentPlan = new();
- // --- Fields for Dummy API Client UI ---
- private List<string> _dummyTriggerPhrases = new();
- private int _selectedDummyTriggerIndex;
- private bool _dummyResponsesLoaded;
- [MenuItem("LLM/MCP Assistant")]
- public static void ShowWindow()
- {
- GetWindow<MCPWindow>("MCP Assistant");
- }
- private void OnEnable()
- {
- CommandExecutor.OnQueueUpdated += Repaint;
- CommandExecutor.OnPlanUpdated += HandlePlanUpdated;
- RequestAnalysisContextCommand.OnAnalysisContextRequested += HandleAnalysisContextRequested;
- CommandExecutor.OnContextReadyForNextTurn += HandleContextReadyForNextTurn;
- _apiClient = ApiClientFactory.GetClient();
-
- LoadDummyTestCases();
- }
- private void OnDisable()
- {
- CommandExecutor.OnQueueUpdated -= Repaint;
- CommandExecutor.OnPlanUpdated -= HandlePlanUpdated;
- RequestAnalysisContextCommand.OnAnalysisContextRequested -= HandleAnalysisContextRequested;
- CommandExecutor.OnContextReadyForNextTurn -= HandleContextReadyForNextTurn;
-
- _cancellationTokenSource?.Cancel();
- _cancellationTokenSource?.Dispose();
- }
- private void HandlePlanUpdated(List<string> plan)
- {
- _currentPlan = plan;
- Repaint();
- }
- private void HandleAnalysisContextRequested(RequestAnalysisContextParams contextParams)
- {
- _isAnalysisContextRequested = true;
- _requestedRoles = new List<string>(contextParams.subjectRoles);
- while (_stagedContextObjects.Count < _requestedRoles.Count)
- {
- _stagedContextObjects.Add(null);
- }
- Repaint();
- }
-
- private void HandleContextReadyForNextTurn(string detailedContext)
- {
- Debug.Log("[MCPWindow] Received detailed context. Sending follow-up to LLM.");
- _ = SendFollowUpAsync(detailedContext);
- }
- private void OnGUI()
- {
- EditorGUILayout.LabelField("LLM Co-Pilot", EditorStyles.boldLabel);
-
- DrawSessionManagement();
- EditorGUILayout.Space();
- EditorGUI.BeginDisabledGroup(!SessionManager.HasActiveSession());
- DrawContextStagingArea();
- EditorGUILayout.Space();
- if (!(_apiClient is DummyApiClient))
- {
- DrawSelectionHint();
- }
- EditorGUILayout.Space();
- EditorGUILayout.LabelField("Enter your request:");
-
- // --- Conditional UI for Prompt Input ---
- if (_apiClient is DummyApiClient)
- {
- if (!_dummyResponsesLoaded)
- {
- EditorGUILayout.HelpBox("Loading dummy responses...", MessageType.Info);
- }
- else if (_dummyTriggerPhrases.Any())
- {
- _selectedDummyTriggerIndex = EditorGUILayout.Popup("Select Test Case", _selectedDummyTriggerIndex, _dummyTriggerPhrases.ToArray());
- // Update prompt text based on selection to be used by the send function
- if(_selectedDummyTriggerIndex < _dummyTriggerPhrases.Count)
- {
- _promptText = _dummyTriggerPhrases[_selectedDummyTriggerIndex];
- }
- }
- else
- {
- EditorGUILayout.HelpBox("No dummy test cases found in DummyResponses.json.", MessageType.Warning);
- }
- }
- else
- {
- // Default text area for other clients like Gemini
- _promptText = EditorGUILayout.TextArea(_promptText, GUILayout.Height(60));
- }
- // --- End of Conditional UI ---
- DrawActionButtons();
- EditorGUILayout.Space();
-
- DrawPlanArea();
- EditorGUILayout.Space();
-
- DrawCommandQueue();
-
- EditorGUI.EndDisabledGroup();
- }
-
- private void LoadDummyTestCases()
- {
- var testCases = DummyApiClient.GetTestCases();
- if (testCases != null && testCases.Any())
- {
- _dummyTriggerPhrases = testCases.Select(c => c.TriggerPhrase).ToList();
- }
- else
- {
- _dummyTriggerPhrases = new List<string> { "No C# test cases found" };
- }
- _dummyResponsesLoaded = true;
- Repaint();
- }
-
- private void DrawSessionManagement()
- {
- EditorGUILayout.BeginHorizontal();
- if (SessionManager.HasActiveSession())
- {
- EditorGUILayout.LabelField($"Session: {SessionManager.GetCurrentSessionId()}");
- if (GUILayout.Button("End Session"))
- {
- SessionManager.EndSession();
- ClearStagingArea();
- _currentPlan.Clear();
- }
- }
- else
- {
- if (GUILayout.Button("Start New Session"))
- {
- SessionManager.StartNewSession();
- }
- }
- EditorGUILayout.EndHorizontal();
- }
-
- private void DrawContextStagingArea()
- {
- EditorGUILayout.LabelField("Manual Context", EditorStyles.boldLabel);
- EditorGUILayout.HelpBox("You can manually provide context by dragging and dropping any asset (prefab, script, etc.) or GameObject into the slots below.", MessageType.Info);
- if (GUILayout.Button("Add Manual Context Slot", GUILayout.Width(200)))
- {
- _stagedContextObjects.Add(null);
- if (_isAnalysisContextRequested) _requestedRoles.Add("Custom Role");
- }
- var indexToRemove = -1;
- for (var i = 0; i < _stagedContextObjects.Count; i++)
- {
- EditorGUILayout.BeginHorizontal();
- var label = (_isAnalysisContextRequested && i < _requestedRoles.Count) ? _requestedRoles[i] : $"Slot {i + 1}";
- _stagedContextObjects[i] = EditorGUILayout.ObjectField(label, _stagedContextObjects[i], typeof(Object), true);
- if (GUILayout.Button("X", GUILayout.Width(20)))
- {
- indexToRemove = i;
- }
- EditorGUILayout.EndHorizontal();
- }
- if (indexToRemove != -1)
- {
- _stagedContextObjects.RemoveAt(indexToRemove);
- if (_isAnalysisContextRequested && indexToRemove < _requestedRoles.Count)
- {
- _requestedRoles.RemoveAt(indexToRemove);
- }
- Repaint();
- }
- }
- private void DrawSelectionHint()
- {
- EditorGUILayout.LabelField("Automatic Context (From Selection)", EditorStyles.boldLabel);
- var selectedObjects = Selection.objects;
- if (selectedObjects.Length > 0)
- {
- EditorGUILayout.HelpBox("The following selected objects will automatically be included as context.", MessageType.None);
-
- EditorGUI.BeginDisabledGroup(true);
- foreach (var obj in selectedObjects)
- {
- EditorGUILayout.ObjectField(obj.name, obj, typeof(Object), true);
- }
- EditorGUI.EndDisabledGroup();
- }
- else
- {
- EditorGUILayout.HelpBox("No objects are currently selected in the editor.", MessageType.None);
- }
- }
-
- private void DrawActionButtons()
- {
- EditorGUILayout.BeginHorizontal();
-
- EditorGUI.BeginDisabledGroup(_isRequestInProgress);
- if (_isAnalysisContextRequested)
- {
- if (GUILayout.Button("Provide Staged Context to LLM"))
- {
- _stagedContextObjects.AddRange(Selection.objects);
- var contextSummary = ContextBuilder.BuildTier1Summary(_stagedContextObjects);
- var followUpMessage = "Here is the context you requested:\n" + contextSummary;
- _ = SendFollowUpAsync(followUpMessage);
- }
- }
- else
- {
- if (GUILayout.Button("Send Prompt"))
- {
- _ = SendInitialPromptAsync();
- }
- }
-
- EditorGUI.EndDisabledGroup();
-
- EditorGUI.BeginDisabledGroup(!_isRequestInProgress);
- if (GUILayout.Button("Stop", GUILayout.Width(80)))
- {
- _cancellationTokenSource?.Cancel();
- }
- EditorGUI.EndDisabledGroup();
-
- EditorGUILayout.EndHorizontal();
- }
- private void DrawPlanArea()
- {
- if (_currentPlan == null || !_currentPlan.Any()) return;
-
- EditorGUILayout.LabelField("Current Plan", EditorStyles.boldLabel);
-
- EditorGUILayout.BeginVertical(EditorStyles.helpBox);
- for(var i = 0; i < _currentPlan.Count; i++)
- {
- var step = _currentPlan[i];
- var label = $"{i + 1}. {step}";
- EditorGUILayout.LabelField(label, EditorStyles.wordWrappedLabel);
- }
- EditorGUILayout.EndVertical();
- }
-
- private void DrawCommandQueue()
- {
- EditorGUILayout.LabelField("Pending Commands", EditorStyles.boldLabel);
- _scrollPos = EditorGUILayout.BeginScrollView(_scrollPos, EditorStyles.helpBox);
-
- if (CommandExecutor.HasPendingCommands())
- {
- var nextCommand = CommandExecutor.GetNextCommand();
- EditorGUILayout.LabelField("Next Up: " + nextCommand.commandName);
- EditorGUI.BeginDisabledGroup(_isRequestInProgress);
- if (GUILayout.Button("Execute Next Command"))
- {
- _ = CommandExecutor.ExecuteNextCommand();
- }
- EditorGUI.EndDisabledGroup();
- }
- else
- {
- EditorGUILayout.LabelField("No pending commands.");
- }
- EditorGUILayout.EndScrollView();
- }
-
- private async Task SendInitialPromptAsync()
- {
- _isRequestInProgress = true;
- _cancellationTokenSource = new CancellationTokenSource();
- _currentPlan.Clear();
- Repaint();
-
- try
- {
- var combinedContext = new List<Object>();
- combinedContext.AddRange(_stagedContextObjects);
- if (!(_apiClient is DummyApiClient))
- {
- combinedContext.AddRange(Selection.objects);
- }
-
- await _apiClient.SendPrompt(_promptText, combinedContext, _cancellationTokenSource.Token);
- }
- catch (TaskCanceledException)
- {
- Debug.Log("[MCPWindow] Task was cancelled.");
- }
- finally
- {
- _isRequestInProgress = false;
- _cancellationTokenSource.Dispose();
- _cancellationTokenSource = null;
- Repaint();
- }
- }
-
- private async Task SendFollowUpAsync(string followUpMessage)
- {
- _isRequestInProgress = true;
- _cancellationTokenSource = new CancellationTokenSource();
- Repaint();
- try
- {
- await _apiClient.SendFollowUp(followUpMessage, _cancellationTokenSource.Token);
- }
- catch (TaskCanceledException)
- {
- Debug.Log("[MCPWindow] Task was cancelled.");
- }
- finally
- {
- _isRequestInProgress = false;
- _cancellationTokenSource.Dispose();
- _cancellationTokenSource = null;
-
- if (_isAnalysisContextRequested)
- {
- ClearStagingArea();
- }
- Repaint();
- }
- }
-
- private void ClearStagingArea()
- {
- _isAnalysisContextRequested = false;
- _stagedContextObjects.Clear();
- _requestedRoles.Clear();
- }
- }
- }
|