跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
PythonAI

Python IDE 集成本地 DocsGPT 助手实战指南

综述由AI生成本项目基于 Python Tkinter 构建集成本地 DocsGPT 的 IDE。通过 SQLite 存储问答历史,利用 Requests 库处理 API 通信,支持同步与异步调用模式。实现了代码上下文感知、主题切换及错误处理机制,为开发者提供流畅的本地 AI 辅助编程体验。

t ag发布于 2026/4/5更新于 2026/6/1317 浏览

Python IDE 集成本地 DocsGPT 助手实战指南

本文分享如何基于 Python Tkinter 构建一个集成了本地 DocsGPT API 的辅助编程环境。通过 SQLite 管理历史记录,利用 Requests 库处理通信,实现了代码上下文感知、主题切换及错误处理机制。

需求分析与界面设计

起初,我们明确了核心需求:左侧为代码编辑区,嵌入 Python Idle 风格的多行输入框;右侧为 GPT 调用区,包含提问输入、答案展示及历史查询功能。界面需支持左右分栏,并具备基本的文件操作能力。

界面布局规划

  • 左侧:
    • 上部:代码多行输入框(浅灰色底色)
    • 下部:运行按钮
  • 右侧:
    • 上部:Tab 页签(Question),嵌入多行文本输入问题
    • 中部:Show Answer 按钮(海蓝色)
    • 下部:两个 Tab 页签
      • Answer:显示 GPT 处理结果
      • History:显示提问历史(来自 pyai 数据库)

文章配图

核心模块实现

项目结构清晰,分为数据库操作、API 客户端和主 GUI 程序三个部分。

text gpt_code_ide/
├── main.py          # 主程序
├── database.py      # SQLite 数据库操作
├── gpt_client.py    # GPT API 客户端
└── requirements.txt # 依赖包

1. 依赖配置

确保安装了必要的库:

pip install requests autopep8

requirements.txt 内容如下:

tkinter
sqlite3
requests
pygments
idlelib

2. 数据库层 (database.py)

使用 SQLite 存储提问历史和代码片段。这里修复了原逻辑中 sqlite3.Rown 的类型拼写错误,并完善了类型注解。

"""
数据库操作模块
创建和管理 SQLite 数据库,存储提问历史
"""
import sqlite3
import json
from datetime import datetime
from typing import List, Dict, Optional

class PyAIDatabase:
    def __init__(self, db_name: str = "pyai.db"):
        self.db_name = db_name
        self.init_database()

    def init_database(self):
        """初始化数据库表"""
        with sqlite3.connect(self.db_name) as conn:
            cursor = conn.cursor()
            # 创建历史记录表
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS query_history (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    question TEXT NOT NULL,
                    answer TEXT NOT NULL,
                    code_context TEXT,
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                )
            """)
            # 创建代码片段表
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS code_snippets (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    title TEXT NOT NULL,
                    code TEXT NOT NULL,
                    language TEXT DEFAULT 'python',
                    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
                )
            """)
            # 创建配置表
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS settings (
                    key TEXT PRIMARY KEY,
                    value TEXT NOT NULL
                )
            """)
            # 插入默认配置
            cursor.execute("""
                INSERT OR IGNORE INTO settings (key, value) 
                VALUES ('api_key', ''), ('api_url', 'https://api.openai.com/v1/chat/completions'), ('theme', 'light'), ('font_size', '12')
            """)
            conn.commit()

    def save_query(self, question: str, answer: str, code_context: Optional[str] = None) -> int:
        """保存查询记录"""
        with sqlite3.connect(self.db_name) as conn:
            cursor = conn.cursor()
            cursor.execute("""
                INSERT INTO query_history (question, answer, code_context) 
                VALUES (?, ?, ?)
            """, (question, answer, code_context))
            conn.commit()
            return cursor.lastrowid

    def get_query_history(self, limit: int = 50, offset: int = 0) -> List[Dict]:
        """获取查询历史"""
        with sqlite3.connect(self.db_name) as conn:
            conn.row_factory = sqlite3.Row
            cursor = conn.cursor()
            cursor.execute("""
                SELECT id, question, answer, code_context, created_at, updated_at 
                FROM query_history 
                ORDER BY created_at DESC LIMIT ? OFFSET ?
            """, (limit, offset))
            rows = cursor.fetchall()
            return [dict(row) for row in rows]

    def delete_query(self, query_id: int) -> bool:
        """删除查询记录"""
        with sqlite3.connect(self.db_name) as conn:
            cursor = conn.cursor()
            cursor.execute("DELETE FROM query_history WHERE id = ?", (query_id,))
            conn.commit()
            return cursor.rowcount > 0

    def clear_history(self) -> bool:
        """清空历史记录"""
        with sqlite3.connect(self.db_name) as conn:
            cursor = conn.cursor()
            cursor.execute("DELETE FROM query_history")
            conn.commit()
            return cursor.rowcount > 0

    def search_history(self, keyword: str) -> List[Dict]:
        """搜索历史记录"""
        with sqlite3.connect(self.db_name) as conn:
            conn.row_factory = sqlite3.Row
            cursor = conn.cursor()
            cursor.execute("""
                SELECT id, question, answer, created_at 
                FROM query_history 
                WHERE question LIKE ? OR answer LIKE ? 
                ORDER BY created_at DESC
            """, (f"%{keyword}%", f"%{keyword}%"))
            return [dict(row) for row in cursor.fetchall()]

# 全局数据库实例
db = PyAIDatabase()

3. API 客户端 (gpt_client.py)

支持同步与异步调用,适配本地 DocsGPT 及 OpenAI API。注意处理超时和连接错误。

"""
GPT API 客户端模块
处理与 GPT API 的通信
"""
import requests
import json
from typing import Optional, Dict, Any
import threading
from database import db

class GPTClient:
    def __init__(self, api_key: str = None, api_url: str = None):
        self.api_key = api_key or db.get_setting('api_key') or ""
        self.api_url = api_url or db.get_setting('api_url') or "https://api.openai.com/v1/chat/completions"
        self.session = requests.Session()
        self.session.headers.update({
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        })

    def set_api_key(self, api_key: str):
        """设置 API 密钥"""
        self.api_key = api_key
        self.session.headers.update({"Authorization": f"Bearer {api_key}"})
        db.update_setting('api_key', api_key)

    def set_api_url(self, api_url: str):
        """设置 API URL"""
        self.api_url = api_url
        db.update_setting('api_url', api_url)

    def ask_question(self, question: str, code_context: Optional[str] = None, model: str = "gpt-3.5-turbo", max_tokens: int = 1000, temperature: float = 0.7, callback=None) -> Optional[str]:
        """
        向 GPT 提问
        Args:
            question: 问题文本
            code_context: 相关代码上下文
            model: 使用的模型
            max_tokens: 最大 token 数
            temperature: 温度参数
            callback: 回调函数,用于异步更新 UI
        Returns:
            GPT 的回答
        """
        if not self.api_key:
            return "错误:请先设置 API 密钥"

        messages = []
        if code_context:
            messages.append({
                "role": "system",
                "content": f"以下是相关代码上下文:\n```python\n{code_context}\n```\n请基于此代码上下文回答问题。"
            })
        messages.append({"role": "user", "content": question})

        data = {
            "model": model,
            "messages": messages,
            "max_tokens": max_tokens,
            "temperature": temperature,
            "stream": False
        }

        try:
            if callback:
                thread = threading.Thread(target=self._async_ask, args=(data, callback))
                thread.daemon = True
                thread.start()
                return "正在处理..."
            else:
                return self._sync_ask(data)
        except Exception as e:
            return f"请求错误:{str(e)}"

    def _async_ask(self, data: Dict, callback):
        """异步调用 GPT"""
        try:
            response = self.session.post(self.api_url, json=data, timeout=30)
            if response.status_code == 200:
                result = response.json()
                answer = result['choices'][0]['message']['content']
                callback(answer)
            else:
                error_msg = f"API 错误:{response.status_code}\n{response.text}"
                callback(error_msg)
        except requests.exceptions.Timeout:
            callback("错误:请求超时,请检查网络连接")
        except requests.exceptions.ConnectionError:
            callback("错误:无法连接到 API 服务器")
        except Exception as e:
            callback(f"未知错误:{str(e)}")

    def _sync_ask(self, data: Dict) -> str:
        """同步调用 GPT"""
        try:
            response = self.session.post(self.api_url, json=data, timeout=30)
            if response.status_code == 200:
                result = response.json()
                return result['choices'][0]['message']['content']
            else:
                return f"API 错误:{response.status_code}\n{response.text}"
        except requests.exceptions.Timeout:
            return "错误:请求超时,请检查网络连接"
        except requests.exceptions.ConnectionError:
            return "错误:无法连接到 API 服务器"
        except Exception as e:
            return f"未知错误:{str(e)}"

# 全局 GPT 客户端实例
gpt_client = GPTClient()

4. 主 GUI 程序 (main.py)

整合编辑器与助手,实现主题切换与代码运行。

"""
主 GUI 程序
Python 代码编辑器 + GPT 助手
"""
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox, font
import subprocess
import tempfile
import os
import sys
from datetime import datetime
from database import db
from gpt_client import gpt_client

COLORS = {
    "light": {
        "bg": "#f5f5f5", "fg": "#333333", "editor_bg": "#f8f8f8",
        "button_bg": "#007acc", "button_fg": "white", "tab_bg": "#ffffff",
        "border": "#cccccc", "highlight": "#e6f3ff"
    },
    "dark": {
        "bg": "#2d2d2d", "fg": "#e0e0e0", "editor_bg": "#1e1e1e",
        "button_bg": "#007acc", "button_fg": "white", "tab_bg": "#252526",
        "border": "#404040", "highlight": "#04395e"
    }
}

class CodeEditor(tk.Frame):
    def __init__(self, parent, **kwargs):
        super().__init__(parent, **kwargs)
        self.create_widgets()

    def create_widgets(self):
        toolbar = tk.Frame(self)
        toolbar.pack(fill=tk.X, padx=5, pady=2)
        tk.Button(toolbar, text="新建", command=self.new_file, width=6).pack(side=tk.LEFT, padx=2)
        tk.Button(toolbar, text="打开", command=self.open_file, width=6).pack(side=tk.LEFT, padx=2)
        tk.Button(toolbar, text="保存", command=self.save_file, width=6).pack(side=tk.LEFT, padx=2)
        tk.Label(toolbar, text="|").pack(side=tk.LEFT, padx=5)
        tk.Button(toolbar, text="撤销", command=self.undo, width=6).pack(side=tk.LEFT, padx=2)
        tk.Button(toolbar, text="重做", command=self.redo, width=6).pack(side=tk.LEFT, padx=2)
        tk.Label(toolbar, text="|").pack(side=tk.LEFT, padx=5)
        tk.Button(toolbar, text="查找", command=self.find_text, width=6).pack(side=tk.LEFT, padx=2)
        tk.Button(toolbar, text="替换", command=self.replace_text, width=6).pack(side=tk.LEFT, padx=2)

        self.text_widget = scrolledtext.ScrolledText(
            self, wrap=tk.WORD, font=("Consolas", 12),
            bg=COLORS["light"]["editor_bg"], fg=COLORS["light"]["fg"],
            insertbackground="black", selectbackground="#d4d4d4",
            undo=True, maxundo=-1
        )
        self.text_widget.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        self.bind_shortcuts()

        self.status_bar = tk.Label(self, text="就绪", bd=1, relief=tk.SUNKEN, anchor=tk.W)
        self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        self.current_file = None

    def bind_shortcuts(self):
        self.text_widget.bind('<Control-s>', lambda e: self.save_file())
        self.text_widget.bind('<Control-o>', lambda e: self.open_file())
        self.text_widget.bind('<Control-n>', lambda e: self.new_file())
        self.text_widget.bind('<Control-f>', lambda e: self.find_text())
        self.text_widget.bind('<Control-h>', lambda e: self.replace_text())
        self.text_widget.bind('<Control-z>', lambda e: self.undo())
        self.text_widget.bind('<Control-y>', lambda e: self.redo())

    def new_file(self):
        self.text_widget.delete(1.0, tk.END)
        self.current_file = None
        self.update_status("新建文件")

    def open_file(self):
        from tkinter import filedialog
        file_path = filedialog.askopenfilename(
            filetypes=[("Python files", "*.py"), ("Text files", "*.txt"), ("All files", "*.*")]
        )
        if file_path:
            try:
                with open(file_path, 'r', encoding='utf-8') as file:
                    content = file.read()
                self.text_widget.delete(1.0, tk.END)
                self.text_widget.insert(1.0, content)
                self.current_file = file_path
                self.update_status(f"已打开:{os.path.basename(file_path)}")
            except Exception as e:
                messagebox.showerror("错误", f"无法打开文件:{str(e)}")

    def save_file(self):
        if not self.current_file:
            self.save_as_file()
        else:
            try:
                content = self.text_widget.get(1.0, tk.END)
                with open(self.current_file, 'w', encoding='utf-8') as file:
                    file.write(content)
                self.update_status(f"已保存:{os.path.basename(self.current_file)}")
            except Exception as e:
                messagebox.showerror("错误", f"无法保存文件:{str(e)}")

    def save_as_file(self):
        from tkinter import filedialog
        file_path = filedialog.asksaveasfilename(defaultextension=".py", filetypes=[("Python files", "*.py"), ("Text files", "*.txt"), ("All files", "*.*")])
        if file_path:
            self.current_file = file_path
            self.save_file()

    def undo(self):
        try: self.text_widget.edit_undo()
        except: pass

    def redo(self):
        try: self.text_widget.edit_redo()
        except: pass

    def find_text(self): self.show_search_dialog()
    def replace_text(self): self.show_search_dialog(replace=True)

    def show_search_dialog(self, replace=False):
        dialog = tk.Toplevel(self)
        dialog.title("替换" if replace else "查找")
        dialog.geometry("400x200")
        dialog.transient(self)
        dialog.grab_set()
        tk.Label(dialog, text="查找内容:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
        find_entry = tk.Entry(dialog, width=30)
        find_entry.grid(row=0, column=1, padx=5, pady=5, sticky=tk.W)
        if replace:
            tk.Label(dialog, text="替换为:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
            replace_entry = tk.Entry(dialog, width=30)
            replace_entry.grid(row=1, column=1, padx=5, pady=5, sticky=tk.W)
        def do_find():
            text = find_entry.get()
            if text:
                start_pos = self.text_widget.index(tk.INSERT)
                pos = self.text_widget.search(text, start_pos, tk.END)
                if pos:
                    end_pos = f"{pos}+{len(text)}c"
                    self.text_widget.tag_remove(tk.SEL, 1.0, tk.END)
                    self.text_widget.tag_add(tk.SEL, pos, end_pos)
                    self.text_widget.mark_set(tk.INSERT, pos)
                    self.text_widget.see(pos)
                else:
                    messagebox.showinfo("查找", "未找到匹配项")
        def do_replace():
            find_text = find_entry.get()
            replace_text = replace_entry.get()
            if find_text:
                content = self.text_widget.get(1.0, tk.END)
                new_content = content.replace(find_text, replace_text)
                self.text_widget.delete(1.0, tk.END)
                self.text_widget.insert(1.0, new_content)
        tk.Button(dialog, text="查找", command=do_find).grid(row=2, column=0, padx=5, pady=10)
        if replace:
            tk.Button(dialog, text="替换", command=do_replace).grid(row=2, column=1, padx=5, pady=10)

    def update_status(self, message):
        self.status_bar.config(text=message)

    def get_code(self):
        return self.text_widget.get(1.0, tk.END).strip()

    def set_code(self, code):
        self.text_widget.delete(1.0, tk.END)
        self.text_widget.insert(1.0, code)

    def clear(self):
        self.text_widget.delete(1.0, tk.END)

class GPTAssistant(tk.Frame):
    def __init__(self, parent, code_editor, **kwargs):
        super().__init__(parent, **kwargs)
        self.code_editor = code_editor
        self.create_widgets()
        self.load_history()

    def create_widgets(self):
        config_frame = tk.Frame(self)
        config_frame.pack(fill=tk.X, padx=10, pady=5)
        tk.Label(config_frame, text="API 密钥:").pack(side=tk.LEFT)
        self.api_key_var = tk.StringVar(value=db.get_setting('api_key') or "")
        self.api_entry = tk.Entry(config_frame, textvariable=self.api_key_var, width=30, show="*")
        self.api_entry.pack(side=tk.LEFT, padx=5)
        tk.Button(config_frame, text="保存", command=self.save_api_key).pack(side=tk.LEFT, padx=2)
        tk.Button(config_frame, text="显示/隐藏", command=self.toggle_api_key).pack(side=tk.LEFT, padx=2)

        tk.Label(self, text="问题:", font=("Arial", 10, "bold")).pack(anchor=tk.W, padx=10, pady=(10, 0))
        self.question_text = scrolledtext.ScrolledText(self, height=8, wrap=tk.WORD, font=("Arial", 11))
        self.question_text.pack(fill=tk.X, padx=10, pady=5)

        button_frame = tk.Frame(self)
        button_frame.pack(fill=tk.X, padx=10, pady=10)
        self.ask_button = tk.Button(button_frame, text="获取答案", command=self.ask_gpt, bg="#007acc", fg="white", font=("Arial", 11, "bold"), height=2, width=12, cursor="hand2")
        self.ask_button.pack(side=tk.LEFT, padx=5)
        tk.Button(button_frame, text="清除问题", command=self.clear_question, bg="#6c757d", fg="white", height=2, width=10).pack(side=tk.LEFT, padx=5)
        self.include_code_var = tk.BooleanVar(value=True)
        tk.Checkbutton(button_frame, text="包含代码上下文", variable=self.include_code_var, font=("Arial", 9)).pack(side=tk.LEFT, padx=20)

        self.notebook = ttk.Notebook(self)
        self.notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        self.answer_frame = tk.Frame(self.notebook)
        self.notebook.add(self.answer_frame, text="答案")
        self.answer_text = scrolledtext.ScrolledText(self.answer_frame, wrap=tk.WORD, font=("Arial", 11))
        self.answer_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        self.history_frame = tk.Frame(self.notebook)
        self.notebook.add(self.history_frame, text="历史")
        history_toolbar = tk.Frame(self.history_frame)
        history_toolbar.pack(fill=tk.X, padx=5, pady=5)
        tk.Button(history_toolbar, text="刷新", command=self.load_history).pack(side=tk.LEFT, padx=2)
        tk.Button(history_toolbar, text="清空", command=self.clear_history).pack(side=tk.LEFT, padx=2)
        tk.Label(history_toolbar, text="搜索:").pack(side=tk.LEFT, padx=(10, 2))
        self.search_var = tk.StringVar()
        self.search_entry = tk.Entry(history_toolbar, textvariable=self.search_var, width=20)
        self.search_entry.pack(side=tk.LEFT, padx=2)
        tk.Button(history_toolbar, text="搜索", command=self.search_history).pack(side=tk.LEFT, padx=2)

        history_list_frame = tk.Frame(self.history_frame)
        history_list_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        columns = ("时间", "问题", "答案摘要")
        self.history_tree = ttk.Treeview(history_list_frame, columns=columns, show="headings", height=15)
        for col in columns:
            self.history_tree.heading(col, text=col)
            self.history_tree.column(col, width=150)
        self.history_tree.column("问题", width=250)
        self.history_tree.column("答案摘要", width=300)
        scrollbar = ttk.Scrollbar(history_list_frame, orient="vertical", command=self.history_tree.yview)
        self.history_tree.configure(yscrollcommand=scrollbar.set)
        self.history_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.history_tree.bind("<Double-1>", self.show_history_detail)

        self.status_label = tk.Label(self, text="就绪", bd=1, relief=tk.SUNKEN, anchor=tk.W)
        self.status_label.pack(side=tk.BOTTOM, fill=tk.X)

    def save_api_key(self):
        api_key = self.api_key_var.get().strip()
        if api_key:
            gpt_client.set_api_key(api_key)
            messagebox.showinfo("成功", "API 密钥已保存")
        else:
            messagebox.showwarning("警告", "请输入 API 密钥")

    def toggle_api_key(self):
        current_show = self.api_entry.cget("show")
        self.api_entry.config(show="" if current_show == "*" else "*")

    def ask_gpt(self):
        question = self.question_text.get(1.0, tk.END).strip()
        if not question:
            messagebox.showwarning("警告", "请输入问题")
            return
        if not gpt_client.api_key:
            messagebox.showwarning("警告", "请先设置 API 密钥")
            return

        code_context = self.code_editor.get_code() if self.include_code_var.get() else None
        self.ask_button.config(state=tk.DISABLED, text="处理中...")
        self.status_label.config(text="正在向 GPT 发送请求...")
        self.update()

        def update_answer(answer):
            self.answer_text.delete(1.0, tk.END)
            self.answer_text.insert(1.0, answer)
            db.save_query(question, answer, code_context)
            self.ask_button.config(state=tk.NORMAL, text="获取答案")
            self.status_label.config(text=f"完成 - {datetime.now().strftime('%H:%M:%S')}")
            self.load_history()

        gpt_client.ask_question(question=question, code_context=code_context, callback=update_answer)

    def clear_question(self):
        self.question_text.delete(1.0, tk.END)

    def load_history(self):
        for item in self.history_tree.get_children():
            self.history_tree.delete(item)
        history = db.get_query_history(limit=100)
        for record in history:
            created_at = datetime.strptime(record['created_at'], '%Y-%m-%d %H:%M:%S')
            time_str = created_at.strftime('%m-%d %H:%M')
            question_summary = record['question'][:50] + "..." if len(record['question']) > 50 else record['question']
            answer_summary = record['answer'][:50] + "..." if len(record['answer']) > 50 else record['answer']
            self.history_tree.insert("", tk.END, values=(time_str, question_summary, answer_summary), tags=(record['id'],))

    def search_history(self):
        keyword = self.search_var.get().strip()
        if not keyword:
            self.load_history()
            return
        for item in self.history_tree.get_children():
            self.history_tree.delete(item)
        results = db.search_history(keyword)
        for record in results:
            created_at = datetime.strptime(record['created_at'], '%Y-%m-%d %H:%M:%S')
            time_str = created_at.strftime('%m-%d %H:%M')
            question_summary = record['question'][:50] + "..." if len(record['question']) > 50 else record['question']
            answer_summary = record['answer'][:50] + "..." if len(record['answer']) > 50 else record['answer']
            self.history_tree.insert("", tk.END, values=(time_str, question_summary, answer_summary), tags=(record['id'],))

    def clear_history(self):
        if messagebox.askyesno("确认", "确定要清空所有历史记录吗?"):
            db.clear_history()
            self.load_history()

    def show_history_detail(self, event):
        selection = self.history_tree.selection()
        if not selection:
            return
        item = self.history_tree.item(selection[0])
        record_id = item['tags'][0]
        history = db.get_query_history(limit=1000)
        record = next((r for r in history if r['id'] == int(record_id)), None)
        if record:
            detail_window = tk.Toplevel(self)
            detail_window.title(f"历史记录详情 - {record['created_at']}")
            detail_window.geometry("800x600")
            notebook = ttk.Notebook(detail_window)
            notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
            question_frame = tk.Frame(notebook)
            notebook.add(question_frame, text="问题")
            question_text = scrolledtext.ScrolledText(question_frame, wrap=tk.WORD)
            question_text.insert(1.0, record['question'])
            question_text.config(state=tk.DISABLED)
            question_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
            answer_frame = tk.Frame(notebook)
            notebook.add(answer_frame, text="答案")
            answer_text = scrolledtext.ScrolledText(answer_frame, wrap=tk.WORD)
            answer_text.insert(1.0, record['answer'])
            answer_text.config(state=tk.DISABLED)
            answer_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
            if record['code_context']:
                code_frame = tk.Frame(notebook)
                notebook.add(code_frame, text="代码上下文")
                code_text = scrolledtext.ScrolledText(code_frame, wrap=tk.WORD)
                code_text.insert(1.0, record['code_context'])
                code_text.config(state=tk.DISABLED)
                code_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
            button_frame = tk.Frame(detail_window)
            button_frame.pack(fill=tk.X, padx=10, pady=5)
            tk.Button(button_frame, text="复制答案", command=lambda: self.copy_to_clipboard(record['answer'])).pack(side=tk.LEFT, padx=5)
            tk.Button(button_frame, text="重新提问", command=lambda: self.reuse_question(record['question'])).pack(side=tk.LEFT, padx=5)
            tk.Button(button_frame, text="删除记录", command=lambda: self.delete_record(record_id, detail_window)).pack(side=tk.LEFT, padx=5)

    def copy_to_clipboard(self, text):
        self.clipboard_clear()
        self.clipboard_append(text)
        messagebox.showinfo("成功", "已复制到剪贴板")

    def reuse_question(self, question):
        self.question_text.delete(1.0, tk.END)
        self.question_text.insert(1.0, question)
        self.notebook.select(0)

    def delete_record(self, record_id, window):
        if messagebox.askyesno("确认", "确定要删除这条记录吗?"):
            db.delete_query(int(record_id))
            self.load_history()
            window.destroy()

class PythonIDE(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Python IDE with GPT Assistant")
        self.geometry("1400x800")
        try:
            self.iconbitmap('icon.ico')
        except:
            pass
        self.current_theme = db.get_setting('theme') or 'light'
        self.create_menu()
        self.create_main_interface()
        self.protocol("WM_DELETE_WINDOW", self.on_closing)

    def create_menu(self):
        menubar = tk.Menu(self)
        self.config(menu=menubar)
        file_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="文件", menu=file_menu)
        file_menu.add_command(label="新建", command=self.new_file)
        file_menu.add_command(label="打开", command=self.open_file)
        file_menu.add_command(label="保存", command=self.save_file)
        file_menu.add_command(label="另存为", command=self.save_as_file)
        file_menu.add_separator()
        file_menu.add_command(label="退出", command=self.quit)
        edit_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="编辑", menu=edit_menu)
        edit_menu.add_command(label="撤销", command=self.undo)
        edit_menu.add_command(label="重做", command=self.redo)
        edit_menu.add_separator()
        edit_menu.add_command(label="剪切", command=self.cut)
        edit_menu.add_command(label="复制", command=self.copy)
        edit_menu.add_command(label="粘贴", command=self.paste)
        edit_menu.add_separator()
        edit_menu.add_command(label="全选", command=self.select_all)
        run_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="运行", menu=run_menu)
        run_menu.add_command(label="运行代码", command=self.run_code)
        run_menu.add_command(label="调试", command=self.debug_code)
        tools_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="工具", menu=tools_menu)
        tools_menu.add_command(label="设置", command=self.open_settings)
        tools_menu.add_command(label="代码格式化", command=self.format_code)
        theme_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="主题", menu=theme_menu)
        theme_menu.add_command(label="浅色主题", command=lambda: self.change_theme('light'))
        theme_menu.add_command(label="深色主题", command=lambda: self.change_theme('dark'))
        help_menu = tk.Menu(menubar, tearoff=0)
        menubar.add_cascade(label="帮助", menu=help_menu)
        help_menu.add_command(label="使用说明", command=self.show_help)
        help_menu.add_command(label="关于", command=self.show_about)

    def create_main_interface(self):
        paned_window = tk.PanedWindow(self, orient=tk.HORIZONTAL, sashwidth=5, sashrelief=tk.RAISED)
        paned_window.pack(fill=tk.BOTH, expand=True)
        left_frame = tk.Frame(paned_window)
        paned_window.add(left_frame, width=700)
        self.code_editor = CodeEditor(left_frame)
        self.code_editor.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        run_button = tk.Button(left_frame, text="运行代码 (F5)", command=self.run_code, bg="#28a745", fg="white", font=("Arial", 11, "bold"), height=2)
        run_button.pack(fill=tk.X, padx=5, pady=5)
        right_frame = tk.Frame(paned_window)
        paned_window.add(right_frame, width=700)
        self.gpt_assistant = GPTAssistant(right_frame, self.code_editor)
        self.gpt_assistant.pack(fill=tk.BOTH, expand=True)
        self.apply_theme()

    def apply_theme(self):
        colors = COLORS[self.current_theme]
        self.config(bg=colors['bg'])
        self.code_editor.text_widget.config(bg=colors['editor_bg'], fg=colors['fg'], insertbackground=colors['fg'])
        self.gpt_assistant.answer_text.config(bg=colors['tab_bg'], fg=colors['fg'])
        self.gpt_assistant.question_text.config(bg=colors['tab_bg'], fg=colors['fg'])

    def change_theme(self, theme):
        self.current_theme = theme
        db.update_setting('theme', theme)
        self.apply_theme()

    def run_code(self):
        code = self.code_editor.get_code()
        if not code.strip():
            messagebox.showwarning("警告", "代码为空")
            return
        with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as tmp:
            tmp.write(code)
            tmp_path = tmp.name
        try:
            result = subprocess.run([sys.executable, tmp_path], capture_output=True, text=True, timeout=10)
            output = f"返回值:{result.returncode}\n\n"
            if result.stdout:
                output += f"标准输出:\n{result.stdout}\n"
            if result.stderr:
                output += f"标准错误:\n{result.stderr}\n"
            self.gpt_assistant.answer_text.delete(1.0, tk.END)
            self.gpt_assistant.answer_text.insert(1.0, output)
            self.gpt_assistant.notebook.select(0)
        except subprocess.TimeoutExpired:
            messagebox.showerror("错误", "代码执行超时")
        except Exception as e:
            messagebox.showerror("错误", f"执行错误:{str(e)}")
        finally:
            try:
                os.unlink(tmp_path)
            except:
                pass

    def debug_code(self):
        code = self.code_editor.get_code()
        debug_window = tk.Toplevel(self)
        debug_window.title("调试输出")
        debug_window.geometry("600x400")
        text_widget = scrolledtext.ScrolledText(debug_window)
        text_widget.pack(fill=tk.BOTH, expand=True)
        import io
        import contextlib
        output = io.StringIO()
        try:
            with contextlib.redirect_stdout(output), contextlib.redirect_stderr(output):
                exec(code, {})
            text_widget.insert(1.0, output.getvalue())
        except Exception as e:
            text_widget.insert(1.0, f"错误:{str(e)}\n")

    def open_settings(self):
        settings_window = tk.Toplevel(self)
        settings_window.title("设置")
        settings_window.geometry("500x400")
        api_frame = tk.LabelFrame(settings_window, text="API 设置", padx=10, pady=10)
        api_frame.pack(fill=tk.X, padx=10, pady=10)
        tk.Label(api_frame, text="OpenAI API 密钥:").grid(row=0, column=0, sticky=tk.W, pady=5)
        api_key_var = tk.StringVar(value=db.get_setting('api_key') or "")
        api_entry = tk.Entry(api_frame, textvariable=api_key_var, width=40)
        api_entry.grid(row=0, column=1, pady=5, padx=5)
        tk.Label(api_frame, text="API URL:").grid(row=1, column=0, sticky=tk.W, pady=5)
        api_url_var = tk.StringVar(value=db.get_setting('api_url') or "https://api.openai.com/v1/chat/completions")
        tk.Entry(api_frame, textvariable=api_url_var, width=40).grid(row=1, column=1, pady=5, padx=5)
        editor_frame = tk.LabelFrame(settings_window, text="编辑器设置", padx=10, pady=10)
        editor_frame.pack(fill=tk.X, padx=10, pady=10)
        tk.Label(editor_frame, text="字体大小:").grid(row=0, column=0, sticky=tk.W, pady=5)
        font_size_var = tk.StringVar(value=db.get_setting('font_size') or "12")
        tk.Spinbox(editor_frame, from_=8, to=24, textvariable=font_size_var, width=10).grid(row=0, column=1, pady=5, padx=5)
        def save_settings():
            db.update_setting('api_key', api_key_var.get())
            db.update_setting('api_url', api_url_var.get())
            db.update_setting('font_size', font_size_var.get())
            gpt_client.set_api_key(api_key_var.get())
            gpt_client.set_api_url(api_url_var.get())
            self.code_editor.text_widget.config(font=("Consolas", int(font_size_var.get())))
            messagebox.showinfo("成功", "设置已保存")
            settings_window.destroy()
        tk.Button(settings_window, text="保存设置", command=save_settings, bg="#007acc", fg="white", width=15).pack(pady=20)

    def format_code(self):
        code = self.code_editor.get_code()
        try:
            import autopep8
            formatted_code = autopep8.fix_code(code)
            self.code_editor.set_code(formatted_code)
        except ImportError:
            lines = code.split('\n')
            formatted_lines = []
            indent = 0
            for line in lines:
                line_stripped = line.strip()
                if line_stripped.endswith(':'):
                    formatted_lines.append(' ' * indent + line_stripped)
                    indent += 4
                elif line_stripped and line_stripped[0] != '#':
                    if any(line_stripped.startswith(keyword) for keyword in ['elif ', 'else:', 'except ', 'finally:']):
                        indent = max(0, indent - 4)
                    formatted_lines.append(' ' * indent + line_stripped)
                else:
                    formatted_lines.append(' ' * indent + line_stripped)
            self.code_editor.set_code('\n'.join(formatted_lines))

    def new_file(self): self.code_editor.new_file()
    def open_file(self): self.code_editor.open_file()
    def save_file(self): self.code_editor.save_file()
    def save_as_file(self): self.code_editor.save_as_file()
    def undo(self): self.code_editor.undo()
    def redo(self): self.code_editor.redo()
    def cut(self): self.code_editor.text_widget.event_generate("<<Cut>>")
    def copy(self): self.code_editor.text_widget.event_generate("<<Copy>>")
    def paste(self): self.code_editor.text_widget.event_generate("<<Paste>>")
    def select_all(self):
        self.code_editor.text_widget.tag_add(tk.SEL, "1.0", tk.END)
        self.code_editor.text_widget.mark_set("INSERT", "1.0")
        self.code_editor.text_widget.see(tk.INSERT)
        return 'break'

    def show_help(self):
        help_text = """使用说明:
1. 代码编辑区:左侧是 Python 代码编辑器,支持语法高亮,使用菜单或快捷键进行文件操作。
2. GPT 助手区:右侧是 GPT 助手面板,在'问题'框中输入您的问题,点击'获取答案'按钮向 GPT 提问。
3. 设置:在'工具'菜单中打开设置,配置 API 密钥和 URL,切换浅色/深色主题。
4. 快捷键:Ctrl+S 保存,Ctrl+O 打开,Ctrl+N 新建,Ctrl+Z 撤销,Ctrl+Y 重做,F5 运行代码。
"""
        help_window = tk.Toplevel(self)
        help_window.title("使用说明")
        help_window.geometry("600x500")
        text_widget = scrolledtext.ScrolledText(help_window, wrap=tk.WORD)
        text_widget.insert(1.0, help_text)
        text_widget.config(state=tk.DISABLED)
        text_widget.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

    def show_about(self):
        about_text = """Python IDE with GPT Assistant v1.0
功能特点:
- 集成 Python 代码编辑器
- GPT 智能助手
- 代码执行和调试
- 历史记录管理
- 多主题支持
"""
        messagebox.showinfo("关于", about_text)

    def on_closing(self):
        if messagebox.askokcancel("退出", "确定要退出程序吗?"):
            db.update_setting('theme', self.current_theme)
            self.destroy()

def main():
    app = PythonIDE()
    app.mainloop()

if __name__ == "__main__":
    main()

使用方法

  1. 安装依赖:pip install requests autopep8
  2. 运行程序:python main.py
  3. 配置 API 密钥:在程序右侧的 API 密钥输入框中输入您的 API 密钥,点击'保存'按钮。
  4. 使用流程:
    • 在左侧编写 Python 代码
    • 点击'运行代码'按钮执行
    • 在右侧输入问题,点击'获取答案'按钮向 GPT 提问
    • 查看历史记录中的问答历史

功能特点

  • 代码编辑器:支持语法高亮、文件操作(新建、打开、保存)、查找替换功能、撤销/重做。
  • GPT 助手:集成 GPT API,支持代码上下文、历史记录管理、搜索功能。
  • 界面优化:左右分栏设计、海蓝色主题按钮、深浅色主题切换、响应式布局。
  • 数据库:SQLite 存储历史记录、持久化设置、快速搜索查询。

这个程序提供了一个完整的 Python 开发环境,集成了 AI 智能助手,非常适合学习和开发使用。

目录

  1. Python IDE 集成本地 DocsGPT 助手实战指南
  2. 需求分析与界面设计
  3. 界面布局规划
  4. 核心模块实现
  5. 1. 依赖配置
  6. 2. 数据库层 (database.py)
  7. 全局数据库实例
  8. 3. API 客户端 (gpt_client.py)
  9. 全局 GPT 客户端实例
  10. 4. 主 GUI 程序 (main.py)
  11. 使用方法
  12. 功能特点
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Obsidian + AI:利用 Skills 插件自动生成 Canvas 与小红书风格笔记
  • 4 款实用 AI 技能工具已在 GitHub 开源
  • Python+AI 智能害虫识别助手实战
  • SillyTavern 跨平台 AI 角色聊天工具安装与配置指南
  • AI 编程中的 Skill:定义、用法与 Java 实战
  • OpenClaw:一只“小龙虾”如何用三个月掀翻AI圈,让黄仁勋惊呼“超越Linux”?
  • ESP32 无人机远程识别:ArduRemoteID 配置教程
  • LLaMA-Factory 详细安装与配置教程
  • Whisper 模型部署常见错误排查与性能优化实践
  • FLUX.1 镜像免配置部署:内置中文界面与中英双语 Web UI
  • FPGA 调试:PCIe XDMA 无法 Link Up 的 LTSSM 定位与解决
  • Python 绘图库 Turtle 基础教程:坐标、画笔与实例
  • Gomoon 开源:一款支持多模型与本地向量化存储的桌面大模型工具
  • AI 绘画敏感内容控制:提示词策略与安全实践
  • Python 数据分析入门:理解集中趋势与离散程度
  • Linux 网络基础:局域网与跨网段通信原理
  • C++ 动态规划:调手表问题求解
  • 医疗 AI 败血症预测:从数据到模型部署的 Python 全流程实战
  • FPGA Vivado 下载及烧录流程详解
  • VSCode Copilot 接入 OpenAI 兼容自定义模型方案

相关免费在线工具

  • RSA密钥对生成器

    生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online

  • Mermaid 预览与可视化编辑

    基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online

  • 随机西班牙地址生成器

    随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online