Aufbau von KI-gestützten Handelssystemen in MQL5 (Teil 6): Einführung der Chat-Lösch- und Suchfunktionalität
Einführung
In unserem vorigen Artikel (Teil 5) haben wir das in ChatGPT integrierte Programm in MetaQuotes Language 5 (MQL5) verbessert, indem wir eine zusammenklappbare Seitenleiste hinzugefügt haben, die für eine optimierte Bildschirmnutzung zwischen erweitertem und verkleinertem Zustand umschaltet, kleine und große Verlaufs-Popups für eine effiziente Chat-Navigation integriert und eine nahtlose Verarbeitung von mehrzeiligen Eingaben mit persistenter verschlüsselter Speicherung sichergestellt haben. In Teil 6 führen wir die Funktionen zum Löschen von Chats und Suchfunktionen in das KI-gestützte Handelssystem ein. Dieses Upgrade fügt der Seitenleiste interaktive Löschschaltflächen, Verlaufs-Popups und ein neues Such-Popup mit Echtzeit-Filterung hinzu, das es uns ermöglicht, Konversationen zu verwalten, indem wir veraltete Chats entfernen und relevante Chats über Titel- oder Inhaltsabfragen schnell finden. Dabei bleiben die Kernfunktionen des Systems wie KI-gesteuerte Signale aus Chartdaten erhalten. Wir werden die folgenden Themen behandeln:
- Der Wert von Chat-Lösch- und Suchfunktionen in KI-Schnittstellen
- Die Implementation in MQL5
- Testen von Chat-Löschung und Suche
- Schlussfolgerung
Am Ende haben Sie einen MQL5-KI-Handelsassistenten mit fortschrittlichen Chat-Management-Funktionen, der sofort angepasst werden kann - legen wir los!
Der Wert von Chat-Lösch- und Suchfunktionen in KI-Schnittstellen
Die Funktionen zum Löschen von Chats und der Suche in KI-Handelsoberflächen spielen eine entscheidende Rolle bei der Aufrechterhaltung eines organisierten und effizienten Arbeitsbereichs. Sie ermöglichen es uns, irrelevante oder veraltete Konversationen zu entfernen, die den Verlauf überlagern, sodass wir uns auf die aktuelle Marktanalyse konzentrieren können und Verwirrung während stressiger Handelssitzungen vermieden wird. Die Suchfunktion ergänzt dies, indem sie ein schnelles Auffinden bestimmter Chats durch Schlüsselwortabgleiche in Titeln oder Inhalten ermöglicht. Dies spart Zeit, wenn auf frühere KI-generierte Erkenntnisse wie Signalempfehlungen oder Strategiediskussionen Bezug genommen wird, was besonders in volatilen Märkten von Vorteil ist, wo ein historischer Kontext schnelle Entscheidungen ermöglichen kann. Gemeinsam werden diese Maßnahmen die Nutzerfreundlichkeit verbessern, indem sie die kognitive Belastung verringern, den Datenschutz durch selektives Entfernen von Daten fördern und die Gesamtproduktivität verbessern, sodass der KI-Assistent ein rationalisiertes Werkzeug und kein ungeordnetes Archiv bleibt.
Unser Ansatz besteht darin, in der Seitenleiste durch Mauszeigeraktivierung ausgelöste Löschschaltflächen, kleine/große Historie-Popups sowie Suchergebnisse mit Wingdings-Symbolen einzufügen, eine Echtzeitfilterung in einem eigenen Such-Popup mit groß-/kleinschreibungsunabhängiger Suche und einer nutzerdefinierten Bildlaufleiste zu implementieren, die Chat-Speicherlogik so anzupassen, dass Löschvorgänge durchgeführt werden können, während der aktive Chat erhalten bleibt oder zum neuesten gewechselt wird, und nahtlose UI-Aktualisierungen über alle Ansichten hinweg sicherzustellen, um einen besser verwaltbaren KI-Handelsassistenten für eine effiziente Konversationsabwicklung und schnellen Zugriff auf relevante Erkenntnisse zu schaffen. Wir werden auch einige Dinge leicht ändern, um sie an die aktualisierte Nutzeroberfläche anzupassen, z. B. die Verwendung von Umschaltsymbolen, wie wir es in der vorherigen Version versprochen haben. Nachstehend finden Sie eine visuelle Darstellung der von uns angestrebten Merkmale.

Die Implementation in MQL5
Um die Upgrades zu implementieren, werden wir zunächst die Suchobjekte Bildlaufleiste definieren, die wir für das Such-Popup hinzufügen werden. Dies wird den Kriterien entsprechen, wie wir sie schon immer definiert haben. Die anderen Objekte, wie z. B. die Löschschaltflächen und Suchleisten, verwenden die Standard-Präfixverkettung, um das Löschen zu erleichtern.
//+------------------------------------------------------------------+ //| AI ChatGPT EA Part 6.mq5 | //| Copyright 2025, Allan Munene Mutiiria. | //| https://t.me/Forex_Algo_Trader | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, Allan Munene Mutiiria." #property link "https://t.me/Forex_Algo_Trader" #property version "1.00" #property strict #property icon "1. Forex Algo-Trader.ico" #define SEARCH_SCROLL_LEADER "ChatGPT_Search_Scroll_Leader" #define SEARCH_SCROLL_UP_REC "ChatGPT_Search_Scroll_Up_Rec" #define SEARCH_SCROLL_UP_LABEL "ChatGPT_Search_Scroll_Up_Label" #define SEARCH_SCROLL_DOWN_REC "ChatGPT_Search_Scroll_Down_Rec" #define SEARCH_SCROLL_DOWN_LABEL "ChatGPT_Search_Scroll_Down_Label" #define SEARCH_SCROLL_SLIDER "ChatGPT_Search_Scroll_Slider"
Wir definieren Konstanten für die Elemente der Bildlaufleiste des Such-Popups mit #define und vergeben Namen wie „SEARCH_SCROLL_LEADER“ für die Spur, „SEARCH_SCROLL_UP_REC“ und „SEARCH_SCROLL_UP_LABEL“ für die Aufwärts-Schaltfläche und ihren Pfeil, „SEARCH_SCROLL_DOWN_REC“ und „SEARCH_SCROLL_DOWN_LABEL“ für die Abwärts-Schaltfläche und „SEARCH_SCROLL_SLIDER“ für den verschiebbaren Daumen, wodurch eine konsistente Referenzierung bei der Objekterstellung und den Verwaltungsfunktionen zur Handhabung des Bildlaufs in der Suchergebnisansicht gewährleistet wird. Als Nächstes fügen wir neue Farb- und Hover-Variablen für die Elemente Löschen und Suchen hinzu.
color search_original_bg = clrLightSlateGray; color search_darker_bg; bool search_hover = false; color search_close_original_bg = clrLightGray; color search_close_darker_bg; bool search_close_hover = false; color delete_original_bg = clrBeige; color delete_darker_bg; bool showing_search_popup = false; int search_popup_x, search_popup_y, search_popup_w, search_popup_h; string search_popup_objects[]; string search_chat_bgs[]; string search_delete_btns[]; int search_scroll_pos = 0; bool search_scroll_visible = false; int search_total_height = 0; int search_visible_height = 0; int search_slider_height = 20; bool search_movingStateSlider = false; int search_mlbDownX_Slider = 0; int search_mlbDownY_Slider = 0; int search_mlbDown_YD_Slider = 0; bool just_opened_search = false; string current_search_query = "";
Hier definieren wir zusätzliche globale Variablen, um die neuen Such- und Löschfunktionen in der KI-Schnittstelle zu unterstützen, beginnend mit Farben für die Suchschaltfläche („search_original_bg“ als helles Schiefergrau, „search_darker_bg“ für den Hover-Zustand) und einem „search_hover“-Flag, um die Mausinteraktion zu verfolgen und dynamische Farbänderungen für ein besseres Nutzerfeedback zu ermöglichen. In ähnlicher Weise haben wir die Farben für die Schließen-Schaltfläche des Such-Popups („search_close_original_bg“ als hellgrau, „search_close_darker_bg“) mit dem Hover-Flag „search_close_hover“ und für die Schaltflächen zum Löschen („delete_original_bg“ als beige, „delete_darker_bg“), um ein konsistentes Styling für alle Popups und die Seitenleiste zu gewährleisten.
Für das Such-Popup selbst verwenden wir ein Sichtbarkeits-Flag „showing_search_popup“, Koordinaten („search_popup_x/y/w/h“) für die Positionierung und Arrays „search_popup_objects“, „search_chat_bgs“, „search_delete_btns“, um seine Elemente wie Hintergründe und Schaltflächen zu verwalten und aufzuräumen. Wir fügen Unterstützung für das Scrollen hinzu, mit „search_scroll_pos“ für die Position, dem Flag „search_scroll_visible“, den Höhen („search_total_height“, „search_visible_height“, „search_slider_height“ bei 20), den Status des Schiebereglers „search_movingStateSlider“ sowie die Positionen bei gedrückter Maustaste („search_mlbDownX_Slider“, „search_mlbDownY_Slider“, „search_mlbDown_YD_Slider“) für eine flüssige Interaktion.
Das Flag „just_opened_search“ verhindert das sofortige Schließen bei Öffnungsklicks, während „current_search_query“ die Nutzereingaben für die Filterung in Echtzeit speichert, sodass das System dynamische Suchen und Löschungen in verschiedenen Ansichten (Seitenleiste, kleine/große Popups) handhaben und die Chat-Verwaltung verbessern kann, ohne die Hauptschnittstelle zu überladen. Um die Farben für die neuen Elemente zu behandeln, fügen wir der Ereignishandlung von OnInit Folgendes hinzu.
int OnInit() { //--- added init logic search_darker_bg = DarkenColor(search_original_bg); search_close_darker_bg = DarkenColor(search_close_original_bg); delete_darker_bg = DarkenColor(delete_original_bg); //--- the rest remain the same return(INIT_SUCCEEDED); }
In OnInit fügen wir die Initialisierung für neue dunklere Hover-Farben hinzu, die für die Such- und Löschfunktionen spezifisch sind, indem wir „search_darker_bg“ durch Abdunkeln von „search_original_bg“ mit „DarkenColor“, ähnlich für „search_close_darker_bg“ aus „search_close_original_bg“, und „delete_darker_bg“ aus „delete_original_bg“, um visuelles Feedback auf Mausinteraktionen zu ermöglichen. Der Rest der Funktion bleibt gegenüber früheren Versionen unverändert und behandelt das Öffnen der Protokolldatei, die Bildskalierung, die Einrichtung der Seitenleiste, die Erstellung des Dashboards und die Aktivierung von Ereignissen, bevor INIT_SUCCEEDED zurückgegeben wird, um den erfolgreichen Start zu bestätigen. Bevor wir die Schaltflächen zum Löschen hinzufügen, sollten wir eine Logik zum Löschen der Chats definieren.
void DeleteChat(int id) { int idx = GetChatIndex(id); if (idx < 0) return; if (current_chat_id == id) { if (ArraySize(chats) > 1) { int new_idx = (idx == ArraySize(chats) - 1) ? idx - 1 : ArraySize(chats) - 1; current_chat_id = chats[new_idx].id; current_title = chats[new_idx].title; conversationHistory = chats[new_idx].history; } else { CreateNewChat(); return; } } ArrayRemove(chats, idx, 1); SaveChats(); long history_y = ObjectGetInteger(0, "ChatGPT_HistoryButton", OBJPROP_YDISTANCE); if (showing_small_history_popup) { DeleteSmallHistoryPopup(); if (!sidebarExpanded && ArraySize(chats) > 0) { ShowSmallHistoryPopup((int)history_y); } } if (showing_big_history_popup) { DeleteBigHistoryPopup(); if (ArraySize(chats) > 0) { ShowBigHistoryPopup(); } } if (showing_search_popup) { DeleteSearchPopup(); if (ArraySize(chats) > 0) { ShowSearchPopup(); } } UpdateSidebarDynamic(); UpdateResponseDisplay(); UpdatePromptDisplay(); ChartRedraw(); }
Im weiteren Verlauf implementieren wir die Funktion „DeleteChat“, um einen bestimmten Chat nach seiner ID aus dem dauerhaften Speicher und der Nutzeroberfläche zu entfernen und eine reibungslose Handhabung des aktiven Chats zu gewährleisten, um Unterbrechungen zu vermeiden. Zunächst wird der Index des Chats mit „GetChatIndex“ ermittelt und bei Nichtauffinden vorzeitig beendet. Wenn es sich um den aktuellen Chat handelt („current_chat_id“ stimmt überein) und andere Chats existieren, wählen wir einen neuen aus - wobei wir den vorherigen Index bevorzugen, wenn es der letzte ist, und ansonsten den letzten - und aktualisieren „current_chat_id“, „current_title“ und „conversationHistory“; wenn keine Chats übrig bleiben, rufen wir „CreateNewChat“ auf und kehren zurück.
Dann entfernen wir den Chat aus dem Array „chats“ mit ArrayRemove am Index mit Zählung 1, speichern die Änderungen mit „SaveChats“ und aktualisieren die geöffneten Popups: für den kleinen Verlauf löschen wir mit „DeleteSmallHistoryPopup“ und zeigen ihn erneut an, wenn die Seitenleiste geschlossen ist und Chats übrig sind, indem wir die y-Position des History-Buttons von ObjectGetInteger verwenden; ähnlich für große und Such-Popups, wobei wir löschen und erneut anzeigen, wenn Chats existieren. Schließlich aktualisieren wir die Seitenleiste mit „UpdateSidebarDynamic“, aktualisieren die Antwort- und Eingabeaufforderungsanzeigen und zeichnen das Chart mit ChartRedraw neu, um die Löschung zu berücksichtigen. Als Nächstes aktualisieren wir die dynamische Seitenleistenfunktion, um die Erstellung der Löschschaltflächen einzubeziehen.
void UpdateSidebarDynamic() { int total = ObjectsTotal(0, 0, -1); for (int j = total - 1; j >= 0; j--) { string name = ObjectName(0, j, 0, -1); if (StringFind(name, "ChatGPT_NewChatButton") == 0 || StringFind(name, "ChatGPT_ClearButton") == 0 || StringFind(name, "ChatGPT_HistoryButton") == 0 || StringFind(name, "ChatGPT_SearchButton") == 0 || StringFind(name, "ChatGPT_ChatLabel_") == 0 || StringFind(name, "ChatGPT_ChatBg_") == 0 || StringFind(name, "ChatGPT_SidebarLogo") == 0 || StringFind(name, "ChatGPT_NewChatIcon") == 0 || StringFind(name, "ChatGPT_NewChatLabel") == 0 || StringFind(name, "ChatGPT_ClearIcon") == 0 || StringFind(name, "ChatGPT_ClearLabel") == 0 || StringFind(name, "ChatGPT_HistoryIcon") == 0 || StringFind(name, "ChatGPT_HistoryLabel") == 0 || StringFind(name, "ChatGPT_SearchLabel") == 0 || StringFind(name, "ChatGPT_SearchIcon") == 0 || StringFind(name, "ChatGPT_ToggleButton") == 0 || StringFind(name, "ChatGPT_SideDelete_") == 0) { ObjectDelete(0, name); } } ArrayResize(side_chat_bgs, 0); ArrayResize(side_delete_btns, 0); int sidebarX = g_dashboardX; int itemY = g_mainY + 10; string sidebar_logo_resource = sidebarExpanded ? ((StringLen(g_scaled_sidebar_resource) > 0) ? g_scaled_sidebar_resource : resourceImgLogo) : ((StringLen(g_scaled_sidebar_small) > 0) ? g_scaled_sidebar_small : resourceImgLogo); int logo_size = sidebarExpanded ? 81 : 30; createBitmapLabel("ChatGPT_SidebarLogo", sidebarX + (g_sidebarWidth - logo_size)/2, itemY, logo_size, logo_size, sidebar_logo_resource, clrNONE, CORNER_LEFT_UPPER); ObjectSetInteger(0, "ChatGPT_SidebarLogo", OBJPROP_ZORDER, 1); itemY += logo_size + 5; createButton("ChatGPT_SearchButton", sidebarX + 5, itemY, g_sidebarWidth - 10, g_buttonHeight, "", clrWhite, 11, search_original_bg, clrDarkSlateGray); ObjectSetInteger(0, "ChatGPT_SearchButton", OBJPROP_ZORDER, 1); int iconX = sidebarExpanded ? sidebarX + 5 + 10 : sidebarX + (g_sidebarWidth - 20)/2-6; createLabel("ChatGPT_SearchIcon", iconX, itemY + (g_buttonHeight - 20)/2-6, "L", clrWhite, 24, "Webdings", CORNER_LEFT_UPPER); ObjectSetInteger(0, "ChatGPT_SearchIcon", OBJPROP_ZORDER, 2); ObjectSetInteger(0, "ChatGPT_SearchIcon", OBJPROP_SELECTABLE, false); if (sidebarExpanded) { createLabel("ChatGPT_SearchLabel", sidebarX + 5 + 10 + 20 + 5, itemY + (g_buttonHeight - 20)/2, "Search", clrWhite, 11, "Arial", CORNER_LEFT_UPPER); ObjectSetInteger(0, "ChatGPT_SearchLabel", OBJPROP_ZORDER, 2); ObjectSetInteger(0, "ChatGPT_SearchLabel", OBJPROP_SELECTABLE, false); } itemY += g_buttonHeight + 5; createButton("ChatGPT_NewChatButton", sidebarX + 5, itemY, g_sidebarWidth - 10, g_buttonHeight, "", clrWhite, 11, new_chat_original_bg, clrRoyalBlue); ObjectSetInteger(0, "ChatGPT_NewChatButton", OBJPROP_ZORDER, 1); string newchat_icon_resource = (StringLen(g_scaled_newchat_resource) > 0) ? g_scaled_newchat_resource : resourceNewChat; iconX = sidebarExpanded ? sidebarX + 5 + 10 : sidebarX + 5 + (g_sidebarWidth - 10 - 30)/2; createBitmapLabel("ChatGPT_NewChatIcon", iconX, itemY + (g_buttonHeight - 30)/2, 30, 30, newchat_icon_resource, clrNONE, CORNER_LEFT_UPPER); ObjectSetInteger(0, "ChatGPT_NewChatIcon", OBJPROP_ZORDER, 2); ObjectSetInteger(0, "ChatGPT_NewChatIcon", OBJPROP_SELECTABLE, false); if (sidebarExpanded) { createLabel("ChatGPT_NewChatLabel", sidebarX + 5 + 10 + 30 + 5, itemY + (g_buttonHeight - 20)/2, "New Chat", clrWhite, 11, "Arial", CORNER_LEFT_UPPER); ObjectSetInteger(0, "ChatGPT_NewChatLabel", OBJPROP_ZORDER, 2); ObjectSetInteger(0, "ChatGPT_NewChatLabel", OBJPROP_SELECTABLE, false); } itemY += g_buttonHeight + 5; createButton("ChatGPT_ClearButton", sidebarX + 5, itemY, g_sidebarWidth - 10, g_buttonHeight, "", clrWhite, 11, clear_original_bg, clrIndianRed); ObjectSetInteger(0, "ChatGPT_ClearButton", OBJPROP_ZORDER, 1); string clear_icon_resource = (StringLen(g_scaled_clear_resource) > 0) ? g_scaled_clear_resource : resourceClear; iconX = sidebarExpanded ? sidebarX + 5 + 10 : sidebarX + 5 + (g_sidebarWidth - 10 - 30)/2; createBitmapLabel("ChatGPT_ClearIcon", iconX, itemY + (g_buttonHeight - 30)/2, 30, 30, clear_icon_resource, clrNONE, CORNER_LEFT_UPPER); ObjectSetInteger(0, "ChatGPT_ClearIcon", OBJPROP_ZORDER, 2); ObjectSetInteger(0, "ChatGPT_ClearIcon", OBJPROP_SELECTABLE, false); if (sidebarExpanded) { createLabel("ChatGPT_ClearLabel", sidebarX + 5 + 10 + 30 + 5, itemY + (g_buttonHeight - 20)/2, "Clear", clrWhite, 11, "Arial", CORNER_LEFT_UPPER); ObjectSetInteger(0, "ChatGPT_ClearLabel", OBJPROP_ZORDER, 2); ObjectSetInteger(0, "ChatGPT_ClearLabel", OBJPROP_SELECTABLE, false); } itemY += g_buttonHeight + 5; createButton("ChatGPT_HistoryButton", sidebarX + 5, itemY, g_sidebarWidth - 10, g_buttonHeight, "", clrBlack, 12, history_original_bg, clrGray); ObjectSetInteger(0, "ChatGPT_HistoryButton", OBJPROP_ZORDER, 1); string history_icon_resource = (StringLen(g_scaled_history_resource) > 0) ? g_scaled_history_resource : resourceHistory; iconX = sidebarExpanded ? sidebarX + 5 + 10 : sidebarX + 5 + (g_sidebarWidth - 10 - 30)/2; createBitmapLabel("ChatGPT_HistoryIcon", iconX, itemY + (g_buttonHeight - 30)/2, 30, 30, history_icon_resource, clrNONE, CORNER_LEFT_UPPER); ObjectSetInteger(0, "ChatGPT_HistoryIcon", OBJPROP_ZORDER, 2); ObjectSetInteger(0, "ChatGPT_HistoryIcon", OBJPROP_SELECTABLE, false); if (sidebarExpanded) { createLabel("ChatGPT_HistoryLabel", sidebarX + 5 + 10 + 30 + 5, itemY + (g_buttonHeight - 20)/2, "History", clrBlack, 12, "Arial", CORNER_LEFT_UPPER); ObjectSetInteger(0, "ChatGPT_HistoryLabel", OBJPROP_ZORDER, 2); ObjectSetInteger(0, "ChatGPT_HistoryLabel", OBJPROP_SELECTABLE, false); } itemY += g_buttonHeight + 5; if (sidebarExpanded) { int numChats = MathMin(ArraySize(chats), 7); int chatIndices[7]; for (int i = 0; i < numChats; i++) { chatIndices[i] = ArraySize(chats) - 1 - i; } for (int i = 0; i < numChats; i++) { int chatIdx = chatIndices[i]; string hashed_id = EncodeID(chats[chatIdx].id); string fullText = chats[chatIdx].title + " > " + hashed_id; string labelText = fullText; if (StringLen(fullText) > 19) { labelText = StringSubstr(fullText, 0, 16) + "..."; } string bgName = "ChatGPT_ChatBg_" + hashed_id; createRecLabel(bgName, sidebarX + 5 + 10, itemY, g_sidebarWidth - 10 - 10, 25, clrBeige, 1, DarkenColor(clrBeige, 0.9), BORDER_FLAT, STYLE_SOLID); ObjectSetInteger(0, bgName, OBJPROP_ZORDER, 1); color textColor = (chats[chatIdx].id == current_chat_id) ? clrBlue : clrBlack; createLabel("ChatGPT_ChatLabel_" + hashed_id, sidebarX + 10 + 10, itemY + 3, labelText, textColor, 10, "Arial", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); ObjectSetInteger(0, "ChatGPT_ChatLabel_" + hashed_id, OBJPROP_ZORDER, 2); string deleteName = "ChatGPT_SideDelete_" + hashed_id; createButton(deleteName, sidebarX + g_sidebarWidth - 30, itemY + 3, 0, 20, "V", clrBeige, 15, clrBeige, clrBeige, "Wingdings 2"); ObjectSetInteger(0, deleteName, OBJPROP_ZORDER, 2); int size = ArraySize(side_chat_bgs); ArrayResize(side_chat_bgs, size + 1); side_chat_bgs[size] = bgName; int del_size = ArraySize(side_delete_btns); ArrayResize(side_delete_btns, del_size + 1); side_delete_btns[del_size] = deleteName; itemY += 25 + 3; } } itemY += 5; string toggle_text = sidebarExpanded ? "9" : ":"; createButton("ChatGPT_ToggleButton", sidebarX + 5, itemY, g_sidebarWidth - 10, g_buttonHeight, toggle_text, clrBlack, 15, toggle_original_bg, clrGray,"Webdings"); ObjectSetInteger(0, "ChatGPT_ToggleButton", OBJPROP_ZORDER, 1); ChartRedraw(); }
Hier fügen wir nur die Suchschaltfläche als erste Schaltfläche in der Seitenleiste hinzu (nur mit Symbolen im zusammengezogenen Zustand) und die Löschsymbole, die beim Überfahren erscheinen. Wir haben die spezifischen Änderungen zur leichteren Identifizierung hervorgehoben. Dies wird zu folgendem Ergebnis führen.

Da wir die Schaltflächen zum Löschen zur erweiterten Seitenleiste hinzugefügt haben, müssen wir sie auch zum kleinen Popup im zusammengezogenen Zustand hinzufügen. Wir müssen zu jedem Chat, den wir anzeigen, etwas hinzufügen.
void ShowSmallHistoryPopup(int button_y) { ArrayResize(small_popup_objects, 0); ArrayResize(small_chat_bgs, 0); ArrayResize(small_delete_btns, 0); long history_x = ObjectGetInteger(0, "ChatGPT_HistoryButton", OBJPROP_XDISTANCE); long history_w = ObjectGetInteger(0, "ChatGPT_HistoryButton", OBJPROP_XSIZE); int popup_x = (int)(history_x + history_w); int popup_y = button_y; int popup_w = 200; int item_height = 25; int num_chats = MathMin(ArraySize(chats), 7); int items_h = num_chats * (item_height + 5) - 5; int popup_h = items_h + g_buttonHeight + 20; string popup_bg = "ChatGPT_SmallHistoryBg"; createRecLabel(popup_bg, popup_x, popup_y, popup_w, popup_h, clrWhite, 1, clrLightGray); int size = ArraySize(small_popup_objects); ArrayResize(small_popup_objects, size + 1); small_popup_objects[size] = popup_bg; int item_y = popup_y + 10; for (int i = 0; i < num_chats; i++) { int chatIdx = ArraySize(chats) - 1 - i; string hashed_id = EncodeID(chats[chatIdx].id); string labelText = chats[chatIdx].title; if (StringLen(labelText) > 25) labelText = StringSubstr(labelText, 0, 22) + "..."; string bgName = "ChatGPT_SmallChatBg_" + hashed_id; createRecLabel(bgName, popup_x + 10, item_y, popup_w - 20, item_height, clrBeige, 1, DarkenColor(clrBeige, 0.9), BORDER_FLAT, STYLE_SOLID); string labelName = "ChatGPT_SmallChatLabel_" + hashed_id; createLabel(labelName, popup_x + 20, item_y + 3, labelText, clrBlack, 10, "Arial", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); string deleteName = "ChatGPT_SmallDelete_" + hashed_id; createButton(deleteName, popup_x + popup_w - 20 - 15, item_y + 3, 0, 20, "V", clrBeige, 15, clrBeige, clrBeige, "Wingdings 2"); size = ArraySize(small_popup_objects); ArrayResize(small_popup_objects, size + 3); small_popup_objects[size] = bgName; small_popup_objects[size + 1] = labelName; small_popup_objects[size + 2] = deleteName; int bg_size = ArraySize(small_chat_bgs); ArrayResize(small_chat_bgs, bg_size + 1); small_chat_bgs[bg_size] = bgName; int del_size = ArraySize(small_delete_btns); ArrayResize(small_delete_btns, del_size + 1); small_delete_btns[del_size] = deleteName; item_y += item_height + 5; } string see_more = "ChatGPT_SeeMoreButton"; createButton(see_more, popup_x + 10, item_y, popup_w - 20, g_buttonHeight, "See All", clrWhite, 11, see_more_original_bg, clrDarkBlue); size = ArraySize(small_popup_objects); ArrayResize(small_popup_objects, size + 1); small_popup_objects[size] = see_more; small_popup_x = popup_x; small_popup_y = popup_y; small_popup_w = popup_w; small_popup_h = popup_h; showing_small_history_popup = true; just_opened_small = true; } void DeleteSmallHistoryPopup() { for (int i = 0; i < ArraySize(small_popup_objects); i++) { ObjectDelete(0, small_popup_objects[i]); } ArrayResize(small_popup_objects, 0); ArrayResize(small_chat_bgs, 0); ArrayResize(small_delete_btns, 0); showing_small_history_popup = false; }
Die Logik ist hier ganz einfach und selbsterklärend. Wir fügen einfach die Löschschaltflächen zu jedem Chat hinzu und räumen dann auf, wenn das Popup geschlossen wird. Das Gleiche machen wir auch mit dem großen Popup. Um das Such-Popup zu erstellen, benötigen wir neue Funktionen, aber eine ähnliche Logik wie für das große Popup für Chats. Hier ist die Logik, mit der wir das erreichen.
void ShowSearchPopup() { current_search_query = ""; ArrayResize(search_popup_objects, 0); ArrayResize(search_chat_bgs, 0); ArrayResize(search_delete_btns, 0); search_scroll_pos = 0; search_scroll_visible = false; int popup_w = g_mainWidth - 20; int item_height = 25; int num_chats = ArraySize(chats); search_total_height = num_chats * (item_height + 5) - 5; int max_h = g_displayHeight - 20; int content_h = max_h - 40 - 40 - 10; // extra for search header search_visible_height = content_h - 35; int popup_h = max_h; search_popup_w = popup_w; search_popup_h = popup_h; search_popup_x = g_mainContentX + 10; search_popup_y = g_mainY + g_headerHeight + g_padding + 10; string popup_bg = "ChatGPT_SearchBg"; createRecLabel(popup_bg, search_popup_x, search_popup_y, popup_w, popup_h, C'250,250,250', 1, clrDodgerBlue); int size = ArraySize(search_popup_objects); ArrayResize(search_popup_objects, size + 1); search_popup_objects[size] = popup_bg; string close_button = "ChatGPT_SearchCloseButton"; createButton(close_button, search_popup_x + popup_w -40 -10, search_popup_y + 5, 40, 30, "r", clrRed, 13, search_close_original_bg, clrGray,"Webdings"); size = ArraySize(search_popup_objects); ArrayResize(search_popup_objects, size + 1); search_popup_objects[size] = close_button; string search_edit = "ChatGPT_SearchInput"; createEdit(search_edit, search_popup_x + 10, search_popup_y + 5, popup_w - 20 - 40 - 10, 30, "", clrBlack, 16, clrGainsboro, clrLightGray, "Calibri"); ObjectSetInteger(0, search_edit, OBJPROP_BORDER_TYPE, BORDER_FLAT); size = ArraySize(search_popup_objects); ArrayResize(search_popup_objects, size + 1); search_popup_objects[size] = search_edit; string search_placeholder = "ChatGPT_SearchPlaceholder"; int lineHeight = TextGetHeight("A", "Arial", 11); int labelY = search_popup_y + 5 + (30 - lineHeight) / 2 - 3; createLabel(search_placeholder, search_popup_x + 10 + 2, labelY, "Search Chats", clrGray, 11, "Arial", CORNER_LEFT_UPPER); size = ArraySize(search_popup_objects); ArrayResize(search_popup_objects, size + 1); search_popup_objects[size] = search_placeholder; int content_y = search_popup_y + 40 + 40; string content_bg = "ChatGPT_SearchContentBg"; createRecLabel(content_bg, search_popup_x + 10, content_y, popup_w - 20, content_h, clrWhite, 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID); size = ArraySize(search_popup_objects); ArrayResize(search_popup_objects, size + 1); search_popup_objects[size] = content_bg; bool need_search_scroll = search_total_height > search_visible_height; int reserved_w = need_search_scroll ? 16 : 0; int item_w = popup_w - 20 - 20 - reserved_w; UpdateSearchDisplay(); if (need_search_scroll) { CreateSearchScrollbar(); search_scroll_visible = true; UpdateSearchSliderPosition(); UpdateSearchButtonColors(); } showing_search_popup = true; just_opened_search = true; ChartRedraw(); } void CreateSearchScrollbar() { int scrollbar_x = search_popup_x + search_popup_w - 10 - 16; int scrollbar_y = search_popup_y + 40 + 40 + 16; int scrollbar_width = 16; int scrollbar_height = search_popup_h - 40 - 40 - 10 - 2 * 16; int button_size = 16; createRecLabel(SEARCH_SCROLL_LEADER, scrollbar_x, scrollbar_y, scrollbar_width, scrollbar_height, C'220,220,220', 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); createRecLabel(SEARCH_SCROLL_UP_REC, scrollbar_x, search_popup_y + 40 + 40, scrollbar_width, button_size, clrGainsboro, 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); createLabel(SEARCH_SCROLL_UP_LABEL, scrollbar_x + 2, search_popup_y + 40 + 40 + -2, CharToString(0x35), clrDimGray, getFontSizeByDPI(10), "Webdings", CORNER_LEFT_UPPER); createRecLabel(SEARCH_SCROLL_DOWN_REC, scrollbar_x, search_popup_y + 40 + 40 + (search_popup_h - 40 - 40 - 10) - button_size, scrollbar_width, button_size, clrGainsboro, 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); createLabel(SEARCH_SCROLL_DOWN_LABEL, scrollbar_x + 2, search_popup_y + 40 + 40 + (search_popup_h - 40 - 40 - 10) - button_size + -2, CharToString(0x36), clrDimGray, getFontSizeByDPI(10), "Webdings", CORNER_LEFT_UPPER); search_slider_height = CalculateSearchSliderHeight(); createRecLabel(SEARCH_SCROLL_SLIDER, scrollbar_x, search_popup_y + 40 + 40 + (search_popup_h - 40 - 40 - 10) - button_size - search_slider_height, scrollbar_width, search_slider_height, clrSilver, 1, clrGainsboro, BORDER_FLAT, STYLE_SOLID, CORNER_LEFT_UPPER); int size = ArraySize(search_popup_objects); ArrayResize(search_popup_objects, size + 6); search_popup_objects[size] = SEARCH_SCROLL_LEADER; search_popup_objects[size + 1] = SEARCH_SCROLL_UP_REC; search_popup_objects[size + 2] = SEARCH_SCROLL_UP_LABEL; search_popup_objects[size + 3] = SEARCH_SCROLL_DOWN_REC; search_popup_objects[size + 4] = SEARCH_SCROLL_DOWN_LABEL; search_popup_objects[size + 5] = SEARCH_SCROLL_SLIDER; } int CalculateSearchSliderHeight() { int scroll_area_height = search_popup_h - 40 - 40 - 25 - 2 * 16; int slider_min_height = 20; if (search_total_height <= search_visible_height) return scroll_area_height; double visible_ratio = (double)search_visible_height / search_total_height; int height = (int)MathFloor(scroll_area_height * visible_ratio); return MathMax(slider_min_height, height); } void UpdateSearchSliderPosition() { int scrollbar_x = search_popup_x + search_popup_w - 10 - 16; int scrollbar_y = search_popup_y + 40 + 40 + 16; int scroll_area_height = search_popup_h - 40 - 40 - 25 - 2 * 16; int max_scroll = MathMax(0, search_total_height - search_visible_height); if (max_scroll <= 0) return; double scroll_ratio = (double)search_scroll_pos / max_scroll; int scroll_area_y_max = scrollbar_y + scroll_area_height - search_slider_height; int scroll_area_y_min = scrollbar_y; int new_y = scroll_area_y_min + (int)(scroll_ratio * (scroll_area_y_max - scroll_area_y_min)); new_y = MathMax(scroll_area_y_min, MathMin(new_y, scroll_area_y_max)); ObjectSetInteger(0, SEARCH_SCROLL_SLIDER, OBJPROP_YDISTANCE, new_y); } void UpdateSearchButtonColors() { int max_scroll = MathMax(0, search_total_height - search_visible_height); if (search_scroll_pos == 0) { ObjectSetInteger(0, SEARCH_SCROLL_UP_LABEL, OBJPROP_COLOR, clrSilver); } else { ObjectSetInteger(0, SEARCH_SCROLL_UP_LABEL, OBJPROP_COLOR, clrDimGray); } if (search_scroll_pos == max_scroll) { ObjectSetInteger(0, SEARCH_SCROLL_DOWN_LABEL, OBJPROP_COLOR, clrSilver); } else { ObjectSetInteger(0, SEARCH_SCROLL_DOWN_LABEL, OBJPROP_COLOR, clrDimGray); } } void SearchScrollUp() { if (search_scroll_pos > 0) { search_scroll_pos = MathMax(0, search_scroll_pos - 30); UpdateSearchDisplay(); if (search_scroll_visible) { UpdateSearchSliderPosition(); UpdateSearchButtonColors(); } } } void SearchScrollDown() { int max_scroll = MathMax(0, search_total_height - search_visible_height); if (search_scroll_pos < max_scroll) { search_scroll_pos = MathMin(max_scroll, search_scroll_pos + 30); UpdateSearchDisplay(); if (search_scroll_visible) { UpdateSearchSliderPosition(); UpdateSearchButtonColors(); } } } void UpdateSearchDisplay() { int total = ObjectsTotal(0, 0, -1); for (int j = total - 1; j >= 0; j--) { string name = ObjectName(0, j, 0, -1); if (StringFind(name, "ChatGPT_SearchChatBg_") == 0 || StringFind(name, "ChatGPT_SearchChatLabel_") == 0 || StringFind(name, "ChatGPT_SearchDelete_") == 0) { ObjectDelete(0, name); } } ArrayResize(search_chat_bgs, 0); ArrayResize(search_delete_btns, 0); Chat filtered[]; ArrayResize(filtered, 0); for (int i = 0; i < ArraySize(chats); i++) { string lower_title = chats[i].title; StringToLower(lower_title); string lower_history = chats[i].history; StringToLower(lower_history); string lower_query = current_search_query; StringToLower(lower_query); if (StringFind(lower_title, lower_query) >= 0 || StringFind(lower_history, lower_query) >= 0 || lower_query == "") { int fsize = ArraySize(filtered); ArrayResize(filtered, fsize + 1); filtered[fsize] = chats[i]; } } int num_chats = ArraySize(filtered); search_total_height = num_chats * (25 + 5) - 5; bool need_search_scroll = search_total_height > search_visible_height; bool prev_search_scroll_visible = search_scroll_visible; search_scroll_visible = need_search_scroll; if (search_scroll_visible != prev_search_scroll_visible) { if (search_scroll_visible) { CreateSearchScrollbar(); } else { DeleteSearchScrollbar(); } } int reserved_w = search_scroll_visible ? 16 : 0; int item_w = search_popup_w - 20 - 20 - reserved_w; int item_y = search_popup_y + 40 + 40 + 10 - search_scroll_pos; int end_y = search_popup_y + 40 + 40 + (search_popup_h - 40 - 40 - 10) - 25; int start_idx = 0; int current_h = 0; for (int i = 0; i < num_chats; i++) { if (current_h >= search_scroll_pos) { start_idx = i; item_y = search_popup_y + 40 + 40 + 10 + (current_h - search_scroll_pos); break; } current_h += 25 + 5; } int visible_count = 0; current_h = 0; for (int i = start_idx; i < num_chats; i++) { if (current_h + 25 > search_visible_height) break; current_h += 25 + 5; visible_count++; } for (int i = 0; i < visible_count; i++) { int chatIdx = num_chats - 1 - (start_idx + i); string hashed_id = EncodeID(filtered[chatIdx].id); string fullText = filtered[chatIdx].title + " > " + hashed_id; string labelText = fullText; if (StringLen(fullText) > 35) { labelText = StringSubstr(fullText, 0, 32) + "..."; } string bgName = "ChatGPT_SearchChatBg_" + hashed_id; if (item_y >= search_popup_y + 40 + 40 + 10 && item_y < end_y) { createRecLabel(bgName, search_popup_x + 20, item_y, item_w, 25, clrBeige, 1, DarkenColor(clrBeige, 0.9), BORDER_FLAT, STYLE_SOLID); } string labelName = "ChatGPT_SearchChatLabel_" + hashed_id; if (item_y >= search_popup_y + 40 + 40 + 10 && item_y < end_y) { createLabel(labelName, search_popup_x + 30, item_y + 3, labelText, clrBlack, 10, "Arial", CORNER_LEFT_UPPER, ANCHOR_LEFT_UPPER); } string deleteName = "ChatGPT_SearchDelete_" + hashed_id; if (item_y >= search_popup_y + 40 + 40 + 10 && item_y < end_y) { createButton(deleteName, search_popup_x + 20 + item_w - 25, item_y + 3, 0, 20, "V", clrBeige, 15, clrBeige, clrBeige, "Wingdings 2"); } int size = ArraySize(search_popup_objects); ArrayResize(search_popup_objects, size + 3); search_popup_objects[size] = bgName; search_popup_objects[size + 1] = labelName; search_popup_objects[size + 2] = deleteName; int bg_size = ArraySize(search_chat_bgs); ArrayResize(search_chat_bgs, bg_size + 1); search_chat_bgs[bg_size] = bgName; int del_size = ArraySize(search_delete_btns); ArrayResize(search_delete_btns, del_size + 1); search_delete_btns[del_size] = deleteName; item_y += 25 + 5; } if (search_scroll_visible) { search_slider_height = CalculateSearchSliderHeight(); ObjectSetInteger(0, SEARCH_SCROLL_SLIDER, OBJPROP_YSIZE, search_slider_height); UpdateSearchSliderPosition(); UpdateSearchButtonColors(); } ChartRedraw(); } void DeleteSearchScrollbar() { ObjectDelete(0, SEARCH_SCROLL_LEADER); ObjectDelete(0, SEARCH_SCROLL_UP_REC); ObjectDelete(0, SEARCH_SCROLL_UP_LABEL); ObjectDelete(0, SEARCH_SCROLL_DOWN_REC); ObjectDelete(0, SEARCH_SCROLL_DOWN_LABEL); ObjectDelete(0, SEARCH_SCROLL_SLIDER); } void DeleteSearchPopup() { for (int i = 0; i < ArraySize(search_popup_objects); i++) { ObjectDelete(0, search_popup_objects[i]); } DeleteSearchScrollbar(); ArrayResize(search_popup_objects, 0); ArrayResize(search_chat_bgs, 0); ArrayResize(search_delete_btns, 0); showing_search_popup = false; search_scroll_visible = false; }
Zunächst implementieren wir die Funktion „ShowSearchPopup“, um ein spezielles Popup für die Chatsuche zu erstellen, setzen „current_search_query“ auf leer zurück, löschen die Arrays „search_popup_objects“, „search_chat_bgs“ und „search_delete_btns“ und initialisieren Scroll-Position und Sichtbarkeit auf null/false. Wir berechnen die Abmessungen des Popups auf der Grundlage von „g_mainWidth“ und „g_displayHeight“, positionieren es vom Hauptinhalt abgesetzt, erstellen den Hintergrund „ChatGPT_SearchBg“ mit „createRecLabel“ in hellgrau mit blauem Rand, eine Schließtaste „ChatGPT_SearchCloseButton“ mit „createButton“ mit Webdings 'r' in rot, ein Eingabefeld „ChatGPT_SearchInput“ mit „createEdit“ in gainsboro, und ein Platzhalter-Label „ChatGPT_SearchPlaceholder“ mit „Search Chats“ in grauer Arial Schrift. Wir fügen einen Inhaltshintergrund „ChatGPT_SearchContentBg“ in Weiß mit Gainsboro-Rahmen hinzu, bestimmen, ob Blättern in allen Chats benötigt wird, rufen „UpdateSearchDisplay“ auf, um die Ergebnisse aufzufüllen, und rufen, falls erforderlich, „CreateSearchScrollbar“ aufrufen, Sichtbarkeit auf true setzen, Position und Farben aktualisieren, Markierung wie gezeigt mit „showing_search_popup“ true und „just_opened_search“ true, Neuzeichnen mit der Funktion ChartRedraw.
Die Funktion „CreateSearchScrollbar“ konstruiert die Bildlaufleiste des Such-Popups, indem sie „createRecLabel“ verwendet für die Führungsspur in Hellgrau mit Gainsboro-Rand, Auf-/Ab-Rechtecke in Gainsboro, Beschriftungen mit Webdings-Pfeilen in Dim-Gray und einen Schieberegler in Silber mit Gainsboro-Rand, berechnet die Positionen aus „search_popup_x/y/w/h“ und hängt die Namen zur Bereinigung an „search_popup_objects“ an. „CalculateSearchSliderHeight“ berechnet die Höhe des Suchschiebers proportional von der sichtbaren zur Gesamthöhe, mit mindestens 20 Pixeln, und gibt den vollen Bereich zurück, wenn kein Scrollen erforderlich ist. UpdateSearchSliderPosition“ passt den y-Wert des Suchschiebers auf der Grundlage des Verhältnisses von „search_scroll_pos“ zu „max scroll“ an, wobei die Begrenzung innerhalb des Bildlaufbereichs mit MathMax und MathMin erfolgt, die mit der Funktion ObjectSetInteger festgelegt werden. „UpdateSearchButtonColors“ graut die Auf-/Abwärtspfeile in Silber aus, wenn sie sich oben/unten befinden, oder in Grau, wenn sie gescrollt werden können. „SearchScrollUp“ und „SearchScrollDown“ dekrementieren/erhöhen „search_scroll_pos“ um 30, geklemmt auf 0/max, dann Aktualisierung der Anzeige mit „UpdateSearchDisplay“ und, falls sichtbar, Position/Farben mit „UpdateSearchSliderPosition“ und „UpdateSearchButtonColors“.
In „UpdateSearchDisplay“ löschen wir vorhandene Suchchat-Hintergründe, Beschriftungen und löschen mit ObjectsTotal und ObjectDelete unter Verwendung von StringFind und setzen die Arrays zurück. Wir filtern „Chats“ in ein „gefiltertes“ Array, indem wir Titel, Verlauf und Abfrage mit StringToLower in Kleinbuchstaben umwandeln und mit „StringFind“ auf Übereinstimmungen prüfen oder alle einschließen, wenn die Abfrage leer ist. Wir berechnen „search_total_height“ aus der gefilterten Anzahl, bestimmen den Scrollbedarf und die reservierte Breite, setzen die Elementbreite entsprechend, berechnen den Startindex und y aus „search_scroll_pos“, zählen die sichtbaren Elemente innerhalb von „search_visible_height“ und erstellen in einer Schleife Hintergründe „ChatGPT_SearchChatBg_“, Beschriftungen „ChatGPT_SearchChatLabel_“ mit verkürzten Titeln und Löschschaltflächen „ChatGPT_SearchDelete_“ unter Verwendung von Wingdings 'V' in Beige (anfangs versteckte Größe 0), Speicherung in Arrays, Positionierung nur, wenn innerhalb der Ansichtsgrenzen. Wenn „search_scroll_visible“, aktualisieren wir die Höhe des Schiebereglers mit „CalculateSearchSliderHeight“, setzen Größe, Position mit „UpdateSearchSliderPosition“ und die Farben.
Die Funktion „DeleteSearchScrollbar“ entfernt alle Objekte der Bildlaufleiste für das Suchen. „DeleteSearchPopup“ löscht alle Objekte in „search_popup_objects“, einschließlich der Bildlaufleiste mit „DeleteSearchScrollbar“, setzt Arrays und Flags für „showing_search_popup“ und „search_scroll_visible“ auf false zurück. Dies führt zur Schaffung der folgenden Schnittstelle.

Wenn die Funktionen für die Schnittstelle bereit sind, können wir sie in der Chart-Ereignishandhabung aufrufen, sodass wir die erforderlichen Objekte oder Ereignisse bei Bedarf erstellen können. Wir werden den gleichen Ansatz wie bei der Haupt-Chat-Anzeige verwenden, nur dass diese für die Suchabwicklung zuständig ist.
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- variables for elements if (id == CHARTEVENT_CLICK) { int clickX = (int)lparam; int clickY = (int)dparam; if (showing_big_history_popup) { if (just_opened_big) { just_opened_big = false; } else if (clickX < big_popup_x || clickX > big_popup_x + big_popup_w || clickY < big_popup_y || clickY > big_popup_y + big_popup_h) { DeleteBigHistoryPopup(); ChartRedraw(); } } if (showing_small_history_popup) { if (just_opened_small) { just_opened_small = false; } else if (clickX < small_popup_x || clickX > small_popup_x + small_popup_w || clickY < small_popup_y || clickY > small_popup_y + small_popup_h) { DeleteSmallHistoryPopup(); ChartRedraw(); } } if (showing_search_popup) { if (just_opened_search) { just_opened_search = false; } else if (clickX < search_popup_x || clickX > search_popup_x + search_popup_w || clickY < search_popup_y || clickY > search_popup_y + search_popup_h) { DeleteSearchPopup(); ChartRedraw(); } } return; } if (id == CHARTEVENT_OBJECT_CLICK) { if (StringFind(sparam, "ChatGPT_ChatLabel_") == 0 || StringFind(sparam, "ChatGPT_SmallChatLabel_") == 0 || StringFind(sparam, "ChatGPT_BigChatLabel_") == 0 || StringFind(sparam, "ChatGPT_SearchChatLabel_") == 0) { string prefix = ""; if (StringFind(sparam, "ChatGPT_ChatLabel_") == 0) prefix = "ChatGPT_ChatLabel_"; else if (StringFind(sparam, "ChatGPT_SmallChatLabel_") == 0) prefix = "ChatGPT_SmallChatLabel_"; else if (StringFind(sparam, "ChatGPT_BigChatLabel_") == 0) prefix = "ChatGPT_BigChatLabel_"; else if (StringFind(sparam, "ChatGPT_SearchChatLabel_") == 0) prefix = "ChatGPT_SearchChatLabel_"; string hashed_id = StringSubstr(sparam, StringLen(prefix)); int new_id = DecodeID(hashed_id); int idx = GetChatIndex(new_id); if (idx >= 0 && new_id != current_chat_id) { UpdateCurrentHistory(); current_chat_id = new_id; current_title = chats[idx].title; conversationHistory = chats[idx].history; if (showing_small_history_popup) DeleteSmallHistoryPopup(); if (showing_big_history_popup) DeleteBigHistoryPopup(); if (showing_search_popup) DeleteSearchPopup(); UpdateResponseDisplay(); UpdateSidebarDynamic(); ChartRedraw(); } return; } else if (StringFind(sparam, "ChatGPT_SideDelete_") == 0 || StringFind(sparam, "ChatGPT_SmallDelete_") == 0 || StringFind(sparam, "ChatGPT_BigDelete_") == 0 || StringFind(sparam, "ChatGPT_SearchDelete_") == 0) { string prefix = ""; if (StringFind(sparam, "ChatGPT_SideDelete_") == 0) prefix = "ChatGPT_SideDelete_"; else if (StringFind(sparam, "ChatGPT_SmallDelete_") == 0) prefix = "ChatGPT_SmallDelete_"; else if (StringFind(sparam, "ChatGPT_BigDelete_") == 0) prefix = "ChatGPT_BigDelete_"; else if (StringFind(sparam, "ChatGPT_SearchDelete_") == 0) prefix = "ChatGPT_SearchDelete_"; string hashed_id = StringSubstr(sparam, StringLen(prefix)); int del_id = DecodeID(hashed_id); DeleteChat(del_id); return; } } //--- else if (id == CHARTEVENT_OBJECT_CLICK && sparam == "ChatGPT_SearchButton") { ShowSearchPopup(); just_opened_search = true; } else if (id == CHARTEVENT_OBJECT_CLICK && sparam == "ChatGPT_SearchCloseButton") { DeleteSearchPopup(); ChartRedraw(); } else if (id == CHARTEVENT_OBJECT_CLICK && (sparam == SEARCH_SCROLL_UP_REC || sparam == SEARCH_SCROLL_UP_LABEL)) { SearchScrollUp(); } else if (id == CHARTEVENT_OBJECT_CLICK && (sparam == SEARCH_SCROLL_DOWN_REC || sparam == SEARCH_SCROLL_DOWN_LABEL)) { SearchScrollDown(); } else if (id == CHARTEVENT_OBJECT_ENDEDIT && sparam == "ChatGPT_SearchInput") { current_search_query = (string)ObjectGetString(0, sparam, OBJPROP_TEXT); if (StringLen(current_search_query) == 0) { if (ObjectFind(0, "ChatGPT_SearchPlaceholder") < 0) { int lineHeight = TextGetHeight("A", "Arial", 11); int labelY = search_popup_y + 5 + (30 - lineHeight) / 2 - 3; createLabel("ChatGPT_SearchPlaceholder", search_popup_x + 10 + 2, labelY, "Search Chats", clrGray, 11, "Arial", CORNER_LEFT_UPPER); } } else { if (ObjectFind(0, "ChatGPT_SearchPlaceholder") >= 0) { ObjectDelete(0, "ChatGPT_SearchPlaceholder"); } } UpdateSearchDisplay(); ChartRedraw(); } // mouse move logic same implementation }
Wir verwenden die Ereignisbehandlung von OnChartEvent zur Unterstützung der Chat-Lösch- und Suchfunktionalität und konzentrieren uns dabei auf Klicks und Bearbeitungsereignisse für das neue Such-Popup und die Löschschaltflächen. Für allgemeine Klicks (CHARTEVENT_CLICK) erfassen wir die Koordinaten „clickX“ und „clickY“ und überprüfen, ob ein Popup (großer Verlauf, kleiner Verlauf oder die Suche) geöffnet ist. Wenn ja, und nicht nur geöffnet (mit Flags wie „just_opened_search“), schließen wir das entsprechende Popup („DeleteSearchPopup“ ist unser Anliegen), wenn der Klick außerhalb seiner Grenzen liegt, gefolgt von ChartRedraw, dann wird zurückgekehrt.
Bei Objektklicks (CHARTEVENT_OBJECT_CLICK) behandeln wir die Chat-Auswahl in allen Ansichten (die Suche ist immer noch unser Anliegen), indem wir Präfixe wie „ChatGPT_SearchChatLabel_“ überprüfen. Wir extrahieren die gehashte ID und erledigen den Rest, was inzwischen üblich sein sollte, und wir schließen alle offenen Popups, aktualisieren die Antwortanzeige und die Seitenleiste mit „UpdateResponseDisplay“ und „UpdateSidebarDynamic“, zeichnen mit „ChartRedraw“ neu und kehren zurück. Beim Löschen prüfen wir Präfixe wie „ChatGPT_SideDelete_“ oder „ChatGPT_SearchDelete_“, extrahieren und dekodieren die ID, rufen „DeleteChat“ auf, um den Chat zu löschen, und kehren zurück.
Wenn die Suchschaltfläche „ChatGPT_SearchButton“ angeklickt wird, rufen wir „ShowSearchPopup“ auf und setzen „just_opened_search“ auf true. Für die Schaltfläche zum Schließen der Suche „ChatGPT_SearchCloseButton“, rufen wir „DeleteSearchPopup“ auf und zeichnen neu. Für die Elemente der Bildlaufleiste für das Suchen („SEARCH_SCROLL_UP_REC“/“SEARCH_SCROLL_UP_LABEL“ oder „SEARCH_SCROLL_DOWN_REC“/“SEARCH_SCROLL_DOWN_LABEL“) lösen wir „SearchScrollUp“ oder „SearchScrollDown“ aus. Bei Bearbeitungsereignissen (CHARTEVENT_OBJECT_ENDEDIT) auf „ChatGPT_SearchInput“ erfassen wir die Eingabe als „current_search_query“. Wir erstellen, wenn leer, einen Platzhalter „ChatGPT_SearchPlaceholder“, wenn nicht vorhanden, mit „createLabel“ mit „Search Chats“ in grau Arial, wenn nicht leer, löschen wir den Platzhalter, wenn vorhanden. Anschließend werden die Suchergebnisse mit „UpdateSearchDisplay“ aktualisiert und neu gezeichnet. Die Mausbewegungslogik für Hover-Effekte und Scrollen folgt demselben Muster wie frühere Implementierungen, die hier der Kürze halber weggelassen wurden, um eine konsistente Interaktivität über die gesamte Schnittstelle hinweg zu gewährleisten. Nach dieser Aktualisierung sind die Chartereignisse nun vollständig. Wir müssen sicherstellen, dass wir dem Such-Popup ebenfalls Priorität einräumen, um ein Flackern zu vermeiden.
void UpdateResponseDisplay() { if (showing_small_history_popup || showing_big_history_popup || showing_search_popup) return; //--- rest of the function logic }
Hier erweitern wir lediglich die bedingte Anweisung am Anfang der Funktion, um Aktualisierungen zu blockieren, wenn das Such-Popup angezeigt wird. Das bedeutet, dass wir die Aktualisierung der Anzeige überspringen, solange das Such-Popup geöffnet ist, da wir das Popup innerhalb seines Bereichs erstellen. Nach der Kompilierung ergibt sich das folgende Ergebnis für das Such-Popup.

Anhand der Visualisierung können wir sehen, dass wir das Programm durch Hinzufügen der neuen Such- und Löschelemente verbessern und somit unsere Ziele erreichen können. Bleiben nur noch die Backtests des Programms, und das wird im nächsten Abschnitt behandelt.
Testen von Chat-Löschung und Suche
Wir haben die Tests durchgeführt, und hier ist die kompilierte Visualisierung in einem einzigen Graphics Interchange Format (GIF) Bitmap-Bildformat.

Aus der Visualisierung geht hervor, dass wir die Integrität der Chatprotokolle aufrechterhalten, indem wir sie nicht verfälschen. Für den Fall, dass Sie den Chat häufig nutzen und auch die Protokolle löschen möchten, sollten Sie die folgende Logik anwenden.
void ClearLogsForChat(int del_id) { if (logFileHandle != INVALID_HANDLE) { FileClose(logFileHandle); } string tempFile = "Temp_Log.txt"; int readHandle = FileOpen(LogFileName, FILE_READ | FILE_TXT); if (readHandle == INVALID_HANDLE) { Print("Failed to open log for reading: ", GetLastError()); ReopenLogHandle(); return; } int writeHandle = FileOpen(tempFile, FILE_WRITE | FILE_TXT); if (writeHandle == INVALID_HANDLE) { Print("Failed to open temp log: ", GetLastError()); FileClose(readHandle); ReopenLogHandle(); return; } bool skipBlock = false; while (!FileIsEnding(readHandle)) { string line = FileReadString(readHandle); if (StringFind(line, "Chat ID: ") == 0) { // Reset skip for new block skipBlock = false; // Parse ID int commaPos = StringFind(line, ", Title: "); if (commaPos > 0) { string idStr = StringSubstr(line, 9, commaPos - 9); int chatId = (int)StringToInteger(idStr); if (chatId == del_id) { skipBlock = true; } } // If parse fails, don't skip (safe default) } if (!skipBlock) { FileWrite(writeHandle, line); } } FileClose(readHandle); FileClose(writeHandle); FileDelete(LogFileName); FileMove(tempFile, 0, LogFileName, 0); ReopenLogHandle(); } void ReopenLogHandle() { logFileHandle = FileOpen(LogFileName, FILE_READ | FILE_WRITE | FILE_TXT); if (logFileHandle != INVALID_HANDLE) { FileSeek(logFileHandle, 0, SEEK_END); } else { Print("Failed to reopen log: ", GetLastError()); } } void DeleteChat(int id) { //--- rest of the logic ArrayRemove(chats, idx, 1); ClearLogsForChat(id); //--- we call the function after removing the chats SaveChats(); //--- rest of the logic }
Wir beginnen mit der Funktion „ClearLogsForChat“, die wir erstellen, um alle Protokolleinträge zu entfernen, die zu einem bestimmten, durch „del_id“ identifizierten Chat gehören. Zunächst wird geprüft, ob „logFileHandle“ gültig ist, d. h. ob die Protokolldatei derzeit geöffnet ist. Ist dies der Fall, wird sie mit der Funktion FileClose geschlossen, um Lese- und Schreibvorgänge sicher vorzubereiten. Anschließend definieren wir einen temporären Dateinamen in der Variablen „tempFile“ und weisen ihr den Wert „Temp_Log.txt“ zu, den wir während des Bereinigungsprozesses als Zwischenspeicherdatei verwenden. Als Nächstes öffnen wir die Hauptprotokolldatei, die in „LogFileName“ gespeichert ist, im Lesemodus mit FileOpen mit den Flags FILE_READ und „FILE_TXT“. Wenn die Datei nicht geöffnet werden kann, rufen wir die Funktion „ReopenLogHandle“ auf, um den Zugriff auf die Protokolldatei wiederherzustellen und die Funktion zu beenden.
Anschließend öffnen wir die temporäre Datei erneut mit „FileOpen“ im Schreibmodus, diesmal jedoch mit den Flags FILE_WRITE und „FILE_TXT“. Wenn dies fehlschlägt, rufen wir „ReopenLogHandle“ auf, bevor wir zurückkehren. Sobald beide Dateien erfolgreich geöffnet sind, erstellen wir eine boolesche Variable „skipBlock“ und initialisieren sie auf false. Wir verwenden diese Variable, um zu entscheiden, ob das Schreiben bestimmter Abschnitte des Protokolls übersprungen werden soll. Dann beginnen wir eine Schleife, die mit „while (!FileIsEnding(readHandle))“ bis zum Ende der Protokolldatei läuft. Innerhalb dieser Schleife lesen wir jede Zeile mit FileReadString und speichern sie in „line“. Wenn eine Zeile mit „Chat ID: “ beginnt, die mit StringFind gefunden wurde, erkennen wir dies als Beginn eines neuen Chateintrags. Wir setzen „skipBlock“ auf false zurück und extrahieren dann die numerische Chat-ID. Dazu suchen wir die Position des Textes „Titel:“ in der Zeile mit „StringFind“, extrahieren den Chat-ID-Teil mit StringSubstr und wandeln ihn mit der Funktion StringToInteger in eine Ganzzahl um. Wenn die extrahierte „chatId“ mit der „del_id“ übereinstimmt, die wir entfernen wollen, setzen wir „skipBlock“ auf true, sodass alle Zeilen, die sich auf diesen Chat beziehen, ignoriert werden. Jede weitere Zeile wird mit FileWrite in die temporäre Datei zurückgeschrieben, sodass nur die Protokolle anderer Chats erhalten bleiben.
Nachdem wir alle Zeilen verarbeitet haben, schließen wir beide Datei-Handles mit der Funktion FileClose. Anschließend löschen wir die alte Protokolldatei mit FileDelete und benennen die temporäre Datei, die sie ersetzen soll, mit FileMove um, um sicherzustellen, dass die alten Protokolle sauber ersetzt werden. Schließlich rufen wir „ReopenLogHandle“ auf, um die Hauptprotokolldatei wieder zu öffnen, sodass wir den normalen Betrieb fortsetzen können. Die Logik der Funktion „ReopenLogHandle“ ist hier nicht neu. Beim Löschen eines Chats rufen wir dann die Funktion auf, um die schwere Arbeit zu erledigen. Wenn Sie die Protokolle nicht löschen wollen, können Sie diese Änderung ignorieren. Sie müssen auch beachten, dass bei großen Protokollen das System durch das erneute Öffnen und Schreiben von Dateien verlangsamt wird. Wir haben eine Eingabe hinzugefügt, um das Löschen des Protokolls zu steuern, standardmäßig true. Siehe unten.
input bool DeleteLogsOnChatDelete = true; // Clear logs when deleting chats? //--- added conditional log clearing if (DeleteLogsOnChatDelete) { ClearLogsForChat(id); }
Nach dem Kompilieren erhalten wir folgendes Ergebnis:

Schlussfolgerung
Abschließend haben wir unser KI-gestütztes Handelssystem in MQL5 weiterentwickelt, indem wir die Chat-Löschung durch schwebende Schaltflächen in der Seitenleiste und in Popups sowie ein Echtzeit-Such-Popup mit groß- und kleingeschriebener Filterung von Titeln und Verläufen eingeführt haben, was eine effiziente Verwaltung dauerhafter verschlüsselter Unterhaltungen unter Beibehaltung mehrzeiliger Eingaben und KI-gestützter Signalerzeugung aus Chartdaten ermöglicht. Diese Funktionen ermöglichen es uns, relevante Erkenntnisse zu entrümpeln und schnell abzurufen und die Produktivität in dynamischen Marktumgebungen mit intuitiven Steuerelementen und nahtlosen UI-Updates zu verbessern. In den kommenden Teilen werden wir eine fortschrittliche, auf KI-Signalen basierende Handelsausführung integrieren und die Multi-Symbol-Überwachung für einen vollautomatischen Assistenten untersuchen. Bleiben Sie dran.
Anlagen
| S/N | Name | Typ | Beschreibung |
|---|---|---|---|
| 1 | AI_JSON_FILE.mqh | JSON-Klassenbibliothek | Klasse zur Handhabung der JSON-Serialisierung und -Deserialisierung |
| 2 | AI_CREATE_OBJECTS_FNS.mqh | Bibliothek der Objektfunktionen | Funktionen zur Erstellung von Visualisierungsobjekten wie Beschriftungen und Schaltflächen |
| 3 | AI_ChatGPT_EA_Part_6.mq5 | Expert Advisor | Haupt-Expertenratgeber für die KI-Integration |
Übersetzt aus dem Englischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/en/articles/20254
Warnung: Alle Rechte sind von MetaQuotes Ltd. vorbehalten. Kopieren oder Vervielfältigen untersagt.
Dieser Artikel wurde von einem Nutzer der Website verfasst und gibt dessen persönliche Meinung wieder. MetaQuotes Ltd übernimmt keine Verantwortung für die Richtigkeit der dargestellten Informationen oder für Folgen, die sich aus der Anwendung der beschriebenen Lösungen, Strategien oder Empfehlungen ergeben.
Entwicklung einer Handelsstrategie: Der Flower-Volatilitäts-Index als Trendfolgemethode
Entwicklung einer Handelsstrategie: Ansatz der Pseudo-Pearson-Korrelation
Die Grenzen des maschinellen Lernens überwinden (Teil 7): Automatische Strategieauswahl
Entwicklung des Price Action Analysis Toolkit (Teil 51): Revolutionäre Chart-Suchtechnologie für die Entdeckung von Kerzenmustern
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.