GeminiApiClient.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. using System.Linq;
  2. using System.Text;
  3. using UnityEditor;
  4. using LLM.Editor.Core;
  5. using System.Diagnostics;
  6. using LLM.Editor.Helper;
  7. using UnityEngine.Networking;
  8. using System.Threading.Tasks;
  9. using System.Collections.Generic;
  10. using Debug = UnityEngine.Debug;
  11. namespace LLM.Editor.Client
  12. {
  13. public static class GeminiApiClient
  14. {
  15. [System.Serializable]
  16. private class CommandResponse { public List<Data.CommandData> commands; }
  17. private static Settings.MCPSettings _settings;
  18. private static string _systemPrompt;
  19. private static bool LoadSettings()
  20. {
  21. if (_settings) return true;
  22. var guids = AssetDatabase.FindAssets("t:MCPSettings");
  23. if (guids.Length == 0)
  24. {
  25. Debug.LogError("[GeminiApiClient] Could not find MCPSettings asset. Please create one via Assets > Create > LLM > MCP Settings.");
  26. return false;
  27. }
  28. var path = AssetDatabase.GUIDToAssetPath(guids[0]);
  29. _settings = AssetDatabase.LoadAssetAtPath<Settings.MCPSettings>(path);
  30. return _settings;
  31. }
  32. private static bool LoadSystemPrompt()
  33. {
  34. if (!string.IsNullOrEmpty(_systemPrompt)) return true;
  35. var guids = AssetDatabase.FindAssets("MCP_SystemPrompt");
  36. if (guids.Length == 0)
  37. {
  38. Debug.LogError("[GeminiApiClient] Could not find 'MCP_SystemPrompt.txt'. Please create this text file in your project and enter the system prompt into it.");
  39. return false;
  40. }
  41. var path = AssetDatabase.GUIDToAssetPath(guids[0]);
  42. _systemPrompt = System.IO.File.ReadAllText(path);
  43. return !string.IsNullOrEmpty(_systemPrompt);
  44. }
  45. private static string GetAuthToken()
  46. {
  47. if (!LoadSettings()) return null;
  48. try
  49. {
  50. var startInfo = new ProcessStartInfo
  51. {
  52. FileName = _settings.gcloudPath,
  53. Arguments = "auth print-access-token",
  54. RedirectStandardOutput = true,
  55. RedirectStandardError = true,
  56. UseShellExecute = false,
  57. CreateNoWindow = true
  58. };
  59. using var process = Process.Start(startInfo);
  60. if (process == null) return null;
  61. var accessToken = process.StandardOutput.ReadToEnd().Trim();
  62. var error = process.StandardError.ReadToEnd();
  63. process.WaitForExit();
  64. if (process.ExitCode == 0) return accessToken;
  65. Debug.LogError($"[GeminiApiClient] gcloud auth error: {error}");
  66. return null;
  67. }
  68. catch (System.Exception e)
  69. {
  70. Debug.LogError($"[GeminiApiClient] Exception while getting auth token: {e.Message}");
  71. return null;
  72. }
  73. }
  74. public static async Task SendPrompt(string prompt)
  75. {
  76. if (!LoadSettings() || !LoadSystemPrompt()) return;
  77. Debug.Log($"[GeminiApiClient] Getting auth token...");
  78. var authToken = GetAuthToken();
  79. if (string.IsNullOrEmpty(authToken))
  80. {
  81. Debug.LogError("[GeminiApiClient] Failed to get authentication token. Is gcloud CLI installed and are you logged in?");
  82. return;
  83. }
  84. Debug.Log($"[GeminiApiClient] Sending prompt to live API: {prompt}");
  85. var chatHistory = SessionManager.LoadChatHistory();
  86. chatHistory.Add(new Data.ChatEntry { role = "user", content = prompt });
  87. SessionManager.SaveChatHistory(chatHistory);
  88. var url = $"https://{_settings.gcpRegion}-aiplatform.googleapis.com/v1/projects/{_settings.gcpProjectId}/locations/{_settings.gcpRegion}/publishers/google/models/{_settings.modelName}:generateContent";
  89. // Construct the request payload, adding the system prompt first.
  90. var apiRequest = new Api.ApiRequest
  91. {
  92. system_instruction = new Api.SystemInstruction
  93. {
  94. parts = new List<Api.Part> { new() { text = _systemPrompt } }
  95. },
  96. contents = chatHistory.Select(entry => new Api.Content
  97. {
  98. // The API expects "model" for the assistant's role.
  99. role = entry.role == "assistant" ? "model" : entry.role,
  100. parts = new List<Api.Part> { new() { text = entry.content } }
  101. }).ToList()
  102. };
  103. var jsonPayload = apiRequest.ToJson();
  104. using var request = new UnityWebRequest(url, "POST");
  105. var bodyRaw = Encoding.UTF8.GetBytes(jsonPayload);
  106. request.uploadHandler = new UploadHandlerRaw(bodyRaw);
  107. request.downloadHandler = new DownloadHandlerBuffer();
  108. request.SetRequestHeader("Content-Type", "application/json");
  109. request.SetRequestHeader("Authorization", $"Bearer {authToken}");
  110. var operation = request.SendWebRequest();
  111. while (!operation.isDone)
  112. {
  113. await Task.Yield();
  114. }
  115. if (request.result == UnityWebRequest.Result.Success)
  116. {
  117. var responseJson = request.downloadHandler.text;
  118. Debug.Log($"[GeminiApiClient] Response received: \n{responseJson}.");
  119. var apiResponse = responseJson?.FromJson<Api.ApiResponse>();
  120. if (apiResponse is { candidates: not null } && apiResponse.candidates.Any())
  121. {
  122. var commandJson = apiResponse.candidates[0].content.parts[0].text;
  123. Debug.Log($"[GeminiApiClient] Command received: {commandJson}.");
  124. chatHistory.Add(new Data.ChatEntry { role = "model", content = commandJson });
  125. SessionManager.SaveChatHistory(chatHistory);
  126. var commandResponse = commandJson?.FromJson<CommandResponse>();
  127. if (commandResponse is { commands: not null })
  128. {
  129. Debug.Log($"[GeminiApiClient] Received {commandResponse.commands.Count} commands from LLM.");
  130. CommandExecutor.SetQueue(commandResponse.commands);
  131. }
  132. else
  133. {
  134. Debug.LogError($"[GeminiApiClient] Failed to parse command structure from LLM response text: {commandJson}");
  135. }
  136. }
  137. }
  138. else
  139. {
  140. Debug.LogError($"[GeminiApiClient] API Error: {request.error}\n{request.downloadHandler.text}");
  141. }
  142. }
  143. }
  144. }