diff --git a/ModuleArduinoCompile/MainWindow.axaml b/ModuleArduinoCompile/MainWindow.axaml
new file mode 100644
index 0000000..6028a78
--- /dev/null
+++ b/ModuleArduinoCompile/MainWindow.axaml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ModuleArduinoCompile/MainWindow.axaml.cs b/ModuleArduinoCompile/MainWindow.axaml.cs
new file mode 100644
index 0000000..7bab0bb
--- /dev/null
+++ b/ModuleArduinoCompile/MainWindow.axaml.cs
@@ -0,0 +1,21 @@
+using Avalonia.Controls;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using System.Reflection;
+
+namespace ModuleWeather;
+
+public partial class WeatherView : UserControl
+{
+ public WeatherView() => InitializeComponent();
+
+ private void Update(object? sender, RoutedEventArgs e)
+ {
+
+
+ }
+ private void OnPortChanged(object sender, SelectionChangedEventArgs e)
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/ModuleArduinoCompile/Module.cs b/ModuleArduinoCompile/Module.cs
new file mode 100644
index 0000000..3bc47e1
--- /dev/null
+++ b/ModuleArduinoCompile/Module.cs
@@ -0,0 +1,213 @@
+using System;
+using System.Collections.Generic;
+using System.IO.Ports;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using System.Diagnostics;
+using System.Reflection;
+using System.Threading;
+using VisionAsist.SDK;
+
+namespace ModuleArduino;
+
+public class ArduinoModule : IModule
+{
+ public string Name => "ArduinoModule";
+ public string Description => "Модуль для работы с COM-портами и прямой прошивки кода в Arduino";
+
+ public void Settings(object[] args) { }
+
+ public List GetTools()
+ {
+ return new List
+ {
+ new ToolDefinition
+ {
+ Name = "list_com_ports",
+ Description = "Получает список всех доступных COM-портов в системе",
+ ParametersSchema = "{\"type\": \"object\", \"properties\": {}}"
+ },
+ new ToolDefinition
+ {
+ Name = "send_serial_data",
+ Description = "Отправляет строку в указанный COM-порт",
+ ParametersSchema = JsonSerializer.Serialize(new
+ {
+ type = "object",
+ properties = new
+ {
+ port = new { type = "string", description = "Имя порта (например, COM3)" },
+ baudRate = new { type = "integer", description = "Скорость передачи (по умолчанию 9600)", @default = 9600 },
+ data = new { type = "string", description = "Данные для отправки" }
+ },
+ required = new[] { "port", "data" }
+ })
+ },
+ new ToolDefinition
+ {
+ Name = "read_serial_data",
+ Description = "Читает любые доступные данные из указанного COM-порта",
+ ParametersSchema = JsonSerializer.Serialize(new
+ {
+ type = "object",
+ properties = new
+ {
+ port = new { type = "string", description = "Имя порта (например, COM3)" },
+ baudRate = new { type = "integer", description = "Скорость передачи (по умолчанию 9600)", @default = 9600 },
+ timeoutMs = new { type = "integer", description = "Максимальное время ожидания данных в миллисекундах (по умолчанию 3000)", @default = 3000 }
+ },
+ required = new[] { "port" }
+ })
+ },
+ new ToolDefinition
+ {
+ Name = "compile_upload_code",
+ Description = "Принимает исходный код C++ (Arduino), компилирует его и загружает на плату",
+ ParametersSchema = JsonSerializer.Serialize(new
+ {
+ type = "object",
+ properties = new
+ {
+ code = new { type = "string", description = "Полный текст скетча .ino (включая setup и loop)" },
+ port = new { type = "string", description = "Имя порта (например, COM3)" },
+ boardType = new { type = "string", description = "Тип платы (например, arduino:avr:uno)" }
+ },
+ required = new[] { "code", "port", "boardType" }
+ })
+ }
+ };
+ }
+
+ public string Execute(string toolName, string argumentsJson)
+ {
+ try
+ {
+ var doc = JsonDocument.Parse(argumentsJson);
+ var root = doc.RootElement;
+
+ switch (toolName)
+ {
+ case "list_com_ports":
+ var ports = SerialPort.GetPortNames();
+ return ports.Length > 0 ? string.Join(", ", ports) : "Порты не найдены";
+
+ case "send_serial_data":
+ string portName = root.GetProperty("port").GetString()!;
+ string data = root.GetProperty("data").GetString()!;
+ int baud = root.TryGetProperty("baudRate", out var b) ? b.GetInt32() : 9600;
+
+ using (var serial = new SerialPort(portName, baud))
+ {
+ serial.DtrEnable = true; // Важно для многих плат Arduino
+ serial.RtsEnable = true;
+ serial.Open();
+ serial.WriteLine(data);
+ Thread.Sleep(100); // Даем время на отправку
+ serial.Close();
+ }
+ return $"Отправлено в {portName}: {data}";
+
+ case "read_serial_data":
+ string rPortName = root.GetProperty("port").GetString()!;
+ int rBaud = root.TryGetProperty("baudRate", out var rb) ? rb.GetInt32() : 9600;
+ int timeout = root.TryGetProperty("timeoutMs", out var rt) ? rt.GetInt32() : 3000;
+
+ using (var serial = new SerialPort(rPortName, rBaud))
+ {
+ serial.DtrEnable = true; // Без этого Arduino может не слать данные
+ serial.RtsEnable = true;
+ serial.Open();
+
+ // После открытия порта Arduino может перезагрузиться,
+ // дадим ей немного времени прийти в себя
+ Thread.Sleep(500);
+
+ int waited = 500;
+ while (serial.BytesToRead == 0 && waited < timeout)
+ {
+ Thread.Sleep(100);
+ waited += 100;
+ }
+
+ if (serial.BytesToRead > 0)
+ {
+ string received = serial.ReadExisting();
+ serial.Close();
+ return $"Получено из {rPortName}: {received}";
+ }
+
+ serial.Close();
+ return $"Данные из {rPortName} не поступили за {timeout}мс.";
+ }
+
+ case "compile_upload_code":
+ string code = root.GetProperty("code").GetString()!;
+ string p = root.GetProperty("port").GetString()!;
+ string board = root.GetProperty("boardType").GetString()!;
+ return HandleCompileAndUpload(code, p, board);
+
+ default:
+ return "Инструмент не найден";
+ }
+ }
+ catch (Exception ex) { return $"Ошибка: {ex.Message}"; }
+ }
+
+ private string HandleCompileAndUpload(string code, string port, string board)
+ {
+ string tempDir = Path.Combine(Path.GetTempPath(), "VisionAsist_Arduino_" + Guid.NewGuid().ToString("N"));
+ string sketchName = "Sketch";
+ string sketchDir = Path.Combine(tempDir, sketchName);
+ string sketchPath = Path.Combine(sketchDir, sketchName + ".ino");
+
+ string modulePath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
+ string arduinoCliPath = Path.Combine(modulePath, "arduino-cli.exe");
+ if (!File.Exists(arduinoCliPath)) arduinoCliPath = "arduino-cli";
+
+ try
+ {
+ Directory.CreateDirectory(sketchDir);
+ File.WriteAllText(sketchPath, code);
+
+ var compileResult = RunCommand(arduinoCliPath, $"compile --fqbn {board} \"{sketchDir}\"");
+ if (compileResult.ExitCode != 0) return $"Ошибка компиляции:\n{compileResult.Error}";
+
+ var uploadResult = RunCommand(arduinoCliPath, $"upload -p {port} --fqbn {board} \"{sketchDir}\"");
+ if (uploadResult.ExitCode != 0) return $"Ошибка загрузки:\n{uploadResult.Error}";
+
+ return "Код успешно скомпилирован и загружен в Arduino!";
+ }
+ catch (Exception ex) { return $"Критическая ошибка: {ex.Message}"; }
+ finally
+ {
+ try { if (Directory.Exists(tempDir)) Directory.Delete(tempDir, true); } catch { }
+ }
+ }
+
+ private (int ExitCode, string Output, string Error) RunCommand(string cmd, string args)
+ {
+ try
+ {
+ var process = Process.Start(new ProcessStartInfo
+ {
+ FileName = cmd,
+ Arguments = args,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ });
+
+ string output = process?.StandardOutput.ReadToEnd() ?? "";
+ string error = process?.StandardError.ReadToEnd() ?? "";
+ process?.WaitForExit();
+
+ return (process?.ExitCode ?? -1, output, error);
+ }
+ catch (System.ComponentModel.Win32Exception)
+ {
+ return (-1, "", $"Ошибка: Исполняемый файл '{cmd}' не найден в папке модуля или в системе.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/ModuleArduinoCompile/ModuleArduinoCompile.csproj b/ModuleArduinoCompile/ModuleArduinoCompile.csproj
new file mode 100644
index 0000000..3c72129
--- /dev/null
+++ b/ModuleArduinoCompile/ModuleArduinoCompile.csproj
@@ -0,0 +1,38 @@
+
+
+
+ net10.0
+ enable
+ enable
+ ArduinoModule
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MainWindow.axaml
+ Code
+
+
+
+
diff --git a/ModuleWeather/Module.cs b/ModuleWeather/Module.cs
index 5142a9f..adc9584 100644
--- a/ModuleWeather/Module.cs
+++ b/ModuleWeather/Module.cs
@@ -1,20 +1,14 @@
-using Avalonia.Controls;
+using Avalonia.Controls;
+using System.Collections.Generic;
+using System.Text.Json;
using VisionAsist.SDK;
+
namespace ModuleWeather;
public class WeatherModule : IModule
{
-
- public static string port;
- public WeatherModule()
- {
-
-
- }
- public string Name => "Модуль компиляции ардуино";
-
- public string[] GetCommands() => new[] { "погода", "поверни на *", "верни *" };
-
+ public string Name => "WeatherModule";
+ public string Description => "Модуль для получения данных о погоде";
public void Settings(object[] args)
{
// args[0] — это родительское окно из Ядра
@@ -33,21 +27,76 @@ public class WeatherModule : IModule
else win.Show();
}
-
- public object Execute(string command)
+ public List GetTools()
{
-
-
-
-
-
- switch (command)
+ return new List
{
- case "погода":
-
- return "Погода хорошая!";
- default:
- return "Команда не найдена";
+ new ToolDefinition
+ {
+ Name = "get_weather",
+ Description = "Получает текущую погоду для указанного города",
+ ParametersSchema = JsonSerializer.Serialize(new
+ {
+ type = "object",
+ properties = new
+ {
+ city = new { type = "string", description = "Название города на русском языке" }
+ },
+ required = new[] { "city" }
+ })
+ },
+ new ToolDefinition
+ {
+ Name = "get_qnh",
+ Description = "Получает текущее давление над уровнем моря для указанного города",
+ ParametersSchema = JsonSerializer.Serialize(new
+ {
+ type = "object",
+ properties = new
+ {
+ city = new { type = "string", description = "Название города на русском языке" }
+ },
+ required = new[] { "city" }
+ })
+ }
+ };
+ }
+
+ public string Execute(string toolName, string argumentsJson)
+ {
+ if (toolName == "get_weather")
+ {
+ try
+ {
+ var args = JsonDocument.Parse(argumentsJson);
+ if (args.RootElement.TryGetProperty("city", out var cityElement))
+ {
+ string city = cityElement.GetString() ?? "неизвестном городе";
+ return $"Погода в {city} сейчас отличная, солнечно, +25 градусов!";
+ }
+ }
+ catch
+ {
+ return "Ошибка при обработке аргументов погоды";
+ }
}
+ else if (toolName == "get_qnh")
+ {
+ try
+ {
+ var args = JsonDocument.Parse(argumentsJson);
+ if (args.RootElement.TryGetProperty("city", out var cityElement))
+ {
+ string city = cityElement.GetString() ?? "неизвестном городе";
+ return $"Давление над уровнем моря в {city} сейчас 1020 мм рт. ст!";
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ throw;
+ }
+ }
+ return "Инструмент не найден";
}
}
\ No newline at end of file
diff --git a/ModuleWeather/ModuleWeather.csproj b/ModuleWeather/ModuleWeather.csproj
index 7c3c75f..5b5252c 100644
--- a/ModuleWeather/ModuleWeather.csproj
+++ b/ModuleWeather/ModuleWeather.csproj
@@ -5,9 +5,9 @@
enable
enable
- Module
+ ModuleWeather
true
- linux-x64
+
@@ -39,7 +39,6 @@
-
\ No newline at end of file
diff --git a/VisionAsist.SDK/IModule.cs b/VisionAsist.SDK/IModule.cs
index 5a61986..9501c57 100644
--- a/VisionAsist.SDK/IModule.cs
+++ b/VisionAsist.SDK/IModule.cs
@@ -1,9 +1,30 @@
-namespace VisionAsist.SDK;
+using System.Collections.Generic;
+
+namespace VisionAsist.SDK;
public interface IModule
{
- string Name { get; }
- string[] GetCommands();
+ string Name { get; }
+ string Description { get; }
void Settings(object[] args);
- object Execute(string command);
+ // Возвращает список инструментов.
+ // Каждый инструмент описывает себя через JSON Schema.
+ List GetTools();
+
+ // Выполняет инструмент.
+ // argumentsJson — это JSON объект с параметрами, который сгенерирeовал ИИ.
+ string Execute(string toolName, string argumentsJson);
+}
+
+public class ToolDefinition
+{
+ // Имя функции (например, "get_weather")
+ public string Name { get; set; } = string.Empty;
+
+ // Описание для ИИ (что делает эта функция)
+ public string Description { get; set; } = string.Empty;
+
+ // Схема параметров в формате JSON Schema.
+ // Именно это поле мы будем скармливать нейросети.
+ public string ParametersSchema { get; set; } = "{\"type\": \"object\", \"properties\": {}}";
}
\ No newline at end of file
diff --git a/VisionAsist.sln b/VisionAsist.sln
index 7ecdea2..299bccf 100644
--- a/VisionAsist.sln
+++ b/VisionAsist.sln
@@ -6,6 +6,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionAsist.SDK", "VisionAs
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModuleWeather", "ModuleWeather\ModuleWeather.csproj", "{5103146A-34FD-4431-856E-AB78EF523C03}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModuleArduinoCompile", "ModuleArduinoCompile\ModuleArduinoCompile.csproj", "{167D1FD2-01E8-4AC5-96FA-548BEAE07822}"
+ ProjectSection(ProjectDependencies) = postProject
+ {9E547157-DA01-40FB-9DB1-67A1C310E3F1} = {9E547157-DA01-40FB-9DB1-67A1C310E3F1}
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -24,5 +29,9 @@ Global
{5103146A-34FD-4431-856E-AB78EF523C03}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5103146A-34FD-4431-856E-AB78EF523C03}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5103146A-34FD-4431-856E-AB78EF523C03}.Release|Any CPU.Build.0 = Release|Any CPU
+ {167D1FD2-01E8-4AC5-96FA-548BEAE07822}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {167D1FD2-01E8-4AC5-96FA-548BEAE07822}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {167D1FD2-01E8-4AC5-96FA-548BEAE07822}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {167D1FD2-01E8-4AC5-96FA-548BEAE07822}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/VisionAsist/Models/Core.cs b/VisionAsist/Models/Core.cs
index 35a4edc..04e2514 100644
--- a/VisionAsist/Models/Core.cs
+++ b/VisionAsist/Models/Core.cs
@@ -2,56 +2,110 @@ using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
-using VisionAsist.SDK;
using System.Linq;
+using System.Text.Json;
+using VisionAsist.SDK;
namespace VisionAsist.Models;
public class Core
{
- public class Modules
+ public class LoadedModule
{
- public string Name { get; set; }
- public IModule Module { get; set; }
- public string[] commands { get; set; }
+ public string Name { get; set; } = string.Empty;
+ public IModule Module { get; set; } = null!;
+ public List Tools { get; set; } = new();
}
- public static List modulelist = new();
- static string Plugin = Path.Combine(AppContext.BaseDirectory, "Modules");
+ public static List ModuleList = new();
+ static readonly string PluginPath = Path.Combine(AppContext.BaseDirectory, "Modules");
static Core()
{
Console.OutputEncoding = System.Text.Encoding.UTF8;
Console.InputEncoding = System.Text.Encoding.UTF8;
- if (!Directory.Exists(Plugin))
- {
- Directory.CreateDirectory(Plugin);
- }
+ if (!Directory.Exists(PluginPath)) Directory.CreateDirectory(PluginPath);
- string[] folderNames = new DirectoryInfo(Plugin)
- .GetDirectories()
- .Select(d => d.Name)
- .ToArray();
-
- foreach (string folderName in folderNames)
+ AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
+ LoadModules();
+ }
+
+ private static Assembly? ResolveAssembly(object? sender, ResolveEventArgs args)
+ {
+ string assemblyName = new AssemblyName(args.Name).Name + ".dll";
+ foreach (var dir in Directory.GetDirectories(PluginPath))
{
- string mpn = Path.Combine(Plugin, folderName, "Module.dll");
- if (File.Exists(mpn))
+ string assemblyPath = Path.Combine(dir, assemblyName);
+ if (File.Exists(assemblyPath)) return Assembly.LoadFrom(assemblyPath);
+ }
+ return null;
+ }
+
+ private static void LoadModules()
+ {
+ if (!Directory.Exists(PluginPath)) return;
+
+ foreach (var dir in Directory.GetDirectories(PluginPath))
+ {
+ string folderName = Path.GetFileName(dir);
+ // DLL называется так же, как и папка
+ string dllPath = Path.Combine(dir, folderName + ".dll");
+
+ if (File.Exists(dllPath))
{
- Assembly assembly = Assembly.LoadFrom(mpn);
- var type = assembly.GetTypes().FirstOrDefault(t =>
- typeof(IModule).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
- if (type != null)
+ try
{
- var module = (IModule)Activator.CreateInstance(type)!;
- modulelist.Add(new Modules { Name = module.Name, Module = module, commands = module.GetCommands() });
- foreach (var cmd in module.GetCommands())
+ Assembly assembly = Assembly.LoadFrom(dllPath);
+ var type = assembly.GetTypes().FirstOrDefault(t =>
+ typeof(IModule).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
+
+ if (type != null)
{
- Console.WriteLine($"- {cmd}");
+ var module = (IModule)Activator.CreateInstance(type)!;
+ if (!ModuleList.Any(m => m.Name == module.Name))
+ {
+ ModuleList.Add(new LoadedModule
+ {
+ Name = module.Name,
+ Module = module,
+ Tools = module.GetTools()
+ });
+ Console.WriteLine($"[CORE]: Загружен модуль '{module.Name}' из {dllPath}");
+ }
}
}
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[CORE ERROR]: Не удалось загрузить {dllPath}: {ex.Message}");
+ }
}
}
}
+
+ public static string GetToolsManifestJson()
+ {
+ var allTools = ModuleList.SelectMany(m => m.Tools.Select(t => new
+ {
+ type = "function",
+ function = new
+ {
+ name = t.Name,
+ description = t.Description,
+ parameters = JsonDocument.Parse(t.ParametersSchema).RootElement
+ }
+ })).ToList();
+
+ return JsonSerializer.Serialize(allTools, new JsonSerializerOptions { WriteIndented = true });
+ }
+
+ public static string CallTool(string toolName, string argsJson)
+ {
+ foreach (var module in ModuleList)
+ {
+ var tool = module.Tools.FirstOrDefault(t => t.Name == toolName);
+ if (tool != null) return module.Module.Execute(toolName, argsJson);
+ }
+ return "Ошибка: Инструмент не найден";
+ }
}
\ No newline at end of file
diff --git a/VisionAsist/Models/OllamaService.cs b/VisionAsist/Models/OllamaService.cs
new file mode 100644
index 0000000..b96534f
--- /dev/null
+++ b/VisionAsist/Models/OllamaService.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using System.IO;
+
+namespace VisionAsist.Models;
+
+public class OllamaMessage
+{
+ public string role { get; set; } = string.Empty;
+ public string content { get; set; } = string.Empty;
+}
+
+public static class OllamaService
+{
+ private static readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromMinutes(5) };
+ private const string BaseUrl = "http://localhost:11434/api";
+
+ public static async IAsyncEnumerable SendChatStreamAsync(string model, List messages, string? toolsJson = null)
+ {
+ var requestBody = new Dictionary
+ {
+ { "model", model },
+ { "messages", messages },
+ { "stream", true } // ВКЛЮЧАЕМ СТРИМИНГ
+ };
+
+ if (!string.IsNullOrEmpty(toolsJson))
+ {
+ requestBody.Add("tools", JsonDocument.Parse(toolsJson).RootElement);
+ }
+
+ var json = JsonSerializer.Serialize(requestBody);
+ var content = new StringContent(json, Encoding.UTF8, "application/json");
+
+ using var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/chat") { Content = content };
+ using var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
+ response.EnsureSuccessStatusCode();
+
+ using var stream = await response.Content.ReadAsStreamAsync();
+ using var reader = new StreamReader(stream);
+
+ while (!reader.EndOfStream)
+ {
+ var line = await reader.ReadLineAsync();
+ if (!string.IsNullOrWhiteSpace(line))
+ {
+ yield return line;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/VisionAsist/Models/Selector.cs b/VisionAsist/Models/Selector.cs
index 032351c..35afde2 100644
--- a/VisionAsist/Models/Selector.cs
+++ b/VisionAsist/Models/Selector.cs
@@ -1,38 +1,116 @@
-using System;
+using System;
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Threading.Tasks;
+using System.Linq;
namespace VisionAsist.Models;
public class Selector
{
- public static void selector(string text)
+ private static List _chatHistory = new();
+ public static string CurrentModel = "qwen3.5:4b";
+
+ public static event Action? OnLogUpdate;
+ private static void UpdateUI(string text) => OnLogUpdate?.Invoke(text);
+
+ public static async Task selector(string text)
{
- if (text.Contains("вижен"))
+ try
{
- string novision = text.Replace("вижен", "").Trim();
- foreach (var module in Core.modulelist)
+ UpdateUI($"\n[Вы]: {text}\n");
+ _chatHistory.Add(new OllamaMessage { role = "user", content = text });
+
+ while (true)
{
- foreach (var command in module.commands)
+ string toolsJson = Core.GetToolsManifestJson();
+ string fullContent = "";
+ string toolCallsRaw = "";
+ bool isThinking = false;
+ bool hasStartedContent = false;
+
+ UpdateUI("\n[ИИ]: ");
+
+ await foreach (var line in OllamaService.SendChatStreamAsync(CurrentModel, _chatHistory, toolsJson))
{
- if (command.Contains("*"))
+ using var doc = JsonDocument.Parse(line);
+ var root = doc.RootElement;
+ if (!root.TryGetProperty("message", out var message)) continue;
+
+ // 1. ОБРАБОТКА 'thinking' (если есть в этом чанке)
+ if (message.TryGetProperty("thinking", out var thinkingEl))
{
- string wopo = command.Replace("*", "").Trim();
- if (novision.Contains(wopo))
+ string thinkPart = thinkingEl.GetString() ?? "";
+ if (!string.IsNullOrEmpty(thinkPart))
{
-
- Console.WriteLine(module.Module.Execute(command));
- break;
+ if (!isThinking)
+ {
+ UpdateUI("\n[Мысли]: ");
+ isThinking = true;
+ }
+ UpdateUI(thinkPart);
}
}
- else
+
+ // 2. ОБРАБОТКА 'content' (если есть в этом чанке)
+ if (message.TryGetProperty("content", out var contentEl))
{
- if (command == novision)
+ string part = contentEl.GetString() ?? "";
+ if (!string.IsNullOrEmpty(part))
{
- Console.WriteLine(module.Module.Execute(novision));
- break;
+ // Если мы только что "думали", переключаемся на ответ
+ if (isThinking)
+ {
+ isThinking = false;
+ hasStartedContent = true;
+ UpdateUI("\n[Ответ]: ");
+ }
+ else if (!hasStartedContent && !string.IsNullOrWhiteSpace(part))
+ {
+ UpdateUI("\n[Ответ]: ");
+ hasStartedContent = true;
+ }
+
+ fullContent += part;
+ UpdateUI(part);
}
}
+
+ // 3. ОБРАБОТКА 'tool_calls' (если есть в этом чанке)
+ if (message.TryGetProperty("tool_calls", out var toolsEl))
+ {
+ toolCallsRaw = toolsEl.GetRawText();
+ }
}
+
+ _chatHistory.Add(new OllamaMessage { role = "assistant", content = fullContent });
+
+ // 4. ВЫПОЛНЕНИЕ ИНСТРУМЕНТОВ (после завершения стрима)
+ if (!string.IsNullOrEmpty(toolCallsRaw))
+ {
+ using var toolDoc = JsonDocument.Parse(toolCallsRaw);
+ foreach (var call in toolDoc.RootElement.EnumerateArray())
+ {
+ string toolName = call.GetProperty("function").GetProperty("name").GetString() ?? "";
+ string argsJson = call.GetProperty("function").GetProperty("arguments").GetRawText();
+
+ UpdateUI($"\n\n[ДЕЙСТВИЕ]: {toolName}\n[АРГУМЕНТЫ]: {argsJson}");
+ string result = Core.CallTool(toolName, argsJson);
+ UpdateUI($"\n[РЕЗУЛЬТАТ]: {result}\n");
+
+ _chatHistory.Add(new OllamaMessage { role = "tool", content = result });
+ }
+ continue; // Снова к ИИ с результатами инструментов
+ }
+ return fullContent;
}
}
+ catch (Exception ex)
+ {
+ UpdateUI($"\n[ОШИБКА]: {ex.Message}");
+ return ex.Message;
+ }
}
+
+ public static void ClearHistory() => _chatHistory.Clear();
}
\ No newline at end of file
diff --git a/VisionAsist/ViewModels/MainWindowViewModel.cs b/VisionAsist/ViewModels/MainWindowViewModel.cs
index a81e79d..f336f70 100644
--- a/VisionAsist/ViewModels/MainWindowViewModel.cs
+++ b/VisionAsist/ViewModels/MainWindowViewModel.cs
@@ -1,20 +1,30 @@
using System;
+using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using VisionAsist.Models;
using VisionAsist.Views;
+using Avalonia.Threading;
namespace VisionAsist.ViewModels;
public partial class MainWindowViewModel : ViewModelBase
{
+ public MainWindowViewModel()
+ {
+ Selector.OnLogUpdate += text =>
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ CommandLog += text;
+ });
+ };
+ }
+
[RelayCommand]
private void Settingse()
{
- new Settings
- {
- DataContext = new SettingsViewModel()
- }.Show();
+ new Settings { DataContext = new SettingsViewModel() }.Show();
}
[ObservableProperty]
@@ -23,19 +33,23 @@ public partial class MainWindowViewModel : ViewModelBase
[ObservableProperty]
private string commandText = string.Empty;
+ [ObservableProperty]
+ private bool isGenerating = false;
+
[RelayCommand]
- private void Send()
+ private async Task Send()
{
- if (!string.IsNullOrWhiteSpace(CommandText))
+ if (!string.IsNullOrWhiteSpace(CommandText) && !IsGenerating)
{
- // Отображаем введенную команду в логе
- CommandLog = CommandText;
-
- // Отправляем в селектор
- Selector.selector(CommandText);
-
- // Очищаем поле ввода
+ string input = CommandText;
CommandText = string.Empty;
+ IsGenerating = true;
+
+ // Запускаем логику в фоновом потоке, чтобы UI не зависал
+ await Task.Run(async () => {
+ await Selector.selector(input);
+ IsGenerating = false;
+ });
}
}
}
\ No newline at end of file
diff --git a/VisionAsist/ViewModels/SettingsViewModel.cs b/VisionAsist/ViewModels/SettingsViewModel.cs
index 4850944..a54c75b 100644
--- a/VisionAsist/ViewModels/SettingsViewModel.cs
+++ b/VisionAsist/ViewModels/SettingsViewModel.cs
@@ -23,7 +23,7 @@ public class SettingsViewModel : ViewModelBase
public SettingsViewModel()
{
- foreach (var module in Core.modulelist)
+ foreach (var module in Core.ModuleList)
{
AddModule(module.Name);
@@ -43,7 +43,7 @@ public class SettingsViewModel : ViewModelBase
private void OpenSettings(string moduleName)
{
- var module = Core.modulelist.FirstOrDefault(m => m.Name == moduleName);
+ var module = Core.ModuleList.FirstOrDefault(m => m.Name == moduleName);
if (module != null)
{
module.Module.Settings(new object[] { this });
diff --git a/VisionAsist/Views/MainWindow.axaml b/VisionAsist/Views/MainWindow.axaml
index 7c01ce5..bdd7346 100644
--- a/VisionAsist/Views/MainWindow.axaml
+++ b/VisionAsist/Views/MainWindow.axaml
@@ -16,12 +16,14 @@
+
@@ -30,14 +32,16 @@
-
-
+
+ FontFamily="Cascadia Code, Consolas, Monospace"
+ Foreground="#DCDCDC"/>
\ No newline at end of file
diff --git a/VisionAsist/VisionAsist.csproj b/VisionAsist/VisionAsist.csproj
index 771b23f..e063030 100644
--- a/VisionAsist/VisionAsist.csproj
+++ b/VisionAsist/VisionAsist.csproj
@@ -23,6 +23,7 @@
All
+