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)}")