1、問題描述:
ollama調用千問2.5-vl視頻圖片內容,通常用命令行工具不方便,于是做了一個python UI界面與大家分享。需要提前安裝ollama,并下載千問qwen2.5vl:7b 模型,在ollama官網即可下載。 (8G-6G 顯卡可用),用于識別圖片信息。之前還下載了 qwen3:8b版,發現也可以此程序調用,比圖片識別更快,用qwen3:8b文字直接提問,隨便輸入張圖片即可。圖片不起作用。 不知為何qwen2.5vl:7b 默認只支持cpu預處理圖片,所以,圖片推理的過程非常慢。qwen3:8b 默認支持gpu,速度快100倍,反應迅速,機會秒回復。這就是gpu 與cpu,推理的天壤之別吧,哈哈。南無阿彌陀佛。
如下圖:
使用方法:很簡單,
2、圖片推理:
在模型管理列表欄,選擇相應的qwen2.5vl:7b模型,點擊選擇模型按鈕,之后,直接在最下面,點擊選擇圖片按鈕,支持三張,太多圖片推理太慢了。單張最快,cpu推理就是這樣,之后,在提示詞欄,輸入要對圖片做的推理要求,默認是描述圖片內容,也可以問圖片里的特殊的人事物,等等。也可以指定要求推理輸出的文字字數,1000字以內沒啥問題。
3、文字推理:
同理,在模型管理列表欄,選擇相應的qwen3:8b模型,點擊選擇模型按鈕,之后,直接在最下面,點擊選擇圖片按鈕,隨便選張圖,如果之前已經選了,就忽略此步。之后,在提示詞欄,輸入要提問的提示詞即可,幾千字以內似乎沒啥問題。南無阿彌陀佛。
4、程序免費下載地址:
程序下載地址:https://download.csdn.net/download/tian0000hai/90856287
南無阿彌陀佛。
5、源碼分享:ollama_qwen25vl_gui.py
import os
import sys
import base64
import json
import requestsimport concurrent.futuresimport numpy as np
import cv2 # 需要安裝OpenCV庫# 確保打包后能找到證書
if getattr(sys, 'frozen', False):# 如果是打包后的可執行文件requests.utils.DEFAULT_CA_BUNDLE_PATH = os.path.join(sys._MEIPASS, 'certifi', 'cacert.pem')import threading
import queue
import tkinter as tk
from tkinter import filedialog, ttk, messagebox, scrolledtext
from PIL import Image, ImageTk, PngImagePlugin
import subprocess
import platform
import timeclass OllamaQwenGUI:def __init__(self, root):self.root = rootself.root.title("Ollama Qwen2.5-VL 圖片識別工具 - 南無阿彌陀佛")self.root.geometry("1000x750")self.root.minsize(900, 900)# 設置中文字體支持self.font_family = "SimHei" if sys.platform.startswith("win") else "WenQuanYi Micro Hei"# 創建Ollama API配置self.api_url = "http://localhost:11434/api"self.model_name = "qwen2.5vl:7b"# API服務進程self.api_process = Noneself.console_output_queue = queue.Queue()# 識別進度相關self.is_recognizing = Falseself.progress_queue = queue.Queue()# 創建GUI組件self.create_widgets()# 創建線程間通信隊列self.queue = queue.Queue()# 初始化模型列表和API狀態self.check_api_status()self.refresh_model_list()# 啟動控制臺輸出監控線程self.monitor_console_output = Truethreading.Thread(target=self.read_console_output, daemon=True).start()# 啟動進度更新線程threading.Thread(target=self.update_progress, daemon=True).start()def create_widgets(self):# 創建主框架main_frame = ttk.Frame(self.root, padding="10")main_frame.pack(fill=tk.BOTH, expand=True)# 創建頂部API配置區域config_frame = ttk.LabelFrame(main_frame, text="Ollama API 配置", padding="10")config_frame.pack(fill=tk.X, pady=5)ttk.Label(config_frame, text="API 地址:", font=(self.font_family, 10)).grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)self.api_entry = ttk.Entry(config_frame, width=40, font=(self.font_family, 10))self.api_entry.insert(0, self.api_url)self.api_entry.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)ttk.Label(config_frame, text="模型名稱:", font=(self.font_family, 10)).grid(row=0, column=2, sticky=tk.W, padx=5, pady=5)self.model_entry = ttk.Entry(config_frame, width=20, font=(self.font_family, 10))self.model_entry.insert(0, self.model_name)self.model_entry.grid(row=0, column=3, sticky=tk.W, padx=5, pady=5)# 創建API服務控制區域api_frame = ttk.LabelFrame(main_frame, text="API 服務控制", padding="10")api_frame.pack(fill=tk.X, pady=5)self.api_status_var = tk.StringVar(value="API 狀態: 未知")self.api_status_label = ttk.Label(api_frame, textvariable=self.api_status_var, font=(self.font_family, 10))self.api_status_label.grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)self.start_api_button = ttk.Button(api_frame, text="啟動 API", command=self.start_api, width=12)self.start_api_button.grid(row=0, column=1, padx=5, pady=5)self.stop_api_button = ttk.Button(api_frame, text="停止 API", command=self.stop_api, width=12)self.stop_api_button.grid(row=0, column=2, padx=5, pady=5)self.check_api_button = ttk.Button(api_frame, text="檢查狀態", command=self.check_api_status, width=12)self.check_api_button.grid(row=0, column=3, padx=5, pady=5)# 創建模型管理區域model_frame = ttk.LabelFrame(main_frame, text="模型管理 -(鼠標中鍵滾輪可查看更多內容)", padding="10")model_frame.pack(fill=tk.X, pady=5)self.model_listbox = tk.Listbox(model_frame, width=40, height=5, font=(self.font_family, 10))self.model_listbox.grid(row=0, column=0, rowspan=2, sticky=tk.W+tk.E, padx=5, pady=5)# 添加選擇按鈕self.select_button = ttk.Button(model_frame, text="選擇模型", command=self.select_model, width=12)self.select_button.grid(row=1, column=1, padx=5, pady=5)self.download_button = ttk.Button(model_frame, text="下載模型", command=self.download_model, width=12)self.download_button.grid(row=0, column=1, padx=5, pady=5)self.start_button = ttk.Button(model_frame, text="啟動模型", command=self.start_model, width=12)self.start_button.grid(row=0, column=2, padx=5, pady=5)self.stop_button = ttk.Button(model_frame, text="停止模型", command=self.stop_model, width=12)self.stop_button.grid(row=0, column=3, padx=5, pady=5)self.refresh_button = ttk.Button(model_frame, text="刷新列表", command=self.refresh_model_list, width=12)self.refresh_button.grid(row=1, column=2, padx=5, pady=5)# 添加刪除模型按鈕self.delete_button = ttk.Button(model_frame, text="刪除模型", command=self.delete_model, width=12)self.delete_button.grid(row=1, column=3, padx=5, pady=5)self.model_status_var = tk.StringVar(value="模型狀態: 未知")self.model_status_label = ttk.Label(model_frame, textvariable=self.model_status_var, font=(self.font_family, 10))self.model_status_label.grid(row=1, column=4, sticky=tk.W, padx=5, pady=5)# 創建中間圖片和結果區域middle_frame = ttk.Frame(main_frame, padding="5")middle_frame.pack(fill=tk.BOTH, expand=True)# 左側圖片區域image_frame = ttk.LabelFrame(middle_frame, text="圖片預覽", padding="10")image_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)self.image_container = ttk.Frame(image_frame)self.image_container.pack(fill=tk.BOTH, expand=True)self.image_label = ttk.Label(self.image_container, text="請選擇一張或多張圖片(建議:不超超過三張)", font=(self.font_family, 12))self.image_label.pack(expand=True)# 右側結果區域result_frame = ttk.LabelFrame(middle_frame, text="識別結果", padding="10")result_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)# 創建結果和控制臺輸出的標簽頁self.result_notebook = ttk.Notebook(result_frame)self.result_notebook.pack(fill=tk.BOTH, expand=True)# 識別結果標簽頁self.result_tab = ttk.Frame(self.result_notebook)self.result_notebook.add(self.result_tab, text="識別結果")# 創建進度指示器框架self.progress_frame = ttk.Frame(self.result_tab)self.progress_frame.pack(fill=tk.X, pady=5)self.progress_var = tk.StringVar(value="準備就緒")self.progress_label = ttk.Label(self.progress_frame, textvariable=self.progress_var, font=(self.font_family, 10))self.progress_label.pack(side=tk.LEFT, padx=5)self.progress_bar = ttk.Progressbar(self.progress_frame, orient=tk.HORIZONTAL, length=100, mode='indeterminate')self.progress_bar.pack(side=tk.LEFT, padx=5)self.result_text = scrolledtext.ScrolledText(self.result_tab, wrap=tk.WORD, font=(self.font_family, 11), height=18)self.result_text.pack(fill=tk.BOTH, expand=True)# 控制臺輸出標簽頁self.console_tab = ttk.Frame(self.result_notebook)self.result_notebook.add(self.console_tab, text="控制臺輸出")self.console_text = scrolledtext.ScrolledText(self.console_tab, wrap=tk.WORD, font=(self.font_family, 10), height=20)self.console_text.pack(fill=tk.BOTH, expand=True)# 創建底部控制區域control_frame = ttk.Frame(main_frame, padding="10")control_frame.pack(fill=tk.X, pady=5)ttk.Label(control_frame, text="提示詞:", font=(self.font_family, 10)).grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)self.prompt_var = tk.StringVar(value="描述這些圖片的內容")self.prompt_entry = ttk.Entry(control_frame, textvariable=self.prompt_var, width=60, font=(self.font_family, 10))self.prompt_entry.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)self.browse_button = ttk.Button(control_frame, text="選擇圖片", command=self.browse_image, width=12)self.browse_button.grid(row=0, column=2, padx=5, pady=5)self.recognize_button = ttk.Button(control_frame, text="開始識別", command=self.start_recognition, width=12)self.recognize_button.grid(row=0, column=3, padx=5, pady=5)# 創建狀態欄self.status_var = tk.StringVar(value="就緒")self.status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)def browse_image(self):file_paths = filedialog.askopenfilenames(title="選擇圖片",filetypes=[("選擇圖片", "*.jpg;*.jpeg;*.png;*.bmp;*.webp;*.tiff;*.tif;*.gif")])if file_paths:self.image_paths = file_pathsself.display_images(file_paths)self.status_var.set(f"已選擇 {len(file_paths)} 張圖片")def display_images(self, file_paths):# 清空之前的圖片顯示for widget in self.image_container.winfo_children():widget.destroy()try:max_width = 200max_height = 200for file_path in file_paths:# 增加PNG文本塊的最大大小限制,防止iCCP警告PngImagePlugin.MAX_TEXT_CHUNK = 100 * (1024**2) # 100MBimage = Image.open(file_path)# 檢查圖片格式,如果是PNG則嘗試處理iCCP警告if image.format == 'PNG':# 嘗試移除iCCP chunktry:image = image.convert('RGB')except Exception as e:print(f"處理PNG圖片時出錯: {e}")# 計算調整后的尺寸width, height = image.sizeif width > max_width or height > max_height:ratio = min(max_width/width, max_height/height)width = int(width * ratio)height = int(height * ratio)image = image.resize((width, height), Image.LANCZOS)# 顯示圖片photo = ImageTk.PhotoImage(image)label = ttk.Label(self.image_container, image=photo)label.image = photolabel.pack(side=tk.LEFT, padx=5, pady=5)except Exception as e:self.image_label.config(text=f"無法顯示圖片: {str(e)}")print(f"加載圖片時出錯: {e}")def start_recognition(self):if not hasattr(self, 'image_paths'):messagebox.showwarning("警告", "請先選擇一張或多張圖片")return# 檢查API是否運行if not self.check_api_status(show_message=False):messagebox.showwarning("警告", "API服務未啟動,請先啟動API服務")return# 清空結果區域self.result_text.delete(1.0, tk.END)# 更新狀態self.is_recognizing = Trueself.progress_var.set("準備識別...")self.progress_bar.start()# 更新狀態欄并禁用按鈕self.status_var.set("正在識別...")self.browse_button.config(state=tk.DISABLED)self.recognize_button.config(state=tk.DISABLED)# 在新線程中執行識別任務threading.Thread(target=self.perform_recognition, daemon=True).start()def encode_image_to_base64(self, image_path):"""使用OpenCV和NumPy優化Base64編碼"""try:img = cv2.imread(image_path)if img is None:# 回退到標準方法with open(image_path, "rb") as f:return base64.b64encode(f.read()).decode('utf-8')# 檢查圖像編碼是否成功success, buffer = cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 80])if success:return base64.b64encode(buffer).decode('utf-8')else:raise Exception("圖像編碼失敗")except Exception as e:print(f"使用OpenCV編碼失敗: {e}")# 出錯時回退到標準方法with open(image_path, "rb") as f:return base64.b64encode(f.read()).decode('utf-8')def perform_recognition(self):try:# 優化1: 使用線程池并行處理多個圖像的Base64編碼with concurrent.futures.ThreadPoolExecutor() as executor:future_to_path = {executor.submit(self.encode_image_to_base64, path): path for path in self.image_paths}base64_images = []for future in concurrent.futures.as_completed(future_to_path):try:base64_images.append(future.result())except Exception as e:print(f"編碼圖像時出錯: {e}")# 準備API請求數據api_url = f"{self.api_entry.get().strip()}/generate"model_name = self.model_entry.get().strip()prompt = self.prompt_var.get().strip()# 發送進度更新self.progress_queue.put("正在初始化模型...")payload = {"model": model_name,"prompt": prompt,"stream": True, # 啟用流式輸出"images": base64_images}# 發送請求到Ollama APIself.progress_queue.put("正在發送請求...")response = requests.post(api_url, json=payload, stream=True)if response.status_code == 200:self.progress_queue.put("開始接收識別結果...")self.result_text.insert(tk.END, "識別結果:\n\n")# 處理流式響應full_response = ""for line in response.iter_lines():if line:# 解析JSON行try:chunk = json.loads(line)if 'response' in chunk:text_chunk = chunk['response']full_response += text_chunkself.result_text.insert(tk.END, text_chunk)self.result_text.see(tk.END) # 滾動到底部except json.JSONDecodeError as e:self.progress_queue.put(f"解析響應錯誤: {str(e)}")self.progress_queue.put("識別完成")self.queue.put(("success", "識別完成"))else:self.queue.put(("error", f"API請求失敗: HTTP {response.status_code} - {response.text}"))except Exception as e:self.queue.put(("error", f"發生錯誤: {str(e)}"))finally:# 無論成功或失敗,都要恢復UI狀態self.is_recognizing = Falseself.progress_bar.stop()self.queue.put(("update_ui",))# 安排下一次隊列檢查self.root.after(100, self.check_queue)def download_model(self):model_name = self.model_entry.get().strip()if not model_name:messagebox.showwarning("警告", "請輸入要下載的模型名稱")return# 檢查API是否運行if not self.check_api_status(show_message=False):messagebox.showwarning("警告", "API服務未啟動,請先啟動API服務")return# 更新狀態欄并禁用按鈕self.status_var.set(f"正在下載模型: {model_name}")self.download_button.config(state=tk.DISABLED)# 在新線程中執行下載任務threading.Thread(target=self.perform_model_download, args=(model_name,), daemon=True).start()def perform_model_download(self, model_name):try:api_url = f"{self.api_entry.get().strip()}/pull"payload = {"name": model_name,"stream": True # 啟用流式輸出獲取進度}# 發送請求到Ollama APIresponse = requests.post(api_url, json=payload, stream=True)if response.status_code == 200:self.queue.put(("success", f"開始下載模型: {model_name}"))# 處理流式響應獲取進度for line in response.iter_lines():if line:try:chunk = json.loads(line)if 'status' in chunk:status = chunk['status']self.queue.put(("success", f"下載狀態: {status}"))if 'completed' in chunk and 'total' in chunk:progress = chunk['completed'] / chunk['total'] * 100self.queue.put(("success", f"下載進度: {progress:.1f}%"))except json.JSONDecodeError:passself.queue.put(("success", f"模型 {model_name} 下載成功"))self.refresh_model_list()else:self.queue.put(("error", f"模型下載失敗: HTTP {response.status_code} - {response.text}"))except Exception as e:self.queue.put(("error", f"發生錯誤: {str(e)}"))finally:# 恢復UI狀態self.queue.put(("update_download_ui",))# 安排下一次隊列檢查self.root.after(100, self.check_queue)def start_model(self):model_name = self.model_entry.get().strip()if not model_name:messagebox.showwarning("警告", "請選擇要啟動的模型")return# 檢查API是否運行if not self.check_api_status(show_message=False):messagebox.showwarning("警告", "API服務未啟動,請先啟動API服務")return# 更新狀態欄并禁用按鈕self.status_var.set(f"正在啟動模型: {model_name}")self.start_button.config(state=tk.DISABLED)# 在新線程中執行啟動任務threading.Thread(target=self.perform_model_start, args=(model_name,), daemon=True).start()def perform_model_start(self, model_name):try:# 簡單模擬啟動模型 - Ollama通常在調用時自動啟動模型# 實際可能需要調用特定APIself.queue.put(("success", f"正在加載模型: {model_name}"))# 發送一個簡單請求來觸發模型加載api_url = f"{self.api_entry.get().strip()}/generate"payload = {"model": model_name,"prompt": "加載模型","stream": False}response = requests.post(api_url, json=payload)if response.status_code == 200:self.queue.put(("success", f"模型 {model_name} 已啟動"))else:self.queue.put(("error", f"啟動模型失敗: HTTP {response.status_code}"))self.update_model_status(model_name)except Exception as e:self.queue.put(("error", f"啟動模型失敗: {str(e)}"))finally:# 恢復UI狀態self.queue.put(("update_start_ui",))# 安排下一次隊列檢查self.root.after(100, self.check_queue)def stop_model(self):model_name = self.model_entry.get().strip()if not model_name:messagebox.showwarning("警告", "請選擇要停止的模型")return# 檢查API是否運行if not self.check_api_status(show_message=False):messagebox.showwarning("警告", "API服務未啟動,模型可能已停止")return# 更新狀態欄并禁用按鈕self.status_var.set(f"正在停止模型: {model_name} 并清空顯存...")self.stop_button.config(state=tk.DISABLED)# 在新線程中執行停止任務threading.Thread(target=self.perform_model_stop, args=(model_name,), daemon=True).start()def perform_model_stop(self, model_name):try:# 執行停止模型程序stop_model_proc = "ollama stop " + model_namereturn_code = os.system(stop_model_proc)if return_code == 0:self.queue.put(("success", f"模型 {model_name} 已停止"))self.refresh_model_list()else:self.queue.put(("error", f"模型停止失敗: HTTP {response.status_code} - {response.text}"))except Exception as e:self.queue.put(("error", f"停止模型失敗: {str(e)}"))finally:# 恢復UI狀態self.queue.put(("update_stop_ui",))# 安排下一次隊列檢查self.root.after(100, self.check_queue)def refresh_model_list(self):# 清空列表self.model_listbox.delete(0, tk.END)# 檢查API是否運行if not self.check_api_status(show_message=False):self.queue.put(("error", "無法獲取模型列表:API服務未啟動"))return# 在新線程中獲取模型列表threading.Thread(target=self.fetch_model_list, daemon=True).start()def fetch_model_list(self):try:api_url = f"{self.api_entry.get().strip()}/tags"# 發送請求到Ollama APIresponse = requests.get(api_url)if response.status_code == 200:models = response.json().get("models", [])model_names = [model.get("name", "") for model in models]# 將模型名稱添加到隊列中更新UIself.queue.put(("update_model_list", model_names))# 更新當前模型狀態current_model = self.model_entry.get().strip()self.update_model_status(current_model)else:self.queue.put(("error", f"獲取模型列表失敗: HTTP {response.status_code} - {response.text}"))except Exception as e:self.queue.put(("error", f"發生錯誤: {str(e)}"))# 安排下一次隊列檢查self.root.after(100, self.check_queue)def update_model_status(self, model_name):# 簡單實現,實際應通過API查詢模型狀態# 這里假設如果模型在列表中,則認為是可用狀態model_names = [self.model_listbox.get(i) for i in range(self.model_listbox.size())]status = "已安裝" if model_name in model_names else "未安裝"self.model_status_var.set(f"模型狀態: {status}")def select_model(self):"""從列表中選擇模型并填充到模型名稱輸入框"""selected_indices = self.model_listbox.curselection()if not selected_indices:messagebox.showwarning("警告", "請先從列表中選擇一個模型")returnmodel_name = self.model_listbox.get(selected_indices[0])self.model_entry.delete(0, tk.END)self.model_entry.insert(0, model_name)self.status_var.set(f"已選擇模型: {model_name}")self.update_model_status(model_name)def start_api(self):if self.api_process is not None and self.api_process.poll() is None:messagebox.showwarning("警告", "API服務已在運行中")return# 清空控制臺輸出self.console_text.delete(1.0, tk.END)# 更新狀態欄并禁用按鈕self.status_var.set("正在啟動API服務...")self.start_api_button.config(state=tk.DISABLED)# 在新線程中啟動APIthreading.Thread(target=self.perform_api_start, daemon=True).start()def perform_api_start(self):try:#os.system("set OLLAMA_CUDA=1 # Windows cmd")# 設置CUDA環境變量os.environ["OLLAMA_CUDA"] = "1" # 明確啟用CUDA# 獲取API地址和端口api_full_url = self.api_entry.get().strip()base_url = api_full_url.rsplit('/', 1)[0]host, port = base_url.replace('http://', '').split(':')# 根據操作系統執行不同的命令if platform.system() == "Windows":self.api_process = subprocess.Popen(f"ollama serve -h {host} -p {port}",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,text=True,bufsize=1)else:self.api_process = subprocess.Popen(f"ollama serve -h {host} -p {port}",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,preexec_fn=os.setsid,text=True,bufsize=1)# 等待服務啟動(嘗試連接)max_attempts = 10for attempt in range(max_attempts):try:response = requests.get(f"{api_full_url}/tags", timeout=2)if response.status_code == 200:self.queue.put(("success", "API服務已啟動"))self.check_api_status()returnexcept:time.sleep(1)self.queue.put(("error", "API服務啟動超時"))except Exception as e:self.queue.put(("error", f"啟動API服務失敗: {str(e)}"))finally:# 恢復UI狀態self.queue.put(("update_api_start_ui",))# 安排下一次隊列檢查self.root.after(100, self.check_queue)def stop_api(self):if self.api_process is None or self.api_process.poll() is not None:messagebox.showwarning("警告", "API服務未在運行中")self.api_process = Noneself.api_status_var.set("API 狀態: 未運行")return# 更新狀態欄并禁用按鈕self.status_var.set("正在停止API服務...")self.stop_api_button.config(state=tk.DISABLED)# 在新線程中停止APIthreading.Thread(target=self.perform_api_stop, daemon=True).start()def perform_api_stop(self):try:if self.api_process is not None:# 終止進程if platform.system() == "Windows":self.api_process.terminate()else:import osimport signalos.killpg(os.getpgid(self.api_process.pid), signal.SIGTERM)# 等待進程結束try:self.api_process.wait(timeout=5)except subprocess.TimeoutExpired:self.api_process.kill()self.api_process.wait()self.api_process = Noneself.queue.put(("success", "API服務已停止"))self.api_status_var.set("API 狀態: 未運行")except Exception as e:self.queue.put(("error", f"停止API服務失敗: {str(e)}"))finally:# 恢復UI狀態self.queue.put(("update_api_stop_ui",))# 安排下一次隊列檢查self.root.after(100, self.check_queue)def check_api_status(self, show_message=True):try:api_url = f"{self.api_entry.get().strip()}/tags"response = requests.get(api_url, timeout=2)if response.status_code == 200:self.api_status_var.set("API 狀態: 運行中")if show_message:self.queue.put(("success", "API服務正在運行"))return Trueelse:self.api_status_var.set("API 狀態: 未運行")if show_message:self.queue.put(("error", f"API服務未響應: HTTP {response.status_code}"))return Falseexcept Exception as e:self.api_status_var.set("API 狀態: 未運行")if show_message:self.queue.put(("error", f"無法連接到API服務: {str(e)}"))return Falsedef read_console_output(self):"""讀取API進程的控制臺輸出并添加到隊列中"""while self.monitor_console_output:if self.api_process is not None and self.api_process.poll() is None:# 讀取標準輸出if self.api_process.stdout:for line in iter(self.api_process.stdout.readline, ''):if line:self.console_output_queue.put(line.strip())# 讀取錯誤輸出if self.api_process.stderr:for line in iter(self.api_process.stderr.readline, ''):if line:self.console_output_queue.put(f"[錯誤] {line.strip()}")# 檢查隊列并更新UIself.root.after(100, self.update_console_text)time.sleep(0.1)def update_console_text(self):"""從隊列中獲取控制臺輸出并更新UI"""while not self.console_output_queue.empty():try:line = self.console_output_queue.get()self.console_text.insert(tk.END, line + "\n")self.console_text.see(tk.END) # 滾動到底部except queue.Empty:passdef update_progress(self):"""更新識別進度"""while True:if not self.progress_queue.empty():try:progress_msg = self.progress_queue.get()self.progress_var.set(progress_msg)except queue.Empty:passtime.sleep(0.1)def check_queue(self):while not self.queue.empty():try:msg = self.queue.get()if msg[0] == "success":self.result_text.insert(tk.END, msg[1] + "\n")self.status_var.set(msg[1])elif msg[0] == "error":self.result_text.insert(tk.END, f"錯誤: {msg[1]}\n")self.status_var.set(f"錯誤: {msg[1]}")elif msg[0] == "update_ui":self.browse_button.config(state=tk.NORMAL)self.recognize_button.config(state=tk.NORMAL)elif msg[0] == "update_download_ui":self.download_button.config(state=tk.NORMAL)elif msg[0] == "update_start_ui":self.start_button.config(state=tk.NORMAL)elif msg[0] == "update_stop_ui":self.stop_button.config(state=tk.NORMAL)elif msg[0] == "update_model_list":for model in msg[1]:self.model_listbox.insert(tk.END, model)elif msg[0] == "update_api_start_ui":self.start_api_button.config(state=tk.NORMAL)elif msg[0] == "update_api_stop_ui":self.stop_api_button.config(state=tk.NORMAL)elif msg[0] == "update_delete_ui":self.delete_button.config(state=tk.NORMAL)except queue.Empty:pass# 繼續檢查隊列self.root.after(100, self.check_queue)def delete_model(self):model_name = self.model_entry.get().strip()if not model_name:messagebox.showwarning("警告", "請輸入要刪除的模型名稱")return# 檢查API是否運行if not self.check_api_status(show_message=False):messagebox.showwarning("警告", "API服務未啟動,請先啟動API服務")return# 更新狀態欄并禁用按鈕self.status_var.set(f"正在刪除模型: {model_name}")self.delete_button.config(state=tk.DISABLED)# 在新線程中執行刪除任務threading.Thread(target=self.perform_model_delete, args=(model_name,), daemon=True).start()def perform_model_delete(self, model_name):try:# 執行刪除模型程序delete_model_proc = "ollama rm " + model_namereturn_code = os.system(delete_model_proc)if return_code == 0:self.queue.put(("success", f"模型 {model_name} 已刪除"))self.refresh_model_list()else:self.queue.put(("error", f"模型刪除失敗: HTTP {response.status_code} - {response.text}"))except Exception as e:self.queue.put(("error", f"發生錯誤: {str(e)}"))finally:# 恢復UI狀態self.queue.put(("update_delete_ui",))# 安排下一次隊列檢查self.root.after(100, self.check_queue)if __name__ == "__main__":root = tk.Tk()app = OllamaQwenGUI(root)# 窗口關閉時確保API進程被終止def on_closing():app.monitor_console_output = Falseif app.api_process is not None and app.api_process.poll() is None:app.perform_api_stop()root.destroy()root.protocol("WM_DELETE_WINDOW", on_closing)root.mainloop()
南無阿彌陀佛,哈哈。