Compare commits
6 Commits
9088810aaa
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 00d4461a92 | |||
| 14ebb04c1c | |||
| ae0994409a | |||
| ea2d84f5cc | |||
| 635dacb2ad | |||
| 27817754ce |
1
.idea/.idea.VisionAsist/.idea/.name
generated
1
.idea/.idea.VisionAsist/.idea/.name
generated
@@ -1 +0,0 @@
|
|||||||
VisionAsist
|
|
||||||
10
ModuleArduinoCompile/MainWindow.axaml
Normal file
10
ModuleArduinoCompile/MainWindow.axaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="ModuleWeather.WeatherView">
|
||||||
|
<StackPanel Margin="20" Spacing="10" Background="#2b2b2b">
|
||||||
|
<Button Content="Обновить" Click="Update"
|
||||||
|
HorizontalAlignment="Stretch" HorizontalContentAlignment="Center"/>
|
||||||
|
<TextBlock Name="StatusText" Text="Выберите порт" Foreground="White"/>
|
||||||
|
<ComboBox Name="PortComboBox" SelectionChanged="OnPortChanged"/>
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
||||||
21
ModuleArduinoCompile/MainWindow.axaml.cs
Normal file
21
ModuleArduinoCompile/MainWindow.axaml.cs
Normal file
@@ -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)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
218
ModuleArduinoCompile/Module.cs
Normal file
218
ModuleArduinoCompile/Module.cs
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
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<ToolDefinition> GetTools()
|
||||||
|
{
|
||||||
|
return new List<ToolDefinition>
|
||||||
|
{
|
||||||
|
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)!;
|
||||||
|
|
||||||
|
// Определяем имя файла в зависимости от ОС
|
||||||
|
bool isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows);
|
||||||
|
string exeName = isWindows ? "arduino-cli.exe" : "arduino-cli";
|
||||||
|
|
||||||
|
string arduinoCliPath = Path.Combine(modulePath, exeName);
|
||||||
|
if (!File.Exists(arduinoCliPath)) arduinoCliPath = exeName;
|
||||||
|
|
||||||
|
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}' не найден в папке модуля или в системе.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
ModuleArduinoCompile/ModuleArduinoCompile.csproj
Normal file
38
ModuleArduinoCompile/ModuleArduinoCompile.csproj
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AssemblyName>ArduinoModule</AssemblyName>
|
||||||
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\VisionAsist.SDK\VisionAsist.SDK.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Avalonia" Version="11.3.12" />
|
||||||
|
<PackageReference Include="Avalonia.Desktop" Version="11.3.12" />
|
||||||
|
<PackageReference Include="System.IO.Ports" Version="9.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
<Target Name="CopyModuleToCore" AfterTargets="PostBuildEvent">
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- Копируем ВСЕ DLL и PDB рекурсивно, чтобы не потерять зависимости NuGet -->
|
||||||
|
<ModuleFiles Include="$(TargetDir)**\*.dll" />
|
||||||
|
<ModuleFiles Include="$(TargetDir)**\*.pdb" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Message Text="Копирую файлы модуля и все зависимости..." Importance="high" />
|
||||||
|
|
||||||
|
<Copy SourceFiles="@(ModuleFiles)"
|
||||||
|
DestinationFolder="$(SolutionDir)VisionAsist\bin\$(Configuration)\$(TargetFramework)\Modules\ArduinoModule"
|
||||||
|
OverwriteReadOnlyFiles="true" />
|
||||||
|
</Target>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="MainWindow.axaml.cs">
|
||||||
|
<DependentUpon>MainWindow.axaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Interactivity;
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using System.IO.Ports;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace ModuleWeather;
|
namespace ModuleWeather;
|
||||||
@@ -12,13 +11,11 @@ public partial class WeatherView : UserControl
|
|||||||
|
|
||||||
private void Update(object? sender, RoutedEventArgs e)
|
private void Update(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var ports = SerialPort.GetPortNames();
|
|
||||||
PortComboBox.ItemsSource = ports; // Привязываем массив к списку
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
private void OnPortChanged(object sender, SelectionChangedEventArgs e)
|
private void OnPortChanged(object sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
WeatherModule.port = PortComboBox.SelectedItem as string;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,14 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json;
|
||||||
using VisionAsist.SDK;
|
using VisionAsist.SDK;
|
||||||
using System.IO.Ports;
|
|
||||||
namespace ModuleWeather;
|
namespace ModuleWeather;
|
||||||
|
|
||||||
public class WeatherModule : IModule
|
public class WeatherModule : IModule
|
||||||
{
|
{
|
||||||
public SerialPort myPort = new ();
|
public string Name => "WeatherModule";
|
||||||
public static string port;
|
public string Description => "Модуль для получения данных о погоде";
|
||||||
public WeatherModule()
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
public string Name => "Прогноз Погоды";
|
|
||||||
|
|
||||||
public string[] GetCommands() => new[] { "погода", "поверни на *", "верни *" };
|
|
||||||
|
|
||||||
public void Settings(object[] args)
|
public void Settings(object[] args)
|
||||||
{
|
{
|
||||||
// args[0] — это родительское окно из Ядра
|
// args[0] — это родительское окно из Ядра
|
||||||
@@ -34,34 +27,76 @@ public class WeatherModule : IModule
|
|||||||
else win.Show();
|
else win.Show();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
public List<ToolDefinition> GetTools()
|
||||||
public object Execute(string command)
|
|
||||||
{
|
{
|
||||||
|
return new List<ToolDefinition>
|
||||||
myPort.Close();
|
|
||||||
myPort = new SerialPort(port, 9600);
|
|
||||||
myPort.Open();
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
switch (command)
|
|
||||||
{
|
{
|
||||||
case "погода":
|
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" }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return "Погода хорошая!";
|
public string Execute(string toolName, string argumentsJson)
|
||||||
|
{
|
||||||
case "поверни на *":
|
if (toolName == "get_weather")
|
||||||
myPort.WriteLine("178");
|
{
|
||||||
|
try
|
||||||
return "Повернул на 180";
|
{
|
||||||
case "верни *":
|
var args = JsonDocument.Parse(argumentsJson);
|
||||||
myPort.WriteLine("0");
|
if (args.RootElement.TryGetProperty("city", out var cityElement))
|
||||||
|
{
|
||||||
return "Вернул в 0";
|
string city = cityElement.GetString() ?? "неизвестном городе";
|
||||||
default:
|
return $"Погода в {city} сейчас отличная, солнечно, +25 градусов!";
|
||||||
return "Команда не найдена";
|
}
|
||||||
|
}
|
||||||
|
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 "Инструмент не найден";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,9 +5,9 @@
|
|||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<!-- ВОТ ЭТА СТРОЧКА: она заставит компилятор создать Module.dll -->
|
<!-- ВОТ ЭТА СТРОЧКА: она заставит компилятор создать Module.dll -->
|
||||||
<AssemblyName>Module</AssemblyName>
|
<AssemblyName>ModuleWeather</AssemblyName>
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -27,19 +27,18 @@
|
|||||||
<Message Text="Копирую файлы модуля и нативные библиотеки..." Importance="high" />
|
<Message Text="Копирую файлы модуля и нативные библиотеки..." Importance="high" />
|
||||||
|
|
||||||
<Copy SourceFiles="@(ModuleFiles)"
|
<Copy SourceFiles="@(ModuleFiles)"
|
||||||
DestinationFolder="/home/egor/RiderProjects/Vision/VisionAsist/bin/Debug/net10.0/Modules/ModuleWeather/"
|
DestinationFolder="E:\Project\Visual\VisionAsist\VisionAsist\bin\Debug\net10.0\Modules\ModuleWeather"
|
||||||
OverwriteReadOnlyFiles="true" />
|
OverwriteReadOnlyFiles="true" />
|
||||||
|
|
||||||
<!-- Копируем .so файлы ПРЯМО в корень папки модуля -->
|
<!-- Копируем .so файлы ПРЯМО в корень папки модуля -->
|
||||||
<Copy SourceFiles="@(NativeLibs)"
|
<Copy SourceFiles="@(NativeLibs)"
|
||||||
DestinationFolder="/home/egor/RiderProjects/Vision/VisionAsist/bin/Debug/net10.0/Modules/ModuleWeather/"
|
DestinationFolder="E:\Project\Visual\VisionAsist\VisionAsist\bin\Debug\net10.0\Modules\ModuleWeather"
|
||||||
OverwriteReadOnlyFiles="true" />
|
OverwriteReadOnlyFiles="true" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="11.3.12" />
|
<PackageReference Include="Avalonia" Version="11.3.12" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.12" />
|
<PackageReference Include="Avalonia.Desktop" Version="11.3.12" />
|
||||||
<PackageReference Include="System.IO.Ports" Version="11.0.0-preview.2.26159.112" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,9 +1,30 @@
|
|||||||
namespace VisionAsist.SDK;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace VisionAsist.SDK;
|
||||||
|
|
||||||
public interface IModule
|
public interface IModule
|
||||||
{
|
{
|
||||||
string Name { get; }
|
string Name { get; }
|
||||||
string[] GetCommands();
|
string Description { get; }
|
||||||
void Settings(object[] args);
|
void Settings(object[] args);
|
||||||
object Execute(string command);
|
// Возвращает список инструментов.
|
||||||
|
// Каждый инструмент описывает себя через JSON Schema.
|
||||||
|
List<ToolDefinition> 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\": {}}";
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionAsist.SDK", "VisionAs
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModuleWeather", "ModuleWeather\ModuleWeather.csproj", "{5103146A-34FD-4431-856E-AB78EF523C03}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ModuleWeather", "ModuleWeather\ModuleWeather.csproj", "{5103146A-34FD-4431-856E-AB78EF523C03}"
|
||||||
EndProject
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{5103146A-34FD-4431-856E-AB78EF523C03}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInitHelpers_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3bfb329b50ab4b719e770a577377990fdba600_003F92_003F7acfd65c_003FInitHelpers_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AInitHelpers_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3bfb329b50ab4b719e770a577377990fdba600_003F92_003F7acfd65c_003FInitHelpers_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APorcupine_002Ecs_002Fl_003AC_0021_003FUsers_003Fmarin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F395b25d8175b43798becf32fa73896954c00_003Fa6_003F28640b1d_003FPorcupine_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APorcupine_002Ecs_002Fl_003AC_0021_003FUsers_003Fmarin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F395b25d8175b43798becf32fa73896954c00_003Fa6_003F28640b1d_003FPorcupine_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASerialPort_002EUnix_002Ecs_002Fl_003AC_0021_003FUsers_003Fmarin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FSourcesCache_003F955be2f0e9f11ba554df3fb7b4d1c16483286c82bb3b26ac164fc3657e4477_003FSerialPort_002EUnix_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASoundIO_002Ecs_002Fl_003AC_0021_003FUsers_003Fmarin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2ac0091ca0084789a96acdec8e3a9449d000_003F84_003Fd6e02c94_003FSoundIO_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASoundIOInStream_002Ecs_002Fl_003AC_0021_003FUsers_003Fmarin_003FAppData_003FRoaming_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2ac0091ca0084789a96acdec8e3a9449d000_003F37_003F2b136ae3_003FSoundIOInStream_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASoundIO_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2ac0091ca0084789a96acdec8e3a9449d000_003F84_003Fd6e02c94_003FSoundIO_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASoundIO_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F2ac0091ca0084789a96acdec8e3a9449d000_003F84_003Fd6e02c94_003FSoundIO_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>
|
||||||
@@ -1,79 +1,111 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using VisionAsist.SDK;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using VisionAsist.SDK;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using VisionAsist.SDK;
|
||||||
|
|
||||||
namespace VisionAsist.Models;
|
namespace VisionAsist.Models;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class Core
|
public class Core
|
||||||
{
|
{
|
||||||
|
public class LoadedModule
|
||||||
public class Modules
|
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; } = string.Empty;
|
||||||
public IModule Module { get; set; }
|
public IModule Module { get; set; } = null!;
|
||||||
public string[] commands { get; set; }
|
public List<ToolDefinition> Tools { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Modules> modulelist = new();
|
public static List<LoadedModule> ModuleList = new();
|
||||||
public static TrigerCore triger = new();
|
static readonly string PluginPath = Path.Combine(AppContext.BaseDirectory, "Modules");
|
||||||
public static string TextAsist;
|
|
||||||
static string Plugin = Path.Combine(AppContext.BaseDirectory, "Modules");
|
|
||||||
static Core()
|
static Core()
|
||||||
{
|
{
|
||||||
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
Console.OutputEncoding = System.Text.Encoding.UTF8;
|
||||||
Console.InputEncoding = System.Text.Encoding.UTF8;
|
Console.InputEncoding = System.Text.Encoding.UTF8;
|
||||||
string[] folderNames = new DirectoryInfo(Plugin)
|
|
||||||
.GetDirectories()
|
if (!Directory.Exists(PluginPath)) Directory.CreateDirectory(PluginPath);
|
||||||
.Select(d => d.Name)
|
|
||||||
.ToArray();
|
AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
|
||||||
foreach (string folderName in folderNames)
|
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");
|
string assemblyPath = Path.Combine(dir, assemblyName);
|
||||||
if (File.Exists(mpn))
|
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);
|
try
|
||||||
var type = assembly.GetTypes().FirstOrDefault(t =>
|
|
||||||
typeof(IModule).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
|
|
||||||
if (type != null)
|
|
||||||
{
|
{
|
||||||
var module = (IModule)Activator.CreateInstance(type)!;
|
Assembly assembly = Assembly.LoadFrom(dllPath);
|
||||||
modulelist.Add(new Modules{Name = folderName, Module = module, commands = module.GetCommands()});
|
var type = assembly.GetTypes().FirstOrDefault(t =>
|
||||||
foreach (var cmd in module.GetCommands())
|
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 void StartListing()
|
|
||||||
|
public static string GetToolsManifestJson()
|
||||||
{
|
{
|
||||||
// Подписываемся на событие новых слов
|
var allTools = ModuleList.SelectMany(m => m.Tools.Select(t => new
|
||||||
triger.OnRecognized += word =>
|
|
||||||
{
|
{
|
||||||
|
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 });
|
||||||
TextAsist = triger.RecognizedText;
|
|
||||||
Selector.selector(triger.RecognizedText);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
// Запускаем запись
|
|
||||||
triger.StartRecording();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static public async void StopListing ()
|
public static string CallTool(string toolName, string argsJson)
|
||||||
{
|
{
|
||||||
triger.StopRecording();
|
foreach (var module in ModuleList)
|
||||||
|
{
|
||||||
|
var tool = module.Tools.FirstOrDefault(t => t.Name == toolName);
|
||||||
|
if (tool != null) return module.Module.Execute(toolName, argsJson);
|
||||||
|
}
|
||||||
|
return "Ошибка: Инструмент не найден";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
55
VisionAsist/Models/OllamaService.cs
Normal file
55
VisionAsist/Models/OllamaService.cs
Normal file
@@ -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 static string BaseUrl => SettingsManager.Current.OllamaBaseUrl;
|
||||||
|
|
||||||
|
public static async IAsyncEnumerable<string> SendChatStreamAsync(string model, List<OllamaMessage> messages, string? toolsJson = null)
|
||||||
|
{
|
||||||
|
var requestBody = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "model", string.IsNullOrEmpty(model) ? SettingsManager.Current.OllamaModel : 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
namespace VisionAsist.Models;
|
||||||
|
|
||||||
public class Selector
|
public class Selector
|
||||||
{
|
{
|
||||||
public static void selector(string text)
|
private static List<OllamaMessage> _chatHistory = new();
|
||||||
{
|
public static string CurrentModel => SettingsManager.Current.OllamaModel;
|
||||||
if (text.Contains("вижен"))
|
|
||||||
{
|
|
||||||
string novision = text.Replace("вижен", "").Trim();
|
|
||||||
foreach (var module in Core.modulelist)
|
|
||||||
{
|
|
||||||
foreach (var command in module.commands)
|
|
||||||
{
|
|
||||||
if (command.Contains("*"))
|
|
||||||
{
|
|
||||||
string wopo = command.Replace("*", "").Trim();
|
|
||||||
if (novision.Contains(wopo))
|
|
||||||
{
|
|
||||||
|
|
||||||
Console.WriteLine(module.Module.Execute(command));
|
public static event Action<string>? OnLogUpdate;
|
||||||
break;
|
private static void UpdateUI(string text) => OnLogUpdate?.Invoke(text);
|
||||||
|
|
||||||
|
public static async Task<string> selector(string text)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
UpdateUI($"\n[Вы]: {text}\n");
|
||||||
|
_chatHistory.Add(new OllamaMessage { role = "user", content = text });
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
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 thinkPart = thinkingEl.GetString() ?? "";
|
||||||
|
if (!string.IsNullOrEmpty(thinkPart))
|
||||||
|
{
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
57
VisionAsist/Models/Settings.cs
Normal file
57
VisionAsist/Models/Settings.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace VisionAsist.Models;
|
||||||
|
|
||||||
|
public class AppSettings
|
||||||
|
{
|
||||||
|
public string OllamaBaseUrl { get; set; } = "http://localhost:11434/api";
|
||||||
|
public string OllamaModel { get; set; } = "llama3";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SettingsManager
|
||||||
|
{
|
||||||
|
private static readonly string SettingsFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "settings.json");
|
||||||
|
private static AppSettings _current = new();
|
||||||
|
|
||||||
|
public static AppSettings Current => _current;
|
||||||
|
|
||||||
|
static SettingsManager()
|
||||||
|
{
|
||||||
|
Load();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Load()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (File.Exists(SettingsFilePath))
|
||||||
|
{
|
||||||
|
var json = File.ReadAllText(SettingsFilePath);
|
||||||
|
_current = JsonSerializer.Deserialize<AppSettings>(json) ?? new AppSettings();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_current = new AppSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Save()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = JsonSerializer.Serialize(_current, new JsonSerializerOptions { WriteIndented = true });
|
||||||
|
File.WriteAllText(SettingsFilePath, json);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Error saving settings: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Collections.Concurrent;
|
|
||||||
using Vosk;
|
|
||||||
using SoundIOSharp;
|
|
||||||
using Avalonia.Threading;
|
|
||||||
|
|
||||||
namespace VisionAsist.Models;
|
|
||||||
|
|
||||||
public class TrigerCore : IDisposable
|
|
||||||
{
|
|
||||||
private SoundIO? _soundIO;
|
|
||||||
private SoundIODevice? _device;
|
|
||||||
private SoundIOInStream? _inStream;
|
|
||||||
|
|
||||||
private Thread? _eventThread;
|
|
||||||
private Thread? _processingThread;
|
|
||||||
private bool _isRecording;
|
|
||||||
|
|
||||||
private readonly Model _model;
|
|
||||||
private readonly VoskRecognizer _rec;
|
|
||||||
|
|
||||||
private readonly BlockingCollection<byte[]> _audioQueue = new(100);
|
|
||||||
|
|
||||||
public string RecognizedText { get; private set; } = "";
|
|
||||||
public event Action<string>? OnRecognized;
|
|
||||||
|
|
||||||
// Сохраняем ссылки в полях класса, чтобы GC не удалил их в Debug-режиме
|
|
||||||
private Action<int, int>? _readCallback;
|
|
||||||
private Action? _overflowCallback;
|
|
||||||
private Action? _errorCallback;
|
|
||||||
|
|
||||||
public TrigerCore()
|
|
||||||
{
|
|
||||||
string voskPath = Path.Combine(AppContext.BaseDirectory, "models", "Vosk");
|
|
||||||
if (!Directory.Exists(voskPath))
|
|
||||||
throw new DirectoryNotFoundException($"Модель Vosk не найдена: {voskPath}");
|
|
||||||
|
|
||||||
_model = new Model(voskPath);
|
|
||||||
_rec = new VoskRecognizer(_model, 16000.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StartRecording()
|
|
||||||
{
|
|
||||||
if (_isRecording) return;
|
|
||||||
|
|
||||||
_soundIO = new SoundIO();
|
|
||||||
_soundIO.Connect();
|
|
||||||
_soundIO.FlushEvents();
|
|
||||||
|
|
||||||
int deviceIndex = _soundIO.DefaultInputDeviceIndex;
|
|
||||||
if (deviceIndex < 0) throw new Exception("Микрофон не найден.");
|
|
||||||
|
|
||||||
_device = _soundIO.GetInputDevice(deviceIndex);
|
|
||||||
_inStream = _device.CreateInStream();
|
|
||||||
|
|
||||||
_inStream.Format = SoundIOFormat.S16LE;
|
|
||||||
_inStream.SampleRate = 16000;
|
|
||||||
_inStream.Layout = SoundIOChannelLayout.GetDefault(1);
|
|
||||||
|
|
||||||
// Привязываем методы к полям
|
|
||||||
_readCallback = OnDataAvailable;
|
|
||||||
_overflowCallback = () => Console.WriteLine("Audio Buffer Overflow");
|
|
||||||
_errorCallback = () => Console.WriteLine("Audio Stream Error occurred");
|
|
||||||
|
|
||||||
_inStream.ReadCallback = _readCallback;
|
|
||||||
_inStream.OverflowCallback = _overflowCallback;
|
|
||||||
_inStream.ErrorCallback = _errorCallback;
|
|
||||||
|
|
||||||
_inStream.Open();
|
|
||||||
_inStream.Start();
|
|
||||||
|
|
||||||
_isRecording = true;
|
|
||||||
|
|
||||||
// Поток обработки событий SoundIO
|
|
||||||
_eventThread = new Thread(() => {
|
|
||||||
while (_isRecording && _soundIO != null)
|
|
||||||
{
|
|
||||||
try { _soundIO.WaitEvents(); } catch { break; }
|
|
||||||
}
|
|
||||||
}) { IsBackground = true, Name = "SoundIO_Wait" };
|
|
||||||
_eventThread.Start();
|
|
||||||
|
|
||||||
// Поток обработки Vosk
|
|
||||||
_processingThread = new Thread(ProcessQueue) {
|
|
||||||
IsBackground = true,
|
|
||||||
Name = "Vosk_Worker"
|
|
||||||
};
|
|
||||||
_processingThread.Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe void OnDataAvailable(int frameCountMin, int frameCountMax)
|
|
||||||
{
|
|
||||||
if (!_isRecording || _inStream == null) return;
|
|
||||||
|
|
||||||
int frameCount = frameCountMax;
|
|
||||||
// Работаем с результатом BeginRead как с объектом SoundIOChannelAreas
|
|
||||||
var areas = _inStream.BeginRead(ref frameCount);
|
|
||||||
|
|
||||||
if (frameCount <= 0) return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var area = areas.GetArea(0);
|
|
||||||
if (area.Pointer == IntPtr.Zero) return;
|
|
||||||
|
|
||||||
int bytesNeeded = frameCount * 2; // 2 байта на семпл для S16LE
|
|
||||||
byte[] data = new byte[bytesNeeded];
|
|
||||||
|
|
||||||
fixed (byte* pDst = data)
|
|
||||||
{
|
|
||||||
short* pSrc = (short*)area.Pointer;
|
|
||||||
short* pDstShort = (short*)pDst;
|
|
||||||
int stepInShorts = area.Step / 2;
|
|
||||||
|
|
||||||
for (int i = 0; i < frameCount; i++)
|
|
||||||
{
|
|
||||||
pDstShort[i] = pSrc[i * stepInShorts];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Отправляем в очередь и мгновенно освобождаем аудио-поток
|
|
||||||
_audioQueue.TryAdd(data);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Error copying audio data: " + ex.Message);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_inStream.EndRead();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ProcessQueue()
|
|
||||||
{
|
|
||||||
// GetConsumingEnumerable будет ждать появления данных в очереди
|
|
||||||
foreach (var data in _audioQueue.GetConsumingEnumerable())
|
|
||||||
{
|
|
||||||
if (!_isRecording) break;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
bool isFinal;
|
|
||||||
lock (_rec)
|
|
||||||
{
|
|
||||||
isFinal = _rec.AcceptWaveform(data, data.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isFinal)
|
|
||||||
{
|
|
||||||
ParseAndSend(_rec.Result());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Vosk processing error: " + ex.Message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ParseAndSend(string json)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(json)) return;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using var doc = JsonDocument.Parse(json);
|
|
||||||
if (doc.RootElement.TryGetProperty("text", out var el))
|
|
||||||
{
|
|
||||||
string text = el.GetString() ?? "";
|
|
||||||
if (!string.IsNullOrWhiteSpace(text))
|
|
||||||
{
|
|
||||||
RecognizedText = text;
|
|
||||||
// Безопасный проброс в UI поток Avalonia
|
|
||||||
Dispatcher.UIThread.Post(() => OnRecognized?.Invoke(text));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch { /* JSON Parse Error */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
public void StopRecording()
|
|
||||||
{
|
|
||||||
if (!_isRecording) return;
|
|
||||||
_isRecording = false;
|
|
||||||
|
|
||||||
_audioQueue.CompleteAdding();
|
|
||||||
_soundIO?.Wakeup();
|
|
||||||
|
|
||||||
_inStream?.Dispose();
|
|
||||||
_device?.RemoveReference();
|
|
||||||
_soundIO?.Dispose();
|
|
||||||
|
|
||||||
_inStream = null;
|
|
||||||
_device = null;
|
|
||||||
_soundIO = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
StopRecording();
|
|
||||||
_rec?.Dispose();
|
|
||||||
_model?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +1,55 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using VisionAsist.Models;
|
using VisionAsist.Models;
|
||||||
using VisionAsist.Views;
|
using VisionAsist.Views;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
|
||||||
namespace VisionAsist.ViewModels;
|
namespace VisionAsist.ViewModels;
|
||||||
|
|
||||||
public partial class MainWindowViewModel : ViewModelBase
|
public partial class MainWindowViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
|
public MainWindowViewModel()
|
||||||
|
{
|
||||||
|
Selector.OnLogUpdate += text =>
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
CommandLog += text;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void Settingse()
|
private void Settingse()
|
||||||
{
|
{
|
||||||
new Settings
|
new Settings { DataContext = new SettingsViewModel() }.Show();
|
||||||
{
|
|
||||||
DataContext = new SettingsViewModel()
|
|
||||||
}.Show();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool isListening;
|
private string commandLog = string.Empty;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string recognizedtext;
|
private string commandText = string.Empty;
|
||||||
private Action<string>? _coreHandler;
|
|
||||||
|
|
||||||
partial void OnIsListeningChanged(bool value)
|
[ObservableProperty]
|
||||||
|
private bool isGenerating = false;
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task Send()
|
||||||
{
|
{
|
||||||
if (value)
|
if (!string.IsNullOrWhiteSpace(CommandText) && !IsGenerating)
|
||||||
{
|
{
|
||||||
Core.StartListing();
|
string input = CommandText;
|
||||||
|
CommandText = string.Empty;
|
||||||
|
IsGenerating = true;
|
||||||
|
|
||||||
// Сохраняем ссылку на обработчик
|
// Запускаем логику в фоновом потоке, чтобы UI не зависал
|
||||||
_coreHandler = word =>
|
await Task.Run(async () => {
|
||||||
{
|
await Selector.selector(input);
|
||||||
|
IsGenerating = false;
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
});
|
||||||
{
|
|
||||||
Recognizedtext = Core.TextAsist;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
Core.triger.OnRecognized += _coreHandler;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Core.StopListing();
|
|
||||||
|
|
||||||
// Правильная отписка
|
|
||||||
if (_coreHandler != null)
|
|
||||||
{
|
|
||||||
Core.triger.OnRecognized -= _coreHandler;
|
|
||||||
_coreHandler = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,13 +17,35 @@ public class ModuleItem
|
|||||||
|
|
||||||
public class SettingsViewModel : ViewModelBase
|
public class SettingsViewModel : ViewModelBase
|
||||||
{
|
{
|
||||||
|
public string OllamaBaseUrl
|
||||||
|
{
|
||||||
|
get => SettingsManager.Current.OllamaBaseUrl;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SettingsManager.Current.OllamaBaseUrl = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string OllamaModel
|
||||||
|
{
|
||||||
|
get => SettingsManager.Current.OllamaModel;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SettingsManager.Current.OllamaModel = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IRelayCommand SaveCommand { get; }
|
||||||
|
|
||||||
public ObservableCollection<ModuleItem> Modules { get; } = new();
|
public ObservableCollection<ModuleItem> Modules { get; } = new();
|
||||||
|
|
||||||
public SettingsViewModel()
|
public SettingsViewModel()
|
||||||
{
|
{
|
||||||
|
SaveCommand = new RelayCommand(SettingsManager.Save);
|
||||||
|
|
||||||
foreach (var module in Core.modulelist)
|
foreach (var module in Core.ModuleList)
|
||||||
{
|
{
|
||||||
|
|
||||||
AddModule(module.Name);
|
AddModule(module.Name);
|
||||||
@@ -43,7 +65,7 @@ public class SettingsViewModel : ViewModelBase
|
|||||||
private void OpenSettings(string moduleName)
|
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)
|
if (module != null)
|
||||||
{
|
{
|
||||||
module.Module.Settings(new object[] { this });
|
module.Module.Settings(new object[] { this });
|
||||||
|
|||||||
@@ -10,27 +10,38 @@
|
|||||||
Title="VisionAsist" Height="400" Width="600">
|
Title="VisionAsist" Height="400" Width="600">
|
||||||
|
|
||||||
<Design.DataContext>
|
<Design.DataContext>
|
||||||
<!-- This only sets the DataContext for the previewer in an IDE,
|
|
||||||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
|
||||||
<vm:MainWindowViewModel/>
|
<vm:MainWindowViewModel/>
|
||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
|
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<StackPanel Orientation="Horizontal" Margin="10">
|
<StackPanel Orientation="Horizontal" Margin="10">
|
||||||
<Button Content="Настройки" Command="{Binding SettingseCommand}"/>
|
<Button Content="Настройки" Command="{Binding SettingseCommand}"/>
|
||||||
<ToggleButton IsChecked="{Binding IsListening, Mode=TwoWay}"
|
<ProgressBar IsIndeterminate="True" IsVisible="{Binding IsGenerating}" Width="100" Margin="10,0"/>
|
||||||
Content="Запуск асистента"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
/>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<ScrollViewer Height="350" CornerRadius="5" Background="Black">
|
<Grid ColumnDefinitions="*, Auto" Margin="10,0,10,10">
|
||||||
<TextBlock Text="{Binding Recognizedtext}"
|
<TextBox Grid.Column="0"
|
||||||
TextWrapping="Wrap"
|
Text="{Binding CommandText}"
|
||||||
Padding="10"
|
IsEnabled="{Binding !IsGenerating}"
|
||||||
/>
|
Watermark="Введите команду..."
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<TextBox.KeyBindings>
|
||||||
|
<KeyBinding Gesture="Enter" Command="{Binding SendCommand}"/>
|
||||||
|
</TextBox.KeyBindings>
|
||||||
|
</TextBox>
|
||||||
|
<Button Grid.Column="1"
|
||||||
|
Content="Отправить"
|
||||||
|
Command="{Binding SendCommand}"
|
||||||
|
IsEnabled="{Binding !IsGenerating}"
|
||||||
|
Margin="5,0,0,0"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<ScrollViewer Height="300" CornerRadius="5" Background="#1E1E1E" Name="ChatScroller">
|
||||||
|
<SelectableTextBlock Text="{Binding CommandLog}"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Padding="10"
|
||||||
|
FontFamily="Cascadia Code, Consolas, Monospace"
|
||||||
|
Foreground="#DCDCDC"/>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</Window>
|
</Window>
|
||||||
@@ -13,21 +13,39 @@
|
|||||||
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
|
||||||
<vm:SettingsViewModel/>
|
<vm:SettingsViewModel/>
|
||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
<ListBox ItemsSource="{Binding Modules}"
|
<DockPanel LastChildFill="True" Margin="10">
|
||||||
HorizontalAlignment="Stretch"
|
<StackPanel DockPanel.Dock="Top" Spacing="10" Margin="0,0,0,10">
|
||||||
VerticalAlignment="Stretch">
|
<TextBlock Text="Ollama Settings" FontSize="20" FontWeight="Bold"/>
|
||||||
|
|
||||||
<ListBox.ItemTemplate>
|
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto">
|
||||||
<DataTemplate x:DataType="vm:ModuleItem">
|
<TextBlock Text="Base URL:" VerticalAlignment="Center" Margin="0,0,10,0"/>
|
||||||
<Border Background="DarkBlue" CornerRadius="5" Padding="10" Margin="5">
|
<TextBox Grid.Column="1" Text="{Binding OllamaBaseUrl}" Margin="0,5"/>
|
||||||
<Grid ColumnDefinitions="*,Auto">
|
|
||||||
<TextBlock Text="{Binding Name}" />
|
<TextBlock Grid.Row="1" Text="Model:" VerticalAlignment="Center" Margin="0,0,10,0"/>
|
||||||
<Button Content="Settings"
|
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding OllamaModel}" Margin="0,5"/>
|
||||||
Grid.Column="1"
|
</Grid>
|
||||||
Command="{Binding SettingsCommand}"/>
|
|
||||||
</Grid>
|
<Button Content="Save Settings" Command="{Binding SaveCommand}" HorizontalAlignment="Right"/>
|
||||||
</Border>
|
|
||||||
</DataTemplate>
|
<Separator Margin="0,10"/>
|
||||||
</ListBox.ItemTemplate>
|
<TextBlock Text="Modules" FontSize="18" FontWeight="SemiBold"/>
|
||||||
</ListBox>
|
</StackPanel>
|
||||||
|
|
||||||
|
<ListBox ItemsSource="{Binding Modules}"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:ModuleItem">
|
||||||
|
<Border Background="#2a2a2a" CornerRadius="5" Padding="10" Margin="5">
|
||||||
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
|
<TextBlock Text="{Binding Name}" VerticalAlignment="Center"/>
|
||||||
|
<Button Content="Settings"
|
||||||
|
Grid.Column="1"
|
||||||
|
Command="{Binding SettingsCommand}"/>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</DockPanel>
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
|
||||||
<RunCommand>LD_LIBRARY_PATH=/usr/lib $(RunCommand)</RunCommand>
|
<RunCommand>LD_LIBRARY_PATH=/usr/lib $(RunCommand)</RunCommand>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@@ -24,13 +23,10 @@
|
|||||||
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
<PackageReference Include="libsoundio-sharp-net" Version="1.0.0" />
|
<PackageReference Include="System.IO.Ports" Version="9.0.0" />
|
||||||
<PackageReference Include="NAudio" Version="2.3.0" />
|
|
||||||
<PackageReference Include="Vosk" Version="0.3.38" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\VisionAsist.SDK\VisionAsist.SDK.csproj" />
|
<ProjectReference Include="..\VisionAsist.SDK\VisionAsist.SDK.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
Reference in New Issue
Block a user