Miguel Diaz commited on
Commit
9c5c050
1 Parent(s): ae5a484

Update assistant + google oauth + google search

Browse files
.gitattributes CHANGED
@@ -1,34 +1,34 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tflite filter=lfs diff=lfs merge=lfs -text
29
- *.tgz filter=lfs diff=lfs merge=lfs -text
30
- *.wasm filter=lfs diff=lfs merge=lfs -text
31
- *.xz filter=lfs diff=lfs merge=lfs -text
32
- *.zip filter=lfs diff=lfs merge=lfs -text
33
- *.zst filter=lfs diff=lfs merge=lfs -text
34
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tflite filter=lfs diff=lfs merge=lfs -text
29
+ *.tgz filter=lfs diff=lfs merge=lfs -text
30
+ *.wasm filter=lfs diff=lfs merge=lfs -text
31
+ *.xz filter=lfs diff=lfs merge=lfs -text
32
+ *.zip filter=lfs diff=lfs merge=lfs -text
33
+ *.zst filter=lfs diff=lfs merge=lfs -text
34
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
.gitignore CHANGED
@@ -2,3 +2,5 @@
2
  .vscode/
3
  __pycache__/
4
  keys/
 
 
 
2
  .vscode/
3
  __pycache__/
4
  keys/
5
+ .env
6
+ logs/eventos.log
Dockerfile CHANGED
@@ -1,21 +1,21 @@
1
- FROM python:3.9
2
-
3
- WORKDIR /code
4
- COPY ./requirements.txt /code/requirements.txt
5
-
6
-
7
- RUN useradd -m -u 1000 user
8
-
9
- USER user
10
-
11
- ENV HOME=/home/user \
12
- PATH=/home/user/.local/bin:$PATH
13
-
14
- WORKDIR $HOME/app
15
-
16
-
17
- COPY --chown=user . $HOME/app
18
- RUN pip install --user --no-cache-dir --upgrade -r /code/requirements.txt
19
- #CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
20
- CMD ["hypercorn", "main:app", "--workers", "6", "--bind", "0.0.0.0:7860"]
21
-
 
1
+ FROM python:3.11
2
+
3
+ WORKDIR /code
4
+ COPY ./requirements.txt /code/requirements.txt
5
+
6
+
7
+ RUN useradd -m -u 1000 user
8
+
9
+ USER user
10
+
11
+ ENV HOME=/home/user \
12
+ PATH=/home/user/.local/bin:$PATH
13
+
14
+ WORKDIR $HOME/app
15
+
16
+
17
+ COPY --chown=user . $HOME/app
18
+ RUN pip install --user --no-cache-dir --upgrade -r /code/requirements.txt
19
+ #CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
20
+ CMD ["hypercorn", "main:app", "--workers", "6", "--bind", "0.0.0.0:7860"]
21
+
main.py CHANGED
@@ -1,186 +1,192 @@
1
- from fastapi import FastAPI, Request, staticfiles, Depends, HTTPException, status
2
- from fastapi.responses import HTMLResponse, StreamingResponse, JSONResponse
3
-
4
  from fastapi.security import HTTPBasic, HTTPBasicCredentials
5
- import os, json
6
- from datetime import datetime, timedelta
7
- import time
8
- import jwt
9
- import openai
10
  from hashlib import sha256
11
- from error_map import error_table
 
 
 
 
 
 
 
 
 
 
12
 
13
- #from token_counter import count_tokens
 
 
14
 
15
- app = FastAPI()
16
- security = HTTPBasic()
17
- model = "gpt-3.5-turbo-16k"
18
- app.mount("/static", staticfiles.StaticFiles(directory="static"), name="static")
19
- users = json.loads(str(os.getenv("USER_KEYS")).replace("\n", ""))
20
- for key in users:
21
- if key == "master": continue
22
- password = key+users[key]+users["master"]
23
- users[key] = sha256(password.encode('UTF-8')).hexdigest()
24
 
25
- def write_line(line, level="INFO"):
26
- with open("log.txt", "a") as f:
27
- fecha = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
28
- f.write(f"[{level}][{fecha}] - {line}\n")
29
 
30
- write_line(["inicio programa"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
- fecha_unix = str(int(time.time()))
 
33
 
34
- JWT_SECRET = users["master"]
35
- JWT_ALGORITHM = "HS256"
36
- JWT_EXPIRATION_TIME_MINUTES = 30
37
-
38
- def create_jwt_token(data):
39
- to_encode = {"data": data}
40
- expire = datetime.utcnow() + timedelta(minutes=JWT_EXPIRATION_TIME_MINUTES)
41
- to_encode.update({"exp": expire})
42
- encoded_jwt = jwt.encode(to_encode, JWT_SECRET, algorithm=JWT_ALGORITHM)
43
- return encoded_jwt
44
-
45
- async def validate_token(request: Request):
46
- data = {}
47
- try:
48
- data = await request.json()
49
- token = data.pop("token")
50
- payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
51
- data["token_data"] = payload["data"]
52
- except Exception as e:
53
- raise HTTPException(status_code=404, detail="Datos no válidos")
54
- return data
55
-
56
- def authenticate_user(credentials: HTTPBasicCredentials):
57
-
58
- password = credentials.username+credentials.password+users["master"]
59
- password = sha256(password.encode('UTF-8')).hexdigest()
60
-
61
- if credentials.username not in users or password != users[credentials.username]:
62
- raise HTTPException(
63
- status_code=status.HTTP_401_UNAUTHORIZED,
64
- detail="Incorrect username or password",
65
- headers={"WWW-Authenticate": "Basic"},
66
- )
67
 
68
- return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
 
 
 
 
 
 
70
 
71
- @app.get("/", response_class=HTMLResponse)
72
- async def root(request: Request, credentials: HTTPBasicCredentials = Depends(security)):
73
- if authenticate_user(credentials):
74
- token = create_jwt_token({"user":credentials.username})
75
- with open(os.path.join("static", "main.html")) as f:
76
- return HTMLResponse(f.read().replace("{% token %}", token).replace("{% version %}", fecha_unix))
77
-
78
- @app.post("/web_test")
79
- async def web_test():
80
- return JSONResponse({'estatus_sesion': 0})
81
-
82
- @app.post("/chat_sync")
83
- async def chat_sync(data = Depends(validate_token)):
84
- messages = data.get("messages", "")
85
- if not messages:
86
- write_line("Empty message", "ERROR")
87
- error = "What ??"
88
- raise HTTPException(
89
- status_code=status.HTTP_418_IM_A_TEAPOT,
90
- detail= error
91
- )
92
- config = {
93
- "temperature": float(data.get("config", []).get("temperature", 1)),
94
- "frequency_penalty": float(data.get("config", []).get("frequency_penalty", 1)),
95
- "presence_penalty": float(data.get("config", []).get("presence_penalty", 1))
96
- }
97
- try:
98
- t_s = time.time()
99
- response_sync = openai.ChatCompletion.create(
100
- model=model,
101
- messages=messages,
102
- temperature=config["temperature"],
103
- frequency_penalty=config["frequency_penalty"],
104
- presence_penalty=config["presence_penalty"],
105
- request_timeout = 5,
106
- stream=False
107
- )
108
- print("time:", time.time() - t_s)
109
- except Exception as error:
110
- write_line(str(error), "ERROR")
111
- raise HTTPException( **error_table.get(type(error), error_table["undefined"]))
112
-
113
- response = {"role":"", "content":""}
114
-
115
- response["role"] = response_sync["choices"][0]["delta"]["role"]
116
- response["content"] = response_sync["choices"][0]["delta"]["content"]
117
- token = create_jwt_token(data.pop("token_data"))
118
- response["token"] = token
119
- return JSONResponse(response)
120
-
121
- @app.post("/chat_async")
122
- async def chat_async(data = Depends(validate_token)):
123
- messages = data.get("messages", "")
124
- if not messages:
125
- print("Empty message")
126
- error = "What ??"
127
- write_line("Empty message")
128
  raise HTTPException(
129
  status_code=status.HTTP_418_IM_A_TEAPOT,
130
- detail= error
131
  )
132
- config = {
133
- "temperature": float(data.get("config", []).get("temperature", 1)),
134
- "frequency_penalty": float(data.get("config", []).get("frequency_penalty", 1)),
135
- "presence_penalty": float(data.get("config", []).get("presence_penalty", 1))
136
- }
137
- try:
138
- st = time.time()
139
- response_async = openai.ChatCompletion.create(
140
- model=model,
141
- messages=messages,
142
- temperature=config["temperature"],
143
- frequency_penalty=config["frequency_penalty"],
144
- presence_penalty=config["presence_penalty"],
145
- request_timeout = 5,
146
- stream=True
147
- )
148
- print("time:", time.time() - st)
149
- except Exception as error:
150
- write_line(str(error), "ERROR")
151
- raise HTTPException( **error_table.get(type(error), error_table["undefined"]))
152
- async def __streamer():
153
- yield json.dumps({"comando": "token", "token":create_jwt_token(data.pop("token_data"))})
154
- yield json.dumps({"comando": "status", "status":{"mensaje":"Cargando", "modo": "reemplazar"}})
155
-
156
- chunk = next(response_async)
157
- mensaje = {"role": chunk["choices"][0]["delta"]["role"], "content":""}
158
-
159
- st = time.time()
160
- st_times = 0
161
- for chunk in response_async:
162
- if chunk["choices"][0]["finish_reason"]:
163
- break
164
-
165
- mensaje["content"] += chunk["choices"][0]["delta"]["content"]
166
-
167
- if time.time() - st > 3:
168
- if st_times > 2:
169
- yield json.dumps({"comando": "status", "status":{"mensaje":"Cargando", "modo": "reemplazar"}})
170
- st_times = 0
171
- else:
172
- yield json.dumps({"comando": "status", "status":{"mensaje":".", "modo": "enlinea"}})
173
- st_times += 1
174
- st = time.time()
175
-
176
-
177
- yield json.dumps({"comando": "mensaje", "mensaje": mensaje})
178
- return StreamingResponse(__streamer(), media_type="application/json")
179
-
180
-
181
- @app.get("/read_log", response_class=HTMLResponse)
182
- async def read_log(request: Request, credentials: HTTPBasicCredentials = Depends(security)):
183
- if authenticate_user(credentials):
184
- with open("log.txt", "r") as f:
185
- return HTMLResponse(f.read())
186
 
 
1
+ from fastapi import FastAPI, Request, Depends, HTTPException, status, Response
2
+ from fastapi.responses import HTMLResponse, RedirectResponse, StreamingResponse
 
3
  from fastapi.security import HTTPBasic, HTTPBasicCredentials
4
+ from base64 import b64decode
5
+ import time, json
 
 
 
6
  from hashlib import sha256
7
+ from modules import model, oauth, security, log_module, llm, chat_functions, settings
8
+ from modules.model import User, Session
9
+ from fastapi.staticfiles import StaticFiles
10
+ from fastapi.templating import Jinja2Templates
11
+ from datetime import datetime
12
+ from typing import Annotated
13
+
14
+ ####################### APP SETUP ########################
15
+ app = FastAPI(docs_url=None, redoc_url=None)
16
+ app.mount("/static", StaticFiles(directory="static"), name="static")
17
+ templates = Jinja2Templates(directory="templates")
18
 
19
+ fecha_unix = str(int(time.time()))
20
+ log_module.logger("system").info("iniciado")
21
+ ##########################################################
22
 
 
 
 
 
 
 
 
 
 
23
 
24
+ ######################## ROUTE: / ########################
25
+ @app.get("/", response_class=HTMLResponse)
26
+ async def main_page(request: Request):
 
27
 
28
+ tools = [{"name": k, "desc": v} for k, v in chat_functions.function_user_text.items()]
29
+ asistants = model.get_all_personality_cores()
30
+
31
+ response = templates.TemplateResponse(
32
+ "main.html", {
33
+ "request": request,
34
+ "version": fecha_unix,
35
+ "tools": tools,
36
+ "asistants": asistants
37
+ })
38
+ return response
39
+ ##########################################################
40
+
41
+
42
+
43
+ #################### SECURITY (OAUTH) ####################
44
+ @app.get("/login", response_class=HTMLResponse)
45
+ async def login(request: Request):
46
+ # Shows the Start "session with google" button, and deletes token cookie
47
+ ret = templates.TemplateResponse("login.html", {"request": request, "redirecturi": settings.OAUTH_REDIRECT})
48
+ ret.delete_cookie("token")
49
+ return ret
50
+
51
+ @app.get("/oauth")
52
+ async def validate_oauth(request: Request, response: Response):
53
+ # Get the oauth get params,
54
+ # look for the google validation and info,
55
+ # set the session cookie and save user in DB
56
+
57
+ # Extract the Get params
58
+ params = dict(request.query_params)
59
+
60
+ # Client browser fingerprint bypassed with google oauth as "state"
61
+ data = params["state"].split("=",1)[1]
62
+ data = b64decode(data)
63
+ data = json.loads(data)
64
+
65
+
66
 
67
+ # Get the google user info
68
+ google_userinfo = oauth.validate_redirect(params)
69
 
70
+ # Create the user model with the google info
71
+ user:User = model.User.find_or_create(google_userinfo, data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
+ # Prepare redirect response and set the session cookie
74
+
75
+ token = user.create_cookie()
76
+ if not user.can_use("chat"):
77
+ response = RedirectResponse(url='/hold')
78
+ security.set_cookie(response, key="token", value=token)
79
+ return response
80
+
81
+ # Saves the user
82
+ user.update_user()
83
+ security.set_cookie(response, key="token", value=token)
84
+
85
+ return {"success": True}
86
+
87
+ ##########################################################
88
+
89
+
90
+ ###################### APIs Configs ######################
91
 
92
+ # Get data and structures from its models
93
+ User_find_from_data = Annotated[User, Depends(User.find_from_data)]
94
+ Session_find_from_data = Annotated[Session, Depends(Session.find_from_data)]
95
+
96
+ @app.post("/getConfigs")
97
+ async def get_configs(response: Response, user: User_find_from_data):
98
 
99
+ if not user.can_use("chat"):
100
+ response = RedirectResponse(url='/hold')
101
+ return response
102
+
103
+ # Get llm tokens used
104
+ year_, month_ = datetime.now().strftime("%y-%m").split("-")
105
+ month = sum(user.tokens.get(year_, {}).get(month_, {"_":0}).values())
106
+ total = sum([z for x in user.tokens.values() for y in x.values() for z in y.values()])
107
+ tokens = {"month": month, "total": total}
108
+
109
+ # Create cookies and answer
110
+ user._session.create_cookie(response)
111
+ return user.configs.model_dump() | {"tokens": tokens, "challenge": user._session.challenge}
112
+
113
+ @app.post("/setConfigs")
114
+ async def set_configs(response: Response, user: User_find_from_data):
115
+
116
+ if not user.can_use("chat"):
117
+ response = RedirectResponse(url='/hold')
118
+ return response
119
+
120
+ # Create config model
121
+ user.configs = model.Configs(**user._data)
122
+ user._session.configs = user.configs
123
+ assisntat_prompt = user.update_user()
124
+
125
+ # Create cookies and answer
126
+ user._session.create_cookie(response)
127
+ return {"success":True, "challenge": user._session.challenge, "assistantPrompt": assisntat_prompt}
128
+
129
+ @app.post("/getToken")
130
+ async def get_token(response: Response, user: User_find_from_data):
131
+
132
+ # Generate api token
133
+ user.create_cookie()
134
+ if not user.can_use("chat"):
135
+ return {"success":False, "redirect": "/hold"}
136
+
137
+ # Create cookies and answer
138
+ return {"success":True, "challenge": user._session.challenge}
139
+
140
+ ##########################################################
141
+
142
+ @app.post("/chat")
143
+ async def chat_async(session: Session_find_from_data):
144
+ chat = model.Chat(messages = session.data["messages"], personality=session.configs.assistant)
145
+ if(len(chat.messages) < 1 or chat.messages[-1].content==""):
146
+ log_module.logger(session.gid).warning("Empty message")
 
 
 
 
 
 
 
 
 
147
  raise HTTPException(
148
  status_code=status.HTTP_418_IM_A_TEAPOT,
149
+ detail= "Nope"
150
  )
151
+ return StreamingResponse(llm.streamer(chat, session), media_type="application/json")
152
+
153
+
154
+ ########################## Static Pages ##########################
155
+ @app.get("/privacy", response_class=HTMLResponse)
156
+ async def privacy_policy(request: Request):
157
+ return templates.TemplateResponse("PrivacyPolicy.html", {"request": request})
158
+
159
+ @app.get("/hold")
160
+ async def on_hold(request: Request, user: User = Depends(User.find_from_cookie)):
161
+ if user.can_use("chat"):
162
+ return RedirectResponse(url='/')
163
+ return templates.TemplateResponse(
164
+ "no_access.html", {
165
+ "request": request,
166
+ "description": bool(user.description)
167
+ })
168
+
169
+ @app.post("/hold")
170
+ async def on_hold(request: Request, user: User = Depends(User.find_from_cookie)):
171
+ if not user.description:
172
+ form = await request.form()
173
+ if message := form["message"].strip():
174
+ user.update_description(message)
175
+
176
+ return templates.TemplateResponse(
177
+ "no_access.html", {
178
+ "request": request,
179
+ "description": bool(user.description)
180
+ })
181
+
182
+
183
+ ########################## Other ##########################
184
+ # @app.get("/read_log", response_class=HTMLResponse)
185
+ # async def read_log(request: Request, credentials: HTTPBasicCredentials = Depends(httpsecurity)):
186
+ # if sha256(credentials.username.encode()).hexdigest()=="bc1c32d709aef061bbde4fc848421cdb933e8a9f391c3a089f2861ac0772c168" and security.authenticate_user(credentials):
187
+ # log_module.log_write(credentials.username, "Log Accesado", "")
188
+ # with open("logs/eventos.log", "r") as f:
189
+ # return HTMLResponse(f.read())
190
+ # log_module.log_write(credentials.username, "Intento acceder logs", f"{request.client.host}:{request.client.port} - {str(dict(request.headers))}")
191
+ # raise HTTPException(status_code=404)
 
 
 
 
 
 
 
 
 
 
 
 
 
192
 
modules/chat_functions/__init__.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ functions = []
2
+ function_callbacks = {}
3
+ function_user_text = {}
4
+ import importlib
5
+ import glob
6
+
7
+
8
+ # Obtiene la lista de archivos .py en la carpeta actual
9
+ archivos = glob.glob("*.py", root_dir=__path__[0])
10
+
11
+ # Recorre cada archivo y lee la variable "info" dentro de ellos
12
+ for archivo in archivos:
13
+ if "__" in archivo[:-3]:
14
+ continue
15
+ modulo = importlib.import_module('.'+archivo[:-3], "modules.chat_functions")
16
+ if not modulo.activo:
17
+ continue
18
+ # Lee la variable "info" del módulo
19
+
20
+ functions.append(modulo.info)
21
+ function_callbacks[modulo.info["function"]["name"]] = modulo.ejecutar
22
+ function_user_text[modulo.info["function"]["name"]] = modulo.user_text
modules/chat_functions/buscar_google.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from googleapiclient.discovery import build
4
+ from .. import log_module
5
+
6
+
7
+ activo = True
8
+ try:
9
+ api_key = os.getenv("GOOGLE_API_KEY")
10
+ cse_id = os.getenv("GOOGLE_CSE_ID")
11
+ except:
12
+ api_key = False
13
+ cse_id = False
14
+ if not api_key or not cse_id:
15
+ activo = False
16
+
17
+ user_text = "Busqueda en google"
18
+
19
+ structure_info = {
20
+ "conseguidos": [
21
+ {
22
+ "title": "This will contain the title of the link",
23
+ "link": "Link to the page where the information is found",
24
+ "snippet": "This will contain a summary of the page's content. Use this information to extrapolate a useful response for the user"
25
+ },
26
+ {'x':'y'},
27
+ ]
28
+ }
29
+ info = {
30
+ "type": "function",
31
+ "function":{
32
+ "name": "google_search",
33
+ "description": "A wrapper around Google Search. Useful for when you need to answer questions about current events. Use it ONLY WHEN IS NECESSARY if you want too look for news or people. The you must return a easily readable result. Avoid using this tool too frequently.\nThe output of the execution will be a JSON: \n"+json.dumps(structure_info, indent=2).replace("{'x':'y'},", "...,"),
34
+ "parameters": {
35
+ "type": "object",
36
+ "properties": {
37
+ "search": {
38
+ "type": "string",
39
+ "description": "The search term to use in Google to obtain information. e.g. Achievements of Alexander the Great in Asia.",
40
+ },
41
+ "info": {
42
+ "type": "string",
43
+ "description": "The user won't see this. Explain why did you call this function",
44
+ }
45
+ },
46
+ "required": ["search"],
47
+ }
48
+ }
49
+ }
50
+
51
+
52
+ def ejecutar(params, gid):
53
+ busqueda = params["search"]
54
+ try:
55
+ service = build("customsearch", "v1", developerKey=api_key)
56
+ results = service.cse().list(q=busqueda, cx=cse_id, num=5).execute()
57
+ retorno = {}
58
+ retorno["founds"] = []
59
+ for result in results["items"]:
60
+ if "title" not in result or "snippet" not in result: continue
61
+ retorno["founds"].append({
62
+ "title": result["title"],
63
+ "link": result["link"],
64
+ "snippet": result["snippet"]
65
+ })
66
+ log_module.logger(gid).info(f"Searh google {params}")
67
+ return json.dumps(retorno)
68
+ except Exception as e:
69
+ log_module.logger(gid).error(f"Searh google failed: {repr(e)}")
70
+ return json.dumps({"status": "api failed"})
71
+
72
+
modules/chat_functions/leer_pagina_web.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import requests
4
+ from modules import log_module
5
+ activo = False
6
+
7
+ user_text = "Leyendo pagina web"
8
+
9
+ info = {
10
+ "type": "function",
11
+ "function":{
12
+ "name": "leer_pagina_web",
13
+ "description": "Un portal hacia Internet. Úsalo cuando necesites obtener contenido específico de un sitio web. La entrada debe ser una URL (por ejemplo, https://www.google.com). La salida será la respuesta en texto de la solicitud GET.",
14
+ "parameters": {
15
+ "type": "object",
16
+ "properties": {
17
+ "url": {
18
+ "type": "string",
19
+ "description": "el url a consultar",
20
+ },
21
+ },
22
+ "required": ["url"],
23
+ }
24
+ }
25
+ }
26
+
27
+ def ejecutar(url):
28
+ try:
29
+ req = requests.get(url=url, timeout=5)
30
+ retorno = {
31
+ "data": req.text
32
+ }
33
+ return json.dumps(retorno)
34
+ except Exception as e:
35
+ log_module.logger.error(repr(e))
36
+ return json.dumps({"status": "api failed"})
37
+
modules/chat_functions/obtener_clima.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import requests
4
+ from modules import log_module
5
+ activo = False
6
+ try:
7
+ api_key = os.getenv("WEATHERAPI_KEY")
8
+ except:
9
+ api_key = False
10
+ if not api_key:
11
+ activo = False
12
+
13
+ user_text = "Consultando el clima"
14
+
15
+ info = {
16
+ "type": "function",
17
+ "function": {
18
+ "name": "obtener_clima",
19
+ "description": "Obtiene el clima de una ubicación",
20
+ "parameters": {
21
+ "type": "object",
22
+ "properties": {
23
+ "ubicacion": {
24
+ "type": "string",
25
+ "description": "La ciudad, estado y pais, e.g. Caracas, Distrito Capital, Venezuela",
26
+ },
27
+ "unidad": {
28
+ "type": "string",
29
+ "enum": ["celsius"]
30
+ },
31
+ },
32
+ "required": ["ubicacion", "unidad"],
33
+ }
34
+ }
35
+ }
36
+
37
+ def ejecutar(ubicacion, unidad="celsius"):
38
+ url = f"https://api.weatherapi.com/v1/current.json?key={api_key}&q={ubicacion}&aqi=no"
39
+ elementos = ["temp_c", "is_day", "condition", "wind_kph", "wind_dir", "precip_mm", "humidity", "cloud", "feelslike_c", "vis_km"]
40
+ try:
41
+ rq = requests.get(url, timeout=3)
42
+ if rq.status_code!=200:
43
+ return json.dumps({"status": "api failed"})
44
+ clima = json.loads(rq.text)
45
+ clima = {k: v for k, v in clima["current"].items() if k in elementos}
46
+ return json.dumps(clima)
47
+ except Exception as e:
48
+ log_module.error(repr(e))
49
+ return json.dumps({"status": "api failed"})
modules/error_map.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from openai._exceptions import (
2
+ APIError,
3
+ OpenAIError,
4
+ ConflictError,
5
+ NotFoundError,
6
+ APIStatusError,
7
+ RateLimitError,
8
+ APITimeoutError,
9
+ BadRequestError,
10
+ APIConnectionError,
11
+ AuthenticationError,
12
+ InternalServerError,
13
+ PermissionDeniedError,
14
+ UnprocessableEntityError,
15
+ APIResponseValidationError,
16
+ )
17
+ from fastapi import status
18
+ from requests.exceptions import RequestException
19
+ error_table = {
20
+ RequestException: {
21
+ "status_code": status.HTTP_408_REQUEST_TIMEOUT,
22
+ "detail": "Los servidores tardaron mucho en responder, puede haber sobrecarga en OpenAI, reintenta luego (error 1)"
23
+ },
24
+ APIConnectionError: {
25
+ "status_code": status.HTTP_408_REQUEST_TIMEOUT,
26
+ "detail": "El servidor no respondió, hubo un error de API"
27
+ },
28
+ APITimeoutError: {
29
+ "status_code": status.HTTP_408_REQUEST_TIMEOUT,
30
+ "detail": "El servidor tardó demasiado en responder"
31
+ },
32
+ RateLimitError: {
33
+ "status_code": status.HTTP_408_REQUEST_TIMEOUT,
34
+ "detail": "ChatGPT se gomitó 🤮, este chat ya es muy largo, limpia el chat y reintenta."
35
+ },
36
+ "undefined": {
37
+ "status_code": status.HTTP_408_REQUEST_TIMEOUT,
38
+ "detail": "Error no definido 🙄"
39
+ }
40
+ }
41
+
modules/llm.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json, time
2
+
3
+ from hashlib import sha256
4
+ from fastapi import HTTPException
5
+ from openai import OpenAI
6
+ import tiktoken
7
+
8
+ from . import log_module, error_map, chat_functions as tools, settings
9
+ from typing import TYPE_CHECKING
10
+ if TYPE_CHECKING:
11
+ from . import model
12
+
13
+ import asyncio
14
+
15
+
16
+
17
+
18
+ encoding = tiktoken.encoding_for_model(settings.GPT_MODEL)
19
+
20
+
21
+ def ejecutar(chat: "model.Chat", session: "model.Session"):
22
+ temp_messages = [msg.model_dump() for msg in chat.messages]
23
+
24
+ try:
25
+ client = OpenAI(
26
+ api_key=settings.OPENAI_API_KEY,
27
+ timeout=30.0,
28
+ max_retries=3
29
+ )
30
+ generated = client.chat.completions.create(
31
+ model=settings.GPT_MODEL,
32
+ messages=temp_messages,
33
+ temperature=session.configs.temperature,
34
+ frequency_penalty=session.configs.frequency_penalty,
35
+ presence_penalty=session.configs.presence_penalty,
36
+ tools= tools.functions if session.configs.useTool else None,
37
+ stream=True,
38
+ user=sha256(session.gid.encode('UTF-8')).hexdigest()
39
+ )
40
+
41
+ return generated
42
+ except Exception as error:
43
+ log_module.logger(session.gid).error(repr(error) + " - " + session.gid)
44
+ raise HTTPException( **error_map.error_table.get(type(error), error_map.error_table["undefined"]))
45
+
46
+ async def streamer(chat: "model.Chat", session: "model.Session", sub_exec:bool = False):
47
+ response_async = ejecutar(chat, session)
48
+ if not sub_exec:
49
+ yield json.dumps({"comando": "status", "status":{"mensaje":"Cargando", "modo": "reemplazar"}})
50
+
51
+ message = None
52
+ role = None
53
+
54
+
55
+ chunk = next(response_async)
56
+ role = chunk.choices[0].delta.role
57
+
58
+ if isinstance(chunk.choices[0].delta.content, str):
59
+ message = chat.new_msg(role, response_async)
60
+ chat.append(message)
61
+ else:
62
+ message = chat.new_func(role, response_async, chunk)
63
+ if not sub_exec:
64
+ yield json.dumps({"comando": "status", "status":{"mensaje":"Buscando en google o algo así", "modo": "reemplazar"}})
65
+ message.exec(session.gid)
66
+
67
+ chat.append(message)
68
+
69
+ async for r_async in streamer(chat, session, True):
70
+ yield r_async
71
+
72
+ if not sub_exec:
73
+ session.update_usage(chat.tokens)
74
+
75
+ log_module.logger(session.gid).info(f"Chat used, tokens: {chat.tokens}")
76
+ yield json.dumps({"comando":"challenge", "challenge": session.challenge} )
77
+ yield json.dumps({"comando":"token", "token": session.create_cookie_token() } )
78
+ yield json.dumps({"comando":"mensaje", "mensaje": chat.messages[-1].model_dump()} )
79
+
80
+
81
+
82
+
modules/log_module.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging, os
2
+
3
+ _logger = logging.getLogger(__name__)
4
+ _logger.setLevel(logging.DEBUG)
5
+ # Configura el archivo donde se guardarán los eventos
6
+ if not os.path.exists("logs"):
7
+ os.makedirs("logs")
8
+ file_handler = logging.FileHandler('logs/eventos.log', mode='a', encoding=None, delay=False)
9
+ file_handler.setLevel(logging.DEBUG)
10
+
11
+ stream_handler = logging.StreamHandler()
12
+ stream_handler.setLevel(logging.DEBUG)
13
+
14
+ # Define el formato de los mensajes de log
15
+ formatter = logging.Formatter('%(asctime)s [%(filename)s:%(lineno)d @ %(funcName)s] - %(levelname)s - %(user)s - %(message)s')
16
+
17
+ file_handler.setFormatter(formatter)
18
+ stream_handler.setFormatter(formatter)
19
+
20
+ # Agrega el handler al logger
21
+ _logger.addHandler(file_handler)
22
+ _logger.addHandler(stream_handler)
23
+
24
+ def logger(user:str = "", action:str = ""):
25
+ return logging.LoggerAdapter(_logger, extra={'user': user})
modules/model.py ADDED
@@ -0,0 +1,375 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+ from pymongo.mongo_client import MongoClient
4
+ from pymongo.server_api import ServerApi
5
+ from fastapi import Request, Response
6
+ from . import log_module, security, settings, chat_functions
7
+ from datetime import timezone, datetime, timedelta
8
+ from pydantic import BaseModel, Field, PrivateAttr
9
+ from typing import Any, List, Self
10
+ import tiktoken
11
+ import uuid
12
+
13
+ client: MongoClient = MongoClient(settings.DB_URI, server_api=ServerApi('1'))
14
+ class __DB:
15
+ user = client.ChatDB.users
16
+ sess = client.ChatDB.sessions
17
+ personalities = client.ChatDB.personalityCores
18
+ DB: __DB = __DB()
19
+
20
+ tz: timezone = timezone(timedelta(hours=-4))
21
+ encoding = tiktoken.encoding_for_model(settings.GPT_MODEL)
22
+
23
+ def count_tokens_on_message(args: list) -> int:
24
+ token_count = 0
25
+ for n in args:
26
+ if n: token_count += len(encoding.encode(n))
27
+ return token_count
28
+
29
+ def get_personality_core (personality):
30
+ found = DB.personalities.find_one({"name":personality})
31
+ return found["prompt"].replace(
32
+ "{date}", datetime.now().strftime("%Y-%m-%D")
33
+ )
34
+
35
+ def get_all_personality_cores():
36
+ return [x["name"] for x in DB.personalities.find({})]
37
+
38
+ class Configs(BaseModel):
39
+ temperature: float = 0.5
40
+ frequency_penalty: float = 0.0
41
+ presence_penalty: float = 0.0
42
+ useTool: bool = True
43
+ assistant: str = "Chatsito clasico"
44
+ assistantPrompt: str = get_personality_core("Chatsito clasico")
45
+
46
+ def __init__(self, *args, **kwargs):
47
+
48
+ super(Configs, self).__init__(*args, **kwargs)
49
+ self.assistantPrompt = get_personality_core(self.assistant)
50
+
51
+
52
+
53
+
54
+ class Message(BaseModel):
55
+ role: str
56
+ content: str = ""
57
+ _tokens: int = 1
58
+ _thread = None
59
+ _running:bool = False
60
+
61
+ def __init__(self, *args, **kwargs):
62
+ super(Message, self).__init__(*args, **kwargs)
63
+ self._tokens = count_tokens_on_message([self.content])
64
+
65
+ def consume_stream(self, stream):
66
+ self._running = True
67
+ for chunk in stream:
68
+ self._tokens += 1
69
+ choice = chunk.choices[0]
70
+ if choice.finish_reason == "stop" or not choice.delta.content:
71
+ return
72
+
73
+ self.content += choice.delta.content
74
+
75
+ self._running = False
76
+
77
+ def get_tokens(self):
78
+ return self._tokens, self._tokensOutput
79
+
80
+ class ToolCallsInputFunction(BaseModel):
81
+ name: str = ""
82
+ arguments: str = ""
83
+ class ToolCallsInput(BaseModel):
84
+ id: str
85
+ function: ToolCallsInputFunction
86
+ type: str = "function"
87
+
88
+ class ToolCallsOutput(BaseModel):
89
+ role: str
90
+ tool_call_id: str
91
+ name: str
92
+ content: str
93
+ _tokens: int = 0
94
+
95
+ def __init__(self, *args, **kwargs):
96
+
97
+ super(ToolCallsOutput, self).__init__(*args, **kwargs)
98
+ self._tokens = count_tokens_on_message([self.content])
99
+
100
+
101
+ class MessageTool(BaseModel):
102
+ role: str
103
+ content: str = ""
104
+ tool_calls: list[ToolCallsInput]
105
+ _tokens: int = 0
106
+ _outputs: list[ToolCallsOutput] = []
107
+
108
+ def __init__(self, **kwargs):
109
+ stream = kwargs.pop("stream")
110
+ chunk = kwargs.pop("chunk")
111
+ kwargs["tool_calls"] = []
112
+ super(MessageTool, self).__init__(**kwargs)
113
+
114
+ while True:
115
+ choice = chunk.choices[0]
116
+ self._tokens += 1
117
+
118
+ if choice.finish_reason:
119
+ break
120
+
121
+ if chunk.choices[0].delta.tool_calls == None:
122
+ chunk = next(stream)
123
+ continue
124
+
125
+ tool_call = chunk.choices[0].delta.tool_calls[0]
126
+
127
+ if tool_call.id:
128
+ self.tool_calls.append(
129
+ ToolCallsInput(
130
+ id=tool_call.id,
131
+ function=ToolCallsInputFunction(
132
+ **tool_call.function.model_dump()
133
+ )))
134
+ elif tool_call.function.arguments:
135
+ self.tool_calls[-1].function.arguments += tool_call.function.arguments
136
+
137
+
138
+ chunk = next(stream)
139
+ if not chunk:
140
+ self._tokens += sum
141
+ break
142
+
143
+ def exec(self, gid):
144
+ for func in self.tool_calls:
145
+ self._outputs.append(ToolCallsOutput(
146
+ role="tool",
147
+ tool_call_id=func.id,
148
+ name=func.function.name ,
149
+ content=chat_functions.function_callbacks[func.function.name](json.loads(func.function.arguments), gid)
150
+ ))
151
+
152
+
153
+ class Chat(BaseModel):
154
+ messages: List[Message|MessageTool|ToolCallsOutput]
155
+ tokens: int = 3
156
+
157
+ def __init__(self: Self, *args: list, **kwargs: dict):
158
+ temp = []
159
+ for i in kwargs.pop("messages"):
160
+ temp.append(Message(**i))
161
+ kwargs["messages"] = temp
162
+ super(Chat, self).__init__(*args, **kwargs)
163
+ self.tokens += sum([x._tokens+3 for x in self.messages])
164
+
165
+ def append(self: Self, message: Message|MessageTool):
166
+ if isinstance(message, Message):
167
+ self.messages.append(message)
168
+ self.tokens += message._tokens
169
+ else:
170
+ self.messages.append(message)
171
+ self.tokens += message._tokens
172
+ for out in message._outputs:
173
+ self.tokens += out._tokens
174
+ self.messages.append(out)
175
+
176
+ @classmethod
177
+ def new_msg(cls: Self, role: str, stream):
178
+ message = Message(role=role)
179
+ message.consume_stream(stream)
180
+ return message
181
+
182
+ @classmethod
183
+ def new_func(cls: Self, role: str, stream, chunk):
184
+ return MessageTool(role=role, stream=stream, chunk=chunk)
185
+
186
+ class Session(BaseModel):
187
+ gid: str
188
+ fprint: str
189
+ hashed: str
190
+ guid: str
191
+ public_key: str = ""
192
+ challenge: str = str(uuid.uuid4())
193
+ data: dict = {}
194
+ configs: Configs | None = None
195
+
196
+ def __init__(self, **kwargs):
197
+ kwargs["guid"] = kwargs.get("guid", str(uuid.uuid4()))
198
+ kwargs["hashed"] = security.sha256(kwargs["guid"] + kwargs["fprint"])
199
+ super(Session, self).__init__(**kwargs)
200
+
201
+
202
+ def validate_signature(self: Self, signature:str):
203
+ valid = security.validate_signature(self.public_key, signature, self.challenge)
204
+ if not valid:
205
+ security.raise_401("Cannot validate Session signature")
206
+ return True
207
+
208
+ @classmethod
209
+ def find_from_data(cls:Self, request: Request, data:dict) -> Self:
210
+ cookie_data:dict = security.token_from_cookie(request)
211
+
212
+ if "gid" not in cookie_data or "guid" not in cookie_data:
213
+ log_module.logger().error("Cookie without session needed data")
214
+ security.raise_401("gid or guid not in cookie")
215
+
216
+ if not (public_key := cookie_data.get("public_key", None)): # FIX Vuln Code
217
+ if request.scope["path"] != "/getToken":
218
+ log_module.logger(cookie_data["gid"]).error(f"User without public key saved | {json.dumps(cookie_data)}")
219
+ security.raise_401("the user must have a public key saved in token")
220
+ else:
221
+ log_module.logger(cookie_data['gid']).info("API public key set for user")
222
+ public_key = data["public_key"]
223
+ else:
224
+ cls.check_challenge(data["fingerprint"], cookie_data["challenge"])
225
+
226
+ session: Self = cls(
227
+ gid = cookie_data["gid"],
228
+ fprint = data["fingerprint"],
229
+ guid = cookie_data["guid"],
230
+ public_key = public_key,
231
+ data = data,
232
+ configs = Configs(**cookie_data["configs"])
233
+ )
234
+
235
+
236
+ if session.hashed != cookie_data["fprint"]:
237
+ log_module.logger(session.gid).error(f"Fingerprint didnt match | {json.dumps(cookie_data)}")
238
+ security.raise_401("Fingerprint didnt match")
239
+
240
+
241
+ session.add_challenge()
242
+
243
+ return session
244
+
245
+
246
+ def create_cookie_token(self:Self):
247
+ return security.create_jwt_token({
248
+ "gid":self.gid,
249
+ "guid": self.guid,
250
+ "fprint": self.hashed,
251
+ "public_key": self.public_key,
252
+ "challenge": self.challenge,
253
+ "configs": self.configs.model_dump()
254
+ })
255
+
256
+ def create_cookie(self:Self, response: Response):
257
+ jwt = self.create_cookie_token()
258
+ security.set_cookie(response, "token", jwt, {"hours": 24})
259
+
260
+ def update_usage(self: Self, message:Message):
261
+ User.update_usage(self.gid, message)
262
+
263
+ def add_challenge(self: Self):
264
+ return True
265
+ self.challenge = str(uuid.uuid4())
266
+ DB.sess.insert_one(self.model_dump(include={"fprint", "challenge"}) )
267
+
268
+ @staticmethod
269
+ def check_challenge(fprint:str, challenge: str):
270
+ return True
271
+ found = DB.sess.find_one_and_delete({"fprint":fprint})
272
+ if not found or found["challenge"] != challenge:
273
+ security.raise_401("Check challenge failed")
274
+
275
+
276
+ class User(BaseModel):
277
+ name: str
278
+ tokens: dict = {}
279
+ created: datetime = datetime.now(tz)
280
+ approved: datetime | None = None
281
+ description: str = ""
282
+ email: str
283
+ gid: str
284
+ role: str = "on hold"
285
+ configs: Configs = Configs()
286
+
287
+ _session: Session | None = None
288
+ _data: dict | None = None
289
+
290
+ @classmethod
291
+ def find_or_create(cls: Self, data: dict, loginData: dict)-> Self:
292
+
293
+ found = DB.user.find_one({"gid":data["gid"]})
294
+
295
+ user:Self = cls(**found) if found else cls(**data)
296
+
297
+ if not found:
298
+ DB.user.insert_one(user.model_dump())
299
+
300
+ user._session = Session(gid=user.gid, fprint=loginData["fp"], public_key=loginData["pk"])
301
+
302
+ log_module.logger(user.gid).info(f"User {'logged' if found else'created'} | fp: {user._session.fprint}")
303
+ return user
304
+
305
+ @classmethod
306
+ def find_from_cookie(cls:Self, request: Request) -> Self:
307
+ cookie_data:dict = security.token_from_cookie(request)
308
+
309
+ if "gid" not in cookie_data or "guid" not in cookie_data:
310
+ log_module.logger().error("Cookie without needed data")
311
+ security.raise_307("gid or guid not in cookie")
312
+
313
+ found:dict = DB.user.find_one({"gid":cookie_data["gid"]})
314
+
315
+ if not found:
316
+ log_module.logger(cookie_data["gid"]).error("User not found on DB")
317
+ security.raise_307("User not found on DB")
318
+
319
+ user: Self = cls(**found)
320
+ user._session = Session(
321
+ gid = cookie_data["gid"],
322
+ guid = cookie_data["guid"],
323
+ fprint = cookie_data["fprint"],
324
+ configs = user.configs
325
+ )
326
+ return user
327
+
328
+ @classmethod
329
+ def find_from_data(cls:Self, request: Request, data:dict) -> Self:
330
+ session:Session = Session.find_from_data(request, data)
331
+
332
+ found:dict = DB.user.find_one({"gid":session.gid})
333
+
334
+ if not found:
335
+ log_module.logger(session.gid).error("User not found on DB")
336
+ security.raise_307("User not found on DB")
337
+
338
+ user: Self = cls(**found)
339
+ user._session = session
340
+ user._data = data
341
+ return user
342
+
343
+ def update_description(self: Self, message: str) -> None:
344
+ log_module.logger(self.gid).info("Description Updated")
345
+ DB.user.update_one(
346
+ {"gid":self.gid},
347
+ {"$set": { "description": message}}
348
+ )
349
+ self.description = message
350
+
351
+ def can_use(self: Self, activity: str):
352
+ return security.can_use(self.role, activity)
353
+
354
+ def update_user(self: Self) -> None:
355
+ log_module.logger(self.gid).info("User Updated")
356
+ DB.user.update_one({"gid": self.gid}, {"$set": self.model_dump()})
357
+ return self.configs.assistantPrompt
358
+
359
+ @staticmethod
360
+ def update_usage(gid:str, tokens:int):
361
+ inc_field = datetime.now().strftime("tokens.%y.%m.%d")
362
+ DB.user.update_one({"gid": gid}, {"$inc":{inc_field: tokens}})
363
+
364
+ def create_cookie(self:Self):
365
+ return security.create_jwt_token({
366
+ "gid":self._session.gid,
367
+ "guid": self._session.guid,
368
+ "fprint": self._session.hashed,
369
+ "public_key": self._session.public_key,
370
+ "challenge": self._session.challenge,
371
+ "configs": self.configs.model_dump()
372
+ })
373
+
374
+
375
+
modules/oauth.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ from . import settings
3
+
4
+ def validate_redirect(params):
5
+ data = {
6
+ 'code': params["code"],
7
+ 'redirect_uri': settings.OAUTH_REDIRECT,
8
+ 'client_id': settings.GOOGLE_CLIENT_ID,
9
+ 'client_secret': settings.GOOGLE_CLIENT_SECRET,
10
+ 'scope': params["scope"],
11
+ 'grant_type': 'authorization_code'
12
+ }
13
+
14
+ google_auth_rq = requests.post("https://oauth2.googleapis.com/token", data=data, timeout=30)
15
+ google_auth = google_auth_rq.json()
16
+
17
+ try:
18
+ headers = {
19
+ 'Authorization': google_auth["token_type"]+" "+google_auth["access_token"]
20
+ }
21
+ except Exception as e:
22
+ print(e)
23
+ print(google_auth_rq.content)
24
+ return str(e)+" "+google_auth_rq.content
25
+ google_userinfo_rq = requests.get("https://www.googleapis.com/oauth2/v2/userinfo", headers=headers, timeout=30)
26
+ ret = google_userinfo_rq.json()
27
+ ret["gid"] = ret.get("id","")
28
+ return ret
29
+
modules/personality_cores.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ from pymongo.mongo_client import MongoClient
3
+ from pymongo.server_api import ServerApi
4
+ from . import settings
5
+
6
+ client: MongoClient = MongoClient(settings.DB_URI, server_api=ServerApi('1'))
7
+ class __DB:
8
+ personalities = client.ChatDB.personalityCores
9
+ DB: __DB = __DB()
10
+
11
+
12
+ def get (asistente):
13
+ found = DB.personalities.find_one({"name":asistente})
14
+ return found["prompt"].replace(
15
+ "{date}", datetime.now().strftime("%Y-%m-%D")
16
+ )
17
+
18
+
modules/security.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import jwt
2
+ from hashlib import sha256
3
+ from fastapi import HTTPException, status, Request, Response
4
+ from . import log_module, settings
5
+ from datetime import datetime, timedelta
6
+ from Crypto.Signature import pss
7
+ from Crypto.Hash import SHA256
8
+ from Crypto.PublicKey import RSA
9
+
10
+ import base64
11
+
12
+
13
+ for key in settings.USERS:
14
+ if key == "master": continue
15
+ password = key+settings.USERS[key]+settings.USERS["master"]
16
+ settings.USERS[key] = sha256(password.encode('UTF-8')).hexdigest()
17
+
18
+
19
+ def create_jwt_token(data, maxlife=settings.JWT_EXPIRATION_TIME_MINUTES_API):
20
+ expire = datetime.utcnow() + timedelta(minutes=maxlife)
21
+ to_encode = data | {"exp": expire}
22
+ encoded_jwt = jwt.encode(to_encode, settings.JWT_SECRET, algorithm=settings.JWT_ALGORITHM)
23
+ return encoded_jwt
24
+
25
+ def validate_jwt_token(token: str, usage: str = "api"):
26
+ try:
27
+ payload = jwt.decode(token, settings.JWT_SECRET, algorithms=[settings.JWT_ALGORITHM])
28
+ return payload
29
+ except Exception as e:
30
+ log_module.logger().error(repr(e) + " - Invalid token: " + str(token))
31
+ if usage == "view":
32
+ return raise_307("Failed validating jwt token")
33
+ return raise_401("Failed validating jwt token")
34
+
35
+ def token_from_cookie(request:Request):
36
+ if token := request.cookies.get('token', ""):
37
+ return validate_jwt_token(token, "view")
38
+ return raise_307("Token not found")
39
+
40
+ def token_from_headers(request:Request):
41
+ if (bearer := request.headers.get("Autorization", " ").split(" ",1)) and bearer[0] == "Bearer":
42
+ return validate_jwt_token(bearer[1])
43
+ return raise_401("Bearer malformed")
44
+
45
+
46
+ def can_use(role, activity):
47
+ can = {
48
+ "chat": ["user", "admin"]
49
+ }
50
+ return role in can[activity]
51
+
52
+ def raise_401(detail:str):
53
+ headers = {"set-cookie": "token=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT",
54
+ "Location": "/login"}
55
+ raise HTTPException(
56
+ status_code=status.HTTP_401_UNAUTHORIZED,
57
+ detail=detail,
58
+ headers=headers
59
+ )
60
+
61
+ def raise_307(detail:str):
62
+ headers = {
63
+ "set-cookie": "token=deleted; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT",
64
+ #"Location": "/login"
65
+ }
66
+ raise HTTPException(
67
+ status_code=status.HTTP_307_TEMPORARY_REDIRECT,
68
+ detail=detail,
69
+ headers=headers
70
+ )
71
+
72
+ def validate_signature(public_key: str, signature: str, data:str) -> bool:
73
+ public_key = RSA.import_key(public_key)
74
+ signature = base64.b64decode(signature)
75
+ data_ = SHA256.new(data.encode())
76
+ try:
77
+ pss.new(public_key).verify(data_, signature)
78
+ return True
79
+ except ValueError:
80
+ raise_401("Signature failed")
81
+
82
+ def sha256(data:str|bytes) -> str:
83
+ data_ = data if isinstance(data, bytes) else data.encode()
84
+ return SHA256.new(data_).hexdigest()
85
+
86
+ def set_cookie(response: Response, key: str, value: str, expire_time: dict = {"days":7}):
87
+ expires = datetime.now(tz=settings.TZ) + timedelta(**expire_time)
88
+ response.set_cookie(
89
+ key=key,
90
+ value=value,
91
+ # httponly=True,
92
+ # samesite='none',
93
+ expires=expires.strftime("%a, %d %b %Y %H:%M:%S %Z"),
94
+ #domain='.<YOUR DOMAIN>'
95
+
96
+ )
modules/settings.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os, json
2
+
3
+ from datetime import timezone, timedelta
4
+ from modules import log_module
5
+ from Crypto.Random import get_random_bytes
6
+
7
+ def environ_get(variable, required=False):
8
+ if variable not in os.environ:
9
+ if not required:
10
+ log_module.logger("system").warning(f"Variable not set {variable}")
11
+ return None
12
+ else:
13
+ log_module.logger("system").error(f"Required variable not set {variable}")
14
+
15
+ return os.environ[variable]
16
+
17
+ TZ = timezone(timedelta(hours=-4))
18
+
19
+ GPT_MODEL = environ_get("GPT_MODEL", True)
20
+
21
+ MONGO_URL = environ_get("MONGO_URL")
22
+ MONGO_PWD = environ_get("MONGO_PWD")
23
+ MONGO_USR = environ_get("MONGO_USR")
24
+ DB_URI = f"mongodb+srv://{MONGO_USR}:{MONGO_PWD}@{MONGO_URL}/?retryWrites=true&w=majority"
25
+ DB_URI = "mongodb://127.0.0.1:27017"
26
+
27
+
28
+ OPENAI_API_KEY=environ_get('OPENAI_API_KEY', True)
29
+ USERS = json.loads(str(environ_get("USER_KEYS", True)).replace("\n", ""))
30
+
31
+ JWT_SECRET = environ_get('JWT_SECRET', True)
32
+
33
+ JWT_ALGORITHM = "HS256"
34
+ JWT_EXPIRATION_TIME_MINUTES_API = 7*24*60
35
+ JWT_EXPIRATION_TIME_MINUTES_VIEW = 7*24*60
36
+
37
+ GOOGLE_CLIENT_ID = environ_get("GOOGLE_CLIENT_ID", True)
38
+ GOOGLE_CLIENT_SECRET = environ_get("GOOGLE_CLIENT_SECRET", True)
39
+ GOOGLE_API_KEY = environ_get("GOOGLE_API_KEY")
40
+ GOOGLE_CSE_ID = environ_get("GOOGLE_CSE_ID")
41
+
42
+ OAUTH_REDIRECT = os.environ.get("OAUTH_REDIRECT", True)
requirements.txt CHANGED
@@ -1,7 +1,17 @@
1
- fastapi==0.74.*
2
- requests==2.27.*
3
- openai==0.27.*
4
- uvicorn[standard]==0.17.*
5
- PyJWT==2.6.0
6
- hypercorn>=0.14.3
7
- tiktoken>=0.3.0
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi==0.104.*
2
+ requests==2.31.*
3
+ openai==1.2.*
4
+ uvicorn[standard]==0.23.*
5
+ PyJWT==2.8.*
6
+ hypercorn>=0.15.*
7
+ google-api-python-client==2.108.*
8
+ pymongo[srv]==4.6.*
9
+ Jinja2>=3.1.*
10
+ python-multipart==0.0.6
11
+ tiktoken>=0.5.*
12
+ pycryptodome==3.19.*
13
+ pydantic==2.5.*
14
+ PyJWT==2.8.*
15
+ pymongo[srv]==4.6.*
16
+ python-multipart>=0.0.6
17
+ tiktoken==0.5.*
static/css/app.css CHANGED
@@ -21,6 +21,7 @@ button{
21
  display: grid;
22
  grid-template-rows: 40px auto;
23
  height: 100%;
 
24
  }
25
  .chat {
26
  --textarea: 0px;
@@ -132,7 +133,6 @@ button{
132
 
133
  .input-box textarea,
134
  .input-box button {
135
- height: 100%;
136
  margin: 0;
137
  border: none;
138
  padding: 0 15px;
@@ -164,6 +164,9 @@ button{
164
  .input-box .input-delete {
165
  background-image: url(/static/img/delete.png);
166
  }
 
 
 
167
  .input-box button:first-child{
168
  border-left: none;
169
  }
@@ -245,4 +248,5 @@ button{
245
  }
246
  dialog{
247
  margin: auto;
 
248
  }
 
21
  display: grid;
22
  grid-template-rows: 40px auto;
23
  height: 100%;
24
+ padding: 0px 20px;
25
  }
26
  .chat {
27
  --textarea: 0px;
 
133
 
134
  .input-box textarea,
135
  .input-box button {
 
136
  margin: 0;
137
  border: none;
138
  padding: 0 15px;
 
164
  .input-box .input-delete {
165
  background-image: url(/static/img/delete.png);
166
  }
167
+ .input-box .input-menu {
168
+ background-image: url(/static/img/menu.svg);
169
+ }
170
  .input-box button:first-child{
171
  border-left: none;
172
  }
 
248
  }
249
  dialog{
250
  margin: auto;
251
+ outline: none;
252
  }
static/css/menu.css ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .menu{
2
+ background-color: #FFF;
3
+ }
4
+
5
+ dialog{
6
+ width: 380px;
7
+ /* height: 500px; */
8
+ border: solid 1px #000;
9
+ box-shadow: 5px 5px 5px #000A;
10
+ overflow-x: hidden;
11
+ overflow-y: auto;
12
+ padding: 20px;
13
+ }
14
+
15
+ dialog#menu .title{
16
+ font-size: 32px;
17
+ margin-top: -15px;
18
+ margin-left: -5px;
19
+ }
20
+
21
+ dialog#menu .close{
22
+ position: relative;
23
+ float: right;
24
+ top: -35px;
25
+ right: -12px;
26
+ font-size: 25px;
27
+ padding: 0px 6px;
28
+ line-height: 24px;
29
+ outline: none;
30
+ }
31
+
32
+ dialog#menu .saveButton{
33
+ width: 100%;
34
+ display: flex;
35
+ }
36
+
37
+ dialog#menu .saveButton button{
38
+ margin: auto;
39
+ font-size: 20px;
40
+ }
41
+
42
+ dialog#menu .item{
43
+ width: 100%;
44
+ display: grid;
45
+ align-items: center;
46
+ grid-template-columns: 170px auto;
47
+ margin-bottom: 10px;
48
+ }
49
+
50
+ dialog#menu .item:hover{
51
+ background-color: #eee;
52
+ }
53
+
54
+ dialog#menu hr{
55
+ margin: 15px -5px;
56
+ }
57
+ dialog#menu .item .indented{
58
+ padding-left: 10px;
59
+
60
+ }
61
+ dialog#menu .item .switch {
62
+ --secondary-container: #3a4b39;
63
+ --primary: #84da89;
64
+ font-size: 17px;
65
+ position: relative;
66
+ display: inline-block;
67
+ width: 3.7em;
68
+ height: 1.8em;
69
+ zoom: 0.6;
70
+ margin: auto;
71
+ }
72
+ dialog#menu .item .switch input {
73
+ display: none;
74
+ opacity: 0;
75
+ width: 0;
76
+ height: 0;
77
+ }
78
+ dialog#menu .item .slider {
79
+ position: absolute;
80
+ cursor: pointer;
81
+ top: 0;
82
+ left: 0;
83
+ right: 0;
84
+ bottom: 0;
85
+ background-color: #313033;
86
+ transition: 0.2s;
87
+ border-radius: 30px;
88
+ }
89
+ dialog#menu .item .slider:before {
90
+ position: absolute;
91
+ content: "";
92
+ height: 1.4em;
93
+ width: 1.4em;
94
+ border-radius: 20px;
95
+ left: 0.2em;
96
+ bottom: 0.2em;
97
+ background-color: #aeaaae;
98
+ transition: 0.4s;
99
+ }
100
+ dialog#menu .item input:checked + .slider::before {
101
+ background-color: var(--primary);
102
+ }
103
+ dialog#menu .item input:checked + .slider {
104
+ background-color: var(--secondary-container);
105
+ }
106
+ dialog#menu .item input:focus + .slider {
107
+ box-shadow: 0 0 1px var(--secondary-container);
108
+ }
109
+ dialog#menu .item input:checked + .slider:before {
110
+ transform: translateX(1.9em);
111
+ }
112
+
113
+ dialog#menu .item:has(input:disabled){
114
+ opacity: 0.5;
115
+ }
116
+
117
+ dialog#menu .item label{
118
+
119
+ }
120
+
121
+ dialog#menu .item input{
122
+ outline: none;
123
+ }
124
+
125
+ dialog#menu .item input[type=text]{
126
+
127
+ }
128
+
129
+ dialog#menu .item .range{
130
+ width: 100%;
131
+ display: grid;
132
+ align-items: center;
133
+ grid-template-columns: auto 40px;
134
+ }
135
+
136
+ dialog#menu .item .range span{
137
+ text-align: right;
138
+ }
139
+ *:has(.tooltip){
140
+ position: relative;
141
+ }
142
+ *:hover > .tooltip {
143
+ top: -35px;
144
+ opacity: 0.9;
145
+ }
146
+
147
+ .tooltip {
148
+ position: absolute;
149
+ left: 50px;
150
+ top: 60%;
151
+ padding: 0.55rem 1rem;
152
+ max-width: 300px;
153
+ color: #000;
154
+ border-radius: 50px;
155
+ background-color: #fff;
156
+ box-shadow: 0 15px 35px rgba(50, 50, 93, 0.1), 0 5px 15px rgba(0, 0, 0, 0.07);
157
+ pointer-events: none;
158
+ user-select: none;
159
+ opacity: 0;
160
+ transition: all 0.2s ease-in-out;
161
+ width: max-content;
162
+ z-index: 30;
163
+ }
164
+
165
+ .inserted{
166
+ width: 380px;
167
+ height: 500px;
168
+ border: solid 1px #000;
169
+ box-shadow: 5px 5px 5px #000A;
170
+ overflow: hidden;
171
+ padding: 0px;
172
+ }
173
+ .inserted iframe{
174
+ width: 100%;
175
+ height: 100%;
176
+ border: none;
177
+ }
static/css/tabs.css CHANGED
@@ -19,7 +19,6 @@
19
  }
20
 
21
  .tab {
22
- padding: 4px 20px;
23
  display: none;
24
  }
25
  .tab.active{
@@ -36,6 +35,7 @@
36
  margin-right: 4px;
37
  border-radius: 0px 0px 10px 10px;
38
  transition: 0.2s background-color;
 
39
  }
40
  .tab-label-add {
41
  background: #3b74ad;
@@ -47,26 +47,20 @@
47
  display: block;
48
  line-height: 26px;
49
  }
50
- .tab-label > div, .tab-label-add > div{
51
  display: flex;
52
  align-items: center;
53
- padding: 0 10px;
54
  text-wrap: nowrap;
55
- }
56
- .tab-label:hover {
57
- /* top: -0.25rem;
58
- transition: top 0.25s; */
 
59
  }
60
 
61
  .tab-label:has(.tab-switch:checked) {
62
  background: #fff;
63
-
64
- /* color: #2c3e50;
65
- border-bottom: 0;
66
- border-right: 0.125rem solid #fff;
67
- transition: all 0.35s;
68
- z-index: 1;
69
- top: -0.0625rem; */
70
  }
71
  .template{
72
  display: none;
 
19
  }
20
 
21
  .tab {
 
22
  display: none;
23
  }
24
  .tab.active{
 
35
  margin-right: 4px;
36
  border-radius: 0px 0px 10px 10px;
37
  transition: 0.2s background-color;
38
+ background: #7d9dbd;
39
  }
40
  .tab-label-add {
41
  background: #3b74ad;
 
47
  display: block;
48
  line-height: 26px;
49
  }
50
+ .tab-label > .tab-name, .tab-label-add > .tab-name{
51
  display: flex;
52
  align-items: center;
53
+ margin: 0 10px;
54
  text-wrap: nowrap;
55
+ width: 64px;
56
+ overflow: hidden;
57
+ background: linear-gradient(90deg, rgba(0,0,0,1) 0%, rgba(0,0,0,0.8) 70%, rgba(0,0,0,0) 100%);
58
+ -webkit-background-clip: text;
59
+ -webkit-text-fill-color: transparent;
60
  }
61
 
62
  .tab-label:has(.tab-switch:checked) {
63
  background: #fff;
 
 
 
 
 
 
 
64
  }
65
  .template{
66
  display: none;
static/favicon.png CHANGED
static/img/icon-t.png ADDED
static/img/icon-w.png ADDED
static/img/menu.svg CHANGED
static/js/SecurityHandler.js ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class Security{
2
+ keypair;
3
+ privateKey;
4
+ publicKey;
5
+ fingerprint;
6
+ ready = false;
7
+
8
+ async start(){
9
+ let keypair = await window.crypto.subtle.generateKey(
10
+ {
11
+ name: "RSA-PSS",
12
+ modulusLength: 1024,
13
+ publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
14
+ hash: {name: "SHA-256"},
15
+ },
16
+ false,
17
+ ["sign"]
18
+ )
19
+ this.keypair = keypair
20
+ this.privateKey = keypair.privateKey
21
+ this.publicKey = keypair.publicKey
22
+
23
+ let exportKey_ba = await window.crypto.subtle.exportKey(
24
+ "spki",
25
+ keypair.publicKey
26
+ )
27
+
28
+ let exportKey_u8 = new Uint8Array(exportKey_ba);
29
+ this.publicKey = "-----BEGIN PUBLIC KEY-----\n"+btoa(String.fromCharCode(...exportKey_u8))+"\n-----END PUBLIC KEY-----"
30
+
31
+ this.fingerprint = await this.generateFingerprint()
32
+
33
+ this.ready = true
34
+ return true
35
+
36
+ }
37
+
38
+ async sign(data){
39
+ let encoder = new TextEncoder()
40
+ let sign_ba = await window.crypto.subtle.sign(
41
+ {
42
+ name: "RSA-PSS",
43
+ saltLength: 32,
44
+ },
45
+ this.keypair.privateKey,
46
+ encoder.encode(data)
47
+ )
48
+ let sign_u8 = new Uint8Array(sign_ba);
49
+ return btoa(String.fromCharCode(...sign_u8))
50
+ }
51
+
52
+ async generateFingerprint(){
53
+ const fpPromise = await import('/static/js/fingerprintv4.js').then(FingerprintJS => FingerprintJS.load())
54
+ const fp = await fpPromise.get()
55
+ return fp.visitorId
56
+ }
57
+
58
+
59
+ }
static/js/chatHandler.js CHANGED
@@ -1,184 +1,381 @@
1
  class ChatGPT{
2
- definicion = "Te llamas Chatsito, eres un asistente de apoyo a los amigos de MIA, " +
3
- "tu objetivo principal es responder preguntas de manera puntual y objetiva " +
4
- "a tu interlocutor.\n" +
5
- "Responde de manera amistosa con en el texto más corto y objetivo posible.\n" +
6
- "Knowledge cutoff: 2021-09-01\nCurrent date: {date}";
7
- constructor(token){
8
-
9
- let fecha = new Date().toJSON().slice(0, 10);
10
- this.definicion = this.definicion.replace("{date}", fecha)
11
- this.definicion = {role: "system", content: this.definicion, tokens: 100};
12
- this.cargarHistorial()
13
 
14
- this.execStart = 0;
15
 
16
- this.endpointChat = "/chat_async";
17
- this.token = token
18
- this.reintentos = 0
19
-
20
- $(document).on("chat:enviar", (event, params) => {
21
- this.reintentos = 0;
22
- this.enviar(params.mensaje, params.ctx);
23
- });
24
 
25
- $(document).on("enviar:error", (event, params) => this.reenviar(params));
26
-
27
- $(document).on("chat:crear", ()=>this.crearChat())
28
 
29
- $(document).on("chat:eliminar", (event, params)=>this.eliminarChat(params.ctx, params.index))
30
-
31
-
 
 
 
 
 
 
 
 
 
 
32
  }
33
 
34
- cargarHistorial(){
35
- if (localStorage.getItem("conversaciones") !== null && JSON.parse(localStorage.getItem("conversaciones")).length!=0) {
36
- this.conversaciones = JSON.parse(localStorage.getItem("conversaciones"));
37
- }else{
38
- this.conversaciones = [[this.definicion]];
39
- }
40
-
41
- if (localStorage.getItem("config") !== null) {
42
- this.config = JSON.parse(localStorage.getItem("config"));
43
- }else{
44
- this.config = {
45
- temperature: 1.0,
46
- frequency_penalty: 0.0,
47
- presence_penalty: 0.0
48
- };
49
- }
50
 
51
- this.wHand = []
52
- for(let conversacion of this.conversaciones){
53
- this.wHand.push(new WindowHandler(conversacion, this.wHand.length));
54
- }
55
- $(document).trigger("chat:creado");
56
 
 
57
 
58
 
59
  }
60
 
61
- crearChat(){
62
- for(let hwnd of this.wHand){
63
- if(!hwnd.interacted){
64
- let labels = $(".tab-label input");
65
- labels.prop("checked", false);
66
- $(labels[hwnd.index]).prop("checked", true);
67
- $(document).trigger("chat:creado");
68
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  }
70
- }
71
 
72
- this.conversaciones.push([this.definicion])
73
- this.wHand.push(new WindowHandler(this.conversaciones[this.conversaciones.length-1], this.wHand.length));
74
- let labels = $(".tab-label input");
75
- labels.prop("checked", false);
76
- $(labels[labels.length-1]).prop("checked", true);
77
- $(document).trigger("chat:creado");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
  }
80
 
81
- eliminarChat(ctx, index){
82
- this.conversaciones.splice(index, 1)
83
- this.wHand.splice(index, 1)
84
- $($(".tab-label")[index]).remove()
85
- ctx.remove()
86
-
87
- let labels = $(".tab-label input")
88
- for(let i=0; i<labels.length; i++){
89
- $(labels[i]).val(i)
90
- this.wHand[i].index=i;
91
- }
92
- $(labels[0]).prop("checked", true);
93
- localStorage.setItem("conversaciones", JSON.stringify(this.conversaciones))
94
- $(document).trigger("chat:creado");
95
- if(this.wHand.length==0){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  this.crearChat()
 
97
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  }
100
 
101
- limpiarConfig(){
102
- localStorage.removeItem('convesacion');
103
- localStorage.removeItem('config');
104
- this.config = {
105
- temperature: 1.0,
106
- frequency_penalty: 0.0,
107
- presence_penalty: 0.0
108
- };
109
- this.conversaciones = [this.definicion];
 
 
 
 
 
 
 
 
110
 
 
 
 
 
 
 
 
 
111
  }
112
 
113
- enviar(mensaje, ctx){
114
- let conversacion = this.conversaciones[$(".tab-label input:checked").val()]
 
 
115
  let tempMensajes = [];
116
  for(let actMsg of conversacion){
117
  tempMensajes.push({role: actMsg.role, content: actMsg.content})
118
  }
119
 
120
- tempMensajes.push({role: "user", content: mensaje});
121
- conversacion.push({role: "user", content: mensaje});
122
- ctx.trigger("precarga:inicio", mensaje);
 
123
  let self = this;
124
 
 
125
  const consume = responseReader => {
 
 
126
  return responseReader.read().then(result => {
 
 
127
  if (result.done) { return; }
128
 
129
-
130
  const chunk = result.value;
131
  let text = new TextDecoder("utf-8").decode(chunk)
132
 
 
133
  let responses = JSON.parse('[' + text.replace(/\}\{/g, '},{') + ']')
 
 
134
  for(let response of responses){
135
  switch(response.comando){
 
136
  case "token":
137
  self.token = response.token;
138
  break;
 
 
 
 
 
139
  case "status":
140
- ctx.trigger("precarga:status", response.status);
141
  break;
 
142
  case "mensaje":
143
- conversacion.push(response.mensaje);
144
- localStorage.setItem("conversaciones", JSON.stringify(self.conversaciones))
145
- ctx.trigger("precarga:mensaje", response.mensaje.content);
146
  break;
 
 
 
 
 
 
147
  default:
148
- console.log("???")
149
  }
150
  }
 
 
151
  return consume(responseReader);
152
  }).catch(err =>{
 
153
  console.log('algo paso', err)
154
  });
155
  }
156
 
157
- // Perform the request and consume response stream
158
  fetch(this.endpointChat, {
159
  method: "POST",
160
  body: JSON.stringify({
161
  messages: tempMensajes,
162
- token: this.token,
163
- config: this.config
164
  }),
 
 
 
 
165
  timeout: 60000,
166
  dataType: "json"
167
  }).then(response => {
168
  if(response.status != 200){
169
- ctx.trigger("precarga:error", response);
170
  console.log("Error: ", response)
171
  return
172
  }
173
- ctx.trigger("precarga:iniciada");
174
  return consume(response.body.getReader());
175
- })
176
- .catch(err =>{
177
  console.log('Solicitud fallida', err)
178
- ctx.trigger("precarga:error", "")
 
179
  });
180
 
181
  }
182
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
183
 
184
  }
 
1
  class ChatGPT{
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ /* Eventos generados
4
 
5
+ **********************/
6
+ /* Eventos escuchados
7
+ - chat:enviar
8
+ - chat:eliminar
 
 
 
 
9
 
10
+ **********************/
 
 
11
 
12
+
13
+ definicion ={
14
+ conversacion: [],
15
+ usedTokens: 0
16
+ };
17
+
18
+ config = {
19
+ temperature: 0.5,
20
+ frequency_penalty: 0.0,
21
+ presence_penalty: 0.0,
22
+ useTool: true,
23
+ assistant: "clasico",
24
+ assistantPrompt: ""
25
  }
26
 
27
+ tokens = {
28
+ month: 0,
29
+ total: 0
30
+ }
31
+
32
+ endpointChat = "/chat";
33
+ challenge = null;
34
+ windowHandlers = {}
35
+
36
+ constructor(secHand){
37
+ // Token JWT de ejecución
38
+ this.secHand = secHand;
 
 
 
 
39
 
40
+ this.obtenerToken();
 
 
 
 
41
 
42
+ this.cargarEventos();
43
 
44
 
45
  }
46
 
47
+ obtenerToken(){
48
+ $.ajax({
49
+ method: "POST",
50
+ url: "/getToken",
51
+ headers: {
52
+ "Autorization": "Bearer " + this.token,
53
+ 'Content-Type': 'application/json',
54
+ },
55
+ data: JSON.stringify({
56
+ fingerprint: this.secHand.fingerprint,
57
+ public_key: this.secHand.publicKey
58
+ }),
59
+ timeout: 5000,
60
+ dataType: "json"
61
+ }).done((data) => {
62
+
63
+ if(data.redirect){
64
+ $("#inserted")[0].showModal()
65
+ $("#inserted").html("<iframe src='"+data.redirect+"'>")
66
+ $("#inserted").on("close", (event) =>{
67
+ document.location.reload()
68
+ })
69
+
70
+ return
71
  }
 
72
 
73
+ this.secHand.sign(data.challenge).then(
74
+ (result) => {
75
+ this.challenge = result
76
+ this.cargarConfigs(false, true);
77
+ })
78
+ this.token = data.token
79
+ }).fail((data => {
80
+ $("#inserted")[0].showModal()
81
+ $("#inserted").html("<iframe src='/login'>")
82
+ $("#inserted").on("close", (event) =>{
83
+ document.location.reload()
84
+ })
85
+ $("#inserted iframe").on("load", (event) =>{
86
+ let location = event.target.contentDocument.location.pathname
87
+ if(location == "/login"){
88
+ $("#inserted iframe")[0].contentWindow.data = {
89
+ fp: this.secHand.fingerprint,
90
+ pk: this.secHand.publicKey
91
+ }
92
+ }
93
+ if(location == "/"){
94
+ $("#inserted").html("")
95
+ $("#inserted")[0].close()
96
+
97
+ }
98
+
99
+
100
+ })
101
+ }))
102
+
103
 
104
  }
105
 
106
+ cargarConfigs(abrirMenu, starting){
107
+ self = this
108
+ $.ajax({
109
+ method: "POST",
110
+ url: "/getConfigs",
111
+ headers: {
112
+ "Autorization": "Bearer " + this.token,
113
+ 'Content-Type': 'application/json',
114
+ },
115
+ data:JSON.stringify({
116
+ challenge: this.challenge,
117
+ fingerprint: this.secHand.fingerprint
118
+ }),
119
+ timeout: 5000,
120
+ dataType: "json"
121
+ }).done((data) => {
122
+ let challenge = data["challenge"];
123
+ delete data["challenge"];
124
+ $("#assistant option[value='"+ data.assistant +"']")[0].selected=true
125
+ this.secHand.sign(challenge).then((result) => {this.challenge = result;})
126
+ this.config = data
127
+
128
+ for(let confEl in data){
129
+ if(typeof(data[confEl]) == "boolean"){
130
+ $("#"+confEl).prop("checked", data[confEl])
131
+ }else{
132
+ $("#"+confEl).val(data[confEl])
133
+ }
134
+ }
135
+ $(".range input").each(recalculateRanges)
136
+ $(".range input").on("input", recalculateRanges)
137
+ $(".item.parent input").on("change", function(){
138
+ self.config[this.id] = this.checked
139
+ if(!this.checked){
140
+ $(".item."+this.id+" input").prop("disabled", true)
141
+ }else{
142
+ $(".item."+this.id+" input").prop("disabled", false)
143
+ }
144
+ })
145
+ $(".range select").on("change", function(){
146
+ self.config[this.id] = this.value
147
+ })
148
+ $("#tokensUsedMonth").text(data.tokens.month)
149
+ $("#tokensUsedTotal").text(data.tokens.total)
150
+
151
+
152
+ if(abrirMenu) $("#menu")[0].showModal()
153
+
154
+
155
+ function recalculateRanges(){
156
+ let value = (Math.round(this.value * 100) / 100).toFixed(2)
157
+ $(this).parent().find("span").text(value);
158
+ self.config[this.id] = parseFloat(value)
159
+ }
160
+ $(".item.parent input").trigger("change")
161
+
162
+ if(starting){this.cargarChats()}
163
+ })
164
+ .fail(() => {
165
+ document.location.href = "/";
166
+ })
167
+ }
168
+
169
+ cargarEventos(){
170
+ $(document).on("chat:enviar", (event, params) => {
171
+ // Al enviar un mensaje, reintentos vuelve a 0
172
+ this.reintentos = 0;
173
+ this.enviar(params.ctx, params.ctx.conversacion);
174
+ });
175
+ $(document).on("chat:salvar", (event, params) => this.salvarChats(params.index, params.conversacion))
176
+ $(document).on("chat:eliminar", (event, params) => this.eliminarChat(params.ctx, params.index))
177
+ $(document).on("mostrar:opciones", () => this.mostrarOpciones())
178
+ $("#nuevoChat").on("click", () => this.crearChat())
179
+ $("#menu .saveButton").on("click", () => this.salvarConfigs())
180
+
181
+ }
182
+
183
+ cargarChats(){
184
+ let conversaciones = {}
185
+ try{conversaciones = JSON.parse(localStorage.getItem("conversaciones"))||{}}catch{}
186
+
187
+ if(!conversaciones || !Object.keys(conversaciones).length){
188
  this.crearChat()
189
+ return
190
  }
191
+ for(let key in conversaciones){
192
+ if(!conversaciones[key]){
193
+ continue
194
+ }
195
+ if(Object.keys(conversaciones[key]).indexOf('conversacion')!=-1){
196
+ this.crearChat(key, conversaciones[key].conversacion)
197
+ }else{
198
+ this.crearChat(key, conversaciones[key])
199
+ }
200
+ }
201
+ localStorage.setItem("conversaciones", JSON.stringify(conversaciones))
202
+
203
+ }
204
+
205
+
206
 
207
+ salvarConfigs(){
208
+ $.ajax({
209
+ method: "POST",
210
+ url: "/setConfigs",
211
+ headers: {
212
+ "Autorization": "Bearer " + this.token,
213
+ 'Content-Type': 'application/json',
214
+ },
215
+ data: JSON.stringify(
216
+ {...this.config,
217
+ fingerprint: this.secHand.fingerprint,
218
+ challenge: this.challenge,
219
+ }),
220
+ timeout: 5000,
221
+ dataType: "json"
222
+ }).done((data) => {
223
+ this.secHand.sign(data.challenge).then((result) => {this.challenge = result})
224
+ this.token = data.token
225
+ this.config.assistantPrompt = data.assistantPrompt
226
+ }).fail(() => {
227
+ document.location.href = "/";
228
+ })
229
  }
230
 
231
+ crearChat(index, conversacion){
232
+ // Se crea el nuevo manejador de ventana
233
+ let uuid = this.generateRandID()
234
+ this.windowHandlers[index||uuid] = new WindowHandler(
235
+ conversacion||[],
236
+ index||uuid,
237
+ this
238
+ );
239
+ }
240
+
241
+ eliminarChat(ctx, index){
242
+ // Elimina el elemento de la lista de conversas y handler de ventanas
243
+ delete this.windowHandlers[index]
244
+ let conversaciones = {}
245
+ try{conversaciones = JSON.parse(localStorage.getItem("conversaciones"))||{}}catch{}
246
+ if(index in conversaciones){delete conversaciones[index]}
247
+ localStorage.setItem("conversaciones", JSON.stringify(conversaciones))
248
 
249
+ // Renumera las etiquetas, y selecciona la primera
250
+ let labels = $(".tab-label")
251
+ $(labels[0]).click();
252
+
253
+ // Si no quedaron chats, crea uno vacio
254
+ if(Object.keys(this.windowHandlers).length==0){
255
+ this.crearChat()
256
+ }
257
  }
258
 
259
+ enviar(ctx, conversacion){
260
+ // Envio de mensaje y manejo de comandos async
261
+
262
+ // Crea un espacio temporal para almacenar los mensajes con el formato correcto
263
  let tempMensajes = [];
264
  for(let actMsg of conversacion){
265
  tempMensajes.push({role: actMsg.role, content: actMsg.content})
266
  }
267
 
268
+ // Se anuncia la precarga
269
+ ctx.respuestaInicio()
270
+
271
+ // Se almacena el contexto this
272
  let self = this;
273
 
274
+ // Consumo de mensajes asincronos
275
  const consume = responseReader => {
276
+
277
+ // Se lee la respuesta
278
  return responseReader.read().then(result => {
279
+
280
+ //si finalizó el mensaje, se termina el proceso
281
  if (result.done) { return; }
282
 
283
+ // Se obtiene y decodifica el segmento
284
  const chunk = result.value;
285
  let text = new TextDecoder("utf-8").decode(chunk)
286
 
287
+ // Se obtienen los mensajes y separan si son varios json juntos
288
  let responses = JSON.parse('[' + text.replace(/\}\{/g, '},{') + ']')
289
+
290
+ // Por cada mensaje conseguido validamos el caso de accion
291
  for(let response of responses){
292
  switch(response.comando){
293
+ // Se carga el nuevo token
294
  case "token":
295
  self.token = response.token;
296
  break;
297
+ // Se carga el nuevo challenge
298
+ case "challenge":
299
+ this.secHand.sign(response.challenge).then((result) => {self.challenge = result})
300
+ break;
301
+ // Status del comportamiento
302
  case "status":
303
+ ctx.respuestaStatus(response.status.mensaje, response.status.modo)
304
  break;
305
+ // Mensaje a mostrar
306
  case "mensaje":
307
+ ctx.respuestaMensaje(response.mensaje)
 
 
308
  break;
309
+ // // Es una función
310
+ // case "function":
311
+ // conversacion.push(response.function);
312
+ // localStorage.setItem("conversaciones", JSON.stringify(self.conversaciones))
313
+ // break;
314
+ // Algo falló
315
  default:
316
+ console.log("???", response)
317
  }
318
  }
319
+
320
+ // Se consume la respuesta para continuar el proceso
321
  return consume(responseReader);
322
  }).catch(err =>{
323
+ // Error
324
  console.log('algo paso', err)
325
  });
326
  }
327
 
328
+ // Se ejecuta el request
329
  fetch(this.endpointChat, {
330
  method: "POST",
331
  body: JSON.stringify({
332
  messages: tempMensajes,
333
+ challenge: this.challenge,
334
+ fingerprint: this.secHand.fingerprint
335
  }),
336
+ headers: {
337
+ "Autorization": "Bearer " + this.token,
338
+ 'Content-Type': 'application/json',
339
+ },
340
  timeout: 60000,
341
  dataType: "json"
342
  }).then(response => {
343
  if(response.status != 200){
344
+ ctx.respuestaError(response)
345
  console.log("Error: ", response)
346
  return
347
  }
 
348
  return consume(response.body.getReader());
349
+ }).catch(err =>{
350
+ // Error
351
  console.log('Solicitud fallida', err)
352
+ ctx.respuestaError(response)
353
+ document.location.href = "/";
354
  });
355
 
356
  }
357
 
358
+ salvarChats(index, conversacion){
359
+ let conversaciones = {}
360
+ try{conversaciones = JSON.parse(localStorage.getItem("conversaciones"))||{}}catch{}
361
+ let temp = {}
362
+ for (let i in conversaciones){
363
+ temp[i]=conversaciones[i]
364
+ }
365
+ temp[index] = conversacion
366
+ localStorage.setItem("conversaciones", JSON.stringify(temp))
367
+ }
368
+
369
+
370
+
371
+ mostrarOpciones(){
372
+ this.cargarConfigs(true);
373
+
374
+ }
375
+
376
+ generateRandID(){
377
+ return btoa((Math.random()*100**8)).replaceAll("=","").split("").reverse().join("");
378
+ }
379
+
380
 
381
  }
static/js/fingerprintv4.js ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * FingerprintJS v4.2.1 - Copyright (c) FingerprintJS, Inc, 2023 (https://fingerprint.com)
3
+ *
4
+ * Licensed under Business Source License 1.1 https://mariadb.com/bsl11/
5
+ * Licensor: FingerprintJS, Inc.
6
+ * Licensed Work: FingerprintJS browser fingerprinting library
7
+ * Additional Use Grant: None
8
+ * Change Date: Four years from first release for the specific version.
9
+ * Change License: MIT, text at https://opensource.org/license/mit/ with the following copyright notice:
10
+ * Copyright 2015-present FingerprintJS, Inc.
11
+ */
12
+ var e=function(){return e=Object.assign||function(e){for(var n,t=1,r=arguments.length;t<r;t++)for(var o in n=arguments[t])Object.prototype.hasOwnProperty.call(n,o)&&(e[o]=n[o]);return e},e.apply(this,arguments)};function n(e,n,t,r){return new(t||(t=Promise))((function(o,i){function a(e){try{u(r.next(e))}catch(n){i(n)}}function c(e){try{u(r.throw(e))}catch(n){i(n)}}function u(e){var n;e.done?o(e.value):(n=e.value,n instanceof t?n:new t((function(e){e(n)}))).then(a,c)}u((r=r.apply(e,n||[])).next())}))}function t(e,n){var t,r,o,i,a={label:0,sent:function(){if(1&o[0])throw o[1];return o[1]},trys:[],ops:[]};return i={next:c(0),throw:c(1),return:c(2)},"function"==typeof Symbol&&(i[Symbol.iterator]=function(){return this}),i;function c(c){return function(u){return function(c){if(t)throw new TypeError("Generator is already executing.");for(;i&&(i=0,c[0]&&(a=0)),a;)try{if(t=1,r&&(o=2&c[0]?r.return:c[0]?r.throw||((o=r.return)&&o.call(r),0):r.next)&&!(o=o.call(r,c[1])).done)return o;switch(r=0,o&&(c=[2&c[0],o.value]),c[0]){case 0:case 1:o=c;break;case 4:return a.label++,{value:c[1],done:!1};case 5:a.label++,r=c[1],c=[0];continue;case 7:c=a.ops.pop(),a.trys.pop();continue;default:if(!(o=a.trys,(o=o.length>0&&o[o.length-1])||6!==c[0]&&2!==c[0])){a=0;continue}if(3===c[0]&&(!o||c[1]>o[0]&&c[1]<o[3])){a.label=c[1];break}if(6===c[0]&&a.label<o[1]){a.label=o[1],o=c;break}if(o&&a.label<o[2]){a.label=o[2],a.ops.push(c);break}o[2]&&a.ops.pop(),a.trys.pop();continue}c=n.call(e,a)}catch(u){c=[6,u],r=0}finally{t=o=0}if(5&c[0])throw c[1];return{value:c[0]?c[1]:void 0,done:!0}}([c,u])}}}function r(e,n,t){if(t||2===arguments.length)for(var r,o=0,i=n.length;o<i;o++)!r&&o in n||(r||(r=Array.prototype.slice.call(n,0,o)),r[o]=n[o]);return e.concat(r||Array.prototype.slice.call(n))}function o(e,n){return new Promise((function(t){return setTimeout(t,e,n)}))}function i(){return o(0)}function a(e){return!!e&&"function"==typeof e.then}function c(e,n){try{var t=e();a(t)?t.then((function(e){return n(!0,e)}),(function(e){return n(!1,e)})):n(!0,t)}catch(r){n(!1,r)}}function u(e,r,i){return void 0===i&&(i=16),n(this,void 0,void 0,(function(){var n,a,c,u;return t(this,(function(t){switch(t.label){case 0:n=Array(e.length),a=Date.now(),c=0,t.label=1;case 1:return c<e.length?(n[c]=r(e[c],c),(u=Date.now())>=a+i?(a=u,[4,o(0)]):[3,3]):[3,4];case 2:t.sent(),t.label=3;case 3:return++c,[3,1];case 4:return[2,n]}}))}))}function l(e){e.then(void 0,(function(){}))}function s(e){return parseInt(e)}function d(e){return parseFloat(e)}function f(e,n){return"number"==typeof e&&isNaN(e)?n:e}function m(e){return e.reduce((function(e,n){return e+(n?1:0)}),0)}function v(e,n){if(void 0===n&&(n=1),Math.abs(n)>=1)return Math.round(e/n)*n;var t=1/n;return Math.round(e*t)/t}function h(e,n){var t=e[0]>>>16,r=65535&e[0],o=e[1]>>>16,i=65535&e[1],a=n[0]>>>16,c=65535&n[0],u=n[1]>>>16,l=0,s=0,d=0,f=0;d+=(f+=i+(65535&n[1]))>>>16,f&=65535,s+=(d+=o+u)>>>16,d&=65535,l+=(s+=r+c)>>>16,s&=65535,l+=t+a,l&=65535,e[0]=l<<16|s,e[1]=d<<16|f}function p(e,n){var t=e[0]>>>16,r=65535&e[0],o=e[1]>>>16,i=65535&e[1],a=n[0]>>>16,c=65535&n[0],u=n[1]>>>16,l=65535&n[1],s=0,d=0,f=0,m=0;f+=(m+=i*l)>>>16,m&=65535,d+=(f+=o*l)>>>16,f&=65535,d+=(f+=i*u)>>>16,f&=65535,s+=(d+=r*l)>>>16,d&=65535,s+=(d+=o*u)>>>16,d&=65535,s+=(d+=i*c)>>>16,d&=65535,s+=t*l+r*u+o*c+i*a,s&=65535,e[0]=s<<16|d,e[1]=f<<16|m}function b(e,n){var t=e[0];32===(n%=64)?(e[0]=e[1],e[1]=t):n<32?(e[0]=t<<n|e[1]>>>32-n,e[1]=e[1]<<n|t>>>32-n):(n-=32,e[0]=e[1]<<n|t>>>32-n,e[1]=t<<n|e[1]>>>32-n)}function y(e,n){0!==(n%=64)&&(n<32?(e[0]=e[1]>>>32-n,e[1]=e[1]<<n):(e[0]=e[1]<<n-32,e[1]=0))}function g(e,n){e[0]^=n[0],e[1]^=n[1]}var w=[4283543511,3981806797],L=[3301882366,444984403];function k(e){var n=[0,e[0]>>>1];g(e,n),p(e,w),n[1]=e[0]>>>1,g(e,n),p(e,L),n[1]=e[0]>>>1,g(e,n)}var V=[2277735313,289559509],S=[1291169091,658871167],W=[0,5],Z=[0,1390208809],x=[0,944331445];function M(e,n){var t=function(e){for(var n=new Uint8Array(e.length),t=0;t<e.length;t++){var r=e.charCodeAt(t);if(r>127)return(new TextEncoder).encode(e);n[t]=r}return n}(e);n=n||0;var r,o=[0,t.length],i=o[1]%16,a=o[1]-i,c=[0,n],u=[0,n],l=[0,0],s=[0,0];for(r=0;r<a;r+=16)l[0]=t[r+4]|t[r+5]<<8|t[r+6]<<16|t[r+7]<<24,l[1]=t[r]|t[r+1]<<8|t[r+2]<<16|t[r+3]<<24,s[0]=t[r+12]|t[r+13]<<8|t[r+14]<<16|t[r+15]<<24,s[1]=t[r+8]|t[r+9]<<8|t[r+10]<<16|t[r+11]<<24,p(l,V),b(l,31),p(l,S),g(c,l),b(c,27),h(c,u),p(c,W),h(c,Z),p(s,S),b(s,33),p(s,V),g(u,s),b(u,31),h(u,c),p(u,W),h(u,x);l[0]=0,l[1]=0,s[0]=0,s[1]=0;var d=[0,0];switch(i){case 15:d[1]=t[r+14],y(d,48),g(s,d);case 14:d[1]=t[r+13],y(d,40),g(s,d);case 13:d[1]=t[r+12],y(d,32),g(s,d);case 12:d[1]=t[r+11],y(d,24),g(s,d);case 11:d[1]=t[r+10],y(d,16),g(s,d);case 10:d[1]=t[r+9],y(d,8),g(s,d);case 9:d[1]=t[r+8],g(s,d),p(s,S),b(s,33),p(s,V),g(u,s);case 8:d[1]=t[r+7],y(d,56),g(l,d);case 7:d[1]=t[r+6],y(d,48),g(l,d);case 6:d[1]=t[r+5],y(d,40),g(l,d);case 5:d[1]=t[r+4],y(d,32),g(l,d);case 4:d[1]=t[r+3],y(d,24),g(l,d);case 3:d[1]=t[r+2],y(d,16),g(l,d);case 2:d[1]=t[r+1],y(d,8),g(l,d);case 1:d[1]=t[r],g(l,d),p(l,V),b(l,31),p(l,S),g(c,l)}return g(c,o),g(u,o),h(c,u),h(u,c),k(c),k(u),h(c,u),h(u,c),("00000000"+(c[0]>>>0).toString(16)).slice(-8)+("00000000"+(c[1]>>>0).toString(16)).slice(-8)+("00000000"+(u[0]>>>0).toString(16)).slice(-8)+("00000000"+(u[1]>>>0).toString(16)).slice(-8)}function R(e){return"function"!=typeof e}function F(e,r,o){var i=Object.keys(e).filter((function(e){return!function(e,n){for(var t=0,r=e.length;t<r;++t)if(e[t]===n)return!0;return!1}(o,e)})),a=u(i,(function(n){return function(e,n){var t=new Promise((function(t){var r=Date.now();c(e.bind(null,n),(function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];var o=Date.now()-r;if(!e[0])return t((function(){return{error:e[1],duration:o}}));var i=e[1];if(R(i))return t((function(){return{value:i,duration:o}}));t((function(){return new Promise((function(e){var n=Date.now();c(i,(function(){for(var t=[],r=0;r<arguments.length;r++)t[r]=arguments[r];var i=o+Date.now()-n;if(!t[0])return e({error:t[1],duration:i});e({value:t[1],duration:i})}))}))}))}))}));return l(t),function(){return t.then((function(e){return e()}))}}(e[n],r)}));return l(a),function(){return n(this,void 0,void 0,(function(){var e,n,r,o;return t(this,(function(t){switch(t.label){case 0:return[4,a];case 1:return[4,u(t.sent(),(function(e){var n=e();return l(n),n}))];case 2:return e=t.sent(),[4,Promise.all(e)];case 3:for(n=t.sent(),r={},o=0;o<i.length;++o)r[i[o]]=n[o];return[2,r]}}))}))}}function G(e,n){var t=function(e){return R(e)?n(e):function(){var t=e();return a(t)?t.then(n):n(t)}};return function(n){var r=e(n);return a(r)?r.then(t):t(r)}}function I(){var e=window,n=navigator;return m(["MSCSSMatrix"in e,"msSetImmediate"in e,"msIndexedDB"in e,"msMaxTouchPoints"in n,"msPointerEnabled"in n])>=4}function Y(){var e=window,n=navigator;return m(["msWriteProfilerMark"in e,"MSStream"in e,"msLaunchUri"in n,"msSaveBlob"in n])>=3&&!I()}function j(){var e=window,n=navigator;return m(["webkitPersistentStorage"in n,"webkitTemporaryStorage"in n,0===n.vendor.indexOf("Google"),"webkitResolveLocalFileSystemURL"in e,"BatteryManager"in e,"webkitMediaStream"in e,"webkitSpeechGrammar"in e])>=5}function X(){var e=window,n=navigator;return m(["ApplePayError"in e,"CSSPrimitiveValue"in e,"Counter"in e,0===n.vendor.indexOf("Apple"),"getStorageUpdates"in n,"WebKitMediaKeys"in e])>=4}function P(){var e=window,n=e.HTMLElement,t=e.Document;return m(["safari"in e,!("ongestureend"in e),!("TouchEvent"in e),!("orientation"in e),n&&!("autocapitalize"in n.prototype),t&&"pointerLockElement"in t.prototype])>=4}function E(){var e,n=window;return e=n.print,!!/^function\s.*?\{\s*\[native code]\s*}$/.test(String(e))&&m(["[object WebPageNamespace]"===String(n.browser),"MicrodataExtractor"in n])>=1}function C(){var e,n,t=window;return m(["buildID"in navigator,"MozAppearance"in(null!==(n=null===(e=document.documentElement)||void 0===e?void 0:e.style)&&void 0!==n?n:{}),"onmozfullscreenchange"in t,"mozInnerScreenX"in t,"CSSMozDocumentRule"in t,"CanvasCaptureMediaStream"in t])>=4}function H(){var e=window,n=navigator,t=e.CSS,r=e.HTMLButtonElement;return m([!("getStorageUpdates"in n),r&&"popover"in r.prototype,"CSSCounterStyleRule"in e,t.supports("font-size-adjust: ex-height 0.5"),t.supports("text-transform: full-width")])>=4}function A(){var e=document;return e.fullscreenElement||e.msFullscreenElement||e.mozFullScreenElement||e.webkitFullscreenElement||null}function N(){var e=j(),n=C(),t=window,r=navigator,o="connection";return e?m([!("SharedWorker"in t),r[o]&&"ontypechange"in r[o],!("sinkId"in new window.Audio)])>=2:!!n&&m(["onorientationchange"in t,"orientation"in t,/android/i.test(navigator.appVersion)])>=2}function J(e,r,i){var a,c,u;return void 0===i&&(i=50),n(this,void 0,void 0,(function(){var n,l;return t(this,(function(t){switch(t.label){case 0:n=document,t.label=1;case 1:return n.body?[3,3]:[4,o(i)];case 2:return t.sent(),[3,1];case 3:l=n.createElement("iframe"),t.label=4;case 4:return t.trys.push([4,,10,11]),[4,new Promise((function(e,t){var o=!1,i=function(){o=!0,e()};l.onload=i,l.onerror=function(e){o=!0,t(e)};var a=l.style;a.setProperty("display","block","important"),a.position="absolute",a.top="0",a.left="0",a.visibility="hidden",r&&"srcdoc"in l?l.srcdoc=r:l.src="about:blank",n.body.appendChild(l);var c=function(){var e,n;o||("complete"===(null===(n=null===(e=l.contentWindow)||void 0===e?void 0:e.document)||void 0===n?void 0:n.readyState)?i():setTimeout(c,10))};c()}))];case 5:t.sent(),t.label=6;case 6:return(null===(c=null===(a=l.contentWindow)||void 0===a?void 0:a.document)||void 0===c?void 0:c.body)?[3,8]:[4,o(i)];case 7:return t.sent(),[3,6];case 8:return[4,e(l,l.contentWindow)];case 9:return[2,t.sent()];case 10:return null===(u=l.parentNode)||void 0===u||u.removeChild(l),[7];case 11:return[2]}}))}))}function T(e){for(var n=function(e){for(var n,t,r="Unexpected syntax '".concat(e,"'"),o=/^\s*([a-z-]*)(.*)$/i.exec(e),i=o[1]||void 0,a={},c=/([.:#][\w-]+|\[.+?\])/gi,u=function(e,n){a[e]=a[e]||[],a[e].push(n)};;){var l=c.exec(o[2]);if(!l)break;var s=l[0];switch(s[0]){case".":u("class",s.slice(1));break;case"#":u("id",s.slice(1));break;case"[":var d=/^\[([\w-]+)([~|^$*]?=("(.*?)"|([\w-]+)))?(\s+[is])?\]$/.exec(s);if(!d)throw new Error(r);u(d[1],null!==(t=null!==(n=d[4])&&void 0!==n?n:d[5])&&void 0!==t?t:"");break;default:throw new Error(r)}}return[i,a]}(e),t=n[0],r=n[1],o=document.createElement(null!=t?t:"div"),i=0,a=Object.keys(r);i<a.length;i++){var c=a[i],u=r[c].join(" ");"style"===c?D(o.style,u):o.setAttribute(c,u)}return o}function D(e,n){for(var t=0,r=n.split(";");t<r.length;t++){var o=r[t],i=/^\s*([\w-]+)\s*:\s*(.+?)(\s*!([\w-]+))?\s*$/.exec(o);if(i){var a=i[1],c=i[2],u=i[4];e.setProperty(a,c,u||"")}}}function _(){return n(this,void 0,void 0,(function(){var e,r,i;return t(this,(function(a){switch(a.label){case 0:return r=new Promise((function(e){var n=document,t="visibilitychange",r=function(){n.hidden||(n.removeEventListener(t,r),e())};n.addEventListener(t,r),r()})).then((function(){return o(500)})),i=function(){return n(this,void 0,void 0,(function(){var e,n,r,o,i,a,c;return t(this,(function(t){switch(t.label){case 0:return e=window,(n=e.OfflineAudioContext||e.webkitOfflineAudioContext)?z()?[2,-1]:[4,B(n)]:[2,-2];case 1:return(r=t.sent())?(o=new n(1,r.length-1+4e4,44100),(i=o.createBufferSource()).buffer=r,i.loop=!0,i.loopStart=(r.length-1)/44100,i.loopEnd=r.length/44100,i.connect(o.destination),i.start(),[4,O(o)]):[2,-3];case 2:return(a=t.sent())?(c=function(e,n){for(var t=void 0,r=!1,o=0;o<n.length;o+=Math.floor(n.length/10))if(0===n[o]);else if(void 0===t)t=n[o];else if(t!==n[o]){r=!0;break}void 0===t?t=e.getChannelData(0)[e.length-1]:r&&(t=function(e){for(var n=1/0,t=-1/0,r=0;r<e.length;r++){var o=e[r];0!==o&&(o<n&&(n=o),o>t&&(t=o))}return(n+t)/2}(n));return t}(r,a.getChannelData(0).subarray(r.length-1)),[2,Math.abs(c)]):[2,-3]}}))}))}().then((function(n){return e=[!0,n]}),(function(n){return e=[!1,n]})),[4,Promise.race([r,i])];case 1:return a.sent(),[2,function(){if(!e)return-3;if(!e[0])throw e[1];return e[1]}]}}))}))}function z(){return X()&&!P()&&!(m(["DOMRectList"in(e=window),"RTCPeerConnectionIceEvent"in e,"SVGGeometryElement"in e,"ontransitioncancel"in e])>=3);var e}function B(e){return n(this,void 0,void 0,(function(){var n,r,o,i;return t(this,(function(t){switch(t.label){case 0:return 3395,n=new e(1,3396,44100),(r=n.createOscillator()).type="square",r.frequency.value=1e3,(o=n.createDynamicsCompressor()).threshold.value=-70,o.knee.value=40,o.ratio.value=12,o.attack.value=0,o.release.value=.25,(i=n.createBiquadFilter()).type="allpass",i.frequency.value=5.239622852977861,i.Q.value=.1,r.connect(o),o.connect(i),i.connect(n.destination),r.start(0),[4,O(n)];case 1:return[2,t.sent()]}}))}))}function O(e){return new Promise((function(n,t){var r=25;e.oncomplete=function(e){return n(e.renderedBuffer)};var o=function(){try{var i=e.startRendering();a(i)&&l(i),"suspended"===e.state&&(document.hidden||r--,r>0?setTimeout(o,200):n(null))}catch(c){t(c)}};o()}))}var U=["monospace","sans-serif","serif"],Q=["sans-serif-thin","ARNO PRO","Agency FB","Arabic Typesetting","Arial Unicode MS","AvantGarde Bk BT","BankGothic Md BT","Batang","Bitstream Vera Sans Mono","Calibri","Century","Century Gothic","Clarendon","EUROSTILE","Franklin Gothic","Futura Bk BT","Futura Md BT","GOTHAM","Gill Sans","HELV","Haettenschweiler","Helvetica Neue","Humanst521 BT","Leelawadee","Letter Gothic","Levenim MT","Lucida Bright","Lucida Sans","Menlo","MS Mincho","MS Outlook","MS Reference Specialty","MS UI Gothic","MT Extra","MYRIAD PRO","Marlett","Meiryo UI","Microsoft Uighur","Minion Pro","Monotype Corsiva","PMingLiU","Pristina","SCRIPTINA","Segoe UI Light","Serifa","SimHei","Small Fonts","Staccato222 BT","TRAJAN PRO","Univers CE 55 Medium","Vrinda","ZWAdobeF"];function K(e){return n(this,void 0,void 0,(function(){var n,r,o,i,a,c,u;return t(this,(function(t){switch(t.label){case 0:return n=!1,i=function(){var e=document.createElement("canvas");return e.width=1,e.height=1,[e,e.getContext("2d")]}(),a=i[0],c=i[1],function(e,n){return!(!n||!e.toDataURL)}(a,c)?[3,1]:(r=o="unsupported",[3,4]);case 1:return n=function(e){return e.rect(0,0,10,10),e.rect(2,2,6,6),!e.isPointInPath(5,5,"evenodd")}(c),e?(r=o="skipped",[3,4]):[3,2];case 2:return[4,q(a,c)];case 3:u=t.sent(),r=u[0],o=u[1],t.label=4;case 4:return[2,{winding:n,geometry:r,text:o}]}}))}))}function q(e,r){return n(this,void 0,void 0,(function(){var n,o,a;return t(this,(function(t){switch(t.label){case 0:return function(e,n){e.width=240,e.height=60,n.textBaseline="alphabetic",n.fillStyle="#f60",n.fillRect(100,1,62,20),n.fillStyle="#069",n.font='11pt "Times New Roman"';var t="Cwm fjordbank gly ".concat(String.fromCharCode(55357,56835));n.fillText(t,2,15),n.fillStyle="rgba(102, 204, 0, 0.2)",n.font="18pt Arial",n.fillText(t,4,45)}(e,r),[4,i()];case 1:return t.sent(),n=$(e),o=$(e),n!==o?[2,["unstable","unstable"]]:(function(e,n){e.width=122,e.height=110,n.globalCompositeOperation="multiply";for(var t=0,r=[["#f2f",40,40],["#2ff",80,40],["#ff2",60,80]];t<r.length;t++){var o=r[t],i=o[0],a=o[1],c=o[2];n.fillStyle=i,n.beginPath(),n.arc(a,c,40,0,2*Math.PI,!0),n.closePath(),n.fill()}n.fillStyle="#f9c",n.arc(60,60,60,0,2*Math.PI,!0),n.arc(60,60,20,0,2*Math.PI,!0),n.fill("evenodd")}(e,r),[4,i()]);case 2:return t.sent(),a=$(e),[2,[n,a]]}}))}))}function $(e){return e.toDataURL()}function ee(){var e=screen,n=function(e){return f(s(e),null)},t=[n(e.width),n(e.height)];return t.sort().reverse(),t}var ne,te;function re(){var e=this;return function(){if(void 0===te){var e=function(){var n=oe();ie(n)?te=setTimeout(e,2500):(ne=n,te=void 0)};e()}}(),function(){return n(e,void 0,void 0,(function(){var e;return t(this,(function(n){switch(n.label){case 0:return ie(e=oe())?ne?[2,r([],ne,!0)]:A()?[4,(t=document,(t.exitFullscreen||t.msExitFullscreen||t.mozCancelFullScreen||t.webkitExitFullscreen).call(t))]:[3,2]:[3,2];case 1:n.sent(),e=oe(),n.label=2;case 2:return ie(e)||(ne=e),[2,e]}var t}))}))}}function oe(){var e=screen;return[f(d(e.availTop),null),f(d(e.width)-d(e.availWidth)-f(d(e.availLeft),0),null),f(d(e.height)-d(e.availHeight)-f(d(e.availTop),0),null),f(d(e.availLeft),null)]}function ie(e){for(var n=0;n<4;++n)if(e[n])return!1;return!0}function ae(e){var r;return n(this,void 0,void 0,(function(){var n,a,c,u,l,s,d;return t(this,(function(t){switch(t.label){case 0:for(n=document,a=n.createElement("div"),c=new Array(e.length),u={},ce(a),d=0;d<e.length;++d)"DIALOG"===(l=T(e[d])).tagName&&l.show(),ce(s=n.createElement("div")),s.appendChild(l),a.appendChild(s),c[d]=l;t.label=1;case 1:return n.body?[3,3]:[4,o(50)];case 2:return t.sent(),[3,1];case 3:return n.body.appendChild(a),[4,i()];case 4:t.sent();try{for(d=0;d<e.length;++d)c[d].offsetParent||(u[e[d]]=!0)}finally{null===(r=a.parentNode)||void 0===r||r.removeChild(a)}return[2,u]}}))}))}function ce(e){e.style.setProperty("visibility","hidden","important"),e.style.setProperty("display","block","important")}function ue(e){return matchMedia("(inverted-colors: ".concat(e,")")).matches}function le(e){return matchMedia("(forced-colors: ".concat(e,")")).matches}function se(e){return matchMedia("(prefers-contrast: ".concat(e,")")).matches}function de(e){return matchMedia("(prefers-reduced-motion: ".concat(e,")")).matches}function fe(e){return matchMedia("(prefers-reduced-transparency: ".concat(e,")")).matches}function me(e){return matchMedia("(dynamic-range: ".concat(e,")")).matches}var ve=Math,he=function(){return 0};var pe={default:[],apple:[{font:"-apple-system-body"}],serif:[{fontFamily:"serif"}],sans:[{fontFamily:"sans-serif"}],mono:[{fontFamily:"monospace"}],min:[{fontSize:"1px"}],system:[{fontFamily:"system-ui"}]};function be(e){if(e instanceof Error){if("InvalidAccessError"===e.name){if(/\bfrom\b.*\binsecure\b/i.test(e.message))return-2;if(/\bdifferent\b.*\borigin\b.*top.level\b.*\bframe\b/i.test(e.message))return-3}if("SecurityError"===e.name&&/\bthird.party iframes?.*\bnot.allowed\b/i.test(e.message))return-3}throw e}var ye=new Set([10752,2849,2884,2885,2886,2928,2929,2930,2931,2932,2960,2961,2962,2963,2964,2965,2966,2967,2968,2978,3024,3042,3088,3089,3106,3107,32773,32777,32777,32823,32824,32936,32937,32938,32939,32968,32969,32970,32971,3317,33170,3333,3379,3386,33901,33902,34016,34024,34076,3408,3410,3411,3412,3413,3414,3415,34467,34816,34817,34818,34819,34877,34921,34930,35660,35661,35724,35738,35739,36003,36004,36005,36347,36348,36349,37440,37441,37443,7936,7937,7938]),ge=new Set([34047,35723,36063,34852,34853,34854,34229,36392,36795,38449]),we=["FRAGMENT_SHADER","VERTEX_SHADER"],Le=["LOW_FLOAT","MEDIUM_FLOAT","HIGH_FLOAT","LOW_INT","MEDIUM_INT","HIGH_INT"];function ke(e){if(e.webgl)return e.webgl.context;var n,t=document.createElement("canvas");t.addEventListener("webglCreateContextError",(function(){return n=void 0}));for(var r=0,o=["webgl","experimental-webgl"];r<o.length;r++){var i=o[r];try{n=t.getContext(i)}catch(a){}if(n)break}return e.webgl={context:n},n}function Ve(e,n,t){var r=e.getShaderPrecisionFormat(e[n],e[t]);return r?[r.rangeMin,r.rangeMax,r.precision]:[]}function Se(e){return Object.keys(e.__proto__).filter(We)}function We(e){return"string"==typeof e&&!e.match(/[^A-Z0-9_x]/)}function Ze(){return C()}function xe(e){return"function"==typeof e.getParameter}var Me={fonts:function(){var e=this;return J((function(r,o){var a=o.document;return n(e,void 0,void 0,(function(){var e,n,r,o,c,u,l,s,d,f,m;return t(this,(function(t){switch(t.label){case 0:return(e=a.body).style.fontSize="48px",(n=a.createElement("div")).style.setProperty("visibility","hidden","important"),r={},o={},c=function(e){var t=a.createElement("span"),r=t.style;return r.position="absolute",r.top="0",r.left="0",r.fontFamily=e,t.textContent="mmMwWLliI0O&1",n.appendChild(t),t},u=function(e,n){return c("'".concat(e,"',").concat(n))},l=function(){for(var e={},n=function(n){e[n]=U.map((function(e){return u(n,e)}))},t=0,r=Q;t<r.length;t++){n(r[t])}return e},s=function(e){return U.some((function(n,t){return e[t].offsetWidth!==r[n]||e[t].offsetHeight!==o[n]}))},d=function(){return U.map(c)}(),f=l(),e.appendChild(n),[4,i()];case 1:for(t.sent(),m=0;m<U.length;m++)r[U[m]]=d[m].offsetWidth,o[U[m]]=d[m].offsetHeight;return[2,Q.filter((function(e){return s(f[e])}))]}}))}))}))},domBlockers:function(e){var r=(void 0===e?{}:e).debug;return n(this,void 0,void 0,(function(){var e,n,o,i,a;return t(this,(function(t){switch(t.label){case 0:return X()||N()?(c=atob,e={abpIndo:["#Iklan-Melayang","#Kolom-Iklan-728","#SidebarIklan-wrapper",'[title="ALIENBOLA" i]',c("I0JveC1CYW5uZXItYWRz")],abpvn:[".quangcao","#mobileCatfish",c("LmNsb3NlLWFkcw=="),'[id^="bn_bottom_fixed_"]',"#pmadv"],adBlockFinland:[".mainostila",c("LnNwb25zb3JpdA=="),".ylamainos",c("YVtocmVmKj0iL2NsaWNrdGhyZ2guYXNwPyJd"),c("YVtocmVmXj0iaHR0cHM6Ly9hcHAucmVhZHBlYWsuY29tL2FkcyJd")],adBlockPersian:["#navbar_notice_50",".kadr",'TABLE[width="140px"]',"#divAgahi",c("YVtocmVmXj0iaHR0cDovL2cxLnYuZndtcm0ubmV0L2FkLyJd")],adBlockWarningRemoval:["#adblock-honeypot",".adblocker-root",".wp_adblock_detect",c("LmhlYWRlci1ibG9ja2VkLWFk"),c("I2FkX2Jsb2NrZXI=")],adGuardAnnoyances:[".hs-sosyal","#cookieconsentdiv",'div[class^="app_gdpr"]',".as-oil",'[data-cypress="soft-push-notification-modal"]'],adGuardBase:[".BetterJsPopOverlay",c("I2FkXzMwMFgyNTA="),c("I2Jhbm5lcmZsb2F0MjI="),c("I2NhbXBhaWduLWJhbm5lcg=="),c("I0FkLUNvbnRlbnQ=")],adGuardChinese:[c("LlppX2FkX2FfSA=="),c("YVtocmVmKj0iLmh0aGJldDM0LmNvbSJd"),"#widget-quan",c("YVtocmVmKj0iLzg0OTkyMDIwLnh5eiJd"),c("YVtocmVmKj0iLjE5NTZobC5jb20vIl0=")],adGuardFrench:["#pavePub",c("LmFkLWRlc2t0b3AtcmVjdGFuZ2xl"),".mobile_adhesion",".widgetadv",c("LmFkc19iYW4=")],adGuardGerman:['aside[data-portal-id="leaderboard"]'],adGuardJapanese:["#kauli_yad_1",c("YVtocmVmXj0iaHR0cDovL2FkMi50cmFmZmljZ2F0ZS5uZXQvIl0="),c("Ll9wb3BJbl9pbmZpbml0ZV9hZA=="),c("LmFkZ29vZ2xl"),c("Ll9faXNib29zdFJldHVybkFk")],adGuardMobile:[c("YW1wLWF1dG8tYWRz"),c("LmFtcF9hZA=="),'amp-embed[type="24smi"]',"#mgid_iframe1",c("I2FkX2ludmlld19hcmVh")],adGuardRussian:[c("YVtocmVmXj0iaHR0cHM6Ly9hZC5sZXRtZWFkcy5jb20vIl0="),c("LnJlY2xhbWE="),'div[id^="smi2adblock"]',c("ZGl2W2lkXj0iQWRGb3hfYmFubmVyXyJd"),"#psyduckpockeball"],adGuardSocial:[c("YVtocmVmXj0iLy93d3cuc3R1bWJsZXVwb24uY29tL3N1Ym1pdD91cmw9Il0="),c("YVtocmVmXj0iLy90ZWxlZ3JhbS5tZS9zaGFyZS91cmw/Il0="),".etsy-tweet","#inlineShare",".popup-social"],adGuardSpanishPortuguese:["#barraPublicidade","#Publicidade","#publiEspecial","#queTooltip",".cnt-publi"],adGuardTrackingProtection:["#qoo-counter",c("YVtocmVmXj0iaHR0cDovL2NsaWNrLmhvdGxvZy5ydS8iXQ=="),c("YVtocmVmXj0iaHR0cDovL2hpdGNvdW50ZXIucnUvdG9wL3N0YXQucGhwIl0="),c("YVtocmVmXj0iaHR0cDovL3RvcC5tYWlsLnJ1L2p1bXAiXQ=="),"#top100counter"],adGuardTurkish:["#backkapat",c("I3Jla2xhbWk="),c("YVtocmVmXj0iaHR0cDovL2Fkc2Vydi5vbnRlay5jb20udHIvIl0="),c("YVtocmVmXj0iaHR0cDovL2l6bGVuemkuY29tL2NhbXBhaWduLyJd"),c("YVtocmVmXj0iaHR0cDovL3d3dy5pbnN0YWxsYWRzLm5ldC8iXQ==")],bulgarian:[c("dGQjZnJlZW5ldF90YWJsZV9hZHM="),"#ea_intext_div",".lapni-pop-over","#xenium_hot_offers"],easyList:[".yb-floorad",c("LndpZGdldF9wb19hZHNfd2lkZ2V0"),c("LnRyYWZmaWNqdW5reS1hZA=="),".textad_headline",c("LnNwb25zb3JlZC10ZXh0LWxpbmtz")],easyListChina:[c("LmFwcGd1aWRlLXdyYXBbb25jbGljayo9ImJjZWJvcy5jb20iXQ=="),c("LmZyb250cGFnZUFkdk0="),"#taotaole","#aafoot.top_box",".cfa_popup"],easyListCookie:[".ezmob-footer",".cc-CookieWarning","[data-cookie-number]",c("LmF3LWNvb2tpZS1iYW5uZXI="),".sygnal24-gdpr-modal-wrap"],easyListCzechSlovak:["#onlajny-stickers",c("I3Jla2xhbW5pLWJveA=="),c("LnJla2xhbWEtbWVnYWJvYXJk"),".sklik",c("W2lkXj0ic2tsaWtSZWtsYW1hIl0=")],easyListDutch:[c("I2FkdmVydGVudGll"),c("I3ZpcEFkbWFya3RCYW5uZXJCbG9jaw=="),".adstekst",c("YVtocmVmXj0iaHR0cHM6Ly94bHR1YmUubmwvY2xpY2svIl0="),"#semilo-lrectangle"],easyListGermany:["#SSpotIMPopSlider",c("LnNwb25zb3JsaW5rZ3J1ZW4="),c("I3dlcmJ1bmdza3k="),c("I3Jla2xhbWUtcmVjaHRzLW1pdHRl"),c("YVtocmVmXj0iaHR0cHM6Ly9iZDc0Mi5jb20vIl0=")],easyListItaly:[c("LmJveF9hZHZfYW5udW5jaQ=="),".sb-box-pubbliredazionale",c("YVtocmVmXj0iaHR0cDovL2FmZmlsaWF6aW9uaWFkcy5zbmFpLml0LyJd"),c("YVtocmVmXj0iaHR0cHM6Ly9hZHNlcnZlci5odG1sLml0LyJd"),c("YVtocmVmXj0iaHR0cHM6Ly9hZmZpbGlhemlvbmlhZHMuc25haS5pdC8iXQ==")],easyListLithuania:[c("LnJla2xhbW9zX3RhcnBhcw=="),c("LnJla2xhbW9zX251b3JvZG9z"),c("aW1nW2FsdD0iUmVrbGFtaW5pcyBza3lkZWxpcyJd"),c("aW1nW2FsdD0iRGVkaWt1b3RpLmx0IHNlcnZlcmlhaSJd"),c("aW1nW2FsdD0iSG9zdGluZ2FzIFNlcnZlcmlhaS5sdCJd")],estonian:[c("QVtocmVmKj0iaHR0cDovL3BheTRyZXN1bHRzMjQuZXUiXQ==")],fanboyAnnoyances:["#ac-lre-player",".navigate-to-top","#subscribe_popup",".newsletter_holder","#back-top"],fanboyAntiFacebook:[".util-bar-module-firefly-visible"],fanboyEnhancedTrackers:[".open.pushModal","#issuem-leaky-paywall-articles-zero-remaining-nag","#sovrn_container",'div[class$="-hide"][zoompage-fontsize][style="display: block;"]',".BlockNag__Card"],fanboySocial:["#FollowUs","#meteored_share","#social_follow",".article-sharer",".community__social-desc"],frellwitSwedish:[c("YVtocmVmKj0iY2FzaW5vcHJvLnNlIl1bdGFyZ2V0PSJfYmxhbmsiXQ=="),c("YVtocmVmKj0iZG9rdG9yLXNlLm9uZWxpbmsubWUiXQ=="),"article.category-samarbete",c("ZGl2LmhvbGlkQWRz"),"ul.adsmodern"],greekAdBlock:[c("QVtocmVmKj0iYWRtYW4ub3RlbmV0LmdyL2NsaWNrPyJd"),c("QVtocmVmKj0iaHR0cDovL2F4aWFiYW5uZXJzLmV4b2R1cy5nci8iXQ=="),c("QVtocmVmKj0iaHR0cDovL2ludGVyYWN0aXZlLmZvcnRobmV0LmdyL2NsaWNrPyJd"),"DIV.agores300","TABLE.advright"],hungarian:["#cemp_doboz",".optimonk-iframe-container",c("LmFkX19tYWlu"),c("W2NsYXNzKj0iR29vZ2xlQWRzIl0="),"#hirdetesek_box"],iDontCareAboutCookies:['.alert-info[data-block-track*="CookieNotice"]',".ModuleTemplateCookieIndicator",".o--cookies--container","#cookies-policy-sticky","#stickyCookieBar"],icelandicAbp:[c("QVtocmVmXj0iL2ZyYW1ld29yay9yZXNvdXJjZXMvZm9ybXMvYWRzLmFzcHgiXQ==")],latvian:[c("YVtocmVmPSJodHRwOi8vd3d3LnNhbGlkemluaS5sdi8iXVtzdHlsZT0iZGlzcGxheTogYmxvY2s7IHdpZHRoOiAxMjBweDsgaGVpZ2h0OiA0MHB4OyBvdmVyZmxvdzogaGlkZGVuOyBwb3NpdGlvbjogcmVsYXRpdmU7Il0="),c("YVtocmVmPSJodHRwOi8vd3d3LnNhbGlkemluaS5sdi8iXVtzdHlsZT0iZGlzcGxheTogYmxvY2s7IHdpZHRoOiA4OHB4OyBoZWlnaHQ6IDMxcHg7IG92ZXJmbG93OiBoaWRkZW47IHBvc2l0aW9uOiByZWxhdGl2ZTsiXQ==")],listKr:[c("YVtocmVmKj0iLy9hZC5wbGFuYnBsdXMuY28ua3IvIl0="),c("I2xpdmVyZUFkV3JhcHBlcg=="),c("YVtocmVmKj0iLy9hZHYuaW1hZHJlcC5jby5rci8iXQ=="),c("aW5zLmZhc3R2aWV3LWFk"),".revenue_unit_item.dable"],listeAr:[c("LmdlbWluaUxCMUFk"),".right-and-left-sponsers",c("YVtocmVmKj0iLmFmbGFtLmluZm8iXQ=="),c("YVtocmVmKj0iYm9vcmFxLm9yZyJd"),c("YVtocmVmKj0iZHViaXp6bGUuY29tL2FyLz91dG1fc291cmNlPSJd")],listeFr:[c("YVtocmVmXj0iaHR0cDovL3Byb21vLnZhZG9yLmNvbS8iXQ=="),c("I2FkY29udGFpbmVyX3JlY2hlcmNoZQ=="),c("YVtocmVmKj0id2Vib3JhbWEuZnIvZmNnaS1iaW4vIl0="),".site-pub-interstitiel",'div[id^="crt-"][data-criteo-id]'],officialPolish:["#ceneo-placeholder-ceneo-12",c("W2hyZWZePSJodHRwczovL2FmZi5zZW5kaHViLnBsLyJd"),c("YVtocmVmXj0iaHR0cDovL2Fkdm1hbmFnZXIudGVjaGZ1bi5wbC9yZWRpcmVjdC8iXQ=="),c("YVtocmVmXj0iaHR0cDovL3d3dy50cml6ZXIucGwvP3V0bV9zb3VyY2UiXQ=="),c("ZGl2I3NrYXBpZWNfYWQ=")],ro:[c("YVtocmVmXj0iLy9hZmZ0cmsuYWx0ZXgucm8vQ291bnRlci9DbGljayJd"),c("YVtocmVmXj0iaHR0cHM6Ly9ibGFja2ZyaWRheXNhbGVzLnJvL3Ryay9zaG9wLyJd"),c("YVtocmVmXj0iaHR0cHM6Ly9ldmVudC4ycGVyZm9ybWFudC5jb20vZXZlbnRzL2NsaWNrIl0="),c("YVtocmVmXj0iaHR0cHM6Ly9sLnByb2ZpdHNoYXJlLnJvLyJd"),'a[href^="/url/"]'],ruAd:[c("YVtocmVmKj0iLy9mZWJyYXJlLnJ1LyJd"),c("YVtocmVmKj0iLy91dGltZy5ydS8iXQ=="),c("YVtocmVmKj0iOi8vY2hpa2lkaWtpLnJ1Il0="),"#pgeldiz",".yandex-rtb-block"],thaiAds:["a[href*=macau-uta-popup]",c("I2Fkcy1nb29nbGUtbWlkZGxlX3JlY3RhbmdsZS1ncm91cA=="),c("LmFkczMwMHM="),".bumq",".img-kosana"],webAnnoyancesUltralist:["#mod-social-share-2","#social-tools",c("LmN0cGwtZnVsbGJhbm5lcg=="),".zergnet-recommend",".yt.btn-link.btn-md.btn"]},n=Object.keys(e),[4,ae((a=[]).concat.apply(a,n.map((function(n){return e[n]}))))]):[2,void 0];case 1:return o=t.sent(),r&&function(e,n){for(var t="DOM blockers debug:\n```",r=0,o=Object.keys(e);r<o.length;r++){var i=o[r];t+="\n".concat(i,":");for(var a=0,c=e[i];a<c.length;a++){var u=c[a];t+="\n ".concat(n[u]?"🚫":"➡️"," ").concat(u)}}console.log("".concat(t,"\n```"))}(e,o),(i=n.filter((function(n){var t=e[n];return m(t.map((function(e){return o[e]})))>.6*t.length}))).sort(),[2,i]}var c}))}))},fontPreferences:function(){return function(e,n){void 0===n&&(n=4e3);return J((function(t,o){var i=o.document,a=i.body,c=a.style;c.width="".concat(n,"px"),c.webkitTextSizeAdjust=c.textSizeAdjust="none",j()?a.style.zoom="".concat(1/o.devicePixelRatio):X()&&(a.style.zoom="reset");var u=i.createElement("div");return u.textContent=r([],Array(n/20<<0),!0).map((function(){return"word"})).join(" "),a.appendChild(u),e(i,a)}),'<!doctype html><html><head><meta name="viewport" content="width=device-width, initial-scale=1">')}((function(e,n){for(var t={},r={},o=0,i=Object.keys(pe);o<i.length;o++){var a=i[o],c=pe[a],u=c[0],l=void 0===u?{}:u,s=c[1],d=void 0===s?"mmMwWLliI0fiflO&1":s,f=e.createElement("span");f.textContent=d,f.style.whiteSpace="nowrap";for(var m=0,v=Object.keys(l);m<v.length;m++){var h=v[m],p=l[h];void 0!==p&&(f.style[h]=p)}t[a]=f,n.append(e.createElement("br"),f)}for(var b=0,y=Object.keys(pe);b<y.length;b++){r[a=y[b]]=t[a].getBoundingClientRect().width}return r}))},audio:function(){return n(this,void 0,void 0,(function(){var e;return t(this,(function(n){switch(n.label){case 0:return[4,_()];case 1:return e=n.sent(),[2,function(){return function(e,n){if(0===e)return e;var t=Math.floor(Math.log10(Math.abs(e)))-Math.floor(n)+1,r=Math.pow(10,-t)*(10*n%10||1);return Math.round(e*r)/r}(e(),6.2)}]}}))}))},screenFrame:function(){var e=this;if(X()&&H()&&E())return function(){return Promise.resolve(void 0)};var r=re();return function(){return n(e,void 0,void 0,(function(){var e,n;return t(this,(function(t){switch(t.label){case 0:return[4,r()];case 1:return e=t.sent(),[2,[(n=function(e){return null===e?null:v(e,10)})(e[0]),n(e[1]),n(e[2]),n(e[3])]]}}))}))}},canvas:function(){return K(X()&&H()&&E())},osCpu:function(){return navigator.oscpu},languages:function(){var e,n=navigator,t=[],r=n.language||n.userLanguage||n.browserLanguage||n.systemLanguage;if(void 0!==r&&t.push([r]),Array.isArray(n.languages))j()&&m([!("MediaSettingsRange"in(e=window)),"RTCEncodedAudioFrame"in e,""+e.Intl=="[object Intl]",""+e.Reflect=="[object Reflect]"])>=3||t.push(n.languages);else if("string"==typeof n.languages){var o=n.languages;o&&t.push(o.split(","))}return t},colorDepth:function(){return window.screen.colorDepth},deviceMemory:function(){return f(d(navigator.deviceMemory),void 0)},screenResolution:function(){if(!(X()&&H()&&E()))return ee()},hardwareConcurrency:function(){return f(s(navigator.hardwareConcurrency),void 0)},timezone:function(){var e,n=null===(e=window.Intl)||void 0===e?void 0:e.DateTimeFormat;if(n){var t=(new n).resolvedOptions().timeZone;if(t)return t}var r,o=(r=(new Date).getFullYear(),-Math.max(d(new Date(r,0,1).getTimezoneOffset()),d(new Date(r,6,1).getTimezoneOffset())));return"UTC".concat(o>=0?"+":"").concat(Math.abs(o))},sessionStorage:function(){try{return!!window.sessionStorage}catch(e){return!0}},localStorage:function(){try{return!!window.localStorage}catch(e){return!0}},indexedDB:function(){if(!I()&&!Y())try{return!!window.indexedDB}catch(e){return!0}},openDatabase:function(){return!!window.openDatabase},cpuClass:function(){return navigator.cpuClass},platform:function(){var e=navigator.platform;return"MacIntel"===e&&X()&&!P()?function(){if("iPad"===navigator.platform)return!0;var e=screen,n=e.width/e.height;return m(["MediaSource"in window,!!Element.prototype.webkitRequestFullscreen,n>.65&&n<1.53])>=2}()?"iPad":"iPhone":e},plugins:function(){var e=navigator.plugins;if(e){for(var n=[],t=0;t<e.length;++t){var r=e[t];if(r){for(var o=[],i=0;i<r.length;++i){var a=r[i];o.push({type:a.type,suffixes:a.suffixes})}n.push({name:r.name,description:r.description,mimeTypes:o})}}return n}},touchSupport:function(){var e,n=navigator,t=0;void 0!==n.maxTouchPoints?t=s(n.maxTouchPoints):void 0!==n.msMaxTouchPoints&&(t=n.msMaxTouchPoints);try{document.createEvent("TouchEvent"),e=!0}catch(r){e=!1}return{maxTouchPoints:t,touchEvent:e,touchStart:"ontouchstart"in window}},vendor:function(){return navigator.vendor||""},vendorFlavors:function(){for(var e=[],n=0,t=["chrome","safari","__crWeb","__gCrWeb","yandex","__yb","__ybro","__firefox__","__edgeTrackingPreventionStatistics","webkit","oprt","samsungAr","ucweb","UCShellJava","puffinDevice"];n<t.length;n++){var r=t[n],o=window[r];o&&"object"==typeof o&&e.push(r)}return e.sort()},cookiesEnabled:function(){var e=document;try{e.cookie="cookietest=1; SameSite=Strict;";var n=-1!==e.cookie.indexOf("cookietest=");return e.cookie="cookietest=1; SameSite=Strict; expires=Thu, 01-Jan-1970 00:00:01 GMT",n}catch(t){return!1}},colorGamut:function(){for(var e=0,n=["rec2020","p3","srgb"];e<n.length;e++){var t=n[e];if(matchMedia("(color-gamut: ".concat(t,")")).matches)return t}},invertedColors:function(){return!!ue("inverted")||!ue("none")&&void 0},forcedColors:function(){return!!le("active")||!le("none")&&void 0},monochrome:function(){if(matchMedia("(min-monochrome: 0)").matches){for(var e=0;e<=100;++e)if(matchMedia("(max-monochrome: ".concat(e,")")).matches)return e;throw new Error("Too high value")}},contrast:function(){return se("no-preference")?0:se("high")||se("more")?1:se("low")||se("less")?-1:se("forced")?10:void 0},reducedMotion:function(){return!!de("reduce")||!de("no-preference")&&void 0},reducedTransparency:function(){return!!fe("reduce")||!fe("no-preference")&&void 0},hdr:function(){return!!me("high")||!me("standard")&&void 0},math:function(){var e,n=ve.acos||he,t=ve.acosh||he,r=ve.asin||he,o=ve.asinh||he,i=ve.atanh||he,a=ve.atan||he,c=ve.sin||he,u=ve.sinh||he,l=ve.cos||he,s=ve.cosh||he,d=ve.tan||he,f=ve.tanh||he,m=ve.exp||he,v=ve.expm1||he,h=ve.log1p||he;return{acos:n(.12312423423423424),acosh:t(1e308),acoshPf:(e=1e154,ve.log(e+ve.sqrt(e*e-1))),asin:r(.12312423423423424),asinh:o(1),asinhPf:function(e){return ve.log(e+ve.sqrt(e*e+1))}(1),atanh:i(.5),atanhPf:function(e){return ve.log((1+e)/(1-e))/2}(.5),atan:a(.5),sin:c(-1e300),sinh:u(1),sinhPf:function(e){return ve.exp(e)-1/ve.exp(e)/2}(1),cos:l(10.000000000123),cosh:s(1),coshPf:function(e){return(ve.exp(e)+1/ve.exp(e))/2}(1),tan:d(-1e300),tanh:f(1),tanhPf:function(e){return(ve.exp(2*e)-1)/(ve.exp(2*e)+1)}(1),exp:m(1),expm1:v(1),expm1Pf:function(e){return ve.exp(e)-1}(1),log1p:h(10),log1pPf:function(e){return ve.log(1+e)}(10),powPI:function(e){return ve.pow(ve.PI,e)}(-100)}},pdfViewerEnabled:function(){return navigator.pdfViewerEnabled},architecture:function(){var e=new Float32Array(1),n=new Uint8Array(e.buffer);return e[0]=1/0,e[0]=e[0]-e[0],n[3]},applePay:function(){var e=window.ApplePaySession;if("function"!=typeof(null==e?void 0:e.canMakePayments))return-1;try{return e.canMakePayments()?1:0}catch(n){return be(n)}},privateClickMeasurement:function(){var e,n=document.createElement("a"),t=null!==(e=n.attributionSourceId)&&void 0!==e?e:n.attributionsourceid;return void 0===t?void 0:String(t)},webGlBasics:function(e){var n,t,r,o,i,a,c=ke(e.cache);if(!c)return-1;if(!xe(c))return-2;var u=Ze()?null:c.getExtension("WEBGL_debug_renderer_info");return{version:(null===(n=c.getParameter(c.VERSION))||void 0===n?void 0:n.toString())||"",vendor:(null===(t=c.getParameter(c.VENDOR))||void 0===t?void 0:t.toString())||"",vendorUnmasked:u?null===(r=c.getParameter(u.UNMASKED_VENDOR_WEBGL))||void 0===r?void 0:r.toString():"",renderer:(null===(o=c.getParameter(c.RENDERER))||void 0===o?void 0:o.toString())||"",rendererUnmasked:u?null===(i=c.getParameter(u.UNMASKED_RENDERER_WEBGL))||void 0===i?void 0:i.toString():"",shadingLanguageVersion:(null===(a=c.getParameter(c.SHADING_LANGUAGE_VERSION))||void 0===a?void 0:a.toString())||""}},webGlExtensions:function(e){var n=ke(e.cache);if(!n)return-1;if(!xe(n))return-2;var t=n.getSupportedExtensions(),r=n.getContextAttributes(),o=[],i=[],a=[],c=[];if(r)for(var u=0,l=Object.keys(r);u<l.length;u++){var s=l[u];o.push("".concat(s,"=").concat(r[s]))}for(var d=0,f=Se(n);d<f.length;d++){var m=n[w=f[d]];i.push("".concat(w,"=").concat(m).concat(ye.has(m)?"=".concat(n.getParameter(m)):""))}if(t)for(var v=0,h=t;v<h.length;v++){var p=h[v];if("WEBGL_debug_renderer_info"!==p||!Ze()){var b=n.getExtension(p);if(b)for(var y=0,g=Se(b);y<g.length;y++){var w;m=b[w=g[y]];a.push("".concat(w,"=").concat(m).concat(ge.has(m)?"=".concat(n.getParameter(m)):""))}}}for(var L=0,k=we;L<k.length;L++)for(var V=k[L],S=0,W=Le;S<W.length;S++){var Z=W[S],x=Ve(n,V,Z);c.push("".concat(V,".").concat(Z,"=").concat(x.join(",")))}return a.sort(),i.sort(),{contextAttributes:o,parameters:i,shaderPrecisions:c,extensions:t,extensionParameters:a}}};function Re(e){var n=function(e){if(N())return.4;if(X())return!P()||H()&&E()?.3:.5;var n="value"in e.platform?e.platform.value:"";if(/^Win/.test(n))return.6;if(/^Mac/.test(n))return.5;return.7}(e),t=function(e){return v(.99+.01*e,1e-4)}(n);return{score:n,comment:"$ if upgrade to Pro: https://fpjs.dev/pro".replace(/\$/g,"".concat(t))}}function Fe(n){return JSON.stringify(n,(function(n,t){return t instanceof Error?e({name:(r=t).name,message:r.message,stack:null===(o=r.stack)||void 0===o?void 0:o.split("\n")},r):t;var r,o}),2)}function Ge(e){return M(function(e){for(var n="",t=0,r=Object.keys(e).sort();t<r.length;t++){var o=r[t],i=e[o],a="error"in i?"error":JSON.stringify(i.value);n+="".concat(n?"|":"").concat(o.replace(/([:|\\])/g,"\\$1"),":").concat(a)}return n}(e))}function Ie(e){return void 0===e&&(e=50),function(e,n){void 0===n&&(n=1/0);var t=window.requestIdleCallback;return t?new Promise((function(e){return t.call(window,(function(){return e()}),{timeout:n})})):o(Math.min(e,n))}(e,2*e)}function Ye(e,r){var o=Date.now();return{get:function(i){return n(this,void 0,void 0,(function(){var n,a,c;return t(this,(function(t){switch(t.label){case 0:return n=Date.now(),[4,e()];case 1:return a=t.sent(),c=function(e){var n;return{get visitorId(){return void 0===n&&(n=Ge(this.components)),n},set visitorId(e){n=e},confidence:Re(e),components:e,version:"4.2.1"}}(a),(r||(null==i?void 0:i.debug))&&console.log("Copy the text below to get the debug data:\n\n```\nversion: ".concat(c.version,"\nuserAgent: ").concat(navigator.userAgent,"\ntimeBetweenLoadAndGet: ").concat(n-o,"\nvisitorId: ").concat(c.visitorId,"\ncomponents: ").concat(Fe(a),"\n```")),[2,c]}}))}))}}}function je(e){return void 0===e&&(e={}),n(this,void 0,void 0,(function(){var n,r,o;return t(this,(function(t){switch(t.label){case 0:return e.monitoring,n=e.delayFallback,r=e.debug,[4,Ie(n)];case 1:return t.sent(),o=function(e){return F(Me,e,[])}({cache:{},debug:r}),[2,Ye(o,r)]}}))}))}var Xe={load:je,hashComponents:Ge,componentsToDebugString:Fe},Pe=M;export{Fe as componentsToDebugString,Xe as default,z as doesBrowserSuspendAudioContext,A as getFullscreenElement,_ as getUnstableAudioFingerprint,K as getUnstableCanvasFingerprint,re as getUnstableScreenFrame,ee as getUnstableScreenResolution,ke as getWebGLContext,be as handleApplePayError,Ge as hashComponents,N as isAndroid,j as isChromium,P as isDesktopWebKit,Y as isEdgeHTML,C as isGecko,I as isTrident,X as isWebKit,je as load,F as loadSources,Pe as murmurX64Hash128,Ie as prepareForSources,O as renderAudio,Me as sources,G as transformSource,J as withIframe};
static/js/windowHandler.js CHANGED
@@ -1,159 +1,137 @@
1
  class WindowHandler{
2
- constructor(conversacion, index){
 
 
 
 
 
 
 
 
 
 
 
3
  this.index = index
4
- this.template = $('<div class="message"><div><p></p></div></div>');
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  this.active = false;
6
- this.mensaje = "";
 
7
  this.interacted = false
8
 
 
 
 
9
  this.cargarChat(conversacion)
 
 
10
 
 
 
 
 
 
 
 
11
 
12
- this.ctx.on("chat:enviar", (event, params) => this.recalcularTextarea());
13
- this.ctx.on("chat:eliminar:pre", () => this.eliminarChat());
 
 
14
 
15
- this.ctx.find(".input-text").keypress((event) => {
16
- if (!event.shiftKey && event.keyCode === 13) {
17
- this.manejadorEnviar();
18
- }});
19
- this.ctx.find(".input-send").click(() => this.manejadorEnviar());
20
- this.ctx.find(".input-text").keypress(() => this.recalcularTextarea());
21
- this.ctx.find(".input-text").keyup(() => this.recalcularTextarea());
22
- this.ctx.find(".input-text").keydown(() => this.recalcularTextarea());
23
- this.ctx.find(".input-delete").click(()=> this.ctx.trigger("chat:eliminar:pre"))
24
 
25
- this.ctx.on("precarga:inicio", (event, params) => {
26
- this.respuestaInicio(params)
27
- });
28
- this.ctx.on("precarga:iniciada", (event, params) => {
29
- this.respuestaIniciada()
30
- });
31
- this.ctx.on("precarga:status", (event, params) => {
32
- this.respuestaStatus(...Object.values(params))
33
- });
34
- this.ctx.on("precarga:mensaje", (event, params) => {
35
- this.respuestaMensaje(params)}
36
- );
37
- this.ctx.on("precarga:error", (event, params) => {
38
- this.respuestaError(params)
39
- });
40
-
41
- }
42
-
43
- cargarChat(conversacion){
44
- let tempTabSel = $(".tab-label-template").clone();
45
- tempTabSel.removeClass("tab-label-template")
46
- tempTabSel.addClass("tab-label")
47
- tempTabSel.find("div").text("Tab "+this.index);
48
- tempTabSel.find("input").val(this.index);
49
- if(this.index==0){
50
- tempTabSel.find("input").prop("checked", true);
51
- }
52
- $(".tabs").append(tempTabSel)
53
-
54
- let tempTab = $(".tab-template").clone();
55
- tempTab.removeClass("tab-template")
56
- tempTab.addClass("tab")
57
- $(".wrapper").append(tempTab)
58
  this.ctx = tempTab
59
  this.chatbox = this.ctx.find(".chat");
 
 
60
 
61
-
62
- for(let mensaje of conversacion){
63
- if(mensaje.role!="system"){
64
- let clone = this.template.clone();
65
- let texto = mensaje.content;
66
- if(mensaje.role=="user") {
67
- clone.addClass("me");
68
- clone.find("div p").text(texto.replace(/\n/g, "<br>"));
69
- }else{
70
- texto = this.procesarTexto(texto);
71
- clone.find("div p").html(texto);
72
- }
73
- this.chatbox.append(clone);
74
- this.active = clone;
75
- Prism.highlightAllUnder(this.active[0])
76
- this.active = false;
77
- this.chatbox.scrollTop(this.chatbox[0].scrollHeight);
78
- this.interacted=true
79
  }
80
- }
81
-
82
-
83
-
84
-
85
 
86
- }
 
 
 
87
 
88
- eliminarChat(){
89
- if(confirm("¿Estás seguro que quieres eliminar esta conversación?") == true){
90
- $(document).trigger("chat:eliminar", {ctx:this.ctx, index:this.index})
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  }
93
-
 
 
 
94
  }
95
 
96
  manejadorEnviar(){
97
  let mensaje = this.ctx.find(".input-text").val();
 
 
98
  if(mensaje==""){
99
  return false;
100
  }
101
- $(document).trigger("chat:enviar", {mensaje:mensaje, ctx:this.ctx});
102
- }
103
-
104
- recalcularTextarea(){
105
- this.ctx.find(".input-box").css("height", "30px");
106
- let height = parseInt((this.ctx.find(".input-text").prop('scrollHeight')+15)/15)*15;
107
- this.ctx.find(".input-box").css("height", height+"px");
108
- height -= 30;
109
- this.ctx.find(".chat").css("--textarea", height+"px");
110
- }
111
-
112
- procesarTexto(texto){
113
-
114
- let resultado = "";
115
- let codigo = false;
116
- for(let actual of texto.split("```")){
117
- if(codigo){
118
- let temp = actual.split("\n",1);
119
- resultado += "<pre><code class='language-";
120
- resultado += temp[0].length>1?temp[0]:"none";
121
- temp = $("<div></div>").text(actual.substr(temp[0].length+1)).html()
122
- resultado += "'>"+temp+"</code></pre>";
123
- }else{
124
- resultado += $("<div></div>").text(actual).html().replace(/`([^`]+?)`/gm, "<b>$1</b>").replace(/\n/g, "<br>");
125
- }
126
- codigo = !codigo;
127
  }
128
-
129
- return resultado
 
 
130
  }
131
-
132
 
133
-
134
- respuestaInicio(mensaje){
135
- this.ctx.find(".input-text").val("");
136
- this.ctx.find("button").prop("disabled", true);
137
- this.ctx.find("textarea").prop("disabled", true);
138
- this.mensaje = ""
139
- let clone = this.template.clone();
140
- clone.addClass("me");
141
- clone.find("div p").text(mensaje);
142
- this.chatbox.append(clone);
143
-
144
- clone = this.template.clone();
145
- clone.find("div p").html('<div class="loader-wrap"><span class="loader"></span></div>');
146
- this.chatbox.append(clone);
147
- this.active = clone;
148
- this.chatbox.scrollTop(this.chatbox[0].scrollHeight);
149
  }
150
 
151
- respuestaIniciada(){
152
- this.active.find(".loader").addClass("firststage")
153
- }
154
  respuestaStatus(mensaje, modo=false){
155
  let temp = $("<div></div>")
156
  temp.text(mensaje)
 
 
 
157
  switch(modo){
158
  case "enlinea":
159
  this.active.find("div p div:last-child").text(this.active.find("div p div:last-child").text() + mensaje);
@@ -164,29 +142,35 @@ class WindowHandler{
164
  break;
165
  default:
166
  this.active.find("div p").append(temp)
167
- this.chatbox.scrollTop(this.chatbox[0].scrollHeight);
168
-
169
- }
170
-
171
  }
172
 
173
- respuestaMensaje(mensaje){
 
174
  this.active.find("div p").html("");
175
  mensaje = this.procesarTexto(mensaje);
176
  this.active.find("div p").html(mensaje);
177
  Prism.highlightAllUnder(this.active[0]);
 
 
 
178
  this.active = false;
 
 
179
  this.ctx.find("button").prop("disabled", false);
180
  this.ctx.find("textarea").prop("disabled", false);
 
181
  this.ctx.find("textarea").focus();
182
 
183
- this.chatbox.scrollTop(this.chatbox[0].scrollHeight);
184
- this.interacted = true;
185
  }
186
 
187
  respuestaError(error){
188
  this.ctx.find("button").prop("disabled", false);
189
  this.ctx.find("textarea").prop("disabled", false);
 
190
  this.ctx.find("textarea").focus();
191
  if(error.hasOwnProperty("responseJSON")){
192
  this.active.find("div p").html(error.responseJSON.detail)
@@ -207,11 +191,54 @@ class WindowHandler{
207
  }
208
  this.active = false;
209
  this.chatbox.scrollTop(this.chatbox[0].scrollHeight)
 
 
 
 
 
 
 
210
 
 
 
 
 
 
 
 
211
 
 
 
 
 
 
 
 
212
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  }
214
 
 
 
 
 
215
 
216
 
217
 
 
1
  class WindowHandler{
2
+ /* Eventos generados
3
+ - chat:enviar
4
+ - chat:eliminar
5
+ **********************/
6
+ /* Eventos escuchados
7
+
8
+ **********************/
9
+
10
+ conversacion = []
11
+ constructor(conversacion, index, chatHndl){
12
+
13
+ // El indice de este chat
14
  this.index = index
15
+ this.conversacion = conversacion
16
+ this.chatHandler = chatHndl || 0
17
+
18
+ // Template de mensajes
19
+ this.templateMsg = $("#template-message").contents("div.message").clone()
20
+
21
+ this.tabs = $(".tabs")
22
+
23
+ this.templateLabel = $("#template-label").contents("label").clone()
24
+ this.templateTab = $("#template-tab").contents("div.tab").clone()
25
+ this.chatbox = this.templateTab.find(".chat")
26
+
27
+
28
+ // Parametros base
29
  this.active = false;
30
+
31
+ // ha tenido su primera interacción
32
  this.interacted = false
33
 
34
+ this.crearVentanaChat(index, "Tab "+ index)
35
+ this.cargarEventos();
36
+ $(document).trigger("ventana:cambiada")
37
  this.cargarChat(conversacion)
38
+
39
+ }
40
 
41
+ crearVentanaChat(index, nombre){
42
+ // coloca el valor al input y lo selecciona
43
+ let newLabel = this.templateLabel.clone()
44
+ newLabel.find("input").val(index).prop("checked", true);
45
+ newLabel.find("> div").text(nombre)
46
+ this.tabs.append(newLabel)
47
+ this.label = newLabel
48
 
49
+ // Crea la ventana de chat
50
+ let tempTab = this.templateTab.clone();
51
+ tempTab.attr("id", index)
52
+ $(".chats").append(tempTab)
53
 
 
 
 
 
 
 
 
 
 
54
 
55
+ // establece los contextos de ventana
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  this.ctx = tempTab
57
  this.chatbox = this.ctx.find(".chat");
58
+ this.ventanaSeleccionada()
59
+ }
60
 
61
+ ventanaSeleccionada(){
62
+ // cuando la ventana es seleccionada, cambia
63
+ $(".tab").removeClass("active")
64
+ this.label.find("input")[0].checked=true;
65
+ this.ctx.addClass("active");
66
+ }
67
+
68
+ cargarEventos(){
69
+ this.label.click(()=> this.ventanaSeleccionada())
70
+ this.ctx.find(".input-text").keypress((event) => {
71
+ if (!event.shiftKey && event.keyCode === 13) {
72
+ this.manejadorEnviar();
 
 
 
 
 
 
73
  }
74
+ this.recalcularTextarea()
75
+ });
 
 
 
76
 
77
+ this.ctx.find(".input-send").click(() => this.manejadorEnviar());
78
+ this.ctx.find(".input-text").on("keyup,keydown", () => this.recalcularTextarea());
79
+ this.ctx.find(".input-delete").click(() => this.eliminarChat() )
80
+ this.ctx.find(".input-menu").click(() => $(document).trigger("mostrar:opciones"))
81
 
 
 
 
82
 
83
+ }
84
+
85
+ crearMensaje(texto, user){
86
+ this.active = this.templateMsg.clone();
87
+ switch(user){
88
+ case "system":
89
+ return;
90
+ case "user":
91
+ this.active.addClass("me");
92
+ this.active.find("div p").text(texto);
93
+ break;
94
+ case "assistant":
95
+ texto = this.procesarTexto(texto);
96
+ this.active.find("div p").html(texto);
97
+ Prism.highlightAllUnder(this.active[0])
98
+ break;
99
+ case "loading":
100
+ this.active.find("div p").html('<div class="loader-wrap"><span class="loader"></span></div>');
101
+ break;
102
  }
103
+ this.chatbox.append(this.active);
104
+ this.chatbox.scrollTop(this.chatbox[0].scrollHeight);
105
+ this.interacted=true
106
+ return this.active
107
  }
108
 
109
  manejadorEnviar(){
110
  let mensaje = this.ctx.find(".input-text").val();
111
+ this.ctx.find("button").prop("disabled", true);
112
+ this.ctx.find("textarea").prop("disabled", true);
113
  if(mensaje==""){
114
  return false;
115
  }
116
+ if(this.conversacion.length==0){
117
+ this.conversacion.push({role:"system", content: this.chatHandler.config.assistantPrompt})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  }
119
+ this.conversacion.push({role: "user", content: mensaje})
120
+ self = this
121
+ this.crearMensaje(mensaje, "user");
122
+ $(document).trigger("chat:enviar", {ctx: self, conversacion:this.conversacion})
123
  }
 
124
 
125
+ respuestaInicio(){
126
+ this.crearMensaje("", "loading");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
  }
128
 
 
 
 
129
  respuestaStatus(mensaje, modo=false){
130
  let temp = $("<div></div>")
131
  temp.text(mensaje)
132
+ if(!this.active.find(".loader").hasClass("firststage")){
133
+ this.active.find(".loader").addClass("firststage")
134
+ }
135
  switch(modo){
136
  case "enlinea":
137
  this.active.find("div p div:last-child").text(this.active.find("div p div:last-child").text() + mensaje);
 
142
  break;
143
  default:
144
  this.active.find("div p").append(temp)
145
+ }
146
+ this.chatbox.scrollTop(this.chatbox[0].scrollHeight);
 
 
147
  }
148
 
149
+ respuestaMensaje(data){
150
+ let mensaje = data.content
151
  this.active.find("div p").html("");
152
  mensaje = this.procesarTexto(mensaje);
153
  this.active.find("div p").html(mensaje);
154
  Prism.highlightAllUnder(this.active[0]);
155
+ this.chatbox.scrollTop(this.chatbox[0].scrollHeight);
156
+
157
+ this.conversacion.push(data)
158
  this.active = false;
159
+ this.interacted = true;
160
+
161
  this.ctx.find("button").prop("disabled", false);
162
  this.ctx.find("textarea").prop("disabled", false);
163
+ this.ctx.find("textarea").val("")
164
  this.ctx.find("textarea").focus();
165
 
166
+ $(document).trigger("chat:salvar", {index: this.index, conversacion: this.conversacion})
167
+
168
  }
169
 
170
  respuestaError(error){
171
  this.ctx.find("button").prop("disabled", false);
172
  this.ctx.find("textarea").prop("disabled", false);
173
+ this.ctx.find("textarea").val("")
174
  this.ctx.find("textarea").focus();
175
  if(error.hasOwnProperty("responseJSON")){
176
  this.active.find("div p").html(error.responseJSON.detail)
 
191
  }
192
  this.active = false;
193
  this.chatbox.scrollTop(this.chatbox[0].scrollHeight)
194
+ }
195
+
196
+ cargarChat(conversacion){
197
+ for(let mensaje of this.conversacion){
198
+ this.crearMensaje(mensaje.content, mensaje.role)
199
+ }
200
+ }
201
 
202
+ eliminarChat(){
203
+ if(confirm("¿Estás seguro que quieres eliminar esta conversación?")){
204
+ this.label.remove()
205
+ this.ctx.remove()
206
+ $(document).trigger("chat:eliminar", {ctx:this.ctx, index:this.index})
207
+ }
208
+ }
209
 
210
+ recalcularTextarea(){
211
+ this.ctx.find(".input-box").css("height", "30px");
212
+ let height = parseInt((this.ctx.find(".input-text").prop('scrollHeight')+15)/15)*15;
213
+ this.ctx.find(".input-box").css("height", height+"px");
214
+ height -= 30;
215
+ this.ctx.find(".chat").css("--textarea", height+"px");
216
+ }
217
 
218
+ procesarTexto(texto){
219
+ let resultado = "";
220
+ let codigo = false;
221
+ for(let actual of texto.split("```")){
222
+ if(codigo){
223
+ let temp = actual.split("\n",1);
224
+ resultado += "<pre><code class='language-";
225
+ resultado += temp[0].length>1?temp[0]:"none";
226
+ temp = $("<div></div>").text(actual.substr(temp[0].length+1)).html()
227
+ resultado += "'>"+temp+"</code></pre>";
228
+ }else{
229
+ resultado += $("<div></div>").text(actual).html().replace(/`([^`]+?)`/gm, "<b>$1</b>").replace(/\n/g, "<br>");
230
+ resultado = resultado.replace(/\[(.*?)\]\((.*?)\)/gm, "[<a href='$2' target='_blank'>$1</a>]")
231
+ }
232
+ codigo = !codigo;
233
+ }
234
+
235
+ return resultado
236
  }
237
 
238
+
239
+
240
+
241
+
242
 
243
 
244
 
templates/PrivacyPolicy.html ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
3
+ <title>Privacy Policy</title>
4
+
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <style>body{background:#fff;color:#000;font-family:Helvetica Neue,Helvetica,Arial,sans-serif;font-size:16px;line-height:28px;margin:0}h1,h2,h3,h4,h5,h6,li,p{margin:0 0 16px}h1{font-size:40px;line-height:60px}h1,h2{font-weight:700}h2{font-size:32px;line-height:48px}h3{font-size:24px;line-height:36px}h3,h4{font-weight:700}h4{font-size:20px;line-height:30px}h5,h6{font-size:16px;line-height:24px;font-weight:700}a{text-decoration:none;cursor:pointer;color:#000}a:hover,a[rel~=nofollow]{text-decoration:underline}a[rel~=nofollow]{color:#008461}a[rel~=nofollow]:hover{text-decoration:none}.visible{display:block}.hidden{display:none}.page{width:100%}.container{position:relative;width:90%;max-width:1024px;margin:0 auto}.header{color:#000;padding:16px 0;margin:0 0 16px}.header .title{font-size:24px;line-height:36px;font-weight:700;margin:0}.translations-list-container{padding-bottom:8px;margin:0 0 16px}.translations-list-container .translations-list{list-style:none;margin:0;padding:0}.translations-list-container .translations-list .translations-list-item{display:inline-block;padding:0;margin:0 8px 8px 0;font-weight:700;color:#008461}.translations-list-container .translations-list .translations-list-item a{display:inline-block;color:#008461;border:2px solid #008461;border-radius:4px;padding:4px 8px}.translations-content-container{padding-top:16px;border-top:1px solid #eee}.footer{border-top:1px solid #eee;margin:32px 0 0;padding:16px 0}</style>
8
+ <meta name="robots" content="noindex">
9
+ </head>
10
+ <body>
11
+ <div class="page">
12
+ <div class="header">
13
+ <div class="container">
14
+ <p class="title">Privacy Policy for Chatsito</p>
15
+ </div>
16
+ </div>
17
+ <div class="translations-content-container">
18
+ <div class="container">
19
+ <div class="tab-content translations-content-item en visible" id="en">
20
+ <h1>Privacy Policy</h1>
21
+ <p>Last updated: December 01, 2023</p>
22
+ <p>This Privacy Policy describes Our policies and procedures on the collection, use and disclosure of Your information when You use the Service and tells You about Your privacy rights and how the law protects You.</p>
23
+ <p>We use Your Personal data to provide and improve the Service. By using the Service, You agree to the collection and use of information in accordance with this Privacy Policy. This Privacy Policy has been created with the help of the <a href="https://www.privacypolicies.com/privacy-policy-generator/" target="_blank">Privacy Policy Generator</a>.</p>
24
+ <h2>Interpretation and Definitions</h2>
25
+ <h3>Interpretation</h3>
26
+ <p>The words of which the initial letter is capitalized have meanings defined under the following conditions. The following definitions shall have the same meaning regardless of whether they appear in singular or in plural.</p>
27
+ <h3>Definitions</h3>
28
+ <p>For the purposes of this Privacy Policy:</p>
29
+ <ul>
30
+ <li>
31
+ <p><strong>Account</strong> means a unique account created for You to access our Service or parts of our Service.</p>
32
+ </li>
33
+ <li>
34
+ <p><strong>Affiliate</strong> means an entity that controls, is controlled by or is under common control with a party, where "control" means ownership of 50% or more of the shares, equity interest or other securities entitled to vote for election of directors or other managing authority.</p>
35
+ </li>
36
+ <li>
37
+ <p><strong>Company</strong> (referred to as either "the Company", "We", "Us" or "Our" in this Agreement) refers to Chatsito.</p>
38
+ </li>
39
+ <li>
40
+ <p><strong>Cookies</strong> are small files that are placed on Your computer, mobile device or any other device by a website, containing the details of Your browsing history on that website among its many uses.</p>
41
+ </li>
42
+ <li>
43
+ <p><strong>Country</strong> refers to: Venezuela</p>
44
+ </li>
45
+ <li>
46
+ <p><strong>Device</strong> means any device that can access the Service such as a computer, a cellphone or a digital tablet.</p>
47
+ </li>
48
+ <li>
49
+ <p><strong>Personal Data</strong> is any information that relates to an identified or identifiable individual.</p>
50
+ </li>
51
+ <li>
52
+ <p><strong>Service</strong> refers to the Website.</p>
53
+ </li>
54
+ <li>
55
+ <p><strong>Service Provider</strong> means any natural or legal person who processes the data on behalf of the Company. It refers to third-party companies or individuals employed by the Company to facilitate the Service, to provide the Service on behalf of the Company, to perform services related to the Service or to assist the Company in analyzing how the Service is used.</p>
56
+ </li>
57
+ <li>
58
+ <p><strong>Usage Data</strong> refers to data collected automatically, either generated by the use of the Service or from the Service infrastructure itself (for example, the duration of a page visit).</p>
59
+ </li>
60
+ <li>
61
+ <p><strong>Website</strong> refers to Chatsito, accessible from <a href="https://mgldzm-chatlocal.hf.space/" rel="external nofollow noopener" target="_blank">https://mgldzm-chatlocal.hf.space/</a></p>
62
+ </li>
63
+ <li>
64
+ <p><strong>You</strong> means the individual accessing or using the Service, or the company, or other legal entity on behalf of which such individual is accessing or using the Service, as applicable.</p>
65
+ </li>
66
+ </ul>
67
+ <h2>Collecting and Using Your Personal Data</h2>
68
+ <h3>Types of Data Collected</h3>
69
+ <h4>Personal Data</h4>
70
+ <p>While using Our Service, We may ask You to provide Us with certain personally identifiable information that can be used to contact or identify You. Personally identifiable information may include, but is not limited to:</p>
71
+ <ul>
72
+ <li>
73
+ <p>Email address</p>
74
+ </li>
75
+ <li>
76
+ <p>First name and last name</p>
77
+ </li>
78
+ <li>
79
+ <p>Usage Data</p>
80
+ </li>
81
+ </ul>
82
+ <h4>Usage Data</h4>
83
+ <p>Usage Data is collected automatically when using the Service.</p>
84
+ <p>Usage Data may include information such as Your Device's Internet Protocol address (e.g. IP address), browser type, browser version, the pages of our Service that You visit, the time and date of Your visit, the time spent on those pages, unique device identifiers and other diagnostic data.</p>
85
+ <p>When You access the Service by or through a mobile device, We may collect certain information automatically, including, but not limited to, the type of mobile device You use, Your mobile device unique ID, the IP address of Your mobile device, Your mobile operating system, the type of mobile Internet browser You use, unique device identifiers and other diagnostic data.</p>
86
+ <p>We may also collect information that Your browser sends whenever You visit our Service or when You access the Service by or through a mobile device.</p>
87
+ <h4>Tracking Technologies and Cookies</h4>
88
+ <p>We use Cookies and similar tracking technologies to track the activity on Our Service and store certain information. Tracking technologies used are beacons, tags, and scripts to collect and track information and to improve and analyze Our Service. The technologies We use may include:</p>
89
+ <ul>
90
+ <li><strong>Cookies or Browser Cookies.</strong> A cookie is a small file placed on Your Device. You can instruct Your browser to refuse all Cookies or to indicate when a Cookie is being sent. However, if You do not accept Cookies, You may not be able to use some parts of our Service. Unless you have adjusted Your browser setting so that it will refuse Cookies, our Service may use Cookies.</li>
91
+ <li><strong>Web Beacons.</strong> Certain sections of our Service and our emails may contain small electronic files known as web beacons (also referred to as clear gifs, pixel tags, and single-pixel gifs) that permit the Company, for example, to count users who have visited those pages or opened an email and for other related website statistics (for example, recording the popularity of a certain section and verifying system and server integrity).</li>
92
+ </ul>
93
+ <p>Cookies can be "Persistent" or "Session" Cookies. Persistent Cookies remain on Your personal computer or mobile device when You go offline, while Session Cookies are deleted as soon as You close Your web browser. Learn more about cookies on the <a href="https://www.privacypolicies.com/blog/privacy-policy-template/#Use_Of_Cookies_Log_Files_And_Tracking" target="_blank">Privacy Policies website</a> article.</p>
94
+ <p>We use both Session and Persistent Cookies for the purposes set out below:</p>
95
+ <ul>
96
+ <li>
97
+ <p><strong>Necessary / Essential Cookies</strong></p>
98
+ <p>Type: Session Cookies</p>
99
+ <p>Administered by: Us</p>
100
+ <p>Purpose: These Cookies are essential to provide You with services available through the Website and to enable You to use some of its features. They help to authenticate users and prevent fraudulent use of user accounts. Without these Cookies, the services that You have asked for cannot be provided, and We only use these Cookies to provide You with those services.</p>
101
+ </li>
102
+ <li>
103
+ <p><strong>Cookies Policy / Notice Acceptance Cookies</strong></p>
104
+ <p>Type: Persistent Cookies</p>
105
+ <p>Administered by: Us</p>
106
+ <p>Purpose: These Cookies identify if users have accepted the use of cookies on the Website.</p>
107
+ </li>
108
+ <li>
109
+ <p><strong>Functionality Cookies</strong></p>
110
+ <p>Type: Persistent Cookies</p>
111
+ <p>Administered by: Us</p>
112
+ <p>Purpose: These Cookies allow us to remember choices You make when You use the Website, such as remembering your login details or language preference. The purpose of these Cookies is to provide You with a more personal experience and to avoid You having to re-enter your preferences every time You use the Website.</p>
113
+ </li>
114
+ </ul>
115
+ <p>For more information about the cookies we use and your choices regarding cookies, please visit our Cookies Policy or the Cookies section of our Privacy Policy.</p>
116
+ <h3>Use of Your Personal Data</h3>
117
+ <p>The Company may use Personal Data for the following purposes:</p>
118
+ <ul>
119
+ <li>
120
+ <p><strong>To provide and maintain our Service</strong>, including to monitor the usage of our Service.</p>
121
+ </li>
122
+ <li>
123
+ <p><strong>To manage Your Account:</strong> to manage Your registration as a user of the Service. The Personal Data You provide can give You access to different functionalities of the Service that are available to You as a registered user.</p>
124
+ </li>
125
+ <li>
126
+ <p><strong>For the performance of a contract:</strong> the development, compliance and undertaking of the purchase contract for the products, items or services You have purchased or of any other contract with Us through the Service.</p>
127
+ </li>
128
+ <li>
129
+ <p><strong>To contact You:</strong> To contact You by email, telephone calls, SMS, or other equivalent forms of electronic communication, such as a mobile application's push notifications regarding updates or informative communications related to the functionalities, products or contracted services, including the security updates, when necessary or reasonable for their implementation.</p>
130
+ </li>
131
+ <li>
132
+ <p><strong>To provide You</strong> with news, special offers and general information about other goods, services and events which we offer that are similar to those that you have already purchased or enquired about unless You have opted not to receive such information.</p>
133
+ </li>
134
+ <li>
135
+ <p><strong>To manage Your requests:</strong> To attend and manage Your requests to Us.</p>
136
+ </li>
137
+ <li>
138
+ <p><strong>For business transfers:</strong> We may use Your information to evaluate or conduct a merger, divestiture, restructuring, reorganization, dissolution, or other sale or transfer of some or all of Our assets, whether as a going concern or as part of bankruptcy, liquidation, or similar proceeding, in which Personal Data held by Us about our Service users is among the assets transferred.</p>
139
+ </li>
140
+ <li>
141
+ <p><strong>For other purposes</strong>: We may use Your information for other purposes, such as data analysis, identifying usage trends, determining the effectiveness of our promotional campaigns and to evaluate and improve our Service, products, services, marketing and your experience.</p>
142
+ </li>
143
+ </ul>
144
+ <p>We may share Your personal information in the following situations:</p>
145
+ <ul>
146
+ <li><strong>With Service Providers:</strong> We may share Your personal information with Service Providers to monitor and analyze the use of our Service, to contact You.</li>
147
+ <li><strong>For business transfers:</strong> We may share or transfer Your personal information in connection with, or during negotiations of, any merger, sale of Company assets, financing, or acquisition of all or a portion of Our business to another company.</li>
148
+ <li><strong>With Affiliates:</strong> We may share Your information with Our affiliates, in which case we will require those affiliates to honor this Privacy Policy. Affiliates include Our parent company and any other subsidiaries, joint venture partners or other companies that We control or that are under common control with Us.</li>
149
+ <li><strong>With business partners:</strong> We may share Your information with Our business partners to offer You certain products, services or promotions.</li>
150
+ <li><strong>With other users:</strong> when You share personal information or otherwise interact in the public areas with other users, such information may be viewed by all users and may be publicly distributed outside.</li>
151
+ <li><strong>With Your consent</strong>: We may disclose Your personal information for any other purpose with Your consent.</li>
152
+ </ul>
153
+ <h3>Retention of Your Personal Data</h3>
154
+ <p>The Company will retain Your Personal Data only for as long as is necessary for the purposes set out in this Privacy Policy. We will retain and use Your Personal Data to the extent necessary to comply with our legal obligations (for example, if we are required to retain your data to comply with applicable laws), resolve disputes, and enforce our legal agreements and policies.</p>
155
+ <p>The Company will also retain Usage Data for internal analysis purposes. Usage Data is generally retained for a shorter period of time, except when this data is used to strengthen the security or to improve the functionality of Our Service, or We are legally obligated to retain this data for longer time periods.</p>
156
+ <h3>Transfer of Your Personal Data</h3>
157
+ <p>Your information, including Personal Data, is processed at the Company's operating offices and in any other places where the parties involved in the processing are located. It means that this information may be transferred to — and maintained on — computers located outside of Your state, province, country or other governmental jurisdiction where the data protection laws may differ than those from Your jurisdiction.</p>
158
+ <p>Your consent to this Privacy Policy followed by Your submission of such information represents Your agreement to that transfer.</p>
159
+ <p>The Company will take all steps reasonably necessary to ensure that Your data is treated securely and in accordance with this Privacy Policy and no transfer of Your Personal Data will take place to an organization or a country unless there are adequate controls in place including the security of Your data and other personal information.</p>
160
+ <h3>Delete Your Personal Data</h3>
161
+ <p>You have the right to delete or request that We assist in deleting the Personal Data that We have collected about You.</p>
162
+ <p>Our Service may give You the ability to delete certain information about You from within the Service.</p>
163
+ <p>You may update, amend, or delete Your information at any time by signing in to Your Account, if you have one, and visiting the account settings section that allows you to manage Your personal information. You may also contact Us to request access to, correct, or delete any personal information that You have provided to Us.</p>
164
+ <p>Please note, however, that We may need to retain certain information when we have a legal obligation or lawful basis to do so.</p>
165
+ <h3>Disclosure of Your Personal Data</h3>
166
+ <h4>Business Transactions</h4>
167
+ <p>If the Company is involved in a merger, acquisition or asset sale, Your Personal Data may be transferred. We will provide notice before Your Personal Data is transferred and becomes subject to a different Privacy Policy.</p>
168
+ <h4>Law enforcement</h4>
169
+ <p>Under certain circumstances, the Company may be required to disclose Your Personal Data if required to do so by law or in response to valid requests by public authorities (e.g. a court or a government agency).</p>
170
+ <h4>Other legal requirements</h4>
171
+ <p>The Company may disclose Your Personal Data in the good faith belief that such action is necessary to:</p>
172
+ <ul>
173
+ <li>Comply with a legal obligation</li>
174
+ <li>Protect and defend the rights or property of the Company</li>
175
+ <li>Prevent or investigate possible wrongdoing in connection with the Service</li>
176
+ <li>Protect the personal safety of Users of the Service or the public</li>
177
+ <li>Protect against legal liability</li>
178
+ </ul>
179
+ <h3>Security of Your Personal Data</h3>
180
+ <p>The security of Your Personal Data is important to Us, but remember that no method of transmission over the Internet, or method of electronic storage is 100% secure. While We strive to use commercially acceptable means to protect Your Personal Data, We cannot guarantee its absolute security.</p>
181
+ <h2>Children's Privacy</h2>
182
+ <p>Our Service does not address anyone under the age of 13. We do not knowingly collect personally identifiable information from anyone under the age of 13. If You are a parent or guardian and You are aware that Your child has provided Us with Personal Data, please contact Us. If We become aware that We have collected Personal Data from anyone under the age of 13 without verification of parental consent, We take steps to remove that information from Our servers.</p>
183
+ <p>If We need to rely on consent as a legal basis for processing Your information and Your country requires consent from a parent, We may require Your parent's consent before We collect and use that information.</p>
184
+ <h2>Links to Other Websites</h2>
185
+ <p>Our Service may contain links to other websites that are not operated by Us. If You click on a third party link, You will be directed to that third party's site. We strongly advise You to review the Privacy Policy of every site You visit.</p>
186
+ <p>We have no control over and assume no responsibility for the content, privacy policies or practices of any third party sites or services.</p>
187
+ <h2>Changes to this Privacy Policy</h2>
188
+ <p>We may update Our Privacy Policy from time to time. We will notify You of any changes by posting the new Privacy Policy on this page.</p>
189
+ <p>We will let You know via email and/or a prominent notice on Our Service, prior to the change becoming effective and update the "Last updated" date at the top of this Privacy Policy.</p>
190
+ <p>You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page.</p>
191
+ <h2>Contact Us</h2>
192
+ <p>If you have any questions about this Privacy Policy, You can contact us:</p>
193
+ <ul>
194
+ <li>By visiting this page on our website: <a href="https://mgldzm-chatlocal.hf.space/privacy" rel="external nofollow noopener" target="_blank">https://mgldzm-chatlocal.hf.space/privacy</a></li>
195
+ </ul>
196
+ </div>
197
+ </div>
198
+ </div>
199
+
200
+ <div class="footer">
201
+ </div>
202
+ </div>
203
+ </body></html>
templates/login.html ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+
3
+ <html lang="ES">
4
+ <head>
5
+ <title>En espera</title>
6
+
7
+ <style>
8
+ html,
9
+ body {
10
+ background: #f1f1f1;
11
+ font-family: "Merriweather", sans-serif;
12
+ padding: 1em;
13
+ }
14
+
15
+ h1 {
16
+ text-align: center;
17
+ color: #a8a8a8;
18
+ }
19
+
20
+ form {
21
+ max-width: 600px;
22
+ text-align: center;
23
+ margin: 20px auto;
24
+ }
25
+
26
+ form button {
27
+ border: 0;
28
+ outline: 0;
29
+ padding: 1em;
30
+ display: block;
31
+ width: 100%;
32
+ margin-top: 1em;
33
+ font-family: "Merriweather", sans-serif;
34
+ resize: none;
35
+
36
+ }
37
+
38
+ #input-submit {
39
+ color: white;
40
+ background: #000;
41
+ cursor: pointer;
42
+ }
43
+
44
+ .half{
45
+ display: flex;
46
+ justify-content: center;
47
+ }
48
+ textarea {
49
+ height: 126px;
50
+ }
51
+
52
+ .gsi-material-button {
53
+ -moz-user-select: none;
54
+ -webkit-user-select: none;
55
+ -ms-user-select: none;
56
+ -webkit-appearance: none;
57
+ background-color: WHITE;
58
+ background-image: none;
59
+ border: 1px solid #747775;
60
+ -webkit-border-radius: 20px;
61
+ border-radius: 20px;
62
+ -webkit-box-sizing: border-box;
63
+ box-sizing: border-box;
64
+ color: #1f1f1f;
65
+ cursor: pointer;
66
+ font-family: 'Roboto', arial, sans-serif;
67
+ font-size: 14px;
68
+ height: 40px;
69
+ letter-spacing: 0.25px;
70
+ outline: none;
71
+ overflow: hidden;
72
+ padding: 0 12px;
73
+ position: relative;
74
+ text-align: center;
75
+ -webkit-transition: background-color .218s, border-color .218s, box-shadow .218s;
76
+ transition: background-color .218s, border-color .218s, box-shadow .218s;
77
+ vertical-align: middle;
78
+ white-space: nowrap;
79
+ width: auto;
80
+ max-width: 400px;
81
+ min-width: min-content;
82
+ }
83
+
84
+ .gsi-material-button .gsi-material-button-icon {
85
+ height: 20px;
86
+ margin-right: 12px;
87
+ min-width: 20px;
88
+ width: 20px;
89
+ }
90
+
91
+ .gsi-material-button .gsi-material-button-content-wrapper {
92
+ -webkit-align-items: center;
93
+ align-items: center;
94
+ display: flex;
95
+ -webkit-flex-direction: row;
96
+ flex-direction: row;
97
+ -webkit-flex-wrap: nowrap;
98
+ flex-wrap: nowrap;
99
+ height: 100%;
100
+ justify-content: space-between;
101
+ position: relative;
102
+ width: 100%;
103
+ }
104
+
105
+ .gsi-material-button .gsi-material-button-contents {
106
+ -webkit-flex-grow: 1;
107
+ flex-grow: 1;
108
+ font-family: 'Roboto', arial, sans-serif;
109
+ font-weight: 500;
110
+ overflow: hidden;
111
+ text-overflow: ellipsis;
112
+ vertical-align: top;
113
+ }
114
+
115
+ .gsi-material-button .gsi-material-button-state {
116
+ -webkit-transition: opacity .218s;
117
+ transition: opacity .218s;
118
+ bottom: 0;
119
+ left: 0;
120
+ opacity: 0;
121
+ position: absolute;
122
+ right: 0;
123
+ top: 0;
124
+ }
125
+
126
+ .gsi-material-button:disabled {
127
+ cursor: default;
128
+ background-color: #ffffff61;
129
+ border-color: #1f1f1f1f;
130
+ }
131
+
132
+ .gsi-material-button:disabled .gsi-material-button-contents {
133
+ opacity: 38%;
134
+ }
135
+
136
+ .gsi-material-button:disabled .gsi-material-button-icon {
137
+ opacity: 38%;
138
+ }
139
+
140
+ .gsi-material-button:not(:disabled):active .gsi-material-button-state,
141
+ .gsi-material-button:not(:disabled):focus .gsi-material-button-state {
142
+ background-color: #303030;
143
+ opacity: 12%;
144
+ }
145
+
146
+ .gsi-material-button:not(:disabled):hover {
147
+ -webkit-box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .30), 0 1px 3px 1px rgba(60, 64, 67, .15);
148
+ box-shadow: 0 1px 2px 0 rgba(60, 64, 67, .30), 0 1px 3px 1px rgba(60, 64, 67, .15);
149
+ }
150
+
151
+ .gsi-material-button:not(:disabled):hover .gsi-material-button-state {
152
+ background-color: #303030;
153
+ opacity: 8%;
154
+ }
155
+
156
+ </style>
157
+
158
+ </head>
159
+ <body>
160
+ {% if not description %}
161
+ <h1>Haz click para iniciar sesion con Google Oauth</h1>
162
+ <div class="half cf">
163
+
164
+ <button class="gsi-material-button" onclick="iniciarSesion()">
165
+ <div class="gsi-material-button-state"></div>
166
+ <div class="gsi-material-button-content-wrapper">
167
+ <div class="gsi-material-button-icon">
168
+ <svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" xmlns:xlink="http://www.w3.org/1999/xlink" style="display: block;">
169
+ <path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z"></path>
170
+ <path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 2.96-2.26 5.48-4.78 7.18l7.73 6c4.51-4.18 7.09-10.36 7.09-17.65z"></path>
171
+ <path fill="#FBBC05" d="M10.53 28.59c-.48-1.45-.76-2.99-.76-4.59s.27-3.14.76-4.59l-7.98-6.19C.92 16.46 0 20.12 0 24c0 3.88.92 7.54 2.56 10.78l7.97-6.19z"></path>
172
+ <path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z"></path>
173
+ <path fill="none" d="M0 0h48v48H0z"></path>
174
+ </svg>
175
+ </div>
176
+ <span class="gsi-material-button-contents">Continue with Google</span>
177
+ <span style="display: none;">Continue with Google</span>
178
+ </div>
179
+ </button>
180
+
181
+ </div>
182
+ {% else %}
183
+ <h1>Aun no te he aprobado, avisame y espera</h1>
184
+ {% endif %}
185
+
186
+
187
+ <script>
188
+ var data = {
189
+
190
+ }
191
+
192
+ function iniciarSesion() {
193
+ var redirectUri = '{{redirecturi}}';
194
+ var clientId = '1051565752404-h4un77s3k4el280e589hn98s8ea4bg69.apps.googleusercontent.com';
195
+ let encodedData = btoa(JSON.stringify(data))
196
+ var url = 'https://accounts.google.com/o/oauth2/auth' +
197
+ '?response_type=code' +
198
+ '&client_id=' + encodeURIComponent(clientId) +
199
+ '&state=' + encodeURIComponent("data="+encodedData) +
200
+ '&redirect_uri=' + encodeURIComponent(redirectUri) +
201
+ '&scope=email%20profile';
202
+
203
+
204
+ var OauthWindow = window.open(url, "_blank", "popup");
205
+ OauthWindow.addEventListener("load", (event) => {
206
+
207
+ let location = event.target.location.pathname
208
+
209
+ if(location == "/oauth" && JSON.parse(event.target.documentElement.innerText).success){
210
+ event.currentTarget.close()
211
+ }
212
+ window.location.href = "/hold"
213
+ })
214
+
215
+ }
216
+ </script>
217
+ </body>
218
+
219
+
templates/main.html ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+
3
+ <html lang="ES">
4
+ <head>
5
+ <title> Chatbot </title>
6
+ <meta name="viewport" content="width=device-width, interactive-widget=resizes-content">
7
+ <link rel="shortcut icon" href="static/favicon.png" type="image/png">
8
+ <link rel="stylesheet" href="static/css/app.css?v={{ version }}">
9
+ <link rel="stylesheet" href="static/css/tabs.css?v={{ version }}">
10
+ <link rel="stylesheet" href="static/css/menu.css?v={{ version }}">
11
+
12
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
13
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css" rel="stylesheet" />
14
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js" integrity="sha512-9khQRAUBYEJDCDVP2yw3LRUQvjJ0Pjx0EShmaQjcHa6AXiOv6qHQu9lCAIR8O+/D8FtaCoJ2c0Tf9Xo7hYH01Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
15
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js" integrity="sha512-SkmBfuA2hqjzEVpmnMt/LINrjop3GKWqsuLSSB3e7iBmYK7JuWw4ldmmxwD9mdm2IRTTi0OxSAfEGvgEi0i2Kw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
16
+
17
+ <script src="static/js/windowHandler.js?v={{ version }}"></script>
18
+ <script src="static/js/chatHandler.js?v={{ version }}"></script>
19
+ <script src="static/js/SecurityHandler.js?v={{ version }}"></script>
20
+
21
+
22
+
23
+ </head>
24
+ <body>
25
+
26
+ <!-- Estructura principal -->
27
+ <div class="wrapper">
28
+
29
+ <!-- Pestañas -->
30
+ <div class="tabs">
31
+ <label class="tab-label-add" for="nuevoChat" >
32
+ +
33
+ <input type="button" class="tab-add" id="nuevoChat">
34
+ </label>
35
+ </div>
36
+ <!--/ Pestañas -->
37
+
38
+ <!-- Contenedor de chats -->
39
+ <div class="chats">
40
+
41
+ </div>
42
+ <!--/ Contenedor de chats -->
43
+ </div>
44
+ <!--/ Estructura principal -->
45
+
46
+ <!-- Templates del chat -->
47
+ <template id="template-label">
48
+
49
+ <!-- Pestaña -->
50
+ <label class="tab-label">
51
+ <div class="tab-name">Tab </div>
52
+ <input type="radio" name="tabs" class="tab-switch" value=0>
53
+ </label>
54
+ <!--/ Pestaña -->
55
+ </template>
56
+ <template id="template-tab">
57
+ <!-- Contenido Pestaña -->
58
+ <div class="tab">
59
+ <div class="chat"></div>
60
+ <div class='input-box' id="inputBox">
61
+ <textarea class='input-text' placeholder="Escribe aquí" type="text" autofocus=""></textarea>
62
+ <button class='input-send' ></button>
63
+ <button class='input-menu' ></button>
64
+ <button class='input-delete' ></button>
65
+ </div>
66
+ </div>
67
+ <!--/ Contenido Pestaña -->
68
+ </template>
69
+ <!--/ Templates del chat -->
70
+ <template id="template-message">
71
+ <div class="message">
72
+ <div>
73
+ <p></p>
74
+ </div>
75
+ </div>
76
+ </template>
77
+ <dialog class="inserted" id="inserted">
78
+
79
+ </dialog>
80
+
81
+ <dialog class="menu" id="menu">
82
+ <form method="dialog">
83
+ <div class="title">
84
+ Configuración
85
+ </div>
86
+ <button class="close" value="cancel" >
87
+ X
88
+ </button>
89
+ <div class="item">
90
+ <label for="temperature">Temperatura:
91
+ <span class="tooltip">Si no sabe que hace no toque</span>
92
+ </label>
93
+ <div class="range">
94
+ <input id="temperature" type="range" min=0 max=1 value=0.5 step=0.05>
95
+ <span>0.00</span>
96
+ </div>
97
+ </div>
98
+ <div class="item">
99
+ <label for="frequency_penalty">Frequency penalty:
100
+ <span class="tooltip">Si no sabe que hace no toque</span>
101
+ </label>
102
+ <div class="range">
103
+ <input id="frequency_penalty" type="range" min=-1 max=1 value=0 step=0.05>
104
+ <span>0.00</span>
105
+ </div>
106
+ </div>
107
+ <div class="item">
108
+ <label for="presence_penalty">Presence penalty:
109
+ <span class="tooltip">Si no sabe que hace no toque</span>
110
+ </label>
111
+ <div class="range">
112
+ <input id="presence_penalty" type="range" min=-1 max=1 value=0 step=0.05>
113
+ <span>0.00</span>
114
+ </div>
115
+ </div>
116
+ <hr>
117
+ <div class="item parent">
118
+ <label for="useTool">Usar herramientas:
119
+ <span class="tooltip">Activar o desactivar la capacidad del chatbot de usar herramientas</span>
120
+ </label>
121
+ <label class="switch">
122
+ <input id="useTool" type="checkbox" checked>
123
+ <span class="slider"></span>
124
+ </label>
125
+
126
+ </div>
127
+
128
+ {% for tool in tools %}
129
+ <div class="item useTool">
130
+ <label class="indented" for="tool:{{ tool['name'] }}">{{ tool['desc'] }}:</label>
131
+ <label class="switch">
132
+ <input id="tool:{{ tool['name'] }}" type="checkbox" checked>
133
+ <span class="slider"></span>
134
+ </label>
135
+ </div>
136
+ {% endfor %}
137
+
138
+ <hr>
139
+ <div class="item">
140
+ <label>Tipo Asistente:
141
+ <span class="tooltip">Modos predefinidos de comportamiento del bot, aplica solo a nuevos chats abiertos</span>
142
+ </label>
143
+ <div class="range">
144
+ <select id="assistant" >
145
+ {% for asistant in asistants %}
146
+ <option value="{{ asistant }}">{{ asistant }}</option>
147
+ {% endfor %}
148
+ </select>
149
+
150
+ </div>
151
+
152
+
153
+ </div>
154
+ <hr>
155
+ <div class="item">
156
+ <label>Tokens usados Mes:
157
+ <span class="tooltip">Que tantas vacaciones le has dado a tu cerebro este mes</span>
158
+ </label>
159
+
160
+ <span id="tokensUsedMonth">0</span>
161
+ </div>
162
+ <div class="item">
163
+ <label>Tokens usados Total:
164
+ <span class="tooltip">Que tantas vacaciones le has dado a tu cerebro desde quien sabe cuando...</span>
165
+ </label>
166
+
167
+ <span id="tokensUsedTotal">0</span>
168
+ </div>
169
+ <hr>
170
+ <div class="saveButton">
171
+ <button>Guardar</span>
172
+ </div>
173
+ </form>
174
+ </dialog>
175
+ <script>
176
+
177
+
178
+
179
+
180
+
181
+ $(document).ready(async function() {
182
+
183
+ var secHand = new Security()
184
+ await secHand.start()
185
+
186
+ let versionLocal = localStorage.getItem("version")
187
+ let versionRemota = "{{ version }}"
188
+
189
+ var cHand = new ChatGPT(secHand);
190
+
191
+ });
192
+
193
+
194
+
195
+ </script>
196
+ </body>
197
+ </html>
templates/no_access.html ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+
3
+ <html lang="ES">
4
+ <head>
5
+ <title>En espera</title>
6
+
7
+ <style>
8
+ html,
9
+ body {
10
+ background: #f1f1f1;
11
+ font-family: "Merriweather", sans-serif;
12
+ padding: 1em;
13
+ }
14
+
15
+ h1 {
16
+ text-align: center;
17
+ color: #a8a8a8;
18
+ }
19
+
20
+ form {
21
+ max-width: 600px;
22
+ text-align: center;
23
+ margin: 20px auto;
24
+ }
25
+
26
+ form input, form textarea {
27
+ border: 0;
28
+ outline: 0;
29
+ padding: 1em;
30
+ display: block;
31
+ width: 100%;
32
+ margin-top: 1em;
33
+ font-family: "Merriweather", sans-serif;
34
+ resize: none;
35
+
36
+ }
37
+
38
+ #input-submit {
39
+ color: white;
40
+ background: #000;
41
+ cursor: pointer;
42
+ }
43
+
44
+
45
+ textarea {
46
+ height: 126px;
47
+ }
48
+
49
+
50
+ </style>
51
+ </head>
52
+ <body>
53
+ {% if not description %}
54
+ <h1>Dime quien eres y quien te dio el link</h1>
55
+ <form class="cf" method="POST", action="/hold">
56
+ <div class="half cf">
57
+ <textarea name="message" type="text" id="input-message" placeholder="Message"></textarea>
58
+ <input type="submit" value="Submit" id="input-submit">
59
+ </div>
60
+ </form>
61
+ {% else %}
62
+ <h1>Aun no te he aprobado, avisame y espera</h1>
63
+ {% endif %}
64
+ </body>
65
+
66
+