首页 AI智能 正文
  • 本文约9005字,阅读需45分钟
  • 591
  • 0

本地运行的离线版的AI对话助手可视化界面

import json
import requests
import threading
import PySimpleGUI as sg
from datetime import datetime
import pyperclip
import os
from pathlib import Path
import re

# 硅基流动 API 配置
SILICONFLOW_API_KEY = "sk-123" #换成自己申请的id
SILICONFLOW_API_URL = "https://api.siliconflow.cn/v1/chat/completions"

class SiliconFlowAI:
    def __init__(self):
        self.session = requests.Session()
        self.headers = {
            "Authorization": f"Bearer {SILICONFLOW_API_KEY}",
            "Content-Type": "application/json"
        }
        self.conversation_history = []
        self.last_prompt = ""

    def _call_api(self, prompt, temperature=0.7, max_tokens=2000, web_search=False, response_style="markdown"):
        messages = self.conversation_history.copy()
        messages.append({"role": "user", "content": prompt})

        data = {
            "model": "deepseek-ai/DeepSeek-R1",
            "messages": messages,
            "temperature": temperature,
            "max_tokens": max_tokens,
            "stream": False,
            "use_web": web_search
        }

        try:
            response = self.session.post(SILICONFLOW_API_URL, headers=self.headers, json=data)
            response.raise_for_status()
            return response.json()
        except requests.RequestException as e:
            print(f"API请求错误: {e}")
            return {"error": str(e)}

    def chat(self, prompt, window, web_search=False, step_by_step=False, response_style="markdown"):
        self.last_prompt = prompt

        # 显示思考过程
        window.write_event_value('-THINKING-', "正在分析您的问题...")

        if step_by_step:
            window.write_event_value('-THINKING-', "我将一步步分析这个问题...")
            style_prompt = {
                "markdown": "请用Markdown格式,逐步分析并回答以下问题",
                "detailed": "请详细地一步步分析以下问题",
                "concise": "请简洁地逐步分析以下问题",
                "txt": "请用纯文本格式逐步分析以下问题"
            }.get(response_style, "请一步步分析以下问题")
            prompt = f"{style_prompt}:\n{prompt}"
        else:
            style_prompt = {
                "markdown": "请用Markdown格式回答以下问题",
                "detailed": "请详细回答以下问题",
                "concise": "请简洁回答以下问题",
                "txt": "请用纯文本格式回答以下问题"
            }.get(response_style, "请回答以下问题")
            prompt = f"{style_prompt}:\n{prompt}"

        # 显示搜索状态
        if web_search:
            window.write_event_value('-THINKING-', "正在联网搜索最新信息...")

        result = self._call_api(prompt, web_search=web_search, response_style=response_style)

        if "error" in result:
            return f"**API错误**: {result['error']}"

        if "choices" in result and result["choices"]:
            response = result["choices"][0].get("message", {}).get("content", "未收到回复")
            self.conversation_history.append({"role": "assistant", "content": response})
            return self._format_response(response, response_style)
        return "**未收到有效回复**"

    def _format_response(self, text, response_style):
        """根据选择的格式处理AI的回答"""
        if response_style == "txt":
            # 移除Markdown格式标记
            text = re.sub(r'[*#_`-]', '', text)
            # 合并多余空行
            text = re.sub(r'\n{3,}', '\n\n', text)
        else:
            text = text.replace("\n\n", "\n \n")  # 保留段间距
        return text.strip()

class SiliconFlowGUI:
    def __init__(self):
        self.ai = SiliconFlowAI()
        self.last_response = ""
        self.last_question = ""
        self.export_folder = Path("AI对话记录")
        self.export_folder.mkdir(exist_ok=True)

        self.text_style = {
            'font': ('微软雅黑', 15),
            'background_color': '#FAFAFA',
            'text_color': '#333333'
        }

        self.layout = [
            [sg.Text("AI 专业聊天助手", font=('Helvetica', 20))],
            [sg.Multiline(
                key='-CHAT-',
                expand_x=True,
                expand_y=True,
                **self.text_style,
                disabled=True,
                autoscroll=True,
                reroute_stdout=True
            )],
            [
                sg.Multiline(
                    key='-INPUT-',
                    expand_x=True,
                    size=(None, 5),
                    **self.text_style,
                    enable_events=True,
                    enter_submits=True,
                    right_click_menu=['&Right', ['粘贴', '清空']]
                ),
                sg.Button('发送', key='-SEND-', size=(8, 2)),
                sg.Button('粘贴', key='-PASTE-', size=(8, 2), tooltip="从剪贴板粘贴内容")
            ],
            [
                sg.Button('复制回答', key='-COPY-', size=(10, 1)),
                sg.Button('复制问题', key='-COPY_QUESTION-', size=(10, 1)),
                sg.Button('导出TXT', key='-EXPORT-', size=(10, 1), tooltip="导出当前对话为文本文件"),
                sg.Button('重新生成', key='-REGEN-', size=(10, 1)),
                sg.Button('清除历史', key='-CLEAR-', size=(10, 1))
            ],
            [
                sg.Checkbox('联网搜索', key='-WEB-', tooltip="启用网络搜索获取最新信息"),
                sg.Checkbox('深度思考', key='-STEP-', tooltip="显示逐步分析过程"),
                sg.Combo(
                    ['精简', '详细', 'Markdown', 'TXT'],
                    default_value='TXT',
                    key='-FORMAT-',
                    size=(8, 1),
                    tooltip="选择回答格式"
                ),
                sg.Button('退出', key='-EXIT-', size=(8, 1)),
                sg.Text('状态: 就绪', size=(20, 1), key='-STATUS-', text_color='green')
            ]
        ]

        self.window = sg.Window(
            "AI 专业聊天助手",
            self.layout,
            finalize=True,
            resizable=True,
            size=(1100, 750),
            margins=(10, 10),
            element_justification='center'
        )

        self.window.TKroot.minsize(850, 550)
        self.window['-INPUT-'].bind("<Return>", "_Enter")
        self.window['-INPUT-'].set_focus()

        # 添加快捷键
        self.window.bind('<Control-v>', '-PASTE-')
        self.window.bind('<Control-V>', '-PASTE-')

        self.print_chat("系统", "欢迎使用AI聊天助手,请输入您的问题")

    def print_chat(self, sender, message, is_error=False, is_thinking=False):
        timestamp = datetime.now().strftime("%H:%M:%S")
        chat = self.window['-CHAT-']
        current = chat.get()

        if sender == "你":
            formatted = f"\n【{timestamp}】💬 你:\n{message}\n\n"
            self.last_question = message
        elif sender == "AI":
            formatted = f"\n【{timestamp}】🤖 AI:\n{message.strip()}\n\n"
            self.last_response = message
        elif is_thinking:
            formatted = f"\n【{timestamp}】🤔 思考中...\n{message}\n\n"
        else:
            color = "red" if is_error else "blue"
            formatted = f"\n【{timestamp}】⚠️ {sender}:\n{message}\n\n"

        chat.update(current + formatted, autoscroll=True)
        self.window.refresh()

    def export_to_txt(self):
        """导出对话历史为TXT文件"""
        try:
            content = self.window['-CHAT-'].get()
            if not content.strip():
                self.print_chat("系统", "没有可导出的对话内容", is_error=True)
                return

            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            filename = f"AI对话_{timestamp}.txt"
            filepath = self.export_folder / filename

            # 清理内容格式
            clean_content = re.sub(r'\n{3,}', '\n\n', content)

            with open(filepath, 'w', encoding='utf-8') as f:
                f.write("=== AI对话记录 ===\n\n")
                f.write(clean_content)
                f.write("\n\n=== 结束 ===")

            self.print_chat("系统", f"对话已导出到: {filepath}")
            self.window['-STATUS-'].update('状态: 导出成功', text_color='blue')

            # 询问用户是否要打开文件
            if sg.popup_yes_no('导出成功,是否要打开文件?', title='导出完成') == 'Yes':
                os.startfile(filepath)
        except Exception as e:
            self.print_chat("系统", f"导出失败: {str(e)}", is_error=True)
            self.window['-STATUS-'].update('状态: 导出失败', text_color='red')

    def chat_with_ai(self, values):
        question = values['-INPUT-'].strip()
        if not question:
            self.print_chat("系统", "问题不能为空!", is_error=True)
            return

        self._disable_buttons()
        self.print_chat("你", question)
        self.window['-INPUT-'].update('')
        self.window['-STATUS-'].update('状态: 思考中...', text_color='orange')

        format_map = {'精简': 'concise', '详细': 'detailed', 'Markdown': 'markdown', 'TXT': 'txt'}
        response_format = format_map.get(values['-FORMAT-'], 'markdown')

        threading.Thread(
            target=self._get_ai_response,
            args=(question, values['-WEB-'], values['-STEP-'], response_format),
            daemon=True
        ).start()

    def _disable_buttons(self):
        """禁用所有操作按钮"""
        for btn in ['-SEND-', '-REGEN-', '-COPY-', '-COPY_QUESTION-', '-PASTE-', '-EXPORT-']:
            self.window[btn].update(disabled=True)

    def _enable_buttons(self):
        """启用所有操作按钮"""
        for btn in ['-SEND-', '-REGEN-', '-COPY-', '-COPY_QUESTION-', '-PASTE-', '-EXPORT-']:
            self.window[btn].update(disabled=False)

    def _get_ai_response(self, question, web_search, step_by_step, format_style):
        """获取AI响应并在完成后处理"""
        try:
            response = self.ai.chat(question, self.window, web_search, step_by_step, format_style)
            self.window.write_event_value('-AI_RESPONSE-', response)
        except Exception as e:
            self.window.write_event_value('-AI_ERROR-', str(e))

    def run(self):
        """主事件循环"""
        while True:
            event, values = self.window.read()

            if event in (None, '-EXIT-'):
                break

            elif event in ('-SEND-', '-INPUT-_Enter'):
                self.chat_with_ai(values)

            elif event == '-THINKING-':
                self.print_chat("AI", values[event], is_thinking=True)

            elif event == '-AI_RESPONSE-':
                self.print_chat("AI", values[event])
                self._enable_buttons()
                self.window['-STATUS-'].update('状态: 就绪', text_color='green')

            elif event == '-AI_ERROR-':
                self.print_chat("系统", f"发生错误: {values[event]}", is_error=True)
                self._enable_buttons()
                self.window['-STATUS-'].update('状态: 错误', text_color='red')

            elif event == '-CLEAR-':
                self.ai.conversation_history = []
                self.window['-CHAT-'].update('')
                self.print_chat("系统", "对话历史已清空")

            elif event == '-COPY-':
                if self.last_response:
                    pyperclip.copy(self.last_response)
                    self.window['-STATUS-'].update('状态: 已复制回答', text_color='blue')
                else:
                    self.print_chat("系统", "没有可复制的回答", is_error=True)

            elif event == '-COPY_QUESTION-':
                if self.last_question:
                    pyperclip.copy(self.last_question)
                    self.window['-STATUS-'].update('状态: 已复制问题', text_color='blue')
                else:
                    self.print_chat("系统", "没有可复制的问题", is_error=True)

            elif event == '-EXPORT-':
                self.export_to_txt()

            elif event == '-REGEN-':
                if not self.ai.last_prompt:
                    self.print_chat("系统", "没有可重新生成的问题", is_error=True)
                    continue

                self._disable_buttons()
                self.window['-STATUS-'].update('状态: 重新生成中...', text_color='orange')
                self.print_chat("系统", f"正在重新生成: {self.ai.last_prompt[:50]}...")

                format_map = {'精简': 'concise', '详细': 'detailed', 'Markdown': 'markdown', 'TXT': 'txt'}
                response_format = format_map.get(values['-FORMAT-'], 'markdown')

                threading.Thread(
                    target=self._get_ai_response,
                    args=(self.ai.last_prompt, values['-WEB-'], values['-STEP-'], response_format),
                    daemon=True
                ).start()

            elif event == '-PASTE-':
                try:
                    clipboard_text = pyperclip.paste()
                    if clipboard_text:
                        current_input = values['-INPUT-']
                        self.window['-INPUT-'].update(current_input + clipboard_text)
                        self.window['-STATUS-'].update('状态: 已粘贴', text_color='blue')
                    else:
                        self.print_chat("系统", "剪贴板中没有内容", is_error=True)
                except Exception as e:
                    self.print_chat("系统", f"粘贴失败: {str(e)}", is_error=True)

            elif event == '粘贴':
                try:
                    clipboard_text = pyperclip.paste()
                    if clipboard_text:
                        self.window['-INPUT-'].update(clipboard_text)
                        self.window['-STATUS-'].update('状态: 已粘贴', text_color='blue')
                except Exception as e:
                    self.print_chat("系统", f"粘贴失败: {str(e)}", is_error=True)

            elif event == '清空':
                self.window['-INPUT-'].update('')

            elif event.startswith('Escape'):
                self.window['-INPUT-'].update('')

        self.window.close()

if __name__ == "__main__":
    if not SILICONFLOW_API_KEY or SILICONFLOW_API_KEY == "sk-your-silicon-flow-api-key":
        sg.popup_error("❌ 错误", "请设置正确的API密钥!", keep_on_top=True)
        exit()

    try:
        app = SiliconFlowGUI()
        app.run()
    except Exception as e:
        sg.popup_error("程序崩溃", f"错误信息: {str(e)}")
标签:AI
收藏

扫描二维码,在手机上阅读
评论