iT邦幫忙

2025 iThome 鐵人賽

DAY 24
0

基礎架構與組件設計篇

前言

前面介紹了這麼多都還沒有說到使用者介面是長怎樣的,所以再來會先介紹。但因為有點長,所以會拆成兩篇。

一、建立基本視窗

1.1 主要類別初始化

class LangChainAgentApp(customtkinter.CTk):
    def __init__(self):
        super().__init__()
        
        # 基本窗口設置
        self.title("LangChain Agent 助手")
        self.geometry("1200x700")
        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(0, weight=1)
        self.grid_columnconfigure(1, weight=1)
        
        # 設置主題
        customtkinter.set_appearance_mode("dark")
        customtkinter.set_default_color_theme("blue")
        
        # 初始化變量
        self.chat_history = []
        self.message_queue = queue.Queue()
        self.agent = None
        self.system_tools = None
        
        # 檔案處理相關變量
        self.waiting_for_file_action = False
        self.last_uploaded_file_content = ""
        self.last_uploaded_filename = ""
        self.last_analysis_result = ""  # 保存最近的分析結果
        
        # Word處理防重複標誌
        self._word_opening = False
        
        # 初始化 LangChain 組件
        self._initialize_langchain()
        
        # 建立GUI
        self._create_gui()
        
        # 開始訊息處理迴圈
        self.check_queue()
        
        # 歡迎訊息
        self.message_queue.put(("系統", """🎯 LangChain Agent 助手已啟動!

我可以協助您:
📄 開啟應用程式 (記事本、小畫家、Word等)
🔧 檔案處理 (上傳、分析各種檔案格式)
 內容生成 (創意寫作、心得感想等)
⚙️ 系統操作

請輸入問題或使用上傳檔案功能開始!"""))

1.2 程式碼說明

  • customtkinter.CTk 是主視窗類別
  • 視窗大小設定為 1200x700
  • 主題使用暗色模式和藍色主題
  • 初始化了多個重要變量:聊天歷史、訊息佇列、Agent 實例等
  • 檔案處理相關變量用於檔案上傳功能
  • 啟動時會顯示歡迎訊息

二、建立 GUI 界面

2.1 _create_gui() 完整實作

def _create_gui(self):
    """建立 GUI 界面"""
    # 歷史紀錄顯示區 - 優化長內容顯示
    self.history_textbox = customtkinter.CTkTextbox(
        self, 
        state="disabled", 
        wrap="word",
        scrollbar_button_color=("gray70", "gray30"),
        scrollbar_button_hover_color=("gray60", "gray40")
    )
    self.history_textbox.grid(row=0, column=0, columnspan=2, padx=10, pady=(10, 5), sticky="nsew")
    
    # 配置字體和顏色標籤
    self._configure_text_tags()
    
    # 底部輸入框架
    self.input_frame = customtkinter.CTkFrame(self)
    self.input_frame.grid(row=1, column=0, columnspan=2, padx=10, pady=(5, 10), sticky="ew")
    self.input_frame.grid_columnconfigure(0, weight=1)
    
    # 輸入框
    self.entry = customtkinter.CTkEntry(self.input_frame, placeholder_text="請在這裡輸入...")
    self.entry.grid(row=0, column=0, padx=(10, 5), pady=5, sticky="ew")
    self.entry.bind("<Return>", lambda event: self.start_agent_call())
    
    # 送出按鈕
    self.button = customtkinter.CTkButton(self.input_frame, text="送出問題", command=self.start_agent_call)
    self.button.grid(row=0, column=1, padx=(0, 5), pady=5, sticky="e")
    
    # 清除紀錄按鈕
    self.clear_button = customtkinter.CTkButton(self.input_frame, text="清除紀錄", command=self.clear_history)
    self.clear_button.grid(row=0, column=2, padx=(0, 5), pady=5, sticky="e")
    
    # 上傳檔案按鈕
    self.upload_button = customtkinter.CTkButton(self.input_frame, text="上傳檔案", command=self.upload_file)
    self.upload_button.grid(row=0, column=3, padx=(0, 10), pady=5, sticky="e")

2.2 界面佈局說明

  • 歷史紀錄顯示區:使用 CTkTextbox,設定為 disabled 狀態防止用戶編輯
  • 捲軸客製化:自定義捲軸顏色以符合暗色主題
  • 輸入框架:使用 CTkFrame 包含所有輸入控制項
  • 四個按鈕:送出問題、清除紀錄、上傳檔案,按鈕依序排列
  • 響應式佈局:使用 columnspan=2sticky="nsew" 確保自適應

2.3 各組件詳細參數

  • state="disabled":防止用戶直接編輯歷史記錄
  • wrap="word":按單字換行,避免單字被截斷
  • placeholder_text="請在這裡輸入...":提供用戶輸入提示
  • bind("<Return>"):綁定 Enter 鍵執行發送功能

三、文字標籤配置系統

3.1 _configure_text_tags() 完整實作

def _configure_text_tags(self):
    """配置文字標籤以支援markdown格式"""
    # 取得底層的tkinter textbox
    textbox = self.history_textbox._textbox
    
    # 基本字體
    base_font = font.Font(family="微軟正黑體", size=10)
    
    # 配置不同的文字樣式
    textbox.tag_configure("speaker_user", foreground="#4A9EFF", font=font.Font(family="微軟正黑體", size=10, weight="bold"))
    textbox.tag_configure("speaker_agent", foreground="#00D26A", font=font.Font(family="微軟正黑體", size=10, weight="bold"))
    textbox.tag_configure("speaker_system", foreground="#FF6B35", font=font.Font(family="微軟正黑體", size=10, weight="bold"))
    textbox.tag_configure("speaker_analysis", foreground="#9B59B6", font=font.Font(family="微軟正黑體", size=10, weight="bold"))
    
    # Markdown樣式
    textbox.tag_configure("heading1", foreground="#FFD700", font=font.Font(family="微軟正黑體", size=16, weight="bold"))
    textbox.tag_configure("heading2", foreground="#FFA500", font=font.Font(family="微軟正黑體", size=14, weight="bold"))
    textbox.tag_configure("heading3", foreground="#FF6347", font=font.Font(family="微軟正黑體", size=12, weight="bold"))
    textbox.tag_configure("bold", font=font.Font(family="微軟正黑體", size=10, weight="bold"))
    textbox.tag_configure("italic", font=font.Font(family="微軟正黑體", size=10, slant="italic"))
    textbox.tag_configure("code", background="#2B2B2B", foreground="#F8F8F2", font=font.Font(family="Consolas", size=9))
    textbox.tag_configure("code_block", background="#1E1E1E", foreground="#D4D4D4", font=font.Font(family="Consolas", size=9), relief="solid", borderwidth=1)
    textbox.tag_configure("quote", foreground="#B0B0B0", font=font.Font(family="微軟正黑體", size=10, slant="italic"), lmargin1=20, rmargin=20)
    textbox.tag_configure("list_item", lmargin1=20, lmargin2=30)
    textbox.tag_configure("link", foreground="#4A9EFF", underline=True)
    textbox.tag_configure("emphasis", foreground="#FFD700", font=font.Font(family="微軟正黑體", size=10, weight="bold"))

3.2 發話者標籤色彩系統

  • speaker_user (#4A9EFF):藍色,代表使用者輸入
  • speaker_agent (#00D26A):綠色,代表 Agent 回應
  • speaker_system (#FF6B35):橙色,代表系統訊息
  • speaker_analysis (#9B59B6):紫色,代表分析結果

3.3 Markdown 格式支援

  • 標題系列:heading1(金色16pt) → heading2(橙色14pt) → heading3(番茄紅12pt)
  • 文字格式:bold(粗體)、italic(斜體)
  • 程式碼格式
    • code:行內程式碼,深灰背景
    • code_block:程式碼區塊,黑色背景加邊框
  • 其他格式:quote(引用)、list_item(清單)、link(連結)、emphasis(強調)

3.4 字體設計原則

  • 中文字體:統一使用「微軟正黑體」確保中文顯示品質
  • 程式碼字體:使用 Consolas 等寬字體確保程式碼排版
  • 字體大小:基本 10pt,標題依等級放大到 12-16pt

四、主要事件處理函數

4.1 start_agent_call() - 啟動 Agent 處理

def start_agent_call(self):
    """啟動 Agent 處理用戶輸入"""
    question = self.entry.get().strip()
    if question:
        self.entry.delete(0, "end")
        self._append_to_history("使用者", question)
        self.message_queue.put(("Agent", "正在處理…"))
        
        # 啟動新執行緒來呼叫 Agent
        thread = threading.Thread(target=self.get_agent_response, args=(question,))
        thread.start()
    else:
        self.message_queue.put(("系統", "請輸入一個問題。"))

4.2 clear_history() - 清除對話歷史

def clear_history(self):
    """清除對話歷史"""
    self.chat_history.clear()
    self._update_history_display()
    # 重新初始化 LangChain 組件
    self._initialize_langchain()
    self.message_queue.put(("系統", "對話紀錄已清除。"))

4.3 check_queue() - 訊息佇列處理

def check_queue(self):
    """檢查訊息佇列"""
    try:
        processed_count = 0
        max_process_per_cycle = 5  # 限制每次處理的最大訊息數
        
        while processed_count < max_process_per_cycle:
            speaker, message = self.message_queue.get_nowait()
            if speaker == "remove_waiting":
                self._remove_waiting_message()
            else:
                self._append_to_history(speaker, message)
            processed_count += 1
    except queue.Empty:
        pass
    except Exception as e:
        print(f"[DEBUG] 佇列處理錯誤: {e}")
    
    # 增加檢查間隔,降低CPU使用率
    self.after(200, self.check_queue)

4.4 事件處理特點說明

  • 非阻塞處理:使用多執行緒避免 UI 凍結
  • 訊息佇列:透過 queue 實現執行緒安全的訊息傳遞
  • 批量處理:每次最多處理 5 個訊息,避免 UI 卡頓
  • 錯誤處理:捕捉並記錄錯誤,確保程式穩定性
  • 狀態管理:適當清理和重置組件狀態

上一篇
DAY 23
系列文
我的 AI 助手開發24
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言