using ConVar; using Newtonsoft.Json; using Oxide.Core; using Oxide.Core.Libraries; using Oxide.Core.Libraries.Covalence; using Oxide.Core.Plugins; using System; using System.Collections.Generic; using System.Globalization; using System.IO; namespace Oxide.Plugins { [Info("AITranslation", "Flutes", "0.0.1")] [Description("Translates text using OpenAI")] internal class AITranslation : RustPlugin { #region Fields public static AITranslation Instance { get; private set; } public static PluginConfiguration Configuration { get; private set; } public static LangFilesTranslationAgent LangFilesTranslation { get; private set; } private bool m_isInitialized = false; #endregion #region Agents public interface IAgent { string Name { get; } string Description { get; } string Prompt { get; } } public class LangFilesTranslationAgent : IAgent { public string Name { get; private set; }= "Lang Files Translation"; public string Description { get; private set; } = "Translates lang files"; public string Prompt { get; private set; } = "lang"; private LangFilesTranslationConfguration m_configuration; public LangFilesTranslationAgent(LangFilesTranslationConfguration config) { m_configuration = config; Prompt = config.Model.Prompt; } public void TranslateAllPlugins(List neededLangs) { if (neededLangs == null || neededLangs.Count == 0) { Instance.Puts($"[LangFilesTranslationAgent] Need langs"); return; } var plugins = Interface.Oxide.RootPluginManager.GetPlugins(); foreach(var plugin in plugins) { TranslateFile(plugin.Name, neededLangs); } } public void TranslateFile(string plugin, List neededLangs) { if (neededLangs == null || neededLangs.Count == 0) { Instance.Puts($"[LangFilesTranslationAgent] Need langs"); return; } var englishFolder = Path.Combine(Interface.Oxide.LangDirectory, "en"); var englishFile = Path.Combine(englishFolder, $"{plugin}.json"); if (File.Exists(englishFile) == false) { Instance.Puts($"[LangFilesTranslationAgent] Couldn't find english file for plugin {plugin}"); return; } var englishContent = File.ReadAllText(englishFile); foreach (var lang in neededLangs) { CultureInfo cultureInfo = new CultureInfo(lang); if (cultureInfo == null) { Instance.Puts($"[LangFilesTranslationAgent] Couldn't find culture info for lang {lang}"); continue; } string prompt = Prompt.Replace("$lang", cultureInfo.EnglishName).Replace("$text", englishContent); OpenAIHttpService.Instance.SendMessageAsync(prompt, m_configuration.Model, (response) => { if (response == null) { Instance.Puts($"[LangFilesTranslationAgent] Received null response from OpenAI"); return; } var langFolder = Path.Combine(Interface.Oxide.LangDirectory, lang); var langFile = Path.Combine(langFolder, $"{plugin}.json"); if (Directory.Exists(langFolder) == false) Directory.CreateDirectory(langFolder); File.WriteAllText(langFile, response); }, () => { Instance.Puts($"[LangFilesTranslationAgent] Couldn't get a response from OpenAI for lang {lang}"); }); } } } #endregion #region Http public class OpenAIHttpService { public static OpenAIHttpService Instance { get; private set; } private const string openAiApiUrl = "https://api.openai.com/v1/chat/completions"; // OpenAI Chat API URL public OpenAIHttpService() { Instance = this; } public void SendMessageAsync(string message, AIModelConfiguration model, Action onResult, Action onError = null) { AITranslation.Instance.Puts($"[OpenAIHttpService] Sending message : {message}"); var requestBody = new OpenAiChatMessage() { model = "gpt-3.5-turbo", messages = new List() { new OpenAIMessage { role = "user", content = message } }, temperature = model.Temperature, max_tokens = model.MaxTokens }; AITranslation.Instance.webrequest.Enqueue(openAiApiUrl, Newtonsoft.Json.JsonConvert.SerializeObject(requestBody), (code, response) => { if (code != 200 || response == null) { AITranslation.Instance.Puts($"Couldn't get an answer from OpenAIServer code " + code.ToString()); onError?.Invoke(); return; } OpenAIChatResponse openAIChatResponse = Newtonsoft.Json.JsonConvert.DeserializeObject(response); if (openAIChatResponse == null) { AITranslation.Instance.Puts($"Couldn't deserialize OpenAIChatResponse"); onError?.Invoke(); return; } if (openAIChatResponse.choices == null || openAIChatResponse.choices.Count == 0) { AITranslation.Instance.Puts($"Couldn't get an answer from OpenAIServer"); onError?.Invoke(); return; } var responseMessage = openAIChatResponse.choices[0].Message.content; AITranslation.Instance.Puts($"[OpenAIHttpService] Received response : {responseMessage}"); onResult?.Invoke(responseMessage); }, AITranslation.Instance, RequestMethod.POST, new Dictionary() { { "Content-Type", "application/json" }, { "Authorization", $"Bearer {AITranslation.Configuration.OpenAIToken}" } }); } } public class OpenAiChatMessage { public string model { get; set; } public List messages { get; set; } public float temperature { get; set; } public int max_tokens { get; set; } } public class OpenAIChatResponse { public OpenAIUsage usage { get; set; } public List choices { get; set; } } public class OpenAIUsage { public int prompt_tokens { get; set; } public int completion_tokens { get; set; } public int total_tokens { get; set; } } public class OpenAIChoice { public OpenAIMessage Message { get; set; } public string finish_reason { get; set; } } public class OpenAIMessage { public string role { get; set; } public string content { get; set; } } #endregion #region ConsoleCommands private string m_translatePluginCommandUsage = "Usage: ait.translateplugin "; [ConsoleCommand("ait.translateplugin")] private void TranslatePluginCommand(ConsoleSystem.Arg args) { if (args.IsAdmin == false) return; if (m_isInitialized == false) { Puts("Plugin is not initialized"); return; } if (args.Args == null || args.Args.Length != 2) { Puts(m_translatePluginCommandUsage); return; } var pluginName = args.Args[0]; var lang = args.Args[1]; LangFilesTranslation.TranslateFile(pluginName, new List() { lang }); } #endregion #region Configuration public class PluginConfiguration { public string OpenAIToken { get; set; } public LangFilesTranslationConfguration LangFilesConfiguration { get; set; } } public interface IFeatureConfiguration { bool Enabled { get; set; } } public class LangFilesTranslationConfguration : IFeatureConfiguration { public bool Enabled { get; set; } public AIModelConfiguration Model { get; set; } } public class AIModelConfiguration { public float Temperature { get; set; } public int MaxTokens { get; set; } public string Prompt { get; set; } } protected override void LoadDefaultConfig() { Configuration = new PluginConfiguration() { OpenAIToken = "", LangFilesConfiguration = new LangFilesTranslationConfguration() { Enabled = false, Model = new AIModelConfiguration() { Temperature = 0.6f, MaxTokens = 1024, Prompt = "You must translate the contents of this json into $lang, only the values (and not the keys) need to be translated. When in doubt, use the words/phrases that seem most relevant to you. Under no circumstances should you refer to the fact that you are an AI or anything else, you must only return the translated json:\r\n\r\n$text" } } }; } protected override void LoadConfig() { base.LoadConfig(); try { var config = Config.ReadObject(); if (config == null) { throw new JsonException(); } Configuration = config; } catch (Exception ex) { Puts($"Configuration file {Name}.json is invalid; using defaults error : {ex.Message}"); LoadDefaultConfig(); } } protected override void SaveConfig() { Puts($"Configuration changes saved to {Name}.json"); Config.WriteObject(Configuration, true); } #endregion #region Setup private void Init() { Instance = this; OpenAIHttpService openAIHttpService = new OpenAIHttpService(); LoadConfig(); if (Configuration == null) { Puts($"Imposible to init plugin : Couldn't load configuration"); return; } if (string.IsNullOrEmpty(Configuration.OpenAIToken)) { Puts($"Imposible to init plugin : OpenAI Token is empty"); return; } if (Configuration.LangFilesConfiguration != null) { LangFilesTranslation = new LangFilesTranslationAgent(Configuration.LangFilesConfiguration); } else { Puts("Imposible to use Lang Files Translateion : LangFilesTranslation is not configured"); } m_isInitialized = true; } #endregion } }