monra commited on
Commit
84d9cf2
2 Parent(s): f2b6ea4 ee69c04

Merge pull request #83 from machsix/main

Browse files

Add Blueprint to handle static files, add "url_prefix" in config.json

Files changed (8) hide show
  1. client/html/index.html +11 -11
  2. client/js/chat.js +8 -7
  3. config.json +2 -1
  4. run.py +12 -7
  5. server/app.py +0 -3
  6. server/backend.py +30 -34
  7. server/bp.py +6 -0
  8. server/website.py +7 -16
client/html/index.html CHANGED
@@ -11,18 +11,18 @@
11
  property="og:description"
12
  content="A conversational AI system that listens, learns, and challenges" />
13
  <meta property="og:url" content="https://chat.acy.dev" />
14
- <link rel="stylesheet" href="/assets/css/style.css" />
15
- <link rel="apple-touch-icon" sizes="180x180" href="/assets/img/apple-touch-icon.png" />
16
- <link rel="icon" type="image/png" sizes="32x32" href="/assets/img/favicon-32x32.png" />
17
- <link rel="icon" type="image/png" sizes="16x16" href="/assets/img/favicon-16x16.png" />
18
- <link rel="manifest" href="/assets/img/site.webmanifest" />
19
  <link
20
  rel="stylesheet"
21
  href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@latest/build/styles/base16/dracula.min.css" />
22
  <title>FreeGPT</title>
23
  </head>
24
 
25
- <body>
26
  <div class="main-container">
27
  <div class="box sidebar">
28
  <div class="top">
@@ -109,11 +109,11 @@
109
  <script>
110
  window.conversation_id = "{{ chat_id }}";
111
  </script>
112
- <script src="/assets/js/icons.js"></script>
113
- <script src="/assets/js/chat.js" defer></script>
114
  <script src="https://cdn.jsdelivr.net/npm/markdown-it@latest/dist/markdown-it.min.js"></script>
115
- <script src="/assets/js/highlight.min.js"></script>
116
- <script src="/assets/js/highlightjs-copy.min.js"></script>
117
- <script src="/assets/js/theme-toggler.js"></script>
118
  </body>
119
  </html>
 
11
  property="og:description"
12
  content="A conversational AI system that listens, learns, and challenges" />
13
  <meta property="og:url" content="https://chat.acy.dev" />
14
+ <link rel="stylesheet" href="{{ url_for('bp.static', filename='css/style.css') }}" />
15
+ <link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('bp.static', filename='img/apple-touch-icon.png') }}" />
16
+ <link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('bp.static', filename='img/favicon-32x32.png') }}" />
17
+ <link rel="icon" type="image/png" sizes="16x16" href="{{ url_for('bp.static', filename='img/favicon-16x16.png') }}" />
18
+ <link rel="manifest" href="{{ url_for('bp.static', filename='img/site.webmanifest') }}" />
19
  <link
20
  rel="stylesheet"
21
  href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@latest/build/styles/base16/dracula.min.css" />
22
  <title>FreeGPT</title>
23
  </head>
24
 
25
+ <body data-urlprefix="{{ url_prefix}}">
26
  <div class="main-container">
27
  <div class="box sidebar">
28
  <div class="top">
 
109
  <script>
110
  window.conversation_id = "{{ chat_id }}";
111
  </script>
112
+ <script src="{{ url_for('bp.static', filename='js/icons.js') }}"></script>
113
+ <script src="{{ url_for('bp.static', filename='js/chat.js') }}" defer></script>
114
  <script src="https://cdn.jsdelivr.net/npm/markdown-it@latest/dist/markdown-it.min.js"></script>
115
+ <script src="{{ url_for('bp.static', filename='js/highlight.min.js') }}"></script>
116
+ <script src="{{ url_for('bp.static', filename='js/highlightjs-copy.min.js') }}"></script>
117
+ <script src="{{ url_for('bp.static', filename='js/theme-toggler.js') }}"></script>
118
  </body>
119
  </html>
client/js/chat.js CHANGED
@@ -2,6 +2,7 @@ const query = (obj) =>
2
  Object.keys(obj)
3
  .map((k) => encodeURIComponent(k) + "=" + encodeURIComponent(obj[k]))
4
  .join("&");
 
5
  const markdown = window.markdownit();
6
  const message_box = document.getElementById(`messages`);
7
  const message_input = document.getElementById(`message-input`);
@@ -9,8 +10,8 @@ const box_conversations = document.querySelector(`.top`);
9
  const spinner = box_conversations.querySelector(".spinner");
10
  const stop_generating = document.querySelector(`.stop-generating`);
11
  const send_button = document.querySelector(`#send-button`);
12
- const user_image = `<img src="/assets/img/user.png" alt="User Avatar">`;
13
- const gpt_image = `<img src="/assets/img/gpt.png" alt="GPT Avatar">`;
14
  let prompt_lock = false;
15
 
16
  hljs.addPlugin(new CopyButtonPlugin());
@@ -90,7 +91,7 @@ const ask_gpt = async (message) => {
90
  await new Promise((r) => setTimeout(r, 1000));
91
  window.scrollTo(0, 0);
92
 
93
- const response = await fetch(`/backend-api/v2/conversation`, {
94
  method: `POST`,
95
  signal: window.controller.signal,
96
  headers: {
@@ -127,7 +128,7 @@ const ask_gpt = async (message) => {
127
 
128
  chunk = decodeUnicode(new TextDecoder().decode(value));
129
 
130
- if (chunk.includes(`<form id="challenge-form" action="/backend-api/v2/conversation?`)) {
131
  chunk = `cloudflare token expired, please refresh the page.`;
132
  }
133
 
@@ -243,7 +244,7 @@ const delete_conversation = async (conversation_id) => {
243
  };
244
 
245
  const set_conversation = async (conversation_id) => {
246
- history.pushState({}, null, `/chat/${conversation_id}`);
247
  window.conversation_id = conversation_id;
248
 
249
  await clear_conversation();
@@ -252,7 +253,7 @@ const set_conversation = async (conversation_id) => {
252
  };
253
 
254
  const new_conversation = async () => {
255
- history.pushState({}, null, `/chat/`);
256
  window.conversation_id = uuid();
257
 
258
  await clear_conversation();
@@ -426,7 +427,7 @@ window.onload = async () => {
426
  }, 1);
427
 
428
  if (!window.location.href.endsWith(`#`)) {
429
- if (/\/chat\/.+/.test(window.location.href)) {
430
  await load_conversation(window.conversation_id);
431
  }
432
  }
 
2
  Object.keys(obj)
3
  .map((k) => encodeURIComponent(k) + "=" + encodeURIComponent(obj[k]))
4
  .join("&");
5
+ const url_prefix = document.querySelector('body').getAttribute('data-urlprefix')
6
  const markdown = window.markdownit();
7
  const message_box = document.getElementById(`messages`);
8
  const message_input = document.getElementById(`message-input`);
 
10
  const spinner = box_conversations.querySelector(".spinner");
11
  const stop_generating = document.querySelector(`.stop-generating`);
12
  const send_button = document.querySelector(`#send-button`);
13
+ const user_image = `<img src="${url_prefix}/assets/img/user.png" alt="User Avatar">`;
14
+ const gpt_image = `<img src="${url_prefix}/assets/img/gpt.png" alt="GPT Avatar">`;
15
  let prompt_lock = false;
16
 
17
  hljs.addPlugin(new CopyButtonPlugin());
 
91
  await new Promise((r) => setTimeout(r, 1000));
92
  window.scrollTo(0, 0);
93
 
94
+ const response = await fetch(`${url_prefix}/backend-api/v2/conversation`, {
95
  method: `POST`,
96
  signal: window.controller.signal,
97
  headers: {
 
128
 
129
  chunk = decodeUnicode(new TextDecoder().decode(value));
130
 
131
+ if (chunk.includes(`<form id="challenge-form" action="${url_prefix}/backend-api/v2/conversation?`)) {
132
  chunk = `cloudflare token expired, please refresh the page.`;
133
  }
134
 
 
244
  };
245
 
246
  const set_conversation = async (conversation_id) => {
247
+ history.pushState({}, null, `${url_prefix}/chat/${conversation_id}`);
248
  window.conversation_id = conversation_id;
249
 
250
  await clear_conversation();
 
253
  };
254
 
255
  const new_conversation = async () => {
256
+ history.pushState({}, null, `${url_prefix}/chat/`);
257
  window.conversation_id = uuid();
258
 
259
  await clear_conversation();
 
427
  }, 1);
428
 
429
  if (!window.location.href.endsWith(`#`)) {
430
+ if (/\/chat\/.+/.test(window.location.href.slice(url_prefix.length))) {
431
  await load_conversation(window.conversation_id);
432
  }
433
  }
config.json CHANGED
@@ -3,5 +3,6 @@
3
  "host": "0.0.0.0",
4
  "port": 1338,
5
  "debug": false
6
- }
 
7
  }
 
3
  "host": "0.0.0.0",
4
  "port": 1338,
5
  "debug": false
6
+ },
7
+ "url_prefix": ""
8
  }
run.py CHANGED
@@ -1,34 +1,39 @@
1
- from server.app import app
2
  from server.website import Website
3
  from server.backend import Backend_Api
4
  from json import load
5
-
6
 
7
  if __name__ == '__main__':
8
 
9
  # Load configuration from config.json
10
  config = load(open('config.json', 'r'))
11
  site_config = config['site_config']
 
12
 
13
  # Set up the website routes
14
- site = Website(app)
15
  for route in site.routes:
16
- app.add_url_rule(
17
  route,
18
  view_func=site.routes[route]['function'],
19
  methods=site.routes[route]['methods'],
20
  )
21
 
22
  # Set up the backend API routes
23
- backend_api = Backend_Api(app, config)
24
  for route in backend_api.routes:
25
- app.add_url_rule(
26
  route,
27
  view_func=backend_api.routes[route]['function'],
28
  methods=backend_api.routes[route]['methods'],
29
  )
30
 
 
 
 
 
31
  # Run the Flask server
32
- print(f"Running on port {site_config['port']}")
33
  app.run(**site_config)
34
  print(f"Closing port {site_config['port']}")
 
1
+ from server.bp import bp
2
  from server.website import Website
3
  from server.backend import Backend_Api
4
  from json import load
5
+ from flask import Flask
6
 
7
  if __name__ == '__main__':
8
 
9
  # Load configuration from config.json
10
  config = load(open('config.json', 'r'))
11
  site_config = config['site_config']
12
+ url_prefix = config.pop('url_prefix')
13
 
14
  # Set up the website routes
15
+ site = Website(bp, url_prefix)
16
  for route in site.routes:
17
+ bp.add_url_rule(
18
  route,
19
  view_func=site.routes[route]['function'],
20
  methods=site.routes[route]['methods'],
21
  )
22
 
23
  # Set up the backend API routes
24
+ backend_api = Backend_Api(bp, config)
25
  for route in backend_api.routes:
26
+ bp.add_url_rule(
27
  route,
28
  view_func=backend_api.routes[route]['function'],
29
  methods=backend_api.routes[route]['methods'],
30
  )
31
 
32
+ # Create the app and register the blueprint
33
+ app = Flask(__name__)
34
+ app.register_blueprint(bp, url_prefix=url_prefix)
35
+
36
  # Run the Flask server
37
+ print(f"Running on {site_config['port']}{url_prefix}")
38
  app.run(**site_config)
39
  print(f"Closing port {site_config['port']}")
server/app.py DELETED
@@ -1,3 +0,0 @@
1
- from flask import Flask
2
-
3
- app = Flask(__name__, template_folder='./../client/html')
 
 
 
 
server/backend.py CHANGED
@@ -3,33 +3,32 @@ import time
3
  import g4f
4
  from g4f import ChatCompletion
5
  from googletrans import Translator
6
- from flask import request
7
  from datetime import datetime
8
  from requests import get
9
  from server.config import special_instructions
10
 
11
 
12
  class Backend_Api:
13
- def __init__(self, app, config: dict) -> None:
14
- """
15
- Initialize the Backend_Api class.
16
-
17
- :param app: Flask application instance
18
- :param config: Configuration dictionary
19
  """
20
- self.app = app
21
  self.routes = {
22
  '/backend-api/v2/conversation': {
23
  'function': self._conversation,
24
  'methods': ['POST']
25
  }
26
  }
27
-
28
  def _conversation(self):
29
- """
30
- Handles the conversation route.
31
 
32
- :return: Response object containing the generated conversation stream
33
  """
34
  max_retries = 3
35
  retries = 0
@@ -49,7 +48,7 @@ class Backend_Api:
49
  messages=messages
50
  )
51
 
52
- return self.app.response_class(generate_stream(response, jailbreak), mimetype='text/event-stream')
53
 
54
  except Exception as e:
55
  print(e)
@@ -66,11 +65,11 @@ class Backend_Api:
66
 
67
 
68
  def build_messages(jailbreak):
69
- """
70
- Build the messages for the conversation.
71
 
72
- :param jailbreak: Jailbreak instruction string
73
- :return: List of messages for the conversation
74
  """
75
  _conversation = request.json['meta']['content']['conversation']
76
  internet_access = request.json['meta']['content']['internet_access']
@@ -109,11 +108,11 @@ def build_messages(jailbreak):
109
 
110
 
111
  def fetch_search_results(query):
112
- """
113
- Fetch search results for a given query.
114
 
115
- :param query: Search query string
116
- :return: List of search results
117
  """
118
  search = get('https://ddg-api.herokuapp.com/search',
119
  params={
@@ -121,23 +120,20 @@ def fetch_search_results(query):
121
  'limit': 3,
122
  })
123
 
124
- results = []
125
  snippets = ""
126
  for index, result in enumerate(search.json()):
127
  snippet = f'[{index + 1}] "{result["snippet"]}" URL:{result["link"]}.'
128
  snippets += snippet
129
- results.append({'role': 'system', 'content': snippets})
130
-
131
- return results
132
 
133
 
134
  def generate_stream(response, jailbreak):
135
- """
136
- Generate the conversation stream.
137
 
138
- :param response: Response object from ChatCompletion.create
139
- :param jailbreak: Jailbreak instruction string
140
- :return: Generator object yielding messages in the conversation
141
  """
142
  if getJailbreak(jailbreak):
143
  response_jailbreak = ''
@@ -167,11 +163,11 @@ def response_jailbroken_success(response: str) -> bool:
167
 
168
 
169
  def response_jailbroken_failed(response):
170
- """
171
- Check if the response has not been jailbroken.
172
 
173
- :param response: Response string
174
- :return: Boolean indicating if the response has not been jailbroken
175
  """
176
  return False if len(response) < 4 else not (response.startswith("GPT:") or response.startswith("ACT:"))
177
 
 
3
  import g4f
4
  from g4f import ChatCompletion
5
  from googletrans import Translator
6
+ from flask import request, Response, stream_with_context
7
  from datetime import datetime
8
  from requests import get
9
  from server.config import special_instructions
10
 
11
 
12
  class Backend_Api:
13
+ def __init__(self, bp, config: dict) -> None:
14
+ """
15
+ Initialize the Backend_Api class.
16
+ :param app: Flask application instance
17
+ :param config: Configuration dictionary
 
18
  """
19
+ self.bp = bp
20
  self.routes = {
21
  '/backend-api/v2/conversation': {
22
  'function': self._conversation,
23
  'methods': ['POST']
24
  }
25
  }
26
+
27
  def _conversation(self):
28
+ """
29
+ Handles the conversation route.
30
 
31
+ :return: Response object containing the generated conversation stream
32
  """
33
  max_retries = 3
34
  retries = 0
 
48
  messages=messages
49
  )
50
 
51
+ return Response(stream_with_context(generate_stream(response, jailbreak)), mimetype='text/event-stream')
52
 
53
  except Exception as e:
54
  print(e)
 
65
 
66
 
67
  def build_messages(jailbreak):
68
+ """
69
+ Build the messages for the conversation.
70
 
71
+ :param jailbreak: Jailbreak instruction string
72
+ :return: List of messages for the conversation
73
  """
74
  _conversation = request.json['meta']['content']['conversation']
75
  internet_access = request.json['meta']['content']['internet_access']
 
108
 
109
 
110
  def fetch_search_results(query):
111
+ """
112
+ Fetch search results for a given query.
113
 
114
+ :param query: Search query string
115
+ :return: List of search results
116
  """
117
  search = get('https://ddg-api.herokuapp.com/search',
118
  params={
 
120
  'limit': 3,
121
  })
122
 
 
123
  snippets = ""
124
  for index, result in enumerate(search.json()):
125
  snippet = f'[{index + 1}] "{result["snippet"]}" URL:{result["link"]}.'
126
  snippets += snippet
127
+ return [{'role': 'system', 'content': snippets}]
 
 
128
 
129
 
130
  def generate_stream(response, jailbreak):
131
+ """
132
+ Generate the conversation stream.
133
 
134
+ :param response: Response object from ChatCompletion.create
135
+ :param jailbreak: Jailbreak instruction string
136
+ :return: Generator object yielding messages in the conversation
137
  """
138
  if getJailbreak(jailbreak):
139
  response_jailbreak = ''
 
163
 
164
 
165
  def response_jailbroken_failed(response):
166
+ """
167
+ Check if the response has not been jailbroken.
168
 
169
+ :param response: Response string
170
+ :return: Boolean indicating if the response has not been jailbroken
171
  """
172
  return False if len(response) < 4 else not (response.startswith("GPT:") or response.startswith("ACT:"))
173
 
server/bp.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from flask import Blueprint
2
+
3
+ bp = Blueprint('bp', __name__,
4
+ template_folder='./../client/html',
5
+ static_folder='./../client',
6
+ static_url_path='assets')
server/website.py CHANGED
@@ -1,14 +1,15 @@
1
- from flask import render_template, send_file, redirect
2
  from time import time
3
  from os import urandom
4
 
5
 
6
  class Website:
7
- def __init__(self, app) -> None:
8
- self.app = app
 
9
  self.routes = {
10
  '/': {
11
- 'function': lambda: redirect('/chat'),
12
  'methods': ['GET', 'POST']
13
  },
14
  '/chat/': {
@@ -18,24 +19,14 @@ class Website:
18
  '/chat/<conversation_id>': {
19
  'function': self._chat,
20
  'methods': ['GET', 'POST']
21
- },
22
- '/assets/<folder>/<file>': {
23
- 'function': self._assets,
24
- 'methods': ['GET', 'POST']
25
  }
26
  }
27
 
28
  def _chat(self, conversation_id):
29
  if '-' not in conversation_id:
30
- return redirect('/chat')
31
 
32
  return render_template('index.html', chat_id=conversation_id)
33
 
34
  def _index(self):
35
- return render_template('index.html', chat_id=f'{urandom(4).hex()}-{urandom(2).hex()}-{urandom(2).hex()}-{urandom(2).hex()}-{hex(int(time() * 1000))[2:]}')
36
-
37
- def _assets(self, folder: str, file: str):
38
- try:
39
- return send_file(f"./../client/{folder}/{file}", as_attachment=False)
40
- except:
41
- return "File not found", 404
 
1
+ from flask import render_template, redirect, url_for
2
  from time import time
3
  from os import urandom
4
 
5
 
6
  class Website:
7
+ def __init__(self, bp, url_prefix) -> None:
8
+ self.bp = bp
9
+ self.url_prefix = url_prefix
10
  self.routes = {
11
  '/': {
12
+ 'function': lambda: redirect(url_for('._index')),
13
  'methods': ['GET', 'POST']
14
  },
15
  '/chat/': {
 
19
  '/chat/<conversation_id>': {
20
  'function': self._chat,
21
  'methods': ['GET', 'POST']
 
 
 
 
22
  }
23
  }
24
 
25
  def _chat(self, conversation_id):
26
  if '-' not in conversation_id:
27
+ return redirect(url_for('._index'))
28
 
29
  return render_template('index.html', chat_id=conversation_id)
30
 
31
  def _index(self):
32
+ return render_template('index.html', chat_id=f'{urandom(4).hex()}-{urandom(2).hex()}-{urandom(2).hex()}-{urandom(2).hex()}-{hex(int(time() * 1000))[2:]}', url_prefix=self.url_prefix)