123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250 |
- using System.Diagnostics;
- using System.IO;
- using System.Text;
- using System.Threading.Tasks;
- using UnityEditor;
- using UnityEngine;
- using UnityEngine.Networking;
- using Debug = UnityEngine.Debug;
- namespace LLM.Editor.PoC
- {
- /// <summary>
- /// A Unity Editor Window to interact with Google Cloud's Gemini LLM.
- /// This tool authenticates using the gcloud CLI to get an OAuth 2.0 token.
- /// </summary>
- public class LLMPromptTool : EditorWindow
- {
- // --- PRIVATE FIELDS ---
- // User inputs
- private string _gcpProjectId = "your-gcp-project-id";
- private string _region = "us-central1";
- private string _modelName = "gemini-1.5-flash-preview-0514";
- private string _gcloudPath = "gcloud"; // Path to the gcloud executable
- private string _promptText = "Please explain what this C# script does.";
- private string _filePath = "";
- private string _fileContent = "";
- // Response and status
- private string _llmResponse = "Awaiting prompt...";
- private bool _isRequestInProgress;
- // UI layout
- private Vector2 _promptScrollPos;
- private Vector2 _responseScrollPos;
- // --- UNITY EDITOR WINDOW ---
- [MenuItem("LLM/LLM Prompt Tool")]
- public static void ShowWindow()
- {
- GetWindow<LLMPromptTool>("GCP LLM Prompt");
- }
- void OnGUI()
- {
- var titleStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 16, alignment = TextAnchor.MiddleCenter };
- var headerStyle = new GUIStyle(EditorStyles.boldLabel) { fontSize = 12 };
- var wordWrapLabelStyle = new GUIStyle(EditorStyles.label) { wordWrap = true };
- var wordWrapTextAreaStyle = new GUIStyle(EditorStyles.textArea) { wordWrap = true };
- EditorGUILayout.LabelField("GCP LLM Prototype", titleStyle, GUILayout.Height(25));
- EditorGUILayout.Space(10);
- EditorGUILayout.LabelField("GCP Configuration", headerStyle);
- _gcpProjectId = EditorGUILayout.TextField("Project ID", _gcpProjectId);
- _region = EditorGUILayout.TextField("Region", _region);
- _modelName = EditorGUILayout.TextField("Model Name", _modelName);
- _gcloudPath = EditorGUILayout.TextField("GCloud Executable Path", _gcloudPath);
- EditorGUILayout.HelpBox("Because Unity doesn't know your system's PATH, you may need to provide the full path to the gcloud executable.\n- On macOS/Linux, run 'which gcloud' in terminal.\n- On Windows, run 'where gcloud' in Command Prompt.", MessageType.Info);
- EditorGUILayout.Space(15);
- EditorGUILayout.LabelField("User Prompt", headerStyle);
- _promptScrollPos = EditorGUILayout.BeginScrollView(_promptScrollPos, GUILayout.Height(100));
- _promptText = EditorGUILayout.TextArea(_promptText, wordWrapTextAreaStyle, GUILayout.ExpandHeight(true));
- EditorGUILayout.EndScrollView();
- EditorGUILayout.Space(10);
- EditorGUILayout.LabelField("Attach C# File (Optional)", headerStyle);
- EditorGUILayout.BeginHorizontal();
- EditorGUILayout.TextField("File Path", _filePath);
- if (GUILayout.Button("Browse...", GUILayout.Width(80)))
- {
- SelectFile();
- }
- EditorGUILayout.EndHorizontal();
- if (!string.IsNullOrEmpty(_fileContent))
- {
- EditorGUILayout.HelpBox($"Attached file '{Path.GetFileName(_filePath)}'. Length: {_fileContent.Length} characters.", MessageType.None);
- }
- EditorGUILayout.Space(15);
- UnityEngine.GUI.enabled = !_isRequestInProgress && !string.IsNullOrEmpty(_gcpProjectId);
- if (GUILayout.Button(_isRequestInProgress ? "Waiting for Response..." : "Send Prompt to Gemini", GUILayout.Height(40)))
- {
- _ = SendPromptToLLM();
- }
- UnityEngine.GUI.enabled = true;
- EditorGUILayout.Space(15);
- EditorGUILayout.LabelField("LLM Response", headerStyle);
- _responseScrollPos = EditorGUILayout.BeginScrollView(_responseScrollPos, EditorStyles.helpBox, GUILayout.ExpandHeight(true));
- EditorGUILayout.SelectableLabel(_llmResponse, wordWrapLabelStyle, GUILayout.ExpandHeight(true));
- EditorGUILayout.EndScrollView();
- }
- // --- PRIVATE METHODS ---
- /// <summary>
- /// Executes the 'gcloud auth print-access-token' command to get a valid OAuth 2.0 token.
- /// </summary>
- /// <returns>The access token string, or null if an error occurs.</returns>
- private string GetAccessToken()
- {
- try
- {
- var startInfo = new ProcessStartInfo
- {
- FileName = _gcloudPath, // Use the full path provided by the user
- Arguments = "auth print-access-token",
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- UseShellExecute = false,
- CreateNoWindow = true
- };
- using var process = Process.Start(startInfo);
- if (process != null)
- {
- var accessToken = process.StandardOutput.ReadToEnd().Trim();
- var error = process.StandardError.ReadToEnd();
- process.WaitForExit();
- if (process.ExitCode == 0) return accessToken;
- Debug.LogError($"gcloud error: {error}");
- _llmResponse = $"Failed to get auth token. Is gcloud CLI installed and are you logged in?\nError: {error}";
- return null;
- }
- }
- catch (System.Exception e)
- {
- Debug.LogError($"Exception while getting access token: {e.Message}");
- _llmResponse = $"Failed to run gcloud from path '{_gcloudPath}'. Ensure the path is correct and the Google Cloud CLI is installed.";
- }
- return null;
- }
- private void SelectFile()
- {
- var path = EditorUtility.OpenFilePanel("Select C# Script", Application.dataPath, "cs");
- if (string.IsNullOrEmpty(path)) return;
- _filePath = path;
- try
- {
- _fileContent = File.ReadAllText(_filePath);
- }
- catch (System.Exception e)
- {
- _fileContent = "";
- _filePath = "";
- Debug.LogError($"Failed to read file: {e.Message}");
- _llmResponse = $"Error: Could not read file at path {path}.";
- }
- }
- private async Task SendPromptToLLM()
- {
- _isRequestInProgress = true;
- _llmResponse = "Getting auth token...";
- Repaint();
- // 1. Get the OAuth 2.0 access token from gcloud
- var accessToken = GetAccessToken();
- if (string.IsNullOrEmpty(accessToken))
- {
- _isRequestInProgress = false;
- Repaint();
- return;
- }
- _llmResponse = "Sending request to GCP...";
- Repaint();
- // 2. Construct the API request
- var url = $"https://{_region}-aiplatform.googleapis.com/v1/projects/{_gcpProjectId}/locations/{_region}/publishers/google/models/{_modelName}:generateContent";
- var requestPayload = new RequestPayload();
- var fullPrompt = new StringBuilder(_promptText);
- if (!string.IsNullOrEmpty(_fileContent))
- {
- fullPrompt.AppendLine("\n\n--- Attached C# Script ---");
- fullPrompt.AppendLine(_fileContent);
- }
-
- requestPayload.contents[0].parts[0].text = fullPrompt.ToString();
- requestPayload.contents[0].role = "user";
- var jsonPayload = JsonUtility.ToJson(requestPayload);
- // 3. Send the request with the new auth token
- using (var request = new UnityWebRequest(url, "POST"))
- {
- var bodyRaw = Encoding.UTF8.GetBytes(jsonPayload);
- request.uploadHandler = new UploadHandlerRaw(bodyRaw);
- request.downloadHandler = new DownloadHandlerBuffer();
- request.SetRequestHeader("Content-Type", "application/json");
- request.SetRequestHeader("Authorization", $"Bearer {accessToken}"); // Use the fetched token
- var operation = request.SendWebRequest();
- while (!operation.isDone)
- {
- await Task.Yield();
- }
- if (request.result == UnityWebRequest.Result.Success)
- {
- try
- {
- var responseJson = request.downloadHandler.text;
- var responsePayload = JsonUtility.FromJson<ResponsePayload>(responseJson);
-
- if (responsePayload.candidates != null && responsePayload.candidates.Length > 0 && responsePayload.candidates[0].content?.parts?.Length > 0)
- {
- _llmResponse = responsePayload.candidates[0].content.parts[0].text;
- Debug.Log(_llmResponse);
- }
- else
- {
- _llmResponse = "Received a valid but empty or malformed response.\n\n" + responseJson;
- }
- }
- catch (System.Exception e)
- {
- _llmResponse = $"Failed to parse JSON response: {e.Message}\n\nRaw Response:\n{request.downloadHandler.text}";
- }
- }
- else
- {
- _llmResponse = $"Error: {request.error}\n\n{request.downloadHandler.text}";
- Debug.LogError($"GCP Request Error: {request.error}\nResponse: {request.downloadHandler.text}");
- }
- }
- _isRequestInProgress = false;
- Repaint();
- }
- // --- JSON HELPER CLASSES ---
- [System.Serializable] private class RequestPayload { public Content[] contents = { new() }; }
- [System.Serializable] private class Content { public Part[] parts = { new() }; public string role; }
- [System.Serializable] private class Part { public string text; }
- [System.Serializable] private class ResponsePayload { public Candidate[] candidates; }
- [System.Serializable] private class Candidate { public Content content; }
- }
- }
|