Spaces:
Build error
V.0.2.0 (#23)
Browse files* Bugfix: Not redirecting to page URL the sign-in is initiated from.
* Added base for switching between LLMs
* Update run.py to use environment settings
* Updated Context Prompt for Chat
* Updated README
* Updated auth with supabase backend & working SGID info retrieval & session checking
* Added proper auth checking for backend api endpoints
* Updated frontend functions to pass AccessToken in request header
* Bugfix: Slicing results only when results is not empty
* Profile Page Placeholder
* Package Updates
* Update gitignore
* Removed debug log of response sources
* Updated gitignore
* Updated example.env
* Added option for storing Vector DB in Supabase pgvector
* Fixed loading remote vector DB
* Added Disclaimer to Footer & Dialog
* Moved Disclaimer into input container
* Updated frontend for chat with option to select between PSSCOC & EIR docs
* Updated Search frontend with options to search in PSSCOC & EIR docs + Refactored coded
* Updated About Page
* Increased container width
* Update packages & fixed linting
* Updated backend to use remote vector DB and improved context prompt
* Updated function to send selected document set in req body
* Updated example .env
* Updated to use env val for api key header name
* Make supabase URL env var non public
* Added supabase to healthcheck
* Fixed query wrong count
* Updated Profile page to pull data from database & skeleton loading
* Fixed bug not able to sign-in when no callbackUrl is set
* Removed previous feat, added upgrade in progress banner
* Use default user icon if no imageURL
* Moved PSSCOC Documents into own folder
* Update .gitignore: ignore EIR data folder
* Fixed poetry lock file
* Updated profile api route
* Updated node.js action
- .github/workflows/node-js-tests.yml +8 -2
- README.md +2 -2
- backend/.gitignore +8 -1
- backend/backend/app/api/routers/chat.py +21 -9
- backend/backend/app/api/routers/healthcheck.py +34 -5
- backend/backend/app/api/routers/query.py +2 -1
- backend/backend/app/api/routers/search.py +4 -2
- backend/backend/app/utils/auth.py +141 -0
- backend/backend/app/utils/contants.py +5 -0
- backend/backend/app/utils/index.py +193 -75
- backend/backend/data/{About PSSCOC β PSSCOC/About PSSCOC}/About PSSCOC.json +0 -0
- backend/backend/data/{PSSCOC for Construction Works β PSSCOC/PSSCOC for Construction Works}/list-of-amendments-for-psscoc-for-construction-works-2020.pdf +0 -0
- backend/backend/data/{PSSCOC for Construction Works β PSSCOC/PSSCOC for Construction Works}/option-module-c-on-collaborative-contracting-with-sidp.pdf +0 -0
- backend/backend/data/{PSSCOC for Construction Works β PSSCOC/PSSCOC for Construction Works}/option-module-e-on-collaborative-contracting---sent.pdf +0 -0
- backend/backend/data/{PSSCOC for Construction Works β PSSCOC/PSSCOC for Construction Works}/psscoc-for-construction-works-2020.pdf +0 -0
- backend/backend/data/{PSSCOC for Construction Works β PSSCOC/PSSCOC for Construction Works}/supplement-for-psscoc-for-construction-works-2020.docx +0 -0
- backend/backend/data/{PSSCOC for Design and Build β PSSCOC/PSSCOC for Design and Build}/list-of-amendments-psscoc-design-build-2020.pdf +0 -0
- backend/backend/data/{PSSCOC for Design and Build β PSSCOC/PSSCOC for Design and Build}/option-module-c-on-collaborative-contracting-wo-sidp.pdf +0 -0
- backend/backend/data/{PSSCOC for Design and Build β PSSCOC/PSSCOC for Design and Build}/option-module-c-on-collaborative-contracting.pdf +0 -0
- backend/backend/data/{PSSCOC for Design and Build β PSSCOC/PSSCOC for Design and Build}/psscoc-for-design-build-2020.pdf +0 -0
- backend/backend/data/{PSSCOC for Design and Build β PSSCOC/PSSCOC for Design and Build}/supplement-for-psscoc-for-design-build-2020.docx +0 -0
- backend/backend/data/{Standard Conditions for Nominated Sub-Contract (NSC) β PSSCOC/Standard Conditions for Nominated Sub-Contract (NSC)}/1_nsc_constnwks.pdf +0 -0
- backend/backend/data/{Standard Conditions for Nominated Sub-Contract (NSC) β PSSCOC/Standard Conditions for Nominated Sub-Contract (NSC)}/2_nsc_supplem.docx +0 -0
- backend/backend/data/{Standard Conditions for Nominated Sub-Contract (NSC) β PSSCOC/Standard Conditions for Nominated Sub-Contract (NSC)}/3_nsc_clarify_amendm.pdf +0 -0
- backend/backend/data/{Standard Conditions for Nominated Sub-Contract (NSC) β PSSCOC/Standard Conditions for Nominated Sub-Contract (NSC)}/4_nsc_amendm.pdf +0 -0
- backend/backend/main.py +10 -6
- backend/backend/run.py +38 -1
- backend/example.env +35 -1
- backend/poetry.lock +352 -79
- backend/pyproject.toml +3 -0
- frontend/app/about/page.tsx +58 -1
- frontend/app/api/profile/route.ts +46 -0
- frontend/app/api/status/route.ts +28 -0
- frontend/app/components/chat-section.tsx +48 -20
- frontend/app/components/footer.tsx +24 -7
- frontend/app/components/header.tsx +65 -9
- frontend/app/components/query-section.tsx +20 -4
- frontend/app/components/search-section.tsx +35 -20
- frontend/app/components/ui/autofill-prompt/autofill-prompt-dialog.tsx +14 -5
- frontend/app/components/ui/autofill-prompt/autofill-prompt.interface.tsx +9 -1
- frontend/app/components/ui/autofill-prompt/autofill-search-prompt-dialog.tsx +14 -4
- frontend/app/components/ui/autofill-prompt/index.ts +4 -0
- frontend/app/components/ui/chat/chat-input.tsx +29 -26
- frontend/app/components/ui/chat/chat-messages.tsx +19 -17
- frontend/app/components/ui/chat/chat-selection.tsx +55 -0
- frontend/app/components/ui/chat/chat.interface.ts +2 -0
- frontend/app/components/ui/chat/index.ts +2 -1
- frontend/app/components/ui/login-buttons.tsx +17 -4
- frontend/app/components/ui/mobilemenu.tsx +1 -1
- frontend/app/components/ui/navlink.tsx +5 -4
@@ -31,10 +31,16 @@ jobs:
|
|
31 |
cache-dependency-path: ./frontend/package-lock.json
|
32 |
- name: Install Dependencies
|
33 |
working-directory: ./frontend
|
34 |
-
run: npm
|
35 |
-
- name:
|
|
|
|
|
|
|
36 |
working-directory: ./frontend
|
37 |
run: npm run build --if-present
|
|
|
|
|
|
|
38 |
# - name: Run Test
|
39 |
# working-directory: ./frontend
|
40 |
# run: npm test
|
|
|
31 |
cache-dependency-path: ./frontend/package-lock.json
|
32 |
- name: Install Dependencies
|
33 |
working-directory: ./frontend
|
34 |
+
run: npm install
|
35 |
+
- name: Disable Next.js Telemetry
|
36 |
+
working-directory: ./frontend
|
37 |
+
run: npx next telemetry disable
|
38 |
+
- name: Test Build Package
|
39 |
working-directory: ./frontend
|
40 |
run: npm run build --if-present
|
41 |
+
env:
|
42 |
+
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
|
43 |
+
SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}
|
44 |
# - name: Run Test
|
45 |
# working-directory: ./frontend
|
46 |
# run: npm test
|
@@ -4,7 +4,7 @@ emoji: π
|
|
4 |
colorFrom: blue
|
5 |
colorTo: indigo
|
6 |
sdk: docker
|
7 |
-
python_version: 3.11.
|
8 |
app_port: 8000
|
9 |
pinned: false
|
10 |
---
|
@@ -74,7 +74,7 @@ For more information, see the [DEPLOYMENT.md](./DEPLOYMENT.md).
|
|
74 |
- [Python](https://python.org/) - Backend Server Environment
|
75 |
- [FastAPI](https://fastapi.tiangolo.com/) - Backend API Web Framework
|
76 |
- [LlamaIndex](https://www.llamaindex.ai/) - Data Framework for LLM
|
77 |
-
- [
|
78 |
|
79 |
## π Contributing <a name = "contributing"></a>
|
80 |
|
|
|
4 |
colorFrom: blue
|
5 |
colorTo: indigo
|
6 |
sdk: docker
|
7 |
+
python_version: 3.11.8
|
8 |
app_port: 8000
|
9 |
pinned: false
|
10 |
---
|
|
|
74 |
- [Python](https://python.org/) - Backend Server Environment
|
75 |
- [FastAPI](https://fastapi.tiangolo.com/) - Backend API Web Framework
|
76 |
- [LlamaIndex](https://www.llamaindex.ai/) - Data Framework for LLM
|
77 |
+
- [create-llama](https://github.com/run-llama/LlamaIndexTS/tree/main/packages/create-llama) - LlamaIndex Application Bootstrap Tool
|
78 |
|
79 |
## π Contributing <a name = "contributing"></a>
|
80 |
|
@@ -1,3 +1,10 @@
|
|
|
|
1 |
__pycache__
|
2 |
storage
|
3 |
-
.env
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Production Files
|
2 |
__pycache__
|
3 |
storage
|
4 |
+
.env
|
5 |
+
**/data/EIR/
|
6 |
+
|
7 |
+
# Dev Folders
|
8 |
+
test.py
|
9 |
+
storage backup
|
10 |
+
data backup
|
@@ -4,17 +4,17 @@ from typing import List
|
|
4 |
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
5 |
from fastapi.responses import StreamingResponse
|
6 |
from fastapi.websockets import WebSocketDisconnect
|
7 |
-
from llama_index import VectorStoreIndex
|
8 |
from llama_index.llms.base import ChatMessage
|
9 |
from llama_index.llms.types import MessageRole
|
10 |
from llama_index.memory import ChatMemoryBuffer
|
11 |
from llama_index.prompts import PromptTemplate
|
12 |
from pydantic import BaseModel
|
13 |
|
|
|
14 |
from backend.app.utils.index import get_index
|
15 |
from backend.app.utils.json import json_to_model
|
16 |
|
17 |
-
chat_router = r = APIRouter()
|
18 |
|
19 |
"""
|
20 |
This router is for chatbot functionality which consist of chat memory and chat engine.
|
@@ -34,6 +34,7 @@ class _Message(BaseModel):
|
|
34 |
|
35 |
class _ChatData(BaseModel):
|
36 |
messages: List[_Message]
|
|
|
37 |
|
38 |
|
39 |
# custom prompt template to be used by chat engine
|
@@ -69,8 +70,13 @@ async def chat(
|
|
69 |
# Note: To support clients sending a JSON object using content-type "text/plain",
|
70 |
# we need to use Depends(json_to_model(_ChatData)) here
|
71 |
data: _ChatData = Depends(json_to_model(_ChatData)),
|
72 |
-
index: VectorStoreIndex = Depends(get_index),
|
73 |
):
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
# check preconditions and get last message
|
75 |
if len(data.messages) == 0:
|
76 |
raise HTTPException(
|
@@ -92,8 +98,6 @@ async def chat(
|
|
92 |
for m in data.messages
|
93 |
]
|
94 |
|
95 |
-
logger = logging.getLogger("uvicorn")
|
96 |
-
|
97 |
# query_engine = index.as_query_engine()
|
98 |
# chat_engine = CondenseQuestionChatEngine.from_defaults(
|
99 |
# query_engine=query_engine,
|
@@ -102,9 +106,11 @@ async def chat(
|
|
102 |
# verbose=True,
|
103 |
# )
|
104 |
|
|
|
|
|
105 |
memory = ChatMemoryBuffer.from_defaults(
|
106 |
chat_history=messages,
|
107 |
-
token_limit=
|
108 |
)
|
109 |
|
110 |
logger.info(f"Memory: {memory.get()}")
|
@@ -114,17 +120,23 @@ async def chat(
|
|
114 |
chat_mode="condense_plus_context",
|
115 |
memory=memory,
|
116 |
context_prompt=(
|
117 |
-
"You are a chatbot, able to have normal interactions, as well as
|
118 |
-
"
|
|
|
119 |
"Here are the relevant documents for the context:\n"
|
120 |
"{context_str}"
|
121 |
-
"\nInstruction: Based on the above documents, provide a detailed answer for the user question below
|
|
|
|
|
|
|
122 |
),
|
123 |
)
|
124 |
response = chat_engine.stream_chat(
|
125 |
message=lastMessage.content, chat_history=messages
|
126 |
)
|
127 |
|
|
|
|
|
128 |
# stream response
|
129 |
async def event_generator():
|
130 |
try:
|
|
|
4 |
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
5 |
from fastapi.responses import StreamingResponse
|
6 |
from fastapi.websockets import WebSocketDisconnect
|
|
|
7 |
from llama_index.llms.base import ChatMessage
|
8 |
from llama_index.llms.types import MessageRole
|
9 |
from llama_index.memory import ChatMemoryBuffer
|
10 |
from llama_index.prompts import PromptTemplate
|
11 |
from pydantic import BaseModel
|
12 |
|
13 |
+
from backend.app.utils import auth
|
14 |
from backend.app.utils.index import get_index
|
15 |
from backend.app.utils.json import json_to_model
|
16 |
|
17 |
+
chat_router = r = APIRouter(dependencies=[Depends(auth.validate_user)])
|
18 |
|
19 |
"""
|
20 |
This router is for chatbot functionality which consist of chat memory and chat engine.
|
|
|
34 |
|
35 |
class _ChatData(BaseModel):
|
36 |
messages: List[_Message]
|
37 |
+
document: str
|
38 |
|
39 |
|
40 |
# custom prompt template to be used by chat engine
|
|
|
70 |
# Note: To support clients sending a JSON object using content-type "text/plain",
|
71 |
# we need to use Depends(json_to_model(_ChatData)) here
|
72 |
data: _ChatData = Depends(json_to_model(_ChatData)),
|
|
|
73 |
):
|
74 |
+
logger = logging.getLogger("uvicorn")
|
75 |
+
# get the document set selected from the request body
|
76 |
+
document_set = data.document
|
77 |
+
logger.info(f"Document Set: {document_set}")
|
78 |
+
# get the index for the selected document set
|
79 |
+
index = get_index(collection_name=document_set)
|
80 |
# check preconditions and get last message
|
81 |
if len(data.messages) == 0:
|
82 |
raise HTTPException(
|
|
|
98 |
for m in data.messages
|
99 |
]
|
100 |
|
|
|
|
|
101 |
# query_engine = index.as_query_engine()
|
102 |
# chat_engine = CondenseQuestionChatEngine.from_defaults(
|
103 |
# query_engine=query_engine,
|
|
|
106 |
# verbose=True,
|
107 |
# )
|
108 |
|
109 |
+
logger.info(f"Messages: {messages}")
|
110 |
+
|
111 |
memory = ChatMemoryBuffer.from_defaults(
|
112 |
chat_history=messages,
|
113 |
+
token_limit=3900,
|
114 |
)
|
115 |
|
116 |
logger.info(f"Memory: {memory.get()}")
|
|
|
120 |
chat_mode="condense_plus_context",
|
121 |
memory=memory,
|
122 |
context_prompt=(
|
123 |
+
"You are a helpful chatbot, able to have normal interactions, as well as answer questions"
|
124 |
+
" regarding information relating to the Public Sector Standard Conditions Of Contract (PSSCOC) Documents and JTC's Employer Information Requirements (EIR) Documents.\n"
|
125 |
+
"All the documents are in the context of the construction industry in Singapore.\n"
|
126 |
"Here are the relevant documents for the context:\n"
|
127 |
"{context_str}"
|
128 |
+
"\nInstruction: Based on the above documents, provide a detailed answer for the user question below.\n"
|
129 |
+
"If you cannot answer the question or are unsure of how to answer, inform the user that you do not know.\n"
|
130 |
+
"If you need to clarify the question, ask the user for clarification.\n"
|
131 |
+
"You are to provide the relevant sources of which you got the information from in the context in brackets."
|
132 |
),
|
133 |
)
|
134 |
response = chat_engine.stream_chat(
|
135 |
message=lastMessage.content, chat_history=messages
|
136 |
)
|
137 |
|
138 |
+
# logger.info(f"Response Sources: {response.source_nodes}")
|
139 |
+
|
140 |
# stream response
|
141 |
async def event_generator():
|
142 |
try:
|
@@ -1,6 +1,12 @@
|
|
1 |
-
|
|
|
2 |
|
3 |
-
|
|
|
|
|
|
|
|
|
|
|
4 |
|
5 |
"""
|
6 |
This router is for healthcheck functionality.
|
@@ -19,14 +25,37 @@ async def healthcheck(
|
|
19 |
# else:
|
20 |
# results["index"] = False
|
21 |
|
|
|
|
|
22 |
# TODO: check if other services are ready
|
23 |
|
24 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
|
26 |
-
results =
|
|
|
27 |
return results
|
28 |
|
29 |
|
30 |
# Simple test to check if the healthcheck endpoint is working
|
31 |
def test_healthcheck():
|
32 |
-
assert healthcheck() == {"status": "
|
|
|
1 |
+
import logging
|
2 |
+
import os
|
3 |
|
4 |
+
from fastapi import APIRouter, Depends, Request
|
5 |
+
from supabase import Client, ClientOptions, create_client
|
6 |
+
|
7 |
+
from backend.app.utils import auth
|
8 |
+
|
9 |
+
healthcheck_router = r = APIRouter(dependencies=[Depends(auth.validate_user)])
|
10 |
|
11 |
"""
|
12 |
This router is for healthcheck functionality.
|
|
|
25 |
# else:
|
26 |
# results["index"] = False
|
27 |
|
28 |
+
logger = logging.getLogger("uvicorn")
|
29 |
+
|
30 |
# TODO: check if other services are ready
|
31 |
|
32 |
+
# Try to connect to supabase
|
33 |
+
supabase_url: str = os.environ.get("SUPABASE_URL")
|
34 |
+
supabase_key: str = os.environ.get("SUPABASE_ANON_KEY")
|
35 |
+
|
36 |
+
supabase: Client = create_client(
|
37 |
+
supabase_url=supabase_url,
|
38 |
+
supabase_key=supabase_key,
|
39 |
+
options=ClientOptions(
|
40 |
+
postgrest_client_timeout=10,
|
41 |
+
storage_client_timeout=10,
|
42 |
+
),
|
43 |
+
)
|
44 |
+
|
45 |
+
response = supabase.table("users").select("id", count="exact").execute()
|
46 |
+
|
47 |
+
# logger.info(f"Supabase: {response}")
|
48 |
+
|
49 |
+
if response.count is not None:
|
50 |
+
results["supabase"] = True
|
51 |
+
else:
|
52 |
+
results["supabase"] = False
|
53 |
|
54 |
+
results["backend"] = True
|
55 |
+
logger.debug(f"Healthcheck: {results}")
|
56 |
return results
|
57 |
|
58 |
|
59 |
# Simple test to check if the healthcheck endpoint is working
|
60 |
def test_healthcheck():
|
61 |
+
assert healthcheck() == {"status": True, "supabase": True}
|
@@ -8,10 +8,11 @@ from llama_index import VectorStoreIndex
|
|
8 |
from llama_index.llms.types import MessageRole
|
9 |
from pydantic import BaseModel
|
10 |
|
|
|
11 |
from backend.app.utils.index import get_index
|
12 |
from backend.app.utils.json import json_to_model
|
13 |
|
14 |
-
query_router = r = APIRouter()
|
15 |
|
16 |
"""
|
17 |
This router is for query functionality which consist of query engine.
|
|
|
8 |
from llama_index.llms.types import MessageRole
|
9 |
from pydantic import BaseModel
|
10 |
|
11 |
+
from backend.app.utils import auth
|
12 |
from backend.app.utils.index import get_index
|
13 |
from backend.app.utils.json import json_to_model
|
14 |
|
15 |
+
query_router = r = APIRouter(dependencies=[Depends(auth.validate_user)])
|
16 |
|
17 |
"""
|
18 |
This router is for query functionality which consist of query engine.
|
@@ -6,9 +6,10 @@ from llama_index import VectorStoreIndex
|
|
6 |
from llama_index.postprocessor import SimilarityPostprocessor
|
7 |
from llama_index.retrievers import VectorIndexRetriever
|
8 |
|
|
|
9 |
from backend.app.utils.index import get_index
|
10 |
|
11 |
-
search_router = r = APIRouter()
|
12 |
|
13 |
"""
|
14 |
This router is for search functionality which consist of query engine.
|
@@ -22,8 +23,9 @@ Instead it returns the relevant information from the index.
|
|
22 |
async def search(
|
23 |
request: Request,
|
24 |
index: VectorStoreIndex = Depends(get_index),
|
|
|
25 |
):
|
26 |
-
query = request.query_params.get("query")
|
27 |
logger = logging.getLogger("uvicorn")
|
28 |
logger.info(f"Search: {query}")
|
29 |
if query is None:
|
|
|
6 |
from llama_index.postprocessor import SimilarityPostprocessor
|
7 |
from llama_index.retrievers import VectorIndexRetriever
|
8 |
|
9 |
+
from backend.app.utils import auth
|
10 |
from backend.app.utils.index import get_index
|
11 |
|
12 |
+
search_router = r = APIRouter(dependencies=[Depends(auth.validate_user)])
|
13 |
|
14 |
"""
|
15 |
This router is for search functionality which consist of query engine.
|
|
|
23 |
async def search(
|
24 |
request: Request,
|
25 |
index: VectorStoreIndex = Depends(get_index),
|
26 |
+
query: str = None,
|
27 |
):
|
28 |
+
# query = request.query_params.get("query")
|
29 |
logger = logging.getLogger("uvicorn")
|
30 |
logger.info(f"Search: {query}")
|
31 |
if query is None:
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import logging
|
2 |
+
import os
|
3 |
+
import time
|
4 |
+
from typing import Dict
|
5 |
+
|
6 |
+
import jwt
|
7 |
+
from dotenv import load_dotenv
|
8 |
+
from fastapi import HTTPException, Security
|
9 |
+
from fastapi.security import APIKeyHeader
|
10 |
+
from supabase import Client, ClientOptions, create_client
|
11 |
+
|
12 |
+
load_dotenv()
|
13 |
+
|
14 |
+
# Retrieve the API key header name from the environment
|
15 |
+
API_AUTH_HEADER_NAME: str = os.getenv(
|
16 |
+
key="API_AUTH_HEADER_NAME", default="Authorization"
|
17 |
+
)
|
18 |
+
|
19 |
+
# Retrieve the Backend API key header from the environment
|
20 |
+
API_KEY_HEADER_NAME: str = os.getenv(key="API_KEY_HEADER_NAME", default="X-API-Key")
|
21 |
+
|
22 |
+
# Create an API key header instance
|
23 |
+
API_AUTH_HEADER = APIKeyHeader(name=API_AUTH_HEADER_NAME, auto_error=False)
|
24 |
+
|
25 |
+
# Retrieve the API key from the environment
|
26 |
+
BACKEND_API_KEY: str = os.getenv(key="BACKEND_API_KEY")
|
27 |
+
|
28 |
+
# Create an API key header instance
|
29 |
+
API_KEY_HEADER = APIKeyHeader(name=API_KEY_HEADER_NAME, auto_error=False)
|
30 |
+
|
31 |
+
JWT_SECRET: str = os.getenv("SUPABASE_JWT_SECRET")
|
32 |
+
JWT_ALGORITHM = "HS256"
|
33 |
+
|
34 |
+
|
35 |
+
def verify_jwt(jwtoken: str) -> bool:
|
36 |
+
"""Verify the JWT token and return True if the token is valid, else return False"""
|
37 |
+
isTokenValid: bool = False
|
38 |
+
|
39 |
+
try:
|
40 |
+
payload = decodeJWT(jwtoken)
|
41 |
+
except Exception:
|
42 |
+
payload = None
|
43 |
+
if payload:
|
44 |
+
isTokenValid = True
|
45 |
+
return isTokenValid
|
46 |
+
|
47 |
+
|
48 |
+
def decodeJWT(token: str) -> Dict:
|
49 |
+
"""Decode the JWT token and return the payload if the token is valid, else return None"""
|
50 |
+
try:
|
51 |
+
decoded_token = jwt.decode(
|
52 |
+
token, JWT_SECRET, algorithms=[JWT_ALGORITHM], options={"verify_aud": False}
|
53 |
+
)
|
54 |
+
return decoded_token if decoded_token["exp"] >= time.time() else None
|
55 |
+
except Exception:
|
56 |
+
return None
|
57 |
+
|
58 |
+
|
59 |
+
def get_user_from_JWT(token: str):
|
60 |
+
"""Get the user id from the JWT token and return True if the user exists, else return False"""
|
61 |
+
supabase_url: str = os.environ.get("SUPABASE_URL")
|
62 |
+
supabase_key: str = os.environ.get("SUPABASE_ANON_KEY")
|
63 |
+
|
64 |
+
supabase: Client = create_client(
|
65 |
+
supabase_url=supabase_url,
|
66 |
+
supabase_key=supabase_key,
|
67 |
+
options=ClientOptions(
|
68 |
+
postgrest_client_timeout=10,
|
69 |
+
storage_client_timeout=10,
|
70 |
+
),
|
71 |
+
)
|
72 |
+
|
73 |
+
payload = decodeJWT(token)
|
74 |
+
user_id = payload["sub"]
|
75 |
+
|
76 |
+
if user_id is not None:
|
77 |
+
# Try to get the user from the database using the user_id
|
78 |
+
response = supabase.table("users").select("*").eq("id", user_id).execute()
|
79 |
+
# print(response.data)
|
80 |
+
if len(response.data) == 0:
|
81 |
+
return False
|
82 |
+
return True
|
83 |
+
return False
|
84 |
+
|
85 |
+
|
86 |
+
async def validate_user(
|
87 |
+
auth_token: str = Security(dependency=API_AUTH_HEADER),
|
88 |
+
api_key: str = Security(dependency=API_KEY_HEADER),
|
89 |
+
):
|
90 |
+
try:
|
91 |
+
logger = logging.getLogger("uvicorn")
|
92 |
+
# logger.debug(f"Auth Token: {auth_token} | API Key: {api_key}")
|
93 |
+
if auth_token is not None or api_key is not None:
|
94 |
+
# If the access token is empty, use the 'X-API-Key' from the header
|
95 |
+
if auth_token is None:
|
96 |
+
# Access the 'X-API-Key' header directly
|
97 |
+
if BACKEND_API_KEY is None:
|
98 |
+
raise ValueError("Backend API key is not set in Backend Service!")
|
99 |
+
# If the 'X-API-Key' does not match the backend API key, raise an error
|
100 |
+
if api_key != BACKEND_API_KEY:
|
101 |
+
raise ValueError(
|
102 |
+
"Invalid API key provided in the 'X-API-Key' header!"
|
103 |
+
)
|
104 |
+
else:
|
105 |
+
logger.info("Validated API key successfully!")
|
106 |
+
return None
|
107 |
+
else:
|
108 |
+
auth_token = (
|
109 |
+
auth_token.strip()
|
110 |
+
) # Remove leading and trailing whitespaces
|
111 |
+
isBearer = auth_token.startswith(
|
112 |
+
"Bearer"
|
113 |
+
) # Check if the token starts with 'Bearer'
|
114 |
+
jwtoken = auth_token.split("Bearer ")[
|
115 |
+
1
|
116 |
+
] # Extract the token from the 'Bearer' string
|
117 |
+
if JWT_SECRET is None:
|
118 |
+
raise ValueError(
|
119 |
+
"Supabase JWT Secret is not set in Backend Service!"
|
120 |
+
)
|
121 |
+
if not isBearer:
|
122 |
+
return (
|
123 |
+
"Invalid token scheme. Please use the format 'Bearer [token]'"
|
124 |
+
)
|
125 |
+
# Verify the JWT token is valid
|
126 |
+
if verify_jwt(jwtoken=jwtoken) is None:
|
127 |
+
return "Invalid token. Please provide a valid token."
|
128 |
+
# Check if the user exists in the database
|
129 |
+
if get_user_from_JWT(token=jwtoken):
|
130 |
+
logger.info("Validated User's Auth Token successfully!")
|
131 |
+
return None
|
132 |
+
else:
|
133 |
+
raise ValueError("User does not exist in the database!")
|
134 |
+
else:
|
135 |
+
raise ValueError(
|
136 |
+
"Either Access token [Authorization] or API key [X-API-Key] needed!"
|
137 |
+
)
|
138 |
+
except Exception as e:
|
139 |
+
logger = logging.getLogger("uvicorn")
|
140 |
+
logger.error(f"Error validating Auth Token / API key: {e}")
|
141 |
+
raise HTTPException(status_code=401, detail=f"Unauthorized - {e}")
|
@@ -30,6 +30,11 @@ CHUNK_OVERLAP = 100
|
|
30 |
# Embedding Model Constants
|
31 |
EMBED_MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
|
32 |
EMBED_POOLING = "mean"
|
|
|
|
|
|
|
|
|
|
|
33 |
|
34 |
# Prompt Helper Constants
|
35 |
# set maximum input size
|
|
|
30 |
# Embedding Model Constants
|
31 |
EMBED_MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
|
32 |
EMBED_POOLING = "mean"
|
33 |
+
EMBED_MODEL_DIMENSIONS = 384 # MiniLM-L6-v2 uses 384 dimensions
|
34 |
+
DEF_EMBED_MODEL_DIMENSIONS = (
|
35 |
+
1536 # Default embedding model dimensions used by OpenAI text-embedding-ada-002
|
36 |
+
)
|
37 |
+
EMBED_BATCH_SIZE = 100 # batch size for openai embeddings
|
38 |
|
39 |
# Prompt Helper Constants
|
40 |
# set maximum input size
|
@@ -1,9 +1,11 @@
|
|
1 |
import logging
|
2 |
import os
|
3 |
|
|
|
4 |
from llama_index import (
|
5 |
PromptHelper,
|
6 |
ServiceContext,
|
|
|
7 |
SimpleDirectoryReader,
|
8 |
StorageContext,
|
9 |
VectorStoreIndex,
|
@@ -11,11 +13,14 @@ from llama_index import (
|
|
11 |
set_global_service_context,
|
12 |
)
|
13 |
from llama_index.embeddings import HuggingFaceEmbedding
|
14 |
-
from llama_index.
|
|
|
15 |
from llama_index.llms.llama_utils import (
|
16 |
completion_to_prompt,
|
17 |
messages_to_prompt,
|
18 |
)
|
|
|
|
|
19 |
|
20 |
from backend.app.utils.contants import (
|
21 |
CHUNK_OVERLAP,
|
@@ -24,7 +29,10 @@ from backend.app.utils.contants import (
|
|
24 |
CHUNK_SIZE_LIMIT,
|
25 |
CONTEXT_SIZE,
|
26 |
DATA_DIR,
|
|
|
27 |
DEVICE_TYPE,
|
|
|
|
|
28 |
EMBED_MODEL_NAME,
|
29 |
EMBED_POOLING,
|
30 |
LLM_MODEL_URL,
|
@@ -35,91 +43,201 @@ from backend.app.utils.contants import (
|
|
35 |
STORAGE_DIR,
|
36 |
)
|
37 |
|
38 |
-
|
39 |
-
|
40 |
-
temperature=LLM_TEMPERATURE,
|
41 |
-
max_new_tokens=MAX_NEW_TOKENS,
|
42 |
-
context_window=CONTEXT_SIZE,
|
43 |
-
# kwargs to pass to __call__()
|
44 |
-
generate_kwargs={},
|
45 |
-
# kwargs to pass to __init__()
|
46 |
-
model_kwargs=MODEL_KWARGS,
|
47 |
-
# transform inputs into Llama2 format
|
48 |
-
messages_to_prompt=messages_to_prompt,
|
49 |
-
completion_to_prompt=completion_to_prompt,
|
50 |
-
verbose=True,
|
51 |
-
)
|
52 |
|
53 |
-
|
54 |
-
|
55 |
-
pooling=EMBED_POOLING,
|
56 |
-
device=DEVICE_TYPE,
|
57 |
-
)
|
58 |
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
num_output=NUM_OUTPUT,
|
63 |
-
)
|
64 |
|
65 |
-
service_context = ServiceContext.from_defaults(
|
66 |
-
llm=llm,
|
67 |
-
embed_model=embed_model,
|
68 |
-
chunk_size=CHUNK_SIZE,
|
69 |
-
chunk_overlap=CHUNK_OVERLAP,
|
70 |
-
prompt_helper=prompt_helper,
|
71 |
-
)
|
72 |
|
73 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
74 |
|
75 |
|
76 |
def create_index():
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
#
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
else:
|
95 |
-
#
|
96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
97 |
|
98 |
|
99 |
-
def load_existing_index():
|
100 |
# load the existing index
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
107 |
|
108 |
|
109 |
-
def get_index():
|
110 |
-
#
|
111 |
-
|
112 |
-
# create the index if it does not exist
|
113 |
-
create_index()
|
114 |
-
# load the index from storage
|
115 |
-
index = load_existing_index()
|
116 |
-
# check if storage is empty, 4 files should be present if using simplevectorstore
|
117 |
-
elif os.path.exists(STORAGE_DIR) and len(os.listdir(STORAGE_DIR)) < 4:
|
118 |
-
# create the index if it does not exist
|
119 |
-
create_index()
|
120 |
-
# load the index from storage
|
121 |
-
index = load_existing_index()
|
122 |
-
else:
|
123 |
-
# load the index from storage
|
124 |
-
index = load_existing_index()
|
125 |
return index
|
|
|
1 |
import logging
|
2 |
import os
|
3 |
|
4 |
+
from dotenv import load_dotenv
|
5 |
from llama_index import (
|
6 |
PromptHelper,
|
7 |
ServiceContext,
|
8 |
+
# Document,
|
9 |
SimpleDirectoryReader,
|
10 |
StorageContext,
|
11 |
VectorStoreIndex,
|
|
|
13 |
set_global_service_context,
|
14 |
)
|
15 |
from llama_index.embeddings import HuggingFaceEmbedding
|
16 |
+
from llama_index.embeddings.openai import OpenAIEmbedding
|
17 |
+
from llama_index.llms import LlamaCPP, OpenAI
|
18 |
from llama_index.llms.llama_utils import (
|
19 |
completion_to_prompt,
|
20 |
messages_to_prompt,
|
21 |
)
|
22 |
+
from llama_index.vector_stores.supabase import SupabaseVectorStore
|
23 |
+
from vecs import IndexMeasure
|
24 |
|
25 |
from backend.app.utils.contants import (
|
26 |
CHUNK_OVERLAP,
|
|
|
29 |
CHUNK_SIZE_LIMIT,
|
30 |
CONTEXT_SIZE,
|
31 |
DATA_DIR,
|
32 |
+
DEF_EMBED_MODEL_DIMENSIONS,
|
33 |
DEVICE_TYPE,
|
34 |
+
EMBED_BATCH_SIZE,
|
35 |
+
EMBED_MODEL_DIMENSIONS,
|
36 |
EMBED_MODEL_NAME,
|
37 |
EMBED_POOLING,
|
38 |
LLM_MODEL_URL,
|
|
|
43 |
STORAGE_DIR,
|
44 |
)
|
45 |
|
46 |
+
# from llama_index.vector_stores.supabase import SupabaseVectorStore
|
47 |
+
# import textwrap
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
|
49 |
+
load_dotenv()
|
50 |
+
logger = logging.getLogger("uvicorn")
|
|
|
|
|
|
|
51 |
|
52 |
+
# ENV variables
|
53 |
+
USE_LOCAL_LLM = bool(os.getenv("USE_LOCAL_LLM").lower() == "true")
|
54 |
+
USE_LOCAL_VECTOR_STORE = bool(os.getenv("USE_LOCAL_VECTOR_STORE").lower() == "true")
|
|
|
|
|
55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
|
57 |
+
# use local LLM if USE_LOCAL_LLM is set to True, else use openai's API
|
58 |
+
if USE_LOCAL_LLM:
|
59 |
+
logger.info("Using local LLM...")
|
60 |
+
llm = LlamaCPP(
|
61 |
+
model_url=LLM_MODEL_URL,
|
62 |
+
temperature=LLM_TEMPERATURE,
|
63 |
+
max_new_tokens=MAX_NEW_TOKENS,
|
64 |
+
context_window=CONTEXT_SIZE,
|
65 |
+
# kwargs to pass to __call__()
|
66 |
+
generate_kwargs={},
|
67 |
+
# kwargs to pass to __init__()
|
68 |
+
model_kwargs=MODEL_KWARGS,
|
69 |
+
# transform inputs into Llama2 format
|
70 |
+
messages_to_prompt=messages_to_prompt,
|
71 |
+
completion_to_prompt=completion_to_prompt,
|
72 |
+
verbose=True,
|
73 |
+
)
|
74 |
+
embed_model = HuggingFaceEmbedding(
|
75 |
+
model_name=EMBED_MODEL_NAME,
|
76 |
+
pooling=EMBED_POOLING,
|
77 |
+
device=DEVICE_TYPE,
|
78 |
+
)
|
79 |
+
|
80 |
+
prompt_helper = PromptHelper(
|
81 |
+
chunk_size_limit=CHUNK_SIZE_LIMIT,
|
82 |
+
chunk_overlap_ratio=CHUNK_OVERLAP_RATIO,
|
83 |
+
num_output=NUM_OUTPUT,
|
84 |
+
)
|
85 |
+
|
86 |
+
service_context = ServiceContext.from_defaults(
|
87 |
+
llm=llm,
|
88 |
+
embed_model=embed_model,
|
89 |
+
chunk_size=CHUNK_SIZE,
|
90 |
+
chunk_overlap=CHUNK_OVERLAP,
|
91 |
+
prompt_helper=prompt_helper,
|
92 |
+
)
|
93 |
+
|
94 |
+
set_global_service_context(service_context)
|
95 |
+
else:
|
96 |
+
logger.info("Using OpenAI's API...")
|
97 |
+
llm = OpenAI(
|
98 |
+
model="gpt-3.5-turbo",
|
99 |
+
temperature=0.2,
|
100 |
+
api_key=os.getenv("OPENAI_API_KEY"),
|
101 |
+
)
|
102 |
+
# By default, LlamaIndex uses text-embedding-ada-002 from OpenAI
|
103 |
+
embed_model = OpenAIEmbedding(embed_batch_size=EMBED_BATCH_SIZE)
|
104 |
+
|
105 |
+
prompt_helper = PromptHelper(
|
106 |
+
chunk_size_limit=CHUNK_SIZE_LIMIT,
|
107 |
+
chunk_overlap_ratio=CHUNK_OVERLAP_RATIO,
|
108 |
+
num_output=NUM_OUTPUT,
|
109 |
+
)
|
110 |
+
|
111 |
+
service_context = ServiceContext.from_defaults(
|
112 |
+
llm=llm,
|
113 |
+
embed_model=embed_model,
|
114 |
+
chunk_size=CHUNK_SIZE,
|
115 |
+
chunk_overlap=CHUNK_OVERLAP,
|
116 |
+
prompt_helper=prompt_helper,
|
117 |
+
)
|
118 |
+
|
119 |
+
set_global_service_context(service_context)
|
120 |
|
121 |
|
122 |
def create_index():
|
123 |
+
# if use local vector store, create & store the index locally
|
124 |
+
if USE_LOCAL_VECTOR_STORE:
|
125 |
+
# get the folders in the data directory
|
126 |
+
collection_names = os.listdir(DATA_DIR)
|
127 |
+
# to create each folder as a collection in local storage
|
128 |
+
for collection_name in collection_names:
|
129 |
+
logger.info(f"Checking if [{collection_names}] index exists locally...")
|
130 |
+
# build the new data directory
|
131 |
+
new_data_dir = os.path.join(DATA_DIR, collection_name)
|
132 |
+
# build the new storage directory
|
133 |
+
new_storage_dir = os.path.join(STORAGE_DIR, collection_name)
|
134 |
+
# check if storage folder and index files already exists
|
135 |
+
if (
|
136 |
+
not os.path.exists(new_storage_dir)
|
137 |
+
or len(os.listdir(new_storage_dir))
|
138 |
+
< 4 # 4 files should be present if using simplevectorstore
|
139 |
+
):
|
140 |
+
logger.info(f"Creating [{collection_names}] index")
|
141 |
+
# load the documents and create the index
|
142 |
+
try:
|
143 |
+
documents = SimpleDirectoryReader(
|
144 |
+
input_dir=new_data_dir, recursive=True
|
145 |
+
).load_data()
|
146 |
+
except ValueError as e:
|
147 |
+
logger.error(f"{e}")
|
148 |
+
index = VectorStoreIndex.from_documents(
|
149 |
+
documents=documents,
|
150 |
+
service_context=service_context,
|
151 |
+
show_progress=True,
|
152 |
+
)
|
153 |
+
# store it for later
|
154 |
+
index.storage_context.persist(STORAGE_DIR)
|
155 |
+
logger.info(f"Finished creating new index. Stored in {STORAGE_DIR}")
|
156 |
+
else:
|
157 |
+
# do nothing
|
158 |
+
logger.info(f"Index already exist at {STORAGE_DIR}...")
|
159 |
+
# else, create & store the index in Supabase pgvector
|
160 |
else:
|
161 |
+
# get the folders in the data directory
|
162 |
+
collection_names = os.listdir(DATA_DIR)
|
163 |
+
# to create each folder as a collection in Supabase
|
164 |
+
for collection_name in collection_names:
|
165 |
+
# check if remote storage already exists
|
166 |
+
logger.info(f"Checking if [{collection_name}] index exists in Supabase...")
|
167 |
+
# set the dimension based on the LLM model used
|
168 |
+
dimension = (
|
169 |
+
EMBED_MODEL_DIMENSIONS if USE_LOCAL_LLM else DEF_EMBED_MODEL_DIMENSIONS
|
170 |
+
)
|
171 |
+
# create the vector store, will create the collection if it does not exist
|
172 |
+
vector_store = SupabaseVectorStore(
|
173 |
+
postgres_connection_string=os.getenv("POSTGRES_CONNECTION_STRING"),
|
174 |
+
collection_name=collection_name,
|
175 |
+
dimension=dimension,
|
176 |
+
)
|
177 |
+
# create the storage context
|
178 |
+
storage_context = StorageContext.from_defaults(vector_store=vector_store)
|
179 |
+
logger.info(f"Creating [{collection_name}] index")
|
180 |
+
# create the data directory
|
181 |
+
new_data_dir = os.path.join(DATA_DIR, collection_name)
|
182 |
+
# load the documents and create the index
|
183 |
+
try:
|
184 |
+
documents = SimpleDirectoryReader(
|
185 |
+
input_dir=new_data_dir, recursive=True
|
186 |
+
).load_data()
|
187 |
+
except ValueError as e:
|
188 |
+
logger.error(f"{e}")
|
189 |
+
index = VectorStoreIndex.from_documents(
|
190 |
+
documents=documents,
|
191 |
+
storage_context=storage_context,
|
192 |
+
show_progress=True,
|
193 |
+
)
|
194 |
+
logger.info(f"Finished creating [{collection_name}] vector store")
|
195 |
|
196 |
|
197 |
+
def load_existing_index(collection_name="PSSCOC"):
|
198 |
# load the existing index
|
199 |
+
if USE_LOCAL_VECTOR_STORE:
|
200 |
+
# create the storage directory
|
201 |
+
new_storage_dir = os.path.join(STORAGE_DIR, collection_name)
|
202 |
+
# load the index from local storage
|
203 |
+
logger.info(f"Loading [{collection_name}] index from {new_storage_dir}...")
|
204 |
+
storage_context = StorageContext.from_defaults(persist_dir=new_storage_dir)
|
205 |
+
index = load_index_from_storage(
|
206 |
+
storage_context, service_context=service_context
|
207 |
+
)
|
208 |
+
logger.info(
|
209 |
+
f"Finished loading [{collection_name}] index from {new_storage_dir}"
|
210 |
+
)
|
211 |
+
logger.info(f"Index ID: {index.index_id}")
|
212 |
+
return index
|
213 |
+
else:
|
214 |
+
# load the index from Supabase
|
215 |
+
logger.info(f"Loading [{collection_name}] index from Supabase...")
|
216 |
+
# set the dimension based on the LLM model used
|
217 |
+
dimension = (
|
218 |
+
EMBED_MODEL_DIMENSIONS if USE_LOCAL_LLM else DEF_EMBED_MODEL_DIMENSIONS
|
219 |
+
)
|
220 |
+
# create the vector store
|
221 |
+
vector_store = SupabaseVectorStore(
|
222 |
+
postgres_connection_string=os.getenv("POSTGRES_CONNECTION_STRING"),
|
223 |
+
collection_name=collection_name,
|
224 |
+
dimension=dimension,
|
225 |
+
)
|
226 |
+
# check if the vector store has been indexed
|
227 |
+
if not vector_store._collection.is_indexed_for_measure(
|
228 |
+
IndexMeasure.cosine_distance
|
229 |
+
):
|
230 |
+
logger.info(f"Indexing [{collection_name}] vector store...")
|
231 |
+
vector_store._collection.create_index()
|
232 |
+
logger.info(f"Finished indexing [{collection_name}] vector store")
|
233 |
+
logger.info(vector_store._collection.name)
|
234 |
+
index = VectorStoreIndex.from_vector_store(vector_store=vector_store)
|
235 |
+
logger.info(f"Finished loading [{collection_name}] index from Supabase")
|
236 |
+
logger.info(f"Index ID: {index.index_id}")
|
237 |
+
return index
|
238 |
|
239 |
|
240 |
+
def get_index(collection_name):
|
241 |
+
# load the index from storage
|
242 |
+
index = load_existing_index(collection_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
243 |
return index
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -11,19 +11,20 @@ from backend.app.api.routers.chat import chat_router
|
|
11 |
from backend.app.api.routers.healthcheck import healthcheck_router
|
12 |
from backend.app.api.routers.query import query_router
|
13 |
from backend.app.api.routers.search import search_router
|
14 |
-
from backend.app.utils.index import create_index
|
15 |
|
16 |
load_dotenv()
|
17 |
|
18 |
app = FastAPI()
|
19 |
|
20 |
-
environment = os.getenv("ENVIRONMENT", "dev") # Default to '
|
21 |
|
22 |
# Add allowed origins from environment variables
|
23 |
allowed_origins = os.getenv("ALLOWED_ORIGINS", "*")
|
24 |
|
25 |
if environment == "dev":
|
|
|
26 |
logger = logging.getLogger("uvicorn")
|
|
|
27 |
logger.warning("Running in development mode - allowing CORS for all origins")
|
28 |
app.add_middleware(
|
29 |
middleware_class=CORSMiddleware,
|
@@ -36,8 +37,9 @@ if environment == "dev":
|
|
36 |
if environment == "prod":
|
37 |
# In production, specify the allowed origins
|
38 |
allowed_origins = allowed_origins.split(",") if allowed_origins != "*" else ["*"]
|
39 |
-
|
40 |
logger = logging.getLogger("uvicorn")
|
|
|
41 |
logger.info(f"Running in production mode - allowing CORS for {allowed_origins}")
|
42 |
app.add_middleware(
|
43 |
middleware_class=CORSMiddleware,
|
@@ -48,15 +50,17 @@ if environment == "prod":
|
|
48 |
)
|
49 |
|
50 |
logger.info(f"CUDA available: {is_cuda_available()}")
|
|
|
|
|
|
|
|
|
|
|
51 |
|
52 |
app.include_router(chat_router, prefix="/api/chat")
|
53 |
app.include_router(query_router, prefix="/api/query")
|
54 |
app.include_router(search_router, prefix="/api/search")
|
55 |
app.include_router(healthcheck_router, prefix="/api/healthcheck")
|
56 |
|
57 |
-
# Try to create the index first on startup
|
58 |
-
create_index()
|
59 |
-
|
60 |
|
61 |
# Redirect to the /docs endpoint
|
62 |
@app.get("/")
|
|
|
11 |
from backend.app.api.routers.healthcheck import healthcheck_router
|
12 |
from backend.app.api.routers.query import query_router
|
13 |
from backend.app.api.routers.search import search_router
|
|
|
14 |
|
15 |
load_dotenv()
|
16 |
|
17 |
app = FastAPI()
|
18 |
|
19 |
+
environment = os.getenv("ENVIRONMENT", "dev") # Default to 'dev' if not set
|
20 |
|
21 |
# Add allowed origins from environment variables
|
22 |
allowed_origins = os.getenv("ALLOWED_ORIGINS", "*")
|
23 |
|
24 |
if environment == "dev":
|
25 |
+
# In development, allow all origins, methods, and headers
|
26 |
logger = logging.getLogger("uvicorn")
|
27 |
+
logger.level = logging.DEBUG
|
28 |
logger.warning("Running in development mode - allowing CORS for all origins")
|
29 |
app.add_middleware(
|
30 |
middleware_class=CORSMiddleware,
|
|
|
37 |
if environment == "prod":
|
38 |
# In production, specify the allowed origins
|
39 |
allowed_origins = allowed_origins.split(",") if allowed_origins != "*" else ["*"]
|
40 |
+
# Set the logger level to INFO
|
41 |
logger = logging.getLogger("uvicorn")
|
42 |
+
logger.level = logging.INFO
|
43 |
logger.info(f"Running in production mode - allowing CORS for {allowed_origins}")
|
44 |
app.add_middleware(
|
45 |
middleware_class=CORSMiddleware,
|
|
|
50 |
)
|
51 |
|
52 |
logger.info(f"CUDA available: {is_cuda_available()}")
|
53 |
+
logger.info("Use Local LLM: " + os.getenv("USE_LOCAL_LLM", "false"))
|
54 |
+
logger.info("Use Local Vector Store: " + os.getenv("USE_LOCAL_VECTOR_STORE", "false"))
|
55 |
+
|
56 |
+
# Set logger for httpx to WARNING
|
57 |
+
logging.getLogger("httpx").setLevel(logging.WARNING)
|
58 |
|
59 |
app.include_router(chat_router, prefix="/api/chat")
|
60 |
app.include_router(query_router, prefix="/api/query")
|
61 |
app.include_router(search_router, prefix="/api/search")
|
62 |
app.include_router(healthcheck_router, prefix="/api/healthcheck")
|
63 |
|
|
|
|
|
|
|
64 |
|
65 |
# Redirect to the /docs endpoint
|
66 |
@app.get("/")
|
@@ -1,4 +1,41 @@
|
|
|
|
|
|
|
|
1 |
import uvicorn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
|
3 |
if __name__ == "__main__":
|
4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import logging
|
2 |
+
import os
|
3 |
+
|
4 |
import uvicorn
|
5 |
+
from dotenv import load_dotenv
|
6 |
+
|
7 |
+
load_dotenv()
|
8 |
+
|
9 |
+
ENVIRONMENT = os.getenv("ENVIRONMENT", "dev") # Default to 'dev' if not set
|
10 |
+
CREATE_VECTOR_STORE = bool(
|
11 |
+
os.getenv("CREATE_VECTOR_STORE", "false").lower() == "true"
|
12 |
+
) # Default to False if not set
|
13 |
+
|
14 |
+
|
15 |
+
def run_app():
|
16 |
+
# Run the app
|
17 |
+
if ENVIRONMENT == "dev":
|
18 |
+
# Run the app with the development settings to auto reload
|
19 |
+
uvicorn.run(app="main:app", host="0.0.0.0", reload=True)
|
20 |
+
if ENVIRONMENT == "prod":
|
21 |
+
# Run the app with the production settings, no auto reload
|
22 |
+
uvicorn.run(app="main:app", host="0.0.0.0", reload=False)
|
23 |
+
|
24 |
|
25 |
if __name__ == "__main__":
|
26 |
+
logging_format = "%(levelname)s: %(message)s"
|
27 |
+
logging.basicConfig(level=logging.INFO, format=logging_format)
|
28 |
+
logger = logging.getLogger(__name__)
|
29 |
+
logger.info("Create vector store: " + str(CREATE_VECTOR_STORE))
|
30 |
+
if CREATE_VECTOR_STORE:
|
31 |
+
# Create the vector store
|
32 |
+
from backend.app.utils.index import create_index
|
33 |
+
|
34 |
+
logger.info("Creating vector stores first...")
|
35 |
+
create_index()
|
36 |
+
logger.info("Vector stores created successfully! Running App...")
|
37 |
+
# Run the app
|
38 |
+
run_app()
|
39 |
+
else:
|
40 |
+
# Run the app
|
41 |
+
run_app()
|
@@ -1 +1,35 @@
|
|
1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Setting up the environment
|
2 |
+
|
3 |
+
# Specify to start uvicorn app in dev or prod mode. default is dev if not set [prod|dev]
|
4 |
+
ENVIRONMENT=prod
|
5 |
+
|
6 |
+
# Allowed origins for CORS in production, separated by comma, no spaces
|
7 |
+
ALLOWED_ORIGINS=http://localhost:3000,https://smart-retrieval-demo.vercel.app
|
8 |
+
|
9 |
+
# Use Local LLM [true|false]
|
10 |
+
USE_LOCAL_LLM=true
|
11 |
+
|
12 |
+
# Use Local Vector Store [true|false]
|
13 |
+
USE_LOCAL_VECTOR_STORE=true
|
14 |
+
|
15 |
+
# Create the vector store [true|false], remember to change to false after creating for a one-time setup
|
16 |
+
CREATE_VECTOR_STORE=false
|
17 |
+
|
18 |
+
# OpenAI API Key
|
19 |
+
OPENAI_API_KEY=sk-YourOpenAIKey
|
20 |
+
|
21 |
+
# Backend API Authorization Settings
|
22 |
+
# Auth Header Name for the API
|
23 |
+
API_AUTH_HEADER_NAME=Authorization
|
24 |
+
# Alternate API Key Header Name for the API
|
25 |
+
API_KEY_HEADER_NAME=X-API-Key
|
26 |
+
# Ensure this key is the same in the frontend app environment
|
27 |
+
# https://generate-random.org/api-key-generator?count=1&length=128&type=mixed-numbers&prefix=sr-
|
28 |
+
BACKEND_API_KEY=sr-SomeRandomKey
|
29 |
+
|
30 |
+
# Supabase Settings
|
31 |
+
SUPABASE_URL=https://YourSupabaseProjectID.supabase.co
|
32 |
+
SUPABASE_ANON_KEY=YourSupabaseAnonKey
|
33 |
+
SUPABASE_JWT_SECRET=YourSupabaseJWTSecret
|
34 |
+
# Note: Rename 'postgres://' to 'postgresql://' in the connection string due to sqlalchemy not supporting 'postgres://'
|
35 |
+
POSTGRES_CONNECTION_STRING=postgresql://postgres:postgres@localhost:5432/smart_retrieval
|
@@ -349,6 +349,20 @@ wrapt = ">=1.10,<2"
|
|
349 |
[package.extras]
|
350 |
dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"]
|
351 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
352 |
[[package]]
|
353 |
name = "dirtyjson"
|
354 |
version = "1.0.8"
|
@@ -459,6 +473,22 @@ mccabe = ">=0.7.0,<0.8.0"
|
|
459 |
pycodestyle = ">=2.11.0,<2.12.0"
|
460 |
pyflakes = ">=3.2.0,<3.3.0"
|
461 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
462 |
[[package]]
|
463 |
name = "frozenlist"
|
464 |
version = "1.4.1"
|
@@ -580,6 +610,21 @@ smb = ["smbprotocol"]
|
|
580 |
ssh = ["paramiko"]
|
581 |
tqdm = ["tqdm"]
|
582 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
583 |
[[package]]
|
584 |
name = "greenlet"
|
585 |
version = "3.0.3"
|
@@ -733,13 +778,13 @@ test = ["Cython (>=0.29.24,<0.30.0)"]
|
|
733 |
|
734 |
[[package]]
|
735 |
name = "httpx"
|
736 |
-
version = "0.
|
737 |
description = "The next generation HTTP client."
|
738 |
optional = false
|
739 |
python-versions = ">=3.8"
|
740 |
files = [
|
741 |
-
{file = "httpx-0.
|
742 |
-
{file = "httpx-0.
|
743 |
]
|
744 |
|
745 |
[package.dependencies]
|
@@ -1560,6 +1605,19 @@ sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-d
|
|
1560 |
test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"]
|
1561 |
xml = ["lxml (>=4.9.2)"]
|
1562 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1563 |
[[package]]
|
1564 |
name = "pluggy"
|
1565 |
version = "1.4.0"
|
@@ -1575,6 +1633,104 @@ files = [
|
|
1575 |
dev = ["pre-commit", "tox"]
|
1576 |
testing = ["pytest", "pytest-benchmark"]
|
1577 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1578 |
[[package]]
|
1579 |
name = "pycodestyle"
|
1580 |
version = "2.11.1"
|
@@ -1707,6 +1863,23 @@ files = [
|
|
1707 |
{file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"},
|
1708 |
]
|
1709 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1710 |
[[package]]
|
1711 |
name = "pypdf"
|
1712 |
version = "3.17.4"
|
@@ -1832,7 +2005,6 @@ files = [
|
|
1832 |
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
|
1833 |
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
|
1834 |
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
|
1835 |
-
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
|
1836 |
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
|
1837 |
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
|
1838 |
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
|
@@ -1867,6 +2039,22 @@ files = [
|
|
1867 |
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
|
1868 |
]
|
1869 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1870 |
[[package]]
|
1871 |
name = "regex"
|
1872 |
version = "2023.12.25"
|
@@ -2248,6 +2436,71 @@ anyio = ">=3.4.0,<5"
|
|
2248 |
[package.extras]
|
2249 |
full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"]
|
2250 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2251 |
[[package]]
|
2252 |
name = "sympy"
|
2253 |
version = "1.12"
|
@@ -2755,6 +3008,28 @@ files = [
|
|
2755 |
docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
|
2756 |
test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"]
|
2757 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2758 |
[[package]]
|
2759 |
name = "watchfiles"
|
2760 |
version = "0.21.0"
|
@@ -2844,83 +3119,81 @@ anyio = ">=3.0.0"
|
|
2844 |
|
2845 |
[[package]]
|
2846 |
name = "websockets"
|
2847 |
-
version = "
|
2848 |
description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
|
2849 |
optional = false
|
2850 |
-
python-versions = ">=3.
|
2851 |
files = [
|
2852 |
-
{file = "websockets-
|
2853 |
-
{file = "websockets-
|
2854 |
-
{file = "websockets-
|
2855 |
-
{file = "websockets-
|
2856 |
-
{file = "websockets-
|
2857 |
-
{file = "websockets-
|
2858 |
-
{file = "websockets-
|
2859 |
-
{file = "websockets-
|
2860 |
-
{file = "websockets-
|
2861 |
-
{file = "websockets-
|
2862 |
-
{file = "websockets-
|
2863 |
-
{file = "websockets-
|
2864 |
-
{file = "websockets-
|
2865 |
-
{file = "websockets-
|
2866 |
-
{file = "websockets-
|
2867 |
-
{file = "websockets-
|
2868 |
-
{file = "websockets-
|
2869 |
-
{file = "websockets-
|
2870 |
-
{file = "websockets-
|
2871 |
-
{file = "websockets-
|
2872 |
-
{file = "websockets-
|
2873 |
-
{file = "websockets-
|
2874 |
-
{file = "websockets-
|
2875 |
-
{file = "websockets-
|
2876 |
-
{file = "websockets-
|
2877 |
-
{file = "websockets-
|
2878 |
-
{file = "websockets-
|
2879 |
-
{file = "websockets-
|
2880 |
-
{file = "websockets-
|
2881 |
-
{file = "websockets-
|
2882 |
-
{file = "websockets-
|
2883 |
-
{file = "websockets-
|
2884 |
-
{file = "websockets-
|
2885 |
-
{file = "websockets-
|
2886 |
-
{file = "websockets-
|
2887 |
-
{file = "websockets-
|
2888 |
-
{file = "websockets-
|
2889 |
-
{file = "websockets-
|
2890 |
-
{file = "websockets-
|
2891 |
-
{file = "websockets-
|
2892 |
-
{file = "websockets-
|
2893 |
-
{file = "websockets-
|
2894 |
-
{file = "websockets-
|
2895 |
-
{file = "websockets-
|
2896 |
-
{file = "websockets-
|
2897 |
-
{file = "websockets-
|
2898 |
-
{file = "websockets-
|
2899 |
-
{file = "websockets-
|
2900 |
-
{file = "websockets-
|
2901 |
-
{file = "websockets-
|
2902 |
-
{file = "websockets-
|
2903 |
-
{file = "websockets-
|
2904 |
-
{file = "websockets-
|
2905 |
-
{file = "websockets-
|
2906 |
-
{file = "websockets-
|
2907 |
-
{file = "websockets-
|
2908 |
-
{file = "websockets-
|
2909 |
-
{file = "websockets-
|
2910 |
-
{file = "websockets-
|
2911 |
-
{file = "websockets-
|
2912 |
-
{file = "websockets-
|
2913 |
-
{file = "websockets-
|
2914 |
-
{file = "websockets-
|
2915 |
-
{file = "websockets-
|
2916 |
-
{file = "websockets-
|
2917 |
-
{file = "websockets-
|
2918 |
-
{file = "websockets-
|
2919 |
-
{file = "websockets-
|
2920 |
-
{file = "websockets-
|
2921 |
-
{file = "websockets-
|
2922 |
-
{file = "websockets-12.0-py3-none-any.whl", hash = "sha256:dc284bbc8d7c78a6c69e0c7325ab46ee5e40bb4d50e494d8131a07ef47500e9e"},
|
2923 |
-
{file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"},
|
2924 |
]
|
2925 |
|
2926 |
[[package]]
|
@@ -3108,4 +3381,4 @@ multidict = ">=4.0"
|
|
3108 |
[metadata]
|
3109 |
lock-version = "2.0"
|
3110 |
python-versions = "^3.11,<3.12"
|
3111 |
-
content-hash = "
|
|
|
349 |
[package.extras]
|
350 |
dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"]
|
351 |
|
352 |
+
[[package]]
|
353 |
+
name = "deprecation"
|
354 |
+
version = "2.1.0"
|
355 |
+
description = "A library to handle automated deprecations"
|
356 |
+
optional = false
|
357 |
+
python-versions = "*"
|
358 |
+
files = [
|
359 |
+
{file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"},
|
360 |
+
{file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"},
|
361 |
+
]
|
362 |
+
|
363 |
+
[package.dependencies]
|
364 |
+
packaging = "*"
|
365 |
+
|
366 |
[[package]]
|
367 |
name = "dirtyjson"
|
368 |
version = "1.0.8"
|
|
|
473 |
pycodestyle = ">=2.11.0,<2.12.0"
|
474 |
pyflakes = ">=3.2.0,<3.3.0"
|
475 |
|
476 |
+
[[package]]
|
477 |
+
name = "flupy"
|
478 |
+
version = "1.2.0"
|
479 |
+
description = "Method chaining built on generators"
|
480 |
+
optional = false
|
481 |
+
python-versions = "*"
|
482 |
+
files = [
|
483 |
+
{file = "flupy-1.2.0.tar.gz", hash = "sha256:12487a008e9744cd35d0f6ea3cfa06f4b2b27cb138bf57d0788f5c26e57afe69"},
|
484 |
+
]
|
485 |
+
|
486 |
+
[package.dependencies]
|
487 |
+
typing_extensions = "*"
|
488 |
+
|
489 |
+
[package.extras]
|
490 |
+
dev = ["black", "mypy", "pre-commit", "pylint", "pytest", "pytest-benchmark", "pytest-cov"]
|
491 |
+
|
492 |
[[package]]
|
493 |
name = "frozenlist"
|
494 |
version = "1.4.1"
|
|
|
610 |
ssh = ["paramiko"]
|
611 |
tqdm = ["tqdm"]
|
612 |
|
613 |
+
[[package]]
|
614 |
+
name = "gotrue"
|
615 |
+
version = "2.4.1"
|
616 |
+
description = "Python Client Library for GoTrue"
|
617 |
+
optional = false
|
618 |
+
python-versions = ">=3.8,<4.0"
|
619 |
+
files = [
|
620 |
+
{file = "gotrue-2.4.1-py3-none-any.whl", hash = "sha256:9647bb7a585c969d26667df21168fa20b18f91c5d6afe286af08d7a0610fd2cc"},
|
621 |
+
{file = "gotrue-2.4.1.tar.gz", hash = "sha256:8b260ef285f45a3a2f9b5a006f12afb9fad7a36a28fa277f19e733f22eb88584"},
|
622 |
+
]
|
623 |
+
|
624 |
+
[package.dependencies]
|
625 |
+
httpx = ">=0.23,<0.26"
|
626 |
+
pydantic = ">=1.10,<3"
|
627 |
+
|
628 |
[[package]]
|
629 |
name = "greenlet"
|
630 |
version = "3.0.3"
|
|
|
778 |
|
779 |
[[package]]
|
780 |
name = "httpx"
|
781 |
+
version = "0.25.2"
|
782 |
description = "The next generation HTTP client."
|
783 |
optional = false
|
784 |
python-versions = ">=3.8"
|
785 |
files = [
|
786 |
+
{file = "httpx-0.25.2-py3-none-any.whl", hash = "sha256:a05d3d052d9b2dfce0e3896636467f8a5342fb2b902c819428e1ac65413ca118"},
|
787 |
+
{file = "httpx-0.25.2.tar.gz", hash = "sha256:8b8fcaa0c8ea7b05edd69a094e63a2094c4efcb48129fb757361bc423c0ad9e8"},
|
788 |
]
|
789 |
|
790 |
[package.dependencies]
|
|
|
1605 |
test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"]
|
1606 |
xml = ["lxml (>=4.9.2)"]
|
1607 |
|
1608 |
+
[[package]]
|
1609 |
+
name = "pgvector"
|
1610 |
+
version = "0.1.8"
|
1611 |
+
description = "pgvector support for Python"
|
1612 |
+
optional = false
|
1613 |
+
python-versions = ">=3.6"
|
1614 |
+
files = [
|
1615 |
+
{file = "pgvector-0.1.8-py2.py3-none-any.whl", hash = "sha256:99dce3a6580ef73863edb9b8441937671f4e1a09383826e6b0838176cd441a96"},
|
1616 |
+
]
|
1617 |
+
|
1618 |
+
[package.dependencies]
|
1619 |
+
numpy = "*"
|
1620 |
+
|
1621 |
[[package]]
|
1622 |
name = "pluggy"
|
1623 |
version = "1.4.0"
|
|
|
1633 |
dev = ["pre-commit", "tox"]
|
1634 |
testing = ["pytest", "pytest-benchmark"]
|
1635 |
|
1636 |
+
[[package]]
|
1637 |
+
name = "postgrest"
|
1638 |
+
version = "0.16.1"
|
1639 |
+
description = "PostgREST client for Python. This library provides an ORM interface to PostgREST."
|
1640 |
+
optional = false
|
1641 |
+
python-versions = ">=3.8,<4.0"
|
1642 |
+
files = [
|
1643 |
+
{file = "postgrest-0.16.1-py3-none-any.whl", hash = "sha256:412ec6bf61c58f38c92b6b61f57ab50e25c73ca9ef415a6f56ed9cf5429614cb"},
|
1644 |
+
{file = "postgrest-0.16.1.tar.gz", hash = "sha256:d955824d37e7123a8313cbf10c8e0a8d42418fcb942cd8e1526e8509fb71574d"},
|
1645 |
+
]
|
1646 |
+
|
1647 |
+
[package.dependencies]
|
1648 |
+
deprecation = ">=2.1.0,<3.0.0"
|
1649 |
+
httpx = ">=0.24,<0.26"
|
1650 |
+
pydantic = ">=1.9,<3.0"
|
1651 |
+
strenum = ">=0.4.9,<0.5.0"
|
1652 |
+
|
1653 |
+
[[package]]
|
1654 |
+
name = "psycopg2-binary"
|
1655 |
+
version = "2.9.9"
|
1656 |
+
description = "psycopg2 - Python-PostgreSQL Database Adapter"
|
1657 |
+
optional = false
|
1658 |
+
python-versions = ">=3.7"
|
1659 |
+
files = [
|
1660 |
+
{file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"},
|
1661 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"},
|
1662 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"},
|
1663 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"},
|
1664 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"},
|
1665 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"},
|
1666 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"},
|
1667 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"},
|
1668 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"},
|
1669 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"},
|
1670 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"},
|
1671 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"},
|
1672 |
+
{file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"},
|
1673 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"},
|
1674 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"},
|
1675 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"},
|
1676 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"},
|
1677 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"},
|
1678 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"},
|
1679 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"},
|
1680 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"},
|
1681 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"},
|
1682 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"},
|
1683 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"},
|
1684 |
+
{file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"},
|
1685 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"},
|
1686 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"},
|
1687 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"},
|
1688 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"},
|
1689 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"},
|
1690 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"},
|
1691 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"},
|
1692 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"},
|
1693 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"},
|
1694 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"},
|
1695 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"},
|
1696 |
+
{file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"},
|
1697 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"},
|
1698 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"},
|
1699 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"},
|
1700 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"},
|
1701 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"},
|
1702 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"},
|
1703 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"},
|
1704 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"},
|
1705 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"},
|
1706 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"},
|
1707 |
+
{file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"},
|
1708 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"},
|
1709 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"},
|
1710 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"},
|
1711 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"},
|
1712 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"},
|
1713 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"},
|
1714 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"},
|
1715 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"},
|
1716 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"},
|
1717 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"},
|
1718 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"},
|
1719 |
+
{file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"},
|
1720 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"},
|
1721 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"},
|
1722 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"},
|
1723 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"},
|
1724 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"},
|
1725 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"},
|
1726 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"},
|
1727 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"},
|
1728 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"},
|
1729 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"},
|
1730 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"},
|
1731 |
+
{file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"},
|
1732 |
+
]
|
1733 |
+
|
1734 |
[[package]]
|
1735 |
name = "pycodestyle"
|
1736 |
version = "2.11.1"
|
|
|
1863 |
{file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"},
|
1864 |
]
|
1865 |
|
1866 |
+
[[package]]
|
1867 |
+
name = "pyjwt"
|
1868 |
+
version = "2.8.0"
|
1869 |
+
description = "JSON Web Token implementation in Python"
|
1870 |
+
optional = false
|
1871 |
+
python-versions = ">=3.7"
|
1872 |
+
files = [
|
1873 |
+
{file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"},
|
1874 |
+
{file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"},
|
1875 |
+
]
|
1876 |
+
|
1877 |
+
[package.extras]
|
1878 |
+
crypto = ["cryptography (>=3.4.0)"]
|
1879 |
+
dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
|
1880 |
+
docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"]
|
1881 |
+
tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"]
|
1882 |
+
|
1883 |
[[package]]
|
1884 |
name = "pypdf"
|
1885 |
version = "3.17.4"
|
|
|
2005 |
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
|
2006 |
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
|
2007 |
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
|
|
|
2008 |
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
|
2009 |
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
|
2010 |
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
|
|
|
2039 |
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
|
2040 |
]
|
2041 |
|
2042 |
+
[[package]]
|
2043 |
+
name = "realtime"
|
2044 |
+
version = "1.0.2"
|
2045 |
+
description = ""
|
2046 |
+
optional = false
|
2047 |
+
python-versions = ">=3.8,<4.0"
|
2048 |
+
files = [
|
2049 |
+
{file = "realtime-1.0.2-py3-none-any.whl", hash = "sha256:8f8375199fd917cd0ded818702321f91b208ab72794ade0a33cee9d55ae30f11"},
|
2050 |
+
{file = "realtime-1.0.2.tar.gz", hash = "sha256:776170a4329edc869b91e104c554cda02c8bf8e052cbb93c377e22482870959c"},
|
2051 |
+
]
|
2052 |
+
|
2053 |
+
[package.dependencies]
|
2054 |
+
python-dateutil = ">=2.8.1,<3.0.0"
|
2055 |
+
typing-extensions = ">=4.2.0,<5.0.0"
|
2056 |
+
websockets = ">=11.0,<12.0"
|
2057 |
+
|
2058 |
[[package]]
|
2059 |
name = "regex"
|
2060 |
version = "2023.12.25"
|
|
|
2436 |
[package.extras]
|
2437 |
full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"]
|
2438 |
|
2439 |
+
[[package]]
|
2440 |
+
name = "storage3"
|
2441 |
+
version = "0.7.3"
|
2442 |
+
description = "Supabase Storage client for Python."
|
2443 |
+
optional = false
|
2444 |
+
python-versions = ">=3.8,<4.0"
|
2445 |
+
files = [
|
2446 |
+
{file = "storage3-0.7.3-py3-none-any.whl", hash = "sha256:dc6a59da801ee6fc00015da4967ac0b5c3e5508d31ffd796f0e4c83957e5c6a0"},
|
2447 |
+
{file = "storage3-0.7.3.tar.gz", hash = "sha256:943c31de4a7c7490ad7960d963a6b410979ebd0e1b3d320d76cb61564ab0b528"},
|
2448 |
+
]
|
2449 |
+
|
2450 |
+
[package.dependencies]
|
2451 |
+
httpx = ">=0.24,<0.26"
|
2452 |
+
python-dateutil = ">=2.8.2,<3.0.0"
|
2453 |
+
typing-extensions = ">=4.2.0,<5.0.0"
|
2454 |
+
|
2455 |
+
[[package]]
|
2456 |
+
name = "strenum"
|
2457 |
+
version = "0.4.15"
|
2458 |
+
description = "An Enum that inherits from str."
|
2459 |
+
optional = false
|
2460 |
+
python-versions = "*"
|
2461 |
+
files = [
|
2462 |
+
{file = "StrEnum-0.4.15-py3-none-any.whl", hash = "sha256:a30cda4af7cc6b5bf52c8055bc4bf4b2b6b14a93b574626da33df53cf7740659"},
|
2463 |
+
{file = "StrEnum-0.4.15.tar.gz", hash = "sha256:878fb5ab705442070e4dd1929bb5e2249511c0bcf2b0eeacf3bcd80875c82eff"},
|
2464 |
+
]
|
2465 |
+
|
2466 |
+
[package.extras]
|
2467 |
+
docs = ["myst-parser[linkify]", "sphinx", "sphinx-rtd-theme"]
|
2468 |
+
release = ["twine"]
|
2469 |
+
test = ["pylint", "pytest", "pytest-black", "pytest-cov", "pytest-pylint"]
|
2470 |
+
|
2471 |
+
[[package]]
|
2472 |
+
name = "supabase"
|
2473 |
+
version = "2.4.0"
|
2474 |
+
description = "Supabase client for Python."
|
2475 |
+
optional = false
|
2476 |
+
python-versions = ">=3.8,<4.0"
|
2477 |
+
files = [
|
2478 |
+
{file = "supabase-2.4.0-py3-none-any.whl", hash = "sha256:f2f02b0e7903247ef9e2b3cb5dde067924a19a068f1c8befbdf40fb091bf8dd3"},
|
2479 |
+
{file = "supabase-2.4.0.tar.gz", hash = "sha256:d51556d3884f2e6f4588c33f1fcac954d4304238253bc35e9a87fdd22c43bafb"},
|
2480 |
+
]
|
2481 |
+
|
2482 |
+
[package.dependencies]
|
2483 |
+
gotrue = ">=1.3,<3.0"
|
2484 |
+
httpx = ">=0.24,<0.26"
|
2485 |
+
postgrest = ">=0.10.8,<0.17.0"
|
2486 |
+
realtime = ">=1.0.0,<2.0.0"
|
2487 |
+
storage3 = ">=0.5.3,<0.8.0"
|
2488 |
+
supafunc = ">=0.3.1,<0.4.0"
|
2489 |
+
|
2490 |
+
[[package]]
|
2491 |
+
name = "supafunc"
|
2492 |
+
version = "0.3.3"
|
2493 |
+
description = "Library for Supabase Functions"
|
2494 |
+
optional = false
|
2495 |
+
python-versions = ">=3.8,<4.0"
|
2496 |
+
files = [
|
2497 |
+
{file = "supafunc-0.3.3-py3-none-any.whl", hash = "sha256:8260b4742335932f9cab64c8f66fb6998681b7e8ca7a46b559a4eb640cc0af80"},
|
2498 |
+
{file = "supafunc-0.3.3.tar.gz", hash = "sha256:c35897a2f40465b40d7a08ae11f872f08eb8d1390c3ebc72c80e27d33ba91b99"},
|
2499 |
+
]
|
2500 |
+
|
2501 |
+
[package.dependencies]
|
2502 |
+
httpx = ">=0.24,<0.26"
|
2503 |
+
|
2504 |
[[package]]
|
2505 |
name = "sympy"
|
2506 |
version = "1.12"
|
|
|
3008 |
docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
|
3009 |
test = ["Cython (>=0.29.36,<0.30.0)", "aiohttp (==3.9.0b0)", "aiohttp (>=3.8.1)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"]
|
3010 |
|
3011 |
+
[[package]]
|
3012 |
+
name = "vecs"
|
3013 |
+
version = "0.4.3"
|
3014 |
+
description = "pgvector client"
|
3015 |
+
optional = false
|
3016 |
+
python-versions = "*"
|
3017 |
+
files = [
|
3018 |
+
{file = "vecs-0.4.3.tar.gz", hash = "sha256:0a60294143aec43bd0344bb9235b6e57f8f919d102538f6b989d7b85095a31ce"},
|
3019 |
+
]
|
3020 |
+
|
3021 |
+
[package.dependencies]
|
3022 |
+
deprecated = "==1.2.*"
|
3023 |
+
flupy = "==1.*"
|
3024 |
+
pgvector = "==0.1.*"
|
3025 |
+
psycopg2-binary = "==2.9.*"
|
3026 |
+
sqlalchemy = "==2.*"
|
3027 |
+
|
3028 |
+
[package.extras]
|
3029 |
+
dev = ["numpy", "parse", "pytest", "pytest-cov"]
|
3030 |
+
docs = ["mike", "mkdocs", "pygments", "pymarkdown", "pymdown-extensions"]
|
3031 |
+
text-embedding = ["sentence-transformers (==2.*)"]
|
3032 |
+
|
3033 |
[[package]]
|
3034 |
name = "watchfiles"
|
3035 |
version = "0.21.0"
|
|
|
3119 |
|
3120 |
[[package]]
|
3121 |
name = "websockets"
|
3122 |
+
version = "11.0.3"
|
3123 |
description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
|
3124 |
optional = false
|
3125 |
+
python-versions = ">=3.7"
|
3126 |
files = [
|
3127 |
+
{file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"},
|
3128 |
+
{file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"},
|
3129 |
+
{file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"},
|
3130 |
+
{file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"},
|
3131 |
+
{file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"},
|
3132 |
+
{file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"},
|
3133 |
+
{file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"},
|
3134 |
+
{file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"},
|
3135 |
+
{file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"},
|
3136 |
+
{file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"},
|
3137 |
+
{file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"},
|
3138 |
+
{file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"},
|
3139 |
+
{file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"},
|
3140 |
+
{file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"},
|
3141 |
+
{file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"},
|
3142 |
+
{file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"},
|
3143 |
+
{file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"},
|
3144 |
+
{file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"},
|
3145 |
+
{file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"},
|
3146 |
+
{file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"},
|
3147 |
+
{file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"},
|
3148 |
+
{file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"},
|
3149 |
+
{file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"},
|
3150 |
+
{file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"},
|
3151 |
+
{file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"},
|
3152 |
+
{file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"},
|
3153 |
+
{file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"},
|
3154 |
+
{file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"},
|
3155 |
+
{file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"},
|
3156 |
+
{file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"},
|
3157 |
+
{file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"},
|
3158 |
+
{file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"},
|
3159 |
+
{file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"},
|
3160 |
+
{file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"},
|
3161 |
+
{file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"},
|
3162 |
+
{file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"},
|
3163 |
+
{file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"},
|
3164 |
+
{file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"},
|
3165 |
+
{file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"},
|
3166 |
+
{file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"},
|
3167 |
+
{file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"},
|
3168 |
+
{file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"},
|
3169 |
+
{file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"},
|
3170 |
+
{file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"},
|
3171 |
+
{file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"},
|
3172 |
+
{file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"},
|
3173 |
+
{file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"},
|
3174 |
+
{file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"},
|
3175 |
+
{file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"},
|
3176 |
+
{file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"},
|
3177 |
+
{file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"},
|
3178 |
+
{file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"},
|
3179 |
+
{file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"},
|
3180 |
+
{file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"},
|
3181 |
+
{file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"},
|
3182 |
+
{file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"},
|
3183 |
+
{file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"},
|
3184 |
+
{file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"},
|
3185 |
+
{file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"},
|
3186 |
+
{file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"},
|
3187 |
+
{file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"},
|
3188 |
+
{file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"},
|
3189 |
+
{file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"},
|
3190 |
+
{file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"},
|
3191 |
+
{file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"},
|
3192 |
+
{file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"},
|
3193 |
+
{file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"},
|
3194 |
+
{file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"},
|
3195 |
+
{file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"},
|
3196 |
+
{file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"},
|
|
|
|
|
3197 |
]
|
3198 |
|
3199 |
[[package]]
|
|
|
3381 |
[metadata]
|
3382 |
lock-version = "2.0"
|
3383 |
python-versions = "^3.11,<3.12"
|
3384 |
+
content-hash = "1d354d5ac24eb7e482fe9895fef5888582caeb9d6b52982db87f88a8f968da19"
|
@@ -17,6 +17,9 @@ llama-cpp-python = "^0.2.52"
|
|
17 |
transformers = "^4.38.1"
|
18 |
docx2txt = "^0.8"
|
19 |
doc2docx = "^0.2.4"
|
|
|
|
|
|
|
20 |
|
21 |
[tool.poetry.group.dev]
|
22 |
optional = true
|
|
|
17 |
transformers = "^4.38.1"
|
18 |
docx2txt = "^0.8"
|
19 |
doc2docx = "^0.2.4"
|
20 |
+
supabase = "^2.4.0"
|
21 |
+
pyjwt = "^2.8.0"
|
22 |
+
vecs = "^0.4.3"
|
23 |
|
24 |
[tool.poetry.group.dev]
|
25 |
optional = true
|
@@ -4,7 +4,7 @@ export default function About() {
|
|
4 |
|
5 |
return (
|
6 |
<div className="rounded-xl shadow-xl p-4 max-w-5xl w-full">
|
7 |
-
<div className="max-w-
|
8 |
<div className="bg-gradient-to-r from-blue-500 to-indigo-500 text-white p-8 rounded-lg shadow-lg">
|
9 |
<h1 className="text-2xl md:text-4xl font-bold mb-4">About Smart Retrieval</h1>
|
10 |
<p className="text-l mb-4">
|
@@ -19,6 +19,63 @@ export default function About() {
|
|
19 |
With cutting-edge technology, including Large Language Models (LLM) like GPT, BERT, and advanced chatbot
|
20 |
integration, we aim to revolutionize the way JTC employees access and comprehend crucial documents.
|
21 |
</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
</div>
|
23 |
</div>
|
24 |
</div>
|
|
|
4 |
|
5 |
return (
|
6 |
<div className="rounded-xl shadow-xl p-4 max-w-5xl w-full">
|
7 |
+
<div className="max-w-4xl mx-auto p-4">
|
8 |
<div className="bg-gradient-to-r from-blue-500 to-indigo-500 text-white p-8 rounded-lg shadow-lg">
|
9 |
<h1 className="text-2xl md:text-4xl font-bold mb-4">About Smart Retrieval</h1>
|
10 |
<p className="text-l mb-4">
|
|
|
19 |
With cutting-edge technology, including Large Language Models (LLM) like GPT, BERT, and advanced chatbot
|
20 |
integration, we aim to revolutionize the way JTC employees access and comprehend crucial documents.
|
21 |
</p>
|
22 |
+
<p className="text-l mb-4">
|
23 |
+
We are committed to providing accurate, reliable, and user-friendly information retrieval services that
|
24 |
+
empower JTC employees to make informed decisions and stay compliant with the latest regulations.
|
25 |
+
</p>
|
26 |
+
<p className="text-l mb-4">
|
27 |
+
Thank you for choosing Smart Retrieval. We look forward to serving you and making your work life easier.
|
28 |
+
</p>
|
29 |
+
|
30 |
+
<h2 className="text-2xl md:text-4xl font-bold mt-8 mb-4">Functions of Smart Retrieval</h2>
|
31 |
+
<p className="text-l mb-4">
|
32 |
+
Smart Retrieval is designed to provide users with a seamless and efficient experience when searching for
|
33 |
+
information. Some of the key functions of Smart Retrieval include:
|
34 |
+
</p>
|
35 |
+
<ul className="list-disc list-inside mb-4">
|
36 |
+
<li className="text-l">Chat</li>
|
37 |
+
<li className="text-l">Question & Answer (Q&A)</li>
|
38 |
+
<li className="text-l">Search</li>
|
39 |
+
</ul>
|
40 |
+
<p className="text-l mb-4">
|
41 |
+
These functions are designed to cater to different user preferences and requirements, ensuring that users
|
42 |
+
can easily access the information they need.
|
43 |
+
</p>
|
44 |
+
|
45 |
+
<h2 className="text-2xl md:text-4xl font-bold mt-8 mb-4">Functions In-Depth</h2>
|
46 |
+
<h3 className="text-xl md:text-2xl font-bold mt-4 mb-2">Chat</h3>
|
47 |
+
<p className="text-l mb-4">
|
48 |
+
The chat function allows users to interact with Smart Retrieval through a conversational interface via a fixed set of documents.
|
49 |
+
Users can ask questions, seek information, and engage in dialogue with the system to retrieve the information they
|
50 |
+
need. The chat function is designed to be intuitive, user-friendly, and responsive, providing users with a
|
51 |
+
seamless experience. Current document sets include:
|
52 |
+
</p>
|
53 |
+
<ul className="list-disc list-inside mb-4">
|
54 |
+
<li className="text-l">PSSCOC</li>
|
55 |
+
<li className="text-l">EIR</li>
|
56 |
+
<li className="text-l">And more...</li>
|
57 |
+
</ul>
|
58 |
+
|
59 |
+
<h3 className="text-xl md:text-2xl font-bold mt-4 mb-2">Question & Answer (Q&A)</h3>
|
60 |
+
<p className="text-l mb-4">
|
61 |
+
The Q&A function enables users to ask specific questions relating to their own documents and receive accurate answers from Smart Retrieval.
|
62 |
+
Users can simply upload their files and input their queries, and the system will provide relevant information based on the question asked.
|
63 |
+
The Q&A function is designed to provide users with precise and concise answers to their queries, enhancing
|
64 |
+
the information retrieval process.
|
65 |
+
</p>
|
66 |
+
|
67 |
+
<h3 className="text-xl md:text-2xl font-bold mt-4 mb-2">Search</h3>
|
68 |
+
<p className="text-l mb-4">
|
69 |
+
The search function allows users to input keywords or phrases to search for specific information within the
|
70 |
+
document repository. Users can enter their search queries, and the system will retrieve relevant documents
|
71 |
+
based on the search terms provided. The search function is designed to help users quickly locate the
|
72 |
+
information they need, streamlining the search process.
|
73 |
+
</p>
|
74 |
+
|
75 |
+
<h2 className="text-2xl md:text-4xl font-bold mt-8 mb-4">Disclaimer</h2>
|
76 |
+
<p className="text-l mb-4">
|
77 |
+
The answer provided by Smart Retrieval may not be accurate and might be prone to hallucination. Users are advised to fact-check the answer and not use it as is. Smart Retrieval is not responsible for any consequences arising from the use of the answer.
|
78 |
+
</p>
|
79 |
</div>
|
80 |
</div>
|
81 |
</div>
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { createClient } from '@supabase/supabase-js';
|
2 |
+
import { NextRequest, NextResponse } from "next/server";
|
3 |
+
|
4 |
+
export async function GET(request: NextRequest) {
|
5 |
+
const { pathname, origin } = request.nextUrl;
|
6 |
+
const signinPage = new URL('/sign-in', origin);
|
7 |
+
// Retrieve the session token from the request cookies
|
8 |
+
const session = request.cookies.get('next-auth.session-token');
|
9 |
+
|
10 |
+
// Create a new Supabase client
|
11 |
+
const supabase = createClient(
|
12 |
+
process.env.SUPABASE_URL ?? '',
|
13 |
+
process.env.SUPABASE_SERVICE_ROLE_KEY ?? '',
|
14 |
+
{ db: { schema: 'next_auth' } },
|
15 |
+
);
|
16 |
+
|
17 |
+
// Retrieve the user's ID from the session token
|
18 |
+
const { data: sessionData, error: sessionError } = await supabase
|
19 |
+
.from('sessions')
|
20 |
+
.select('userId')
|
21 |
+
.eq('sessionToken', session?.value)
|
22 |
+
.single();
|
23 |
+
|
24 |
+
const userId = sessionData?.userId;
|
25 |
+
|
26 |
+
if (sessionError) {
|
27 |
+
console.error('Error fetching session from database:', sessionError.message);
|
28 |
+
return NextResponse.redirect(signinPage.href, { status: 302 });
|
29 |
+
}
|
30 |
+
|
31 |
+
// Retrieve the user's profile data
|
32 |
+
const { data: userData, error: userError } = await supabase
|
33 |
+
.from('users')
|
34 |
+
.select('id, name, email, image')
|
35 |
+
.eq('id', userId)
|
36 |
+
.single();
|
37 |
+
|
38 |
+
if (userError) {
|
39 |
+
console.error('Error fetching user data from database:', userError.message);
|
40 |
+
return NextResponse.redirect(signinPage.href, { status: 302 });
|
41 |
+
}
|
42 |
+
|
43 |
+
// console.log('userData:', userData);
|
44 |
+
|
45 |
+
return NextResponse.json({ userData: userData });
|
46 |
+
}
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export async function GET(request: Request) {
|
2 |
+
const healthcheck_api = process.env.NEXT_PUBLIC_HEALTHCHECK_API as string;
|
3 |
+
|
4 |
+
// Retrieve the session token from the request headers
|
5 |
+
let session = request.headers.get('Authorization');
|
6 |
+
|
7 |
+
console.log('Status API - headers:', request.headers);
|
8 |
+
|
9 |
+
// Public API key
|
10 |
+
let api_key = null;
|
11 |
+
|
12 |
+
// If no session, use the public API key
|
13 |
+
if (!session) {
|
14 |
+
api_key = process.env.BACKEND_API_KEY as string;
|
15 |
+
}
|
16 |
+
|
17 |
+
const res = await fetch(healthcheck_api, {
|
18 |
+
signal: AbortSignal.timeout(5000), // Abort the request if it takes longer than 5 seconds
|
19 |
+
headers: {
|
20 |
+
'Content-Type': 'application/json',
|
21 |
+
'Authorization': session,
|
22 |
+
'X-API-Key': api_key,
|
23 |
+
} as any,
|
24 |
+
})
|
25 |
+
const data = await res.json()
|
26 |
+
|
27 |
+
return Response.json({ data })
|
28 |
+
}
|
@@ -2,9 +2,15 @@
|
|
2 |
|
3 |
import { useChat } from "ai/react";
|
4 |
import { ChatInput, ChatMessages } from "@/app/components/ui/chat";
|
|
|
5 |
import AutofillQuestion from "@/app/components/ui/autofill-prompt/autofill-prompt-dialog";
|
|
|
|
|
6 |
|
7 |
export default function ChatSection() {
|
|
|
|
|
|
|
8 |
const {
|
9 |
messages,
|
10 |
input,
|
@@ -13,29 +19,51 @@ export default function ChatSection() {
|
|
13 |
handleInputChange,
|
14 |
reload,
|
15 |
stop,
|
16 |
-
} = useChat({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
return (
|
19 |
<div className="space-y-4 max-w-5xl w-full relative">
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
</div>
|
40 |
);
|
41 |
}
|
|
|
2 |
|
3 |
import { useChat } from "ai/react";
|
4 |
import { ChatInput, ChatMessages } from "@/app/components/ui/chat";
|
5 |
+
import ChatSelection from "./ui/chat/chat-selection";
|
6 |
import AutofillQuestion from "@/app/components/ui/autofill-prompt/autofill-prompt-dialog";
|
7 |
+
import { useSession } from "next-auth/react";
|
8 |
+
import { useState } from "react";
|
9 |
|
10 |
export default function ChatSection() {
|
11 |
+
const { data: session } = useSession();
|
12 |
+
const supabaseAccessToken = session?.supabaseAccessToken;
|
13 |
+
const [docSelected, setDocSelected] = useState<string>('');
|
14 |
const {
|
15 |
messages,
|
16 |
input,
|
|
|
19 |
handleInputChange,
|
20 |
reload,
|
21 |
stop,
|
22 |
+
} = useChat({
|
23 |
+
api: process.env.NEXT_PUBLIC_CHAT_API,
|
24 |
+
headers: {
|
25 |
+
// Add the access token to the request headers
|
26 |
+
'Authorization': `Bearer ${supabaseAccessToken}`,
|
27 |
+
},
|
28 |
+
body: {
|
29 |
+
// Add the selected document to the request body
|
30 |
+
document: docSelected,
|
31 |
+
},
|
32 |
+
});
|
33 |
|
34 |
return (
|
35 |
<div className="space-y-4 max-w-5xl w-full relative">
|
36 |
+
{docSelected ?
|
37 |
+
(
|
38 |
+
<>
|
39 |
+
<ChatMessages
|
40 |
+
messages={messages}
|
41 |
+
isLoading={isLoading}
|
42 |
+
reload={reload}
|
43 |
+
stop={stop}
|
44 |
+
/>
|
45 |
+
<AutofillQuestion
|
46 |
+
docSelected={docSelected}
|
47 |
+
messages={messages}
|
48 |
+
isLoading={isLoading}
|
49 |
+
handleSubmit={handleSubmit}
|
50 |
+
handleInputChange={handleInputChange}
|
51 |
+
input={input}
|
52 |
+
/>
|
53 |
+
<ChatInput
|
54 |
+
input={input}
|
55 |
+
handleSubmit={handleSubmit}
|
56 |
+
handleInputChange={handleInputChange}
|
57 |
+
isLoading={isLoading}
|
58 |
+
/>
|
59 |
+
</>
|
60 |
+
)
|
61 |
+
:
|
62 |
+
<ChatSelection
|
63 |
+
docSelected={docSelected}
|
64 |
+
handleDocSelect={setDocSelected}
|
65 |
+
/>
|
66 |
+
}
|
67 |
</div>
|
68 |
);
|
69 |
}
|
@@ -1,32 +1,50 @@
|
|
1 |
"use client";
|
2 |
|
|
|
3 |
import { FooterNavLink } from "@/app/components/ui/navlink";
|
4 |
import { IconGitHub } from "@/app/components/ui/icons";
|
5 |
-
import { Text, Cookie } from "lucide-react";
|
6 |
|
7 |
export default function Footer() {
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
return (
|
9 |
-
<footer>
|
10 |
<div className="flex flex-col items-center justify-center bg-gray-800 text-white p-4 mb-4 rounded-lg shadow-xl">
|
11 |
-
<div className="flex flex-col items-center">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
<p className="text-sm text-center">
|
13 |
Β© 2024 JTC DBE. All rights reserved.
|
14 |
</p>
|
15 |
</div>
|
16 |
<div className="flex items-center mt-2 gap-4">
|
17 |
-
<FooterNavLink href="https://github.com/digitalbuiltenvironment/Smart-Retrieval/" target="_blank">
|
18 |
<div className="text-sm text-center underline">
|
19 |
<IconGitHub className="h-5 w-5 inline mr-2 mb-1" />
|
20 |
Github
|
21 |
</div>
|
22 |
</FooterNavLink>
|
23 |
-
<FooterNavLink href="/terms-of-service">
|
24 |
<div className="text-sm text-center underline">
|
25 |
<Text className="h-5 w-5 inline mr-2 mb-1" />
|
26 |
Terms of Service
|
27 |
</div>
|
28 |
</FooterNavLink>
|
29 |
-
<FooterNavLink href="/privacy-policy">
|
30 |
<div className="text-sm text-center underline">
|
31 |
<Cookie className="h-5 w-5 inline mr-2 mb-1" />
|
32 |
Privacy Policy
|
@@ -37,4 +55,3 @@ export default function Footer() {
|
|
37 |
</footer>
|
38 |
);
|
39 |
}
|
40 |
-
|
|
|
1 |
"use client";
|
2 |
|
3 |
+
import { useState } from "react";
|
4 |
import { FooterNavLink } from "@/app/components/ui/navlink";
|
5 |
import { IconGitHub } from "@/app/components/ui/icons";
|
6 |
+
import { Text, Cookie, AlertCircle } from "lucide-react";
|
7 |
|
8 |
export default function Footer() {
|
9 |
+
const [showDisclaimer, setShowDisclaimer] = useState(false);
|
10 |
+
|
11 |
+
const toggleDisclaimer = () => {
|
12 |
+
setShowDisclaimer(!showDisclaimer);
|
13 |
+
};
|
14 |
+
|
15 |
return (
|
16 |
+
<footer className="z-10">
|
17 |
<div className="flex flex-col items-center justify-center bg-gray-800 text-white p-4 mb-4 rounded-lg shadow-xl">
|
18 |
+
<div className="flex flex-col items-center text-red-500">
|
19 |
+
<button className="text-sm text-center underline" onClick={toggleDisclaimer} title="Disclaimer">
|
20 |
+
<AlertCircle className="h-5 w-5 inline mr-2 mb-1" />
|
21 |
+
Disclaimer
|
22 |
+
</button>
|
23 |
+
{showDisclaimer && (
|
24 |
+
<p className="text-sm text-center w-64 mb-2">
|
25 |
+
The answer provided by Smart Retrieval may not be accurate and might be prone to hallucination. Users are advised to fact check the answer and not use it as is. Smart Retrieval is not responsible for any consequences arising from the use of the answer.
|
26 |
+
</p>
|
27 |
+
)}
|
28 |
+
</div>
|
29 |
+
<div className="flex flex-col items-center mt-2 gap-4">
|
30 |
<p className="text-sm text-center">
|
31 |
Β© 2024 JTC DBE. All rights reserved.
|
32 |
</p>
|
33 |
</div>
|
34 |
<div className="flex items-center mt-2 gap-4">
|
35 |
+
<FooterNavLink href="https://github.com/digitalbuiltenvironment/Smart-Retrieval/" title="Github" target="_blank">
|
36 |
<div className="text-sm text-center underline">
|
37 |
<IconGitHub className="h-5 w-5 inline mr-2 mb-1" />
|
38 |
Github
|
39 |
</div>
|
40 |
</FooterNavLink>
|
41 |
+
<FooterNavLink href="/terms-of-service" title="Terms Of Service">
|
42 |
<div className="text-sm text-center underline">
|
43 |
<Text className="h-5 w-5 inline mr-2 mb-1" />
|
44 |
Terms of Service
|
45 |
</div>
|
46 |
</FooterNavLink>
|
47 |
+
<FooterNavLink href="/privacy-policy" title="Privacy Policy">
|
48 |
<div className="text-sm text-center underline">
|
49 |
<Cookie className="h-5 w-5 inline mr-2 mb-1" />
|
50 |
Privacy Policy
|
|
|
55 |
</footer>
|
56 |
);
|
57 |
}
|
|
@@ -1,7 +1,7 @@
|
|
1 |
"use client";
|
2 |
|
3 |
import Image from 'next/image';
|
4 |
-
import { Home, InfoIcon, MessageCircle, Search, FileQuestion, Menu, X } from 'lucide-react';
|
5 |
import { useTheme } from "next-themes";
|
6 |
import { useEffect, useState } from "react";
|
7 |
import { useMedia } from 'react-use';
|
@@ -9,7 +9,9 @@ import useSWR from 'swr';
|
|
9 |
import logo from '@/public/smart-retrieval-logo.webp';
|
10 |
import { HeaderNavLink } from '@/app/components/ui/navlink';
|
11 |
import { MobileMenu } from '@/app/components/ui/mobilemenu';
|
12 |
-
import { IconSpinner } from '@/app/components/ui/icons'
|
|
|
|
|
13 |
|
14 |
const MobileMenuItems = [
|
15 |
{
|
@@ -43,13 +45,28 @@ export default function Header() {
|
|
43 |
const isLargeScreen = useMedia('(min-width: 1024px)', false);
|
44 |
const [mounted, setMounted] = useState(false);
|
45 |
const { theme, setTheme } = useTheme();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
// Use SWR for API status fetching
|
47 |
-
const healthcheck_api =
|
48 |
const { data, error: apiError, isLoading } = useSWR(healthcheck_api, async (url) => {
|
49 |
try {
|
50 |
// Fetch the data
|
51 |
const response = await fetch(url, {
|
52 |
signal: AbortSignal.timeout(5000), // Abort the request if it takes longer than 5 seconds
|
|
|
|
|
|
|
|
|
53 |
});
|
54 |
if (!response.ok) {
|
55 |
throw new Error(response.statusText || 'Unknown Error');
|
@@ -74,6 +91,7 @@ export default function Header() {
|
|
74 |
}
|
75 |
}
|
76 |
|
|
|
77 |
useEffect(() => {
|
78 |
setMounted(true);
|
79 |
}, []);
|
@@ -135,31 +153,31 @@ export default function Header() {
|
|
135 |
</button>
|
136 |
</div>
|
137 |
<div className={`hidden items-center gap-4 lg:flex`}>
|
138 |
-
<HeaderNavLink href="/">
|
139 |
<div className="flex items-center transition duration-300 ease-in-out transform hover:scale-125">
|
140 |
<Home className="mr-1 h-4 w-4" />
|
141 |
Home
|
142 |
</div>
|
143 |
</HeaderNavLink>
|
144 |
-
<HeaderNavLink href="/about">
|
145 |
<div className="flex items-center transition duration-300 ease-in-out transform hover:scale-125">
|
146 |
<InfoIcon className="mr-1 h-4 w-4" />
|
147 |
About
|
148 |
</div>
|
149 |
</HeaderNavLink>
|
150 |
-
<HeaderNavLink href="/chat">
|
151 |
<div className="flex items-center transition duration-300 ease-in-out transform hover:scale-125">
|
152 |
<MessageCircle className="mr-1 h-4 w-4" />
|
153 |
Chat
|
154 |
</div>
|
155 |
</HeaderNavLink>
|
156 |
-
<HeaderNavLink href="/query">
|
157 |
<div className="flex items-center transition duration-300 ease-in-out transform hover:scale-125">
|
158 |
<FileQuestion className="mr-1 h-4 w-4" />
|
159 |
Q&A
|
160 |
</div>
|
161 |
</HeaderNavLink>
|
162 |
-
<HeaderNavLink href="/search">
|
163 |
<div className="flex items-center transition duration-300 ease-in-out transform hover:scale-125">
|
164 |
<Search className="mr-1 h-4 w-4" />
|
165 |
Search
|
@@ -169,7 +187,7 @@ export default function Header() {
|
|
169 |
<div className="flex items-center ml-auto">
|
170 |
{/* Status Page Button/Indicator */}
|
171 |
<span className='flex items-center mr-1'>API:</span>
|
172 |
-
<HeaderNavLink href='/status'>
|
173 |
<div className="flex items-center mr-2 text-xl transition duration-300 ease-in-out transform hover:scale-125">
|
174 |
{isLoading ? (
|
175 |
<IconSpinner className="mr-2 animate-spin" />
|
@@ -201,8 +219,46 @@ export default function Header() {
|
|
201 |
</span>
|
202 |
)}
|
203 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
204 |
</div>
|
205 |
</div >
|
|
|
206 |
{/* Mobile menu component */}
|
207 |
< MobileMenu isOpen={isMobileMenuOpen} onClose={() => setMobileMenuOpen(false)
|
208 |
} logoSrc={logo} items={MobileMenuItems} />
|
|
|
1 |
"use client";
|
2 |
|
3 |
import Image from 'next/image';
|
4 |
+
import { Home, InfoIcon, MessageCircle, Search, FileQuestion, Menu, X, User2, LogOut, LogIn } from 'lucide-react';
|
5 |
import { useTheme } from "next-themes";
|
6 |
import { useEffect, useState } from "react";
|
7 |
import { useMedia } from 'react-use';
|
|
|
9 |
import logo from '@/public/smart-retrieval-logo.webp';
|
10 |
import { HeaderNavLink } from '@/app/components/ui/navlink';
|
11 |
import { MobileMenu } from '@/app/components/ui/mobilemenu';
|
12 |
+
import { IconSpinner } from '@/app/components/ui/icons';
|
13 |
+
import { useSession, signOut } from 'next-auth/react';
|
14 |
+
import { usePathname } from 'next/navigation';
|
15 |
|
16 |
const MobileMenuItems = [
|
17 |
{
|
|
|
45 |
const isLargeScreen = useMedia('(min-width: 1024px)', false);
|
46 |
const [mounted, setMounted] = useState(false);
|
47 |
const { theme, setTheme } = useTheme();
|
48 |
+
// Get the current path
|
49 |
+
const currentPath = usePathname();
|
50 |
+
// Ensure the currentPath is encoded
|
51 |
+
const encodedPath = encodeURIComponent(currentPath);
|
52 |
+
// Add callbackUrl params to the signinPage URL
|
53 |
+
const signinPage = "/sign-in?callbackUrl=" + encodedPath;
|
54 |
+
|
55 |
+
// Get user session for conditional rendering of user profile and logout buttons and for fetching the API status
|
56 |
+
const { data: session, status } = useSession();
|
57 |
+
// console.log('session:', session, 'status:', status);
|
58 |
+
const supabaseAccessToken = session?.supabaseAccessToken;
|
59 |
// Use SWR for API status fetching
|
60 |
+
const healthcheck_api = "/api/status";
|
61 |
const { data, error: apiError, isLoading } = useSWR(healthcheck_api, async (url) => {
|
62 |
try {
|
63 |
// Fetch the data
|
64 |
const response = await fetch(url, {
|
65 |
signal: AbortSignal.timeout(5000), // Abort the request if it takes longer than 5 seconds
|
66 |
+
// Add the access token to the request headers
|
67 |
+
headers: {
|
68 |
+
'Authorization': `Bearer ${supabaseAccessToken}`,
|
69 |
+
}
|
70 |
});
|
71 |
if (!response.ok) {
|
72 |
throw new Error(response.statusText || 'Unknown Error');
|
|
|
91 |
}
|
92 |
}
|
93 |
|
94 |
+
|
95 |
useEffect(() => {
|
96 |
setMounted(true);
|
97 |
}, []);
|
|
|
153 |
</button>
|
154 |
</div>
|
155 |
<div className={`hidden items-center gap-4 lg:flex`}>
|
156 |
+
<HeaderNavLink href="/" title='Home'>
|
157 |
<div className="flex items-center transition duration-300 ease-in-out transform hover:scale-125">
|
158 |
<Home className="mr-1 h-4 w-4" />
|
159 |
Home
|
160 |
</div>
|
161 |
</HeaderNavLink>
|
162 |
+
<HeaderNavLink href="/about" title='About Us'>
|
163 |
<div className="flex items-center transition duration-300 ease-in-out transform hover:scale-125">
|
164 |
<InfoIcon className="mr-1 h-4 w-4" />
|
165 |
About
|
166 |
</div>
|
167 |
</HeaderNavLink>
|
168 |
+
<HeaderNavLink href="/chat" title='Chat'>
|
169 |
<div className="flex items-center transition duration-300 ease-in-out transform hover:scale-125">
|
170 |
<MessageCircle className="mr-1 h-4 w-4" />
|
171 |
Chat
|
172 |
</div>
|
173 |
</HeaderNavLink>
|
174 |
+
<HeaderNavLink href="/query" title='Q&A'>
|
175 |
<div className="flex items-center transition duration-300 ease-in-out transform hover:scale-125">
|
176 |
<FileQuestion className="mr-1 h-4 w-4" />
|
177 |
Q&A
|
178 |
</div>
|
179 |
</HeaderNavLink>
|
180 |
+
<HeaderNavLink href="/search" title='Search'>
|
181 |
<div className="flex items-center transition duration-300 ease-in-out transform hover:scale-125">
|
182 |
<Search className="mr-1 h-4 w-4" />
|
183 |
Search
|
|
|
187 |
<div className="flex items-center ml-auto">
|
188 |
{/* Status Page Button/Indicator */}
|
189 |
<span className='flex items-center mr-1'>API:</span>
|
190 |
+
<HeaderNavLink href='/status' title='API Status'>
|
191 |
<div className="flex items-center mr-2 text-xl transition duration-300 ease-in-out transform hover:scale-125">
|
192 |
{isLoading ? (
|
193 |
<IconSpinner className="mr-2 animate-spin" />
|
|
|
219 |
</span>
|
220 |
)}
|
221 |
</button>
|
222 |
+
|
223 |
+
<span className="lg:text-lg font-nunito ml-2 mr-2"> </span>
|
224 |
+
|
225 |
+
{/* Conditionally render the user profile and logout buttons based on the user's authentication status */}
|
226 |
+
{status === 'loading' ? (
|
227 |
+
<div className="flex items-center ml-2 mr-2 text-xl transition duration-300 ease-in-out transform hover:scale-125">
|
228 |
+
<IconSpinner className="mr-2 animate-spin" />
|
229 |
+
</div>
|
230 |
+
) : session ? (
|
231 |
+
<>
|
232 |
+
{/* User Profile Button */}
|
233 |
+
<HeaderNavLink href="/profile" title='Profile'>
|
234 |
+
<div className="flex items-center ml-2 mr-2 text-xl transition duration-300 ease-in-out transform hover:scale-125">
|
235 |
+
<User2 className="mr-1 h-5 w-5" />
|
236 |
+
</div>
|
237 |
+
</HeaderNavLink>
|
238 |
+
|
239 |
+
{/* Sign Out Button */}
|
240 |
+
<button title='Sign Out'
|
241 |
+
onClick={
|
242 |
+
async () => {
|
243 |
+
await signOut();
|
244 |
+
}
|
245 |
+
}>
|
246 |
+
<div className="flex items-center ml-2 text-xl transition duration-300 ease-in-out transform hover:scale-125">
|
247 |
+
<LogOut className="mr-1 h-5 w-5" />
|
248 |
+
</div>
|
249 |
+
</button>
|
250 |
+
</>
|
251 |
+
) : (
|
252 |
+
<HeaderNavLink href={signinPage} title='Sign In'>
|
253 |
+
<div className="flex items-center ml-2 transition duration-300 ease-in-out transform hover:scale-125">
|
254 |
+
<LogIn className="mr-1 h-5 w-5" />
|
255 |
+
Sign In
|
256 |
+
</div>
|
257 |
+
</HeaderNavLink>
|
258 |
+
)}
|
259 |
</div>
|
260 |
</div >
|
261 |
+
|
262 |
{/* Mobile menu component */}
|
263 |
< MobileMenu isOpen={isMobileMenuOpen} onClose={() => setMobileMenuOpen(false)
|
264 |
} logoSrc={logo} items={MobileMenuItems} />
|
@@ -2,9 +2,11 @@
|
|
2 |
|
3 |
import { useChat } from "ai/react";
|
4 |
import { ChatInput, ChatMessages } from "@/app/components/ui/chat";
|
5 |
-
import AutofillQuestion from "
|
|
|
6 |
|
7 |
export default function QuerySection() {
|
|
|
8 |
const {
|
9 |
messages,
|
10 |
input,
|
@@ -13,17 +15,24 @@ export default function QuerySection() {
|
|
13 |
handleInputChange,
|
14 |
reload,
|
15 |
stop,
|
16 |
-
} = useChat({
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
return (
|
19 |
<div className="space-y-4 max-w-5xl w-full">
|
20 |
-
<ChatMessages
|
21 |
messages={messages}
|
22 |
isLoading={isLoading}
|
23 |
reload={reload}
|
24 |
stop={stop}
|
25 |
/>
|
26 |
<AutofillQuestion
|
|
|
27 |
messages={messages}
|
28 |
isLoading={isLoading}
|
29 |
handleSubmit={handleSubmit}
|
@@ -35,7 +44,14 @@ export default function QuerySection() {
|
|
35 |
handleSubmit={handleSubmit}
|
36 |
handleInputChange={handleInputChange}
|
37 |
isLoading={isLoading}
|
38 |
-
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
</div>
|
40 |
);
|
41 |
}
|
|
|
2 |
|
3 |
import { useChat } from "ai/react";
|
4 |
import { ChatInput, ChatMessages } from "@/app/components/ui/chat";
|
5 |
+
import { AutofillQuestion } from "./ui/autofill-prompt";
|
6 |
+
import { useSession } from "next-auth/react";
|
7 |
|
8 |
export default function QuerySection() {
|
9 |
+
const { data: session } = useSession();
|
10 |
const {
|
11 |
messages,
|
12 |
input,
|
|
|
15 |
handleInputChange,
|
16 |
reload,
|
17 |
stop,
|
18 |
+
} = useChat({
|
19 |
+
api: process.env.NEXT_PUBLIC_QUERY_API,
|
20 |
+
// Add the access token to the request headers
|
21 |
+
headers: {
|
22 |
+
'Authorization': `Bearer ${session?.supabaseAccessToken}`,
|
23 |
+
}
|
24 |
+
});
|
25 |
|
26 |
return (
|
27 |
<div className="space-y-4 max-w-5xl w-full">
|
28 |
+
{/* <ChatMessages
|
29 |
messages={messages}
|
30 |
isLoading={isLoading}
|
31 |
reload={reload}
|
32 |
stop={stop}
|
33 |
/>
|
34 |
<AutofillQuestion
|
35 |
+
docSelected="PSSCOC"
|
36 |
messages={messages}
|
37 |
isLoading={isLoading}
|
38 |
handleSubmit={handleSubmit}
|
|
|
44 |
handleSubmit={handleSubmit}
|
45 |
handleInputChange={handleInputChange}
|
46 |
isLoading={isLoading}
|
47 |
+
/> */}
|
48 |
+
|
49 |
+
{/* Maintenance Page */}
|
50 |
+
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative text-center" role="alert">
|
51 |
+
<strong className="font-bold">A new feature is coming your way!</strong>
|
52 |
+
<br />
|
53 |
+
<span className="block sm:inline">The Q&A Page is currently undergoing upgrades. Please check back later.</span>
|
54 |
+
</div>
|
55 |
</div>
|
56 |
);
|
57 |
}
|
@@ -2,16 +2,14 @@
|
|
2 |
"use client";
|
3 |
|
4 |
import { useState, ChangeEvent, FormEvent } from "react";
|
5 |
-
import
|
6 |
-
import SearchResults from "
|
7 |
-
import SearchInput from "@/app/components/ui/search/search-input";
|
8 |
-
import AutofillSearchQuery from "@/app/components/ui/autofill-prompt/autofill-search-prompt-dialog";
|
9 |
|
10 |
const SearchSection: React.FC = () => {
|
11 |
const [query, setQuery] = useState("");
|
12 |
const { searchResults, isLoading, handleSearch } = useSearch();
|
13 |
const [searchButtonPressed, setSearchButtonPressed] = useState(false);
|
14 |
-
|
15 |
|
16 |
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
17 |
setQuery(e.target.value);
|
@@ -26,21 +24,38 @@ const SearchSection: React.FC = () => {
|
|
26 |
|
27 |
return (
|
28 |
<div className="space-y-4 max-w-5xl w-full">
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
</div>
|
45 |
);
|
46 |
};
|
|
|
2 |
"use client";
|
3 |
|
4 |
import { useState, ChangeEvent, FormEvent } from "react";
|
5 |
+
import { AutofillSearchQuery } from "@/app/components/ui/autofill-prompt";
|
6 |
+
import { SearchSelection, useSearch, SearchResults, SearchInput } from "./ui/search";
|
|
|
|
|
7 |
|
8 |
const SearchSection: React.FC = () => {
|
9 |
const [query, setQuery] = useState("");
|
10 |
const { searchResults, isLoading, handleSearch } = useSearch();
|
11 |
const [searchButtonPressed, setSearchButtonPressed] = useState(false);
|
12 |
+
const [docSelected, setDocSelected] = useState<string>('');
|
13 |
|
14 |
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
|
15 |
setQuery(e.target.value);
|
|
|
24 |
|
25 |
return (
|
26 |
<div className="space-y-4 max-w-5xl w-full">
|
27 |
+
{docSelected ? (
|
28 |
+
<>
|
29 |
+
<h2 className="text-lg text-center font-semibold mb-4">Searching in {docSelected}</h2>
|
30 |
+
<SearchInput
|
31 |
+
docSelected={docSelected}
|
32 |
+
query={query}
|
33 |
+
isLoading={isLoading}
|
34 |
+
results={searchResults}
|
35 |
+
onInputChange={handleInputChange}
|
36 |
+
onSearchSubmit={handleSearchSubmit}
|
37 |
+
/>
|
38 |
+
<AutofillSearchQuery
|
39 |
+
docSelected={docSelected}
|
40 |
+
query={query}
|
41 |
+
isLoading={isLoading}
|
42 |
+
results={searchResults}
|
43 |
+
onInputChange={handleInputChange}
|
44 |
+
onSearchSubmit={handleSearchSubmit}
|
45 |
+
/>
|
46 |
+
<SearchResults
|
47 |
+
query={query}
|
48 |
+
results={searchResults}
|
49 |
+
isLoading={isLoading}
|
50 |
+
searchButtonPressed={searchButtonPressed}
|
51 |
+
/>
|
52 |
+
</>
|
53 |
+
) : (
|
54 |
+
<SearchSelection
|
55 |
+
docSelected={docSelected}
|
56 |
+
handleDocSelect={setDocSelected}
|
57 |
+
/>
|
58 |
+
)}
|
59 |
</div>
|
60 |
);
|
61 |
};
|
@@ -1,11 +1,11 @@
|
|
1 |
import { useEffect, useState } from "react";
|
2 |
-
import { QuestionsBankProp,
|
3 |
import { ChatHandler } from "@/app/components/ui/chat/chat.interface";
|
4 |
|
5 |
export default function AutofillQuestion(
|
6 |
props: Pick<
|
7 |
ChatHandler,
|
8 |
-
"messages" | "isLoading" | "handleSubmit" | "handleInputChange" | "input"
|
9 |
>,
|
10 |
) {
|
11 |
// Keep track of whether to show the overlay
|
@@ -14,6 +14,8 @@ export default function AutofillQuestion(
|
|
14 |
const [randomQuestions, setRandomQuestions] = useState<QuestionsBankProp[]>([]);
|
15 |
// Keep track of the current question index
|
16 |
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
|
|
|
|
17 |
|
18 |
// Shuffle the array using Fisher-Yates algorithm
|
19 |
function shuffleArray(array: any[]) {
|
@@ -28,6 +30,13 @@ export default function AutofillQuestion(
|
|
28 |
|
29 |
// Randomly select a subset of 3-4 questions
|
30 |
useEffect(() => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
// Shuffle the questionsBank array
|
32 |
const shuffledQuestions = shuffleArray(questionsBank);
|
33 |
// Get a random subset of 3-4 questions
|
@@ -37,7 +46,7 @@ export default function AutofillQuestion(
|
|
37 |
setTimeout(() => {
|
38 |
setRandomQuestions(selectedQuestions);
|
39 |
}, 300);
|
40 |
-
}, []);
|
41 |
|
42 |
|
43 |
// Hide overlay when there are messages
|
@@ -72,9 +81,9 @@ export default function AutofillQuestion(
|
|
72 |
return (
|
73 |
<>
|
74 |
{showOverlay && (
|
75 |
-
<div className="
|
76 |
<div className="rounded-lg pt-5 pr-10 pl-10 flex h-[50vh] flex-col divide-y overflow-y-auto pb-4">
|
77 |
-
<h2 className="text-lg text-center font-semibold mb-4">How can I help you today?</h2>
|
78 |
{randomQuestions.map((question, index) => (
|
79 |
<ul>
|
80 |
<li key={index} className={`p-2 mb-2 border border-zinc-500/30 dark:border-white rounded-lg hover:bg-zinc-500/30 transition duration-300 ease-in-out transform cursor-pointer ${index <= currentQuestionIndex ? 'opacity-100 duration-500' : 'opacity-0'}`}>
|
|
|
1 |
import { useEffect, useState } from "react";
|
2 |
+
import { QuestionsBankProp, psscocQuestionsBank, eirQuestionsBank } from "@/app/components/ui/autofill-prompt/autofill-prompt.interface";
|
3 |
import { ChatHandler } from "@/app/components/ui/chat/chat.interface";
|
4 |
|
5 |
export default function AutofillQuestion(
|
6 |
props: Pick<
|
7 |
ChatHandler,
|
8 |
+
"docSelected" | "messages" | "isLoading" | "handleSubmit" | "handleInputChange" | "input"
|
9 |
>,
|
10 |
) {
|
11 |
// Keep track of whether to show the overlay
|
|
|
14 |
const [randomQuestions, setRandomQuestions] = useState<QuestionsBankProp[]>([]);
|
15 |
// Keep track of the current question index
|
16 |
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
17 |
+
// Questions bank for PSSCOC or EIR
|
18 |
+
const [questionsBank, setQuestionsBank] = useState<QuestionsBankProp[]>(psscocQuestionsBank);
|
19 |
|
20 |
// Shuffle the array using Fisher-Yates algorithm
|
21 |
function shuffleArray(array: any[]) {
|
|
|
30 |
|
31 |
// Randomly select a subset of 3-4 questions
|
32 |
useEffect(() => {
|
33 |
+
// Select the questions bank based on the document set selected
|
34 |
+
if (props.docSelected === "EIR") {
|
35 |
+
setQuestionsBank(eirQuestionsBank);
|
36 |
+
}
|
37 |
+
else {
|
38 |
+
setQuestionsBank(psscocQuestionsBank);
|
39 |
+
}
|
40 |
// Shuffle the questionsBank array
|
41 |
const shuffledQuestions = shuffleArray(questionsBank);
|
42 |
// Get a random subset of 3-4 questions
|
|
|
46 |
setTimeout(() => {
|
47 |
setRandomQuestions(selectedQuestions);
|
48 |
}, 300);
|
49 |
+
}, [questionsBank, props.docSelected]);
|
50 |
|
51 |
|
52 |
// Hide overlay when there are messages
|
|
|
81 |
return (
|
82 |
<>
|
83 |
{showOverlay && (
|
84 |
+
<div className="w-full rounded-xl bg-white dark:bg-zinc-700/30 dark:from-inherit p-4 shadow-xl pb-0">
|
85 |
<div className="rounded-lg pt-5 pr-10 pl-10 flex h-[50vh] flex-col divide-y overflow-y-auto pb-4">
|
86 |
+
<h2 className="text-lg text-center font-semibold mb-4">How can I help you with {props.docSelected} today?</h2>
|
87 |
{randomQuestions.map((question, index) => (
|
88 |
<ul>
|
89 |
<li key={index} className={`p-2 mb-2 border border-zinc-500/30 dark:border-white rounded-lg hover:bg-zinc-500/30 transition duration-300 ease-in-out transform cursor-pointer ${index <= currentQuestionIndex ? 'opacity-100 duration-500' : 'opacity-0'}`}>
|
@@ -2,7 +2,7 @@ export interface QuestionsBankProp {
|
|
2 |
title: string;
|
3 |
}
|
4 |
|
5 |
-
export const
|
6 |
{ title: "Under PSSCOC, what are the differences between the role of the SO Rep and SO?" },
|
7 |
{ title: "Who has the authority to appoint SO Rep assistants and what can the SO Rep assistants be authorized to do?" },
|
8 |
{ title: "What are the general obligations of the contractor and consultant?" },
|
@@ -11,4 +11,12 @@ export const questionsBank: QuestionsBankProp[] = [
|
|
11 |
{ title: "What are the requirements for the contractor to claim for loss and expense?" },
|
12 |
{ title: "Under PSSCOC, briefly describe the proper payment claim process prescribed." },
|
13 |
// Add more common questions as needed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
];
|
|
|
2 |
title: string;
|
3 |
}
|
4 |
|
5 |
+
export const psscocQuestionsBank: QuestionsBankProp[] = [
|
6 |
{ title: "Under PSSCOC, what are the differences between the role of the SO Rep and SO?" },
|
7 |
{ title: "Who has the authority to appoint SO Rep assistants and what can the SO Rep assistants be authorized to do?" },
|
8 |
{ title: "What are the general obligations of the contractor and consultant?" },
|
|
|
11 |
{ title: "What are the requirements for the contractor to claim for loss and expense?" },
|
12 |
{ title: "Under PSSCOC, briefly describe the proper payment claim process prescribed." },
|
13 |
// Add more common questions as needed
|
14 |
+
];
|
15 |
+
|
16 |
+
export const eirQuestionsBank: QuestionsBankProp[] = [
|
17 |
+
{ title: "I have confidential projects that cannot go on the Internet. Can you list me all the clauses which specify the use of Internet platforms or tools, as I will need to remove them." },
|
18 |
+
{ title: "My BIM files have speciality equipment such as ovens, microwaves and refrigerators. What should I categorise them as according to the model content requirements. What parameters are required?" },
|
19 |
+
{ title: "I use my own platforms such as BIM360 and Novade for safety observations. Do I need to use JTCβs OPTIMUS for such issues or can I use my own platforms?" },
|
20 |
+
{ title: "How many days do I have to submit the BIM Execution Plan and who should I submit it to?" },
|
21 |
+
// Add more common questions as needed
|
22 |
];
|
@@ -1,11 +1,11 @@
|
|
1 |
import { useEffect, useState } from "react";
|
2 |
-
import { QuestionsBankProp,
|
3 |
import { SearchHandler } from "@/app/components/ui/search/search.interface";
|
4 |
|
5 |
export default function AutofillSearchQuery(
|
6 |
props: Pick<
|
7 |
SearchHandler,
|
8 |
-
"query" | "isLoading" | "onSearchSubmit" | "onInputChange" | "results" | "searchButtonPressed"
|
9 |
>,
|
10 |
) {
|
11 |
// Keep track of whether to show the overlay
|
@@ -14,6 +14,8 @@ export default function AutofillSearchQuery(
|
|
14 |
const [randomQuestions, setRandomQuestions] = useState<QuestionsBankProp[]>([]);
|
15 |
// Keep track of the current question index
|
16 |
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
|
|
|
|
17 |
|
18 |
// Shuffle the array using Fisher-Yates algorithm
|
19 |
function shuffleArray(array: any[]) {
|
@@ -28,6 +30,13 @@ export default function AutofillSearchQuery(
|
|
28 |
|
29 |
// Randomly select a subset of 3-4 questions
|
30 |
useEffect(() => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
// Shuffle the questionsBank array
|
32 |
const shuffledQuestions = shuffleArray(questionsBank);
|
33 |
// Get a random subset of 3-4 questions
|
@@ -37,7 +46,7 @@ export default function AutofillSearchQuery(
|
|
37 |
setTimeout(() => {
|
38 |
setRandomQuestions(selectedQuestions);
|
39 |
}, 300);
|
40 |
-
}, []);
|
41 |
|
42 |
|
43 |
// Hide overlay when there are query
|
@@ -76,7 +85,8 @@ export default function AutofillSearchQuery(
|
|
76 |
{showOverlay && (
|
77 |
<div className="relative mx-auto">
|
78 |
<div className="rounded-lg pt-5 pr-10 pl-10 flex flex-col divide-y overflow-y-auto pb-4 bg-white dark:bg-zinc-700/30 shadow-xl">
|
79 |
-
<h2 className="text-lg text-center font-semibold mb-4">How can I help
|
|
|
80 |
{randomQuestions.map((question, index) => (
|
81 |
<ul>
|
82 |
<li key={index} className={`p-2 mb-2 border border-zinc-500/30 dark:border-white rounded-lg hover:bg-zinc-500/30 transition duration-300 ease-in-out transform cursor-pointer ${index <= currentQuestionIndex ? 'opacity-100 duration-500' : 'opacity-0'}`}>
|
|
|
1 |
import { useEffect, useState } from "react";
|
2 |
+
import { QuestionsBankProp, psscocQuestionsBank, eirQuestionsBank } from "@/app/components/ui/autofill-prompt/autofill-prompt.interface";
|
3 |
import { SearchHandler } from "@/app/components/ui/search/search.interface";
|
4 |
|
5 |
export default function AutofillSearchQuery(
|
6 |
props: Pick<
|
7 |
SearchHandler,
|
8 |
+
"docSelected" | "query" | "isLoading" | "onSearchSubmit" | "onInputChange" | "results" | "searchButtonPressed"
|
9 |
>,
|
10 |
) {
|
11 |
// Keep track of whether to show the overlay
|
|
|
14 |
const [randomQuestions, setRandomQuestions] = useState<QuestionsBankProp[]>([]);
|
15 |
// Keep track of the current question index
|
16 |
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
17 |
+
// Questions bank for PSSCOC or EIR
|
18 |
+
const [questionsBank, setQuestionsBank] = useState<QuestionsBankProp[]>(psscocQuestionsBank);
|
19 |
|
20 |
// Shuffle the array using Fisher-Yates algorithm
|
21 |
function shuffleArray(array: any[]) {
|
|
|
30 |
|
31 |
// Randomly select a subset of 3-4 questions
|
32 |
useEffect(() => {
|
33 |
+
// Select the questions bank based on the document set selected
|
34 |
+
if (props.docSelected === "EIR") {
|
35 |
+
setQuestionsBank(eirQuestionsBank);
|
36 |
+
}
|
37 |
+
else {
|
38 |
+
setQuestionsBank(psscocQuestionsBank);
|
39 |
+
}
|
40 |
// Shuffle the questionsBank array
|
41 |
const shuffledQuestions = shuffleArray(questionsBank);
|
42 |
// Get a random subset of 3-4 questions
|
|
|
46 |
setTimeout(() => {
|
47 |
setRandomQuestions(selectedQuestions);
|
48 |
}, 300);
|
49 |
+
}, [questionsBank, props.docSelected]);
|
50 |
|
51 |
|
52 |
// Hide overlay when there are query
|
|
|
85 |
{showOverlay && (
|
86 |
<div className="relative mx-auto">
|
87 |
<div className="rounded-lg pt-5 pr-10 pl-10 flex flex-col divide-y overflow-y-auto pb-4 bg-white dark:bg-zinc-700/30 shadow-xl">
|
88 |
+
<h2 className="text-lg text-center font-semibold mb-4">How can I help with {props.docSelected} today?</h2>
|
89 |
+
{/* {dialogMessage && <p className="text-center text-sm text-gray-500 mb-4">{dialogMessage}</p>} */}
|
90 |
{randomQuestions.map((question, index) => (
|
91 |
<ul>
|
92 |
<li key={index} className={`p-2 mb-2 border border-zinc-500/30 dark:border-white rounded-lg hover:bg-zinc-500/30 transition duration-300 ease-in-out transform cursor-pointer ${index <= currentQuestionIndex ? 'opacity-100 duration-500' : 'opacity-0'}`}>
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import AutofillQuestion from "./autofill-prompt-dialog";
|
2 |
+
import AutofillSearchQuery from "./autofill-search-prompt-dialog";
|
3 |
+
|
4 |
+
export { AutofillQuestion, AutofillSearchQuery };
|
@@ -14,34 +14,37 @@ export default function ChatInput(
|
|
14 |
return (
|
15 |
<form
|
16 |
onSubmit={props.handleSubmit}
|
17 |
-
className="
|
18 |
>
|
19 |
-
<
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
{props.isLoading
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
34 |
<Send className="h-5 w-5" />
|
35 |
-
|
36 |
-
|
37 |
-
</
|
38 |
-
<
|
39 |
-
{props.isLoading ? (
|
40 |
-
<IconSpinner className="animate-spin" />
|
41 |
-
) : (
|
42 |
-
<Send className="h-5 w-5" />
|
43 |
-
)}
|
44 |
-
</Button>
|
45 |
</form>
|
46 |
);
|
47 |
}
|
|
|
14 |
return (
|
15 |
<form
|
16 |
onSubmit={props.handleSubmit}
|
17 |
+
className="w-full items-start justify-between gap-4 rounded-xl bg-white dark:bg-zinc-700/30 p-4 shadow-xl"
|
18 |
>
|
19 |
+
<div className="flex w-full items-start justify-between gap-4">
|
20 |
+
<Input
|
21 |
+
autoFocus
|
22 |
+
name="message"
|
23 |
+
placeholder="Type a Message"
|
24 |
+
className="flex-1 bg-white dark:bg-zinc-500/30"
|
25 |
+
value={props.input}
|
26 |
+
onChange={props.handleInputChange}
|
27 |
+
/>
|
28 |
+
<Button type="submit" disabled={props.isLoading} className="hidden md:flex items-center transition duration-300 ease-in-out transform hover:scale-110 z-10">
|
29 |
+
{props.isLoading ? (
|
30 |
+
<IconSpinner className="animate-spin" />
|
31 |
+
) : (
|
32 |
+
// Fragment to avoid wrapping the text in a button
|
33 |
+
<>
|
34 |
+
<span className="pr-2">Send</span>
|
35 |
+
<Send className="h-5 w-5" />
|
36 |
+
</>
|
37 |
+
)}
|
38 |
+
</Button>
|
39 |
+
<Button type="submit" disabled={props.isLoading} className="md:hidden z-10"> {/* Hide on larger screens */}
|
40 |
+
{props.isLoading ? (
|
41 |
+
<IconSpinner className="animate-spin" />
|
42 |
+
) : (
|
43 |
<Send className="h-5 w-5" />
|
44 |
+
)}
|
45 |
+
</Button>
|
46 |
+
</div>
|
47 |
+
<p className="text-center text-sm text-gray-400 mt-2">Smart Retrieval may not be 100% accurate. Consider checking important information.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
</form>
|
49 |
);
|
50 |
}
|
@@ -29,23 +29,25 @@ export default function ChatMessages(
|
|
29 |
}, [messageLength, lastMessage]);
|
30 |
|
31 |
return (
|
32 |
-
|
33 |
-
<div
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
</div>
|
41 |
-
|
42 |
-
<ChatActions
|
43 |
-
reload={props.reload}
|
44 |
-
stop={props.stop}
|
45 |
-
showReload={showReload}
|
46 |
-
showStop={showStop}
|
47 |
-
/>
|
48 |
-
</div>
|
49 |
-
</div>
|
50 |
);
|
51 |
}
|
|
|
29 |
}, [messageLength, lastMessage]);
|
30 |
|
31 |
return (
|
32 |
+
messageLength > 0 && (
|
33 |
+
<div className="w-full rounded-xl bg-white dark:bg-zinc-700/30 dark:from-inherit p-4 shadow-xl pb-0">
|
34 |
+
<div
|
35 |
+
className="flex h-[50vh] flex-col gap-5 divide-y overflow-y-auto pb-4"
|
36 |
+
ref={scrollableChatContainerRef}
|
37 |
+
>
|
38 |
+
{props.messages.map((m) => (
|
39 |
+
<ChatMessage key={m.id} {...m} />
|
40 |
+
))}
|
41 |
+
</div>
|
42 |
+
<div className="flex justify-end py-4">
|
43 |
+
<ChatActions
|
44 |
+
reload={props.reload}
|
45 |
+
stop={props.stop}
|
46 |
+
showReload={showReload}
|
47 |
+
showStop={showStop}
|
48 |
+
/>
|
49 |
+
</div>
|
50 |
</div>
|
51 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
);
|
53 |
}
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"use client";
|
2 |
+
|
3 |
+
import { useState, useEffect } from 'react';
|
4 |
+
import { ChatHandler } from '@/app/components/ui/chat';
|
5 |
+
|
6 |
+
const DocumentSet = [
|
7 |
+
'PSSCOC',
|
8 |
+
'EIR',
|
9 |
+
// Add More Document Set as needed
|
10 |
+
];
|
11 |
+
|
12 |
+
export default function ChatSelection(
|
13 |
+
props: Pick<ChatHandler, "docSelected" | "handleDocSelect">,
|
14 |
+
) {
|
15 |
+
const [currentDocumentIndex, setCurrentDocumentIndex] = useState(0);
|
16 |
+
|
17 |
+
const handleDocumentSetChange = (documentSet: string) => {
|
18 |
+
props.handleDocSelect(documentSet);
|
19 |
+
};
|
20 |
+
|
21 |
+
// Automatically advance to the next document set after a delay
|
22 |
+
useEffect(() => {
|
23 |
+
const timer = setInterval(() => {
|
24 |
+
if (currentDocumentIndex < DocumentSet.length - 1) {
|
25 |
+
setCurrentDocumentIndex((prevIndex) => prevIndex + 1);
|
26 |
+
}
|
27 |
+
else {
|
28 |
+
clearInterval(timer); // Stop the timer when all document set have been displayed
|
29 |
+
}
|
30 |
+
}, 100); // Adjust the delay time as needed (e.g., 5000 milliseconds = 5 seconds)
|
31 |
+
|
32 |
+
return () => clearInterval(timer); // Cleanup the timer on component unmount
|
33 |
+
}, [currentDocumentIndex, DocumentSet]);
|
34 |
+
|
35 |
+
return (
|
36 |
+
<div className="w-full rounded-xl bg-white dark:bg-zinc-700/30 dark:from-inherit p-4 shadow-xl pb-0">
|
37 |
+
<div className="rounded-lg pt-5 pr-10 pl-10 flex h-[50vh] flex-col divide-y overflow-y-auto pb-4">
|
38 |
+
<h2 className="text-lg text-center font-semibold mb-4">Select Document Set to Chat with:</h2>
|
39 |
+
{/* <p className="text-center text-sm text-gray-500 mb-4">{dialogMessage}</p> */}
|
40 |
+
{DocumentSet.map((title, index) => (
|
41 |
+
<ul>
|
42 |
+
<li key={index} className={`p-2 mb-2 border border-zinc-500/30 dark:border-white rounded-lg hover:bg-zinc-500/30 transition duration-300 ease-in-out transform cursor-pointer ${index <= currentDocumentIndex ? 'opacity-100 duration-500' : 'opacity-0'}`}>
|
43 |
+
<button
|
44 |
+
className="text-blue-500 w-full text-left"
|
45 |
+
onClick={() => handleDocumentSetChange(title)}
|
46 |
+
>
|
47 |
+
{title}
|
48 |
+
</button>
|
49 |
+
</li>
|
50 |
+
</ul>
|
51 |
+
))}
|
52 |
+
</div>
|
53 |
+
</div>
|
54 |
+
);
|
55 |
+
};
|
@@ -5,6 +5,8 @@ export interface Message {
|
|
5 |
}
|
6 |
|
7 |
export interface ChatHandler {
|
|
|
|
|
8 |
messages: Message[];
|
9 |
input: string;
|
10 |
isLoading: boolean;
|
|
|
5 |
}
|
6 |
|
7 |
export interface ChatHandler {
|
8 |
+
docSelected: string;
|
9 |
+
handleDocSelect: (doc: string) => void;
|
10 |
messages: Message[];
|
11 |
input: string;
|
12 |
isLoading: boolean;
|
@@ -1,5 +1,6 @@
|
|
1 |
import ChatInput from "./chat-input";
|
2 |
import ChatMessages from "./chat-messages";
|
|
|
3 |
|
4 |
export { type ChatHandler, type Message } from "./chat.interface";
|
5 |
-
export { ChatInput, ChatMessages };
|
|
|
1 |
import ChatInput from "./chat-input";
|
2 |
import ChatMessages from "./chat-messages";
|
3 |
+
import ChatSelection from "./chat-selection";
|
4 |
|
5 |
export { type ChatHandler, type Message } from "./chat.interface";
|
6 |
+
export { ChatInput, ChatMessages, ChatSelection };
|
@@ -5,6 +5,7 @@ import { signIn } from 'next-auth/react'
|
|
5 |
import { cn } from '@/app/components/ui/lib/utils'
|
6 |
import { Button, type ButtonProps } from '@/app/components/ui/button'
|
7 |
import { IconGoogle, IconSGid, IconSpinner } from '@/app/components/ui/icons'
|
|
|
8 |
|
9 |
interface LoginButtonProps extends ButtonProps {
|
10 |
showIcon?: boolean;
|
@@ -18,13 +19,19 @@ function GoogleLoginButton({
|
|
18 |
...props
|
19 |
}: LoginButtonProps) {
|
20 |
const [isLoading, setIsLoading] = useState(false);
|
21 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
return (
|
23 |
<Button
|
24 |
variant="outline"
|
25 |
onClick={() => {
|
26 |
setIsLoading(true);
|
27 |
-
signIn('google');
|
28 |
}}
|
29 |
className={cn(className)}
|
30 |
{...props}
|
@@ -47,13 +54,19 @@ function SGIDLoginButton({
|
|
47 |
...props
|
48 |
}: LoginButtonProps) {
|
49 |
const [isLoading, setIsLoading] = useState(false);
|
50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
return (
|
52 |
<Button
|
53 |
variant="outline"
|
54 |
onClick={() => {
|
55 |
setIsLoading(true);
|
56 |
-
signIn('sgid');
|
57 |
}}
|
58 |
className={cn(className)}
|
59 |
{...props}
|
|
|
5 |
import { cn } from '@/app/components/ui/lib/utils'
|
6 |
import { Button, type ButtonProps } from '@/app/components/ui/button'
|
7 |
import { IconGoogle, IconSGid, IconSpinner } from '@/app/components/ui/icons'
|
8 |
+
import { useSearchParams } from 'next/navigation'
|
9 |
|
10 |
interface LoginButtonProps extends ButtonProps {
|
11 |
showIcon?: boolean;
|
|
|
19 |
...props
|
20 |
}: LoginButtonProps) {
|
21 |
const [isLoading, setIsLoading] = useState(false);
|
22 |
+
const searchParams = useSearchParams()
|
23 |
+
let tempcallbackURL = searchParams.get("callbackUrl"); // Get the 'callbackURL' query parameter
|
24 |
+
let callbackURL = tempcallbackURL;
|
25 |
+
// if callbackURL is not provided, default to home page
|
26 |
+
if (!tempcallbackURL) {
|
27 |
+
callbackURL = '/';
|
28 |
+
}
|
29 |
return (
|
30 |
<Button
|
31 |
variant="outline"
|
32 |
onClick={() => {
|
33 |
setIsLoading(true);
|
34 |
+
signIn('google', { redirect: true, callbackUrl: callbackURL as string });
|
35 |
}}
|
36 |
className={cn(className)}
|
37 |
{...props}
|
|
|
54 |
...props
|
55 |
}: LoginButtonProps) {
|
56 |
const [isLoading, setIsLoading] = useState(false);
|
57 |
+
const searchParams = useSearchParams()
|
58 |
+
const tempcallbackURL = searchParams.get("callbackUrl"); // Get the 'callbackURL' query parameter
|
59 |
+
let callbackURL = tempcallbackURL;
|
60 |
+
// if callbackURL is not provided, default to home page
|
61 |
+
if (!tempcallbackURL) {
|
62 |
+
callbackURL = '/';
|
63 |
+
}
|
64 |
return (
|
65 |
<Button
|
66 |
variant="outline"
|
67 |
onClick={() => {
|
68 |
setIsLoading(true);
|
69 |
+
signIn('sgid', { redirect: true, callbackUrl: callbackURL as string });
|
70 |
}}
|
71 |
className={cn(className)}
|
72 |
{...props}
|
@@ -70,7 +70,7 @@ const MobileMenu: React.FC<MobileMenuProps> = ({ isOpen, onClose, logoSrc, items
|
|
70 |
{/* Mobile menu content */}
|
71 |
<div className="w-64 p-4 rounded-r-md">
|
72 |
{items.map((item, index) => (
|
73 |
-
<HeaderNavLink key={index} href={item.href} onClick={onClose}>
|
74 |
<div className="flex items-center mb-4">
|
75 |
{item.icon}
|
76 |
{item.label}
|
|
|
70 |
{/* Mobile menu content */}
|
71 |
<div className="w-64 p-4 rounded-r-md">
|
72 |
{items.map((item, index) => (
|
73 |
+
<HeaderNavLink key={index} href={item.href} title={item.label} onClick={onClose}>
|
74 |
<div className="flex items-center mb-4">
|
75 |
{item.icon}
|
76 |
{item.label}
|
@@ -5,12 +5,13 @@ import Link from 'next/link';
|
|
5 |
|
6 |
export interface NavLinkProps {
|
7 |
href: string;
|
|
|
8 |
children: React.ReactNode;
|
9 |
onClick?: () => void; // Include onClick as an optional prop
|
10 |
target?: string;
|
11 |
}
|
12 |
|
13 |
-
const HeaderNavLink: React.FC<NavLinkProps> = ({ href, children, onClick }) => {
|
14 |
// Use the useRouter hook to get information about the current route
|
15 |
const pathname = usePathname();
|
16 |
|
@@ -24,7 +25,7 @@ const HeaderNavLink: React.FC<NavLinkProps> = ({ href, children, onClick }) => {
|
|
24 |
};
|
25 |
|
26 |
return (
|
27 |
-
<Link href={href} passHref>
|
28 |
{/* Add a class to highlight the active tab */}
|
29 |
<div className={`flex items-center font-bold ${isActive ? 'text-blue-500' : ''}`} onClick={handleClick}>
|
30 |
{children}
|
@@ -33,7 +34,7 @@ const HeaderNavLink: React.FC<NavLinkProps> = ({ href, children, onClick }) => {
|
|
33 |
);
|
34 |
};
|
35 |
|
36 |
-
const FooterNavLink: React.FC<NavLinkProps> = ({ href, children, onClick, target }) => {
|
37 |
const handleClick = () => {
|
38 |
if (onClick) {
|
39 |
onClick(); // Call the onClick handler if provided
|
@@ -41,7 +42,7 @@ const FooterNavLink: React.FC<NavLinkProps> = ({ href, children, onClick, target
|
|
41 |
};
|
42 |
|
43 |
return (
|
44 |
-
<Link href={href} passHref target={target}>
|
45 |
{/* Add a class to highlight the active tab */}
|
46 |
<div className="flex items-center font-bold" onClick={handleClick}>
|
47 |
{children}
|
|
|
5 |
|
6 |
export interface NavLinkProps {
|
7 |
href: string;
|
8 |
+
title: string;
|
9 |
children: React.ReactNode;
|
10 |
onClick?: () => void; // Include onClick as an optional prop
|
11 |
target?: string;
|
12 |
}
|
13 |
|
14 |
+
const HeaderNavLink: React.FC<NavLinkProps> = ({ href, title, children, onClick }) => {
|
15 |
// Use the useRouter hook to get information about the current route
|
16 |
const pathname = usePathname();
|
17 |
|
|
|
25 |
};
|
26 |
|
27 |
return (
|
28 |
+
<Link href={href} passHref title={title}>
|
29 |
{/* Add a class to highlight the active tab */}
|
30 |
<div className={`flex items-center font-bold ${isActive ? 'text-blue-500' : ''}`} onClick={handleClick}>
|
31 |
{children}
|
|
|
34 |
);
|
35 |
};
|
36 |
|
37 |
+
const FooterNavLink: React.FC<NavLinkProps> = ({ href, title, children, onClick, target }) => {
|
38 |
const handleClick = () => {
|
39 |
if (onClick) {
|
40 |
onClick(); // Call the onClick handler if provided
|
|
|
42 |
};
|
43 |
|
44 |
return (
|
45 |
+
<Link href={href} passHref target={target} title={title}>
|
46 |
{/* Add a class to highlight the active tab */}
|
47 |
<div className="flex items-center font-bold" onClick={handleClick}>
|
48 |
{children}
|