Files
SkyAI/Program.cs

228 lines
6.9 KiB
C#

using DotNetEnv;
using Valour.Sdk.Client;
using Valour.Sdk.Models;
using SkyAI;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
namespace SkyAI;
public class ChatMessage
{
public string role { get; set; } = "";
public string content { get; set; } = "";
}
public class Program
{
const int MaxHistoryMessages = 10;
const int MaxResponseLength = 2048;
const string ModelName = "llama3.1:latest";
const string prefix = "s.";
const string sourceLink = "https://github.com/SkyJoshua/SkyAI";
private static readonly Dictionary<long, Channel> channelCache = new();
private static readonly Dictionary<long, List<ChatMessage>> ChatHistory = new();
private static readonly HashSet<long> InitializedPlanets = new();
public static async Task Main()
{
Env.Load();
var token = Environment.GetEnvironmentVariable("TOKEN");
var openWebApi = Environment.GetEnvironmentVariable("OPENWEBAPI");
var openWebUrl = Environment.GetEnvironmentVariable("OPENWEBURL");
if (string.IsNullOrWhiteSpace(token) ||
string.IsNullOrWhiteSpace(openWebApi) ||
string.IsNullOrWhiteSpace(openWebUrl))
{
Console.WriteLine("Missing required environment variables.");
return;
}
var client = new ValourClient("https://api.valour.gg/");
client.SetupHttpClient();
var loginResult = await client.InitializeUser(token);
if (!loginResult.Success)
{
Console.WriteLine($"Login Failed: {loginResult.Message}");
return;
}
Console.WriteLine($"Logged in as {client.Me.Name}");
await Utils.InitializePlanetsAsync(client, channelCache, InitializedPlanets);
var httpClient = new HttpClient
{
BaseAddress = new Uri(openWebUrl)
};
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", openWebApi);
client.MessageService.MessageReceived += async (message) =>
{
if (string.IsNullOrWhiteSpace(message.Content))
return;
var content = message.Content;
var channelId = message.ChannelId;
var member = await message.FetchAuthorMemberAsync();
var ping = $"«@m-{member.Id}»";
if (content.StartsWith($"{prefix}cm"))
{
ChatHistory.Remove(channelId);
await Utils.SendReplyAsync(channelCache, channelId, $"{ping} Channel memory cleared.");
return;
}
if (content.StartsWith($"{prefix}source"))
{
await Utils.SendReplyAsync(channelCache, channelId, $"{ping} You can find my source code here: {sourceLink}");
}
if (!content.StartsWith($"{prefix}ai"))
return;
var prompt = content.Substring(prefix.Length+3).Trim();
if (string.IsNullOrWhiteSpace(prompt))
{
await Utils.SendReplyAsync(channelCache, channelId, $"{ping} Enter a question.");
return;
}
var history = GetOrCreateHistory(channelId);
history.Add(new ChatMessage { role = "user", content = $"User: {member.Name} (ID: {member.Id}) | {prompt}" });
TrimHistory(history);
var payload = new
{
model = ModelName,
messages = history
};
var json = JsonSerializer.Serialize(payload);
HttpResponseMessage response;
try
{
response = await httpClient.PostAsync(
"/api/chat/completions",
new StringContent(json, Encoding.UTF8, "application/json")
);
}
catch (HttpRequestException ex)
{
await Utils.SendReplyAsync(
channelCache,
channelId,
$"{ping} Cannot reach OpenWebUI server. Check OPENWEBURL.\nError: {ex.Message}"
);
return;
}
catch (TaskCanceledException)
{
await Utils.SendReplyAsync(
channelCache,
channelId,
$"{ping} Connection timed out. Check OPENWEBURL."
);
return;
}
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
await Utils.SendReplyAsync(
channelCache,
channelId,
$"{ping} OpenWebUI API key is invalid."
);
return;
}
if (!response.IsSuccessStatusCode)
{
var errorBody = await response.Content.ReadAsStringAsync();
await Utils.SendReplyAsync(
channelCache,
channelId,
$"{ping} OpenWebUI error {(int)response.StatusCode}:\n{errorBody}"
);
return;
}
var responseText = await response.Content.ReadAsStringAsync();
using var doc = JsonDocument.Parse(responseText);
var output = doc.RootElement
.GetProperty("choices")[0]
.GetProperty("message")
.GetProperty("content")
.GetString() ?? "";
history.Add(new ChatMessage { role = "assistant", content = output });
output = $"{ping} {output}";
output = Truncate(output);
await Utils.SendReplyAsync(channelCache, channelId, output);
};
Console.WriteLine("Listening...");
await Task.Delay(Timeout.Infinite);
}
private static List<ChatMessage> GetOrCreateHistory(long channelId)
{
if (!ChatHistory.ContainsKey(channelId))
{
ChatHistory[channelId] = new List<ChatMessage>
{
new ChatMessage
{
role = "system",
content = "You are a helpful AI assistant. Keep responses under 2048 characters."
}
};
}
return ChatHistory[channelId];
}
private static void TrimHistory(List<ChatMessage> history)
{
if (history.Count <= (MaxHistoryMessages * 2) + 1)
return;
var system = history[0];
var trimmed = history
.Skip(history.Count - (MaxHistoryMessages * 2))
.ToList();
trimmed.Insert(0, system);
history.Clear();
history.AddRange(trimmed);
}
private static string Truncate(string text)
{
if (string.IsNullOrEmpty(text))
return text;
return text.Length <= MaxResponseLength
? text
: text.Substring(0, MaxResponseLength);
}
}