Upload 5 files
Browse files- app.py +176 -0
- chat_analysis.py +49 -0
- img2chat.py +96 -0
- index.html +557 -0
- requirements.txt +6 -0
app.py
ADDED
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Flask backend for the chat assistance website.
|
3 |
+
|
4 |
+
This application provides API endpoints for processing chat screenshots,
|
5 |
+
manual text input, and generating response suggestions.
|
6 |
+
"""
|
7 |
+
import os
|
8 |
+
import base64
|
9 |
+
import json
|
10 |
+
from flask import Flask, request, jsonify, send_file
|
11 |
+
from flask_cors import CORS
|
12 |
+
from werkzeug.utils import secure_filename
|
13 |
+
from dotenv import load_dotenv
|
14 |
+
from img2chat import img2chat
|
15 |
+
from chat_analysis import get_suggestion
|
16 |
+
|
17 |
+
# Load environment variables
|
18 |
+
load_dotenv()
|
19 |
+
|
20 |
+
# Initialize Flask app
|
21 |
+
app = Flask(__name__)
|
22 |
+
CORS(app) # Enable CORS for all routes
|
23 |
+
|
24 |
+
# Configure upload folder for images
|
25 |
+
UPLOAD_FOLDER = 'uploads'
|
26 |
+
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
|
27 |
+
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
28 |
+
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB max upload size
|
29 |
+
|
30 |
+
# Create uploads directory if it doesn't exist
|
31 |
+
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
32 |
+
|
33 |
+
def allowed_file(filename):
|
34 |
+
"""Check if the file extension is allowed"""
|
35 |
+
return '.' in filename and \
|
36 |
+
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
37 |
+
|
38 |
+
def generate_response(chat_text):
|
39 |
+
"""
|
40 |
+
Generate response suggestions based on chat text.
|
41 |
+
|
42 |
+
Uses the get_suggestion function from chat_analysis.py to generate
|
43 |
+
response suggestions based on the provided chat text.
|
44 |
+
|
45 |
+
Args:
|
46 |
+
chat_text (str): The chat text to analyze
|
47 |
+
|
48 |
+
Returns:
|
49 |
+
dict: Analysis and response suggestions
|
50 |
+
"""
|
51 |
+
# Get suggestion from chat_analysis module
|
52 |
+
suggestion_text = get_suggestion(chat_text)
|
53 |
+
|
54 |
+
# Parse the XML response to extract suggestion and example
|
55 |
+
# The response format is expected to be:
|
56 |
+
# <suggestion>Suggestion text</suggestion><example>Example text</example>
|
57 |
+
|
58 |
+
analysis = "分析中..."
|
59 |
+
suggestion = suggestion_text
|
60 |
+
|
61 |
+
# Simple parsing of XML tags (could be improved with proper XML parsing)
|
62 |
+
if "<suggestion>" in suggestion_text and "</suggestion>" in suggestion_text:
|
63 |
+
start_idx = suggestion_text.find("<suggestion>") + len("<suggestion>")
|
64 |
+
end_idx = suggestion_text.find("</suggestion>")
|
65 |
+
analysis = suggestion_text[start_idx:end_idx].strip()
|
66 |
+
|
67 |
+
if "<example>" in suggestion_text and "</example>" in suggestion_text:
|
68 |
+
start_idx = suggestion_text.find("<example>") + len("<example>")
|
69 |
+
end_idx = suggestion_text.find("</example>")
|
70 |
+
suggestion = suggestion_text[start_idx:end_idx].strip()
|
71 |
+
|
72 |
+
return {
|
73 |
+
"analysis": analysis,
|
74 |
+
"suggestion": suggestion
|
75 |
+
}
|
76 |
+
|
77 |
+
@app.route('/', methods=['GET'])
|
78 |
+
def index():
|
79 |
+
"""Serve the main HTML page"""
|
80 |
+
return send_file('index.html')
|
81 |
+
|
82 |
+
@app.route('/api/health', methods=['GET'])
|
83 |
+
def health_check():
|
84 |
+
"""Health check endpoint"""
|
85 |
+
return jsonify({"status": "ok"})
|
86 |
+
|
87 |
+
@app.route('/api/process-image', methods=['POST'])
|
88 |
+
def process_image():
|
89 |
+
"""
|
90 |
+
Process a chat screenshot and return analysis and suggestions.
|
91 |
+
|
92 |
+
Accepts an image file or a base64 encoded image string.
|
93 |
+
"""
|
94 |
+
try:
|
95 |
+
# Check if the post request has the file part
|
96 |
+
if 'file' in request.files:
|
97 |
+
file = request.files['file']
|
98 |
+
if file.filename == '':
|
99 |
+
return jsonify({"error": "No selected file"}), 400
|
100 |
+
|
101 |
+
if file and allowed_file(file.filename):
|
102 |
+
filename = secure_filename(file.filename)
|
103 |
+
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
104 |
+
file.save(filepath)
|
105 |
+
|
106 |
+
# Convert file to base64 for processing
|
107 |
+
with open(filepath, "rb") as image_file:
|
108 |
+
base64_image = base64.b64encode(image_file.read()).decode('utf-8')
|
109 |
+
image_url = f"data:image/{filepath.split('.')[-1]};base64,{base64_image}"
|
110 |
+
|
111 |
+
# Process the image
|
112 |
+
chat_text = img2chat(image_url)
|
113 |
+
|
114 |
+
# Generate response suggestions
|
115 |
+
response_data = generate_response(chat_text)
|
116 |
+
|
117 |
+
# Return the results
|
118 |
+
return jsonify({
|
119 |
+
"chat_text": chat_text,
|
120 |
+
"analysis": response_data["analysis"],
|
121 |
+
"suggestion": response_data["suggestion"]
|
122 |
+
})
|
123 |
+
else:
|
124 |
+
return jsonify({"error": "File type not allowed"}), 400
|
125 |
+
|
126 |
+
# Check if base64 image was provided
|
127 |
+
elif 'image_data' in request.json:
|
128 |
+
image_data = request.json['image_data']
|
129 |
+
|
130 |
+
# Process the image
|
131 |
+
chat_text = img2chat(image_data)
|
132 |
+
|
133 |
+
# Generate response suggestions
|
134 |
+
response_data = generate_response(chat_text)
|
135 |
+
|
136 |
+
# Return the results
|
137 |
+
return jsonify({
|
138 |
+
"chat_text": chat_text,
|
139 |
+
"analysis": response_data["analysis"],
|
140 |
+
"suggestion": response_data["suggestion"]
|
141 |
+
})
|
142 |
+
|
143 |
+
else:
|
144 |
+
return jsonify({"error": "No image provided"}), 400
|
145 |
+
|
146 |
+
except Exception as e:
|
147 |
+
return jsonify({"error": str(e)}), 500
|
148 |
+
|
149 |
+
@app.route('/api/process-text', methods=['POST'])
|
150 |
+
def process_text():
|
151 |
+
"""
|
152 |
+
Process manually entered chat text and return analysis and suggestions.
|
153 |
+
"""
|
154 |
+
try:
|
155 |
+
data = request.json
|
156 |
+
|
157 |
+
if not data or 'chat_text' not in data:
|
158 |
+
return jsonify({"error": "No chat text provided"}), 400
|
159 |
+
|
160 |
+
chat_text = data['chat_text']
|
161 |
+
|
162 |
+
# Generate response suggestions
|
163 |
+
response_data = generate_response(chat_text)
|
164 |
+
|
165 |
+
# Return the results
|
166 |
+
return jsonify({
|
167 |
+
"analysis": response_data["analysis"],
|
168 |
+
"suggestion": response_data["suggestion"]
|
169 |
+
})
|
170 |
+
|
171 |
+
except Exception as e:
|
172 |
+
return jsonify({"error": str(e)}), 500
|
173 |
+
|
174 |
+
if __name__ == '__main__':
|
175 |
+
port = int(os.environ.get('PORT', 8080))
|
176 |
+
app.run(host='0.0.0.0', port=port, debug=True)
|
chat_analysis.py
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Chat analysis module for generating response suggestions.
|
3 |
+
|
4 |
+
This module provides functionality to generate response suggestions
|
5 |
+
using OpenAI's GPT models.
|
6 |
+
"""
|
7 |
+
|
8 |
+
from openai import OpenAI
|
9 |
+
|
10 |
+
|
11 |
+
def get_suggestion(user_input):
|
12 |
+
"""
|
13 |
+
Generate a response suggestion based on user input.
|
14 |
+
|
15 |
+
This function uses OpenAI's GPT model to generate a response suggestion
|
16 |
+
for a given user input. The response is formatted in XML with suggestion
|
17 |
+
and example tags.
|
18 |
+
|
19 |
+
Args:
|
20 |
+
user_input (str): The user's input message
|
21 |
+
|
22 |
+
Returns:
|
23 |
+
str: The generated response suggestion
|
24 |
+
"""
|
25 |
+
client = OpenAI()
|
26 |
+
|
27 |
+
completion = client.chat.completions.create(
|
28 |
+
model="gpt-4o",
|
29 |
+
messages=[
|
30 |
+
{
|
31 |
+
"role": "system",
|
32 |
+
"content": "你是一個可以提供尊重對方且讓提問者開心的回覆的助手,"
|
33 |
+
"使用者將會輸入對方的問題,回覆時請撇除預設立場且一定要給出回復不要轉移話題,"
|
34 |
+
"可以偏愛提問者,要強調出他特殊的魅力。請使用XML格式,"
|
35 |
+
"生成針對使用者的應對建議,以及實際舉例來回覆使用者訊息,"
|
36 |
+
"使用<suggestion>和<example>標記來格式化你的回覆,<example>不超過30字"
|
37 |
+
},
|
38 |
+
{"role": "user", "content": user_input}
|
39 |
+
]
|
40 |
+
)
|
41 |
+
|
42 |
+
return completion.choices[0].message.content
|
43 |
+
|
44 |
+
|
45 |
+
if __name__ == "__main__":
|
46 |
+
# Example usage
|
47 |
+
dialogue = "你喜歡過除了我以外的異性嗎"
|
48 |
+
response = get_suggestion(dialogue)
|
49 |
+
print(response)
|
img2chat.py
ADDED
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Function to convert an image of chat to structured text using OpenAI's GPT-4o model.
|
3 |
+
|
4 |
+
This module provides a function to process chat screenshots and convert them
|
5 |
+
into structured text format using OpenAI's vision capabilities.
|
6 |
+
"""
|
7 |
+
import os
|
8 |
+
import base64
|
9 |
+
from dotenv import load_dotenv
|
10 |
+
from openai import OpenAI
|
11 |
+
|
12 |
+
# Load environment variables from .env file
|
13 |
+
load_dotenv()
|
14 |
+
|
15 |
+
def encode_image(image_path):
|
16 |
+
"""
|
17 |
+
Encode an image file to base64.
|
18 |
+
|
19 |
+
Args:
|
20 |
+
image_path (str): Path to the image file
|
21 |
+
|
22 |
+
Returns:
|
23 |
+
str: Base64 encoded image string
|
24 |
+
"""
|
25 |
+
with open(image_path, "rb") as image_file:
|
26 |
+
return base64.b64encode(image_file.read()).decode("utf-8")
|
27 |
+
|
28 |
+
def img2chat(image_input):
|
29 |
+
"""
|
30 |
+
Convert a chat screenshot to structured text using OpenAI's GPT-4o model.
|
31 |
+
|
32 |
+
This function takes either an image path or a base64 encoded image string
|
33 |
+
and uses OpenAI's GPT-4o model to extract the conversation in a structured format.
|
34 |
+
|
35 |
+
Args:
|
36 |
+
image_input (str): Either a file path to an image, a URL, or a base64 encoded image
|
37 |
+
|
38 |
+
Returns:
|
39 |
+
str: The structured chat text extracted from the image
|
40 |
+
"""
|
41 |
+
# Initialize OpenAI client with API key from environment variable
|
42 |
+
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
43 |
+
|
44 |
+
# Determine if the input is a file path, URL, or base64 string
|
45 |
+
if os.path.isfile(image_input):
|
46 |
+
# It's a file path, encode it to base64
|
47 |
+
base64_image = encode_image(image_input)
|
48 |
+
image_url = f"data:image/jpeg;base64,{base64_image}"
|
49 |
+
elif image_input.startswith("data:image"):
|
50 |
+
# It's already a base64 data URL
|
51 |
+
image_url = image_input
|
52 |
+
elif image_input.startswith("http"):
|
53 |
+
# It's a URL
|
54 |
+
image_url = image_input
|
55 |
+
else:
|
56 |
+
# Assume it's a raw base64 string
|
57 |
+
image_url = f"data:image/jpeg;base64,{image_input}"
|
58 |
+
|
59 |
+
response = client.chat.completions.create(
|
60 |
+
model="gpt-4o",
|
61 |
+
messages=[
|
62 |
+
{
|
63 |
+
"role": "system",
|
64 |
+
"content": "你是一位善於將對話的聊天紀錄的截圖,還原成文本結構的聊天資訊的專家。"
|
65 |
+
"對於收到的每一張截圖,請仔細閱讀他們的聊天紀錄,左側的訊息表示其他人傳的訊息、"
|
66 |
+
"右邊則是用戶傳的訊息,將聊天紀錄的截圖轉換成文本格式的聊天。"
|
67 |
+
"請依照訊息的時間順序,由舊到新的順序,使用XML的格式來輸出聊天紀錄,"
|
68 |
+
"<usr>表示使用者的聊天對象的訊息、<self>表示使用者傳的訊息,"
|
69 |
+
"最多只能有chat、usr這種二級結構,"
|
70 |
+
"例如<chat><usr>你好你好</usr><usr>哈囉哈囉</usr></chat>\n\n"
|
71 |
+
"務必要確保忠時的還原使用者的對話紀錄,並且忽略聊天室中像是圖片、音訊等非文字的資訊。"
|
72 |
+
"並使用<chat></chat>為標記,包裹整段的聊天資訊。如果你解析不出任何訊息,輸出<chat></chat>就好。"
|
73 |
+
},
|
74 |
+
{
|
75 |
+
"role": "user",
|
76 |
+
"content": [
|
77 |
+
{"type": "text", "text": "請分析這張聊天截圖並轉換成文本格式:"},
|
78 |
+
{
|
79 |
+
"type": "image_url",
|
80 |
+
"image_url": {
|
81 |
+
"url": image_url,
|
82 |
+
}
|
83 |
+
}
|
84 |
+
]
|
85 |
+
}
|
86 |
+
],
|
87 |
+
max_tokens=913,
|
88 |
+
)
|
89 |
+
|
90 |
+
return response.choices[0].message.content
|
91 |
+
|
92 |
+
|
93 |
+
if __name__ == "__main__":
|
94 |
+
# Example usage with a file path
|
95 |
+
print("\nExample with file path:")
|
96 |
+
print(img2chat("tst_img/tst1.png"))
|
index.html
ADDED
@@ -0,0 +1,557 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="zh">
|
3 |
+
|
4 |
+
<head>
|
5 |
+
<meta charset="UTF-8">
|
6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
7 |
+
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@300;400;700&display=swap" rel="stylesheet">
|
8 |
+
<title>聊天助手</title>
|
9 |
+
<style>
|
10 |
+
* {
|
11 |
+
margin: 0;
|
12 |
+
padding: 0;
|
13 |
+
box-sizing: border-box;
|
14 |
+
}
|
15 |
+
|
16 |
+
body {
|
17 |
+
font-family: 'Noto Sans', sans-serif;
|
18 |
+
display: flex;
|
19 |
+
justify-content: center;
|
20 |
+
align-items: center;
|
21 |
+
height: 100vh;
|
22 |
+
background-color: #f5f5f5;
|
23 |
+
}
|
24 |
+
|
25 |
+
select {
|
26 |
+
width: 20%;
|
27 |
+
padding: 12px 15px;
|
28 |
+
font-size: 18px;
|
29 |
+
border: 1px solid #ddd;
|
30 |
+
border-radius: 8px;
|
31 |
+
background-color: #fafafa;
|
32 |
+
color: #333;
|
33 |
+
cursor: pointer;
|
34 |
+
box-sizing: border-box;
|
35 |
+
}
|
36 |
+
|
37 |
+
select:focus {
|
38 |
+
border-color: #007bff;
|
39 |
+
outline: none;
|
40 |
+
}
|
41 |
+
|
42 |
+
option {
|
43 |
+
padding: 10px;
|
44 |
+
}
|
45 |
+
|
46 |
+
.chatbox {
|
47 |
+
height: fit-content;
|
48 |
+
width: 80%;
|
49 |
+
background-color: white;
|
50 |
+
border-radius: 20px;
|
51 |
+
margin-top: 10px;
|
52 |
+
margin-bottom: 10px;
|
53 |
+
}
|
54 |
+
|
55 |
+
.myInputArea {
|
56 |
+
display: flex;
|
57 |
+
justify-content: center;
|
58 |
+
margin-top: 10px;
|
59 |
+
}
|
60 |
+
|
61 |
+
.myMessageArea {
|
62 |
+
display: flex;
|
63 |
+
justify-content: flex-end;
|
64 |
+
margin-top: 10px;
|
65 |
+
}
|
66 |
+
|
67 |
+
.GPTmessageArea {
|
68 |
+
display: flex;
|
69 |
+
justify-content: flex-start;
|
70 |
+
margin-top: 10px;
|
71 |
+
}
|
72 |
+
|
73 |
+
.myMessageBox {
|
74 |
+
height: fit-content;
|
75 |
+
width: fit-content;
|
76 |
+
margin: 10px;
|
77 |
+
padding: 10px;
|
78 |
+
border-radius: 10px;
|
79 |
+
background-color: rgb(195, 252, 233);
|
80 |
+
}
|
81 |
+
|
82 |
+
.GPTmessageBox {
|
83 |
+
height: fit-content;
|
84 |
+
width: fit-content;
|
85 |
+
margin: 10px;
|
86 |
+
padding: 10px;
|
87 |
+
border-radius: 10px;
|
88 |
+
background-color: rgb(252, 206, 238);
|
89 |
+
}
|
90 |
+
|
91 |
+
.myInputBox {
|
92 |
+
height: fit-content;
|
93 |
+
width: 70%;
|
94 |
+
margin: 10px;
|
95 |
+
padding: 10px;
|
96 |
+
border-radius: 10px;
|
97 |
+
background-color: #f5f5f5;
|
98 |
+
}
|
99 |
+
|
100 |
+
.addMessageButton {
|
101 |
+
width: 35px;
|
102 |
+
height: 35px;
|
103 |
+
font-size: 15px;
|
104 |
+
font-weight: bold;
|
105 |
+
border: solid 1px #a7a7a7;
|
106 |
+
border-radius: 10px;
|
107 |
+
background-color: white;
|
108 |
+
cursor: pointer;
|
109 |
+
}
|
110 |
+
|
111 |
+
.sendMessageButton {
|
112 |
+
background-color: #007bff;
|
113 |
+
/* 藍色 */
|
114 |
+
color: white;
|
115 |
+
font-size: 16px;
|
116 |
+
padding: 10px 20px;
|
117 |
+
border: none;
|
118 |
+
border-radius: 5px;
|
119 |
+
cursor: pointer;
|
120 |
+
transition: background 0.3s;
|
121 |
+
}
|
122 |
+
|
123 |
+
.sendMessageButton:hover {
|
124 |
+
background-color: #0056b3;
|
125 |
+
}
|
126 |
+
|
127 |
+
.addMessageButton:hover {
|
128 |
+
background-color: rgb(209, 209, 209);
|
129 |
+
}
|
130 |
+
|
131 |
+
.messagePending {
|
132 |
+
margin-left: 5px;
|
133 |
+
width: 70%;
|
134 |
+
padding: 12px 15px;
|
135 |
+
font-size: 16px;
|
136 |
+
border: 2px solid #ccc;
|
137 |
+
border-radius: 8px;
|
138 |
+
background-color: #fafafa;
|
139 |
+
color: #333;
|
140 |
+
outline: none;
|
141 |
+
box-sizing: border-box;
|
142 |
+
transition: border 0.3s ease, box-shadow 0.3s ease;
|
143 |
+
}
|
144 |
+
|
145 |
+
.messagePending:focus {
|
146 |
+
border-color: #007bff;
|
147 |
+
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
|
148 |
+
}
|
149 |
+
|
150 |
+
.messagePending::placeholder {
|
151 |
+
color: #888;
|
152 |
+
font-style: italic;
|
153 |
+
}
|
154 |
+
|
155 |
+
#imgInputBox {
|
156 |
+
width: 100%;
|
157 |
+
border: solid 1px #a7a7a7;
|
158 |
+
border-radius: 10px;
|
159 |
+
height: 50px;
|
160 |
+
}
|
161 |
+
|
162 |
+
#textInputBox {
|
163 |
+
margin-top: 10px;
|
164 |
+
width: 100%;
|
165 |
+
border: solid 1px #a7a7a7;
|
166 |
+
border-radius: 10px;
|
167 |
+
height: 200px;
|
168 |
+
padding: 10px;
|
169 |
+
overflow: auto;
|
170 |
+
}
|
171 |
+
</style>
|
172 |
+
</head>
|
173 |
+
|
174 |
+
<body>
|
175 |
+
<div id="chatbox" class="chatbox">
|
176 |
+
|
177 |
+
<!--對話輸入框-->
|
178 |
+
<div class="myInputArea">
|
179 |
+
<div class="myInputBox">
|
180 |
+
<div id="imgInputBox">
|
181 |
+
<div style="display: flex; justify-content: center; align-items: center; height: 100%;">
|
182 |
+
<input type="file" id="imageUpload" accept="image/*" style="display: none;">
|
183 |
+
<button onclick="document.getElementById('imageUpload').click()" style="padding: 5px 10px; background-color: #f0f0f0; border: 1px solid #ccc; border-radius: 5px; cursor: pointer;">選擇圖片</button>
|
184 |
+
<span id="selectedFileName" style="margin-left: 10px; font-size: 14px;"></span>
|
185 |
+
</div>
|
186 |
+
</div>
|
187 |
+
<div style="text-align: center;">
|
188 |
+
或
|
189 |
+
</div>
|
190 |
+
<div id="textInputBox">
|
191 |
+
<!--已建立的訊息填寫框-->
|
192 |
+
<div style="display: flex;flex-wrap: nowrap;justify-content: end; margin-top: 10px;">
|
193 |
+
<select id="character0" name="character">
|
194 |
+
<option value="self">自己</option>
|
195 |
+
<option value="usr">對方</option>
|
196 |
+
</select>
|
197 |
+
<input type="text" id="messagePending0" name="messagePending" class="messagePending">
|
198 |
+
</div>
|
199 |
+
|
200 |
+
<!--建立新的訊息填寫框-->
|
201 |
+
<div style="display: flex;justify-content: end; margin-top: 10px;">
|
202 |
+
<button class="addMessageButton" onclick="addMessageBox()">+</button>
|
203 |
+
</div>
|
204 |
+
</div>
|
205 |
+
<!--傳送訊息-->
|
206 |
+
<div style="display: flex;justify-content: end;margin-top: 10px;">
|
207 |
+
<button class="sendMessageButton" onclick="sendMessage()">傳送</button>
|
208 |
+
</div>
|
209 |
+
</div>
|
210 |
+
</div>
|
211 |
+
|
212 |
+
<!--對話紀錄-我方-->
|
213 |
+
<!--
|
214 |
+
<div class="myMessageArea">
|
215 |
+
<div class="myMessageBox">
|
216 |
+
<p>這是我傳的訊息或圖片</p>
|
217 |
+
</div>
|
218 |
+
</div>
|
219 |
+
|
220 |
+
|
221 |
+
<div class="GPTmessageArea">
|
222 |
+
<div class="GPTmessageBox">
|
223 |
+
<p>這是GPT傳的訊息</p>
|
224 |
+
</div>
|
225 |
+
</div>
|
226 |
+
-->
|
227 |
+
</div>
|
228 |
+
|
229 |
+
<script>
|
230 |
+
let messageCount = 1; // 記錄訊息框數量
|
231 |
+
let selectedImage = null; // 儲存選擇的圖片
|
232 |
+
|
233 |
+
// 當頁面載入完成後,添加事件監聽器
|
234 |
+
document.addEventListener('DOMContentLoaded', function() {
|
235 |
+
// 為圖片上傳添加事件監聽器
|
236 |
+
const imageUpload = document.getElementById('imageUpload');
|
237 |
+
if (imageUpload) {
|
238 |
+
imageUpload.addEventListener('change', handleImageSelect);
|
239 |
+
}
|
240 |
+
});
|
241 |
+
|
242 |
+
// 處理圖片選擇
|
243 |
+
function handleImageSelect(event) {
|
244 |
+
const file = event.target.files[0];
|
245 |
+
if (file) {
|
246 |
+
selectedImage = file;
|
247 |
+
document.getElementById('selectedFileName').textContent = file.name;
|
248 |
+
|
249 |
+
// 選擇圖片後立即上傳,不需要額外的上傳按鈕
|
250 |
+
uploadImage();
|
251 |
+
}
|
252 |
+
}
|
253 |
+
|
254 |
+
// 上傳圖片到伺服器
|
255 |
+
function uploadImage() {
|
256 |
+
if (!selectedImage) {
|
257 |
+
alert('請先選擇一張圖片');
|
258 |
+
return;
|
259 |
+
}
|
260 |
+
|
261 |
+
const formData = new FormData();
|
262 |
+
formData.append('file', selectedImage);
|
263 |
+
|
264 |
+
// 暫時刪除 myInputArea
|
265 |
+
const myInputArea = document.querySelector(".myInputArea");
|
266 |
+
if (myInputArea) {
|
267 |
+
myInputArea.remove();
|
268 |
+
}
|
269 |
+
|
270 |
+
// 新增 myMessageArea (顯示上傳的圖片)
|
271 |
+
const chatbox = document.getElementById("chatbox");
|
272 |
+
const myMessageArea = document.createElement("div");
|
273 |
+
myMessageArea.className = "myMessageArea";
|
274 |
+
myMessageArea.innerHTML = `
|
275 |
+
<div class="myMessageBox">
|
276 |
+
<p>上傳的圖片: ${selectedImage.name}</p>
|
277 |
+
</div>
|
278 |
+
`;
|
279 |
+
chatbox.appendChild(myMessageArea);
|
280 |
+
|
281 |
+
// 新增 GPTmessageArea (初始內容: 等待回應...)
|
282 |
+
const GPTmessageArea = document.createElement("div");
|
283 |
+
GPTmessageArea.className = "GPTmessageArea";
|
284 |
+
GPTmessageArea.innerHTML = `
|
285 |
+
<div class="GPTmessageBox">
|
286 |
+
<p>(等待回應......)</p>
|
287 |
+
</div>
|
288 |
+
`;
|
289 |
+
chatbox.appendChild(GPTmessageArea);
|
290 |
+
|
291 |
+
// 發送 API 請求
|
292 |
+
fetch('/api/process-image', {
|
293 |
+
method: 'POST',
|
294 |
+
body: formData
|
295 |
+
})
|
296 |
+
.then(response => {
|
297 |
+
if (!response.ok) {
|
298 |
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
299 |
+
}
|
300 |
+
return response.json();
|
301 |
+
})
|
302 |
+
.then(data => {
|
303 |
+
console.log("Server Response:", data);
|
304 |
+
|
305 |
+
// 確保回應包含 analysis 和 suggestion
|
306 |
+
const analysis = data.analysis || "無分析";
|
307 |
+
const suggestion = data.suggestion || "無建議";
|
308 |
+
const chatText = data.chat_text || "";
|
309 |
+
|
310 |
+
// 處理 chatText,去掉外層的 <chat> 標記
|
311 |
+
let formattedChatText = chatText;
|
312 |
+
if (formattedChatText.startsWith("<chat>") && formattedChatText.endsWith("</chat>")) {
|
313 |
+
formattedChatText = formattedChatText.substring(6, formattedChatText.length - 7);
|
314 |
+
}
|
315 |
+
|
316 |
+
// 先顯示識別的聊天內容
|
317 |
+
GPTmessageArea.innerHTML = `
|
318 |
+
<div class="GPTmessageBox">
|
319 |
+
<p><strong>識別的聊天內容:</strong><br>${formattedChatText}</p>
|
320 |
+
</div>
|
321 |
+
`;
|
322 |
+
|
323 |
+
// 創建新的 GPTmessageArea 來顯示分析和建議
|
324 |
+
const analysisArea = document.createElement("div");
|
325 |
+
analysisArea.className = "GPTmessageArea";
|
326 |
+
analysisArea.innerHTML = `
|
327 |
+
<div class="GPTmessageBox">
|
328 |
+
<p><strong>分析:</strong><br>${analysis}</p>
|
329 |
+
<p><strong>建議回覆:</strong><br>${suggestion}</p>
|
330 |
+
</div>
|
331 |
+
`;
|
332 |
+
chatbox.appendChild(analysisArea);
|
333 |
+
})
|
334 |
+
.catch(error => {
|
335 |
+
console.error("Error uploading image:", error);
|
336 |
+
GPTmessageArea.innerHTML = `
|
337 |
+
<div class="GPTmessageBox">
|
338 |
+
<p>(錯誤:無法取得回應)</p>
|
339 |
+
<p>錯誤詳情:${error.message}</p>
|
340 |
+
</div>
|
341 |
+
`;
|
342 |
+
})
|
343 |
+
.finally(() => {
|
344 |
+
// 重新新增 myInputArea
|
345 |
+
resetInputArea();
|
346 |
+
});
|
347 |
+
}
|
348 |
+
|
349 |
+
// 重設輸入區域
|
350 |
+
function resetInputArea() {
|
351 |
+
const chatbox = document.getElementById("chatbox");
|
352 |
+
const newMyInputArea = document.createElement("div");
|
353 |
+
newMyInputArea.className = "myInputArea";
|
354 |
+
newMyInputArea.innerHTML = `
|
355 |
+
<div class="myInputBox">
|
356 |
+
<div id="imgInputBox">
|
357 |
+
<div style="display: flex; justify-content: center; align-items: center; height: 100%;">
|
358 |
+
<input type="file" id="imageUpload" accept="image/*" style="display: none;">
|
359 |
+
<button onclick="document.getElementById('imageUpload').click()" style="padding: 5px 10px; background-color: #f0f0f0; border: 1px solid #ccc; border-radius: 5px; cursor: pointer;">選擇圖片</button>
|
360 |
+
<span id="selectedFileName" style="margin-left: 10px; font-size: 14px;"></span>
|
361 |
+
</div>
|
362 |
+
</div>
|
363 |
+
<div style="text-align: center;">
|
364 |
+
或
|
365 |
+
</div>
|
366 |
+
<div id="textInputBox">
|
367 |
+
<!--已建立的訊息填寫框-->
|
368 |
+
<div style="display: flex;flex-wrap: nowrap;justify-content: end; margin-top: 10px;">
|
369 |
+
<select id="character0" name="character">
|
370 |
+
<option value="self">自己</option>
|
371 |
+
<option value="usr">對方</option>
|
372 |
+
</select>
|
373 |
+
<input type="text" id="messagePending0" name="messagePending" class="messagePending">
|
374 |
+
</div>
|
375 |
+
|
376 |
+
<!--建立新的訊息填寫框-->
|
377 |
+
<div style="display: flex;justify-content: end; margin-top: 10px;">
|
378 |
+
<button class="addMessageButton" onclick="addMessageBox()">+</button>
|
379 |
+
</div>
|
380 |
+
</div>
|
381 |
+
<!--傳送訊息-->
|
382 |
+
<div style="display: flex;justify-content: end;margin-top: 10px;">
|
383 |
+
<button class="sendMessageButton" onclick="sendMessage()">傳送</button>
|
384 |
+
</div>
|
385 |
+
</div>
|
386 |
+
`;
|
387 |
+
chatbox.appendChild(newMyInputArea);
|
388 |
+
|
389 |
+
// 重新添加事件監聽器
|
390 |
+
const imageUpload = document.getElementById('imageUpload');
|
391 |
+
if (imageUpload) {
|
392 |
+
imageUpload.addEventListener('change', handleImageSelect);
|
393 |
+
}
|
394 |
+
|
395 |
+
// 重設選擇的圖片
|
396 |
+
selectedImage = null;
|
397 |
+
messageCount = 1;
|
398 |
+
}
|
399 |
+
|
400 |
+
// 添加新的訊息填寫框
|
401 |
+
function addMessageBox() {
|
402 |
+
const newMessageBox = document.createElement('div');
|
403 |
+
newMessageBox.style.display = "flex";
|
404 |
+
newMessageBox.style.justifyContent = "end";
|
405 |
+
newMessageBox.style.marginTop = "10px";
|
406 |
+
|
407 |
+
// 新增一個X按鈕
|
408 |
+
const deleteButton = document.createElement('button');
|
409 |
+
deleteButton.innerText = 'X';
|
410 |
+
deleteButton.style.marginRight = '10px';
|
411 |
+
deleteButton.style.cursor = 'pointer';
|
412 |
+
deleteButton.style.background = 'red';
|
413 |
+
deleteButton.style.color = 'white';
|
414 |
+
deleteButton.style.border = 'none';
|
415 |
+
deleteButton.style.borderRadius = '50%';
|
416 |
+
deleteButton.style.width = '25px';
|
417 |
+
deleteButton.style.height = '25px';
|
418 |
+
deleteButton.style.textAlign = 'center';
|
419 |
+
deleteButton.style.fontSize = '16px';
|
420 |
+
|
421 |
+
// 點擊刪除按鈕時刪除對應的訊息框
|
422 |
+
deleteButton.addEventListener('click', function() {
|
423 |
+
newMessageBox.remove();
|
424 |
+
});
|
425 |
+
|
426 |
+
// 設定新增訊息框的內容
|
427 |
+
newMessageBox.innerHTML = `
|
428 |
+
<select id="character${messageCount}" name="character">
|
429 |
+
<option value="self">自己</option>
|
430 |
+
<option value="usr">對方</option>
|
431 |
+
</select>
|
432 |
+
<input type="text" id="messagePending${messageCount}" name="messagePending" class="messagePending">
|
433 |
+
`;
|
434 |
+
|
435 |
+
// 在訊息框前插入X按鈕
|
436 |
+
newMessageBox.insertBefore(deleteButton, newMessageBox.firstChild);
|
437 |
+
|
438 |
+
// 把新的訊息框插入到#textInputBox
|
439 |
+
document.getElementById('textInputBox').insertBefore(newMessageBox, document.querySelector('.addMessageButton').parentNode);
|
440 |
+
messageCount++;
|
441 |
+
}
|
442 |
+
|
443 |
+
function sendMessage() {
|
444 |
+
let result = "<chat>";
|
445 |
+
let formattedMessage = ""; // 用於顯示在 myMessageArea 的格式化文字
|
446 |
+
|
447 |
+
for (let i = 0; i < messageCount; i++) {
|
448 |
+
try {
|
449 |
+
const character = document.getElementById(`character${i}`);
|
450 |
+
const messageElement = document.getElementById(`messagePending${i}`);
|
451 |
+
if (!character || !messageElement) continue; // 跳過已刪除的元素
|
452 |
+
|
453 |
+
const message = messageElement.value.trim();
|
454 |
+
if (message) {
|
455 |
+
result += `<${character.value}>${message}</${character.value}>`;
|
456 |
+
formattedMessage += `${character.value === 'self' ? "您" : "對方"}: ${message}<br>`; // **換行**
|
457 |
+
}
|
458 |
+
} catch (error) {
|
459 |
+
console.warn(`Message box ${i} does not exist. Skipping.`);
|
460 |
+
continue;
|
461 |
+
}
|
462 |
+
}
|
463 |
+
result += "</chat>";
|
464 |
+
|
465 |
+
// **1. 暫時刪除 myInputArea**
|
466 |
+
const myInputArea = document.querySelector(".myInputArea");
|
467 |
+
if (myInputArea) {
|
468 |
+
myInputArea.remove();
|
469 |
+
}
|
470 |
+
|
471 |
+
// **2. 新增 myMessageArea (符合提供的 HTML 結構)**
|
472 |
+
const chatbox = document.getElementById("chatbox");
|
473 |
+
const myMessageArea = document.createElement("div");
|
474 |
+
myMessageArea.className = "myMessageArea";
|
475 |
+
myMessageArea.innerHTML = `
|
476 |
+
<div class="myMessageBox">
|
477 |
+
<p>${formattedMessage}</p>
|
478 |
+
</div>
|
479 |
+
`;
|
480 |
+
chatbox.appendChild(myMessageArea);
|
481 |
+
|
482 |
+
// **3. 新增 GPTmessageArea (初始內容: 等待回應...)**
|
483 |
+
const GPTmessageArea = document.createElement("div");
|
484 |
+
GPTmessageArea.className = "GPTmessageArea";
|
485 |
+
GPTmessageArea.innerHTML = `
|
486 |
+
<div class="GPTmessageBox">
|
487 |
+
<p>(等待回應......)</p>
|
488 |
+
</div>
|
489 |
+
`;
|
490 |
+
chatbox.appendChild(GPTmessageArea);
|
491 |
+
|
492 |
+
// **4. 傳送 API 請求**
|
493 |
+
const data = {
|
494 |
+
chat_text: result
|
495 |
+
};
|
496 |
+
fetch('/api/process-text', {
|
497 |
+
method: 'POST',
|
498 |
+
headers: {
|
499 |
+
'Content-Type': 'application/json'
|
500 |
+
},
|
501 |
+
body: JSON.stringify(data)
|
502 |
+
})
|
503 |
+
.then(response => {
|
504 |
+
if (!response.ok) {
|
505 |
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
506 |
+
}
|
507 |
+
return response.json();
|
508 |
+
})
|
509 |
+
.then(data => {
|
510 |
+
console.log("Server Response:", data);
|
511 |
+
|
512 |
+
// 確保回應包含 analysis 和 suggestion
|
513 |
+
const analysis = data.analysis || "無分析";
|
514 |
+
const suggestion = data.suggestion || "無建議";
|
515 |
+
|
516 |
+
// 處理 result,去掉外層的 <chat> 標記
|
517 |
+
let formattedChatText = result;
|
518 |
+
if (formattedChatText.startsWith("<chat>") && formattedChatText.endsWith("</chat>")) {
|
519 |
+
formattedChatText = formattedChatText.substring(6, formattedChatText.length - 7);
|
520 |
+
}
|
521 |
+
|
522 |
+
// 先顯示原始聊天文本
|
523 |
+
GPTmessageArea.innerHTML = `
|
524 |
+
<div class="GPTmessageBox">
|
525 |
+
<p><strong>識別的聊天內容:</strong><br>${formattedChatText}</p>
|
526 |
+
</div>
|
527 |
+
`;
|
528 |
+
|
529 |
+
// 創建新的 GPTmessageArea 來顯示分析和建議
|
530 |
+
const analysisArea = document.createElement("div");
|
531 |
+
analysisArea.className = "GPTmessageArea";
|
532 |
+
analysisArea.innerHTML = `
|
533 |
+
<div class="GPTmessageBox">
|
534 |
+
<p><strong>分析:</strong><br>${analysis}</p>
|
535 |
+
<p><strong>建議回覆:</strong><br>${suggestion}</p>
|
536 |
+
</div>
|
537 |
+
`;
|
538 |
+
chatbox.appendChild(analysisArea);
|
539 |
+
})
|
540 |
+
.catch(error => {
|
541 |
+
console.error("Error sending message:", error);
|
542 |
+
GPTmessageArea.innerHTML = `
|
543 |
+
<div class="GPTmessageBox">
|
544 |
+
<p>(錯誤:無法取得回應)</p>
|
545 |
+
<p>錯誤詳情:${error.message}</p>
|
546 |
+
</div>
|
547 |
+
`;
|
548 |
+
})
|
549 |
+
.finally(() => {
|
550 |
+
// 使用重設輸入區域函數
|
551 |
+
resetInputArea();
|
552 |
+
});
|
553 |
+
}
|
554 |
+
</script>
|
555 |
+
</body>
|
556 |
+
|
557 |
+
</html>
|
requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
flask==2.3.3
|
2 |
+
flask-cors==4.0.0
|
3 |
+
python-dotenv==1.0.0
|
4 |
+
openai==1.3.0
|
5 |
+
werkzeug==2.3.7
|
6 |
+
gunicorn==21.2.0
|