Article-22495-Dispatch-Driv.../AI Canvas State.mqh

308 lines
No EOL
13 KiB
MQL5

//+------------------------------------------------------------------+
//| AI Canvas State.mqh |
//| Copyright 2026, Allan Munene Mutiiria. |
//| https://t.me/Forex_Algo_Trader |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026, Allan Munene Mutiiria."
#property link "https://t.me/Forex_Algo_Trader"
//--- Include guard
#ifndef AI_CANVAS_STATE_MQH
#define AI_CANVAS_STATE_MQH
//--- Pull theme for AI_DASHBOARD_X_DEFAULT and AI_DASHBOARD_Y_DEFAULT
#include "AI Canvas Theme.mqh"
//+------------------------------------------------------------------+
//| Canvas Object Names |
//+------------------------------------------------------------------+
#define AI_CANVAS_NAME_MAIN "ChatGPT_AI_MainCanvas" // Main dashboard canvas object
#define AI_CANVAS_NAME_PROMPT "ChatGPT_AI_PromptCanvas" // Prompt overlay canvas object
//+------------------------------------------------------------------+
//| Chat Record Structure |
//+------------------------------------------------------------------+
struct Chat
{
int id; // Unique chat identifier
string title; // Chat display title
string history; // Full conversation transcript
};
//+------------------------------------------------------------------+
//| Chat State |
//+------------------------------------------------------------------+
Chat g_ai_chats[]; // All loaded chats
int g_ai_currentChatId = -1; // Active chat id (-1 = new chat)
string g_ai_currentTitle = ""; // Active chat title
string g_ai_conversationHistory = ""; // Active chat transcript
string g_ai_currentPrompt = ""; // Persisted prompt text
//+------------------------------------------------------------------+
//| UI Visibility Flags |
//+------------------------------------------------------------------+
bool g_ai_sidebarExpanded = true; // Sidebar expanded state
bool g_ai_dashboardVisible = true; // Dashboard visible state
//+------------------------------------------------------------------+
//| Popup State Flags |
//+------------------------------------------------------------------+
bool g_ai_showSmallHistory = false; // Small history popup visible
bool g_ai_showBigHistory = false; // Big history popup visible
bool g_ai_showSearch = false; // Search popup visible
bool g_ai_justOpenedSmall = false; // Skip first-click close on small popup
bool g_ai_justOpenedBig = false; // Skip first-click close on big popup
bool g_ai_justOpenedSearch = false; // Skip first-click close on search popup
string g_ai_searchQuery = ""; // Current search query text
//+------------------------------------------------------------------+
//| Drag State |
//+------------------------------------------------------------------+
bool g_ai_dragging = false; // Header drag in progress
int g_ai_dragOffsetX = 0; // Drag X offset from anchor
int g_ai_dragOffsetY = 0; // Drag Y offset from anchor
//+------------------------------------------------------------------+
//| Dashboard Position |
//+------------------------------------------------------------------+
int AI_DASHBOARD_X = AI_DASHBOARD_X_DEFAULT; // Current dashboard X
int AI_DASHBOARD_Y = AI_DASHBOARD_Y_DEFAULT; // Current dashboard Y
//+------------------------------------------------------------------+
//| Footer Dropdown State |
//+------------------------------------------------------------------+
bool g_ai_showFooterDropdown = false; // Dropdown popup visible
int g_ai_footerDropdownSelectedIdx = 0; // Selected action index
//+------------------------------------------------------------------+
//| Footer Action Dispatch Tables |
//+------------------------------------------------------------------+
const int AI_FOOTER_DD_COUNT = 7; // Number of dropdown actions
const string AI_FOOTER_DD_LABELS[] = {
"Get Chart Data", // 0 - chart data dump
"Twin Bars", // 1 - twin-bars signal check
"Quick Scalp", // 2 - single-bar entry signal
"Daily Signal", // 3 - daily H1 bias signal
"Trend Read", // 4 - trendline anchors
"Key Level", // 5 - significant S/R level
"Clear Drawings" // 6 - wipe signal drawings
};
const int AI_FOOTER_DD_ACTION_IDS[]= { 0, 1, 2, 3, 4, 5, 6 }; // Stable action IDs
const int AI_FOOTER_DD_ICONS[] = {
0, // chart icon
1, // twin bars icon
2, // lightning icon
3, // day icon
4, // trend icon
5, // level icon
6 // close icon
};
//+------------------------------------------------------------------+
//| API Throttle Flag |
//+------------------------------------------------------------------+
bool g_ai_signalRequestInFlight = false; // Block parallel API calls
//+------------------------------------------------------------------+
//| Tick and Animation State |
//+------------------------------------------------------------------+
datetime g_ai_lastBarTime = 0; // Last seen bar time (for new-bar detection)
int g_ai_spinnerCycle = 0; // Loading spinner animation tick
//+------------------------------------------------------------------+
//| Toast Notification State |
//+------------------------------------------------------------------+
string g_ai_toastText = ""; // Active toast text (empty = no toast)
bool g_ai_toastIsError = false;// Pick error vs success color
ulong g_ai_toastExpiryMs = 0; // Tick count when toast expires
//+------------------------------------------------------------------+
//| Pencil Hover Tracking |
//+------------------------------------------------------------------+
bool g_ai_overPencilIcon = false; // Cursor over narrow pencil icon
//+------------------------------------------------------------------+
//| Send Button State Tracking |
//+------------------------------------------------------------------+
bool g_ai_lastRenderedSendDisabled = true; // Disabled state at last footer render
//+------------------------------------------------------------------+
//| Encode integer ID as base62 string |
//+------------------------------------------------------------------+
string AiEncodeID(int id)
{
//--- Setup base62 alphabet
string chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
string res = "";
//--- Special-case zero
if(id == 0) return "0";
//--- Build digits from least significant
while(id > 0)
{
res = StringSubstr(chars, id % 62, 1) + res;
id /= 62;
}
return res;
}
//+------------------------------------------------------------------+
//| Decode base62 string back to integer ID |
//+------------------------------------------------------------------+
int AiDecodeID(string enc)
{
//--- Setup base62 alphabet
string chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
int id = 0;
//--- Accumulate digits from most significant
for(int i = 0; i < StringLen(enc); i++)
{
id = id * 62 + StringFind(chars, StringSubstr(enc, i, 1));
}
return id;
}
//+------------------------------------------------------------------+
//| Find chat array index by chat ID |
//+------------------------------------------------------------------+
int AiGetChatIndex(int id)
{
//--- Linear scan for matching id
for(int i = 0; i < ArraySize(g_ai_chats); i++)
{
if(g_ai_chats[i].id == id) return i;
}
return -1;
}
//+------------------------------------------------------------------+
//| Test if line matches HH:MM timestamp format |
//+------------------------------------------------------------------+
bool AiIsTimestamp(string line)
{
//--- Trim whitespace and check exact length
StringTrimLeft(line);
StringTrimRight(line);
if(StringLen(line) != 5) return false;
//--- Verify colon separator at position 2
if(StringGetCharacter(line, 2) != ':') return false;
//--- Parse and validate hour and minute fields
string hh = StringSubstr(line, 0, 2);
string mm = StringSubstr(line, 3, 2);
int h = (int)StringToInteger(hh);
int m = (int)StringToInteger(mm);
if(h < 0 || h > 23 || m < 0 || m > 59) return false;
return true;
}
//+------------------------------------------------------------------+
//| Split string by delimiter into output array |
//+------------------------------------------------------------------+
int AiSplitOnString(string inputText, string delim, string &result[])
{
//--- Clear output array
ArrayResize(result, 0);
int pos = 0;
int delim_len = StringLen(delim);
//--- Walk delimiter occurrences
while(true)
{
int found = StringFind(inputText, delim, pos);
if(found == -1)
{
//--- Append final remainder
string part = StringSubstr(inputText, pos);
if(StringLen(part) > 0 || ArraySize(result) > 0)
{
int size = ArraySize(result);
ArrayResize(result, size + 1);
result[size] = part;
}
break;
}
//--- Append substring up to delimiter
string part = StringSubstr(inputText, pos, found - pos);
int size = ArraySize(result);
ArrayResize(result, size + 1);
result[size] = part;
pos = found + delim_len;
}
return ArraySize(result);
}
//+------------------------------------------------------------------+
//| Darken a color by scaling each channel toward black |
//+------------------------------------------------------------------+
color AiDarkenColor(color colorValue, double factor = 0.8)
{
//--- Scale RGB channels by factor
int red = int(((colorValue ) & 0xFF) * factor);
int green = int(((colorValue >> 8) & 0xFF) * factor);
int blue = int(((colorValue >> 16) & 0xFF) * factor);
return (color)(red | (green << 8) | (blue << 16));
}
//+------------------------------------------------------------------+
//| Lighten a color by interpolating each channel toward white |
//+------------------------------------------------------------------+
color AiLightenColor(color colorValue, double amount = 0.25)
{
//--- Extract RGB channels
int r = (int)((colorValue ) & 0xFF);
int g = (int)((colorValue >> 8) & 0xFF);
int b = (int)((colorValue >> 16) & 0xFF);
//--- Interpolate each channel toward 255
r = r + (int)((255 - r) * amount);
g = g + (int)((255 - g) * amount);
b = b + (int)((255 - b) * amount);
//--- Clamp at 255
if(r > 255) r = 255; if(g > 255) g = 255; if(b > 255) b = 255;
return (color)(r | (g << 8) | (b << 16));
}
//+------------------------------------------------------------------+
//| Replace standalone double-periods with newlines |
//+------------------------------------------------------------------+
string AiReplaceExactDoublePeriods(string text)
{
//--- Build result by scanning input
string result = "";
int len = StringLen(text);
for(int i = 0; i < len; i++)
{
//--- Detect ".." pattern
if(i + 1 < len && StringGetCharacter(text, i) == '.' && StringGetCharacter(text, i + 1) == '.')
{
//--- Reject when surrounded by additional dots
bool preceded = (i > 0 && StringGetCharacter(text, i - 1) == '.');
bool followed = (i + 2 < len && StringGetCharacter(text, i + 2) == '.');
if(!preceded && !followed)
{
//--- Standalone ".." converts to newline
result += "\n";
i++;
}
else
{
//--- Part of longer dot run - keep literal
result += ".";
}
}
else
{
//--- Copy character through unchanged
result += StringSubstr(text, i, 1);
}
}
return result;
}
#endif // AI_CANVAS_STATE_MQH