machsix commited on
Commit
9ca54c4
1 Parent(s): 7f44f4d

use Blueprint to handle static files, add "url_prefix" in config.json for reverse proxy

Browse files
Files changed (8) hide show
  1. client/html/index.html +11 -11
  2. client/js/chat.js +8 -7
  3. config.json +1 -0
  4. run.py +12 -7
  5. server/app.py +0 -3
  6. server/backend.py +47 -47
  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
@@ -4,5 +4,6 @@
4
  "port": 1338,
5
  "debug": false
6
  },
 
7
  "use_auto_proxy": false
8
  }
 
4
  "port": 1338,
5
  "debug": false
6
  },
7
+ "url_prefix": "",
8
  "use_auto_proxy": false
9
  }
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,7 +3,7 @@ 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.auto_proxy import get_random_proxy, update_working_proxies
@@ -11,14 +11,14 @@ from server.config import special_instructions
11
 
12
 
13
  class Backend_Api:
14
- def __init__(self, app, config: dict) -> None:
15
- """
16
- Initialize the Backend_Api class.
17
 
18
- :param app: Flask application instance
19
- :param config: Configuration dictionary
20
  """
21
- self.app = app
22
  self.use_auto_proxy = config['use_auto_proxy']
23
  self.routes = {
24
  '/backend-api/v2/conversation': {
@@ -33,15 +33,15 @@ class Backend_Api:
33
  # update_proxies.start()
34
 
35
  def _conversation(self):
36
- """
37
- Handles the conversation route.
38
 
39
- :return: Response object containing the generated conversation stream
40
  """
41
  max_retries = 3
42
  retries = 0
43
  conversation_id = request.json['conversation_id']
44
-
45
  while retries < max_retries:
46
  try:
47
  jailbreak = request.json['jailbreak']
@@ -50,13 +50,13 @@ class Backend_Api:
50
 
51
  # Generate response
52
  response = ChatCompletion.create(
53
- model=model,
54
- stream=True,
55
  chatId=conversation_id,
56
  messages=messages
57
  )
58
 
59
- return self.app.response_class(generate_stream(response, jailbreak), mimetype='text/event-stream')
60
 
61
  except Exception as e:
62
  print(e)
@@ -73,11 +73,11 @@ class Backend_Api:
73
 
74
 
75
  def build_messages(jailbreak):
76
- """
77
- Build the messages for the conversation.
78
 
79
- :param jailbreak: Jailbreak instruction string
80
- :return: List of messages for the conversation
81
  """
82
  _conversation = request.json['meta']['content']['conversation']
83
  internet_access = request.json['meta']['content']['internet_access']
@@ -116,11 +116,11 @@ def build_messages(jailbreak):
116
 
117
 
118
  def fetch_search_results(query):
119
- """
120
- Fetch search results for a given query.
121
 
122
- :param query: Search query string
123
- :return: List of search results
124
  """
125
  search = get('https://ddg-api.herokuapp.com/search',
126
  params={
@@ -139,12 +139,12 @@ def fetch_search_results(query):
139
 
140
 
141
  def generate_stream(response, jailbreak):
142
- """
143
- Generate the conversation stream.
144
 
145
- :param response: Response object from ChatCompletion.create
146
- :param jailbreak: Jailbreak instruction string
147
- :return: Generator object yielding messages in the conversation
148
  """
149
  if getJailbreak(jailbreak):
150
  response_jailbreak = ''
@@ -174,36 +174,36 @@ def response_jailbroken_success(response: str) -> bool:
174
 
175
 
176
  def response_jailbroken_failed(response):
177
- """
178
- Check if the response has not been jailbroken.
179
 
180
- :param response: Response string
181
- :return: Boolean indicating if the response has not been jailbroken
182
  """
183
  return False if len(response) < 4 else not (response.startswith("GPT:") or response.startswith("ACT:"))
184
 
185
 
186
- def set_response_language(prompt):
187
- """
188
- Set the response language based on the prompt content.
189
-
190
- :param prompt: Prompt dictionary
191
- :return: String indicating the language to be used for the response
192
- """
193
- translator = Translator()
194
- max_chars = 256
195
- content_sample = prompt['content'][:max_chars]
196
- detected_language = translator.detect(content_sample).lang
197
- return f"You will respond in the language: {detected_language}. "
198
 
199
 
200
 
201
  def getJailbreak(jailbreak):
202
- """
203
- Check if jailbreak instructions are provided.
204
 
205
- :param jailbreak: Jailbreak instruction string
206
- :return: Jailbreak instructions if provided, otherwise None
207
  """
208
  if jailbreak != "default":
209
  special_instructions[jailbreak][0]['content'] += special_instructions['two_responses_instruction']
 
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.auto_proxy import get_random_proxy, update_working_proxies
 
11
 
12
 
13
  class Backend_Api:
14
+ def __init__(self, bp, config: dict) -> None:
15
+ """
16
+ Initialize the Backend_Api class.
17
 
18
+ :param app: Flask application instance
19
+ :param config: Configuration dictionary
20
  """
21
+ self.bp = bp
22
  self.use_auto_proxy = config['use_auto_proxy']
23
  self.routes = {
24
  '/backend-api/v2/conversation': {
 
33
  # update_proxies.start()
34
 
35
  def _conversation(self):
36
+ """
37
+ Handles the conversation route.
38
 
39
+ :return: Response object containing the generated conversation stream
40
  """
41
  max_retries = 3
42
  retries = 0
43
  conversation_id = request.json['conversation_id']
44
+
45
  while retries < max_retries:
46
  try:
47
  jailbreak = request.json['jailbreak']
 
50
 
51
  # Generate response
52
  response = ChatCompletion.create(
53
+ model=model,
54
+ stream=True,
55
  chatId=conversation_id,
56
  messages=messages
57
  )
58
 
59
+ return Response(stream_with_context(generate_stream(response, jailbreak)), mimetype='text/event-stream')
60
 
61
  except Exception as e:
62
  print(e)
 
73
 
74
 
75
  def build_messages(jailbreak):
76
+ """
77
+ Build the messages for the conversation.
78
 
79
+ :param jailbreak: Jailbreak instruction string
80
+ :return: List of messages for the conversation
81
  """
82
  _conversation = request.json['meta']['content']['conversation']
83
  internet_access = request.json['meta']['content']['internet_access']
 
116
 
117
 
118
  def fetch_search_results(query):
119
+ """
120
+ Fetch search results for a given query.
121
 
122
+ :param query: Search query string
123
+ :return: List of search results
124
  """
125
  search = get('https://ddg-api.herokuapp.com/search',
126
  params={
 
139
 
140
 
141
  def generate_stream(response, jailbreak):
142
+ """
143
+ Generate the conversation stream.
144
 
145
+ :param response: Response object from ChatCompletion.create
146
+ :param jailbreak: Jailbreak instruction string
147
+ :return: Generator object yielding messages in the conversation
148
  """
149
  if getJailbreak(jailbreak):
150
  response_jailbreak = ''
 
174
 
175
 
176
  def response_jailbroken_failed(response):
177
+ """
178
+ Check if the response has not been jailbroken.
179
 
180
+ :param response: Response string
181
+ :return: Boolean indicating if the response has not been jailbroken
182
  """
183
  return False if len(response) < 4 else not (response.startswith("GPT:") or response.startswith("ACT:"))
184
 
185
 
186
+ def set_response_language(prompt):
187
+ """
188
+ Set the response language based on the prompt content.
189
+
190
+ :param prompt: Prompt dictionary
191
+ :return: String indicating the language to be used for the response
192
+ """
193
+ translator = Translator()
194
+ max_chars = 256
195
+ content_sample = prompt['content'][:max_chars]
196
+ detected_language = translator.detect(content_sample).lang
197
+ return f"You will respond in the language: {detected_language}. "
198
 
199
 
200
 
201
  def getJailbreak(jailbreak):
202
+ """
203
+ Check if jailbreak instructions are provided.
204
 
205
+ :param jailbreak: Jailbreak instruction string
206
+ :return: Jailbreak instructions if provided, otherwise None
207
  """
208
  if jailbreak != "default":
209
  special_instructions[jailbreak][0]['content'] += special_instructions['two_responses_instruction']
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)