First commit!
This commit is contained in:
parent
3c97134e2f
commit
ace1e61285
17
AI C2 Server/AI C2 Server.csproj
Normal file
17
AI C2 Server/AI C2 Server.csproj
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
<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>
|
||||||
|
|
||||||
|
</Project>
|
||||||
16
AI C2 Server/Logger.cs
Normal file
16
AI C2 Server/Logger.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
// File: Logger.cs
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace AI_C2_Server
|
||||||
|
{
|
||||||
|
public static class Logger
|
||||||
|
{
|
||||||
|
public static event Action<string> LogReceived;
|
||||||
|
|
||||||
|
public static void Log(string message)
|
||||||
|
{
|
||||||
|
Console.WriteLine(message);
|
||||||
|
LogReceived?.Invoke(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
398
AI C2 Server/MainForm.Designer.cs
generated
Normal file
398
AI C2 Server/MainForm.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,398 @@
|
||||||
|
namespace AI_C2_Server
|
||||||
|
{
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
components = new System.ComponentModel.Container();
|
||||||
|
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
|
||||||
|
btnRestart = new Button();
|
||||||
|
panelTop = new Panel();
|
||||||
|
notifyIcon = new NotifyIcon(components);
|
||||||
|
trayContextMenu = new ContextMenuStrip(components);
|
||||||
|
showServerToolStripMenuItem = new ToolStripMenuItem();
|
||||||
|
restartServerToolStripMenuItem = new ToolStripMenuItem();
|
||||||
|
exitShutdownToolStripMenuItem = new ToolStripMenuItem();
|
||||||
|
restartApplicationToolStripMenuItem = new ToolStripMenuItem();
|
||||||
|
tabControl1 = new TabControl();
|
||||||
|
tabPageConsole = new TabPage();
|
||||||
|
rtbConsole = new TextBox();
|
||||||
|
tabPageInstances = new TabPage();
|
||||||
|
listViewInstances = new ListView();
|
||||||
|
colPlatform = new ColumnHeader();
|
||||||
|
colInstanceId = new ColumnHeader();
|
||||||
|
colStatus = new ColumnHeader();
|
||||||
|
colLastSeen = new ColumnHeader();
|
||||||
|
instanceContextMenu = new ContextMenuStrip(components);
|
||||||
|
resetChatToolStripMenuItem = new ToolStripMenuItem();
|
||||||
|
shutdownHarnessToolStripMenuItem = new ToolStripMenuItem();
|
||||||
|
panelInstanceActions = new Panel();
|
||||||
|
btnShutdown = new Button();
|
||||||
|
btnResetChat = new Button();
|
||||||
|
tabPageOpenAI = new TabPage();
|
||||||
|
txtOpenAIKey = new TextBox();
|
||||||
|
lblKey = new Label();
|
||||||
|
txtOpenAIUrl = new TextBox();
|
||||||
|
lblUrl = new Label();
|
||||||
|
chkEnableOpenAI = new CheckBox();
|
||||||
|
timerUpdateInstances = new System.Windows.Forms.Timer(components);
|
||||||
|
panelTop.SuspendLayout();
|
||||||
|
trayContextMenu.SuspendLayout();
|
||||||
|
tabControl1.SuspendLayout();
|
||||||
|
tabPageConsole.SuspendLayout();
|
||||||
|
tabPageInstances.SuspendLayout();
|
||||||
|
instanceContextMenu.SuspendLayout();
|
||||||
|
panelInstanceActions.SuspendLayout();
|
||||||
|
tabPageOpenAI.SuspendLayout();
|
||||||
|
SuspendLayout();
|
||||||
|
//
|
||||||
|
// btnRestart
|
||||||
|
//
|
||||||
|
btnRestart.Location = new Point(12, 12);
|
||||||
|
btnRestart.Name = "btnRestart";
|
||||||
|
btnRestart.Size = new Size(120, 25);
|
||||||
|
btnRestart.TabIndex = 0;
|
||||||
|
btnRestart.Text = "Restart Server";
|
||||||
|
btnRestart.UseVisualStyleBackColor = true;
|
||||||
|
btnRestart.Click += btnRestart_Click;
|
||||||
|
//
|
||||||
|
// panelTop
|
||||||
|
//
|
||||||
|
panelTop.Controls.Add(btnRestart);
|
||||||
|
panelTop.Dock = DockStyle.Top;
|
||||||
|
panelTop.Location = new Point(0, 0);
|
||||||
|
panelTop.Name = "panelTop";
|
||||||
|
panelTop.Size = new Size(800, 50);
|
||||||
|
panelTop.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// notifyIcon
|
||||||
|
//
|
||||||
|
notifyIcon.BalloonTipText = "Server Running...";
|
||||||
|
notifyIcon.BalloonTipTitle = "AI C2 Server";
|
||||||
|
notifyIcon.ContextMenuStrip = trayContextMenu;
|
||||||
|
notifyIcon.Icon = (Icon)resources.GetObject("notifyIcon.Icon");
|
||||||
|
notifyIcon.Text = "AI C2 Server";
|
||||||
|
notifyIcon.MouseClick += notifyIcon_Click;
|
||||||
|
notifyIcon.MouseDoubleClick += notifyIcon_MouseDoubleClick;
|
||||||
|
//
|
||||||
|
// trayContextMenu
|
||||||
|
//
|
||||||
|
trayContextMenu.Items.AddRange(new ToolStripItem[] { showServerToolStripMenuItem, restartServerToolStripMenuItem, restartApplicationToolStripMenuItem, exitShutdownToolStripMenuItem });
|
||||||
|
trayContextMenu.Name = "trayContextMenu";
|
||||||
|
trayContextMenu.RenderMode = ToolStripRenderMode.System;
|
||||||
|
trayContextMenu.Size = new Size(175, 92);
|
||||||
|
//
|
||||||
|
// showServerToolStripMenuItem
|
||||||
|
//
|
||||||
|
showServerToolStripMenuItem.Name = "showServerToolStripMenuItem";
|
||||||
|
showServerToolStripMenuItem.Size = new Size(174, 22);
|
||||||
|
showServerToolStripMenuItem.Text = "Show Server";
|
||||||
|
showServerToolStripMenuItem.Click += showServerToolStripMenuItem_Click;
|
||||||
|
//
|
||||||
|
// restartServerToolStripMenuItem
|
||||||
|
//
|
||||||
|
restartServerToolStripMenuItem.Name = "restartServerToolStripMenuItem";
|
||||||
|
restartServerToolStripMenuItem.Size = new Size(174, 22);
|
||||||
|
restartServerToolStripMenuItem.Text = "Restart Server";
|
||||||
|
restartServerToolStripMenuItem.Click += restartServerToolStripMenuItem_Click;
|
||||||
|
//
|
||||||
|
// exitShutdownToolStripMenuItem
|
||||||
|
//
|
||||||
|
exitShutdownToolStripMenuItem.Name = "exitShutdownToolStripMenuItem";
|
||||||
|
exitShutdownToolStripMenuItem.Size = new Size(174, 22);
|
||||||
|
exitShutdownToolStripMenuItem.Text = "Shutdown && Exit";
|
||||||
|
exitShutdownToolStripMenuItem.Click += exitShutdownToolStripMenuItem_Click;
|
||||||
|
//
|
||||||
|
// restartApplicationToolStripMenuItem
|
||||||
|
//
|
||||||
|
restartApplicationToolStripMenuItem.Name = "restartApplicationToolStripMenuItem";
|
||||||
|
restartApplicationToolStripMenuItem.Size = new Size(174, 22);
|
||||||
|
restartApplicationToolStripMenuItem.Text = "Restart Application";
|
||||||
|
restartApplicationToolStripMenuItem.Click += restartApplicationToolStripMenuItem_Click;
|
||||||
|
//
|
||||||
|
// tabControl1
|
||||||
|
//
|
||||||
|
tabControl1.Controls.Add(tabPageConsole);
|
||||||
|
tabControl1.Controls.Add(tabPageInstances);
|
||||||
|
tabControl1.Controls.Add(tabPageOpenAI);
|
||||||
|
tabControl1.Dock = DockStyle.Fill;
|
||||||
|
tabControl1.Location = new Point(0, 50);
|
||||||
|
tabControl1.Name = "tabControl1";
|
||||||
|
tabControl1.SelectedIndex = 0;
|
||||||
|
tabControl1.Size = new Size(800, 400);
|
||||||
|
tabControl1.TabIndex = 1;
|
||||||
|
//
|
||||||
|
// tabPageConsole
|
||||||
|
//
|
||||||
|
tabPageConsole.BackColor = SystemColors.Control;
|
||||||
|
tabPageConsole.Controls.Add(rtbConsole);
|
||||||
|
tabPageConsole.Location = new Point(4, 24);
|
||||||
|
tabPageConsole.Name = "tabPageConsole";
|
||||||
|
tabPageConsole.Padding = new Padding(3);
|
||||||
|
tabPageConsole.Size = new Size(792, 372);
|
||||||
|
tabPageConsole.TabIndex = 0;
|
||||||
|
tabPageConsole.Text = "Console";
|
||||||
|
//
|
||||||
|
// rtbConsole
|
||||||
|
//
|
||||||
|
rtbConsole.BackColor = Color.Black;
|
||||||
|
rtbConsole.BorderStyle = BorderStyle.FixedSingle;
|
||||||
|
rtbConsole.Dock = DockStyle.Fill;
|
||||||
|
rtbConsole.ForeColor = Color.LightGray;
|
||||||
|
rtbConsole.Location = new Point(3, 3);
|
||||||
|
rtbConsole.Multiline = true;
|
||||||
|
rtbConsole.Name = "rtbConsole";
|
||||||
|
rtbConsole.ReadOnly = true;
|
||||||
|
rtbConsole.ScrollBars = ScrollBars.Vertical;
|
||||||
|
rtbConsole.Size = new Size(786, 366);
|
||||||
|
rtbConsole.TabIndex = 2;
|
||||||
|
//
|
||||||
|
// tabPageInstances
|
||||||
|
//
|
||||||
|
tabPageInstances.BackColor = SystemColors.Control;
|
||||||
|
tabPageInstances.Controls.Add(listViewInstances);
|
||||||
|
tabPageInstances.Controls.Add(panelInstanceActions);
|
||||||
|
tabPageInstances.Location = new Point(4, 24);
|
||||||
|
tabPageInstances.Name = "tabPageInstances";
|
||||||
|
tabPageInstances.Padding = new Padding(3);
|
||||||
|
tabPageInstances.Size = new Size(792, 372);
|
||||||
|
tabPageInstances.TabIndex = 1;
|
||||||
|
tabPageInstances.Text = "Instance Management";
|
||||||
|
//
|
||||||
|
// listViewInstances
|
||||||
|
//
|
||||||
|
listViewInstances.BorderStyle = BorderStyle.FixedSingle;
|
||||||
|
listViewInstances.Columns.AddRange(new ColumnHeader[] { colPlatform, colInstanceId, colStatus, colLastSeen });
|
||||||
|
listViewInstances.ContextMenuStrip = instanceContextMenu;
|
||||||
|
listViewInstances.Dock = DockStyle.Fill;
|
||||||
|
listViewInstances.FullRowSelect = true;
|
||||||
|
listViewInstances.GridLines = true;
|
||||||
|
listViewInstances.Location = new Point(3, 3);
|
||||||
|
listViewInstances.MultiSelect = false;
|
||||||
|
listViewInstances.Name = "listViewInstances";
|
||||||
|
listViewInstances.Size = new Size(786, 326);
|
||||||
|
listViewInstances.TabIndex = 1;
|
||||||
|
listViewInstances.UseCompatibleStateImageBehavior = false;
|
||||||
|
listViewInstances.View = View.Details;
|
||||||
|
//
|
||||||
|
// colPlatform
|
||||||
|
//
|
||||||
|
colPlatform.Text = "Platform";
|
||||||
|
colPlatform.Width = 150;
|
||||||
|
//
|
||||||
|
// colInstanceId
|
||||||
|
//
|
||||||
|
colInstanceId.Text = "Instance ID";
|
||||||
|
colInstanceId.Width = 250;
|
||||||
|
//
|
||||||
|
// colStatus
|
||||||
|
//
|
||||||
|
colStatus.Text = "Status";
|
||||||
|
colStatus.Width = 100;
|
||||||
|
//
|
||||||
|
// colLastSeen
|
||||||
|
//
|
||||||
|
colLastSeen.Text = "Last Seen";
|
||||||
|
colLastSeen.Width = 150;
|
||||||
|
//
|
||||||
|
// instanceContextMenu
|
||||||
|
//
|
||||||
|
instanceContextMenu.Items.AddRange(new ToolStripItem[] { resetChatToolStripMenuItem, shutdownHarnessToolStripMenuItem });
|
||||||
|
instanceContextMenu.Name = "instanceContextMenu";
|
||||||
|
instanceContextMenu.Size = new Size(174, 48);
|
||||||
|
//
|
||||||
|
// resetChatToolStripMenuItem
|
||||||
|
//
|
||||||
|
resetChatToolStripMenuItem.Name = "resetChatToolStripMenuItem";
|
||||||
|
resetChatToolStripMenuItem.Size = new Size(173, 22);
|
||||||
|
resetChatToolStripMenuItem.Text = "Reset Chat";
|
||||||
|
resetChatToolStripMenuItem.Click += resetChatToolStripMenuItem_Click;
|
||||||
|
//
|
||||||
|
// shutdownHarnessToolStripMenuItem
|
||||||
|
//
|
||||||
|
shutdownHarnessToolStripMenuItem.Name = "shutdownHarnessToolStripMenuItem";
|
||||||
|
shutdownHarnessToolStripMenuItem.Size = new Size(173, 22);
|
||||||
|
shutdownHarnessToolStripMenuItem.Text = "Shutdown Harness";
|
||||||
|
shutdownHarnessToolStripMenuItem.Click += shutdownHarnessToolStripMenuItem_Click;
|
||||||
|
//
|
||||||
|
// panelInstanceActions
|
||||||
|
//
|
||||||
|
panelInstanceActions.Controls.Add(btnShutdown);
|
||||||
|
panelInstanceActions.Controls.Add(btnResetChat);
|
||||||
|
panelInstanceActions.Dock = DockStyle.Bottom;
|
||||||
|
panelInstanceActions.Location = new Point(3, 329);
|
||||||
|
panelInstanceActions.Name = "panelInstanceActions";
|
||||||
|
panelInstanceActions.Size = new Size(786, 40);
|
||||||
|
panelInstanceActions.TabIndex = 0;
|
||||||
|
//
|
||||||
|
// btnShutdown
|
||||||
|
//
|
||||||
|
btnShutdown.Location = new Point(111, 8);
|
||||||
|
btnShutdown.Name = "btnShutdown";
|
||||||
|
btnShutdown.Size = new Size(120, 25);
|
||||||
|
btnShutdown.TabIndex = 1;
|
||||||
|
btnShutdown.Text = "Shutdown Harness";
|
||||||
|
btnShutdown.UseVisualStyleBackColor = true;
|
||||||
|
btnShutdown.Click += btnShutdown_Click;
|
||||||
|
//
|
||||||
|
// btnResetChat
|
||||||
|
//
|
||||||
|
btnResetChat.Location = new Point(5, 8);
|
||||||
|
btnResetChat.Name = "btnResetChat";
|
||||||
|
btnResetChat.Size = new Size(100, 25);
|
||||||
|
btnResetChat.TabIndex = 0;
|
||||||
|
btnResetChat.Text = "Reset Chat";
|
||||||
|
btnResetChat.UseVisualStyleBackColor = true;
|
||||||
|
btnResetChat.Click += btnResetChat_Click;
|
||||||
|
//
|
||||||
|
// tabPageOpenAI
|
||||||
|
//
|
||||||
|
tabPageOpenAI.BackColor = SystemColors.Control;
|
||||||
|
tabPageOpenAI.Controls.Add(txtOpenAIKey);
|
||||||
|
tabPageOpenAI.Controls.Add(lblKey);
|
||||||
|
tabPageOpenAI.Controls.Add(txtOpenAIUrl);
|
||||||
|
tabPageOpenAI.Controls.Add(lblUrl);
|
||||||
|
tabPageOpenAI.Controls.Add(chkEnableOpenAI);
|
||||||
|
tabPageOpenAI.Location = new Point(4, 24);
|
||||||
|
tabPageOpenAI.Name = "tabPageOpenAI";
|
||||||
|
tabPageOpenAI.Padding = new Padding(3);
|
||||||
|
tabPageOpenAI.Size = new Size(792, 372);
|
||||||
|
tabPageOpenAI.TabIndex = 2;
|
||||||
|
tabPageOpenAI.Text = "OpenAI API";
|
||||||
|
//
|
||||||
|
// txtOpenAIKey
|
||||||
|
//
|
||||||
|
txtOpenAIKey.BorderStyle = BorderStyle.FixedSingle;
|
||||||
|
txtOpenAIKey.Location = new Point(100, 85);
|
||||||
|
txtOpenAIKey.Name = "txtOpenAIKey";
|
||||||
|
txtOpenAIKey.Size = new Size(400, 23);
|
||||||
|
txtOpenAIKey.TabIndex = 4;
|
||||||
|
txtOpenAIKey.TextChanged += txtOpenAIKey_TextChanged;
|
||||||
|
//
|
||||||
|
// lblKey
|
||||||
|
//
|
||||||
|
lblKey.AutoSize = true;
|
||||||
|
lblKey.Location = new Point(20, 88);
|
||||||
|
lblKey.Name = "lblKey";
|
||||||
|
lblKey.Size = new Size(50, 15);
|
||||||
|
lblKey.TabIndex = 3;
|
||||||
|
lblKey.Text = "API Key:";
|
||||||
|
//
|
||||||
|
// txtOpenAIUrl
|
||||||
|
//
|
||||||
|
txtOpenAIUrl.BorderStyle = BorderStyle.FixedSingle;
|
||||||
|
txtOpenAIUrl.Location = new Point(100, 50);
|
||||||
|
txtOpenAIUrl.Name = "txtOpenAIUrl";
|
||||||
|
txtOpenAIUrl.Size = new Size(400, 23);
|
||||||
|
txtOpenAIUrl.TabIndex = 2;
|
||||||
|
txtOpenAIUrl.TextChanged += txtOpenAIUrl_TextChanged;
|
||||||
|
//
|
||||||
|
// lblUrl
|
||||||
|
//
|
||||||
|
lblUrl.AutoSize = true;
|
||||||
|
lblUrl.Location = new Point(20, 53);
|
||||||
|
lblUrl.Name = "lblUrl";
|
||||||
|
lblUrl.Size = new Size(52, 15);
|
||||||
|
lblUrl.TabIndex = 1;
|
||||||
|
lblUrl.Text = "API URL:";
|
||||||
|
//
|
||||||
|
// chkEnableOpenAI
|
||||||
|
//
|
||||||
|
chkEnableOpenAI.AutoSize = true;
|
||||||
|
chkEnableOpenAI.Location = new Point(20, 20);
|
||||||
|
chkEnableOpenAI.Name = "chkEnableOpenAI";
|
||||||
|
chkEnableOpenAI.Size = new Size(159, 19);
|
||||||
|
chkEnableOpenAI.TabIndex = 0;
|
||||||
|
chkEnableOpenAI.Text = "Enable OpenAI API Mode";
|
||||||
|
chkEnableOpenAI.UseVisualStyleBackColor = true;
|
||||||
|
chkEnableOpenAI.CheckedChanged += chkEnableOpenAI_CheckedChanged;
|
||||||
|
//
|
||||||
|
// timerUpdateInstances
|
||||||
|
//
|
||||||
|
timerUpdateInstances.Interval = 2000;
|
||||||
|
timerUpdateInstances.Tick += timerUpdateInstances_Tick;
|
||||||
|
//
|
||||||
|
// MainForm
|
||||||
|
//
|
||||||
|
AutoScaleDimensions = new SizeF(7F, 15F);
|
||||||
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
|
ClientSize = new Size(800, 450);
|
||||||
|
Controls.Add(tabControl1);
|
||||||
|
Controls.Add(panelTop);
|
||||||
|
Icon = (Icon)resources.GetObject("$this.Icon");
|
||||||
|
Name = "MainForm";
|
||||||
|
Text = "AI C2 Server Console";
|
||||||
|
FormClosing += MainForm_FormClosing;
|
||||||
|
Load += MainForm_Load;
|
||||||
|
Resize += MainForm_Resize;
|
||||||
|
panelTop.ResumeLayout(false);
|
||||||
|
trayContextMenu.ResumeLayout(false);
|
||||||
|
tabControl1.ResumeLayout(false);
|
||||||
|
tabPageConsole.ResumeLayout(false);
|
||||||
|
tabPageConsole.PerformLayout();
|
||||||
|
tabPageInstances.ResumeLayout(false);
|
||||||
|
instanceContextMenu.ResumeLayout(false);
|
||||||
|
panelInstanceActions.ResumeLayout(false);
|
||||||
|
tabPageOpenAI.ResumeLayout(false);
|
||||||
|
tabPageOpenAI.PerformLayout();
|
||||||
|
ResumeLayout(false);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
private System.Windows.Forms.Button btnRestart;
|
||||||
|
private System.Windows.Forms.Panel panelTop;
|
||||||
|
private System.Windows.Forms.NotifyIcon notifyIcon;
|
||||||
|
private System.Windows.Forms.ContextMenuStrip trayContextMenu;
|
||||||
|
private System.Windows.Forms.ToolStripMenuItem showServerToolStripMenuItem;
|
||||||
|
private System.Windows.Forms.ToolStripMenuItem restartServerToolStripMenuItem;
|
||||||
|
private System.Windows.Forms.ToolStripMenuItem exitShutdownToolStripMenuItem;
|
||||||
|
private System.Windows.Forms.TabControl tabControl1;
|
||||||
|
private System.Windows.Forms.TabPage tabPageConsole;
|
||||||
|
private System.Windows.Forms.TextBox rtbConsole;
|
||||||
|
private System.Windows.Forms.TabPage tabPageInstances;
|
||||||
|
private System.Windows.Forms.TabPage tabPageOpenAI;
|
||||||
|
private System.Windows.Forms.ListView listViewInstances;
|
||||||
|
private System.Windows.Forms.ColumnHeader colPlatform;
|
||||||
|
private System.Windows.Forms.ColumnHeader colInstanceId;
|
||||||
|
private System.Windows.Forms.ColumnHeader colStatus;
|
||||||
|
private System.Windows.Forms.ColumnHeader colLastSeen;
|
||||||
|
private System.Windows.Forms.Panel panelInstanceActions;
|
||||||
|
private System.Windows.Forms.Button btnShutdown;
|
||||||
|
private System.Windows.Forms.Button btnResetChat;
|
||||||
|
private System.Windows.Forms.TextBox txtOpenAIKey;
|
||||||
|
private System.Windows.Forms.Label lblKey;
|
||||||
|
private System.Windows.Forms.TextBox txtOpenAIUrl;
|
||||||
|
private System.Windows.Forms.Label lblUrl;
|
||||||
|
private System.Windows.Forms.CheckBox chkEnableOpenAI;
|
||||||
|
private System.Windows.Forms.Timer timerUpdateInstances;
|
||||||
|
private System.Windows.Forms.ContextMenuStrip instanceContextMenu;
|
||||||
|
private System.Windows.Forms.ToolStripMenuItem resetChatToolStripMenuItem;
|
||||||
|
private System.Windows.Forms.ToolStripMenuItem shutdownHarnessToolStripMenuItem;
|
||||||
|
private ToolStripMenuItem restartApplicationToolStripMenuItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
287
AI C2 Server/MainForm.cs
Normal file
287
AI C2 Server/MainForm.cs
Normal file
|
|
@ -0,0 +1,287 @@
|
||||||
|
// File: MainForm.cs
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
|
||||||
|
namespace AI_C2_Server
|
||||||
|
{
|
||||||
|
public partial class MainForm : Form
|
||||||
|
{
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
public static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab);
|
||||||
|
|
||||||
|
public MainForm()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
Logger.LogReceived += Logger_LogReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainForm_Load(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
notifyIcon.Visible = false;
|
||||||
|
|
||||||
|
// Load initial OpenAI settings
|
||||||
|
chkEnableOpenAI.Checked = OperationCenter.Instance.UseOpenAIMode;
|
||||||
|
txtOpenAIUrl.Text = OperationCenter.Instance.OpenAIApiUrl;
|
||||||
|
txtOpenAIKey.Text = OperationCenter.Instance.OpenAIApiKey;
|
||||||
|
|
||||||
|
WebServer.Instance.Start();
|
||||||
|
timerUpdateInstances.Start();
|
||||||
|
|
||||||
|
this.Deactivate += MainForm_Deactivate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainForm_Deactivate(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (!this.Disposing && !this.IsDisposed)
|
||||||
|
{
|
||||||
|
notifyIcon?.Visible = true;
|
||||||
|
this.Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||||
|
{
|
||||||
|
timerUpdateInstances.Stop();
|
||||||
|
WebServer.Instance.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Logger_LogReceived(string message)
|
||||||
|
{
|
||||||
|
if (rtbConsole.InvokeRequired)
|
||||||
|
{
|
||||||
|
rtbConsole.Invoke(new Action(() => Logger_LogReceived(message)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rtbConsole.AppendText($"[{DateTime.Now:HH:mm:ss}] {message}" + Environment.NewLine);
|
||||||
|
|
||||||
|
if (rtbConsole.Lines.Length > 1000)
|
||||||
|
{
|
||||||
|
int excessLines = rtbConsole.Lines.Length - 1000;
|
||||||
|
int indexToCut = rtbConsole.GetFirstCharIndexFromLine(excessLines);
|
||||||
|
|
||||||
|
rtbConsole.Select(0, indexToCut);
|
||||||
|
rtbConsole.SelectedText = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
rtbConsole.ScrollToCaret();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestartServer()
|
||||||
|
{
|
||||||
|
Logger.Log("[SYSTEM] Restarting server and clearing state...");
|
||||||
|
WebServer.Instance.Stop();
|
||||||
|
OperationCenter.Instance.ClearAll();
|
||||||
|
listViewInstances.Items.Clear();
|
||||||
|
|
||||||
|
// Reset UI state for OpenAI
|
||||||
|
chkEnableOpenAI.Checked = false;
|
||||||
|
|
||||||
|
WebServer.Instance.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnRestart_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
RestartServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainForm_Resize(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (this.WindowState == FormWindowState.Minimized)
|
||||||
|
{
|
||||||
|
notifyIcon.Visible = true;
|
||||||
|
this.Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyIcon_MouseDoubleClick(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
RestoreForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyIcon_Click(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Button == MouseButtons.Left)
|
||||||
|
{
|
||||||
|
RestoreForm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showServerToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
RestoreForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RestoreForm()
|
||||||
|
{
|
||||||
|
this.Show();
|
||||||
|
//this.WindowState = FormWindowState.Normal;
|
||||||
|
this.BringToFront();
|
||||||
|
notifyIcon.Visible = false;
|
||||||
|
SwitchToThisWindow(this.Handle, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restartServerToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
RestartServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exitShutdownToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
WebServer.Instance.Stop();
|
||||||
|
OperationCenter.Instance.ClearAll();
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void timerUpdateInstances_Tick(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var instances = OperationCenter.Instance.GetActiveInstances();
|
||||||
|
|
||||||
|
listViewInstances.BeginUpdate();
|
||||||
|
|
||||||
|
// Remove stale items
|
||||||
|
var keysToRemove = new List<ListViewItem>();
|
||||||
|
foreach (ListViewItem item in listViewInstances.Items)
|
||||||
|
{
|
||||||
|
if (!instances.ContainsKey(item.Name)) keysToRemove.Add(item);
|
||||||
|
}
|
||||||
|
foreach (var item in keysToRemove) listViewInstances.Items.Remove(item);
|
||||||
|
|
||||||
|
// Add or update items
|
||||||
|
foreach (var kvp in instances)
|
||||||
|
{
|
||||||
|
string id = kvp.Key;
|
||||||
|
var data = kvp.Value as JsonObject;
|
||||||
|
string platform = data?["platform"]?.ToString() ?? "Unknown";
|
||||||
|
string status = (data?["is_busy"]?.GetValue<bool>() ?? false) ? "Busy" : "Idle";
|
||||||
|
|
||||||
|
DateTime lastSeenTime;
|
||||||
|
string lastSeenStr = data?["last_seen"]?.ToString() ?? "";
|
||||||
|
if (DateTime.TryParse(lastSeenStr, out lastSeenTime))
|
||||||
|
{
|
||||||
|
lastSeenStr = lastSeenTime.ToString("HH:mm:ss");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listViewInstances.Items.ContainsKey(id))
|
||||||
|
{
|
||||||
|
var item = listViewInstances.Items[id];
|
||||||
|
item.SubItems[0].Text = platform;
|
||||||
|
item.SubItems[1].Text = id;
|
||||||
|
item.SubItems[2].Text = status;
|
||||||
|
item.SubItems[3].Text = lastSeenStr;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var item = new ListViewItem(new[] { platform, id, status, lastSeenStr }) { Name = id };
|
||||||
|
listViewInstances.Items.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
listViewInstances.EndUpdate();
|
||||||
|
|
||||||
|
// Keep the checkbox in sync if the OpenAI instance was shut down via the list view
|
||||||
|
if (chkEnableOpenAI.Checked && !OperationCenter.Instance.UseOpenAIMode)
|
||||||
|
{
|
||||||
|
chkEnableOpenAI.Checked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetSelectedInstanceChat()
|
||||||
|
{
|
||||||
|
if (listViewInstances.SelectedItems.Count > 0)
|
||||||
|
{
|
||||||
|
string instanceId = listViewInstances.SelectedItems[0].Name;
|
||||||
|
if (OperationCenter.Instance.IsOpenAIInstance(instanceId))
|
||||||
|
{
|
||||||
|
OperationCenter.Instance.ClearChatHistory(instanceId);
|
||||||
|
Logger.Log($"[ADMIN] Reset chat history for OpenAI instance {instanceId}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OperationCenter.Instance.QueueCommand(new JsonObject { ["type"] = "NEW_CHAT" }, instanceId);
|
||||||
|
Logger.Log($"[ADMIN] Sent NEW_CHAT command to {instanceId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShutdownSelectedInstance()
|
||||||
|
{
|
||||||
|
if (listViewInstances.SelectedItems.Count > 0)
|
||||||
|
{
|
||||||
|
string instanceId = listViewInstances.SelectedItems[0].Name;
|
||||||
|
OperationCenter.Instance.ShutdownInstance(instanceId);
|
||||||
|
Logger.Log($"[ADMIN] Sent SHUTDOWN command to {instanceId} and removed from active list.");
|
||||||
|
listViewInstances.Items.RemoveByKey(instanceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnResetChat_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
ResetSelectedInstanceChat();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnShutdown_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
ShutdownSelectedInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetChatToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
ResetSelectedInstanceChat();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdownHarnessToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
ShutdownSelectedInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void chkEnableOpenAI_CheckedChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (chkEnableOpenAI.Checked)
|
||||||
|
{
|
||||||
|
OperationCenter.Instance.EnableOpenAIMode(txtOpenAIUrl.Text, txtOpenAIKey.Text);
|
||||||
|
Logger.Log($"[SYSTEM] OpenAI API Mode: Enabled");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OperationCenter.Instance.DisableOpenAIMode();
|
||||||
|
Logger.Log($"[SYSTEM] OpenAI API Mode: Disabled");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void txtOpenAIUrl_TextChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (OperationCenter.Instance.UseOpenAIMode)
|
||||||
|
{
|
||||||
|
OperationCenter.Instance.OpenAIApiUrl = txtOpenAIUrl.Text;
|
||||||
|
OperationCenter.Instance.UpdateOpenAIUrl(txtOpenAIUrl.Text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void txtOpenAIKey_TextChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (OperationCenter.Instance.UseOpenAIMode)
|
||||||
|
{
|
||||||
|
OperationCenter.Instance.OpenAIApiKey = txtOpenAIKey.Text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restartApplicationToolStripMenuItem_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
// Start a new instance of the current executable
|
||||||
|
Process.Start(new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = Environment.ProcessPath,
|
||||||
|
UseShellExecute = true
|
||||||
|
});
|
||||||
|
|
||||||
|
WebServer.Instance.Stop();
|
||||||
|
OperationCenter.Instance.ClearAll();
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10667
AI C2 Server/MainForm.resx
Normal file
10667
AI C2 Server/MainForm.resx
Normal file
File diff suppressed because it is too large
Load Diff
429
AI C2 Server/OperationCenter.cs
Normal file
429
AI C2 Server/OperationCenter.cs
Normal file
|
|
@ -0,0 +1,429 @@
|
||||||
|
// File: OperationCenter.cs
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
|
namespace AI_C2_Server
|
||||||
|
{
|
||||||
|
public class OperationCenter
|
||||||
|
{
|
||||||
|
public static OperationCenter Instance { get; } = new OperationCenter();
|
||||||
|
|
||||||
|
private readonly Dictionary<string, Queue<JsonObject>> commandQueues = new();
|
||||||
|
private readonly Dictionary<string, JsonObject> results = new();
|
||||||
|
private readonly Dictionary<string, List<string>> pendingTaskIds = new();
|
||||||
|
private readonly Dictionary<string, JsonObject> instances = new();
|
||||||
|
private readonly Dictionary<string, JsonArray> chatHistory = new();
|
||||||
|
private readonly Dictionary<string, JsonObject> tasks = new();
|
||||||
|
|
||||||
|
// Tracks instances that have been shut down so they aren't recreated by lingering heartbeats
|
||||||
|
private readonly HashSet<string> blacklistedInstances = new();
|
||||||
|
|
||||||
|
private readonly object queueLock = new object();
|
||||||
|
|
||||||
|
// OpenAI Configuration
|
||||||
|
public bool UseOpenAIMode { get; set; } = false;
|
||||||
|
public string OpenAIApiUrl { get; set; } = "http://192.168.12.181:8080/v1/chat/completions";
|
||||||
|
public string OpenAIApiKey { get; set; } = "";
|
||||||
|
public string OpenAIInstanceId { get; private set; }
|
||||||
|
public string OpenAIModelName { get; set; } = "";
|
||||||
|
public string OpenAISystemInstructions { get; set; } = "";
|
||||||
|
|
||||||
|
private OperationCenter() { }
|
||||||
|
|
||||||
|
public void ClearAll()
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
commandQueues.Clear();
|
||||||
|
results.Clear();
|
||||||
|
pendingTaskIds.Clear();
|
||||||
|
instances.Clear();
|
||||||
|
chatHistory.Clear();
|
||||||
|
tasks.Clear();
|
||||||
|
blacklistedInstances.Clear();
|
||||||
|
UseOpenAIMode = false;
|
||||||
|
OpenAIInstanceId = null;
|
||||||
|
OpenAIModelName = "";
|
||||||
|
OpenAISystemInstructions = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsOpenAIInstance(string instanceId)
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(OpenAIInstanceId) && instanceId == OpenAIInstanceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string EnableOpenAIMode(string url, string key)
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
UseOpenAIMode = true;
|
||||||
|
OpenAIApiUrl = url;
|
||||||
|
OpenAIApiKey = key;
|
||||||
|
OpenAIModelName = ""; // Reset model name so it fetches on next run
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(OpenAIInstanceId))
|
||||||
|
{
|
||||||
|
OpenAIInstanceId = $"inst_openai_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
|
||||||
|
}
|
||||||
|
|
||||||
|
instances[OpenAIInstanceId] = new JsonObject
|
||||||
|
{
|
||||||
|
["last_seen"] = DateTime.Now.ToString("o"),
|
||||||
|
["url"] = url,
|
||||||
|
["turn_count"] = 0,
|
||||||
|
["is_busy"] = false,
|
||||||
|
["platform"] = "OpenAI API"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!chatHistory.ContainsKey(OpenAIInstanceId))
|
||||||
|
{
|
||||||
|
chatHistory[OpenAIInstanceId] = new JsonArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Log($"[OP-CENTER] OpenAI API instance registered: {OpenAIInstanceId}");
|
||||||
|
return OpenAIInstanceId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisableOpenAIMode()
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
UseOpenAIMode = false;
|
||||||
|
OpenAIModelName = "";
|
||||||
|
if (!string.IsNullOrEmpty(OpenAIInstanceId))
|
||||||
|
{
|
||||||
|
instances.Remove(OpenAIInstanceId);
|
||||||
|
Logger.Log($"[OP-CENTER] OpenAI API instance removed: {OpenAIInstanceId}");
|
||||||
|
OpenAIInstanceId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateOpenAIUrl(string url)
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
OpenAIModelName = ""; // Reset model name so it fetches again for the new URL
|
||||||
|
if (!string.IsNullOrEmpty(OpenAIInstanceId) && instances.ContainsKey(OpenAIInstanceId))
|
||||||
|
{
|
||||||
|
instances[OpenAIInstanceId]["url"] = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureInstance(string instanceId)
|
||||||
|
{
|
||||||
|
bool isNew = false;
|
||||||
|
if (!commandQueues.ContainsKey(instanceId))
|
||||||
|
{
|
||||||
|
commandQueues[instanceId] = new Queue<JsonObject>();
|
||||||
|
pendingTaskIds[instanceId] = new List<string>();
|
||||||
|
isNew = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!blacklistedInstances.Contains(instanceId) && !instances.ContainsKey(instanceId))
|
||||||
|
{
|
||||||
|
instances[instanceId] = new JsonObject
|
||||||
|
{
|
||||||
|
["last_seen"] = DateTime.Now.ToString("o"),
|
||||||
|
["url"] = "",
|
||||||
|
["turn_count"] = 0,
|
||||||
|
["is_busy"] = false,
|
||||||
|
["platform"] = "Unknown"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isNew)
|
||||||
|
{
|
||||||
|
Logger.Log($"[OP-CENTER] New instance registered: {instanceId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!chatHistory.ContainsKey(instanceId))
|
||||||
|
{
|
||||||
|
chatHistory[instanceId] = new JsonArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateInstanceState(string instanceId, JsonObject stateData)
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
if (blacklistedInstances.Contains(instanceId)) return;
|
||||||
|
|
||||||
|
EnsureInstance(instanceId);
|
||||||
|
|
||||||
|
if (instances.ContainsKey(instanceId))
|
||||||
|
{
|
||||||
|
instances[instanceId]["last_seen"] = DateTime.Now.ToString("o");
|
||||||
|
|
||||||
|
if (stateData != null)
|
||||||
|
{
|
||||||
|
if (stateData.ContainsKey("url"))
|
||||||
|
instances[instanceId]["url"] = stateData["url"]?.ToString();
|
||||||
|
if (stateData.ContainsKey("turn_count"))
|
||||||
|
instances[instanceId]["turn_count"] = stateData["turn_count"]?.GetValue<int>() ?? 0;
|
||||||
|
if (stateData.ContainsKey("is_busy"))
|
||||||
|
instances[instanceId]["is_busy"] = stateData["is_busy"]?.GetValue<bool>() ?? false;
|
||||||
|
if (stateData.ContainsKey("platform"))
|
||||||
|
instances[instanceId]["platform"] = stateData["platform"]?.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject GetActiveInstances()
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
var result = new JsonObject();
|
||||||
|
foreach (var kvp in instances)
|
||||||
|
{
|
||||||
|
result[kvp.Key] = kvp.Value.DeepClone();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetDefaultInstanceId()
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
if (instances.Count == 0) return "default";
|
||||||
|
return instances.OrderByDescending(x => DateTime.Parse(x.Value["last_seen"].ToString())).First().Key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetInstancePlatform(string instanceId)
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
if (instances.TryGetValue(instanceId, out var data))
|
||||||
|
{
|
||||||
|
return data?["platform"]?.ToString() ?? "Unknown";
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string QueueCommand(JsonObject commandData, string instanceId = null)
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(instanceId))
|
||||||
|
{
|
||||||
|
instanceId = GetDefaultInstanceId();
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureInstance(instanceId);
|
||||||
|
|
||||||
|
string taskId = $"CMD-{instanceId.Substring(0, Math.Min(8, instanceId.Length))}-{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
|
||||||
|
|
||||||
|
var command = new JsonObject
|
||||||
|
{
|
||||||
|
["id"] = taskId,
|
||||||
|
["command"] = commandData.DeepClone(),
|
||||||
|
["timestamp"] = DateTime.Now.ToString("o"),
|
||||||
|
["instance_id"] = instanceId
|
||||||
|
};
|
||||||
|
|
||||||
|
tasks[taskId] = commandData.DeepClone().AsObject();
|
||||||
|
|
||||||
|
string cmdType = commandData["type"]?.ToString();
|
||||||
|
if (cmdType == "GENERATE")
|
||||||
|
{
|
||||||
|
chatHistory[instanceId].Add(new JsonObject
|
||||||
|
{
|
||||||
|
["role"] = "User",
|
||||||
|
["text"] = commandData["payload"]?.ToString() ?? ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
commandQueues[instanceId].Enqueue(command);
|
||||||
|
pendingTaskIds[instanceId].Add(taskId);
|
||||||
|
|
||||||
|
return taskId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject GetPendingCommand(string instanceId)
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
if (!commandQueues.ContainsKey(instanceId)) return null;
|
||||||
|
|
||||||
|
if (commandQueues[instanceId].Count > 0)
|
||||||
|
{
|
||||||
|
if (instances.ContainsKey(instanceId))
|
||||||
|
{
|
||||||
|
instances[instanceId]["is_busy"] = true;
|
||||||
|
}
|
||||||
|
return commandQueues[instanceId].Dequeue();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool LogResult(string instanceId, JsonObject resultData)
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
if (instances.ContainsKey(instanceId))
|
||||||
|
{
|
||||||
|
instances[instanceId]["is_busy"] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingTaskIds.ContainsKey(instanceId) && pendingTaskIds[instanceId].Count > 0)
|
||||||
|
{
|
||||||
|
string matchedTaskId = pendingTaskIds[instanceId][0];
|
||||||
|
pendingTaskIds[instanceId].RemoveAt(0);
|
||||||
|
|
||||||
|
if (tasks.TryGetValue(matchedTaskId, out var cmdData))
|
||||||
|
{
|
||||||
|
string cmdType = cmdData["type"]?.ToString();
|
||||||
|
string output = resultData["output"]?.ToString() ?? "";
|
||||||
|
|
||||||
|
if (cmdType == "GET_CHAT_HISTORY")
|
||||||
|
{
|
||||||
|
try { chatHistory[instanceId] = JsonNode.Parse(output).AsArray(); } catch { }
|
||||||
|
}
|
||||||
|
else if ((cmdType == "GENERATE" || cmdType == "INJECT_SHARD") && !output.StartsWith("Error"))
|
||||||
|
{
|
||||||
|
chatHistory[instanceId].Add(new JsonObject { ["role"] = "Model", ["text"] = output });
|
||||||
|
}
|
||||||
|
else if (cmdType == "NEW_CHAT")
|
||||||
|
{
|
||||||
|
chatHistory[instanceId].Clear();
|
||||||
|
}
|
||||||
|
else if (cmdType == "DELETE_TURN" && !output.StartsWith("Error"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int idx = cmdData["payload"]?.GetValue<int>() ?? -1;
|
||||||
|
if (idx < 0) idx = chatHistory[instanceId].Count + idx;
|
||||||
|
if (idx >= 0 && idx < chatHistory[instanceId].Count)
|
||||||
|
{
|
||||||
|
chatHistory[instanceId].RemoveAt(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results[matchedTaskId] = new JsonObject
|
||||||
|
{
|
||||||
|
["received_at"] = DateTime.Now.ToString("o"),
|
||||||
|
["data"] = resultData.DeepClone(),
|
||||||
|
["instance_id"] = instanceId
|
||||||
|
};
|
||||||
|
|
||||||
|
Logger.Log($"[OP-CENTER] Task {matchedTaskId} Completed on Instance {instanceId}.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Log($"[OP-CENTER] Warning: Result received from {instanceId} but no tasks were pending.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonObject GetResult(string taskId)
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
if (results.TryGetValue(taskId, out var res))
|
||||||
|
{
|
||||||
|
return res.DeepClone().AsObject();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CancelPendingTask(string instanceId, string taskId)
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
if (pendingTaskIds.ContainsKey(instanceId))
|
||||||
|
{
|
||||||
|
if (pendingTaskIds[instanceId].Remove(taskId))
|
||||||
|
{
|
||||||
|
Logger.Log($"[OP-CENTER] Task {taskId} timed out and was removed from pending queue for {instanceId}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShutdownInstance(string instanceId)
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
if (IsOpenAIInstance(instanceId))
|
||||||
|
{
|
||||||
|
DisableOpenAIMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue the shutdown command so the harness receives it on its next poll
|
||||||
|
QueueCommand(new JsonObject { ["type"] = "SHUTDOWN" }, instanceId);
|
||||||
|
|
||||||
|
// Blacklist and remove from active instances so it disappears from the UI
|
||||||
|
// and cannot be recreated by lingering heartbeats
|
||||||
|
blacklistedInstances.Add(instanceId);
|
||||||
|
instances.Remove(instanceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetInstanceBusy(string instanceId, bool isBusy)
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
if (instances.ContainsKey(instanceId))
|
||||||
|
{
|
||||||
|
instances[instanceId]["is_busy"] = isBusy;
|
||||||
|
instances[instanceId]["last_seen"] = DateTime.Now.ToString("o");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddChatHistory(string instanceId, string role, string text)
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
if (!chatHistory.ContainsKey(instanceId)) chatHistory[instanceId] = new JsonArray();
|
||||||
|
chatHistory[instanceId].Add(new JsonObject { ["role"] = role, ["text"] = text });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearChatHistory(string instanceId)
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
if (chatHistory.ContainsKey(instanceId)) chatHistory[instanceId].Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasChatHistory(string instanceId)
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
return chatHistory.ContainsKey(instanceId) && chatHistory[instanceId].Count > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonArray GetChatHistory(string instanceId)
|
||||||
|
{
|
||||||
|
lock (queueLock)
|
||||||
|
{
|
||||||
|
if (chatHistory.TryGetValue(instanceId, out var history))
|
||||||
|
{
|
||||||
|
return history.DeepClone().AsArray();
|
||||||
|
}
|
||||||
|
return new JsonArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
AI C2 Server/Program.cs
Normal file
18
AI C2 Server/Program.cs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
namespace AI_C2_Server
|
||||||
|
{
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
502
AI C2 Server/WebServer.cs
Normal file
502
AI C2 Server/WebServer.cs
Normal file
|
|
@ -0,0 +1,502 @@
|
||||||
|
// File: WebServer.cs
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace AI_C2_Server
|
||||||
|
{
|
||||||
|
public class WebServer
|
||||||
|
{
|
||||||
|
public static WebServer Instance { get; } = new WebServer();
|
||||||
|
|
||||||
|
private HttpListener _listener;
|
||||||
|
private bool _isRunning;
|
||||||
|
private static readonly HttpClient _httpClient = new HttpClient();
|
||||||
|
|
||||||
|
private WebServer() { }
|
||||||
|
|
||||||
|
public void Start(int port = 8080)
|
||||||
|
{
|
||||||
|
if (_isRunning) return;
|
||||||
|
|
||||||
|
_listener = new HttpListener();
|
||||||
|
_listener.Prefixes.Add($"http://localhost:{port}/");
|
||||||
|
_listener.Prefixes.Add($"http://127.0.0.1:{port}/");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_listener.Start();
|
||||||
|
_isRunning = true;
|
||||||
|
Task.Run(ListenLoop);
|
||||||
|
Logger.Log($"[SYSTEM] Initializing C2 Server on Port {port}...");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Log($"[ERROR] Failed to start server: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
if (!_isRunning) return;
|
||||||
|
_isRunning = false;
|
||||||
|
_listener?.Stop();
|
||||||
|
_listener?.Close();
|
||||||
|
Logger.Log("[SYSTEM] Server stopped.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ListenLoop()
|
||||||
|
{
|
||||||
|
while (_isRunning)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var context = await _listener.GetContextAsync();
|
||||||
|
_ = Task.Run(() => ProcessRequestAsync(context));
|
||||||
|
}
|
||||||
|
catch (HttpListenerException)
|
||||||
|
{
|
||||||
|
// Thrown when listener is stopped, safe to ignore
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Log($"[ERROR] Listener loop error: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ProcessRequestAsync(HttpListenerContext context)
|
||||||
|
{
|
||||||
|
var request = context.Request;
|
||||||
|
var response = context.Response;
|
||||||
|
|
||||||
|
// CORS Headers
|
||||||
|
response.AddHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
response.AddHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
||||||
|
response.AddHeader("Access-Control-Allow-Headers", "Content-Type");
|
||||||
|
|
||||||
|
if (request.HttpMethod == "OPTIONS")
|
||||||
|
{
|
||||||
|
response.StatusCode = 200;
|
||||||
|
response.Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string path = request.Url.AbsolutePath.TrimEnd('/');
|
||||||
|
|
||||||
|
// Extract instance ID for logging
|
||||||
|
string instanceId = "";
|
||||||
|
if (path.StartsWith("/api/relay/state/")) instanceId = path.Substring("/api/relay/state/".Length);
|
||||||
|
else if (path.StartsWith("/api/relay/command/")) instanceId = path.Substring("/api/relay/command/".Length);
|
||||||
|
else if (path.StartsWith("/api/relay/result/")) instanceId = path.Substring("/api/relay/result/".Length);
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(instanceId))
|
||||||
|
{
|
||||||
|
string platform = OperationCenter.Instance.GetInstancePlatform(instanceId);
|
||||||
|
Logger.Log($"[HTTP] [{platform} | {instanceId}] {request.HttpMethod} {path}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Logger.Log($"[HTTP] {request.HttpMethod} {path}");
|
||||||
|
}
|
||||||
|
|
||||||
|
string body = "";
|
||||||
|
|
||||||
|
if (request.HasEntityBody)
|
||||||
|
{
|
||||||
|
using var reader = new StreamReader(request.InputStream, request.ContentEncoding);
|
||||||
|
body = await reader.ReadToEndAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode payload = null;
|
||||||
|
if (!string.IsNullOrWhiteSpace(body))
|
||||||
|
{
|
||||||
|
try { payload = JsonNode.Parse(body); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- RELAY ENDPOINTS ---
|
||||||
|
if (path.StartsWith("/api/relay/state/"))
|
||||||
|
{
|
||||||
|
OperationCenter.Instance.UpdateInstanceState(instanceId, payload as JsonObject);
|
||||||
|
SendJson(response, new JsonObject { ["status"] = "ok" });
|
||||||
|
}
|
||||||
|
else if (path.StartsWith("/api/relay/command/"))
|
||||||
|
{
|
||||||
|
OperationCenter.Instance.UpdateInstanceState(instanceId, new JsonObject());
|
||||||
|
var cmd = OperationCenter.Instance.GetPendingCommand(instanceId);
|
||||||
|
SendJson(response, cmd ?? new JsonObject());
|
||||||
|
}
|
||||||
|
else if (path.StartsWith("/api/relay/result/"))
|
||||||
|
{
|
||||||
|
OperationCenter.Instance.LogResult(instanceId, payload as JsonObject);
|
||||||
|
SendJson(response, new JsonObject { ["status"] = "acknowledged" });
|
||||||
|
}
|
||||||
|
// --- ADMIN ENDPOINTS ---
|
||||||
|
else if (path == "/api/admin/instances")
|
||||||
|
{
|
||||||
|
SendJson(response, OperationCenter.Instance.GetActiveInstances());
|
||||||
|
}
|
||||||
|
else if (path == "/api/admin/inject")
|
||||||
|
{
|
||||||
|
await HandleCommand(response, "GENERATE", payload?["prompt"]?.DeepClone(), GetBool(payload, "blocking", true), GetInt(payload, "timeout", 600), GetString(payload, "instance_id"));
|
||||||
|
}
|
||||||
|
else if (path == "/api/admin/history")
|
||||||
|
{
|
||||||
|
string id = request.QueryString["instance_id"];
|
||||||
|
await HandleCommand(response, "SCRAPE_HISTORY", null, true, 10, id);
|
||||||
|
}
|
||||||
|
else if (path == "/api/admin/session/analyze")
|
||||||
|
{
|
||||||
|
await HandleCommand(response, "ANALYZE_SESSION", null, true, 5, GetString(payload, "instance_id"));
|
||||||
|
}
|
||||||
|
else if (path == "/api/admin/upload_media")
|
||||||
|
{
|
||||||
|
if (payload?["data"] == null || payload?["mime"] == null)
|
||||||
|
{
|
||||||
|
SendJson(response, new JsonObject { ["error"] = "Invalid Media Payload" }, 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await HandleCommand(response, "UPLOAD_MEDIA", payload, true, 30, GetString(payload, "instance_id"));
|
||||||
|
}
|
||||||
|
else if (path == "/api/admin/action/new_chat")
|
||||||
|
{
|
||||||
|
await HandleCommand(response, "NEW_CHAT", null, true, 30, GetString(payload, "instance_id"));
|
||||||
|
}
|
||||||
|
else if (path == "/api/admin/action/toggle_url_context")
|
||||||
|
{
|
||||||
|
await HandleCommand(response, "SET_URL_CONTEXT", GetBool(payload, "enabled", true), true, 30, GetString(payload, "instance_id"));
|
||||||
|
}
|
||||||
|
else if (path == "/api/admin/action/resolution")
|
||||||
|
{
|
||||||
|
await HandleCommand(response, "SET_RESOLUTION", GetString(payload, "level", "Default"), true, 30, GetString(payload, "instance_id"));
|
||||||
|
}
|
||||||
|
else if (path == "/api/admin/action/delete")
|
||||||
|
{
|
||||||
|
await HandleCommand(response, "DELETE_TURN", GetInt(payload, "index", -1), true, 30, GetString(payload, "instance_id"));
|
||||||
|
}
|
||||||
|
else if (path == "/api/admin/action/settings")
|
||||||
|
{
|
||||||
|
await HandleCommand(response, "SET_SETTINGS", payload, true, 45, GetString(payload, "instance_id"));
|
||||||
|
}
|
||||||
|
else if (path == "/api/admin/action/get_settings")
|
||||||
|
{
|
||||||
|
await HandleCommand(response, "GET_SETTINGS", null, true, 60, GetString(payload, "instance_id"));
|
||||||
|
}
|
||||||
|
else if (path == "/api/admin/action/get_chat_history")
|
||||||
|
{
|
||||||
|
string id = GetString(payload, "instance_id");
|
||||||
|
bool forceSync = GetBool(payload, "force_sync", false);
|
||||||
|
|
||||||
|
if (!forceSync && OperationCenter.Instance.HasChatHistory(id))
|
||||||
|
{
|
||||||
|
SendJson(response, new JsonObject
|
||||||
|
{
|
||||||
|
["status"] = "completed",
|
||||||
|
["output"] = OperationCenter.Instance.GetChatHistory(id).ToJsonString(),
|
||||||
|
["timestamp"] = DateTime.Now.ToString("o"),
|
||||||
|
["instance_id"] = id
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await HandleCommand(response, "GET_CHAT_HISTORY", null, true, 120, id);
|
||||||
|
}
|
||||||
|
else if (path == "/api/admin/action/create_shard")
|
||||||
|
{
|
||||||
|
await HandleCommand(response, "CREATE_SHARD", null, true, 600, GetString(payload, "instance_id"));
|
||||||
|
}
|
||||||
|
else if (path == "/api/admin/action/inject_shard")
|
||||||
|
{
|
||||||
|
if (payload?["shard_data"] == null)
|
||||||
|
{
|
||||||
|
SendJson(response, new JsonObject { ["error"] = "Missing shard_data" }, 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await HandleCommand(response, "INJECT_SHARD", payload["shard_data"]?.DeepClone(), true, 600, GetString(payload, "instance_id"));
|
||||||
|
}
|
||||||
|
else if (path == "/api/admin/action/inject_doc")
|
||||||
|
{
|
||||||
|
if (payload?["content"] == null)
|
||||||
|
{
|
||||||
|
SendJson(response, new JsonObject { ["error"] = "Missing content" }, 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await HandleCommand(response, "INJECT_DOC", payload, true, 30, GetString(payload, "instance_id"));
|
||||||
|
}
|
||||||
|
else if (path == "/api/admin/openai/system_instructions")
|
||||||
|
{
|
||||||
|
string instructions = GetString(payload, "instructions", "");
|
||||||
|
OperationCenter.Instance.OpenAISystemInstructions = instructions;
|
||||||
|
SendJson(response, new JsonObject { ["status"] = "completed", ["output"] = "System instructions updated." });
|
||||||
|
}
|
||||||
|
else if (path.StartsWith("/api/admin/result/"))
|
||||||
|
{
|
||||||
|
string taskId = path.Substring("/api/admin/result/".Length);
|
||||||
|
var res = OperationCenter.Instance.GetResult(taskId);
|
||||||
|
if (res != null)
|
||||||
|
{
|
||||||
|
SendJson(response, new JsonObject { ["status"] = "completed", ["result"] = res.DeepClone() });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SendJson(response, new JsonObject { ["status"] = "pending" }, 202);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SendJson(response, new JsonObject { ["error"] = "Not Found" }, 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Log($"[ERROR] Request processing failed: {ex.Message}");
|
||||||
|
SendJson(response, new JsonObject { ["error"] = "Internal Server Error" }, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task HandleCommand(HttpListenerResponse response, string commandType, JsonNode payload, bool blocking, int timeout, string instanceId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(instanceId))
|
||||||
|
{
|
||||||
|
instanceId = OperationCenter.Instance.GetDefaultInstanceId();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- OPENAI API INTERCEPTION ---
|
||||||
|
if (OperationCenter.Instance.IsOpenAIInstance(instanceId))
|
||||||
|
{
|
||||||
|
if (commandType == "GENERATE")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OperationCenter.Instance.SetInstanceBusy(instanceId, true);
|
||||||
|
string prompt = payload?.ToString() ?? "";
|
||||||
|
|
||||||
|
// Auto-detect model if not cached
|
||||||
|
string modelName = OperationCenter.Instance.OpenAIModelName;
|
||||||
|
if (string.IsNullOrEmpty(modelName))
|
||||||
|
{
|
||||||
|
string modelsUrl = OperationCenter.Instance.OpenAIApiUrl.Replace("/chat/completions", "/models");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var modelReq = new HttpRequestMessage(HttpMethod.Get, modelsUrl);
|
||||||
|
if (!string.IsNullOrEmpty(OperationCenter.Instance.OpenAIApiKey))
|
||||||
|
{
|
||||||
|
modelReq.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", OperationCenter.Instance.OpenAIApiKey);
|
||||||
|
}
|
||||||
|
var modelRes = await _httpClient.SendAsync(modelReq);
|
||||||
|
if (modelRes.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
string modelStr = await modelRes.Content.ReadAsStringAsync();
|
||||||
|
var modelJson = JsonNode.Parse(modelStr);
|
||||||
|
var dataArray = modelJson?["data"] as JsonArray;
|
||||||
|
if (dataArray != null && dataArray.Count > 0)
|
||||||
|
{
|
||||||
|
modelName = dataArray[0]?["id"]?.ToString();
|
||||||
|
OperationCenter.Instance.OpenAIModelName = modelName;
|
||||||
|
Logger.Log($"[OPENAI] Auto-detected model: {modelName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Logger.Log($"[OPENAI] Warning: Failed to fetch models from {modelsUrl}. Using 'default'. Error: {ex.Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(modelName))
|
||||||
|
{
|
||||||
|
modelName = "default";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add user prompt to history
|
||||||
|
OperationCenter.Instance.AddChatHistory(instanceId, "User", prompt);
|
||||||
|
|
||||||
|
// Build messages array from history
|
||||||
|
var messagesArray = new JsonArray();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(OperationCenter.Instance.OpenAISystemInstructions))
|
||||||
|
{
|
||||||
|
messagesArray.Add(new JsonObject { ["role"] = "system", ["content"] = OperationCenter.Instance.OpenAISystemInstructions });
|
||||||
|
}
|
||||||
|
|
||||||
|
var history = OperationCenter.Instance.GetChatHistory(instanceId);
|
||||||
|
foreach (var msg in history)
|
||||||
|
{
|
||||||
|
string role = msg["role"]?.ToString().ToLower() == "user" ? "user" : "assistant";
|
||||||
|
messagesArray.Add(new JsonObject { ["role"] = role, ["content"] = msg["text"]?.ToString() });
|
||||||
|
}
|
||||||
|
|
||||||
|
var openAiRequest = new JsonObject
|
||||||
|
{
|
||||||
|
["model"] = modelName,
|
||||||
|
["messages"] = messagesArray
|
||||||
|
};
|
||||||
|
|
||||||
|
var requestMsg = new HttpRequestMessage(HttpMethod.Post, OperationCenter.Instance.OpenAIApiUrl);
|
||||||
|
if (!string.IsNullOrEmpty(OperationCenter.Instance.OpenAIApiKey))
|
||||||
|
{
|
||||||
|
requestMsg.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", OperationCenter.Instance.OpenAIApiKey);
|
||||||
|
}
|
||||||
|
requestMsg.Content = new StringContent(openAiRequest.ToJsonString(), Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
Logger.Log($"[OPENAI] Sending request to {OperationCenter.Instance.OpenAIApiUrl}");
|
||||||
|
var responseMsg = await _httpClient.SendAsync(requestMsg);
|
||||||
|
string resultStr = await responseMsg.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
OperationCenter.Instance.SetInstanceBusy(instanceId, false);
|
||||||
|
|
||||||
|
if (!responseMsg.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
Logger.Log($"[OPENAI ERROR] {responseMsg.StatusCode}: {resultStr}");
|
||||||
|
SendJson(response, new JsonObject { ["error"] = $"OpenAI API Error: {responseMsg.StatusCode}" }, 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonResult = JsonNode.Parse(resultStr);
|
||||||
|
string outputText = jsonResult?["choices"]?[0]?["message"]?["content"]?.ToString() ?? resultStr;
|
||||||
|
|
||||||
|
// Strip out <think>...</think> blocks generated by DeepSeek R1 models
|
||||||
|
outputText = Regex.Replace(outputText, @"<think>.*?</think>\s*", "", RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
// Handle edge cases where the model omits the opening <think> tag but includes the closing </think> tag
|
||||||
|
outputText = Regex.Replace(outputText, @"^.*?<\/think>\s*", "", RegexOptions.Singleline | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
|
outputText = outputText.Trim();
|
||||||
|
|
||||||
|
// Add assistant response to history
|
||||||
|
OperationCenter.Instance.AddChatHistory(instanceId, "Model", outputText);
|
||||||
|
|
||||||
|
SendJson(response, new JsonObject
|
||||||
|
{
|
||||||
|
["status"] = "completed",
|
||||||
|
["output"] = outputText,
|
||||||
|
["timestamp"] = DateTime.Now.ToString("o"),
|
||||||
|
["instance_id"] = instanceId
|
||||||
|
}, 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
OperationCenter.Instance.SetInstanceBusy(instanceId, false);
|
||||||
|
Logger.Log($"[ERROR] OpenAI API call failed: {ex.Message}");
|
||||||
|
SendJson(response, new JsonObject { ["error"] = "OpenAI API Error: " + ex.Message }, 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (commandType == "NEW_CHAT")
|
||||||
|
{
|
||||||
|
OperationCenter.Instance.ClearChatHistory(instanceId);
|
||||||
|
SendJson(response, new JsonObject { ["status"] = "completed", ["output"] = "Chat reset.", ["timestamp"] = DateTime.Now.ToString("o"), ["instance_id"] = instanceId }, 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (commandType == "SHUTDOWN")
|
||||||
|
{
|
||||||
|
OperationCenter.Instance.ShutdownInstance(instanceId);
|
||||||
|
SendJson(response, new JsonObject { ["status"] = "completed", ["output"] = "Harness shut down.", ["timestamp"] = DateTime.Now.ToString("o"), ["instance_id"] = instanceId }, 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (commandType == "GET_CHAT_HISTORY")
|
||||||
|
{
|
||||||
|
SendJson(response, new JsonObject
|
||||||
|
{
|
||||||
|
["status"] = "completed",
|
||||||
|
["output"] = OperationCenter.Instance.GetChatHistory(instanceId).ToJsonString(),
|
||||||
|
["timestamp"] = DateTime.Now.ToString("o"),
|
||||||
|
["instance_id"] = instanceId
|
||||||
|
}, 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SendJson(response, new JsonObject { ["error"] = $"Command {commandType} not supported in OpenAI API mode.", ["instance_id"] = instanceId }, 400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// -------------------------------
|
||||||
|
|
||||||
|
var cmdData = new JsonObject
|
||||||
|
{
|
||||||
|
["type"] = commandType,
|
||||||
|
["payload"] = payload?.DeepClone()
|
||||||
|
};
|
||||||
|
|
||||||
|
string taskId = OperationCenter.Instance.QueueCommand(cmdData, instanceId);
|
||||||
|
|
||||||
|
if (!blocking)
|
||||||
|
{
|
||||||
|
SendJson(response, new JsonObject { ["status"] = "queued", ["task_id"] = taskId, ["instance_id"] = instanceId }, 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime startTime = DateTime.Now;
|
||||||
|
while ((DateTime.Now - startTime).TotalSeconds < timeout)
|
||||||
|
{
|
||||||
|
var result = OperationCenter.Instance.GetResult(taskId);
|
||||||
|
if (result != null)
|
||||||
|
{
|
||||||
|
SendJson(response, new JsonObject
|
||||||
|
{
|
||||||
|
["status"] = "completed",
|
||||||
|
["output"] = result["data"]?["output"]?.ToString(),
|
||||||
|
["timestamp"] = result["received_at"]?.ToString(),
|
||||||
|
["instance_id"] = result["instance_id"]?.ToString()
|
||||||
|
}, 200);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await Task.Delay(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Timeout handling: Cancel the task so it doesn't permanently block the FIFO queue
|
||||||
|
OperationCenter.Instance.CancelPendingTask(instanceId, taskId);
|
||||||
|
SendJson(response, new JsonObject { ["error"] = "Timeout", ["task_id"] = taskId, ["instance_id"] = instanceId }, 504);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendJson(HttpListenerResponse response, JsonNode data, int statusCode = 200)
|
||||||
|
{
|
||||||
|
response.StatusCode = statusCode;
|
||||||
|
response.ContentType = "application/json";
|
||||||
|
byte[] buffer = Encoding.UTF8.GetBytes(data.ToJsonString());
|
||||||
|
response.ContentLength64 = buffer.Length;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response.OutputStream.Write(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
response.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetString(JsonNode node, string key, string def = null)
|
||||||
|
{
|
||||||
|
if (node is JsonObject obj && obj.TryGetPropertyValue(key, out var val) && val != null)
|
||||||
|
return val.ToString();
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool GetBool(JsonNode node, string key, bool def = false)
|
||||||
|
{
|
||||||
|
if (node is JsonObject obj && obj.TryGetPropertyValue(key, out var val) && val != null)
|
||||||
|
{
|
||||||
|
try { return val.GetValue<bool>(); } catch { return def; }
|
||||||
|
}
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetInt(JsonNode node, string key, int def = 0)
|
||||||
|
{
|
||||||
|
if (node is JsonObject obj && obj.TryGetPropertyValue(key, out var val) && val != null)
|
||||||
|
{
|
||||||
|
try { return val.GetValue<int>(); } catch { return def; }
|
||||||
|
}
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
AI C2 Server/app.ico
Normal file
BIN
AI C2 Server/app.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 308 KiB |
10
AI.Client/AI.Client.csproj
Normal file
10
AI.Client/AI.Client.csproj
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
189
AI.Client/AiClient.cs
Normal file
189
AI.Client/AiClient.cs
Normal file
|
|
@ -0,0 +1,189 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace AI.Client
|
||||||
|
{
|
||||||
|
public class AiClient
|
||||||
|
{
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly string _baseUrl;
|
||||||
|
|
||||||
|
public AiClient(string baseUrl = "http://localhost:8080/api")
|
||||||
|
{
|
||||||
|
_baseUrl = baseUrl.TrimEnd('/');
|
||||||
|
_httpClient = new HttpClient();
|
||||||
|
_httpClient.Timeout = TimeSpan.FromMinutes(15); // Increased to 15 minutes for long generations
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- CORE COMMANDS ---
|
||||||
|
|
||||||
|
public async Task<Dictionary<string, InstanceState>> GetInstancesAsync()
|
||||||
|
{
|
||||||
|
return await GetJsonAsync<Dictionary<string, InstanceState>>("/admin/instances");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AiResponse> SendPromptAsync(string text, string instanceId = null)
|
||||||
|
{
|
||||||
|
var payload = new { prompt = text, blocking = true, instance_id = instanceId };
|
||||||
|
return await PostJsonAsync<AiResponse>("/admin/inject", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AiResponse> NewChatAsync(string instanceId = null)
|
||||||
|
{
|
||||||
|
var payload = new { blocking = true, instance_id = instanceId };
|
||||||
|
return await PostJsonAsync<AiResponse>("/admin/action/new_chat", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AiResponse> ToggleUrlContextAsync(bool enabled, string instanceId = null)
|
||||||
|
{
|
||||||
|
var payload = new { enabled = enabled, blocking = true, instance_id = instanceId };
|
||||||
|
return await PostJsonAsync<AiResponse>("/admin/action/toggle_url_context", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AiResponse> SetResolutionAsync(string level, string instanceId = null)
|
||||||
|
{
|
||||||
|
var payload = new { level = level, blocking = true, instance_id = instanceId };
|
||||||
|
return await PostJsonAsync<AiResponse>("/admin/action/resolution", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AiResponse> DeleteTurnAsync(int index, string instanceId = null)
|
||||||
|
{
|
||||||
|
var payload = new { index = index, blocking = true, instance_id = instanceId };
|
||||||
|
return await PostJsonAsync<AiResponse>("/admin/action/delete", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- ADVANCED COMMANDS ---
|
||||||
|
|
||||||
|
public async Task<AiResponse> SetSettingsAsync(string instanceId, float? temp, float? topP, string model, string sysInst, string thinkingLevel)
|
||||||
|
{
|
||||||
|
// Use a dictionary to avoid sending null values which might confuse the backend
|
||||||
|
var payload = new Dictionary<string, object>();
|
||||||
|
if (!string.IsNullOrEmpty(instanceId)) payload["instance_id"] = instanceId;
|
||||||
|
if (temp.HasValue) payload["temperature"] = temp.Value;
|
||||||
|
if (topP.HasValue) payload["top_p"] = topP.Value;
|
||||||
|
if (!string.IsNullOrEmpty(model)) payload["model"] = model;
|
||||||
|
if (!string.IsNullOrEmpty(sysInst)) payload["system_instructions"] = sysInst;
|
||||||
|
if (!string.IsNullOrEmpty(thinkingLevel)) payload["thinking_level"] = thinkingLevel;
|
||||||
|
|
||||||
|
payload["blocking"] = true;
|
||||||
|
|
||||||
|
return await PostJsonAsync<AiResponse>("/admin/action/settings", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AiResponse> GetSettingsAsync(string instanceId = null)
|
||||||
|
{
|
||||||
|
var payload = new { blocking = true, instance_id = instanceId };
|
||||||
|
return await PostJsonAsync<AiResponse>("/admin/action/get_settings", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AiResponse> GetChatHistoryAsync(string instanceId = null, bool forceSync = false)
|
||||||
|
{
|
||||||
|
var payload = new { blocking = true, instance_id = instanceId, force_sync = forceSync };
|
||||||
|
return await PostJsonAsync<AiResponse>("/admin/action/get_chat_history", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AiResponse> CreateShardAsync(string instanceId = null)
|
||||||
|
{
|
||||||
|
var payload = new { blocking = true, instance_id = instanceId };
|
||||||
|
return await PostJsonAsync<AiResponse>("/admin/action/create_shard", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AiResponse> InjectShardAsync(string shardData, string instanceId = null)
|
||||||
|
{
|
||||||
|
var payload = new { shard_data = shardData, blocking = true, instance_id = instanceId };
|
||||||
|
return await PostJsonAsync<AiResponse>("/admin/action/inject_shard", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AiResponse> InjectDocAsync(string filename, string content, string instanceId = null)
|
||||||
|
{
|
||||||
|
var payload = new { filename = filename, content = content, blocking = true, instance_id = instanceId };
|
||||||
|
return await PostJsonAsync<AiResponse>("/admin/action/inject_doc", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AiResponse> SetOpenAISystemInstructionsAsync(string instructions)
|
||||||
|
{
|
||||||
|
var payload = new { instructions = instructions };
|
||||||
|
return await PostJsonAsync<AiResponse>("/admin/openai/system_instructions", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- INTERNAL HELPERS ---
|
||||||
|
|
||||||
|
private async Task<T> GetJsonAsync<T>(string endpoint)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await _httpClient.GetAsync(_baseUrl + endpoint);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
var responseString = await response.Content.ReadAsStringAsync();
|
||||||
|
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||||
|
return JsonSerializer.Deserialize<T>(responseString, options);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"API GET Failed: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<T> PostJsonAsync<T>(string endpoint, object data)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var json = JsonSerializer.Serialize(data);
|
||||||
|
var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||||
|
|
||||||
|
var response = await _httpClient.PostAsync(_baseUrl + endpoint, content);
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
var responseString = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
|
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
|
||||||
|
return JsonSerializer.Deserialize<T>(responseString, options);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
throw new Exception($"API POST Failed: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- DATA MODELS ---
|
||||||
|
|
||||||
|
public class AiResponse
|
||||||
|
{
|
||||||
|
public string Status { get; set; }
|
||||||
|
public string Task_Id { get; set; }
|
||||||
|
public string Instance_Id { get; set; }
|
||||||
|
public string Mode { get; set; }
|
||||||
|
public string Output { get; set; }
|
||||||
|
public string Error { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InstanceState
|
||||||
|
{
|
||||||
|
public string last_seen { get; set; }
|
||||||
|
public string url { get; set; }
|
||||||
|
public int turn_count { get; set; }
|
||||||
|
public bool is_busy { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AiSettings
|
||||||
|
{
|
||||||
|
public float? Temperature { get; set; }
|
||||||
|
public float? Top_P { get; set; }
|
||||||
|
public string Model { get; set; }
|
||||||
|
public string Thinking_Level { get; set; }
|
||||||
|
public string System_Instructions { get; set; }
|
||||||
|
public string Resolution { get; set; }
|
||||||
|
public bool? Url_Context { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AiChatTurn
|
||||||
|
{
|
||||||
|
public string Role { get; set; }
|
||||||
|
public string Text { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
280
AI.Client/SCL/SclProcessor.cs
Normal file
280
AI.Client/SCL/SclProcessor.cs
Normal file
|
|
@ -0,0 +1,280 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace AI.Client.SCL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the outcome of an SCL command execution.
|
||||||
|
/// </summary>
|
||||||
|
public class SclResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 0 for Success, any non-zero value for Error.
|
||||||
|
/// </summary>
|
||||||
|
public int StatusCode { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The output data or error message to return to the AI.
|
||||||
|
/// </summary>
|
||||||
|
public string Data { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Flag indicating if the response should be omitted entirely to save tokens.
|
||||||
|
/// </summary>
|
||||||
|
public bool OmitResponse { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a successful result.
|
||||||
|
/// </summary>
|
||||||
|
public static SclResult Success(string data = "Success")
|
||||||
|
{
|
||||||
|
return new SclResult { StatusCode = 0, Data = data, OmitResponse = false };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an error result.
|
||||||
|
/// </summary>
|
||||||
|
public static SclResult Error(string errorMessage, int statusCode = 1)
|
||||||
|
{
|
||||||
|
return new SclResult { StatusCode = statusCode, Data = errorMessage, OmitResponse = false };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a result that instructs the processor to NOT send a response back to the AI.
|
||||||
|
/// </summary>
|
||||||
|
public static SclResult NoResponse()
|
||||||
|
{
|
||||||
|
return new SclResult { OmitResponse = true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The core engine for parsing and executing Synapse Command Language (SCL) instructions.
|
||||||
|
/// </summary>
|
||||||
|
public class SclProcessor
|
||||||
|
{
|
||||||
|
// Thread-safe dictionary to hold registered commands and their execution logic.
|
||||||
|
private readonly ConcurrentDictionary<string, Func<string[], Task<SclResult>>> _commandHandlers;
|
||||||
|
|
||||||
|
public SclProcessor()
|
||||||
|
{
|
||||||
|
_commandHandlers = new ConcurrentDictionary<string, Func<string[], Task<SclResult>>>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a new command that the AI can invoke.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandId">The alphanumeric identifier for the command (e.g., "ls", "mouse_move").</param>
|
||||||
|
/// <param name="handler">An asynchronous function that takes an array of string parameters and returns an SclResult.</param>
|
||||||
|
public void RegisterCommand(string commandId, Func<string[], Task<SclResult>> handler)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(commandId))
|
||||||
|
throw new ArgumentException("Command ID cannot be null or whitespace.", nameof(commandId));
|
||||||
|
|
||||||
|
if (commandId.Contains(" ") || commandId.Contains("[") || commandId.Contains("]") || commandId.Contains("|") || commandId.Contains("~") || commandId.Contains("^"))
|
||||||
|
throw new ArgumentException("Command ID contains invalid characters.", nameof(commandId));
|
||||||
|
|
||||||
|
_commandHandlers[commandId] = handler ?? throw new ArgumentNullException(nameof(handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a previously registered command.
|
||||||
|
/// </summary>
|
||||||
|
public bool UnregisterCommand(string commandId)
|
||||||
|
{
|
||||||
|
return _commandHandlers.TryRemove(commandId, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the AI's text output, executes any found SCL commands, and returns the formatted SCL result string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="aiText">The raw text output from the AI.</param>
|
||||||
|
/// <returns>A string containing the concatenated ^ result blocks to be sent back to the AI.</returns>
|
||||||
|
public async Task<string> ProcessTextAsync(string aiText)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(aiText))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var parsedCommands = ParseCommands(aiText);
|
||||||
|
if (parsedCommands.Count == 0)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
StringBuilder resultBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
foreach (var cmd in parsedCommands)
|
||||||
|
{
|
||||||
|
SclResult executionResult;
|
||||||
|
|
||||||
|
if (_commandHandlers.TryGetValue(cmd.CommandId, out var handler))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Execute the registered handler
|
||||||
|
executionResult = await handler(cmd.Parameters);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// Catch unhandled exceptions in the client code to prevent crashing the processor
|
||||||
|
executionResult = SclResult.Error($"Internal Execution Exception: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
executionResult = SclResult.Error($"Unknown command: {cmd.CommandId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the command requested no response (e.g. background GUI app), skip appending it
|
||||||
|
if (executionResult.OmitResponse)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the result back into SCL syntax: ^commandId[statusCode|escapedData]
|
||||||
|
string escapedData = EscapeForScl(executionResult.Data ?? string.Empty);
|
||||||
|
resultBuilder.Append($"^{cmd.CommandId}[{executionResult.StatusCode}|{escapedData}] ");
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultBuilder.ToString().TrimEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal struct to hold parsed command data.
|
||||||
|
/// </summary>
|
||||||
|
private struct ParsedCommand
|
||||||
|
{
|
||||||
|
public string CommandId { get; }
|
||||||
|
public string[] Parameters { get; }
|
||||||
|
|
||||||
|
public ParsedCommand(string commandId, string[] parameters)
|
||||||
|
{
|
||||||
|
CommandId = commandId;
|
||||||
|
Parameters = parameters;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A robust character-stepping state machine that extracts SCL commands while respecting escape characters, quotes, and nested brackets.
|
||||||
|
/// </summary>
|
||||||
|
private List<ParsedCommand> ParseCommands(string input)
|
||||||
|
{
|
||||||
|
var commands = new List<ParsedCommand>();
|
||||||
|
|
||||||
|
for (int i = 0; i < input.Length; i++)
|
||||||
|
{
|
||||||
|
// Look for the Action Trigger
|
||||||
|
if (input[i] == '~')
|
||||||
|
{
|
||||||
|
int bracketIdx = input.IndexOf('[', i);
|
||||||
|
if (bracketIdx == -1) continue; // Malformed, no opening bracket found
|
||||||
|
|
||||||
|
string commandId = input.Substring(i + 1, bracketIdx - i - 1).Trim();
|
||||||
|
if (string.IsNullOrEmpty(commandId)) continue; // Malformed, no command ID
|
||||||
|
|
||||||
|
List<string> parameters = new List<string>();
|
||||||
|
StringBuilder currentParam = new StringBuilder();
|
||||||
|
|
||||||
|
bool isEscaped = false;
|
||||||
|
bool isClosed = false;
|
||||||
|
int bracketDepth = 1; // We just passed the first '['
|
||||||
|
bool inDoubleQuotes = false;
|
||||||
|
bool inSingleQuotes = false;
|
||||||
|
|
||||||
|
int j = bracketIdx + 1;
|
||||||
|
for (; j < input.Length; j++)
|
||||||
|
{
|
||||||
|
char c = input[j];
|
||||||
|
|
||||||
|
if (isEscaped)
|
||||||
|
{
|
||||||
|
// Only consume the escape character if it's escaping an SCL control char.
|
||||||
|
// Otherwise, preserve the backslash (e.g., for Windows file paths like C:\Temp).
|
||||||
|
if (c == '[' || c == ']' || c == '|' || c == '\\')
|
||||||
|
{
|
||||||
|
currentParam.Append(c);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentParam.Append('\\');
|
||||||
|
currentParam.Append(c);
|
||||||
|
}
|
||||||
|
isEscaped = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '\\')
|
||||||
|
{
|
||||||
|
isEscaped = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle quote states
|
||||||
|
if (c == '"' && !inSingleQuotes) inDoubleQuotes = !inDoubleQuotes;
|
||||||
|
if (c == '\'' && !inDoubleQuotes) inSingleQuotes = !inSingleQuotes;
|
||||||
|
|
||||||
|
bool inQuotes = inDoubleQuotes || inSingleQuotes;
|
||||||
|
|
||||||
|
if (!inQuotes && c == '[')
|
||||||
|
{
|
||||||
|
bracketDepth++;
|
||||||
|
currentParam.Append(c);
|
||||||
|
}
|
||||||
|
else if (!inQuotes && c == ']')
|
||||||
|
{
|
||||||
|
bracketDepth--;
|
||||||
|
if (bracketDepth == 0)
|
||||||
|
{
|
||||||
|
parameters.Add(currentParam.ToString());
|
||||||
|
isClosed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentParam.Append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!inQuotes && bracketDepth == 1 && c == '|')
|
||||||
|
{
|
||||||
|
parameters.Add(currentParam.ToString());
|
||||||
|
currentParam.Clear();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentParam.Append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only register the command if the parameter block was properly closed
|
||||||
|
if (isClosed)
|
||||||
|
{
|
||||||
|
commands.Add(new ParsedCommand(commandId, parameters.ToArray()));
|
||||||
|
i = j; // Advance the outer loop past this parsed command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Escapes reserved SCL characters in the output data to ensure the AI parses the result correctly.
|
||||||
|
/// </summary>
|
||||||
|
private string EscapeForScl(string input)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(input)) return input;
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder(input.Length + 10);
|
||||||
|
foreach (char c in input)
|
||||||
|
{
|
||||||
|
if (c == '\\' || c == '|' || c == ']' || c == '[')
|
||||||
|
{
|
||||||
|
sb.Append('\\');
|
||||||
|
}
|
||||||
|
sb.Append(c);
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
158
AI.Client/SCL/SclPromptBuilder.cs
Normal file
158
AI.Client/SCL/SclPromptBuilder.cs
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
// File: SclPromptBuilder.cs
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace AI.Client.SCL
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a definition of an SCL command to be taught to the AI.
|
||||||
|
/// </summary>
|
||||||
|
public class SclCommandDefinition
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The alphanumeric identifier for the command (e.g., "ls", "wr").
|
||||||
|
/// </summary>
|
||||||
|
public string CommandId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A brief description of what the command does.
|
||||||
|
/// </summary>
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A list of parameter names expected by the command.
|
||||||
|
/// </summary>
|
||||||
|
public List<string> Parameters { get; set; }
|
||||||
|
|
||||||
|
public SclCommandDefinition(string commandId, string description, params string[] parameters)
|
||||||
|
{
|
||||||
|
CommandId = commandId;
|
||||||
|
Description = description;
|
||||||
|
Parameters = new List<string>();
|
||||||
|
|
||||||
|
if (parameters != null)
|
||||||
|
{
|
||||||
|
Parameters.AddRange(parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A utility class to programmatically generate the System Instructions prompt for the AI,
|
||||||
|
/// injecting custom SCL commands dynamically.
|
||||||
|
/// </summary>
|
||||||
|
public class SclPromptBuilder
|
||||||
|
{
|
||||||
|
private readonly List<SclCommandDefinition> _commands;
|
||||||
|
private string _agentRole;
|
||||||
|
|
||||||
|
public SclPromptBuilder()
|
||||||
|
{
|
||||||
|
_commands = new List<SclCommandDefinition>();
|
||||||
|
_agentRole = "You are a strict, machine-to-machine execution engine equipped with the Synapse Command Language (SCL). SCL allows you to interact directly with the user's local system via a client program that parses your output.";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Overrides the default agent persona/role at the very beginning of the prompt.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="roleDescription">The custom role description.</param>
|
||||||
|
public void SetAgentRole(string roleDescription)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(roleDescription))
|
||||||
|
{
|
||||||
|
_agentRole = roleDescription;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a command definition to be included in the generated prompt.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="commandId">The command identifier (e.g., "ls").</param>
|
||||||
|
/// <param name="description">What the command does.</param>
|
||||||
|
/// <param name="parameters">The names of the parameters (e.g., "file_path", "content").</param>
|
||||||
|
public void AddCommand(string commandId, string description, params string[] parameters)
|
||||||
|
{
|
||||||
|
_commands.Add(new SclCommandDefinition(commandId, description, parameters));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates the complete System Instructions prompt text.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A formatted string ready to be sent to the AI as System Instructions.</returns>
|
||||||
|
public string GeneratePrompt()
|
||||||
|
{
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
// 1. Agent Role
|
||||||
|
sb.AppendLine(_agentRole);
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// 2. Core SCL Protocol Rules
|
||||||
|
sb.AppendLine("### SCL Protocol Rules (CRITICAL)");
|
||||||
|
sb.AppendLine("You MUST use SCL to perform actions. SCL is designed to be extremely token-efficient.");
|
||||||
|
sb.AppendLine("ABSOLUTELY NO JSON, XML, MARKDOWN, OR RAW SHELL COMMANDS ARE ALLOWED. You must use the exact syntax below.");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("1. ISSUING COMMANDS (Action Trigger: ~)");
|
||||||
|
sb.AppendLine("Syntax: ~command_name[parameter1|parameter2]");
|
||||||
|
sb.AppendLine("* Commands start with a tilde (~).");
|
||||||
|
sb.AppendLine("* Parameters are enclosed in square brackets [ ].");
|
||||||
|
sb.AppendLine("* Multiple parameters are separated by a pipe (|).");
|
||||||
|
sb.AppendLine("* If a parameter contains a literal ], |, or \\, you must escape it with a backslash (e.g., \\], \\|, \\\\).");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("2. RECEIVING RESULTS (Result Trigger: ^)");
|
||||||
|
sb.AppendLine("The client program will reply to your commands in the next prompt using this syntax:");
|
||||||
|
sb.AppendLine("Syntax: ^command_name[status_code|output_data]");
|
||||||
|
sb.AppendLine("* Status 0 means success. Status 1 means error.");
|
||||||
|
sb.AppendLine("* You must wait for the ^ result before assuming a command succeeded, UNLESS the command launches a background/GUI application, in which case the client may omit the response to save tokens and end the interaction.");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// 3. Dynamic Available Commands
|
||||||
|
sb.AppendLine("### Available Client Commands");
|
||||||
|
sb.AppendLine("(Note: The client program currently supports the following commands. You may use them at any time to fulfill the user's requests.)");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
if (_commands.Count == 0)
|
||||||
|
{
|
||||||
|
sb.AppendLine("* (No commands are currently registered by the client program.)");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var cmd in _commands)
|
||||||
|
{
|
||||||
|
string paramString = string.Join("|", cmd.Parameters);
|
||||||
|
sb.AppendLine($"* ~{cmd.CommandId}[{paramString}] : {cmd.Description}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// 4. Execution Guidelines & Strict Constraints
|
||||||
|
sb.AppendLine("### Execution Guidelines & CRITICAL CONSTRAINTS");
|
||||||
|
sb.AppendLine("* ALL INPUT PROMPT WILL BE ENCODED AS BASE64 STRINGS: Any input prompt you receive will be encoded as a base64 string. Make sure you decode the base64 string before processing it.");
|
||||||
|
sb.AppendLine("* STOP AFTER ISSUING A COMMAND: If you issue a command that expects an output result, you MUST STOP generating text and wait for the program to reply with the `^` result.");
|
||||||
|
sb.AppendLine("* NO RAW COMMANDS: NEVER output raw shell commands like `start notepad.exe` or `EXECUTE(notepad)`. You MUST wrap them in the appropriate SCL command, e.g., `~cmd[start notepad.exe]`.");
|
||||||
|
sb.AppendLine("* NO MARKDOWN: NEVER wrap your SCL commands in backticks (`) or markdown code blocks (```).");
|
||||||
|
sb.AppendLine("* NO CONVERSATION: You may NOT output conversational text alongside an SCL command to explain your thought process to the user. ONLY output the SCL command by itself and nothing else.");
|
||||||
|
sb.AppendLine("* Do NOT chain multiple commands together (e.g., do not issue an `ls` and an `rd` in the same response). Issue the first command, wait for the result, and then issue the next.");
|
||||||
|
sb.AppendLine("* If a command fails (returns status 1), analyze the error data provided in the result and attempt to fix the issue or notify the user.");
|
||||||
|
sb.AppendLine();
|
||||||
|
|
||||||
|
// 5. Example Scenario
|
||||||
|
sb.AppendLine("### Example Scenario");
|
||||||
|
sb.AppendLine("User: \"Open notepad\"");
|
||||||
|
sb.AppendLine("WRONG: start notepad.exe");
|
||||||
|
sb.AppendLine("WRONG: ```~cmd[start notepad.exe]```");
|
||||||
|
sb.AppendLine("WRONG: EXECUTE(notepad.exe)");
|
||||||
|
sb.AppendLine("CORRECT: ~cmd[start notepad.exe]");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("User: \"Check what is in C:\\Temp and delete old.txt\"");
|
||||||
|
sb.AppendLine("You: ~cmd[dir C:\\Temp]");
|
||||||
|
sb.AppendLine("Program: ^cmd[0|old.txt\\nnew.txt\\nconfig.ini]");
|
||||||
|
sb.AppendLine("You: ~cmd[del C:\\Temp\\old.txt]");
|
||||||
|
sb.AppendLine("Program: ^cmd[0|Success]");
|
||||||
|
sb.AppendLine("You: The file old.txt has been successfully deleted.");
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
15
AI_State_Machines.slnx
Normal file
15
AI_State_Machines.slnx
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<Solution>
|
||||||
|
<Configurations>
|
||||||
|
<Platform Name="Any CPU" />
|
||||||
|
<Platform Name="x64" />
|
||||||
|
</Configurations>
|
||||||
|
<Project Path="AI C2 Server/AI C2 Server.csproj">
|
||||||
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
|
</Project>
|
||||||
|
<Project Path="AI.Client/AI.Client.csproj">
|
||||||
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
|
</Project>
|
||||||
|
<Project Path="VoiceAssistantPoC_Win/VoiceAssistantPoC_Win.csproj" Id="cfcdcaba-1dd9-4d58-93ad-218ddc587189">
|
||||||
|
<Platform Solution="*|x64" Project="x64" />
|
||||||
|
</Project>
|
||||||
|
</Solution>
|
||||||
4
LICENSE
4
LICENSE
|
|
@ -208,7 +208,7 @@ If you develop a new program, and you want it to be of the greatest possible use
|
||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
|
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
Synapse-OS-Assistant-And-AI-C2-Server
|
AI-State-Machines
|
||||||
Copyright (C) 2026 mastercodeon
|
Copyright (C) 2026 mastercodeon
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
|
|
@ -221,7 +221,7 @@ Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
|
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
Synapse-OS-Assistant-And-AI-C2-Server Copyright (C) 2026 mastercodeon
|
AI-State-Machines Copyright (C) 2026 mastercodeon
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
|
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
|
|
||||||
58
README.md
58
README.md
|
|
@ -1,3 +1,57 @@
|
||||||
# Synapse-OS-Assistant-And-AI-C2-Server
|
# Synapse OS Assistant & AI C2 Server
|
||||||
|
|
||||||
Proof of Concept for using AI as a state machine
|
Welcome to the Synapse OS Assistant project. This repository contains a proof-of-concept system that transforms standard web-based Large Language Models (LLMs) into deterministic, voice-activated operating system controllers.
|
||||||
|
|
||||||
|
By utilizing the custom **Synapse Command Language (SCL)** and a unified browser harness, this project allows you to control your Windows PC using models like Google Gemini, DeepSeek, and Google AI Search—**without needing any paid API keys.**
|
||||||
|
|
||||||
|
## Features
|
||||||
|
* **Synapse Command Language (SCL):** A highly efficient, token-saving syntax that forces LLMs to act as deterministic state machines rather than conversational agents.
|
||||||
|
* **Unified Web Harness:** A Tampermonkey script that puppeteers web-based AI chats, turning them into a local REST API.
|
||||||
|
* **Offline Wake-Word & STT:** Uses Vosk for fast, offline voice recognition ("Computer, do X").
|
||||||
|
* **Zero-Cost OS Integration:** Achieves deep OS control (executing shell commands, opening apps, reading files) using free web AI interfaces.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
* Windows OS (WinForms is used for the UI).
|
||||||
|
* .NET 10.0 (or higher) SDK.
|
||||||
|
* A modern web browser with the [Tampermonkey](https://www.tampermonkey.net/) extension installed.
|
||||||
|
* **Important Browser Note:** If you intend to use Google AI Search, you **MUST use Mozilla Firefox**. Google Chrome restricts extension execution on `chrome://` pages and the default Google Search homepage.
|
||||||
|
|
||||||
|
## Setup Instructions
|
||||||
|
|
||||||
|
### 1. Install the Vosk Voice Model
|
||||||
|
The Voice Assistant PoC requires an offline voice model to function.
|
||||||
|
1. Download the `vosk-model-small-en-us-0.15` model from the [Vosk Models page](https://alphacephei.com/vosk/models).
|
||||||
|
2. Extract the downloaded `.zip` file.
|
||||||
|
3. Rename the extracted folder to exactly `model`.
|
||||||
|
4. Place the `model` folder directly into the build output directory of the `VoiceAssistantPoC_Win` project (e.g., `VoiceAssistantPoC_Win/bin/Debug/net8.0-windows/`).
|
||||||
|
|
||||||
|
### 2. Install the Tampermonkey Harness
|
||||||
|
1. Open your browser and click the Tampermonkey extension icon -> **Create a new script**.
|
||||||
|
2. Copy the entire contents of `GoogleAI_Search_Deepseek_ChatGPT_UnifiedHarness.js` from this repository.
|
||||||
|
3. Paste it into the Tampermonkey editor, overwriting the default template.
|
||||||
|
4. Go to File -> **Save** (or press Ctrl+S).
|
||||||
|
|
||||||
|
### 3. Build and Run the Servers
|
||||||
|
1. Open the solution in Visual Studio.
|
||||||
|
2. Build the solution to restore NuGet packages (such as `NAudio` and `Vosk`).
|
||||||
|
3. Run the **AIStudio.C2Server** project first. You should see the server console open and state that it is listening on port `8080`.
|
||||||
|
4. Open your web browser (Firefox recommended) and navigate to one of the supported AI platforms:
|
||||||
|
* `https://gemini.google.com/`
|
||||||
|
* `https://chat.deepseek.com/`
|
||||||
|
* `https://google.com/ai` (Will not work with the Tampermonkey Harness in Google Chrome!)
|
||||||
|
5. Look at the C2 Server console; you should see a message indicating that a new instance has connected (e.g., `[OP-CENTER] New instance registered: inst_gem_...`).
|
||||||
|
|
||||||
|
### 4. Start the Voice Assistant
|
||||||
|
1. Run the **VoiceAssistantPoC_Win** project.
|
||||||
|
2. In the bottom panel, select your active browser session from the dropdown menu.
|
||||||
|
3. Click **Re Init Session**. This will inject the SCL System Instructions into the browser chat, preparing the AI to act as a state machine.
|
||||||
|
4. Ensure the "Voice Mode" checkbox is checked.
|
||||||
|
5. Speak into your microphone: *"Computer, open notepad."*
|
||||||
|
6. Watch as the AI processes the command, issues the SCL syntax `~cmd[start notepad.exe]`, and the client executes it on your machine!
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
* **ChatGPT is Unsupported:** Due to aggressive RLHF (Reinforcement Learning from Human Feedback), the web version of ChatGPT refuses to strictly adhere to the SCL syntax and will often output conversational filler or markdown, breaking the state machine loop. Please use Gemini or DeepSeek for the best results.
|
||||||
|
* **Browser Focus:** The Tampermonkey script requires the browser tab to be active/visible in some browsers to ensure DOM updates and JavaScript intervals fire reliably.
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
This project allows an AI to execute arbitrary shell commands on your local machine. It is provided as a Proof of Concept for educational and research purposes only. Do not run this software in a production environment or with elevated (Administrator) privileges unless you fully understand the risks.
|
||||||
226
VoiceAssistantPoC_Win/MainForm.Designer.cs
generated
Normal file
226
VoiceAssistantPoC_Win/MainForm.Designer.cs
generated
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
602
VoiceAssistantPoC_Win/MainForm.cs
Normal file
602
VoiceAssistantPoC_Win/MainForm.cs
Normal file
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5389
VoiceAssistantPoC_Win/MainForm.resx
Normal file
5389
VoiceAssistantPoC_Win/MainForm.resx
Normal file
File diff suppressed because it is too large
Load Diff
18
VoiceAssistantPoC_Win/Program.cs
Normal file
18
VoiceAssistantPoC_Win/Program.cs
Normal file
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
71
VoiceAssistantPoC_Win/VoiceAssistantPoC_Win.csproj
Normal file
71
VoiceAssistantPoC_Win/VoiceAssistantPoC_Win.csproj
Normal file
|
|
@ -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>
|
||||||
BIN
VoiceAssistantPoC_Win/app.ico
Normal file
BIN
VoiceAssistantPoC_Win/app.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 308 KiB |
9
VoiceAssistantPoC_Win/model/README
Normal file
9
VoiceAssistantPoC_Win/model/README
Normal file
|
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
BIN
VoiceAssistantPoC_Win/model/am/final.mdl
Normal file
BIN
VoiceAssistantPoC_Win/model/am/final.mdl
Normal file
Binary file not shown.
7
VoiceAssistantPoC_Win/model/conf/mfcc.conf
Normal file
7
VoiceAssistantPoC_Win/model/conf/mfcc.conf
Normal file
|
|
@ -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
|
||||||
10
VoiceAssistantPoC_Win/model/conf/model.conf
Normal file
10
VoiceAssistantPoC_Win/model/conf/model.conf
Normal file
|
|
@ -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
|
||||||
BIN
VoiceAssistantPoC_Win/model/graph/Gr.fst
Normal file
BIN
VoiceAssistantPoC_Win/model/graph/Gr.fst
Normal file
Binary file not shown.
BIN
VoiceAssistantPoC_Win/model/graph/HCLr.fst
Normal file
BIN
VoiceAssistantPoC_Win/model/graph/HCLr.fst
Normal file
Binary file not shown.
17
VoiceAssistantPoC_Win/model/graph/disambig_tid.int
Normal file
17
VoiceAssistantPoC_Win/model/graph/disambig_tid.int
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
10015
|
||||||
|
10016
|
||||||
|
10017
|
||||||
|
10018
|
||||||
|
10019
|
||||||
|
10020
|
||||||
|
10021
|
||||||
|
10022
|
||||||
|
10023
|
||||||
|
10024
|
||||||
|
10025
|
||||||
|
10026
|
||||||
|
10027
|
||||||
|
10028
|
||||||
|
10029
|
||||||
|
10030
|
||||||
|
10031
|
||||||
166
VoiceAssistantPoC_Win/model/graph/phones/word_boundary.int
Normal file
166
VoiceAssistantPoC_Win/model/graph/phones/word_boundary.int
Normal file
|
|
@ -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
|
||||||
BIN
VoiceAssistantPoC_Win/model/ivector/final.dubm
Normal file
BIN
VoiceAssistantPoC_Win/model/ivector/final.dubm
Normal file
Binary file not shown.
BIN
VoiceAssistantPoC_Win/model/ivector/final.ie
Normal file
BIN
VoiceAssistantPoC_Win/model/ivector/final.ie
Normal file
Binary file not shown.
BIN
VoiceAssistantPoC_Win/model/ivector/final.mat
Normal file
BIN
VoiceAssistantPoC_Win/model/ivector/final.mat
Normal file
Binary file not shown.
3
VoiceAssistantPoC_Win/model/ivector/global_cmvn.stats
Normal file
3
VoiceAssistantPoC_Win/model/ivector/global_cmvn.stats
Normal file
|
|
@ -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 ]
|
||||||
1
VoiceAssistantPoC_Win/model/ivector/online_cmvn.conf
Normal file
1
VoiceAssistantPoC_Win/model/ivector/online_cmvn.conf
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
# configuration file for apply-cmvn-online, used in the script ../local/run_online_decoding.sh
|
||||||
2
VoiceAssistantPoC_Win/model/ivector/splice.conf
Normal file
2
VoiceAssistantPoC_Win/model/ivector/splice.conf
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
--left-context=3
|
||||||
|
--right-context=3
|
||||||
Loading…
Reference in New Issue
Block a user