#include #include #include #include #include #include #include #include Global $oErrorHandler = ObjEvent("AutoIt.Error", "COM_ErrHandler") Global $sHistoryDir = "D:\gemiox" Global $sCurrentChatFile = "" Global $bIsDarkMode = False Global $bIsGenerating = False Global $aChatHistory[0][3] Global $iEditIndexRequest = -1 Global $sConfigFile = StringRegExpReplace(@ScriptFullPath, "(?i)\.[^.]*$", ".txt") If Not FileExists($sHistoryDir) Then DirCreate($sHistoryDir) ; =================== TẠO GIAO DIỆN =================== Local $hGUI = GUICreate("Ollama Client Pro - Giao diện Chat UI", 950, 690) ; [Cột trái] Local $idLblHist = GUICtrlCreateLabel("Lịch sử chat (D:\gemiox):", 12, 12, 180, 18) Local $idListHist = GUICtrlCreateList("", 12, 35, 200, 595, BitOR($LBS_NOTIFY, $WS_VSCROLL, $WS_BORDER)) GUICtrlSetFont($idListHist, 10, 400, 0, "Segoe UI") Local $idBtnNewChat = GUICtrlCreateButton("Mới (+)", 12, 642, 60, 30) Local $idBtnDeleteHist = GUICtrlCreateButton("Xóa (-)", 77, 642, 60, 30) Local $idBtnReloadHist = GUICtrlCreateButton("Làm Mới", 142, 642, 70, 30) ; [Cột phải - Hàng 1] MỞ RỘNG Ô CHỌN MODEL TỐI ĐA (Tăng từ 135px lên 225px) Local $idLblModel = GUICtrlCreateLabel("Model:", 230, 15, 40, 20) Local $idComboModel = GUICtrlCreateCombo("", 270, 12, 225, 25, $CBS_DROPDOWNLIST) Local $idBtnRefresh = GUICtrlCreateButton("Quét", 500, 11, 45, 25) Local $idBtnDownload = GUICtrlCreateButton("Tải", 550, 11, 40, 25) Local $idBtnDeleteModel = GUICtrlCreateButton("Xóa", 595, 11, 40, 25) Local $idLblCtx = GUICtrlCreateLabel("Context:", 645, 15, 45, 20) Local $idComboCtx = GUICtrlCreateCombo("4096", 695, 12, 60, 25) GUICtrlSetData($idComboCtx, "2048|8192|16384|32768") Local $idLblZoom = GUICtrlCreateLabel("Zoom:", 765, 15, 35, 20) Global $idComboZoom = GUICtrlCreateCombo("100%", 805, 12, 55, 25, $CBS_DROPDOWNLIST) GUICtrlSetData($idComboZoom, "90%|100%|110%|125%|150%|175%|200%") Local $idBtnTheme = GUICtrlCreateButton("Chế độ Tối", 865, 11, 70, 25) ; [Cột phải - Hàng 2] Local $idChkNoThink = GUICtrlCreateCheckbox("Tắt Thinking ()", 230, 45, 150, 20) Local $idChkWebSearch = GUICtrlCreateCheckbox("Tìm kiếm Online", 390, 45, 130, 20) ; [Cột phải - Hàng 3] KHUNG HIỂN THỊ CHAT (Web Browser) Global $oIE = ObjCreate("Shell.Explorer.2") Local $idBrowser = GUICtrlCreateObj($oIE, 230, 75, 700, 450) ; [Cột phải - Hàng 4] KHUNG NHẬP PROMPT Local $idLblPrompt = GUICtrlCreateLabel("Prompt (Shift+Enter để xuống dòng, Enter để gửi):", 230, 532, 400, 18) Global $idPrompt = GUICtrlCreateEdit("", 230, 555, 700, 80, BitOR($ES_AUTOVSCROLL, $WS_VSCROLL, $ES_MULTILINE)) GUICtrlSetFont($idPrompt, 11, 400, 0, "Segoe UI") ; [Cột phải - Hàng 5] Global $idBtnSend = GUICtrlCreateButton("Gửi Prompt", 230, 642, 90, 30) Global $idBtnStop = GUICtrlCreateButton("Dừng Lại", 325, 642, 90, 30) GUICtrlSetState($idBtnStop, $GUI_DISABLE) Global $idLblStatus = GUICtrlCreateLabel("Trạng thái: Sẵn sàng.", 425, 648, 455, 20) ; --- THIẾT LẬP PHÍM TẮT --- Local $idDummyEnter = GUICtrlCreateDummy() Local $idDummyShiftEnter = GUICtrlCreateDummy() Local $aAccelKeys[2][2] = [["{ENTER}", $idDummyEnter], ["+{ENTER}", $idDummyShiftEnter]] GUISetAccelerators($aAccelKeys) ; Khởi tạo Web HTML $oIE.navigate("about:blank") While $oIE.readyState <> 4 Sleep(10) WEnd InitHTMLContainer() Global $oIEEvents = ObjEvent($oIE, "IEEvent_") ; Gom nhóm controls đổi Theme Global $aLabels[] = [$idLblHist, $idLblModel, $idLblCtx, $idLblZoom, $idLblPrompt, $idLblStatus] Global $aChecks[] = [$idChkNoThink, $idChkWebSearch] Global $aEdits[] = [$idPrompt, $idListHist] Local $sSavedModel = LoadSettings($hGUI, $idBtnTheme, $idComboCtx, $idChkNoThink, $idChkWebSearch) GUISetState(@SW_SHOW, $hGUI) ScanOllamaModels($idComboModel, $idLblStatus, $sSavedModel) RefreshHistoryList($idListHist) ; =================== VÒNG LẶP SỰ KIỆN =================== While 1 If $iEditIndexRequest <> -1 Then EditPreviousPrompt($iEditIndexRequest) $iEditIndexRequest = -1 EndIf Switch GUIGetMsg() Case $GUI_EVENT_CLOSE SaveSettings($idComboModel, $idComboCtx, $idChkNoThink, $idChkWebSearch) ExitLoop Case $idBtnDownload ProcessDownloadModel() Case $idBtnDeleteModel ProcessDeleteModel() Case $idComboZoom ApplyZoom(GUICtrlRead($idComboZoom)) SaveSettings($idComboModel, $idComboCtx, $idChkNoThink, $idChkWebSearch) Case $idDummyShiftEnter Local $hFocus = ControlGetHandle($hGUI, "", ControlGetFocus($hGUI)) If $hFocus = GUICtrlGetHandle($idPrompt) Then _GUICtrlEdit_ReplaceSel($idPrompt, @CRLF) EndIf Case $idDummyEnter Local $hFocus = ControlGetHandle($hGUI, "", ControlGetFocus($hGUI)) If $hFocus = GUICtrlGetHandle($idPrompt) Then ProcessSendPrompt() EndIf Case $idBtnTheme $bIsDarkMode = Not $bIsDarkMode ApplyTheme($hGUI, $idBtnTheme, $bIsDarkMode) SaveSettings($idComboModel, $idComboCtx, $idChkNoThink, $idChkWebSearch) Case $idBtnRefresh ScanOllamaModels($idComboModel, $idLblStatus, GUICtrlRead($idComboModel)) Case $idBtnReloadHist RefreshHistoryList($idListHist) Case $idBtnNewChat $sCurrentChatFile = "" Dim $aChatHistory[0][3] GUICtrlSetData($idPrompt, "") RenderHTML() GUICtrlSetData($idLblStatus, "Trạng thái: Đã bắt đầu phiên chat mới.") Case $idBtnDeleteHist Local $sSelectedFile = GUICtrlRead($idListHist) If $sSelectedFile = "" Then MsgBox(48, "Thông báo", "Vui lòng chọn một lịch sử chat trong danh sách để xóa.") Else Local $sFullPath = $sHistoryDir & "\" & $sSelectedFile If FileExists($sFullPath) Then If MsgBox(36, "Xác nhận xóa", "Bạn có chắc chắn muốn xóa file lịch sử này không?" & @CRLF & @CRLF & $sSelectedFile) = 6 Then FileDelete($sFullPath) If $sCurrentChatFile = $sFullPath Then $sCurrentChatFile = "" Dim $aChatHistory[0][3] RenderHTML() EndIf RefreshHistoryList($idListHist) GUICtrlSetData($idLblStatus, "Trạng thái: Đã xóa file " & $sSelectedFile) EndIf EndIf EndIf Case $idListHist Local $sSelectedFile = GUICtrlRead($idListHist) If $sSelectedFile <> "" Then Local $sFullPath = $sHistoryDir & "\" & $sSelectedFile If FileExists($sFullPath) Then $sCurrentChatFile = $sFullPath LoadChatFromFile($sFullPath) GUICtrlSetData($idLblStatus, "Đang mở: " & $sSelectedFile) EndIf EndIf Case $idBtnSend ProcessSendPrompt() EndSwitch WEnd ; =================== HÀM TẢI (DOWNLOAD/PULL) MODEL =================== Func ProcessDownloadModel() If $bIsGenerating Then Return MsgBox(48, "Thông báo", "Vui lòng đợi AI phản hồi xong trước khi thực hiện tải.") Local $sInput = InputBox("Tải Model Mới / Cập nhật", "Bạn có thể dán nguyên lệnh từ trang chủ Ollama vào đây." & @CRLF & "Ví dụ: ollama run huihui_ai/gemma-4-abliterated:12b", "", "", 450, 160) If @error Or $sInput = "" Then Return Local $sModelName = StringRegExpReplace($sInput, "(?i).*ollama\s+(run|pull)\s+", "") $sModelName = StringStripWS($sModelName, 3) If $sModelName = "" Then Return MsgBox(48, "Lỗi", "Không nhận diện được tên model. Hãy thử lại.") $bIsGenerating = True GUICtrlSetState($idBtnSend, $GUI_DISABLE) GUICtrlSetData($idLblStatus, "Bắt đầu tải [" & $sModelName & "]...") Local $iPID = Run(@ComSpec & ' /c ollama pull "' & $sModelName & '"', "", @SW_HIDE, 8) Local $sLastStatus = "" While ProcessExists($iPID) Local $sChunk = StdoutRead($iPID) If $sChunk <> "" Then Local $aLines = StringRegExp($sChunk, "([^\r\n]+)", 3) If IsArray($aLines) Then $sLastStatus = StringStripWS($aLines[UBound($aLines) - 1], 3) $sLastStatus = StringRegExpReplace($sLastStatus, '\x1b\[[0-9;]*m', '') GUICtrlSetData($idLblStatus, "Đang tải: " & $sLastStatus) EndIf EndIf Local $iMsg = GUIGetMsg() If $iMsg = $GUI_EVENT_CLOSE Then Exit WEnd $bIsGenerating = False GUICtrlSetState($idBtnSend, $GUI_ENABLE) GUICtrlSetData($idLblStatus, "Trạng thái: Hoàn tất tải/cập nhật model [" & $sModelName & "].") ScanOllamaModels($idComboModel, $idLblStatus, $sModelName) EndFunc ; =================== HÀM XÓA (DELETE) MODEL =================== Func ProcessDeleteModel() If $bIsGenerating Then Return MsgBox(48, "Thông báo", "Vui lòng đợi AI phản hồi xong trước khi xóa.") Local $sModel = GUICtrlRead($idComboModel) If $sModel = "" Then Return MsgBox(48, "Lỗi", "Chưa có model nào được chọn để xóa.") If MsgBox(36, "Xác nhận Xóa Model", "Bạn có chắc chắn muốn XÓA VĨNH VIỄN model [" & $sModel & "] khỏi ổ cứng không?") <> 6 Then Return GUICtrlSetData($idLblStatus, "Trạng thái: Đang tiến hành xóa [" & $sModel & "]...") Local $oHTTP = ObjCreate("WinHttp.WinHttpRequest.5.1") $oHTTP.Open("DELETE", "http://localhost:11434/api/delete", False) $oHTTP.SetRequestHeader("Content-Type", "application/json; charset=utf-8") $oHTTP.Send('{"name": "' & $sModel & '"}') If $oHTTP.Status = 200 Then GUICtrlSetData($idLblStatus, "Trạng thái: Đã xóa thành công [" & $sModel & "].") ScanOllamaModels($idComboModel, $idLblStatus) Else GUICtrlSetData($idLblStatus, "Lỗi xóa model: HTTP " & $oHTTP.Status & ". Có thể model đang được sử dụng.") EndIf EndFunc ; =================== HÀM XỬ LÝ ZOOM =================== Func ApplyZoom($sZoomValue) Local $iZoom = Int($sZoomValue) If $iZoom < 50 Then $iZoom = 100 If IsObj($oIE) And IsObj($oIE.document) And IsObj($oIE.document.body) Then $oIE.document.body.style.zoom = $sZoomValue EndIf Local $iFontSize = Round(11 * ($iZoom / 100)) GUICtrlSetFont($idPrompt, $iFontSize, 400, 0, "Segoe UI") EndFunc ; =================== HÀM GỬI PROMPT CHÍNH =================== Func ProcessSendPrompt() If $bIsGenerating Then Return Local $sModel = GUICtrlRead($idComboModel) Local $sPrompt = StringStripWS(GUICtrlRead($idPrompt), 3) Local $iNumCtx = Int(GUICtrlRead($idComboCtx)) If $sModel = "" Or $sPrompt = "" Then Return $bIsGenerating = True GUICtrlSetState($idBtnSend, $GUI_DISABLE) GUICtrlSetState($idBtnStop, $GUI_ENABLE) AddChatEntry("user", $sPrompt, @HOUR & ":" & @MIN & ":" & @SEC) RenderHTML() GUICtrlSetData($idPrompt, "") Local $sFinalPrompt = $sPrompt If BitAND(GUICtrlRead($idChkWebSearch), $GUI_CHECKED) = $GUI_CHECKED Then GUICtrlSetData($idLblStatus, "Trạng thái: Đang tìm kiếm online...") Local $sWebData = PerformWebSearch($sPrompt) If $sWebData <> "" Then $sFinalPrompt = "[THÔNG TIN TỪ INTERNET]:" & @CRLF & $sWebData & @CRLF & "Yêu cầu: " & $sPrompt EndIf EndIf GUICtrlSetData($idLblStatus, "Trạng thái: Đang chờ [" & $sModel & "] trả lời...") Local $hTimer = TimerInit() Local $sResponse = QueryOllama($sModel, $sFinalPrompt, $iNumCtx) Local $fTimeSec = TimerDiff($hTimer) / 1000 Local $sTimeFormatted = StringFormat("%.1f", $fTimeSec) & "s" If BitAND(GUICtrlRead($idChkNoThink), $GUI_CHECKED) = $GUI_CHECKED And $sResponse <> "[Đã bị người dùng hủy]" Then $sResponse = StringRegExpReplace($sResponse, '(?s).*?', '') $sResponse = StringStripWS($sResponse, 3) EndIf AddChatEntry("ai", $sResponse, $sModel & " ⏱ " & $sTimeFormatted) RenderHTML() If $sCurrentChatFile = "" Then $sCurrentChatFile = $sHistoryDir & "\" & @YEAR & @MON & @MDAY & "_" & @HOUR & @MIN & @SEC & "_" & RemoveVietnameseAccents($sPrompt) & ".txt" EndIf SaveCurrentChatToFile() GUICtrlSetData($idLblStatus, "Phản hồi trong " & $sTimeFormatted & ". Đã lưu file lịch sử.") $bIsGenerating = False GUICtrlSetState($idBtnStop, $GUI_DISABLE) GUICtrlSetState($idBtnSend, $GUI_ENABLE) RefreshHistoryList($idListHist) EndFunc ; =================== BẮT SỰ KIỆN GIAO DIỆN WEB =================== Func IEEvent_TitleChange($sText) If StringLeft($sText, 5) = "EDIT_" Then $iEditIndexRequest = Int(StringTrimLeft($sText, 5)) EndIf EndFunc Func IEEvent_BeforeNavigate2($pDisp, ByRef $sURL, ByRef $iFlags, ByRef $sTargetFrameName, ByRef $vPostData, ByRef $sHeaders, ByRef $bCancel) If StringLeft($sURL, 4) = "http" Then $bCancel = True ShellExecute($sURL) EndIf EndFunc ; ================================================================= Func EditPreviousPrompt($iIndex) If $iIndex < 0 Or $iIndex >= UBound($aChatHistory) Then Return GUICtrlSetData($idPrompt, $aChatHistory[$iIndex][1]) ReDim $aChatHistory[$iIndex][3] RenderHTML() SaveCurrentChatToFile() GUICtrlSetData($idLblStatus, "Trạng thái: Đang sửa câu hỏi...") EndFunc Func AddChatEntry($sRole, $sText, $sMeta) Local $iIdx = UBound($aChatHistory) ReDim $aChatHistory[$iIdx + 1][3] $aChatHistory[$iIdx][0] = $sRole $aChatHistory[$iIdx][1] = $sText $aChatHistory[$iIdx][2] = $sMeta EndFunc Func InitHTMLContainer() Local $sBase = "
" $oIE.document.write($sBase) $oIE.document.close() EndFunc Func RenderHTML() Local $sHTML = "" For $i = 0 To UBound($aChatHistory) - 1 Local $sEscapedText = EscapeHTML($aChatHistory[$i][1]) If $aChatHistory[$i][0] = "user" Then $sHTML &= "
" & $sEscapedText Local $sOnClick = "document.title='EDIT_" & $i & "'; setTimeout(function(){document.title='Chat';}, 100);" $sHTML &= "
✏️ Sửa câu hỏi
" Else $sHTML &= "
" & $sEscapedText & "
" If $aChatHistory[$i][2] <> "" Then $sHTML &= "
" & $aChatHistory[$i][2] & "
" EndIf $sHTML &= "
" EndIf Next $oIE.document.getElementById("content").innerHTML = $sHTML $oIE.document.body.className = $bIsDarkMode ? "dark" : "light" ApplyZoom(GUICtrlRead($idComboZoom)) $oIE.document.parentWindow.scrollTo(0, 99999) EndFunc Func EscapeHTML($sString) $sString = StringReplace($sString, "&", "&") $sString = StringReplace($sString, "<", "<") $sString = StringReplace($sString, ">", ">") $sString = StringReplace($sString, '"', """) Return $sString EndFunc ; =================== XỬ LÝ API VÀ FILE =================== Func SaveCurrentChatToFile() If $sCurrentChatFile = "" Then Return Local $sData = "" For $i = 0 To UBound($aChatHistory) - 1 If $aChatHistory[$i][0] = "user" Then $sData &= "=== [USER - " & $aChatHistory[$i][2] & "] ===" & @CRLF & $aChatHistory[$i][1] & @CRLF & @CRLF Else $sData &= "=== [" & $aChatHistory[$i][2] & "] ===" & @CRLF & $aChatHistory[$i][1] & @CRLF & @CRLF & "----------------------------------------" & @CRLF & @CRLF EndIf Next Local $hFile = FileOpen($sCurrentChatFile, $FO_OVERWRITE + $FO_UTF8_NOBOM) FileWrite($hFile, $sData) FileClose($hFile) EndFunc Func LoadChatFromFile($sPath) Dim $aChatHistory[0][3] Local $sContent = FileRead(FileOpen($sPath, $FO_READ + $FO_UTF8_NOBOM)) Local $aBlocks = StringSplit($sContent, "----------------------------------------", 1) For $i = 1 To $aBlocks[0] Local $sBlock = StringStripWS($aBlocks[$i], 3) If $sBlock = "" Then ContinueLoop Local $aUser = StringRegExp($sBlock, '(?s)=== \[USER - (.*?)\] ===\r?\n(.*?)(?=\r?\n=== \[|$)', 1) Local $aAI = StringRegExp($sBlock, '(?s)=== \[(?!USER)(.*?)\] ===\r?\n(.*)', 1) If IsArray($aUser) Then AddChatEntry("user", StringStripWS($aUser[1], 3), $aUser[0]) If IsArray($aAI) Then AddChatEntry("ai", StringStripWS($aAI[1], 3), $aAI[0]) Next RenderHTML() EndFunc Func QueryOllama($sModel, $sPrompt, $iNumCtx) Local $oHTTP = ObjCreate("WinHttp.WinHttpRequest.5.1") If Not IsObj($oHTTP) Then Return "Lỗi: Không khởi tạo được WinHttp." $sPrompt = StringReplace(StringReplace(StringReplace(StringReplace($sPrompt, '\', '\\'), '"', '\"'), @CRLF, '\n'), @CR, '\n') Local $sPayload = '{"model": "' & $sModel & '", "prompt": "' & $sPrompt & '", "stream": false, "options": {"num_ctx": ' & $iNumCtx & '}}' $oHTTP.Open("POST", "http://localhost:11434/api/generate", True) $oHTTP.SetRequestHeader("Content-Type", "application/json; charset=utf-8") $oHTTP.SetTimeouts(5000, 5000, 30000, 240000) $oHTTP.Send(StringToBinary($sPayload, 4)) Local $iTick = TimerInit(), $iDots = 0 While 1 If $oHTTP.WaitForResponse(0.05) Then ExitLoop If TimerDiff($iTick) > 500 Then $iTick = TimerInit() $iDots = Mod($iDots + 1, 4) GUICtrlSetData($idLblStatus, "Trạng thái: Đang chờ [" & $sModel & "] trả lời" & StringLeft("...", $iDots)) EndIf Local $iMsg = GUIGetMsg() If $iMsg = $GUI_EVENT_CLOSE Then Exit If $iMsg = $idBtnStop Then $oHTTP.Abort() Return "[Đã bị người dùng hủy]" EndIf WEnd If $oHTTP.Status <> 200 Then Return "Lỗi HTTP " & $oHTTP.Status Local $aMatch = StringRegExp(BinaryToString($oHTTP.ResponseBody, 4), '(?s)"response"\s*:\s*"((?:[^"\\]|\\.)*)"', 1) If @error Then Return "Lỗi JSON." Return StringReplace(StringReplace(StringReplace($aMatch[0], '\n', @CRLF), '\"', '"'), '\\', '\') EndFunc Func PerformWebSearch($sQuery) Local $oHTTP = ObjCreate("WinHttp.WinHttpRequest.5.1") $oHTTP.Open("GET", "https://html.duckduckgo.com/html/?q=" & $sQuery, True) $oHTTP.SetRequestHeader("User-Agent", "Mozilla/5.0") $oHTTP.Send() While 1 If $oHTTP.WaitForResponse(0.05) Then ExitLoop Local $iMsg = GUIGetMsg() If $iMsg = $GUI_EVENT_CLOSE Then Exit If $iMsg = $idBtnStop Then $oHTTP.Abort() Return "" EndIf WEnd Local $aSnippets = StringRegExp(BinaryToString($oHTTP.ResponseBody, 4), '(?s) 3) ? 2 : UBound($aSnippets) - 1) $sResult &= "- " & StringStripWS(StringRegExpReplace($aSnippets[$i], '<[^>]+>', ''), 3) & @CRLF Next Return StringReplace(StringReplace($sResult, '"', '"'), ''', "'") EndFunc Func SaveSettings($iCtrlModel, $iCtrlCtx, $iCtrlNoThink, $iCtrlWebSearch) IniWrite($sConfigFile, "Settings", "Model", GUICtrlRead($iCtrlModel)) IniWrite($sConfigFile, "Settings", "Context", GUICtrlRead($iCtrlCtx)) IniWrite($sConfigFile, "Settings", "DarkMode", $bIsDarkMode ? "1" : "0") IniWrite($sConfigFile, "Settings", "NoThink", (BitAND(GUICtrlRead($iCtrlNoThink), $GUI_CHECKED) = $GUI_CHECKED) ? "1" : "0") IniWrite($sConfigFile, "Settings", "WebSearch", (BitAND(GUICtrlRead($iCtrlWebSearch), $GUI_CHECKED) = $GUI_CHECKED) ? "1" : "0") IniWrite($sConfigFile, "Settings", "Zoom", GUICtrlRead($idComboZoom)) EndFunc Func LoadSettings($hWindow, $iBtnTheme, $iCtrlCtx, $iCtrlNoThink, $iCtrlWebSearch) If Not FileExists($sConfigFile) Then Return "" GUICtrlSetData($iCtrlCtx, IniRead($sConfigFile, "Settings", "Context", "4096")) GUICtrlSetState($iCtrlNoThink, IniRead($sConfigFile, "Settings", "NoThink", "0") = "1" ? $GUI_CHECKED : $GUI_UNCHECKED) GUICtrlSetState($iCtrlWebSearch, IniRead($sConfigFile, "Settings", "WebSearch", "0") = "1" ? $GUI_CHECKED : $GUI_UNCHECKED) $bIsDarkMode = (IniRead($sConfigFile, "Settings", "DarkMode", "0") = "1") Local $sZoom = IniRead($sConfigFile, "Settings", "Zoom", "100%") GUICtrlSetData($idComboZoom, $sZoom) ApplyZoom($sZoom) ApplyTheme($hWindow, $iBtnTheme, $bIsDarkMode) Return IniRead($sConfigFile, "Settings", "Model", "") EndFunc Func RemoveVietnameseAccents($sText) $sText = StringRegExpReplace(StringLower($sText), "(?i)(à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ)", "a") $sText = StringRegExpReplace($sText, "(?i)(è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ)", "e") $sText = StringRegExpReplace($sText, "(?i)(ì|í|ị|ỉ|ĩ)", "i") $sText = StringRegExpReplace($sText, "(?i)(ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ)", "o") $sText = StringRegExpReplace($sText, "(?i)(ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ)", "u") $sText = StringRegExpReplace($sText, "(?i)(ỳ|ý|ỵ|ỷ|ỹ)", "y") $sText = StringRegExpReplace($sText, "(?i)đ", "d") Return StringLeft(StringRegExpReplace($sText, "[^a-z0-9]+", "_"), 35) EndFunc Func ScanOllamaModels($iCtrlID, $iStatusCtrl, $sPreferredModel = "") Local $oHTTP = ObjCreate("WinHttp.WinHttpRequest.5.1") If Not IsObj($oHTTP) Then Return False $oHTTP.Open("GET", "http://localhost:11434/api/tags", True) $oHTTP.Send() While 1 If $oHTTP.WaitForResponse(0.05) Then ExitLoop If GUIGetMsg() = $GUI_EVENT_CLOSE Then Exit WEnd Local $aNames = StringRegExp(BinaryToString($oHTTP.ResponseBody, 4), '"name"\s*:\s*"([^"]+)"', 3) If @error Then Return False Local $sComboData = "", $sDefaultSelect = $aNames[0] For $i = 0 To UBound($aNames) - 1 $sComboData &= "|" & $aNames[$i] If $sPreferredModel = $aNames[$i] Then $sDefaultSelect = $sPreferredModel Next GUICtrlSetData($iCtrlID, $sComboData, $sDefaultSelect) EndFunc Func RefreshHistoryList($iListCtrl) Local $hSearch = FileFindFirstFile($sHistoryDir & "\*.txt"), $sListStr = "|" If $hSearch <> -1 Then While 1 Local $sFile = FileFindNextFile($hSearch) If @error Then ExitLoop $sListStr &= $sFile & "|" WEnd FileClose($hSearch) EndIf GUICtrlSetData($iListCtrl, $sListStr) EndFunc Func ApplyTheme($hWindow, $idToggleBtn, $bDark) Local $iGuiBg = $bDark ? 0x1E1E1E : 0xF0F0F0 Local $iEditBg = $bDark ? 0x252526 : 0xFFFFFF Local $iEditText = $bDark ? 0xD4D4D4 : 0x000000 Local $iLabelText = $bDark ? 0xCCCCCC : 0x000000 GUISetBkColor($iGuiBg, $hWindow) GUICtrlSetData($idToggleBtn, $bDark ? "Chế độ Sáng" : "Chế độ Tối") For $i = 0 To UBound($aLabels) - 1 GUICtrlSetColor($aLabels[$i], $iLabelText) GUICtrlSetBkColor($aLabels[$i], -2) Next For $i = 0 To UBound($aChecks) - 1 GUICtrlSetColor($aChecks[$i], $iLabelText) GUICtrlSetBkColor($aChecks[$i], -2) Next For $i = 0 To UBound($aEdits) - 1 GUICtrlSetColor($aEdits[$i], $iEditText) GUICtrlSetBkColor($aEdits[$i], $iEditBg) Next RenderHTML() EndFunc Func COM_ErrHandler() EndFunc