mistpe commited on
Commit
fe4533e
1 Parent(s): cd12a79

Upload 4 files

Browse files
Files changed (4) hide show
  1. README.md +14 -0
  2. app.py +230 -0
  3. index.html +273 -0
  4. requirements (1).txt +4 -0
README.md ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: functioncall
3
+ emoji: 🚀
4
+ colorFrom: yellow
5
+ colorTo: yellow
6
+ sdk: gradio
7
+ sdk_version: 5.0.2
8
+ app_file: app.py
9
+ app_port: 7860
10
+ pinned: false
11
+ license: mit
12
+ ---
13
+
14
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import requests
4
+ import smtplib
5
+ from email.mime.text import MIMEText
6
+ from email.mime.multipart import MIMEMultipart
7
+ from flask import Flask, request, jsonify, send_from_directory
8
+ from openai import OpenAI
9
+ from duckduckgo_search import DDGS
10
+
11
+ app = Flask(__name__)
12
+
13
+ API_KEY = "sk-909Q1F68ZRwTJfT4Bc81BeE1317c4986A5067f347a2f97C9"
14
+ BASE_URL = "https://api.oaipro.com/v1"
15
+
16
+ client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
17
+
18
+ FUNCTIONS = [
19
+ {
20
+ "name": "search_duckduckgo",
21
+ "description": "使用DuckDuckGo搜索引擎查询信息。可以搜索最新新闻、文章、博客等内容。",
22
+ "parameters": {
23
+ "type": "object",
24
+ "properties": {
25
+ "keywords": {
26
+ "type": "array",
27
+ "items": {"type": "string"},
28
+ "description": "搜索的关键词列表。例如:['Python', '机器学习', '最新进展']。"
29
+ }
30
+ },
31
+ "required": ["keywords"]
32
+ }
33
+ },
34
+ {
35
+ "name": "search_papers",
36
+ "description": "使用Crossref API搜索学术论文。",
37
+ "parameters": {
38
+ "type": "object",
39
+ "properties": {
40
+ "query": {
41
+ "type": "string",
42
+ "description": "搜索查询字符串。例如:'climate change'。"
43
+ }
44
+ },
45
+ "required": ["query"]
46
+ }
47
+ },
48
+ {
49
+ "name": "send_email",
50
+ "description": "发送电子邮件。",
51
+ "parameters": {
52
+ "type": "object",
53
+ "properties": {
54
+ "to": {
55
+ "type": "string",
56
+ "description": "收件人邮箱地址"
57
+ },
58
+ "subject": {
59
+ "type": "string",
60
+ "description": "邮件主题"
61
+ },
62
+ "content": {
63
+ "type": "string",
64
+ "description": "邮件内容"
65
+ }
66
+ },
67
+ "required": ["to", "subject", "content"]
68
+ }
69
+ }
70
+ ]
71
+
72
+ def search_duckduckgo(keywords):
73
+ search_term = " ".join(keywords)
74
+ with DDGS() as ddgs:
75
+ return list(ddgs.text(keywords=search_term, region="cn-zh", safesearch="on", max_results=5))
76
+
77
+ def search_papers(query):
78
+ url = f"https://api.crossref.org/works?query={query}"
79
+ response = requests.get(url)
80
+ if response.status_code == 200:
81
+ data = response.json()
82
+ papers = data['message']['items']
83
+ processed_papers = []
84
+ for paper in papers:
85
+ processed_paper = {
86
+ "标题": paper.get('title', [''])[0],
87
+ "作者": ", ".join([f"{author.get('given', '')} {author.get('family', '')}" for author in paper.get('author', [])]),
88
+ "DOI": paper.get('DOI', ''),
89
+ "ISBN": ", ".join(paper.get('ISBN', [])),
90
+ "摘要": paper.get('abstract', '').replace('<p>', '').replace('</p>', '').replace('<italic>', '').replace('</italic>', '')
91
+ }
92
+ processed_papers.append(processed_paper)
93
+ return processed_papers
94
+ else:
95
+ return []
96
+
97
+ def send_email(to, subject, content):
98
+ try:
99
+ with smtplib.SMTP('106.15.184.28', 8025) as smtp:
100
+ smtp.login("jwt", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZGRyZXNzIjoiYWl4aWFvQGFpeGlhby54eXoiLCJhZGRyZXNzX2lkIjoiMSJ9.CidcEUdbixWPkI0wwWJtcnUzfHtTJE3WMIDLHNAIIxo")
101
+ message = MIMEMultipart()
102
+ message['From'] = "Me <[email protected]>"
103
+ message['To'] = to
104
+ message['Subject'] = subject
105
+ message.attach(MIMEText(content, 'html'))
106
+ smtp.sendmail("[email protected]", to, message.as_string())
107
+ return True
108
+ except Exception as e:
109
+ print(f"发送邮件时出错: {str(e)}")
110
+ return False
111
+
112
+ def get_openai_response(messages, model="gpt-4o-mini", functions=None, function_call=None):
113
+ try:
114
+ response = client.chat.completions.create(
115
+ model=model,
116
+ messages=messages,
117
+ functions=functions,
118
+ function_call=function_call
119
+ )
120
+ return response.choices[0].message
121
+ except Exception as e:
122
+ print(f"调用OpenAI API时出错: {str(e)}")
123
+ return None
124
+
125
+ def process_function_call(response_message):
126
+ function_name = response_message.function_call.name
127
+ function_args = json.loads(response_message.function_call.arguments)
128
+ if function_name == "search_duckduckgo":
129
+ keywords = function_args.get('keywords', [])
130
+ if not keywords:
131
+ return None
132
+ return search_duckduckgo(keywords)
133
+ elif function_name == "search_papers":
134
+ query = function_args.get('query', '')
135
+ if not query:
136
+ return None
137
+ return search_papers(query)
138
+ elif function_name == "send_email":
139
+ to = function_args.get('to', '')
140
+ subject = function_args.get('subject', '')
141
+ content = function_args.get('content', '')
142
+ if not to or not subject or not content:
143
+ return None
144
+ success = send_email(to, subject, content)
145
+ return {
146
+ "success": success,
147
+ "to": to,
148
+ "subject": subject,
149
+ "content": content
150
+ }
151
+ else:
152
+ return None
153
+
154
+ @app.route('/')
155
+ def index():
156
+ return send_from_directory('.', 'index.html')
157
+
158
+ @app.route('/chat', methods=['POST'])
159
+ def chat():
160
+ data = request.json
161
+ question = data['question']
162
+ history = data.get('history', [])
163
+ messages = history + [{"role": "user", "content": question}]
164
+
165
+ status_log = []
166
+
167
+ # First model: Decide whether to use search or send email
168
+ status_log.append("次模型:正在判断是否需要进行搜索或发送邮件")
169
+ decision_response = get_openai_response(messages, model="gpt-4o-mini", functions=FUNCTIONS, function_call="auto")
170
+
171
+ search_results = None
172
+ email_result = None
173
+ if decision_response and decision_response.function_call:
174
+ function_name = decision_response.function_call.name
175
+ status_log.append(f"次模型:判断需要{function_name}")
176
+ status_log.append(f"正在进行{function_name}")
177
+ result = process_function_call(decision_response)
178
+ if result:
179
+ if function_name == "search_papers":
180
+ search_results = result[:10]
181
+ elif function_name == "search_duckduckgo":
182
+ search_results = result
183
+ elif function_name == "send_email":
184
+ email_result = result
185
+ status_log.append(f"{function_name}完成")
186
+ else:
187
+ status_log.append("次模型:判断不需要进行搜索或发送邮件")
188
+
189
+ # Generate response based on the action taken
190
+ if email_result:
191
+ response = f"邮件{'已成功' if email_result['success'] else '未能成功'}发送到 {email_result['to']}。\n\n主题:{email_result['subject']}\n\n内容:\n{email_result['content']}"
192
+ elif search_results:
193
+ # Second model: Generate final response with search results
194
+ status_log.append("主模型:正在生成回答")
195
+ messages.append({
196
+ "role": "assistant",
197
+ "content": decision_response.content if decision_response.content else f"I need to {function_name}."
198
+ })
199
+ messages.append({
200
+ "role": "function",
201
+ "name": function_name,
202
+ "content": json.dumps(search_results, ensure_ascii=False)
203
+ })
204
+ final_response = get_openai_response(messages, model="gpt-4o-mini")
205
+ response = final_response.content if final_response else "Error occurred"
206
+ status_log.append("主模型:回答生成完成")
207
+ else:
208
+ # Second model: Generate final response without search results
209
+ status_log.append("主模型:正在生成回答")
210
+ final_response = get_openai_response(messages, model="gpt-4o-mini")
211
+ response = final_response.content if final_response else "Error occurred"
212
+ status_log.append("主模型:回答生成完成")
213
+
214
+ return jsonify({
215
+ "response": response,
216
+ "search_used": bool(search_results),
217
+ "search_results": search_results,
218
+ "email_sent": email_result['success'] if email_result else False,
219
+ "status_log": status_log
220
+ })
221
+
222
+ @app.route('/settings', methods=['POST'])
223
+ def update_settings():
224
+ data = request.json
225
+ max_history = data.get('max_history', 10)
226
+ # 在这里可以添加其他设置项
227
+ return jsonify({"status": "success", "max_history": max_history})
228
+
229
+ if __name__ == '__main__':
230
+ app.run(host='0.0.0.0', port=7860, debug=True)
index.html ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI 对话系统</title>
7
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet">
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css">
10
+ <style>
11
+ .chat-bubble {
12
+ max-width: 80%;
13
+ padding: 10px;
14
+ border-radius: 10px;
15
+ margin-bottom: 10px;
16
+ }
17
+ .user-bubble {
18
+ background-color: #DCF8C6;
19
+ margin-left: auto;
20
+ }
21
+ .ai-bubble {
22
+ background-color: #E5E5EA;
23
+ margin-right: auto;
24
+ }
25
+ .status-indicator {
26
+ width: 10px;
27
+ height: 10px;
28
+ border-radius: 50%;
29
+ display: inline-block;
30
+ margin-right: 5px;
31
+ }
32
+ .status-active {
33
+ background-color: #4CAF50;
34
+ }
35
+ .status-inactive {
36
+ background-color: #9E9E9E;
37
+ }
38
+ pre code {
39
+ white-space: pre-wrap;
40
+ word-wrap: break-word;
41
+ }
42
+ </style>
43
+ </head>
44
+ <body class="bg-gray-100">
45
+ <div class="container mx-auto p-4 flex h-screen">
46
+ <!-- 左侧对话窗口 -->
47
+ <div class="w-2/3 bg-white rounded-lg shadow-md p-4 mr-4 flex flex-col">
48
+ <div id="chat-window" class="flex-grow overflow-y-auto mb-4"></div>
49
+ <div class="flex">
50
+ <input id="user-input" type="text" class="flex-grow border rounded-l-lg p-2" placeholder="输入您的问题...">
51
+ <button id="send-btn" class="bg-blue-500 text-white px-4 py-2 rounded-r-lg">
52
+ <i class="fas fa-paper-plane"></i> 发送
53
+ </button>
54
+ </div>
55
+ </div>
56
+
57
+ <!-- 右侧状态窗口 -->
58
+ <div class="w-1/3 bg-white rounded-lg shadow-md p-4 flex flex-col">
59
+ <h2 class="text-xl font-bold mb-4">运行状态</h2>
60
+ <div class="mb-4">
61
+ <div class="mb-2">
62
+ <span class="status-indicator" id="main-model-status"></span>
63
+ 主模型
64
+ </div>
65
+ <div class="mb-2">
66
+ <span class="status-indicator" id="sub-model-status"></span>
67
+ 次模型
68
+ </div>
69
+ <div class="mb-2">
70
+ <span class="status-indicator" id="search-status"></span>
71
+ 搜索
72
+ </div>
73
+ <div>
74
+ <span class="status-indicator" id="email-status"></span>
75
+ 邮件发送
76
+ </div>
77
+ </div>
78
+ <div id="status-log" class="bg-gray-100 p-2 rounded flex-grow overflow-y-auto"></div>
79
+ <button id="settings-btn" class="mt-4 bg-gray-300 text-gray-800 px-4 py-2 rounded">
80
+ <i class="fas fa-cog"></i> 设置
81
+ </button>
82
+ </div>
83
+ </div>
84
+
85
+ <!-- 设置对话框 -->
86
+ <div id="settings-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden flex items-center justify-center">
87
+ <div class="bg-white p-4 rounded-lg">
88
+ <h2 class="text-xl font-bold mb-4">设置</h2>
89
+ <div class="mb-4">
90
+ <label for="max-history" class="block mb-2">保留对话轮数:</label>
91
+ <input type="number" id="max-history" class="border rounded p-2" min="1" max="50" value="10">
92
+ </div>
93
+ <div class="flex justify-end">
94
+ <button id="save-settings" class="bg-blue-500 text-white px-4 py-2 rounded mr-2">保存</button>
95
+ <button id="cancel-settings" class="bg-gray-300 text-gray-800 px-4 py-2 rounded">取消</button>
96
+ </div>
97
+ </div>
98
+ </div>
99
+
100
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/4.0.2/marked.min.js"></script>
101
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
102
+ <script>
103
+ const chatWindow = document.getElementById('chat-window');
104
+ const userInput = document.getElementById('user-input');
105
+ const sendBtn = document.getElementById('send-btn');
106
+ const mainModelStatus = document.getElementById('main-model-status');
107
+ const subModelStatus = document.getElementById('sub-model-status');
108
+ const searchStatus = document.getElementById('search-status');
109
+ const emailStatus = document.getElementById('email-status');
110
+ const statusLog = document.getElementById('status-log');
111
+ const settingsBtn = document.getElementById('settings-btn');
112
+ const settingsModal = document.getElementById('settings-modal');
113
+ const saveSettingsBtn = document.getElementById('save-settings');
114
+ const cancelSettingsBtn = document.getElementById('cancel-settings');
115
+ const maxHistoryInput = document.getElementById('max-history');
116
+
117
+ let conversationHistory = [];
118
+ let maxHistory = 10;
119
+
120
+ function addMessage(content, isUser) {
121
+ const bubble = document.createElement('div');
122
+ bubble.className = `chat-bubble ${isUser ? 'user-bubble' : 'ai-bubble'}`;
123
+ bubble.innerHTML = isUser ? content : marked.parse(content);
124
+ chatWindow.appendChild(bubble);
125
+ chatWindow.scrollTop = chatWindow.scrollHeight;
126
+
127
+ // 高亮代码块
128
+ bubble.querySelectorAll('pre code').forEach((block) => {
129
+ hljs.highlightBlock(block);
130
+ });
131
+
132
+ // 更新对话历史
133
+ conversationHistory.push({
134
+ role: isUser ? 'user' : 'assistant',
135
+ content: content
136
+ });
137
+
138
+ // 限制历史记录长度
139
+ if (conversationHistory.length > maxHistory * 2) {
140
+ conversationHistory = conversationHistory.slice(-maxHistory * 2);
141
+ }
142
+ }
143
+
144
+ function updateStatus(mainActive, subActive, searchActive, emailActive) {
145
+ mainModelStatus.className = `status-indicator ${mainActive ? 'status-active' : 'status-inactive'}`;
146
+ subModelStatus.className = `status-indicator ${subActive ? 'status-active' : 'status-inactive'}`;
147
+ searchStatus.className = `status-indicator ${searchActive ? 'status-active' : 'status-inactive'}`;
148
+ emailStatus.className = `status-indicator ${emailActive ? 'status-active' : 'status-inactive'}`;
149
+ }
150
+
151
+ function addStatusLog(message) {
152
+ const logEntry = document.createElement('div');
153
+ logEntry.className = 'mb-2 p-2 bg-white rounded shadow';
154
+ logEntry.textContent = message;
155
+ statusLog.appendChild(logEntry);
156
+ statusLog.scrollTop = statusLog.scrollHeight;
157
+ }
158
+
159
+ function displaySearchResults(results, type) {
160
+ const searchResults = document.createElement('details');
161
+ searchResults.className = 'mb-2 p-2 bg-gray-100 rounded';
162
+ const summary = document.createElement('summary');
163
+ summary.textContent = type === 'papers' ? '论文搜索结果' : '搜索结果';
164
+ searchResults.appendChild(summary);
165
+ const resultsList = document.createElement('ul');
166
+ resultsList.className = 'mt-2';
167
+ results.forEach(result => {
168
+ const li = document.createElement('li');
169
+ li.className = 'mb-2';
170
+ if (type === 'papers') {
171
+ li.innerHTML = `
172
+ <strong>标题:</strong> ${result.标题}<br>
173
+ <strong>作者:</strong> ${result.作者}<br>
174
+ <strong>DOI:</strong> ${result.DOI}<br>
175
+ <strong>ISBN:</strong> ${result.ISBN}<br>
176
+ <strong>摘要:</strong> ${result.摘要}
177
+ `;
178
+ } else {
179
+ li.innerHTML = `<strong>${result.title}</strong><br>${result.body}`;
180
+ }
181
+ resultsList.appendChild(li);
182
+ });
183
+ searchResults.appendChild(resultsList);
184
+ chatWindow.appendChild(searchResults);
185
+ }
186
+
187
+ async function sendMessage() {
188
+ const question = userInput.value.trim();
189
+ if (!question) return;
190
+ addMessage(question, true);
191
+ userInput.value = '';
192
+ updateStatus(false, false, false, false);
193
+ statusLog.innerHTML = '';
194
+ addStatusLog('开始处理问题...');
195
+ try {
196
+ const response = await fetch('/chat', {
197
+ method: 'POST',
198
+ headers: {
199
+ 'Content-Type': 'application/json'
200
+ },
201
+ body: JSON.stringify({
202
+ question,
203
+ history: conversationHistory.slice(-maxHistory * 2 + 1) // 发送历史消息,但不包括刚刚添加的用户消息
204
+ })
205
+ });
206
+ const data = await response.json();
207
+ data.status_log.forEach(log => addStatusLog(log));
208
+ updateStatus(true, true, data.search_used, data.email_sent);
209
+ if (data.search_used) {
210
+ if (Array.isArray(data.search_results) && data.search_results.length > 0 && 'DOI' in data.search_results[0]) {
211
+ displaySearchResults(data.search_results, 'papers');
212
+ } else {
213
+ displaySearchResults(data.search_results, 'web');
214
+ }
215
+ }
216
+ addMessage(data.response, false);
217
+ addStatusLog('回答完成');
218
+ } catch (error) {
219
+ console.error('Error:', error);
220
+ addMessage('发生错误,请稍后再���。', false);
221
+ updateStatus(false, false, false, false);
222
+ addStatusLog('发生错误');
223
+ }
224
+ }
225
+
226
+ sendBtn.addEventListener('click', sendMessage);
227
+
228
+ userInput.addEventListener('keypress', (e) => {
229
+ if (e.key === 'Enter') {
230
+ sendMessage();
231
+ }
232
+ });
233
+
234
+ // 设置相关功能
235
+ settingsBtn.addEventListener('click', () => {
236
+ settingsModal.classList.remove('hidden');
237
+ maxHistoryInput.value = maxHistory;
238
+ });
239
+
240
+ saveSettingsBtn.addEventListener('click', async () => {
241
+ const newMaxHistory = parseInt(maxHistoryInput.value);
242
+ if (newMaxHistory >= 1 && newMaxHistory <= 50) {
243
+ maxHistory = newMaxHistory;
244
+ try {
245
+ const response = await fetch('/settings', {
246
+ method: 'POST',
247
+ headers: {
248
+ 'Content-Type': 'application/json'
249
+ },
250
+ body: JSON.stringify({ max_history: maxHistory })
251
+ });
252
+ const data = await response.json();
253
+ if (data.status === 'success') {
254
+ alert('设置已保存');
255
+ } else {
256
+ alert('保存设置时出错');
257
+ }
258
+ } catch (error) {
259
+ console.error('Error:', error);
260
+ alert('保存设置时出错');
261
+ }
262
+ settingsModal.classList.add('hidden');
263
+ } else {
264
+ alert('请输入1到50之间的数字');
265
+ }
266
+ });
267
+
268
+ cancelSettingsBtn.addEventListener('click', () => {
269
+ settingsModal.classList.add('hidden');
270
+ });
271
+ </script>
272
+ </body>
273
+ </html>
requirements (1).txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ duckduckgo-search
2
+ Flask
3
+ flask_cors
4
+ openai