Boton tipo switch para búsqueda en Google

#2
by xillegas - opened
.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,5 +2,3 @@
2
  .vscode/
3
  __pycache__/
4
  keys/
5
- .env
6
- logs/eventos.log
 
2
  .vscode/
3
  __pycache__/
4
  keys/
 
 
Dockerfile CHANGED
@@ -1,21 +1,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
-
 
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", "--bind", "0.0.0.0:7860"]
21
+
error_map.py DELETED
@@ -1,30 +0,0 @@
1
- from openai import error as openai_error
2
- from fastapi import status
3
- import requests
4
- error_table = {
5
- requests.exceptions.RequestException: {
6
- "status_code": status.HTTP_408_REQUEST_TIMEOUT,
7
- "detail": "Los servidores tardaron mucho en responder, puede haber sobrecarga en OpenAI, reintenta luego (error 1)"
8
- },
9
- openai_error.APIConnectionError: {
10
- "status_code": status.HTTP_408_REQUEST_TIMEOUT,
11
- "detail": "El servidor no respondió, hubo un error de API"
12
- },
13
- openai_error.Timeout: {
14
- "status_code": status.HTTP_408_REQUEST_TIMEOUT,
15
- "detail": "El servidor tardó demasiado en responder"
16
- },
17
- openai_error.InvalidRequestError: {
18
- "status_code": status.HTTP_408_REQUEST_TIMEOUT,
19
- "detail": "ChatGPT se gomitó 🤮, este chat ya es muy largo, limpia el chat y reintenta."
20
- },
21
- openai.RateLimitError: {
22
- "status_code": status.HTTP_408_REQUEST_TIMEOUT,
23
- "detail": "ChatGPT no quiere responder 😩 ellos dicen que lo usamos mucho."
24
- }
25
- "undefined": {
26
- "status_code": status.HTTP_408_REQUEST_TIMEOUT,
27
- "detail": "Error no definido 🙄"
28
- }
29
- }
30
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
main.py CHANGED
@@ -1,193 +1,210 @@
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 = FastAPI()
17
- app.mount("/static", StaticFiles(directory="static"), name="static")
18
- templates = Jinja2Templates(directory="templates")
19
-
20
- fecha_unix = str(int(time.time()))
21
- log_module.logger("system").info("iniciado")
22
- ##########################################################
23
-
24
-
25
- ######################## ROUTE: / ########################
26
- @app.get("/", response_class=HTMLResponse)
27
- async def main_page(request: Request):
28
-
29
- tools = [{"name": k, "desc": v} for k, v in chat_functions.function_user_text.items()]
30
- asistants = model.get_all_personality_cores()
31
-
32
- response = templates.TemplateResponse(
33
- "main.html", {
34
- "request": request,
35
- "version": fecha_unix,
36
- "tools": tools,
37
- "asistants": asistants
38
- })
39
- return response
40
- ##########################################################
41
-
42
-
43
-
44
- #################### SECURITY (OAUTH) ####################
45
- @app.get("/login", response_class=HTMLResponse)
46
- async def login(request: Request):
47
- # Shows the Start "session with google" button, and deletes token cookie
48
- ret = templates.TemplateResponse("login.html", {"request": request, "redirecturi": settings.OAUTH_REDIRECT})
49
- ret.delete_cookie("token")
50
- return ret
51
-
52
- @app.get("/oauth")
53
- async def validate_oauth(request: Request, response: Response):
54
- # Get the oauth get params,
55
- # look for the google validation and info,
56
- # set the session cookie and save user in DB
57
-
58
- # Extract the Get params
59
- params = dict(request.query_params)
60
-
61
- # Client browser fingerprint bypassed with google oauth as "state"
62
- data = params["state"].split("=",1)[1]
63
- data = b64decode(data)
64
- data = json.loads(data)
65
-
66
-
67
 
68
- # Get the google user info
69
- google_userinfo = oauth.validate_redirect(params)
 
 
 
 
 
 
 
70
 
71
- # Create the user model with the google info
72
- user:User = model.User.find_or_create(google_userinfo, data)
 
 
73
 
74
- # Prepare redirect response and set the session cookie
75
-
76
- token = user.create_cookie()
77
- if not user.can_use("chat"):
78
- response = RedirectResponse(url='/hold')
79
- security.set_cookie(response, key="token", value=token)
80
- return response
81
-
82
- # Saves the user
83
- user.update_user()
84
- security.set_cookie(response, key="token", value=token)
85
-
86
- return {"success": True}
87
 
88
- ##########################################################
89
 
 
90
 
91
- ###################### APIs Configs ######################
92
 
93
- # Get data and structures from its models
94
- User_find_from_data = Annotated[User, Depends(User.find_from_data)]
95
- Session_find_from_data = Annotated[Session, Depends(Session.find_from_data)]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
- @app.post("/getConfigs")
98
- async def get_configs(response: Response, user: User_find_from_data):
99
-
100
- if not user.can_use("chat"):
101
- response = RedirectResponse(url='/hold')
102
- return response
103
-
104
- # Get llm tokens used
105
- year_, month_ = datetime.now().strftime("%y-%m").split("-")
106
- month = sum(user.tokens.get(year_, {}).get(month_, {"_":0}).values())
107
- total = sum([z for x in user.tokens.values() for y in x.values() for z in y.values()])
108
- tokens = {"month": month, "total": total}
109
-
110
- # Create cookies and answer
111
- user._session.create_cookie(response)
112
- return user.configs.model_dump() | {"tokens": tokens, "challenge": user._session.challenge}
113
 
114
- @app.post("/setConfigs")
115
- async def set_configs(response: Response, user: User_find_from_data):
116
 
117
- if not user.can_use("chat"):
118
- response = RedirectResponse(url='/hold')
119
- return response
120
-
121
- # Create config model
122
- user.configs = model.Configs(**user._data)
123
- user._session.configs = user.configs
124
- assisntat_prompt = user.update_user()
125
-
126
- # Create cookies and answer
127
- user._session.create_cookie(response)
128
- return {"success":True, "challenge": user._session.challenge, "assistantPrompt": assisntat_prompt}
129
-
130
- @app.post("/getToken")
131
- async def get_token(response: Response, user: User_find_from_data):
132
-
133
- # Generate api token
134
- user.create_cookie()
135
- if not user.can_use("chat"):
136
- return {"success":False, "redirect": "/hold"}
137
-
138
- # Create cookies and answer
139
- return {"success":True, "challenge": user._session.challenge}
140
-
141
- ##########################################################
142
-
143
- @app.post("/chat")
144
- async def chat_async(session: Session_find_from_data):
145
- chat = model.Chat(messages = session.data["messages"], personality=session.configs.assistant)
146
- if(len(chat.messages) < 1 or chat.messages[-1].content==""):
147
- log_module.logger(session.gid).warning("Empty message")
148
  raise HTTPException(
149
  status_code=status.HTTP_418_IM_A_TEAPOT,
150
- detail= "Nope"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  )
152
- return StreamingResponse(llm.streamer(chat, session), media_type="application/json")
153
-
154
-
155
- ########################## Static Pages ##########################
156
- @app.get("/privacy", response_class=HTMLResponse)
157
- async def privacy_policy(request: Request):
158
- return templates.TemplateResponse("PrivacyPolicy.html", {"request": request})
159
-
160
- @app.get("/hold")
161
- async def on_hold(request: Request, user: User = Depends(User.find_from_cookie)):
162
- if user.can_use("chat"):
163
- return RedirectResponse(url='/')
164
- return templates.TemplateResponse(
165
- "no_access.html", {
166
- "request": request,
167
- "description": bool(user.description)
168
- })
169
-
170
- @app.post("/hold")
171
- async def on_hold(request: Request, user: User = Depends(User.find_from_cookie)):
172
- if not user.description:
173
- form = await request.form()
174
- if message := form["message"].strip():
175
- user.update_description(message)
176
-
177
- return templates.TemplateResponse(
178
- "no_access.html", {
179
- "request": request,
180
- "description": bool(user.description)
181
- })
182
-
183
 
184
- ########################## Other ##########################
185
- # @app.get("/read_log", response_class=HTMLResponse)
186
- # async def read_log(request: Request, credentials: HTTPBasicCredentials = Depends(httpsecurity)):
187
- # if sha256(credentials.username.encode()).hexdigest()=="bc1c32d709aef061bbde4fc848421cdb933e8a9f391c3a089f2861ac0772c168" and security.authenticate_user(credentials):
188
- # log_module.log_write(credentials.username, "Log Accesado", "")
189
- # with open("logs/eventos.log", "r") as f:
190
- # return HTMLResponse(f.read())
191
- # log_module.log_write(credentials.username, "Intento acceder logs", f"{request.client.host}:{request.client.port} - {str(dict(request.headers))}")
192
- # raise HTTPException(status_code=404)
193
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, re, requests
6
+ from datetime import datetime, timedelta
7
+ import time
8
+ import jwt
9
+ import openai
10
+ from openai import error as openai_error
11
  from hashlib import sha256
12
+ import tiktoken
13
+ from supported import supp_langs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
+ app = FastAPI()
16
+ security = HTTPBasic()
17
+ tokenizer = tiktoken.encoding_for_model("gpt-3.5-turbo")
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):
26
+ with open("log.txt", "a") as f:
27
+ f.write(line)
28
+ f.write("\n")
29
 
30
+ def write_multi_line(list_text):
31
+ for line in list_text:
32
+ write_line(line)
33
+ write_line("---------------------------")
 
 
 
 
 
 
 
 
 
34
 
 
35
 
36
+ write_multi_line(["inicio"])
37
 
38
+ fecha_unix = str(int(time.time()))
39
 
40
+ JWT_SECRET = users["master"]
41
+ JWT_ALGORITHM = "HS256"
42
+ JWT_EXPIRATION_TIME_MINUTES = 30
43
+
44
+ def create_jwt_token(data):
45
+ to_encode = {"data": data}
46
+ expire = datetime.utcnow() + timedelta(minutes=JWT_EXPIRATION_TIME_MINUTES)
47
+ to_encode.update({"exp": expire})
48
+ encoded_jwt = jwt.encode(to_encode, JWT_SECRET, algorithm=JWT_ALGORITHM)
49
+ return encoded_jwt
50
+
51
+ async def validate_token(request: Request):
52
+ data = {}
53
+ try:
54
+ data = await request.json()
55
+ token = data.pop("token")
56
+ payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
57
+ data["token_data"] = payload["data"]
58
+ except:
59
+ raise HTTPException(status_code=404, detail="Datos no válidos")
60
+ return data
61
+
62
+ def authenticate_user(credentials: HTTPBasicCredentials):
63
+
64
+ password = credentials.username+credentials.password+users["master"]
65
+ password = sha256(password.encode('UTF-8')).hexdigest()
66
+
67
+ if credentials.username not in users or password != users[credentials.username]:
68
+ raise HTTPException(
69
+ status_code=status.HTTP_401_UNAUTHORIZED,
70
+ detail="Incorrect username or password",
71
+ headers={"WWW-Authenticate": "Basic"},
72
+ )
73
 
74
+ return True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
 
 
76
 
77
+ @app.get("/", response_class=HTMLResponse)
78
+ async def root(request: Request, credentials: HTTPBasicCredentials = Depends(security)):
79
+ if authenticate_user(credentials):
80
+ token = create_jwt_token({"user":credentials.username})
81
+ with open(os.path.join("static", "main.html")) as f:
82
+ return HTMLResponse(f.read().replace("{% token %}", token).replace("{% version %}", fecha_unix))
83
+
84
+ @app.get("/tabs", response_class=HTMLResponse)
85
+ async def root_tabs(request: Request, credentials: HTTPBasicCredentials = Depends(security)):
86
+ if authenticate_user(credentials):
87
+ token = create_jwt_token({"user":credentials.username})
88
+ with open(os.path.join("static", "main2.html")) as f:
89
+ return HTMLResponse(f.read().replace("{% token %}", token).replace("{% version %}", fecha_unix))
90
+
91
+
92
+ @app.post("/chat_stream")
93
+ async def chat_stream(data = Depends(validate_token)):
94
+ messages = data.get("messages", "")
95
+ if not messages:
96
+ print("Empty message")
97
+ error = "What??"
 
 
 
 
 
 
 
 
 
 
98
  raise HTTPException(
99
  status_code=status.HTTP_418_IM_A_TEAPOT,
100
+ detail= error
101
+ )
102
+ try:
103
+ token_length = len(tokenizer.encode(messages[-1]["content"]))
104
+ except:
105
+ token_length = len(messages[-1]["content"])
106
+ print("Error in token length")
107
+ print("Message:", messages[-1]["content"])
108
+
109
+ config = {
110
+ "temperature": float(data.get("config", []).get("temperature", 1)),
111
+ "frequency_penalty": float(data.get("config", []).get("frequency_penalty", 1)),
112
+ "presence_penalty": float(data.get("config", []).get("presence_penalty", 1))
113
+ }
114
+
115
+ try:
116
+ response = openai.ChatCompletion.create(
117
+ model="gpt-3.5-turbo",
118
+ messages=messages,
119
+ temperature=config["temperature"],
120
+ frequency_penalty=config["frequency_penalty"],
121
+ presence_penalty=config["presence_penalty"],
122
+ request_timeout = 25,
123
+ stream=True
124
+ )
125
+ except requests.exceptions.RequestException as e:
126
+ print("Timeout (requests)")
127
+ print(e)
128
+ raise HTTPException(
129
+ status_code=status.HTTP_408_REQUEST_TIMEOUT,
130
+ detail="Los servidores tardaron mucho en responder, puede haber sobrecarga en OpenAI, reintenta luego (error 1)"
131
+ )
132
+ except openai_error.APIConnectionError as e:
133
+ print("APIConnectionError")
134
+ print(e)
135
+ raise HTTPException(
136
+ status_code=status.HTTP_408_REQUEST_TIMEOUT,
137
+ detail="El servidor no respondió, puede haber sobrecarga en OpenAI, reintenta luego (error 2)"
138
+ )
139
+ except openai_error.Timeout as e:
140
+ print("Timeout (openai)")
141
+ print(e)
142
+ raise HTTPException(
143
+ status_code=status.HTTP_408_REQUEST_TIMEOUT,
144
+ detail="El servidor no respondió, puede haber sobrecarga en OpenAI, reintenta luego (error 3)"
145
+ )
146
+ except openai_error.InvalidRequestError as e:
147
+ print("Timeout (openai)")
148
+ print(e)
149
+ error = "El servidor no respondió, puede haber sobrecarga en OpenAI, reintenta luego (error 3)"
150
+ if "This model's maximum context length is 4097 tokens" in e.message:
151
+ error = "ChatGPT se gomitó 🤮, limpia el chat y reintenta."
152
+ raise HTTPException(
153
+ status_code=status.HTTP_408_REQUEST_TIMEOUT,
154
+ detail= error
155
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
 
157
+ token = create_jwt_token(data.pop("token_data"))
158
+
159
+ async def __streamer():
160
+ yield json.dumps({"object": "chat.token", "token": token})
161
+ #yield json.dumps({"object": "chat.user.length", "length": token_length})
162
+ tokens = 0
163
+ for chunk in response:
164
+ tokens += 1
165
+ yield json.dumps(chunk)
166
+ #yield({"object": "chat.assistant.length", "length": tokens})
167
+
168
+ return StreamingResponse(__streamer(), media_type="application/json")
169
+
170
+ @app.get("/read_log", response_class=HTMLResponse)
171
+ async def read_log(request: Request, credentials: HTTPBasicCredentials = Depends(security)):
172
+ if authenticate_user(credentials):
173
+ with open("log.txt", "r") as f:
174
+ return HTMLResponse(f.read())
175
+
176
+
177
+ @app.get("/mfdfastapi")
178
+ async def webhook_get(request: Request):
179
+
180
+ write_multi_line([
181
+ "GET mfdfastapi:",
182
+ "headers: {}".format(json.dumps(dict(request.headers.items()))),
183
+ "params : {}".format(json.dumps(dict(request.query_params.items()))),
184
+
185
+ ])
186
+
187
+ mode = request.query_params.get('hub.mode', "")
188
+ if mode=="subscribe":
189
+ challenge = request.query_params.get('hub.challenge', "")
190
+ verify_token = request.query_params.get('hub.verify_token', "")
191
+
192
+ if not mode or not challenge or not verify_token:
193
+ raise HTTPException(status_code=404, detail="Datos no válidos")
194
+
195
+ if verify_token != users["master"]:
196
+ raise HTTPException(status_code=404, detail="Datos no válidos")
197
+ return HTMLResponse(str(challenge))
198
+
199
+ @app.post("/mfdfastapi")
200
+ async def webhook_post(request: Request):
201
+ parametros_post = await request.json()
202
+ write_multi_line([
203
+ "POST mfdfastapi:",
204
+ "headers: {}".format(json.dumps(dict(request.headers.items()))),
205
+ "params : {}".format(json.dumps(dict(request.query_params.items()))),
206
+ "post : {}".format(json.dumps(parametros_post))
207
+ ])
208
+
209
+ return HTMLResponse("ok")
210
+
modules/chat_functions/__init__.py DELETED
@@ -1,22 +0,0 @@
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 DELETED
@@ -1,76 +0,0 @@
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. The input will return titles, links and a small resume of the content of the pages found, so it will not help with some task as translations.\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
- "introspection": {
46
- "type": "string",
47
- "description": "The user won't see this. Could you answer this without looking on google? what you answerd could be?",
48
- }
49
- },
50
- "required": ["search"],
51
- }
52
- }
53
- }
54
-
55
-
56
- def ejecutar(params, gid):
57
- busqueda = params["search"]
58
- try:
59
- service = build("customsearch", "v1", developerKey=api_key)
60
- results = service.cse().list(q=busqueda, cx=cse_id, num=5).execute()
61
- retorno = {}
62
- retorno["founds"] = []
63
- for result in results["items"]:
64
- if "title" not in result or "snippet" not in result: continue
65
- retorno["founds"].append({
66
- "title": result["title"],
67
- "link": result["link"],
68
- "snippet": result["snippet"]
69
- })
70
- log_module.logger(gid).info(f"Searh google {params}")
71
- return json.dumps(retorno)
72
- except Exception as e:
73
- log_module.logger(gid).error(f"Searh google failed: {repr(e)}")
74
- return json.dumps({"status": "api failed"})
75
-
76
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modules/chat_functions/leer_pagina_web.py DELETED
@@ -1,37 +0,0 @@
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 DELETED
@@ -1,49 +0,0 @@
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 DELETED
@@ -1,41 +0,0 @@
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 DELETED
@@ -1,82 +0,0 @@
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 DELETED
@@ -1,25 +0,0 @@
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 DELETED
@@ -1,375 +0,0 @@
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 DELETED
@@ -1,29 +0,0 @@
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 DELETED
@@ -1,18 +0,0 @@
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 DELETED
@@ -1,96 +0,0 @@
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 DELETED
@@ -1,41 +0,0 @@
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
-
26
-
27
- OPENAI_API_KEY=environ_get('OPENAI_API_KEY', True)
28
- USERS = json.loads(str(environ_get("USER_KEYS", True)).replace("\n", ""))
29
-
30
- JWT_SECRET = environ_get('JWT_SECRET', True)
31
-
32
- JWT_ALGORITHM = "HS256"
33
- JWT_EXPIRATION_TIME_MINUTES_API = 7*24*60
34
- JWT_EXPIRATION_TIME_MINUTES_VIEW = 7*24*60
35
-
36
- GOOGLE_CLIENT_ID = environ_get("GOOGLE_CLIENT_ID", True)
37
- GOOGLE_CLIENT_SECRET = environ_get("GOOGLE_CLIENT_SECRET", True)
38
- GOOGLE_API_KEY = environ_get("GOOGLE_API_KEY")
39
- GOOGLE_CSE_ID = environ_get("GOOGLE_CSE_ID")
40
-
41
- OAUTH_REDIRECT = os.environ.get("OAUTH_REDIRECT", True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,17 +1,7 @@
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.*
 
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
 
 
 
 
 
 
 
 
 
 
static/css/app.css CHANGED
@@ -1,12 +1,15 @@
 
 
1
  html,
2
  body {
3
  margin: 0;
4
  padding: 0;
5
  width: 100%;
6
- height: 100svh;
7
  font-family: Monospace;
8
  font-size: 15px;
9
  }
 
10
  body {
11
  display: flex;
12
  }
@@ -14,25 +17,59 @@ button{
14
  cursor: pointer;
15
  }
16
  .wrapper {
 
 
17
  background: #34495e;
18
  margin: 0;
19
  min-width: 320px;
20
  width: 100%;
21
- display: grid;
22
- grid-template-rows: 40px auto;
23
- height: 100%;
24
- padding: 0px 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  }
26
  .chat {
27
  --textarea: 0px;
28
  border-radius: 5px;
29
  display: block;
30
  width: 100%;
 
31
  overflow-y: scroll;
32
  overflow-x: hidden;
33
  background: rgb(161, 161, 161);
34
  padding: 10px 0;
35
- height: max( calc( 100svh - 120px - var(--textarea) ), calc(50svh - 90px) );
36
  }
37
 
38
  .chat .message {
@@ -82,7 +119,6 @@ button{
82
  border-radius: 3px;
83
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
84
  min-width: 40%;
85
- transition: 0.5s height;
86
  }
87
 
88
  .chat .message.me div p {
@@ -128,11 +164,11 @@ button{
128
  height: 30px;
129
  display: flex;
130
  border-radius: 5px;
131
- max-height: 50svh;
132
  }
133
 
134
  .input-box textarea,
135
  .input-box button {
 
136
  margin: 0;
137
  border: none;
138
  padding: 0 15px;
@@ -156,7 +192,7 @@ button{
156
  background-color: #ddd;
157
  background-repeat: no-repeat;
158
  background-position: center;
159
- border-left: solid 1px #555;
160
  }
161
  .input-box .input-send {
162
  background-image: url(/static/img/send.png);
@@ -164,89 +200,18 @@ button{
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
  }
173
  .input-box button:last-child{
174
  border-radius: 0 5px 5px 0;
175
  }
176
- .input-box button:disabled, .input-box textarea:disabled{
177
  background-color: #8b8b8b;
178
  border-color: #8b8b8b;
179
-
180
  }
181
 
182
  #message-template{
183
  display: none;
184
  }
185
-
186
- .loader-wrap {
187
- display: flex;
188
- }
189
- .loader {
190
- margin: auto;
191
- width: 48px;
192
- height: 48px;
193
- border: 3px dotted #476380;
194
- border-style: solid solid dotted dotted;
195
- border-radius: 50%;
196
- display: inline-block;
197
- position: relative;
198
- box-sizing: border-box;
199
- animation: rotation 2s linear infinite;
200
- }
201
- .loader.firststage{
202
- border: 3px dotted #49b359;
203
- border-style: solid solid dotted dotted;
204
- transition:all 1s;
205
- }
206
- .loader::after {
207
- content: '';
208
- box-sizing: border-box;
209
- position: absolute;
210
- left: 0;
211
- right: 0;
212
- top: 0;
213
- bottom: 0;
214
- margin: auto;
215
- border: 3px dotted #445464;
216
- border-style: solid solid dotted;
217
- width: 24px;
218
- height: 24px;
219
- border-radius: 50%;
220
- animation: rotationBack 1s linear infinite;
221
- transform-origin: center center;
222
- }
223
- .loader.firststage::after {
224
- border: 3px dotted #49b359;
225
- border-style: solid solid dotted;
226
- transition:all 1s;
227
- }
228
-
229
- @keyframes rotation {
230
- 0% {
231
- transform: rotate(0deg);
232
- }
233
- 100% {
234
- transform: rotate(360deg);
235
- }
236
- }
237
- @keyframes rotationBack {
238
- 0% {
239
- transform: rotate(0deg);
240
- }
241
- 100% {
242
- transform: rotate(-360deg);
243
- }
244
- }
245
- .loader-wrap ~ div {
246
- text-align: center;
247
- margin-top: 10px;
248
- }
249
- dialog{
250
- margin: auto;
251
- outline: none;
252
- }
 
1
+ @import "switch.css";
2
+
3
  html,
4
  body {
5
  margin: 0;
6
  padding: 0;
7
  width: 100%;
8
+ height: 100%;
9
  font-family: Monospace;
10
  font-size: 15px;
11
  }
12
+
13
  body {
14
  display: flex;
15
  }
 
17
  cursor: pointer;
18
  }
19
  .wrapper {
20
+ padding: 20px 20px 80px 20px;
21
+ border-bottom: 5px solid #222f3d;
22
  background: #34495e;
23
  margin: 0;
24
  min-width: 320px;
25
  width: 100%;
26
+ }
27
+ .submenu {
28
+ position: fixed;
29
+ top: 0;
30
+ left: 0;
31
+ width: fit-content;
32
+ height: fit-content;
33
+
34
+ }
35
+ .submenu .abrir{
36
+ width: 30px;
37
+ height: 30px;
38
+ background-image: url(/static/img/menu.svg);
39
+ background-color: #8093a5;
40
+ background-repeat: no-repeat;
41
+ background-position: center;
42
+ border-radius: 0px 0px 5px 0px;
43
+ border-bottom: solid 1px;
44
+ border-right: solid 1px;
45
+ position: fixed;
46
+ }
47
+ .submenu .configuracion{
48
+ width: calc( 100vw - 20px);
49
+ height: 0px;
50
+ background: #cbcbcb;
51
+ margin-left: 10px;
52
+ border-radius: 0px 0px 5px 5px;
53
+ top: -2px;
54
+ transition: box-shadow 0.5s, height 0.5s, top 0.5s;
55
+ }
56
+ .submenu .configuracion.desplegado{
57
+ top: 0px;
58
+ height: 300px;
59
+ border: solid 1px;
60
+ border-top: none;
61
+ box-shadow: 5px 5px 10px 2px #000000ad;
62
  }
63
  .chat {
64
  --textarea: 0px;
65
  border-radius: 5px;
66
  display: block;
67
  width: 100%;
68
+ height: calc( 100% - var(--textarea) );
69
  overflow-y: scroll;
70
  overflow-x: hidden;
71
  background: rgb(161, 161, 161);
72
  padding: 10px 0;
 
73
  }
74
 
75
  .chat .message {
 
119
  border-radius: 3px;
120
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
121
  min-width: 40%;
 
122
  }
123
 
124
  .chat .message.me div p {
 
164
  height: 30px;
165
  display: flex;
166
  border-radius: 5px;
 
167
  }
168
 
169
  .input-box textarea,
170
  .input-box button {
171
+ height: 100%;
172
  margin: 0;
173
  border: none;
174
  padding: 0 15px;
 
192
  background-color: #ddd;
193
  background-repeat: no-repeat;
194
  background-position: center;
195
+ /* border-left: solid 1px #555; */
196
  }
197
  .input-box .input-send {
198
  background-image: url(/static/img/send.png);
 
200
  .input-box .input-delete {
201
  background-image: url(/static/img/delete.png);
202
  }
 
 
 
203
  .input-box button:first-child{
204
  border-left: none;
205
  }
206
  .input-box button:last-child{
207
  border-radius: 0 5px 5px 0;
208
  }
209
+ .input-box button:disabled, .input-box textarea:disabled, .input-box input:disabled{
210
  background-color: #8b8b8b;
211
  border-color: #8b8b8b;
212
+ filter: contrast(0.5);
213
  }
214
 
215
  #message-template{
216
  display: none;
217
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/css/app2.css ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ .wrapper {
3
+ padding: 0px 20px 80px 20px !important;
4
+ border-bottom: 5px solid #222f3d;
5
+ background: #34495e;
6
+ margin: 0;
7
+ min-width: 320px;
8
+ width: 100%;
9
+ }
10
+
11
+ .tab {
12
+ overflow: hidden;
13
+ border: 1px solid #ccc;
14
+ background-color: #f1f1f1;
15
+ }
16
+
17
+ .tab button {
18
+ background-color: inherit;
19
+ float: left;
20
+ border: none;
21
+ outline: none;
22
+ cursor: pointer;
23
+ padding: 14px 16px;
24
+ transition: 0.3s;
25
+ }
26
+
27
+ .tab button:hover {
28
+ background-color: #ddd;
29
+ }
30
+
31
+ .tab button.active {
32
+ background-color: #ccc;
33
+ }
34
+
35
+ .tabcontent {
36
+ display: none;
37
+ padding: 6px 12px;
38
+ border: 1px solid #ccc;
39
+ border-top: none;
40
+ }
41
+
42
+ .tabcontent:first-child {
43
+ display: block;
44
+ }
45
+
46
+
47
+ .wrapper .tab {
48
+ overflow: hidden;
49
+ display: flex;
50
+ border-bottom: 1px solid #ccc;
51
+ background-color: #fff;
52
+ }
53
+
54
+ .wrapper .tab button {
55
+ background-color: transparent;
56
+ border: none;
57
+ outline: none;
58
+ cursor: pointer;
59
+ padding: 10px 15px;
60
+ font-size: 16px;
61
+ font-weight: bold;
62
+ color: #777;
63
+ transition: 0.3s;
64
+ }
65
+
66
+ .wrapper .tab button:hover {
67
+ background-color: #ddd;
68
+ }
69
+
70
+ .wrapper .tab button.active {
71
+ border-bottom: 2px solid #4285f4;
72
+ color: #4285f4;
73
+ }
74
+
75
+ /* Estilos para el contenido de los tabs */
76
+ .wrapper .tabcontent {
77
+ display: none;
78
+ padding: 20px;
79
+ border: 1px solid #ccc;
80
+ border-top: none;
81
+ }
82
+
83
+ .wrapper .tabcontent:first-child {
84
+ display: block;
85
+ }
static/css/menu.css DELETED
@@ -1,177 +0,0 @@
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/switch.css ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Estilos del botón para google*/
2
+
3
+ .switch {
4
+ position: relative;
5
+ display: inline-block;
6
+ min-width: 54px;
7
+ max-width: 54px;
8
+ height: 30px;
9
+ margin: auto;
10
+ }
11
+
12
+ .switch input {
13
+ opacity: 0;
14
+ width: 0;
15
+ height: 0;
16
+ }
17
+
18
+ .slider {
19
+ position: absolute;
20
+ cursor: pointer;
21
+ top: 0;
22
+ left: 0;
23
+ right: 0;
24
+ bottom: 0;
25
+ background-color: #a1a1a1;
26
+ -webkit-transition: .4s;
27
+ transition: .4s;
28
+ }
29
+
30
+ .slider:before {
31
+ position: absolute;
32
+ content: "";
33
+ height: 23px;
34
+ width: 23px;
35
+ left: 4px;
36
+ bottom: 4px;
37
+ background: no-repeat center/80% url(https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Google_%22G%22_Logo.svg/235px-Google_%22G%22_Logo.svg.png);
38
+ background-color: white;
39
+ -webkit-transition: .4s;
40
+ transition: .4s;
41
+ }
42
+
43
+ input:checked+.slider {
44
+ background-color: #3BBD96;
45
+ }
46
+
47
+ input:focus+.slider {
48
+ box-shadow: 0 0 1px #3BBD96;
49
+ }
50
+
51
+ input:checked+.slider:before {
52
+ -webkit-transform: translateX(23px);
53
+ -ms-transform: translateX(23px);
54
+ transform: translateX(23px);
55
+ box-shadow: 0 0 3px rgba(0, 0, 0, 5.5);
56
+ }
57
+
58
+ .slider.round {
59
+ border-radius: 34px;
60
+ }
61
+
62
+ .slider.round:before {
63
+ border-radius: 50%;
64
+ box-shadow: 0 0 3px rgba(0, 0, 0, 5.5);
65
+ background: no-repeat center/80% url(https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Google_%22G%22_Logo.svg/235px-Google_%22G%22_Logo.svg.png);
66
+ background-color: white;
67
+ }
68
+
69
+ .switch:has(input:disabled) {
70
+ background: #8b8b8b;
71
+ filter: contrast(0.5);
72
+ }
static/css/tabs.css DELETED
@@ -1,67 +0,0 @@
1
- .tabs {
2
- scrollbar-color: #34373d #ffffff00;
3
- scrollbar-width: 5px;
4
- overflow-x: scroll;
5
- padding-bottom: 5px;
6
- user-select: none;
7
- display: -webkit-inline-box;
8
- }
9
-
10
- .tabs::-webkit-scrollbar {
11
- height: 5px;
12
- }
13
- .tabs::-webkit-scrollbar-track {
14
- background: #ffffff00;
15
- }
16
- .tabs::-webkit-scrollbar-thumb {
17
- background-color: #34373d;
18
- border: none;
19
- }
20
-
21
- .tab {
22
- display: none;
23
- }
24
- .tab.active{
25
- display: grid;
26
- }
27
-
28
- .tab-switch, .tab-add {
29
- display: none;
30
- }
31
- .tab-label, .tab-label-add{
32
- display: flex;
33
- height: 30px;
34
- float: left;
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;
42
- width: 30px;
43
- color: #fff;
44
- border-radius: 15px;
45
- font-size: 30;
46
- text-align: center;
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;
67
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/favicon.png CHANGED
static/img/icon-t.png DELETED
Binary file (349 kB)
 
static/img/icon-w.png DELETED
Binary file (475 kB)
 
static/img/menu.svg CHANGED
static/js/SecurityHandler.js DELETED
@@ -1,59 +0,0 @@
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,381 +1,181 @@
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
  }
 
1
  class ChatGPT{
2
+ constructor(token){
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
+ $("#input-delete").click(()=> $(document).trigger("chat:limpiar"))
5
+
6
+ let fecha = new Date().toJSON().slice(0, 10);
7
+ this.definicion = "Te llamas Chatsito, eres un asistente de apoyo a los amigos de MIA, ";
8
+ this.definicion += "tu objetivo principal es responder preguntas y hacer sentir bien ";
9
+ this.definicion += "a tu interlocutor.\n";
10
+ this.definicion += "Responde de manera amistosa con un poco de comedia.\n";
11
+ this.definicion += "Knowledge cutoff: 2021-09-01\nCurrent date: "+fecha;
12
+ this.definicion = {role: "system", content: this.definicion, tokens: 100};
13
+
14
+ if (localStorage.getItem("convesacion") !== null) {
15
+ this.convesacion = JSON.parse(localStorage.getItem("convesacion"));
16
+ $(document).trigger("chat:cargar", this.convesacion);
17
+ }else{
18
+ this.convesacion = [this.definicion];
19
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
+ if (localStorage.getItem("config") !== null) {
22
+ this.config = JSON.parse(localStorage.getItem("config"));
23
+ }else{
24
+ this.config = {
25
+ temperature: 1.0,
26
+ frequency_penalty: 0.0,
27
+ presence_penalty: 0.0,
28
+ googleSearch: false
29
+ };
30
+ }
31
+ this.execStart = 0;
32
+ this.endpointChat = "/chat_stream";
33
+ this.token = token
34
+ // this.evCtx = document
35
+ this.reintentos = 0
36
+ $(document).on("chat:limpiar", () => {
37
+ this.limpiarConfig()
38
+ });
39
  $(document).on("chat:enviar", (event, params) => {
 
40
  this.reintentos = 0;
41
+ this.enviar(params);
42
  });
43
+ $(document).on("enviar:reintentar", (event, params) => {
44
+ this.reintentos++;
45
+ this.enviar(params.mensaje)
46
+ });
47
+ // Signaling
48
+ // los on se escuchan cuando suena lo que se està disparando.
49
+ $(document).on("accion:switch_change", (event, params) => {
50
+ this.config.googleSearch = params;
51
+ console.log(this.config);
52
+ })
53
+ $(document).on("enviar:error", (event, params) => this.reenviar(params));
54
+
55
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  }
57
 
58
+ limpiarConfig(){
59
+ localStorage.removeItem('convesacion');
60
+ localStorage.removeItem('config');
61
+ this.config = {
62
+ temperature: 1.0,
63
+ frequency_penalty: 0.0,
64
+ presence_penalty: 0.0
65
+ };
66
+ this.convesacion = [this.definicion];
67
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  }
69
 
70
+ reenviar(params){
71
+ if(params.status==404){
72
+ $(document).trigger("precarga:error",{jqXHR:params.jqXHR, status:params.status, error:params.error, execTime:params.execTime, mensaje:params.mensaje});
73
+ return
74
+ }
75
+ if(this.reintentos < 3 ){
76
+ $(document).trigger("enviar:enviar",{jqXHR:params.jqXHR, status:params.status, error:params.error, execTime:params.execTime, mensaje:params.mensaje});
 
 
 
 
 
 
 
 
 
 
77
 
78
+ }else{
79
+ $(document).trigger("precarga:error",{jqXHR:params.jqXHR, status:params.status, error:params.error, execTime:params.execTime, mensaje:params.mensaje});
 
 
 
 
 
80
  }
81
  }
82
 
83
+ enviar(mensaje){
84
+ // Configuración de variables base
 
 
85
  let tempMensajes = [];
86
+ let tokens = 0;
87
+
88
+ // Calculo de tokens y reconfiguración de mensajes para este caso
89
+ for(let i = 0; i < this.convesacion.length; i++){
90
+ let actMsg = this.convesacion[i];
91
  tempMensajes.push({role: actMsg.role, content: actMsg.content})
92
+ tokens += actMsg.tokens
93
  }
94
 
95
+ console.log("Enviando: ", mensaje);
96
+ tempMensajes.push({role: "user", content: mensaje});
97
+ this.convesacion.push({role: "user", content: mensaje});
98
+ this.convesacion.push({});
99
+ $(document).trigger("precarga:inicio", mensaje);
100
  let self = this;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  fetch(this.endpointChat, {
102
  method: "POST",
103
  body: JSON.stringify({
104
  messages: tempMensajes,
105
+ token: this.token,
106
+ config: this.config
107
+ }),
108
+ }).then(response => ({
109
+ rb: response.body,
110
+ rstat: response.status
111
+ })).then(({rb, rstat}) => {
112
+ if(!rb){
113
+ return false;
 
 
 
 
 
114
  }
115
+ const reader = rb.getReader();
116
+
117
+ return new ReadableStream({
118
+ start(controller) {
119
+ function push() {
120
+ reader.read().then(({done, value}) => {
121
+
122
+ if(done){
123
+ controller.close();
124
+ if(rstat==200){
125
+ $(document).trigger("precarga:fin");
126
+ localStorage.setItem("convesacion", JSON.stringify(self.convesacion))
127
+ console.log("terminado", self.convesacion[self.convesacion.length-1].content)
128
+ }else{
129
+ self.convesacion.pop()
130
+ $(document).trigger("enviar:error", {mensaje: mensaje})
131
+
132
+ }
133
+ return
134
+ }
135
+
136
+ let elements = new TextDecoder("utf-8").decode(value).split("}{");
137
+ let elLen = elements.length;
138
+ for(let i in elements){
139
+ let data = elements[i];
140
+ if(!data){
141
+ continue;
142
+ }
143
+
144
+ data =JSON.parse((i>0?"{":"") + data + (i<elLen-1?"}":""));
145
+
146
+ if(rstat==200){
147
+ if(data.object == "chat.token"){
148
+ self.token = data.token;
149
+ }else if(data.choices[0].hasOwnProperty("delta")){
150
+ let temp = data.choices[0].delta;
151
+ let key = Object.keys(temp)[0];
152
+ let elActual = self.convesacion[self.convesacion.length-1]
153
+ if(!elActual.hasOwnProperty(key)){elActual[key]="";}
154
+ elActual[key] += temp[key]
155
+ if(elActual.hasOwnProperty("content") && temp.content){
156
+ $(document).trigger("precarga:mensaje", temp.content);
157
+ }
158
+ }
159
+ }else{
160
+ $(document).trigger("precarga:error", {status: rstat, mensaje: data.detail});
161
+ }
162
+
163
+ }
164
+ push();
165
+ })
166
+ }
167
+
168
+ push();
169
+ },
170
+ });
171
+ }).then(data => {
172
  }).catch(err =>{
 
173
  console.log('Solicitud fallida', err)
 
 
174
  });
 
 
175
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
 
 
 
177
  }
178
 
 
 
 
179
 
180
 
181
  }
static/js/fingerprintv4.js DELETED
@@ -1,12 +0,0 @@
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/tabsHandler.js DELETED
@@ -1,26 +0,0 @@
1
- $(document).ready(function() {
2
- $(document).on("chat:creado",function(event){
3
- $(".tab-switch").off("change")
4
- $(".tab-switch").on("change", radioChanged)
5
- radioChanged()
6
- });
7
-
8
- $("#nuevoChat").on("click", (event) => {
9
- $(document).trigger("chat:crear");
10
- })
11
-
12
- })
13
-
14
- function radioChanged(){
15
- let tab = $(".tab");
16
- let tabActive = $(tab[$(".tab-label input:checked").val()]);
17
- let chat = tabActive.find(".chat")
18
-
19
-
20
- tab.removeClass("active")
21
- tabActive.addClass("active")
22
- tabActive.find("textarea").focus()
23
- if(chat.length>0){
24
- chat.scrollTop(chat[0].scrollHeight);
25
- }
26
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/js/windowHandler.js CHANGED
@@ -1,245 +1,179 @@
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);
138
- break;
139
- case "reemplazar":
140
- this.active.find("div p div:not(.loader-wrap)").remove();
141
- this.active.find("div p").append(temp)
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)
177
- }else{
178
- this.active.find("div p").html("El API no responde, la conexión pudo haberse caido")
179
- }
180
-
181
- switch(error.status | 0){
182
  case 404:
183
  this.active.addClass("error")
184
- break;
185
  case 408:
186
  this.active.addClass("warning")
187
- break;
188
  default:
189
- this.active.addClass("error")
190
-
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
-
245
- }
 
 
 
 
 
1
  class WindowHandler{
2
+ //en este se hacen los cambios visuales
3
+ constructor(){
4
+ this.chatbox = $("#chat");
5
+ this.template = $('<div class="message"><div><p></p></div></div>');
6
+ this.active = false;
7
+ this.mensaje = "";
 
 
 
 
 
 
 
 
 
8
 
9
+ // this.evCtx = document;
 
10
 
11
+ $(document).on("chat:enviar", (event, params) => this.recalcularTextarea());
12
+ $(document).on("chat:limpiar", () => this.limpiarChat());
13
 
14
+ $("#input-text").keypress((event) => {
15
+ if (!event.shiftKey && event.keyCode === 13) {
16
+ this.manejadorEnviar();
17
+ }});
18
+ $("#input-send").click(() => this.manejadorEnviar());
19
+ $("#input-text").keypress(() => this.recalcularTextarea());
20
+ $("#input-text").keyup(() => this.recalcularTextarea());
21
+ $("#input-text").keydown(() => this.recalcularTextarea());
22
 
 
 
 
23
 
24
+ $("#input-activar").change((event) => this.toggleSwitch(event));
 
 
 
 
 
 
25
 
26
+
27
+
28
+ this.cargarChat(chatH.convesacion);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
+ $(document).on("precarga:inicio", (event, params) => {
31
+ this.respuestaInicio(params)
 
 
 
 
 
32
  });
33
+ $(document).on("precarga:mensaje", (event, params) => {
34
+ this.respuestaMensaje(params)}
35
+ );
36
+ $(document).on("precarga:fin", (event, params) => {
37
+ this.respuestaFin()
38
+ });
39
+ $(document).on("precarga:error", (event, params) => {
40
+ this.respuestaError(params)
41
+ });
42
+
43
  }
44
 
45
+ limpiarChat(){
46
+ $("#input-text").val("");
47
+ this.recalcularTextarea();
48
+ $("#chat").html("");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  }
50
 
51
  manejadorEnviar(){
52
+ let mensaje = $("#input-text").val();
 
 
53
  if(mensaje==""){
54
  return false;
55
  }
56
+ $(document).trigger("chat:enviar", mensaje);
 
 
 
 
 
 
57
  }
58
 
59
+ recalcularTextarea(){
60
+ $(".input-box").css("height", "30px");
61
+ let height = parseInt(($(".input-text").prop('scrollHeight')+15)/15)*15;
62
+ $(".input-box").css("height", height+"px");
63
+ height -= 30;
64
+ $(".chat").css("--textarea", height+"px");
65
  }
66
 
67
+ procesarTexto(texto){
68
+ let segmentos = texto.split("```");
69
+ let resultado = "";
70
+
71
+ for(let i=0; i<segmentos.length;i++){
72
+ let textoActual = segmentos[i];
73
+ if(i%2==0){
74
+ resultado += textoActual.replace(/\n/g, "<br>").replace(/`(.*?)`/gm, "<b>$1</b>");
75
+ }else{
76
+ let temp = textoActual.split("\n",1);
77
+ resultado += "<pre><code class='language-";
78
+ resultado += temp[0].length>1?temp[0]:"none";
79
+ temp = $("<div></div>").text(textoActual.substr(temp[0].length)).html()
80
+ resultado += "'>"+temp+"</code></pre>";
81
+ }
82
  }
83
+
84
+ return resultado
 
 
 
 
 
 
 
 
 
 
85
  }
86
 
87
+ cargarChat(mensajes){
88
+ mensajes.forEach((mensaje) => {
89
+ if(mensaje.role!="system"){
90
+ let clone = this.template.clone();
91
+ let texto = mensaje.content;
92
+ if(mensaje.role=="user") {
93
+ clone.addClass("me");
94
+ clone.find("div p").text(texto.replace(/\n/g, "<br>"));
95
+ }else{
96
+ texto = this.procesarTexto(texto);
97
+ clone.find("div p").html(texto);
98
+ }
99
+ this.chatbox.append(clone);
100
+ this.active = clone;
101
+ Prism.highlightAllUnder(this.active[0])
102
+ this.active = false;
103
+ this.chatbox.scrollTop(this.chatbox[0].scrollHeight);
104
+ }
105
+ });
106
+ }
107
 
108
+ respuestaInicio(mensaje){
109
+ $("#input-text").val("");
110
+ $("button").prop("disabled", true);
111
+ $("textarea").prop("disabled", true);
112
+ $(".switch input").prop("disabled", true);
113
+ this.mensaje = ""
114
+ let clone = this.template.clone();
115
+ clone.addClass("me");
116
+ clone.find("div p").text(mensaje);
117
+ this.chatbox.append(clone);
118
 
119
+ clone = this.template.clone();
120
+ clone.find("div p").html("");
121
+ this.chatbox.append(clone);
122
+ this.active = clone;
123
+ this.chatbox.scrollTop(this.chatbox[0].scrollHeight);
124
+ }
125
 
126
+ respuestaMensaje(mensaje){
127
+ this.mensaje += mensaje
128
+ let html = this.active.find("div p").html();
129
+ html += mensaje.replace(/\n/g, "<br>").replace(/`([^`\w\W]+?)`/gm, "<b>$1</b>")
130
+ this.active.find("div p").html(html);
131
+ this.chatbox.scrollTop(this.chatbox[0].scrollHeight);
132
 
133
  }
134
 
135
+ respuestaFin(){
136
+ let msgProcesado = this.procesarTexto(this.mensaje);
137
+ this.mensaje = "";
138
+ $("button").prop("disabled", false);
139
+ $("textarea").prop("disabled", false);
140
+ $(".switch input").prop("disabled", false);
141
+ $("textarea").focus();
142
+ this.active.find("div p").html(msgProcesado);
143
+ Prism.highlightAllUnder(this.active[0]);
144
+ this.active = false;
145
+ this.chatbox.scrollTop(this.chatbox[0].scrollHeight);
146
+ }
147
+
148
  respuestaError(error){
149
+ $("button").prop("disabled", false);
150
+ $("textarea").prop("disabled", false);
151
+ $("textarea").focus();
152
+ this.active.find("div p").html(error.mensaje)
153
+ switch(error.status){
 
 
 
 
 
 
154
  case 404:
155
  this.active.addClass("error")
 
156
  case 408:
157
  this.active.addClass("warning")
 
158
  default:
159
+ this.active.addClass("warning")
 
160
  }
161
  this.active = false;
162
  this.chatbox.scrollTop(this.chatbox[0].scrollHeight)
 
 
 
 
 
 
 
163
 
 
 
 
 
 
 
 
164
 
 
 
 
 
 
 
 
165
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  }
167
 
168
+ toggleSwitch(event) {
169
+ //boton de switch para consultar con google.
170
+ let checkBox = event.currentTarget;
171
+ $(document).trigger("accion:switch_change", checkBox.checked);
172
+ if (checkBox.checked) {
173
+ console.log("Switch is turned on.");
174
+ } else {
175
+ console.log("Switch is turned off.");
176
+ }
177
+ }
178
+
179
+ }
static/main.html CHANGED
@@ -1,77 +1,42 @@
1
  <html>
2
- <head>
3
- <title> Chatbot </title>
4
- <meta name="viewport" content="width=device-width, interactive-widget=resizes-content">
5
- <link rel="shortcut icon" href="static/favicon.png" type="image/png">
6
- <link rel="stylesheet" href="static/css/app.css?v={% version %}">
7
- <link rel="stylesheet" href="static/css/tabs.css?v={% version %}">
8
-
9
- <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
10
- <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css" rel="stylesheet" />
11
- <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>
12
- <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>
13
- <script src="static/js/tabsHandler.js?v={% version %}"></script>
14
- <script src="static/js/windowHandler.js?v={% version %}"></script>
15
- <script src="static/js/chatHandler.js?v={% version %}"></script>
16
-
17
- </head>
18
- <body>
19
-
20
-
21
- <div class="wrapper">
22
- <div class="tabs">
23
- <label class="tab-label-add" >
24
- +
25
- <input type="button" class="tab-add" id="nuevoChat">
26
- </label>
27
- </div>
28
-
29
-
30
- </div>
31
-
32
-
33
- <div class="template">
34
- <label class="tab-label-template">
35
- <div>Tab </div>
36
- <input type="radio" name="tabs" class="tab-switch" value=0>
37
- </label>
38
- <div class="tab-template">
39
- <div class="chat"></div>
40
- <div class='input-box'>
41
- <textarea class='input-text' placeholder="Escribe aquí" type="text" autofocus=""></textarea>
42
- <button class='input-send' ></button>
43
- <button class='input-delete' ></button>
44
- </div>
45
- </div>
46
- </div>
47
-
48
- <dialog id="updates">
49
- <div> Cambios de la versión {% version %}:
50
- <ul>
51
- <li>El mensaje ya no crgará en tiempo real, sino que lo hará una vez el texto se haya mostrado completo</li>
52
- <li>Paran indicar como va la carga hay un nuevo icono, el mismo pasará a verde cuando se empieze a recibir la data
53
- <div class="loader-wrap"><span class="loader"></span><span class="loader firststage"></span></div>
54
- </li>
55
- <li>Se pueden tener multiples conversaciones ahora por los tabs (esto se almacena a nivel de dispositivo)</li>
56
- <li>La papelera ahora elimina la conversación, asi mismo pide confirmación para evitar borrar accidentalmente</li>
57
- <li>La conversación se mantiene en 16k tokens</li>
58
- <li>Se le procuró bajar la "tontez" al chatbot y ahora es más serio</li>
59
- <li>El chat consulta el estado de conexión</li>
60
- <li>Ahora existe este cuadrito que indica las actualizaciones</li>
61
- </ul>
62
- <p>Has click en cualquier lugar para cerrar este cuadro</p>
63
- </div>
64
- <dialog>
65
-
66
- <script>
67
- var cHand;
68
- $(document).ready(function() {
69
- cHand = new ChatGPT("{% token %}");
70
- });
71
- let versionLocal = localStorage.getItem("version")
72
- let versionRemota = "{% version %}"
73
-
74
-
75
- </script>
76
- </body>
77
  </html>
 
1
  <html>
2
+ <head>
3
+ <title> Chatbot </title>
4
+ <meta name="viewport" content="width=device-width">
5
+ <link rel="shortcut icon" href="static/favicon.png" type="image/png">
6
+ <link rel="stylesheet" href="static/css/app.css?v={% version %}">
7
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
8
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css" rel="stylesheet" />
9
+ <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>
10
+ <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>
11
+ <script src="static/js/chatHandler.js?v={% version %}"></script>
12
+ <script src="static/js/windowHandler.js?v={% version %}"></script>
13
+
14
+ </head>
15
+ <body>
16
+ <div class="wrapper pag1">
17
+ <div class="chat" id="chat"></div>
18
+ <div class='input-box'>
19
+ <textarea class='input-text' id='input-text' placeholder="Type something" type="text" autofocus=""></textarea>
20
+ <button class='input-send' id='input-send' ></button>
21
+ <label class="switch">
22
+ <input type="checkbox" id="input-activar">
23
+ <span class="slider round"></span>
24
+ </label>
25
+ <button class='input-delete' id='input-delete' ></button>
26
+ </div>
27
+ </div>
28
+
29
+
30
+
31
+ <script>
32
+ let windH = null;
33
+ let chatH = null;
34
+
35
+ $(document).ready(function() {
36
+ chatH = new ChatGPT("{% token %}");
37
+ windH = new WindowHandler();
38
+ });
39
+
40
+ </script>
41
+ </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  </html>
static/main2.html ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <html>
2
+ <head>
3
+ <title> Chatbot </title>
4
+ <meta name="viewport" content="width=device-width">
5
+ <link rel="shortcut icon" href="static/favicon.png" type="image/png">
6
+ <link rel="stylesheet" href="static/css/app.css?v={% version %}">
7
+ <link rel="stylesheet" href="static/css/app2.css?v={% version %}">
8
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
9
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css" rel="stylesheet" />
10
+ <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>
11
+ <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>
12
+ <script src="static/js/chatHandler.js?v={% version %}"></script>
13
+ <script src="static/js/windowHandler.js?v={% version %}"></script>
14
+
15
+ </head>
16
+ <body>
17
+
18
+ <div class="wrapper">
19
+ <div class="tab">
20
+ <button class="tablinks" onclick="openTab(event, 'tab1')">Tab 1</button>
21
+ <button class="tablinks" onclick="openTab(event, 'tab2')">Tab 2</button>
22
+ <button class="tablinks" onclick="openTab(event, 'tab3')">Tab 3</button>
23
+ </div>
24
+ <div id="tab1" class="tabcontent">
25
+ <div class="chat" id="chat1"></div>
26
+ <div class="input-box">
27
+ <textarea class="input-text" id="input-text1" placeholder="Type something" type="text" autofocus></textarea>
28
+ <button class="input-send" id="input-send1"></button>
29
+ <button class="input-delete" id="input-delete1"></button>
30
+ </div>
31
+ </div>
32
+ <div id="tab2" class="tabcontent">
33
+ <div class="chat" id="chat2"></div>
34
+ <div class="input-box">
35
+ <textarea class="input-text" id="input-text2" placeholder="Type something" type="text" autofocus></textarea>
36
+ <button class="input-send" id="input-send2"></button>
37
+ <button class="input-delete" id="input-delete2"></button>
38
+ </div>
39
+ </div>
40
+ <div id="tab3" class="tabcontent">
41
+ <div class="chat" id="chat3"></div>
42
+ <div class="input-box">
43
+ <textarea class="input-text" id="input-text3" placeholder="Type something" type="text" autofocus></textarea>
44
+ <button class="input-send" id="input-send3"></button>
45
+ <button class="input-delete" id="input-delete3"></button>
46
+ </div>
47
+ </div>
48
+ </div>
49
+
50
+
51
+
52
+ <script>
53
+ let windH = null;
54
+ let chatH = null;
55
+
56
+ $(document).ready(function() {
57
+ chatH = new ChatGPT("{% token %}");
58
+ windH = new WindowHandler();
59
+
60
+
61
+
62
+ });
63
+ function openTab(evt, tabName) {
64
+ var i, tabcontent, tablinks;
65
+
66
+ tabcontent = document.getElementsByClassName("tabcontent");
67
+ for (i = 0; i < tabcontent.length; i++) {
68
+ tabcontent[i].style.display = "none";
69
+ }
70
+
71
+ tablinks = document.getElementsByClassName("tablinks");
72
+ for (i = 0; i < tablinks.length; i++) {
73
+ tablinks[i].className = tablinks[i].className.replace(" active", "");
74
+ }
75
+
76
+ document.getElementById(tabName).style.display = "block";
77
+ evt.currentTarget.className += " active";
78
+ }
79
+ </script>
80
+ </body>
81
+ </html>
templates/PrivacyPolicy.html DELETED
@@ -1,203 +0,0 @@
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 DELETED
@@ -1,219 +0,0 @@
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 DELETED
@@ -1,197 +0,0 @@
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 DELETED
@@ -1,66 +0,0 @@
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
-