diff --git a/app.py b/app.py new file mode 100644 index 0000000..fc0b489 --- /dev/null +++ b/app.py @@ -0,0 +1,66 @@ +import requests +from typing import Optional + +from flask import Flask, render_template, request, redirect, url_for, flash, jsonify + +from chat_gpt import ChatGPT + +app = Flask(__name__) +app.secret_key = 'supersecretkey' +chat_gpt: Optional[ChatGPT] = None + + +@app.route('/') +def setup(): + return render_template('setup.html') + + +@app.route('/setup', methods=['POST']) +def setup_submit(): + global chat_gpt + api_key = request.form['api_key'] + model_id = request.form['model_id'] + context = request.form['context'] + + if not test_api(api_key): + return jsonify({'status': 'error', 'message': 'Failed to connect to OpenAI API'}) + + chat_gpt = ChatGPT(model_id, api_key, context) + chat_gpt.add_system_message(f"{context}. Respond precisely. Do not give more information than necessary.") + + try: + chat_gpt.gpt_conversation() + return jsonify({'status': 'success', 'message': 'Connection successful'}) + except Exception as e: + return jsonify({'status': 'error', 'message': str(e)}) + + +def test_api(api_key: str) -> bool: + headers = { + 'Authorization': f'Bearer {api_key}', + } + response = requests.get('https://api.openai.com/v1/models', headers=headers) + return response.status_code == 200 + + +@app.route('/chat') +def chat(): + if chat_gpt is None: + return redirect(url_for('setup')) + return render_template('chat.html') + + +@app.route('/send_message', methods=['POST']) +def send_message(): + user_input = request.form['message'] + chat_gpt.add_user_message(user_input) + response = chat_gpt.gpt_conversation() + + if response['status'] == 'success': + return jsonify({'status': 'success', 'message': response['message']}) + else: + return jsonify({'status': 'error', 'message': response['message']}) + + +if __name__ == '__main__': + app.run(debug=True, port=8801) diff --git a/chat_gpt.py b/chat_gpt.py new file mode 100644 index 0000000..5326965 --- /dev/null +++ b/chat_gpt.py @@ -0,0 +1,58 @@ +import openai +from openai.error import AuthenticationError, InvalidRequestError, RateLimitError + +# Constants +PRICE_MAP: dict = { + "gpt-3.5-turbo": 0.002, + "gpt-4": 0.06, +} + + +class ChatGPT: + def __init__(self, model_id: str, api_key: str, context: str): + self.model_id = model_id + self.api_key = api_key + self.context = context + self.conversation = [] + self.cost = 0.0 + + openai.api_key = api_key + + def add_system_message(self, message: str) -> None: + self.conversation.append({'role': 'system', 'content': message}) + + def add_user_message(self, message: str) -> None: + self.conversation.append({'role': 'user', 'content': message}) + + def gpt_conversation(self) -> dict: + try: + response = openai.ChatCompletion.create( + model=self.model_id, + messages=self.conversation + ) + + self.conversation.append( + { + 'role': response.choices[0].message.role, + 'content': response.choices[0].message.content, + } + ) + + total_tokens = response.usage.total_tokens + self.calculate_price_from_tokens(total_tokens) + + return {'status': 'success', 'message': response.choices[0].message.content.strip()} + + except InvalidRequestError as exc: + return {'status': 'error', 'message': f"Invalid request. Error: {exc}"} + except RateLimitError as exc: + return {'status': 'error', 'message': f"Rate limit exceeded. Error: {exc}"} + except Exception as exc: + return {'status': 'error', 'message': f"Unexpected error. Error: {exc}"} + + def calculate_price_from_tokens(self, total_tokens: int) -> float: + price_per_1000 = PRICE_MAP[self.model_id] + price = (total_tokens / 1000) * price_per_1000 + self.cost += price + + return round(price, 4) diff --git a/requirements.txt b/requirements.txt index e42c9c9..961d7c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ openai rich cchardet keyboard +Flask diff --git a/static/scripts.js b/static/scripts.js new file mode 100644 index 0000000..ffab33a --- /dev/null +++ b/static/scripts.js @@ -0,0 +1,85 @@ +$(document).ready(function () { + $("#setup_form").on("submit", function (event) { + event.preventDefault(); + const formData = $(this).serialize(); // Serialize the form data before disabling the fields + toggleFormFields(true); // Disable the form fields + $.ajax({ + url: "/setup", + method: "POST", + data: formData, + success: function (response) { + if (response.status === "success") { + // Display success alert + $("#message").html(''); + // Redirect to chat page + window.location.href = "/chat"; + } else { + $("#message").html(''); + toggleFormFields(false); // Enable the form fields + } + }, + error: function () { + $("#message").html(''); + toggleFormFields(false); // Enable the form fields + } + }); + }); + + $("#send_button").on("click", function () { + var userMessage = $("#user_message").val().trim(); + if (userMessage !== "") { + $("#user_message").val(""); + $("#chatlog").append('
You: ' + escapeHtml(userMessage) + '
'); + scrollToBottom(); // Scroll to the bottom after appending user message + $.ajax({ + url: "/send_message", // Change this line to use the correct endpoint + method: "POST", + data: { message: userMessage }, + success: function (response) { + if (response.status === "success") { + if (response.message) { + $("#chatlog").append('
ChatGPT: ' + escapeHtml(response.message) + '
'); + } else { + $("#chatlog").append('
Error: Received an empty response.
'); + } + scrollToBottom(); // Scroll to the bottom after appending assistant message + } else { + if (response.message) { + $("#chatlog").append('
Error: ' + escapeHtml(response.message) + '
'); + } else { + $("#chatlog").append('
Error: An unknown error occurred.
'); + } + scrollToBottom(); // Scroll to the bottom after appending error message + } + } + }); + } + }); + + $("#user_message").on("keypress", function (event) { + if (event.which == 13 && !event.shiftKey) { + event.preventDefault(); + $("#send_button").click(); + } + }); +}); + +function toggleFormFields(disable) { + $("#setup_form input, #setup_form select, #setup_form button").prop("disabled", disable); +} + +function escapeHtml(text) { + return text + .replace(//g, '>') + .replace(/(?:\r\n|\r|\n)/g, '
') // Replace newline characters with
+ .replace(/\t/g, '    ') // Replace tab characters with four non-breaking spaces + .replace(/ +/g, function (match) { + return match.split('').map(() => ' ').join(''); + }); // Replace consecutive spaces with non-breaking spaces +} + +function scrollToBottom() { + const chatlog = document.getElementById("chatlog"); + chatlog.scrollTop = chatlog.scrollHeight; +} diff --git a/static/styles.css b/static/styles.css new file mode 100644 index 0000000..e0fde3c --- /dev/null +++ b/static/styles.css @@ -0,0 +1,167 @@ +:root { + --user-bg-color: #1e88e5; + --assistant-bg-color: #f5f5f5; + --error-bg-color: #e53935; + --primary-text-color: #212121; + --error-text-color: #fff; + --message-margin: 20px; + --border-radius: 8px; + --font-family: 'Roboto', sans-serif; + --container-bg: #e0e0e0; + --gradient-start: #455a64; + --gradient-end: #607d8b; + --input-border-color: #bdbdbd; + --button-color: #1976d2; + --button-hover-color: #1565c0; +} + +html, body { + height: 100%; + margin: 0; + padding: 0; +} + +body { + font-family: var(--font-family); + background-color: var(--container-bg); + background-image: linear-gradient(120deg, var(--gradient-start), var(--gradient-end)); +} + +.container { + width: 100%; + max-width: 100%; + max-height: 100vh; + padding: 20px; /* Add padding */ + box-sizing: border-box; /* Add box-sizing to prevent the container from overflowing */ + margin: 0; + height: calc(100vh - 40px); /* Subtract the footer height */ + display: flex; + flex-direction: column; + background-color: #ffffff; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +h1 { + text-align: center; + margin-bottom: 50px; + color: var(--primary-text-color); +} + +.form-group { + margin-bottom: 20px; +} + +label { + display: block; + color: var(--primary-text-color); +} + +input[type="text"], textarea, select { + width: 100%; + padding: 8px 12px; + box-sizing: border-box; + border: 1px solid var(--input-border-color); + border-radius: var(--border-radius); + background-color: #ffffff; +} + +button[type="submit"], .btn-primary { + background-color: var(--button-color); + color: white; + padding: 8px 16px; + border: none; + border-radius: var(--border-radius); + cursor: pointer; + transition: background-color 0.3s; +} + +button[type="submit"]:hover, +.btn-primary:hover { + background-color: var(--button-hover-color); +} + +.chatbox { + width: 100%; + background-color: white; + border: 1px solid var(--input-border-color); + border-radius: var(--border-radius); +} + +.chatlog { + overflow-y: scroll; + padding: 20px; + border-bottom: 1px solid var(--input-border-color); + height: calc(100% - 82px); +} + +.input-group { + border-top: 2px solid #f8f9fa; +} + +.card { + flex-grow: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.card-body { + display: flex; + flex-direction: column; + height: calc(100% - 50px); + overflow: hidden; +} + +.chatlog { + flex-grow: 1; + overflow-y: scroll; +} + +.user-message, +.assistant-message, + .error-message { + border-radius: 5px; + padding: 10px; + margin-bottom: 10px; +} + +.message { + margin-right: var(--message-margin); + margin-left: var(--message-margin); + border-radius: var(--border-radius); + padding: 12px 16px; + line-height: 1.5; + font-size: 16px; + max-width: 80%; + word-wrap: break-word; + margin-bottom: 10px; +} + +.user-message { + background-color: var(--user-bg-color); + color: var(--error-text-color); + align-self: flex-end; +} + +.assistant-message { + background-color: var(--assistant-bg-color); + color: var(--primary-text-color); + align-self: flex-start; +} + +.error-message { + background-color: var(--error-bg-color); + color: var(--error-text-color); + align-self: center; +} + +.footer { + position: absolute; + bottom: 0; + width: 100%; + text-align: center; + padding: 10px 0; + background-color: #455a64; /* Updated background color */ + color: #fff; /* Updated text color */ + height: 40px; /* Add a fixed height for the footer */ +} diff --git a/templates/chat.html b/templates/chat.html new file mode 100644 index 0000000..4ccb5b2 --- /dev/null +++ b/templates/chat.html @@ -0,0 +1,37 @@ + + + + + + ChatGPT + + + + + + + +
+

ChatGPT Mini

+
+
+
+
+
+ +
+ +
+
+
+
+
+ + + + + + + diff --git a/templates/setup.html b/templates/setup.html new file mode 100644 index 0000000..8685681 --- /dev/null +++ b/templates/setup.html @@ -0,0 +1,50 @@ + + + + + + ChatGPT Setup + + + + + +
+

ChatGPT Mini Setup

+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+
+ + + + + + +