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('
' + response.message + '
');
+ // Redirect to chat page
+ window.location.href = "/chat";
+ } else {
+ $("#message").html('' + response.message + '
');
+ toggleFormFields(false); // Enable the form fields
+ }
+ },
+ error: function () {
+ $("#message").html('An error occurred while processing the request.
');
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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
+
+
+
+
+
+
+
+
+
+