diff --git a/ModuleWeather/Module.cs b/ModuleWeather/Module.cs index e778984..5142a9f 100644 --- a/ModuleWeather/Module.cs +++ b/ModuleWeather/Module.cs @@ -11,7 +11,7 @@ public class WeatherModule : IModule } - public string Name => "Прогноз Погоды"; + public string Name => "Модуль компиляции ардуино"; public string[] GetCommands() => new[] { "погода", "поверни на *", "верни *" }; diff --git a/VisionAsist/Models/Core.cs b/VisionAsist/Models/Core.cs index 4e0be9c..35a4edc 100644 --- a/VisionAsist/Models/Core.cs +++ b/VisionAsist/Models/Core.cs @@ -1,18 +1,14 @@ -using System; +using System; using System.IO; using System.Reflection; -using VisionAsist.SDK; using System.Collections.Generic; using VisionAsist.SDK; using System.Linq; + namespace VisionAsist.Models; - - - public class Core { - public class Modules { public string Name { get; set; } @@ -21,17 +17,23 @@ public class Core } public static List modulelist = new(); - public static TrigerCore triger = new(); - public static string TextAsist; static string Plugin = Path.Combine(AppContext.BaseDirectory, "Modules"); + static Core() { Console.OutputEncoding = System.Text.Encoding.UTF8; Console.InputEncoding = System.Text.Encoding.UTF8; + + if (!Directory.Exists(Plugin)) + { + Directory.CreateDirectory(Plugin); + } + string[] folderNames = new DirectoryInfo(Plugin) .GetDirectories() .Select(d => d.Name) .ToArray(); + foreach (string folderName in folderNames) { string mpn = Path.Combine(Plugin, folderName, "Module.dll"); @@ -43,37 +45,13 @@ public class Core if (type != null) { var module = (IModule)Activator.CreateInstance(type)!; - modulelist.Add(new Modules{Name = folderName, Module = module, commands = module.GetCommands()}); + modulelist.Add(new Modules { Name = module.Name, Module = module, commands = module.GetCommands() }); foreach (var cmd in module.GetCommands()) { Console.WriteLine($"- {cmd}"); } } - - } } - - } - public static void StartListing() - { - // Подписываемся на событие новых слов - triger.OnRecognized += word => - { - - - TextAsist = triger.RecognizedText; - Selector.selector(triger.RecognizedText); - - }; - - // Запускаем запись - triger.StartRecording(); - } - - static public async void StopListing () - { - triger.StopRecording(); - } } \ No newline at end of file diff --git a/VisionAsist/Models/TrigerCore.cs b/VisionAsist/Models/TrigerCore.cs deleted file mode 100644 index 8ad2ed2..0000000 --- a/VisionAsist/Models/TrigerCore.cs +++ /dev/null @@ -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 _audioQueue = new(100); - - public string RecognizedText { get; private set; } = ""; - public event Action? OnRecognized; - - // Сохраняем ссылки в полях класса, чтобы GC не удалил их в Debug-режиме - private Action? _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(); - } -} \ No newline at end of file diff --git a/VisionAsist/Program.cs b/VisionAsist/Program.cs index c60ff59..ded86ea 100644 --- a/VisionAsist/Program.cs +++ b/VisionAsist/Program.cs @@ -18,4 +18,4 @@ sealed class Program .UsePlatformDetect() .WithInterFont() .LogToTrace(); -} \ No newline at end of file +} \ No newline at end of file diff --git a/VisionAsist/ViewModels/MainWindowViewModel.cs b/VisionAsist/ViewModels/MainWindowViewModel.cs index db65799..a81e79d 100644 --- a/VisionAsist/ViewModels/MainWindowViewModel.cs +++ b/VisionAsist/ViewModels/MainWindowViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using VisionAsist.Models; @@ -15,23 +15,21 @@ public partial class MainWindowViewModel : ViewModelBase { DataContext = new SettingsViewModel() }.Show(); - } [ObservableProperty] - private bool isListening; + private string commandLog = string.Empty; + [ObservableProperty] - private string recognizedtext; - [ObservableProperty] - private string commandText; + private string commandText = string.Empty; [RelayCommand] private void Send() { if (!string.IsNullOrWhiteSpace(CommandText)) { - // Эмулируем распознанный текст для отображения в логе - Recognizedtext = CommandText; + // Отображаем введенную команду в логе + CommandLog = CommandText; // Отправляем в селектор Selector.selector(CommandText); @@ -40,37 +38,4 @@ public partial class MainWindowViewModel : ViewModelBase CommandText = string.Empty; } } - - private Action? _coreHandler; - - partial void OnIsListeningChanged(bool value) - { - if (value) - { - Core.StartListing(); - - // Сохраняем ссылку на обработчик - _coreHandler = word => - { - - Avalonia.Threading.Dispatcher.UIThread.Post(() => - { - Recognizedtext = Core.TextAsist; - }); - }; - - Core.triger.OnRecognized += _coreHandler; - } - else - { - Core.StopListing(); - - // Правильная отписка - if (_coreHandler != null) - { - Core.triger.OnRecognized -= _coreHandler; - _coreHandler = null; - } - } - } } \ No newline at end of file diff --git a/VisionAsist/Views/MainWindow.axaml b/VisionAsist/Views/MainWindow.axaml index b60952a..7c01ce5 100644 --- a/VisionAsist/Views/MainWindow.axaml +++ b/VisionAsist/Views/MainWindow.axaml @@ -10,18 +10,12 @@ Title="VisionAsist" Height="400" Width="600"> - +