Add api_client.cs
This commit is contained in:
commit
985eb20a38
626
api_client.cs
Normal file
626
api_client.cs
Normal file
@ -0,0 +1,626 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Data.Sqlite;
|
||||
|
||||
namespace AISApp
|
||||
{
|
||||
// JSON DTOs for API
|
||||
public sealed class ChatMessage
|
||||
{
|
||||
[JsonPropertyName("role")] public string Role { get; set; } = "";
|
||||
[JsonPropertyName("content")] public string Content { get; set; } = "";
|
||||
}
|
||||
|
||||
public sealed class ChatRequest
|
||||
{
|
||||
[JsonPropertyName("model")] public string Model { get; set; } = "";
|
||||
[JsonPropertyName("messages")] public List<ChatMessage> Messages { get; set; } = new();
|
||||
[JsonPropertyName("temperature")] public double Temperature { get; set; } = 0.0;
|
||||
}
|
||||
|
||||
public sealed class ChatChoice
|
||||
{
|
||||
[JsonPropertyName("message")] public ChatMessage Message { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class ChatResponse
|
||||
{
|
||||
[JsonPropertyName("choices")] public List<ChatChoice> Choices { get; set; } = new();
|
||||
}
|
||||
|
||||
public sealed class AIS
|
||||
{
|
||||
// -------- Static / constants --------
|
||||
private static readonly HttpClient Http = new HttpClient();
|
||||
private static readonly JsonSerializerOptions JsonOpts = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true,
|
||||
WriteIndented = false
|
||||
};
|
||||
|
||||
public static readonly Dictionary<string, string> PredefinedModels = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["dobby"] = "sentientagi/dobby-mini-unhinged-plus-llama-3.1-8b",
|
||||
["dolphin"] = "cognitivecomputations/dolphin-mixtral-8x22b",
|
||||
["dolphin_free"] = "cognitivecomputations/dolphin3.0-mistral-24b:free",
|
||||
["gemma"] = "google/gemma-3-12b-it",
|
||||
["gpt-4o-mini"] = "openai/gpt-4o-mini",
|
||||
["gpt-4.1-nano"] = "openai/gpt-4.1-nano",
|
||||
["qwen"] = "qwen/qwen3-30b-a3b",
|
||||
["unslop"] = "thedrummer/unslopnemo-12b",
|
||||
["euryale"] = "sao10k/l3.3-euryale-70b",
|
||||
["wizard"] = "microsoft/wizardlm-2-8x22b",
|
||||
["deepseek"] = "deepseek/deepseek-chat-v3-0324"
|
||||
};
|
||||
|
||||
// -------- Instance state (mirrors Python) --------
|
||||
public string Name { get; private set; }
|
||||
public double Temperature { get; private set; }
|
||||
public double Backoff { get; private set; }
|
||||
public int MaxRetries { get; private set; }
|
||||
public bool SafeMode { get; private set; }
|
||||
public string SystemMessage { get; private set; }
|
||||
public string Model { get; private set; }
|
||||
public string BaseUrl { get; private set; }
|
||||
public string Rel { get; private set; }
|
||||
public string ApiKey { get; private set; }
|
||||
public string DbName { get; private set; }
|
||||
public bool Loaded { get; private set; }
|
||||
|
||||
private readonly List<ChatMessage> _messages = new();
|
||||
private readonly SqliteConnection _conn;
|
||||
|
||||
private Dictionary<string, string> Headers => new()
|
||||
{
|
||||
["Authorization"] = $"Bearer {ApiKey}",
|
||||
["Content-Type"] = "application/json"
|
||||
};
|
||||
|
||||
// -------- ctor --------
|
||||
public AIS(
|
||||
string? systemMessage = null,
|
||||
string model = "gemma",
|
||||
int maxRetries = 3,
|
||||
double backoff = 1,
|
||||
string? name = null,
|
||||
double temperature = 0.0,
|
||||
string dbName = ":memory:",
|
||||
bool safeMode = true,
|
||||
string baseUrl = "openrouter.ai",
|
||||
string rel = "/api",
|
||||
string? apiKey = null
|
||||
)
|
||||
{
|
||||
Loaded = dbName != ":memory:" && File.Exists(dbName);
|
||||
DbName = dbName;
|
||||
|
||||
// Open connection and create schema
|
||||
_conn = OpenConnection(dbName);
|
||||
InitDb();
|
||||
|
||||
// If existing DB, hydrate settings first
|
||||
if (Loaded)
|
||||
{
|
||||
temperature = GetSetting("temperature", temperature);
|
||||
name = GetSetting("name", name ?? "");
|
||||
backoff = GetSetting("backoff", backoff);
|
||||
maxRetries = GetSetting("max_retries", maxRetries);
|
||||
safeMode = GetSetting("safe_mode", safeMode);
|
||||
systemMessage = GetSetting("system_message", systemMessage ?? "");
|
||||
model = GetSetting("model", model);
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(systemMessage))
|
||||
{
|
||||
systemMessage = "You respond like a calculator; only the result is the answer. Respond only with the result. No explanations or extra words.";
|
||||
}
|
||||
|
||||
SystemMessage = systemMessage;
|
||||
Name = !string.IsNullOrEmpty(name)
|
||||
? $"ais_{name}"
|
||||
: $"ais_{Guid.NewGuid().ToString("N").Substring(0, 8)}";
|
||||
|
||||
Backoff = backoff;
|
||||
SafeMode = safeMode;
|
||||
MaxRetries = maxRetries;
|
||||
BaseUrl = baseUrl;
|
||||
Rel = rel;
|
||||
ApiKey = apiKey ?? Environment.GetEnvironmentVariable("OPENROUTER_API_KEY")
|
||||
?? "sk-or-REPLACE_ME"; // keep explicit default like Python had
|
||||
Temperature = temperature;
|
||||
Model = PredefinedModels.TryGetValue(model, out var mapped) ? mapped : model;
|
||||
|
||||
if (MessagesExist())
|
||||
{
|
||||
_messages = LoadMessages();
|
||||
}
|
||||
else
|
||||
{
|
||||
_messages.Add(new ChatMessage { Role = "system", Content = SystemMessage });
|
||||
SaveMessage("system", SystemMessage);
|
||||
}
|
||||
|
||||
Console.WriteLine($"[INFO] Initialized AIS name={Name} model={Model} db={DbName}");
|
||||
}
|
||||
|
||||
// -------- DB helpers --------
|
||||
private static SqliteConnection OpenConnection(string dbName)
|
||||
{
|
||||
var cs = dbName == ":memory:" ? "Data Source=:memory:" : $"Data Source={dbName}";
|
||||
var conn = new SqliteConnection(cs);
|
||||
conn.Open();
|
||||
return conn;
|
||||
}
|
||||
|
||||
private void InitDb()
|
||||
{
|
||||
using var cmd1 = _conn.CreateCommand();
|
||||
cmd1.CommandText = @"
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
role TEXT NOT NULL,
|
||||
content TEXT NOT NULL
|
||||
)";
|
||||
cmd1.ExecuteNonQuery();
|
||||
|
||||
using var cmd2 = _conn.CreateCommand();
|
||||
cmd2.CommandText = @"
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
json_value TEXT NOT NULL
|
||||
)";
|
||||
cmd2.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
private bool MessagesExist()
|
||||
{
|
||||
using var cmd = _conn.CreateCommand();
|
||||
cmd.CommandText = "SELECT COUNT(*) FROM messages";
|
||||
var count = Convert.ToInt32(cmd.ExecuteScalar());
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
private List<ChatMessage> LoadMessages()
|
||||
{
|
||||
var list = new List<ChatMessage>();
|
||||
using var cmd = _conn.CreateCommand();
|
||||
cmd.CommandText = "SELECT role, content FROM messages ORDER BY id ASC";
|
||||
using var rdr = cmd.ExecuteReader();
|
||||
while (rdr.Read())
|
||||
{
|
||||
list.Add(new ChatMessage
|
||||
{
|
||||
Role = rdr.GetString(0),
|
||||
Content = rdr.GetString(1)
|
||||
});
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private void SaveMessage(string role, string content)
|
||||
{
|
||||
using var cmd = _conn.CreateCommand();
|
||||
cmd.CommandText = "INSERT INTO messages (role, content) VALUES ($r, $c)";
|
||||
cmd.Parameters.AddWithValue("$r", role);
|
||||
cmd.Parameters.AddWithValue("$c", content);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
// Generic typed setting helpers
|
||||
private T GetSetting<T>(string name, T defaultValue)
|
||||
{
|
||||
using var cmd = _conn.CreateCommand();
|
||||
cmd.CommandText = "SELECT json_value FROM settings WHERE name = $n LIMIT 1";
|
||||
cmd.Parameters.AddWithValue("$n", name);
|
||||
var obj = cmd.ExecuteScalar();
|
||||
if (obj is string s)
|
||||
{
|
||||
try
|
||||
{
|
||||
var node = JsonNode.Parse(s);
|
||||
if (node is null) return defaultValue!;
|
||||
return node.Deserialize<T>(JsonOpts)!;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue!;
|
||||
}
|
||||
}
|
||||
return defaultValue!;
|
||||
}
|
||||
|
||||
private void SetSetting(string name, object? value)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(value, JsonOpts);
|
||||
|
||||
using (var upd = _conn.CreateCommand())
|
||||
{
|
||||
upd.CommandText = "UPDATE settings SET json_value = $v WHERE name = $n";
|
||||
upd.Parameters.AddWithValue("$v", json);
|
||||
upd.Parameters.AddWithValue("$n", name);
|
||||
var rows = upd.ExecuteNonQuery();
|
||||
if (rows == 0)
|
||||
{
|
||||
using var ins = _conn.CreateCommand();
|
||||
ins.CommandText = "INSERT INTO settings (name, json_value) VALUES ($n, $v)";
|
||||
ins.Parameters.AddWithValue("$n", name);
|
||||
ins.Parameters.AddWithValue("$v", json);
|
||||
ins.ExecuteNonQuery();
|
||||
}
|
||||
}
|
||||
|
||||
// Reflectively update instance if property exists (case-insensitive)
|
||||
var prop = GetType().GetProperty(name, System.Reflection.BindingFlags.Instance |
|
||||
System.Reflection.BindingFlags.Public |
|
||||
System.Reflection.BindingFlags.IgnoreCase);
|
||||
if (prop != null && prop.CanWrite)
|
||||
{
|
||||
try
|
||||
{
|
||||
var targetVal = value;
|
||||
if (value is JsonNode node)
|
||||
{
|
||||
targetVal = node.Deserialize(prop.PropertyType, JsonOpts);
|
||||
}
|
||||
else if (value is string str && prop.PropertyType != typeof(string))
|
||||
{
|
||||
try
|
||||
{
|
||||
var jn = JsonNode.Parse(str);
|
||||
if (jn != null) targetVal = jn.Deserialize(prop.PropertyType, JsonOpts);
|
||||
}
|
||||
catch { /* ignore */ }
|
||||
}
|
||||
prop.SetValue(this, ConvertIfNeeded(targetVal, prop.PropertyType));
|
||||
}
|
||||
catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
|
||||
private static object? ConvertIfNeeded(object? value, Type targetType)
|
||||
{
|
||||
if (value == null) return null;
|
||||
if (targetType.IsAssignableFrom(value.GetType())) return value;
|
||||
try { return Convert.ChangeType(value, targetType); }
|
||||
catch { return value; }
|
||||
}
|
||||
|
||||
private void SaveSettings()
|
||||
{
|
||||
SetSetting("name", Name);
|
||||
SetSetting("temperature", Temperature);
|
||||
SetSetting("backoff", Backoff);
|
||||
SetSetting("max_retries", MaxRetries);
|
||||
SetSetting("safe_mode", SafeMode);
|
||||
SetSetting("system_message", SystemMessage);
|
||||
SetSetting("model", Model);
|
||||
}
|
||||
|
||||
// -------- Public API (mirrors Python) --------
|
||||
public void Append(string messageUser, string messageAssistant, bool save = true)
|
||||
{
|
||||
_messages.Add(new ChatMessage { Role = "user", Content = messageUser });
|
||||
if (save) SaveMessage("user", messageUser);
|
||||
_messages.Add(new ChatMessage { Role = "assistant", Content = messageAssistant });
|
||||
if (save) SaveMessage("assistant", messageAssistant);
|
||||
}
|
||||
|
||||
public void Save(string dbPath)
|
||||
{
|
||||
if (DbName != ":memory:")
|
||||
{
|
||||
Console.WriteLine("[INFO] Already using persistent database.");
|
||||
return;
|
||||
}
|
||||
var dbFile = dbPath.EndsWith(".db", StringComparison.OrdinalIgnoreCase) ? dbPath : dbPath + ".db";
|
||||
|
||||
using var dest = new SqliteConnection($"Data Source={dbFile}");
|
||||
dest.Open();
|
||||
_conn.BackupDatabase(dest);
|
||||
dest.Close();
|
||||
|
||||
DbName = dbFile;
|
||||
Console.WriteLine($"[INFO] Converted in-memory DB to persistent DB at {DbName}");
|
||||
}
|
||||
|
||||
public void PatchTime()
|
||||
{
|
||||
const string timePrefix = "Current time: `";
|
||||
if (_messages.Count == 0) return;
|
||||
|
||||
var content = _messages[0].Content ?? "";
|
||||
var nowStr = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
if (content.StartsWith(timePrefix, StringComparison.Ordinal))
|
||||
{
|
||||
var end = content.IndexOf('`', timePrefix.Length);
|
||||
if (end >= 0)
|
||||
{
|
||||
var rest = content.Substring(end + 1).TrimStart('\n');
|
||||
_messages[0].Content = $"{timePrefix}{nowStr}`\n\n{rest}";
|
||||
}
|
||||
else
|
||||
{
|
||||
_messages[0].Content = $"{timePrefix}{nowStr}`\n\n{content}";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_messages[0].Content = $"{timePrefix}{nowStr}`\n\n{content}";
|
||||
}
|
||||
|
||||
// Persist system message update
|
||||
using var cmd = _conn.CreateCommand();
|
||||
cmd.CommandText = "UPDATE messages SET content = $c WHERE id = (SELECT MIN(id) FROM messages)";
|
||||
cmd.Parameters.AddWithValue("$c", _messages[0].Content);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
public async Task<object> ChatAsync(string message, double? temperature = null, CancellationToken ct = default)
|
||||
{
|
||||
if (!SafeMode)
|
||||
{
|
||||
// command handling
|
||||
if (message.StartsWith("!reset", StringComparison.OrdinalIgnoreCase)) { Reset(); return "true"; }
|
||||
if (message.StartsWith("!instruct", StringComparison.OrdinalIgnoreCase)) { Instruct(message.Length >= 10 ? message[10..] : ""); return "true"; }
|
||||
if (message.StartsWith("!undo", StringComparison.OrdinalIgnoreCase)) { return Undo().ToString().ToLowerInvariant(); }
|
||||
if (message.StartsWith("!get", StringComparison.OrdinalIgnoreCase)) { var key = message[4..].Trim(); return GetRawSettingOr("not found", key); }
|
||||
if (message.StartsWith("!set", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var rest = message.Length >= 5 ? message[5..] : "";
|
||||
var idx = rest.IndexOf(' ');
|
||||
if (idx > 0)
|
||||
{
|
||||
var nm = rest[..idx].Trim();
|
||||
var val = rest[(idx + 1)..].Trim();
|
||||
SetSetting(nm, TryJson(val));
|
||||
return "true";
|
||||
}
|
||||
}
|
||||
if (message.StartsWith("!query", StringComparison.OrdinalIgnoreCase)) { return Query(message.Length >= 6 ? message[6..] : ""); }
|
||||
if (message.StartsWith("!edit", StringComparison.OrdinalIgnoreCase)) { Edit(message.Length >= 5 ? message[5..] : ""); return SystemMessage; }
|
||||
if (message.StartsWith("!append", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var rest = message.Length >= 7 ? message[7..] : "";
|
||||
var firstColon = rest.IndexOf(':');
|
||||
if (firstColon >= 0 && rest.IndexOf(':', firstColon + 1) < 0)
|
||||
{
|
||||
var user = rest[..firstColon];
|
||||
var assistant = rest[(firstColon + 1)..];
|
||||
Append(user, assistant);
|
||||
return "true";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PatchTime();
|
||||
|
||||
_messages.Add(new ChatMessage { Role = "user", Content = message });
|
||||
SaveMessage("user", message);
|
||||
|
||||
var payload = new ChatRequest
|
||||
{
|
||||
Model = Model,
|
||||
Messages = _messages,
|
||||
Temperature = temperature ?? Temperature
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(payload, JsonOpts);
|
||||
var url = $"https://{BaseUrl}{Rel}/v1/chat/completions";
|
||||
Console.WriteLine(url);
|
||||
|
||||
int attempt = 0;
|
||||
string assistant = "ERROR";
|
||||
double backoff = Backoff;
|
||||
|
||||
while (attempt < MaxRetries)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var req = new HttpRequestMessage(HttpMethod.Post, url)
|
||||
{
|
||||
Content = new StringContent(json, Encoding.UTF8, "application/json")
|
||||
};
|
||||
foreach (var kv in Headers) req.Headers.TryAddWithoutValidation(kv.Key, kv.Value);
|
||||
|
||||
using var resp = await Http.SendAsync(req, ct);
|
||||
var text = await resp.Content.ReadAsStringAsync(ct);
|
||||
|
||||
var parsed = JsonSerializer.Deserialize<ChatResponse>(text, JsonOpts);
|
||||
assistant = parsed?.Choices?[0]?.Message?.Content ?? "ERROR";
|
||||
|
||||
SaveSettings();
|
||||
|
||||
_messages.Add(new ChatMessage { Role = "assistant", Content = assistant });
|
||||
SaveMessage("assistant", assistant);
|
||||
|
||||
return TryJson(assistant);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
attempt++;
|
||||
Console.WriteLine($"[WARN] Chat attempt {attempt} failed: {ex.Message}");
|
||||
if (attempt < MaxRetries)
|
||||
{
|
||||
await Task.Delay(TimeSpan.FromSeconds(backoff), ct);
|
||||
backoff *= 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
_messages.Add(new ChatMessage { Role = "assistant", Content = "ERROR" });
|
||||
SaveMessage("assistant", "ERROR");
|
||||
Console.WriteLine($"[CRITICAL] Failed after {MaxRetries} attempts.");
|
||||
return "ERROR";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "ERROR";
|
||||
}
|
||||
|
||||
public object Chat(string message, double? temperature = null)
|
||||
=> ChatAsync(message, temperature).GetAwaiter().GetResult();
|
||||
|
||||
// JSON helper that mirrors Python _try_json (also strips ``` fences)
|
||||
private static object TryJson(string response)
|
||||
{
|
||||
var fixedStr = (response ?? "").Trim();
|
||||
if (fixedStr.StartsWith("```", StringComparison.Ordinal) && fixedStr.EndsWith("```", StringComparison.Ordinal))
|
||||
{
|
||||
var lines = fixedStr.Split('\n');
|
||||
if (lines.Length >= 2)
|
||||
{
|
||||
fixedStr = string.Join("\n", lines, 1, lines.Length - 2).Trim();
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var node = JsonNode.Parse(fixedStr);
|
||||
return node ?? response;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
private object GetRawSettingOr(string fallback, string key)
|
||||
{
|
||||
using var cmd = _conn.CreateCommand();
|
||||
cmd.CommandText = "SELECT json_value FROM settings WHERE name = $n LIMIT 1";
|
||||
cmd.Parameters.AddWithValue("$n", key);
|
||||
var obj = cmd.ExecuteScalar();
|
||||
return obj is string s ? s : fallback;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Console.WriteLine("[INFO] Reset conversation history (keeping system message).");
|
||||
ChatMessage sys = _messages.Count > 0 ? _messages[0] : new ChatMessage { Role = "system", Content = "" };
|
||||
_messages.Clear();
|
||||
_messages.Add(sys);
|
||||
|
||||
using var cmd = _conn.CreateCommand();
|
||||
cmd.CommandText = "DELETE FROM messages WHERE id != (SELECT MIN(id) FROM messages)";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
public void Instruct(string instructions)
|
||||
{
|
||||
SystemMessage = instructions;
|
||||
Console.WriteLine($"[INFO] System instructions updated.");
|
||||
SaveSettings();
|
||||
|
||||
if (_messages.Count > 0)
|
||||
{
|
||||
_messages[0].Content = instructions;
|
||||
using var cmd = _conn.CreateCommand();
|
||||
cmd.CommandText = "UPDATE messages SET content = $c WHERE id = (SELECT MIN(id) FROM messages)";
|
||||
cmd.Parameters.AddWithValue("$c", instructions);
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
else
|
||||
{
|
||||
_messages.Add(new ChatMessage { Role = "system", Content = instructions });
|
||||
SaveMessage("system", instructions);
|
||||
}
|
||||
Reset();
|
||||
}
|
||||
|
||||
public object Query(string query)
|
||||
{
|
||||
var response = Chat(query);
|
||||
Undo();
|
||||
return response;
|
||||
}
|
||||
|
||||
private bool DeleteLastMessage()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_messages.Count == 0) return false;
|
||||
_messages.RemoveAt(_messages.Count - 1);
|
||||
|
||||
using var cmd = _conn.CreateCommand();
|
||||
cmd.CommandText = "DELETE FROM messages WHERE id = (SELECT MAX(id) FROM messages)";
|
||||
cmd.ExecuteNonQuery();
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine($"[ERROR] Undo error: {e.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Edit(string change)
|
||||
{
|
||||
var prompt = $"Apply this update: `{change}` to `{SystemMessage}` and return the result";
|
||||
var result = Query(prompt);
|
||||
var newSys = result is JsonNode jn ? jn.ToJsonString() : result?.ToString() ?? SystemMessage;
|
||||
Instruct(newSys);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Undo()
|
||||
{
|
||||
// delete assistant + user
|
||||
return DeleteLastMessage() && DeleteLastMessage();
|
||||
}
|
||||
|
||||
public void Repl()
|
||||
{
|
||||
Console.WriteLine("[INFO] REPL started. Ctrl+C to quit.");
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
Console.Write("You: ");
|
||||
var line = Console.ReadLine();
|
||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||
|
||||
var response = Chat(line);
|
||||
var typeName = response switch
|
||||
{
|
||||
JsonNode => "JsonNode",
|
||||
null => "null",
|
||||
_ => response.GetType().Name
|
||||
};
|
||||
Console.WriteLine($"{Name}: {typeName} {FormatForPrint(response)}");
|
||||
}
|
||||
catch (OperationCanceledException) { break; }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[CRITICAL] Unexpected error: {ex.Message}");
|
||||
Console.WriteLine("AI: ERROR");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string FormatForPrint(object? obj)
|
||||
{
|
||||
if (obj is JsonNode jn) return jn.ToJsonString(new JsonSerializerOptions { WriteIndented = false });
|
||||
return obj?.ToString() ?? "null";
|
||||
}
|
||||
}
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
// Equivalent to: if __name__ == '__main__': ai = AIS(model="wizard", safe_mode=False); ai.repl()
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
var ai = new AIS(model: "wizard", safeMode: false);
|
||||
ai.Repl();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user