//+------------------------------------------------------------------+ //| 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