Game4all commited on
Commit
51f2dc1
·
0 Parent(s):

Initial commit

Browse files
.gitignore ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+ #poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ #pdm.lock
116
+ #pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ #pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # SageMath parsed files
135
+ *.sage.py
136
+
137
+ # Environments
138
+ .env
139
+ .envrc
140
+ .venv
141
+ env/
142
+ venv/
143
+ ENV/
144
+ env.bak/
145
+ venv.bak/
146
+
147
+ # Spyder project settings
148
+ .spyderproject
149
+ .spyproject
150
+
151
+ # Rope project settings
152
+ .ropeproject
153
+
154
+ # mkdocs documentation
155
+ /site
156
+
157
+ # mypy
158
+ .mypy_cache/
159
+ .dmypy.json
160
+ dmypy.json
161
+
162
+ # Pyre type checker
163
+ .pyre/
164
+
165
+ # pytype static type analyzer
166
+ .pytype/
167
+
168
+ # Cython debug symbols
169
+ cython_debug/
170
+
171
+ # PyCharm
172
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
173
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
174
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
175
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
176
+ #.idea/
177
+
178
+ # Abstra
179
+ # Abstra is an AI-powered process automation framework.
180
+ # Ignore directories containing user credentials, local state, and settings.
181
+ # Learn more at https://abstra.io/docs
182
+ .abstra/
183
+
184
+ # Visual Studio Code
185
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
186
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
187
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
188
+ # you could uncomment the following to ignore the entire vscode folder
189
+ # .vscode/
190
+
191
+ # Ruff stuff:
192
+ .ruff_cache/
193
+
194
+ # PyPI configuration file
195
+ .pypirc
196
+
197
+ # Cursor
198
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
199
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
200
+ # refer to https://docs.cursor.com/context/ignore-files
201
+ .cursorignore
202
+ .cursorindexingignore
203
+
204
+ # Marimo
205
+ marimo/_static/
206
+ marimo/_lsp/
207
+ __marimo__/
208
+
209
+ # Streamlit
210
+ .streamlit/secrets.toml
Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ EXPOSE 7860
11
+
12
+ ENTRYPOINT ["uvicorn", "main:api", "--port", "7860", "--host", "0.0.0.0"]
README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Graphify
3
+ emoji: 🏢
4
+ colorFrom: yellow
5
+ colorTo: red
6
+ sdk: docker
7
+ pinned: false
8
+ license: mit
9
+ short_description: Transform data into knowledge graphs
10
+ ---
main.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ from litellm import acompletion
4
+ from dotenv import load_dotenv
5
+ from fastapi import FastAPI
6
+ from fastapi.staticfiles import StaticFiles
7
+ from jinja2 import Environment, FileSystemLoader, StrictUndefined, TemplateNotFound
8
+ from schemas import CreateSearchPlanRequest, CreateSearchPlanResponse, ExtractEntitiesRequest, ExtractEntitiesResponse, ExtractedRelationsResponse
9
+ from utils import build_visjs_graph, fmt_prompt
10
+ import logging
11
+
12
+ load_dotenv()
13
+
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format='[%(asctime)s][%(levelname)s][%(filename)s:%(lineno)d]: %(message)s',
17
+ datefmt='%Y-%m-%d %H:%M:%S'
18
+ )
19
+
20
+
21
+ LLM_MODEL = os.environ.get('LLM_MODEL', default=None)
22
+ LLM_TOKEN = os.environ.get('LLM_TOKEN', default=None)
23
+ LLM_BASE_URL = os.environ.get('LLM_BASE_URL', default=None)
24
+
25
+ if not LLM_MODEL and not LLM_TOKEN:
26
+ logging.error("No LLM_TOKEN and LLM_MODEL were provided.")
27
+ sys.exit(-1)
28
+
29
+ prompt_env = Environment(loader=FileSystemLoader(
30
+ "prompts"), undefined=StrictUndefined, enable_async=True)
31
+
32
+ api = FastAPI()
33
+
34
+
35
+ @api.post("/extract_entities")
36
+ async def extract_entities(body: ExtractEntitiesRequest):
37
+ """Extract entities from the given input text and return them"""
38
+ # Extract entities from the text
39
+ entities_completion = await acompletion(LLM_MODEL, api_key=LLM_TOKEN, base_url=LLM_BASE_URL, messages=[
40
+ {
41
+ "role": "user",
42
+ "content": await fmt_prompt(prompt_env, "ner/extract_entities", **{
43
+ "response_format": ExtractEntitiesResponse.model_json_schema(),
44
+ "input_text": body.content
45
+ })
46
+ }
47
+ ], response_format=ExtractEntitiesResponse)
48
+
49
+ extracted_entities = ExtractEntitiesResponse.model_validate_json(
50
+ entities_completion.choices[0].message.content)
51
+
52
+ # Extract relationships in a second step
53
+ relations_completion = await acompletion(LLM_MODEL, api_key=LLM_TOKEN, base_url=LLM_BASE_URL, messages=[
54
+ {
55
+ "role": "user",
56
+ "content": await fmt_prompt(prompt_env, "ner/extract_relations", **{
57
+ "response_format": ExtractedRelationsResponse.model_json_schema(),
58
+ "input_text": body.content,
59
+ "entities": extracted_entities.entities
60
+ })
61
+ }
62
+ ], response_format=ExtractedRelationsResponse, num_retries=5)
63
+
64
+ relation_model = ExtractedRelationsResponse.model_validate_json(
65
+ relations_completion.choices[0].message.content)
66
+
67
+ display_lists = build_visjs_graph(
68
+ extracted_entities.entities, relation_model.relations)
69
+
70
+ return display_lists
71
+
72
+
73
+ @api.post("/create_search_plan")
74
+ async def create_search_plan(body: CreateSearchPlanRequest):
75
+ plan_completion = await acompletion(LLM_MODEL, api_key=LLM_TOKEN, base_url=LLM_BASE_URL, messages=[
76
+ {
77
+ "role": "user",
78
+ "content": await fmt_prompt(prompt_env, "search/create_search_plan", **{
79
+ "response_format": CreateSearchPlanResponse.model_json_schema(),
80
+ "user_query": body.query,
81
+ })
82
+ }
83
+ ], response_format=CreateSearchPlanResponse)
84
+
85
+ plan_model = CreateSearchPlanResponse.model_validate_json(
86
+ plan_completion.choices[0].message.content)
87
+
88
+ return plan_model
89
+
90
+
91
+ api.mount("/", StaticFiles(directory="static", html=True), name="static")
prompts/ner/extract_entities ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <role>You are a useful search assistant. </role>
2
+ <task>
3
+ Extract all the entities that appear in the following given input texxt
4
+ An entity may refer to:
5
+ - A person (eg. Marie Curie)
6
+ - An organisation (eg. Google Inc, United Nations)
7
+ - A date (eg. July 14, 2025)
8
+ - A fact
9
+ - An event (eg. WWII)
10
+ - An acronym (eg.NASA)
11
+ - A location (eg. NY City)
12
+ - A product (eg. ChatGPT, Iphone 14)
13
+ - A quantity, money or percentage
14
+ - A technical term or domain concept
15
+ </task>
16
+
17
+ <response_format>
18
+ Reply in JSON with the following reponse schema:
19
+ {{response_format}}
20
+ </response_format>
21
+
22
+
23
+ <input_text>
24
+ Here is the text:
25
+
26
+ {{input_text}}
27
+ </input_text>
prompts/ner/extract_relations ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <role>You are a useful search assistant. </role>
2
+ <task>
3
+ Extract all the relations described in the following text for the following list of entities.
4
+ Please provide relations for the entities listed under.
5
+ </task>
6
+
7
+ <entities>
8
+ Here is the list of entities:
9
+ {% for entity in entities -%}
10
+ - {{entity}}
11
+ {% endfor %}
12
+ </entities>
13
+
14
+ <response_format>
15
+ Reply in JSON with the following response schema:
16
+ {{response_format}}
17
+ </response_format>
18
+
19
+ <input_text>
20
+ Here is the text:
21
+ {{input_text}}
22
+ </input_text>
prompts/search/create_search_plan ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <role>You are a useful search assistant. </role>
2
+ <task>
3
+ You are tasked with creating a comprehensive search plan to explore a complex query given by the user under.
4
+ Your goal is to break down the query into multiple sub-queries that represents dimensions that must be explored for
5
+ a holistic understanding of
6
+ the initial query.
7
+ </task>
8
+
9
+ <response_format>
10
+ Only provide the sub-queries formulated as questions.
11
+ Provide the answer as a JSON object using the following response schema:
12
+ {{response_format}}
13
+ </response_format>
14
+
15
+ <topic>
16
+ The user query is:
17
+ **{{user_query}}**
18
+ </topic>
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ litellm
2
+ pydantic
3
+ fastapi
4
+ uvicorn
5
+ networkx
6
+ Jinja2
7
+ dotenv
schemas.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+
3
+
4
+ # ============================================= Entity + Relations extraction
5
+
6
+ class ExtractEntitiesRequest(BaseModel):
7
+ content: str
8
+
9
+
10
+ class ExtractEntitiesResponse(BaseModel):
11
+ entities: list[str] = Field(..., description="A list of entities")
12
+
13
+
14
+ class ExtractedRelation(BaseModel):
15
+ start: str = Field(..., description="The first entity in the relationship")
16
+ to: str = Field(..., description="The second entity of the relationship")
17
+ tag: str = Field(..., description="A tag describing the relationship", examples=[
18
+ "related_to", "born_in", "made", "created"])
19
+ description: str = Field(...,
20
+ description="A detailled description of the relationship")
21
+
22
+
23
+ class ExtractedRelationsResponse(BaseModel):
24
+ relations: list[ExtractedRelation]
25
+
26
+
27
+ # ======================================================== Create search plan ==================
28
+
29
+ class CreateSearchPlanRequest(BaseModel):
30
+ query: str
31
+
32
+
33
+ class CreateSearchPlanResponse(BaseModel):
34
+ sub_queries: list[str] = Field(...,
35
+ description="A list of subqueries formulated as questions")
static/css/index.css ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ .dot-grid {
2
+ position: relative;
3
+ width: 100%;
4
+ height: 100%;
5
+ background-image: radial-gradient(rgba(128, 128, 128, 0.4) 1px, transparent 1px);
6
+ background-size: 12px 12px; /* Adjust spacing between dots */
7
+ background-repeat: repeat;
8
+ overflow: hidden;
9
+ }
static/index.html ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" data-theme="dark">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Chatbot UI</title>
8
+ <link href="https://cdn.jsdelivr.net/npm/daisyui@5" rel="stylesheet" type="text/css" />
9
+ <link href="css/index.css" rel="stylesheet" type="text/css" />
10
+ <script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
11
+ <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
12
+ <script src="js/index.js"></script>
13
+ </head>
14
+
15
+ <body class="bg-base-200 p-4 overflow-hidden">
16
+ <div class="relative h-[95vh] bg-base-100 rounded-2xl p-5 shadow-xl">
17
+
18
+ <!-- tab container-->
19
+ <div class="absolute -top-2 flex left-1/2 transform -translate-x-1/2 z-10">
20
+ <div class="tabs tabs-box glass">
21
+ <button id="chat-tab" class="tab tab-active flex items-center gap-2" onclick="switchTab('chat-tab')">
22
+ <svg width="16px" height="16px" viewBox="0 0 24 24" fill="none">
23
+ <g stroke-width="0"></g>
24
+ <g stroke-linecap="round" stroke-linejoin="round"></g>
25
+ <g>
26
+ <path d="M8 10.5H16" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round"></path>
27
+ <path d="M8 14H13.5" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round"></path>
28
+ <path
29
+ d="M17 3.33782C15.5291 2.48697 13.8214 2 12 2C6.47715 2 2 6.47715 2 12C2 13.5997 2.37562 15.1116 3.04346 16.4525C3.22094 16.8088 3.28001 17.2161 3.17712 17.6006L2.58151 19.8267C2.32295 20.793 3.20701 21.677 4.17335 21.4185L6.39939 20.8229C6.78393 20.72 7.19121 20.7791 7.54753 20.9565C8.88837 21.6244 10.4003 22 12 22C17.5228 22 22 17.5228 22 12C22 10.1786 21.513 8.47087 20.6622 7"
30
+ stroke="#ffffff" stroke-width="1.5" stroke-linecap="round"></path>
31
+ </g>
32
+ </svg>
33
+ Chat
34
+ </button>
35
+ <button id="explore-tab" class="tab flex items-center gap-2" onclick="switchTab('explore-tab')">
36
+ <svg fill="#ffffff" width="16px" height="16px" viewBox="0 -64 640 640" stroke="#ffffff">
37
+ <g stroke-width="0"></g>
38
+ <g stroke-linecap="round" stroke-linejoin="round"></g>
39
+ <g>
40
+ <path
41
+ d="M384 320H256c-17.67 0-32 14.33-32 32v128c0 17.67 14.33 32 32 32h128c17.67 0 32-14.33 32-32V352c0-17.67-14.33-32-32-32zM192 32c0-17.67-14.33-32-32-32H32C14.33 0 0 14.33 0 32v128c0 17.67 14.33 32 32 32h95.72l73.16 128.04C211.98 300.98 232.4 288 256 288h.28L192 175.51V128h224V64H192V32zM608 0H480c-17.67 0-32 14.33-32 32v128c0 17.67 14.33 32 32 32h128c17.67 0 32-14.33 32-32V32c0-17.67-14.33-32-32-32z">
42
+ </path>
43
+ </g>
44
+ </svg>
45
+ Explore
46
+ </button>
47
+ </div>
48
+ </div>
49
+
50
+ <!-- Chat UI Container -->
51
+ <div id="chat-tab-contents" class="relative w-full h-full">
52
+ <!-- <div class=" flex flex-col overflow-hidden"> -->
53
+ <div id="chat-messages-container" class="w-full h-full space-y-4 overflow-y-auto flex-1 pr-2 pb-20">
54
+ <!-- Message Bubble (Bot) -->
55
+ <div class="chat chat-start">
56
+ <div class="chat-bubble chat-bubble-secondary">Hello! Give me some data and I'll turn it into a nice KG graph for you</div>
57
+ </div>
58
+ </div>
59
+
60
+ <!-- Input Area -->
61
+ <div class="absolute bottom-0 left-0 right-0 p-4 rounded-xl shadow-xl bg-gray-700 text-base-content">
62
+ <div class="w-full">
63
+ <div class="flex gap-2 items-end">
64
+ <textarea id="chat-input"
65
+ class="w-full bg-base-800 text-base-content border max-h-[25vh] overflow-y-auto resize-y p-2 rounded-md"
66
+ placeholder="Type your message..." rows="1"></textarea>
67
+ <button id="chat-send-input" class="btn btn-sm btn-primary rounded-full">Send</button>
68
+ </div>
69
+ </div>
70
+ </div>
71
+
72
+
73
+ <!-- </div> -->
74
+ </div>
75
+
76
+
77
+ <div class="relative w-full h-full dot-grid overflow-hidden hidden" id="explore-tab-contents">
78
+ <div id="grapha" class="w-full h-full "></div>
79
+ </div>
80
+
81
+ </div>
82
+ </body>
83
+ </html>
static/js/index.js ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ // mapping of tab to its contents
3
+ const TABS = {
4
+ "chat-tab": "chat-tab-contents",
5
+ "explore-tab": "explore-tab-contents"
6
+ };
7
+
8
+ // switch to the specified tab
9
+ function switchTab(newTab) {
10
+ Object.keys(TABS).forEach(tabId => {
11
+ const tabElement = document.getElementById(tabId);
12
+ if (tabElement) {
13
+ tabElement.classList.remove("tab-active");
14
+ }
15
+ });
16
+
17
+ // Hide all tab contents
18
+ Object.values(TABS).forEach(contentId => {
19
+ const contentElement = document.getElementById(contentId);
20
+ if (contentElement) {
21
+ contentElement.classList.add("hidden");
22
+ }
23
+ });
24
+
25
+ // Activate the new tab if it exists in the mapping
26
+ if (newTab in TABS) {
27
+ const newTabElement = document.getElementById(newTab);
28
+ const newContentElement = document.getElementById(TABS[newTab]);
29
+
30
+ if (newTabElement) newTabElement.classList.add("tab-active");
31
+ if (newContentElement) newContentElement.classList.remove("hidden");
32
+ }
33
+ }
34
+
35
+ function addMessage(message, role = "user") {
36
+ const chatContainer = document.getElementById("chat-messages-container");
37
+
38
+ const chatDiv = document.createElement("div");
39
+ chatDiv.classList.add("chat");
40
+
41
+ if (role === "user")
42
+ chatDiv.classList.add("chat-end");
43
+ else if (role === "bot")
44
+ chatDiv.classList.add("chat-start");
45
+ else
46
+ chatDiv.classList.add("chat-start");
47
+
48
+ const bubbleDiv = document.createElement("div");
49
+ bubbleDiv.classList.add("chat-bubble");
50
+
51
+ if (role === "user")
52
+ bubbleDiv.classList.add("chat-bubble-primary");
53
+ else if (role === "bot")
54
+ bubbleDiv.classList.add("chat-bubble-secondary");
55
+ else
56
+ bubbleDiv.classList.add("chat-bubble-secondary");
57
+
58
+ bubbleDiv.textContent = message;
59
+
60
+ chatDiv.appendChild(bubbleDiv);
61
+ chatContainer.appendChild(chatDiv);
62
+ chatContainer.scrollTop = chatContainer.scrollHeight;
63
+ }
64
+
65
+
66
+ function escapeString(str) {
67
+ return str
68
+ .replace(/\\/g, '\\\\') // Escape backslashes
69
+ .replace(/"/g, '\\"') // Escape double quotes
70
+ .replace(/\n/g, '\\n') // Escape newlines
71
+ .replace(/\r/g, '\\r') // Escape carriage returns
72
+ .replace(/\t/g, '\\t'); // Escape tabs
73
+ }
74
+
75
+ // extract entities from the given input text
76
+ async function extractEntities(text) {
77
+ let escaped_text = escapeString(text);
78
+
79
+ try {
80
+ let graph_data_req = await fetch("/extract_entities", {
81
+ method: "POST",
82
+ headers: new Headers({
83
+ "Content-Type": "application/json"
84
+ }),
85
+ body: JSON.stringify({
86
+ content: escaped_text
87
+ })
88
+ })
89
+
90
+ let graph_data = await graph_data_req.json();
91
+
92
+ console.log(graph_data);
93
+
94
+ var options = {};
95
+ var network = new vis.Network(document.getElementById('grapha'), graph_data, options);
96
+ addMessage("I've created a knowledge graph based on what you've provided me. Check the explore tab !", "bot");
97
+ } catch (e) {
98
+ console.error("Error while trying to extract entities into a KG", e);
99
+ }
100
+ }
101
+
102
+
103
+ document.addEventListener('DOMContentLoaded', _ => {
104
+ document.getElementById('chat-send-input').addEventListener('click', (ev) => {
105
+ let textbox = document.getElementById('chat-input');
106
+ let text = textbox.value;
107
+
108
+ addMessage(text, "user");
109
+ extractEntities(text);
110
+ textbox.value = "";
111
+ });
112
+ })
utils.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, List
2
+ from jinja2 import Environment
3
+
4
+ from schemas import ExtractedRelation
5
+
6
+
7
+ def build_visjs_graph(entities: List[str], relations: List[ExtractedRelation]) -> Dict[str, List[Dict]]:
8
+ """Builds a vertex and edge graph for displaying in UI"""
9
+
10
+ unique_entities = set(entities) # maintains order, removes duplicates
11
+ entity_to_id = {entity: idx for idx, entity in enumerate(unique_entities)}
12
+ nodes = [
13
+ {"id": entity_to_id[entity], "label": entity, "title": entity}
14
+ for entity in unique_entities
15
+ ]
16
+
17
+ # Create edges list from relations
18
+ edges = []
19
+ for rel in relations:
20
+ start_id = entity_to_id.get(rel.start)
21
+ end_id = entity_to_id.get(rel.to)
22
+ if start_id is not None and end_id is not None:
23
+ edges.append({
24
+ "from": start_id,
25
+ "to": end_id,
26
+ "label": rel.tag,
27
+ "title": rel.description,
28
+ "arrows": "to",
29
+ })
30
+
31
+ return {"nodes": nodes, "edges": edges}
32
+
33
+
34
+ async def fmt_prompt(env: Environment, prompt_id: str, **args):
35
+ """Returns a formatted prompt"""
36
+ prompt = env.get_template(prompt_id)
37
+ return await prompt.render_async(args)