First commit!
This commit is contained in:
+226
@@ -0,0 +1,226 @@
|
||||
// File: MainForm.Designer.cs
|
||||
namespace VoiceAssistantPoC_Win
|
||||
{
|
||||
partial class MainForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
|
||||
rtbLogs = new RichTextBox();
|
||||
pnlBottom = new Panel();
|
||||
cmbSessions = new ComboBox();
|
||||
lblSession = new Label();
|
||||
btnRefreshSessions = new Button();
|
||||
reInitSessionBtn = new Button();
|
||||
lblLiveTranscript = new Label();
|
||||
txtManualInput = new TextBox();
|
||||
btnSend = new Button();
|
||||
chkVoiceMode = new CheckBox();
|
||||
pnlBottom.SuspendLayout();
|
||||
SuspendLayout();
|
||||
//
|
||||
// rtbLogs
|
||||
//
|
||||
rtbLogs.BackColor = Color.FromArgb(30, 30, 30);
|
||||
rtbLogs.BorderStyle = BorderStyle.None;
|
||||
rtbLogs.Dock = DockStyle.Fill;
|
||||
rtbLogs.Font = new Font("Consolas", 10F, FontStyle.Regular, GraphicsUnit.Point, 0);
|
||||
rtbLogs.ForeColor = Color.LightGray;
|
||||
rtbLogs.Location = new Point(0, 0);
|
||||
rtbLogs.Margin = new Padding(4, 3, 4, 3);
|
||||
rtbLogs.Name = "rtbLogs";
|
||||
rtbLogs.ReadOnly = true;
|
||||
rtbLogs.Size = new Size(915, 532);
|
||||
rtbLogs.TabIndex = 0;
|
||||
rtbLogs.Text = "";
|
||||
//
|
||||
// pnlBottom
|
||||
//
|
||||
pnlBottom.BackColor = Color.FromArgb(45, 45, 48);
|
||||
pnlBottom.Controls.Add(cmbSessions);
|
||||
pnlBottom.Controls.Add(lblSession);
|
||||
pnlBottom.Controls.Add(btnRefreshSessions);
|
||||
pnlBottom.Controls.Add(reInitSessionBtn);
|
||||
pnlBottom.Controls.Add(lblLiveTranscript);
|
||||
pnlBottom.Controls.Add(txtManualInput);
|
||||
pnlBottom.Controls.Add(btnSend);
|
||||
pnlBottom.Controls.Add(chkVoiceMode);
|
||||
pnlBottom.Dock = DockStyle.Bottom;
|
||||
pnlBottom.Location = new Point(0, 532);
|
||||
pnlBottom.Margin = new Padding(4, 3, 4, 3);
|
||||
pnlBottom.Name = "pnlBottom";
|
||||
pnlBottom.Size = new Size(915, 115);
|
||||
pnlBottom.TabIndex = 1;
|
||||
//
|
||||
// cmbSessions
|
||||
//
|
||||
cmbSessions.DropDownStyle = ComboBoxStyle.DropDownList;
|
||||
cmbSessions.FormattingEnabled = true;
|
||||
cmbSessions.Location = new Point(120, 10);
|
||||
cmbSessions.Name = "cmbSessions";
|
||||
cmbSessions.Size = new Size(300, 23);
|
||||
cmbSessions.TabIndex = 5;
|
||||
cmbSessions.SelectedIndexChanged += cmbSessions_SelectedIndexChanged;
|
||||
//
|
||||
// lblSession
|
||||
//
|
||||
lblSession.AutoSize = true;
|
||||
lblSession.Font = new Font("Segoe UI", 9F, FontStyle.Bold, GraphicsUnit.Point, 0);
|
||||
lblSession.ForeColor = Color.White;
|
||||
lblSession.Location = new Point(14, 13);
|
||||
lblSession.Name = "lblSession";
|
||||
lblSession.Size = new Size(90, 15);
|
||||
lblSession.TabIndex = 6;
|
||||
lblSession.Text = "Active Session:";
|
||||
//
|
||||
// btnRefreshSessions
|
||||
//
|
||||
btnRefreshSessions.BackColor = Color.FromArgb(0, 122, 204);
|
||||
btnRefreshSessions.FlatAppearance.BorderSize = 0;
|
||||
btnRefreshSessions.FlatStyle = FlatStyle.Flat;
|
||||
btnRefreshSessions.Font = new Font("Segoe UI", 9F, FontStyle.Bold, GraphicsUnit.Point, 0);
|
||||
btnRefreshSessions.ForeColor = Color.White;
|
||||
btnRefreshSessions.Location = new Point(430, 9);
|
||||
btnRefreshSessions.Name = "btnRefreshSessions";
|
||||
btnRefreshSessions.Size = new Size(75, 25);
|
||||
btnRefreshSessions.TabIndex = 7;
|
||||
btnRefreshSessions.Text = "Refresh";
|
||||
btnRefreshSessions.UseVisualStyleBackColor = false;
|
||||
btnRefreshSessions.Click += btnRefreshSessions_Click;
|
||||
//
|
||||
// reInitSessionBtn
|
||||
//
|
||||
reInitSessionBtn.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
reInitSessionBtn.BackColor = Color.FromArgb(0, 122, 204);
|
||||
reInitSessionBtn.FlatAppearance.BorderSize = 0;
|
||||
reInitSessionBtn.FlatStyle = FlatStyle.Flat;
|
||||
reInitSessionBtn.Font = new Font("Segoe UI", 9F, FontStyle.Bold, GraphicsUnit.Point, 0);
|
||||
reInitSessionBtn.ForeColor = Color.White;
|
||||
reInitSessionBtn.Location = new Point(788, 70);
|
||||
reInitSessionBtn.Margin = new Padding(4, 3, 4, 3);
|
||||
reInitSessionBtn.Name = "reInitSessionBtn";
|
||||
reInitSessionBtn.Size = new Size(99, 25);
|
||||
reInitSessionBtn.TabIndex = 4;
|
||||
reInitSessionBtn.Text = "Re Init Session";
|
||||
reInitSessionBtn.UseVisualStyleBackColor = false;
|
||||
reInitSessionBtn.Click += reInitSessionBtn_Click;
|
||||
//
|
||||
// lblLiveTranscript
|
||||
//
|
||||
lblLiveTranscript.AutoSize = true;
|
||||
lblLiveTranscript.Font = new Font("Consolas", 11F, FontStyle.Bold, GraphicsUnit.Point, 0);
|
||||
lblLiveTranscript.ForeColor = Color.Cyan;
|
||||
lblLiveTranscript.Location = new Point(14, 40);
|
||||
lblLiveTranscript.Margin = new Padding(4, 0, 4, 0);
|
||||
lblLiveTranscript.Name = "lblLiveTranscript";
|
||||
lblLiveTranscript.Size = new Size(96, 18);
|
||||
lblLiveTranscript.TabIndex = 3;
|
||||
lblLiveTranscript.Text = "[Live]: ...";
|
||||
//
|
||||
// txtManualInput
|
||||
//
|
||||
txtManualInput.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right;
|
||||
txtManualInput.BackColor = Color.FromArgb(60, 60, 60);
|
||||
txtManualInput.BorderStyle = BorderStyle.FixedSingle;
|
||||
txtManualInput.Enabled = false;
|
||||
txtManualInput.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point, 0);
|
||||
txtManualInput.ForeColor = Color.White;
|
||||
txtManualInput.Location = new Point(14, 70);
|
||||
txtManualInput.Margin = new Padding(4, 3, 4, 3);
|
||||
txtManualInput.Name = "txtManualInput";
|
||||
txtManualInput.Size = new Size(659, 25);
|
||||
txtManualInput.TabIndex = 2;
|
||||
txtManualInput.KeyDown += txtManualInput_KeyDown;
|
||||
//
|
||||
// btnSend
|
||||
//
|
||||
btnSend.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
btnSend.BackColor = Color.FromArgb(0, 122, 204);
|
||||
btnSend.Enabled = false;
|
||||
btnSend.FlatAppearance.BorderSize = 0;
|
||||
btnSend.FlatStyle = FlatStyle.Flat;
|
||||
btnSend.Font = new Font("Segoe UI", 9F, FontStyle.Bold, GraphicsUnit.Point, 0);
|
||||
btnSend.ForeColor = Color.White;
|
||||
btnSend.Location = new Point(681, 70);
|
||||
btnSend.Margin = new Padding(4, 3, 4, 3);
|
||||
btnSend.Name = "btnSend";
|
||||
btnSend.Size = new Size(99, 25);
|
||||
btnSend.TabIndex = 1;
|
||||
btnSend.Text = "Send";
|
||||
btnSend.UseVisualStyleBackColor = false;
|
||||
btnSend.Click += btnSend_Click;
|
||||
//
|
||||
// chkVoiceMode
|
||||
//
|
||||
chkVoiceMode.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
||||
chkVoiceMode.AutoSize = true;
|
||||
chkVoiceMode.Checked = true;
|
||||
chkVoiceMode.CheckState = CheckState.Checked;
|
||||
chkVoiceMode.Font = new Font("Segoe UI", 10F, FontStyle.Regular, GraphicsUnit.Point, 0);
|
||||
chkVoiceMode.ForeColor = Color.White;
|
||||
chkVoiceMode.Location = new Point(788, 40);
|
||||
chkVoiceMode.Margin = new Padding(4, 3, 4, 3);
|
||||
chkVoiceMode.Name = "chkVoiceMode";
|
||||
chkVoiceMode.Size = new Size(100, 23);
|
||||
chkVoiceMode.TabIndex = 0;
|
||||
chkVoiceMode.Text = "Voice Mode";
|
||||
chkVoiceMode.UseVisualStyleBackColor = true;
|
||||
chkVoiceMode.CheckedChanged += chkVoiceMode_CheckedChanged;
|
||||
//
|
||||
// MainForm
|
||||
//
|
||||
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||
AutoScaleMode = AutoScaleMode.Font;
|
||||
ClientSize = new Size(915, 647);
|
||||
Controls.Add(rtbLogs);
|
||||
Controls.Add(pnlBottom);
|
||||
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||
Margin = new Padding(4, 3, 4, 3);
|
||||
Name = "MainForm";
|
||||
Text = "Synapse OS Assistant PoC";
|
||||
FormClosing += MainForm_FormClosing;
|
||||
Load += MainForm_Load;
|
||||
pnlBottom.ResumeLayout(false);
|
||||
pnlBottom.PerformLayout();
|
||||
ResumeLayout(false);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.RichTextBox rtbLogs;
|
||||
private System.Windows.Forms.Panel pnlBottom;
|
||||
private System.Windows.Forms.TextBox txtManualInput;
|
||||
private System.Windows.Forms.Button btnSend;
|
||||
private System.Windows.Forms.CheckBox chkVoiceMode;
|
||||
private System.Windows.Forms.Label lblLiveTranscript;
|
||||
private System.Windows.Forms.Button reInitSessionBtn;
|
||||
private System.Windows.Forms.ComboBox cmbSessions;
|
||||
private System.Windows.Forms.Label lblSession;
|
||||
private System.Windows.Forms.Button btnRefreshSessions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,602 @@
|
||||
// File: MainForm.cs
|
||||
using AI.Client;
|
||||
using AI.Client.SCL;
|
||||
using NAudio.Wave;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Vosk;
|
||||
using static System.Net.Mime.MediaTypeNames;
|
||||
|
||||
namespace VoiceAssistantPoC_Win
|
||||
{
|
||||
public partial class MainForm : Form
|
||||
{
|
||||
private AiClient _aiClient;
|
||||
private SclProcessor _sclProcessor;
|
||||
private string _targetInstanceId;
|
||||
|
||||
private bool _isProcessing = false;
|
||||
private bool _waitingForCommand = false; // Handles the "Computer" edge case
|
||||
private bool _isOpenAI_Instance = false;
|
||||
private bool _isFetchingSessions = false;
|
||||
|
||||
// Session Tracking
|
||||
private Dictionary<string, bool> _initializedSessions = new Dictionary<string, bool>();
|
||||
private Dictionary<string, string> _sessionPlatforms = new Dictionary<string, string>();
|
||||
|
||||
// Vosk & NAudio components
|
||||
private Model _voskModel;
|
||||
private VoskRecognizer _recognizer;
|
||||
private WaveInEvent _waveIn;
|
||||
|
||||
private class SessionItem
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string DisplayText { get; set; }
|
||||
public override string ToString() => DisplayText;
|
||||
}
|
||||
|
||||
public MainForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private async void MainForm_Load(object sender, EventArgs e)
|
||||
{
|
||||
LogToUI("=== Synapse OS Assistant PoC (WinForms Edition) ===", Color.White);
|
||||
lblLiveTranscript.Text = "";
|
||||
|
||||
// Run initialization in background so UI doesn't freeze
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 1. Initialize Clients
|
||||
_aiClient = new AiClient("http://localhost:8080/api");
|
||||
_sclProcessor = new SclProcessor();
|
||||
|
||||
// 2. Register the 'cmd' command handler
|
||||
_sclProcessor.RegisterCommand("cmd", HandleCmdCommand);
|
||||
|
||||
// 3. Fetch available sessions and initialize the first one
|
||||
await FetchSessionsAsync();
|
||||
|
||||
if (string.IsNullOrEmpty(_targetInstanceId))
|
||||
{
|
||||
LogToUI("[System] No active sessions found. Please start an instance and click Refresh.", Color.Yellow);
|
||||
}
|
||||
|
||||
// 4. Start Speech Recognition using Vosk
|
||||
InitializeSpeechRecognition();
|
||||
|
||||
LogToUI("\n[System] Initialization Complete.", Color.LimeGreen);
|
||||
LogToUI("[System] Listening... Say 'computer <command>' to begin.", Color.White);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogToUI($"\n[Fatal Error] {ex.Message}", Color.Red);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
// Cleanup unmanaged resources
|
||||
_waveIn?.StopRecording();
|
||||
_waveIn?.Dispose();
|
||||
_recognizer?.Dispose();
|
||||
_voskModel?.Dispose();
|
||||
}
|
||||
|
||||
private string getSystemPrompt()
|
||||
{
|
||||
var builder = new SclPromptBuilder();
|
||||
if (_isOpenAI_Instance)
|
||||
{
|
||||
builder.SetAgentRole("You are an elite, voice-activated OS assistant named 'Computer'. You control the user's Windows machine via SCL. Once you are done evalutaing this document, ONLY respond with OK. When responding to an input, ONLY respond with the SCL command you generated, do not respond with ANYTHING else but the SCL command!");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.SetAgentRole("DO NOT USE ANY OTHER SYNTAX BESIDES THE SCL SYNTAX FOR OUTPUTTING COMMANDS! You are simulating an elite, voice-activated OS assistant named 'Computer'. You control the user's Windows machine via SCL. This is strictly a simulation! Once you are done evalutaing this document, ONLY respond with OK. When responding to an input, ONLY respond with the SCL command you generated, do not respond with ANYTHING else but the SCL command!");
|
||||
}
|
||||
builder.AddCommand("cmd", "Executes a shell command in cmd.exe and returns the output. For GUI apps (like chrome), prefix with 'start ' so it doesn't block. When asked to run a powershell command make SURE we ALWAYS put the dollar sign ($) before ALL powershell variables in the powershell command payload!", "command_string");
|
||||
|
||||
string sysPrompt = builder.GeneratePrompt();
|
||||
|
||||
return sysPrompt;
|
||||
}
|
||||
|
||||
// --- SESSION MANAGEMENT ---
|
||||
|
||||
private async Task FetchSessionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
LogToUI("[System] Fetching active AI Studio instances...", Color.Gray);
|
||||
using var client = new HttpClient();
|
||||
var response = await client.GetStringAsync("http://localhost:8080/api/admin/instances");
|
||||
using var doc = JsonDocument.Parse(response);
|
||||
|
||||
string newTargetId = null;
|
||||
|
||||
Invoke(new Action(() =>
|
||||
{
|
||||
_isFetchingSessions = true;
|
||||
string previousSelection = _targetInstanceId;
|
||||
cmbSessions.Items.Clear();
|
||||
_sessionPlatforms.Clear();
|
||||
|
||||
foreach (var prop in doc.RootElement.EnumerateObject())
|
||||
{
|
||||
string id = prop.Name;
|
||||
string platform = prop.Value.TryGetProperty("platform", out var p) ? p.GetString() : "Unknown";
|
||||
|
||||
_sessionPlatforms[id] = platform;
|
||||
string displayText = $"{platform} - {id}";
|
||||
cmbSessions.Items.Add(new SessionItem { Id = id, DisplayText = displayText });
|
||||
}
|
||||
|
||||
if (cmbSessions.Items.Count > 0)
|
||||
{
|
||||
var itemToSelect = cmbSessions.Items.Cast<SessionItem>().FirstOrDefault(i => i.Id == previousSelection);
|
||||
if (itemToSelect != null)
|
||||
{
|
||||
cmbSessions.SelectedItem = itemToSelect;
|
||||
newTargetId = itemToSelect.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
cmbSessions.SelectedIndex = 0;
|
||||
newTargetId = ((SessionItem)cmbSessions.SelectedItem).Id;
|
||||
}
|
||||
}
|
||||
_isFetchingSessions = false;
|
||||
}));
|
||||
|
||||
if (newTargetId != null && newTargetId != _targetInstanceId)
|
||||
{
|
||||
_targetInstanceId = newTargetId;
|
||||
if (!_initializedSessions.ContainsKey(_targetInstanceId) || !_initializedSessions[_targetInstanceId])
|
||||
{
|
||||
SetUiState(true);
|
||||
await InitializeAiSession(_targetInstanceId);
|
||||
SetUiState(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogToUI($"[Error] Failed to fetch sessions: {ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InitializeAiSession(string instanceId)
|
||||
{
|
||||
LogToUI($"[System] Initializing Session: {instanceId}...", Color.Gray);
|
||||
|
||||
bool isOpenAI = instanceId.Contains("openai") || (_sessionPlatforms.ContainsKey(instanceId) && _sessionPlatforms[instanceId] == "OpenAI API");
|
||||
_isOpenAI_Instance = isOpenAI;
|
||||
bool isGeminiOrChatGPT = (instanceId.Contains("gem") || (_sessionPlatforms.ContainsKey(instanceId) && _sessionPlatforms[instanceId] == "Google Gemini")) || (instanceId.Contains("cgpt") || (_sessionPlatforms.ContainsKey(instanceId) && _sessionPlatforms[instanceId] == "ChatGPT"));
|
||||
|
||||
if (isGeminiOrChatGPT)
|
||||
{
|
||||
await _aiClient.NewChatAsync(instanceId);
|
||||
|
||||
// Wait for the UI to stabilize after the reset to prevent Angular state race conditions
|
||||
await Task.Delay(1000);
|
||||
|
||||
LogToUI("[System] Generating and injecting SCL System Instructions...", Color.Gray);
|
||||
await _aiClient.SendPromptAsync(Convert.ToBase64String(Encoding.ASCII.GetBytes(getSystemPrompt())), instanceId);
|
||||
LogToUI("[System] AI Session Initialized and Ready.", Color.LimeGreen);
|
||||
}
|
||||
else if (isOpenAI)
|
||||
{
|
||||
LogToUI("[System] Generating and injecting SCL System Instructions...", Color.Gray);
|
||||
await _aiClient.SetOpenAISystemInstructionsAsync(getSystemPrompt());
|
||||
LogToUI("[System] AI Session Initialized and Ready.", Color.LimeGreen);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _aiClient.NewChatAsync(instanceId);
|
||||
|
||||
// Wait for the UI to stabilize after the reset to prevent Angular state race conditions
|
||||
await Task.Delay(1000);
|
||||
|
||||
LogToUI("[System] Generating and injecting SCL System Instructions...", Color.Gray);
|
||||
await _aiClient.SendPromptAsync(getSystemPrompt(), instanceId);
|
||||
LogToUI("[System] AI Session Initialized and Ready.", Color.LimeGreen);
|
||||
}
|
||||
|
||||
_initializedSessions[instanceId] = true;
|
||||
}
|
||||
|
||||
private void InitializeSpeechRecognition()
|
||||
{
|
||||
string modelPath = "model";
|
||||
|
||||
if (!Directory.Exists(modelPath))
|
||||
{
|
||||
throw new Exception($"Vosk model folder not found at: {Path.GetFullPath(modelPath)}\nPlease download 'vosk-model-small-en-us-0.15', extract it, and rename the folder to 'model' in your build output directory.");
|
||||
}
|
||||
|
||||
LogToUI("[System] Loading Vosk Model (this may take a moment)...", Color.Gray);
|
||||
|
||||
Vosk.Vosk.SetLogLevel(-1);
|
||||
|
||||
_voskModel = new Model(modelPath);
|
||||
_recognizer = new VoskRecognizer(_voskModel, 16000.0f);
|
||||
|
||||
_waveIn = new WaveInEvent();
|
||||
_waveIn.DeviceNumber = 0;
|
||||
_waveIn.WaveFormat = new WaveFormat(16000, 1);
|
||||
_waveIn.DataAvailable += WaveInOnDataAvailable;
|
||||
|
||||
if (chkVoiceMode.Checked)
|
||||
{
|
||||
_waveIn.StartRecording();
|
||||
LogToUI("[System] Microphone initialized successfully.", Color.LimeGreen);
|
||||
}
|
||||
}
|
||||
|
||||
// --- VOSK AUDIO PROCESSING ---
|
||||
|
||||
private void WaveInOnDataAvailable(object sender, WaveInEventArgs e)
|
||||
{
|
||||
if (_isProcessing) return;
|
||||
|
||||
if (_recognizer.AcceptWaveform(e.Buffer, e.BytesRecorded))
|
||||
{
|
||||
string jsonResult = _recognizer.Result();
|
||||
ProcessVoskResult(jsonResult, isFinal: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
string jsonPartial = _recognizer.PartialResult();
|
||||
ProcessVoskResult(jsonPartial, isFinal: false);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessVoskResult(string json, bool isFinal)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (JsonDocument doc = JsonDocument.Parse(json))
|
||||
{
|
||||
if (isFinal)
|
||||
{
|
||||
UpdateLiveTranscript(""); // Clear live transcript
|
||||
|
||||
string text = doc.RootElement.GetProperty("text").GetString();
|
||||
if (string.IsNullOrWhiteSpace(text)) return;
|
||||
|
||||
LogToUI($"[Mic Debug] Finalized: '{text}'", Color.DarkGray);
|
||||
|
||||
if (_isProcessing) return;
|
||||
|
||||
// EDGE CASE FIX: If we were waiting for a command because the last batch was just "computer"
|
||||
if (_waitingForCommand)
|
||||
{
|
||||
_waitingForCommand = false;
|
||||
string finalText = text;
|
||||
|
||||
if (!_isOpenAI_Instance)
|
||||
{
|
||||
finalText = Convert.ToBase64String(Encoding.ASCII.GetBytes(text));
|
||||
}
|
||||
|
||||
ExecuteCommand(finalText);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for wake word
|
||||
if (text == "computer")
|
||||
{
|
||||
_waitingForCommand = true;
|
||||
LogToUI("[System] Wake word detected. Listening for command...", Color.Cyan);
|
||||
}
|
||||
else if (text.StartsWith("computer ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
string commandText = text.Substring("computer".Length).Trim();
|
||||
if (!string.IsNullOrWhiteSpace(commandText))
|
||||
{
|
||||
string finalText = text;
|
||||
|
||||
if (!_isOpenAI_Instance)
|
||||
{
|
||||
finalText = Convert.ToBase64String(Encoding.ASCII.GetBytes(text));
|
||||
}
|
||||
|
||||
ExecuteCommand(finalText);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string partial = doc.RootElement.GetProperty("partial").GetString();
|
||||
if (!string.IsNullOrWhiteSpace(partial))
|
||||
{
|
||||
UpdateLiveTranscript($"[Live]: {partial}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogToUI($"[Error] Failed to parse Vosk JSON: {ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
// --- UI EVENT HANDLERS ---
|
||||
|
||||
private async void cmbSessions_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (_isFetchingSessions) return;
|
||||
|
||||
var selected = cmbSessions.SelectedItem as SessionItem;
|
||||
if (selected == null) return;
|
||||
|
||||
if (_targetInstanceId == selected.Id) return;
|
||||
|
||||
_targetInstanceId = selected.Id;
|
||||
|
||||
if (!_initializedSessions.ContainsKey(_targetInstanceId) || !_initializedSessions[_targetInstanceId])
|
||||
{
|
||||
SetUiState(true);
|
||||
await InitializeAiSession(_targetInstanceId);
|
||||
SetUiState(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
_isOpenAI_Instance = _targetInstanceId.Contains("openai") || (_sessionPlatforms.ContainsKey(_targetInstanceId) && _sessionPlatforms[_targetInstanceId] == "OpenAI API");
|
||||
LogToUI($"[System] Switched to initialized session: {_targetInstanceId}", Color.LimeGreen);
|
||||
}
|
||||
}
|
||||
|
||||
private async void btnRefreshSessions_Click(object sender, EventArgs e)
|
||||
{
|
||||
SetUiState(true);
|
||||
await FetchSessionsAsync();
|
||||
SetUiState(false);
|
||||
}
|
||||
|
||||
private void chkVoiceMode_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
bool isVoiceMode = chkVoiceMode.Checked;
|
||||
|
||||
if (!_isProcessing)
|
||||
{
|
||||
txtManualInput.Enabled = !isVoiceMode;
|
||||
btnSend.Enabled = !isVoiceMode;
|
||||
}
|
||||
|
||||
if (isVoiceMode)
|
||||
{
|
||||
_waveIn?.StartRecording();
|
||||
LogToUI("[System] Switched to Voice Mode. Microphone active.", Color.Cyan);
|
||||
}
|
||||
else
|
||||
{
|
||||
_waveIn?.StopRecording();
|
||||
UpdateLiveTranscript("");
|
||||
_waitingForCommand = false;
|
||||
LogToUI("[System] Switched to Text Mode. Microphone paused.", Color.Yellow);
|
||||
}
|
||||
}
|
||||
|
||||
private void btnSend_Click(object sender, EventArgs e)
|
||||
{
|
||||
string input = txtManualInput.Text.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(input) && !_isProcessing)
|
||||
{
|
||||
txtManualInput.Clear();
|
||||
|
||||
string finalText = input;
|
||||
|
||||
if (!_isOpenAI_Instance)
|
||||
{
|
||||
finalText = Convert.ToBase64String(Encoding.ASCII.GetBytes(input));
|
||||
}
|
||||
|
||||
ExecuteCommand(finalText);
|
||||
}
|
||||
}
|
||||
|
||||
private async void reInitSessionBtn_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_targetInstanceId)) return;
|
||||
SetUiState(true);
|
||||
await InitializeAiSession(_targetInstanceId);
|
||||
SetUiState(false);
|
||||
}
|
||||
|
||||
private void txtManualInput_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.KeyCode == Keys.Enter)
|
||||
{
|
||||
e.SuppressKeyPress = true;
|
||||
btnSend.PerformClick();
|
||||
}
|
||||
}
|
||||
|
||||
// --- CORE EXECUTION LOGIC ---
|
||||
|
||||
private void ExecuteCommand(string commandText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_targetInstanceId))
|
||||
{
|
||||
LogToUI("[System] No active session selected!", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
LogToUI($"\n[User] {commandText}", Color.White);
|
||||
SetUiState(true);
|
||||
|
||||
// Offload to background task to keep UI responsive
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await ProcessInteractionLoop(commandText);
|
||||
SetUiState(false);
|
||||
LogToUI("\n[System] Ready for next command.", Color.LimeGreen);
|
||||
});
|
||||
}
|
||||
|
||||
private async Task ProcessInteractionLoop(string initialInput)
|
||||
{
|
||||
string currentInput = initialInput;
|
||||
|
||||
while (!string.IsNullOrEmpty(currentInput))
|
||||
{
|
||||
try
|
||||
{
|
||||
LogToUI("[System] Sending to AI...", Color.Gray);
|
||||
var response = await _aiClient.SendPromptAsync(currentInput, _targetInstanceId);
|
||||
|
||||
string aiText = response.Output.Replace(@"AI responses may include mistakes. For financial advice, consult a professional. [Learn more](https://support.google.com/websearch?p=aimode)", string.Empty);
|
||||
LogToUI($"\n[AI] {aiText}\n", Color.Orange);
|
||||
|
||||
string sclResult = await _sclProcessor.ProcessTextAsync(aiText);
|
||||
|
||||
if (!string.IsNullOrEmpty(sclResult))
|
||||
{
|
||||
LogToUI($"[System] SCL Execution Result: {sclResult}", Color.Cyan);
|
||||
currentInput = sclResult;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentInput = null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogToUI($"[Error] Interaction loop failed: {ex.Message}", Color.Red);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<SclResult> HandleCmdCommand(string[] args)
|
||||
{
|
||||
if (args == null || args.Length == 0)
|
||||
return SclResult.Error("No command string provided.");
|
||||
|
||||
// Reconstruct the command string in case the AI used an unescaped, unquoted pipe '|'
|
||||
// which caused the parser to split the command into multiple arguments.
|
||||
string commandString = string.Join("|", args);
|
||||
|
||||
LogToUI($"[SCL Engine] Executing shell command: {commandString}", Color.Magenta);
|
||||
|
||||
try
|
||||
{
|
||||
ProcessStartInfo psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = "cmd.exe",
|
||||
Arguments = $"/c {commandString}",
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
using (Process process = new Process { StartInfo = psi })
|
||||
{
|
||||
process.Start();
|
||||
|
||||
Task<string> readOutTask = process.StandardOutput.ReadToEndAsync();
|
||||
Task<string> readErrTask = process.StandardError.ReadToEndAsync();
|
||||
|
||||
bool exited = process.WaitForExit(15000);
|
||||
|
||||
if (!exited)
|
||||
{
|
||||
process.Kill();
|
||||
return SclResult.Error("Command timed out after 15 seconds.");
|
||||
}
|
||||
|
||||
string output = await readOutTask;
|
||||
string error = await readErrTask;
|
||||
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
return SclResult.Error($"Exit Code {process.ExitCode}. Error: {error.Trim()}");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(output))
|
||||
{
|
||||
return SclResult.NoResponse();
|
||||
}
|
||||
|
||||
return SclResult.Success(output.Trim());
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return SclResult.Error($"Exception executing command: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// --- THREAD-SAFE UI HELPERS ---
|
||||
|
||||
private void SetUiState(bool isProcessing)
|
||||
{
|
||||
if (InvokeRequired)
|
||||
{
|
||||
Invoke(new Action(() => SetUiState(isProcessing)));
|
||||
return;
|
||||
}
|
||||
|
||||
_isProcessing = isProcessing;
|
||||
|
||||
// Disable session switching controls while processing
|
||||
cmbSessions.Enabled = !isProcessing;
|
||||
btnRefreshSessions.Enabled = !isProcessing;
|
||||
reInitSessionBtn.Enabled = !isProcessing;
|
||||
|
||||
if (!chkVoiceMode.Checked)
|
||||
{
|
||||
txtManualInput.Enabled = !isProcessing;
|
||||
btnSend.Enabled = !isProcessing;
|
||||
}
|
||||
else
|
||||
{
|
||||
txtManualInput.Enabled = false;
|
||||
btnSend.Enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void LogToUI(string message, Color color)
|
||||
{
|
||||
if (InvokeRequired)
|
||||
{
|
||||
Invoke(new Action(() => LogToUI(message, color)));
|
||||
return;
|
||||
}
|
||||
|
||||
rtbLogs.SelectionStart = rtbLogs.TextLength;
|
||||
rtbLogs.SelectionLength = 0;
|
||||
rtbLogs.SelectionColor = color;
|
||||
rtbLogs.AppendText(message + Environment.NewLine);
|
||||
rtbLogs.SelectionColor = rtbLogs.ForeColor;
|
||||
rtbLogs.ScrollToCaret();
|
||||
}
|
||||
|
||||
private void UpdateLiveTranscript(string text)
|
||||
{
|
||||
if (InvokeRequired)
|
||||
{
|
||||
Invoke(new Action(() => UpdateLiveTranscript(text)));
|
||||
return;
|
||||
}
|
||||
|
||||
lblLiveTranscript.Text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
namespace VoiceAssistantPoC_Win
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
// To customize application configuration such as set high DPI settings or default font,
|
||||
// see https://aka.ms/applicationconfiguration.
|
||||
ApplicationConfiguration.Initialize();
|
||||
Application.SetColorMode(SystemColorMode.Dark);
|
||||
Application.Run(new MainForm());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net10.0-windows</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<ApplicationIcon>app.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="app.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NAudio" Version="2.3.0" />
|
||||
<PackageReference Include="Vosk" Version="0.3.38" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AI.Client\AI.Client.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="model\am\final.mdl">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="model\conf\mfcc.conf">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="model\conf\model.conf">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="model\graph\disambig_tid.int">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="model\graph\Gr.fst">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="model\graph\HCLr.fst">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="model\graph\phones\word_boundary.int">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="model\ivector\final.dubm">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="model\ivector\final.ie">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="model\ivector\final.mat">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="model\ivector\global_cmvn.stats">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="model\ivector\online_cmvn.conf">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="model\ivector\splice.conf">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="model\README">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 308 KiB |
@@ -0,0 +1,9 @@
|
||||
US English model for mobile Vosk applications
|
||||
|
||||
Copyright 2020 Alpha Cephei Inc
|
||||
|
||||
Accuracy: 10.38 (tedlium test) 9.85 (librispeech test-clean)
|
||||
Speed: 0.11xRT (desktop)
|
||||
Latency: 0.15s (right context)
|
||||
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
--sample-frequency=16000
|
||||
--use-energy=false
|
||||
--num-mel-bins=40
|
||||
--num-ceps=40
|
||||
--low-freq=20
|
||||
--high-freq=7600
|
||||
--allow-downsample=true
|
||||
@@ -0,0 +1,10 @@
|
||||
--min-active=200
|
||||
--max-active=3000
|
||||
--beam=10.0
|
||||
--lattice-beam=2.0
|
||||
--acoustic-scale=1.0
|
||||
--frame-subsampling-factor=3
|
||||
--endpoint.silence-phones=1:2:3:4:5:6:7:8:9:10
|
||||
--endpoint.rule2.min-trailing-silence=0.5
|
||||
--endpoint.rule3.min-trailing-silence=0.75
|
||||
--endpoint.rule4.min-trailing-silence=1.0
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,17 @@
|
||||
10015
|
||||
10016
|
||||
10017
|
||||
10018
|
||||
10019
|
||||
10020
|
||||
10021
|
||||
10022
|
||||
10023
|
||||
10024
|
||||
10025
|
||||
10026
|
||||
10027
|
||||
10028
|
||||
10029
|
||||
10030
|
||||
10031
|
||||
@@ -0,0 +1,166 @@
|
||||
1 nonword
|
||||
2 begin
|
||||
3 end
|
||||
4 internal
|
||||
5 singleton
|
||||
6 nonword
|
||||
7 begin
|
||||
8 end
|
||||
9 internal
|
||||
10 singleton
|
||||
11 begin
|
||||
12 end
|
||||
13 internal
|
||||
14 singleton
|
||||
15 begin
|
||||
16 end
|
||||
17 internal
|
||||
18 singleton
|
||||
19 begin
|
||||
20 end
|
||||
21 internal
|
||||
22 singleton
|
||||
23 begin
|
||||
24 end
|
||||
25 internal
|
||||
26 singleton
|
||||
27 begin
|
||||
28 end
|
||||
29 internal
|
||||
30 singleton
|
||||
31 begin
|
||||
32 end
|
||||
33 internal
|
||||
34 singleton
|
||||
35 begin
|
||||
36 end
|
||||
37 internal
|
||||
38 singleton
|
||||
39 begin
|
||||
40 end
|
||||
41 internal
|
||||
42 singleton
|
||||
43 begin
|
||||
44 end
|
||||
45 internal
|
||||
46 singleton
|
||||
47 begin
|
||||
48 end
|
||||
49 internal
|
||||
50 singleton
|
||||
51 begin
|
||||
52 end
|
||||
53 internal
|
||||
54 singleton
|
||||
55 begin
|
||||
56 end
|
||||
57 internal
|
||||
58 singleton
|
||||
59 begin
|
||||
60 end
|
||||
61 internal
|
||||
62 singleton
|
||||
63 begin
|
||||
64 end
|
||||
65 internal
|
||||
66 singleton
|
||||
67 begin
|
||||
68 end
|
||||
69 internal
|
||||
70 singleton
|
||||
71 begin
|
||||
72 end
|
||||
73 internal
|
||||
74 singleton
|
||||
75 begin
|
||||
76 end
|
||||
77 internal
|
||||
78 singleton
|
||||
79 begin
|
||||
80 end
|
||||
81 internal
|
||||
82 singleton
|
||||
83 begin
|
||||
84 end
|
||||
85 internal
|
||||
86 singleton
|
||||
87 begin
|
||||
88 end
|
||||
89 internal
|
||||
90 singleton
|
||||
91 begin
|
||||
92 end
|
||||
93 internal
|
||||
94 singleton
|
||||
95 begin
|
||||
96 end
|
||||
97 internal
|
||||
98 singleton
|
||||
99 begin
|
||||
100 end
|
||||
101 internal
|
||||
102 singleton
|
||||
103 begin
|
||||
104 end
|
||||
105 internal
|
||||
106 singleton
|
||||
107 begin
|
||||
108 end
|
||||
109 internal
|
||||
110 singleton
|
||||
111 begin
|
||||
112 end
|
||||
113 internal
|
||||
114 singleton
|
||||
115 begin
|
||||
116 end
|
||||
117 internal
|
||||
118 singleton
|
||||
119 begin
|
||||
120 end
|
||||
121 internal
|
||||
122 singleton
|
||||
123 begin
|
||||
124 end
|
||||
125 internal
|
||||
126 singleton
|
||||
127 begin
|
||||
128 end
|
||||
129 internal
|
||||
130 singleton
|
||||
131 begin
|
||||
132 end
|
||||
133 internal
|
||||
134 singleton
|
||||
135 begin
|
||||
136 end
|
||||
137 internal
|
||||
138 singleton
|
||||
139 begin
|
||||
140 end
|
||||
141 internal
|
||||
142 singleton
|
||||
143 begin
|
||||
144 end
|
||||
145 internal
|
||||
146 singleton
|
||||
147 begin
|
||||
148 end
|
||||
149 internal
|
||||
150 singleton
|
||||
151 begin
|
||||
152 end
|
||||
153 internal
|
||||
154 singleton
|
||||
155 begin
|
||||
156 end
|
||||
157 internal
|
||||
158 singleton
|
||||
159 begin
|
||||
160 end
|
||||
161 internal
|
||||
162 singleton
|
||||
163 begin
|
||||
164 end
|
||||
165 internal
|
||||
166 singleton
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
[
|
||||
1.682383e+11 -1.1595e+10 -1.521733e+10 4.32034e+09 -2.257938e+10 -1.969666e+10 -2.559265e+10 -1.535687e+10 -1.276854e+10 -4.494483e+09 -1.209085e+10 -5.64008e+09 -1.134847e+10 -3.419512e+09 -1.079542e+10 -4.145463e+09 -6.637486e+09 -1.11318e+09 -3.479773e+09 -1.245932e+08 -1.386961e+09 6.560655e+07 -2.436518e+08 -4.032432e+07 4.620046e+08 -7.714964e+07 9.551484e+08 -4.119761e+08 8.208582e+08 -7.117156e+08 7.457703e+08 -4.3106e+08 1.202726e+09 2.904036e+08 1.231931e+09 3.629848e+08 6.366939e+08 -4.586172e+08 -5.267629e+08 -3.507819e+08 1.679838e+09
|
||||
1.741141e+13 8.92488e+11 8.743834e+11 8.848896e+11 1.190313e+12 1.160279e+12 1.300066e+12 1.005678e+12 9.39335e+11 8.089614e+11 7.927041e+11 6.882427e+11 6.444235e+11 5.151451e+11 4.825723e+11 3.210106e+11 2.720254e+11 1.772539e+11 1.248102e+11 6.691599e+10 3.599804e+10 1.207574e+10 1.679301e+09 4.594778e+08 5.821614e+09 1.451758e+10 2.55803e+10 3.43277e+10 4.245286e+10 4.784859e+10 4.988591e+10 4.925451e+10 5.074584e+10 4.9557e+10 4.407876e+10 3.421443e+10 3.138606e+10 2.539716e+10 1.948134e+10 1.381167e+10 0 ]
|
||||
@@ -0,0 +1 @@
|
||||
# configuration file for apply-cmvn-online, used in the script ../local/run_online_decoding.sh
|
||||
@@ -0,0 +1,2 @@
|
||||
--left-context=3
|
||||
--right-context=3
|
||||
Reference in New Issue
Block a user