import os
import sys
import requests
import json
from datetime import datetime
from flask import Flask, request, jsonify, render_template_string
import webbrowser
import threading
import pystray
from PIL import Image
import base64
from io import BytesIO
# 将标准输出和标准错误重定向到一个文件
log_file = open('error.log', 'a')
sys.stdout = log_file
sys.stderr = log_file
# 从环境变量读取API密钥
#运行“ $env:OPENAI_API_KEY="your_api_key_here" ”配置
api_key = os.getenv("OPENAI_API_KEY")
if not api_key:
raise ValueError("API key not found. Please set the OPENAI_API_KEY environment variable.")
# 打印部分API密钥进行调试
print(f"Using API Key: {api_key[:5]}...{api_key[-5:]}")
# 更新 API URL
api_url = "https://api.oneabc.org/v1/chat/completions" #替换为你对应的api地址
# 请求头,包含 API 密钥
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
# 存储记录的文件路径
records_file_path = "api_records.json"
# Flask app 初始化
app = Flask(__name__)
# 函数:读取记录
def read_records(file_path):
if os.path.exists(file_path):
with open(file_path, 'r', encoding='utf-8') as file:
try:
records = json.load(file)
except json.JSONDecodeError:
records = []
else:
records = []
return records
# 函数:保存记录
def save_record(file_path, record):
records = read_records(file_path)
records.append(record)
with open(file_path, 'w', encoding='utf-8') as file:
json.dump(records, file, ensure_ascii=False, indent=4)
# 函数:获取响应
def get_response(data):
try:
print("Sending request to OpenAI API...")
print("Request data:", data)
response = requests.post(api_url, headers=headers, json=data, timeout=60) # 增加超时时间到60秒
response.raise_for_status()
response_data = response.json()
print("Response received from OpenAI API:", response_data)
# 存储请求和响应记录
record = {
"timestamp": datetime.now().isoformat(),
"request": data,
"response": response_data
}
save_record(records_file_path, record)
return response_data
except requests.exceptions.RequestException as e:
# 打印详细的错误信息
print(f"HTTP request failed: {e}")
if e.response:
print("Response status code:", e.response.status_code)
print("Response content:", e.response.content.decode())
except json.JSONDecodeError:
print("Failed to parse response as JSON.")
print("Response content:", response.text)
return None
@app.route("/")
def index():
return render_template_string(r'''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Enhanced GPT-4 Chat</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/default.min.css">
<style>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f9;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
height: 100vh;
}
.container {
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
}
.chat-log {
flex: 1;
border: 1px solid #ccc;
padding: 10px;
overflow-y: auto;
padding-bottom: 70px; /* 确保底部有足够的空间显示完整的内容 */
}
.input-area-container {
position: fixed;
bottom: 0;
width: 100%;
display: flex;
border-top: 1px solid #ccc;
background-color: #fff;
}
.input-area {
flex: 1;
height: 50px;
padding: 10px;
border: none;
resize: none;
}
.send-btn {
width: 80px;
height: 70px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
}
.send-btn:hover {
background-color: #0056b3;
}
.message {
margin: 20px 0; /* 增大对话框之间的空隙 */
padding: 10px;
border-radius: 10px;
position: relative;
}
.message.user {
background-color: #d1d1d1;
text-align: right;
}
.message.assistant {
background-color: #b3d1ff;
}
.code-container {
position: relative;
}
.copy-btn-container {
display: flex;
justify-content: space-between;
margin-top: 5px;
}
button.copy-btn {
padding: 5px 10px;
border: none;
background: #007bff;
color: white;
cursor: pointer;
}
button.copy-btn:hover {
background: #0056b3;
}
.copy-btn-right {
position: absolute;
top: 5px;
right: 5px;
}
#result-dialog {
display: none;
position: fixed;
top: 20%;
left: 50%;
transform: translate(-50%, -20%);
background: white;
padding: 20px;
border: 1px solid #ccc;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
#result-dialog textarea {
width: 100%;
height: 200px;
margin-bottom: 10px;
}
#result-dialog .dialog-buttons {
display: flex;
justify-content: space-between;
}
.btn {
padding: 10px 20px;
margin: 5px;
border: none;
background-color: #007bff;
color: white;
cursor: pointer;
}
.btn:hover {
background-color: #0056b3;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dompurify@2.3.3/dist/purify.min.js"></script>
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
<script>
// MathJax 配置
window.MathJax = {
tex: {
inlineMath: [['$', '$'], ['\\(', '\\)']],
displayMath: [['$$', '$$'], ['\\[', '\\]']],
processEscapes: true,
processEnvironments: true
},
options: {
ignoreHtmlClass: "tex2jax_ignore",
processHtmlClass: "tex2jax_process"
}
};
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll('pre code').forEach((block) => {
hljs.highlightBlock(block);
const pre = block.parentElement;
pre.classList.add('code-container');
const copyButton = document.createElement('button');
copyButton.className = 'copy-btn copy-btn-right';
copyButton.textContent = 'Copy';
copyButton.onclick = () => copyToClipboard(block);
pre.appendChild(copyButton);
});
});
let messages = [
{"role": "system", "content": "You are a helpful assistant. When writing mathematical expressions, use the $$...$$ format in Markdown."}
];
function sendMessage() {
const userInput = document.getElementById("input-text").value;
if (userInput.toLowerCase() === "exit") {
alert("Exiting...");
return;
}
const chatLog = document.getElementById("chat-log");
chatLog.innerHTML += `
<div class="message user">
${userInput}
<div class="copy-btn-container" style="justify-content: flex-end;">
<button class="copy-btn" onclick="copyToClipboardText(this)">Copy</button>
</div>
</div>`;
fetch("/send_message", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
message: userInput,
messages: messages
})
})
.then(response => response.json())
.then(data => {
if (data.assistant_message) {
let assistantMessage = DOMPurify.sanitize(marked.parse(data.assistant_message));
// 调试输出
console.log("Assistant raw message: ", data.assistant_message);
// 替换自定义数学标记为MathJax标记
assistantMessage = assistantMessage
.replace(/\\\(/g, '\\(') // 替换 \( 为 \(
.replace(/\\\)/g, '\\)') // 替换 \) 为 \)
.replace(/\$\$/g, '$$'); // 确保 $$ 保持不变
// 调试输出
console.log("Assistant sanitized message: ", assistantMessage);
chatLog.innerHTML += `
<div class="message assistant">
${assistantMessage}
<div class="copy-btn-container" style="justify-content: flex-start;">
<button class="copy-btn" onclick="copyToClipboardText(this)">Copy</button>
</div>
</div>`;
messages = data.messages;
document.getElementById("input-text").value = "";
// 重新渲染MathJax
MathJax.typesetPromise().then(() => {
chatLog.scrollTop = chatLog.scrollHeight; // 滚动到底部
document.querySelectorAll('pre code').forEach((block) => {
hljs.highlightBlock(block);
const pre = block.parentElement;
pre.classList.add('code-container');
if (!pre.querySelector('.copy-btn-right')) {
const copyButton = document.createElement('button');
copyButton.className = 'copy-btn copy-btn-right';
copyButton.textContent = 'Copy';
copyButton.onclick = () => copyToClipboard(block);
pre.appendChild(copyButton);
}
});
});
} else {
chatLog.innerHTML += `<div class="message error">Failed to get response</div>`;
}
})
.catch(error => {
chatLog.innerHTML += `<div class="message error">Error: ${error}</div>`;
});
}
function copyToClipboardText(button) {
const text = button.closest('.message').textContent.replace("Copy", "").trim();
navigator.clipboard.writeText(text);
alert("Copied to clipboard: " + text);
}
function copyToClipboard(block) {
const text = block.textContent;
navigator.clipboard.writeText(text);
alert("Copied to clipboard: " + text);
}
function showResultDialog() {
const resultContent = messages.map(msg => `${msg.role}: ${msg.content}`).join('\n\n');
document.getElementById("result-content").value = resultContent;
document.getElementById("result-dialog").style.display = 'block';
}
function copyResult() {
const resultContent = document.getElementById("result-content");
resultContent.select();
document.execCommand("copy");
alert("Results copied to clipboard!");
}
function closeResultDialog() {
document.getElementById("result-dialog").style.display = 'none';
}
</script>
</head>
<body>
<div class="container">
<div class="chat-log" id="chat-log"></div>
<div class="input-area-container">
<textarea class="input-area" id="input-text"></textarea>
<button class="send-btn" onclick="sendMessage()">Send</button>
<button class="send-btn" onclick="showResultDialog()">Show Results</button>
</div>
</div>
<div id="result-dialog">
<textarea id="result-content" readonly></textarea>
<div class="dialog-buttons">
<button class="btn" onclick="copyResult()">Copy</button>
<button class="btn" onclick="closeResultDialog()">Close</button>
</div>
</div>
</body>
</html>
''')
@app.route("/send_message", methods=["POST"])
def send_message():
user_input = request.json.get("message")
messages = request.json.get("messages", [])
messages.append({"role": "user", "content": user_input})
data = {
"model": "gpt-4o-mini-2024-07-18", #替换为你所需的模型
"messages": messages
}
response_data = get_response(data)
if response_data:
assistant_message = response_data['choices'][0]['message']['content']
messages.append({"role": "assistant", "content": assistant_message})
return jsonify({
"assistant_message": assistant_message,
"messages": messages
})
else:
print("Failed to get a valid response.")
return jsonify({"error": "Failed to get response"}), 500
# 系统托盘图标创建
def create_image():
icon_base64_data = (
"xxx"
)
image_data = base64.b64decode(icon_base64_data)
image = Image.open(BytesIO(image_data))
return image
# 退出Flask应用和系统托盘图标
def quit_application(icon, item):
icon.stop()
os._exit(0)
# 系统托盘图标设置
def setup_tray():
icon = pystray.Icon("test_icon")
icon.icon = create_image()
icon.title = "Test Icon"
icon.menu = pystray.Menu(
pystray.MenuItem("Open", lambda: webbrowser.open("http://localhost:5000")),
pystray.MenuItem("Quit", quit_application)
)
icon.run()
if __name__ == "__main__":
# 启动 Flask 应用
flask_thread = threading.Thread(target=lambda: app.run(debug=True, use_reloader=False))
flask_thread.start()
# 启动系统托盘图标
setup_tray()
'''
创建一个新Pyhon脚本
# convert_icon.py
import base64
# 使用绝对路径读取图标文件
icon_path = "替换为你的路径"
with open(icon_path, "rb") as png_file:
icon_bytes = png_file.read()
# 将字节数组转换为Base64编码字符串
encoded_icon = base64.b64encode(icon_bytes).decode('utf-8')
# 指定输出文件路径
output_path = "替换为你的路径"
# 将Base64编码字符串写入指定文件
with open(output_path, "w") as text_file:
text_file.write(encoded_icon)
print(f"Base64 encoded icon has been saved to {output_path}")
'''
'''
# 系统托盘图标创建
def create_image():
icon_base64_data = (
"xxx"
)
把xxx替换为输出的结果
'''
web页面,具体的看注释,可以markdown