Greasy Fork is available in English.

ChatGPT ChatTree 🌳

ChatGPT ChatTree 🌳, 🚀gestión permanente e ilimitada de tus interacciones con ChatGPT🚀 🔄actualizaciones en tiempo real y visualización del árbol de conversación de ChatGPT🔄 💡consejos para conversaciones con ChatGPT, anotaciones personalizadas, marcadores💡🔍Búsqueda inteligente en ChatGPT: localiza rápidamente conversaciones específicas🔍 📋Panel de gestión de interacción con ChatGPT, interfaz fácil de usar, opciones completas de gestión de interacción, categorización, etiquetas y más📋

// ==UserScript==
// @name         ChatGPT ChatTree 🌳
// @name:zh-CN   ChatGPT ChatTree 🌳
// @name:es   ChatGPT ChatTree 🌳
// @name:ar   ChatGPT ChatTree 🌳
// @namespace    https://czz9.top
// @version      2024.06.21.01
// @description ChatGPT ChatTree 🌳, 🚀permanent and unrestricted management of your interactions with ChatGPT🚀 🔄real-time updates and visualization of ChatGPT conversation tree🔄 💡ChatGPT conversation tips, custom annotations, bookmarks💡🔍Smart Search in ChatGPT: quickly locate specific conversations🔍 📋ChatGPT Interaction Management Panel, user-friendly interface, comprehensive ChatGPT interaction management options, categorization, tags, and more📋
// @description:ar ChatGPT ChatTree 🌳، 🚀إدارة دائمة وغير محدودة لتفاعلاتك مع ChatGPT🚀 🔄تحديث شجرة المحادثة ChatGPT بالوقت الفعلي + مرئيات🔄 💡نصائح للمحادثة مع ChatGPT، تعليقات مخصصة، إشارات مرجعية💡🔍 بحث ذكي في ChatGPT: تحديد المواضع الدقيقة للمحادثات بسرعة🔍 📋لوحة إدارة التفاعل مع ChatGPT، واجهة سهلة الاستخدام، خيارات إدارة التفاعل الكاملة مع ChatGPT، التصنيف، والعلامات وأكثر📋
// @description:bg ChatGPT ChatTree 🌳, 🚀постоянно и без ограничения управлявайте взаимодействията си с ChatGPT🚀 🔄реално време актуализации и визуализации на дървото на разговори с ChatGPT🔄 💡Съвети за разговори с ChatGPT, персонализирани коментари, отметки💡🔍 Интелигентно търсене в ChatGPT: бързо намиране на конкретни разговори🔍 📋Панел за управление на взаимодействията с ChatGPT, удобен интерфейс, пълни опции за управление на взаимодействията, категории, етикети и други📋
// @description:ckb ChatGPT ChatTree 🌳، 🚀بەردووم و بێ سنووریدانی کارپێکردنی تایبەتمەندیەکانت لەگەڵ ChatGPT🚀 🔄نوێکردنەوەی بەرزی ڕووداوی ChatGPT بە ڕاستی گشتی + ڕوانین🔄 💡پێشاندانی تایبەتمەندیەکانی ChatGPT، تێبینی تایبەت، نیشانکردن💡🔍 گەڕانی زانایی لە ChatGPT: چاودێری ئاسانی گفتوگۆی تایبەت🔍 📋پانێڵی باشکردنی تایبەتمەندی لەگەڵ ChatGPT، ڕووکاری ئاسان بەکارهێنان، هەڵبژاردنە پڕەکانی باشکردن، پۆلەکان، تاگ و زیاتر📋
// @description:cs ChatGPT ChatTree 🌳, 🚀trvalé a neomezené řízení interakcí s ChatGPT🚀 🔄aktualizace stromu konverzace ChatGPT v reálném čase + vizualizace🔄 💡tipy na konverzaci s ChatGPT, vlastní komentáře, záložky💡🔍 inteligentní vyhledávání v ChatGPT: rychlé nalezení konkrétních konverzací🔍 📋Panel pro správu interakcí s ChatGPT, uživatelsky přívětivé rozhraní, komplexní možnosti správy interakcí, kategorie, štítky a více📋
// @description:da ChatGPT ChatTree 🌳, 🚀permanent og ubegrænset styring af dine interaktioner med ChatGPT🚀 🔄real-tids opdatering og visualisering af ChatGPT samtaletræ🔄 💡tips til samtaler med ChatGPT, brugerdefinerede kommentarer, bogmærker💡🔍 Intelligent søgning i ChatGPT: hurtig lokalisering af specifikke samtaler🔍 📋Styringspanel for interaktion med ChatGPT, brugervenlig grænseflade, fuldstændige styringsmuligheder for interaktion, kategorier, tags og mere📋
// @description:de ChatGPT ChatTree 🌳, 🚀dauerhafte und uneingeschränkte Verwaltung Ihrer Interaktionen mit ChatGPT🚀 🔄Echtzeit-Aktualisierung und Visualisierung des ChatGPT-Gesprächsbaums🔄 💡Tipps für Gespräche mit ChatGPT, benutzerdefinierte Kommentare, Lesezeichen💡🔍Intelligente Suche in ChatGPT: schnelle Auffindung spezifischer Gespräche🔍 📋Verwaltungsoberfläche für Interaktionen mit ChatGPT, benutzerfreundlich, umfassende Verwaltungsoptionen, Kategorien, Tags und mehr📋
// @description:el ChatGPT ChatTree 🌳, 🚀μόνιμη και απεριόριστη διαχείριση των αλληλεπιδράσεών σας με το ChatGPT🚀 🔄πραγματικός χρόνος ενημέρωσης και οπτικοποίησης του δέντρου συνομιλίας ChatGPT🔄 💡συμβουλές για συνομιλίες με το ChatGPT, προσαρμοσμένα σχόλια, σελιδοδείκτες💡🔍Έξυπνη αναζήτηση στο ChatGPT: γρήγορος εντοπισμός συγκεκριμένων συνομιλιών🔍 📋Πίνακας διαχείρισης αλληλεπιδράσεων με το ChatGPT, φιλικό προς τον χρήστη περιβάλλον, πλήρεις επιλογές διαχείρισης, κατηγορίες, ετικέτες και περισσότερα📋
// @description:en ChatGPT ChatTree 🌳, 🚀permanent and unrestricted management of your interactions with ChatGPT🚀 🔄real-time updates and visualization of ChatGPT conversation tree🔄 💡ChatGPT conversation tips, custom annotations, bookmarks💡🔍Smart Search in ChatGPT: quickly locate specific conversations🔍 📋ChatGPT Interaction Management Panel, user-friendly interface, comprehensive ChatGPT interaction management options, categorization, tags, and more📋
// @description:eo ChatGPT ChatTree 🌳, 🚀eterna kaj senlima administrado de viaj interagoj kun ChatGPT🚀 🔄aktualigoj en reala tempo kaj vida prezento de la babilo-arbo de ChatGPT🔄 💡konsiletoj por babili kun ChatGPT, propraj notoj, legosignoj💡🔍Inteligenta serĉo en ChatGPT: rapide trovi specifajn konversaciojn🔍 📋Administra panelo por interagoj kun ChatGPT, uzantamika interfaco, ampleksaj opcioj por administri interagojn, kategorioj, etikedoj kaj pli📋
// @description:es ChatGPT ChatTree 🌳, 🚀gestión permanente e ilimitada de tus interacciones con ChatGPT🚀 🔄actualizaciones en tiempo real y visualización del árbol de conversación de ChatGPT🔄 💡consejos para conversaciones con ChatGPT, anotaciones personalizadas, marcadores💡🔍Búsqueda inteligente en ChatGPT: localiza rápidamente conversaciones específicas🔍 📋Panel de gestión de interacción con ChatGPT, interfaz fácil de usar, opciones completas de gestión de interacción, categorización, etiquetas y más📋
// @description:fi ChatGPT ChatTree 🌳, 🚀jatkuva ja rajoittamaton vuorovaikutuksesi hallinta ChatGPT:n kanssa🚀 🔄ChatGPT-keskustelupuun reaaliaikaiset päivitykset ja visualisointi🔄 💡vinkkejä keskusteluihin ChatGPT:n kanssa, mukautetut huomautukset, kirjanmerkit💡🔍Älykäs haku ChatGPT:ssa: löydä nopeasti tiettyjä keskusteluja🔍 📋ChatGPT-vuorovaikutuksen hallintapaneeli, käyttäjäystävällinen käyttöliittymä, kattavat vuorovaikutuksen hallintaoptiot, luokittelu, tagit ja lisää📋
// @description:fr ChatGPT ChatTree 🌳, 🚀surveillance continue et sans restriction de vos dialogues avec ChatGPT🚀 🔄rafraîchissement et affichage en direct de l'arborescence des échanges avec ChatGPT🔄 💡astuces pour discuter avec ChatGPT, notes sur mesure, favoris💡🔍Fouille astucieuse dans ChatGPT : repérage immédiat des discussions ciblées🔍 📋Tableau de bord pour la gestion des interactions avec ChatGPT, ergonomie intuitive, choix exhaustifs pour la supervision des dialogues, classement, balises et davantage📋
// @description:fr-CA ChatGPT ChatTree 🌳, 🚀contrôle ininterrompu de vos échanges avec ChatGPT🚀 🔄actualisation en temps réel de la structure de conversation de ChatGPT🔄 💡trucs pour converser avec ChatGPT, commentaires personnalisés, marque-pages💡🔍identification express des conversations spécifiques🔍 📋Tableau de contrôle des interactions avec ChatGPT, interface accessible, catégorisation, balises et plus📋
// @description:he ChatGPT ChatTree 🌳, 🚀ניהול תמידי וללא הגבלות של האינטראקציה שלך עם ChatGPT🚀 🔄עדכונים בזמן אמת וויזואליזציה של עץ השיחה של ChatGPT🔄 💡טיפים לשיחה עם ChatGPT, הערות מותאמות אישית, סימניות💡🔍חיפוש חכם ב-ChatGPT: מיקום מהיר של שיחות מסוימות🔍 📋פנל ניהול האינטראקציה עם ChatGPT, ממשק ידידותי למשתמש, אפשרויות ניהול האינטראקציה המלאות, קטגוריזציה, תגיות ועוד📋
// @description:hr ChatGPT ChatTree 🌳, 🚀stalno i neograničeno upravljanje vašim interakcijama s ChatGPT-om🚀 🔄ažuriranja u stvarnom vremenu i vizualizacija ChatGPT-ovog razgovornog stabla🔄 💡savjeti za razgovore s ChatGPT-om, prilagođene napomene, oznake💡🔍Pametno pretraživanje u ChatGPT: brzo lociranje određenih razgovora🔍 📋Ploča za upravljanje interakcijama s ChatGPT-om, sučelje pogodno za korisnika, sveobuhvatne opcije za upravljanje interakcijama, kategorizacija, oznake i još mnogo toga📋
// @description:hu ChatGPT ChatTree 🌳, 🚀állandó és korlátlan interakciók kezelése a ChatGPT-vel🚀 🔄ChatGPT beszélgetési fa valós idejű frissítései és vizualizációja🔄 💡beszélgetési tippek a ChatGPT-tel, egyéni megjegyzések, könyvjelzők💡🔍Intelligens keresés a ChatGPT-ben: gyorsan megtalálja a kijelölt beszélgetéseket🔍 📋ChatGPT Interakció Kezelési Panel, felhasználóbarát felület, átfogó interakciókezelési lehetőségek, kategorizálás, címkék és több📋
// @description:id ChatGPT ChatTree 🌳, 🚀pengelolaan interaksi Anda dengan ChatGPT secara permanen dan tanpa batas🚀 🔄pembaruan real-time dan visualisasi pohon percakapan ChatGPT🔄 💡tips percakapan ChatGPT, anotasi kustom, bookmark💡🔍Pencarian Cerdas di ChatGPT: cepat menemukan percakapan spesifik🔍 📋Panel Manajemen Interaksi ChatGPT, antarmuka yang ramah pengguna, pilihan manajemen interaksi ChatGPT yang komprehensif, kategorisasi, tag, dan lain-lain📋
// @description:it ChatGPT ChatTree 🌳, 🚀gestione permanente e illimitata delle tue interazioni con ChatGPT🚀 🔄aggiornamenti in tempo reale e visualizzazione dell'albero di conversazione di ChatGPT🔄 💡consigli per conversazioni con ChatGPT, annotazioni personalizzate, segnalibri💡🔍Ricerca intelligente in ChatGPT: individuazione rapida di conversazioni specifiche🔍 📋Pannello di gestione interazioni con ChatGPT, interfaccia user-friendly, opzioni complete di gestione interazioni, categorizzazione, tag e altro ancora📋
// @description:ja ChatGPT ChatTree 🌳, 🚀ChatGPTとの対話を永続的かつ無制限に管理🚀 🔄ChatGPT会話ツリーのリアルタイム更新と可視化🔄 💡ChatGPTとの会話のヒント、カスタム注釈、ブックマーク💡🔍ChatGPTでのスマート検索:特定の会話を迅速に見つける🔍 📋ChatGPTとの対話管理パネル、ユーザーフレンドリーなインターフェース、対話管理の包括的なオプション、カテゴリー、タグなど📋
// @description:ka ChatGPT ChatTree 🌳, 🚀თქვენი ინტერაქციების მუდმივი და ულიმიტო მართვა ChatGPT-თან🚀 🔄ChatGPT სტუმრობის ხეს რეალურ დროში განახლებები და ვიზუალიზაცია🔄 💡რჩევები სტუმრობაში ChatGPT-თან, პერსონალური კომენტარები, წიგნის ნიშნები💡🔍სწრაფი ძებნა ChatGPT-ში: კონკრეტულ სასტუმროში სწრაფი მოძებნა🔍 📋ChatGPT-თან ინტერაქციის მართვის პანელი, მომხმარებლისთვის მეგობრიანი ინტერფეისი, ინტერაქციის მართვის რეგულაცია, კატეგორიზაცია, თეგები და სხვა📋
// @description:ko ChatGPT ChatTree 🌳, 🚀ChatGPT와의 상호작용을 영구적이고 무제한으로 관리🚀 🔄ChatGPT 대화 트리의 실시간 업데이트 및 시각화🔄 💡ChatGPT 대화 팁, 사용자 정의 주석, 북마크💡🔍ChatGPT 스마트 검색: 특정 대화를 빠르게 찾기🔍 📋ChatGPT 상호작용 관리 패널, 사용자 친화적 인터페이스, 전체 상호 작용 관리 옵션, 카테고리, 태그 및 기타📋
// @description:nb ChatGPT ChatTree 🌳, 🚀permanent og ubegrenset styring av dine interaksjoner med ChatGPT🚀 🔄sanntidsoppdateringer og visualisering av ChatGPT samtaletre🔄 💡ChatGPT samtale tips, egendefinerte kommentarer, bokmerker💡🔍Smart søk i ChatGPT: raskt finne spesifikke samtaler🔍 📋ChatGPT interaksjonsstyringspanel, brukervennlig grensesnitt, omfattende interaksjonsstyringsalternativer, kategorisering, tagger og mer📋
// @description:nl ChatGPT ChatTree 🌳, 🚀permanente en onbeperkte beheer van uw interacties met ChatGPT🚀 🔄real-time updates en visualisatie van ChatGPT gespreksboom🔄 💡ChatGPT gesprekstips, aangepaste opmerkingen, bladwijzers💡🔍Slim zoeken in ChatGPT: snel specifieke gesprekken lokaliseren🔍 📋ChatGPT Interactiebeheerpaneel, gebruiksvriendelijke interface, uitgebreide beheeropties voor interactie, categorisatie, tags en meer📋
// @description:pl ChatGPT ChatTree 🌳, 🚀stałe i nieograniczone zarządzanie interakcjami z ChatGPT🚀 🔄aktualizacje w czasie rzeczywistym i wizualizacja drzewa rozmów z ChatGPT🔄 💡wskazówki do rozmów z ChatGPT, niestandardowe komentarze, zakładki💡🔍inteligentne wyszukiwanie w ChatGPT: szybkie lokalizowanie konkretnych rozmów🔍 📋Panel zarządzania interakcjami z ChatGPT, intuicyjny interfejs, kompleksowe opcje zarządzania interakcjami, kategorie, etykiety i więcej📋
// @description:pt-BR ChatGPT ChatTree 🌳, 🚀gerenciamento permanente e irrestrito de suas interações com o ChatGPT🚀 🔄atualizações em tempo real e visualização da árvore de conversas do ChatGPT🔄 💡dicas de conversa com ChatGPT, anotações personalizadas, favoritos💡🔍Busca inteligente no ChatGPT: localizar rapidamente conversas específicas🔍 📋Painel de gerenciamento de interação com ChatGPT, interface amigável, opções abrangentes de gerenciamento de interação, categorização, tags e mais📋
// @description:ro ChatGPT ChatTree 🌳, 🚀administrare permanentă și nelimitată a interacțiunilor tale cu ChatGPT🚀 🔄actualizări în timp real și vizualizarea arborelui de conversație ChatGPT🔄 💡sfaturi de conversație cu ChatGPT, comentarii personalizate, semne de carte💡🔍Căutare inteligentă în ChatGPT: localizare rapidă a conversațiilor specifice🔍 📋Panoul de administrare a interacțiunilor cu ChatGPT, interfață prietenoasă, opțiuni complete de administrare a interacțiunilor, categorii, etichete și mai mult📋
// @description:ru ChatGPT ChatTree 🌳, 🚀постоянное и неограниченное управление вашими взаимодействиями с ChatGPT🚀 🔄обновления в реальном времени и визуализация дерева диалогов ChatGPT🔄 💡советы для разговоров с ChatGPT, настраиваемые комментарии, закладки💡🔍Умный поиск в ChatGPT: быстрое нахождение конкретных диалогов🔍 📋Панель управления взаимодействием с ChatGPT, удобный интерфейс, полные настройки управления взаимодействием, категории, теги и многое другое📋
// @description:sk ChatGPT ChatTree 🌳, 🚀trvalé a neobmedzené riadenie vašich interakcií s ChatGPT🚀 🔄aktualizácie v reálnom čase a vizualizácia rozhovorového stromu ChatGPT🔄 💡tipy na rozhovory s ChatGPT, vlastné poznámky, záložky💡🔍inteligentné vyhľadávanie v ChatGPT: rýchle nájdenie konkrétnych rozhovorov🔍 📋Panel na správu interakcií s ChatGPT, používateľsky prívetivé rozhranie, komplexné možnosti riadenia interakcií, kategórie, štítky a viac📋
// @description:sr ChatGPT ChatTree 🌳, 🚀stalno i neograničeno upravljanje vašim interakcijama sa ChatGPT🚀 🔄ažuriranja u realnom vremenu i vizualizacija stabla razgovora ChatGPT🔄 💡saveti za razgovore sa ChatGPT, prilagođene napomene, obeleživači💡🔍Pametna pretraga u ChatGPT: brzo lociranje specifičnih razgovora🔍 📋Panel za upravljanje interakcijama sa ChatGPT, korisnički prijatan interfejs, sveobuhvatne opcije za upravljanje interakcijama, kategorizacija, oznake i više📋
// @description:sv ChatGPT ChatTree 🌳, 🚀permanent och obegränsad hantering av dina interaktioner med ChatGPT🚀 🔄real-tidsuppdateringar och visualisering av ChatGPTs konversationsträd🔄 💡ChatGPT konversationstips, anpassade anteckningar, bokmärken💡🔍Smart sökning i ChatGPT: snabbt lokalisera specifika konversationer🔍 📋ChatGPT Interaktionshanteringspanel, användarvänligt gränssnitt, omfattande interaktionshanteringsalternativ, kategorisering, taggar och mer📋
// @description:th ChatGPT ChatTree 🌳, 🚀การจัดการที่ถาวรและไม่จำกัดกับการโต้ตอบของคุณกับ ChatGPT🚀 🔄การอัปเดตและการแสดงภาพของต้นไม้บทสนทนาของ ChatGPT ในเวลาจริง🔄 💡เคล็ดลับการสนทนา ChatGPT, หมายเหตุที่กำหนดเอง, บุ๊กมาร์ก💡🔍การค้นหาอย่างฉลาดใน ChatGPT: ค้นหาบทสนทนาเฉพาะอย่างรวดเร็ว🔍 📋แผงควบคุมการโต้ตอบกับ ChatGPT, อินเตอร์เฟซที่เป็นมิตร, ตัวเลือกการจัดการโต้ตอบที่ครอบคลุม, การจัดประเภท, แท็ก และอื่น ๆ📋
// @description:tr ChatGPT ChatTree 🌳, 🚀ChatGPT ile etkileşimlerinizi kalıcı ve sınırsız bir şekilde yönetme🚀 🔄ChatGPT konuşma ağacının gerçek zamanlı güncellemeleri ve görselleştirmesi🔄 💡ChatGPT ile konuşma ipuçları, özel notlar, yer imleri💡🔍ChatGPT'te Akıllı Arama: belirli konuşmaları hızlı bir şekilde bulun🔍 📋ChatGPT Etkileşim Yönetim Paneli, kullanıcı dostu arayüz, kapsamlı etkileşim yönetim seçenekleri, kategorizasyon, etiketler ve daha fazlası📋
// @description:uk ChatGPT ChatTree 🌳, 🚀постійне та безобмежене управління вашими взаємодіями з ChatGPT🚀 🔄оновлення дерева розмови ChatGPT у реальному часі + візуалізація🔄 💡поради для розмови з ChatGPT, власні анотації, закладки💡🔍розумний пошук в ChatGPT: швидке знаходження конкретних розмов🔍 📋Панель управління взаємодіями з ChatGPT, зручний інтерфейс, повний набір опцій для управління взаємодіями, категорії, теги та більше📋
// @description:ug ChatGPT ChatTree 🌳, 🚀مەڭگۈلۈك ۋە چەكسىز بولغان ChatGPT بىلەن ئالاقىڭىزنى باشقۇرۇڭ🚀 🔄ChatGPT سۆھبەت ئاگىدىنى چۈشەندۈرۈش ۋە كۆرسىتىش🔄 💡ChatGPT بىلەن سۆھبەتكە كېلىشىش ئۈچۈن تەۋسىيەلەر، شەخسىي ئىزاھاتلار، خەتكۈچلەر💡🔍ChatGPT دا ئەقىللىق ئىزدەش: مەلۇم سۆھبەتلەرنى تېز تاپالايدۇ🔍 📋ChatGPT ئارىلىق باشقۇرۇش تاختىسى، ئىشلىتىشكە يېڭىراك يۈزى، تولۇق باشقۇرۇش تاللانمىلىرى، تۈرلەر، خەتكۈچلەر ۋە باشقا📋
// @description:vi ChatGPT ChatTree 🌳, 🚀quản lý vĩnh viễn và không giới hạn các tương tác của bạn với ChatGPT🚀 🔄cập nhật và trực quan hóa cây trò chuyện ChatGPT theo thời gian thực🔄 💡mẹo trò chuyện với ChatGPT, ghi chú tùy chỉnh, dấu trang💡🔍Tìm kiếm thông minh trong ChatGPT: xác định nhanh các cuộc trò chuyện cụ thể🔍 📋Bảng điều khiển quản lý tương tác với ChatGPT, giao diện thân thiện, các tùy chọn quản lý tương tác đầy đủ, phân loại, thẻ và nhiều hơn nữa📋
// @description:zh-CN ChatGPT ChatTree 🌳,🚀永久、不受限制地管理您与ChatGPT的每一次互动🚀 🔄实时ChatGPT对话树更新+可视化🔄 💡ChatGPT对话提示,自定义注释,书签💡🔍 ChatGPT智能搜索:快速定位特定对话🔍 📋ChatGPT互动管理面板,界面友好,全面ChatGPT互动管理选项,分类、标签和更多📋
// @description:zh-TW ChatGPT ChatTree 🌳,🚀永久、不受限制地管理您與ChatGPT的每一次互動🚀 🔄即時ChatGPT對話樹更新+視覺化🔄 💡ChatGPT對話提示,自訂註解,書籤💡🔍 ChatGPT智能搜尋:快速定位特定對話🔍 📋ChatGPT互動管理面板,介面友善,全面ChatGPT互動管理選項,分類、標籤和更多📋
// @description:zh-HK ChatGPT ChatTree 🌳,🚀永久、不受限制地管理您與ChatGPT的每一次互動🚀 🔄實時ChatGPT對話樹更新+可視化🔄 💡ChatGPT對話提示,自定義註釋,書籤💡🔍 ChatGPT智能搜索:快速定位特定對話🔍 📋ChatGPT互動管理面板,界面友好,全面ChatGPT互動管理選項,分類、標籤和更多📋
// @description:zh-SG ChatGPT ChatTree 🌳,🚀永久、不受限制地管理您与ChatGPT的每一次互动🚀 🔄实时ChatGPT对话树更新+可视化🔄 💡ChatGPT对话提示,自定义注释,书签💡🔍 ChatGPT智能搜索:快速定位特定对话🔍 📋ChatGPT互动管理面板,界面友好,全面ChatGPT互动管理选项,分类、标签和更多📋
// @author   cuizhenzhi
// @match    *://chatgpt.com/*
// @grant    GM_addStyle
// @grant    GM_getResourceText
// @require  https://cdnjs.cloudflare.com/ajax/libs/d3/5.16.0/d3.min.js
// @require  https://cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js
// @require  https://cdnjs.cloudflare.com/ajax/libs/interact.js/1.10.11/interact.min.js
// @require  https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/2.0.8/clipboard.min.js
// @require      https://code.jquery.com/jquery-3.5.1.min.js
// @require      https://cdn.jsdelivr.net/npm/@popperjs/core@2.10.2/dist/umd/popper.min.js
// @run-at document-end
// @homepageURL         https://github.com/cuizhenzhi/ChatTree
// @supportURL   https://github.com/cuizhenzhi/ChatTree/issues
// @license GPL-2.0-only
// @icon data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="24" width="24" stroke-width="8" fill="none" stroke="white" viewBox="0 0 100 100"><rect width="100%" height="100%" fill="rgb(25, 195, 125)"/><path d="M40 61 V89 Q40 90 41 90 H59 Q60 90 60 89 V61 Q60 60 61 61 Q66 65 69 61 70 60 71 61 75 65 79 61 80 60 81 61 85 65 89 61 90 60 89 59 75 55 61 41 60 40 60.5 40.5 Q66 45 69 41 70 40 71 41 75 45 79 41 80 40 79 39 70 35 61 26 60 25 61 26 Q65 30 69 26 70 25 69 24 60 20 51 11 50 10 49 11 40 20 31 24 30 25 31 26 Q35 30 39 26 40 25 39 26 30 35 21 39 20 40 21 41 Q25 45 29 41 30 40 31 41 35 45 39 41 40 40 39 41 25 55 11 59 10 60 11 61 Q15 65 19 61 20 60 21 61 25 65 29 61 30 60 31 61 35 65 39 61 40 60 40 61"></path></svg>
// ==/UserScript==

(function (node) {
// @require             https://cdn.jsdelivr.net/gh/kudoai/chatgpt.js@516ad148b02335b98db82c89dec02e5fa28c7d56/dist/chatgpt-2.3.13.min.js

  "use strict";
  //chatgpt.logout();
  console.log("chatgpt chattree!");
  const isDevelopmentMode = true;
  // 定义支持的类别
  const LogCategories = {
    //DEFAULT: 'DEFAULT',
    IMPORTANT: 'IMPORTANT',
    DEBUG: 'DEBUG',
    INFO: 'INFO',
    WARNING: 'WARNING',
    ERROR: 'ERROR',
    SUCCESS: 'SUCCESS',
  };
  const LogStyles = {
    IMPORTANT: 'background-color: #007bff; color: white; padding: 8px; font-size: 16px;',
    //DEBUG: 'background-color: #f0f0f0; color: #333; border: 1px solid #ccc; padding: 5px;',
    INFO: 'background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; padding: 0px;',
    WARNING: 'background-color: yellow; color: black; border: 1px solid red; padding: 5px;',
    ERROR: 'color: red; font-weight: bold; font-size: 16px;',
    SUCCESS: 'background-color: green; color: white; border: 1px solid black;',
  }

  class Logger {
    constructor(moduleName) {
      this.moduleName = moduleName;
      this.log(LogCategories.SUCCESS, `${this.moduleName} logger created!`);
    }

    log(category = LogCategories.DEBUG, ...message) {
      // 发布模块信息
      //console.log(`[${this.moduleName}] ${category}: ${message}`);
      let metadata = {
        moduleName: this.moduleName,
        category: category,
        style: LogStyles[category],
        //mesage: message,
      }

      let info = [metadata, ...message];
      // 调用全局 log 函数

      //console.log('Global log function, info:', info);
      log(...info);
    }
  }

  function log(...messages) {
    // 如果不是开发模式,则直接返回
    if (!isDevelopmentMode) {
      return;
    }

    // 默认使用的类别
    let defaultCategory = LogCategories.DEBUG;
    let defaultStyle = LogStyles.DEBUG;
    // 检查是否提供了类别参数
    let category = defaultCategory;
    let style = defaultStyle;
    let metadata = {};
    metadata.moduleName = 'undefined';
    //console.log(messages[0], messages[0].category, LogCategories[messages[0].category])

    if (messages.length > 0 && typeof messages[0] === 'object' && LogCategories[messages[0].category]) {
      metadata = messages.shift();
      category = metadata.category;
      style = metadata.style;
    }
    if (LogCategories[messages[0]]) {
      category = messages[0];
      style = LogStyles[category];
      messages.shift();
    }
    // 根据类别进行不同的处理
    switch (category) {
      case LogCategories.IMPORTANT:
        console.log(`%c[IMPORTANT]\n ${metadata.moduleName}\n ${messages.map(msg => typeof msg === 'object' ? JSON.stringify(msg) : msg).join('\n')}`, style);
        // 这里可以添加额外的处理逻辑
        break;
      case LogCategories.DEBUG:
        //console.debug(`%c[DEBUG]\n ${metadata.moduleName}\n ${messages.map(msg => typeof msg === 'object' ? JSON.stringify(msg) : msg).join('\n')}`, style);
        console.debug(`[DEBUG]\n ${metadata.moduleName}\n`, ...messages);
        break;
      case LogCategories.INFO:
        console.info(`%c[INFO]\n ${metadata.moduleName}\n ${messages.map(msg => typeof msg === 'object' ? JSON.stringify(msg) : msg).join('\n')}`, style);
        break;
      case LogCategories.WARNING:
        console.warn(`%c[WARNING]\n ${metadata.moduleName}\n ${messages.map(msg => typeof msg === 'object' ? JSON.stringify(msg) : msg).join('\n')}`, style);
        break;
      case LogCategories.ERROR:
        console.error(`%c[ERROR]\n ${metadata.moduleName}\n ${messages.map(msg => typeof msg === 'object' ? JSON.stringify(msg) : msg).join('\n')}`, style);
        break;
      case LogCategories.SUCCESS:
        console.log(`%c[SUCCESS]\n ${metadata.moduleName}\n ${messages.map(msg => typeof msg === 'object' ? JSON.stringify(msg) : msg).join('\n')}`, style);
        break;
      default:
        console.log(`[${category.toUpperCase()}]`, ...messages);
    }


    /* 以下输出方式: 如果不是字符串形式, 则正常输出
    *   // 根据类别进行不同的处理
  switch (category) {
    case LogCategories.IMPORTANT:
      console.log(`%c[IMPORTANT]\n ${metadata.moduleName}\n ${messages.map(msg => typeof msg !== 'string' ? (console.log(msg), '') : msg).join('\n')}`, style);
      // 这里可以添加额外的处理逻辑
      break;
    case LogCategories.DEBUG:
      console.debug(`[DEBUG]\n ${metadata.moduleName}`);
      messages.forEach(msg => {
        if (typeof msg !== 'string') {
          console.log(msg);
        } else {
          console.debug(msg);
        }
      });
      break;
    case LogCategories.INFO:
      console.info(`%c[INFO]\n ${metadata.moduleName}\n ${messages.map(msg => typeof msg !== 'string' ? (console.log(msg), '') : msg).join('\n')}`, style);
      break;
    case LogCategories.WARNING:
      console.warn(`%c[WARNING]\n ${metadata.moduleName}\n ${messages.map(msg => typeof msg !== 'string' ? (console.log(msg), '') : msg).join('\n')}`, style);
      break;
    case LogCategories.ERROR:
      console.error(`%c[ERROR]\n ${metadata.moduleName}\n ${messages.map(msg => typeof msg !== 'string' ? (console.log(msg), '') : msg).join('\n')}`, style);
      break;
    case LogCategories.SUCCESS:
      console.log(`%c[SUCCESS]\n ${metadata.moduleName}\n ${messages.map(msg => typeof msg !== 'string' ? (console.log(msg), '') : msg).join('\n')}`, style);
      break;
    default:
      console.log(`[${category.toUpperCase()}]`, ...messages);
  }
  * */
  }

  function isDark() {
    return document.documentElement.classList.contains('dark');
  }

  GM_addStyle(`

        #searchTopicContainer {
            margin-bottom: 20px;
        }

         .conversation {
            border: 1px solid #ccc;
            margin-bottom: 10px;
            padding: 10px;
            margin-top: 20px;
            margin-left: 20px;
            margin-right: 20px;
            display: flex;
            justify-content: space-between;
            align-items: center;
            transition: border 0.1s ease-in-out; /* 添加过渡效果 */
            border-radius: 4px; /* 添加边框圆角 */
        }

        .conversation:hover {
            border: 2px solid black; /* 鼠标悬停时改变边框样式 */
            box-shadow: 0px 0px 10px black; /* 添加外边框阴影效果 */
        }


        .category {
            background-color: #f3f3f3;
            display: inline-block;
            padding: 2px 8px;
            margin-right: 2px;
            margin-bottom: 2px;

        }

        .chatTreeTag {
            background-color: #e0e0e0;
            display: inline-block;
            padding: 2px 8px;
            margin-right: 5px;
            margin-bottom: 2px;

        }

        .delete-icon,
        .add-icon {
            cursor: pointer;
            margin-left: 5px;
        }

        .delete-icon:hover,
        .add-icon:hover {
            background: #adb5bd;
        }

        .conversation h3 {
            display: inline-block;
            margin: 0; /* 移除默认边距 */
        }

        .conversation-actions {
            margin-left: auto; /* 推到右侧 */
        }




        .topicContainer{
          width: 35%;
          overflow: hidden;
        }

        .optionsContainer{
          width: 12%;
          display: flex;
          justify-content: center;
        }

        .tagContainers {
            width: 26%;
            display: flex;
            justify-content: center;
            align-items: center;
            flex-wrap: wrap;
        }

        .categoriesContainer {
            width: 26%;
            display: flex;
            justify-content: center;
            align-items: center;
            flex-wrap: wrap;
        }

        #managePanel {
            color: black;
            overflow: hidden;
            background: rgb(255,253,249);
            position: fixed;
            top: 0px;
            left: 0px;
            width: 100%;
            height: 100%;
            z-index: 999;  /* 根据需要设置,以确保该元素位于其他元素之上 */
        }



        #panelToggleButtonSVGShow {
            position: fixed;
            left: 0;
            top: 40%;
            z-index: 1000;
            padding: 10px 20px;
            border: none;
            background: rgb(171, 104, 255);
            color: white;
            cursor: pointer;
        }
        .card-header{
            height: 40px;
            display: flex;
            border: 1px solid #adb5bd;
            align-items: center;
            justify-content: space-between;
            padding: 0.5rem 1.25rem;  /* Optional: for adding some space inside the card-header */

        }
        .card-body{
            height: 40px;
            display: flex;
            border: 1px solid #adb5bd;
            align-items: center;
            justify-content: space-between;
            padding: 0.5rem 1.25rem;  /* Optional: for adding some space inside the card-header */
            overflow: hidden;  /* Add this line to hide the overflow content */
        }
        .card-header h5 {
            margin: 0;  /* Optional: to remove the default margin of the h5 element */
        }
        .btn.btn-link:hover {
            cursor: pointer !important;
            text-decoration: underline !important;
            border: 1px solid #80bdff !important;
            border-radius: 5px !important;
            color: #005cbf !important;
            margin-right: auto;  /* It pushes the link to the extreme right */
        }
        .btn.btn-sm.btn-primary{
            background: #007bff;
            color: white;
            border-radius: 5px !important;
            border: 1px solid #80bdff !important;
            height: 30px;
            cursor: pointer;
        }
        .btn.btn-sm.btn-danger{
            background: #dc3545;
            color: white;
            border-radius: 5px !important;
            border: 1px solid red !important;
            height: 30px;
            cursor: pointer;
        }
        .btn.btn-sm.btn-success{
            background: #28a745;
            color: white;
            border-radius: 5px !important;
            border: 1px solid green !important;
            height: 30px;
            cursor: pointer;
        }

        .collapse {
            height: 0;
            transition: height 0.5s ease-in-out;
            overflow: hidden;
        }




    @keyframes textBlink {
    0%, 100% {
        color: black;
    }
    50% {
        color: white;
    }
}

.blinkText {
    animation: textBlink 1s ease-in-out infinite;
}

    @keyframes highlight {
    0% {
        background-color: initial;
    }
    50% {
        background-color: #ffffff;
    }
    100% {
        background-color: initial;
    }
}
.highlight {
    animation: highlight 2s ease-in-out infinite;
}

  @keyframes highlightt {
    0% {
        background-color: initial;
    }
    50% {
        background-color: #000000;

    }
    100% {
        background-color: initial;
    }
}
.highlightt {
    animation: highlightt 2s ease-in-out infinite;
}

    .close-button {
    margin-left: 5px;
    color: red;
    cursor: pointer;
}

    .suggestions-container {
    position: absolute;

    left: 40px;
    width: 270px;
    top: 100%; /* Place it below the search box */
     max-height: 150px; /* Adjust as per your preference */
    overflow-y: auto; /* Add scrollbar if content is too much */
    border: 1px solid #ccc;
    background-color: white;
    z-index: 99; /* Make it appear on top of other elements */
}

.suggestion-item {
    padding: 10px;
    cursor: pointer;
}

.suggestion-item:hover {
    background-color: #eee;
}



        .node-menu {
            display: none;
            position: absolute;
            border: 1px solid black;
            background-color: white;
        }

        #contentDiv {
            box-sizing: border-box;
        }

        /* Cursor styles */
        .node {
            cursor: pointer;
        }
       .node circle {
            fill: #e5e5e5;
        }

        .node circle.chatgpt {
        }

        .node circle.用户 {
            fill: #1E90FF;
        }


        .node.descendant-dragging circle {
            fill: #b0e0e6 !important;
        }

        .node.selectedNode circle {
            fill: red !important;
        }

        .link.highlighted {
            /*stroke: yellow;*/
            stroke: #808080;
            stroke-width: 4px;
        }

        .link.descendant-highlighted {
            stroke: #d3d3d3; /* 选择你想要的颜色 */
            stroke-width: 4px;
        }


        .content-text {
            white-space: pre-line;
        }

        /* Menu styles */
        .menu {
            opacity: 0;
            transition: opacity 300ms, transform 300ms;
            transform: translateX(100%); /* Start from the right */
            border: none; /* Remove border */
        }

        .menu.show {
            opacity: 1;
            transform: translateX(0); /* Move to its original position */
        }

        #commentForm {
            width: 300px;
            padding: 20px;
            border: 1px solid #ccc;
            border-radius: 5px;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
        }

        #commentForm label, #commentForm textarea, #commentForm button {
            display: block;
            width: 100%;
            margin-bottom: 10px;
        }

        #commentForm textarea {
            resize: none; /* 禁止调整大小 */
        }

        .commentHoverEffect {
    transition: all 0.3s ease-in-out;
}

.commentHoverEffect:hover {
    font-weight: bold;
    text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
}

        /*这是显示对话内容的注释样式*/
        .comment-text {
            color: #ff5555;
            font-style: italic;
            margin-right: 10px;
            display: block;
            font-weight: bold;
            border-bottom: 2px dashed #ff5555;
        }

        /* SVG background */
        #thumbnailSvg {
            /*background-color: rgba(128, 128, 128, 0.4); !* Gray with 0.4 opacity *!*/
            /*background-image: linear-gradient(to top, #fad0c4 0%, #ffd1ff 100%);*/
            background: linear-gradient(to top, rgba(250, 208, 196, 1) 0%, rgba(255, 209, 255, 0.9) 100%);
           /*background-image: linear-gradient(to top, rgba(220,220,220, 1) 0%, rgba(220,220,220, 1) 100%);*/


        }
         #mainSvg {
            /*background-color: rgba(128, 128, 128, 0.4); !* Gray with 0.4 opacity *!*/
            /*background-image: linear-gradient(to top, #fad0c4 0%, #ffd1ff 100%);*/
            /*background-image: linear-gradient(to top, rgba(220,220,220, 0.2) 0%, rgba(220,220,220, 0.2) 100%);*/
        }

        #search-container {
            top: 20px;
            left: 20px;
            display: flex;
            align-items: center;
            position: fixed;
            width: 50px;
            height: 40px;
            color: black;
            /*overflow: hidden;*/

            background: transparent;
            /*flex-wrap: wrap;*/
            /*z-index: 10000;*/

        }

        #search-icon {
            width: 40px;
            height: 40px;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 24px; /* adjust as needed */
            background: transparent;
            /*background-color: #f1f1f1;*/
        }

        #search-box {

            margin-top: 10px;
            width: 300px;
            height: 40px;
            /*background-color: #f1f1f1;*/
            border: none;
            padding: 0 10px;
            outline: none;
            background: transparent;
            opacity: 0;
            pointer-events: none;
            border-radius: 6px;
        }

        #search-btn {
            padding: 5px 15px;
            margin-left: 10px;
            display: none;
        }

        #search-history {
            position: absolute;
            top: 50px;
            left: 0;
            /*display: none;*/
            opacity: 0; /* 设置为 0 使其默认隐藏 */
            /*display: ;*/
            display: flex;
            flex-direction: row; /* 横向排列 */
            flex-wrap: wrap; /* 当空间不足时换行 */
            width: 0px;
            min-height: 0px; /* instead of height: 0px; */
            align-content: flex-start;
            height: 20px;
            max-height: 200px;
            /*align-items: center;  !* 确保每行的内容垂直居中对齐 *!*/
            overflow-y: scroll;
            overflow-x: hidden;
            padding: 10px; /* 为了避免内容紧贴边缘,给予一些内边距 */
            /*z-index: 10001;*/
            /*border-radius: 6px;*/
        }

        #search-results-count {
            box-sizing: border-box;
            line-height: 26px; /* 重置行高 */
            margin: 0;
            padding: 3px 6px;
            display: flex;
            justify-content: center; /* 水平居中 */
            align-items: center; /* 垂直居中 */
        }

        .history-item {
            display: flex;
            align-items: center;
            width: auto; /* 根据内容自动调整宽度 */
            max-width: 150px; /* 设置最大宽度 */
            max-height: 30px;
            overflow: hidden;
            background-color: #f5f5f5;
            border-radius: 15px;
            padding: 5px 10px;
            margin-top: 10px;
            margin-right: 10px; /* 每个 history-item 之间的间距 */
            /*gap: 10px;*/

        }

        .history-text {
            /*max-width: 100px;  !* 设置最大宽度 *!*/
            flex-shrink: 1;
            min-width: 0;
            white-space: nowrap; /* 防止文本换行 */
            overflow: hidden;
            text-overflow: ellipsis; /* 使用省略号 (...) 表示被裁切的文本 */
            /*padding: 5px 10px;*/

        }

        .history-delete {
            cursor: pointer;
            padding: 5px;
            border-radius: 50%;
            transition: background-color 0.3s;
            margin-left: auto; /* 这会将按钮推到其父元素的右边 */
        }

        .history-delete:hover {
            background-color: rgba(200, 0, 0, 0.1);
        }


        #settingsDiv, .actionDiv, .rightMiddleDiv {
            width: 40px;
            height: 40px;
            display: flex;
            align-items: center;
            justify-content: center;
            position: fixed;
            z-index: 1000;
            background-color: rgba(255, 255, 255, 0.8);
            border-radius: 50%; /* 让div成为圆形 */
            /*box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5);*/
            box-shadow: none;
            transition: background-color 0.3s; /* 平滑过渡效果 */
            user-select: none;
        }

        .rightAlwaysShownDiv {

            position: fixed;
            bottom: 70%;
            display: flex;
            right: 10px;
            cursor: pointer;
            font-size: 1.5em;
            width: 40px;
            height: 40px;
            align-items: center;
            justify-content: center;
            z-index: 1000;
            background-color: rgba(191,96,210,0.5);
            border-radius: 50%; /* 让div成为圆形 */
            /*box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5);*/
            box-shadow: none;
            transition: background-color 0.3s; /* 平滑过渡效果 */
            user-select: none;
        }

        #colorSelectDiv {
            bottom: 64%;
            right: 10px;
            cursor: pointer;
            color: deepskyblue;
            font-size: 1.5em;
        }
       .language-container {
          width: 200px;
          margin: 20px auto;
          position: relative;
          bottom: 56%;
          right: 60px;
        }
        #feedbackDiv {
            bottom: 40%;
            display: flex;
            right: 10px;
            cursor: pointer;
            color: deepskyblue;
            font-size: 1.5em;
        }

        #WeChatDiv {
            bottom: 46%;
            right: 10px;
            cursor: pointer;
            color: deepskyblue;
            font-size: 1.5em;
        }
        #TencentDiv {
            bottom: 52%;
            right: 10px;
            cursor: pointer;
            color: deepskyblue;
            font-size: 1.5em;
        }
       #languageSelectDiv {
            bottom: 58%;
            right: 10px;
            cursor: pointer;
            color: deepskyblue;
            font-size: 12px;
        }
        .rightMiddleDiv:hover  {
            background-color: rgba(200, 200, 255, 0.9); /* 悬停时的背景色 */
        }
        .rightAlwaysShownDiv:hover {
            background-color: rgba(200, 200, 255, 0.9); /* 悬停时的背景色 */
        }
        #rightMiddleMenu {
          position: fixed;
          bottom: 52%;
          right: 60px;
          z-Index: 10000;
        }

       .actionDiv {
            display: none;
            right: 10px;
            cursor: pointer;
            color: deepskyblue;
            font-size: 1.5em;
        }

        @keyframes spin {
            0% {
                transform: rotate(0deg);
            }
            100% {
                transform: rotate(360deg);
            }
        }

        #settingsDiv {
            animation: spin 4s linear infinite;
        }

        #settingsDiv {
            right: 10px;
            bottom: 10px;
            color: rebeccapurple; /* 或你想要的任何颜色 */
            cursor: pointer;
            display: flex;
            justify-content: center; /* 水平居中 */
            align-items: center; /* 垂直居中 */
        }

        #plusDiv {
            bottom: 60px;
            right: 60px;
        }

        #minusDiv {
            bottom: 60px;
        }

        #thumbNailDiv {
            bottom: 10px;
            right: 60px;
        }

        #refreshTree {
            bottom: 10px;
            right: 110px;
        }

        #undoDiv {
            bottom: 60px;
            right: 160px;
        }

        #redoDiv {
            bottom: 60px;
            right: 110px;
        }

        #deleteDiv {
            bottom: 10px;
            right: 160px;
        }



        #settingsDiv:hover, .actionDiv:hover {
            background-color: rgba(200, 200, 255, 0.9); /* 悬停时的背景色 */
        }






.language-dropdown {
    width: 100%;
    padding: 10px 15px;
    font-size: 16px;
    border-radius: 5px;
    border: none;
    appearance: none; /* 移除默认的外观 */
    -webkit-appearance: none; /* 为了Safari */
    -moz-appearance: none; /* 为了Firefox */
    background-color: #f5f5f5;
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
    cursor: pointer;
    transition: background-color 0.2s;
    color: black;
}

.language-dropdown:focus {
    outline: none;
    background-color: #e9e9e9;
}

.language-container::after {


    font-size: 14px;
    position: absolute;
    right: 15px;
    top: 50%;
    transform: translateY(-50%);
    pointer-events: none; /* 这样它就不会干扰下拉列表的点击事件了 */
}

    `);

  const getLang = function (selectLang = null) {
    let lang =
      `{
   "ar": {
        "chatTreeRunning": "🥳🌳ChatTree🌳في التشغيل!🎉",
        "updateCurrentConversationTree": "🌈 تحديث شجرة المحادثة الحالية 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 تعديل لون الخلفية والشفافية 🎨",
        "toggleConversationTree": "🌳 عرض/إخفاء شجرة المحادثة 🌳",
        "noDatabaseAndCreationFailed": "لا يوجد قاعدة بيانات، ولم يتم الإنشاء بنجاح! الخروج من البرنامج النصي!",
        "mismatchedLink": "الرابط غير متطابق، يرجى تحديث الصفحة!",
        "startUpdatingConversationTree": "بدء تحديث شجرة المحادثة",
        "selectedItem": "لقد اخترت {item}",
        "successSavingChanges": "تم حفظ التغييرات بنجاح!",
        "codeCopiedToClipboard": "تم نسخ الكود إلى الحافظة!",
        "contentCopied": "تم نسخ المحتوى!",
        "emptyCommentPrompt": "التعليق فارغ. هل تريد ضبط التعليق على \\"فارغ\\"؟",
        "confirmDeleteLinkData": "تم التحقق من الرابط كـ {item}! هل تريد حذف بيانات هذا الرابط؟ سيتم حذف جميع محتوى المحادثة والتعليقات!",
        "confirmCurrentURL": "تم التحقق من الرابط كـ {item}! هل أنت متأكد؟",
        "commentSetToEmpty": "تم تعيين التعليق على \\"فارغ\\"",
        "enterCommentFirst": "الرجاء إدخال التعليق أولا",
        "commentSaved": "تم حفظ التعليق",
        "conversationDataDeleted": "تم حذف بيانات المحادثة بنجاح",
        "executeSearchWithQuery": "تنفيذ البحث: {item}",
        "searchPlaceholder": "أدخل كلمات البحث...",
        "searchButton": "بحث",
        "searchInContent": "في محتوى المحادثة",
        "searchInComments": "في التعليقات",
        "searchInBoth": "في المحتوى والتعليقات",
        "goToPrevious": "الانتقال إلى السابق",
        "goToNext": "الانتقال إلى التالي",
        "numberOfMatches": "{matches} نتائج",
        "nodeDetails": "تفاصيل",
        "enterComment": "الرجاء إدخال تعليق",
        "userCommentSave": "حفظ",
        "userCommentCancel": "إلغاء",
        "userCommentClear": "مسح",
        "openAdminPanel": "لوحة الإدارة",
        "allCategoriesFilter": "جميع الفئات",
        "conversationTitle": "عنوان",
        "actionOptions": "الإجراءات",
        "conversationCategory": "الفئة",
        "conversationTags": "العلامات"
    },
    "bg": {
        "chatTreeRunning": "🥳🌳ChatTree🌳работи!🎉",
        "updateCurrentConversationTree": "🌈 Актуализация на текущото дърво на разговори 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Регулиране на цвета на фона и прозрачността 🎨",
        "toggleConversationTree": "🌳 Показване/скриване на дървото на разговори 🌳",
        "noDatabaseAndCreationFailed": "Няма база данни и създаването не бе успешно! Скриптът излиза!",
        "mismatchedLink": "Линкът не съответства, моля, опреснете страницата!",
        "startUpdatingConversationTree": "Започнете актуализация на дървото на разговори",
        "selectedItem": "Избрахте {item}",
        "successSavingChanges": "Промените са запазени успешно!",
        "codeCopiedToClipboard": "Кодът е копиран в клипборда!",
        "contentCopied": "Съдържанието е копирано!",
        "emptyCommentPrompt": "Коментарът е празен. Искате ли да зададете коментар като \\"празен\\"?",
        "confirmDeleteLinkData": "Линкът е проверен като {item}! Сигурни ли сте, че искате да изтриете информацията за този линк? Всички съдържания на разговори и коментари ще бъдат изтрити!",
        "confirmCurrentURL": "Линкът е проверен като {item}! Сигурни ли сте?",
        "commentSetToEmpty": "Коментарът е зададен като \\"празен\\"",
        "enterCommentFirst": "Моля, първо въведете коментар",
        "commentSaved": "Коментарът е запазен",
        "conversationDataDeleted": "Данните за разговора са изтрити успешно",
        "executeSearchWithQuery": "Изпълнение на търсене: {item}",
        "searchPlaceholder": "Въведете ключови думи за търсене...",
        "searchButton": "Търсене",
        "searchInContent": "В съдържанието на разговора",
        "searchInComments": "В коментарите",
        "searchInBoth": "В съдържание и коментари",
        "goToPrevious": "Към предишния",
        "goToNext": "Към следващия",
        "numberOfMatches": "{matches} съвпадения",
        "nodeDetails": "Подробности",
        "enterComment": "Моля, въведете коментар",
        "userCommentSave": "Запази",
        "userCommentCancel": "Отказ",
        "userCommentClear": "Изчисти",
        "openAdminPanel": "Админ панел",
        "allCategoriesFilter": "Всички категории",
        "conversationTitle": "Заглавие",
        "actionOptions": "Действия",
        "conversationCategory": "Категория",
        "conversationTags": "Етикети"
    },
    "cs": {
        "chatTreeRunning": "🥳🌳ChatTree🌳běží!🎉",
        "updateCurrentConversationTree": "🌈 Aktualizovat aktuální strom konverzace 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Upravit barvu pozadí a průhlednost 🎨",
        "toggleConversationTree": "🌳 Zobrazit/skrýt strom konverzace 🌳",
        "noDatabaseAndCreationFailed": "Není databáze a vytvoření se nezdařilo! Skript se ukončuje!",
        "mismatchedLink": "Odkaz nesouzní, obnovte stránku!",
        "startUpdatingConversationTree": "Začít aktualizovat strom konverzace",
        "selectedItem": "Vybrali jste {item}",
        "successSavingChanges": "Změny byly úspěšně uloženy!",
        "codeCopiedToClipboard": "Kód byl zkopírován do schránky!",
        "contentCopied": "Obsah byl zkopírován!",
        "emptyCommentPrompt": "Komentář je prázdný. Chcete nastavit komentář na \\"prázdný\\"?",
        "confirmDeleteLinkData": "Odkaz byl ověřen jako {item}! Opravdu chcete smazat informace tohoto odkazu? Veškerý obsah konverzace a komentáře budou smazány!",
        "confirmCurrentURL": "Odkaz byl ověřen jako {item}! Potvrzujete?",
        "commentSetToEmpty": "Komentář byl nastaven na \\"prázdný\\"",
        "enterCommentFirst": "Nejprve zadejte komentář",
        "commentSaved": "Komentář byl uložen",
        "conversationDataDeleted": "Data konverzace byla úspěšně smazána",
        "executeSearchWithQuery": "Spustit vyhledávání: {item}",
        "searchPlaceholder": "Zadejte klíčová slova pro vyhledávání...",
        "searchButton": "Hledat",
        "searchInContent": "Ve obsahu konverzace",
        "searchInComments": "V komentářích",
        "searchInBoth": "V obsahu i komentářích",
        "goToPrevious": "Jít na předchozí",
        "goToNext": "Jít na další",
        "numberOfMatches": "{matches} shod",
        "nodeDetails": "Detaily",
        "enterComment": "Zadejte komentář",
        "userCommentSave": "Uložit",
        "userCommentCancel": "Zrušit",
        "userCommentClear": "Vymazat",
        "openAdminPanel": "Administrační panel",
        "allCategoriesFilter": "Všechny kategorie",
        "conversationTitle": "Název",
        "actionOptions": "Akce",
        "conversationCategory": "Kategorie",
        "conversationTags": "Tagy"
    },
    "da": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 kører!🎉",
        "updateCurrentConversationTree": "🌈 Opdater nuværende samtaletræ 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Juster baggrundsfarve og gennemsigtighed 🎨",
        "toggleConversationTree": "🌳 Vis/skjul samtaletræ 🌳",
        "noDatabaseAndCreationFailed": "Ingen database, og oprettelsen mislykkedes! Scriptet afslutter!",
        "mismatchedLink": "Link matcher ikke, opdater siden!",
        "startUpdatingConversationTree": "Begynder at opdatere samtaletræ",
        "selectedItem": "Du valgte {item}",
        "successSavingChanges": "Ændringer gemt succesfuldt!",
        "codeCopiedToClipboard": "Kode kopieret til udklipsholder!",
        "contentCopied": "Indhold kopieret!",
        "emptyCommentPrompt": "Kommentaren er tom. Vil du sætte kommentaren til \\"tom\\"?",
        "confirmDeleteLinkData": "Linket er {item}! Er du sikker på, at du vil slette disse linkdata? Alle samtaler og kommentarer vil blive slettet!",
        "confirmCurrentURL": "Linket er {item}! Er du sikker?",
        "commentSetToEmpty": "Kommentar sat til \\"tom\\"",
        "enterCommentFirst": "Indtast kommentar først",
        "commentSaved": "Kommentar gemt",
        "conversationDataDeleted": "Samtaledata slettet succesfuldt",
        "executeSearchWithQuery": "Udfør søgning: {item}",
        "searchPlaceholder": "Indtast søgeord...",
        "searchButton": "Søg",
        "searchInContent": "I samtaleindholdet",
        "searchInComments": "I kommentarer",
        "searchInBoth": "Både i indhold og kommentarer",
        "goToPrevious": "Gå til den forrige",
        "goToNext": "Gå til den næste",
        "numberOfMatches": "{matches} match",
        "nodeDetails": "Detaljer",
        "enterComment": "Indtast kommentar",
        "userCommentSave": "Gem",
        "userCommentCancel": "Annuller",
        "userCommentClear": "Ryd",
        "openAdminPanel": "Admin panel",
        "allCategoriesFilter": "Alle kategorier",
        "conversationTitle": "Titel",
        "actionOptions": "Handlinger",
        "conversationCategory": "Kategori",
        "conversationTags": "Tags"
    },
    "de": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 läuft!🎉",
        "updateCurrentConversationTree": "🌈 Aktualisiere aktuellen Konversationsbaum 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Hintergrundfarbe und Transparenz anpassen 🎨",
        "toggleConversationTree": "🌳 Konversationsbaum anzeigen/verbergen 🌳",
        "noDatabaseAndCreationFailed": "Keine Datenbank und Erstellung fehlgeschlagen! Skript beendet!",
        "mismatchedLink": "Link stimmt nicht überein, bitte aktualisieren Sie die Seite!",
        "startUpdatingConversationTree": "Beginne mit der Aktualisierung des Konversationsbaums",
        "selectedItem": "Sie haben {item} ausgewählt",
        "successSavingChanges": "Änderungen erfolgreich gespeichert!",
        "codeCopiedToClipboard": "Code wurde in die Zwischenablage kopiert!",
        "contentCopied": "Inhalt kopiert!",
        "emptyCommentPrompt": "Der Kommentar ist leer. Möchten Sie den Kommentar auf \\"leer\\" setzen?",
        "confirmDeleteLinkData": "Der erkannte Link ist {item}! Möchten Sie wirklich die Informationen zu diesem Link löschen? Alle Gesprächsinhalte und Kommentare werden gelöscht!",
        "confirmCurrentURL": "Der erkannte Link ist {item}! Sicher?",
        "commentSetToEmpty": "Kommentar auf \\"leer\\" gesetzt",
        "enterCommentFirst": "Bitte geben Sie zuerst einen Kommentar ein",
        "commentSaved": "Kommentar gespeichert",
        "conversationDataDeleted": "Konversationsdaten erfolgreich gelöscht",
        "executeSearchWithQuery": "Suche ausführen: {item}",
        "searchPlaceholder": "Suchbegriffe eingeben...",
        "searchButton": "Suchen",
        "searchInContent": "Im Gesprächsinhalt",
        "searchInComments": "In den Kommentaren",
        "searchInBoth": "In Inhalt und Kommentaren",
        "goToPrevious": "Zum vorherigen gehen",
        "goToNext": "Zum nächsten gehen",
        "numberOfMatches": "{matches} Übereinstimmungen",
        "nodeDetails": "Details",
        "enterComment": "Bitte Kommentar eingeben",
        "userCommentSave": "Speichern",
        "userCommentCancel": "Abbrechen",
        "userCommentClear": "Löschen",
        "openAdminPanel": "Admin-Panel",
        "allCategoriesFilter": "Alle Kategorien",
        "conversationTitle": "Titel",
        "actionOptions": "Optionen",
        "conversationCategory": "Kategorie",
        "conversationTags": "Tags"
    },
    "el": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 Είναι σε λειτουργία!🎉",
        "updateCurrentConversationTree": "🌈 Ενημέρωση του τρέχοντος δέντρου συνομιλίας 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Ρύθμιση του χρώματος φόντου και της διαφάνειας 🎨",
        "toggleConversationTree": "🌳 Εμφάνιση/Απόκρυψη δέντρου συνομιλίας 🌳",
        "noDatabaseAndCreationFailed": "Δεν υπάρχει βάση δεδομένων και η δημιουργία απέτυχε! Το σενάριο τερματίζεται!",
        "mismatchedLink": "Ο σύνδεσμος δεν ταιριάζει, παρακαλώ ανανεώστε τη σελίδα!",
        "startUpdatingConversationTree": "Ξεκινώντας την ενημέρωση του δέντρου συνομιλίας",
        "selectedItem": "Επιλέξατε {item}",
        "successSavingChanges": "Οι αλλαγές αποθηκεύτηκαν επιτυχώς!",
        "codeCopiedToClipboard": "Ο κώδικας αντιγράφηκε στο πρόχειρο!",
        "contentCopied": "Το περιεχόμενο αντιγράφηκε!",
        "emptyCommentPrompt": "Το σχόλιο είναι κενό. Θέλετε να ορίσετε το σχόλιο ως \\"κενό\\";",
        "confirmDeleteLinkData": "Ο σύνδεσμος που εντοπίστηκε είναι {item}! Είστε σίγουροι ότι θέλετε να διαγράψετε αυτά τα δεδομένα συνδέσμου; Όλο το περιεχόμενο της συζήτησης και τα σχόλια θα διαγραφούν!",
        "confirmCurrentURL": "Ο σύνδεσμος που εντοπίστηκε είναι {item}! Είστε σίγουροι;",
        "commentSetToEmpty": "Το σχόλιο ορίστηκε ως \\"κενό\\"",
        "enterCommentFirst": "Παρακαλώ εισάγετε πρώτα ένα σχόλιο",
        "commentSaved": "Το σχόλιο αποθηκεύτηκε",
        "conversationDataDeleted": "Τα δεδομένα συνομιλίας διαγράφηκαν επιτυχώς",
        "executeSearchWithQuery": "Εκτέλεση αναζήτησης: {item}",
        "searchPlaceholder": "Εισάγετε λέξεις κλειδιά αναζήτησης...",
        "searchButton": "Αναζήτηση",
        "searchInContent": "Στο περιεχόμενο της συνομιλίας",
        "searchInComments": "Στα σχόλια",
        "searchInBoth": "Τόσο στο περιεχόμενο όσο και στα σχόλια",
        "goToPrevious": "Πήγαινε στο προηγούμενο",
        "goToNext": "Πήγαινε στο επόμενο",
        "numberOfMatches": "{matches} αντιστοιχίες",
        "nodeDetails": "Λεπτομέρειες",
        "enterComment": "Πληκτρολογήστε σχόλιο",
        "userCommentSave": "Αποθήκευση",
        "userCommentCancel": "Ακύρωση",
        "userCommentClear": "Καθαρισμός",
        "openAdminPanel": "Πίνακας Διαχείρισης",
        "allCategoriesFilter": "Όλες οι Κατηγορίες",
        "conversationTitle": "Τίτλος",
        "actionOptions": "Ενέργειες",
        "conversationCategory": "Κατηγορία",
        "conversationTags": "Ετικέτες"
    },
    "en": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 is Running!🎉",
        "updateCurrentConversationTree": "🌈 Update Current Conversation Tree 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Adjust Background Color and Opacity 🎨",
        "toggleConversationTree": "🌳 Show/Hide Conversation Tree 🌳",
        "noDatabaseAndCreationFailed": "No database, and creation failed! The script is terminating.",
        "mismatchedLink": "Link does not match, please refresh the page!",
        "startUpdatingConversationTree": "Starting to update the conversation tree",
        "selectedItem": "You selected {item}",
        "successSavingChanges": "Changes Saved Successfully!",
        "codeCopiedToClipboard": "Code Copied to Clipboard!",
        "contentCopied": "Content Copied!",
        "emptyCommentPrompt": "The comment is empty. Do you want to set the comment to \\"empty\\"?",
        "confirmDeleteLinkData": "Detected link is {item}! Are you sure you want to delete this link's information? All conversation content and comments will be deleted!",
        "confirmCurrentURL": "Detected link is {item}! Are you sure?",
        "commentSetToEmpty": "Comment set to \\"empty\\"",
        "enterCommentFirst": "Please enter a comment first",
        "commentSaved": "Comment Saved",
        "conversationDataDeleted": "Conversation Data Successfully Deleted",
        "executeSearchWithQuery": "Executing Search: {item}",
        "searchPlaceholder": "Enter search keywords...",
        "searchButton": "Search",
        "searchInContent": "Conversation Content",
        "searchInComments": "User Comments",
        "searchInBoth": "Content & Comments",
        "goToPrevious": "Go to Previous",
        "goToNext": "Go to Next",
        "numberOfMatches": "{matches} matches found",
        "nodeDetails": "Details",
        "enterComment": "Please Enter Comment",
        "userCommentSave": "Save",
        "userCommentCancel": "Cancel",
        "userCommentClear": "Clear",
        "openAdminPanel": "Admin Panel",
        "allCategoriesFilter": "All Categories",
        "conversationTitle": "Title",
        "actionOptions": "Actions",
        "conversationCategory": "Category",
        "conversationTags": "Tags"
    },
    "eo": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 funkcias!🎉",
        "updateCurrentConversationTree": "🌈 Ĝisdatigi la aktivan konversacian arbon 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Agordi fondon koloron kaj opakecon 🎨",
        "toggleConversationTree": "🌳 Montri/Kaŝi konversacian arbon 🌳",
        "noDatabaseAndCreationFailed": "Neniu datumbazo, kaj ne sukcesis krei! Skripto eliras!",
        "mismatchedLink": "Ligilo ne kongruas, bonvolu refreŝigi la paĝon!",
        "startUpdatingConversationTree": "Komenci ĝisdatigi la konversacian arbon",
        "selectedItem": "Vi selektis {item}",
        "successSavingChanges": "Ŝanĝoj sukcese konservitaj!",
        "codeCopiedToClipboard": "Kodo estis kopii al tondejo!",
        "contentCopied": "Enhavo estis kopii!",
        "emptyCommentPrompt": "Komento estas malplena. Ĉu vi volas agordi komenton al \\"malplena\\"?",
        "confirmDeleteLinkData": "Detektis ligilon {item}! Ĉu vi certas, ke vi volas forigi ĉi tiun ligilon informon? Ĉiuj konversaciaj enhavoj kaj komentoj estos forigitaj!",
        "confirmCurrentURL": "Detektis ligilon {item}! Ĉu vi certas?",
        "commentSetToEmpty": "Komento estis agordita al \\"malplena\\"",
        "enterCommentFirst": "Bonvolu unue enigi komenton",
        "commentSaved": "Komento konservita",
        "conversationDataDeleted": "Konversaciaj datenoj sukcese forigitaj",
        "executeSearchWithQuery": "Efektivi serĉon: {item}",
        "searchPlaceholder": "Enmetu serĉajn ŝlosilvortojn...",
        "searchButton": "Serĉi",
        "searchInContent": "Konversacia enhavo",
        "searchInComments": "Komentoj de uzantoj",
        "searchInBoth": "Enhavo & Komentoj",
        "goToPrevious": "Iru al antaŭa",
        "goToNext": "Iru al sekvanta",
        "numberOfMatches": "{matches} kongruajn trovis",
        "nodeDetails": "Detaloj",
        "enterComment": "Bonvolu Enmeti Komenton",
        "userCommentSave": "Konservi",
        "userCommentCancel": "Nuligi",
        "userCommentClear": "Forigi",
        "openAdminPanel": "Malfermi Adminan Panelon",
        "allCategoriesFilter": "Ĉiuj Kategorioj",
        "conversationTitle": "Titolo",
        "actionOptions": "Agadoj",
        "conversationCategory": "Kategorio",
        "conversationTags": "Etikedoj"
    },
    "es": {
        "chatTreeRunning": "🥳🌳¡ChatTree🌳 en funcionamiento!🎉",
        "updateCurrentConversationTree": "🌈 Actualizar el árbol de conversación actual 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Ajustar color de fondo y opacidad 🎨",
        "toggleConversationTree": "🌳 Mostrar/Ocultar árbol de conversación 🌳",
        "noDatabaseAndCreationFailed": "¡No hay base de datos y no se pudo crear! ¡Saliendo del script!",
        "mismatchedLink": "¡El enlace no coincide, por favor actualiza la página!",
        "startUpdatingConversationTree": "Comenzar a actualizar el árbol de conversación",
        "selectedItem": "Seleccionaste {item}",
        "successSavingChanges": "¡Cambios guardados con éxito!",
        "codeCopiedToClipboard": "¡Código copiado al portapapeles!",
        "contentCopied": "¡Contenido copiado!",
        "emptyCommentPrompt": "El comentario está vacío. ¿Quieres establecer el comentario como \\"vacío\\"?",
        "confirmDeleteLinkData": "¡Se detectó el enlace {item}! ¿Estás seguro de que quieres eliminar la información de este enlace? ¡Toda la conversación y los comentarios se eliminarán!",
        "confirmCurrentURL": "¡Se detectó el enlace {item}! ¿Estás seguro?",
        "commentSetToEmpty": "Comentario establecido como \\"vacío\\"",
        "enterCommentFirst": "Por favor, ingresa un comentario primero",
        "commentSaved": "Comentario guardado",
        "conversationDataDeleted": "Datos de la conversación eliminados con éxito",
        "executeSearchWithQuery": "Ejecutar búsqueda: {item}",
        "searchPlaceholder": "Ingrese palabras clave para buscar...",
        "searchButton": "Buscar",
        "searchInContent": "Contenido de la conversación",
        "searchInComments": "Comentarios de usuarios",
        "searchInBoth": "Contenido y comentarios",
        "goToPrevious": "Ir al anterior",
        "goToNext": "Ir al siguiente",
        "numberOfMatches": "{matches} coincidencias encontradas",
        "nodeDetails": "Detalles",
        "enterComment": "Por Favor Introduce un Comentario",
        "userCommentSave": "Guardar",
        "userCommentCancel": "Cancelar",
        "userCommentClear": "Borrar",
        "openAdminPanel": "Abrir Panel de Administración",
        "allCategoriesFilter": "Todas las Categorías",
        "conversationTitle": "Título",
        "actionOptions": "Opciones",
        "conversationCategory": "Categoría",
        "conversationTags": "Etiquetas"
    },
    "fi": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 on käynnissä!🎉",
        "updateCurrentConversationTree": "🌈 Päivitä nykyinen keskustelupuu 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Säädä taustavärin ja läpinäkyvyyden 🎨",
        "toggleConversationTree": "🌳 Näytä/Piilota keskustelupuu 🌳",
        "noDatabaseAndCreationFailed": "Tietokantaa ei ole, eikä luonti onnistunut! Skripti päättyy!",
        "mismatchedLink": "Linkki ei täsmää, päivitä sivu!",
        "startUpdatingConversationTree": "Aloita keskustelupuun päivittäminen",
        "selectedItem": "Valitsit kohteen {item}",
        "successSavingChanges": "Muutosten tallennus onnistui!",
        "codeCopiedToClipboard": "Koodi kopioitu leikepöydälle!",
        "contentCopied": "Sisältö kopioitu!",
        "emptyCommentPrompt": "Kommentti on tyhjä. Haluatko asettaa kommentin \\"tyhjäksi\\"?",
        "confirmDeleteLinkData": "Linkki {item} havaittu! Haluatko varmasti poistaa tämän linkin tiedot? Kaikki keskustelut ja kommentit poistetaan!",
        "confirmCurrentURL": "Linkki {item} havaittu! Oletko varma?",
        "commentSetToEmpty": "Kommentti asetettu \\"tyhjäksi\\"",
        "enterCommentFirst": "Syötä kommentti ensin",
        "commentSaved": "Kommentti tallennettu",
        "conversationDataDeleted": "Keskustelutiedot poistettu onnistuneesti",
        "executeSearchWithQuery": "Suorita haku: {item}",
        "searchPlaceholder": "Anna hakusanat...",
        "searchButton": "Hae",
        "searchInContent": "Keskustelun sisältö",
        "searchInComments": "Käyttäjien kommentit",
        "searchInBoth": "Sisältö ja kommentit",
        "goToPrevious": "Mene edelliseen",
        "goToNext": "Mene seuraavaan",
        "numberOfMatches": "{matches} osumaa löydetty",
        "nodeDetails": "Tiedot",
        "enterComment": "Ole Hyvä ja Kirjoita Kommentti",
        "userCommentSave": "Tallenna",
        "userCommentCancel": "Peruuta",
        "userCommentClear": "Tyhjennä",
        "openAdminPanel": "Avaa Hallintapaneeli",
        "allCategoriesFilter": "Kaikki Kategoriat",
        "conversationTitle": "Otsikko",
        "actionOptions": "Toiminnot",
        "conversationCategory": "Kategoria",
        "conversationTags": "Tagit"
    },
    "fr": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 est en cours d'exécution !🎉",
        "updateCurrentConversationTree": "🌈 Mettre à jour l'arbre de conversation actuel 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Ajuster la couleur de fond et l'opacité 🎨",
        "toggleConversationTree": "🌳 Afficher/Masquer l'arbre de conversation 🌳",
        "noDatabaseAndCreationFailed": "Pas de base de données et la création a échoué ! Sortie du script !",
        "mismatchedLink": "Le lien ne correspond pas, veuillez rafraîchir la page !",
        "startUpdatingConversationTree": "Commencer à mettre à jour l'arbre de conversation",
        "selectedItem": "Vous avez sélectionné {item}",
        "successSavingChanges": "Modifications enregistrées avec succès !",
        "codeCopiedToClipboard": "Le code a été copié dans le presse-papiers !",
        "contentCopied": "Contenu copié !",
        "emptyCommentPrompt": "Le commentaire est vide. Voulez-vous définir le commentaire comme \\"vide\\" ?",
        "confirmDeleteLinkData": "Le lien {item} a été détecté ! Êtes-vous sûr de vouloir supprimer les informations de ce lien ? Toutes les conversations et les commentaires seront supprimés !",
        "confirmCurrentURL": "Le lien {item} a été détecté ! Êtes-vous sûr ?",
        "commentSetToEmpty": "Commentaire défini comme \\"vide\\"",
        "enterCommentFirst": "Veuillez d'abord entrer un commentaire",
        "commentSaved": "Commentaire enregistré",
        "conversationDataDeleted": "Données de conversation supprimées avec succès",
        "executeSearchWithQuery": "Effectuer une recherche : {item}",
        "searchPlaceholder": "Entrez des mots-clés pour rechercher...",
        "searchButton": "Rechercher",
        "searchInContent": "Contenu de la conversation",
        "searchInComments": "Commentaires des utilisateurs",
        "searchInBoth": "Contenu et commentaires",
        "goToPrevious": "Aller au précédent",
        "goToNext": "Aller au suivant",
        "numberOfMatches": "{matches} correspondances trouvées",
        "nodeDetails": "Détails",
        "enterComment": "Veuillez Entrer un Commentaire",
        "userCommentSave": "Enregistrer",
        "userCommentCancel": "Annuler",
        "userCommentClear": "Effacer",
        "openAdminPanel": "Ouvrir le Panneau d'Administration",
        "allCategoriesFilter": "Toutes les Catégories",
        "conversationTitle": "Titre",
        "actionOptions": "Options",
        "conversationCategory": "Catégorie",
        "conversationTags": "Étiquettes"
    },
    "fr-CA": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 est en marche !🎉",
        "updateCurrentConversationTree": "🌈 Mettre à jour l'arbre de conversation actuel 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Ajustez la couleur de fond et l'opacité 🎨",
        "toggleConversationTree": "🌳 Afficher/Masquer l'arbre de conversation 🌳",
        "noDatabaseAndCreationFailed": "Pas de base de données et la création a échoué ! Le script se termine !",
        "mismatchedLink": "Le lien ne correspond pas, veuillez rafraîchir la page !",
        "startUpdatingConversationTree": "Commencer la mise à jour de l'arbre de conversation",
        "selectedItem": "Vous avez sélectionné {item}",
        "successSavingChanges": "Modifications enregistrées avec succès !",
        "codeCopiedToClipboard": "Le code a été copié dans le presse-papiers !",
        "contentCopied": "Contenu copié !",
        "emptyCommentPrompt": "Le commentaire est vide. Souhaitez-vous définir le commentaire comme \\"vide\\" ?",
        "confirmDeleteLinkData": "Le lien {item} a été détecté ! Êtes-vous sûr de vouloir supprimer les données de ce lien ? Toutes les conversations et les commentaires seront supprimés !",
        "confirmCurrentURL": "Le lien {item} a été détecté ! Êtes-vous sûr ?",
        "commentSetToEmpty": "Commentaire défini comme \\"vide\\"",
        "enterCommentFirst": "Veuillez d'abord entrer un commentaire",
        "commentSaved": "Commentaire sauvegardé",
        "conversationDataDeleted": "Données de conversation supprimées avec succès",
        "executeSearchWithQuery": "Exécuter la recherche : {item}",
        "searchPlaceholder": "Entrez des mots-clés pour chercher...",
        "searchButton": "Rechercher",
        "searchInContent": "Contenu de la conversation",
        "searchInComments": "Commentaires des utilisateurs",
        "searchInBoth": "Contenu et commentaires",
        "goToPrevious": "Aller au précédent",
        "goToNext": "Aller au suivant",
        "numberOfMatches": "{matches} correspondances trouvées",
        "nodeDetails": "Détails",
        "enterComment": "Veuillez Entrer un Commentaire",
        "userCommentSave": "Enregistrer",
        "userCommentCancel": "Annuler",
        "userCommentClear": "Effacer",
        "openAdminPanel": "Ouvrir le Panneau Administratif",
        "allCategoriesFilter": "Toutes les Catégories",
        "conversationTitle": "Titre",
        "actionOptions": "Options",
        "conversationCategory": "Catégorie",
        "conversationTags": "Balises"
    },
    "he": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 פועל!🎉",
        "updateCurrentConversationTree": "🌈 עדכן את עץ השיחה הנוכחי 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 כוונן את צבע הרקע והשקיפות 🎨",
        "toggleConversationTree": "🌳 הצג/הסתר עץ שיחה 🌳",
        "noDatabaseAndCreationFailed": "אין מסד נתונים ויצירתו נכשלה! סקריפט יוצא!",
        "mismatchedLink": "הקישור אינו תואם, אנא רענן את הדף!",
        "startUpdatingConversationTree": "התחל לעדכן את עץ השיחה",
        "selectedItem": "בחרת {item}",
        "successSavingChanges": "השינויים נשמרו בהצלחה!",
        "codeCopiedToClipboard": "הקוד הועתק ללוח!",
        "contentCopied": "התוכן הועתק!",
        "emptyCommentPrompt": "ההערה ריקה. האם תרצה להגדיר את ההערה כ\\"ריקה\\"?",
        "confirmDeleteLinkData": "הקישור {item} זוהה! האם אתה בטוח שאתה רוצה למחוק את נתוני הקישור הזה? כל השיחות וההערות יימחקו!",
        "confirmCurrentURL": "הקישור {item} זוהה! האם אתה בטוח?",
        "commentSetToEmpty": "ההערה הוגדרה כ\\"ריקה\\"",
        "enterCommentFirst": "אנא הזן הערה קודם",
        "commentSaved": "ההערה נשמרה",
        "conversationDataDeleted": "נתוני השיחה נמחקו בהצלחה",
        "executeSearchWithQuery": "בצע חיפוש: {item}",
        "searchPlaceholder": "הזן מילות חיפוש...",
        "searchButton": "חפש",
        "searchInContent": "תוכן השיחה",
        "searchInComments": "הערות משתמש",
        "searchInBoth": "תוכן והערות",
        "goToPrevious": "עבור לקודם",
        "goToNext": "עבור להבא",
        "numberOfMatches": "{matches} תוצאות",
        "nodeDetails": "פרטים",
        "enterComment": "אנא הקלד תגובה",
        "userCommentSave": "שמור",
        "userCommentCancel": "בטל",
        "userCommentClear": "נקה",
        "openAdminPanel": "פתח לוח בקרה",
        "allCategoriesFilter": "כל הקטגוריות",
        "conversationTitle": "כותרת",
        "actionOptions": "אפשרויות",
        "conversationCategory": "קטגוריה",
        "conversationTags": "תגיות"
    },
    "hu": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 fut!🎉",
        "updateCurrentConversationTree": "🌈 Frissítsd az aktuális beszélgetésfát 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Állítsd be a háttérszín és az átlátszóság értékeit 🎨",
        "toggleConversationTree": "🌳 Beszélgetésfa megjelenítése/elrejtése 🌳",
        "noDatabaseAndCreationFailed": "Nincs adatbázis és a létrehozás sikertelen! A szkript kilép!",
        "mismatchedLink": "A link nem egyezik, kérjük, frissítse az oldalt!",
        "startUpdatingConversationTree": "Beszélgetésfa frissítésének kezdése",
        "selectedItem": "Kiválasztott elem: {item}",
        "successSavingChanges": "A változások mentése sikerült!",
        "codeCopiedToClipboard": "A kód vágólapra másolva!",
        "contentCopied": "A tartalom másolva!",
        "emptyCommentPrompt": "A megjegyzés üres. Beállítja \\"üres\\"-ként?",
        "confirmDeleteLinkData": "A {item} link észlelve! Biztosan törli e link adatát? Minden beszélgetés és megjegyzés törlődik!",
        "confirmCurrentURL": "A {item} link észlelve! Biztos benne?",
        "commentSetToEmpty": "A megjegyzés \\"üres\\"-re van állítva",
        "enterCommentFirst": "Kérjük, először írja be a megjegyzést",
        "commentSaved": "Megjegyzés mentve",
        "conversationDataDeleted": "A beszélgetés adatai sikeresen törölve",
        "executeSearchWithQuery": "Keresés indítása: {item}",
        "searchPlaceholder": "Írja be a keresendő kifejezéseket...",
        "searchButton": "Keresés",
        "searchInContent": "Beszélgetés tartalma",
        "searchInComments": "Felhasználói megjegyzések",
        "searchInBoth": "Tartalom és megjegyzések",
        "goToPrevious": "Ugrás az előzőre",
        "goToNext": "Ugrás a következőre",
        "numberOfMatches": "{matches} találat",
        "nodeDetails": "Részletek",
        "enterComment": "Kérjük, írja be a megjegyzést",
        "userCommentSave": "Mentés",
        "userCommentCancel": "Mégse",
        "userCommentClear": "Törlés",
        "openAdminPanel": "Admin Panel Megnyitása",
        "allCategoriesFilter": "Minden Kategória",
        "conversationTitle": "Cím",
        "actionOptions": "Műveletek",
        "conversationCategory": "Kategória",
        "conversationTags": "Címkék"
    },
    "id": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 Sedang Berjalan!🎉",
        "updateCurrentConversationTree": "🌈 Perbarui Pohon Percakapan Saat Ini 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Sesuaikan Warna Latar Belakang dan Kecerahan 🎨",
        "toggleConversationTree": "🌳 Tampilkan/Sembunyikan Pohon Percakapan 🌳",
        "noDatabaseAndCreationFailed": "Tidak ada database, dan pembuatan gagal! Script keluar!",
        "mismatchedLink": "Tautan tidak cocok, silakan segarkan halaman!",
        "startUpdatingConversationTree": "Mulai memperbarui Pohon Percakapan",
        "selectedItem": "Anda memilih {item}",
        "successSavingChanges": "Berhasil Menyimpan Perubahan!",
        "codeCopiedToClipboard": "Kode disalin ke papan klip!",
        "contentCopied": "Konten disalin!",
        "emptyCommentPrompt": "Komentar kosong. Apakah Anda ingin mengatur komentar ke \\"kosong\\"?",
        "confirmDeleteLinkData": "Tautan {item} terdeteksi! Apakah Anda yakin ingin menghapus data tautan ini? Semua konten percakapan dan komentar akan dihapus!",
        "confirmCurrentURL": "Tautan {item} terdeteksi! Apakah Anda yakin?",
        "commentSetToEmpty": "Komentar diatur ke \\"kosong\\"",
        "enterCommentFirst": "Silakan masukkan komentar terlebih dahulu",
        "commentSaved": "Komentar disimpan",
        "conversationDataDeleted": "Data Percakapan Berhasil Dihapus",
        "executeSearchWithQuery": "Melakukan pencarian: {item}",
        "searchPlaceholder": "Masukkan kata kunci pencarian...",
        "searchButton": "Cari",
        "searchInContent": "Konten Percakapan",
        "searchInComments": "Komentar Pengguna",
        "searchInBoth": "Konten dan Komentar",
        "goToPrevious": "Pergi ke Sebelumnya",
        "goToNext": "Pergi ke Berikutnya",
        "numberOfMatches": "{matches} hasil pencarian",
        "nodeDetails": "Detail",
        "enterComment": "Silakan masukkan komentar",
        "userCommentSave": "Simpan",
        "userCommentCancel": "Batal",
        "userCommentClear": "Hapus",
        "openAdminPanel": "Buka Panel Admin",
        "allCategoriesFilter": "Semua Kategori",
        "conversationTitle": "Judul",
        "actionOptions": "Opsi",
        "conversationCategory": "Kategori",
        "conversationTags": "Tag"
    },
    "it": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 in esecuzione!🎉",
        "updateCurrentConversationTree": "🌈 Aggiorna l'albero di conversazione corrente 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Ajusta il colore di sfondo e l'opacità 🎨",
        "toggleConversationTree": "🌳 Mostra/Nascondi l'albero di conversazione 🌳",
        "noDatabaseAndCreationFailed": "Nessun database e la creazione non è riuscita! Script interrotto!",
        "mismatchedLink": "Il link non corrisponde, si prega di aggiornare la pagina!",
        "startUpdatingConversationTree": "Inizia l'aggiornamento dell'albero di conversazione",
        "selectedItem": "Hai selezionato {item}",
        "successSavingChanges": "Modifiche salvate con successo!",
        "codeCopiedToClipboard": "Codice copiato negli appunti!",
        "contentCopied": "Contenuto copiato!",
        "emptyCommentPrompt": "Il commento è vuoto. Vuoi impostare il commento su 'vuoto'?",
        "confirmDeleteLinkData": "Link rilevato {item}! Sei sicuro di voler eliminare le informazioni di questo link? Tutti i contenuti della conversazione e i commenti verranno eliminati!",
        "confirmCurrentURL": "Link rilevato {item}! Confermi?",
        "commentSetToEmpty": "Il commento è stato impostato su 'vuoto'",
        "enterCommentFirst": "Per favore, inserisci prima un commento",
        "commentSaved": "Commento salvato",
        "conversationDataDeleted": "Dati della conversazione eliminati con successo",
        "executeSearchWithQuery": "Esegui ricerca: {item}",
        "searchPlaceholder": "Inserisci parole chiave per la ricerca...",
        "searchButton": "Cerca",
        "searchInContent": "Contenuto della conversazione",
        "searchInComments": "Commenti degli utenti",
        "searchInBoth": "Contenuti e commenti",
        "goToPrevious": "Vai al precedente",
        "goToNext": "Vai al successivo",
        "numberOfMatches": "{matches} corrispondenze trovate",
        "nodeDetails": "Dettagli",
        "enterComment": "Per favore, inserisci un commento",
        "userCommentSave": "Salva",
        "userCommentCancel": "Annulla",
        "userCommentClear": "Cancella",
        "openAdminPanel": "Apri Pannello di Amministrazione",
        "allCategoriesFilter": "Tutte le Categorie",
        "conversationTitle": "Titolo",
        "actionOptions": "Opzioni",
        "conversationCategory": "Categoria",
        "conversationTags": "Tag"
    },
    "ja": {
        "chatTreeRunning": "🥳🌳ChatTree🌳実行中!🎉",
        "updateCurrentConversationTree": "🌈 現在の会話ツリーを更新 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 背景色と透明度を調整 🎨",
        "toggleConversationTree": "🌳 会話ツリーを表示/非表示 🌳",
        "noDatabaseAndCreationFailed": "データベースがなく、作成に失敗しました!スクリプトを終了します!",
        "mismatchedLink": "リンクが一致しません、ページを更新してください!",
        "startUpdatingConversationTree": "会話ツリーの更新を開始",
        "selectedItem": "{item}を選択しました",
        "successSavingChanges": "変更の保存に成功しました!",
        "codeCopiedToClipboard": "コードがクリップボードにコピーされました!",
        "contentCopied": "内容がコピーされました!",
        "emptyCommentPrompt": "コメントが空です。コメントを'空'に設定しますか?",
        "confirmDeleteLinkData": "{item}のリンクが検出されました! このリンクの情報を削除してもよろしいですか? すべての会話内容とコメントが削除されます!",
        "confirmCurrentURL": "{item}のリンクが検出されました!よろしいですか?",
        "commentSetToEmpty": "コメントが'空'に設定されました",
        "enterCommentFirst": "先にコメントを入力してください",
        "commentSaved": "コメントが保存されました",
        "conversationDataDeleted": "会話データが正常に削除されました",
        "executeSearchWithQuery": "検索を実行:{item}",
        "searchPlaceholder": "検索キーワードを入力...",
        "searchButton": "検索",
        "searchInContent": "会話の内容",
        "searchInComments": "ユーザーコメント",
        "searchInBoth": "内容とコメント",
        "goToPrevious": "前へ",
        "goToNext": "次へ",
        "numberOfMatches": "一致する項目{matches}件",
        "nodeDetails": "詳細",
        "enterComment": "コメントを入力してください",
        "userCommentSave": "保存",
        "userCommentCancel": "キャンセル",
        "userCommentClear": "クリア",
        "openAdminPanel": "管理パネルを開く",
        "allCategoriesFilter": "すべてのカテゴリ",
        "conversationTitle": "タイトル",
        "actionOptions": "オプション",
        "conversationCategory": "カテゴリ",
        "conversationTags": "タグ"
    },
    "ka": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 გაშვებულია!🎉",
        "updateCurrentConversationTree": "🌈 განაახლეთ ამჟამინდელი სასაუბრო ხე 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 გაასწორეთ ფონის ფერი და გამჭვირვალობა 🎨",
        "toggleConversationTree": "🌳 ჩვენება/დამალვა სასაუბრო ხე 🌳",
        "noDatabaseAndCreationFailed": "არ არის მონაცემთა ბაზა და შექმნა ვერ ხერხდება! სკრიპტი გამოირიყება!",
        "mismatchedLink": "ბმული არ ემთხვევა, გთხოვთ, განაახლოთ გვერდი!",
        "startUpdatingConversationTree": "სასაუბრო ხის განახლების დაწყება",
        "selectedItem": "თქვენ აირჩიეთ {item}",
        "successSavingChanges": "ცვლილებები წარმატებით შენახულია!",
        "codeCopiedToClipboard": "კოდი კოპირებულია ბუფერში!",
        "contentCopied": "კონტენტი კოპირებულია!",
        "emptyCommentPrompt": "კომენტარი ცარიელია. გსურთ კომენტარი 'ცარიელი' განსაზღვროთ?",
        "confirmDeleteLinkData": "{item} ბმული აღმოჩენილია! დარწმუნებული ხართ, რომ გსურთ ამ ბმულის ინფორმაციის წაშლა? ყველა სასაუბრო კონტენტი და კომენტარი წაიშლება!",
        "confirmCurrentURL": "{item} ბმული აღმოჩენილია! დარწმუნებული ხართ?",
        "commentSetToEmpty": "კომენტარი 'ცარიელია' განსაზღვრულია",
        "enterCommentFirst": "გთხოვთ, ჯერ შეიყვანეთ კომენტარი",
        "commentSaved": "კომენტარი შენახულია",
        "conversationDataDeleted": "სასაუბრო მონაცემები წარმატებით წაიშალა",
        "executeSearchWithQuery": "ძიება შესრულება: {item}",
        "searchPlaceholder": "შეიყვანეთ ძიების სიტყვები...",
        "searchButton": "ძებნა",
        "searchInContent": "კონვერსიის კონტენტი",
        "searchInComments": "მომხმარებლის კომენტარები",
        "searchInBoth": "კონტენტი და კომენტარები",
        "goToPrevious": "წინაში გადასვლა",
        "goToNext": "შემდეგში გადასვლა",
        "numberOfMatches": "{matches} შესატყვისი",
        "nodeDetails": "დეტალები",
        "enterComment": "გთხოვთ შეიყვანოთ კომენტარი",
        "userCommentSave": "შენახვა",
        "userCommentCancel": "გაუქმება",
        "userCommentClear": "გასუფთავება",
        "openAdminPanel": "ადმინის პანელის გახსნა",
        "allCategoriesFilter": "ყველა კატეგორია",
        "conversationTitle": "სათაური",
        "actionOptions": "ოფციები",
        "conversationCategory": "კატეგორია",
        "conversationTags": "თეგები"
    },
    "ko": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 실행 중!🎉",
        "updateCurrentConversationTree": "🌈 현재 대화 트리 업데이트 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 배경색과 투명도 조정 🎨",
        "toggleConversationTree": "🌳 대화 트리 표시/숨기기 🌳",
        "noDatabaseAndCreationFailed": "데이터베이스가 없으며 생성에 실패했습니다! 스크립트 종료!",
        "mismatchedLink": "링크가 일치하지 않습니다. 페이지를 새로고침하세요!",
        "startUpdatingConversationTree": "대화 트리 업데이트 시작",
        "selectedItem": "{item}를 선택했습니다",
        "successSavingChanges": "변경 사항이 성공적으로 저장되었습니다!",
        "codeCopiedToClipboard": "코드가 클립보드에 복사되었습니다!",
        "contentCopied": "내용이 복사되었습니다!",
        "emptyCommentPrompt": "댓글이 비어 있습니다. 댓글을 '비움'으로 설정하시겠습니까?",
        "confirmDeleteLinkData": "{item} 링크를 감지했습니다! 이 링크의 정보를 삭제하시겠습니까? 모든 대화 내용과 댓글이 삭제됩니다!",
        "confirmCurrentURL": "{item} 링크를 감지했습니다! 확인하시겠습니까?",
        "commentSetToEmpty": "댓글이 '비움'으로 설정되었습니다",
        "enterCommentFirst": "먼저 댓글을 입력해주세요",
        "commentSaved": "댓글이 저장되었습니다",
        "conversationDataDeleted": "대화 데이터가 성공적으로 삭제되었습니다",
        "executeSearchWithQuery": "검색 실행: {item}",
        "searchPlaceholder": "검색어를 입력하세요...",
        "searchButton": "검색",
        "searchInContent": "대화 내용",
        "searchInComments": "사용자 코멘트",
        "searchInBoth": "내용과 코멘트",
        "goToPrevious": "이전으로",
        "goToNext": "다음으로",
        "numberOfMatches": "{matches}개의 일치 항목",
        "nodeDetails": "세부 정보",
        "enterComment": "댓글을 입력하세요",
        "userCommentSave": "저장",
        "userCommentCancel": "취소",
        "userCommentClear": "지우기",
        "openAdminPanel": "관리 패널",
        "allCategoriesFilter": "모든 카테고리",
        "conversationTitle": "제목",
        "actionOptions": "작업",
        "conversationCategory": "카테고리",
        "conversationTags": "태그"
    },
 "nb": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 er i gang! 🎉",
        "updateCurrentConversationTree": "🌈 Oppdater gjeldende samtale-tre 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Juster bakgrunnsfarge og opasitet 🎨",
        "toggleConversationTree": "🌳 Vis/Skjul samtale-tre 🌳",
        "noDatabaseAndCreationFailed": "Ingen database, og opprettelsen mislyktes! Skriptet avslutter!",
        "mismatchedLink": "Lenken stemmer ikke, vennligst oppfrisk siden!",
        "startUpdatingConversationTree": "Begynner å oppdatere samtale-treet",
        "selectedItem": "Du valgte {item}",
        "successSavingChanges": "Endringene ble lagret!",
        "codeCopiedToClipboard": "Koden er kopiert til utklippstavlen!",
        "contentCopied": "Innholdet er kopiert!",
        "emptyCommentPrompt": "Kommentaren er tom. Vil du sette kommentaren til \\"tom\\"?",
        "confirmDeleteLinkData": "Linken er {item}! Er du sikker på at du vil slette denne linkens informasjon? All samtale og kommentarinnhold vil bli slettet!",
        "confirmCurrentURL": "Linken er {item}! Er du sikker?",
        "commentSetToEmpty": "Kommentaren er satt til \\"tom\\"",
        "enterCommentFirst": "Vennligst skriv en kommentar først",
        "commentSaved": "Kommentaren er lagret",
        "conversationDataDeleted": "Samtaledata er slettet",
        "executeSearchWithQuery": "Utfør søk: {item}",
        "searchPlaceholder": "Skriv inn søkeord...",
        "searchButton": "Søk",
        "searchInContent": "Samtaleinnhold",
        "searchInComments": "Brukerkommentarer",
        "searchInBoth": "Innhold og kommentarer",
        "goToPrevious": "Gå til forrige",
        "goToNext": "Gå til neste",
        "numberOfMatches": "{matches} treff",
        "nodeDetails": "Detaljer",
        "enterComment": "Vennligst skriv en kommentar",
        "userCommentSave": "Lagre",
        "userCommentCancel": "Avbryt",
        "userCommentClear": "Tøm",
        "openAdminPanel": "Administrasjonspanel",
        "allCategoriesFilter": "Alle kategorier",
        "conversationTitle": "Tittel",
        "actionOptions": "Handlinger",
        "conversationCategory": "Kategori",
        "conversationTags": "Merkelapper"
    },
    "nl": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 is actief! 🎉",
        "updateCurrentConversationTree": "🌈 Huidige gespreksboom bijwerken 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Achtergrondkleur en opaciteit aanpassen 🎨",
        "toggleConversationTree": "🌳 Gespreksboom weergeven/verbergen 🌳",
        "noDatabaseAndCreationFailed": "Geen database en het aanmaken is mislukt! Script wordt afgesloten!",
        "mismatchedLink": "Link komt niet overeen, ververs de pagina!",
        "startUpdatingConversationTree": "Begint met het bijwerken van de gespreksboom",
        "selectedItem": "Je hebt {item} geselecteerd",
        "successSavingChanges": "Wijzigingen succesvol opgeslagen!",
        "codeCopiedToClipboard": "Code gekopieerd naar klembord!",
        "contentCopied": "Inhoud gekopieerd!",
        "emptyCommentPrompt": "De opmerking is leeg. Wil je de opmerking instellen op \\"leeg\\"?",
        "confirmDeleteLinkData": "Gedetecteerde link is {item}! Weet je zeker dat je de informatie van deze link wilt verwijderen? Alle gespreks- en opmerkingsinhoud zal worden verwijderd!",
        "confirmCurrentURL": "Gedetecteerde link is {item}! Zeker weten?",
        "commentSetToEmpty": "Opmerking is ingesteld op \\"leeg\\"",
        "enterCommentFirst": "Voer eerst een opmerking in",
        "commentSaved": "Opmerking opgeslagen",
        "conversationDataDeleted": "Gespreksgegevens succesvol verwijderd",
        "executeSearchWithQuery": "Zoekopdracht uitvoeren: {item}",
        "searchPlaceholder": "Voer zoekwoorden in...",
        "searchButton": "Zoeken",
        "searchInContent": "In gespreksinhoud",
        "searchInComments": "In gebruikersopmerkingen",
        "searchInBoth": "In inhoud en opmerkingen",
        "goToPrevious": "Ga naar vorige",
        "goToNext": "Ga naar volgende",
        "numberOfMatches": "{matches} overeenkomsten",
        "nodeDetails": "Details",
        "enterComment": "Voer alstublieft een opmerking in",
        "userCommentSave": "Opslaan",
        "userCommentCancel": "Annuleren",
        "userCommentClear": "Wissen",
        "openAdminPanel": "Beheerpaneel",
        "allCategoriesFilter": "Alle categorieën",
        "conversationTitle": "Titel",
        "actionOptions": "Acties",
        "conversationCategory": "Categorie",
        "conversationTags": "Labels"
    },
    "pl": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 jest uruchomiony! 🎉",
        "updateCurrentConversationTree": "🌈 Aktualizuj aktualne drzewo rozmów 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Dostosuj kolor tła i przezroczystość 🎨",
        "toggleConversationTree": "🌳 Pokaż/ukryj drzewo rozmów 🌳",
        "noDatabaseAndCreationFailed": "Brak bazy danych, a jej utworzenie nie powiodło się! Skrypt zakończy działanie!",
        "mismatchedLink": "Link nie pasuje, odśwież stronę!",
        "startUpdatingConversationTree": "Rozpoczyna aktualizację drzewa rozmów",
        "selectedItem": "Wybrałeś {item}",
        "successSavingChanges": "Zmiany zostały zapisane!",
        "codeCopiedToClipboard": "Kod skopiowany do schowka!",
        "contentCopied": "Treść skopiowana!",
        "emptyCommentPrompt": "Komentarz jest pusty. Czy chcesz ustawić komentarz na \\"pusty\\"?",
        "confirmDeleteLinkData": "Wykryto link {item}! Czy na pewno chcesz usunąć informacje z tego linku? Wszystkie treści rozmów i komentarze zostaną usunięte!",
        "confirmCurrentURL": "Wykryto link {item}! Czy jesteś pewien?",
        "commentSetToEmpty": "Komentarz został ustawiony na \\"pusty\\"",
        "enterCommentFirst": "Najpierw wprowadź komentarz",
        "commentSaved": "Komentarz został zapisany",
        "conversationDataDeleted": "Dane rozmowy zostały pomyślnie usunięte",
        "executeSearchWithQuery": "Wykonaj wyszukiwanie: {item}",
        "searchPlaceholder": "Wprowadź słowa kluczowe...",
        "searchButton": "Szukaj",
        "searchInContent": "W treści rozmowy",
        "searchInComments": "W komentarzach użytkowników",
        "searchInBoth": "W treści i komentarzach",
        "goToPrevious": "Przejdź do poprzedniego",
        "goToNext": "Przejdź do następnego",
        "numberOfMatches": "{matches} dopasowań",
        "nodeDetails": "Szczegóły",
        "enterComment": "Proszę wprowadzić komentarz",
        "userCommentSave": "Zapisz",
        "userCommentCancel": "Anuluj",
        "userCommentClear": "Wyczyść",
        "openAdminPanel": "Panel administracyjny",
        "allCategoriesFilter": "Wszystkie kategorie",
        "conversationTitle": "Tytuł",
        "actionOptions": "Operacje",
        "conversationCategory": "Kategoria",
        "conversationTags": "Tagi"
    },
 "pt-PT": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 em execução! 🎉",
        "updateCurrentConversationTree": "🌈 Atualizar a árvore de conversação atual 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Ajustar a cor de fundo e a opacidade 🎨",
        "toggleConversationTree": "🌳 Mostrar/ocultar árvore de conversação 🌳",
        "noDatabaseAndCreationFailed": "Sem base de dados e a criação falhou! Script a encerrar!",
        "mismatchedLink": "Link não corresponde, por favor atualize a página!",
        "startUpdatingConversationTree": "A iniciar a atualização da árvore de conversação",
        "selectedItem": "Selecionou {item}",
        "successSavingChanges": "Alterações guardadas com sucesso!",
        "codeCopiedToClipboard": "Código copiado para a área de transferência!",
        "contentCopied": "Conteúdo copiado!",
        "emptyCommentPrompt": "O comentário está vazio. Deseja definir o comentário como \\"vazio\\"?",
        "confirmDeleteLinkData": "O link detetado é {item}! Tem a certeza que quer eliminar as informações deste link? Todo o conteúdo da conversa e comentários serão eliminados!",
        "confirmCurrentURL": "O link detetado é {item}! Confirma?",
        "commentSetToEmpty": "O comentário foi definido como \\"vazio\\"",
        "enterCommentFirst": "Por favor, insira um comentário primeiro",
        "commentSaved": "Comentário guardado",
        "conversationDataDeleted": "Dados de conversação eliminados com sucesso",
        "executeSearchWithQuery": "Executar pesquisa: {item}",
        "searchPlaceholder": "Insira palavras-chave...",
        "searchButton": "Pesquisar",
        "searchInContent": "No conteúdo da conversa",
        "searchInComments": "Nos comentários do usuário",
        "searchInBoth": "Em conteúdo e comentários",
        "goToPrevious": "Ir para o anterior",
        "goToNext": "Ir para o próximo",
        "numberOfMatches": "{matches} correspondências",
        "nodeDetails": "Detalhes",
        "enterComment": "Insira um comentário",
        "userCommentSave": "Guardar",
        "userCommentCancel": "Cancelar",
        "userCommentClear": "Limpar",
        "openAdminPanel": "Painel de administração",
        "allCategoriesFilter": "Todas as categorias",
        "conversationTitle": "Título",
        "actionOptions": "Opções",
        "conversationCategory": "Categoria",
        "conversationTags": "Etiquetas"
    },
 "pt-BR": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 em execução! 🎉",
        "updateCurrentConversationTree": "🌈 Atualizar a árvore de conversa atual 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Ajustar a cor de fundo e a opacidade 🎨",
        "toggleConversationTree": "🌳 Mostrar/esconder árvore de conversa 🌳",
        "noDatabaseAndCreationFailed": "Sem banco de dados e a criação falhou! Script encerrando!",
        "mismatchedLink": "Link não corresponde, por favor, atualize a página!",
        "startUpdatingConversationTree": "Começando a atualizar a árvore de conversa",
        "selectedItem": "Você selecionou {item}",
        "successSavingChanges": "Alterações salvas com sucesso!",
        "codeCopiedToClipboard": "Código copiado para a área de transferência!",
        "contentCopied": "Conteúdo copiado!",
        "emptyCommentPrompt": "O comentário está vazio. Você quer definir o comentário como \\"vazio\\"?",
        "confirmDeleteLinkData": "O link detectado é {item}! Tem certeza de que deseja excluir as informações deste link? Todo o conteúdo da conversa e os comentários serão excluídos!",
        "confirmCurrentURL": "O link detectado é {item}! Confirma?",
        "commentSetToEmpty": "O comentário foi definido como \\"vazio\\"",
        "enterCommentFirst": "Por favor, insira um comentário primeiro",
        "commentSaved": "Comentário salvo",
        "conversationDataDeleted": "Dados da conversa excluídos com sucesso",
        "executeSearchWithQuery": "Executar pesquisa: {item}",
        "searchPlaceholder": "Digite palavras-chave...",
        "searchButton": "Pesquisar",
        "searchInContent": "No conteúdo da conversa",
        "searchInComments": "Nos comentários do usuário",
        "searchInBoth": "Em conteúdo e comentários",
        "goToPrevious": "Ir para o anterior",
        "goToNext": "Ir para o próximo",
        "numberOfMatches": "{matches} correspondências",
        "nodeDetails": "Detalhes",
        "enterComment": "Digite um comentário",
        "userCommentSave": "Salvar",
        "userCommentCancel": "Cancelar",
        "userCommentClear": "Limpar",
        "openAdminPanel": "Painel de administração",
        "allCategoriesFilter": "Todas as categorias",
        "conversationTitle": "Título",
        "actionOptions": "Opções",
        "conversationCategory": "Categoria",
        "conversationTags": "Tags"
    },
   "ro": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 este în funcțiune!🎉",
        "updateCurrentConversationTree": "🌈 Actualizați arborele conversației curente 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Ajustați culoarea de fundal și opacitatea 🎨",
        "toggleConversationTree": "🌳 Afișați/Ascundeți arborele conversației 🌳",
        "noDatabaseAndCreationFailed": "Nu există bază de date și crearea a eșuat! Scriptul se închide!",
        "mismatchedLink": "Link-ul nu se potrivește, vă rugăm să reîmprospătați pagina!",
        "startUpdatingConversationTree": "Începeți să actualizați arborele conversației",
        "selectedItem": "Ați selectat {item}",
        "successSavingChanges": "Modificările au fost salvate cu succes!",
        "codeCopiedToClipboard": "Codul a fost copiat în clipboard!",
        "contentCopied": "Conținutul a fost copiat!",
        "emptyCommentPrompt": "Comentariul este gol. Doriți să setați comentariul ca \\"gol\\"?",
        "confirmDeleteLinkData": "Link detectat ca fiind {item}! Confirmați ștergerea informațiilor acestui link? Tot conținutul conversației și comentariile vor fi șterse!",
        "confirmCurrentURL": "Link detectat ca fiind {item}! Confirmați?",
        "commentSetToEmpty": "Comentariu setat ca \\"gol\\"",
        "enterCommentFirst": "Vă rugăm să introduceți mai întâi un comentariu",
        "commentSaved": "Comentariu salvat",
        "conversationDataDeleted": "Datele conversației au fost șterse cu succes",
        "executeSearchWithQuery": "Executați căutarea: {item}",
        "searchPlaceholder": "Introduceți cuvinte cheie...",
        "searchButton": "Căutare",
        "searchInContent": "În conținutul conversației",
        "searchInComments": "În comentariile utilizatorului",
        "searchInBoth": "În conținut și comentarii",
        "goToPrevious": "Mergi la precedentul",
        "goToNext": "Mergi la următorul",
        "numberOfMatches": "{matches} potriviri",
        "nodeDetails": "Detalii",
        "enterComment": "Introduceți un comentariu",
        "userCommentSave": "Salvați",
        "userCommentCancel": "Anulați",
        "userCommentClear": "Șterge",
        "openAdminPanel": "Panou administrativ",
        "allCategoriesFilter": "Toate categoriile",
        "conversationTitle": "Titlu",
        "actionOptions": "Opțiuni",
        "conversationCategory": "Categorie",
        "conversationTags": "Etichete"
    },
    "ru": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 запущен!🎉",
        "updateCurrentConversationTree": "🌈 Обновить текущее дерево беседы 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Настроить цвет фона и прозрачность 🎨",
        "toggleConversationTree": "🌳 Показать/Скрыть дерево беседы 🌳",
        "noDatabaseAndCreationFailed": "База данных отсутствует, и создание не удалось! Скрипт выходит!",
        "mismatchedLink": "Ссылка не совпадает, пожалуйста, обновите страницу!",
        "startUpdatingConversationTree": "Начать обновление дерева беседы",
        "selectedItem": "Вы выбрали {item}",
        "successSavingChanges": "Изменения успешно сохранены!",
        "codeCopiedToClipboard": "Код скопирован в буфер обмена!",
        "contentCopied": "Содержимое скопировано!",
        "emptyCommentPrompt": "Комментарий пуст. Вы хотите установить комментарий как \\"пустой\\"?",
        "confirmDeleteLinkData": "Обнаружена ссылка {item}! Подтвердить удаление информации этой ссылки? Весь контент беседы и комментарии будут удалены!",
        "confirmCurrentURL": "Обнаружена ссылка {item}! Подтвердить?",
        "commentSetToEmpty": "Комментарий установлен как \\"пустой\\"",
        "enterCommentFirst": "Пожалуйста, сначала введите комментарий",
        "commentSaved": "Комментарий сохранен",
        "conversationDataDeleted": "Данные беседы успешно удалены",
        "executeSearchWithQuery": "Выполнить поиск: {item}",
        "searchPlaceholder": "Введите ключевые слова...",
        "searchButton": "Поиск",
        "searchInContent": "В контенте диалога",
        "searchInComments": "В комментариях пользователя",
        "searchInBoth": "В контенте и комментариях",
        "goToPrevious": "Перейти к предыдущему",
        "goToNext": "Перейти к следующему",
        "numberOfMatches": "{matches} совпадений",
        "nodeDetails": "Подробности",
        "enterComment": "Введите комментарий",
        "userCommentSave": "Сохранить",
        "userCommentCancel": "Отмена",
        "userCommentClear": "Очистить",
        "openAdminPanel": "Панель управления",
        "allCategoriesFilter": "Все категории",
        "conversationTitle": "Заголовок",
        "actionOptions": "Опции",
        "conversationCategory": "Категория",
        "conversationTags": "Теги"
    },
    "sk": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 beží!🎉",
        "updateCurrentConversationTree": "🌈 Aktualizovať aktuálny konverzačný strom 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Upraviť farbu pozadia a priehľadnosť 🎨",
        "toggleConversationTree": "🌳 Zobraziť/Skryť konverzačný strom 🌳",
        "noDatabaseAndCreationFailed": "Nie je databáza a vytvorenie zlyhalo! Skript končí!",
        "mismatchedLink": "Odkaz sa nezhoduje, prosím, obnovte stránku!",
        "startUpdatingConversationTree": "Začať aktualizovať konverzačný strom",
        "selectedItem": "Vybrali ste {item}",
        "successSavingChanges": "Zmeny boli úspešne uložené!",
        "codeCopiedToClipboard": "Kód bol skopírovaný do schránky!",
        "contentCopied": "Obsah bol skopírovaný!",
        "emptyCommentPrompt": "Komentár je prázdny. Chcete nastaviť komentár na \\"prázdny\\"?",
        "confirmDeleteLinkData": "Odkaz detekovaný ako {item}! Potvrdiť odstránenie informácií tohto odkazu? Všetok obsah konverzácie a komentáre budú odstránené!",
        "confirmCurrentURL": "Odkaz detekovaný ako {item}! Potvrdiť?",
        "commentSetToEmpty": "Komentár bol nastavený na \\"prázdny\\"",
        "enterCommentFirst": "Najprv prosím, zadajte komentár",
        "commentSaved": "Komentár bol uložený",
        "conversationDataDeleted": "Dáta konverzácie boli úspešne odstránené",
        "executeSearchWithQuery": "Vykonať vyhľadávanie: {item}",
        "searchPlaceholder": "Zadajte kľúčové slová pre vyhľadávanie...",
        "searchButton": "Hľadať",
        "searchInContent": "V obsahu konverzácie",
        "searchInComments": "V komentároch",
        "searchInBoth": "V obsahu a komentároch",
        "goToPrevious": "Prejsť na predchádzajúci",
        "goToNext": "Prejsť na ďalší",
        "numberOfMatches": "{matches} zhôd",
        "nodeDetails": "Detaily",
        "enterComment": "Zadajte komentár",
        "userCommentSave": "Uložiť",
        "userCommentCancel": "Zrušiť",
        "userCommentClear": "Vymazať",
        "openAdminPanel": "Administračný panel",
        "allCategoriesFilter": "Všetky kategórie",
        "conversationTitle": "Názov",
        "actionOptions": "Možnosti",
        "conversationCategory": "Kategória",
        "conversationTags": "Značky"
    },
    "sr": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 je pokrenut!🎉",
        "updateCurrentConversationTree": "🌈 Ažurirajte trenutno stablo razgovora 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Podesite boju pozadine i providnost 🎨",
        "toggleConversationTree": "🌳 Prikaži/Sakrij stablo razgovora 🌳",
        "noDatabaseAndCreationFailed": "Nema baze podataka, a kreiranje nije uspelo! Skript izlazi!",
        "mismatchedLink": "Link se ne podudara, molimo osvežite stranicu!",
        "startUpdatingConversationTree": "Počnite ažurirati stablo razgovora",
        "selectedItem": "Izabrali ste {item}",
        "successSavingChanges": "Promene su uspešno sačuvane!",
        "codeCopiedToClipboard": "Kod je kopiran u klipbord!",
        "contentCopied": "Sadržaj je kopiran!",
        "emptyCommentPrompt": "Komentar je prazan. Da li želite postaviti komentar kao \\"prazan\\"?",
        "confirmDeleteLinkData": "Link je detektovan kao {item}! Potvrditi brisanje informacija o ovom linku? Svi podaci razgovora i komentari će biti obrisani!",
        "confirmCurrentURL": "Link je detektovan kao {item}! Potvrditi?",
        "commentSetToEmpty": "Komentar je postavljen kao \\"prazan\\"",
        "enterCommentFirst": "Molimo prvo unesite komentar",
        "commentSaved": "Komentar je sačuvan",
        "conversationDataDeleted": "Podaci razgovora su uspešno obrisani",
        "executeSearchWithQuery": "Izvršiti pretragu: {item}",
        "searchPlaceholder": "Unesite ključne reči za pretragu...",
        "searchButton": "Pretraga",
        "searchInContent": "U sadržaju razgovora",
        "searchInComments": "U komentarima",
        "searchInBoth": "U sadržaju i komentarima",
        "goToPrevious": "Idi na prethodni",
        "goToNext": "Idi na sledeći",
        "numberOfMatches": "{matches} poklapanja",
        "nodeDetails": "Detalji",
        "enterComment": "Unesite komentar",
        "userCommentSave": "Sačuvaj",
        "userCommentCancel": "Otkaži",
        "userCommentClear": "Obriši",
        "openAdminPanel": "Administrativni panel",
        "allCategoriesFilter": "Sve kategorije",
        "conversationTitle": "Naslov",
        "actionOptions": "Opcije",
        "conversationCategory": "Kategorija",
        "conversationTags": "Tagovi"
    },
    "sv": {
        "chatTreeRunning": "🥳🌳ChatTree🌳 är igång!🎉",
        "updateCurrentConversationTree": "🌈 Uppdatera aktuellt konversations träd 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Justera bakgrundsfärg och opacitet 🎨",
        "toggleConversationTree": "🌳 Visa/Dölj konversationsträd 🌳",
        "noDatabaseAndCreationFailed": "Ingen databas, och skapandet misslyckades! Skriptet avslutas!",
        "mismatchedLink": "Länken matchar inte, vänligen uppdatera sidan!",
        "startUpdatingConversationTree": "Börja uppdatera konversationsträdet",
        "selectedItem": "Du valde {item}",
        "successSavingChanges": "Ändringar sparade framgångsrikt!",
        "codeCopiedToClipboard": "Koden har kopierats till urklipp!",
        "contentCopied": "Innehållet har kopierats!",
        "emptyCommentPrompt": "Kommentaren är tom. Vill du ställa in kommentaren som \\"tom\\"?",
        "confirmDeleteLinkData": "Länk identifierad som {item}! Bekräfta radering av denna länks information? All konversationsinnehåll och kommentarer kommer att raderas!",
        "confirmCurrentURL": "Länk identifierad som {item}! Bekräfta?",
        "commentSetToEmpty": "Kommentaren är inställd på \\"tom\\"",
        "enterCommentFirst": "Vänligen skriv en kommentar först",
        "commentSaved": "Kommentar sparad",
        "conversationDataDeleted": "Konversationsdata har raderats framgångsrikt",
        "executeSearchWithQuery": "Utför sökning: {item}",
        "searchPlaceholder": "Ange sökord...",
        "searchButton": "Sök",
        "searchInContent": "I konversationsinnehållet",
        "searchInComments": "I kommentarer",
        "searchInBoth": "I både innehåll och kommentarer",
        "goToPrevious": "Gå till föregående",
        "goToNext": "Gå till nästa",
        "numberOfMatches": "{matches} träffar",
        "nodeDetails": "Detaljer",
        "enterComment": "Ange en kommentar",
        "userCommentSave": "Spara",
        "userCommentCancel": "Avbryt",
        "userCommentClear": "Rensa",
        "openAdminPanel": "Administrationspanel",
        "allCategoriesFilter": "Alla kategorier",
        "conversationTitle": "Titel",
        "actionOptions": "Åtgärder",
        "conversationCategory": "Kategori",
        "conversationTags": "Taggar"
    },
     "th": {
        "chatTreeRunning": "🥳🌳ChatTree🌳กำลังทำงาน!🎉",
        "updateCurrentConversationTree": "🌈 อัปเดตต้นไม้การสนทนาปัจจุบัน 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 ปรับสีพื้นหลังและความโปร่งใส 🎨",
        "toggleConversationTree": "🌳 แสดง/ซ่อนต้นไม้การสนทนา 🌳",
        "noDatabaseAndCreationFailed": "ไม่มีฐานข้อมูล และไม่สามารถสร้างสำเร็จ! สคริปต์ออก!",
        "mismatchedLink": "ลิงก์ไม่ตรงกัน โปรดรีเฟรชหน้า!",
        "startUpdatingConversationTree": "เริ่มต้นอัปเดตต้นไม้การสนทนา",
        "selectedItem": "คุณเลือก {item}",
        "successSavingChanges": "บันทึกการเปลี่ยนแปลงสำเร็จ!",
        "codeCopiedToClipboard": "คัดลอกโค้ดไปยังคลิปบอร์ดแล้ว!",
        "contentCopied": "คัดลอกเนื้อหาแล้ว!",
        "emptyCommentPrompt": "คอมเมนต์ว่างเปล่า คุณต้องการตั้งค่าคอมเมนต์เป็น \\"ว่างเปล่า\\" หรือไม่?",
        "confirmDeleteLinkData": "ตรวจพบลิงก์เป็น {item}! ยืนยันที่จะลบข้อมูลลิงก์นี้หรือไม่? ข้อมูลการสนทนาและคอมเมนต์ทั้งหมดจะถูกลบ!",
        "confirmCurrentURL": "ตรวจพบลิงก์เป็น {item}! ยืนยันหรือไม่?",
        "commentSetToEmpty": "ตั้งค่าคอมเมนต์เป็น \\"ว่างเปล่า\\"",
        "enterCommentFirst": "โปรดใส่คอมเมนต์ก่อน",
        "commentSaved": "บันทึกคอมเมนต์แล้ว",
        "conversationDataDeleted": "ลบข้อมูลการสนทนาสำเร็จ",
        "executeSearchWithQuery": "ดำเนินการค้นหา: {item}",
        "searchPlaceholder": "ป้อนคำค้นหา...",
        "searchButton": "ค้นหา",
        "searchInContent": "ในเนื้อหาการสนทนา",
        "searchInComments": "ในความคิดเห็น",
        "searchInBoth": "ทั้งเนื้อหาและความคิดเห็น",
        "goToPrevious": "ไปที่ก่อนหน้า",
        "goToNext": "ไปที่ถัดไป",
        "numberOfMatches": "{matches} รายการที่ตรงกัน",
        "nodeDetails": "รายละเอียด",
        "enterComment": "กรุณาใส่ความคิดเห็น",
        "userCommentSave": "บันทึก",
        "userCommentCancel": "ยกเลิก",
        "userCommentClear": "ล้าง",
        "openAdminPanel": "แผงการจัดการ",
        "allCategoriesFilter": "ทุกหมวดหมู่",
        "conversationTitle": "ชื่อเรื่อง",
        "actionOptions": "การดำเนินการ",
        "conversationCategory": "ประเภท",
        "conversationTags": "แท็ก"
    },
    "tr": {
        "chatTreeRunning": "🥳🌳ChatTree🌳Çalışıyor!🎉",
        "updateCurrentConversationTree": "🌈 Güncel Konuşma Ağacını Güncelle 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Arka Plan Rengini ve Şeffaflığı Ayarla 🎨",
        "toggleConversationTree": "🌳 Konuşma Ağacını Göster/Gizle 🌳",
        "noDatabaseAndCreationFailed": "Veritabanı yok ve oluşturulamadı! Betik çıkıyor!",
        "mismatchedLink": "Bağlantı eşleşmiyor, lütfen sayfayı yenileyin!",
        "startUpdatingConversationTree": "Konuşma Ağacını Güncellemeye Başla",
        "selectedItem": "{item} seçildi",
        "successSavingChanges": "Değişiklikler Başarıyla Kaydedildi!",
        "codeCopiedToClipboard": "Kod panoya kopyalandı!",
        "contentCopied": "İçerik kopyalandı!",
        "emptyCommentPrompt": "Yorum boş. Yorumu \\"boş\\" olarak ayarlamak istiyor musunuz?",
        "confirmDeleteLinkData": "{item} bağlantısı tespit edildi! Bu bağlantının bilgilerini silmek istediğinizi onaylıyor musunuz? Tüm konuşma ve yorum içerikleri silinecek!",
        "confirmCurrentURL": "{item} bağlantısı tespit edildi! Onaylıyor musunuz?",
        "commentSetToEmpty": "Yorum \\"boş\\" olarak ayarlandı",
        "enterCommentFirst": "Lütfen önce bir yorum girin",
        "commentSaved": "Yorum kaydedildi",
        "conversationDataDeleted": "Konuşma Verileri Başarıyla Silindi",
        "executeSearchWithQuery": "Arama Yapılıyor: {item}",
        "searchPlaceholder": "Arama anahtar kelimelerini girin...",
        "searchButton": "Ara",
        "searchInContent": "Konuşma İçeriğinde",
        "searchInComments": "Yorumlarda",
        "searchInBoth": "İçerik ve Yorumlarda",
        "goToPrevious": "Öncekine Git",
        "goToNext": "Sonrakine Git",
        "numberOfMatches": "{matches} eşleşme",
        "nodeDetails": "Detaylar",
        "enterComment": "Bir yorum girin",
        "userCommentSave": "Kaydet",
        "userCommentCancel": "İptal",
        "userCommentClear": "Temizle",
        "openAdminPanel": "Yönetim Paneli",
        "allCategoriesFilter": "Tüm Kategoriler",
        "conversationTitle": "Başlık",
        "actionOptions": "İşlemler",
        "conversationCategory": "Kategori",
        "conversationTags": "Etiketler"
    },
    "uk": {
        "chatTreeRunning": "🥳🌳ChatTree🌳в роботі!🎉",
        "updateCurrentConversationTree": "🌈 Оновити поточне дерево розмов 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Налаштувати колір фону та прозорість 🎨",
        "toggleConversationTree": "🌳 Показати/сховати дерево розмов 🌳",
        "noDatabaseAndCreationFailed": "Немає бази даних, і не вдалося створити! Скрипт виходить!",
        "mismatchedLink": "Посилання не збігається, будь ласка, оновіть сторінку!",
        "startUpdatingConversationTree": "Почати оновлення дерева розмов",
        "selectedItem": "Ви обрали {item}",
        "successSavingChanges": "Зміни успішно збережено!",
        "codeCopiedToClipboard": "Код скопійовано в буфер обміну!",
        "contentCopied": "Зміст скопійовано!",
        "emptyCommentPrompt": "Коментар порожній. Ви хочете встановити коментар як \\"порожній\\"?",
        "confirmDeleteLinkData": "Посилання виявлене як {item}! Підтверджуєте видалення цього посилання? Всі дані розмов і коментарі будуть видалені!",
        "confirmCurrentURL": "Посилання виявлене як {item}! Підтвердити?",
        "commentSetToEmpty": "Коментар встановлено як \\"порожній\\"",
        "enterCommentFirst": "Будь ласка, спочатку введіть коментар",
        "commentSaved": "Коментар збережено",
        "conversationDataDeleted": "Дані розмови успішно видалено",
        "executeSearchWithQuery": "Виконати пошук: {item}",
        "searchPlaceholder": "Введіть ключові слова для пошуку...",
        "searchButton": "Пошук",
        "searchInContent": "У вмісті розмови",
        "searchInComments": "У коментарях",
        "searchInBoth": "У вмісті та коментарях",
        "goToPrevious": "Перейти до попереднього",
        "goToNext": "Перейти до наступного",
        "numberOfMatches": "{matches} збігів",
        "nodeDetails": "Деталі",
        "enterComment": "Введіть коментар",
        "userCommentSave": "Зберегти",
        "userCommentCancel": "Скасувати",
        "userCommentClear": "Очистити",
        "openAdminPanel": "Панель адміністрування",
        "allCategoriesFilter": "Всі категорії",
        "conversationTitle": "Заголовок",
        "actionOptions": "Дії",
        "conversationCategory": "Категорія",
        "conversationTags": "Теги"
    },
    "ug": {
        "chatTreeRunning": "🥳🌳ChatTree🌳ئىشلىتىلىۋاتىدۇ!🎉",
        "updateCurrentConversationTree": "🌈 نۆۋەتتىكي سۆھبەت تەرەككىنى يېڭىلاڭ 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 فون رەڭگىنى ۋە شېفافلىقىنى تەڭشەڭ 🎨",
        "toggleConversationTree": "🌳 سۆھبەت تەرەككىنى كۆرسىتىش/يوشۇرۇش 🌳",
        "noDatabaseAndCreationFailed": "ماڭلىق يوق، ھەمدە قۇرۇلمىدى! سكرىپت چىقىدۇ!",
        "mismatchedLink": "ئۇلىنىش ماس كەلمىدى، تور بەتنى يېڭىلاڭ!",
        "startUpdatingConversationTree": "سۆھبەت تەرەككىنى يېڭىلاشنى باشلاڭ",
        "selectedItem": "سىز تاللىغان {item}",
        "successSavingChanges": "ئۆزگەرتىشنى ساقلاش مۇۋەپپەقىيەتلىك بولدى!",
        "codeCopiedToClipboard": "كود تەسۋىرلىتىش تاختىسىغا كۆچۈرۈلدى!",
        "contentCopied": "مەزمۇن كۆچۈرۈلدى!",
        "emptyCommentPrompt": "ئىزاھات قۇرۇق. \\"قۇرۇق\\" دەپ بەلگىلەمسىز؟",
        "confirmDeleteLinkData": "{item} ئۇلىنىش تەپلىگەن! بۇ ئۇلىنىشنىڭ ئۇچۇرىنى ئۆچۈرۈشنى جەزملەسىزمۇ؟ بارلىق سۆھبەت ۋە ئىزاھات ئۆچۈرۈلىدۇ!",
        "confirmCurrentURL": "{item} ئۇلىنىش تەپلىگەن! جەزملەسىزمۇ؟",
        "commentSetToEmpty": "ئىزاھات \\"قۇرۇق\\" دەپ بەلگىلەندى",
        "enterCommentFirst": "ئاۋۋال ئىزاھات كىرگۈزۈڭ",
        "commentSaved": "ئىزاھات ساقلاندى",
        "conversationDataDeleted": "سۆھبەت ئۇچۇرى مۇۋەپپەقىيەتلىك ئۆچۈرۈلدى",
        "executeSearchWithQuery": "ئىزدەشنى ئىجرا قىلىدۇ: {item}",
        "searchPlaceholder": "ئىزدەش سۆزىنى كىرگۈزۈڭ...",
        "searchButton": "ئىزدەش",
        "searchInContent": "سۆھبەت مەزمۇنىدا",
        "searchInComments": "ئىزاھاتتا",
        "searchInBoth": "مەزمۇن ۋە ئىزاھاتتا",
        "goToPrevious": "ئالدىنقىغا بېرىش",
        "goToNext": "كېيىنكىگە بېرىش",
        "numberOfMatches": "{matches} دەسلەپتىكى ئۇنۇم",
        "nodeDetails": "نود تەپسىلاتى",
        "enterComment": "ئىزاھات كىرگۈزۈڭ",
        "userCommentSave": "ساقلا",
        "userCommentCancel": "بىكار قىل",
        "userCommentClear": "تازىلا",
        "openAdminPanel": "باشقۇرۇش تاختىسى",
        "allCategoriesFilter": "بارلىق تۈرلەر",
        "conversationTitle": "تېما",
        "actionOptions": "مەشغۇلاتلار",
        "conversationCategory": "تۈر",
        "conversationTags": "بەلگىلەر"
    },
    "vi": {
        "chatTreeRunning": "🥳🌳ChatTree🌳đang chạy!🎉",
        "updateCurrentConversationTree": "🌈 Cập nhật cây trò chuyện hiện tại 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 Điều chỉnh màu nền và độ mờ 🎨",
        "toggleConversationTree": "🌳 Hiển thị/Ẩn cây trò chuyện 🌳",
        "noDatabaseAndCreationFailed": "Không có cơ sở dữ liệu và không thể tạo được! Script thoát!",
        "mismatchedLink": "Liên kết không khớp, vui lòng làm mới trang!",
        "startUpdatingConversationTree": "Bắt đầu cập nhật cây trò chuyện",
        "selectedItem": "Bạn đã chọn {item}",
        "successSavingChanges": "Lưu thay đổi thành công!",
        "codeCopiedToClipboard": "Mã đã được sao chép vào bảng tạm!",
        "contentCopied": "Nội dung đã được sao chép!",
        "emptyCommentPrompt": "Bình luận trống. Bạn có muốn đặt bình luận thành \\"trống\\" không?",
        "confirmDeleteLinkData": "Đã phát hiện liên kết là {item}! Bạn có chắc chắn muốn xóa thông tin của liên kết này không? Tất cả nội dung trò chuyện và bình luận sẽ bị xóa!",
        "confirmCurrentURL": "Đã phát hiện liên kết là {item}! Bạn có xác nhận không?",
        "commentSetToEmpty": "Bình luận đã được đặt thành \\"trống\\"",
        "enterCommentFirst": "Vui lòng nhập bình luận trước",
        "commentSaved": "Bình luận đã được lưu",
        "conversationDataDeleted": "Dữ liệu trò chuyện đã được xóa thành công",
        "executeSearchWithQuery": "Thực hiện tìm kiếm: {item}",
        "searchPlaceholder": "Nhập từ khóa tìm kiếm...",
        "searchButton": "Tìm kiếm",
        "searchInContent": "Trong nội dung cuộc trò chuyện",
        "searchInComments": "Trong bình luận",
        "searchInBoth": "Cả trong nội dung và bình luận",
        "goToPrevious": "Đi đến trước đó",
        "goToNext": "Đi đến tiếp theo",
        "numberOfMatches": "{matches} kết quả phù hợp",
        "nodeDetails": "Chi tiết nút",
        "enterComment": "Nhập bình luận",
        "userCommentSave": "Lưu",
        "userCommentCancel": "Hủy",
        "userCommentClear": "Xóa sạch",
        "openAdminPanel": "Bảng Quản trị",
        "allCategoriesFilter": "Tất cả danh mục",
        "conversationTitle": "Tiêu đề",
        "actionOptions": "Tùy chọn",
        "conversationCategory": "Danh mục",
        "conversationTags": "Thẻ"
    },
     "zh": {
        "chatTreeRunning": "🥳🌳ChatTree🌳运行中!🎉",
        "updateCurrentConversationTree": "🌈 更新当前对话树 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 调整背景颜色和透明度 🎨",
        "toggleConversationTree": "🌳 显示/隐藏对话树 🌳",
        "noDatabaseAndCreationFailed": "没有数据库,并且未能成功创建!脚本退出!",
        "mismatchedLink": "链接不匹配,请刷新页面!",
        "startUpdatingConversationTree": "开始更新对话树",
        "selectedItem": "您选择了{item}",
        "successSavingChanges": "更改保存成功!",
        "codeCopiedToClipboard": "代码已复制到剪贴板!",
        "contentCopied": "内容已复制!",
        "emptyCommentPrompt": "注释为空。是否将注释设置为\\"空\\"?",
        "confirmDeleteLinkData": "监测到链接为 {item}!确认要删除这个链接的信息吗?所有的对话内容和注释都将被删除!",
        "confirmCurrentURL": "监测到链接为 {item}!确认吗?",
        "commentSetToEmpty": "注释已设置为\\"空\\"",
        "enterCommentFirst": "请先输入注释内容",
        "commentSaved": "注释已保存",
        "conversationDataDeleted": "对话数据成功删除",
        "executeSearchWithQuery": "执行搜索:{item}",
        "searchPlaceholder": "输入搜索关键词...",
        "searchButton": "搜索",
        "searchInContent": "对话内容",
        "searchInComments": "用户注释",
        "searchInBoth": "内容与注释",
        "goToPrevious": "转到上一个",
        "goToNext": "转到下一个",
        "numberOfMatches": "{matches} 个匹配项",
        "nodeDetails": "详情",
        "enterComment": "请输入注释",
        "userCommentSave": "保存",
        "userCommentCancel": "取消",
        "userCommentClear": "清空",
        "openAdminPanel": "管理面板",
        "allCategoriesFilter": "所有分类",
        "conversationTitle": "标题",
        "actionOptions": "操作",
        "conversationCategory": "分类",
        "conversationTags": "标签"
    },
        "zh-CN": {
        "chatTreeRunning": "🥳🌳ChatTree🌳运行中!🎉",
        "updateCurrentConversationTree": "🌈 更新当前对话树 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 调整背景颜色和透明度 🎨",
        "toggleConversationTree": "🌳 显示/隐藏对话树 🌳",
        "noDatabaseAndCreationFailed": "没有数据库,并且未能成功创建!脚本退出!",
        "mismatchedLink": "链接不匹配,请刷新页面!",
        "startUpdatingConversationTree": "开始更新对话树",
        "selectedItem": "您选择了{item}",
        "successSavingChanges": "更改保存成功!",
        "codeCopiedToClipboard": "代码已复制到剪贴板!",
        "contentCopied": "内容已复制!",
        "emptyCommentPrompt": "注释为空。是否将注释设置为\\"空\\"?",
        "confirmDeleteLinkData": "监测到链接为 {item}!确认要删除这个链接的信息吗?所有的对话内容和注释都将被删除!",
        "confirmCurrentURL": "监测到链接为 {item}!确认吗?",
        "commentSetToEmpty": "注释已设置为\\"空\\"",
        "enterCommentFirst": "请先输入注释内容",
        "commentSaved": "注释已保存",
        "conversationDataDeleted": "对话数据成功删除",
        "executeSearchWithQuery": "执行搜索:{item}",
        "searchPlaceholder": "输入搜索关键词...",
        "searchButton": "搜索",
        "searchInContent": "对话内容",
        "searchInComments": "用户注释",
        "searchInBoth": "内容与注释",
        "goToPrevious": "转到上一个",
        "goToNext": "转到下一个",
        "numberOfMatches": "{matches} 个匹配项",
        "nodeDetails": "详情",
        "enterComment": "请输入注释",
        "userCommentSave": "保存",
        "userCommentCancel": "取消",
        "userCommentClear": "清空",
        "openAdminPanel": "管理面板",
        "allCategoriesFilter": "所有分类",
        "conversationTitle": "标题",
        "actionOptions": "操作",
        "conversationCategory": "分类",
        "conversationTags": "标签"
    },
    "zh-TW": {
        "chatTreeRunning": "🥳🌳ChatTree🌳運行中!🎉",
        "updateCurrentConversationTree": "🌈 更新當前對話樹 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 調整背景顏色和透明度 🎨",
        "toggleConversationTree": "🌳 顯示/隱藏對話樹 🌳",
        "noDatabaseAndCreationFailed": "沒有資料庫,且未能成功創建!腳本退出!",
        "mismatchedLink": "鏈接不匹配,請刷新頁面!",
        "startUpdatingConversationTree": "開始更新對話樹",
        "selectedItem": "您選擇了{item}",
        "successSavingChanges": "更改保存成功!",
        "codeCopiedToClipboard": "代碼已複製到剪貼板!",
        "contentCopied": "內容已複製!",
        "emptyCommentPrompt": "註釋為空。是否將註釋設置為\\"空\\"?",
        "confirmDeleteLinkData": "監測到鏈接為 {item}!確認要刪除這個鏈接的信息嗎?所有的對話內容和註釋都將被刪除!",
        "confirmCurrentURL": "監測到鏈接為 {item}!確認嗎?",
        "commentSetToEmpty": "註釋已設置為\\"空\\"",
        "enterCommentFirst": "請先輸入註釋內容",
        "commentSaved": "註釋已保存",
        "conversationDataDeleted": "對話數據成功刪除",
        "executeSearchWithQuery": "執行搜索:{item}",
        "searchPlaceholder": "輸入搜索關鍵詞...",
        "searchButton": "搜索",
        "searchInContent": "對話內容",
        "searchInComments": "使用者註釋",
        "searchInBoth": "內容與註釋",
        "goToPrevious": "轉到上一個",
        "goToNext": "轉到下一個",
        "numberOfMatches": "{matches} 個匹配項",
        "nodeDetails": "詳情",
        "enterComment": "請輸入注釋",
        "userCommentSave": "保存",
        "userCommentCancel": "取消",
        "userCommentClear": "清空",
        "openAdminPanel": "管理面板",
        "allCategoriesFilter": "所有類別",
        "conversationTitle": "標題",
        "actionOptions": "操作選項",
        "conversationCategory": "類別",
        "conversationTags": "標籤"
    },
    "zh-HK": {
        "chatTreeRunning": "🥳🌳ChatTree🌳運行中!🎉",
        "updateCurrentConversationTree": "🌈 更新當前對話樹 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 調整背景顏色和透明度 🎨",
        "toggleConversationTree": "🌳 顯示/隱藏對話樹 🌳",
        "noDatabaseAndCreationFailed": "沒有資料庫,且未能成功創建!腳本退出!",
        "mismatchedLink": "鏈接不匹配,請刷新頁面!",
        "startUpdatingConversationTree": "開始更新對話樹",
        "selectedItem": "您選擇了{item}",
        "successSavingChanges": "更改保存成功!",
        "codeCopiedToClipboard": "代碼已複製到剪貼板!",
        "contentCopied": "內容已複製!",
        "emptyCommentPrompt": "註釋為空。是否將註釋設置為\\"空\\"?",
        "confirmDeleteLinkData": "監測到鏈接為 {item}!確認要刪除這個鏈接的信息嗎?所有的對話內容和註釋都將被刪除!",
        "confirmCurrentURL": "監測到鏈接為 {item}!確認嗎?",
        "commentSetToEmpty": "註釋已設置為\\"空\\"",
        "enterCommentFirst": "請先輸入註釋內容",
        "commentSaved": "註釋已保存",
        "conversationDataDeleted": "對話數據成功刪除",
        "executeSearchWithQuery": "執行搜索:{item}",
        "searchPlaceholder": "輸入搜尋關鍵詞...",
        "searchButton": "搜尋",
        "searchInContent": "對話內容",
        "searchInComments": "用戶註釋",
        "searchInBoth": "內容與註釋",
        "goToPrevious": "轉到上一個",
        "goToNext": "轉到下一個",
        "numberOfMatches": "{matches} 個匹配項",
        "nodeDetails": "詳情",
        "enterComment": "請輸入註釋",
        "userCommentSave": "儲存",
        "userCommentCancel": "取消",
        "userCommentClear": "清除",
        "openAdminPanel": "管理面板",
        "allCategoriesFilter": "所有類別",
        "conversationTitle": "標題",
        "actionOptions": "操作選項",
        "conversationCategory": "類別",
        "conversationTags": "標籤"
    },
    "zh-SG": {
       "chatTreeRunning": "🥳🌳ChatTree🌳运行中!🎉",
        "updateCurrentConversationTree": "🌈 更新当前对话树 🌈",
        "adjustBackgroundColorAndOpacity": "🎨 调整背景颜色和透明度 🎨",
        "toggleConversationTree": "🌳 显示/隐藏对话树 🌳",
        "noDatabaseAndCreationFailed": "没有数据库,并且未能成功创建!脚本退出!",
        "mismatchedLink": "链接不匹配,请刷新页面!",
        "startUpdatingConversationTree": "开始更新对话树",
        "selectedItem": "您选择了{item}",
        "successSavingChanges": "更改保存成功!",
        "codeCopiedToClipboard": "代码已复制到剪贴板!",
        "contentCopied": "内容已复制!",
        "emptyCommentPrompt": "注释为空。是否将注释设置为\\"空\\"?",
        "confirmDeleteLinkData": "监测到链接为 {item}!确认要删除这个链接的信息吗?所有的对话内容和注释都将被删除!",
        "confirmCurrentURL": "监测到链接为 {item}!确认吗?",
        "commentSetToEmpty": "注释已设置为\\"空\\"",
        "enterCommentFirst": "请先输入注释内容",
        "commentSaved": "注释已保存",
        "conversationDataDeleted": "对话数据成功删除",
        "executeSearchWithQuery": "执行搜索:{item}",
        "searchPlaceholder": "输入搜索关键词...",
        "searchButton": "搜索",
        "searchInContent": "对话内容",
        "searchInComments": "用户注释",
        "searchInBoth": "内容与注释",
        "goToPrevious": "转到上一个",
        "goToNext": "转到下一个",
        "numberOfMatches": "{matches} 个匹配项",
        "nodeDetails": "详情",
        "enterComment": "请输入注释",
        "userCommentSave": "保存",
        "userCommentCancel": "取消",
        "userCommentClear": "清空",
        "openAdminPanel": "管理面板",
        "allCategoriesFilter": "所有分类",
        "conversationTitle": "标题",
        "actionOptions": "操作",
        "conversationCategory": "分类",
        "conversationTags": "标签"
    }
}`;
    lang = JSON.parse(lang);

    const userLang = selectLang ? lang[selectLang] ? selectLang : 'en' : navigator.languages.find(l => l in lang) || 'en';
    //const userLang =  'en';
    //log("currentLang:",userLang);
    globalUserLang = userLang;
    return {
      langPack: lang[userLang],
      userLang: userLang
    }
  };
  let globalUserLang;
  //let  = getLang().userLang;
  let currentLangPack = getLang().langPack;//log("currentLangPack", currentLangPack);
  function translate(key) {
    return currentLangPack[key] || key;
  }

  const domain = 'https://chatgpt.com';
  const endpoints = {
    assets: 'https://raw.githubusercontent.com/KudoAI/chatgpt.js/main',
    openAI: {
      session: `${domain}/api/auth/session`,
      chats: `${domain}/backend-api/conversations`,
      chat: `${domain}/backend-api/conversation`,
      share_create: `${domain}/backend-api/share/create`,
      share: `${domain}/backend-api/share`,
      instructions: `${domain}/backend-api/user_system_messages`
    }
  };
  let chatgpt = {
    openAIaccessToken: {},

    getAccessToken: function () {
      return new Promise((resolve, reject) => {
        if (Object.keys(chatgpt.openAIaccessToken).length > 0 && // populated
          (Date.parse(chatgpt.openAIaccessToken.expireDate) - Date.parse(new Date()) >= 0)) // not expired
          return resolve(chatgpt.openAIaccessToken.token);
        const xhr = new XMLHttpRequest();
        xhr.open('GET', endpoints.openAI.session, true);
        xhr.setRequestHeader('Content-Type', 'application/json');
        xhr.onload = () => {
          if (xhr.status !== 200) return reject('🤖 chatgpt.js >> Request failed. Cannot retrieve access token.');
          console.info('Token expiration: ' + new Date(JSON.parse(xhr.responseText).expires).toLocaleString().replace(',', ' at'));
          chatgpt.openAIaccessToken = {
            token: JSON.parse(xhr.responseText).accessToken,
            expireDate: JSON.parse(xhr.responseText).expires
          };
          return resolve(chatgpt.openAIaccessToken.token);
        };
        xhr.send();
      });
    }
  };

  class ButtonCreator {
    static createButton(options = {}) {
      const {
        id,
        text,
        innerHTML,
        eventListeners = [],
        additionalStyles = {},
      } = options;

      const button = document.createElement('button');
      button.id = id;
      button.textContent = text;
      if (!button.textContent)
        button.innerHTML = innerHTML;
      eventListeners.forEach(({type, handler}) => {
        button.addEventListener(type, handler);
      });

      Object.assign(button.style, additionalStyles);

      return button;
    }
  }

  let DEFAULT_USER_TOP_COLOR = "#ff7f00", DEFAULT_USER_BOTTOM_COLOR = "#ffc085",
    DEFAULT_CHATGPT_TOP_COLOR = "#0a87d8", DEFAULT_CHATGPT_BOTTOM_COLOR = "#34aeeb";
  let GPT_Avatar_Config = {
    gpt4_Inner_Html: "<div class=\"relative p-1 rounded-sm h-9 w-9 text-white flex items-center justify-center\" style=\"background-color: rgb(171, 104, 255); width: 36px; height: 36px; \"><svg width=\"41\" height=\"41\" viewBox=\"0 0 41 41\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon-md\" role=\"img\"><text x=\"-9999\" y=\"-9999\">ChatGPT</text><path d=\"M37.5324 16.8707C37.9808 15.5241 38.1363 14.0974 37.9886 12.6859C37.8409 11.2744 37.3934 9.91076 36.676 8.68622C35.6126 6.83404 33.9882 5.3676 32.0373 4.4985C30.0864 3.62941 27.9098 3.40259 25.8215 3.85078C24.8796 2.7893 23.7219 1.94125 22.4257 1.36341C21.1295 0.785575 19.7249 0.491269 18.3058 0.500197C16.1708 0.495044 14.0893 1.16803 12.3614 2.42214C10.6335 3.67624 9.34853 5.44666 8.6917 7.47815C7.30085 7.76286 5.98686 8.3414 4.8377 9.17505C3.68854 10.0087 2.73073 11.0782 2.02839 12.312C0.956464 14.1591 0.498905 16.2988 0.721698 18.4228C0.944492 20.5467 1.83612 22.5449 3.268 24.1293C2.81966 25.4759 2.66413 26.9026 2.81182 28.3141C2.95951 29.7256 3.40701 31.0892 4.12437 32.3138C5.18791 34.1659 6.8123 35.6322 8.76321 36.5013C10.7141 37.3704 12.8907 37.5973 14.9789 37.1492C15.9208 38.2107 17.0786 39.0587 18.3747 39.6366C19.6709 40.2144 21.0755 40.5087 22.4946 40.4998C24.6307 40.5054 26.7133 39.8321 28.4418 38.5772C30.1704 37.3223 31.4556 35.5506 32.1119 33.5179C33.5027 33.2332 34.8167 32.6547 35.9659 31.821C37.115 30.9874 38.0728 29.9178 38.7752 28.684C39.8458 26.8371 40.3023 24.6979 40.0789 22.5748C39.8556 20.4517 38.9639 18.4544 37.5324 16.8707ZM22.4978 37.8849C20.7443 37.8874 19.0459 37.2733 17.6994 36.1501C17.7601 36.117 17.8666 36.0586 17.936 36.0161L25.9004 31.4156C26.1003 31.3019 26.2663 31.137 26.3813 30.9378C26.4964 30.7386 26.5563 30.5124 26.5549 30.2825V19.0542L29.9213 20.998C29.9389 21.0068 29.9541 21.0198 29.9656 21.0359C29.977 21.052 29.9842 21.0707 29.9867 21.0902V30.3889C29.9842 32.375 29.1946 34.2791 27.7909 35.6841C26.3872 37.0892 24.4838 37.8806 22.4978 37.8849ZM6.39227 31.0064C5.51397 29.4888 5.19742 27.7107 5.49804 25.9832C5.55718 26.0187 5.66048 26.0818 5.73461 26.1244L13.699 30.7248C13.8975 30.8408 14.1233 30.902 14.3532 30.902C14.583 30.902 14.8088 30.8408 15.0073 30.7248L24.731 25.1103V28.9979C24.7321 29.0177 24.7283 29.0376 24.7199 29.0556C24.7115 29.0736 24.6988 29.0893 24.6829 29.1012L16.6317 33.7497C14.9096 34.7416 12.8643 35.0097 10.9447 34.4954C9.02506 33.9811 7.38785 32.7263 6.39227 31.0064ZM4.29707 13.6194C5.17156 12.0998 6.55279 10.9364 8.19885 10.3327C8.19885 10.4013 8.19491 10.5228 8.19491 10.6071V19.808C8.19351 20.0378 8.25334 20.2638 8.36823 20.4629C8.48312 20.6619 8.64893 20.8267 8.84863 20.9404L18.5723 26.5542L15.206 28.4979C15.1894 28.5089 15.1703 28.5155 15.1505 28.5173C15.1307 28.5191 15.1107 28.516 15.0924 28.5082L7.04046 23.8557C5.32135 22.8601 4.06716 21.2235 3.55289 19.3046C3.03862 17.3858 3.30624 15.3413 4.29707 13.6194ZM31.955 20.0556L22.2312 14.4411L25.5976 12.4981C25.6142 12.4872 25.6333 12.4805 25.6531 12.4787C25.6729 12.4769 25.6928 12.4801 25.7111 12.4879L33.7631 17.1364C34.9967 17.849 36.0017 18.8982 36.6606 20.1613C37.3194 21.4244 37.6047 22.849 37.4832 24.2684C37.3617 25.6878 36.8382 27.0432 35.9743 28.1759C35.1103 29.3086 33.9415 30.1717 32.6047 30.6641C32.6047 30.5947 32.6047 30.4733 32.6047 30.3889V21.188C32.6066 20.9586 32.5474 20.7328 32.4332 20.5338C32.319 20.3348 32.154 20.1698 31.955 20.0556ZM35.3055 15.0128C35.2464 14.9765 35.1431 14.9142 35.069 14.8717L27.1045 10.2712C26.906 10.1554 26.6803 10.0943 26.4504 10.0943C26.2206 10.0943 25.9948 10.1554 25.7963 10.2712L16.0726 15.8858V11.9982C16.0715 11.9783 16.0753 11.9585 16.0837 11.9405C16.0921 11.9225 16.1048 11.9068 16.1207 11.8949L24.1719 7.25025C25.4053 6.53903 26.8158 6.19376 28.2383 6.25482C29.6608 6.31589 31.0364 6.78077 32.2044 7.59508C33.3723 8.40939 34.2842 9.53945 34.8334 10.8531C35.3826 12.1667 35.5464 13.6095 35.3055 15.0128ZM14.2424 21.9419L10.8752 19.9981C10.8576 19.9893 10.8423 19.9763 10.8309 19.9602C10.8195 19.9441 10.8122 19.9254 10.8098 19.9058V10.6071C10.8107 9.18295 11.2173 7.78848 11.9819 6.58696C12.7466 5.38544 13.8377 4.42659 15.1275 3.82264C16.4173 3.21869 17.8524 2.99464 19.2649 3.1767C20.6775 3.35876 22.0089 3.93941 23.1034 4.85067C23.0427 4.88379 22.937 4.94215 22.8668 4.98473L14.9024 9.58517C14.7025 9.69878 14.5366 9.86356 14.4215 10.0626C14.3065 10.2616 14.2466 10.4877 14.2479 10.7175L14.2424 21.9419ZM16.071 17.9991L20.4018 15.4978L24.7325 17.9975V22.9985L20.4018 25.4983L16.071 22.9985V17.9991Z\" fill=\"currentColor\"></path></svg></div>",
    gpt3_Inner_Html: "<div class=\"relative p-1 rounded-sm h-9 w-9 text-white flex items-center justify-center\" style=\"background-color: rgb(25, 195, 125); width: 36px; height: 36px;\"><svg width=\"41\" height=\"41\" viewBox=\"0 0 41 41\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" class=\"icon-md\" role=\"img\"><text x=\"-9999\" y=\"-9999\">ChatGPT</text><path d=\"M37.5324 16.8707C37.9808 15.5241 38.1363 14.0974 37.9886 12.6859C37.8409 11.2744 37.3934 9.91076 36.676 8.68622C35.6126 6.83404 33.9882 5.3676 32.0373 4.4985C30.0864 3.62941 27.9098 3.40259 25.8215 3.85078C24.8796 2.7893 23.7219 1.94125 22.4257 1.36341C21.1295 0.785575 19.7249 0.491269 18.3058 0.500197C16.1708 0.495044 14.0893 1.16803 12.3614 2.42214C10.6335 3.67624 9.34853 5.44666 8.6917 7.47815C7.30085 7.76286 5.98686 8.3414 4.8377 9.17505C3.68854 10.0087 2.73073 11.0782 2.02839 12.312C0.956464 14.1591 0.498905 16.2988 0.721698 18.4228C0.944492 20.5467 1.83612 22.5449 3.268 24.1293C2.81966 25.4759 2.66413 26.9026 2.81182 28.3141C2.95951 29.7256 3.40701 31.0892 4.12437 32.3138C5.18791 34.1659 6.8123 35.6322 8.76321 36.5013C10.7141 37.3704 12.8907 37.5973 14.9789 37.1492C15.9208 38.2107 17.0786 39.0587 18.3747 39.6366C19.6709 40.2144 21.0755 40.5087 22.4946 40.4998C24.6307 40.5054 26.7133 39.8321 28.4418 38.5772C30.1704 37.3223 31.4556 35.5506 32.1119 33.5179C33.5027 33.2332 34.8167 32.6547 35.9659 31.821C37.115 30.9874 38.0728 29.9178 38.7752 28.684C39.8458 26.8371 40.3023 24.6979 40.0789 22.5748C39.8556 20.4517 38.9639 18.4544 37.5324 16.8707ZM22.4978 37.8849C20.7443 37.8874 19.0459 37.2733 17.6994 36.1501C17.7601 36.117 17.8666 36.0586 17.936 36.0161L25.9004 31.4156C26.1003 31.3019 26.2663 31.137 26.3813 30.9378C26.4964 30.7386 26.5563 30.5124 26.5549 30.2825V19.0542L29.9213 20.998C29.9389 21.0068 29.9541 21.0198 29.9656 21.0359C29.977 21.052 29.9842 21.0707 29.9867 21.0902V30.3889C29.9842 32.375 29.1946 34.2791 27.7909 35.6841C26.3872 37.0892 24.4838 37.8806 22.4978 37.8849ZM6.39227 31.0064C5.51397 29.4888 5.19742 27.7107 5.49804 25.9832C5.55718 26.0187 5.66048 26.0818 5.73461 26.1244L13.699 30.7248C13.8975 30.8408 14.1233 30.902 14.3532 30.902C14.583 30.902 14.8088 30.8408 15.0073 30.7248L24.731 25.1103V28.9979C24.7321 29.0177 24.7283 29.0376 24.7199 29.0556C24.7115 29.0736 24.6988 29.0893 24.6829 29.1012L16.6317 33.7497C14.9096 34.7416 12.8643 35.0097 10.9447 34.4954C9.02506 33.9811 7.38785 32.7263 6.39227 31.0064ZM4.29707 13.6194C5.17156 12.0998 6.55279 10.9364 8.19885 10.3327C8.19885 10.4013 8.19491 10.5228 8.19491 10.6071V19.808C8.19351 20.0378 8.25334 20.2638 8.36823 20.4629C8.48312 20.6619 8.64893 20.8267 8.84863 20.9404L18.5723 26.5542L15.206 28.4979C15.1894 28.5089 15.1703 28.5155 15.1505 28.5173C15.1307 28.5191 15.1107 28.516 15.0924 28.5082L7.04046 23.8557C5.32135 22.8601 4.06716 21.2235 3.55289 19.3046C3.03862 17.3858 3.30624 15.3413 4.29707 13.6194ZM31.955 20.0556L22.2312 14.4411L25.5976 12.4981C25.6142 12.4872 25.6333 12.4805 25.6531 12.4787C25.6729 12.4769 25.6928 12.4801 25.7111 12.4879L33.7631 17.1364C34.9967 17.849 36.0017 18.8982 36.6606 20.1613C37.3194 21.4244 37.6047 22.849 37.4832 24.2684C37.3617 25.6878 36.8382 27.0432 35.9743 28.1759C35.1103 29.3086 33.9415 30.1717 32.6047 30.6641C32.6047 30.5947 32.6047 30.4733 32.6047 30.3889V21.188C32.6066 20.9586 32.5474 20.7328 32.4332 20.5338C32.319 20.3348 32.154 20.1698 31.955 20.0556ZM35.3055 15.0128C35.2464 14.9765 35.1431 14.9142 35.069 14.8717L27.1045 10.2712C26.906 10.1554 26.6803 10.0943 26.4504 10.0943C26.2206 10.0943 25.9948 10.1554 25.7963 10.2712L16.0726 15.8858V11.9982C16.0715 11.9783 16.0753 11.9585 16.0837 11.9405C16.0921 11.9225 16.1048 11.9068 16.1207 11.8949L24.1719 7.25025C25.4053 6.53903 26.8158 6.19376 28.2383 6.25482C29.6608 6.31589 31.0364 6.78077 32.2044 7.59508C33.3723 8.40939 34.2842 9.53945 34.8334 10.8531C35.3826 12.1667 35.5464 13.6095 35.3055 15.0128ZM14.2424 21.9419L10.8752 19.9981C10.8576 19.9893 10.8423 19.9763 10.8309 19.9602C10.8195 19.9441 10.8122 19.9254 10.8098 19.9058V10.6071C10.8107 9.18295 11.2173 7.78848 11.9819 6.58696C12.7466 5.38544 13.8377 4.42659 15.1275 3.82264C16.4173 3.21869 17.8524 2.99464 19.2649 3.1767C20.6775 3.35876 22.0089 3.93941 23.1034 4.85067C23.0427 4.88379 22.937 4.94215 22.8668 4.98473L14.9024 9.58517C14.7025 9.69878 14.5366 9.86356 14.4215 10.0626C14.3065 10.2616 14.2466 10.4877 14.2479 10.7175L14.2424 21.9419ZM16.071 17.9991L20.4018 15.4978L24.7325 17.9975V22.9985L20.4018 25.4983L16.071 22.9985V17.9991Z\" fill=\"currentColor\"></path></svg></div>",
  }
  const states = {
    mainButton: {
      isDragging: false,  //
      isMouseOver: false,  //
      isThereNavbar: false,  //用来定义是否存在历史记录栏
      isMouseInNavbar: false,  //用来定义拖动mainbtn时鼠标是否在navbar内
      isMainTreeBtnInNavbar: false,  //历史记录栏里的treemainbtn是否显示 , 用来定义navbar的mainbtn的可见性
      canNotEnterNavbar: false //小圣诞树是绿色/红色, 用来定义maintreeBtn的固定性
    },
    url: {
      isForLiveValidURL: false,
      isForDeletedValidURL: false,
      url: ''
    },
    treeUpdate: {
      isDOMOperating: false,
    },
    visualization: {
      contentDiv: 0,
      thumbnailSvg: 0,
      panelToggleButton: 0,
    },
    colorSetting: {
      customUser: {
        is: false,
        url: "url(#CUSTOM_USER_GRADIENT)",
        top: DEFAULT_USER_TOP_COLOR,
        bottom: DEFAULT_USER_BOTTOM_COLOR,
      },
      customChatGPT: {
        is: false,
        url: "url(#CUSTOM_CHATGPT_GRADIENT)",
        top: DEFAULT_CHATGPT_TOP_COLOR,
        bottom: DEFAULT_CHATGPT_BOTTOM_COLOR,
      },
    },
  };
  const selector = {
    allDivs: ".w-full.text-token-text-primary",
    gptContentDiv: ".relative.items-end",
    userContentDiv: "div[data-message-author-role='user']",
    fowardBackwardButton: ".flex.items-center.justify-center.rounded-lg.text-token-text-secondary",
    branchesInfo: ".text-sm.font-semibold.tabular-nums",
    mainNavbar: ".flex-shrink-0.overflow-x-hidden",//侧边栏里有style属性的那个div, 用来确定是否有必要判断maintreebtn从移动到固定
    navbarSize: '.flex.h-full.min-h-0.flex-col',//侧边栏的**实际大小和边缘监测**用的div,
    historyDiv: '.flex-col.flex-1.transition-opacity.duration-500.-mr-2.pr-2.overflow-y-auto',
  };

  const Default_RootNode_Content_2 = {
    "content_type": "text",
    "parts": [
      "CHAT STARTS HERE"
    ],
    "author": {
      "role": "chatGPT",
      "metadata": {}
    },
    "metadata": {
      "finish_details": {
        "type": "stop",
      },
      "is_complete": true,
      "timestamp_": "absolute"
    },
    "recipient": "all",
    "status": "finished_successfully",
    "weight": 1
  };
  const Default_RootNode_Content = "CHAT STARTS HERE";
  const DB_NAME = 'ChatTreeDB';
  const CONVERSATIONS_STORE_NAME = 'conversations';
  const SEARCH_HISTORY_STORE_NAME = 'searchHistory';
  const USER_SETTINGS_STORE_NAME = 'userSettings';
  let db;

  class DialogueNode {
    constructor(content, type, uuid = -1, parent = -1) {
      this.content = content;
      this.type = type;
      this.uuid = uuid === -1 ? generateUUID() : uuid;
      this.children = [];
      this.comment = '';
      this.parent = parent;
      // this.content_type = content_type;
    }
  }

  let USER_Avatar_Config = {
    USER_DEFAULT_HTML: "<div class=\"relative p-1 rounded-sm h-9 w-9 text-white flex items-center justify-center\" style=\"background-color: rgb(25, 195, 125); width: 36px; height: 36px;\"><svg stroke=\"currentColor\" fill=\"none\" stroke-width=\"2\" viewBox=\"0 0 24 24\" stroke-linecap=\"round\" stroke-linejoin=\"round\" class=\"icon-md\" height=\"1em\" width=\"1em\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2\"></path><circle cx=\"12\" cy=\"7\" r=\"4\"></circle></svg></div>"
  }
  let DEFAULT_CONVERSATION_DATA = {
    rootNode: new DialogueNode(Default_RootNode_Content, "chatGPT"),
    uuid2nodeMap: new Map(),
    bookMarked: new Map(),
    commentMap: new Map(),
    timestamp: Date.now(),
    isNovemberSeventh: true,
    isWholeConversationBookMarked: false,
    categories: [],
    chatTreeTags: [],
    participants: {
      user: {
        name: "UserName",
        avatarURL: "UserAvatarURL",
        avatarHTML: "User",
      },
      gpt: {
        type: "GPT-3",
      }
    }
  };
  let conversationData = DEFAULT_CONVERSATION_DATA;

  let root, treeLayout, svg, svgThumbnail, defs, gLinks, gNodes,
    nodeDrag, canvasDrag, zoom, searchHistoryRecord, chatHistory = [], curMouseOnUUID = null;
  const urlOperations = {
    log(category, ...message) {
      this.logger.log(category, ...message);
    },
    initUrlOperations: function () {
      this.logger = new Logger('urlOperations');
    },

    getCurrentURL: function () {
      return window.location.href;
    },

    isForLiveValidURL: function (url) {
      const pattern = /^https:\/\/chatgpt\.com\/c\/[a-z0-9\-]+\/?$/;

      const pattern2 = /^https:\/\/chatgpt\.com\/g\/[a-zA-Z0-9\-]+\/c\/[0-9a-z\-]+$/;

      return !!pattern.test(url) || !!pattern2.test(url);
    },

    isForDeletedValidURL: function (url) {
      const pattern = /^https:\/\/chatgpt\.com\/chattree\/[a-z0-9\-]+\/?$/;
      return pattern.test(url);
    },

    changeBetweenChattreeWithCAndOneMeansChattreeToC: function (chattreetoc, url) {
      function replaceFirstChattreeWithC(url) {
        return url.replace(/^https:\/\/chatgpt\.com\/chattree/, `${domain}/c`);
      }

      function replaceFirstCWithChattree(url) {
        return url.replace(/^https:\/\/chatgpt\.com\/c/, `${domain}/chattree`);
      }

      if (chattreetoc) {
        return replaceFirstChattreeWithC(url);
      } else {
        return replaceFirstCWithChattree(url);
      }
    },

    isNonUniqueURL: function (url) {
      const nonUniquePatterns = [
        /^https:\/\/chatgpt\.com\/?$/,
        /^https:\/\/chatgpt\.com\/\?model=.+$/
      ];
      return nonUniquePatterns.some(pattern => pattern.test(url));
    },
    getCurURLInfo: function () {
      let curURL = window.location.href;
      let validURL = this.isForLiveValidURL(curURL);
      let nonUniqueURL = !this.isNonUniqueURL(curURL);
      return {
        curURL: curURL,
        validURL: validURL,
        nonUniqueURL: nonUniqueURL,
      };
    },
    observeTargetChanges: function () {
      let lastURL = window.location.href;
      this.log(LogCategories.INFO, "fistURL:", lastURL);
      if (urlOperations.isForLiveValidURL(lastURL) || urlOperations.isForDeletedValidURL(lastURL)) {
        //log("is_anyKind_of_valid");
        urlOperations.handleURLChange(lastURL);
        states.url.url = lastURL;
      }

      const self = this;

      function callback(mutationsList, observer) {

        const currentURL = window.location.href;
        //log("currentURL:", currentURL);
        if (urlOperations.isForLiveValidURL(currentURL)) {
          if (currentURL !== lastURL) {
            self.log(LogCategories.INFO, "URL changed:", currentURL);
            lastURL = currentURL;
            urlOperations.handleURLChange(currentURL);
            states.treeUpdate.isDOMOperating = false;
          } else {
            //this.log("Current URL:", currentURL);
          }
        } else if (urlOperations.isForDeletedValidURL(currentURL)) {
          //log("URL changed:", currentURL, " detected. Please refresh the page.");
          urlOperations.handleURLChange(currentURL);
        } else if (urlOperations.isNonUniqueURL(currentURL)) {
          self.log(LogCategories.WARNING, "Non-unique URL:", currentURL, " detected. Please refresh the page.");
          urlOperations.handleURLChange(currentURL);
        }
      };

      setInterval(() => {
        callback();
        //console.log("Current URL:", window.location.href);
      }, 2000);
    },
    handleURLChange: function (url) {
      //log("In handleURLChange, Data:", conversationData);
      if (urlOperations.isNonUniqueURL(url)) {
        this.log(LogCategories.WARNING, "nonuniqueURL: ", url);
        states.url.isForLiveValidURL = false;
        states.url.isForDeletedValidURL = false;
        states.url.url = '';
        states.treeUpdate.isDOMOperating = false;
        conversationData = DEFAULT_CONVERSATION_DATA;

        root = d3.hierarchy(conversationData.rootNode);
        const widthPerNode = 30;
        const heightPerNode = 30;
        treeLayout = d3.tree().nodeSize([widthPerNode, heightPerNode]);
        treeLayout(root);
        settingsKit.refreshTree();
        //this.log("请刷新页面或者转到具有对话信息的页面从而获取正确的链接");
      } else if (urlOperations.isForDeletedValidURL(url)) {
        //this.log(LogCategories.INFO, "DeletedValidURL");
        states.url.isForLiveValidURL = false;
        states.url.isForDeletedValidURL = true;
        states.treeUpdate.isDOMOperating = false;
        states.url.url = url;
        document.documentElement.setAttribute('class', 'light');
        // const htmlClass = document.documentElement.getAttribute('class');
        // let wholeScreenDiv = document.getElementById("__next"); // 修正单词拼写
        // if (wholeScreenDiv && htmlClass && htmlClass === 'black') {
        //   wholeScreenDiv.style.background = 'rgb(51,53,65)';
        // } else {
        //   document.documentElement.className = 'light'; // 使用 className
        // }

        //rgb(51,53,65)
        url = urlOperations.changeBetweenChattreeWithCAndOneMeansChattreeToC(1, url);
        //log("delete_url_to_new_url", url);
        dbOperations.loadConversationsData(url).then(loadeddata => {
          this.log("Loaded data for URL:", loadeddata);
          let interval;
          interval = setInterval(() => {
            if (document.title === "查看模式(ChatTree提供支持): " + loadeddata.rootNode.content) {
              clearInterval(interval);
            }
            document.title = "查看模式(ChatTree提供支持): " + loadeddata.rootNode.content;
          }, 1500);
          conversationData = loadeddata;
          root = d3.hierarchy(conversationData.rootNode);
          const widthPerNode = 30;
          const heightPerNode = 30;
          treeLayout = d3.tree().nodeSize([widthPerNode, heightPerNode]);

          treeLayout(root);
          settingsKit.refreshTree();
        }).catch(error => {
          console.error("Error loading data:", error);
        });
      } else {
        this.log(LogCategories.INFO, "liveValidURL");

        states.url.isForLiveValidURL = true;
        states.url.isForDeletedValidURL = false;
        states.treeUpdate.isDOMOperating = false;
        states.url.url = url;

        fetchRawChatMessages(url.slice(-36)).then(data => {
          dbOperations.loadConversationsData(url).then(loadeddata => {
            this.log("Loaded data for URL:", loadeddata);
            conversationData = loadeddata;
            if (!conversationData.isNovemberSeventh) {
              let result = conver_to_new_style();
              conversationData.bookMarked = result.bookMarked;
              conversationData.commentMap = result.commentMap;
              //delete conversationData.uuid2pathMap;
              delete conversationData.path2nodeMap;
              dbOperations.saveConversationsData(conversationData).then(() => {
                this.log("Convert To November_Type!");
              });
            }

            this.log("RAW DATA FROM OPENAI:", data);
            //log("New URL Catched!");
            let processResult = processChatMessage(data);
            conversationData.rootNode = conversationRootNode;
            conversationData.uuid2nodeMap = processResult.uuid2nodeMap;
            conversationData.uuid2pathMap = processResult.uuid2pathMap;
            dbOperations.saveConversationsData(conversationData).then(() => {

              controlPanelKit.renderConversations(chatHistory);
              controlPanelKit.updateCategorySelect();
            });
            //log("to draw svg:", conversationData);
            //conversationData = loadeddata;
            root = d3.hierarchy(conversationData.rootNode);
            const widthPerNode = 30;
            const heightPerNode = 30;
            treeLayout = d3.tree().nodeSize([widthPerNode, heightPerNode]);
            treeLayout(root);
            //drawMainSVG();
            settingsKit.refreshTree();
          }).catch(error => {
            console.error("Error loading data:", error);
          });

        }).catch(error => {
          this.log(LogCategories.ERROR, "error:", error);
        })
      }
    }
  };
  urlOperations.initUrlOperations();
  //const DEFAULT_MAINSVG_BACKGROUND = 'linear-gradient(to top, rgba(51, 230, 15, 0.74) 0%, rgba(250, 0, 187, 0.74) 100%)';
  const DEFAULT_MAINSVG_BACKGROUND = 'linear-gradient(to top, rgba(117, 194, 102, 0.31) 0%, rgba(206, 44, 166, 0.31) 100%)';

  //'linear-gradient(to top, rgba(210, 108, 196, 0.2) 0%, rgba(205, 209, 83, 0.2) 100%)'
  const GlobalUserSettings = {
    MainTreeBtnColorSettings: {id: 'mainTreeBtn', color: '#0FD126', opacity: 0.9},
    MainTreeBtnPositionSettings: {id: 'mainTreeBtnPos', top: '20px', left: '800px'},
    mainTreeBtnSticky: {id: 'mainTreeBtnSticky', canNotEnterNavbar: false},
    MainSVGBackground: DEFAULT_MAINSVG_BACKGROUND,
  };

  const dbOperations = {
    log(category, ...message) {
      // 使用 Logger 实例的 log 方法
      //console.log("moduleLog called.");
      this.logger.log(category, ...message);
    },
    initDatabase: function () {

      this.logger = new Logger('dbOperations');
      //this.log(LogCategories.SUCCESS, 'DBLoggerCreated!');


      return new Promise((resolve, reject) => {
        const request = indexedDB.open(DB_NAME, 1);
        request.onerror = event => {
          reject("Error opening database:", event);
        };

        request.onupgradeneeded = event => {
          this.log(LogCategories.INFO, "After LOADING db: ", db);
          this.log(LogCategories.INFO, "升级数据库!");
          db = event.target.result;
          if (!db.objectStoreNames.contains(CONVERSATIONS_STORE_NAME)) {
            db.createObjectStore(CONVERSATIONS_STORE_NAME, {keyPath: "url"});
          }
          if (!db.objectStoreNames.contains(SEARCH_HISTORY_STORE_NAME)) {
            db.createObjectStore(SEARCH_HISTORY_STORE_NAME, {keyPath: "id"});
            const initSearchHistory = {id: 'searchHistory', records: []};
            const transaction = event.target.transaction;
            const historyStore = transaction.objectStore(SEARCH_HISTORY_STORE_NAME);
            historyStore.add(initSearchHistory);
          }
          if (!db.objectStoreNames.contains(USER_SETTINGS_STORE_NAME)) {
            db.createObjectStore(USER_SETTINGS_STORE_NAME, {keyPath: "id"});
            const transaction = event.target.transaction;
            const userSettingsStore = transaction.objectStore(USER_SETTINGS_STORE_NAME);
            const mainTreeBtnColorSettings = {id: 'mainTreeBtn', color: '#0FD126', opacity: 0.9};
            userSettingsStore.add(mainTreeBtnColorSettings);
            const mainTreeBtnPosSettings = {id: 'mainTreeBtnPos', top: '20px', right: '20px'};
            userSettingsStore.add(mainTreeBtnPosSettings);
            const mainTreeBtnSticky = {id: 'mainTreeBtnSticky', canNotEnterNavbar: false};
            userSettingsStore.add(mainTreeBtnSticky);
          }
        };
        request.onsuccess = event => {
          db = event.target.result;
          resolve();
        };
      });

    },
    usedatabase: function () {
      dbOperations.getSearchHistory()
        .then(records => {
          this.log(LogCategories.INFO, '获取到搜索历史记录:', records);
        })
        .then(() => dbOperations.getUserSettings('mainTreeBtn'))
        .then(mainTreeBtnSettings => {
          this.log(LogCategories.INFO, '获取到主树按钮设置:', mainTreeBtnSettings);
          GlobalUserSettings.MainTreeBtnColorSettings = mainTreeBtnSettings;
          treeMainBtn.style.background = GlobalUserSettings.MainTreeBtnColorSettings.color;
          treeMainBtn.style.opacity = GlobalUserSettings.MainTreeBtnColorSettings.opacity;
          navMainButton.style.backgroundColor = GlobalUserSettings.MainTreeBtnColorSettings ? GlobalUserSettings.MainTreeBtnColorSettings.color : "rgb(16,209,38)";
          navMainButton.style.opacity = '1';
        })
        .then(() => dbOperations.getUserSettings('mainTreeBtnPos'))
        .then(mainTreeBtnPos => {
          this.log(LogCategories.INFO, '获取到主树按钮位置设置:', mainTreeBtnPos);
          GlobalUserSettings.MainTreeBtnPositionSettings = mainTreeBtnPos;
          treeMainBtn.style.top = GlobalUserSettings.MainTreeBtnPositionSettings.top;
          treeMainBtn.style.left = GlobalUserSettings.MainTreeBtnPositionSettings.left;
          if (mainTreeBtnPos.isInNavbar === true) {
            navMainButton.style.display = 'block';
            treeMainBtn.style.display = 'none';
            states.mainButton.isMainTreeBtnInNavbar = true;
          }
        })
        .then(() => dbOperations.getUserSettings('userLang'))
        .then(userLang => {
          languageSelect.addEventListener('change', languageKits.init);
          this.log(LogCategories.INFO, '获取到用户语言设置:', userLang);
          if (userLang && userLang.globalUserLang) {
            globalUserLang = userLang.globalUserLang;
          }
          ButtonOperations.showUserNotification(translate("chatTreeRunning"));

          const options = languageSelect.querySelectorAll('option');
          options.forEach(option => {
            option.selected = option.value === globalUserLang;
          });
        })
        .then(() => dbOperations.getUserSettings('mainSVG'))
        .then(mainSVGBackground => {
          this.log(LogCategories.INFO, '获取到主SVG背景设置:', mainSVGBackground);
          if (mainSVGBackground && mainSVGBackground.background) {
            GlobalUserSettings.MainSVGBackground = mainSVGBackground.background;
            let mainSvg = document.getElementById('mainSvg');
            if (mainSvg) {
              mainSvg.style.background = mainSVGBackground.background.toString();
            }
          }
        })
        .then(() => dbOperations.getUserSettings('userColor'))
        .then(userColor => {
          this.log(LogCategories.INFO, '获取到用户颜色设置:', userColor);
          if (userColor && userColor.state && userColor.state.is) {
            initSvgAndGradient.createLinearGradient(defs, "CUSTOM_USER_GRADIENT", userColor.state.bottom, userColor.state.top);
            states.colorSetting.customUser = userColor.state;
            states.colorSetting.customUser.url = 'url(#CUSTOM_USER_GRADIENT)';
          }
        })
        .then(() => dbOperations.getUserSettings('chatGPTColor'))
        .then(chatGPTColor => {
          this.log(LogCategories.INFO, '获取到ChatGPT颜色设置:', chatGPTColor);
          if (chatGPTColor && chatGPTColor.state && chatGPTColor.state.is) {
            initSvgAndGradient.createLinearGradient(defs, "CUSTOM_CHATGPT_GRADIENT", chatGPTColor.state.bottom, chatGPTColor.state.top);
            states.colorSetting.customChatGPT = chatGPTColor.state;
            states.colorSetting.customChatGPT.url = 'url(#CUSTOM_CHATGPT_GRADIENT)';
          }
        })
        .then(drawMainSVG)
        .then(() => dbOperations.getUserSettings('mainTreeBtnSticky'))
        .then(mainTreeBtnSticky => {
          console.log("mainTreeBtnSticky: ", mainTreeBtnSticky);
          if (mainTreeBtnSticky.id) {
            states.mainButton.canNotEnterNavbar = mainTreeBtnSticky.canNotEnterNavbar;
            toggleMainBtnMovingAccessbility.innerHTML = mainTreeBtnSticky.canNotEnterNavbar ? greenForNotEnter : redForEnterable;
            // treeMainBtn.style.display = mainTreeBtnSticky.canNotEnterNavbar ? 'none' : 'block';
            // navMainButton.style.display = mainTreeBtnSticky.canNotEnterNavbar ? 'block' : 'none';
          } else {
            throw new Error("从getUserSettings获取的结构不符合预期");
          }
        })
        .catch(error => {
          console.error(error);
          toggleMainBtnMovingAccessbility.innerHTML = redForEnterable;
        })
        .then(() => dbOperations.initConversationData())
        .then(information => {
          this.log(LogCategories.INFO, "获取到对话数据:", information);
          controlPanelKit.renderConversations(chatHistory);
          controlPanelKit.updateCategorySelect();
          filteredConversations = chatHistory;
        })
        .catch(error => console.error(error));
    },


    initConversationData: function () {
      if (!db) {
        return;
      }
      return new Promise((resolve, reject) => {
        let information = {
          conversations: []
        };

        const transaction = db.transaction([CONVERSATIONS_STORE_NAME], "readwrite");
        const objectStore = transaction.objectStore(CONVERSATIONS_STORE_NAME);

        // 获取现有的 "conversations_information" 对象(如果存在)
        const getRequest = objectStore.get("conversations_information");

        getRequest.onsuccess = event => {
          if (getRequest.result) {
            objectStore.delete("conversations_information");
          }

          // 现在,我们可以遍历对象存储中的每个对象
          objectStore.openCursor().onsuccess = event => {
            const cursor = event.target.result;

            if (cursor) {
              const url = cursor.value.url;

              if (url && !url.includes("conversations_information")) {

                if (cursor.value.rootNode.children.length === 0) {
                  dbOperations.deleteConversationData(url)
                    .then(this.log(LogCategories.WARNING, "a conversation is deleted!"))
                    .catch(error => {
                      console.error(error);
                    });
                } else {

                  const id = url.split('/').pop();
                  const safeId = id.replace(/-/g, '');

                  information.conversations.push({
                    id: safeId,
                    topic: cursor.value.rootNode.content,
                    link: url,
                    categories: cursor.value.categories || [], // 新增字段
                    chatTreeTags: cursor.value.chatTreeTags || [], // 新增字段
                    isWholeConversationBookMarked: cursor.value.isWholeConversationBookMarked || false,
                    timestamp: cursor.value.timestamp
                  });
                }
              }
              cursor.continue();
            } else {
              const putRequest = objectStore.put({
                url: "conversations_information",
                data: information
              });

              putRequest.onsuccess = event => {
                this.log(LogCategories.SUCCESS, "Information updated successfully.");
                resolve(information);
              };

              putRequest.onerror = event => {
                console.error("Failed to update information.", event.target.error);
                reject(event.target.error);
              };
            }
            chatHistory = information.conversations;
            //log("information.conversations:",information.conversations);
          };

        };

        getRequest.onerror = event => {
          console.error("Failed to get information.", event.target.error);
          reject(event.target.error);
        };
      });
    },

    changeWholeConversationBookMarked: function (url, shouldBeBookMarked) {
      if (!url || !urlOperations.isForLiveValidURL(url)) {
        return;
      }
      return new Promise((resolve, reject) => {
        if (!db) {
          console.error("加载数据:Database has not been initialized.");
          return;
        }
        if (!url) {
          reject("加载数据:No URL key specified.");
          return;
        }
        const transaction = db.transaction([CONVERSATIONS_STORE_NAME], "readwrite");
        const objectStore = transaction.objectStore(CONVERSATIONS_STORE_NAME);
        const request = objectStore.get(url);

        request.onsuccess = event => {
          let result = event.target.result;
          result.isWholeConversationBookMarked = shouldBeBookMarked;
          dbOperations.saveConversationsData(result)
            .then(() => dbOperations.initConversationData())
            .then(information => {
              //controlPanelKit.updateCategorySelect();
            })
            .catch(error => {
              console.error(error);
            });
        };
        request.onerror = event => reject("Error loading data:", event.target.errorCode);
      });

    },

    addOrDeleteTagOrClassToURL: async function (url, isTag, value, isAdd) {

      if (!url || !urlOperations.isForLiveValidURL(url)) {
        return;
      }
      return new Promise((resolve, reject) => {
        if (!db) {
          console.error("加载数据:Database has not been initialized.");
          return;
        }
        if (!url) {
          reject("加载数据:No URL key specified.");
          return;
        }
        const transaction = db.transaction([CONVERSATIONS_STORE_NAME], "readwrite");
        const objectStore = transaction.objectStore(CONVERSATIONS_STORE_NAME);
        const request = objectStore.get(url);

        request.onsuccess = event => {
          let result = event.target.result;
          //this.log("result:", result);
          if (isAdd) {
            if (!result.chatTreeTags) {
              result.chatTreeTags = [];
            }
            if (!result.categories) {
              result.categories = [];
            }
            if (isTag && !result.chatTreeTags.includes(value)) {
              result.chatTreeTags.push(value);
              //log("added!");
            } else if (!isTag && !result.categories.includes(value)) {
              result.categories.push(value);
              //log("added!");
            }
          } else {
            if (isTag && result.chatTreeTags.includes(value)) {
              result.chatTreeTags = result.chatTreeTags.filter(tag => tag != value);
              //log("deleted!");
            } else if (!isTag && result.categories.includes(value)) {
              result.categories = result.categories.filter(tag => tag != value);
              //log("deleted!");
            }
          }
          dbOperations.saveConversationsData(result)
            .then(() => dbOperations.initConversationData())
            .then(information => {
              controlPanelKit.updateCategorySelect();
              this.log(LogCategories.INFO, "In DBOper:", "url:", url, "isTag:", isTag, "value:", value, "isAdd:", isAdd);
            })
            .catch(error => {
              console.error(error);
            });
        };
        request.onerror = event => reject("Error loading data:", event.target.errorCode);
      });

    },


    saveConversationsData: function (data) {
      if (data.url === null) {
        this.log(LogCategories.ERROR, "保存对话数据:No URL key specified.");
        return;
      }
      return new Promise((resolve, reject) => {
        if (!db) {
          console.error("保存数据:Database has not been initialized.");
          return;
        }
        if (!data.url) {
          reject("保存数据:No URL key specified.");
          return;
        }
        data.timestamp = Date.now();
        const transaction = db.transaction([CONVERSATIONS_STORE_NAME], "readwrite");
        const objectStore = transaction.objectStore(CONVERSATIONS_STORE_NAME);
        this.log(LogCategories.INFO, "in save data, conversations:", objectStore);
        const request = objectStore.put(data);
        request.onsuccess = () => {
          dbOperations.initConversationData().then(() => {
            this.log(LogCategories.INFO, "update panel.");
          })
          resolve();
        }
        request.onerror = event => reject("Error saving data:", event.target.errorCode);
      });
    },
    getUserSettings: function (key) {
      return new Promise((resolve, reject) => {
        const transaction = db.transaction([USER_SETTINGS_STORE_NAME], 'readonly');
        const store = transaction.objectStore(USER_SETTINGS_STORE_NAME);
        const request = store.get(key);
        request.onsuccess = function (event) {
          const settings = event.target.result;
          //this.log(LogCategories.SUCCESS, 'Got USER SETTING:', settings);
          resolve(settings);
        };
        request.onerror = function (event) {
          this.log(LogCategories.ERROR, '读取设置失败', event.target.errorCode);
          reject(event.target.errorCode);
        };
      });
    },
    updateUserSettings: function (newSettings) {
      let self = this;
      return new Promise((resolve, reject) => {
        //console.log("db:",db);
        const transaction = db.transaction([USER_SETTINGS_STORE_NAME], 'readwrite');
        const store = transaction.objectStore(USER_SETTINGS_STORE_NAME);
        const request = store.put(newSettings);

        request.onsuccess = function (event) {
          self.log(LogCategories.SUCCESS, '设置更新成功');
          resolve();
        };
        request.onerror = function (event) {
          self.log(LogCategories.ERROR, '更新设置失败', event.target.errorCode);
          reject(event.target.errorCode);
        };
      });
    },

    changeConversationDataTopic: async function (url, newTopic) {
      if (!url || !urlOperations.isForLiveValidURL(url)) {
        return;
      }
      return new Promise((resolve, reject) => {
        if (!db) {
          console.error("加载数据:Database has not been initialized.");
          return;
        }
        if (!url) {
          reject("加载数据:No URL key specified.");
          return;
        }
        const transaction = db.transaction([CONVERSATIONS_STORE_NAME], "readwrite");
        const objectStore = transaction.objectStore(CONVERSATIONS_STORE_NAME);
        const request = objectStore.get(url);

        request.onsuccess = event => {
          let result = event.target.result;
          result.rootNode.content = newTopic;
          dbOperations.saveConversationsData(result)
            .then(() => dbOperations.initConversationData())
            .then(information => {
              controlPanelKit.updateCategorySelect();
            })
            .catch(error => {
              console.error(error);
            });
        };
        request.onerror = event => reject("Error loading data:", event.target.errorCode);
      });
    },
    loadConversationsData: function (url) {
      if (!url || (!urlOperations.isForLiveValidURL(url)) && (!urlOperations.isForDeletedValidURL(url))) {
        return;
      }
      return new Promise((resolve, reject) => {
        if (!db) {
          console.error("加载数据:Database has not been initialized.");
          return;
        }
        if (!url) {
          reject("加载数据:No URL key specified.");
          return;
        }
        const transaction = db.transaction([CONVERSATIONS_STORE_NAME], "readwrite");
        const objectStore = transaction.objectStore(CONVERSATIONS_STORE_NAME);
        const request = objectStore.get(url);

        request.onsuccess = event => {
          let result = event.target.result;
          //this.log(" resultOfRequest:", result);
          if (!result) {
            let conversationData = DEFAULT_CONVERSATION_DATA;
            conversationData.url = url;
            //conversationData.uuid2nodeMap.set(conversationData.rootNode.uuid, conversationData.rootNode);
            //conversationData.uuid2pathMap.set(conversationData.rootNode.uuid, []);
            //conversationData.path2nodeMap.set('', conversationData.rootNode);
            //log("在load中: data", conversationData);
            resolve(conversationData);
            // const addRequest = objectStore.add(conversationData);
            // addRequest.onsuccess = () => {
            //   log("返回data:", conversationData);
            //   resolve(conversationData);
            // }
            // addRequest.onerror = event => reject("Error creating new data:", event.target.errorCode);
          } else {
            conversationData = result;
            this.log("loaded conversationData:", conversationData);
            if (!conversationData.bookMarked) {
              conversationData.bookMarked = new Map();
            }
            if (!conversationData.commentMap) {
              conversationData.commentMap = new Map();
            }
            if (!conversationData.participants) {
              conversationData.participants = {
                user: {
                  name: "UserName",
                  avatarURL: "UserAvatarURL",
                  avatarHTML: "User",
                },
                gpt: {
                  type: "GPT-3",
                }
              }
            }
            resolve(result);
          }
        };
        request.onerror = event => reject("Error loading data:", event.target.errorCode);
      });
    },

    deleteConversationData: function (url) {
      return new Promise((resolve, reject) => {
        if (!url || !urlOperations.isForLiveValidURL(url)) {
          reject('Invalid URL');
          return;
        }

        if (!db) {
          console.error('删除数据: Database has not been initialized.');
          reject('Database not initialized');
          return;
        }
        const transaction = db.transaction([CONVERSATIONS_STORE_NAME], 'readwrite');
        const objectStore = transaction.objectStore(CONVERSATIONS_STORE_NAME);
        const deleteRequest = objectStore.delete(url);

        deleteRequest.onsuccess = () => {
          this.log(LogCategories.WARNING, `Data for URL ${url} has been deleted.`);
          resolve();
        };
        deleteRequest.onerror = event => {
          console.error('Error deleting data:', event.target.errorCode);
          reject(event.target.errorCode);
        };
      });
    },
    addSearchRecord: function (records) {
      return new Promise((resolve, reject) => {
        const transaction = db.transaction([SEARCH_HISTORY_STORE_NAME], 'readwrite');
        const store = transaction.objectStore(SEARCH_HISTORY_STORE_NAME);

        const request = store.put({id: 'searchRecords', records: records});

        request.onsuccess = () => {
          this.log(LogCategories.SUCCESS, '搜索记录已成功更新');
          resolve();
        };

        request.onerror = () => {
          this.log(LogCategories.ERROR, '更新搜索记录时出错');
          reject(new Error('更新搜索记录时出错'));
        };
      });
    },
    getSearchHistory: function () {
      return new Promise((resolve, reject) => {
        const transaction = db.transaction([SEARCH_HISTORY_STORE_NAME], 'readonly');
        const store = transaction.objectStore(SEARCH_HISTORY_STORE_NAME);
        const request = store.get('searchRecords');

        request.onsuccess = event => {
          const records = event.target.result?.records || [];
          this.log(LogCategories.SUCCESS, '搜索历史记录:', records);
          searchHistoryRecord = records;
          resolve(records);
        };

        request.onerror = event => {
          console.error('获取搜索历史记录失败');
          reject(new Error('获取搜索历史记录失败'));
        };
      });
    }
  };
  window.addEventListener('DOMContentLoaded', (event) => {
    log(LogCategories.INFO, 'DOM fully loaded and parsed');
    //let timeout;
    //timeout = setTimeout(() => DOMOperations.setNavBarDiv(), 1000);
    DOMOperations.setNavBarDiv()

  });

  const titleDiv = document.createElement('div');
  const title = document.createElement('h3');
  const stickyDiv = document.createElement('div');
  let navPanelButton;
  let navMainButton;
  const DOMOperations = {
    log(category, ...message) {
      // 使用 Logger 实例的 log 方法
      //console.log("moduleLog called.");
      this.logger.log(category, ...message);
    },
    initDOMOperations: function () {

      this.logger = new Logger('DOMOperations');
    },
    jumpToDialogueItem: async function (uuid) {
      function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
      }

      if (!states.url.isForLiveValidURL) {
        return;
      }
      if (!uuid || !conversationData.uuid2pathMap.get(uuid) || uuid === conversationData.rootNode.uuid) {
        return;
      }
      let path = conversationData.uuid2pathMap.get(uuid);
      let content = conversationData.uuid2nodeMap.get(uuid);

      if (!path) {
        log("没有从map中获取路径! 请检查!")
        return;
      }


      let result = DOMOperations.getButtonInfo();
      log("path:", path);
      let i = 0;
      while (i < path.length) {
        result = DOMOperations.getButtonInfo();
        if (path[i] === result.childIndicesPath[i]) {
          i++;
          continue;
        }
        if (path[i] < result.childIndicesPath[i]) {
          if (result.childIndicesPath[i] === 0) {
            confirm("Due to potential issues with the chatgpt page, the previously saved path might be outdated or untraceable. To retrieve this path, please refresh the entire tree via the central ChatTree button. If you wish to perform a complete tree update in order to get the accurate path, enter 'Y' as instructed in the previous prompt.")
            return;
          }
          DOMOperations.clickButtonAtDivLevel(0, i);
          log("path:", path);
          result = DOMOperations.getButtonInfo();
          await sleep(200);
          continue;
        }
        if (path[i] > result.childIndicesPath[i]) {
          if (result.childIndicesPath[i] === result.childrenCountPath[i]) {
            confirm("Due to potential issues with the chatgpt page, the previously saved path might be outdated or untraceable. To retrieve this path, please refresh the entire tree via the central ChatTree button. If you wish to perform a complete tree update in order to get the accurate path, enter 'Y' as instructed in the previous prompt.")
            return;
          }
          DOMOperations.clickButtonAtDivLevel(1, i);
          log("path:", path);
          result = DOMOperations.getButtonInfo();
          await sleep(200);

          continue;
        }
      }
      let j = 0;
      for (; j < path.length; j++) {
        if (path[j] !== result.childIndicesPath[j]) {
          log("在:", j, "没有成功转到!");
          log(path, result.childIndicesPath);
          break;
        }
      }
      sleep(200);
      if (j === path.length) {
        let allDivs = DOMOperations.getAllDivs();
        log("path", path, "result.childIndicesPath", result.childIndicesPath, "allDivs[path.length - 1]", allDivs[path.length - 1]);


        //if content
        function highlightDiv1() {
          allDivs[path.length - 1].classList.add('highlight');
          setTimeout(() => {
            allDivs[path.length - 1].classList.remove('highlight');
          }, 4000);
        }

        function highlightDiv2() {
          allDivs[path.length - 1].classList.add('highlightt');
          setTimeout(() => {
            allDivs[path.length - 1].classList.remove('highlightt');
          }, 4000);
        }

        function blinkText() {
          allDivs[path.length - 1].classList.add('blinkText');
          setTimeout(() => {
            allDivs[path.length - 1].classList.remove('blinkText');
          }, 5000);
        }

        allDivs[path.length - 1].scrollIntoView({behavior: 'smooth', block: 'center', inline: 'start'});
        const dark = isDark();
        if (dark) {
          highlightDiv1();
        } else {
          highlightDiv2();
        }
        setTimeout(() => {
          allDivs[path.length - 1].scrollIntoView({behavior: 'smooth', block: 'center', inline: 'start'});
          const dark = isDark()
          if (dark) {
            highlightDiv1();
          } else {
            highlightDiv2();
          }
        }, 1000);
        //log("成功转到!");
      }
    },
    setNavBarDiv: function () {
      const tryLocateSidebar = () => {
        let HistoryDiv = document.querySelector(selector.historyDiv);
        if (HistoryDiv) {
          // 如果找到侧边栏,则执行相应操作
          if (HistoryDiv.parentNode) {
            HistoryDiv.parentNode.insertBefore(stickyDiv, HistoryDiv);
            navPanelButton.parentNode.insertBefore(navMainButton, navPanelButton);
            HistoryDiv.parentNode.insertBefore(titleDiv, stickyDiv);
          }
          return true; // 返回 true 表示已经成功定位侧边栏
        } else {
          // 如果未找到侧边栏,则继续尝试
          return false; // 返回 false 表示侧边栏未定位
        }
      };

      const intervalId = setInterval(() => {
        const isAdded = tryLocateSidebar();
        if (isAdded) {
          clearInterval(intervalId); // 如果成功定位到侧边栏,则停止定时器
        } else {
          this.log("侧边栏尚未定位,继续尝试...");
        }
      }, 1000); // 每隔 1 秒尝试一次

      // 返回 intervalId 以便在需要时能够停止定时器
      return intervalId;
    },

    getAllDivs: function () {
      //log("allDivs:",document.querySelectorAll(selector.allDivs));
      return document.querySelectorAll(selector.allDivs);

    },
    clickButtonAtDivLevel: function (buttonIndex, divLevel = -1,) {
      let allChatDivs = DOMOperations.getAllDivs();
      if (divLevel >= allChatDivs.length) {
        return;
      }
      let conversationDiv = allChatDivs[divLevel];
      const buttonInfoDiv = conversationDiv.querySelector(selector.fowardBackwardButton);
      //log("buttonInfo:",buttonInfoDiv);
      let buttons = buttonInfoDiv.querySelectorAll("button");
      if (buttons) {
        //log("In clickButtonAtDivLevel", "divLevel: ", divLevel, "buttons:", buttons);
        buttons[buttonIndex].click();
      } else {
        this.log(LogCategories.ERROR, "未能找到应点击的Div");
      }
    },
    getButtonInfo: function () {
      //this.log("In getButtonInfo!");
      let hasRightButtonUnClicked = false;
      let hasLeftButtonUnClicked = false;
      let allChatDivs = DOMOperations.getAllDivs();
      let childIndicesPath = [];
      let childrenCountPath = [];
      for (let i = 0; i < allChatDivs.length; i++) {
        let div = allChatDivs[i];
        //text-xs flex items-center justify-center gap-1 absolute left-0 top-2 -ml-4 -translate-x-full gizmo:top-1 gizmo:-ml-6 group:hover-visible visible
        //text-xs flex items-center justify-center gap-1 self-center pt-2 visible
        const buttonInfoDiv = div.querySelector(selector.fowardBackwardButton);
        if (buttonInfoDiv) {
          let span = buttonInfoDiv.querySelector(selector.branchesInfo);
          if (span) {
            let spanText = span.innerText || span.textContent;
            let match = spanText.match(/(\d+) \/ (\d+)/);
            if (match) {
              let currentVersion = parseInt(match[1], 10);
              let totalVersions = parseInt(match[2], 10);
              childIndicesPath.push(currentVersion);
              childrenCountPath.push(totalVersions);
            } else {
              childIndicesPath.push(1);
              childrenCountPath.push(1);
            }
          } else {
            childIndicesPath.push(1);
            childrenCountPath.push(1);
          }
        } else {
          childIndicesPath.push(1);
          childrenCountPath.push(1);
        }
      }
      //log("divlength:", allChatDivs.length, "childIndices:", childIndicesPath, "childrenCount", childrenCountPath);
      for (let i = 0; i < childrenCountPath.length; i++) {
        if (childIndicesPath[i] !== 1) hasLeftButtonUnClicked = true;
        if (childIndicesPath[i] !== childrenCountPath[i]) hasRightButtonUnClicked = true;
      }
      return {
        childIndicesPath: childIndicesPath,
        childrenCountPath: childrenCountPath,
        hasRightButtonUnClicked: hasRightButtonUnClicked,
        hasLeftButtonUnClicked: hasLeftButtonUnClicked
      };
    },

    getTextContent: function (div, i) {
      let isUser = null;
      let isGPT = null;
      //isUser = div.querySelector('div.flex.flex-col.items-start.gap-3.overflow-x-auto.whitespace-pre-wrap.break-words');
      isGPT = div.querySelector(selector.gptContentDiv);

      if (isGPT) {
        isGPT = div.querySelector(selector.gptContentDiv);
      } else {
        isUser = div.querySelector(selector.userContentDiv);
      }
      let userType = isUser ? "用户" : "chatGPT";
      if (!userType) {
        userType = i % 2 ? "chatGPT" : "用户";
      }
      let contentText;
      if (userType === "用户")
        if (isUser)
          contentText = isUser.innerText;
        else
          contentText = '';
      else {
        if (isGPT)
          contentText = isGPT.innerHTML;
        else
          contentText = '';
      }
      return {
        userType: userType,
        contentText: contentText
      }
    }

  };

  function arrayToKey(arr) {
    return arr.join('|');
  }

  function keyToArray(str) {
    const numArray = str.split('|').map(function (item) {
      return parseInt(item, 10);
    });
  }

  function generateUUID() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
      var r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
      return v.toString(16);
    });
  }


  function toggleSvgShow(isShowSvgButton = 0) {
    let mainSvg = document.getElementById("mainSvg")
    let mainSvgDiv = document.getElementById("mainSvgDiv")
    let thumbnail = document.getElementById("thumbnailSvg")
    let isMainSvgsDisplayed = mainSvgDiv.style.display;
    if (states.visualization.thumbnailSvg === 0) {
      states.visualization.thumbnailSvg = thumbnail.getAttribute("visibility");
      states.visualization.contentDiv = contentDiv.style.display;
      states.visualization.panelToggleButton = panelToggleButton.style.display;
      states.visualization.treeMainBtn = treeMainBtn.style.display;
    }
    log("states.visualization.thumbnailSvg", states.visualization.thumbnailSvg);
    if (isMainSvgsDisplayed === 'block') {
      managePanel.style.display = 'none';
      mainSvgDiv.style.display = "none";
      settingsContainer.style.display = "none";
      searchContainer.style.display = 'none';
      mainSvg.setAttribute("visibility", "hidden");
      rightMiddleContainer.style.display = 'none';
      if (states.visualization.thumbnailSvg === "visible") {
        thumbnail.setAttribute("visibility", "hidden");
      }
      if (states.visualization.contentDiv === 'block') {
        contentDiv.style.display = 'none';
      }
      if (navMainButton.style.display === 'block') {
        treeMainBtn.style.display = 'none';
      }
      panelToggleButton.style.display = 'none';
      commentForm.style.display = 'none';
      document.documentElement.style.overflow = '';

    } else if (isShowSvgButton) {
      mainSvgDiv.style.display = "block";
      settingsContainer.style.display = "block";
      searchContainer.style.display = 'flex';
      mainSvg.setAttribute("visibility", "visible");
      rightMiddleContainer.style.display = 'block';
      treeMainBtn.style.display = 'block';
      if (states.visualization.contentDiv === 'block') {
        contentDiv.style.display = 'block';
      }
      if (states.visualization.thumbnailSvg === "visible") {
        thumbnail.setAttribute("visibility", "visible");
      }
      panelToggleButton.style.display = 'block';
      states.visualization.thumbnailSvg = 0;
      states.visualization.contentDiv = 0;
      document.documentElement.style.overflow = 'hidden';
    }
  }


  window.addEventListener('keydown', function (event) {
    if (event.key === 'Escape' || event.keyCode === 27) {
      log('Esc key was pressed');
      toggleSvgShow();
    }
  });
  let treeMainBtn;//= document.createElement("button");

  let isDragging = false;
  let isMouseOver = false;
  let mainBtnColorPicking = false;
  let offsetX, offsetY;
  let menu;
  let mainBtnColorPicker, mainBtnOpacityPicker;

  const ButtonOperations = {
    createButton: function () {
      treeMainBtn = ButtonCreator.createButton({
        id: 'chatTreeBtn',
        text: '🌳ChatTree🌳',
        eventListeners: [
          {type: 'mouseenter', handler: () => ButtonOperations.showMenu(0)},
          {type: 'mouseleave', handler: ButtonOperations.hideMenuIfNotOver},
          {type: 'mousedown', handler: ButtonOperations.onMouseDown},
          {type: 'click', handler: ButtonOperations.onClick},
        ],
        additionalStyles: {
          display: 'block',
          position: 'fixed',
          zIndex: '9999',
          resize: 'both',
          width: '150px',
          color: 'white',
          height: '30px',
          backgroundColor: GlobalUserSettings.MainTreeBtnColorSettings ? GlobalUserSettings.MainTreeBtnColorSettings.color : 'rgb(16, 209, 38)',
          opacity: GlobalUserSettings.MainTreeBtnColorSettings ? GlobalUserSettings.MainTreeBtnColorSettings.opacity : '0.9',
          borderRadius: '12px',
          // ... 添加其他样式
        },
      });
      try {
        treeMainBtn.style.left = GlobalUserSettings.MainTreeBtnPositionSettings.left ? GlobalUserSettings.MainTreeBtnPositionSettings.left : '300px';
        treeMainBtn.style.top = GlobalUserSettings.MainTreeBtnPositionSettings.top ? GlobalUserSettings.MainTreeBtnPositionSettings.top : '20px';
      } catch (e) {
        treeMainBtn.style.right = '30%';
        treeMainBtn.style.top = '20px';
      }
      document.body.appendChild(treeMainBtn);

      titleDiv.classList.add('sticky', 'top-0', 'z-[16]');
      titleDiv.setAttribute('data-projection-id', '6');
      titleDiv.style.opacity = '1';
      title.classList.add('h-9', 'pb-2', 'pt-3', 'px-2', 'text-xs', 'font-medium', 'text-ellipsis', 'overflow-hidden', 'break-all', 'bg-white', 'dark:bg-black', 'text-gizmo-gray-600');
      title.textContent = '🌳ChatGPT • ChatTree🌳';
      titleDiv.appendChild(title);

      stickyDiv.style.flexDirection = 'column';
      stickyDiv.style.alignItems = 'stretch';
      stickyDiv.style.display = 'flex';

      navPanelButton = ButtonCreator.createButton({
        id: 'navPanelButton',
        text: translate("openAdminPanel"),
        additionalStyles: {
          display: 'block',
          borderRadius: '12px',
          opacity: '0.9',
          background: 'linear-gradient(to right, rgb(0, 123, 255), rgb(0, 198, 255))',
          color: 'white',
          padding: '4px',
          fontWeight: 'bold',
          boxShadow: 'rgba(0, 0, 0, 0.2) 0px 3px 5px',
          margin: '6px',
          height: '30px',
          width: '230px',
        },
        eventListeners: [
          {type: 'click', handler: controlPanelKit.toggleHistoryPanel},
        ],
      });
      stickyDiv.appendChild(navPanelButton);


      navMainButton = ButtonCreator.createButton({
        id: 'navMainButton',
        text: '🌳ChatTree🌳',
        eventListeners: [
          {type: 'mouseenter', handler: () => ButtonOperations.showMenu(1)},
          {type: 'mouseleave', handler: ButtonOperations.hideMenuIfNotOver},
          {type: 'mousedown', handler: ButtonOperations.onMouseDown},
        ],
        additionalStyles: {
          display: 'none',
          zIndex: '9999',
          resize: 'both',
          height: '30px',
          borderRadius: '12px',
          margin: '6px',
          right: 'auto',
          bottom: 'auto',
          color: 'white',
          padding: '4px',
          fontWeight: 'bold',
          boxShadow: 'rgba(0, 0, 0, 0.2) 0px 3px 5px',
          width: '230px',
        },
      });
    },

    toggleStickyMainBtn: function () {


      //log("rightClick!");
      if (states.mainButton.canNotEnterNavbar) {//要可能被固定
        states.mainButton.canNotEnterNavbar = false;
        toggleMainBtnMovingAccessbility.innerHTML = redForEnterable;
        ButtonOperations.showUserNotification("ChatTree now can be fixed in the nav bar!")
      } else {//要变得可以到处移动
        states.mainButton.canNotEnterNavbar = true;
        navMainButton.style.display = 'none';
        toggleMainBtnMovingAccessbility.innerHTML = greenForNotEnter;
        states.mainButton.isMainTreeBtnInNavbar = false;
        if (treeMainBtn.style.display === 'none') {//如果此时不见了, 要先展示出来. 如果此时本来就block, 就不动
          treeMainBtn.style.left = window.innerWidth - 300 + 'px';
          treeMainBtn.style.top = '20px';
          treeMainBtn.style.display = 'block';
        }
        ButtonOperations.showUserNotification("ChatTree Now Can Move AnyWhere!")
      }
      const newSettings = {id: 'mainTreeBtnSticky', canNotEnterNavbar: states.mainButton.canNotEnterNavbar};
      dbOperations.updateUserSettings(newSettings).then(() => {
      }).catch(error => {
        console.error("Error saving Change:", error);
      });
      log("newSettings:", newSettings);
      ButtonOperations.onMouseUp();
    },

    onMouseDown: function (e) {
      //console.log("target:",e.target);
      if (e.button !== 0) return;
      let rect = treeMainBtn.getBoundingClientRect();
      offsetX = e.clientX - rect.left;
      offsetY = e.clientY - rect.top;
      isDragging = true;
      window.addEventListener("mousemove", ButtonOperations.onMouseMove);
      window.addEventListener("mouseup", ButtonOperations.onMouseUp);
    },
    onMouseMove: function (e) {
      if (!isDragging) return;
      //let navbar = document.querySelector(selector.mainNavbar);//这里原来使用的是selector.navbarSize, 但是貌似可以直接用mainNavbar来复用就好了
      let mainNavBar = document.querySelector(selector.mainNavbar)
      // let mainNavBarVisible = "hidden";
      // if (mainNavBar){
      //   mainNavBarVisible = mainNavBar.style.visibility;
      //   //log("navbarVisible:",mainNavBarVisible, "navmain:",mainNavBar);
      // }
      let mainSvgDiv = document.getElementById("mainSvgDiv")
      states.mainButton.isThereNavbar = !!mainNavBar;
      if (states.mainButton.isThereNavbar) {
        if (mainNavBar.style.visibility !== 'hidden' && mainSvgDiv.style.display === 'none') {

          let navbarRect = mainNavBar.getBoundingClientRect();
          let mouseX = e.clientX;
          let mouseY = e.clientY;

          states.mainButton.isMouseInNavbar =
            mouseX >= navbarRect.left &&
            mouseX <= navbarRect.right &&
            mouseY >= navbarRect.top &&
            mouseY <= navbarRect.bottom;

          if (states.mainButton.isMouseInNavbar && !states.mainButton.canNotEnterNavbar) {
            if (!states.mainButton.isMainTreeBtnInNavbar) {
              navMainButton.style.display = 'block';
              treeMainBtn.style.display = 'none';
              states.mainButton.isMainTreeBtnInNavbar = true;
            }
            return;
          } else if (states.mainButton.isMainTreeBtnInNavbar) {
            navMainButton.style.display = 'none';
            treeMainBtn.style.display = 'block';
            states.mainButton.isMainTreeBtnInNavbar = false;
            offsetX = 15/// - rect.left;
            offsetY = 5// - rect.top;
          }
        }
      }
      ButtonOperations.hideMenu();
      let top = e.clientY - offsetY;
      let left = e.clientX - offsetX;
      let maxWidth = window.innerWidth;
      let maxHeight = window.innerHeight;
      let elementWidth = treeMainBtn.offsetWidth;
      let elementHeight = treeMainBtn.offsetHeight;

      if (left < 0) {
        left = 0;
      } else if (left > maxWidth - elementWidth) {
        left = maxWidth - elementWidth;
      }
      if (top < 0) {
        top = 0;
      } else if (top > maxHeight - elementHeight) {
        top = maxHeight - elementHeight;
      }
      treeMainBtn.style.left = left + "px";
      treeMainBtn.style.top = top + "px";
      treeMainBtn.style.right = "auto";
      treeMainBtn.style.bottom = "auto";
    },
    onMouseUp: function () {
      log("mouseup_in_treemainBtn!");
      isDragging = false;
      if (states.mainButton.isMainTreeBtnInNavbar === false) {
        const newSettings = {
          id: 'mainTreeBtnPos',
          left: treeMainBtn.style.left,
          top: treeMainBtn.style.top,
          isInNavbar: false
        };
        dbOperations.updateUserSettings(newSettings).then(() => {
        }).catch(error => {
          console.error("Error saving Change:", error);
        });
      } else {
        const newSettings = {
          id: 'mainTreeBtnPos',
          left: treeMainBtn.style.left,
          top: treeMainBtn.style.top,
          isInNavbar: true
        };
        dbOperations.updateUserSettings(newSettings).then(() => {
        }).catch(error => {
          console.error("Error saving Change:", error);
        });
      }
      window.removeEventListener("mousemove", ButtonOperations.onMouseMove);
      window.removeEventListener("mouseup", ButtonOperations.onMouseUp);
    },
    onClick: function (e) {
      if (isDragging) {
        e.stopPropagation();
        e.preventDefault();
        ButtonOperations.hideMenu();
      }
      if (menu) {

        ButtonOperations.hideMenu();
      }
    },
    showMenu: function (fromNavbar = 0) {
      //log("mouseEnter!, fromNavbar:", fromNavbar);
      if (isDragging) return;
      isMouseOver = true;
      if (menu) return;
      menu = document.createElement("div");
      let updateCurrentConversationTreeText = translate("updateCurrentConversationTree");
      let adjustBackgroundColorAndOpacityText = translate("adjustBackgroundColorAndOpacity");
      let toggleConversationTreeText = translate("toggleConversationTree");

      function rgbToHex(rgb) {
        let match = rgb.match(/\d+/g);
        if (match) {
          let r = parseInt(match[0]);
          let g = parseInt(match[1]);
          let b = parseInt(match[2]);
          return '#' + r.toString(16).padStart(2, '0') + g.toString(16).padStart(2, '0') + b.toString(16).padStart(2, '0');
        }
        return '#000000';  // Return a default color if the conversion fails
      }

      let color = treeMainBtn.style.background;
      let hexColor = rgbToHex(color);
      let opacity = parseFloat(treeMainBtn.style.opacity);
      log("color:", hexColor, 'opacity:', opacity);

      menu.innerHTML = states.url.isForLiveValidURL ?

        `
    <button class='menu-option' id='opt_updateTree' style='color: white; width: 180px; height: 40px; padding: 3px; border-radius: 6px; font-size: 0.5em'>${updateCurrentConversationTreeText}</button>
    <button class='menu-option' id='adjustOption' style='color: white; width: 180px; height: 40px; padding: 3px; border-radius: 6px; font-size: 0.5em'>${adjustBackgroundColorAndOpacityText}</button>
    <button class='menu-option' id='showSvg' style='color: white; width: 180px; height: 40px; padding: 3px; border-radius: 6px; font-size: 0.5em'>${toggleConversationTreeText}</button>
    <input type='range' id='mainBtnOpacityPicker' style='display:none;' min='20' max='100' value=${opacity * 100}>
    <input type='color' id='mainBtnColorPicker' style='display:none;' value=${hexColor}>
`
        :
        `
    <button class='menu-option' id='adjustOption' style='color: white; width: 180px; height: 40px; padding: 3px; border-radius: 6px; font-size: 0.5em'>${adjustBackgroundColorAndOpacityText}</button>
    <button class='menu-option' id='showSvg' style='color: white; width: 180px; height: 40px; padding: 3px; border-radius: 6px; font-size: 0.5em'>${toggleConversationTreeText}</button>
    <input type='range' id='mainBtnOpacityPicker' style='color: white; display:none;' min='20' max='100' value=${opacity * 100}>
    <input type='color' id='mainBtnColorPicker' style='display:none;' value=${hexColor}>
`
      ;

      menu.style.position = "fixed";
      menu.style.zIndex = "10000";
      menu.style.backgroundColor = "transparent";
      menu.style.border = "none";
      document.body.appendChild(menu);
      ButtonOperations.positionMenu(fromNavbar);
      const menuOptions = menu.querySelectorAll('.menu-option');
      menuOptions.forEach((el, index) => {
        el.style.transition = 'all 400ms ease-out';
        el.style.transform = `translateX(${treeMainBtn.getBoundingClientRect().width / 3}px)`;
        el.style.opacity = Math.max(0.2, parseFloat(treeMainBtn.style.opacity) - 0.4);
        setTimeout(() => {
          el.style.transform = 'translateX(0)';
          el.style.opacity = treeMainBtn.style.opacity;
        }, index * 100);
      })

      function rgbToHsl(r, g, b) {
        r /= 255, g /= 255, b /= 255;
        let max = Math.max(r, g, b), min = Math.min(r, g, b);
        let h, s, l = (max + min) / 2;
        if (max === min) {
          h = s = 0;
        } else {
          let d = max - min;
          s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
          switch (max) {
            case r:
              h = (g - b) / d + (g < b ? 6 : 0);
              break;
            case g:
              h = (b - r) / d + 2;
              break;
            case b:
              h = (r - g) / d + 4;
              break;
          }
          h /= 6;
        }
        return [h, s, l];
      }

      function hslToRgb(h, s, l) {
        let r, g, b;
        if (s === 0) {
          r = g = b = l;
        } else {
          function hue2rgb(p, q, t) {
            if (t < 0) t += 1;
            if (t > 1) t -= 1;
            if (t < 1 / 6) return p + (q - p) * 6 * t;
            if (t < 1 / 2) return q;
            if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
            return p;
          }

          let q = l < 0.5 ? l * (1 + s) : l + s - l * s;
          let p = 2 * l - q;
          r = hue2rgb(p, q, h + 1 / 3);
          g = hue2rgb(p, q, h);
          b = hue2rgb(p, q, h - 1 / 3);
        }
        return [r * 255, g * 255, b * 255];
      }

      menuOptions.forEach(option => {
        option.addEventListener('mouseover', () => {
          const bgColor = window.getComputedStyle(treeMainBtn).backgroundColor;
          const rgb = bgColor.match(/[\d.]+/g).map(Number);
          let [h, s, l] = rgbToHsl(...rgb);
          l *= 0.8
          const [r, g, b] = hslToRgb(h, s, l);
          option.style.backgroundColor = `rgb(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)})`;
        });

        option.addEventListener('mouseout', () => {
          option.style.backgroundColor = treeMainBtn.style.backgroundColor;
        });
      });

      menu.addEventListener("click", ButtonOperations.onMenuClick);
      menu.addEventListener("mouseenter", () => isMouseOver = true);
      menu.addEventListener("mouseleave", ButtonOperations.hideMenuIfNotOver);
      mainBtnColorPicker = document.getElementById('mainBtnColorPicker');
      mainBtnOpacityPicker = document.getElementById('mainBtnOpacityPicker');
      mainBtnColorPicker.addEventListener('input', ButtonOperations.onColorChange);
      mainBtnColorPicker.addEventListener('change', ButtonOperations.onColorChangeDone);
      mainBtnOpacityPicker.addEventListener('input', ButtonOperations.onOpacityChange);
      mainBtnOpacityPicker.addEventListener('change', ButtonOperations.onOpacityChangeDone);

      document.querySelectorAll('.menu-option').forEach(el => {
        el.style.backgroundColor = treeMainBtn.style.backgroundColor;
        el.style.opacity = treeMainBtn.style.opacity;
      });
    },

    hideMenuIfNotOver: function () {
      if (mainBtnColorPicking) {
        return;
      }
      isMouseOver = false;
      setTimeout(() => {
        if (!isMouseOver) ButtonOperations.hideMenu();
      }, 100);
    },

    hideMenu: function () {
      if (menu) {
        document.body.removeChild(menu);
        menu = null;
      }
    },

    onMenuClick: function (e) {
      if (e.target.id === 'adjustOption') {
        mainBtnColorPicker.style.display = 'block';
        mainBtnOpacityPicker.style.display = 'inline-block';
        mainBtnColorPicking = true;

      }
      if (e.target.id === 'opt_updateTree') {
        let curURL = window.location.href;
        log("curURL:", curURL, "states:", states);
        if (curURL !== states.url.url) {
          if (urlOperations.isForLiveValidURL(curURL)) {
            if (confirm(translate("confirmCurrentURL").replace('{item}', curURL))) {
              urlOperations.handleURLChange(curURL);
              ButtonOperations.showUserNotification(translate("startUpdatingConversationTree"));
            } else {
              return;
            }
          } else {
            alert("Please Refresh The Page First!🔄️")
            return;
          }
        }
        log("按钮点击而开始更新树!states:", states);

        if (states.url.isForLiveValidURL === true && states.url.url !== '' && !states.treeUpdate.isDOMOperating) {
          log("由于按钮点击而开始更新树!");
          let allDivs = DOMOperations.getAllDivs();
          if (allDivs.length === 0) {
            log("没有检测到Div!请刷新页面获取对话信息!");
            return;
          }
          //console.log("states.url.url: ",states.url.url);
          setTimeout(() => {
            fetchRawChatMessages(conversationData.url.slice(-36)).then(data => {

              let processResult = processChatMessage(data);
              conversationData.rootNode = conversationRootNode;
              conversationData.uuid2nodeMap = processResult.uuid2nodeMap;
              conversationData.uuid2pathMap = processResult.uuid2pathMap;
              dbOperations.saveConversationsData(conversationData).then(() => {

                controlPanelKit.renderConversations(chatHistory);
                controlPanelKit.updateCategorySelect();
              });
              log("to draw svg:", conversationData);
              //conversationData = loadeddata;
              root = d3.hierarchy(conversationData.rootNode);
              const widthPerNode = 30;
              const heightPerNode = 30;
              treeLayout = d3.tree().nodeSize([widthPerNode, heightPerNode]);
              treeLayout(root);
              //drawMainSVG();
              settingsKit.refreshTree();
            }).catch(error => {
              log("error:", error);
            })
          }, 100);
        } else {
          log("按钮点击而开始更新树!但是条件不允许!states:", states);
        }
      }

      if (e.target.id === 'showSvg') {
        toggleSvgShow(1);
      }
      if (e.target.id === 'adjustOption' || e.target.id === 'opt_updateTree' || e.target.id === 'showSvg')
        ButtonOperations.showUserNotification(translate("selectedItem").replace('{item}', e.target.innerText));
    },

    currentMessages: 0,
    showUserNotification: function (text, alertOrNote = "note", duration = 3000) {
      const message = document.createElement("div");

      const innerHtml = (alertOrNote === "note") ?

        `<div class="px-3 py-2 rounded-md text-white inline-flex flex-row border pointer-events-auto gap-2 border-green-500 bg-green-500" role="alert">
        <div class="mt-1 flex-shrink-0 flex-grow-0">
            <svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
                <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path>
                <polyline points="22 4 12 14.01 9 11.01"></polyline>
            </svg>
        </div>
        <div class="flex-1 justify-center gap-2">
            <div class="whitespace-pre-wrap text-left">${text}</div>
        </div>
    </div>` :
        `
    <div data-state="entered" class="toast-root" style="height: 50px; margin-bottom: 0px;">
        <div class="w-full p-1 text-center md:w-auto md:text-justify">
            <div class="px-3 py-2 rounded-md text-white inline-flex flex-row border pointer-events-auto gap-2 border-red-500 bg-red-500" role="alert">
                <div class="mt-1 flex-shrink-0 flex-grow-0">
                    <svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
                        <polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"></polygon>
                        <line x1="12" y1="8" x2="12" y2="12"></line>
                        <line x1="12" y1="16" x2="12.01" y2="16"></line>
                    </svg>
                </div>
                <div class="flex-1 justify-center gap-2">
                    <div class="whitespace-pre-wrap text-left">${text}</div>
                </div>
            </div>
        </div>
    </div>
`;
      /*
      <div data-state="entered" class="toast-root" style="height: 50px; margin-bottom: 0px;"><div class="w-full p-1 text-center md:w-auto md:text-justify"><div class="px-3 py-2 rounded-md text-white inline-flex flex-row border pointer-events-auto gap-2 border-red-500 bg-red-500" role="alert"><div class="mt-1 flex-shrink-0 flex-grow-0"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><polygon points="7.86 2 16.14 2 22 7.86 22 16.14 16.14 22 7.86 22 2 16.14 2 7.86 7.86 2"></polygon><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg></div><div class="flex-1 justify-center gap-2"><div class="whitespace-pre-wrap text-left">Unable to load conversation efdb10a3-3d0d-49de-a21e-a3c2f76c56ea</div></div></div></div></div>
      */
      message.style.position = "fixed";
      message.style.top = "0";
      message.style.zIndex = "10001";
      message.style.opacity = treeMainBtn.style.opacity;
      message.style.transform = "translateY(-100%)";
      message.style.transition = "all 400ms ease-in";
      message.style.marginTop = "10px";
      message.style.opacity = '0';

      message.innerHTML = innerHtml;
      //log("message:", message);
      document.body.appendChild(message);

      const leftPosition = (window.innerWidth - message.offsetWidth) / 2;
      message.style.left = `${leftPosition}px`;

      let offset = this.currentMessages * 50 + 10;
      message.style.top = `${offset}px`;
      this.currentMessages++;
      document.body.appendChild(message);
      setTimeout(() => {
        message.style.transform = "translateY(0)";
        message.style.opacity = '1';
      }, 0);

      setTimeout(() => {
        message.style.transform = "translateY(-100%)";
        message.style.opacity = '0';
        setTimeout(() => {
          document.body.removeChild(message);
          this.currentMessages--;
        }, 400);
      }, duration);
    },

    positionMenu: function (fromNavbar = 0) {
      log("in_position:,fromNavbar", fromNavbar);

      let rect;
      if (!fromNavbar) {
        rect = treeMainBtn.getBoundingClientRect();
      } else {
        rect = navMainButton.getBoundingClientRect();
      }
      log("rect:", rect);
      let menuLeft = fromNavbar ? rect.left + 15 : rect.left - 15;
      menu.style.top = `${rect.bottom}px`;
      menu.style.left = `${menuLeft}px`;
      menu.style.width = `${rect.width}px`;
    },

    onColorChange: function (e) {
      const color = e.target.value;
      treeMainBtn.style.backgroundColor = color;
      navMainButton.style.backgroundColor = color;
      if (menu) {
        document.querySelectorAll('.menu-option').forEach(el => {
          el.style.backgroundColor = color;
        });
      }
    },

    onOpacityChange: function (e) {
      const opacity = e.target.value / 100;
      treeMainBtn.style.opacity = opacity.toString();
      if (menu) {
        document.querySelectorAll('.menu-option').forEach(el => {
          el.style.opacity = opacity.toString();
        });
      }
    },
    onOpacityChangeDone: function (e) {
      const opacity = e.target.value / 100;
      let opacity_string = opacity.toString();
      const newSettings = {id: 'mainTreeBtn', color: treeMainBtn.style.backgroundColor, opacity: opacity_string};
      dbOperations.updateUserSettings(newSettings).then(() => {
        ButtonOperations.showUserNotification(translate("successSavingChanges"));
      }).catch(error => {
        console.error("Error saving Change:", error);
      });
    },
    onColorChangeDone: function (e) {
      const opacity_string = treeMainBtn.style.opacity

      const newSettings = {id: 'mainTreeBtn', color: treeMainBtn.style.backgroundColor, opacity: opacity_string};
      dbOperations.updateUserSettings(newSettings).then(() => {
        ButtonOperations.showUserNotification(translate("successSavingChanges"));
      }).catch(error => {
        console.error("Error saving Change:", error);
      });
    },
  };


  const initSvgAndGradient = {
    thumbnailStyles: {
      "position": "fixed",
      "bottom": "10px",
      "left": "10px"
    },

    createSvg: function (selection, styles = {}, width = "100%",
                         height = "100%", createDiv = true, svgId) {
      let parent = selection;

      if (createDiv) {
        parent = selection.append("div")
          .style("width", width)
          .style("height", height)
          .style("position", "fixed")
          .style("top", "0px")
          .style("left", "0px")
          .style("display", 'none')
          .attr("id", "mainSvgDiv")
      }

      let svgEl = parent.append("svg")
        .attr("width", "100%")
        .attr("height", "100%")
        .attr("visibility", "hidden")

      for (let style in styles) {
        svgEl.style(style, styles[style]);
      }
      if (svgId) {
        svgEl.attr("id", svgId);
      }
      return svgEl;
    },


    createLinearGradient: function (defs, id, colorStart, colorEnd) {
      const gradient = defs.append("linearGradient")
        .attr("id", id)
        .attr("x1", "0%")
        .attr("y1", "100%")
        .attr("x2", "0%")
        .attr("y2", "0%");

      gradient.append("stop")
        .attr("offset", "0%")
        .attr("stop-color", colorStart)
        .attr("stop-opacity", 1);

      gradient.append("stop")
        .attr("offset", "100%")
        .attr("stop-color", colorEnd)
        .attr("stop-opacity", 1);
    },

    Visualizationinit: function (data) {
      if (!data) {
        log("data = null! return!");

      } else {
        root = d3.hierarchy(data);
        const widthPerNode = 30;
        const heightPerNode = 30;
        treeLayout = d3.tree().nodeSize([widthPerNode, heightPerNode]);

        treeLayout(root);
      }
      svg = initSvgAndGradient.createSvg(d3.select("body"), {}, "100%", "100%", true, "mainSvg");
      svgThumbnail = initSvgAndGradient.createSvg(d3.select("body"), initSvgAndGradient.thumbnailStyles, "0px", "0px", false, "thumbnailSvg");
      let mainSvg = document.getElementById('mainSvg');
      if (mainSvg) {
        mainSvg.style.background = GlobalUserSettings.MainSVGBackground ? GlobalUserSettings.MainSVGBackground : DEFAULT_MAINSVG_BACKGROUND;
      }
      defs = svg.append("defs");
      initSvgAndGradient.createLinearGradient(defs, "DEFAULT_CHATGPT_GRADIENT", "#34aeeb", "#0a87d8");
      initSvgAndGradient.createLinearGradient(defs, "DEFAULT_USER_GRADIENT", "#ffc085", "#ff7f00");

      gLinks = svg.append("g");
      gNodes = svg.append("g");
      window.addEventListener('wheel', function (event) {
        if (states.visualization.thumbnailSvg === 'visible') {
          setTimeout(drawMainSVG, 200);
        }
        let mainSvgDiv = document.getElementById("mainSvgDiv");
        if (mainSvgDiv.style.display === 'block' && event.ctrlKey) {
          event.preventDefault(); // 阻止默认行为,即浏览器的缩放操作
        }
      }, {passive: false});

      window.addEventListener("resize", function () {
        if (treeMainBtn) {
          const maxX = window.innerWidth - treeMainBtn.offsetWidth;
          const maxY = window.innerHeight - treeMainBtn.offsetHeight;

          let left = parseInt(treeMainBtn.style.left);
          let top = parseInt(treeMainBtn.style.top);

          left = Math.min(maxX, Math.max(0, left));
          top = Math.min(maxY, Math.max(0, top));

          treeMainBtn.style.left = left + "px";
          treeMainBtn.style.top = top + "px";
          treeMainBtn.style.right = "auto";
          treeMainBtn.style.bottom = "auto";
        }

        let contentDiv = document.getElementById("contentDiv");
        if (contentDiv) {
          const maxX = window.innerWidth - contentDiv.offsetWidth;
          const maxY = window.innerHeight - contentDiv.offsetHeight;

          let left = parseInt(contentDiv.style.left, 10); // 解析当前的 left 值
          let top = parseInt(contentDiv.style.top, 10); // 解析当前的 top 值

          // 判断是否超出窗口界限
          let outOfBoundsX = left < 0 || left > maxX;
          let outOfBoundsY = top < 0 || top > maxY;

          if (outOfBoundsX || outOfBoundsY) {
            // 如果超过窗口界限,则重新定位到窗口右上角
            contentDiv.style.left = maxX + "px";
            contentDiv.style.top = "0px";
            contentDiv.style.right = "auto";
            contentDiv.style.bottom = "auto";
          } else {
            // 如果没有超过界限,则保持现有位置
            left = Math.min(maxX, Math.max(0, left));
            top = Math.min(maxY, Math.max(0, top));

            contentDiv.style.left = left + "px";
            contentDiv.style.top = top + "px";
          }
        }
      });
    }
  }
  initSvgAndGradient.Visualizationinit(conversationData.rootNode);
  let canvas, ctx;
  let canvasWidth, canvasHeight;
  let canvasOffset = 1500;
  //dragAndZoomKits
  const dragAndZoomKits = {

    initialPageX: 0,
    initialPageY: 0,
    initialTranslateX: 0,
    initialTranslateY: 0,

    log(category, ...message) {
      // 使用 Logger 实例的 log 方法
      //console.log("moduleLog called.");
      this.logger.log(category, ...message);
    },
    nodeDragStarted: function (d) {
      nodesInAndOutKit.highlightDescendantsOnDrag(d);
      newOperation =
        {
          type: "drag",
          uuid: d.data.uuid,
          startX: d.x,
          startY: d.y,
        }
      redoStack = [];
    },

    nodeDragged: function (d, i) {
      const dx = d3.event.x - d.x;
      const dy = d3.event.y - d.y;

      function move(node) {
        node.x += dx;
        node.y += dy;
        if (node.children) {
          node.children.forEach(move);
        }

        let windowElem = document.getElementById(node.data.uuid + "-window");
        if (windowElem) {
          let currentTop = parseFloat(windowElem.style.top);
          let currentLeft = parseFloat(windowElem.style.left);
          windowElem.style.top = (currentTop + dy) + 'px';
          windowElem.style.left = (currentLeft + dx) + 'px';
        }
      }

      move(d);
      drawMainSVG();
    },

    nodeDragEnded: function (d) {
      gNodes.selectAll(".node").classed("descendant-dragging", false);
      d3.select(this).style("cursor", "pointer");
      newOperation.endX = d.x;
      newOperation.endY = d.y;
      if (newOperation.startX !== newOperation.endX || newOperation.startY !== newOperation.endY)
        settingsKit.performAction(newOperation);
    },

    setCanvasSize: function () {
      const links = gLinks.selectAll(".link").data();
      const nodes = gNodes.selectAll(".node").data();

      let minX = Number.MAX_VALUE;
      let minY = Number.MAX_VALUE;
      let maxX = Number.MIN_VALUE;
      let maxY = Number.MIN_VALUE;

      links.forEach(link => {
        minX = Math.min(minX, link.source.x, link.target.x);
        minY = Math.min(minY, link.source.y, link.target.y);
        maxX = Math.max(maxX, link.source.x, link.target.x);
        maxY = Math.max(maxY, link.source.y, link.target.y);
      });

      nodes.forEach(node => {
        minX = Math.min(minX, node.x);
        minY = Math.min(minY, node.y);
        maxX = Math.max(maxX, node.x);
        maxY = Math.max(maxY, node.y);
      });
      // 设置 Canvas 的大小,考虑边界位置
      const canvasWidth = maxX - minX;
      const canvasHeight = maxY - minY;
      canvas.width = canvasWidth;
      canvas.height = canvasHeight;
    },

    svgDragStarted: function () {

      if (!canvas) {
        canvas = document.createElement('canvas');
        canvasWidth = svg.node().getBoundingClientRect().width + 2 * canvasOffset;
        canvasHeight = svg.node().getBoundingClientRect().height + 2 * canvasOffset;
        canvas.width = canvasWidth;
        canvas.height = canvasHeight;
        //dragAndZoomKits.setCanvasSize();
        document.body.appendChild(canvas);
        ctx = canvas.getContext('2d');
      }

      gNodes.attr("opacity", 0); // 隐藏所有节点
      gLinks.attr("opacity", 0); // 隐藏所有链接
      // 绘制SVG内容到canvas
      drawSvgContentToCanvas();

      function createRadialGradient(ctx, x, y, radius, colorStart, colorEnd) {
        const gradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
        gradient.addColorStop(0, colorStart);
        gradient.addColorStop(1, colorEnd);
        return gradient;
      }

      function drawCircleWithLinearGradient(ctx, x, y, radius, colorStart, colorEnd) {
        const gradient = ctx.createLinearGradient(x, y - radius, x, y + radius);
        gradient.addColorStop(1, colorStart);
        gradient.addColorStop(0, colorEnd);

        ctx.fillStyle = gradient;
        ctx.beginPath();
        ctx.arc(x, y, radius, 0, 2 * Math.PI);
        ctx.fill();
      }

      function drawSvgContentToCanvas() {

        const currentTransform = d3.zoomTransform(svg.node());


        const links = gLinks.selectAll(".link").data();
        const nodes = gNodes.selectAll(".node").data();

        ctx.save();
        // 使用SVG的当前transform和偏移量来调整canvas上的内容
        ctx.translate(currentTransform.x, currentTransform.y);
        ctx.scale(currentTransform.k, currentTransform.k);


        // 计算根据缩放调整后的偏移量
        const adjustedOffset = canvasOffset / currentTransform.k;

        // 绘制线
        links.forEach(link => {
          ctx.beginPath();
          ctx.moveTo(link.source.x + adjustedOffset, link.source.y + adjustedOffset);
          const midX = (link.source.x + link.target.x + 2 * adjustedOffset) / 2;
          ctx.bezierCurveTo(midX, link.source.y + adjustedOffset, midX, link.target.y + adjustedOffset, link.target.x + adjustedOffset, link.target.y + adjustedOffset);
          ctx.stroke();
        });

        nodes.forEach(node => {
          ctx.beginPath();

          ctx.arc(node.x + adjustedOffset, node.y + adjustedOffset, 10, 0, 2 * Math.PI, false);

          const GPTGradient = createRadialGradient(ctx, node.x, node.y, 10, states.colorSetting.customChatGPT.bottom, states.colorSetting.customChatGPT.top);
          const UserGradient = createRadialGradient(ctx, node.x, node.y, 10, states.colorSetting.customUser.bottom, states.colorSetting.customUser.top);

          if (node.data.type.toLowerCase() === "chatgpt") {
            drawCircleWithLinearGradient(ctx, node.x + adjustedOffset, node.y + adjustedOffset, 10, states.colorSetting.customChatGPT.bottom, states.colorSetting.customChatGPT.top);
          } else {
            drawCircleWithLinearGradient(ctx, node.x + adjustedOffset, node.y + adjustedOffset, 10, states.colorSetting.customUser.bottom, states.colorSetting.customUser.top);
          }
          ctx.fill();

        });
        ctx.restore();

      }

      // 显示canvas,不隐藏SVG
      canvas.style.position = "absolute";
      canvas.style.top = (svg.node().getBoundingClientRect().top - canvasOffset) + "px";
      canvas.style.left = (svg.node().getBoundingClientRect().left - canvasOffset) + "px";
      canvas.style.visibility = "visible";

      const currentTransform = d3.zoomTransform(svg.node());
      this.initialTranslateX = currentTransform.x;
      this.initialTranslateY = currentTransform.y;
      this.initialPageX = d3.event.sourceEvent.pageX;
      this.initialPageY = d3.event.sourceEvent.pageY;
    },

    svgDragged: function () {
      const dx = (d3.event.sourceEvent.pageX - this.initialPageX);
      const dy = (d3.event.sourceEvent.pageY - this.initialPageY);
      //const newTranslateX = this.initialTranslateX + dx;
      //const newTranslateY = this.initialTranslateY + dy;
      canvas.style.top = (svg.node().getBoundingClientRect().top + dy - canvasOffset) + "px";
      canvas.style.left = (svg.node().getBoundingClientRect().left + dx - canvasOffset) + "px";

      //svg.call(zoom.transform, d3.zoomIdentity.translate(newTranslateX, newTranslateY).scale(d3.zoomTransform(svg.node()).k));
      //drawMainSVG();
    },

    svgDragEnded: function () {
      const dx = (d3.event.sourceEvent.pageX - this.initialPageX);
      const dy = (d3.event.sourceEvent.pageY - this.initialPageY);
      const newTranslateX = this.initialTranslateX + dx;
      const newTranslateY = this.initialTranslateY + dy;

      gNodes.attr("opacity", 1); // 显示所有节点
      gLinks.attr("opacity", 1); // 显示所有链接
      // 更新SVG的位置
      svg.call(zoom.transform, d3.zoomIdentity.translate(newTranslateX, newTranslateY).scale(d3.zoomTransform(svg.node()).k));

      // 清除canvas的内容
      ctx.clearRect(0, 0, canvasWidth, canvasHeight);

      canvas.remove();
      canvas = null;

      drawMainSVG();
    },
    zoomed: function () {
      const transform = d3.event.transform;
      gNodes.attr("transform", transform.toString());
      gLinks.attr("transform", transform.toString());
      drawThumbnailSVG();
    },
    init: function (svgElement) {

      this.logger = new Logger('DragAndZoomOperations');
      //this.log(LogCategories.SUCCESS, 'DragAndZoomOperations!');

      nodeDrag = d3.drag()
        .on("start", dragAndZoomKits.nodeDragStarted)
        .on("drag", dragAndZoomKits.nodeDragged)
        .on("end", dragAndZoomKits.nodeDragEnded);

      canvasDrag = d3.drag()
        .on("start", this.svgDragStarted)
        .on("drag", this.svgDragged)
        .on("end", this.svgDragEnded);

      zoom = d3.zoom()
        .scaleExtent([0.1, 10])
        .filter(() => d3.event.ctrlKey)
        .on("zoom", this.zoomed);

      svgElement.call(zoom);
      svgElement.call(canvasDrag);
    },


  }
  dragAndZoomKits.init(svg);

  function drawThumbnailSVG() {
    const thumbReservedSpace = 0.2;
    svgThumbnail.selectAll("*").remove();

    const xRange = d3.extent(root.descendants(), d => d.x);
    const yRange = d3.extent(root.descendants(), d => d.y);
    const treeWidth = xRange[1] - xRange[0];
    const treeHeight = yRange[1] - yRange[0];


    const thumbScale = 0.2;
    const DEFAULTS = {
      THUMB_WIDTH_MIN: 270,
      THUMB_WIDTH_MAX: 400,
      THUMB_HEIGHT_MIN: 250,
      THUMB_HEIGHT_MAX: 1000,
    };
    let thumbWidth = Math.max(DEFAULTS.THUMB_WIDTH_MIN, Math.min(DEFAULTS.THUMB_WIDTH_MAX, treeWidth * thumbScale * (1 + 1.2 * thumbReservedSpace)));
    let thumbHeight = Math.max(DEFAULTS.THUMB_HEIGHT_MIN, Math.min(DEFAULTS.THUMB_HEIGHT_MAX, treeHeight * (thumbScale) * (1 + 1 * thumbReservedSpace)));

    const xOffset = Math.min(Math.max(treeWidth * thumbScale * 0.8 * thumbReservedSpace, 0.9 * thumbWidth), 0.1 * thumbWidth);
    const yOffset = thumbHeight * 0.05;


    svgThumbnail
      .attr("width", thumbWidth)
      .attr("height", thumbHeight)
      .style("border-radius", "10px")

    svgThumbnail.append("rect")
      .attr("width", thumbWidth)
      .attr("height", thumbHeight)
      .attr("rx", 10)
      .attr("ry", 10)
      .style("fill", "none")
      .style("stroke", "rgba(211,211,211,1)")
      .style("stroke-width", 5);

    svgThumbnail.selectAll(".link")
      .data(root.links())
      .enter().append("line")
      .attr("class", "link")
      .attr("x1", d => (d.source.x - xRange[0]) * thumbScale + xOffset)
      .attr("y1", d => (d.source.y - yRange[0]) * thumbScale + yOffset)
      .attr("x2", d => (d.target.x - xRange[0]) * thumbScale + xOffset)
      .attr("y2", d => (d.target.y - yRange[0]) * thumbScale + yOffset)
      .style("stroke", "#aaa");


    let userGradient = states.colorSetting.customUser.is ? states.colorSetting.customUser.url : "url(#DEFAULT_USER_GRADIENT)";
    let chatGPTGradient = states.colorSetting.customChatGPT.is ? states.colorSetting.customChatGPT.url : "url(#DEFAULT_CHATGPT_GRADIENT)";

    svgThumbnail.selectAll(".node")
      .data(root.descendants())
      .enter().append("circle")
      .attr("class", "node")
      .attr("cx", d => (d.x - xRange[0]) * thumbScale + xOffset)
      .attr("cy", d => (d.y - yRange[0]) * thumbScale + yOffset)
      .attr("r", 2)
      .style("fill", d => {
        return d.data.type === "用户" ? userGradient : chatGPTGradient;
      });

    const transform = d3.zoomTransform(svg.node());
    const currentZoom = transform.k;
    const currentTranslateX = transform.x;
    const currentTranslateY = transform.y;
    let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
    root.descendants().forEach(node => {

      const adjustedX = node.x * currentZoom + currentTranslateX;
      const adjustedY = node.y * currentZoom + currentTranslateY;
      const svgRect = svg.node().getBoundingClientRect();
      const screenX = adjustedX + svgRect.left;
      const screenY = adjustedY + svgRect.top;

      const isVisible = (screenX >= -6 && screenX <= window.innerWidth + 6) &&
        (screenY >= -6 && screenY <= window.innerHeight + 6);

      if (isVisible) {
        if (node.x < minX) minX = node.x;
        if (node.x > maxX) maxX = node.x;
        if (node.y < minY) minY = node.y;
        if (node.y > maxY) maxY = node.y;
      }
    });

    if (minX === Infinity || minY === Infinity || maxX === -Infinity || maxY === -Infinity) {
      return;
    }
    const rectX = (minX - xRange[0]) * thumbScale + xOffset;
    const rectY = (minY - yRange[0]) * thumbScale + yOffset;
    const rectWidth = (maxX - minX) * thumbScale;
    const rectHeight = (maxY - minY) * thumbScale;

    svgThumbnail.append("rect")
      .attr("x", rectX - 6)
      .attr("y", rectY - 6)
      .attr("width", rectWidth + 12)
      .attr("height", rectHeight + 12)
      .attr("rx", 10)
      .attr("ry", 10)
      .style("fill", "rgba(211,211,211,0.3)")
      .style("stroke", "rgba(80, 80, 80, 1)")
      .style("stroke-width", 2);

  }

  function drawMainSVG() {

    const links = gLinks.selectAll(".link")
      .data(root.links(), d => d.target.data.uuid);

    links.enter()
      .append("path")
      .attr("class", "link")
      .attr("fill", "none")
      .attr("stroke", "white")
      .attr("stroke-width", "2")
      .merge(links)
      .attr("d", d => {
        return `M ${d.source.x} ${d.source.y} C ${(d.source.x + d.target.x) / 2} ${d.source.y}, ${(d.source.x + d.target.x) / 2} ${d.target.y}, ${d.target.x} ${d.target.y}`;
      });

    links.exit().remove();

    const nodes = gNodes.selectAll(".node")
      .data(root.descendants(), d => d.data.uuid);

    const nodesEnter = nodes.enter()
      .append("g")
      .attr("class", "node")
      .attr("transform", d => `translate(${d.x},${d.y})`)
      .call(nodeDrag);

    nodesEnter.append("circle")
      .attr("r", 10)
      .attr("class", function (d) {
        return d.data.type.toLowerCase();
      });
    nodesEnter.append("text")
      .text("✅")
      .style("display", "none")
      .attr("text-anchor", "middle")
      .attr("dy", ".35em")
      .style("font-size", "12px");

    let userGradient = states.colorSetting.customUser.is ? states.colorSetting.customUser.url : "url(#DEFAULT_USER_GRADIENT)";
    let chatGPTGradient = states.colorSetting.customChatGPT.is ? states.colorSetting.customChatGPT.url : "url(#DEFAULT_CHATGPT_GRADIENT)";

    d3.selectAll(".node circle.chatgpt")
      .style("fill", chatGPTGradient);
    d3.selectAll(".node circle.用户")
      .style("fill", userGradient);

    //log("root:", root);
    //log("conversationData.bookMarked:", conversationData.bookMarked);
    //log("Filtered Data:", nodesEnter.filter(d => conversationData.bookMarked.get(d.data.uuid)));

    nodesEnter.append("text")
      .attr("class", "emoji-tag")
      .attr("x", d => +0)
      .attr("y", d => -4)
      .text("📌")
      .attr("visibility", "hidden")
      .filter(d => {
        // 在这里打印uuid和从Map中获取的值
        const bookmarkedValue = conversationData.bookMarked.get(d.data.uuid);
        // log(`UUID: ${d.data.uuid}, Bookmarked Value:`, bookmarkedValue);
        return bookmarkedValue !== undefined; // 例如,如果你只想保留映射中有值的项
      })
      .attr("visibility", "visible");

    nodes
      .selectAll(".emoji-tag")
      .filter(d => {
        // 在这里打印uuid和从Map中获取的值
        const bookmarkedValue = conversationData.bookMarked.get(d.data.uuid);
        //log(`UUID: ${d.data.uuid}, Bookmarked Value:`, bookmarkedValue);

        // 你仍然需要返回一个布尔值来决定是否保留元素
        return bookmarkedValue !== undefined; // 例如,如果你只想保留映射中有值的项
      })
      .attr("visibility", "visible");

    nodesEnter.on("contextmenu", nodesInAndOutKit.onContextMenu)
    nodesEnter.on("mouseover", nodesInAndOutKit.handleMouseOver);
    nodesEnter.on("mouseout", nodesInAndOutKit.handleMouseOut);
    nodesEnter.on('click', nodesInAndOutKit.showNode);

    nodes.merge(nodesEnter)
      .attr("transform", d => `translate(${d.x},${d.y})`);


    nodes.exit().remove();
    //drawThumbnailSVG();
    //updateStylesBasedOnTheme();

  }

  //nodesInAndOutKit
  const nodesInAndOutKit = {


    highlightDescendantsOnDrag: function (node) {
      gNodes.selectAll(".node")
        .classed("descendant-dragging", d => nodesInAndOutKit.isDescendant(node, d));
    },
    isDescendant: function (parent, node) {
      if (node === parent) return false;
      while (node) {
        if (node === parent) {
          return true;
        }
        node = node.parent;
      }
      return false;
    },
    highlightDescendantLinks: function (node) {
      gLinks.selectAll(".link")
        .classed("descendant-highlighted", function (d) {
          return nodesInAndOutKit.isDescendant(node, d.target);
        });
    },
    showNode: function (d) {
      const existingComment = conversationData.commentMap.get(d.data.uuid);
      //d.data.comment;
      if (existingComment && existingComment !== '') {
        commentTextarea.value = existingComment;
      } else {
        commentTextarea.value = '';
      }
      const nodeElement = d3.select(this);
      const isAlreadySelected = nodeElement.classed("selectedNode");
      if (isAlreadySelected) {
        contentDiv.style.display = 'none';
        nodeElement.classed("selectedNode", false);
        if (commentForm.style.display === 'block') {
          commentForm.style.display = 'none';
        }
      } else {
        if (d.data.type === "用户") {
          talkingPerson.innerHTML = conversationData.participants.user.avatarHTML;
        } else if (d.data.type === "chatGPT") {
          talkingPerson.innerHTML = (conversationData.participants.gpt.type === "GPT-3") ? GPT_Avatar_Config.gpt3_Inner_Html : GPT_Avatar_Config.gpt4_Inner_Html;
        } else {
          talkingPerson.innerHTML = '';
        }
        gNodes.selectAll(".node")
          .classed("selectedNode", false);
        nodeElement.classed("selectedNode", true);
        contentDiv.style.display = 'block';
        log("nodeElement:", nodeElement);
        contentDiv.dataset.curDisplay = d.data.uuid;
        let selectedNodeContent = document.getElementById("nodeContent");
        const dark = isDark();
        contentDiv.style.backgroundColor = dark ?
          d.data.type === "chatGPT" ? 'rgb(68,70,84)' : 'rgb(51,53,65)' :
          d.data.type === "chatGPT" ? 'rgb(247,247,248)' : 'rgb(256,256,256)';
        contentDiv.style.boxShadow = dark ? '0px 4px 20px rgba(255, 255, 255, 0.1)' : '0px 4px 20px rgba(0, 0, 0, 0.1)';
        nodesInAndOutKit.updateSelectedNodeContentDiv(d, selectedNodeContent);
        selectedNodeContent.removeEventListener('click', nodesInAndOutKit.handleClick);
        selectedNodeContent.addEventListener('click', nodesInAndOutKit.handleClick);
        var initialHeight = contentDiv.offsetHeight;
        selectedNodeContent.style.height = initialHeight - 60 + 'px';
        //selectedNodeContent.style.maxHeight = '600px';
      }
    },

    createTalkingPersonElement: function (d) {
      const talkingPerson1 = document.createElement('div');
      talkingPerson1.style.marginRight = '8px';
      talkingPerson1.style.background = 'none';
      talkingPerson1.style.height = 'auto';
      talkingPerson1.classList.add("relative", "flex")
      if (d.data.type === "用户") {
        //talkingPerson1.innerHTML = conversationData.participants.user.avatarHTML;
        //talkingPerson1 = document.createElement('div');
        let url = conversationData.participants.user.avatarURL ? conversationData.participants.user.avatarURL : -1;
        if (url === -1) {
          talkingPerson1.innerHTML = USER_Avatar_Config.USER_DEFAULT_HTML;
        } else {
          talkingPerson1.innerHTML = `
        <img alt="User" loading="lazy" width="36" height="36" decoding="async" data-nimg="1"
                                class="rounded-sm"
                                src="${conversationData.participants.user.avatarURL}"
                                style="color: transparent;">
`
        }
        // const img = talkingPerson1.querySelector('img');
        // if (img) {
        //   img.style.display = 'block';
        //   img.style.margin = '0';
        //   img.style.height = '35px';
        //   img.style.width = '35px';
        // }

      } else if (d.data.type === "chatGPT") {
        talkingPerson1.innerHTML = (conversationData.participants.gpt.type === "GPT-3") ? GPT_Avatar_Config.gpt3_Inner_Html : GPT_Avatar_Config.gpt4_Inner_Html;
      } else {
        talkingPerson1.innerHTML = '';
      }
      return talkingPerson1;
    },
    updateSelectedNodeContentDiv: async function (d, targetDiv) {
      const talkingPerson1 = nodesInAndOutKit.createTalkingPersonElement(d);


      function createAllPicsDiv() {
        const div = document.createElement('div');
        div.className = "flex-wrap";
        return div;
      }

      function createOnePic(sourceURL) {
        let contentWidth;
        if (targetDiv.id === "nodeContent") {
          let contentDiv = document.getElementById("contentDiv");
          contentWidth = contentDiv.offsetWidth;
        } else {
          contentWidth = targetDiv.offsetWidth || 600;
        }
        const button = document.createElement('button');
        button.type = "button";
        button.className = 'm-1';
        button.style.width = contentWidth / 2 - 60 + 'px';
        button.innerHTML =
          `
        <img alt="Generated by DALL·E" loading="lazy" src="${sourceURL}">
    `
        return button;
      }

      function createDownloadBtn() {
        const btn = document.createElement('button');
        btn.classList.add("flex-wrap", "h-6", "w-6", "items-center", "justify-center", "rounded", "bg-black/50");
        btn.innerHTML =
          `
        <svg width="24" height="24" viewBox="-5.5 2 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" class="icon-sm text-white">
            <path fill-rule="evenodd" clip-rule="evenodd" d="M7.70711 10.2929C7.31658 9.90237 6.68342 9.90237 6.29289 10.2929C5.90237 10.6834 5.90237 11.3166 6.29289 11.7071L11.2929 16.7071C11.6834 17.0976 12.3166 17.0976 12.7071 16.7071L17.7071 11.7071C18.0976 11.3166 18.0976 10.6834 17.7071 10.2929C17.3166 9.90237 16.6834 9.90237 16.2929 10.2929L13 13.5858L13 4C13 3.44771 12.5523 3 12 3C11.4477 3 11 3.44771 11 4L11 13.5858L7.70711 10.2929ZM5 19C4.44772 19 4 19.4477 4 20C4 20.5523 4.44772 21 5 21H19C19.5523 21 20 20.5523 20 20C20 19.4477 19.5523 19 19 19L5 19Z" fill="currentColor"></path>
        </svg>
`;
        btn.addEventListener('click', function () {
          // 创建一个隐藏的下载链接并触发下载
          let downloadLink = document.createElement('a');
          downloadLink.href = btn.dataset.sourceUrl; // 从按钮的 dataset 中获取 URL
          downloadLink.setAttribute('download', '');  // 这将使链接触发下载
          downloadLink.style.display = 'none';
          document.body.appendChild(downloadLink);
          downloadLink.click();
          document.body.removeChild(downloadLink);
        });
        return btn;
      }

      let commentHTML = conversationData.commentMap.get(d.data.uuid) ? `<span class="comment-text">注释: ${conversationData.commentMap.get(d.data.uuid)}</span><br>` : '';
      targetDiv.innerHTML = commentHTML;
      targetDiv.appendChild(talkingPerson1);

      let realContentDiv = document.createElement('div');

      if (d.data.uuid === conversationData.rootNode.uuid) {
        log("rootNode:", d);
        let contentSpan = document.createElement('span');
        contentSpan.innerText = conversationData.rootNode.content; // 如果只需要纯文本
        realContentDiv.appendChild(contentSpan);
        targetDiv.appendChild(realContentDiv);
        return;
      }

      log("d.data.content:", d.data.content);

      for (let i = 0; i < d.data.content.length; i++) {
        if ((d.data.content[i].author.name === "dalle.text2im" && d.data.content[i].content_type === 'text') ||
          (d.data.content[i].author.role === "assistant" && d.data.content[i].recipient === "dalle.text2im")) {
          continue;
        }
        if (d.data.content[i].content_type === "text") {
          log("the ", i, "th one is text.");
          let accumulatedParts = [...d.data.content[i].parts[0]];
          let combinedText;
          let text2HTML;
          if (d.data.content[i].author.role !== "user") {
            log("d.data.content[i].author.role !== \"user\"");
            if (i + 1 < d.data.content.length) {
              while (d.data.content[i + 1].content_type === "text") {
                log("处理 ", i + 1, "中");
                accumulatedParts.push(d.data.content[i + 1].parts[0]);
                i++;
                if (i + 1 === d.data.content.length) {
                  break;
                }
              }
            }
            combinedText = accumulatedParts.join('');
            text2HTML = markdownToHTML(combinedText);
            let contentSpan = document.createElement('span');
            contentSpan.innerHTML = text2HTML;
            realContentDiv.appendChild(contentSpan);
          } else {
            log("d.data.content[i].author.role === \"user\"");
            text2HTML = accumulatedParts.join('');
            log("text2HTML:", text2HTML);
            let contentSpan = document.createElement('span');
            contentSpan.innerText = text2HTML; // 如果只需要纯文本
            realContentDiv.appendChild(contentSpan);
          }
        } else if (d.data.content[i].content_type === "multimodal_text") {//这个对象里, parts数组里, 可能有多组图片群
          log("the ", i, "th one is multimodal_text.");
          for (let j = 0; j < d.data.content[i].parts.length; j++) {
            if (d.data.content[i].parts[j].content_type && d.data.content[i].parts[j].content_type === "image_asset_pointer") {//开始载入图片
              let allPicsDiv = createAllPicsDiv();
              allPicsDiv.id = "allPicsDiv";
              realContentDiv.appendChild(allPicsDiv);
              while (d.data.content[i].parts[j].content_type && d.data.content[i].parts[j].content_type === "image_asset_pointer") {
                // const regex = /file-[\w-]+/;
                // const matchedstring = d.data.content[i].asset_pointer.match(regex);
                const matchedstring = d.data.content[i].parts[j].asset_pointer.slice(15);  // 从第16个字符开始截取到字符串结尾
                if (matchedstring) {
                  //log(matchedstring);  // file-0rbJDxE2JZqAUM8R9Jz8eUOS
                  let sourceURL = pictureURL.get(matchedstring);
                  if (!sourceURL) {
                    try {
                      sourceURL = await fetchPictureURL(matchedstring);
                      pictureURL.set(matchedstring, sourceURL);
                    } catch (error) {
                      console.error(error);
                      return;  // 如果有错误,提前退出函数
                    }
                  }
                  let picDiv = createOnePic(sourceURL);
                  let downloadBtn = createDownloadBtn();
                  downloadBtn.dataset.sourceUrl = sourceURL;  // 在所有情况下都设置
                  allPicsDiv.appendChild(picDiv);
                  allPicsDiv.appendChild(downloadBtn);
                } else {
                  log("No match found");
                }
                j++;
                if (j === d.data.content[i].parts.length) {
                  break;
                }
              }//退出当前图片群, 且当前j没有加入
              if (j < d.data.content[i].parts.length) {
                let accumulatedParts = [...d.data.content[i].parts[j]];
                j++;
                let text2HTML;
                for (; j < d.data.content[i].parts.length; j++) {
                  accumulatedParts.push(d.data.content[i].parts[j]);
                }
                let combinedText;
                if (d.data.content[i].author.role !== "user") {
                  log("d.data.content[i].author.role !== \"user\"");
                  combinedText = accumulatedParts.join('');
                  text2HTML = markdownToHTML(combinedText);
                  let contentSpan = document.createElement('span');
                  contentSpan.innerHTML = text2HTML;
                  realContentDiv.appendChild(contentSpan);
                } else {
                  log("d.data.content[i].author.role === \"user\"");
                  text2HTML = accumulatedParts.join('');
                  log("text2HTML:", text2HTML);
                  let contentSpan = document.createElement('span');
                  contentSpan.innerText = text2HTML; // 如果只需要纯文本
                  realContentDiv.appendChild(contentSpan);
                }
              }
            }
          }
        }
      }

      function markdownToHTML(markdown) {
        markdown = markdown.replace(/</g, '&lt;').replace(/>/g, '&gt;');
        // 1. Convert code blocks
//         markdown = markdown.replace(/```(\w+)?\s*([\s\S]*?)```/g, function (match, lang, code) {
//           lang = lang || 'plaintext';
//           // Escape HTML tags inside the code block
//           const escapedCode = code.replace(/</g, '&lt;').replace(/>/g, '&gt;');
//           return `<pre><div class="bg-black rounded-md"><div class="flex items-center relative text-gray-200 bg-gray-800 gizmo:dark:bg-token-surface-primary px-4 py-2 text-xs font-sans justify-between rounded-t-md"><span>${lang}</span><button class="flex ml-auto gizmo:ml-0 gap-2 items-center"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>Copy code</button></div><div class="p-4 overflow-y-auto"><code class="!whitespace-pre hljs language-${lang}">${escapedCode}
// </code></div></div></pre>`;
//         });
        const codeBlocks = [];

        markdown = markdown.replace(/(?:\n\n|^)```(\w+)?\s*([\s\S]*?)\n```(?:\n\n)/gm, function (match, lang, code, index) {
          lang = lang || 'plaintext';
          const escapedCode = code.replace(/</g, '&lt;').replace(/>/g, '&gt;');
          const codeHtml = `<pre><div class="bg-black rounded-md"><div class="flex items-center relative text-gray-200 bg-gray-800 gizmo:dark:bg-token-surface-primary px-4 py-2 text-xs font-sans justify-between rounded-t-md"><span>${lang}</span><button class="flex ml-auto gizmo:ml-0 gap-2 items-center"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>Copy code</button></div><div class="p-4 overflow-y-auto"><code class="!whitespace-pre hljs language-${lang}">${escapedCode}</code></div></div></pre>`;
          // Push the current code block's HTML to the array and replace with a placeholder
          codeBlocks.push(codeHtml);
          return `CODEBLOCK${codeBlocks.length - 1}\n`;
        });

        markdown = markdown.replace(/(?:\n\n|^)```(\w+)?\s*([\s\S]*)$/, function (match, lang, code, index) {
          lang = lang || 'plaintext';
          const escapedCode = code.replace(/</g, '&lt;').replace(/>/g, '&gt;');
          const codeHtml = `<pre><div class="bg-black rounded-md"><div class="flex items-center relative text-gray-200 bg-gray-800 gizmo:dark:bg-token-surface-primary px-4 py-2 text-xs font-sans justify-between rounded-t-md"><span>${lang}</span><button class="flex ml-auto gizmo:ml-0 gap-2 items-center"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>Copy code</button></div><div class="p-4 overflow-y-auto"><code class="!whitespace-pre hljs language-${lang}">${escapedCode}</code></div></div></pre>`;
          // Push the current code block's HTML to the array and replace with a placeholder
          codeBlocks.push(codeHtml);
          return `CODEBLOCK${codeBlocks.length - 1}\n`;
        });
        markdown = markdown.replace(/```/g, '`');
        // markdown = markdown.replace(/(?:\n\n|^)```(\w+)?\s*([\s\S]*?)\n\n```(?:\n\n|$)/gm, function (match, pre, lang, code, post, index) {
        //   lang = lang || 'plaintext';
        //   const escapedCode = code.replace(/</g, '&lt;').replace(/>/g, '&gt;');
        //   const codeHtml = `<pre><div class="bg-black rounded-md"><div class="flex items-center relative text-gray-200 bg-gray-800 gizmo:dark:bg-token-surface-primary px-4 py-2 text-xs font-sans justify-between rounded-t-md"><span>${lang}</span><button class="flex ml-auto gizmo:ml-0 gap-2 items-center"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>Copy code</button></div><div class="p-4 overflow-y-auto"><code class="!whitespace-pre hljs language-${lang}">${escapedCode}</code></div></div></pre>`;
        //   // Push the current code block's HTML to the array and replace with a placeholder
        //   codeBlocks.push(codeHtml);
        //   return `CODEBLOCK${codeBlocks.length - 1}`;
        // });
        // 2. Convert headers
        markdown = markdown.replace(/^#####\s+(.+)$/gm, '<h5>$1</h5>');
        markdown = markdown.replace(/^####\s+(.+)$/gm, '<h4>$1</h4>');
        markdown = markdown.replace(/^###\s+(.+)$/gm, '<h3>$1</h3>');
        markdown = markdown.replace(/^##\s+(.+)$/gm, '<h2>$1</h2>');
        markdown = markdown.replace(/^#\s+(.+)$/gm, '<h1>$1</h1>');

        // 3. Convert horizontal rules
        markdown = markdown.replace(/^(---)$/gm, '<hr>');

        // 4. Convert links
        markdown = markdown.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_new">$1</a>');

        // 5. Convert unordered lists marked with single dash
        //markdown = markdown.replace(/^- ([^\n]+)(\n|$)/gm, '<li>$1</li>');
        //markdown = markdown.replace(/(<li>.*?<\/li>)(?=<li>|$)/gs, '<ul>$1</ul>');

        // 6. Convert inline code and escape HTML
        // markdown = markdown.replace(/`([^`]+)`/g, function (match, code) {
        //   return '<code>' + code.replace(/</g, '&lt;').replace(/>/g, '&gt;') + '</code>';
        // });
        // 对于内联代码的匹配,我们使用否定的前瞻和后顾来确保反引号前后没有其他反引号
        markdown = markdown.replace(/(?<!`)`([^`\n]+)`(?!`)/g, function (match, code) {
          return '<code>' + code.replace(/</g, '&lt;').replace(/>/g, '&gt;') + '</code>';
        });

// 7. Convert bold text
        markdown = markdown.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
// 8. Convert paragraphs
        markdown = markdown.replace(/^(?!<li>|<h[1-6]>|<ul>|<pre>|<hr>|<a>|<\/?code>)([\s\S]+?)(?=\n\n|$)/gm, '<p>$1</p>');
        markdown = markdown.replace(/CODEBLOCK(\d+)/g, function (match, n) {
          return codeBlocks[n];
        });
        return markdown;
      }

//       function markdownToHTML(markdown) {
//         // Convert bold text
//         markdown = markdown.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
//
//         markdown = markdown.replace(/```(\w+)?\s*([\s\S]*?)```/g, function (match, lang, code) {
//           // 如果没有指定语言,我们可以默认使用'plaintext',或者选择不显示
//           lang = lang || 'plaintext';
//           return `<pre><div class="bg-black rounded-md"><div class="flex items-center relative text-gray-200 bg-gray-800 gizmo:dark:bg-token-surface-primary px-4 py-2 text-xs font-sans justify-between rounded-t-md"><span>${lang}</span><button class="flex ml-auto gizmo:ml-0 gap-2 items-center"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>Copy code</button></div><div class="p-4 overflow-y-auto"><code>${code.trim()}
// </code></div></div></pre>`;
//         });
//
//         // Convert inline code and escape HTML
//         markdown = markdown.replace(/`([^`]+)`/g, function (match, code) {
//           return '<code>' + code.replace(/</g, '&lt;').replace(/>/g, '&gt;') + '</code>';
//         });
//
//         // Convert ordered lists
//         markdown = markdown.replace(/\d+\.\s+(.+)(\n|$)/g, '<li>$1</li>');
//         markdown = '<ol>' + markdown + '</ol>';
//
//         // Convert unordered lists (assuming they are marked with - )
//         markdown = markdown.replace(/^-\s+(.+)(\n|$)/gm, '<li>$1</li>');
//         markdown = '<ul>' + markdown + '</ul>';
//
//         // Convert paragraphs
//         markdown = markdown.replace(/([^\n]+)(\n|$)/g, '<p>$1</p>');
//
//         return markdown;
//       }


      // Convert code blocks
//         markdown = markdown.replace(/```([\s\S]*?)```/g, function (match, code) {
//           return `<pre><div class="bg-black rounded-md"><div class="flex items-center relative text-gray-200 bg-gray-800 gizmo:dark:bg-token-surface-primary px-4 py-2 text-xs font-sans justify-between rounded-t-md"><span>javascript</span><button class="flex ml-auto gizmo:ml-0 gap-2 items-center"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>Copy code</button></div><div class="p-4 overflow-y-auto"><code>${code.trim()}
// </code></div></div></pre>`;
//         });

//         // Convert code blocks with language detection
//         markdown = markdown.replace(/```(\\w*)\\n([\\s\\S]*?)```/g, function(match, language, code) {
//           return `<pre><div class="bg-black rounded-md"><div class="flex items-center relative text-gray-200 bg-gray-800 gizmo:dark:bg-token-surface-primary px-4 py-2 text-xs font-sans justify-between rounded-t-md"><span>${language || 'plain text'}</span><button class="flex ml-auto gizmo:ml-0 gap-2 items-center"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path><rect x="8" y="2" width="8" height="4" rx="1" ry="1"></rect></svg>Copy code</button></div><div class="p-4 overflow-y-auto"><code class="language-\${language}">${code.trim()}
// </code></div></div></pre>`;
//         });


      //
      // if(d.data.content[i].content_type && d.data.content[i].content_type === "image_asset_pointer"){
      //   let allPicsDiv = createAllPicsDiv();
      //   allPicsDiv.id = "allPicsDiv";
      //   realContentDiv.appendChild(allPicsDiv);
      //   while(d.data.content[i] && d.data.content[i].content_type === "image_asset_pointer"){
      //     // const regex = /file-[\w-]+/;
      //     // const matchedstring = d.data.content[i].asset_pointer.match(regex);
      //     const matchedstring = d.data.content[i].asset_pointer.slice(15);  // 从第16个字符开始截取到字符串结尾
      //
      //     if (matchedstring) {
      //       log(matchedstring);  // file-0rbJDxE2JZqAUM8R9Jz8eUOS
      //       let sourceURL = pictureURL.get(matchedstring) ;
      //       if (!sourceURL) {
      //         try {
      //           sourceURL = await fetchPictureURL(matchedstring);
      //           pictureURL.set(matchedstring, sourceURL);
      //         } catch (error) {
      //           console.error(error);
      //           return;  // 如果有错误,提前退出函数
      //         }
      //       }
      //
      //       let picDiv = createOnePic(sourceURL);
      //       let downloadBtn = createDownloadBtn();
      //       downloadBtn.dataset.sourceUrl = sourceURL;  // 在所有情况下都设置
      //       allPicsDiv.appendChild(picDiv);
      //       allPicsDiv.appendChild(downloadBtn);
      //     } else {
      //       log("No match found");
      //     }
      //     i++;
      //   }
      //   if ( i < d.data.content.length) {
      //     let contentSpan = document.createElement('div');
      //     contentSpan.className = 'content-text';
      //     contentSpan.innerHTML = d.data.content[i]; // 如果只需要纯文本
      //     realContentDiv.appendChild(contentSpan);
      //   }
      // }
      // if(d.data.content_type === "text"){
      //     let div = document.createElement('div');
      //       div.innerHTML = d.data.content[0];
      //   div.className = 'content-text';
      //   realContentDiv.appendChild(div);
      // }
      // else {
      //   for(let i = 0 ; i < d.data.content.length; i ++){
      //     if(d.data.content[i].content_type && d.data.content[i].content_type === "image_asset_pointer"){
      //       let allPicsDiv = createAllPicsDiv();
      //       allPicsDiv.id = "allPicsDiv";
      //       realContentDiv.appendChild(allPicsDiv);
      //       while(d.data.content[i] && d.data.content[i].content_type === "image_asset_pointer"){
      //         // const regex = /file-[\w-]+/;
      //         // const matchedstring = d.data.content[i].asset_pointer.match(regex);
      //         const matchedstring = d.data.content[i].asset_pointer.slice(15);  // 从第16个字符开始截取到字符串结尾
      //
      //         if (matchedstring) {
      //           log(matchedstring);  // file-0rbJDxE2JZqAUM8R9Jz8eUOS
      //           let sourceURL = pictureURL.get(matchedstring) ;
      //           if (!sourceURL) {
      //             try {
      //               sourceURL = await fetchPictureURL(matchedstring);
      //               pictureURL.set(matchedstring, sourceURL);
      //             } catch (error) {
      //               console.error(error);
      //               return;  // 如果有错误,提前退出函数
      //             }
      //           }
      //
      //           let picDiv = createOnePic(sourceURL);
      //           let downloadBtn = createDownloadBtn();
      //           downloadBtn.dataset.sourceUrl = sourceURL;  // 在所有情况下都设置
      //           allPicsDiv.appendChild(picDiv);
      //           allPicsDiv.appendChild(downloadBtn);
      //         } else {
      //           log("No match found");
      //         }
      //         i++;
      //       }
      //       if ( i < d.data.content.length) {
      //         let contentSpan = document.createElement('div');
      //         contentSpan.className = 'content-text';
      //         contentSpan.innerHTML = d.data.content[i]; // 如果只需要纯文本
      //         realContentDiv.appendChild(contentSpan);
      //       }
      //     }
      //     else{
      //       let contentSpan = document.createElement('div');
      //       contentSpan.className = 'content-text';
      //       contentSpan.innerHTML = d.data.content[i]; // 如果只需要纯文本
      //       realContentDiv.appendChild(contentSpan);
      //     }
      //   }
      // }

      targetDiv.appendChild(realContentDiv);
    },
    handleClick: function (e) {
      if (e.target.closest('button.flex.ml-auto.gizmo\\:ml-0.gap-2.items-center')) {
        var codeBlock = e.target.closest('.bg-black').querySelector('code').innerText;
        nodesInAndOutKit.copyToClipboard(codeBlock);
      }
    },
    copyToClipboard: function (text) {
      var textArea = document.createElement("textarea");
      textArea.value = text;
      document.body.appendChild(textArea);
      textArea.select();
      document.execCommand('Copy');
      textArea.remove();
      ButtonOperations.showUserNotification(translate("codeCopiedToClipboard"));
    },
    handleMouseOver: function (d) {
      curMouseOnUUID = d.data.uuid + "-window";
      d3.select(this).style("cursor", "pointer");
      d3.select(this).select("circle")
        .style("stroke", "#FFFFFF")
        .style("stroke-width", "3px")
        .style("cursor", "pointer");
      if (d.parent) {
        d3.selectAll(".node")
          .filter(function (data) {
            return data === d.parent;
          })
          .select("circle")
          .style("stroke", "cyan")
          .style("stroke-width", "3px");
      }
      const ancestors = d.ancestors();
      gLinks.selectAll(".link")
        .filter(link => ancestors.indexOf(link.target) > -1)
        .classed("highlighted", true);
      nodesInAndOutKit.highlightDescendantLinks(d);
      let windowId = d.data.uuid + "-window";
      if (!document.getElementById(windowId)) {

        temporaryWindowKit.createContentWindowForNode(d);
        temporaryWindowKit.positionContentWindow(d, event.pageX, event.pageY);
      }
    },
    handleMouseOut: function (d) {
      curMouseOnUUID = null;

      setTimeout(() => {
        if (curMouseOnUUID !== d.data.uuid + "-window") {
          let windowElem = document.getElementById(d.data.uuid + "-window");
          if (windowElem && windowElem.id === d.data.uuid + "-window") {
            windowElem.remove();
          }
        }
      }, 300);
      gLinks.selectAll(".link.highlighted")
        .classed("highlighted", false);
      gLinks.selectAll(".link.descendant-highlighted")
        .classed("descendant-highlighted", false);
      d3.select(this).select("circle")
        .style("stroke", null)
        .style("stroke-width", null);
      if (d.parent) {
        d3.selectAll(".node")
          .filter(function (data) {
            return data === d.parent;
          })
          .select("circle")
          .style("stroke", null)
          .style("stroke-width", null);
      }
    },
    onContextMenu: function (d) {
      log("右击了节点!");
      d3.event.stopPropagation()
      d3.event.preventDefault();
      const tag = d3.select(this).select(".emoji-tag");
      if (tag.attr("visibility") === "hidden") {
        tag.attr("visibility", "visible");
        conversationData.bookMarked.set(d.data.uuid, true);
        dbOperations.saveConversationsData(conversationData)
          .then(() => {
            log("已保存变化!",);
          })
          .catch(error => {
            console.error("Error saving data:", error);
          });

      } else {
        tag.attr("visibility", "hidden");
        conversationData.bookMarked.delete(d.data.uuid);
        dbOperations.saveConversationsData(conversationData)
          .then(() => {
          })
          .catch(error => {
            console.error("Error saving data:", error);
          });
      }
    },
  }

  //temporaryWindowKit
  const temporaryWindowKit = {

    log(category, ...message) {
      // 使用 Logger 实例的 log 方法
      //console.log("moduleLog called.");
      this.logger.log(category, ...message);
    },
    initTemporaryWindowKit: function () {

      this.logger = new Logger('temporaryWindowKit');
      //this.log(LogCategories.SUCCESS, 'temporaryWindowKitLoggerCreated!');
    },
    positionContentWindow: function (d, x, y) {
      let windowElem = document.getElementById(d.data.uuid + "-window");
      if (windowElem) {
        windowElem.style.top = (y + 15) + 'px';
        windowElem.style.left = (x + 20) + 'px';
      }
    },
    createContentWindowForNode: function (d) {
      let elements = document.querySelectorAll('.temporalityContentLayoutDiv');
      elements.forEach(element => {
        element.remove();
      });
      let windowElem = document.createElement('div');
      windowElem.className = "temporalityContentLayoutDiv";
      windowElem.id = d.data.uuid + "-window";
      windowElem.style = `position: absolute; width: 600px; padding: 20px; font-size: 16px; lineHeight: 1.6; border: 1px solid #ccc; borderRadius: 5px; boxShadow: 0px 0px 10px rgba(0,0,0,0.2); overflow: auto; opacity: 0.8; max-Height: 500px;`;
      //fontFamily: Arial, sans-serif;
      nodesInAndOutKit.updateSelectedNodeContentDiv(d, windowElem);
      const dark = isDark();
      windowElem.classList.add('markdown', 'prose', 'w-full', 'break-words', 'dark:prose-invert', dark ? "dark" : "light");

      windowElem.style.backgroundColor = dark ?
        d.data.type === "chatGPT" ? 'rgb(68,70,84)' : 'rgb(51,53,65)' :
        d.data.type === "chatGPT" ? 'rgb(247,247,248)' : 'rgb(256,256,256)';
      windowElem.style.boxShadow = dark ? '0px 4px 20px rgba(255, 255, 255, 0.1)' : '0px 4px 20px rgba(0, 0, 0, 0.1)';
      windowElem.addEventListener('click', nodesInAndOutKit.handleClick);
      document.body.appendChild(windowElem);
      windowElem.addEventListener('mouseenter', temporaryWindowKit.windowEnter);
      windowElem.addEventListener('mouseleave', temporaryWindowKit.windowLeave);
    },
    windowEnter: function (e) {
      curMouseOnUUID = e.target.id;
    },
    windowLeave: function (e) {
      curMouseOnUUID = null;
      setTimeout(() => {
        if (curMouseOnUUID !== e.target.id) {
          e.target.remove();
        }
      }, 300);
    },
  }
  temporaryWindowKit.initTemporaryWindowKit();

  //---ContentKit---//
  const contentDiv = document.createElement('div');
  const selectedNodeContent = document.createElement('div');
  const contentHeader = document.createElement('div');
  let goToNodeButton;//注意gotoNode的逻辑!
  const talkingPerson = document.createElement('div');
  const commentForm = document.createElement('div');
  const commentLabel = document.createElement('label');
  const commentTextarea = document.createElement('textarea');

  let submitButton = document.createElement('button');
  let cancelButton = document.createElement('button');
  let clearButton = document.createElement('button');

  function updateStylesBasedOnTheme() {
    const dark = isDark()
    contentDiv.style.backgroundColor = dark ?
      'rgb(68,70,84)' : 'rgb(256,256,256)';
    contentDiv.style.boxShadow = dark ? '0px 4px 20px rgba(255, 255, 255, 0.1)' : '0px 4px 20px rgba(0, 0, 0, 0.1)';
    const links = gLinks.selectAll(".link");

    if (dark) {
      links
        .attr("stroke", "white")
    } else {
      links
        .attr("stroke", "black")
    }

    commentTextarea.style.background = dark ?
      'rgb(68,70,84)' : 'rgb(256,256,256)';
  }

  updateStylesBasedOnTheme();

  const HTMLClassObserver = new MutationObserver(mutations => {
    for (let mutation of mutations) {
      if (mutation.attributeName === 'class') {
        updateStylesBasedOnTheme();
      }
    }
  });
  HTMLClassObserver.observe(document.documentElement, {attributes: true});

  let isRightMouseDown = false;
  let isLeftMouseDown = false;
  let initialMouseX, initialMouseY, initialDivX, initialDivY;

  const ContentKit = {
    init: function () {
      this.createContentDivs();
      this.addEventListeners();
    },

    createContentDivs: function () {
      selectedNodeContent.id = 'nodeContent';
      contentHeader.id = 'contentHeader';

      contentHeader.style.fontFamily = 'Times New Roman';
      contentHeader.textContent = translate("nodeDetails");
      contentHeader.style.fontSize = '20px';
      contentHeader.style.fontWeight = 'bold';
      contentHeader.style.marginBottom = '10px';
      contentHeader.style.display = 'flex';
      contentHeader.style.justifyContent = 'space-between';
      contentHeader.style.alignItems = 'center';

      const htmlClass = document.documentElement.getAttribute('class');

      contentDiv.style = `position :fixed; top: 10px; right: 10px; width: 400px; padding: 10px; border :8px solid #ddd; display :none; lineHeight :1.6; overflow :hidden; userSelect: text; font-size: 14px;`;
      contentDiv.id = 'contentDiv';
      contentDiv.style.borderRadius = '8px';
      let windowHeight = window.innerHeight;
      contentDiv.style.height = windowHeight - 20 + 'px';

      selectedNodeContent.style.paddingTop = '10px';
      selectedNodeContent.style.paddingRight = '10px';
      selectedNodeContent.style.paddingBottom = '10px';
      selectedNodeContent.style.paddingLeft = '10px';
      selectedNodeContent.style.userSelect = 'text';
      selectedNodeContent.style.overflow = 'auto';
      //selectedNodeContent.classList.add('markdown', 'prose', 'w-full', 'break-words', 'dark:prose-invert', htmlClass);
      selectedNodeContent.classList.add('markdown', 'prose', 'w-full', 'break-words', 'dark:prose-invert');

      let copyButton = ButtonCreator.createButton({
        id: 'copyButton',
        innerHTML: '📋',
        eventListeners: [
          {
            type: 'click', handler: () => {
              ContentKit.copyToClipboard(selectedNodeContent.innerHTML.replace(/<[^>]*>/g, ' '));
              ButtonOperations.showUserNotification(translate("contentCopied"));
            }
          },
        ],
        additionalStyles: {
          border: 'none',
          cursor: 'pointer',
          fontSize: '20px',
          position: 'absolute',
          top: '6px',
          right: '150px',
          background: 'none',
        },
      });

      goToNodeButton = ButtonCreator.createButton({
        id: 'goToNodeButton',
        text: '🚩',
        eventListeners: [
          {type: 'click', handler: () => DOMOperations.jumpToDialogueItem(contentDiv.dataset.curDisplay)},
        ],
        additionalStyles: {
          border: 'none',
          cursor: 'pointer',
          fontSize: '18px',
          position: 'absolute',
          top: '7px',
          right: '90px',
          background: 'none',
        },
      });

      let commentButton = ButtonCreator.createButton({
        id: 'commentButton',
        text: '🖊',
        eventListeners: [
          {type: 'click', handler: this.commentToSave},
        ],
        additionalStyles: {
          border: 'none',
          cursor: 'pointer',
          fontSize: '18px',
          position: 'absolute',
          top: '8px',
          right: '210px',
          background: 'none',
        },
      });

      talkingPerson.id = 'talkingPerson';
      talkingPerson.style.position = 'absolute';
      talkingPerson.style.top = '8px';
      talkingPerson.style.right = '240px';
      talkingPerson.style.background = 'none';


      let closeButton = ButtonCreator.createButton({
        id: 'closeButton',
        innerHTML: '<svg stroke="black" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" height="20" width="20" xmlns="http://www.w3.org/2000/svg"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>',
        eventListeners: [
          {
            type: 'click', handler: () => {
              contentDiv.style.display = 'none';
              if (commentForm.style.display === 'block')
                commentForm.style.display = 'none';
            }
          }
        ],
        additionalStyles: {
          border: 'none',
          cursor: 'pointer',
          fontSize: '30px',
          position: 'absolute',
          top: '10px',
          right: '40px',
          backgroundColor: 'white',
        },
      });
      commentForm.id = 'commentForm';
      commentForm.style.position = 'fixed';
      ContentKit.positionCommentFormRelativeToContentDiv();
      commentForm.style.display = 'none';


      commentLabel.setAttribute('for', 'comment');
      commentLabel.innerText = translate("enterComment") + ":";

      commentTextarea.id = 'commentText';
      commentTextarea.rows = '5';

      cancelButton.className = 'commentHoverEffect';
      clearButton.className = 'commentHoverEffect';

      submitButton.id = 'submitComment';
      submitButton.innerText = translate("userCommentSave");
      submitButton.className = 'commentHoverEffect';


      commentForm.appendChild(commentLabel);
      commentForm.appendChild(commentTextarea);
      commentForm.appendChild(submitButton);
      commentForm.appendChild(cancelButton);
      commentForm.appendChild(clearButton);
      document.body.appendChild(commentForm);

      contentHeader.appendChild(copyButton);
      contentHeader.appendChild(goToNodeButton);
      contentHeader.appendChild(closeButton);
      contentHeader.appendChild(commentButton);
      contentHeader.appendChild(talkingPerson);
      contentDiv.appendChild(selectedNodeContent);
      contentDiv.insertBefore(contentHeader, selectedNodeContent);
      document.body.appendChild(contentDiv);

    },
    positionCommentFormRelativeToContentDiv: function () {
      const rect = contentDiv.getBoundingClientRect();
      commentForm.style.position = 'fixed';
      commentForm.style.left = (rect.left - 300) + 'px';
      commentForm.style.top = rect.top + 'px';
      const commentRect = commentForm.getBoundingClientRect();
    },

    addEventListeners: function () {

      talkingPerson.addEventListener('mouseenter', function (e) {
        talkingPerson.style.cursor = 'grab';
      });
      talkingPerson.addEventListener('mousedown', function (e) {
        if (e.button === 0) {
          e.preventDefault();
          e.stopPropagation();
          isLeftMouseDown = true;

          initialMouseX = e.clientX;
          initialMouseY = e.clientY;
          initialDivX = contentDiv.offsetLeft;
          initialDivY = contentDiv.offsetTop;
          let rect = contentDiv.getBoundingClientRect();
          offsetX = e.clientX - rect.left;
          offsetY = e.clientY - rect.top;
          document.body.style.cursor = 'grabbing';
          document.addEventListener('mousemove', moveContentDiv);
        }
      });
      contentDiv.addEventListener('mousedown', function (e) {
        if (e.button === 2) {
          e.preventDefault();
          isRightMouseDown = true;

          initialMouseX = e.clientX;
          initialMouseY = e.clientY;
          initialDivX = contentDiv.offsetLeft;
          initialDivY = contentDiv.offsetTop;
          let rect = contentDiv.getBoundingClientRect();
          offsetX = e.clientX - rect.left;
          offsetY = e.clientY - rect.top;
          document.body.style.cursor = 'move';
          document.addEventListener('mousemove', moveContentDiv);
        }
      });

      document.addEventListener('mouseup', function (e) {
        if (isRightMouseDown && e.button === 2 || isLeftMouseDown && e.button === 0) {
          e.preventDefault();
          isRightMouseDown = false;
          document.body.style.cursor = '';
          talkingPerson.style.cursor = 'grab';

          document.removeEventListener('mousemove', moveContentDiv);
        }
      });

      contentDiv.addEventListener('contextmenu', function (e) {
        e.preventDefault();

      });
      // onMouseDown: function (e) {
      //   if (e.button !== 0) return;
      //   let rect = treeMainBtn.getBoundingClientRect();
      //   offsetX = e.clientX - rect.left;
      //   offsetY = e.clientY - rect.top;
      //   isDragging = true;
      //   window.addEventListener("mousemove", ButtonOperations.onMouseMove);
      //   window.addEventListener("mouseup", ButtonOperations.onMouseUp);
      // },
      function moveContentDiv(e) {
        if (isRightMouseDown || isLeftMouseDown) {
          ButtonOperations.hideMenu();
          let top = e.clientY - offsetY;
          let left = e.clientX - offsetX;
          let maxWidth = window.innerWidth;
          let maxHeight = window.innerHeight;
          let elementWidth = contentDiv.offsetWidth;
          let elementHeight = contentDiv.offsetHeight;
          if (left < 0) {
            left = 0;
          } else if (left > maxWidth - elementWidth) {
            left = maxWidth - elementWidth;
          }
          if (top < 0) {
            top = 0;
          } else if (top > maxHeight - elementHeight) {
            top = maxHeight - elementHeight;
          }
          contentDiv.style.left = left + "px";
          contentDiv.style.top = top + "px";
          contentDiv.style.right = "auto";
          contentDiv.style.bottom = "auto";

          document.body.style.cursor = isRightMouseDown ? 'move' : 'grabbing';
          talkingPerson.style.cursor = 'grabbing';

          ContentKit.positionCommentFormRelativeToContentDiv();
        }
      }

      contentDiv.addEventListener('mousemove', ContentKit.handleContentDivMouseMove);
      contentDiv.addEventListener('mousedown', ContentKit.handleContentDivMouseDown);
      contentDiv.onmouseleave = function () {
        contentDiv.style.cursor = 'null';
      };
    },
    handleContentDivMouseMove: function (e) {
      var borderWidth = 8;
      var oBoxW = contentDiv.offsetWidth - borderWidth;
      var oBoxH = contentDiv.offsetHeight - borderWidth;
      var x = e.clientX - contentDiv.getBoundingClientRect().left;
      var y = e.clientY - contentDiv.getBoundingClientRect().top;

      contentDiv.style.cursor = '';

      if (x < borderWidth) {
        contentDiv.style.cursor = 'w-resize';
      } else if (x > oBoxW) {
        contentDiv.style.cursor = 'e-resize';
      }

      if (y < borderWidth) {
        contentDiv.style.cursor = 'n-resize';
      } else if (y > oBoxH) {
        contentDiv.style.cursor = 's-resize';
      }

      if (x < borderWidth && y < borderWidth) {
        contentDiv.style.cursor = 'nw-resize';
      } else if (x > oBoxW && y < borderWidth) {
        contentDiv.style.cursor = 'ne-resize';
      } else if (x < borderWidth && y > oBoxH) {
        contentDiv.style.cursor = 'sw-resize';
      } else if (x > oBoxW && y > oBoxH) {
        contentDiv.style.cursor = 'se-resize';
      }
    },
    handleContentDivMouseDown: function (e) {
      var borderWidth = 8;
      e = e || event;
      var d = null;
      var oBoxL = contentDiv.offsetLeft;
      var oBoxT = contentDiv.offsetTop;
      var oBoxW = contentDiv.offsetWidth - borderWidth;
      var oBoxH = contentDiv.offsetHeight - borderWidth;

      var initialWidth = contentDiv.offsetWidth;
      var initialHeight = contentDiv.offsetHeight;

      var x = e.clientX - oBoxL;
      var y = e.clientY - oBoxT;
      var directions = {
        top: y < borderWidth,
        bottom: y > oBoxH,
        left: x < borderWidth,
        right: x > oBoxW
      };

      var corners = [
        {corner: 'LT', conditions: ['left', 'top']},
        {corner: 'LB', conditions: ['left', 'bottom']},
        {corner: 'RT', conditions: ['right', 'top']},
        {corner: 'RB', conditions: ['right', 'bottom']}
      ];

      var d = Object.keys(directions).find(key => directions[key]) || null;

      corners.forEach(corner => {
        if (corner.conditions.every(condition => directions[condition])) {
          d = corner.corner;
        }
      });
      if (!d) {
        return;
      }


      var startX = e.clientX;
      var startY = e.clientY;

      function handleMouseMove(e) {
        e = e || event;
        var dx = e.clientX - startX;
        var dy = e.clientY - startY;
        const cursors = {
          'left': 'ew-resize',
          'right': 'ew-resize',
          'top': 'ns-resize',
          'bottom': 'ns-resize',
          'LB': 'nesw-resize',
          'LT': 'nwse-resize',
          'RB': 'nwse-resize',
          'RT': 'nesw-resize'
        };

        const MIN_WIDTH = 400;
        const MIN_HEIGHT = 200;

        function updateStyle(d, dx, dy) {
          let newWidth, newHeight;

          switch (d) {
            case 'left':
            case 'LB':
            case 'LT':
              newWidth = initialWidth - dx;
              if (d !== 'left') {
                newHeight = d === 'LB' ? initialHeight + dy : initialHeight - dy;
              }
              break;
            case 'right':
            case 'RB':
            case 'RT':
              newWidth = initialWidth + dx;
              if (d !== 'right') {
                newHeight = d === 'RB' ? initialHeight + dy : initialHeight - dy;
              }
              break;
            case 'top':
            case 'bottom':
              newHeight = d === 'top' ? initialHeight - dy : initialHeight + dy;
              break;
          }

          if (newWidth >= MIN_WIDTH) {
            contentDiv.style.width = newWidth + 'px';
            if (['left', 'LB', 'LT'].includes(d)) {
              contentDiv.style.left = (oBoxL + dx) + 'px';
            }
          }

          if (newHeight >= MIN_HEIGHT) {
            contentDiv.style.height = newHeight + 'px';
            selectedNodeContent.style.height = (newHeight - 60) + 'px';
            if (['top', 'LT', 'RT'].includes(d)) {
              contentDiv.style.top = (oBoxT + dy) + 'px';
            }
          }
          //log("contentDiv:",contentDiv);
          let imagesBtns = contentDiv.querySelectorAll('.m-1');
          //log("images:",images);
          let contentWidth = contentDiv.offsetWidth;

          imagesBtns.forEach(btn => {
            btn.style.width = `${contentWidth / 2 - 60}px`;
            btn.style.height = `${contentWidth / 2 - 60}px`;
          });
          document.body.style.cursor = cursors[d];
        }

        switch (d) {
          case 'left':
          case 'right':
          case 'top':
          case 'bottom':
          case 'LB':
          case 'LT':
          case 'RB':
          case 'RT':
            updateStyle(d, dx, dy);
            break;
        }
        if (commentForm.style.display === 'block') {
          ContentKit.positionCommentFormRelativeToContentDiv();
        }
        return false;
      }

      function handleMouseUp() {
        log("mouseup!");
        document.body.style.cursor = '';
        contentDiv.style.cursor = '';
        document.removeEventListener('mousemove', handleMouseMove);
        document.removeEventListener('mouseup', handleMouseUp);
      }

      document.addEventListener('mousemove', handleMouseMove);
      document.addEventListener('mouseup', handleMouseUp);

      if (e.preventDefault) {
        e.preventDefault();
      }
    },
    copyToClipboard: function (text) {
      const textArea = document.createElement("textarea");
      textArea.value = text;
      document.body.appendChild(textArea);
      textArea.select();
      document.execCommand("copy");
      document.body.removeChild(textArea);
    },
    commentToSave: function (d) {
      let curUUID = contentDiv.dataset.curDisplay;
      log("curUUID:", curUUID);
      if (commentForm.style.display !== 'block') {
        commentForm.style.display = 'block';
      } else {
        commentForm.style.display = 'none';
        return;
      }
      ContentKit.positionCommentFormRelativeToContentDiv();
      let selectedNode = gNodes.selectAll(".node")
        .filter(function (d) {
          return d.data.uuid === curUUID;
        });
      log("commentForm Node:", selectedNode);
      const existingComment = selectedNode.data.comment;

      if (existingComment && existingComment !== '') {
        commentTextarea.value = existingComment;
      }

      if (!cancelButton.hasListener) {
        cancelButton.innerText = translate("userCommentCancel");
        cancelButton.addEventListener('click', function () {
          commentForm.style.display = 'none';
        });
        cancelButton.hasListener = true;
      }

      if (!clearButton.hasListener) {
        clearButton.innerText = translate("userCommentClear");
        clearButton.addEventListener('click', function () {
          commentTextarea.value = '';
        });
        clearButton.hasListener = true;
      }

      if (!submitButton.hasListener) {
        log("curUUID:", curUUID);
        commentForm.style.display = 'block';
        submitButton.addEventListener('click', function () {
          let commentValue = commentTextarea.value;
          let curUUID = contentDiv.dataset.curDisplay;
          let selectedNode = gNodes.selectAll(".node")
            .filter(function (d) {
              return d.data.uuid === curUUID;
            });
          let selectedNodeData;
          selectedNodeData = selectedNode.data()[0];
          let selectedMapNode;
          selectedMapNode = conversationData.uuid2nodeMap.get(curUUID);
          if (commentValue.trim() === '') {
            if (confirm(translate("emptyCommentPrompt"))) {
              ButtonOperations.showUserNotification(translate("commentSetToEmpty"));
              selectedNodeData.data.comment = '';
              conversationData.commentMap.delete(selectedMapNode.uuid);
              log("renew selectedNodeData:", selectedNodeData);
              //log("selectedNodeData.comment:", selectedNodeData.data.comment);
              selectedMapNode.comment = '';
              const existingComment = "";

              if (existingComment && existingComment !== '') {
                commentTextarea.value = existingComment;
              }
              dbOperations.saveConversationsData(conversationData)
                .then(() => {
                })
                .catch(error => {
                  console.error("Error saving data:", error);
                });
              log("newData", conversationData);
              commentForm.style.display = 'none';
              nodesInAndOutKit.updateSelectedNodeContentDiv(selectedNodeData, selectedNodeContent);
            } else {
              ButtonOperations.showUserNotification(translate("enterCommentFirst"), "alert");
            }
          } else {
            ButtonOperations.showUserNotification(translate("commentSaved"));
            try {
              selectedNodeData.data.comment = commentValue;
              conversationData.commentMap.set(selectedNodeData.data.uuid, commentValue);
              log("conversationData:", conversationData);
              log("renew selectedNodeData:", selectedNodeData);
              log("selectedNodeData.comment:", selectedNodeData.data.comment);
              //selectedMapNode.comment = commentValue;
              const existingComment = selectedNode.data.comment;

              if (existingComment && existingComment !== '') {
                commentTextarea.value = existingComment;
              }

              dbOperations.saveConversationsData(conversationData)
                .then(() => {
                  nodesInAndOutKit.updateSelectedNodeContentDiv(selectedNodeData, selectedNodeContent);
                })
                .catch(error => {
                  console.error("Error saving data:", error);
                });
              commentForm.style.display = 'none';
            } catch (error) {
              //log("error:", error);
            }
          }
        });
        submitButton.hasListener = true;
      }
    },
  }
  //___ContentKit___//

  //---settingsKit---//
  let settingsDiv = document.createElement('div');
  let zoomInButton = document.createElement('div');
  let zoomOutButton = document.createElement('div');
  let refreshTreeButton = document.createElement('div');
  let thumbNailButton = document.createElement('div');
  let undoButton = document.createElement('div');
  let redoButton = document.createElement('div');
  let deleteDiv = document.createElement('div');
  let feedbackDiv = document.createElement('div');
  let settingsContainer = document.createElement('div');
  let rightMiddleContainer = document.createElement('div');
  let colorSelectDiv = document.createElement('div');
  let languageSelectDiv = document.createElement('div');
  let WeChatDiv = document.createElement('div');
  let TencentDiv = document.createElement('div');
  const WeChatPicDiv = document.createElement('div');
  const TencentPicDiv = document.createElement('div');
  const feedBackPicDiv = document.createElement('div');
  let rightMiddleMenu = document.createElement("div");
  let toggleMainBtnMovingAccessbility = document.createElement("div");


  let languageContainer = document.createElement('div');
  let languageSelect = document.createElement('select');


  let scaleIncrementSmall = 0.1;
  let scaleIncrementLarge = 0.3;
  let centerX = window.innerWidth / 2;
  let centerY = window.innerHeight / 3;


  let undoStack = [];
  let redoStack = [];
  let newOperation = {};


  let redForEnterable = `
      <svg viewBox="-3 -3 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="si-glyph si-glyph-tree" fill="#000000" stroke="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title>929</title> <defs> </defs> <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <path d="M14.779,12.18 L11.795,8.501 C11.795,8.501 13.396,8.937 13.57,8.937 C14.035,8.937 13.765,8.42 13.57,8.223 L11.185,5.192 C11.185,5.192 12.333,4.918 12.75,4.918 C13.168,4.918 12.947,4.401 12.75,4.204 L9.4,0.061 C9.203,-0.136 8.883,-0.136 8.686,0.061 L5.291,4.161 C5.093,4.358 4.805,4.876 5.291,4.876 C5.777,4.876 6.913,5.192 6.913,5.192 L4.325,8.079 C4.127,8.276 3.768,8.793 4.325,8.793 C4.644,8.793 6.275,8.502 6.275,8.502 L3.317,12.189 C3.12,12.385 2.76,12.903 3.317,12.903 C3.874,12.903 8.008,11.896 8.008,11.896 L8.008,14.941 C8.008,15.478 8.444,15.914 8.983,15.914 C9.52,15.914 9.998,15.478 9.998,14.941 L9.998,11.896 C9.998,11.896 14.373,12.895 14.778,12.895 C15.183,12.895 14.976,12.376 14.779,12.18 L14.779,12.18 Z" fill="#ff6600" class="si-glyph-fill"> </path> </g> </g></svg>
`;
  let greenForNotEnter = `<svg viewBox="-3 -3 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="si-glyph si-glyph-tree" fill="#000000" stroke="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <title>929</title> <defs> </defs> <g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <path d="M14.779,12.18 L11.795,8.501 C11.795,8.501 13.396,8.937 13.57,8.937 C14.035,8.937 13.765,8.42 13.57,8.223 L11.185,5.192 C11.185,5.192 12.333,4.918 12.75,4.918 C13.168,4.918 12.947,4.401 12.75,4.204 L9.4,0.061 C9.203,-0.136 8.883,-0.136 8.686,0.061 L5.291,4.161 C5.093,4.358 4.805,4.876 5.291,4.876 C5.777,4.876 6.913,5.192 6.913,5.192 L4.325,8.079 C4.127,8.276 3.768,8.793 4.325,8.793 C4.644,8.793 6.275,8.502 6.275,8.502 L3.317,12.189 C3.12,12.385 2.76,12.903 3.317,12.903 C3.874,12.903 8.008,11.896 8.008,11.896 L8.008,14.941 C8.008,15.478 8.444,15.914 8.983,15.914 C9.52,15.914 9.998,15.478 9.998,14.941 L9.998,11.896 C9.998,11.896 14.373,12.895 14.778,12.895 C15.183,12.895 14.976,12.376 14.779,12.18 L14.779,12.18 Z" fill="#08aa13" class="si-glyph-fill"> </path> </g> </g></svg>`;

  const settingsKit = {
    init: function () {
      this.createSettingDivs();
      this.addEventListeners();
    },

    createSettingDivs: function () {
      settingsDiv.id = "settingsDiv";
      settingsDiv.style.fontSize = '24px';

      settingsContainer.appendChild(settingsDiv);
      settingsContainer.style.display = 'none';
      rightMiddleContainer.style.display = 'none';

      const settingSVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="30" height="30"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path fill="rgb(217, 74, 171)" d="M487.4 315.7l-42.6-24.6c4.3-23.2 4.3-47 0-70.2l42.6-24.6c4.9-2.8 7.1-8.6 5.5-14-11.1-35.6-30-67.8-54.7-94.6-3.8-4.1-10-5.1-14.8-2.3L380.8 110c-17.9-15.4-38.5-27.3-60.8-35.1V25.8c0-5.6-3.9-10.5-9.4-11.7-36.7-8.2-74.3-7.8-109.2 0-5.5 1.2-9.4 6.1-9.4 11.7V75c-22.2 7.9-42.8 19.8-60.8 35.1L88.7 85.5c-4.9-2.8-11-1.9-14.8 2.3-24.7 26.7-43.6 58.9-54.7 94.6-1.7 5.4.6 11.2 5.5 14L67.3 221c-4.3 23.2-4.3 47 0 70.2l-42.6 24.6c-4.9 2.8-7.1 8.6-5.5 14 11.1 35.6 30 67.8 54.7 94.6 3.8 4.1 10 5.1 14.8 2.3l42.6-24.6c17.9 15.4 38.5 27.3 60.8 35.1v49.2c0 5.6 3.9 10.5 9.4 11.7 36.7 8.2 74.3 7.8 109.2 0 5.5-1.2 9.4-6.1 9.4-11.7v-49.2c22.2-7.9 42.8-19.8 60.8-35.1l42.6 24.6c4.9 2.8 11 1.9 14.8-2.3 24.7-26.7 43.6-58.9 54.7-94.6 1.5-5.5-.7-11.3-5.6-14.1zM256 336c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80z"/></svg>';
      settingsDiv.innerHTML = settingSVG;

      zoomInButton.className = "actionDiv";
      zoomInButton.id = 'plusDiv';
      zoomOutButton.className = "actionDiv";
      zoomOutButton.id = 'minusDiv';
      zoomInButton.innerText = '➕';
      zoomOutButton.innerText = '➖';
      settingsContainer.appendChild(zoomInButton);
      settingsContainer.appendChild(zoomOutButton);


      thumbNailButton.id = 'thumbNailDiv';
      thumbNailButton.className = "actionDiv";
      thumbNailButton.innerText = '🌐';
      settingsContainer.appendChild(thumbNailButton);

      refreshTreeButton.id = 'refreshTree';
      refreshTreeButton.className = "actionDiv";
      refreshTreeButton.innerText = '🔄️';
      settingsContainer.appendChild(refreshTreeButton);

      undoButton.id = 'undoDiv';
      undoButton.className = "actionDiv";
      undoButton.innerText = '⏪';
      settingsContainer.appendChild(undoButton);

      redoButton.id = 'redoDiv';
      redoButton.className = "actionDiv";
      redoButton.innerText = '⏩';
      settingsContainer.appendChild(redoButton);


      deleteDiv.id = 'deleteDiv';
      deleteDiv.className = "actionDiv";
      deleteDiv.innerText = '🗑️';
      settingsContainer.appendChild(deleteDiv);

      feedbackDiv.id = 'feedbackDiv';
      feedbackDiv.className = "rightMiddleDiv";
      feedbackDiv.innerText = '✉️';
      rightMiddleContainer.appendChild(feedbackDiv);

      rightMiddleMenu.style.display = 'none';
      rightMiddleMenu.id = 'rightMiddleMenu';
      rightMiddleContainer.appendChild(rightMiddleMenu);

      colorSelectDiv.id = "colorSelectDiv";
      colorSelectDiv.className = "rightMiddleDiv";
      colorSelectDiv.innerText = '🎨';
      rightMiddleContainer.appendChild(colorSelectDiv);

      languageSelectDiv.id = "languageSelectDiv";
      languageSelectDiv.className = "rightMiddleDiv";
      languageSelectDiv.innerText = globalUserLang;
      languageSelectDiv.style.fontSize = '12px';

      rightMiddleContainer.appendChild(languageSelectDiv);

      WeChatDiv.id = "WeChatDiv";
      WeChatDiv.className = "rightMiddleDiv";
      WeChatDiv.innerHTML =
        `
      <svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" viewBox="-7.582815 -10.290675 65.71773 61.74405"><defs><linearGradient gradientUnits="userSpaceOnUse" gradientTransform="scale(1.06228 .94137)" id="a" y2=".1504" x2="17.2422" y1="32.4312" x1="17.2422"><stop offset="0%" stop-color="#78D431"/><stop offset="100%" stop-color="#9EEE69"/><stop offset="100%" stop-color="#9EEE69"/></linearGradient><linearGradient gradientUnits="userSpaceOnUse" gradientTransform="scale(1.05667 .94637)" id="b" y2="14.6966" x2="33.4727" y1="41.634" x1="33.4727"><stop offset="0%" stop-color="#E4E6E6"/><stop offset="100%" stop-color="#F5F5FF"/></linearGradient></defs><g fill="none"><path fill="url(#a)" d="M0 15.3063c0 4.5919 2.4846 8.787 6.3245 11.5648.3388.2267.5082.5669.5082 1.0204 0 .1134-.0565.2835-.0565.3968-.2823 1.1338-.7906 3.0046-.847 3.0613-.0565.17-.113.2834-.113.4535 0 .3402.2824.6236.6212.6236.113 0 .2259-.0567.3388-.1134l4.0093-2.3243c.2823-.17.6211-.2834.96-.2834.1693 0 .3952 0 .5646.0567 1.8635.5669 3.8963.8503 5.9857.8503 10.1078 0 18.2957-6.8595 18.2957-15.3063S28.4035 0 18.2958 0C8.1879 0 0 6.8595 0 15.3063"/><path fill="url(#b)" d="M35.3424 39.6205c1.7463 0 3.4363-.2284 4.9572-.6854.1127-.057.2817-.057.4507-.057.2817 0 .5633.1142.7887.2284l3.3236 1.942c.1126.057.169.1142.2816.1142.2817 0 .507-.2285.507-.514 0-.1143-.0563-.2285-.0563-.3999 0-.0571-.4507-1.5992-.676-2.5702-.0563-.1142-.0563-.2285-.0563-.3427 0-.3427.169-.6283.4506-.8568 3.211-2.3417 5.239-5.8258 5.239-9.7097 0-7.0824-6.8163-12.851-15.2098-12.851s-15.2097 5.7115-15.2097 12.851c0 7.0824 6.8162 12.8511 15.2097 12.8511z"/><path fill="#187E28" d="M14.5484 10.3647c0 1.3223-1.0389 2.369-2.3512 2.369-1.3124 0-2.3513-1.0467-2.3513-2.369 0-1.3223 1.039-2.369 2.3513-2.369 1.3123 0 2.3512 1.0467 2.3512 2.369m12.1972 0c0 1.3223-1.039 2.369-2.3513 2.369-1.3123 0-2.3512-1.0467-2.3512-2.369 0-1.3223 1.039-2.369 2.3512-2.369 1.3124 0 2.3513 1.0467 2.3513 2.369"/><path fill="#858C8C" d="M38.502 22.8023c0 1.1517.9143 2.073 2.0573 2.073 1.143 0 2.0573-.9213 2.0573-2.073 0-1.1516-.9144-2.0729-2.0573-2.0729-1.143 0-2.0574.9213-2.0574 2.073m-10.1398 0c0 1.1516.9144 2.0729 2.0573 2.0729 1.143 0 2.0574-.9213 2.0574-2.073 0-1.1516-.9144-2.0729-2.0574-2.0729-1.143 0-2.0573.9213-2.0573 2.073"/></g></svg>
      `
      ;
      rightMiddleContainer.appendChild(WeChatDiv);

      TencentDiv.id = "TencentDiv";
      TencentDiv.className = "rightMiddleDiv";
      TencentDiv.innerHTML =
        `
        <svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" viewBox="-18.15 -35.9725 157.3 215.835"><path fill="#faab07" d="M60.503 142.237c-12.533 0-24.038-4.195-31.445-10.46-3.762 1.124-8.574 2.932-11.61 5.175-2.6 1.918-2.275 3.874-1.807 4.663 2.056 3.47 35.273 2.216 44.862 1.136zm0 0c12.535 0 24.039-4.195 31.447-10.46 3.76 1.124 8.573 2.932 11.61 5.175 2.598 1.918 2.274 3.874 1.805 4.663-2.056 3.47-35.272 2.216-44.862 1.136zm0 0"/><path d="M60.576 67.119c20.698-.14 37.286-4.147 42.907-5.683 1.34-.367 2.056-1.024 2.056-1.024.005-.189.085-3.37.085-5.01C105.624 27.768 92.58.001 60.5 0 28.42.001 15.375 27.769 15.375 55.401c0 1.642.08 4.822.086 5.01 0 0 .583.615 1.65.913 5.19 1.444 22.09 5.65 43.312 5.795zm56.245 23.02c-1.283-4.129-3.034-8.944-4.808-13.568 0 0-1.02-.126-1.537.023-15.913 4.623-35.202 7.57-49.9 7.392h-.153c-14.616.175-33.774-2.737-49.634-7.315-.606-.175-1.802-.1-1.802-.1-1.774 4.624-3.525 9.44-4.808 13.568-6.119 19.69-4.136 27.838-2.627 28.02 3.239.392 12.606-14.821 12.606-14.821 0 15.459 13.957 39.195 45.918 39.413h.848c31.96-.218 45.917-23.954 45.917-39.413 0 0 9.368 15.213 12.607 14.822 1.508-.183 3.491-8.332-2.627-28.021"/><path fill="#fff" d="M49.085 40.824c-4.352.197-8.07-4.76-8.304-11.063-.236-6.305 3.098-11.576 7.45-11.773 4.347-.195 8.064 4.76 8.3 11.065.238 6.306-3.097 11.577-7.446 11.771m31.133-11.063c-.233 6.302-3.951 11.26-8.303 11.063-4.35-.195-7.684-5.465-7.446-11.77.236-6.305 3.952-11.26 8.3-11.066 4.352.197 7.686 5.468 7.449 11.773"/><path fill="#faab07" d="M87.952 49.725C86.79 47.15 75.077 44.28 60.578 44.28h-.156c-14.5 0-26.212 2.87-27.375 5.446a.863.863 0 00-.085.367.88.88 0 00.16.496c.98 1.427 13.985 8.487 27.3 8.487h.156c13.314 0 26.319-7.058 27.299-8.487a.873.873 0 00.16-.498.856.856 0 00-.085-.365"/><path d="M54.434 29.854c.199 2.49-1.167 4.702-3.046 4.943-1.883.242-3.568-1.58-3.768-4.07-.197-2.492 1.167-4.704 3.043-4.944 1.886-.244 3.574 1.58 3.771 4.07m11.956.833c.385-.689 3.004-4.312 8.427-2.993 1.425.347 2.084.857 2.223 1.057.205.296.262.718.053 1.286-.412 1.126-1.263 1.095-1.734.875-.305-.142-4.082-2.66-7.562 1.097-.24.257-.668.346-1.073.04-.407-.308-.574-.93-.334-1.362"/><path fill="#fff" d="M60.576 83.08h-.153c-9.996.12-22.116-1.204-33.854-3.518-1.004 5.818-1.61 13.132-1.09 21.853 1.316 22.043 14.407 35.9 34.614 36.1h.82c20.208-.2 33.298-14.057 34.616-36.1.52-8.723-.087-16.035-1.092-21.854-11.739 2.315-23.862 3.64-33.86 3.518"/><path fill="#eb1923" d="M32.102 81.235v21.693s9.937 2.004 19.893.616V83.535c-6.307-.357-13.109-1.152-19.893-2.3"/><path fill="#eb1923" d="M105.539 60.412s-19.33 6.102-44.963 6.275h-.153c-25.591-.172-44.896-6.255-44.962-6.275L8.987 76.57c16.193 4.882 36.261 8.028 51.436 7.845h.153c15.175.183 35.242-2.963 51.437-7.845zm0 0"/></svg>
        `;
      rightMiddleContainer.appendChild(TencentDiv);


      languageContainer.className = "language-container";
      languageContainer.style.position = 'fixed';
      languageContainer.style.display = 'none';
      languageSelect.id = 'languageSelect';
      languageSelect.className = "language-dropdown";
      let languages = ['ar', 'bg', 'ckb', 'cs', 'da', 'de', 'el', 'en', 'eo', 'es', 'fi', 'fr', 'fr-CA'
        , 'he', 'hu', 'id', 'it', 'ja', 'ka', 'ko', 'nb', 'nl', 'pl', 'pt-PT', 'pt-BR', 'ro', 'ru', 'sk'
        , 'sr', 'sv', 'th', 'tr', 'uk', 'ug', 'vi', 'zh-CN', 'zh-TW', 'zh-HK', 'zh-SG']
      languages.forEach(lang => {
        let option = document.createElement('option');
        option.value = lang;
        option.innerText = lang;
        if (lang === globalUserLang) {
          option.selected = true;
        }
        languageSelect.appendChild(option);
      });
      languageContainer.appendChild(languageSelect);
      rightMiddleContainer.appendChild(languageContainer);


      toggleMainBtnMovingAccessbility.id = "toggleMainBtnMovingAccessbility";
      toggleMainBtnMovingAccessbility.className = "rightAlwaysShownDiv";
      toggleMainBtnMovingAccessbility.addEventListener('click', ButtonOperations.toggleStickyMainBtn)
      document.body.appendChild(toggleMainBtnMovingAccessbility);

      feedBackPicDiv.style.display = 'none';
      rightMiddleContainer.appendChild(feedBackPicDiv);
      WeChatPicDiv.style.display = 'none';
      rightMiddleContainer.appendChild(WeChatPicDiv);
      TencentPicDiv.style.display = 'none';
      rightMiddleContainer.appendChild(TencentPicDiv);


      document.body.appendChild(settingsContainer);
      document.body.appendChild(rightMiddleContainer);

    },

    deletechatgptDB: function () {
      const DB_NAME = 'ChatTreeDB';
      console.log('try to delete:');

      var req = indexedDB.deleteDatabase(DB_NAME);
      req.onsuccess = function () {
        console.log("Deleted database successfully");
      };
      req.onerror = function () {
        console.log("Couldn't delete database");
      };
      req.onblocked = function () {
        console.log("Couldn't delete database due to the operation being blocked");
      };
    },
    toggleLanguageSelectShow: function () {
      if (languageContainer.style.display !== 'none') {
        languageContainer.style.display = 'none';
        return;
      }
      languageContainer.style.display = 'flex';
      document.addEventListener('click', (e) => {
        if (!rightMiddleContainer.contains(e.target)) {
          languageContainer.style.display = 'none';
        }
      });
    },
    toggleColorSelectShow: function () {
      if (rightMiddleMenu.style.display === 'flex') {
        rightMiddleMenu.style.display = 'none';
        return;
      }
      rightMiddleMenu.style.display = 'flex';
      document.addEventListener('click', (e) => {
        if (!rightMiddleContainer.contains(e.target)) {
          rightMiddleMenu.style.display = 'none';
        }
      });

      function rgbToHex(rgb) {
        const result = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)$/.exec(rgb);
        return result
          ? '#' +
          parseInt(result[1]).toString(16).padStart(2, '0') +
          parseInt(result[2]).toString(16).padStart(2, '0') +
          parseInt(result[3]).toString(16).padStart(2, '0')
          : null;
      }


      //let hexColor = rgbToHex(color);
      //let opacity = parseFloat(treeMainBtn.style.opacity);
      rightMiddleMenu.style.color = 'black';
      rightMiddleMenu.innerHTML = `
        <style>
            .color-select-menu {
                display: flex;
                gap: 20px;
                padding: 20px;
                border-radius: 10px;
                background-color: rgba(255, 255, 255, 0.95);
                box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            }
            .color-group {
                display: flex;
                flex-direction: column;
                align-items: center;
                gap: 10px;
            }
            .color-group div {
                display: flex;
                align-items: center;
                gap: 10px;
            }
            label {
                font-weight: bold;
                margin-bottom: 10px;
            }
            input[type='range'] {
                margin-bottom: 20px;
            }
        </style>
        <div class="color-select-menu">
            <div class="color-group">
                <label>Background</label>
                <div>
                    <input type='range' id='SVGOpacityPicker' min='10' max='99' value='90'>
                </div>
                <div>
                    <input type='color' id='SVGColorTopPicker'>
                    <input type='color' id='SVGColorBottomPicker'>
                </div>
            </div>
            <div class="color-group">
                <label>ChatGPT</label>
                <div>
                    <input type='color' id='chatGPTColorTopPicker'>
                </div>
                <div>
                    <input type='color' id='chatGPTColorBottomPicker'>
                </div>
            </div>
            <div class="color-group">
                <label>User</label>
                <div>
                    <input type='color' id='userColorTopPicker'>
                </div>
                <div>
                    <input type='color' id='userColorBottomPicker'>
                </div>
            </div>
        </div>
    `;
      let SVGOpacityPicker = document.getElementById('SVGOpacityPicker');

      let SVGColorTopPicker = document.getElementById('SVGColorTopPicker');
      let SVGColorBottomPicker = document.getElementById('SVGColorBottomPicker');
      let chatGPTColorTopPicker = document.getElementById('chatGPTColorTopPicker');
      let chatGPTColorBottomPicker = document.getElementById('chatGPTColorBottomPicker');
      let userColorTopPicker = document.getElementById('userColorTopPicker');
      let userColorBottomPicker = document.getElementById('userColorBottomPicker');

      //改变
      SVGOpacityPicker.addEventListener('input', settingsKit.colorAndOpacityKit.onSVGOpacityChange);

      SVGColorTopPicker.addEventListener('input', settingsKit.colorAndOpacityKit.onSVGColorTopChange);
      SVGColorBottomPicker.addEventListener('input', settingsKit.colorAndOpacityKit.onSVGColorBottomChange);
      // chatGPTColorPicker.addEventListener('input', settingsKit.colorAndOpacityKit.onChatGPTColorChange);
      // userColorPicker.addEventListener('input', settingsKit.colorAndOpacityKit.onUserColorChange);

      //改完
      SVGOpacityPicker.addEventListener('change', settingsKit.colorAndOpacityKit.onSVGOpacityChangeDone);
      SVGColorTopPicker.addEventListener('change', settingsKit.colorAndOpacityKit.onSVGColorTopChangeDone);
      SVGColorBottomPicker.addEventListener('change', settingsKit.colorAndOpacityKit.onSVGColorBottomChangeDone);
      chatGPTColorTopPicker.addEventListener('change', settingsKit.colorAndOpacityKit.onChatGPTColorTopPickerChangeDone);
      chatGPTColorBottomPicker.addEventListener('change', settingsKit.colorAndOpacityKit.onChatGPTColorBottomPickerChangeDone);
      userColorTopPicker.addEventListener('change', settingsKit.colorAndOpacityKit.onUserColorTopPickerChangeDone);
      userColorBottomPicker.addEventListener('change', settingsKit.colorAndOpacityKit.onUserColorBottomPickerChangeDone);

      let mainSvg = document.getElementById('mainSvg');
      if (mainSvg) {
        let currentBackground = mainSvg.style.background;

        // 获取顶部和底部的颜色
        let colorMatches = currentBackground.match(/rgba?\((\d{1,3}, \d{1,3}, \d{1,3})(?:, ([\d\.]+))?\)/g);
        let topColor = colorMatches && colorMatches[0];
        let bottomColor = colorMatches && colorMatches[1];

        // 获取 opacity
        let opacityMatch = topColor.match(/rgba?\(\d{1,3}, \d{1,3}, \d{1,3}, ([\d\.]+)\)/);
        let opacity = opacityMatch ? parseFloat(opacityMatch[1]) * 100 : 100; // 将 opacity 转换回百分比

        log("opacity:", opacity);
        log("color:", rgbToHex(topColor));
        SVGOpacityPicker.value = opacity;
        SVGColorTopPicker.value = rgbToHex(topColor);
        SVGColorBottomPicker.value = rgbToHex(bottomColor);
        chatGPTColorTopPicker.value = states.colorSetting.customChatGPT.top;
        chatGPTColorBottomPicker.value = states.colorSetting.customChatGPT.bottom;
        userColorTopPicker.value = states.colorSetting.customUser.top;
        userColorBottomPicker.value = states.colorSetting.customUser.bottom;
      }

    },

    colorAndOpacityKit: {


      onSVGOpacityChange: function (e) {
        const opacity = e.target.value / 100;
        let mainSvg = document.getElementById('mainSvg');
        if (mainSvg) {
          let currentBackground = mainSvg.style.background;

          // 使用正则表达式替换顶部和底部的opacity
          let newBackground = currentBackground.replace(
            /rgba\((\d{1,3}, \d{1,3}, \d{1,3}), [\d\.]+\)/g,
            `rgba($1, ${opacity})`
          );
          mainSvg.style.background = newBackground;
          return newBackground;
        }
        return DEFAULT_MAINSVG_BACKGROUND;
      },
      hexToRgb: function (hex) {
        var bigint = parseInt(hex.substring(1), 16);
        var r = (bigint >> 16) & 255;
        var g = (bigint >> 8) & 255;
        var b = bigint & 255;
        return r + "," + g + "," + b;
      },
      onSVGColorTopChange: function (e) {
        const color = e.target.value;
        let mainSvg = document.getElementById('mainSvg');
        if (mainSvg) {
          let currentBackground = mainSvg.style.background;

          // 使用正则表达式获取当前的opacity
          let opacityMatch = currentBackground.match(/rgba\(\d{1,3}, \d{1,3}, \d{1,3}, ([\d\.]+)\)/);
          let opacity = opacityMatch ? opacityMatch[1] : 1;  // 默认值为1,如果没有匹配到opacity

          // 使用正则表达式替换顶部的颜色,同时保留原有的opacity
          let newBackground = currentBackground.replace(
            /linear-gradient\(to top, rgba\(\d{1,3}, \d{1,3}, \d{1,3}, [\d\.]+\) 0%/,
            `linear-gradient(to top, rgba(${settingsKit.colorAndOpacityKit.hexToRgb(color)}, ${opacity}) 0%`
          );
          mainSvg.style.background = newBackground;
          return newBackground;
        }
        return DEFAULT_MAINSVG_BACKGROUND;
      },
      onSVGColorBottomChange: function (e) {
        const color = e.target.value;
        let mainSvg = document.getElementById('mainSvg');
        if (mainSvg) {
          let currentBackground = mainSvg.style.background;
          // 使用正则表达式获取当前的opacity
          let opacityMatch = currentBackground.match(/rgba\(\d{1,3}, \d{1,3}, \d{1,3}, ([\d\.]+)\) 100%/);
          let opacity = opacityMatch ? opacityMatch[1] : 1;  // 默认值为1,如果没有匹配到opacity
          // 使用正则表达式替换底部的颜色,同时保留原有的opacity
          let newBackground = currentBackground.replace(
            /rgba\(\d{1,3}, \d{1,3}, \d{1,3}, [\d\.]+\) 100%/,
            `rgba(${settingsKit.colorAndOpacityKit.hexToRgb(color)}, ${opacity}) 100%`
          );
          mainSvg.style.background = newBackground;
          return newBackground;
        }
        return DEFAULT_MAINSVG_BACKGROUND;
      },
      onSVGOpacityChangeDone: function (e) {
        let newBackground = settingsKit.colorAndOpacityKit.onSVGOpacityChange(e);
        const newSettings = {id: 'mainSVG', background: newBackground};
        dbOperations.updateUserSettings(newSettings).then(() => {
          ButtonOperations.showUserNotification(translate("successSavingChanges"));
        }).catch(error => {
          console.error("Error saving Change:", error);
        });
      },
      onSVGColorTopChangeDone: function (e) {
        let newBackground = settingsKit.colorAndOpacityKit.onSVGColorTopChange(e);
        const newSettings = {id: 'mainSVG', background: newBackground};
        dbOperations.updateUserSettings(newSettings).then(() => {
          ButtonOperations.showUserNotification(translate("successSavingChanges"));
        }).catch(error => {
          console.error("Error saving Change:", error);
        });
      },
      onSVGColorBottomChangeDone: function (e) {
        let newBackground = settingsKit.colorAndOpacityKit.onSVGColorBottomChange(e);
        const newSettings = {id: 'mainSVG', background: newBackground};
        dbOperations.updateUserSettings(newSettings).then(() => {
          ButtonOperations.showUserNotification(translate("successSavingChanges"));
        }).catch(error => {
          console.error("Error saving Change:", error);
        });
      },

      onChatGPTColorTopPickerChangeDone: function (e) {
        log("ChangeDone!");
        states.colorSetting.customChatGPT.is = true;
        // 移除现有的渐变
        // const existingGradient = defs.querySelector('#CUSTOM_USER_GRADIENT');
        const defsNode = defs.node();
        const existingGradient = defsNode.querySelector('#CUSTOM_CHATGPT_GRADIENT');
        if (existingGradient) {
          defsNode.removeChild(existingGradient);
        }
        states.colorSetting.customChatGPT.top = e.target.value.toString();
        initSvgAndGradient.createLinearGradient(defs, "CUSTOM_CHATGPT_GRADIENT", states.colorSetting.customChatGPT.bottom, states.colorSetting.customChatGPT.top);
        drawMainSVG();

        const newSettings = {id: 'chatGPTColor', state: states.colorSetting.customChatGPT};
        log("newGPTSetting:", newSettings);
        dbOperations.updateUserSettings(newSettings).then(() => {
          ButtonOperations.showUserNotification(translate("successSavingChanges"));
        }).catch(error => {
          console.error("Error saving Change:", error);
        });
      },
      onChatGPTColorBottomPickerChangeDone: function (e) {
        log("ChangeDone!");
        states.colorSetting.customChatGPT.is = true;
        // 移除现有的渐变
        const defsNode = defs.node();
        const existingGradient = defsNode.querySelector('#CUSTOM_CHATGPT_GRADIENT');
        if (existingGradient) {
          defsNode.removeChild(existingGradient);
        }
        states.colorSetting.customChatGPT.bottom = e.target.value.toString();
        initSvgAndGradient.createLinearGradient(defs, "CUSTOM_CHATGPT_GRADIENT", states.colorSetting.customChatGPT.bottom, states.colorSetting.customChatGPT.top);
        drawMainSVG();
        const newSettings = {id: 'chatGPTColor', state: states.colorSetting.customChatGPT};
        log("newGPTSetting:", newSettings);
        dbOperations.updateUserSettings(newSettings).then(() => {
          ButtonOperations.showUserNotification(translate("successSavingChanges"));
        }).catch(error => {
          console.error("Error saving Change:", error);
        });
      },
      onUserColorTopPickerChangeDone: function (e) {
        log("ChangeDone!");
        states.colorSetting.customUser.is = true;
        // 移除现有的渐变
        const defsNode = defs.node();
        const existingGradient = defsNode.querySelector('#CUSTOM_USER_GRADIENT');
        if (existingGradient) {
          defsNode.removeChild(existingGradient);
        }
        states.colorSetting.customUser.top = e.target.value.toString();
        initSvgAndGradient.createLinearGradient(defs, "CUSTOM_USER_GRADIENT", states.colorSetting.customUser.bottom, states.colorSetting.customUser.top);
        drawMainSVG();
        const newSettings = {id: 'userColor', state: states.colorSetting.customUser};
        log("newUserSetting:", newSettings);
        dbOperations.updateUserSettings(newSettings).then(() => {
          ButtonOperations.showUserNotification(translate("successSavingChanges"));
        }).catch(error => {
          console.error("Error saving Change:", error);
        });
      },
      onUserColorBottomPickerChangeDone: function (e) {
        log("ChangeDone!");
        states.colorSetting.customUser.is = true;
        // 移除现有的渐变
        const defsNode = defs.node();
        const existingGradient = defsNode.querySelector('#CUSTOM_USER_GRADIENT');
        if (existingGradient) {
          defsNode.removeChild(existingGradient);
        }
        states.colorSetting.customUser.bottom = e.target.value.toString();
        initSvgAndGradient.createLinearGradient(defs, "CUSTOM_USER_GRADIENT", states.colorSetting.customUser.bottom, states.colorSetting.customUser.top);
        drawMainSVG();
        const newSettings = {id: 'userColor', state: states.colorSetting.customUser};
        log("newUserSetting:", newSettings);
        dbOperations.updateUserSettings(newSettings).then(() => {
          ButtonOperations.showUserNotification(translate("successSavingChanges"));
        }).catch(error => {
          console.error("Error saving Change:", error);
        });
      }
    },


    addEventListeners: function () {

      languageSelectDiv.addEventListener('click', settingsKit.toggleLanguageSelectShow);
      colorSelectDiv.addEventListener('click', settingsKit.toggleColorSelectShow);
      feedbackDiv.addEventListener('click', () => {
        // 检测用户的语言设置
        if (globalUserLang.startsWith('zh')) {
          window.open('https://wj.qq.com/s2/13235492/27ec', '_blank');
        } else {
          window.open('https://docs.google.com/forms/d/e/1FAIpQLSetbHqiS1GBM6bG0QaaKy9cN31jKXK76BcYCW8_wkRNH7I5kQ/viewform', '_blank');
        }
      });

      // 全局保存所有的PicDivs
      let allPicDivs = [];

      function handleMouseEvents(mainDiv, picDiv) {
        let disappearTimeOut;

        // 当一个PicDiv被展示时,隐藏所有其他的PicDivs
        function hideOtherPicDivs(currentPicDiv) {
          for (let div of allPicDivs) {
            if (div !== currentPicDiv) {
              div.style.display = 'none';
            }
          }
        }

        mainDiv.addEventListener('mouseenter', function (e) {
          log(mainDiv.id + " mouseover");
          clearTimeout(disappearTimeOut);
          hideOtherPicDivs(picDiv); // 隐藏其他PicDivs

          let elements = [mainDiv, picDiv];
          elements.forEach(element => {
            element.style.display = 'flex';
          });
        });

        picDiv.addEventListener('mouseenter', function (e) {
          log(picDiv.id + " mouseover");
          clearTimeout(disappearTimeOut);
          hideOtherPicDivs(picDiv); // 隐藏其他PicDivs
          picDiv.style.display = 'block';
        });

        mainDiv.addEventListener('mouseleave', function () {
          log(mainDiv.id + " mouseleave");
          disappearTimeOut = setTimeout(() => {
            picDiv.style.display = 'none';
          }, 400);
        });

        picDiv.addEventListener('mouseleave', function (e) {
          log(picDiv.id + " mouseleave");
          disappearTimeOut = setTimeout(() => {
            picDiv.style.display = 'none';
          }, 400);
        });

        // 将当前的PicDiv加入到集中管理的数组中
        allPicDivs.push(picDiv);
      }

      handleMouseEvents(feedbackDiv, feedBackPicDiv);
      handleMouseEvents(WeChatDiv, WeChatPicDiv);
      handleMouseEvents(TencentDiv, TencentPicDiv);
      // let feedbackTimeOut;
      // feedbackDiv.addEventListener('mouseenter', function (e) {
      //   log("feedbackDiv mouseover");
      //   clearTimeout(feedbackTimeOut);
      //   let elements = [feedbackDiv, feedBackPicDiv];
      //   elements.forEach(element => {
      //     element.style.display = 'flex';
      //   });
      // });
      // feedBackPicDiv.addEventListener('mouseenter', function (e) {
      //   log("feedBackPicDiv mouseover");
      //   clearTimeout(feedbackTimeOut);
      //   feedBackPicDiv.style.display = 'block';
      // });
      // feedbackDiv.addEventListener('mouseleave', function () {
      //   log("feedbackDiv mouseleave");
      //   feedbackTimeOut = setTimeout(() => {
      //     feedBackPicDiv.style.display = 'none';
      //   }, 400);
      // });
      // feedBackPicDiv.addEventListener('mouseleave', function (e) {
      //   log("feedBackPicDiv mouseleave");
      //   feedbackTimeOut = setTimeout(() => {
      //     feedBackPicDiv.style.display = 'none';
      //   }, 400);
      // });


      settingsDiv.addEventListener('click', function (e) {
        log("settingsContainer click");
        let elements = document.querySelectorAll('.actionDiv');
        if (deleteDiv.style.display !== 'flex') {
          elements.forEach(element => {
            element.style.display = 'flex';
          });
        } else {
          elements.forEach(element => {
            element.style.display = 'none';
          });
        }
      });
      zoomInButton.addEventListener('click', this.zoomIn);
      zoomOutButton.addEventListener('click', this.zoomOut);
      thumbNailButton.addEventListener('click', this.toggleThumbnail);
      refreshTreeButton.addEventListener('click', this.refreshTree);
      undoButton.addEventListener('click', this.undo);
      redoButton.addEventListener('click', this.redo);
      deleteDiv.addEventListener('click', function () {
        settingsKit.handleTwoTypesOfDeleteConversationData(null, false);
      });
    },
    handleTwoTypesOfDeleteConversationData: async function (operatingLink, IsfromPanel) {
      if (!operatingLink) {
        if (states.treeUpdate.isDOMOperating || (!states.url.isForLiveValidURL && !states.url.isForDeletedValidURL) || conversationData.url === null) {
          return;
        }
      }
      let operatingURL;
      if (operatingLink === null && IsfromPanel === false) {
        operatingURL = conversationData.url;
      } else if (operatingLink && IsfromPanel === true) {
        operatingURL = operatingLink;
      }
      //log("going to confirm delete");
      if (confirm(translate("confirmDeleteLinkData").replace('{item}', operatingURL))) {
        try {
          //await dbOperations.deleteConversationData(operatingURL);
          dbOperations.deleteConversationData(operatingURL)
            .then(() => dbOperations.initConversationData())
            .then(information => {
              log("after_init_conversationData:", chatHistory);
              controlPanelKit.updateCategorySelect();
              log("before_filteredConversations:", filteredConversations);

              filteredConversations = filteredConversations.filter(aconv => {
                return !(aconv.link === operatingURL)
              });
              controlPanelKit.renderConversations(filteredConversations);
              log("after_filteredConversations:", filteredConversations);
            })
            .catch(error => {
              console.error(error);
            });
          log('Data deleted successfully');
          ButtonOperations.showUserNotification(translate("conversationDataDeleted"), 'alert');
          if (IsfromPanel) {
            return;
          }
          try {
            dbOperations.loadConversationsData(operatingURL).then(loadeddata => {
              log("Loaded data for URL:", loadeddata);
              conversationData = loadeddata;
              root = d3.hierarchy(conversationData.rootNode);
              log("reNewedRoot:", root);
              const widthPerNode = 30;
              const heightPerNode = 30;
              treeLayout = d3.tree().nodeSize([widthPerNode, heightPerNode]);
              treeLayout(root);
              settingsKit.refreshTree();
              //drawMainSVG();
            }).catch(error => {
              console.error("Error loading data:", error);
            });
          } catch (loadError) {
            console.error('Error loading data:', loadError);
          }
        } catch (deleteError) {
          console.error('Error deleting data:', deleteError);
        }
      } else {
        log('数据未被删除!');
      }
    },

    zoomIn: function () {
      let currentScale = d3.zoomTransform(svg.node()).k;
      let increment = currentScale < 1 ? scaleIncrementSmall : scaleIncrementLarge;
      let newScale = currentScale + increment;
      if (newScale > 10) newScale = 10;
      svg.transition().duration(250)
        .call(zoom.scaleTo, newScale, [centerX, centerY]);
    },

    zoomOut: function () {
      let currentScale = d3.zoomTransform(svg.node()).k;
      let decrement = currentScale <= 1 ? scaleIncrementSmall : scaleIncrementLarge;
      let newScale = currentScale - decrement;
      if (newScale < 0.1) newScale = 0.1;
      svg.transition().duration(250)
        .call(zoom.scaleTo, newScale, [centerX, centerY]);

    },


    toggleThumbnail: function () {
      let currentVisibility = svgThumbnail.attr('visibility');
      svgThumbnail.attr('visibility', currentVisibility === 'hidden' ? 'visible' : 'hidden');
      log("currentVisibility:", currentVisibility);
    },


    refreshTree: function () {
      undoStack = [];
      redoStack = [];
      svg.call(zoom.transform, d3.zoomIdentity.translate(0, 0).scale("1"));
      treeLayout(root);
      const yOffset = window.innerHeight / 5 - root.x;
      const xOffset = window.innerWidth / 5 - root.x;
      root.each(d => {
        d.y += yOffset;
      });
      root.each(d => {
        d.x += xOffset;
      });
      drawMainSVG();
    },


    simulateMove: function (action, doType) {
      let nodeData;

      d3.selectAll("g.node")
        .filter(function (d) {
          return d.data.uuid === action.uuid;
        })
        .each(function (d) {
          nodeData = d;
        });

      if (!nodeData) {
        console.error("Node not found for UUID:", action.uuid);
        return;
      }

      let dx;
      let dy;
      if (doType === 'undo') {
        dx = action.startX - action.endX;
        dy = action.startY - action.endY;
      } else if (doType === 'redo') {
        dx = action.endX - action.startX;
        dy = action.endY - action.startY;
      }
      nodeData.x += dx;
      nodeData.y += dy;
      if (nodeData.children) {
        nodeData.children.forEach(child => {
          settingsKit.simulateMove({
            uuid: child.data.uuid,
            startX: child.x,
            startY: child.y,
            endX: child.x - dx,
            endY: child.y - dy
          }, "undo");
        });
      }
    },

    performAction: function (action) {
      undoStack.push(action);
      redoStack = [];
    },
    redo: function () {
      if (redoStack.length <= 0) return;
      let action = redoStack.pop();
      log("action:", action);
      if (action.type === 'drag') {
        settingsKit.simulateMove(action, "redo");
        drawMainSVG();
      }
      undoStack.push(action);
    },
    undo: function () {
      if (undoStack.length <= 0) return;
      let action = undoStack.pop();
      log("action:", action);
      if (action.type === 'drag') {
        settingsKit.simulateMove(action, "undo");
        drawMainSVG();
      }
      redoStack.push(action);
    },
  };


  let searchContainer = document.createElement('div');
  let searchIcon = document.createElement('div');
  let searchBox = document.createElement('input');
  let searchBtn = document.createElement('button');
  let searchResultsCount = document.createElement('div');
  let searchHistory = document.createElement('div');
  let suggestionsContainer = document.createElement('div');
  let matchedUUIDs = new Set();
  let matchedArray = [];
  let currentTreeBreathingIndex = -1;
  const searchKit = {
    isSearchKitExpanded: false,
    stopBreathing: false,
    init: function () {
      settingsKit.refreshTree();
      searchContainer.id = 'search-container';
      searchContainer.style.display = 'none';
      searchIcon.id = 'search-icon';
      searchIcon.innerText = '🔍';
      searchIcon.onclick = searchKit.toggleSearch;
      searchContainer.appendChild(searchIcon);
      searchBox.type = 'text';
      searchBox.id = 'search-box';
      suggestionsContainer.id = 'suggestions-container';
      suggestionsContainer.className = 'suggestions-container';
      searchContainer.appendChild(suggestionsContainer);
      searchBox.setAttribute("placeholder", translate("searchPlaceholder"));
      searchBox.addEventListener('keydown', function (event) {
        if (event.key === 'Enter' || event.keyCode === 13) {
          event.preventDefault();
          event.stopPropagation();
          searchKit.executeSearch();
        }
      });

      function updateSuggestions(suggestions) {
        suggestionsContainer.style.display = 'block';
        suggestionsContainer.innerHTML = '';
        suggestions.forEach(suggestion => {
          const item = document.createElement('div');
          item.className = 'suggestion-item';
          item.textContent = suggestion;
          const closeButton = document.createElement('span');
          closeButton.textContent = 'x';
          closeButton.className = 'close-button';
          closeButton.onclick = (e) => {
            e.stopPropagation();
            const index = suggestions.indexOf(suggestion);
            if (index > -1) {
              suggestions.splice(index, 1);
              dbOperations.addSearchRecord(searchHistoryRecord)
                .then(() => dbOperations.getSearchHistory())
                .then(records => {
                  log('Got search history:', records);
                })
                .catch(error => console.error(error));
            }
            updateSuggestions(suggestions);
          };

          item.appendChild(closeButton);

          item.onclick = () => {
            searchBox.value = suggestion;
            clearSuggestions();
          };

          item.addEventListener('contextmenu', function (e) {
            e.preventDefault();
            const index = suggestions.indexOf(suggestion);
            if (index > -1) {
              suggestions.splice(index, 1);
            }
            updateSuggestions(suggestions);
            dbOperations.addSearchRecord(searchHistoryRecord)
              .then(() => dbOperations.getSearchHistory())
              .then(records => {
                log('Got search history:', records);
              })
              .catch(error => console.error(error));
          });

          suggestionsContainer.appendChild(item);
        });
      }

      function clearSuggestions() {
        suggestionsContainer.innerHTML = '';
        suggestionsContainer.style.display = 'none';
      }

      document.addEventListener('click', function (e) {
        if (!searchContainer.contains(e.target)) {
          clearSuggestions();
        }
      });
      searchBox.addEventListener('input', function () {
        const query = this.value.trim();
        if (query.length > 0) {
          const suggestions = searchHistoryRecord.filter(existing => existing.includes(query));
          updateSuggestions(suggestions);
        } else {
          clearSuggestions();
        }
      });
      searchContainer.appendChild(searchBox);
      searchBtn.id = 'search-btn';
      searchBtn.innerText = translate("searchButton");
      searchBtn.onclick = searchKit.executeSearch;
      searchBtn.style.border = 'none';
      searchBtn.style.borderRadius = '3px';
      searchBtn.style.height = '32px';
      searchBtn.style.cursor = 'pointer';
      searchBtn.style.position = 'relative';
      searchBtn.style.top = '4px';
      searchBtn.style.backgroundColor = 'rgb(241, 241, 241)';

      searchContainer.appendChild(searchBtn);
      searchResultsCount.id = 'search-results-count';
      searchResultsCount.style.borderRadius = '3px';
      searchResultsCount.style.background = '#f1f1f1';
      searchResultsCount.style.height = '32px';
      searchResultsCount.style.position = 'relative';
      searchResultsCount.style.fontSize = '10px';
      searchResultsCount.style.top = '4px';
      searchResultsCount.style.left = '5px';
      searchResultsCount.style.display = 'none';

      const optionsButtons = document.createElement('div');
      optionsButtons.style.width = '490px';
      optionsButtons.style.position = 'absolute';
      optionsButtons.style.top = '250px';
      optionsButtons.style.left = '4px';
      optionsButtons.style.display = 'flex';
      optionsButtons.style.flexDirection = 'column';
      optionsButtons.style.alignItems = 'center';
      optionsButtons.style.background = 'none';
      optionsButtons.id = "optionsButtons";
      optionsButtons.style.display = 'none';
      optionsButtons.style.marginTop = '10px';

      const labelsDiv = document.createElement('div');
      labelsDiv.style.display = 'flex';
      labelsDiv.style.justifyContent = 'center';
      labelsDiv.style.width = '100%';
      labelsDiv.style.marginBottom = '20px';


      const buttonsDiv = document.createElement('div');
      buttonsDiv.style.display = 'flex';
      buttonsDiv.style.justifyContent = 'center';
      buttonsDiv.style.width = '100%';

      const options = [translate("searchInContent"), translate("searchInComments"), translate("searchInBoth")];
      options.forEach(option => {
        const label = document.createElement('label');
        label.style.marginRight = '10px';
        label.style.backgroundColor = 'rgb(221, 221, 221)';
        label.style.padding = '5px';
        label.style.borderRadius = '5px';
        const radio = document.createElement('input');
        radio.type = 'radio';
        radio.name = 'searchOption';
        radio.id = option.toLowerCase();
        radio.value = option.toLowerCase();
        if (option === translate("searchInContent")) radio.checked = true;
        label.appendChild(radio);
        label.appendChild(document.createTextNode(option));
        labelsDiv.appendChild(label);
      });
      optionsButtons.appendChild(labelsDiv);

      const navButtons = [translate("goToPrevious"), translate("goToNext")];
      navButtons.forEach(btnText => {
        const btn = document.createElement('button');
        btn.innerText = btnText;
        btn.style.marginRight = '10px';
        btn.style.backgroundColor = 'rgb(221, 221, 221)';
        btn.style.padding = '5px';
        btn.style.borderRadius = '5px';
        if (btnText === translate("goToNext")) {
          btn.addEventListener('click', function () {
            searchKit.goToNextMatch();
          });
        }
        if (btnText === translate("goToPrevious")) {
          btn.addEventListener('click', function () {
            searchKit.gpToPreviousMatch();
          });
        }
        buttonsDiv.appendChild(btn);
      });
      optionsButtons.appendChild(buttonsDiv);


      searchContainer.appendChild(searchResultsCount);
      searchHistory.id = 'search-history';
      searchContainer.appendChild(searchHistory);
      searchContainer.appendChild(optionsButtons);
      document.body.appendChild(searchContainer);
    },
    toggleSearch: function () {
      const searchBox = document.getElementById('search-box');
      const searchHistory = document.getElementById('search-history');
      const searchIcon = document.getElementById('search-icon');
      if (!this.isSearchKitExpanded) {
        searchContainer.style.display = 'flex';
        searchContainer.style.width = '600px';
        searchHistory.style.display = 'block';
        searchBox.style.display = 'block';
        anime({
          targets: searchBox,
          width: '300px',
          duration: 500,
          easing: 'easeInOutQuart',
          opacity: 1,
          background: '#f1f1f1',
          complete: function () {
            searchBox.style.pointerEvents = 'auto';
            searchBtn.style.display = 'block';
            document.getElementById('search-history').style.display = 'flex';
            searchResultsCount.style.display = 'block';
            let opbtn = document.getElementById("optionsButtons");
            opbtn.style.display = 'block';
            searchKit.displaySearchHistory();
          }
        });
        anime({
          targets: searchHistory,
          width: '500px',
          height: '300px',
          opacity: 1,
          duration: 500,
          easing: 'easeInOutQuart',
          background: '#f1f1f1',
        });
        searchIcon.innerHTML = '❌';
        this.isSearchKitExpanded = true;
        log("this.isSearchKitExpanded:", this.isSearchKitExpanded);
      } else {
        searchContainer.style.width = '50px';
        suggestionsContainer.style.display = 'none';
        gNodes.selectAll(".node")
          .select("circle")
          .interrupt()
          .attr("opacity", 1);
        gNodes.selectAll(".node")
          .each(function (d) {
            if (matchedUUIDs.has(d.data.uuid)) {
              d3.select(this)
                .select("text")
                .style("display", "none");
            }
          });
        let opbtn = document.getElementById("optionsButtons");
        opbtn.style.display = 'none';
        searchResultsCount.style.display = 'none';
        anime({
          targets: searchBox,
          width: '0px',
          duration: 500,
          easing: 'easeInOutQuart',
          opacity: 0,
          background: 'transparent',
          begin: function () {
            searchBtn.style.display = 'none';
          },
          complete: function () {
            searchHistory.style.display = 'none';
            searchBox.style.display = 'none';
          }
        });
        anime({
          targets: searchHistory,
          width: '20px',
          height: '0px',
          opacity: 0,
          duration: 500,
          easing: 'easeInOutQuart',
          background: '#f1f1f1',
        });
        searchIcon.innerHTML = '🔍';
        this.isSearchKitExpanded = false;
        log("this.isSearchKitExpanded:", this.isSearchKitExpanded);
      }
    },
    escapeRegExp: function (string) {
      return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    },
    executeSearch: function () {
      gNodes.selectAll(".node")
        .select("circle")
        .interrupt()
        .attr("opacity", 1);
      gNodes.selectAll(".node")
        .each(function (d) {
          if (matchedUUIDs.has(d.data.uuid)) {
            d3.select(this)
              .select("text")
              .style("display", "none");
          }
        });
      let rawQuery = document.getElementById('search-box').value;
      let trimmedQuery = rawQuery.trim();
      if (trimmedQuery !== null && trimmedQuery !== '' && trimmedQuery !== ' ') {
        ButtonOperations.showUserNotification(translate("executeSearchWithQuery").replace(`{item}`, rawQuery));
        searchKit.addSearchHistory(rawQuery);
        searchKit.displaySearchHistory();


        const searchOptions = document.getElementsByName("searchOption");
        let selectedOption = "";
        let selectedNum = 0;
        for (const option of searchOptions) {
          if (option.checked) {
            selectedOption = option.value;
            break;
          }
          selectedNum++;
        }
        selectedOption = selectedNum === 0 ? translate("searchInContent") : selectedNum === 1 ? translate("searchInComments") : translate("searchInBoth");
        let escapedQuery = searchKit.escapeRegExp(rawQuery);
        matchedArray = searchKit.bfsSearch(conversationData.rootNode, escapedQuery, selectedOption);
        matchedUUIDs = new Set(matchedArray.map(d => d.uuid));
        log('results:', matchedArray);
        let matches = matchedArray.length;
        document.getElementById('search-results-count').innerText = translate("numberOfMatches").replace('{matches}', matches);
        dbOperations.addSearchRecord(searchHistoryRecord)
          .then(() => dbOperations.getSearchHistory())
          .then(records => {
            log('Got search history:', records);
          })
          .catch(error => console.error(error));
        gNodes.selectAll(".node")
          .each(function (d) {
            if (matchedUUIDs.has(d.data.uuid)) {
              d3.select(this)
                .select("text")
                .style("display", "block");
            }
          });

      } else {
        alert("Please Enter Before Searching!");
      }
    },

    bfsSearch: function (tree, searchTerm, searchOption) {
      log("searchTerm:", searchTerm);
      log("in bfsSearch");
      //log("searchOption:",searchOption, "translate:",translate("searchInContent"),"\n",translate("searchInComments"),"\n",translate("searchInBoth"));
      let queue = [tree];
      let results = [];
      while (queue.length > 0) {
        let current = queue.shift();
        if (current.uuid === conversationRootNode.uuid) {
          queue.push(...current.children);
          continue;
        }
        //log("In BFSSearch, current_content:",current.content);
        if (searchOption === translate("searchInContent") && current.content) {
          log("搜索内容");
          for (let i = 0; i < current.content.length; i++) {
            for (let j = 0; j < current.content[i].parts.length; j++) {
              if (!current.content[i].parts[j].content_type && current.content[i].parts[j].match(new RegExp(searchTerm, 'i')))
                results.push(current);
            }
          }
        } else if (searchOption === translate("searchInComments")) {
          log("搜索评论");
          if (conversationData.commentMap.get(current.uuid))
            results.push(current);
        } else if (searchOption === translate("searchInBoth") && (current.content)) {
          log("搜索两者");
          for (let i = 0; i < current.content.length; i++) {
            for (let j = 0; j < current.content[i].parts.length; j++) {
              if (!current.content[i].parts[j].content_type && current.content[i].parts[j].match(new RegExp(searchTerm, 'i')))
                results.push(current);
            }
          }
          if (conversationData.commentMap.get(current.uuid))
            results.push(current);
        }
        if (current.children) {
          queue.push(...current.children);
        }
      }
      return results;
    },


    breathe: function (selection) {
      selection.select("circle")
        .transition()
        .duration(100)
        .attr("opacity", 0)
        .transition()
        .duration(100)
        .attr("opacity", 1)
        .on("end", function () {
          if (!this.stopBreathing) {
            searchKit.breathe(selection);
          }
        });
    },

    goToNextMatch: function () {

      log("In goToNextMatch");

      if (matchedArray.length === 0) return;
      this.stopBreathing = true;
      gNodes.selectAll(".node")
        .select("circle")
        .interrupt()
        .attr("opacity", 1);
      log("matchedArray:", matchedArray);
      log("before_currentTreeBreathingIndex", currentTreeBreathingIndex);
      currentTreeBreathingIndex = (currentTreeBreathingIndex + 1) % matchedArray.length;
      log("after_currentTreeBreathingIndex", currentTreeBreathingIndex);
      log("matchedArray[currentTreeBreathingIndex]", matchedArray[currentTreeBreathingIndex]);
      let currentNodeUUID = matchedArray[currentTreeBreathingIndex].uuid;
      log("NextUUID:", currentNodeUUID);
      let nextNodeSelection = gNodes.selectAll(".node").filter(d => d.data.uuid === currentNodeUUID);
      log("nextNodeSelection:", nextNodeSelection);
      this.stopBreathing = false;
      searchKit.breathe(nextNodeSelection);
      searchKit.centerNodeOnScreen(nextNodeSelection);
      log("Out goToNextMatch");
    },

    gpToPreviousMatch: function () {
      if (matchedArray.length === 0) return;
      log("In goToNextMatch");
      this.stopBreathing = true;
      gNodes.selectAll(".node")
        .select("circle")
        .interrupt()
        .attr("opacity", 1);
      log("matchedArray:", matchedArray);
      currentTreeBreathingIndex = (currentTreeBreathingIndex - 1 + matchedArray.length) % matchedArray.length;
      log("currentTreeBreathingIndex", currentTreeBreathingIndex);
      log("matchedArray[currentTreeBreathingIndex]", matchedArray[currentTreeBreathingIndex]);
      let currentNodeUUID = matchedArray[currentTreeBreathingIndex].uuid;
      log("NextUUID:", currentNodeUUID);
      let nextNodeSelection = gNodes.selectAll(".node").filter(d => d.data.uuid === currentNodeUUID);
      log("nextNodeSelection:", nextNodeSelection);
      this.stopBreathing = false;
      searchKit.breathe(nextNodeSelection);
      searchKit.centerNodeOnScreen(nextNodeSelection);
      log("Out goToNextMatch");

    },
    centerNodeOnScreen: function (node) {
      let nodeData = node.datum();
      let nodeX = nodeData.x;
      let nodeY = nodeData.y;
      let newScale = 2;
      let horizontalTranslate = (window.innerWidth / 2) - (nodeX * newScale);
      let verticalTranslate = (window.innerHeight / 3) - (nodeY * newScale);
      let nodeTransform = d3.zoomIdentity.translate(horizontalTranslate, verticalTranslate).scale(newScale);
      svg.transition().duration(750).call(zoom.transform, nodeTransform);
    },

    addSearchHistory: function (query) {
      if (searchHistoryRecord.includes(query)) {
        let index = searchHistoryRecord.indexOf(query);
        searchHistoryRecord.splice(index, 1);
      }
      searchHistoryRecord.unshift(query);
    },

    displaySearchHistory: function () {
      let container = document.getElementById('search-history');
      container.innerHTML = '';
      let displayedCount = 0;
      searchHistoryRecord.forEach(item => {
        if (displayedCount >= 20) {
          return;
        }
        displayedCount++;
        let historyItem = document.createElement('div');
        historyItem.className = 'history-item';

        let text = document.createElement('div');
        text.className = 'history-text';
        text.innerText = item;
        historyItem.appendChild(text);

        historyItem.onclick = () => {

          searchBox.value = item;
        };
        historyItem.style.cursor = 'pointer';
        let deleteBtn = document.createElement('div');
        deleteBtn.className = 'history-delete';
        deleteBtn.innerText = 'x';
        deleteBtn.onclick = function (e) {
          e.stopPropagation();
          let index = searchHistoryRecord.indexOf(item);
          searchHistoryRecord.splice(index, 1);
          searchKit.displaySearchHistory();
          dbOperations.addSearchRecord(searchHistoryRecord)
            .then(() => dbOperations.getSearchHistory())
            .then(records => {
              log('Got search history:', records);
            })
            .catch(error => console.error(error));

        };
        historyItem.appendChild(deleteBtn);
        container.appendChild(historyItem);
      });
    },
  };


  let panelToggleButton;//= document.createElement('button');
  // Create the 'managePanel' div
  let managePanel = document.createElement('div');
  let topicSearchContainer = document.createElement('div');
  let searchTopicBox = document.createElement('input');
  let categoryEditor = document.createElement('div');
  let categorySelect = document.createElement('select');
  let conversationContainer = document.createElement('div');
  let right_Top_togglePanel = document.createElement('div');
  let panelHeader = document.createElement('div');


  let filteredConversations = [];
  const controlPanelKit = {
    init: function () {
      panelToggleButton = ButtonCreator.createButton({
        id: 'panelToggleButtonSVGShow',
        text: translate("openAdminPanel"),
        eventListeners: [
          {type: 'click', handler: controlPanelKit.toggleHistoryPanel},
        ],
        additionalStyles: {
          display: 'none',
          borderRadius: '12px',
          opacity: '0.9',
          background: 'linear-gradient(to right, #007BFF, #00C6FF)',
          color: 'white',
          padding: '10px 20px',
          fontWeight: 'bold',
          boxShadow: '0px 3px 5px rgba(0,0,0,0.2)',
        },
      });
      document.body.appendChild(panelToggleButton);


      managePanel.id = 'managePanel';
      managePanel.style.display = 'none';
      // Create the 'searchContainer' div
      topicSearchContainer.id = 'searchContainer';
      topicSearchContainer.style.marginTop = '20px';
      topicSearchContainer.style.marginLeft = '20px';

      // Create the search input box
      searchTopicBox.type = 'text';
      searchTopicBox.id = 'searchTopicBox';
      searchTopicBox.setAttribute("placeholder", translate("searchPlaceholder"));

      // Append the search box to the 'searchContainer' div
      topicSearchContainer.appendChild(searchTopicBox);

      // Create the 'categoryEditor' div
      categoryEditor.id = 'categoryEditor';
      categoryEditor.className = 'editor';

      // Create the select box for categories
      categorySelect.id = 'categorySelect';
      categorySelect.style.marginTop = '20px';
      categorySelect.style.marginLeft = '20px';
      // Add options to the select box
      // dbOperations.initConversationData()
      //   .then(information => {
      //     controlPanelKit.updateCategorySelect();
      //   })
      //   .catch(error => console.error(error));
      let allCategoriesString = translate("allCategoriesFilter");
      let categories = [allCategoriesString];
      for (let i = 0; i < chatHistory.length; i++) {
        let curChat = chatHistory[i];
        if (curChat.categories) {
          for (let j = 0; j < curChat.categories.length; j++) {
            if (!categories.includes(curChat.categories[j]))
              categories.push(curChat.categories[j]);
          }
        }
      }
      categories.forEach(optionText => {
        let option = document.createElement('option');
        option.text = optionText;
        categorySelect.appendChild(option);
      });
      // Append the select box to the 'categoryEditor' div
      categoryEditor.appendChild(categorySelect);

      panelHeader.id = 'panelHeader';
      let translatedTitle = translate('conversationTitle');
      let translatedOption = translate('actionOptions');
      let translatedCategory = translate('conversationCategory');
      let translatedTags = translate('conversationTags');

      panelHeader.innerHTML = `
  <br>
  <hr>
  <hr>
  <hr>
  <hr>
  <br>
  <div class="conversation" style="display: flex; justify-content: space-between; align-items: center; background-color: #f3f4f6; padding: 10px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">
    <div class="topic-container" style="flex: 2; text-align: center; display: flex; justify-content: center; flex-wrap: wrap;">
      <span class="category" style="background-color: #83c5be; color: #ffffff; padding: 5px; border-radius: 4px; margin: 2px; width: 40%;">
        ${translatedTitle}
      </span>
    </div>
    <div class="options-container" style="flex: 0.7; text-align: center; background-color: #e2e8f0; padding: 5px; border-radius: 4px;">
      ${translatedOption}
    </div>
    <div class="categories-container" style="flex: 1.5; text-align: center; display: flex; justify-content: center; flex-wrap: wrap;">
      <span class="category" style="background-color: #a0aec0; color: #ffffff; padding: 5px; border-radius: 4px; margin: 2px; width: 30%;">
        ${translatedCategory}
      </span>
    </div>
    <div class="tags-container" style="flex: 1.5; text-align: center; display: flex; justify-content: center; flex-wrap: wrap;">
      <span class="chat-tree-tag" style="background-color: #a0aec0; color: #ffffff; padding: 5px; border-radius: 4px; margin: 2px; width: 30%;">
        ${translatedTags}
      </span>
    </div>
  </div>
  <br>
  <hr>
  <hr>
  <hr>
  <hr>
`;
      // Create the 'conversationContainer' div
      conversationContainer.id = 'conversationContainer';
      conversationContainer.style.marginTop = '20px';
      conversationContainer.style.overflow = 'auto';
      conversationContainer.style.maxHeight = '700px';
      // Append all child divs to the 'managePanel' div
      managePanel.appendChild(topicSearchContainer);
      managePanel.appendChild(categoryEditor);
      managePanel.appendChild(panelHeader);

      managePanel.appendChild(conversationContainer);

      // Append 'managePanel' to the document body or another container element
      document.body.appendChild(managePanel);

      right_Top_togglePanel.innerHTML = '×';
      right_Top_togglePanel.style.position = 'fixed';
      right_Top_togglePanel.style.fontSize = '30px';
      right_Top_togglePanel.style.top = '20px';
      right_Top_togglePanel.style.right = '20px';
      right_Top_togglePanel.style.cursor = 'pointer';

      managePanel.appendChild(right_Top_togglePanel);
      //controlPanelKit.renderConversations(chatHistory);
      controlPanelKit.addEventListeners();
    },
    updateCategorySelect: function () {
      log("inupdateSelect!");
      categorySelect.innerHTML = '';
      let allCategoriesString = translate("allCategoriesFilter");
      let categories = [allCategoriesString];
      for (let i = 0; i < chatHistory.length; i++) {
        let curChat = chatHistory[i];
        if (curChat.categories) {
          for (let j = 0; j < curChat.categories.length; j++) {
            if (!categories.includes(curChat.categories[j]))
              categories.push(curChat.categories[j]);
          }
        }
      }
      categories.forEach(optionText => {
        let option = document.createElement('option');
        option.text = optionText;
        categorySelect.appendChild(option);
      });
    },
    addEventListeners: function () {

      searchTopicBox.addEventListener("input", controlPanelKit.executeFilter);
      categorySelect.addEventListener('change', controlPanelKit.executeFilter);
      right_Top_togglePanel.addEventListener('click', function () {
        managePanel.style.display = 'none';
        if (states.visualization.thumbnailSvg === "visible") {
          panelToggleButton.style.display = 'block';
        }
      });
    },
    executeFilter: function () {
      const selectedCategory = categorySelect.value.toLowerCase(); // 注意转换为小写
      const query = searchTopicBox.value.toLowerCase();
      filteredConversations = chatHistory.filter(conv => {
        return conv.topic.toLowerCase().includes(query);
      });
      if (selectedCategory !== translate('allCategoriesFilter').toLowerCase()) {
        filteredConversations = filteredConversations.filter(conv => {
          return conv.categories.some(category => category.toLowerCase().includes(selectedCategory));
        });
        controlPanelKit.renderConversations(filteredConversations);
        log(`User selected category: ${selectedCategory}, filteredConversations:`, filteredConversations);
      } else {
        controlPanelKit.renderConversations(filteredConversations);
        log(`User selected category: ${selectedCategory}, filteredConversations:`, filteredConversations);
      }
    },
    toggleHistoryPanel: function () {
      managePanel.style.display = 'block';
      panelToggleButton.style.display = 'none';
    },
    renderConversations: function (conversations) {
      log("in_renderConv:", conversations);
      conversations.sort((a, b) => {
        // 比较isWholeConversationBookMarked字段
        if (a.isWholeConversationBookMarked && !b.isWholeConversationBookMarked) {
          return -1; // a排在b前面
        }
        if (!a.isWholeConversationBookMarked && b.isWholeConversationBookMarked) {
          return 1; // b排在a前面
        }

        // 如果isWholeConversationBookMarked字段相同,则比较timestamp字段
        if (a.timestamp > b.timestamp) {
          return -1; // a排在b前面
        }
        if (a.timestamp < b.timestamp) {
          return 1; // b排在a前面
        }

        return 0; // a和b相等,保持原来的顺序(稳定排序)
      });

// 现在chatHistory数组已经根据你的规则进行了排序。

      //log("in render:");
      const conversationContainer = document.getElementById("conversationContainer");
      conversationContainer.innerHTML = ""; // Clear existing conversations
      let conversation_num = 0;
      conversations.forEach(conv => {
        conversation_num++;
        //const safeId = conv.id.replace(/-/g, '');
        const safeId = conv.id;

        const convElem = document.createElement("div");
        convElem.classList.add("conversation");
        convElem.style.display = "flex";
        convElem.style.justifyContent = "space-between";


        // Create action buttons container
        const actionContainer = document.createElement("div");
        actionContainer.classList.add("conversation-actions");

        // Add topic
        const topicElem = document.createElement("h3");
        topicElem.classList.add("topic");
        const topicElem_2 = document.createElement("h3");
        topicElem_2.innerText = conversation_num.toString() + ' ';
        topicElem.innerText = conv.topic;
        // 创建包含标题和按钮的一个容器
        const topicContainer = document.createElement("div");
        topicContainer.classList.add('topicContainer');
        topicContainer.appendChild(topicElem_2);
        topicContainer.appendChild(topicElem);

        // 创建包含修改和删除按钮的一个容器
        const optionsContainer = document.createElement("div");
        optionsContainer.id = "optionsContainer_" + safeId;
        optionsContainer.classList.add("optionsContainer");

        optionsContainer.innerHTML = conv.isWholeConversationBookMarked === false ?
          `<div class="flex visible">
  <button class="p-1 hover:text-token-text-primary"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path></svg>
  </button>
  <button class="p-1 hover:text-token-text-primary"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>
  </button>
  <button class="p-1 hover:text-token-text-primary"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M14.851 11.923c-.179-.641-.521-1.246-1.025-1.749-1.562-1.562-4.095-1.563-5.657 0l-4.998 4.998c-1.562 1.563-1.563 4.095 0 5.657 1.562 1.563 4.096 1.561 5.656 0l3.842-3.841.333.009c.404 0 .802-.04 1.189-.117l-4.657 4.656c-.975.976-2.255 1.464-3.535 1.464-1.28 0-2.56-.488-3.535-1.464-1.952-1.951-1.952-5.12 0-7.071l4.998-4.998c.975-.976 2.256-1.464 3.536-1.464 1.279 0 2.56.488 3.535 1.464.493.493.861 1.063 1.105 1.672l-.787.784zm-5.703.147c.178.643.521 1.25 1.026 1.756 1.562 1.563 4.096 1.561 5.656 0l4.999-4.998c1.563-1.562 1.563-4.095 0-5.657-1.562-1.562-4.095-1.563-5.657 0l-3.841 3.841-.333-.009c-.404 0-.802.04-1.189.117l4.656-4.656c.975-.976 2.256-1.464 3.536-1.464 1.279 0 2.56.488 3.535 1.464 1.951 1.951 1.951 5.119 0 7.071l-4.999 4.998c-.975.976-2.255 1.464-3.535 1.464-1.28 0-2.56-.488-3.535-1.464-.494-.495-.863-1.067-1.107-1.678l.788-.785z"/></svg>
  </button>
  <button class="p-1 hover:text-token-text-primary" style="cursor: pointer;"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M17.5 14H19C20.1046 14 21 13.1046 21 12V5C21 3.89543 20.1046 3 19 3H12C10.8954 3 10 3.89543 10 5V6.5M5 10H12C13.1046 10 14 10.8954 14 12V19C14 20.1046 13.1046 21 12 21H5C3.89543 21 3 20.1046 3 19V12C3 10.8954 3.89543 10 5 10Z"></path></svg>
  </button>
  <button class="p-1 hover:text-token-text-primary" style="cursor: pointer;"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M9 4.45962C9.91153 4.16968 10.9104 4 12 4C16.1819 4 19.028 6.49956 20.7251 8.70433C21.575 9.80853 22 10.3606 22 12C22 13.6394 21.575 14.1915 20.7251 15.2957C19.028 17.5004 16.1819 20 12 20C7.81811 20 4.97196 17.5004 3.27489 15.2957C2.42496 14.1915 2 13.6394 2 12C2 10.3606 2.42496 9.80853 3.27489 8.70433C3.75612 8.07914 4.32973 7.43025 5 6.82137" stroke-width="1.5" stroke-linecap="round"/>
    <path d="M15 12C15 13.6569 13.6569 15 12 15C10.3431 15 9 13.6569 9 12C9 10.3431 10.3431 9 12 9C13.6569 9 15 10.3431 15 12Z" stroke-width="1.5"/>
  </svg>
  </button>
  <button class="p-1 hover:text-token-text-primary" style="cursor: pointer;"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
    <path d="M11.2691 4.41115C11.5006 3.89177 11.6164 3.63208 11.7776 3.55211C11.9176 3.48263 12.082 3.48263 12.222 3.55211C12.3832 3.63208 12.499 3.89177 12.7305 4.41115L14.5745 8.54808C14.643 8.70162 14.6772 8.77839 14.7302 8.83718C14.777 8.8892 14.8343 8.93081 14.8982 8.95929C14.9705 8.99149 15.0541 9.00031 15.2213 9.01795L19.7256 9.49336C20.2911 9.55304 20.5738 9.58288 20.6997 9.71147C20.809 9.82316 20.8598 9.97956 20.837 10.1342C20.8108 10.3122 20.5996 10.5025 20.1772 10.8832L16.8125 13.9154C16.6877 14.0279 16.6252 14.0842 16.5857 14.1527C16.5507 14.2134 16.5288 14.2807 16.5215 14.3503C16.5132 14.429 16.5306 14.5112 16.5655 14.6757L17.5053 19.1064C17.6233 19.6627 17.6823 19.9408 17.5989 20.1002C17.5264 20.2388 17.3934 20.3354 17.2393 20.3615C17.0619 20.3915 16.8156 20.2495 16.323 19.9654L12.3995 17.7024C12.2539 17.6184 12.1811 17.5765 12.1037 17.56C12.0352 17.5455 11.9644 17.5455 11.8959 17.56C11.8185 17.5765 11.7457 17.6184 11.6001 17.7024L7.67662 19.9654C7.18404 20.2495 6.93775 20.3915 6.76034 20.3615C6.60623 20.3354 6.47319 20.2388 6.40075 20.1002C6.31736 19.9408 6.37635 19.6627 6.49434 19.1064L7.4341 14.6757C7.46898 14.5112 7.48642 14.429 7.47814 14.3503C7.47081 14.2807 7.44894 14.2134 7.41394 14.1527C7.37439 14.0842 7.31195 14.0279 7.18708 13.9154L3.82246 10.8832C3.40005 10.5025 3.18884 10.3122 3.16258 10.1342C3.13978 9.97956 3.19059 9.82316 3.29993 9.71147C3.42581 9.58288 3.70856 9.55304 4.27406 9.49336L8.77835 9.01795C8.94553 9.00031 9.02911 8.99149 9.10139 8.95929C9.16534 8.93081 9.2226 8.8892 9.26946 8.83718C9.32241 8.77839 9.35663 8.70162 9.42508 8.54808L11.2691 4.41115Z" stroke="#fe1616" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"></path></svg>
  </button>
</div>
` :
          `
<div class="flex visible">
  <button class="p-1 hover:text-token-text-primary"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path></svg>
  </button>
  <button class="p-1 hover:text-token-text-primary"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>
  </button>
  <button class="p-1 hover:text-token-text-primary"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M14.851 11.923c-.179-.641-.521-1.246-1.025-1.749-1.562-1.562-4.095-1.563-5.657 0l-4.998 4.998c-1.562 1.563-1.563 4.095 0 5.657 1.562 1.563 4.096 1.561 5.656 0l3.842-3.841.333.009c.404 0 .802-.04 1.189-.117l-4.657 4.656c-.975.976-2.255 1.464-3.535 1.464-1.28 0-2.56-.488-3.535-1.464-1.952-1.951-1.952-5.12 0-7.071l4.998-4.998c.975-.976 2.256-1.464 3.536-1.464 1.279 0 2.56.488 3.535 1.464.493.493.861 1.063 1.105 1.672l-.787.784zm-5.703.147c.178.643.521 1.25 1.026 1.756 1.562 1.563 4.096 1.561 5.656 0l4.999-4.998c1.563-1.562 1.563-4.095 0-5.657-1.562-1.562-4.095-1.563-5.657 0l-3.841 3.841-.333-.009c-.404 0-.802.04-1.189.117l4.656-4.656c.975-.976 2.256-1.464 3.536-1.464 1.279 0 2.56.488 3.535 1.464 1.951 1.951 1.951 5.119 0 7.071l-4.999 4.998c-.975.976-2.255 1.464-3.535 1.464-1.28 0-2.56-.488-3.535-1.464-.494-.495-.863-1.067-1.107-1.678l.788-.785z"/></svg>
  </button>
  <button class="p-1 hover:text-token-text-primary" style="cursor: pointer;"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M17.5 14H19C20.1046 14 21 13.1046 21 12V5C21 3.89543 20.1046 3 19 3H12C10.8954 3 10 3.89543 10 5V6.5M5 10H12C13.1046 10 14 10.8954 14 12V19C14 20.1046 13.1046 21 12 21H5C3.89543 21 3 20.1046 3 19V12C3 10.8954 3.89543 10 5 10Z"></path></svg>
  </button>
  <button class="p-1 hover:text-token-text-primary" style="cursor: pointer;"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M9 4.45962C9.91153 4.16968 10.9104 4 12 4C16.1819 4 19.028 6.49956 20.7251 8.70433C21.575 9.80853 22 10.3606 22 12C22 13.6394 21.575 14.1915 20.7251 15.2957C19.028 17.5004 16.1819 20 12 20C7.81811 20 4.97196 17.5004 3.27489 15.2957C2.42496 14.1915 2 13.6394 2 12C2 10.3606 2.42496 9.80853 3.27489 8.70433C3.75612 8.07914 4.32973 7.43025 5 6.82137" stroke-width="1.5" stroke-linecap="round"/>
    <path d="M15 12C15 13.6569 13.6569 15 12 15C10.3431 15 9 13.6569 9 12C9 10.3431 10.3431 9 12 9C13.6569 9 15 10.3431 15 12Z" stroke-width="1.5"/>
  </svg>
  </button>
  <button class="p-1 hover:text-token-text-primary" style="cursor: pointer;"><svg stroke="currentColor" fill="none" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="icon-sm" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
    <path d="M11.2691 4.41115C11.5006 3.89177 11.6164 3.63208 11.7776 3.55211C11.9176 3.48263 12.082 3.48263 12.222 3.55211C12.3832 3.63208 12.499 3.89177 12.7305 4.41115L14.5745 8.54808C14.643 8.70162 14.6772 8.77839 14.7302 8.83718C14.777 8.8892 14.8343 8.93081 14.8982 8.95929C14.9705 8.99149 15.0541 9.00031 15.2213 9.01795L19.7256 9.49336C20.2911 9.55304 20.5738 9.58288 20.6997 9.71147C20.809 9.82316 20.8598 9.97956 20.837 10.1342C20.8108 10.3122 20.5996 10.5025 20.1772 10.8832L16.8125 13.9154C16.6877 14.0279 16.6252 14.0842 16.5857 14.1527C16.5507 14.2134 16.5288 14.2807 16.5215 14.3503C16.5132 14.429 16.5306 14.5112 16.5655 14.6757L17.5053 19.1064C17.6233 19.6627 17.6823 19.9408 17.5989 20.1002C17.5264 20.2388 17.3934 20.3354 17.2393 20.3615C17.0619 20.3915 16.8156 20.2495 16.323 19.9654L12.3995 17.7024C12.2539 17.6184 12.1811 17.5765 12.1037 17.56C12.0352 17.5455 11.9644 17.5455 11.8959 17.56C11.8185 17.5765 11.7457 17.6184 11.6001 17.7024L7.67662 19.9654C7.18404 20.2495 6.93775 20.3915 6.76034 20.3615C6.60623 20.3354 6.47319 20.2388 6.40075 20.1002C6.31736 19.9408 6.37635 19.6627 6.49434 19.1064L7.4341 14.6757C7.46898 14.5112 7.48642 14.429 7.47814 14.3503C7.47081 14.2807 7.44894 14.2134 7.41394 14.1527C7.37439 14.0842 7.31195 14.0279 7.18708 13.9154L3.82246 10.8832C3.40005 10.5025 3.18884 10.3122 3.16258 10.1342C3.13978 9.97956 3.19059 9.82316 3.29993 9.71147C3.42581 9.58288 3.70856 9.55304 4.27406 9.49336L8.77835 9.01795C8.94553 9.00031 9.02911 8.99149 9.10139 8.95929C9.16534 8.93081 9.2226 8.8892 9.26946 8.83718C9.32241 8.77839 9.35663 8.70162 9.42508 8.54808L11.2691 4.41115Z" stroke="#fe1616" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="#fe1616"></path></svg>
  </button>
</div>
`;
        let buttons = optionsContainer.querySelectorAll('button');
        for (let i = 0; i < buttons.length; i++) {
          buttons[i].style.cursor = 'pointer';
        }

        buttons[0].addEventListener('click', function () {
          let topicElem = convElem.querySelector(".topic");
          createTopicInput(conv, topicElem);
        })
        buttons[1].addEventListener('click', function () {
          log("button1 clicked");
          settingsKit.handleTwoTypesOfDeleteConversationData(conv.link, true)
        })
        buttons[2].addEventListener('click', function () {
          window.open(conv.link, '_blank');
          // let templink = document.createElement('a');
          // templink.href = conv.link;
          // templink.target = '_blank';
          // document.body.appendChild(templink);
          // templink.click();
          // templink.remove();
        })
        buttons[3].addEventListener('click', function () {
          nodesInAndOutKit.copyToClipboard(conv.link);
          log("link_copied!");
        })
        buttons[4].addEventListener('click', function () {
          let seekURL = urlOperations.changeBetweenChattreeWithCAndOneMeansChattreeToC(0, conv.link);
          window.open(seekURL, '_blank');

          log("deleted_information_to_load!");
        })
        buttons[5].addEventListener('click', function () {
          return handleBookMarks(conv, buttons[5]);
        });

        async function handleBookMarks(conv, button) {
          //log("beforeClickIsBookMarked:", JSON.stringify(conv));
          //await sleep(1000);

          const path = button.querySelector('path');
          const currentFill = path.getAttribute('fill');
          const newFill = currentFill === 'none' ? '#fe1616' : 'none';
          path.setAttribute('fill', newFill);
          let shouldBeBookMarked = currentFill === 'none';
          dbOperations.changeWholeConversationBookMarked(conv.link, shouldBeBookMarked);
          //conv.isWholeConversationBookMarked = currentFill === 'none';
          // await sleep(1000);
          // log("afterClickIsBookMarked:", JSON.stringify(conv));
          // await sleep(1000);
          // log("afterClickChatHistory:", JSON.stringify(chatHistory));
          // await sleep(1000);
          // log("afterClickChatHistory:", JSON.stringify(filteredConversations));
        }

        function createTopicInput(conv, replacedtopicElem) {
          const input = document.createElement("input");
          input.type = "text";
          input.value = replacedtopicElem.innerText;
          input.addEventListener('blur', function () {
            const value = input.value;
            if (value.trim()) {
              const topicElem = document.createElement("h3");
              topicElem.innerText = value;
              conv.topic = value;
              topicElem.classList.add("topic");
              topicContainer.appendChild(topicElem);
              dbOperations.changeConversationDataTopic(conv.link, value)
            } else {
              const topicElem = document.createElement("h3");
              topicElem.innerText = conv.topic;
              topicElem.classList.add("topic");
              topicContainer.appendChild(topicElem);
            }
            input.remove();
          });
          topicContainer.replaceChild(input, replacedtopicElem);
          input.focus();
        }

        // 向 convElem 添加左和右容器
        topicContainer.style.overflow = 'hidden';

        convElem.appendChild(topicContainer);
        convElem.appendChild(optionsContainer);


        let categoriesContainer = document.createElement('div');
        categoriesContainer.className = 'categoriesContainer';
        categoriesContainer.id = "categoriesContainer_" + safeId;
        conv.categories.forEach(category => {

          // Add category
          const categoryElem = document.createElement("span");
          categoryElem.classList.add("category");
          categoryElem.innerText = `${category}`;


          // Add 'x' button for category
          const catDeleteBtn = document.createElement("span");
          catDeleteBtn.innerHTML = "&times;";
          catDeleteBtn.classList.add("delete-icon");
          catDeleteBtn.onclick = function () {
            deleteClassOrTag("category", categoryElem, conv, category)
            // categoryElem.remove();
            // conv.categories = conv.categories.filter(cat => cat !== category);
          };
          categoryElem.appendChild(catDeleteBtn);
          categoriesContainer.appendChild(categoryElem);

        });
        convElem.appendChild(categoriesContainer);
        // Add '+' button for adding new chatTreeTag or category
        const addClassBtn = document.createElement("span");
        addClassBtn.innerHTML = "+";
        addClassBtn.classList.add("add-icon");
        addClassBtn.onclick = function () {
          createClassOrTagInput(categoriesContainer, "category", addClassBtn, conv);
        };

        categoriesContainer.appendChild(addClassBtn);


        let tagContainers = document.createElement('div');
        tagContainers.className = 'tagContainers';
        tagContainers.id = "tagContainers_" + safeId;

        // Add chatTreeTags
        conv.chatTreeTags.forEach(chatTreeTag => {
          const chatTreeTagElem = document.createElement("span");
          chatTreeTagElem.classList.add("chatTreeTag");
          chatTreeTagElem.innerText = `${chatTreeTag}`;

          // Add 'x' button for chatTreeTag
          const chatTreeTagDeleteBtn = document.createElement("span");
          chatTreeTagDeleteBtn.innerHTML = "&times;";
          chatTreeTagDeleteBtn.classList.add("delete-icon");
          chatTreeTagDeleteBtn.onclick = function () {
            deleteClassOrTag("chatTreeTag", chatTreeTagElem, conv, chatTreeTag)
            // chatTreeTagElem.remove();
            // conv.chatTreeTags = conv.chatTreeTags.filter(tag => tag !== chatTreeTag);
          };
          chatTreeTagElem.appendChild(chatTreeTagDeleteBtn);

          tagContainers.appendChild(chatTreeTagElem);
        });
        convElem.appendChild(tagContainers);

        // Add '+' button for adding new chatTreeTag or category
        const addChatTreeTagBtn = document.createElement("span");
        addChatTreeTagBtn.innerHTML = "+";
        addChatTreeTagBtn.classList.add("add-icon");
        addChatTreeTagBtn.onclick = function () {
          createClassOrTagInput(tagContainers, "chatTreeTag", addChatTreeTagBtn, conv);
        };

        tagContainers.appendChild(addChatTreeTagBtn);

        function deleteClassOrTag(className, deletedTag, conv, value) {
          let operatingURL = conv.link;
          if (className === 'chatTreeTag') {
            dbOperations.addOrDeleteTagOrClassToURL(operatingURL, true, value, false);
            conv.chatTreeTags = conv.chatTreeTags.filter(tag => tag != value);
          } else {
            dbOperations.addOrDeleteTagOrClassToURL(operatingURL, false, value, false);
            conv.categories = conv.categories.filter(tag => tag != value);
          }
          deletedTag.remove();
        }

        function createClassOrTagInput(convElem, className, replacedButton, conv) {
          // let safeId = convElem.id.split("_")[1];
          // let foundObject = chatHistory.find(chat => chat.id === safeId);
          //
          // if (foundObject) {
          //   log("Found the object:", foundObject);
          // } else {
          //   log("Object not found");
          //   return;
          // }
          // log("conv:",conv);
          let operatingURL = conv.link;
          const input = document.createElement("input");
          input.type = "text";
          input.placeholder = `Enter new value`;
          convElem.replaceChild(input, replacedButton);
          input.focus();
          input.addEventListener('blur', function () {
            const value = input.value;
            if (value.trim()) {
              // 创建新的类别或标签
              const newElem = document.createElement("span");
              newElem.classList.add(className); // 使用合适的类名
              newElem.innerText = value;
              convElem.insertBefore(newElem, input);

              const catDeleteBtn = document.createElement("span");
              catDeleteBtn.innerHTML = "&times;";
              catDeleteBtn.classList.add("delete-icon");

              newElem.appendChild(catDeleteBtn);
              if (className === 'chatTreeTag') {
                conv.chatTreeTags.push(value);
                dbOperations.addOrDeleteTagOrClassToURL(operatingURL, true, value, true);
                catDeleteBtn.onclick = function () {
                  deleteClassOrTag(className, newElem, conv, value);
                };

              } else {

                conv.categories.push(value);
                dbOperations.addOrDeleteTagOrClassToURL(operatingURL, false, value, true);
                catDeleteBtn.onclick = function () {
                  deleteClassOrTag(className, newElem, conv, value);
                };
              }
            }

            let addedBtn;

            if (className === 'chatTreeTag') {
              // Add '+' button for adding new chatTreeTag or category
              addedBtn = document.createElement("span");
              addedBtn.innerHTML = "+";
              addedBtn.classList.add("add-icon");
              addedBtn.onclick = function () {
                createClassOrTagInput(convElem, "chatTreeTag", addedBtn, conv);
              }
            } else {
              // Add '+' button for adding new chatTreeTag or category
              addedBtn = document.createElement("span");
              addedBtn.innerHTML = "+";
              addedBtn.classList.add("add-icon");
              addedBtn.onclick = function () {
                createClassOrTagInput(convElem, "category", addedBtn, conv);
              };
            }
            if (input.nextSibling) {
              convElem.insertBefore(addedBtn, input.nextSibling);
            } else {
              convElem.appendChild(addedBtn);
            }
            input.remove();
          });
        }

        conversationContainer.appendChild(convElem);
      });
    },
  };


  const languageKits = {
    init: function (e) {
      log("changeLanguageclick!");
      if (!db) {
        return;
      }

      document.getElementById('search-results-count').innerText = '';

      if (e) {
        log("e.target.value:", e.target.value);
        globalUserLang = e.target.value;
        currentLangPack = getLang(globalUserLang).langPack;
        log("currentLangPack:", currentLangPack);
        languageSelectDiv.innerText = e.target.value;
        // languageContainer.style.display = 'none';
        languageKits.updateUIWithTranslations();
        if (e.target.shouldNotSave) {
          return;
        }
        const newSetting = {id: "userLang", globalUserLang: globalUserLang};
        dbOperations.updateUserSettings(newSetting).then(() => {
          ButtonOperations.showUserNotification(translate("successSavingChanges"));
        }).catch(error => {
          console.error("Error saving Change:", error);
        });
      }
    },
    updateUIWithTranslations: function () {

      // 获取optionsButtons元素
      const optionsButtonsDiv = document.getElementById("optionsButtons");
      controlPanelKit.updateCategorySelect();

      if (!optionsButtonsDiv) {
        console.error("optionsButtonsDiv not found");
        return;
      }

      // 更新单选按钮标签
      const radioLabels = optionsButtonsDiv.querySelectorAll("label");
      const optionsTranslated = [translate("searchInContent"), translate("searchInComments"), translate("searchInBoth")];

      radioLabels.forEach((label, index) => {
        if (optionsTranslated[index]) {
          log("Updating label:", label);
          // 打印所有子节点以找到正确的文本节点索引
          label.childNodes.forEach((child, idx) => {
            log("Child node at index", idx, ":", child);
          });
          // 获取当前label中的文本节点并更新
          const textNode = label.lastChild; // 也许我们可以直接获取最后一个子节点作为文本节点
          if (textNode && textNode.nodeType === Node.TEXT_NODE) {
            textNode.nodeValue = optionsTranslated[index];
          } else {
            console.error("No text node found for label", label);
          }
        }
      });
      const navButtonsElems = optionsButtonsDiv.querySelectorAll("button");
      const navButtonsTranslated = [translate("goToPrevious"), translate("goToNext")];

      navButtonsElems.forEach((button, index) => {
        if (navButtonsTranslated[index]) {
          button.innerText = navButtonsTranslated[index];
        }
      });
      navPanelButton.textContent = translate("openAdminPanel");
      //contentHeader.textContent = translate("nodeDetails");
      commentLabel.innerText = translate("enterComment") + ":";
      cancelButton.innerText = translate("userCommentCancel");
      clearButton.innerText = translate("userCommentClear");
      searchBtn.innerText = translate("searchButton");
      panelToggleButton.innerText = translate("openAdminPanel");
      searchTopicBox.setAttribute("placeholder", translate("searchPlaceholder"));
      searchBox.setAttribute("placeholder", translate("searchPlaceholder"));
      {
        let translatedTitle = translate('conversationTitle');
        let translatedOption = translate('actionOptions');
        let translatedCategory = translate('conversationCategory');
        let translatedTags = translate('conversationTags');

        panelHeader.innerHTML = `
  <br>
  <hr>
  <hr>
  <hr>
  <hr>
  <br>
  <div class="conversation" style="display: flex; justify-content: space-between; align-items: center; background-color: #f3f4f6; padding: 10px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">
    <div class="topic-container" style="flex: 2; text-align: center; display: flex; justify-content: center; flex-wrap: wrap;">
      <span class="category" style="background-color: #83c5be; color: #ffffff; padding: 5px; border-radius: 4px; margin: 2px; width: 40%;">
        ${translatedTitle}
      </span>
    </div>
    <div class="options-container" style="flex: 0.7; text-align: center; background-color: #e2e8f0; padding: 5px; border-radius: 4px;">
      ${translatedOption}
    </div>
    <div class="categories-container" style="flex: 1.5; text-align: center; display: flex; justify-content: center; flex-wrap: wrap;">
      <span class="category" style="background-color: #a0aec0; color: #ffffff; padding: 5px; border-radius: 4px; margin: 2px; width: 30%;">
        ${translatedCategory}
      </span>
    </div>
    <div class="tags-container" style="flex: 1.5; text-align: center; display: flex; justify-content: center; flex-wrap: wrap;">
      <span class="chat-tree-tag" style="background-color: #a0aec0; color: #ffffff; padding: 5px; border-radius: 4px; margin: 2px; width: 30%;">
        ${translatedTags}
      </span>
    </div>
  </div>
  <br>
  <hr>
  <hr>
  <hr>
  <hr>
`;
      }
      {
        if (globalUserLang.startsWith('zh')) {
          feedBackPicDiv.innerHTML = `
<div data-radix-popper-content-wrapper="" style="position: fixed; right: 60px; bottom: 12%; min-width: max-content; z-index: auto; --radix-popper-anchor-width:30px; --radix-popper-anchor-height:33px; --radix-popper-available-width:1091px; --radix-popper-available-height:59px; --radix-popper-transform-origin:83px 13.5px;">
    <div data-side="left" data-align="center" data-state="delayed-open" class="relative rounded-lg border border-black/10 bg-black p-1 shadow-xs transition-opacity" style="--radix-tooltip-content-transform-origin:var(--radix-popper-transform-origin); --radix-tooltip-content-available-width:var(--radix-popper-available-width); --radix-tooltip-content-available-height:var(--radix-popper-available-height); --radix-tooltip-trigger-width:var(--radix-popper-anchor-width); --radix-tooltip-trigger-height:var(--radix-popper-anchor-height);">
        <span class="block text-center font-medium normal-case text-white text-sm mb-2">点击参与问卷调查 (腾讯问卷)</span>
        <div style="width: 300px; height: 270px; margin: auto;">
            <img src="https://cdn.jsdelivr.net/gh/cuizhenzhi/pic_bed/img/000pureCode.png" alt="问卷二维码" style="width: 250px; height:250px; display: block; margin: auto;">
        </div>
        <span style="position: absolute; right: 0px; transform-origin: 100% 0px; transform: translateY(50%) rotate(-90deg) translateX(50%); top: 10.5px;">
        <div width="10" height="5" viewbox="0 0 30 10" preserveaspectratio="none" class="relative top-[-3px] h-2 w-2 rotate-45 transform border-r border-b border-black/10 bg-black shadow-xs" style="display: block;"></div></span>
    </div>
</div>
`;
        } else {
          feedBackPicDiv.innerHTML = `
<div data-radix-popper-content-wrapper="" style="position: fixed; right: 60px; bottom: 7%; min-width: max-content; z-index: auto; --radix-popper-anchor-width:30px; --radix-popper-anchor-height:33px; --radix-popper-available-width:1091px; --radix-popper-available-height:59px; --radix-popper-transform-origin:83px 13.5px;">
    <div data-side="left" data-align="center" data-state="delayed-open" class="relative rounded-lg border border-black/10 bg-black p-1 shadow-xs transition-opacity" style="--radix-tooltip-content-transform-origin:var(--radix-popper-transform-origin); --radix-tooltip-content-available-width:var(--radix-popper-available-width); --radix-tooltip-content-available-height:var(--radix-popper-available-height); --radix-tooltip-trigger-width:var(--radix-popper-anchor-width); --radix-tooltip-trigger-height:var(--radix-popper-anchor-height);">
        <p class="block text-center font-medium normal-case text-white text-sm mb-2">Click to take the survey</br>(Google Forms)</p>
        <div style="width: 300px; height: 260px; margin: auto;">
            <img src="https://cdn.jsdelivr.net/gh/cuizhenzhi/pic_bed/img/google_form.png" alt="Survey Code" style="width: 250px; height: 250px; display: block; margin: auto;">
        </div>
        <span style="position: absolute; right: 0px; transform-origin: 100% 0px; transform: translateY(50%) rotate(-90deg) translateX(50%); top: 10.5px;">
        <div width="10" height="5" viewbox="0 0 30 10" preserveaspectratio="none" class="relative top-[-3px] h-2 w-2 rotate-45 transform border-r border-b border-black/10 bg-black shadow-xs" style="display: block;"></div></span>
    </div>
</div>`;
        }
      }
      WeChatPicDiv.innerHTML =
        `
<div data-radix-popper-content-wrapper="" style="position: fixed; right: 60px; bottom: 13%; min-width: max-content; z-index: auto; --radix-popper-anchor-width:30px; --radix-popper-anchor-height:33px; --radix-popper-available-width:1091px; --radix-popper-available-height:59px; --radix-popper-transform-origin:83px 13.5px;">
    <div data-side="left" data-align="center" data-state="delayed-open" class="relative rounded-lg border border-black/10 bg-black p-1 shadow-xs transition-opacity" style="--radix-tooltip-content-transform-origin:var(--radix-popper-transform-origin); --radix-tooltip-content-available-width:var(--radix-popper-available-width); --radix-tooltip-content-available-height:var(--radix-popper-available-height); --radix-tooltip-trigger-width:var(--radix-popper-anchor-width); --radix-tooltip-trigger-height:var(--radix-popper-anchor-height);">
        <span class="block text-center font-medium normal-case text-white text-sm mb-2">作者微信</span>
        <div style="width: 300px; height: 320px; margin: auto;">
            <img src="https://cdn.jsdelivr.net/gh/cuizhenzhi/pic_bed/img/wechatpic.jpg" alt="问卷二维码" style="width: 250px; height: 300px; display: block; margin: auto;">
        </div>
        <span style="position: absolute; right: 0px; transform-origin: 100% 0px; transform: translateY(50%) rotate(-90deg) translateX(50%); top: 10.5px;">
        <div width="10" height="5" viewbox="0 0 30 10" preserveaspectratio="none" class="relative top-[-3px] h-2 w-2 rotate-45 transform border-r border-b border-black/10 bg-black shadow-xs" style="display: block;"></div></span>
    </div>
</div>
        `
      TencentPicDiv.innerHTML =
        `
<div data-radix-popper-content-wrapper="" style="position: fixed; right: 60px; bottom: 22%; min-width: max-content; z-index: auto; --radix-popper-anchor-width:30px; --radix-popper-anchor-height:33px; --radix-popper-available-width:1091px; --radix-popper-available-height:59px; --radix-popper-transform-origin:83px 13.5px;">    <div data-side="left" data-align="center" data-state="delayed-open" class="relative rounded-lg border border-black/10 bg-black p-1 shadow-xs transition-opacity" style="--radix-tooltip-content-transform-origin:var(--radix-popper-transform-origin); --radix-tooltip-content-available-width:var(--radix-popper-available-width); --radix-tooltip-content-available-height:var(--radix-popper-available-height); --radix-tooltip-trigger-width:var(--radix-popper-anchor-width); --radix-tooltip-trigger-height:var(--radix-popper-anchor-height);">
        <span class="block text-center font-medium normal-case text-white text-sm mb-2">QQ群二维码, 欢迎进群交流</span>
        <div style="width: 300px; height: 300px; margin: auto;">
            <img src="https://cdn.jsdelivr.net/gh/cuizhenzhi/pic_bed/img/QQ_Group_Pic.jpg" alt="问卷二维码" style="width: 250px; height: 300px; display: block; margin: auto;">
        </div>
        <span style="position: absolute; right: 0px; transform-origin: 100% 0px; transform: translateY(50%) rotate(-90deg) translateX(50%); top: 10.5px;">
        <div width="10" height="5" viewbox="0 0 30 10" preserveaspectratio="none" class="relative top-[-3px] h-2 w-2 rotate-45 transform border-r border-b border-black/10 bg-black shadow-xs" style="display: block;"></div></span>
    </div>
</div>
        `
    }
  }

  function fetchRawChatDetails() {
    return new Promise((resolve, reject) => {
      chatgpt.getAccessToken().then(token => {
        log("get Token:",token);
        const xhr = new XMLHttpRequest();
        xhr.open('GET', endpoints.openAI.chats, true);  // `endpoints` object should be accessible from the original script
        xhr.setRequestHeader('Content-Type', 'application/json');
        xhr.setRequestHeader('Authorization', 'Bearer ' + token);
        xhr.onload = () => {
          if (xhr.status !== 200) {
            reject('Request failed. Cannot retrieve chat details.');
          } else {
            resolve(JSON.parse(xhr.responseText));
          }
        };
        xhr.onerror = () => {
          reject('Request error.');
        };
        xhr.send();
      });
    });
  }
  function fetchRawChatMessages(chatId = "") {
    if (chatId === "") {
      return;
    }
    return new Promise((resolve, reject) => {
      chatgpt.getAccessToken().then(token => {
        //token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik1UaEVOVUpHTkVNMVFURTFgoLYTsrmDFyiz9d6coNcS4YLMpkufWtaNRU13UmtGRVFrRXpSZyJ9.eyJodHRwczovL2FwaS5vcGVuYWkuY29tL3Byb2ZpbGUiOnsiZW1haWwiOiJwdGN5ZGRkbjl6QHByaXZhdGVyZWxheS5hcHBsZWlkLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlfSwiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS9hdXRoIjp7InBvaWQiOiJvcmctNlZoeUlReGlGQWlPbW1hVXg5WHlETFgoLYTsrmDFyiz9d6coNcS4YLMpkufWtaFNZkU5emhHcndUdEJ1SGFheGszIn0sImlzcyI6Imh0dHBzOi8vYXV0aDAub3BlbmFpLmNvbS8iLCJzdWIiOiJhcHBsZXwwMDAxMjMuNjQ4NjIzMDE5NmZlNDhiM2I4MTFgoLYTsrmDFyiz9d6coNcS4YLMpkufWta6WyJodHRwczovL2FwaS5vcGVuYWkuY29tL3YxIiwiaHR0cHM6Ly9vcGVuYWkub3BlbmFpLmF1dGgwYXBwLmNvbS91c2VyaW5mbyJdLCJpYXQiOjE2OTc4ODYyNTFgoLYTsrmDFyiz9d6coNcS4YLMpkufWtaiVGRKSWNiZTFgoLYTsrmDFyiz9d6coNcS4YLMpkufWtaiLCJzY29wZSI6Im9wZW5pZCBlbWFpbCBwcm9maWxlIG1vZGVsLnJlYWQgbW9kZWwucmVxdWVzdCBvcmdhbml6YXRpb24ucmVhZCBvcmdhbml6YXRpb24ud3JpdGUgb2ZmbGluZV9hY2Nlc3MifQ.KDeLRadnLd9vzMzbrhJC7u65mwQHY0E7Hd3wOTzJpnAN0qLvzKTGA_tDdmglcW4qIJcZddTFU2Hn7YJt1DC3MSXfZNdC1sqKk0Uj9ep-iodiNCmCo9O1V-9JTh0GcW75BmbSOe5L4hAguJYhAhz2xaGs1zfr6gFBXrqdNxjzKiN-mrKtm4hjkWTdWdf-KZC2ZPun81h2k30x2hsBDxIJIwv8PeAYIZKzQJJdAA9V6X1WXQZX1vI79rp3tnIk-WAJgtW-U1F_UgR8bKRivfgUYSa2NCpCPimnU_LDLvipE8jZMrSTxU8amdb_0Z22YiUvTL-wFWVg6m5IKg-82x-zBQ";
        const xhr = new XMLHttpRequest();
        xhr.open('GET', `${endpoints.openAI.chat}/${chatId}`, true);
        xhr.setRequestHeader('Content-Type', 'application/json');
        xhr.setRequestHeader('Authorization', 'Bearer ' + token);
        xhr.onload = () => {
          if (xhr.status !== 200) {
            reject('Request failed. Cannot retrieve chat messages.');
          } else {
            resolve(JSON.parse(xhr.responseText)); // 返回整个原始数据
          }
        };
        xhr.onerror = () => {
          reject('Request error.');
        };
        xhr.send();
      });
    });
  }

  function fetchAccountDetails() {
    return new Promise((resolve, reject) => {
      const xhr = new XMLHttpRequest();
      xhr.open('GET', endpoints.openAI.session, true);
      xhr.setRequestHeader('Content-Type', 'application/json');
      xhr.onload = () => {
        // if (xhr.status !== 200) return reject('? chatgpt.js >> Request failed. Cannot retrieve access token.');
        // console.info('Token expiration: ' + new Date(JSON.parse(xhr.responseText).expires).toLocaleString().replace(',', ' at'));
        // chatgpt.openAIaccessToken = {
        //   token: JSON.parse(xhr.responseText).accessToken,
        //   expireDate: JSON.parse(xhr.responseText).expires
        // };
        let getResponse = JSON.parse(xhr.responseText);
        return resolve(getResponse); // 直接返回响应文本或数据
        //return resolve(chatgpt.openAIaccessToken.token);
      };
      xhr.send();
    });
  }

  let pictureURL = new Map();

  async function fetchPictureURL(asset_pointer = "file-P2IMiUABxJbug9oU8Vh3sBBZ") {
    return new Promise((resolve, reject) => {
      chatgpt.getAccessToken().then(token => {
        const xhr = new XMLHttpRequest();
        let requestURL = `${domain}/backend-api/files/` + asset_pointer + "/download";
        log("requestURL:", requestURL);
        xhr.open('GET', requestURL, true);
        xhr.setRequestHeader('Content-Type', 'application/json'); // 可以考虑删除此行
        xhr.setRequestHeader('Authorization', 'Bearer ' + token);
        log("XHR sent!");
        xhr.onload = () => {
          if (xhr.status !== 200) {
            reject('Request failed. Cannot retrieve the file.');
          } else {
            let getResponse = JSON.parse(xhr.responseText);
            resolve(getResponse.download_url); // 直接返回响应文本或数据
          }
        };
        xhr.onerror = () => {
          reject('Request error.');
        };
        xhr.send();
      });
    });
  }

  let fileHeader = `${domain}/backend-api/files`;


  fetchPictureURL().then(data => {
    log("fetchURL: ", data);  // 打印原始聊天数据
  }).catch(error => {
    console.error(error);
  });

// 使用上述函数
//   fetchRawChatMessages().then(data => {
//     log(data);  // 打印原始聊天数据
//     const timestamp = data.create_time * 1000;
//     // const timestamp2 = data.update_time * 1000; // 转换为毫秒
//     // const date2 = new Date(timestamp2);
//     // log("update_time:",timestamp2);
//     // log("update_time:",date2);
//     processChatMessage(data);
//   }).catch(error => {
//     console.error(error);
//   });
  let conversationRootNode;

  // function processChatMessage1(data) {
  //   log("In ProcessChatMessage, mapping:", data.mapping);
  //   const tokenMap = new Map(Object.entries(data.mapping));
  //
  //   let accessToken = chatgpt.getAccessToken();
  //   log(tokenMap); // 这会显示一个 Map 对象,它的内容与原始的 mapping 对象相同
  //   fetchAccountDetails().then(userData => {
  //     log("user DATA:", userData);
  //     conversationData.participants.user.name = userData.user.name;
  //     conversationData.participants.user.avatarURL = userData.user.image;
  //   })
  //   conversationData.url = `${domain}/c/` + data.conversation_id;
  //   let gptInfoDiv = document.querySelector('.flex.flex-1.flex-grow.items-center.gap-1.px-2.py-1.text-gray-600');
  //   log("GPTINFOR:", gptInfoDiv);
  //   //log(gptInfoDiv);
  //   if (gptInfoDiv) {
  //     conversationData.participants.gpt.type = gptInfoDiv.innerText;
  //   }
  //   log("conversationData.participants.gpt.type:", conversationData.participants.gpt.type);
  //
  //   let uuid2nodeMap = new Map();
  //   let uuid2pathMap = new Map();
  //   //我们实际上现在在做一个map到map的转换. 也就是说, 从服务器获取的map要先存储下来, 然后根据这个map来自己做一个新的map.
  //   //所以说, 两个map的信息不能丢, 都要同时拿在手里.
  //
  //
  //   for (let [key, value] of tokenMap) {
  //     //log(`Key: ${key}, Value: ${value}`);
  //     if (!value.parent) {
  //       //log("got ParentNode:",key, value);
  //       //得到了头节点/root节点
  //       let tokenRootNode = tokenMap.get(value.children[0]);
  //       // if(tokenRootNode.message && tokenRootNode.message.weight === 0){
  //       //   tokenRootNode = tokenMap.get(tokenRootNode.children[0]);
  //       // }
  //       conversationData.title = data.title;
  //       if (tokenRootNode) {
  //         log("rootNode:", tokenRootNode);
  //         conversationRootNode = new DialogueNode(data.title ? data.title : Default_RootNode_Content, "chatGPT", tokenRootNode.id, -1);
  //         uuid2nodeMap.set(tokenRootNode.id, conversationRootNode)
  //       } else {
  //         log("Creating RootNode Wrong! Exit Updating Map!");
  //         return;
  //       }
  //       // {
  //       //   let testChild = tokenMap.get(tokenRootNode.children[0]);
  //       //   if (testChild.message.author.role === 'system' && testChild.message.metadata.is_user_system_message) {
  //       //     tokenRootNode = testChild;
  //       //   }
  //       // }
  //
  //       function DFSFilterFirstChildren(currentRootNodeId) {
  //         let currentRootNode = tokenMap.get(currentRootNodeId);
  //         console.log("currentRootNode: ", currentRootNode);
  //
  //         // 使用.slice()来复制数组,避免在遍历时修改原数组
  //         currentRootNode.children.slice().forEach(childId => {
  //           let child = tokenMap.get(childId);
  //           if (child && child.message && child.message.author && child.message.author.role.toLowerCase() === "user") {
  //             // 是用户, 直接跳过
  //             //tokenRootNode.children.push(childId);
  //             if (!tokenRootNode.children.includes(childId))
  //               tokenRootNode.children.push(childId);
  //             return;
  //           }
  //
  //           // 不是用户, 处理其子节点
  //           if (child && child.children) {
  //             child.children.forEach(grandsonId => {
  //               let grandson = tokenMap.get(grandsonId);
  //               if (grandson && grandson.message && grandson.message.author && grandson.message.author.role.toLowerCase() === "user") {
  //                 // 是用户, 直接跳过
  //                 if (!tokenRootNode.children.includes(grandsonId))
  //                   tokenRootNode.children.push(grandsonId);
  //                 return;
  //               }
  //               DFSFilterFirstChildren(grandsonId);
  //             });
  //           }
  //
  //           // 从根节点的孩子数组中删除当前非用户节点
  //           let index = tokenRootNode.children.indexOf(childId);
  //           if (index > -1) {
  //             tokenRootNode.children.splice(index, 1);
  //           }
  //         });
  //       }
  //
  //       DFSFilterFirstChildren(tokenRootNode.id)
  //       function DFSUpdateMap(tokenNode, curRootNode, parentID) {
  //         // if(tokenNode.message.weight === 0 && tokenNode.id !== tokenRootNode.id){
  //         //   tokenNode = tokenMap.get(tokenNode.children[0]);
  //         // }
  //
  //         for (let i = 0; i < tokenNode.children.length; i++) {
  //           let child = tokenMap.get(tokenNode.children[i]);
  //           // if(child.message && child.message.metadata && child.message.metadata.is_visually_hidden_from_conversation){
  //           //   continue;
  //           // }
  //
  //           // if(tokenRootNode === tokenNode && child.message.author.role.toLowerCase() !== "user"){
  //           //   //将这个空闲节点的孩子节点设置为父节点的孩子, 并且从父节点孩子数组中删除这个节点
  //           //   child.children.forEach(grandson=>{
  //           //     tokenRootNode.children.push(grandson.id)
  //           //   })
  //           //   tokenRootNode.children.filter(anychild=>anychild.id!=child.id);
  //           //   DFSUpdateMap(tokenNode, curRootNode, parentID);
  //           // }
  //           let a_node_content = [];
  //           let nodeID = child.id;
  //           let parent = child.parent
  //           if (child.message.author.role.toLowerCase() !== "user") {
  //             updateChildContent(child);
  //             a_node_content.push(child.message.content);
  //             let grandSon;
  //             while (child.message.author.role.toLowerCase() !== "user" && child.children.length > 0) {
  //               grandSon = tokenMap.get(child.children[0]);
  //               if (grandSon.message.author.role.toLowerCase() === "user")
  //                 break;
  //               updateChildContent(grandSon);
  //               a_node_content.push(grandSon.message.content);
  //               child = grandSon;
  //             }
  //             if (child.message.author.role.toLowerCase() === "user") {
  //               child = tokenMap.get(child.parent);
  //             }
  //           } else {
  //             updateChildContent(child);
  //             a_node_content.push(child.message.content);
  //           }
  //           let type = child.message.author.role.toLowerCase() === "user" ? "用户" : "chatGPT";
  //           let newDialogueNode = new DialogueNode(a_node_content, type, nodeID, parentID);
  //           curRootNode.children.push(newDialogueNode);
  //           uuid2nodeMap.set(child.id, newDialogueNode);
  //           if (child.children.length > 0) {
  //             DFSUpdateMap(child, newDialogueNode, nodeID);
  //           }
  //         }
  //
  //       }
  //
  //       function updateChildContent(child) {
  //         child.message.content.author = child.message.author;
  //         child.message.content.create_time = child.message.create_time;
  //         child.message.content.metadata = child.message.metadata;
  //         child.message.content.recipient = child.message.recipient;
  //         child.message.content.status = child.message.status;
  //         child.message.content.weight = child.message.weight;
  //       }
  //
  //
  //       DFSUpdateMap(tokenRootNode, conversationRootNode, tokenRootNode.id);
  //
  //
  //       uuid2pathMap.set(conversationRootNode.uuid, "");
  //       DFSUpdatePathMap(conversationRootNode, [], uuid2pathMap);
  //
  //
  //       log("uuid2nodeMap:", uuid2nodeMap);
  //       log("uuid2pathMap:", uuid2pathMap);
  //       root = d3.hierarchy(conversationRootNode);
  //       const widthPerNode = 30;
  //       const heightPerNode = 30;
  //       treeLayout = d3.tree().nodeSize([widthPerNode, heightPerNode]);
  //       treeLayout(root);
  //       drawMainSVG();
  //       break;
  //     }
  //   }
  //
  //   //const arrayOfEntries = [...mappingMap];
  //   //log("array:",arrayOfEntries);
  //   return {
  //     uuid2nodeMap: uuid2nodeMap,
  //     uuid2pathMap: uuid2pathMap
  //   };
  //
  // }
  /**
   * 主处理函数,用于处理聊天消息。
   * @param {Object} data - 包含消息映射和其他聊天相关数据的对象。
   */
  function processChatMessage(data) {
    log("In ProcessChatMessage, mapping:", data.mapping);
    const tokenMap = new Map(Object.entries(data.mapping));

    let accessToken = chatgpt.getAccessToken();
    log(tokenMap); // 显示映射对象,以便于调试
    fetchAccountDetails().then(userData => {
      log("user DATA:", userData);
      conversationData.participants.user.name = userData.user.name;
      conversationData.participants.user.avatarURL = userData.user.image;
    });
    conversationData.url = `${domain}/c/` + data.conversation_id;
    let gptInfoDiv = document.querySelector('.flex.flex-1.flex-grow.items-center.gap-1.px-2.py-1.text-gray-600');
    log("GPTINFOR:", gptInfoDiv);
    if (gptInfoDiv) {
      conversationData.participants.gpt.type = gptInfoDiv.innerText;
    }
    log("conversationData.participants.gpt.type:", conversationData.participants.gpt.type);

    let uuid2nodeMap = new Map();
    let uuid2pathMap = new Map();

    for (let [key, value] of tokenMap) {
      if (!value.parent) {
        let tokenRootNode = tokenMap.get(value.children[0]);
        conversationData.title = data.title;
        if (tokenRootNode) {
          log("rootNode:", tokenRootNode);
          conversationRootNode = new DialogueNode(data.title ? data.title : Default_RootNode_Content, "chatGPT", tokenRootNode.id, -1);
          uuid2nodeMap.set(tokenRootNode.id, conversationRootNode)
        } else {
          log("Creating RootNode Wrong! Exit Updating Map!");
          return;
        }

        /**
         * 深度优先搜索,过滤出第一个子节点,特别关注用户节点。
         * @param {string} currentRootNodeId - 当前根节点的 ID。
         */
        function DFSFilterFirstChildren(currentRootNodeId) {
          let currentRootNode = tokenMap.get(currentRootNodeId);
          console.log("currentRootNode: ", currentRootNode);
          currentRootNode.children.slice().forEach(childId => {
            let child = tokenMap.get(childId);
            if (child && child.message && child.message.author && child.message.author.role.toLowerCase() === "user") {
              if (!tokenRootNode.children.includes(childId))
                tokenRootNode.children.push(childId);
              return;
            }
            if (child && child.children) {
              child.children.forEach(grandsonId => {
                let grandson = tokenMap.get(grandsonId);
                if (grandson && grandson.message && grandson.message.author && grandson.message.author.role.toLowerCase() === "user") {
                  if (!tokenRootNode.children.includes(grandsonId))
                    tokenRootNode.children.push(grandsonId);
                  return;
                }
                DFSFilterFirstChildren(grandsonId);
              });
            }
            let index = tokenRootNode.children.indexOf(childId);
            if (index > -1) {
              tokenRootNode.children.splice(index, 1);
            }
          });
        }

        /**
         * 深度优先搜索更新映射,用于构建对话节点。
         * @param {Object} tokenNode - 当前处理的token节点。
         * @param {Object} curRootNode - 当前的根节点对象。
         * @param {number} parentID - 父节点的ID。
         */
        function DFSUpdateMap(tokenNode, curRootNode, parentID) {
          for (let i = 0; i < tokenNode.children.length; i++) {
            let child = tokenMap.get(tokenNode.children[i]);
            let a_node_content = [];
            let nodeID = child.id;
            let parent = child.parent;
            if (child.message.author.role.toLowerCase() !== "user") {
              updateChildContent(child);
              a_node_content.push(child.message.content);
              let grandSon;
              while (child.message.author.role.toLowerCase() !== "user" && child.children.length > 0) {
                grandSon = tokenMap.get(child.children[0]);
                if (grandSon.message.author.role.toLowerCase() === "user")
                  break;
                updateChildContent(grandSon);
                a_node_content.push(grandSon.message.content);
                child = grandSon;
              }
              if (child.message.author.role.toLowerCase() === "user") {
                child = tokenMap.get(child.parent);
              }
            } else {
              updateChildContent(child);
              a_node_content.push(child.message.content);
            }
            let type = child.message.author.role.toLowerCase() === "user" ? "用户" : "chatGPT";
            let newDialogueNode = new DialogueNode(a_node_content, type, nodeID, parentID);
            curRootNode.children.push(newDialogueNode);
            uuid2nodeMap.set(child.id, newDialogueNode);
            if (child.children.length > 0) {
              DFSUpdateMap(child, newDialogueNode, nodeID);
            }
          }
        }

        /**
         * 更新子节点的内容。
         * @param {Object} child - 子节点对象。
         */
        function updateChildContent(child) {
          child.message.content.author = child.message.author;
          child.message.content.create_time = child.message.create_time;
          child.message.content.metadata = child.message.metadata;
          child.message.content.recipient = child.message.recipient;
          child.message.content.status = child.message.status;
          child.message.content.weight = child.message.weight;
        }

        DFSFilterFirstChildren(tokenRootNode.id);
        DFSUpdateMap(tokenRootNode, conversationRootNode, tokenRootNode.id);

        uuid2pathMap.set(conversationRootNode.uuid, "");
        DFSUpdatePathMap(conversationRootNode, [], uuid2pathMap);

        log("uuid2nodeMap:", uuid2nodeMap);
        log("uuid2pathMap:", uuid2pathMap);
        root = d3.hierarchy(conversationRootNode);
        const widthPerNode = 30;
        const heightPerNode = 30;
        treeLayout = d3.tree().nodeSize([widthPerNode, heightPerNode]);
        treeLayout(root);
        drawMainSVG();
        break;
      }
    }

    return {
      uuid2nodeMap: uuid2nodeMap,
      uuid2pathMap: uuid2pathMap
    };
  }

  function DFSUpdatePathMap(conversationRootNode, fatherPath, uuid2pathMap) {
    if (conversationRootNode.children.length > 0) {
      for (let i = 0; i < conversationRootNode.children.length; i++) {
        let child = conversationRootNode.children[i];
        // 使用 slice() 创建 fatherPath 的副本,并在这个副本上 push 新元素
        let childPath = fatherPath.slice(); // 先复制现有数组
        childPath.push(i + 1); // 然后添加新元素
        //log("passing path:", childPath);
        uuid2pathMap.set(child.uuid, childPath);
        DFSUpdatePathMap(child, childPath, uuid2pathMap); // 传递副本给递归调用
      }
    }
  }

  function DFSUpdatePathMap_1(conversationRootNode, fatherPath, uuid2pathMap) {
    if (conversationRootNode.children.length > 0) {
      for (let i = 0; i < conversationRootNode.children.length; i++) {
        let child = conversationRootNode.children[i];
        // 使用 slice() 创建 fatherPath 的副本,并在这个副本上 push 新元素
        let childPath = fatherPath.slice(); // 先复制现有数组
        childPath.push(i + 1); // 然后添加新元素
        //log("passing path:", childPath);
        let stringPath = arrayToKey(childPath);
        uuid2pathMap.set(child.uuid, stringPath);
        DFSUpdatePathMap_1(child, childPath, uuid2pathMap); // 传递副本给递归调用
      }
    }
  }

  function DFSUpdatePathMap_2(fatherPath, conversationRootNode, path2uuid) {
    //log("in DFSUpdatePathMap_2", fatherPath, conversationRootNode, path2uuid);
    if (conversationRootNode.children.length > 0) {
      for (let i = 0; i < conversationRootNode.children.length; i++) {
        let child = conversationRootNode.children[i];
        //log("the ",i, "child:", child);
        // 使用 slice() 创建 fatherPath 的副本,并在这个副本上 push 新元素
        let childPath = fatherPath.slice(); // 先复制现有数组
        childPath.push(i + 1); // 然后添加新元素
        //log("passing path:", childPath);
        let stringPath = arrayToKey(childPath);
        path2uuid.set(stringPath, child.uuid);
        DFSUpdatePathMap_2(childPath, child, path2uuid); // 传递副本给递归调用
      }
    }
  }

  function conver_to_new_style() {
    log("in conver_to_new_style:");
    log("conversationRootNode:", conversationRootNode);
    let path2uuid = new Map();
    path2uuid.set("", conversationRootNode.uuid);
    path2uuid = new Map();

    log("start DFSUpdatePathMap_2");
    DFSUpdatePathMap_2([], conversationRootNode, path2uuid);
    log("stop DFSUpdatePathMap_2");
    let commentMap = new Map();
    let bookMarkedMap = new Map();

    function transfer_comment(OldRootNode) {
      if (OldRootNode.children.length > 0) {
        for (let i = 0; i < OldRootNode.children.length; i++) {
          let child = OldRootNode.children[i];
          if (child.comment !== "") {
            let path = conversationData.uuid2pathMap.get(child.uuid);
            let newUUID = path2uuid.get(arrayToKey(path));
            commentMap.set(newUUID, child.comment);
            //console.log("comment:",child.comment);
          }
          transfer_comment(child); // 传递副本给递归调用
        }
      }
    }

    //console.log("OldRootNode:",conversationData.rootNode);

    transfer_comment(conversationData.rootNode);
    log("commentMap:", commentMap);
    transfer_bookeMark(conversationData.rootNode);
    log("bookMarkedMap:", bookMarkedMap);


    function transfer_bookeMark(OldRootNode) {
      conversationData.bookMarked.forEach((value, key) => {
        log("conversationData:", conversationData);
        log("conversationData.uuid2pathMap:", conversationData.uuid2pathMap);
        //log(`Key: ${key}, Value: ${value}`);
        let path = conversationData.uuid2pathMap.get(key);
        log("path:", path);
        let newUUID = path2uuid.get(arrayToKey(path));
        log("newUUID:", newUUID);
        bookMarkedMap.set(newUUID, true);
      });
    }

    log("out conver_to_new_style");
    // conversationData.uuid2nodeMap
    conversationData.isNovemberSeventh = true;
    return {
      rootNode: conversationRootNode,
      bookMarked: bookMarkedMap,
      commentMap: commentMap,
    }
  }


  function init() {

    ButtonOperations.createButton();
    controlPanelKit.init();
    ContentKit.init();
    DOMOperations.initDOMOperations();
    settingsKit.init();
    searchKit.init();
    languageKits.init({
      target: {
        value: globalUserLang,
        shouldNotSave: true,
      }
    })
  }


  function main() {
    //ButtonOperations.showUserNotification(translate("chatTreeRunning"));
    if (db) {
      db.close();
    }
    dbOperations.initDatabase().then(() => {
      if (!db) {
        ButtonOperations.showUserNotification(translate("noDatabaseAndCreationFailed"));
        return;
      }
      dbOperations.usedatabase();
      log(LogCategories.SUCCESS, "数据库加载成功!");
      //console.log("database:", db);
      urlOperations.observeTargetChanges();
    }).catch(error => {
      log(LogCategories.ERROR, "Error initializing database:", error);
    });
    init();
  }

  main();
})();