add db
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ credentials.json
|
|||||||
.zed
|
.zed
|
||||||
.DS_STORE
|
.DS_STORE
|
||||||
*.sqlite3
|
*.sqlite3
|
||||||
|
*.db
|
||||||
21
app.py
21
app.py
@@ -5,6 +5,7 @@ from fastapi.templating import Jinja2Templates
|
|||||||
from fastapi import Request
|
from fastapi import Request
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
|
from db import DB
|
||||||
from chain import RagChain
|
from chain import RagChain
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
|
|
||||||
@@ -16,7 +17,18 @@ app.mount("/static", StaticFiles(directory="static"), name="static")
|
|||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
def home(request: Request):
|
def home(request: Request):
|
||||||
return templates.TemplateResponse("index.html", {"request": request})
|
db = DB()
|
||||||
|
prompts = db.get_prompts()
|
||||||
|
|
||||||
|
return templates.TemplateResponse("index.html", {"request": request, "prompts": prompts})
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/prova")
|
||||||
|
def prova(request: Request):
|
||||||
|
cursor = DB()
|
||||||
|
prompts = cursor.get_prompts()
|
||||||
|
|
||||||
|
return prompts
|
||||||
|
|
||||||
|
|
||||||
@app.websocket("/ws")
|
@app.websocket("/ws")
|
||||||
@@ -40,8 +52,10 @@ async def websocket_endpoint(websocket: WebSocket):
|
|||||||
top_k=int(config.get("top_k", 40)),
|
top_k=int(config.get("top_k", 40)),
|
||||||
top_p=float(config.get("top_p", 0.0)),
|
top_p=float(config.get("top_p", 0.0)),
|
||||||
temperature=float(config.get("temperature", 0.0)),
|
temperature=float(config.get("temperature", 0.0)),
|
||||||
retriever_max_docs=int(config.get("retriever_max_docs", 40)),
|
retriever_max_docs=int(
|
||||||
reranker_max_results=int(config.get("reranker_max_results", 20)),
|
config.get("retriever_max_docs", 40)),
|
||||||
|
reranker_max_results=int(
|
||||||
|
config.get("reranker_max_results", 20)),
|
||||||
)
|
)
|
||||||
|
|
||||||
async for chunk in rag_chain.stream(message):
|
async for chunk in rag_chain.stream(message):
|
||||||
@@ -52,6 +66,7 @@ async def websocket_endpoint(websocket: WebSocket):
|
|||||||
"type": "report",
|
"type": "report",
|
||||||
"sources": rag_chain.getSources(),
|
"sources": rag_chain.getSources(),
|
||||||
"reranked_sources": rag_chain.getRankedSources(),
|
"reranked_sources": rag_chain.getRankedSources(),
|
||||||
|
"rephrased_question": rag_chain.getRephrasedQuestion(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
11
chain.py
11
chain.py
@@ -45,6 +45,7 @@ class RagChain:
|
|||||||
|
|
||||||
self._retriever_sources: list[dict] = []
|
self._retriever_sources: list[dict] = []
|
||||||
self._reranked_sources: list[dict] = []
|
self._reranked_sources: list[dict] = []
|
||||||
|
self._rephrased_question: str = ""
|
||||||
|
|
||||||
self._llm = ChatGoogleGenerativeAI(
|
self._llm = ChatGoogleGenerativeAI(
|
||||||
model=MODEL,
|
model=MODEL,
|
||||||
@@ -80,7 +81,7 @@ class RagChain:
|
|||||||
| question_rewrite_prompt
|
| question_rewrite_prompt
|
||||||
| self._llm
|
| self._llm
|
||||||
| StrOutputParser()
|
| StrOutputParser()
|
||||||
| RunnableLambda(self._log_rewritten_question)
|
| RunnableLambda(self._log_rephrased_question)
|
||||||
)
|
)
|
||||||
|
|
||||||
rag_chain = (
|
rag_chain = (
|
||||||
@@ -95,8 +96,9 @@ class RagChain:
|
|||||||
|
|
||||||
self._full_chain = question_rewrite_chain | rag_chain
|
self._full_chain = question_rewrite_chain | rag_chain
|
||||||
|
|
||||||
def _log_rewritten_question(self, rewritten_question: str) -> str:
|
def _log_rephrased_question(self, rephrased_question: str) -> str:
|
||||||
return rewritten_question
|
self._rephrased_question = rephrased_question
|
||||||
|
return rephrased_question
|
||||||
|
|
||||||
def _format_docs(self, question: str) -> str:
|
def _format_docs(self, question: str) -> str:
|
||||||
retrieved_docs = self._base_retriever.invoke(question)
|
retrieved_docs = self._base_retriever.invoke(question)
|
||||||
@@ -150,5 +152,8 @@ class RagChain:
|
|||||||
def getRankedSources(self) -> list[dict]:
|
def getRankedSources(self) -> list[dict]:
|
||||||
return list(self._reranked_sources)
|
return list(self._reranked_sources)
|
||||||
|
|
||||||
|
def getRephrasedQuestion(self) -> str:
|
||||||
|
return self._rephrased_question
|
||||||
|
|
||||||
def stream(self, message: str):
|
def stream(self, message: str):
|
||||||
return self._full_chain.astream(message)
|
return self._full_chain.astream(message)
|
||||||
|
|||||||
15
db.py
Normal file
15
db.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from typing import List
|
||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from models.orm import Prompt
|
||||||
|
|
||||||
|
|
||||||
|
class DB:
|
||||||
|
def __init__(self, db: str = "sqlite:///example.db"):
|
||||||
|
self.engine = create_engine(
|
||||||
|
db, connect_args={"check_same_thread": False})
|
||||||
|
|
||||||
|
def get_prompts(self) -> List[Prompt]:
|
||||||
|
with Session(self.engine) as session:
|
||||||
|
return session.query(Prompt).all()
|
||||||
17
models/orm.py
Normal file
17
models/orm.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from sqlalchemy import create_engine, Column, String, Text, Integer
|
||||||
|
from sqlalchemy.orm import declarative_base
|
||||||
|
|
||||||
|
Base = declarative_base()
|
||||||
|
|
||||||
|
|
||||||
|
class Prompt(Base):
|
||||||
|
__tablename__ = "prompts"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True)
|
||||||
|
name = Column(String, nullable=False)
|
||||||
|
text = Column(Text, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
engine = create_engine("sqlite:///example.db")
|
||||||
|
Base.metadata.create_all(engine)
|
||||||
6
models/validation.py
Normal file
6
models/validation.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class Prompt(BaseModel):
|
||||||
|
name: str
|
||||||
|
text: str
|
||||||
@@ -16,6 +16,8 @@ dependencies = [
|
|||||||
"python-dotenv>=1.2.1",
|
"python-dotenv>=1.2.1",
|
||||||
"fastapi>=0.129.0",
|
"fastapi>=0.129.0",
|
||||||
"fastapi[standard]",
|
"fastapi[standard]",
|
||||||
|
"pydantic>=2.12.5",
|
||||||
|
"sqlalchemy>=2.0.46",
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ mdurl==0.1.2
|
|||||||
multidict==6.7.1
|
multidict==6.7.1
|
||||||
mypy-extensions==1.1.0
|
mypy-extensions==1.1.0
|
||||||
nodeenv==1.10.0
|
nodeenv==1.10.0
|
||||||
nuitka==4.0.1
|
|
||||||
numexpr==2.14.1
|
numexpr==2.14.1
|
||||||
numpy==2.4.2
|
numpy==2.4.2
|
||||||
openai==2.21.0
|
openai==2.21.0
|
||||||
|
|||||||
@@ -1,274 +1,272 @@
|
|||||||
:root {
|
:root {
|
||||||
color-scheme: light;
|
color-scheme: light;
|
||||||
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
font-family:
|
||||||
|
system-ui,
|
||||||
|
-apple-system,
|
||||||
|
BlinkMacSystemFont,
|
||||||
|
"Segoe UI",
|
||||||
|
sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: #f7f8fa;
|
background: #f7f8fa;
|
||||||
color: #1f2937;
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app {
|
.app {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat {
|
.chat {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid #e5e7eb;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat__header {
|
.chat__header {
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
border-bottom: 1px solid #e5e7eb;
|
border-bottom: 1px solid #e5e7eb;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
background: #f9fafb;
|
background: #f9fafb;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat__settings {
|
.chat__settings {
|
||||||
padding: 6px 12px;
|
padding: 6px 12px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
border: 1px solid #d1d5db;
|
border: 1px solid #d1d5db;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
color: #1f2937;
|
color: #1f2937;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat__settings:hover {
|
.chat__settings:hover {
|
||||||
background: #f3f4f6;
|
background: #f3f4f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat__messages {
|
.chat__messages {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
max-width: 70%;
|
max-width: 70%;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message--out {
|
.message--out {
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
background: #2563eb;
|
background: #2563eb;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message--in {
|
.message--in {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
background: #f3f4f6;
|
background: #f3f4f6;
|
||||||
color: #111827;
|
color: #111827;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Typing / loading indicator */
|
/* Typing / loading indicator */
|
||||||
.message--loading {
|
.message--loading {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.typing-dot {
|
.typing-dot {
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #9ca3af;
|
background: #9ca3af;
|
||||||
animation: typing-bounce 1.2s infinite ease-in-out;
|
animation: typing-bounce 1.2s infinite ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.typing-dot:nth-child(1) {
|
.typing-dot:nth-child(1) {
|
||||||
animation-delay: 0s;
|
animation-delay: 0s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.typing-dot:nth-child(2) {
|
.typing-dot:nth-child(2) {
|
||||||
animation-delay: 0.2s;
|
animation-delay: 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.typing-dot:nth-child(3) {
|
.typing-dot:nth-child(3) {
|
||||||
animation-delay: 0.4s;
|
animation-delay: 0.4s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes typing-bounce {
|
@keyframes typing-bounce {
|
||||||
|
0%,
|
||||||
|
60%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
background: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
0%,
|
30% {
|
||||||
60%,
|
transform: translateY(-6px);
|
||||||
100% {
|
background: #6b7280;
|
||||||
transform: translateY(0);
|
}
|
||||||
background: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
30% {
|
|
||||||
transform: translateY(-6px);
|
|
||||||
background: #6b7280;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat__footer {
|
.chat__footer {
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
border-top: 1px solid #e5e7eb;
|
border-top: 1px solid #e5e7eb;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
background: #f9fafb;
|
background: #f9fafb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat__input {
|
.chat__input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid #d1d5db;
|
border: 1px solid #d1d5db;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
color: #111827;
|
color: #111827;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat__input::placeholder {
|
.chat__input::placeholder {
|
||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat__button {
|
.chat__button {
|
||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: none;
|
border: none;
|
||||||
background: #2563eb;
|
background: #2563eb;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat__button:disabled {
|
.chat__button:disabled {
|
||||||
background: #cbd5f5;
|
background: #cbd5f5;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat__button--secondary {
|
.chat__button--secondary {
|
||||||
background: #e5e7eb;
|
background: #e5e7eb;
|
||||||
color: #1f2937;
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat__button--secondary:hover {
|
.chat__button--secondary:hover {
|
||||||
background: #d1d5db;
|
background: #d1d5db;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat__status {
|
.chat__status {
|
||||||
padding: 8px 20px 0;
|
padding: 8px 20px 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer {
|
.drawer {
|
||||||
width: 320px;
|
width: 320px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-left: 1px solid #e5e7eb;
|
border-left: 1px solid #e5e7eb;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
transform: translateX(100%);
|
transform: translateX(100%);
|
||||||
transition: transform 0.25s ease;
|
transition: transform 0.25s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer--open {
|
.drawer--open {
|
||||||
transform: translateX(0%);
|
transform: translateX(0%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer__header {
|
.drawer__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer__title {
|
.drawer__title {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer__close {
|
.drawer__close {
|
||||||
border: 1px solid #d1d5db;
|
border: 1px solid #d1d5db;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer__form {
|
.drawer__form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer__field {
|
.drawer__field {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
grid-template-rows: auto auto;
|
grid-template-rows: auto auto;
|
||||||
gap: 6px 12px;
|
gap: 6px 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #1f2937;
|
color: #1f2937;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer__label {
|
.drawer__label {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer__range {
|
.drawer__range {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawer__value {
|
.drawer__value {
|
||||||
font-variant-numeric: tabular-nums;
|
font-variant-numeric: tabular-nums;
|
||||||
}
|
|
||||||
|
|
||||||
.drawer__hint {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #6b7280;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.drawer {
|
.drawer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
box-shadow: -12px 0 24px rgba(15, 23, 42, 0.08);
|
box-shadow: -12px 0 24px rgba(15, 23, 42, 0.08);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,14 +8,14 @@ const settingsToggle = document.getElementById("settings-toggle");
|
|||||||
const settingsDrawer = document.getElementById("settings-drawer");
|
const settingsDrawer = document.getElementById("settings-drawer");
|
||||||
const settingsClose = document.getElementById("settings-close");
|
const settingsClose = document.getElementById("settings-close");
|
||||||
const rangeInputs = settingsDrawer
|
const rangeInputs = settingsDrawer
|
||||||
? Array.from(settingsDrawer.querySelectorAll("input[type='range']"))
|
? Array.from(settingsDrawer.querySelectorAll("input[type='range']"))
|
||||||
: [];
|
: [];
|
||||||
const configInputs = {
|
const configInputs = {
|
||||||
topK: document.getElementById("config-top-k"),
|
topK: document.getElementById("config-top-k"),
|
||||||
topP: document.getElementById("config-top-p"),
|
topP: document.getElementById("config-top-p"),
|
||||||
temperature: document.getElementById("config-temperature"),
|
temperature: document.getElementById("config-temperature"),
|
||||||
retrieverMaxDocs: document.getElementById("config-retriever-max-docs"),
|
retrieverMaxDocs: document.getElementById("config-retriever-max-docs"),
|
||||||
rerankerMaxDocs: document.getElementById("config-reranker-max-docs"),
|
rerankerMaxDocs: document.getElementById("config-reranker-max-docs"),
|
||||||
};
|
};
|
||||||
|
|
||||||
const scheme = window.location.protocol === "https:" ? "wss" : "ws";
|
const scheme = window.location.protocol === "https:" ? "wss" : "ws";
|
||||||
@@ -26,178 +26,189 @@ let loadingBubble = null;
|
|||||||
let isAwaitingResponse = false;
|
let isAwaitingResponse = false;
|
||||||
|
|
||||||
const updateSendButtonState = () => {
|
const updateSendButtonState = () => {
|
||||||
if (!sendButtonEl) return;
|
if (!sendButtonEl) return;
|
||||||
sendButtonEl.disabled = isAwaitingResponse;
|
sendButtonEl.disabled = isAwaitingResponse;
|
||||||
};
|
};
|
||||||
|
|
||||||
const addMessage = (text, direction) => {
|
const addMessage = (text, direction) => {
|
||||||
const bubble = document.createElement("div");
|
const bubble = document.createElement("div");
|
||||||
bubble.className = `message message--${direction}`;
|
bubble.className = `message message--${direction}`;
|
||||||
bubble.textContent = text;
|
bubble.textContent = text;
|
||||||
messagesEl.appendChild(bubble);
|
messagesEl.appendChild(bubble);
|
||||||
messagesEl.scrollTop = messagesEl.scrollHeight;
|
messagesEl.scrollTop = messagesEl.scrollHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
const showLoadingBubble = () => {
|
const showLoadingBubble = () => {
|
||||||
if (loadingBubble) return;
|
if (loadingBubble) return;
|
||||||
loadingBubble = document.createElement("div");
|
loadingBubble = document.createElement("div");
|
||||||
loadingBubble.className = "message message--in message--loading";
|
loadingBubble.className = "message message--in message--loading";
|
||||||
loadingBubble.innerHTML =
|
loadingBubble.innerHTML =
|
||||||
'<span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span>';
|
'<span class="typing-dot"></span><span class="typing-dot"></span><span class="typing-dot"></span>';
|
||||||
messagesEl.appendChild(loadingBubble);
|
messagesEl.appendChild(loadingBubble);
|
||||||
messagesEl.scrollTop = messagesEl.scrollHeight;
|
messagesEl.scrollTop = messagesEl.scrollHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeLoadingBubble = () => {
|
const removeLoadingBubble = () => {
|
||||||
if (!loadingBubble) return;
|
if (!loadingBubble) return;
|
||||||
loadingBubble.remove();
|
loadingBubble.remove();
|
||||||
loadingBubble = null;
|
loadingBubble = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearMessages = () => {
|
const clearMessages = () => {
|
||||||
if (!messagesEl) return;
|
if (!messagesEl) return;
|
||||||
messagesEl.innerHTML = "";
|
messagesEl.innerHTML = "";
|
||||||
streamingBubble = null;
|
streamingBubble = null;
|
||||||
loadingBubble = null;
|
loadingBubble = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const connect = () => {
|
const connect = () => {
|
||||||
socket = new WebSocket(socketUrl);
|
socket = new WebSocket(socketUrl);
|
||||||
|
|
||||||
socket.addEventListener("open", () => {
|
socket.addEventListener("open", () => {
|
||||||
statusEl.textContent = "Connected";
|
statusEl.textContent = "Connected";
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.addEventListener("message", (event) => {
|
socket.addEventListener("message", (event) => {
|
||||||
let payload;
|
let payload;
|
||||||
try {
|
try {
|
||||||
payload = JSON.parse(event.data);
|
payload = JSON.parse(event.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addMessage(event.data, "in");
|
addMessage(event.data, "in");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.type === "start") {
|
if (payload.type === "start") {
|
||||||
// Keep the loading bubble visible until the first chunk arrives.
|
// Keep the loading bubble visible until the first chunk arrives.
|
||||||
// Just prepare the streaming bubble but don't append it yet.
|
// Just prepare the streaming bubble but don't append it yet.
|
||||||
streamingBubble = document.createElement("div");
|
streamingBubble = document.createElement("div");
|
||||||
streamingBubble.className = "message message--in";
|
streamingBubble.className = "message message--in";
|
||||||
streamingBubble.textContent = "";
|
streamingBubble.textContent = "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.type === "chunk") {
|
if (payload.type === "chunk") {
|
||||||
if (!streamingBubble) {
|
if (!streamingBubble) {
|
||||||
streamingBubble = document.createElement("div");
|
streamingBubble = document.createElement("div");
|
||||||
streamingBubble.className = "message message--in";
|
streamingBubble.className = "message message--in";
|
||||||
}
|
}
|
||||||
// First chunk: swap loading bubble for the streaming bubble.
|
// First chunk: swap loading bubble for the streaming bubble.
|
||||||
if (!streamingBubble.isConnected) {
|
if (!streamingBubble.isConnected) {
|
||||||
removeLoadingBubble();
|
|
||||||
messagesEl.appendChild(streamingBubble);
|
|
||||||
}
|
|
||||||
streamingBubble.textContent += payload.data ?? "";
|
|
||||||
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (payload.type === "report") {
|
|
||||||
console.log("%cSOURCES", 'border: 2px solid red; padding: 1em; color: red; font-size: 14px; font-weight: bold;')
|
|
||||||
console.log(payload.sources);
|
|
||||||
console.log("%cRE-RANKED SOURCES", 'border: 2px solid red; padding: 1em; color: red; font-size: 14px; font-weight: bold;')
|
|
||||||
console.log(payload.reranked_sources);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (payload.type === "end") {
|
|
||||||
// Safety net: remove loading bubble if no chunks were ever received.
|
|
||||||
removeLoadingBubble();
|
|
||||||
streamingBubble = null;
|
|
||||||
isAwaitingResponse = false;
|
|
||||||
updateSendButtonState();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.addEventListener("close", () => {
|
|
||||||
statusEl.textContent = "Disconnected";
|
|
||||||
removeLoadingBubble();
|
removeLoadingBubble();
|
||||||
isAwaitingResponse = false;
|
messagesEl.appendChild(streamingBubble);
|
||||||
updateSendButtonState();
|
}
|
||||||
});
|
streamingBubble.textContent += payload.data ?? "";
|
||||||
|
messagesEl.scrollTop = messagesEl.scrollHeight;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
socket.addEventListener("error", () => {
|
if (payload.type === "report") {
|
||||||
statusEl.textContent = "Connection error";
|
console.log(
|
||||||
removeLoadingBubble();
|
"%cREPHRASED QUESTION",
|
||||||
isAwaitingResponse = false;
|
"border: 2px solid red; padding: 1em; color: red; font-size: 14px; font-weight: bold;",
|
||||||
updateSendButtonState();
|
);
|
||||||
});
|
console.log(payload.rephrased_question);
|
||||||
|
console.log(
|
||||||
|
"%cSOURCES",
|
||||||
|
"border: 2px solid red; padding: 1em; color: red; font-size: 14px; font-weight: bold;",
|
||||||
|
);
|
||||||
|
console.log(payload.sources);
|
||||||
|
console.log(
|
||||||
|
"%cRE-RANKED SOURCES",
|
||||||
|
"border: 2px solid red; padding: 1em; color: red; font-size: 14px; font-weight: bold;",
|
||||||
|
);
|
||||||
|
console.log(payload.reranked_sources);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload.type === "end") {
|
||||||
|
// Safety net: remove loading bubble if no chunks were ever received.
|
||||||
|
removeLoadingBubble();
|
||||||
|
streamingBubble = null;
|
||||||
|
isAwaitingResponse = false;
|
||||||
|
updateSendButtonState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener("close", () => {
|
||||||
|
statusEl.textContent = "Disconnected";
|
||||||
|
removeLoadingBubble();
|
||||||
|
isAwaitingResponse = false;
|
||||||
|
updateSendButtonState();
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.addEventListener("error", () => {
|
||||||
|
statusEl.textContent = "Connection error";
|
||||||
|
removeLoadingBubble();
|
||||||
|
isAwaitingResponse = false;
|
||||||
|
updateSendButtonState();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateDrawerValue = (input) => {
|
const updateDrawerValue = (input) => {
|
||||||
const output = input.parentElement?.querySelector(".drawer__value");
|
const output = input.parentElement?.querySelector(".drawer__value");
|
||||||
if (!output) return;
|
if (!output) return;
|
||||||
output.textContent = input.value;
|
output.textContent = input.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
rangeInputs.forEach((input) => {
|
rangeInputs.forEach((input) => {
|
||||||
updateDrawerValue(input);
|
updateDrawerValue(input);
|
||||||
input.addEventListener("input", () => updateDrawerValue(input));
|
input.addEventListener("input", () => updateDrawerValue(input));
|
||||||
});
|
});
|
||||||
|
|
||||||
const closeDrawer = () => {
|
const closeDrawer = () => {
|
||||||
settingsDrawer?.classList.remove("drawer--open");
|
settingsDrawer?.classList.remove("drawer--open");
|
||||||
if (settingsDrawer) settingsDrawer.setAttribute("aria-hidden", "true");
|
if (settingsDrawer) settingsDrawer.setAttribute("aria-hidden", "true");
|
||||||
if (settingsToggle) settingsToggle.setAttribute("aria-expanded", "false");
|
if (settingsToggle) settingsToggle.setAttribute("aria-expanded", "false");
|
||||||
};
|
};
|
||||||
|
|
||||||
const openDrawer = () => {
|
const openDrawer = () => {
|
||||||
settingsDrawer?.classList.add("drawer--open");
|
settingsDrawer?.classList.add("drawer--open");
|
||||||
if (settingsDrawer) settingsDrawer.setAttribute("aria-hidden", "false");
|
if (settingsDrawer) settingsDrawer.setAttribute("aria-hidden", "false");
|
||||||
if (settingsToggle) settingsToggle.setAttribute("aria-expanded", "true");
|
if (settingsToggle) settingsToggle.setAttribute("aria-expanded", "true");
|
||||||
};
|
};
|
||||||
|
|
||||||
settingsToggle?.addEventListener("click", () => {
|
settingsToggle?.addEventListener("click", () => {
|
||||||
if (settingsDrawer?.classList.contains("drawer--open")) {
|
if (settingsDrawer?.classList.contains("drawer--open")) {
|
||||||
closeDrawer();
|
closeDrawer();
|
||||||
} else {
|
} else {
|
||||||
openDrawer();
|
openDrawer();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
settingsClose?.addEventListener("click", closeDrawer);
|
settingsClose?.addEventListener("click", closeDrawer);
|
||||||
|
|
||||||
clearEl?.addEventListener("click", () => {
|
clearEl?.addEventListener("click", () => {
|
||||||
clearMessages();
|
clearMessages();
|
||||||
inputEl?.focus();
|
inputEl?.focus();
|
||||||
updateSendButtonState();
|
updateSendButtonState();
|
||||||
});
|
});
|
||||||
|
|
||||||
inputEl?.addEventListener("input", updateSendButtonState);
|
inputEl?.addEventListener("input", updateSendButtonState);
|
||||||
|
|
||||||
formEl.addEventListener("submit", (event) => {
|
formEl.addEventListener("submit", (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const text = inputEl.value.trim();
|
const text = inputEl.value.trim();
|
||||||
if (!text) return;
|
if (!text) return;
|
||||||
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
if (!socket || socket.readyState !== WebSocket.OPEN) {
|
||||||
addMessage("Not connected.", "in");
|
addMessage("Not connected.", "in");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isAwaitingResponse) return;
|
if (isAwaitingResponse) return;
|
||||||
const config = {
|
const config = {
|
||||||
top_k: Number(configInputs.topK?.value ?? 0),
|
top_k: Number(configInputs.topK?.value ?? 0),
|
||||||
top_p: Number(configInputs.topP?.value ?? 0),
|
top_p: Number(configInputs.topP?.value ?? 0),
|
||||||
temperature: Number(configInputs.temperature?.value ?? 0),
|
temperature: Number(configInputs.temperature?.value ?? 0),
|
||||||
retriever_max_docs: Number(configInputs.retrieverMaxDocs?.value ?? 0),
|
retriever_max_docs: Number(configInputs.retrieverMaxDocs?.value ?? 0),
|
||||||
reranker_max_results: Number(configInputs.rerankerMaxDocs?.value ?? 0),
|
reranker_max_results: Number(configInputs.rerankerMaxDocs?.value ?? 0),
|
||||||
};
|
};
|
||||||
addMessage(text, "out");
|
addMessage(text, "out");
|
||||||
socket.send(JSON.stringify({ message: text, config }));
|
socket.send(JSON.stringify({ message: text, config }));
|
||||||
inputEl.value = "";
|
inputEl.value = "";
|
||||||
inputEl.focus();
|
inputEl.focus();
|
||||||
isAwaitingResponse = true;
|
isAwaitingResponse = true;
|
||||||
updateSendButtonState();
|
updateSendButtonState();
|
||||||
showLoadingBubble();
|
showLoadingBubble();
|
||||||
});
|
});
|
||||||
|
|
||||||
connect();
|
connect();
|
||||||
|
|||||||
@@ -1,72 +1,143 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
<head>
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>AKERN Assistant</title>
|
<title>AKERN Assistant</title>
|
||||||
<link rel="stylesheet" href="/static/css/chat.css" />
|
<link rel="stylesheet" href="/static/css/chat.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="app" aria-label="Chat application">
|
<div class="app" aria-label="Chat application">
|
||||||
<section class="chat" aria-label="Chat">
|
<section class="chat" aria-label="Chat">
|
||||||
<div class="chat__header">
|
<div class="chat__header">
|
||||||
<span>AKERN Assistant</span>
|
<span>AKERN Assistant</span>
|
||||||
<button class="chat__settings" id="settings-toggle" type="button" aria-haspopup="dialog"
|
<button
|
||||||
aria-expanded="false" aria-controls="settings-drawer">Configurazione</button>
|
class="chat__settings"
|
||||||
</div>
|
id="settings-toggle"
|
||||||
<div class="chat__messages" id="messages"></div>
|
type="button"
|
||||||
<div class="chat__status" id="status">Connecting…</div>
|
aria-haspopup="dialog"
|
||||||
<form class="chat__footer" id="chat-form">
|
aria-expanded="false"
|
||||||
<input class="chat__input" id="chat-input" type="text" placeholder="Type a message" autocomplete="off"
|
aria-controls="settings-drawer"
|
||||||
required />
|
>
|
||||||
<button class="chat__button" type="submit">Send</button>
|
Configurazione
|
||||||
<button class="chat__button chat__button--secondary" id="chat-clear" type="button">Clear</button>
|
</button>
|
||||||
</form>
|
</div>
|
||||||
</section>
|
<div class="chat__messages" id="messages"></div>
|
||||||
|
<div class="chat__status" id="status">Connecting…</div>
|
||||||
|
<form class="chat__footer" id="chat-form">
|
||||||
|
<input
|
||||||
|
class="chat__input"
|
||||||
|
id="chat-input"
|
||||||
|
type="text"
|
||||||
|
placeholder="Type a message"
|
||||||
|
autocomplete="off"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<button class="chat__button" type="submit">Send</button>
|
||||||
|
<button
|
||||||
|
class="chat__button chat__button--secondary"
|
||||||
|
id="chat-clear"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Clear
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
<aside class="drawer" id="settings-drawer" aria-label="Configurazione" aria-hidden="true">
|
<aside
|
||||||
<div class="drawer__header">
|
class="drawer"
|
||||||
<h2 class="drawer__title">Configurazione</h2>
|
id="settings-drawer"
|
||||||
<button class="drawer__close" id="settings-close" type="button" aria-label="Chiudi">✕</button>
|
aria-label="Configurazione"
|
||||||
</div>
|
aria-hidden="true"
|
||||||
<form class="drawer__form">
|
>
|
||||||
<label class="drawer__field">
|
<div class="drawer__header">
|
||||||
<span class="drawer__label">top_k</span>
|
<h2 class="drawer__title">Configurazione</h2>
|
||||||
<input class="drawer__range" id="config-top-k" type="range" min="0" max="100" step="1" value="40" />
|
<button
|
||||||
<output class="drawer__value">40</output>
|
class="drawer__close"
|
||||||
</label>
|
id="settings-close"
|
||||||
<label class="drawer__field">
|
type="button"
|
||||||
<span class="drawer__label">top_p</span>
|
aria-label="Chiudi"
|
||||||
<input class="drawer__range" id="config-top-p" type="range" min="0" max="1" step="0.1"
|
>
|
||||||
value="0.0" />
|
✕
|
||||||
<output class="drawer__value">0.0</output>
|
</button>
|
||||||
</label>
|
</div>
|
||||||
<label class="drawer__field">
|
<form class="drawer__form">
|
||||||
<span class="drawer__label">temperature</span>
|
<label class="drawer__field">
|
||||||
<input class="drawer__range" id="config-temperature" type="range" min="0" max="1.5" step="0.1"
|
<span class="drawer__label">top_k</span>
|
||||||
value="0.0" />
|
<input
|
||||||
<output class="drawer__value">0.0</output>
|
class="drawer__range"
|
||||||
</label>
|
id="config-top-k"
|
||||||
<label class="drawer__field">
|
type="range"
|
||||||
<span class="drawer__label">retriever max docs</span>
|
min="0"
|
||||||
<input class="drawer__range" id="config-retriever-max-docs" type="range" min="5" max="100" step="1"
|
max="100"
|
||||||
value="40" />
|
step="1"
|
||||||
<output class="drawer__value">40</output>
|
value="40"
|
||||||
</label>
|
/>
|
||||||
<label class="drawer__field">
|
<output class="drawer__value">40</output>
|
||||||
<span class="drawer__label">reranker max docs</span>
|
</label>
|
||||||
<input class="drawer__range" id="config-reranker-max-docs" type="range" min="5" max="100" step="1"
|
<label class="drawer__field">
|
||||||
value="20" />
|
<span class="drawer__label">top_p</span>
|
||||||
<output class="drawer__value">20</output>
|
<input
|
||||||
</label>
|
class="drawer__range"
|
||||||
<p class="drawer__hint">Solo frontend, nessuna logica applicata.</p>
|
id="config-top-p"
|
||||||
</form>
|
type="range"
|
||||||
</aside>
|
min="0"
|
||||||
|
max="1"
|
||||||
|
step="0.1"
|
||||||
|
value="0.0"
|
||||||
|
/>
|
||||||
|
<output class="drawer__value">0.0</output>
|
||||||
|
</label>
|
||||||
|
<label class="drawer__field">
|
||||||
|
<span class="drawer__label">temperature</span>
|
||||||
|
<input
|
||||||
|
class="drawer__range"
|
||||||
|
id="config-temperature"
|
||||||
|
type="range"
|
||||||
|
min="0"
|
||||||
|
max="1.5"
|
||||||
|
step="0.1"
|
||||||
|
value="0.0"
|
||||||
|
/>
|
||||||
|
<output class="drawer__value">0.0</output>
|
||||||
|
</label>
|
||||||
|
<label class="drawer__field">
|
||||||
|
<span class="drawer__label">retriever max docs</span>
|
||||||
|
<input
|
||||||
|
class="drawer__range"
|
||||||
|
id="config-retriever-max-docs"
|
||||||
|
type="range"
|
||||||
|
min="5"
|
||||||
|
max="100"
|
||||||
|
step="1"
|
||||||
|
value="40"
|
||||||
|
/>
|
||||||
|
<output class="drawer__value">40</output>
|
||||||
|
</label>
|
||||||
|
<label class="drawer__field">
|
||||||
|
<span class="drawer__label">reranker max docs</span>
|
||||||
|
<input
|
||||||
|
class="drawer__range"
|
||||||
|
id="config-reranker-max-docs"
|
||||||
|
type="range"
|
||||||
|
min="5"
|
||||||
|
max="100"
|
||||||
|
step="1"
|
||||||
|
value="20"
|
||||||
|
/>
|
||||||
|
<output class="drawer__value">20</output>
|
||||||
|
</label>
|
||||||
|
<label for="drawer__field">
|
||||||
|
<span class="drawer__label">Prompts</span>
|
||||||
|
<!-- TODO -->
|
||||||
|
<!-- Here i need a select -->
|
||||||
|
<!-- {% for prompt in prompts %} {{ prompt.name }} {{ prompt.id }} {% endfor %} -->
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="/static/js/chat.js" defer></script>
|
<script src="/static/js/chat.js" defer></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
7
uv.lock
generated
7
uv.lock
generated
@@ -109,7 +109,9 @@ dependencies = [
|
|||||||
{ name = "langchain-google-genai" },
|
{ name = "langchain-google-genai" },
|
||||||
{ name = "langchain-google-vertexai" },
|
{ name = "langchain-google-vertexai" },
|
||||||
{ name = "langchain-openai" },
|
{ name = "langchain-openai" },
|
||||||
|
{ name = "pydantic" },
|
||||||
{ name = "python-dotenv" },
|
{ name = "python-dotenv" },
|
||||||
|
{ name = "sqlalchemy" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dev-dependencies]
|
[package.dev-dependencies]
|
||||||
@@ -129,7 +131,9 @@ requires-dist = [
|
|||||||
{ name = "langchain-google-genai", specifier = ">=4.2.0" },
|
{ name = "langchain-google-genai", specifier = ">=4.2.0" },
|
||||||
{ name = "langchain-google-vertexai", specifier = ">=3.2.0" },
|
{ name = "langchain-google-vertexai", specifier = ">=3.2.0" },
|
||||||
{ name = "langchain-openai", specifier = ">=1.1.9" },
|
{ name = "langchain-openai", specifier = ">=1.1.9" },
|
||||||
|
{ name = "pydantic", specifier = ">=2.12.5" },
|
||||||
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
||||||
|
{ name = "sqlalchemy", specifier = ">=2.0.46" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
[package.metadata.requires-dev]
|
||||||
@@ -920,6 +924,7 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/ec/ab/d26750f2b7242c2b90ea2ad71de70cfcd73a948a49513188a0fc0d6fc15a/greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", size = 275205, upload-time = "2026-01-23T15:30:24.556Z" },
|
{ url = "https://files.pythonhosted.org/packages/ec/ab/d26750f2b7242c2b90ea2ad71de70cfcd73a948a49513188a0fc0d6fc15a/greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", size = 275205, upload-time = "2026-01-23T15:30:24.556Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/10/d3/be7d19e8fad7c5a78eeefb2d896a08cd4643e1e90c605c4be3b46264998f/greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", size = 599284, upload-time = "2026-01-23T16:00:58.584Z" },
|
{ url = "https://files.pythonhosted.org/packages/10/d3/be7d19e8fad7c5a78eeefb2d896a08cd4643e1e90c605c4be3b46264998f/greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", size = 599284, upload-time = "2026-01-23T16:00:58.584Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/ae/21/fe703aaa056fdb0f17e5afd4b5c80195bbdab701208918938bd15b00d39b/greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", size = 610274, upload-time = "2026-01-23T16:05:29.312Z" },
|
{ url = "https://files.pythonhosted.org/packages/ae/21/fe703aaa056fdb0f17e5afd4b5c80195bbdab701208918938bd15b00d39b/greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", size = 610274, upload-time = "2026-01-23T16:05:29.312Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/06/00/95df0b6a935103c0452dad2203f5be8377e551b8466a29650c4c5a5af6cc/greenlet-3.3.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:12184c61e5d64268a160226fb4818af4df02cfead8379d7f8b99a56c3a54ff3e", size = 624375, upload-time = "2026-01-23T16:15:55.915Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/cb/86/5c6ab23bb3c28c21ed6bebad006515cfe08b04613eb105ca0041fecca852/greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", size = 612904, upload-time = "2026-01-23T15:32:52.317Z" },
|
{ url = "https://files.pythonhosted.org/packages/cb/86/5c6ab23bb3c28c21ed6bebad006515cfe08b04613eb105ca0041fecca852/greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", size = 612904, upload-time = "2026-01-23T15:32:52.317Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/f3/7949994264e22639e40718c2daf6f6df5169bf48fb038c008a489ec53a50/greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", size = 1567316, upload-time = "2026-01-23T16:04:23.316Z" },
|
{ url = "https://files.pythonhosted.org/packages/c2/f3/7949994264e22639e40718c2daf6f6df5169bf48fb038c008a489ec53a50/greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", size = 1567316, upload-time = "2026-01-23T16:04:23.316Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/8d/6e/d73c94d13b6465e9f7cd6231c68abde838bb22408596c05d9059830b7872/greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", size = 1636549, upload-time = "2026-01-23T15:33:48.643Z" },
|
{ url = "https://files.pythonhosted.org/packages/8d/6e/d73c94d13b6465e9f7cd6231c68abde838bb22408596c05d9059830b7872/greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", size = 1636549, upload-time = "2026-01-23T15:33:48.643Z" },
|
||||||
@@ -928,6 +933,7 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" },
|
{ url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" },
|
{ url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" },
|
{ url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e2/89/b95f2ddcc5f3c2bc09c8ee8d77be312df7f9e7175703ab780f2014a0e781/greenlet-3.3.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3e0f3878ca3a3ff63ab4ea478585942b53df66ddde327b59ecb191b19dbbd62d", size = 671455, upload-time = "2026-01-23T16:15:57.232Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" },
|
{ url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" },
|
{ url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" },
|
{ url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" },
|
||||||
@@ -936,6 +942,7 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" },
|
{ url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" },
|
{ url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" },
|
{ url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7c/25/c51a63f3f463171e09cb586eb64db0861eb06667ab01a7968371a24c4f3b/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b9721549a95db96689458a1e0ae32412ca18776ed004463df3a9299c1b257ab", size = 662574, upload-time = "2026-01-23T16:15:58.364Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" },
|
{ url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" },
|
{ url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" },
|
{ url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" },
|
||||||
|
|||||||
Reference in New Issue
Block a user