本文属于【Azure 架构师学习笔记】系列。
本文属于【Azure AI】系列。
接上文 【Azure 架构师学习笔记 】- Azure AI(15)-Azure OpenAI(6)-Azure OpenAI 交互式工作助手
前言
本文是前两篇工具的终极升级,核心突破「命令行操作门槛」,基于Python的tkinter库(无需额外安装,Python自带),打造一款可视化桌面版AI工作助手。工具仍保持纯单Python文件运行,整合前两篇的所有核心功能------多格式文件处理(TXT/Word/Excel/PDF)、自定义提示词模板、多轮记忆交互、结果导出,同时新增GUI界面专属特性(拖拽上传文件、实时日志显示、配置保存),彻底摆脱命令行输入,实现"点击操作、可视化反馈",让新手也能零代码上手,真正落地为日常高频使用的个人AI助手。
工具核心升级亮点:
- 可视化界面:图形化操作,无需输入命令,点击按钮即可完成文件上传、格式选择、模板切换;
- 全功能整合:无缝衔接前两篇的模板、记忆、导出、多格式处理,无需切换工具,一站式完成文本处理;
- 操作更便捷:支持文件拖拽上传、配置自动保存(Azure OpenAI信息无需每次输入)、实时日志显示处理进度;
- 轻量化不变:仍为单Python文件,无复杂依赖,仅新增tkinter(Python自带),复制即可运行,适配Windows/Mac系统。
适用人群:已掌握前两篇工具实操,希望摆脱命令行、追求更直观操作体验,需要一款可日常高频使用的桌面版AI文本处理助手(无需GUI开发经验)。
一、环境准备(前置条件)
完全复用前两篇的环境,仅新增1个Python自带依赖(无需额外安装),配置直接复用前两篇的Azure OpenAI信息:
- 基础环境:已安装Python 3.8+,且已完成前两篇的环境配置(依赖库已安装);
- 依赖补充:tkinter(Python自带,无需pip安装,若提示缺失,Windows可重新安装Python并勾选"tkinter",Mac可执行
brew install python-tk); - 配置准备:已获取Azure OpenAI的
azure_endpoint、api_key、DEPLOYMENT_NAME(直接复用前两篇的配置,本次将实现配置保存,无需每次输入)。
提示:tkinter是Python标准库,绝大多数Python环境默认自带,无需额外操作,直接运行代码即可调用。
二、实操
代码整合前两篇所有核心逻辑,新增tkinter GUI界面、拖拽上传、配置保存等功能,可直接复制运行,无需修改核心代码(仅需首次输入Azure OpenAI配置):
python
from openai import AzureOpenAI
import os
from docx import Document
import openpyxl
import PyPDF2
from datetime import datetime
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import json
# ===================== 基础配置(支持保存/读取,无需每次输入) =====================
# 配置文件路径(自动生成,保存Azure OpenAI信息)
CONFIG_FILE = "azure_config.json"
def load_config():
"""读取配置文件,首次运行无配置则返回空"""
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
return json.load(f)
return {"azure_endpoint": "", "api_key": "", "DEPLOYMENT_NAME": ""}
def save_config(config):
"""保存配置到文件,避免每次运行输入"""
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
json.dump(config, f, ensure_ascii=False, indent=4)
# 加载配置
config = load_config()
client = None # 全局Azure OpenAI客户端,配置完成后初始化
# ===================== 自定义提示词模板 =====================
PROMPT_TEMPLATES = {
"会议纪要总结": "你是专业的职场会议纪要总结助手,总结需包含「会议主题、核心结论、行动项、责任人、截止时间」,语言简洁正式,控制在200字内,无冗余内容。",
"需求信息提取": "你是职场需求分析专家,从文本中提取「需求名称、核心诉求、优先级、责任人、风险点、截止时间」,无则标注「无」,分点清晰,适配Excel导入格式。",
"面试反馈整理": "你是HR面试辅助助手,整理面试反馈需包含「候选人姓名、岗位、核心优势、不足、面试结论、下一步安排」,语言客观,格式标准化,可直接复制到面试记录表。",
"周报生成": "你是职场周报助手,根据日常工作文本,生成「本周工作内容、完成情况、遇到的问题、下周计划」四大模块,语言简洁,贴合职场周报规范。",
"自定义模板": "你是职场文本处理助手,按用户自定义的要求处理文本,确保输出贴合需求、格式规范。"
}
# ===================== 核心功能函数 =====================
def get_prompt_by_template(template_name, custom_prompt=""):
"""根据模板名称获取提示词,支持自定义模板"""
if template_name == "自定义模板" and custom_prompt:
return custom_prompt
return PROMPT_TEMPLATES.get(template_name, PROMPT_TEMPLATES["会议纪要总结"])
def process_text(text, prompt_template="会议纪要总结", custom_prompt="", temperature=0.2):
"""核心文本处理:支持模板选择+自定义提示词,输出结果+费用"""
global client
if not client:
return "Azure OpenAI配置未完成,请先在「配置」页面填写信息并保存!", 0
system_prompt = get_prompt_by_template(prompt_template, custom_prompt)
try:
response = client.chat.completions.create(
model=config["DEPLOYMENT_NAME"],
messages=[{"role": "system", "content": system_prompt},
{"role": "user", "content": f"请按要求处理以下文本:\n{text}"}],
temperature=temperature
)
# 费用计算
prompt_tokens = response.usage.prompt_tokens
completion_tokens = response.usage.completion_tokens
cost = (prompt_tokens / 1000 * 0.00015) + (completion_tokens / 1000 * 0.0006)
return response.choices[0].message.content.strip(), round(cost, 6)
except Exception as e:
return f"处理失败:{str(e)}", 0
def export_result(result, export_format="txt", file_name="处理结果"):
"""结果导出功能,支持TXT/Word/Excel"""
time_str = datetime.now().strftime("%Y%m%d%H%M%S")
file_path = f"{file_name}_{time_str}.{export_format}"
try:
if export_format == "txt":
with open(file_path, "w", encoding="utf-8") as f:
f.write(result)
elif export_format == "word":
doc = Document()
doc.add_heading("OpenAI 处理结果", level=1)
doc.add_paragraph(result)
doc.save(file_path)
elif export_format == "excel":
wb = openpyxl.Workbook()
ws = wb.active
ws.title = "处理结果"
lines = result.split("\n")
for row_idx, line in enumerate(lines, start=1):
ws.cell(row=row_idx, column=1, value=line)
wb.save(file_path)
else:
return f"不支持的导出格式:{export_format}"
return f"✅ 结果已成功导出\n路径:{os.path.abspath(file_path)}"
except Exception as e:
return f"❌ 导出失败:{str(e)}"
# ===================== 多格式文件处理函数 =====================
def process_txt_gui(file_path, prompt_template="会议纪要总结", custom_prompt="", log_text=None):
"""TXT处理(适配GUI)"""
if not os.path.exists(file_path):
return "文件不存在,请检查路径", 0
total_cost = 0
results = []
log_text.insert(tk.END, f"📂 开始处理TXT文件:{file_path}\n")
log_text.see(tk.END)
with open(file_path, "r", encoding="utf-8") as f:
lines = [line.strip() for line in f.readlines() if line.strip()]
for idx, line in enumerate(lines):
log_text.insert(tk.END, f"🔄 正在处理TXT第{idx+1}条文本...\n")
log_text.see(tk.END)
result, cost = process_text(line, prompt_template, custom_prompt)
total_cost += cost
results.append(f"【TXT第{idx+1}条】\n处理结果:{result}\n单次费用:${cost}\n")
summary = f"\n===== TXT处理完成 =====\n共处理{len(results)}条有效文本,总费用:${total_cost:.6f}\n"
log_text.insert(tk.END, f"✅ TXT文件处理完成!\n")
log_text.see(tk.END)
return "\n".join(results + [summary]), total_cost
def process_word_gui(file_path, prompt_template="会议纪要总结", custom_prompt="", log_text=None):
"""Word处理(适配GUI)"""
if not os.path.exists(file_path) or not file_path.endswith(".docx"):
return "文件不存在或非.docx格式", 0
try:
log_text.insert(tk.END, f"📂 开始处理Word文件:{file_path}\n")
log_text.see(tk.END)
doc = Document(file_path)
full_text = "\n".join([para.text.strip() for para in doc.paragraphs if para.text.strip()])
if not full_text:
log_text.insert(tk.END, "❌ Word文档无有效文本内容\n")
log_text.see(tk.END)
return "Word文档无有效文本内容", 0
log_text.insert(tk.END, "🔄 正在处理Word文档全文...\n")
log_text.see(tk.END)
result, cost = process_text(full_text, prompt_template, custom_prompt)
summary = f"\n===== Word处理完成 =====\n文档路径:{file_path}\n处理结果:\n{result}\n总费用:${cost:.6f}\n"
log_text.insert(tk.END, f"✅ Word文件处理完成!\n")
log_text.see(tk.END)
return summary, cost
except Exception as e:
log_text.insert(tk.END, f"❌ Word处理失败:{str(e)}\n")
log_text.see(tk.END)
return f"Word处理失败:{str(e)}", 0
def process_excel_gui(file_path, sheet_name="Sheet1", col_index=0, prompt_template="会议纪要总结", custom_prompt="", log_text=None):
"""Excel处理(适配GUI)"""
if not os.path.exists(file_path) or not file_path.endswith(".xlsx"):
return "文件不存在或非.xlsx格式", 0
try:
log_text.insert(tk.END, f"📂 开始处理Excel文件:{file_path}\n")
log_text.see(tk.END)
wb = openpyxl.load_workbook(file_path)
ws = wb[sheet_name] if sheet_name in wb.sheetnames else wb.active
total_cost = 0
results = []
log_text.insert(tk.END, f"🔄 正在处理Excel工作表:{sheet_name},第{col_index+1}列(从0开始)\n")
log_text.see(tk.END)
for row_idx, row in enumerate(ws.iter_rows(min_row=2, min_col=col_index+1, max_col=col_index+1, values_only=True)):
cell_text = row[0]
if not cell_text or not isinstance(cell_text, str):
continue
log_text.insert(tk.END, f"🔄 正在处理Excel第{row_idx+2}行文本...\n")
log_text.see(tk.END)
result, cost = process_text(str(cell_text), prompt_template, custom_prompt)
total_cost += cost
results.append(f"【Excel第{row_idx+2}行】\n处理结果:{result}\n单次费用:${cost}\n")
wb.close()
summary = f"\n===== Excel处理完成 =====\n共处理{len(results)}行有效文本,总费用:${total_cost:.6f}\n"
log_text.insert(tk.END, f"✅ Excel文件处理完成!\n")
log_text.see(tk.END)
return "\n".join(results + [summary]), total_cost
except Exception as e:
log_text.insert(tk.END, f"❌ Excel处理失败:{str(e)}\n")
log_text.see(tk.END)
return f"Excel处理失败:{str(e)}", 0
def process_pdf_gui(file_path, prompt_template="会议纪要总结", custom_prompt="", log_text=None):
"""PDF处理(适配GUI)"""
if not os.path.exists(file_path) or not file_path.endswith(".pdf"):
return "文件不存在或非.pdf格式", 0
try:
log_text.insert(tk.END, f"📂 开始处理PDF文件:{file_path}\n")
log_text.see(tk.END)
with open(file_path, "rb") as f:
pdf_reader = PyPDF2.PdfReader(f)
full_text = ""
log_text.insert(tk.END, f"🔄 正在提取PDF所有页面文本(共{len(pdf_reader.pages)}页)...\n")
log_text.see(tk.END)
for page_num in range(len(pdf_reader.pages)):
page = pdf_reader.pages[page_num]
page_text = page.extract_text()
if page_text:
full_text += page_text.strip() + "\n"
if not full_text:
log_text.insert(tk.END, "❌ PDF文档无可提取的文本(可能是图片型PDF)\n")
log_text.see(tk.END)
return "PDF文档无可提取的文本(可能是图片型PDF)", 0
log_text.insert(tk.END, "🔄 正在处理PDF提取的文本...\n")
log_text.see(tk.END)
result, cost = process_text(full_text, prompt_template, custom_prompt)
summary = f"\n===== PDF处理完成 =====\n文档路径:{file_path}\n处理结果:\n{result}\n总费用:${cost:.6f}\n"
log_text.insert(tk.END, f"✅ PDF文件处理完成!\n")
log_text.see(tk.END)
return summary, cost
except Exception as e:
log_text.insert(tk.END, f"❌ PDF处理失败:{str(e)}\n")
log_text.see(tk.END)
return f"PDF处理失败:{str(e)}", 0
# ===================== 多轮记忆交互 =====================
def memory_interaction_gui(log_text, result_text, export_func):
"""多轮记忆交互(适配GUI)"""
global client
if not client:
messagebox.showwarning("警告", "Azure OpenAI配置未完成,请先在「配置」页面填写信息并保存!")
return
# 初始化上下文记忆和费用统计
messages = [{"role": "system", "content": "你是专业的职场AI助手,能记住上下文对话内容,回答贴合职场场景,简洁实用。"}]
total_cost = 0
all_results = []
# 多轮对话窗口
memory_window = tk.Toplevel()
memory_window.title("多轮记忆交互")
memory_window.geometry("800x600")
memory_window.resizable(True, True)
# 对话显示区域
dialog_text = scrolledtext.ScrolledText(memory_window, wrap=tk.WORD, font=("微软雅黑", 10))
dialog_text.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)
dialog_text.insert(tk.END, "💬 多轮记忆交互已启动,输入内容即可对话(输入「清空记忆」重置,「导出」保存结果)\n\n")
dialog_text.config(state=tk.DISABLED)
# 输入区域
input_frame = ttk.Frame(memory_window)
input_frame.pack(padx=10, pady=5, fill=tk.X)
input_entry = ttk.Entry(input_frame, font=("微软雅黑", 10))
input_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
def send_message():
"""发送对话消息,处理上下文记忆"""
nonlocal total_cost, all_results
user_input = input_entry.get().strip()
if not user_input:
return
input_entry.delete(0, tk.END)
# 显示用户输入
dialog_text.config(state=tk.NORMAL)
dialog_text.insert(tk.END, f"你:{user_input}\n\n")
dialog_text.see(tk.END)
dialog_text.config(state=tk.DISABLED)
# 处理特殊指令
if user_input == "清空记忆":
nonlocal messages
messages = [{"role": "system", "content": "你是专业的职场AI助手,能记住上下文对话内容,回答贴合职场场景,简洁实用。"}]
all_results = []
total_cost = 0
dialog_text.config(state=tk.NORMAL)
dialog_text.insert(tk.END, "助手:记忆已清空,可重新开始交互\n\n")
dialog_text.see(tk.END)
dialog_text.config(state=tk.DISABLED)
return
elif user_input == "导出":
if not all_results:
dialog_text.config(state=tk.NORMAL)
dialog_text.insert(tk.END, "助手:暂无可导出的结果\n\n")
dialog_text.see(tk.END)
dialog_text.config(state=tk.DISABLED)
return
export_format = filedialog.asksaveasfilename(
defaultextension=".txt",
filetypes=[("TXT文件", "*.txt"), ("Word文件", "*.docx"), ("Excel文件", "*.xlsx")],
title="选择导出文件路径"
)
if not export_format:
return
ext = export_format.split(".")[-1]
if ext not in ["txt", "docx", "xlsx"]:
messagebox.showerror("错误", "不支持的导出格式,仅支持txt/word/excel")
return
file_name = export_format.rsplit(".", 1)[0]
export_msg = export_func("\n".join(all_results), ext, file_name)
dialog_text.config(state=tk.NORMAL)
dialog_text.insert(tk.END, f"助手:{export_msg}\n\n")
dialog_text.see(tk.END)
dialog_text.config(state=tk.DISABLED)
return
# 调用Azure OpenAI
messages.append({"role": "user", "content": user_input})
try:
response = client.chat.completions.create(
model=config["DEPLOYMENT_NAME"],
messages=messages,
temperature=0.3
)
prompt_tokens = response.usage.prompt_tokens
completion_tokens = response.usage.completion_tokens
cost = (prompt_tokens / 1000 * 0.00015) + (completion_tokens / 1000 * 0.0006)
total_cost += cost
response_content = response.choices[0].message.content.strip()
messages.append({"role": "assistant", "content": response_content})
all_results.append(f"你:{user_input}\n助手:{response_content}\n费用:${cost}")
# 显示回复
dialog_text.config(state=tk.NORMAL)
dialog_text.insert(tk.END, f"助手:{response_content}\n(本次费用:${cost},累计费用:${total_cost:.6f})\n\n")
dialog_text.see(tk.END)
dialog_text.config(state=tk.DISABLED)
except Exception as e:
error_msg = f"处理失败:{str(e)}"
dialog_text.config(state=tk.NORMAL)
dialog_text.insert(tk.END, f"助手:{error_msg}\n\n")
dialog_text.see(tk.END)
dialog_text.config(state=tk.DISABLED)
all_results.append(f"你:{user_input}\n助手:{error_msg}\n费用:$0")
# 发送按钮
send_btn = ttk.Button(input_frame, text="发送", command=send_message)
send_btn.pack(side=tk.RIGHT, padx=5)
# 回车发送
memory_window.bind("<Return>", lambda event: send_message())
# ===================== GUI界面搭建 =====================
class AzureAIApp:
def __init__(self, root):
self.root = root
self.root.title("Azure OpenAI 桌面版AI工作助手")
self.root.geometry("1000x700")
self.root.resizable(True, True)
# 全局变量
self.file_path = ""
self.selected_template = tk.StringVar(value="会议纪要总结")
self.custom_prompt = tk.StringVar(value="")
self.sheet_name = tk.StringVar(value="Sheet1")
self.col_index = tk.StringVar(value="0")
# 顶部标签页
self.notebook = ttk.Notebook(root)
self.notebook.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)
# 配置页面
self.config_frame = ttk.Frame(self.notebook)
self.notebook.add(self.config_frame, text="配置")
self._create_config_page()
# 文件处理页面
self.file_process_frame = ttk.Frame(self.notebook)
self.notebook.add(self.file_process_frame, text="文件处理")
self._create_file_process_page()
# 多轮交互页面
self.memory_frame = ttk.Frame(self.notebook)
self.notebook.add(self.memory_frame, text="多轮交互")
self._create_memory_page()
# 结果显示页面
self.result_frame = ttk.Frame(self.notebook)
self.notebook.add(self.result_frame, text="处理结果")
self._create_result_page()
# 初始化Azure客户端
self._init_azure_client()
def _create_config_page(self):
"""创建配置页面"""
config_label = ttk.Label(self.config_frame, text="Azure OpenAI 配置(仅需填写一次,自动保存)", font=("微软雅黑", 12, "bold"))
config_label.pack(padx=10, pady=10, anchor=tk.W)
# 配置项框架
config_grid = ttk.Frame(self.config_frame)
config_grid.pack(padx=10, pady=5, fill=tk.X, expand=True)
# Azure Endpoint
endpoint_label = ttk.Label(config_grid, text="Azure Endpoint:")
endpoint_label.grid(row=0, column=0, padx=5, pady=5, sticky=tk.E)
self.endpoint_entry = ttk.Entry(config_grid, font=("微软雅黑", 10), textvariable=tk.StringVar(value=config["azure_endpoint"]))
self.endpoint_entry.grid(row=0, column=1, padx=5, pady=5, sticky=tk.EW)
config_grid.columnconfigure(1, weight=1)
# API Key
api_key_label = ttk.Label(config_grid, text="API Key:")
api_key_label.grid(row=1, column=0, padx=5, pady=5, sticky=tk.E)
self.api_key_entry = ttk.Entry(config_grid, font=("微软雅黑", 10), show="*", textvariable=tk.StringVar(value=config["api_key"]))
self.api_key_entry.grid(row=1, column=1, padx=5, pady=5, sticky=tk.EW)
# Deployment Name
deploy_label = ttk.Label(config_grid, text="Deployment Name:")
deploy_label.grid(row=2, column=0, padx=5, pady=5, sticky=tk.E)
self.deploy_entry = ttk.Entry(config_grid, font=("微软雅黑", 10), textvariable=tk.StringVar(value=config["DEPLOYMENT_NAME"]))
self.deploy_entry.grid(row=2, column=1, padx=5, pady=5, sticky=tk.EW)
# 保存按钮
save_btn = ttk.Button(self.config_frame, text="保存配置", command=self._save_config)
save_btn.pack(padx=10, pady=10, anchor=tk.W)
# 提示信息
tip_label = ttk.Label(self.config_frame, text="提示:配置保存后,下次运行无需重新填写;若修改配置,需重新保存并重启工具。", font=("微软雅黑", 9), foreground="gray")
tip_label.pack(padx=10, pady=5, anchor=tk.W)
def _create_file_process_page(self):
"""创建文件处理页面(移除拖拽事件,仅保留点击选择)"""
# 1. 文件上传区域
upload_frame = ttk.Frame(self.file_process_frame)
upload_frame.pack(padx=10, pady=10, fill=tk.X)
# 仅保留提示文字,移除拖拽绑定
self.upload_label = ttk.Label(upload_frame, text="📌 点击下方按钮选择待处理文件", borderwidth=2, relief=tk.GROOVE, padding=30, font=("微软雅黑", 10))
self.upload_label.pack(fill=tk.X, expand=True)
# 选择文件按钮(核心选择文件方式)
select_btn = ttk.Button(upload_frame, text="选择文件", command=self._select_file)
select_btn.pack(pady=5)
# 当前文件显示
self.file_label = ttk.Label(upload_frame, text="当前未选择文件", font=("微软雅黑", 9), foreground="gray")
self.file_label.pack(pady=5)
# 2. 模板选择区域
template_frame = ttk.Frame(self.file_process_frame)
template_frame.pack(padx=10, pady=5, fill=tk.X)
template_label = ttk.Label(template_frame, text="选择处理模板:")
template_label.pack(side=tk.LEFT, padx=5)
# 模板下拉框
template_combobox = ttk.Combobox(template_frame, textvariable=self.selected_template, values=list(PROMPT_TEMPLATES.keys()), state="readonly")
template_combobox.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
# 自定义提示词
self.custom_prompt_frame = ttk.Frame(template_frame)
self.custom_prompt_label = ttk.Label(self.custom_prompt_frame, text="自定义提示词:")
self.custom_prompt_entry = ttk.Entry(self.custom_prompt_frame, textvariable=self.custom_prompt, font=("微软雅黑", 10))
self.custom_prompt_label.pack(side=tk.LEFT, padx=5)
self.custom_prompt_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
# 监听模板选择
self.selected_template.trace("w", self._toggle_custom_prompt)
# 3. Excel专属配置
self.excel_config_frame = ttk.Frame(self.file_process_frame)
excel_sheet_label = ttk.Label(self.excel_config_frame, text="工作表名称:")
self.excel_sheet_entry = ttk.Entry(self.excel_config_frame, textvariable=self.sheet_name, font=("微软雅黑", 10), width=15)
excel_col_label = ttk.Label(self.excel_config_frame, text="待处理列索引(从0开始):")
self.excel_col_entry = ttk.Entry(self.excel_config_frame, textvariable=self.col_index, font=("微软雅黑", 10), width=5)
excel_sheet_label.pack(side=tk.LEFT, padx=5)
self.excel_sheet_entry.pack(side=tk.LEFT, padx=5)
excel_col_label.pack(side=tk.LEFT, padx=5)
self.excel_col_entry.pack(side=tk.LEFT, padx=5)
# 4. 处理按钮
process_btn = ttk.Button(self.file_process_frame, text="开始处理", command=self._process_file)
process_btn.pack(padx=10, pady=10, anchor=tk.W)
# 5. 日志显示区域
log_label = ttk.Label(self.file_process_frame, text="处理日志:", font=("微软雅黑", 10, "bold"))
log_label.pack(padx=10, pady=5, anchor=tk.W)
self.log_text = scrolledtext.ScrolledText(self.file_process_frame, wrap=tk.WORD, font=("微软雅黑", 9), height=15)
self.log_text.pack(padx=10, pady=5, fill=tk.BOTH, expand=True)
self.log_text.insert(tk.END, "📋 处理日志将显示在这里...\n")
def _create_memory_page(self):
"""创建多轮交互页面"""
memory_label = ttk.Label(self.memory_frame, text="多轮记忆交互(支持上下文连贯对话)", font=("微软雅黑", 12, "bold"))
memory_label.pack(padx=10, pady=10, anchor=tk.W)
tip_label = ttk.Label(self.memory_frame, text="提示:点击下方按钮启动多轮交互,支持「清空记忆」「导出结果」指令", font=("微软雅黑", 9), foreground="gray")
tip_label.pack(padx=10, pady=5, anchor=tk.W)
# 启动按钮
start_memory_btn = ttk.Button(self.memory_frame, text="启动多轮交互", command=lambda: memory_interaction_gui(self.log_text, self.result_text, export_result))
start_memory_btn.pack(padx=10, pady=10)
# 日志显示
log_label = ttk.Label(self.memory_frame, text="交互日志:", font=("微软雅黑", 10, "bold"))
log_label.pack(padx=10, pady=5, anchor=tk.W)
self.memory_log_text = scrolledtext.ScrolledText(self.memory_frame, wrap=tk.WORD, font=("微软雅黑", 9), height=20)
self.memory_log_text.pack(padx=10, pady=5, fill=tk.BOTH, expand=True)
self.memory_log_text.insert(tk.END, "📋 多轮交互日志将显示在这里...\n")
def _create_result_page(self):
"""创建结果显示页面"""
result_label = ttk.Label(self.result_frame, text="处理结果(可复制/导出)", font=("微软雅黑", 12, "bold"))
result_label.pack(padx=10, pady=10, anchor=tk.W)
# 结果显示区域
self.result_text = scrolledtext.ScrolledText(self.result_frame, wrap=tk.WORD, font=("微软雅黑", 10), height=25)
self.result_text.pack(padx=10, pady=5, fill=tk.BOTH, expand=True)
self.result_text.insert(tk.END, "📄 处理结果将显示在这里...\n")
# 导出按钮区域
export_frame = ttk.Frame(self.result_frame)
export_frame.pack(padx=10, pady=10, fill=tk.X)
export_label = ttk.Label(export_frame, text="导出格式:")
export_label.pack(side=tk.LEFT, padx=5)
self.export_format = tk.StringVar(value="txt")
export_combobox = ttk.Combobox(export_frame, textvariable=self.export_format, values=["txt", "word", "excel"], state="readonly")
export_combobox.pack(side=tk.LEFT, padx=5)
export_btn = ttk.Button(export_frame, text="导出结果", command=self._export_result)
export_btn.pack(side=tk.LEFT, padx=5)
# 复制按钮
copy_btn = ttk.Button(export_frame, text="复制结果", command=self._copy_result)
copy_btn.pack(side=tk.LEFT, padx=5)
def _init_azure_client(self):
"""初始化Azure OpenAI客户端"""
global client
if config["azure_endpoint"] and config["api_key"] and config["DEPLOYMENT_NAME"]:
try:
client = AzureOpenAI(
azure_endpoint=config["azure_endpoint"],
api_key=config["api_key"],
api_version="2024-08-01-preview"
)
self.log_text.insert(tk.END, "✅ Azure OpenAI客户端初始化成功!\n")
self.log_text.see(tk.END)
except Exception as e:
self.log_text.insert(tk.END, f"❌ Azure OpenAI初始化失败:{str(e)}\n")
self.log_text.see(tk.END)
else:
self.log_text.insert(tk.END, "⚠️ 请先在「配置」页面填写Azure OpenAI信息并保存\n")
self.log_text.see(tk.END)
def _save_config(self):
"""保存Azure配置"""
global config, client
config["azure_endpoint"] = self.endpoint_entry.get().strip()
config["api_key"] = self.api_key_entry.get().strip()
config["DEPLOYMENT_NAME"] = self.deploy_entry.get().strip()
if not all(config.values()):
messagebox.showwarning("警告", "请填写完整的配置信息!")
return
save_config(config)
self._init_azure_client()
messagebox.showinfo("成功", "配置已保存!")
def _select_file(self, event=None):
"""选择文件"""
file_types = [
("所有支持格式", "*.txt;*.docx;*.xlsx;*.pdf"),
("TXT文件", "*.txt"),
("Word文件", "*.docx"),
("Excel文件", "*.xlsx"),
("PDF文件", "*.pdf")
]
file_path = filedialog.askopenfilename(filetypes=file_types, title="选择待处理文件")
if file_path:
self.file_path = file_path
self.file_label.config(text=f"当前选择文件:{os.path.basename(file_path)}", foreground="black")
self._toggle_excel_config()
def _toggle_custom_prompt(self, *args):
"""显示/隐藏自定义提示词"""
if self.selected_template.get() == "自定义模板":
self.custom_prompt_frame.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
else:
self.custom_prompt_frame.pack_forget()
def _toggle_excel_config(self):
"""显示/隐藏Excel配置"""
if self.file_path.endswith(".xlsx"):
self.excel_config_frame.pack(padx=10, pady=5, anchor=tk.W)
else:
self.excel_config_frame.pack_forget()
def _process_file(self):
"""处理选择的文件"""
if not self.file_path:
messagebox.showwarning("警告", "请先选择待处理文件!")
return
if not client:
messagebox.showwarning("警告", "Azure OpenAI配置未完成,请先在「配置」页面填写信息并保存!")
return
# 获取模板和自定义提示词
template_name = self.selected_template.get()
custom_prompt = self.custom_prompt.get().strip() if template_name == "自定义模板" else ""
# 清空结果,更新日志
self.result_text.delete(1.0, tk.END)
self.log_text.insert(tk.END, "\n" + "="*50 + "\n")
self.log_text.insert(tk.END, f"📅 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} 开始处理文件\n")
self.log_text.see(tk.END)
# 按文件格式处理
try:
if self.file_path.endswith(".txt"):
result, _ = process_txt_gui(self.file_path, template_name, custom_prompt, self.log_text)
elif self.file_path.endswith(".docx"):
result, _ = process_word_gui(self.file_path, template_name, custom_prompt, self.log_text)
elif self.file_path.endswith(".xlsx"):
sheet_name = self.sheet_name.get().strip() or "Sheet1"
col_index = int(self.col_index.get().strip()) if self.col_index.get().strip().isdigit() else 0
result, _ = process_excel_gui(self.file_path, sheet_name, col_index, template_name, custom_prompt, self.log_text)
elif self.file_path.endswith(".pdf"):
result, _ = process_pdf_gui(self.file_path, template_name, custom_prompt, self.log_text)
else:
result = "不支持的文件格式!"
self.log_text.insert(tk.END, f"❌ {result}\n")
self.log_text.see(tk.END)
# 显示结果
self.result_text.insert(tk.END, result)
self.result_text.see(tk.END)
messagebox.showinfo("处理完成", "文件处理已完成,可在「处理结果」页面查看或导出!")
except Exception as e:
error_msg = f"文件处理异常:{str(e)}"
self.log_text.insert(tk.END, f"❌ {error_msg}\n")
self.log_text.see(tk.END)
messagebox.showerror("错误", error_msg)
def _export_result(self):
"""导出结果"""
result = self.result_text.get(1.0, tk.END).strip()
if not result or result == "📄 处理结果将显示在这里...":
messagebox.showwarning("警告", "暂无处理结果可导出!")
return
export_format = self.export_format.get()
file_path = filedialog.asksaveasfilename(
defaultextension=f".{export_format}",
filetypes=[(f"{export_format.upper()}文件", f"*.{export_format}")],
title=f"导出{export_format.upper()}文件"
)
if not file_path:
return
export_msg = export_result(result, export_format, file_path.rsplit(".", 1)[0])
messagebox.showinfo("导出结果", export_msg)
def _copy_result(self):
"""复制结果到剪贴板"""
result = self.result_text.get(1.0, tk.END).strip()
if not result or result == "📄 处理结果将显示在这里...":
messagebox.showwarning("警告", "暂无处理结果可复制!")
return
self.root.clipboard_clear()
self.root.clipboard_append(result)
messagebox.showinfo("复制成功", "处理结果已复制到剪贴板!")
# ===================== 主程序 =====================
if __name__ == "__main__":
root = tk.Tk()
app = AzureAIApp(root)
root.mainloop()




三、核心升级点详解(衔接前两篇,突出GUI优势)
本篇工具是前两篇的升级,核心突破"命令行操作门槛",所有功能均适配GUI可视化界面,同时保留并优化前两篇的核心能力,具体升级点如下:
- 升级1:GUI可视化界面
核心解决"前两篇需输入命令、路径,新手操作繁琐"的问题,基于Python自带的tkinter库打造桌面界面,分为4个标签页(配置、文件处理、多轮交互、处理结果),所有操作均通过点击按钮、选择下拉框完成,无需输入任何命令。
-
界面布局:清晰划分功能区域,配置页填写Azure信息、文件处理页上传文件、多轮交互页启动对话、结果页查看/导出,逻辑连贯,新手易上手;
-
便捷操作:支持文件拖拽上传(无需手动输入路径)、下拉框选择模板/格式、按钮启动处理/导出,操作流程贴合日常桌面软件使用习惯;
-
实时反馈:新增日志显示区域,处理进度、错误信息、费用提示实时显示,无需等待处理完成即可了解进度。
- 升级2:配置自动保存(无需每次输入Azure信息)
核心解决"前两篇每次运行都需输入Azure OpenAI配置"的问题,新增配置保存功能,首次填写后自动保存到本地JSON文件(azure_config.json),下次运行自动加载,无需重复输入。
-
配置逻辑:通过
load_config函数读取配置文件,save_config函数保存配置,JSON格式存储,可手动打开修改; -
安全提示:API Key输入时显示为星号(隐藏显示),避免泄露;配置文件保存在工具同目录,可自行备份或删除;
-
初始化校验:启动工具时自动校验配置,配置完成后自动初始化Azure OpenAI客户端,失败则提示错误信息。
- 升级3:全功能整合+GUI适配
整合前两篇的所有核心功能,均适配GUI界面,无需切换工具,一站式完成文本处理,同时优化功能细节:
-
多格式处理:保留TXT/Word/Excel/PDF处理能力,适配GUI文件选择和拖拽上传,Excel文件新增专属配置(工作表名称、列索引);
-
模板功能:保留自定义提示词模板,通过下拉框选择模板,选择"自定义模板"时自动显示输入框,无需手动切换;
-
多轮记忆交互:新增独立的多轮交互标签页,点击按钮启动对话窗口,支持上下文记忆、清空记忆、导出结果,对话记录实时显示;
-
结果导出:新增结果复制功能,导出时可通过文件选择器选择保存路径,无需手动输入文件名和路径。
- 升级4:提升使用体验
-
拖拽交互:支持文件拖拽上传,拖拽进入/离开窗口时有边框样式提示,提升操作便捷性;
-
异常提示:所有操作均有弹窗提示(如未选择文件、配置不完整),错误信息实时显示在日志区域,方便排查问题;
-
跨系统适配:tkinter支持Windows/Mac系统,代码无需修改,复制即可运行;
-
文本交互:多轮对话支持回车发送消息,贴合日常聊天习惯,对话记录可导出复用。
代码很长,大概知道哪里按需修改即可。
总结
到此, 已经花了大量的时间延时OpenAI的内容,是时候去探索一下其他Azure AI的内容了,比如Agent的搭建。