123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- using System.Linq;
- using System.Text;
- using UnityEditor;
- using LLM.Editor.Core;
- using System.Diagnostics;
- using LLM.Editor.Helper;
- using UnityEngine.Networking;
- using System.Threading.Tasks;
- using System.Collections.Generic;
- using Debug = UnityEngine.Debug;
- namespace LLM.Editor.Client
- {
- public static class GeminiApiClient
- {
- [System.Serializable]
- private class CommandResponse { public List<Data.CommandData> commands; }
- private static Settings.MCPSettings _settings;
- private static string _systemPrompt;
- private static bool LoadSettings()
- {
- if (_settings) return true;
- var guids = AssetDatabase.FindAssets("t:MCPSettings");
- if (guids.Length == 0)
- {
- Debug.LogError("[GeminiApiClient] Could not find MCPSettings asset. Please create one via Assets > Create > LLM > MCP Settings.");
- return false;
- }
- var path = AssetDatabase.GUIDToAssetPath(guids[0]);
- _settings = AssetDatabase.LoadAssetAtPath<Settings.MCPSettings>(path);
- return _settings;
- }
-
- private static bool LoadSystemPrompt()
- {
- if (!string.IsNullOrEmpty(_systemPrompt)) return true;
- var guids = AssetDatabase.FindAssets("MCP_SystemPrompt");
- if (guids.Length == 0)
- {
- Debug.LogError("[GeminiApiClient] Could not find 'MCP_SystemPrompt.txt'. Please create this text file in your project and enter the system prompt into it.");
- return false;
- }
- var path = AssetDatabase.GUIDToAssetPath(guids[0]);
- _systemPrompt = System.IO.File.ReadAllText(path);
- return !string.IsNullOrEmpty(_systemPrompt);
- }
- private static string GetAuthToken()
- {
- if (!LoadSettings()) return null;
- try
- {
- var startInfo = new ProcessStartInfo
- {
- FileName = _settings.gcloudPath,
- Arguments = "auth print-access-token",
- RedirectStandardOutput = true,
- RedirectStandardError = true,
- UseShellExecute = false,
- CreateNoWindow = true
- };
- using var process = Process.Start(startInfo);
- if (process == null) return null;
-
- var accessToken = process.StandardOutput.ReadToEnd().Trim();
- var error = process.StandardError.ReadToEnd();
- process.WaitForExit();
- if (process.ExitCode == 0) return accessToken;
-
- Debug.LogError($"[GeminiApiClient] gcloud auth error: {error}");
- return null;
- }
- catch (System.Exception e)
- {
- Debug.LogError($"[GeminiApiClient] Exception while getting auth token: {e.Message}");
- return null;
- }
- }
- public static async Task SendPrompt(string prompt)
- {
- if (!LoadSettings() || !LoadSystemPrompt()) return;
- Debug.Log($"[GeminiApiClient] Getting auth token...");
- var authToken = GetAuthToken();
- if (string.IsNullOrEmpty(authToken))
- {
- Debug.LogError("[GeminiApiClient] Failed to get authentication token. Is gcloud CLI installed and are you logged in?");
- return;
- }
-
- Debug.Log($"[GeminiApiClient] Sending prompt to live API: {prompt}");
-
- var chatHistory = SessionManager.LoadChatHistory();
- chatHistory.Add(new Data.ChatEntry { role = "user", content = prompt });
- SessionManager.SaveChatHistory(chatHistory);
- var url = $"https://{_settings.gcpRegion}-aiplatform.googleapis.com/v1/projects/{_settings.gcpProjectId}/locations/{_settings.gcpRegion}/publishers/google/models/{_settings.modelName}:generateContent";
-
- // Construct the request payload, adding the system prompt first.
- var apiRequest = new Api.ApiRequest
- {
- system_instruction = new Api.SystemInstruction
- {
- parts = new List<Api.Part> { new() { text = _systemPrompt } }
- },
- contents = chatHistory.Select(entry => new Api.Content
- {
- // The API expects "model" for the assistant's role.
- role = entry.role == "assistant" ? "model" : entry.role,
- parts = new List<Api.Part> { new() { text = entry.content } }
- }).ToList()
- };
-
- var jsonPayload = apiRequest.ToJson();
- 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 {authToken}");
- var operation = request.SendWebRequest();
- while (!operation.isDone)
- {
- await Task.Yield();
- }
- if (request.result == UnityWebRequest.Result.Success)
- {
- var responseJson = request.downloadHandler.text;
- Debug.Log($"[GeminiApiClient] Response received: \n{responseJson}.");
- var apiResponse = responseJson?.FromJson<Api.ApiResponse>();
-
- if (apiResponse is { candidates: not null } && apiResponse.candidates.Any())
- {
- var commandJson = apiResponse.candidates[0].content.parts[0].text;
- Debug.Log($"[GeminiApiClient] Command received: {commandJson}.");
- chatHistory.Add(new Data.ChatEntry { role = "model", content = commandJson });
- SessionManager.SaveChatHistory(chatHistory);
- var commandResponse = commandJson?.FromJson<CommandResponse>();
- if (commandResponse is { commands: not null })
- {
- Debug.Log($"[GeminiApiClient] Received {commandResponse.commands.Count} commands from LLM.");
- CommandExecutor.SetQueue(commandResponse.commands);
- }
- else
- {
- Debug.LogError($"[GeminiApiClient] Failed to parse command structure from LLM response text: {commandJson}");
- }
- }
- }
- else
- {
- Debug.LogError($"[GeminiApiClient] API Error: {request.error}\n{request.downloadHandler.text}");
- }
- }
- }
- }
|