Compare commits

...

19 Commits

Author SHA1 Message Date
00d4461a92 settings
Some checks failed
Mirror to Gitea / git-sync (push) Has been cancelled
2026-03-28 08:57:29 +02:00
14ebb04c1c CrossPlatform
Some checks failed
Mirror to Gitea / git-sync (push) Has been cancelled
2026-03-28 00:29:00 +02:00
ae0994409a Yess
Some checks failed
Mirror to Gitea / git-sync (push) Has been cancelled
2026-03-28 00:26:55 +02:00
ea2d84f5cc no voice
Some checks failed
Mirror to Gitea / git-sync (push) Has been cancelled
2026-03-27 16:37:05 +02:00
635dacb2ad Merge remote-tracking branch 'main/main'
Some checks failed
Mirror to Gitea / git-sync (push) Has been cancelled
2026-03-26 23:48:08 +02:00
27817754ce disable voice 2026-03-26 23:47:58 +02:00
Egor Basalyga
9088810aaa Update mirror.yml
Some checks failed
Mirror to Gitea / git-sync (push) Has been cancelled
2026-03-26 23:40:37 +02:00
Egor Basalyga
03c323a1a4 Update mirror.yml 2026-03-26 23:39:28 +02:00
Egor Basalyga
a300b7033a Rename mirror.yml to .github/workflows/mirror.yml 2026-03-26 23:36:43 +02:00
Egor Basalyga
a0833865c8 Create mirror.yml 2026-03-26 23:30:47 +02:00
78e2483e7f yay 2026-03-20 21:10:28 +02:00
ac183f8eb6 Well Done 2026-03-20 19:26:33 +02:00
f123690cb4 Module Archeticture 2026-03-19 14:24:32 +02:00
0dd13c5a8b Work Modules 2026-03-19 13:58:29 +02:00
7fde404b5a Modules 2026-03-19 13:14:53 +02:00
9ce8e190a2 Add settings func 2026-03-18 23:16:07 +02:00
2ee4515dfd Add vosk, Naudio 2026-03-18 21:55:11 +02:00
3fc1a4b735 init 2026-03-16 22:29:30 +02:00
977eca7416 Init 2026-03-16 22:29:30 +02:00
37 changed files with 1390 additions and 0 deletions

20
.github/workflows/mirror.yml vendored Normal file
View File

@@ -0,0 +1,20 @@
name: Mirror to Gitea
on:
push:
delete:
jobs:
git-sync:
runs-on: ubuntu-latest
steps:
- name: Sync to Gitea
uses: wei/git-sync@v3
with:
# Теперь мы используем автоматический токен GitHub для доступа к исходному коду
source_repo: "https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/Egorbasalyga/VisionAsist.git"
source_branch: "main"
# Ссылка на Gitea остается без изменений
destination_repo: "https://${{ secrets.GITEA_USER }}:${{ secrets.GITEA_TOKEN }}@git.vision-software.ru/Egor/Vision.git"
destination_branch: "main"

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/

15
.idea/.idea.VisionAsist/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,15 @@
# Default ignored files
/shelf/
/workspace.xml
# Rider ignored files
/modules.xml
/contentModel.xml
/projectSettingsUpdater.xml
/.idea.VisionAsist.iml
# Ignored default folder with query files
/queries/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AvaloniaProject">
<option name="projectPerEditor">
<map>
<entry key="VisionAsist/Views/MainWindow.axaml" value="VisionAsist/VisionAsist.csproj" />
<entry key="VisionAsist/Views/Settings.axaml" value="VisionAsist/VisionAsist.csproj" />
</map>
</option>
</component>
</project>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
</project>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="UserContentModel">
<attachedFolders />
<explicitIncludes />
<explicitExcludes />
</component>
</project>

6
.idea/.idea.VisionAsist/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View 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>

View 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)
{
}
}

View 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}' не найден в папке модуля или в системе.");
}
}
}

View 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>

View 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>

View 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)
{
}
}

102
ModuleWeather/Module.cs Normal file
View File

@@ -0,0 +1,102 @@
using Avalonia.Controls;
using System.Collections.Generic;
using System.Text.Json;
using VisionAsist.SDK;
namespace ModuleWeather;
public class WeatherModule : IModule
{
public string Name => "WeatherModule";
public string Description => "Модуль для получения данных о погоде";
public void Settings(object[] args)
{
// args[0] — это родительское окно из Ядра
var parentWindow = args != null && args.Length > 0 ? args[0] as Window : null;
var win = new Window
{
Title = "Настройки",
Content = new WeatherView(), // Вставляем наш контрол
Width = 350,
Height = 250,
WindowStartupLocation = WindowStartupLocation.CenterOwner
};
if (parentWindow != null) win.Show(parentWindow);
else win.Show();
}
public List<ToolDefinition> GetTools()
{
return new List<ToolDefinition>
{
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 "Инструмент не найден";
}
}

View File

@@ -0,0 +1,44 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- ВОТ ЭТА СТРОЧКА: она заставит компилятор создать Module.dll -->
<AssemblyName>ModuleWeather</AssemblyName>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\VisionAsist.SDK\VisionAsist.SDK.csproj" />
</ItemGroup>
<Target Name="CopyModuleToCore" AfterTargets="PostBuildEvent">
<ItemGroup>
<!-- Копируем все DLL и PDB из корня -->
<ModuleFiles Include="$(TargetDir)*.dll" />
<ModuleFiles Include="$(TargetDir)*.pdb" />
<!-- Ищем нативную либу .so ВЕЗДЕ в выходной папке (включая подпапки runtimes) -->
<NativeLibs Include="$(TargetDir)**\*.so" />
</ItemGroup>
<Message Text="Копирую файлы модуля и нативные библиотеки..." Importance="high" />
<Copy SourceFiles="@(ModuleFiles)"
DestinationFolder="E:\Project\Visual\VisionAsist\VisionAsist\bin\Debug\net10.0\Modules\ModuleWeather"
OverwriteReadOnlyFiles="true" />
<!-- Копируем .so файлы ПРЯМО в корень папки модуля -->
<Copy SourceFiles="@(NativeLibs)"
DestinationFolder="E:\Project\Visual\VisionAsist\VisionAsist\bin\Debug\net10.0\Modules\ModuleWeather"
OverwriteReadOnlyFiles="true" />
</Target>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.12" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.12" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,30 @@
using System.Collections.Generic;
namespace VisionAsist.SDK;
public interface IModule
{
string Name { get; }
string Description { get; }
void Settings(object[] args);
// Возвращает список инструментов.
// Каждый инструмент описывает себя через 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\": {}}";
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

37
VisionAsist.sln Normal file
View File

@@ -0,0 +1,37 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionAsist", "VisionAsist\VisionAsist.csproj", "{C5BD9C58-AE7A-4BBA-8700-1E71F48DBCA0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisionAsist.SDK", "VisionAsist.SDK\VisionAsist.SDK.csproj", "{9E547157-DA01-40FB-9DB1-67A1C310E3F1}"
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
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C5BD9C58-AE7A-4BBA-8700-1E71F48DBCA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C5BD9C58-AE7A-4BBA-8700-1E71F48DBCA0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C5BD9C58-AE7A-4BBA-8700-1E71F48DBCA0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C5BD9C58-AE7A-4BBA-8700-1E71F48DBCA0}.Release|Any CPU.Build.0 = Release|Any CPU
{9E547157-DA01-40FB-9DB1-67A1C310E3F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E547157-DA01-40FB-9DB1-67A1C310E3F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E547157-DA01-40FB-9DB1-67A1C310E3F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E547157-DA01-40FB-9DB1-67A1C310E3F1}.Release|Any CPU.Build.0 = Release|Any CPU
{5103146A-34FD-4431-856E-AB78EF523C03}.Debug|Any CPU.ActiveCfg = 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.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

View File

@@ -0,0 +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">
<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_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>

15
VisionAsist/App.axaml Normal file
View File

@@ -0,0 +1,15 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="VisionAsist.App"
xmlns:local="using:VisionAsist"
RequestedThemeVariant="Default">
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.DataTemplates>
<local:ViewLocator/>
</Application.DataTemplates>
<Application.Styles>
<FluentTheme />
</Application.Styles>
</Application>

47
VisionAsist/App.axaml.cs Normal file
View File

@@ -0,0 +1,47 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using System.Linq;
using Avalonia.Markup.Xaml;
using VisionAsist.ViewModels;
using VisionAsist.Views;
namespace VisionAsist;
public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
}
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
// Avoid duplicate validations from both Avalonia and the CommunityToolkit.
// More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins
DisableAvaloniaDataAnnotationValidation();
desktop.MainWindow = new MainWindow
{
DataContext = new MainWindowViewModel(),
};
}
base.OnFrameworkInitializationCompleted();
}
private void DisableAvaloniaDataAnnotationValidation()
{
// Get an array of plugins to remove
var dataValidationPluginsToRemove =
BindingPlugins.DataValidators.OfType<DataAnnotationsValidationPlugin>().ToArray();
// remove each entry found
foreach (var plugin in dataValidationPluginsToRemove)
{
BindingPlugins.DataValidators.Remove(plugin);
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

111
VisionAsist/Models/Core.cs Normal file
View File

@@ -0,0 +1,111 @@
using System;
using System.IO;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using VisionAsist.SDK;
namespace VisionAsist.Models;
public class Core
{
public class LoadedModule
{
public string Name { get; set; } = string.Empty;
public IModule Module { get; set; } = null!;
public List<ToolDefinition> Tools { get; set; } = new();
}
public static List<LoadedModule> 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(PluginPath)) Directory.CreateDirectory(PluginPath);
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 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))
{
try
{
Assembly assembly = Assembly.LoadFrom(dllPath);
var type = assembly.GetTypes().FirstOrDefault(t =>
typeof(IModule).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract);
if (type != null)
{
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 "Ошибка: Инструмент не найден";
}
}

View 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;
}
}
}
}

View File

@@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading.Tasks;
using System.Linq;
namespace VisionAsist.Models;
public class Selector
{
private static List<OllamaMessage> _chatHistory = new();
public static string CurrentModel => SettingsManager.Current.OllamaModel;
public static event Action<string>? OnLogUpdate;
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);
}
}
// 2. ОБРАБОТКА 'content' (если есть в этом чанке)
if (message.TryGetProperty("content", out var contentEl))
{
string part = contentEl.GetString() ?? "";
if (!string.IsNullOrEmpty(part))
{
// Если мы только что "думали", переключаемся на ответ
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();
}

View 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}");
}
}
}

21
VisionAsist/Program.cs Normal file
View File

@@ -0,0 +1,21 @@
using Avalonia;
using System;
namespace VisionAsist;
sealed class Program
{
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace();
}

View File

@@ -0,0 +1,37 @@
using System;
using System.Diagnostics.CodeAnalysis;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using VisionAsist.ViewModels;
namespace VisionAsist;
/// <summary>
/// Given a view model, returns the corresponding view if possible.
/// </summary>
[RequiresUnreferencedCode(
"Default implementation of ViewLocator involves reflection which may be trimmed away.",
Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")]
public class ViewLocator : IDataTemplate
{
public Control? Build(object? param)
{
if (param is null)
return null;
var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
var type = Type.GetType(name);
if (type != null)
{
return (Control)Activator.CreateInstance(type)!;
}
return new TextBlock { Text = "Not Found: " + name };
}
public bool Match(object? data)
{
return data is ViewModelBase;
}
}

View File

@@ -0,0 +1,55 @@
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();
}
[ObservableProperty]
private string commandLog = string.Empty;
[ObservableProperty]
private string commandText = string.Empty;
[ObservableProperty]
private bool isGenerating = false;
[RelayCommand]
private async Task Send()
{
if (!string.IsNullOrWhiteSpace(CommandText) && !IsGenerating)
{
string input = CommandText;
CommandText = string.Empty;
IsGenerating = true;
// Запускаем логику в фоновом потоке, чтобы UI не зависал
await Task.Run(async () => {
await Selector.selector(input);
IsGenerating = false;
});
}
}
}

View File

@@ -0,0 +1,77 @@
using System;
using System.Reflection;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using VisionAsist.Models;
using VisionAsist.SDK;
using System.IO;
using System.Linq;
namespace VisionAsist.ViewModels;
public class ModuleItem
{
public string Name { get; set; }
public IRelayCommand SettingsCommand { get; set; }
}
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 SettingsViewModel()
{
SaveCommand = new RelayCommand(SettingsManager.Save);
foreach (var module in Core.ModuleList)
{
AddModule(module.Name);
}
}
public void AddModule(string name)
{
Modules.Add(new ModuleItem
{
Name = name,
SettingsCommand = new RelayCommand(() => OpenSettings(name))
});
}
private void OpenSettings(string moduleName)
{
var module = Core.ModuleList.FirstOrDefault(m => m.Name == moduleName);
if (module != null)
{
module.Module.Settings(new object[] { this });
}
}
}

View File

@@ -0,0 +1,11 @@
using System;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace VisionAsist.ViewModels;
public partial class ViewModelBase : ObservableObject
{
}

View File

@@ -0,0 +1,47 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:VisionAsist.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="VisionAsist.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="VisionAsist" Height="400" Width="600">
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="10">
<Button Content="Настройки" Command="{Binding SettingseCommand}"/>
<ProgressBar IsIndeterminate="True" IsVisible="{Binding IsGenerating}" Width="100" Margin="10,0"/>
</StackPanel>
<Grid ColumnDefinitions="*, Auto" Margin="10,0,10,10">
<TextBox Grid.Column="0"
Text="{Binding CommandText}"
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>
</StackPanel>
</Window>

View File

@@ -0,0 +1,11 @@
using Avalonia.Controls;
namespace VisionAsist.Views;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,51 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:VisionAsist.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="VisionAsist.Views.Settings"
x:DataType="vm:SettingsViewModel"
Icon="/Assets/avalonia-logo.ico"
Title="VisionAsist" Height="400" Width="600">
<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:SettingsViewModel/>
</Design.DataContext>
<DockPanel LastChildFill="True" Margin="10">
<StackPanel DockPanel.Dock="Top" Spacing="10" Margin="0,0,0,10">
<TextBlock Text="Ollama Settings" FontSize="20" FontWeight="Bold"/>
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto">
<TextBlock Text="Base URL:" VerticalAlignment="Center" Margin="0,0,10,0"/>
<TextBox Grid.Column="1" Text="{Binding OllamaBaseUrl}" Margin="0,5"/>
<TextBlock Grid.Row="1" Text="Model:" VerticalAlignment="Center" Margin="0,0,10,0"/>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding OllamaModel}" Margin="0,5"/>
</Grid>
<Button Content="Save Settings" Command="{Binding SaveCommand}" HorizontalAlignment="Right"/>
<Separator Margin="0,10"/>
<TextBlock Text="Modules" FontSize="18" FontWeight="SemiBold"/>
</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>

View File

@@ -0,0 +1,13 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace VisionAsist.Views;
public partial class Settings : Window
{
public Settings()
{
InitializeComponent();
}
}

View File

@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ApplicationManifest>app.manifest</ApplicationManifest>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<RunCommand>LD_LIBRARY_PATH=/usr/lib $(RunCommand)</RunCommand>
</PropertyGroup>
<ItemGroup>
<AvaloniaResource Include="Assets\**"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.3.12" />
<PackageReference Include="Avalonia.Desktop" Version="11.3.12" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.12" />
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.12" />
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
<PackageReference Include="Avalonia.Diagnostics" Version="11.3.12">
<IncludeAssets Condition="'$(Configuration)' != 'Debug'">None</IncludeAssets>
<PrivateAssets Condition="'$(Configuration)' != 'Debug'">All</PrivateAssets>
</PackageReference>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
<PackageReference Include="System.IO.Ports" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\VisionAsist.SDK\VisionAsist.SDK.csproj" />
</ItemGroup>
</Project>

18
VisionAsist/app.manifest Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- This manifest is used on Windows only.
Don't remove it as it might cause problems with window transparency and embedded controls.
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
<assemblyIdentity version="1.0.0.0" name="VisionAsist.Desktop"/>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
</assembly>