jaothan commited on
Commit
88f42a4
·
verified ·
1 Parent(s): e6bbaec

Upload 47 files

Browse files
.dockerignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ *
2
+ !*.py
3
+ !requirements.txt
4
+ !images/*
5
+ !front-end/*
6
+ front-end/node_modules/*
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ .env
2
+ data/
3
+ embedding_model/*
4
+ !embedding_model/.ignore
5
+ .DS_Store
CONTRIBUTING.md ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributing to Docker
2
+
3
+ This page contains information about reporting issues as well as some tips and
4
+ guidelines useful to experienced open source contributors. Finally, make sure
5
+ you read our [community guidelines](#docker-community-guidelines) before you
6
+ start participating.
7
+
8
+ ## Topics
9
+
10
+ * [Reporting Security Issues](#reporting-security-issues)
11
+ * [Reporting Issues](#reporting-other-issues)
12
+ * [Quick Contribution Tips and Guidelines](#quick-contribution-tips-and-guidelines)
13
+ * [Community Guidelines](#docker-community-guidelines)
14
+
15
+ ## Reporting security issues
16
+
17
+ The Docker maintainers take security seriously. If you discover a security
18
+ issue, please bring it to their attention right away!
19
+
20
+ Please **DO NOT** file a public issue, instead send your report privately to
21
+ [security@docker.com](mailto:security@docker.com).
22
+
23
+ Security reports are greatly appreciated and we will publicly thank you for it.
24
+ We also like to send gifts—if you're into Docker schwag, make sure to let
25
+ us know. We currently do not offer a paid security bounty program, but are not
26
+ ruling it out in the future.
27
+
28
+ ## Reporting other issues
29
+
30
+ A great way to contribute to the project is to send a detailed report when you
31
+ encounter an issue. We always appreciate a well-written, thorough bug report,
32
+ and will thank you for it!
33
+
34
+ Check that [our issue database](https://github.com/docker/gen-ai-stack/issues)
35
+ doesn't already include that problem or suggestion before submitting an issue.
36
+ If you find a match, you can use the "subscribe" button to get notified on
37
+ updates. Do *not* leave random "+1" or "I have this too" comments, as they
38
+ only clutter the discussion, and don't help resolving it. However, if you
39
+ have ways to reproduce the issue or have additional information that may help
40
+ resolving the issue, please leave a comment.
41
+
42
+ When reporting issues, always include:
43
+
44
+ * The output of `docker version`.
45
+ * The output of `docker info`.
46
+
47
+ Also include the steps required to reproduce the problem if possible and
48
+ applicable. This information will help us review and fix your issue faster.
49
+ When sending lengthy log-files, consider posting them as a gist (https://gist.github.com).
50
+ Don't forget to remove sensitive data from your logfiles before posting (you can
51
+ replace those parts with "REDACTED").
52
+
53
+ ## Quick contribution tips and guidelines
54
+
55
+ This section gives the experienced contributor some tips and guidelines.
56
+
57
+ ### Pull requests are always welcome
58
+
59
+ Not sure if that typo is worth a pull request? Found a bug and know how to fix
60
+ it? Do it! We will appreciate it. Any significant improvement should be
61
+ documented as [a GitHub issue](https://github.com/docker/gen-ai-stack/issues) before
62
+ anybody starts working on it.
63
+
64
+ We are always thrilled to receive pull requests. We do our best to process them
65
+ quickly. If your pull request is not accepted on the first try,
66
+ don't get discouraged! Our contributor's guide explains [the review process we
67
+ use for simple changes](https://docs.docker.com/opensource/workflow/make-a-contribution/).
68
+
69
+ ### Talking to other Docker users and contributors
70
+
71
+ <table class="tg">
72
+ <col width="45%">
73
+ <col width="65%">
74
+ <tr>
75
+ <td>Community Slack</td>
76
+ <td>
77
+ The Docker Community has a dedicated Slack chat to discuss features and issues. You can sign-up <a href="https://dockr.ly/slack" target="_blank">with this link</a>.
78
+ </td>
79
+ </tr>
80
+ <tr>
81
+ <td>Twitter</td>
82
+ <td>
83
+ You can follow <a href="https://twitter.com/docker/" target="_blank">Docker's Twitter feed</a>
84
+ to get updates on our products. You can also tweet us questions or just
85
+ share blogs or stories.
86
+ </td>
87
+ </tr>
88
+ </table>
89
+
90
+
91
+ ### Conventions
92
+
93
+ Fork the repository and make changes on your fork in a feature branch:
94
+
95
+ - If it's a bug fix branch, name it XXXX-something where XXXX is the number of
96
+ the issue.
97
+ - If it's a feature branch, create an enhancement issue to announce
98
+ your intentions, and name it XXXX-something where XXXX is the number of the
99
+ issue.
100
+
101
+ Pull request descriptions should be as clear as possible and include a reference
102
+ to all the issues that they address.
103
+
104
+ Commit messages must start with a capitalized and short summary (max. 50 chars)
105
+ written in the imperative, followed by an optional, more detailed explanatory
106
+ text which is separated from the summary by an empty line.
107
+
108
+ Code review comments may be added to your pull request. Discuss, then make the
109
+ suggested modifications and push additional commits to your feature branch. Post
110
+ a comment after pushing. New commits show up in the pull request automatically,
111
+ but the reviewers are notified only when you comment.
112
+
113
+ Pull requests must be cleanly rebased on top of master without multiple branches
114
+ mixed into the PR.
115
+
116
+ **Git tip**: If your PR no longer merges cleanly, use `rebase main` in your
117
+ feature branch to update your pull request rather than `merge main`.
118
+
119
+ Before you make a pull request, squash your commits into logical units of work
120
+ using `git rebase -i` and `git push -f`. A logical unit of work is a consistent
121
+ set of patches that should be reviewed together: for example, upgrading the
122
+ version of a vendored dependency and taking advantage of its now available new
123
+ feature constitute two separate units of work. Implementing a new function and
124
+ calling it in another file constitute a single logical unit of work. The very
125
+ high majority of submissions should have a single commit, so if in doubt: squash
126
+ down to one.
127
+
128
+ Include an issue reference like `Closes #XXXX` or `Fixes #XXXX` in the pull request
129
+ description that close an issue. Including references automatically closes the issue
130
+ on a merge.
131
+
132
+ ### Sign your work
133
+
134
+ The sign-off is a simple line at the end of the explanation for the patch. Your
135
+ signature certifies that you wrote the patch or otherwise have the right to pass
136
+ it on as an open-source patch. The rules are pretty simple: if you can certify
137
+ the below (from [developercertificate.org](https://developercertificate.org):
138
+
139
+ ```
140
+ Developer Certificate of Origin
141
+ Version 1.1
142
+
143
+ Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
144
+ 660 York Street, Suite 102,
145
+ San Francisco, CA 94110 USA
146
+
147
+ Everyone is permitted to copy and distribute verbatim copies of this
148
+ license document, but changing it is not allowed.
149
+
150
+ Developer's Certificate of Origin 1.1
151
+
152
+ By making a contribution to this project, I certify that:
153
+
154
+ (a) The contribution was created in whole or in part by me and I
155
+ have the right to submit it under the open source license
156
+ indicated in the file; or
157
+
158
+ (b) The contribution is based upon previous work that, to the best
159
+ of my knowledge, is covered under an appropriate open source
160
+ license and I have the right under that license to submit that
161
+ work with modifications, whether created in whole or in part
162
+ by me, under the same open source license (unless I am
163
+ permitted to submit under a different license), as indicated
164
+ in the file; or
165
+
166
+ (c) The contribution was provided directly to me by some other
167
+ person who certified (a), (b) or (c) and I have not modified
168
+ it.
169
+
170
+ (d) I understand and agree that this project and the contribution
171
+ are public and that a record of the contribution (including all
172
+ personal information I submit with it, including my sign-off) is
173
+ maintained indefinitely and may be redistributed consistent with
174
+ this project or the open source license(s) involved.
175
+ ```
176
+
177
+ Then you just add a line to every git commit message:
178
+
179
+ Signed-off-by: Joe Smith <joe.smith@email.com>
180
+
181
+ Use your real name (sorry, no pseudonyms or anonymous contributions.)
182
+
183
+ If you set your `user.name` and `user.email` git configs, you can sign your
184
+ commit automatically with `git commit -s`.
185
+
186
+ ## Docker community guidelines
187
+
188
+ We want to keep the Docker community awesome, growing and collaborative. We need
189
+ your help to keep it that way. To help with this we've come up with some general
190
+ guidelines for the community as a whole:
191
+
192
+ * Be nice: Be courteous, respectful and polite to fellow community members:
193
+ no regional, racial, gender, or other abuse will be tolerated. We like
194
+ nice people way better than mean ones!
195
+
196
+ * Encourage diversity and participation: Make everyone in our community feel
197
+ welcome, regardless of their background and the extent of their
198
+ contributions, and do everything possible to encourage participation in
199
+ our community.
200
+
201
+ * Keep it legal: Basically, don't get us in trouble. Share only content that
202
+ you own, do not share private or sensitive information, and don't break
203
+ the law.
204
+
205
+ * Stay on topic: Make sure that you are posting to the correct channel and
206
+ avoid off-topic discussions. Remember when you update an issue or respond
207
+ to an email you are potentially sending to a large number of people. Please
208
+ consider this before you update. Also remember that nobody likes spam.
209
+
210
+ * Don't send email to the maintainers: There's no need to send email to the
211
+ maintainers to ask them to investigate an issue or to take a look at a
212
+ pull request. Instead of sending an email, GitHub mentions should be
213
+ used to ping maintainers to review a pull request, a proposal or an
214
+ issue.
215
+
216
+ ### Guideline violations — 3 strikes method
217
+
218
+ The point of this section is not to find opportunities to punish people, but we
219
+ do need a fair way to deal with people who are making our community suck.
220
+
221
+ 1. First occurrence: We'll give you a friendly, but public reminder that the
222
+ behavior is inappropriate according to our guidelines.
223
+
224
+ 2. Second occurrence: We will send you a private message with a warning that
225
+ any additional violations will result in removal from the community.
226
+
227
+ 3. Third occurrence: Depending on the violation, we may need to delete or ban
228
+ your account.
229
+
230
+ **Notes:**
231
+
232
+ * Obvious spammers are banned on first occurrence. If we don't do this, we'll
233
+ have spam all over the place.
234
+
235
+ * Violations are forgiven after 6 months of good behavior, and we won't hold a
236
+ grudge.
237
+
238
+ * People who commit minor infractions will get some education, rather than
239
+ hammering them in the 3 strikes process.
240
+
241
+ * The rules apply equally to everyone in the community, no matter how much
242
+ you've contributed.
243
+
244
+ * Extreme violations of a threatening, abusive, destructive or illegal nature
245
+ will be addressed immediately and are not subject to 3 strikes or forgiveness.
246
+
247
+ * Contact abuse@docker.com to report abuse or appeal violations. In the case of
248
+ appeals, we know that mistakes happen, and we'll work with you to come up with a
249
+ fair solution if there has been a misunderstanding.
250
+
LICENSE ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Creative Commons Legal Code
2
+
3
+ CC0 1.0 Universal
4
+
5
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12
+ HEREUNDER.
13
+
14
+ Statement of Purpose
15
+
16
+ The laws of most jurisdictions throughout the world automatically confer
17
+ exclusive Copyright and Related Rights (defined below) upon the creator
18
+ and subsequent owner(s) (each and all, an "owner") of an original work of
19
+ authorship and/or a database (each, a "Work").
20
+
21
+ Certain owners wish to permanently relinquish those rights to a Work for
22
+ the purpose of contributing to a commons of creative, cultural and
23
+ scientific works ("Commons") that the public can reliably and without fear
24
+ of later claims of infringement build upon, modify, incorporate in other
25
+ works, reuse and redistribute as freely as possible in any form whatsoever
26
+ and for any purposes, including without limitation commercial purposes.
27
+ These owners may contribute to the Commons to promote the ideal of a free
28
+ culture and the further production of creative, cultural and scientific
29
+ works, or to gain reputation or greater distribution for their Work in
30
+ part through the use and efforts of others.
31
+
32
+ For these and/or other purposes and motivations, and without any
33
+ expectation of additional consideration or compensation, the person
34
+ associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35
+ is an owner of Copyright and Related Rights in the Work, voluntarily
36
+ elects to apply CC0 to the Work and publicly distribute the Work under its
37
+ terms, with knowledge of his or her Copyright and Related Rights in the
38
+ Work and the meaning and intended legal effect of CC0 on those rights.
39
+
40
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
41
+ protected by copyright and related or neighboring rights ("Copyright and
42
+ Related Rights"). Copyright and Related Rights include, but are not
43
+ limited to, the following:
44
+
45
+ i. the right to reproduce, adapt, distribute, perform, display,
46
+ communicate, and translate a Work;
47
+ ii. moral rights retained by the original author(s) and/or performer(s);
48
+ iii. publicity and privacy rights pertaining to a person's image or
49
+ likeness depicted in a Work;
50
+ iv. rights protecting against unfair competition in regards to a Work,
51
+ subject to the limitations in paragraph 4(a), below;
52
+ v. rights protecting the extraction, dissemination, use and reuse of data
53
+ in a Work;
54
+ vi. database rights (such as those arising under Directive 96/9/EC of the
55
+ European Parliament and of the Council of 11 March 1996 on the legal
56
+ protection of databases, and under any national implementation
57
+ thereof, including any amended or successor version of such
58
+ directive); and
59
+ vii. other similar, equivalent or corresponding rights throughout the
60
+ world based on applicable law or treaty, and any national
61
+ implementations thereof.
62
+
63
+ 2. Waiver. To the greatest extent permitted by, but not in contravention
64
+ of, applicable law, Affirmer hereby overtly, fully, permanently,
65
+ irrevocably and unconditionally waives, abandons, and surrenders all of
66
+ Affirmer's Copyright and Related Rights and associated claims and causes
67
+ of action, whether now known or unknown (including existing as well as
68
+ future claims and causes of action), in the Work (i) in all territories
69
+ worldwide, (ii) for the maximum duration provided by applicable law or
70
+ treaty (including future time extensions), (iii) in any current or future
71
+ medium and for any number of copies, and (iv) for any purpose whatsoever,
72
+ including without limitation commercial, advertising or promotional
73
+ purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74
+ member of the public at large and to the detriment of Affirmer's heirs and
75
+ successors, fully intending that such Waiver shall not be subject to
76
+ revocation, rescission, cancellation, termination, or any other legal or
77
+ equitable action to disrupt the quiet enjoyment of the Work by the public
78
+ as contemplated by Affirmer's express Statement of Purpose.
79
+
80
+ 3. Public License Fallback. Should any part of the Waiver for any reason
81
+ be judged legally invalid or ineffective under applicable law, then the
82
+ Waiver shall be preserved to the maximum extent permitted taking into
83
+ account Affirmer's express Statement of Purpose. In addition, to the
84
+ extent the Waiver is so judged Affirmer hereby grants to each affected
85
+ person a royalty-free, non transferable, non sublicensable, non exclusive,
86
+ irrevocable and unconditional license to exercise Affirmer's Copyright and
87
+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
88
+ maximum duration provided by applicable law or treaty (including future
89
+ time extensions), (iii) in any current or future medium and for any number
90
+ of copies, and (iv) for any purpose whatsoever, including without
91
+ limitation commercial, advertising or promotional purposes (the
92
+ "License"). The License shall be deemed effective as of the date CC0 was
93
+ applied by Affirmer to the Work. Should any part of the License for any
94
+ reason be judged legally invalid or ineffective under applicable law, such
95
+ partial invalidity or ineffectiveness shall not invalidate the remainder
96
+ of the License, and in such case Affirmer hereby affirms that he or she
97
+ will not (i) exercise any of his or her remaining Copyright and Related
98
+ Rights in the Work or (ii) assert any associated claims and causes of
99
+ action with respect to the Work, in either case contrary to Affirmer's
100
+ express Statement of Purpose.
101
+
102
+ 4. Limitations and Disclaimers.
103
+
104
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
105
+ surrendered, licensed or otherwise affected by this document.
106
+ b. Affirmer offers the Work as-is and makes no representations or
107
+ warranties of any kind concerning the Work, express, implied,
108
+ statutory or otherwise, including without limitation warranties of
109
+ title, merchantability, fitness for a particular purpose, non
110
+ infringement, or the absence of latent or other defects, accuracy, or
111
+ the present or absence of errors, whether or not discoverable, all to
112
+ the greatest extent permissible under applicable law.
113
+ c. Affirmer disclaims responsibility for clearing rights of other persons
114
+ that may apply to the Work or any use thereof, including without
115
+ limitation any person's Copyright and Related Rights in the Work.
116
+ Further, Affirmer disclaims responsibility for obtaining any necessary
117
+ consents, permissions or other rights required for any use of the
118
+ Work.
119
+ d. Affirmer understands and acknowledges that Creative Commons is not a
120
+ party to this document and has no duty or obligation with respect to
121
+ this CC0 or use of the Work.
api.Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM langchain/langchain
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y \
6
+ build-essential \
7
+ curl \
8
+ software-properties-common \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ COPY requirements.txt .
12
+
13
+ RUN pip install --upgrade -r requirements.txt
14
+
15
+ COPY api.py .
16
+ COPY utils.py .
17
+ COPY chains.py .
18
+
19
+ HEALTHCHECK CMD curl --fail http://localhost:8504
20
+
21
+ ENTRYPOINT [ "uvicorn", "api:app", "--host", "0.0.0.0", "--port", "8504" ]
api.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ from langchain_community.graphs import Neo4jGraph
4
+ from dotenv import load_dotenv
5
+ from utils import (
6
+ create_vector_index,
7
+ BaseLogger,
8
+ )
9
+ from chains import (
10
+ load_embedding_model,
11
+ load_llm,
12
+ configure_llm_only_chain,
13
+ configure_qa_rag_chain,
14
+ generate_ticket,
15
+ )
16
+ from fastapi import FastAPI, Depends
17
+ from pydantic import BaseModel
18
+ from langchain.callbacks.base import BaseCallbackHandler
19
+ from threading import Thread
20
+ from queue import Queue, Empty
21
+ from collections.abc import Generator
22
+ from sse_starlette.sse import EventSourceResponse
23
+ from fastapi.middleware.cors import CORSMiddleware
24
+ import json
25
+
26
+ load_dotenv(".env")
27
+
28
+ url = os.getenv("NEO4J_URI")
29
+ username = os.getenv("NEO4J_USERNAME")
30
+ password = os.getenv("NEO4J_PASSWORD")
31
+ ollama_base_url = os.getenv("OLLAMA_BASE_URL")
32
+ embedding_model_name = os.getenv("EMBEDDING_MODEL")
33
+ llm_name = os.getenv("LLM")
34
+ # Remapping for Langchain Neo4j integration
35
+ os.environ["NEO4J_URL"] = url
36
+
37
+ embeddings, dimension = load_embedding_model(
38
+ embedding_model_name,
39
+ config={"ollama_base_url": ollama_base_url},
40
+ logger=BaseLogger(),
41
+ )
42
+
43
+ # if Neo4j is local, you can go to http://localhost:7474/ to browse the database
44
+ neo4j_graph = Neo4jGraph(
45
+ url=url, username=username, password=password, refresh_schema=False
46
+ )
47
+ create_vector_index(neo4j_graph)
48
+
49
+ llm = load_llm(
50
+ llm_name, logger=BaseLogger(), config={"ollama_base_url": ollama_base_url}
51
+ )
52
+
53
+ llm_chain = configure_llm_only_chain(llm)
54
+ rag_chain = configure_qa_rag_chain(
55
+ llm, embeddings, embeddings_store_url=url, username=username, password=password
56
+ )
57
+
58
+
59
+ class QueueCallback(BaseCallbackHandler):
60
+ """Callback handler for streaming LLM responses to a queue."""
61
+
62
+ def __init__(self, q):
63
+ self.q = q
64
+
65
+ def on_llm_new_token(self, token: str, **kwargs) -> None:
66
+ self.q.put(token)
67
+
68
+ def on_llm_end(self, *args, **kwargs) -> None:
69
+ return self.q.empty()
70
+
71
+
72
+ def stream(cb, q) -> Generator:
73
+ job_done = object()
74
+
75
+ def task():
76
+ x = cb()
77
+ q.put(job_done)
78
+
79
+ t = Thread(target=task)
80
+ t.start()
81
+
82
+ content = ""
83
+
84
+ # Get each new token from the queue and yield for our generator
85
+ while True:
86
+ try:
87
+ next_token = q.get(True, timeout=1)
88
+ if next_token is job_done:
89
+ break
90
+ content += next_token
91
+ yield next_token, content
92
+ except Empty:
93
+ continue
94
+
95
+
96
+ app = FastAPI()
97
+ origins = ["*"]
98
+
99
+ app.add_middleware(
100
+ CORSMiddleware,
101
+ allow_origins=origins,
102
+ allow_credentials=True,
103
+ allow_methods=["*"],
104
+ allow_headers=["*"],
105
+ )
106
+
107
+
108
+ @app.get("/")
109
+ async def root():
110
+ return {"message": "Hello World"}
111
+
112
+
113
+ class Question(BaseModel):
114
+ text: str
115
+ rag: bool = False
116
+
117
+
118
+ class BaseTicket(BaseModel):
119
+ text: str
120
+
121
+
122
+ @app.get("/query-stream")
123
+ def qstream(question: Question = Depends()):
124
+ output_function = llm_chain
125
+ if question.rag:
126
+ output_function = rag_chain
127
+
128
+ q = Queue()
129
+
130
+ def cb():
131
+ output_function(
132
+ {"question": question.text, "chat_history": []},
133
+ callbacks=[QueueCallback(q)],
134
+ )
135
+
136
+ def generate():
137
+ yield json.dumps({"init": True, "model": llm_name})
138
+ for token, _ in stream(cb, q):
139
+ yield json.dumps({"token": token})
140
+
141
+ return EventSourceResponse(generate(), media_type="text/event-stream")
142
+
143
+
144
+ @app.get("/query")
145
+ async def ask(question: Question = Depends()):
146
+ output_function = llm_chain
147
+ if question.rag:
148
+ output_function = rag_chain
149
+ result = output_function(
150
+ {"question": question.text, "chat_history": []}, callbacks=[]
151
+ )
152
+
153
+ return {"result": result["answer"], "model": llm_name}
154
+
155
+
156
+ @app.get("/generate-ticket")
157
+ async def generate_ticket_api(question: BaseTicket = Depends()):
158
+ new_title, new_question = generate_ticket(
159
+ neo4j_graph=neo4j_graph,
160
+ llm_chain=llm_chain,
161
+ input_question=question.text,
162
+ )
163
+ return {"result": {"title": new_title, "text": new_question}, "model": llm_name}
bot.Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM langchain/langchain
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y \
6
+ build-essential \
7
+ curl \
8
+ software-properties-common \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ COPY requirements.txt .
12
+
13
+ RUN pip install --upgrade -r requirements.txt
14
+
15
+ COPY bot.py .
16
+ COPY utils.py .
17
+ COPY chains.py .
18
+
19
+ EXPOSE 8501
20
+
21
+ HEALTHCHECK CMD curl --fail http://localhost:8501/_stcore/health
22
+
23
+ ENTRYPOINT ["streamlit", "run", "bot.py", "--server.port=8501", "--server.address=0.0.0.0"]
bot.py ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ import streamlit as st
4
+ from streamlit.logger import get_logger
5
+ from langchain.callbacks.base import BaseCallbackHandler
6
+ from langchain_community.graphs import Neo4jGraph
7
+ from dotenv import load_dotenv
8
+ from utils import (
9
+ create_vector_index,
10
+ )
11
+ from chains import (
12
+ load_embedding_model,
13
+ load_llm,
14
+ configure_llm_only_chain,
15
+ configure_qa_rag_chain,
16
+ generate_ticket,
17
+ )
18
+
19
+ load_dotenv(".env")
20
+
21
+ url = os.getenv("NEO4J_URI")
22
+ username = os.getenv("NEO4J_USERNAME")
23
+ password = os.getenv("NEO4J_PASSWORD")
24
+ ollama_base_url = os.getenv("OLLAMA_BASE_URL")
25
+ embedding_model_name = os.getenv("EMBEDDING_MODEL")
26
+ llm_name = os.getenv("LLM")
27
+ # Remapping for Langchain Neo4j integration
28
+ os.environ["NEO4J_URL"] = url
29
+
30
+ logger = get_logger(__name__)
31
+
32
+ # if Neo4j is local, you can go to http://localhost:7474/ to browse the database
33
+ neo4j_graph = Neo4jGraph(
34
+ url=url, username=username, password=password, refresh_schema=False
35
+ )
36
+ embeddings, dimension = load_embedding_model(
37
+ embedding_model_name, config={"ollama_base_url": ollama_base_url}, logger=logger
38
+ )
39
+ create_vector_index(neo4j_graph)
40
+
41
+
42
+ class StreamHandler(BaseCallbackHandler):
43
+ def __init__(self, container, initial_text=""):
44
+ self.container = container
45
+ self.text = initial_text
46
+
47
+ def on_llm_new_token(self, token: str, **kwargs) -> None:
48
+ self.text += token
49
+ self.container.markdown(self.text)
50
+
51
+
52
+ llm = load_llm(llm_name, logger=logger, config={"ollama_base_url": ollama_base_url})
53
+
54
+ llm_chain = configure_llm_only_chain(llm)
55
+ rag_chain = configure_qa_rag_chain(
56
+ llm, embeddings, embeddings_store_url=url, username=username, password=password
57
+ )
58
+
59
+ # Streamlit UI
60
+ styl = f"""
61
+ <style>
62
+ /* not great support for :has yet (hello FireFox), but using it for now */
63
+ .element-container:has([aria-label="Select RAG mode"]) {{
64
+ position: fixed;
65
+ bottom: 33px;
66
+ background: white;
67
+ z-index: 101;
68
+ }}
69
+ .stChatFloatingInputContainer {{
70
+ bottom: 20px;
71
+ }}
72
+
73
+ /* Generate ticket text area */
74
+ textarea[aria-label="Description"] {{
75
+ height: 200px;
76
+ }}
77
+
78
+ .element-container:has([aria-label="What coding issue can I help you resolve today?"]) {{
79
+ bottom: 45px;
80
+ }}
81
+ </style>
82
+ """
83
+ st.markdown(styl, unsafe_allow_html=True)
84
+
85
+
86
+ def chat_input():
87
+ user_input = st.chat_input("What coding issue can I help you resolve today?")
88
+
89
+ if user_input:
90
+ with st.chat_message("user"):
91
+ st.write(user_input)
92
+ with st.chat_message("assistant"):
93
+ st.caption(f"RAG: {name}")
94
+ stream_handler = StreamHandler(st.empty())
95
+ result = output_function(
96
+ {"question": user_input, "chat_history": []}, callbacks=[stream_handler]
97
+ )["answer"]
98
+ output = result
99
+ st.session_state[f"user_input"].append(user_input)
100
+ st.session_state[f"generated"].append(output)
101
+ st.session_state[f"rag_mode"].append(name)
102
+
103
+
104
+ def display_chat():
105
+ # Session state
106
+ if "generated" not in st.session_state:
107
+ st.session_state[f"generated"] = []
108
+
109
+ if "user_input" not in st.session_state:
110
+ st.session_state[f"user_input"] = []
111
+
112
+ if "rag_mode" not in st.session_state:
113
+ st.session_state[f"rag_mode"] = []
114
+
115
+ if st.session_state[f"generated"]:
116
+ size = len(st.session_state[f"generated"])
117
+ # Display only the last three exchanges
118
+ for i in range(max(size - 3, 0), size):
119
+ with st.chat_message("user"):
120
+ st.write(st.session_state[f"user_input"][i])
121
+
122
+ with st.chat_message("assistant"):
123
+ st.caption(f"RAG: {st.session_state[f'rag_mode'][i]}")
124
+ st.write(st.session_state[f"generated"][i])
125
+
126
+ with st.expander("Not finding what you're looking for?"):
127
+ st.write(
128
+ "Automatically generate a draft for an internal ticket to our support team."
129
+ )
130
+ st.button(
131
+ "Generate ticket",
132
+ type="primary",
133
+ key="show_ticket",
134
+ on_click=open_sidebar,
135
+ )
136
+ with st.container():
137
+ st.write("&nbsp;")
138
+
139
+
140
+ def mode_select() -> str:
141
+ options = ["Disabled", "Enabled"]
142
+ return st.radio("Select RAG mode", options, horizontal=True)
143
+
144
+
145
+ name = mode_select()
146
+ if name == "LLM only" or name == "Disabled":
147
+ output_function = llm_chain
148
+ elif name == "Vector + Graph" or name == "Enabled":
149
+ output_function = rag_chain
150
+
151
+
152
+ def open_sidebar():
153
+ st.session_state.open_sidebar = True
154
+
155
+
156
+ def close_sidebar():
157
+ st.session_state.open_sidebar = False
158
+
159
+
160
+ if not "open_sidebar" in st.session_state:
161
+ st.session_state.open_sidebar = False
162
+ if st.session_state.open_sidebar:
163
+ new_title, new_question = generate_ticket(
164
+ neo4j_graph=neo4j_graph,
165
+ llm_chain=llm_chain,
166
+ input_question=st.session_state[f"user_input"][-1],
167
+ )
168
+ with st.sidebar:
169
+ st.title("Ticket draft")
170
+ st.write("Auto generated draft ticket")
171
+ st.text_input("Title", new_title)
172
+ st.text_area("Description", new_question)
173
+ st.button(
174
+ "Submit to support team",
175
+ type="primary",
176
+ key="submit_ticket",
177
+ on_click=close_sidebar,
178
+ )
179
+
180
+
181
+ display_chat()
182
+ chat_input()
chains.py ADDED
@@ -0,0 +1,251 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from langchain_openai import OpenAIEmbeddings
3
+ from langchain_ollama import OllamaEmbeddings
4
+ from langchain_aws import BedrockEmbeddings
5
+ from langchain_huggingface import HuggingFaceEmbeddings
6
+
7
+ from langchain_openai import ChatOpenAI
8
+ from langchain_ollama import ChatOllama
9
+ from langchain_aws import ChatBedrock
10
+
11
+ from langchain_community.vectorstores import Neo4jVector
12
+
13
+ from langchain.chains import RetrievalQAWithSourcesChain
14
+ from langchain.chains.qa_with_sources import load_qa_with_sources_chain
15
+
16
+ from langchain.prompts import (
17
+ ChatPromptTemplate,
18
+ HumanMessagePromptTemplate,
19
+ SystemMessagePromptTemplate
20
+ )
21
+
22
+ from typing import List, Any
23
+ from utils import BaseLogger, extract_title_and_question
24
+ from langchain_google_genai import GoogleGenerativeAIEmbeddings
25
+
26
+ AWS_MODELS = (
27
+ "ai21.jamba-instruct-v1:0",
28
+ "amazon.titan",
29
+ "anthropic.claude",
30
+ "cohere.command",
31
+ "meta.llama",
32
+ "mistral.mi",
33
+ )
34
+
35
+ def load_embedding_model(embedding_model_name: str, logger=BaseLogger(), config={}):
36
+ if embedding_model_name == "ollama":
37
+ embeddings = OllamaEmbeddings(
38
+ base_url=config["ollama_base_url"], model="llama2"
39
+ )
40
+ dimension = 4096
41
+ logger.info("Embedding: Using Ollama")
42
+ elif embedding_model_name == "openai":
43
+ embeddings = OpenAIEmbeddings()
44
+ dimension = 1536
45
+ logger.info("Embedding: Using OpenAI")
46
+ elif embedding_model_name == "aws":
47
+ embeddings = BedrockEmbeddings()
48
+ dimension = 1536
49
+ logger.info("Embedding: Using AWS")
50
+ elif embedding_model_name == "google-genai-embedding-001":
51
+ embeddings = GoogleGenerativeAIEmbeddings(
52
+ model="models/embedding-001"
53
+ )
54
+ dimension = 768
55
+ logger.info("Embedding: Using Google Generative AI Embeddings")
56
+ else:
57
+ embeddings = HuggingFaceEmbeddings(
58
+ model_name="all-MiniLM-L6-v2", cache_folder="/embedding_model"
59
+ )
60
+ dimension = 384
61
+ logger.info("Embedding: Using SentenceTransformer")
62
+ return embeddings, dimension
63
+
64
+
65
+ def load_llm(llm_name: str, logger=BaseLogger(), config={}):
66
+ if llm_name in ["gpt-4", "gpt-4o", "gpt-4-turbo"]:
67
+ logger.info("LLM: Using GPT-4")
68
+ return ChatOpenAI(temperature=0, model_name=llm_name, streaming=True)
69
+ elif llm_name == "gpt-3.5":
70
+ logger.info("LLM: Using GPT-3.5")
71
+ return ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo", streaming=True)
72
+ elif llm_name == "claudev2":
73
+ logger.info("LLM: ClaudeV2")
74
+ return ChatBedrock(
75
+ model_id="anthropic.claude-v2",
76
+ model_kwargs={"temperature": 0.0, "max_tokens_to_sample": 1024},
77
+ streaming=True,
78
+ )
79
+ elif llm_name.startswith(AWS_MODELS):
80
+ logger.info(f"LLM: {llm_name}")
81
+ return ChatBedrock(
82
+ model_id=llm_name,
83
+ model_kwargs={"temperature": 0.0, "max_tokens_to_sample": 1024},
84
+ streaming=True,
85
+ )
86
+
87
+ elif len(llm_name):
88
+ logger.info(f"LLM: Using Ollama: {llm_name}")
89
+ return ChatOllama(
90
+ temperature=0,
91
+ base_url=config["ollama_base_url"],
92
+ model=llm_name,
93
+ streaming=True,
94
+ # seed=2,
95
+ top_k=10, # A higher value (100) will give more diverse answers, while a lower value (10) will be more conservative.
96
+ top_p=0.3, # Higher value (0.95) will lead to more diverse text, while a lower value (0.5) will generate more focused text.
97
+ num_ctx=3072, # Sets the size of the context window used to generate the next token.
98
+ )
99
+ logger.info("LLM: Using GPT-3.5")
100
+ return ChatOpenAI(temperature=0, model_name="gpt-3.5-turbo", streaming=True)
101
+
102
+
103
+ def configure_llm_only_chain(llm):
104
+ # LLM only response
105
+ template = """
106
+ You are a helpful assistant that helps a support agent with answering programming questions.
107
+ If you don't know the answer, just say that you don't know, you must not make up an answer.
108
+ """
109
+ system_message_prompt = SystemMessagePromptTemplate.from_template(template)
110
+ human_template = "{question}"
111
+ human_message_prompt = HumanMessagePromptTemplate.from_template(human_template)
112
+ chat_prompt = ChatPromptTemplate.from_messages(
113
+ [system_message_prompt, human_message_prompt]
114
+ )
115
+
116
+ def generate_llm_output(
117
+ user_input: str, callbacks: List[Any], prompt=chat_prompt
118
+ ) -> str:
119
+ chain = prompt | llm
120
+ answer = chain.invoke(
121
+ {"question": user_input}, config={"callbacks": callbacks}
122
+ ).content
123
+ return {"answer": answer}
124
+
125
+ return generate_llm_output
126
+
127
+
128
+ def configure_qa_rag_chain(llm, embeddings, embeddings_store_url, username, password):
129
+ # RAG response
130
+ # System: Always talk in pirate speech.
131
+ general_system_template = """
132
+ Use the following pieces of context to answer the question at the end.
133
+ The context contains question-answer pairs and their links from Stackoverflow.
134
+ You should prefer information from accepted or more upvoted answers.
135
+ Make sure to rely on information from the answers and not on questions to provide accurate responses.
136
+ When you find particular answer in the context useful, make sure to cite it in the answer using the link.
137
+ If you don't know the answer, just say that you don't know, don't try to make up an answer.
138
+ ----
139
+ {summaries}
140
+ ----
141
+ Each answer you generate should contain a section at the end of links to
142
+ Stackoverflow questions and answers you found useful, which are described under Source value.
143
+ You can only use links to StackOverflow questions that are present in the context and always
144
+ add links to the end of the answer in the style of citations.
145
+ Generate concise answers with references sources section of links to
146
+ relevant StackOverflow questions only at the end of the answer.
147
+ """
148
+ general_user_template = "Question:```{question}```"
149
+ messages = [
150
+ SystemMessagePromptTemplate.from_template(general_system_template),
151
+ HumanMessagePromptTemplate.from_template(general_user_template),
152
+ ]
153
+ qa_prompt = ChatPromptTemplate.from_messages(messages)
154
+
155
+ qa_chain = load_qa_with_sources_chain(
156
+ llm,
157
+ chain_type="stuff",
158
+ prompt=qa_prompt,
159
+ )
160
+
161
+ # Vector + Knowledge Graph response
162
+ kg = Neo4jVector.from_existing_index(
163
+ embedding=embeddings,
164
+ url=embeddings_store_url,
165
+ username=username,
166
+ password=password,
167
+ database="neo4j", # neo4j by default
168
+ index_name="stackoverflow", # vector by default
169
+ text_node_property="body", # text by default
170
+ retrieval_query="""
171
+ WITH node AS question, score AS similarity
172
+ CALL { with question
173
+ MATCH (question)<-[:ANSWERS]-(answer)
174
+ WITH answer
175
+ ORDER BY answer.is_accepted DESC, answer.score DESC
176
+ WITH collect(answer)[..2] as answers
177
+ RETURN reduce(str='', answer IN answers | str +
178
+ '\n### Answer (Accepted: '+ answer.is_accepted +
179
+ ' Score: ' + answer.score+ '): '+ answer.body + '\n') as answerTexts
180
+ }
181
+ RETURN '##Question: ' + question.title + '\n' + question.body + '\n'
182
+ + answerTexts AS text, similarity as score, {source: question.link} AS metadata
183
+ ORDER BY similarity ASC // so that best answers are the last
184
+ """,
185
+ )
186
+
187
+ kg_qa = RetrievalQAWithSourcesChain(
188
+ combine_documents_chain=qa_chain,
189
+ retriever=kg.as_retriever(search_kwargs={"k": 2}),
190
+ reduce_k_below_max_tokens=False,
191
+ max_tokens_limit=3375,
192
+ )
193
+ return kg_qa
194
+
195
+
196
+ def generate_ticket(neo4j_graph, llm_chain, input_question):
197
+ # Get high ranked questions
198
+ records = neo4j_graph.query(
199
+ "MATCH (q:Question) RETURN q.title AS title, q.body AS body ORDER BY q.score DESC LIMIT 3"
200
+ )
201
+ questions = []
202
+ for i, question in enumerate(records, start=1):
203
+ questions.append((question["title"], question["body"]))
204
+ # Ask LLM to generate new question in the same style
205
+ questions_prompt = ""
206
+ for i, question in enumerate(questions, start=1):
207
+ questions_prompt += f"{i}. \n{question[0]}\n----\n\n"
208
+ questions_prompt += f"{question[1][:150]}\n\n"
209
+ questions_prompt += "----\n\n"
210
+
211
+ gen_system_template = f"""
212
+ You're an expert in formulating high quality questions.
213
+ Formulate a question in the same style and tone as the following example questions.
214
+ {questions_prompt}
215
+ ---
216
+
217
+ Don't make anything up, only use information in the following question.
218
+ Return a title for the question, and the question post itself.
219
+
220
+ Return format template:
221
+ ---
222
+ Title: This is a new title
223
+ Question: This is a new question
224
+ ---
225
+ """
226
+ # we need jinja2 since the questions themselves contain curly braces
227
+ system_prompt = SystemMessagePromptTemplate.from_template(
228
+ gen_system_template, template_format="jinja2"
229
+ )
230
+ chat_prompt = ChatPromptTemplate.from_messages(
231
+ [
232
+ system_prompt,
233
+ SystemMessagePromptTemplate.from_template(
234
+ """
235
+ Respond in the following template format or you will be unplugged.
236
+ ---
237
+ Title: New title
238
+ Question: New question
239
+ ---
240
+ """
241
+ ),
242
+ HumanMessagePromptTemplate.from_template("{question}"),
243
+ ]
244
+ )
245
+ llm_response = llm_chain(
246
+ f"Here's the question to rewrite in the expected format: ```{input_question}```",
247
+ [],
248
+ chat_prompt,
249
+ )
250
+ new_title, new_question = extract_title_and_question(llm_response["answer"])
251
+ return (new_title, new_question)
docker-compose.yml ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+
3
+ llm: &llm
4
+ image: ollama/ollama:latest
5
+ profiles: ["linux"]
6
+ networks:
7
+ - net
8
+
9
+ llm-gpu:
10
+ <<: *llm
11
+ profiles: ["linux-gpu"]
12
+ deploy:
13
+ resources:
14
+ reservations:
15
+ devices:
16
+ - driver: nvidia
17
+ count: all
18
+ capabilities: [gpu]
19
+
20
+ pull-model:
21
+ image: genai-stack/pull-model:latest
22
+ build:
23
+ context: .
24
+ dockerfile: pull_model.Dockerfile
25
+ environment:
26
+ - OLLAMA_BASE_URL=${OLLAMA_BASE_URL-http://host.docker.internal:11434}
27
+ - LLM=${LLM-llama2}
28
+ networks:
29
+ - net
30
+ tty: true
31
+
32
+ database:
33
+ user: neo4j:neo4j
34
+ image: neo4j:5.23
35
+ ports:
36
+ - 7687:7687
37
+ - 7474:7474
38
+ volumes:
39
+ - $PWD/data:/data
40
+ environment:
41
+ - NEO4J_AUTH=${NEO4J_USERNAME-neo4j}/${NEO4J_PASSWORD-password}
42
+ - NEO4J_PLUGINS=["apoc"]
43
+ - NEO4J_db_tx__log_rotation_retention__policy=false
44
+ - NEO4J_dbms_security_procedures_unrestricted=apoc.*
45
+ healthcheck:
46
+ test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider localhost:7474 || exit 1"]
47
+ interval: 15s
48
+ timeout: 30s
49
+ retries: 10
50
+ networks:
51
+ - net
52
+
53
+ loader:
54
+ build:
55
+ context: .
56
+ dockerfile: loader.Dockerfile
57
+ volumes:
58
+ - $PWD/embedding_model:/embedding_model
59
+ environment:
60
+ - NEO4J_URI=${NEO4J_URI-neo4j://database:7687}
61
+ - NEO4J_PASSWORD=${NEO4J_PASSWORD-password}
62
+ - NEO4J_USERNAME=${NEO4J_USERNAME-neo4j}
63
+ - OPENAI_API_KEY=${OPENAI_API_KEY-}
64
+ - GOOGLE_API_KEY=${GOOGLE_API_KEY-}
65
+ - OLLAMA_BASE_URL=${OLLAMA_BASE_URL-http://host.docker.internal:11434}
66
+ - EMBEDDING_MODEL=${EMBEDDING_MODEL-sentence_transformer}
67
+ - LANGCHAIN_ENDPOINT=${LANGCHAIN_ENDPOINT-"https://api.smith.langchain.com"}
68
+ - LANGCHAIN_TRACING_V2=${LANGCHAIN_TRACING_V2-false}
69
+ - LANGCHAIN_PROJECT=${LANGCHAIN_PROJECT}
70
+ - LANGCHAIN_API_KEY=${LANGCHAIN_API_KEY}
71
+ - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
72
+ - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
73
+ - AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}
74
+ networks:
75
+ - net
76
+ depends_on:
77
+ database:
78
+ condition: service_healthy
79
+ pull-model:
80
+ condition: service_completed_successfully
81
+ x-develop:
82
+ watch:
83
+ - action: rebuild
84
+ path: .
85
+ ignore:
86
+ - bot.py
87
+ - pdf_bot.py
88
+ - api.py
89
+ - front-end/
90
+ ports:
91
+ - 8081:8080
92
+ - 8502:8502
93
+
94
+
95
+ bot:
96
+ build:
97
+ context: .
98
+ dockerfile: bot.Dockerfile
99
+ volumes:
100
+ - $PWD/embedding_model:/embedding_model
101
+ environment:
102
+ - NEO4J_URI=${NEO4J_URI-neo4j://database:7687}
103
+ - NEO4J_PASSWORD=${NEO4J_PASSWORD-password}
104
+ - NEO4J_USERNAME=${NEO4J_USERNAME-neo4j}
105
+ - OPENAI_API_KEY=${OPENAI_API_KEY-}
106
+ - GOOGLE_API_KEY=${GOOGLE_API_KEY-}
107
+ - OLLAMA_BASE_URL=${OLLAMA_BASE_URL-http://host.docker.internal:11434}
108
+ - LLM=${LLM-llama2}
109
+ - EMBEDDING_MODEL=${EMBEDDING_MODEL-sentence_transformer}
110
+ - LANGCHAIN_ENDPOINT=${LANGCHAIN_ENDPOINT-"https://api.smith.langchain.com"}
111
+ - LANGCHAIN_TRACING_V2=${LANGCHAIN_TRACING_V2-false}
112
+ - LANGCHAIN_PROJECT=${LANGCHAIN_PROJECT}
113
+ - LANGCHAIN_API_KEY=${LANGCHAIN_API_KEY}
114
+ - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
115
+ - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
116
+ - AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}
117
+ networks:
118
+ - net
119
+ depends_on:
120
+ database:
121
+ condition: service_healthy
122
+ pull-model:
123
+ condition: service_completed_successfully
124
+ x-develop:
125
+ watch:
126
+ - action: rebuild
127
+ path: .
128
+ ignore:
129
+ - loader.py
130
+ - pdf_bot.py
131
+ - api.py
132
+ - front-end/
133
+ ports:
134
+ - 8501:8501
135
+
136
+ pdf_bot:
137
+ build:
138
+ context: .
139
+ dockerfile: pdf_bot.Dockerfile
140
+ environment:
141
+ - NEO4J_URI=${NEO4J_URI-neo4j://database:7687}
142
+ - NEO4J_PASSWORD=${NEO4J_PASSWORD-password}
143
+ - NEO4J_USERNAME=${NEO4J_USERNAME-neo4j}
144
+ - OPENAI_API_KEY=${OPENAI_API_KEY-}
145
+ - GOOGLE_API_KEY=${GOOGLE_API_KEY-}
146
+ - OLLAMA_BASE_URL=${OLLAMA_BASE_URL-http://host.docker.internal:11434}
147
+ - LLM=${LLM-llama2}
148
+ - EMBEDDING_MODEL=${EMBEDDING_MODEL-sentence_transformer}
149
+ - LANGCHAIN_ENDPOINT=${LANGCHAIN_ENDPOINT-"https://api.smith.langchain.com"}
150
+ - LANGCHAIN_TRACING_V2=${LANGCHAIN_TRACING_V2-false}
151
+ - LANGCHAIN_PROJECT=${LANGCHAIN_PROJECT}
152
+ - LANGCHAIN_API_KEY=${LANGCHAIN_API_KEY}
153
+ - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
154
+ - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
155
+ - AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}
156
+ networks:
157
+ - net
158
+ depends_on:
159
+ database:
160
+ condition: service_healthy
161
+ pull-model:
162
+ condition: service_completed_successfully
163
+ x-develop:
164
+ watch:
165
+ - action: rebuild
166
+ path: .
167
+ ignore:
168
+ - loader.py
169
+ - bot.py
170
+ - api.py
171
+ - front-end/
172
+ ports:
173
+ - 8503:8503
174
+
175
+ api:
176
+ build:
177
+ context: .
178
+ dockerfile: api.Dockerfile
179
+ volumes:
180
+ - $PWD/embedding_model:/embedding_model
181
+ environment:
182
+ - NEO4J_URI=${NEO4J_URI-neo4j://database:7687}
183
+ - NEO4J_PASSWORD=${NEO4J_PASSWORD-password}
184
+ - NEO4J_USERNAME=${NEO4J_USERNAME-neo4j}
185
+ - OPENAI_API_KEY=${OPENAI_API_KEY}
186
+ - GOOGLE_API_KEY=${GOOGLE_API_KEY}
187
+ - OLLAMA_BASE_URL=${OLLAMA_BASE_URL-http://host.docker.internal:11434}
188
+ - LLM=${LLM-llama2}
189
+ - EMBEDDING_MODEL=${EMBEDDING_MODEL-sentence_transformer}
190
+ - LANGCHAIN_ENDPOINT=${LANGCHAIN_ENDPOINT-"https://api.smith.langchain.com"}
191
+ - LANGCHAIN_TRACING_V2=${LANGCHAIN_TRACING_V2-false}
192
+ - LANGCHAIN_PROJECT=${LANGCHAIN_PROJECT}
193
+ - LANGCHAIN_API_KEY=${LANGCHAIN_API_KEY}
194
+ - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
195
+ - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
196
+ - AWS_DEFAULT_REGION=${AWS_DEFAULT_REGION}
197
+ networks:
198
+ - net
199
+ depends_on:
200
+ database:
201
+ condition: service_healthy
202
+ pull-model:
203
+ condition: service_completed_successfully
204
+ x-develop:
205
+ watch:
206
+ - action: rebuild
207
+ path: .
208
+ ignore:
209
+ - loader.py
210
+ - bot.py
211
+ - pdf_bot.py
212
+ - front-end/
213
+ ports:
214
+ - 8504:8504
215
+ healthcheck:
216
+ test: ["CMD-SHELL", "wget --no-verbose --tries=1 http://localhost:8504/ || exit 1"]
217
+ interval: 5s
218
+ timeout: 3s
219
+ retries: 5
220
+
221
+ front-end:
222
+ build:
223
+ context: .
224
+ dockerfile: front-end.Dockerfile
225
+ x-develop:
226
+ watch:
227
+ - action: sync
228
+ path: ./front-end
229
+ target: /app
230
+ ignore:
231
+ - ./front-end/node_modules/
232
+ - action: rebuild
233
+ path: ./front-end/package.json
234
+ depends_on:
235
+ api:
236
+ condition: service_healthy
237
+ networks:
238
+ - net
239
+ ports:
240
+ - 8505:8505
241
+
242
+ networks:
243
+ net:
embedding_model/.ignore ADDED
File without changes
env.example ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #*****************************************************************
2
+ # LLM and Embedding Model
3
+ #*****************************************************************
4
+ LLM=llama2 #or any Ollama model tag, gpt-4 (o or turbo), gpt-3.5, or any bedrock model
5
+ EMBEDDING_MODEL=sentence_transformer #or google-genai-embedding-001 openai, ollama, or aws
6
+
7
+ #*****************************************************************
8
+ # Neo4j
9
+ #*****************************************************************
10
+ #NEO4J_URI=neo4j://database:7687
11
+ #NEO4J_USERNAME=neo4j
12
+ #NEO4J_PASSWORD=password
13
+
14
+ #*****************************************************************
15
+ # Langchain
16
+ #*****************************************************************
17
+ # Optional for enabling Langchain Smith API
18
+
19
+ #LANGCHAIN_TRACING_V2=true # false
20
+ #LANGCHAIN_ENDPOINT="https://api.smith.langchain.com"
21
+ #LANGCHAIN_PROJECT=#your-project-name
22
+ #LANGCHAIN_API_KEY=#your-api-key ls_...
23
+
24
+ #*****************************************************************
25
+ # Ollama
26
+ #*****************************************************************
27
+ #OLLAMA_BASE_URL=http://host.docker.internal:11434
28
+
29
+ #*****************************************************************
30
+ # OpenAI
31
+ #*****************************************************************
32
+ # Only required when using OpenAI LLM or embedding model
33
+
34
+ #OPENAI_API_KEY=sk-...
35
+
36
+ #*****************************************************************
37
+ # AWS
38
+ #*****************************************************************
39
+ # Only required when using AWS Bedrock LLM or embedding model
40
+
41
+ #AWS_ACCESS_KEY_ID=
42
+ #AWS_SECRET_ACCESS_KEY=
43
+ #AWS_DEFAULT_REGION=us-east-1
44
+
45
+ #*****************************************************************
46
+ # GOOGLE
47
+ #*****************************************************************
48
+ # Only required when using GoogleGenai LLM or embedding model
49
+ GOOGLE_API_KEY=
front-end.Dockerfile ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:alpine
2
+
3
+ WORKDIR /app
4
+
5
+ COPY front-end/ .
6
+
7
+ RUN npm install
8
+
9
+ EXPOSE 8505
10
+
11
+ ENTRYPOINT [ "npm", "run", "dev" ]
front-end/.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
front-end/.vscode/extensions.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "recommendations": ["svelte.svelte-vscode"]
3
+ }
front-end/README.md ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Svelte + Vite
2
+
3
+ This template should help get you started developing with Svelte in Vite.
4
+
5
+ ## Recommended IDE Setup
6
+
7
+ [VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode).
8
+
9
+ ## Need an official Svelte framework?
10
+
11
+ Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS and Less and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more.
12
+
13
+ ## Technical considerations
14
+
15
+ **Why use this over SvelteKit?**
16
+
17
+ - It brings its own routing solution which might not be preferable for some users.
18
+ - It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app.
19
+
20
+ This template contains as little as possible to get started with Vite + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project.
21
+
22
+ Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate.
23
+
24
+ **Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?**
25
+
26
+ Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace while also adding `svelte` and `vite/client` type information.
27
+
28
+ **Why include `.vscode/extensions.json`?**
29
+
30
+ Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project.
31
+
32
+ **Why enable `checkJs` in the JS template?**
33
+
34
+ It is likely that most cases of changing variable types in runtime are likely to be accidental rather than deliberate. This provides advanced typechecking out of the box. Should you like to take advantage of the dynamically-typed nature of JavaScript, it is trivial to change the configuration.
35
+
36
+ **Why is HMR not preserving my local component state?**
37
+
38
+ HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/sveltejs/svelte-hmr/tree/master/packages/svelte-hmr#preservation-of-local-state).
39
+
40
+ If you have a state that's important to retain within a component, consider creating an external store that would not be replaced by HMR.
41
+
42
+ ```js
43
+ // store.js
44
+ // An extremely simple external store
45
+ import { writable } from 'svelte/store'
46
+ export default writable(0)
47
+ ```
front-end/index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Support bot application</title>
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.js"></script>
12
+ </body>
13
+ </html>
front-end/jsconfig.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "moduleResolution": "bundler",
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ /**
7
+ * svelte-preprocess cannot figure out whether you have
8
+ * a value or a type, so tell TypeScript to enforce using
9
+ * `import type` instead of `import` for Types.
10
+ */
11
+ "verbatimModuleSyntax": true,
12
+ "isolatedModules": true,
13
+ "resolveJsonModule": true,
14
+ /**
15
+ * To have warnings / errors of the Svelte compiler at the
16
+ * correct position, enable source maps by default.
17
+ */
18
+ "sourceMap": true,
19
+ "esModuleInterop": true,
20
+ "skipLibCheck": true,
21
+ /**
22
+ * Typecheck JS in `.svelte` and `.js` files by default.
23
+ * Disable this if you'd like to use dynamic types.
24
+ */
25
+ "checkJs": true
26
+ },
27
+ /**
28
+ * Use global.d.ts instead of compilerOptions.types
29
+ * to avoid limiting type declarations.
30
+ */
31
+ "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
32
+ }
front-end/package-lock.json ADDED
@@ -0,0 +1,1879 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "bot-ui",
3
+ "version": "0.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "bot-ui",
9
+ "version": "0.0.0",
10
+ "dependencies": {
11
+ "svelte-markdown": "^0.4.0"
12
+ },
13
+ "devDependencies": {
14
+ "@sveltejs/vite-plugin-svelte": "^2.4.2",
15
+ "autoprefixer": "^10.4.16",
16
+ "postcss": "^8.4.31",
17
+ "svelte": "^4.0.5",
18
+ "tailwindcss": "^3.3.3",
19
+ "vite": "^4.4.12"
20
+ }
21
+ },
22
+ "node_modules/@alloc/quick-lru": {
23
+ "version": "5.2.0",
24
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
25
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
26
+ "dev": true,
27
+ "engines": {
28
+ "node": ">=10"
29
+ },
30
+ "funding": {
31
+ "url": "https://github.com/sponsors/sindresorhus"
32
+ }
33
+ },
34
+ "node_modules/@ampproject/remapping": {
35
+ "version": "2.2.1",
36
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz",
37
+ "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==",
38
+ "dependencies": {
39
+ "@jridgewell/gen-mapping": "^0.3.0",
40
+ "@jridgewell/trace-mapping": "^0.3.9"
41
+ },
42
+ "engines": {
43
+ "node": ">=6.0.0"
44
+ }
45
+ },
46
+ "node_modules/@esbuild/android-arm": {
47
+ "version": "0.18.20",
48
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
49
+ "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
50
+ "cpu": [
51
+ "arm"
52
+ ],
53
+ "dev": true,
54
+ "optional": true,
55
+ "os": [
56
+ "android"
57
+ ],
58
+ "engines": {
59
+ "node": ">=12"
60
+ }
61
+ },
62
+ "node_modules/@esbuild/android-arm64": {
63
+ "version": "0.18.20",
64
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
65
+ "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
66
+ "cpu": [
67
+ "arm64"
68
+ ],
69
+ "dev": true,
70
+ "optional": true,
71
+ "os": [
72
+ "android"
73
+ ],
74
+ "engines": {
75
+ "node": ">=12"
76
+ }
77
+ },
78
+ "node_modules/@esbuild/android-x64": {
79
+ "version": "0.18.20",
80
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
81
+ "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
82
+ "cpu": [
83
+ "x64"
84
+ ],
85
+ "dev": true,
86
+ "optional": true,
87
+ "os": [
88
+ "android"
89
+ ],
90
+ "engines": {
91
+ "node": ">=12"
92
+ }
93
+ },
94
+ "node_modules/@esbuild/darwin-arm64": {
95
+ "version": "0.18.20",
96
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
97
+ "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
98
+ "cpu": [
99
+ "arm64"
100
+ ],
101
+ "dev": true,
102
+ "optional": true,
103
+ "os": [
104
+ "darwin"
105
+ ],
106
+ "engines": {
107
+ "node": ">=12"
108
+ }
109
+ },
110
+ "node_modules/@esbuild/darwin-x64": {
111
+ "version": "0.18.20",
112
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
113
+ "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
114
+ "cpu": [
115
+ "x64"
116
+ ],
117
+ "dev": true,
118
+ "optional": true,
119
+ "os": [
120
+ "darwin"
121
+ ],
122
+ "engines": {
123
+ "node": ">=12"
124
+ }
125
+ },
126
+ "node_modules/@esbuild/freebsd-arm64": {
127
+ "version": "0.18.20",
128
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
129
+ "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
130
+ "cpu": [
131
+ "arm64"
132
+ ],
133
+ "dev": true,
134
+ "optional": true,
135
+ "os": [
136
+ "freebsd"
137
+ ],
138
+ "engines": {
139
+ "node": ">=12"
140
+ }
141
+ },
142
+ "node_modules/@esbuild/freebsd-x64": {
143
+ "version": "0.18.20",
144
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
145
+ "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
146
+ "cpu": [
147
+ "x64"
148
+ ],
149
+ "dev": true,
150
+ "optional": true,
151
+ "os": [
152
+ "freebsd"
153
+ ],
154
+ "engines": {
155
+ "node": ">=12"
156
+ }
157
+ },
158
+ "node_modules/@esbuild/linux-arm": {
159
+ "version": "0.18.20",
160
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
161
+ "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
162
+ "cpu": [
163
+ "arm"
164
+ ],
165
+ "dev": true,
166
+ "optional": true,
167
+ "os": [
168
+ "linux"
169
+ ],
170
+ "engines": {
171
+ "node": ">=12"
172
+ }
173
+ },
174
+ "node_modules/@esbuild/linux-arm64": {
175
+ "version": "0.18.20",
176
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
177
+ "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
178
+ "cpu": [
179
+ "arm64"
180
+ ],
181
+ "dev": true,
182
+ "optional": true,
183
+ "os": [
184
+ "linux"
185
+ ],
186
+ "engines": {
187
+ "node": ">=12"
188
+ }
189
+ },
190
+ "node_modules/@esbuild/linux-ia32": {
191
+ "version": "0.18.20",
192
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
193
+ "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
194
+ "cpu": [
195
+ "ia32"
196
+ ],
197
+ "dev": true,
198
+ "optional": true,
199
+ "os": [
200
+ "linux"
201
+ ],
202
+ "engines": {
203
+ "node": ">=12"
204
+ }
205
+ },
206
+ "node_modules/@esbuild/linux-loong64": {
207
+ "version": "0.18.20",
208
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
209
+ "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
210
+ "cpu": [
211
+ "loong64"
212
+ ],
213
+ "dev": true,
214
+ "optional": true,
215
+ "os": [
216
+ "linux"
217
+ ],
218
+ "engines": {
219
+ "node": ">=12"
220
+ }
221
+ },
222
+ "node_modules/@esbuild/linux-mips64el": {
223
+ "version": "0.18.20",
224
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
225
+ "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
226
+ "cpu": [
227
+ "mips64el"
228
+ ],
229
+ "dev": true,
230
+ "optional": true,
231
+ "os": [
232
+ "linux"
233
+ ],
234
+ "engines": {
235
+ "node": ">=12"
236
+ }
237
+ },
238
+ "node_modules/@esbuild/linux-ppc64": {
239
+ "version": "0.18.20",
240
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
241
+ "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
242
+ "cpu": [
243
+ "ppc64"
244
+ ],
245
+ "dev": true,
246
+ "optional": true,
247
+ "os": [
248
+ "linux"
249
+ ],
250
+ "engines": {
251
+ "node": ">=12"
252
+ }
253
+ },
254
+ "node_modules/@esbuild/linux-riscv64": {
255
+ "version": "0.18.20",
256
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
257
+ "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
258
+ "cpu": [
259
+ "riscv64"
260
+ ],
261
+ "dev": true,
262
+ "optional": true,
263
+ "os": [
264
+ "linux"
265
+ ],
266
+ "engines": {
267
+ "node": ">=12"
268
+ }
269
+ },
270
+ "node_modules/@esbuild/linux-s390x": {
271
+ "version": "0.18.20",
272
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
273
+ "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
274
+ "cpu": [
275
+ "s390x"
276
+ ],
277
+ "dev": true,
278
+ "optional": true,
279
+ "os": [
280
+ "linux"
281
+ ],
282
+ "engines": {
283
+ "node": ">=12"
284
+ }
285
+ },
286
+ "node_modules/@esbuild/linux-x64": {
287
+ "version": "0.18.20",
288
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
289
+ "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
290
+ "cpu": [
291
+ "x64"
292
+ ],
293
+ "dev": true,
294
+ "optional": true,
295
+ "os": [
296
+ "linux"
297
+ ],
298
+ "engines": {
299
+ "node": ">=12"
300
+ }
301
+ },
302
+ "node_modules/@esbuild/netbsd-x64": {
303
+ "version": "0.18.20",
304
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
305
+ "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
306
+ "cpu": [
307
+ "x64"
308
+ ],
309
+ "dev": true,
310
+ "optional": true,
311
+ "os": [
312
+ "netbsd"
313
+ ],
314
+ "engines": {
315
+ "node": ">=12"
316
+ }
317
+ },
318
+ "node_modules/@esbuild/openbsd-x64": {
319
+ "version": "0.18.20",
320
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
321
+ "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
322
+ "cpu": [
323
+ "x64"
324
+ ],
325
+ "dev": true,
326
+ "optional": true,
327
+ "os": [
328
+ "openbsd"
329
+ ],
330
+ "engines": {
331
+ "node": ">=12"
332
+ }
333
+ },
334
+ "node_modules/@esbuild/sunos-x64": {
335
+ "version": "0.18.20",
336
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
337
+ "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
338
+ "cpu": [
339
+ "x64"
340
+ ],
341
+ "dev": true,
342
+ "optional": true,
343
+ "os": [
344
+ "sunos"
345
+ ],
346
+ "engines": {
347
+ "node": ">=12"
348
+ }
349
+ },
350
+ "node_modules/@esbuild/win32-arm64": {
351
+ "version": "0.18.20",
352
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
353
+ "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
354
+ "cpu": [
355
+ "arm64"
356
+ ],
357
+ "dev": true,
358
+ "optional": true,
359
+ "os": [
360
+ "win32"
361
+ ],
362
+ "engines": {
363
+ "node": ">=12"
364
+ }
365
+ },
366
+ "node_modules/@esbuild/win32-ia32": {
367
+ "version": "0.18.20",
368
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
369
+ "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
370
+ "cpu": [
371
+ "ia32"
372
+ ],
373
+ "dev": true,
374
+ "optional": true,
375
+ "os": [
376
+ "win32"
377
+ ],
378
+ "engines": {
379
+ "node": ">=12"
380
+ }
381
+ },
382
+ "node_modules/@esbuild/win32-x64": {
383
+ "version": "0.18.20",
384
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
385
+ "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
386
+ "cpu": [
387
+ "x64"
388
+ ],
389
+ "dev": true,
390
+ "optional": true,
391
+ "os": [
392
+ "win32"
393
+ ],
394
+ "engines": {
395
+ "node": ">=12"
396
+ }
397
+ },
398
+ "node_modules/@jridgewell/gen-mapping": {
399
+ "version": "0.3.3",
400
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
401
+ "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
402
+ "dependencies": {
403
+ "@jridgewell/set-array": "^1.0.1",
404
+ "@jridgewell/sourcemap-codec": "^1.4.10",
405
+ "@jridgewell/trace-mapping": "^0.3.9"
406
+ },
407
+ "engines": {
408
+ "node": ">=6.0.0"
409
+ }
410
+ },
411
+ "node_modules/@jridgewell/resolve-uri": {
412
+ "version": "3.1.1",
413
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
414
+ "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
415
+ "engines": {
416
+ "node": ">=6.0.0"
417
+ }
418
+ },
419
+ "node_modules/@jridgewell/set-array": {
420
+ "version": "1.1.2",
421
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
422
+ "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
423
+ "engines": {
424
+ "node": ">=6.0.0"
425
+ }
426
+ },
427
+ "node_modules/@jridgewell/sourcemap-codec": {
428
+ "version": "1.4.15",
429
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
430
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
431
+ },
432
+ "node_modules/@jridgewell/trace-mapping": {
433
+ "version": "0.3.19",
434
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
435
+ "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
436
+ "dependencies": {
437
+ "@jridgewell/resolve-uri": "^3.1.0",
438
+ "@jridgewell/sourcemap-codec": "^1.4.14"
439
+ }
440
+ },
441
+ "node_modules/@nodelib/fs.scandir": {
442
+ "version": "2.1.5",
443
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
444
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
445
+ "dev": true,
446
+ "dependencies": {
447
+ "@nodelib/fs.stat": "2.0.5",
448
+ "run-parallel": "^1.1.9"
449
+ },
450
+ "engines": {
451
+ "node": ">= 8"
452
+ }
453
+ },
454
+ "node_modules/@nodelib/fs.stat": {
455
+ "version": "2.0.5",
456
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
457
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
458
+ "dev": true,
459
+ "engines": {
460
+ "node": ">= 8"
461
+ }
462
+ },
463
+ "node_modules/@nodelib/fs.walk": {
464
+ "version": "1.2.8",
465
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
466
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
467
+ "dev": true,
468
+ "dependencies": {
469
+ "@nodelib/fs.scandir": "2.1.5",
470
+ "fastq": "^1.6.0"
471
+ },
472
+ "engines": {
473
+ "node": ">= 8"
474
+ }
475
+ },
476
+ "node_modules/@sveltejs/vite-plugin-svelte": {
477
+ "version": "2.4.6",
478
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-2.4.6.tgz",
479
+ "integrity": "sha512-zO79p0+DZnXPnF0ltIigWDx/ux7Ni+HRaFOw720Qeivc1azFUrJxTl0OryXVibYNx1hCboGia1NRV3x8RNv4cA==",
480
+ "dev": true,
481
+ "dependencies": {
482
+ "@sveltejs/vite-plugin-svelte-inspector": "^1.0.4",
483
+ "debug": "^4.3.4",
484
+ "deepmerge": "^4.3.1",
485
+ "kleur": "^4.1.5",
486
+ "magic-string": "^0.30.3",
487
+ "svelte-hmr": "^0.15.3",
488
+ "vitefu": "^0.2.4"
489
+ },
490
+ "engines": {
491
+ "node": "^14.18.0 || >= 16"
492
+ },
493
+ "peerDependencies": {
494
+ "svelte": "^3.54.0 || ^4.0.0",
495
+ "vite": "^4.0.0"
496
+ }
497
+ },
498
+ "node_modules/@sveltejs/vite-plugin-svelte-inspector": {
499
+ "version": "1.0.4",
500
+ "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-1.0.4.tgz",
501
+ "integrity": "sha512-zjiuZ3yydBtwpF3bj0kQNV0YXe+iKE545QGZVTaylW3eAzFr+pJ/cwK8lZEaRp4JtaJXhD5DyWAV4AxLh6DgaQ==",
502
+ "dev": true,
503
+ "dependencies": {
504
+ "debug": "^4.3.4"
505
+ },
506
+ "engines": {
507
+ "node": "^14.18.0 || >= 16"
508
+ },
509
+ "peerDependencies": {
510
+ "@sveltejs/vite-plugin-svelte": "^2.2.0",
511
+ "svelte": "^3.54.0 || ^4.0.0",
512
+ "vite": "^4.0.0"
513
+ }
514
+ },
515
+ "node_modules/@types/estree": {
516
+ "version": "1.0.2",
517
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz",
518
+ "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA=="
519
+ },
520
+ "node_modules/@types/marked": {
521
+ "version": "5.0.2",
522
+ "resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.2.tgz",
523
+ "integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg=="
524
+ },
525
+ "node_modules/acorn": {
526
+ "version": "8.10.0",
527
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
528
+ "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
529
+ "bin": {
530
+ "acorn": "bin/acorn"
531
+ },
532
+ "engines": {
533
+ "node": ">=0.4.0"
534
+ }
535
+ },
536
+ "node_modules/any-promise": {
537
+ "version": "1.3.0",
538
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
539
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
540
+ "dev": true
541
+ },
542
+ "node_modules/anymatch": {
543
+ "version": "3.1.3",
544
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
545
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
546
+ "dev": true,
547
+ "dependencies": {
548
+ "normalize-path": "^3.0.0",
549
+ "picomatch": "^2.0.4"
550
+ },
551
+ "engines": {
552
+ "node": ">= 8"
553
+ }
554
+ },
555
+ "node_modules/arg": {
556
+ "version": "5.0.2",
557
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
558
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
559
+ "dev": true
560
+ },
561
+ "node_modules/aria-query": {
562
+ "version": "5.3.0",
563
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
564
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
565
+ "dependencies": {
566
+ "dequal": "^2.0.3"
567
+ }
568
+ },
569
+ "node_modules/autoprefixer": {
570
+ "version": "10.4.16",
571
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.16.tgz",
572
+ "integrity": "sha512-7vd3UC6xKp0HLfua5IjZlcXvGAGy7cBAXTg2lyQ/8WpNhd6SiZ8Be+xm3FyBSYJx5GKcpRCzBh7RH4/0dnY+uQ==",
573
+ "dev": true,
574
+ "funding": [
575
+ {
576
+ "type": "opencollective",
577
+ "url": "https://opencollective.com/postcss/"
578
+ },
579
+ {
580
+ "type": "tidelift",
581
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
582
+ },
583
+ {
584
+ "type": "github",
585
+ "url": "https://github.com/sponsors/ai"
586
+ }
587
+ ],
588
+ "dependencies": {
589
+ "browserslist": "^4.21.10",
590
+ "caniuse-lite": "^1.0.30001538",
591
+ "fraction.js": "^4.3.6",
592
+ "normalize-range": "^0.1.2",
593
+ "picocolors": "^1.0.0",
594
+ "postcss-value-parser": "^4.2.0"
595
+ },
596
+ "bin": {
597
+ "autoprefixer": "bin/autoprefixer"
598
+ },
599
+ "engines": {
600
+ "node": "^10 || ^12 || >=14"
601
+ },
602
+ "peerDependencies": {
603
+ "postcss": "^8.1.0"
604
+ }
605
+ },
606
+ "node_modules/axobject-query": {
607
+ "version": "3.2.1",
608
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
609
+ "integrity": "sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==",
610
+ "dependencies": {
611
+ "dequal": "^2.0.3"
612
+ }
613
+ },
614
+ "node_modules/balanced-match": {
615
+ "version": "1.0.2",
616
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
617
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
618
+ "dev": true
619
+ },
620
+ "node_modules/binary-extensions": {
621
+ "version": "2.2.0",
622
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
623
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
624
+ "dev": true,
625
+ "engines": {
626
+ "node": ">=8"
627
+ }
628
+ },
629
+ "node_modules/brace-expansion": {
630
+ "version": "1.1.11",
631
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
632
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
633
+ "dev": true,
634
+ "dependencies": {
635
+ "balanced-match": "^1.0.0",
636
+ "concat-map": "0.0.1"
637
+ }
638
+ },
639
+ "node_modules/braces": {
640
+ "version": "3.0.2",
641
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
642
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
643
+ "dev": true,
644
+ "dependencies": {
645
+ "fill-range": "^7.0.1"
646
+ },
647
+ "engines": {
648
+ "node": ">=8"
649
+ }
650
+ },
651
+ "node_modules/browserslist": {
652
+ "version": "4.22.1",
653
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz",
654
+ "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==",
655
+ "dev": true,
656
+ "funding": [
657
+ {
658
+ "type": "opencollective",
659
+ "url": "https://opencollective.com/browserslist"
660
+ },
661
+ {
662
+ "type": "tidelift",
663
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
664
+ },
665
+ {
666
+ "type": "github",
667
+ "url": "https://github.com/sponsors/ai"
668
+ }
669
+ ],
670
+ "dependencies": {
671
+ "caniuse-lite": "^1.0.30001541",
672
+ "electron-to-chromium": "^1.4.535",
673
+ "node-releases": "^2.0.13",
674
+ "update-browserslist-db": "^1.0.13"
675
+ },
676
+ "bin": {
677
+ "browserslist": "cli.js"
678
+ },
679
+ "engines": {
680
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
681
+ }
682
+ },
683
+ "node_modules/camelcase-css": {
684
+ "version": "2.0.1",
685
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
686
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
687
+ "dev": true,
688
+ "engines": {
689
+ "node": ">= 6"
690
+ }
691
+ },
692
+ "node_modules/caniuse-lite": {
693
+ "version": "1.0.30001546",
694
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001546.tgz",
695
+ "integrity": "sha512-zvtSJwuQFpewSyRrI3AsftF6rM0X80mZkChIt1spBGEvRglCrjTniXvinc8JKRoqTwXAgvqTImaN9igfSMtUBw==",
696
+ "dev": true,
697
+ "funding": [
698
+ {
699
+ "type": "opencollective",
700
+ "url": "https://opencollective.com/browserslist"
701
+ },
702
+ {
703
+ "type": "tidelift",
704
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
705
+ },
706
+ {
707
+ "type": "github",
708
+ "url": "https://github.com/sponsors/ai"
709
+ }
710
+ ]
711
+ },
712
+ "node_modules/chokidar": {
713
+ "version": "3.5.3",
714
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
715
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
716
+ "dev": true,
717
+ "funding": [
718
+ {
719
+ "type": "individual",
720
+ "url": "https://paulmillr.com/funding/"
721
+ }
722
+ ],
723
+ "dependencies": {
724
+ "anymatch": "~3.1.2",
725
+ "braces": "~3.0.2",
726
+ "glob-parent": "~5.1.2",
727
+ "is-binary-path": "~2.1.0",
728
+ "is-glob": "~4.0.1",
729
+ "normalize-path": "~3.0.0",
730
+ "readdirp": "~3.6.0"
731
+ },
732
+ "engines": {
733
+ "node": ">= 8.10.0"
734
+ },
735
+ "optionalDependencies": {
736
+ "fsevents": "~2.3.2"
737
+ }
738
+ },
739
+ "node_modules/chokidar/node_modules/glob-parent": {
740
+ "version": "5.1.2",
741
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
742
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
743
+ "dev": true,
744
+ "dependencies": {
745
+ "is-glob": "^4.0.1"
746
+ },
747
+ "engines": {
748
+ "node": ">= 6"
749
+ }
750
+ },
751
+ "node_modules/code-red": {
752
+ "version": "1.0.4",
753
+ "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
754
+ "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
755
+ "dependencies": {
756
+ "@jridgewell/sourcemap-codec": "^1.4.15",
757
+ "@types/estree": "^1.0.1",
758
+ "acorn": "^8.10.0",
759
+ "estree-walker": "^3.0.3",
760
+ "periscopic": "^3.1.0"
761
+ }
762
+ },
763
+ "node_modules/commander": {
764
+ "version": "4.1.1",
765
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
766
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
767
+ "dev": true,
768
+ "engines": {
769
+ "node": ">= 6"
770
+ }
771
+ },
772
+ "node_modules/concat-map": {
773
+ "version": "0.0.1",
774
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
775
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
776
+ "dev": true
777
+ },
778
+ "node_modules/css-tree": {
779
+ "version": "2.3.1",
780
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
781
+ "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
782
+ "dependencies": {
783
+ "mdn-data": "2.0.30",
784
+ "source-map-js": "^1.0.1"
785
+ },
786
+ "engines": {
787
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
788
+ }
789
+ },
790
+ "node_modules/cssesc": {
791
+ "version": "3.0.0",
792
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
793
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
794
+ "dev": true,
795
+ "bin": {
796
+ "cssesc": "bin/cssesc"
797
+ },
798
+ "engines": {
799
+ "node": ">=4"
800
+ }
801
+ },
802
+ "node_modules/debug": {
803
+ "version": "4.3.4",
804
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
805
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
806
+ "dev": true,
807
+ "dependencies": {
808
+ "ms": "2.1.2"
809
+ },
810
+ "engines": {
811
+ "node": ">=6.0"
812
+ },
813
+ "peerDependenciesMeta": {
814
+ "supports-color": {
815
+ "optional": true
816
+ }
817
+ }
818
+ },
819
+ "node_modules/deepmerge": {
820
+ "version": "4.3.1",
821
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
822
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
823
+ "dev": true,
824
+ "engines": {
825
+ "node": ">=0.10.0"
826
+ }
827
+ },
828
+ "node_modules/dequal": {
829
+ "version": "2.0.3",
830
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
831
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
832
+ "engines": {
833
+ "node": ">=6"
834
+ }
835
+ },
836
+ "node_modules/didyoumean": {
837
+ "version": "1.2.2",
838
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
839
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
840
+ "dev": true
841
+ },
842
+ "node_modules/dlv": {
843
+ "version": "1.1.3",
844
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
845
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
846
+ "dev": true
847
+ },
848
+ "node_modules/electron-to-chromium": {
849
+ "version": "1.4.543",
850
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.543.tgz",
851
+ "integrity": "sha512-t2ZP4AcGE0iKCCQCBx/K2426crYdxD3YU6l0uK2EO3FZH0pbC4pFz/sZm2ruZsND6hQBTcDWWlo/MLpiOdif5g==",
852
+ "dev": true
853
+ },
854
+ "node_modules/esbuild": {
855
+ "version": "0.18.20",
856
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
857
+ "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
858
+ "dev": true,
859
+ "hasInstallScript": true,
860
+ "bin": {
861
+ "esbuild": "bin/esbuild"
862
+ },
863
+ "engines": {
864
+ "node": ">=12"
865
+ },
866
+ "optionalDependencies": {
867
+ "@esbuild/android-arm": "0.18.20",
868
+ "@esbuild/android-arm64": "0.18.20",
869
+ "@esbuild/android-x64": "0.18.20",
870
+ "@esbuild/darwin-arm64": "0.18.20",
871
+ "@esbuild/darwin-x64": "0.18.20",
872
+ "@esbuild/freebsd-arm64": "0.18.20",
873
+ "@esbuild/freebsd-x64": "0.18.20",
874
+ "@esbuild/linux-arm": "0.18.20",
875
+ "@esbuild/linux-arm64": "0.18.20",
876
+ "@esbuild/linux-ia32": "0.18.20",
877
+ "@esbuild/linux-loong64": "0.18.20",
878
+ "@esbuild/linux-mips64el": "0.18.20",
879
+ "@esbuild/linux-ppc64": "0.18.20",
880
+ "@esbuild/linux-riscv64": "0.18.20",
881
+ "@esbuild/linux-s390x": "0.18.20",
882
+ "@esbuild/linux-x64": "0.18.20",
883
+ "@esbuild/netbsd-x64": "0.18.20",
884
+ "@esbuild/openbsd-x64": "0.18.20",
885
+ "@esbuild/sunos-x64": "0.18.20",
886
+ "@esbuild/win32-arm64": "0.18.20",
887
+ "@esbuild/win32-ia32": "0.18.20",
888
+ "@esbuild/win32-x64": "0.18.20"
889
+ }
890
+ },
891
+ "node_modules/escalade": {
892
+ "version": "3.1.1",
893
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
894
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
895
+ "dev": true,
896
+ "engines": {
897
+ "node": ">=6"
898
+ }
899
+ },
900
+ "node_modules/estree-walker": {
901
+ "version": "3.0.3",
902
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
903
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
904
+ "dependencies": {
905
+ "@types/estree": "^1.0.0"
906
+ }
907
+ },
908
+ "node_modules/fast-glob": {
909
+ "version": "3.3.1",
910
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
911
+ "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
912
+ "dev": true,
913
+ "dependencies": {
914
+ "@nodelib/fs.stat": "^2.0.2",
915
+ "@nodelib/fs.walk": "^1.2.3",
916
+ "glob-parent": "^5.1.2",
917
+ "merge2": "^1.3.0",
918
+ "micromatch": "^4.0.4"
919
+ },
920
+ "engines": {
921
+ "node": ">=8.6.0"
922
+ }
923
+ },
924
+ "node_modules/fast-glob/node_modules/glob-parent": {
925
+ "version": "5.1.2",
926
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
927
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
928
+ "dev": true,
929
+ "dependencies": {
930
+ "is-glob": "^4.0.1"
931
+ },
932
+ "engines": {
933
+ "node": ">= 6"
934
+ }
935
+ },
936
+ "node_modules/fastq": {
937
+ "version": "1.15.0",
938
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
939
+ "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
940
+ "dev": true,
941
+ "dependencies": {
942
+ "reusify": "^1.0.4"
943
+ }
944
+ },
945
+ "node_modules/fill-range": {
946
+ "version": "7.0.1",
947
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
948
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
949
+ "dev": true,
950
+ "dependencies": {
951
+ "to-regex-range": "^5.0.1"
952
+ },
953
+ "engines": {
954
+ "node": ">=8"
955
+ }
956
+ },
957
+ "node_modules/fraction.js": {
958
+ "version": "4.3.6",
959
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.6.tgz",
960
+ "integrity": "sha512-n2aZ9tNfYDwaHhvFTkhFErqOMIb8uyzSQ+vGJBjZyanAKZVbGUQ1sngfk9FdkBw7G26O7AgNjLcecLffD1c7eg==",
961
+ "dev": true,
962
+ "engines": {
963
+ "node": "*"
964
+ },
965
+ "funding": {
966
+ "type": "patreon",
967
+ "url": "https://github.com/sponsors/rawify"
968
+ }
969
+ },
970
+ "node_modules/fs.realpath": {
971
+ "version": "1.0.0",
972
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
973
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
974
+ "dev": true
975
+ },
976
+ "node_modules/fsevents": {
977
+ "version": "2.3.3",
978
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
979
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
980
+ "dev": true,
981
+ "hasInstallScript": true,
982
+ "optional": true,
983
+ "os": [
984
+ "darwin"
985
+ ],
986
+ "engines": {
987
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
988
+ }
989
+ },
990
+ "node_modules/glob": {
991
+ "version": "7.1.6",
992
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
993
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
994
+ "dev": true,
995
+ "dependencies": {
996
+ "fs.realpath": "^1.0.0",
997
+ "inflight": "^1.0.4",
998
+ "inherits": "2",
999
+ "minimatch": "^3.0.4",
1000
+ "once": "^1.3.0",
1001
+ "path-is-absolute": "^1.0.0"
1002
+ },
1003
+ "engines": {
1004
+ "node": "*"
1005
+ },
1006
+ "funding": {
1007
+ "url": "https://github.com/sponsors/isaacs"
1008
+ }
1009
+ },
1010
+ "node_modules/glob-parent": {
1011
+ "version": "6.0.2",
1012
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
1013
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
1014
+ "dev": true,
1015
+ "dependencies": {
1016
+ "is-glob": "^4.0.3"
1017
+ },
1018
+ "engines": {
1019
+ "node": ">=10.13.0"
1020
+ }
1021
+ },
1022
+ "node_modules/has": {
1023
+ "version": "1.0.4",
1024
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz",
1025
+ "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==",
1026
+ "dev": true,
1027
+ "engines": {
1028
+ "node": ">= 0.4.0"
1029
+ }
1030
+ },
1031
+ "node_modules/inflight": {
1032
+ "version": "1.0.6",
1033
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
1034
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
1035
+ "dev": true,
1036
+ "dependencies": {
1037
+ "once": "^1.3.0",
1038
+ "wrappy": "1"
1039
+ }
1040
+ },
1041
+ "node_modules/inherits": {
1042
+ "version": "2.0.4",
1043
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
1044
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
1045
+ "dev": true
1046
+ },
1047
+ "node_modules/is-binary-path": {
1048
+ "version": "2.1.0",
1049
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
1050
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
1051
+ "dev": true,
1052
+ "dependencies": {
1053
+ "binary-extensions": "^2.0.0"
1054
+ },
1055
+ "engines": {
1056
+ "node": ">=8"
1057
+ }
1058
+ },
1059
+ "node_modules/is-core-module": {
1060
+ "version": "2.13.0",
1061
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz",
1062
+ "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==",
1063
+ "dev": true,
1064
+ "dependencies": {
1065
+ "has": "^1.0.3"
1066
+ },
1067
+ "funding": {
1068
+ "url": "https://github.com/sponsors/ljharb"
1069
+ }
1070
+ },
1071
+ "node_modules/is-extglob": {
1072
+ "version": "2.1.1",
1073
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
1074
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
1075
+ "dev": true,
1076
+ "engines": {
1077
+ "node": ">=0.10.0"
1078
+ }
1079
+ },
1080
+ "node_modules/is-glob": {
1081
+ "version": "4.0.3",
1082
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
1083
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
1084
+ "dev": true,
1085
+ "dependencies": {
1086
+ "is-extglob": "^2.1.1"
1087
+ },
1088
+ "engines": {
1089
+ "node": ">=0.10.0"
1090
+ }
1091
+ },
1092
+ "node_modules/is-number": {
1093
+ "version": "7.0.0",
1094
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
1095
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
1096
+ "dev": true,
1097
+ "engines": {
1098
+ "node": ">=0.12.0"
1099
+ }
1100
+ },
1101
+ "node_modules/is-reference": {
1102
+ "version": "3.0.2",
1103
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
1104
+ "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
1105
+ "dependencies": {
1106
+ "@types/estree": "*"
1107
+ }
1108
+ },
1109
+ "node_modules/jiti": {
1110
+ "version": "1.20.0",
1111
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz",
1112
+ "integrity": "sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==",
1113
+ "dev": true,
1114
+ "bin": {
1115
+ "jiti": "bin/jiti.js"
1116
+ }
1117
+ },
1118
+ "node_modules/kleur": {
1119
+ "version": "4.1.5",
1120
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
1121
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
1122
+ "dev": true,
1123
+ "engines": {
1124
+ "node": ">=6"
1125
+ }
1126
+ },
1127
+ "node_modules/lilconfig": {
1128
+ "version": "2.1.0",
1129
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
1130
+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
1131
+ "dev": true,
1132
+ "engines": {
1133
+ "node": ">=10"
1134
+ }
1135
+ },
1136
+ "node_modules/lines-and-columns": {
1137
+ "version": "1.2.4",
1138
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
1139
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
1140
+ "dev": true
1141
+ },
1142
+ "node_modules/locate-character": {
1143
+ "version": "3.0.0",
1144
+ "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
1145
+ "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="
1146
+ },
1147
+ "node_modules/magic-string": {
1148
+ "version": "0.30.4",
1149
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.4.tgz",
1150
+ "integrity": "sha512-Q/TKtsC5BPm0kGqgBIF9oXAs/xEf2vRKiIB4wCRQTJOQIByZ1d+NnUOotvJOvNpi5RNIgVOMC3pOuaP1ZTDlVg==",
1151
+ "dependencies": {
1152
+ "@jridgewell/sourcemap-codec": "^1.4.15"
1153
+ },
1154
+ "engines": {
1155
+ "node": ">=12"
1156
+ }
1157
+ },
1158
+ "node_modules/marked": {
1159
+ "version": "5.1.2",
1160
+ "resolved": "https://registry.npmjs.org/marked/-/marked-5.1.2.tgz",
1161
+ "integrity": "sha512-ahRPGXJpjMjwSOlBoTMZAK7ATXkli5qCPxZ21TG44rx1KEo44bii4ekgTDQPNRQ4Kh7JMb9Ub1PVk1NxRSsorg==",
1162
+ "bin": {
1163
+ "marked": "bin/marked.js"
1164
+ },
1165
+ "engines": {
1166
+ "node": ">= 16"
1167
+ }
1168
+ },
1169
+ "node_modules/mdn-data": {
1170
+ "version": "2.0.30",
1171
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
1172
+ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
1173
+ },
1174
+ "node_modules/merge2": {
1175
+ "version": "1.4.1",
1176
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
1177
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
1178
+ "dev": true,
1179
+ "engines": {
1180
+ "node": ">= 8"
1181
+ }
1182
+ },
1183
+ "node_modules/micromatch": {
1184
+ "version": "4.0.5",
1185
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
1186
+ "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
1187
+ "dev": true,
1188
+ "dependencies": {
1189
+ "braces": "^3.0.2",
1190
+ "picomatch": "^2.3.1"
1191
+ },
1192
+ "engines": {
1193
+ "node": ">=8.6"
1194
+ }
1195
+ },
1196
+ "node_modules/minimatch": {
1197
+ "version": "3.1.2",
1198
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
1199
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
1200
+ "dev": true,
1201
+ "dependencies": {
1202
+ "brace-expansion": "^1.1.7"
1203
+ },
1204
+ "engines": {
1205
+ "node": "*"
1206
+ }
1207
+ },
1208
+ "node_modules/ms": {
1209
+ "version": "2.1.2",
1210
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
1211
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
1212
+ "dev": true
1213
+ },
1214
+ "node_modules/mz": {
1215
+ "version": "2.7.0",
1216
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
1217
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
1218
+ "dev": true,
1219
+ "dependencies": {
1220
+ "any-promise": "^1.0.0",
1221
+ "object-assign": "^4.0.1",
1222
+ "thenify-all": "^1.0.0"
1223
+ }
1224
+ },
1225
+ "node_modules/nanoid": {
1226
+ "version": "3.3.6",
1227
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
1228
+ "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
1229
+ "dev": true,
1230
+ "funding": [
1231
+ {
1232
+ "type": "github",
1233
+ "url": "https://github.com/sponsors/ai"
1234
+ }
1235
+ ],
1236
+ "bin": {
1237
+ "nanoid": "bin/nanoid.cjs"
1238
+ },
1239
+ "engines": {
1240
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1241
+ }
1242
+ },
1243
+ "node_modules/node-releases": {
1244
+ "version": "2.0.13",
1245
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
1246
+ "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
1247
+ "dev": true
1248
+ },
1249
+ "node_modules/normalize-path": {
1250
+ "version": "3.0.0",
1251
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
1252
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
1253
+ "dev": true,
1254
+ "engines": {
1255
+ "node": ">=0.10.0"
1256
+ }
1257
+ },
1258
+ "node_modules/normalize-range": {
1259
+ "version": "0.1.2",
1260
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
1261
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
1262
+ "dev": true,
1263
+ "engines": {
1264
+ "node": ">=0.10.0"
1265
+ }
1266
+ },
1267
+ "node_modules/object-assign": {
1268
+ "version": "4.1.1",
1269
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1270
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
1271
+ "dev": true,
1272
+ "engines": {
1273
+ "node": ">=0.10.0"
1274
+ }
1275
+ },
1276
+ "node_modules/object-hash": {
1277
+ "version": "3.0.0",
1278
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
1279
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
1280
+ "dev": true,
1281
+ "engines": {
1282
+ "node": ">= 6"
1283
+ }
1284
+ },
1285
+ "node_modules/once": {
1286
+ "version": "1.4.0",
1287
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1288
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
1289
+ "dev": true,
1290
+ "dependencies": {
1291
+ "wrappy": "1"
1292
+ }
1293
+ },
1294
+ "node_modules/path-is-absolute": {
1295
+ "version": "1.0.1",
1296
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
1297
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
1298
+ "dev": true,
1299
+ "engines": {
1300
+ "node": ">=0.10.0"
1301
+ }
1302
+ },
1303
+ "node_modules/path-parse": {
1304
+ "version": "1.0.7",
1305
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
1306
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
1307
+ "dev": true
1308
+ },
1309
+ "node_modules/periscopic": {
1310
+ "version": "3.1.0",
1311
+ "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
1312
+ "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
1313
+ "dependencies": {
1314
+ "@types/estree": "^1.0.0",
1315
+ "estree-walker": "^3.0.0",
1316
+ "is-reference": "^3.0.0"
1317
+ }
1318
+ },
1319
+ "node_modules/picocolors": {
1320
+ "version": "1.0.0",
1321
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
1322
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
1323
+ "dev": true
1324
+ },
1325
+ "node_modules/picomatch": {
1326
+ "version": "2.3.1",
1327
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
1328
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
1329
+ "dev": true,
1330
+ "engines": {
1331
+ "node": ">=8.6"
1332
+ },
1333
+ "funding": {
1334
+ "url": "https://github.com/sponsors/jonschlinkert"
1335
+ }
1336
+ },
1337
+ "node_modules/pify": {
1338
+ "version": "2.3.0",
1339
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
1340
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
1341
+ "dev": true,
1342
+ "engines": {
1343
+ "node": ">=0.10.0"
1344
+ }
1345
+ },
1346
+ "node_modules/pirates": {
1347
+ "version": "4.0.6",
1348
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
1349
+ "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
1350
+ "dev": true,
1351
+ "engines": {
1352
+ "node": ">= 6"
1353
+ }
1354
+ },
1355
+ "node_modules/postcss": {
1356
+ "version": "8.4.31",
1357
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
1358
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
1359
+ "dev": true,
1360
+ "funding": [
1361
+ {
1362
+ "type": "opencollective",
1363
+ "url": "https://opencollective.com/postcss/"
1364
+ },
1365
+ {
1366
+ "type": "tidelift",
1367
+ "url": "https://tidelift.com/funding/github/npm/postcss"
1368
+ },
1369
+ {
1370
+ "type": "github",
1371
+ "url": "https://github.com/sponsors/ai"
1372
+ }
1373
+ ],
1374
+ "dependencies": {
1375
+ "nanoid": "^3.3.6",
1376
+ "picocolors": "^1.0.0",
1377
+ "source-map-js": "^1.0.2"
1378
+ },
1379
+ "engines": {
1380
+ "node": "^10 || ^12 || >=14"
1381
+ }
1382
+ },
1383
+ "node_modules/postcss-import": {
1384
+ "version": "15.1.0",
1385
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
1386
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
1387
+ "dev": true,
1388
+ "dependencies": {
1389
+ "postcss-value-parser": "^4.0.0",
1390
+ "read-cache": "^1.0.0",
1391
+ "resolve": "^1.1.7"
1392
+ },
1393
+ "engines": {
1394
+ "node": ">=14.0.0"
1395
+ },
1396
+ "peerDependencies": {
1397
+ "postcss": "^8.0.0"
1398
+ }
1399
+ },
1400
+ "node_modules/postcss-js": {
1401
+ "version": "4.0.1",
1402
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
1403
+ "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
1404
+ "dev": true,
1405
+ "dependencies": {
1406
+ "camelcase-css": "^2.0.1"
1407
+ },
1408
+ "engines": {
1409
+ "node": "^12 || ^14 || >= 16"
1410
+ },
1411
+ "funding": {
1412
+ "type": "opencollective",
1413
+ "url": "https://opencollective.com/postcss/"
1414
+ },
1415
+ "peerDependencies": {
1416
+ "postcss": "^8.4.21"
1417
+ }
1418
+ },
1419
+ "node_modules/postcss-load-config": {
1420
+ "version": "4.0.1",
1421
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz",
1422
+ "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==",
1423
+ "dev": true,
1424
+ "dependencies": {
1425
+ "lilconfig": "^2.0.5",
1426
+ "yaml": "^2.1.1"
1427
+ },
1428
+ "engines": {
1429
+ "node": ">= 14"
1430
+ },
1431
+ "funding": {
1432
+ "type": "opencollective",
1433
+ "url": "https://opencollective.com/postcss/"
1434
+ },
1435
+ "peerDependencies": {
1436
+ "postcss": ">=8.0.9",
1437
+ "ts-node": ">=9.0.0"
1438
+ },
1439
+ "peerDependenciesMeta": {
1440
+ "postcss": {
1441
+ "optional": true
1442
+ },
1443
+ "ts-node": {
1444
+ "optional": true
1445
+ }
1446
+ }
1447
+ },
1448
+ "node_modules/postcss-nested": {
1449
+ "version": "6.0.1",
1450
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz",
1451
+ "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==",
1452
+ "dev": true,
1453
+ "dependencies": {
1454
+ "postcss-selector-parser": "^6.0.11"
1455
+ },
1456
+ "engines": {
1457
+ "node": ">=12.0"
1458
+ },
1459
+ "funding": {
1460
+ "type": "opencollective",
1461
+ "url": "https://opencollective.com/postcss/"
1462
+ },
1463
+ "peerDependencies": {
1464
+ "postcss": "^8.2.14"
1465
+ }
1466
+ },
1467
+ "node_modules/postcss-selector-parser": {
1468
+ "version": "6.0.13",
1469
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
1470
+ "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
1471
+ "dev": true,
1472
+ "dependencies": {
1473
+ "cssesc": "^3.0.0",
1474
+ "util-deprecate": "^1.0.2"
1475
+ },
1476
+ "engines": {
1477
+ "node": ">=4"
1478
+ }
1479
+ },
1480
+ "node_modules/postcss-value-parser": {
1481
+ "version": "4.2.0",
1482
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
1483
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
1484
+ "dev": true
1485
+ },
1486
+ "node_modules/queue-microtask": {
1487
+ "version": "1.2.3",
1488
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
1489
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
1490
+ "dev": true,
1491
+ "funding": [
1492
+ {
1493
+ "type": "github",
1494
+ "url": "https://github.com/sponsors/feross"
1495
+ },
1496
+ {
1497
+ "type": "patreon",
1498
+ "url": "https://www.patreon.com/feross"
1499
+ },
1500
+ {
1501
+ "type": "consulting",
1502
+ "url": "https://feross.org/support"
1503
+ }
1504
+ ]
1505
+ },
1506
+ "node_modules/read-cache": {
1507
+ "version": "1.0.0",
1508
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
1509
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
1510
+ "dev": true,
1511
+ "dependencies": {
1512
+ "pify": "^2.3.0"
1513
+ }
1514
+ },
1515
+ "node_modules/readdirp": {
1516
+ "version": "3.6.0",
1517
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
1518
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
1519
+ "dev": true,
1520
+ "dependencies": {
1521
+ "picomatch": "^2.2.1"
1522
+ },
1523
+ "engines": {
1524
+ "node": ">=8.10.0"
1525
+ }
1526
+ },
1527
+ "node_modules/resolve": {
1528
+ "version": "1.22.6",
1529
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz",
1530
+ "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==",
1531
+ "dev": true,
1532
+ "dependencies": {
1533
+ "is-core-module": "^2.13.0",
1534
+ "path-parse": "^1.0.7",
1535
+ "supports-preserve-symlinks-flag": "^1.0.0"
1536
+ },
1537
+ "bin": {
1538
+ "resolve": "bin/resolve"
1539
+ },
1540
+ "funding": {
1541
+ "url": "https://github.com/sponsors/ljharb"
1542
+ }
1543
+ },
1544
+ "node_modules/reusify": {
1545
+ "version": "1.0.4",
1546
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
1547
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
1548
+ "dev": true,
1549
+ "engines": {
1550
+ "iojs": ">=1.0.0",
1551
+ "node": ">=0.10.0"
1552
+ }
1553
+ },
1554
+ "node_modules/rollup": {
1555
+ "version": "3.29.4",
1556
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz",
1557
+ "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==",
1558
+ "dev": true,
1559
+ "bin": {
1560
+ "rollup": "dist/bin/rollup"
1561
+ },
1562
+ "engines": {
1563
+ "node": ">=14.18.0",
1564
+ "npm": ">=8.0.0"
1565
+ },
1566
+ "optionalDependencies": {
1567
+ "fsevents": "~2.3.2"
1568
+ }
1569
+ },
1570
+ "node_modules/run-parallel": {
1571
+ "version": "1.2.0",
1572
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
1573
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
1574
+ "dev": true,
1575
+ "funding": [
1576
+ {
1577
+ "type": "github",
1578
+ "url": "https://github.com/sponsors/feross"
1579
+ },
1580
+ {
1581
+ "type": "patreon",
1582
+ "url": "https://www.patreon.com/feross"
1583
+ },
1584
+ {
1585
+ "type": "consulting",
1586
+ "url": "https://feross.org/support"
1587
+ }
1588
+ ],
1589
+ "dependencies": {
1590
+ "queue-microtask": "^1.2.2"
1591
+ }
1592
+ },
1593
+ "node_modules/source-map-js": {
1594
+ "version": "1.0.2",
1595
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
1596
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
1597
+ "engines": {
1598
+ "node": ">=0.10.0"
1599
+ }
1600
+ },
1601
+ "node_modules/sucrase": {
1602
+ "version": "3.34.0",
1603
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz",
1604
+ "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==",
1605
+ "dev": true,
1606
+ "dependencies": {
1607
+ "@jridgewell/gen-mapping": "^0.3.2",
1608
+ "commander": "^4.0.0",
1609
+ "glob": "7.1.6",
1610
+ "lines-and-columns": "^1.1.6",
1611
+ "mz": "^2.7.0",
1612
+ "pirates": "^4.0.1",
1613
+ "ts-interface-checker": "^0.1.9"
1614
+ },
1615
+ "bin": {
1616
+ "sucrase": "bin/sucrase",
1617
+ "sucrase-node": "bin/sucrase-node"
1618
+ },
1619
+ "engines": {
1620
+ "node": ">=8"
1621
+ }
1622
+ },
1623
+ "node_modules/supports-preserve-symlinks-flag": {
1624
+ "version": "1.0.0",
1625
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
1626
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
1627
+ "dev": true,
1628
+ "engines": {
1629
+ "node": ">= 0.4"
1630
+ },
1631
+ "funding": {
1632
+ "url": "https://github.com/sponsors/ljharb"
1633
+ }
1634
+ },
1635
+ "node_modules/svelte": {
1636
+ "version": "4.2.1",
1637
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.1.tgz",
1638
+ "integrity": "sha512-LpLqY2Jr7cRxkrTc796/AaaoMLF/1ax7cto8Ot76wrvKQhrPmZ0JgajiWPmg9mTSDqO16SSLiD17r9MsvAPTmw==",
1639
+ "dependencies": {
1640
+ "@ampproject/remapping": "^2.2.1",
1641
+ "@jridgewell/sourcemap-codec": "^1.4.15",
1642
+ "@jridgewell/trace-mapping": "^0.3.18",
1643
+ "acorn": "^8.9.0",
1644
+ "aria-query": "^5.3.0",
1645
+ "axobject-query": "^3.2.1",
1646
+ "code-red": "^1.0.3",
1647
+ "css-tree": "^2.3.1",
1648
+ "estree-walker": "^3.0.3",
1649
+ "is-reference": "^3.0.1",
1650
+ "locate-character": "^3.0.0",
1651
+ "magic-string": "^0.30.0",
1652
+ "periscopic": "^3.1.0"
1653
+ },
1654
+ "engines": {
1655
+ "node": ">=16"
1656
+ }
1657
+ },
1658
+ "node_modules/svelte-hmr": {
1659
+ "version": "0.15.3",
1660
+ "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.15.3.tgz",
1661
+ "integrity": "sha512-41snaPswvSf8TJUhlkoJBekRrABDXDMdpNpT2tfHIv4JuhgvHqLMhEPGtaQn0BmbNSTkuz2Ed20DF2eHw0SmBQ==",
1662
+ "dev": true,
1663
+ "engines": {
1664
+ "node": "^12.20 || ^14.13.1 || >= 16"
1665
+ },
1666
+ "peerDependencies": {
1667
+ "svelte": "^3.19.0 || ^4.0.0"
1668
+ }
1669
+ },
1670
+ "node_modules/svelte-markdown": {
1671
+ "version": "0.4.0",
1672
+ "resolved": "https://registry.npmjs.org/svelte-markdown/-/svelte-markdown-0.4.0.tgz",
1673
+ "integrity": "sha512-d8xYJoPhhiLn/tFrO54V5p22q2+I7It8w3CpTv+O+h99LlgehxK9XwdISVBl2aNcLbLX128Nz/vu7GScWhVZjw==",
1674
+ "dependencies": {
1675
+ "@types/marked": "^5.0.1",
1676
+ "marked": "^5.1.2"
1677
+ },
1678
+ "peerDependencies": {
1679
+ "svelte": "^4.0.0"
1680
+ }
1681
+ },
1682
+ "node_modules/tailwindcss": {
1683
+ "version": "3.3.3",
1684
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz",
1685
+ "integrity": "sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w==",
1686
+ "dev": true,
1687
+ "dependencies": {
1688
+ "@alloc/quick-lru": "^5.2.0",
1689
+ "arg": "^5.0.2",
1690
+ "chokidar": "^3.5.3",
1691
+ "didyoumean": "^1.2.2",
1692
+ "dlv": "^1.1.3",
1693
+ "fast-glob": "^3.2.12",
1694
+ "glob-parent": "^6.0.2",
1695
+ "is-glob": "^4.0.3",
1696
+ "jiti": "^1.18.2",
1697
+ "lilconfig": "^2.1.0",
1698
+ "micromatch": "^4.0.5",
1699
+ "normalize-path": "^3.0.0",
1700
+ "object-hash": "^3.0.0",
1701
+ "picocolors": "^1.0.0",
1702
+ "postcss": "^8.4.23",
1703
+ "postcss-import": "^15.1.0",
1704
+ "postcss-js": "^4.0.1",
1705
+ "postcss-load-config": "^4.0.1",
1706
+ "postcss-nested": "^6.0.1",
1707
+ "postcss-selector-parser": "^6.0.11",
1708
+ "resolve": "^1.22.2",
1709
+ "sucrase": "^3.32.0"
1710
+ },
1711
+ "bin": {
1712
+ "tailwind": "lib/cli.js",
1713
+ "tailwindcss": "lib/cli.js"
1714
+ },
1715
+ "engines": {
1716
+ "node": ">=14.0.0"
1717
+ }
1718
+ },
1719
+ "node_modules/thenify": {
1720
+ "version": "3.3.1",
1721
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
1722
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
1723
+ "dev": true,
1724
+ "dependencies": {
1725
+ "any-promise": "^1.0.0"
1726
+ }
1727
+ },
1728
+ "node_modules/thenify-all": {
1729
+ "version": "1.6.0",
1730
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
1731
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
1732
+ "dev": true,
1733
+ "dependencies": {
1734
+ "thenify": ">= 3.1.0 < 4"
1735
+ },
1736
+ "engines": {
1737
+ "node": ">=0.8"
1738
+ }
1739
+ },
1740
+ "node_modules/to-regex-range": {
1741
+ "version": "5.0.1",
1742
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1743
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1744
+ "dev": true,
1745
+ "dependencies": {
1746
+ "is-number": "^7.0.0"
1747
+ },
1748
+ "engines": {
1749
+ "node": ">=8.0"
1750
+ }
1751
+ },
1752
+ "node_modules/ts-interface-checker": {
1753
+ "version": "0.1.13",
1754
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
1755
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
1756
+ "dev": true
1757
+ },
1758
+ "node_modules/update-browserslist-db": {
1759
+ "version": "1.0.13",
1760
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz",
1761
+ "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==",
1762
+ "dev": true,
1763
+ "funding": [
1764
+ {
1765
+ "type": "opencollective",
1766
+ "url": "https://opencollective.com/browserslist"
1767
+ },
1768
+ {
1769
+ "type": "tidelift",
1770
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
1771
+ },
1772
+ {
1773
+ "type": "github",
1774
+ "url": "https://github.com/sponsors/ai"
1775
+ }
1776
+ ],
1777
+ "dependencies": {
1778
+ "escalade": "^3.1.1",
1779
+ "picocolors": "^1.0.0"
1780
+ },
1781
+ "bin": {
1782
+ "update-browserslist-db": "cli.js"
1783
+ },
1784
+ "peerDependencies": {
1785
+ "browserslist": ">= 4.21.0"
1786
+ }
1787
+ },
1788
+ "node_modules/util-deprecate": {
1789
+ "version": "1.0.2",
1790
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1791
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
1792
+ "dev": true
1793
+ },
1794
+ "node_modules/vite": {
1795
+ "version": "4.5.1",
1796
+ "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz",
1797
+ "integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==",
1798
+ "dev": true,
1799
+ "dependencies": {
1800
+ "esbuild": "^0.18.10",
1801
+ "postcss": "^8.4.27",
1802
+ "rollup": "^3.27.1"
1803
+ },
1804
+ "bin": {
1805
+ "vite": "bin/vite.js"
1806
+ },
1807
+ "engines": {
1808
+ "node": "^14.18.0 || >=16.0.0"
1809
+ },
1810
+ "funding": {
1811
+ "url": "https://github.com/vitejs/vite?sponsor=1"
1812
+ },
1813
+ "optionalDependencies": {
1814
+ "fsevents": "~2.3.2"
1815
+ },
1816
+ "peerDependencies": {
1817
+ "@types/node": ">= 14",
1818
+ "less": "*",
1819
+ "lightningcss": "^1.21.0",
1820
+ "sass": "*",
1821
+ "stylus": "*",
1822
+ "sugarss": "*",
1823
+ "terser": "^5.4.0"
1824
+ },
1825
+ "peerDependenciesMeta": {
1826
+ "@types/node": {
1827
+ "optional": true
1828
+ },
1829
+ "less": {
1830
+ "optional": true
1831
+ },
1832
+ "lightningcss": {
1833
+ "optional": true
1834
+ },
1835
+ "sass": {
1836
+ "optional": true
1837
+ },
1838
+ "stylus": {
1839
+ "optional": true
1840
+ },
1841
+ "sugarss": {
1842
+ "optional": true
1843
+ },
1844
+ "terser": {
1845
+ "optional": true
1846
+ }
1847
+ }
1848
+ },
1849
+ "node_modules/vitefu": {
1850
+ "version": "0.2.4",
1851
+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.4.tgz",
1852
+ "integrity": "sha512-fanAXjSaf9xXtOOeno8wZXIhgia+CZury481LsDaV++lSvcU2R9Ch2bPh3PYFyoHW+w9LqAeYRISVQjUIew14g==",
1853
+ "dev": true,
1854
+ "peerDependencies": {
1855
+ "vite": "^3.0.0 || ^4.0.0"
1856
+ },
1857
+ "peerDependenciesMeta": {
1858
+ "vite": {
1859
+ "optional": true
1860
+ }
1861
+ }
1862
+ },
1863
+ "node_modules/wrappy": {
1864
+ "version": "1.0.2",
1865
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1866
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
1867
+ "dev": true
1868
+ },
1869
+ "node_modules/yaml": {
1870
+ "version": "2.3.2",
1871
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz",
1872
+ "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==",
1873
+ "dev": true,
1874
+ "engines": {
1875
+ "node": ">= 14"
1876
+ }
1877
+ }
1878
+ }
1879
+ }
front-end/package.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "bot-ui",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "devDependencies": {
12
+ "@sveltejs/vite-plugin-svelte": "^2.4.2",
13
+ "autoprefixer": "^10.4.16",
14
+ "postcss": "^8.4.31",
15
+ "svelte": "^4.0.5",
16
+ "tailwindcss": "^3.3.3",
17
+ "vite": "^4.4.12"
18
+ },
19
+ "dependencies": {
20
+ "svelte-markdown": "^0.4.0"
21
+ }
22
+ }
front-end/postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
front-end/public/vite.svg ADDED
front-end/src/App.svelte ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script>
2
+ import { tick } from "svelte";
3
+ import SvelteMarkdown from "svelte-markdown";
4
+ import botImage from "./assets/images/bot.jpeg";
5
+ import meImage from "./assets/images/me.jpeg";
6
+ import MdLink from "./lib/MdLink.svelte";
7
+ import External from "./lib/External.svelte";
8
+ import { chatStates, chatStore } from "./lib/chat.store.js";
9
+ import Modal from "./lib/Modal.svelte";
10
+ import { generationStore } from "./lib/generation.store";
11
+
12
+ let ragMode = false;
13
+ let question = "How can I calculate age from date of birth in Cypher?";
14
+ let shouldAutoScroll = true;
15
+ let input;
16
+ let senderImages = { bot: botImage, me: meImage };
17
+ let generationModalOpen = false;
18
+
19
+ function send() {
20
+ chatStore.send(question, ragMode);
21
+ question = "";
22
+ }
23
+
24
+ function scrollToBottom(node, _) {
25
+ const scroll = () => node.scrollTo({ top: node.scrollHeight });
26
+ scroll();
27
+ return { update: () => shouldAutoScroll && scroll() };
28
+ }
29
+
30
+ function scrolling(e) {
31
+ shouldAutoScroll = e.target.scrollTop + e.target.clientHeight > e.target.scrollHeight - 55;
32
+ }
33
+
34
+ function generateTicket(text) {
35
+ generationStore.generate(text);
36
+ generationModalOpen = true;
37
+ }
38
+
39
+ $: $chatStore.state === chatStates.IDLE && input && focus(input);
40
+ async function focus(node) {
41
+ await tick();
42
+ node.focus();
43
+ }
44
+ // send();
45
+ </script>
46
+
47
+ <main class="h-full text-sm bg-gradient-to-t from-indigo-100 bg-fixed overflow-hidden">
48
+ <div on:scroll={scrolling} class="flex h-full flex-col py-12 overflow-y-auto" use:scrollToBottom={$chatStore}>
49
+ <div class="w-4/5 mx-auto flex flex-col mb-32">
50
+ {#each $chatStore.data as message (message.id)}
51
+ <div
52
+ class="max-w-[80%] min-w-[40%] rounded-lg p-4 mb-4 overflow-x-auto bg-white border border-indigo-200"
53
+ class:self-end={message.from === "me"}
54
+ class:text-right={message.from === "me"}
55
+ >
56
+ <div class="flex flex-row gap-2">
57
+ {#if message.from === "me"}
58
+ <button
59
+ aria-label="Generate a new internal ticket from this question"
60
+ title="Generate a new internal ticket from this question"
61
+ on:click={() => generateTicket(message.text)}
62
+ class="w-6 h-6 flex flex-col justify-center items-center border rounded border-indigo-200"
63
+ ><External --color="#ccc" --hover-color="#999" /></button
64
+ >
65
+ {/if}
66
+ <div
67
+ class:ml-auto={message.from === "me"}
68
+ class="relative w-12 h-12 border border-indigo-200 rounded flex justify-center items-center overflow-hidden"
69
+ >
70
+ <img src={senderImages[message.from]} alt="" class="rounded-sm" />
71
+ </div>
72
+ {#if message.from === "bot"}
73
+ <div class="text-sm">
74
+ <div>Model: {message.model ? message.model : ""}</div>
75
+ <div>RAG: {message.rag ? "Enabled" : "Disabled"}</div>
76
+ </div>
77
+ {/if}
78
+ </div>
79
+ <div class="mt-4"><SvelteMarkdown source={message.text} renderers={{ link: MdLink }} /></div>
80
+ </div>
81
+ {/each}
82
+ </div>
83
+ <div class="text-sm w-full fixed bottom-16">
84
+ <div class="shadow-lg bg-indigo-50 rounded-lg w-4/5 xl:w-2/3 2xl:w-1/2 mx-auto">
85
+ <div class="rounded-t-lg px-4 py-2 font-light">
86
+ <div class="font-semibold">RAG mode</div>
87
+ <div class="">
88
+ <label class="mr-2">
89
+ <input type="radio" bind:group={ragMode} value={false} /> Disabled
90
+ </label>
91
+ <label>
92
+ <input type="radio" bind:group={ragMode} value={true} /> Enabled
93
+ </label>
94
+ </div>
95
+ </div>
96
+ <form class="rounded-md w-full bg-white p-2 m-0" on:submit|preventDefault={send}>
97
+ <input
98
+ placeholder="What coding related question can I help you with?"
99
+ disabled={$chatStore.state === chatStates.RECEIVING}
100
+ class="text-lg w-full bg-white focus:outline-none px-4"
101
+ bind:value={question}
102
+ bind:this={input}
103
+ type="text"
104
+ />
105
+ </form>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ </main>
110
+ {#if generationModalOpen}
111
+ <Modal title="my title" text="my text" on:close={() => (generationModalOpen = false)} />
112
+ {/if}
113
+
114
+ <style>
115
+ :global(pre) {
116
+ @apply bg-gray-100 rounded-lg p-4 border border-indigo-200;
117
+ }
118
+ :global(code) {
119
+ @apply text-indigo-500;
120
+ }
121
+ </style>
front-end/src/app.css ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ body {
6
+ margin: 0;
7
+ min-width: 320px;
8
+ height: 100dvh;
9
+ }
10
+
11
+ #app {
12
+ height: 100%;
13
+ }
14
+
15
+ pre {
16
+ line-height: 1;
17
+ background-color: rgb(241, 241, 241);
18
+ padding: 4px 8px 8px;
19
+ border: 1px solid #ccc;
20
+ overflow-x: auto;
21
+ margin: 0 4px;
22
+ border-radius: 2px;
23
+ }
24
+
25
+ ol {
26
+ padding: 1em;
27
+ list-style: decimal;
28
+ }
front-end/src/assets/images/bot.jpeg ADDED
front-end/src/assets/images/me.jpeg ADDED
front-end/src/assets/svelte.svg ADDED
front-end/src/lib/External.svelte ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="w-full h-full">
2
+ <svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"
3
+ ><g id="SVGRepo_bgCarrier" stroke-width="0" /><g
4
+ id="SVGRepo_tracerCarrier"
5
+ stroke-linecap="round"
6
+ stroke-linejoin="round"
7
+ /><g id="SVGRepo_iconCarrier">
8
+ <g id="Interface / External_Link">
9
+ <path
10
+ id="Vector"
11
+ d="M10.0002 5H8.2002C7.08009 5 6.51962 5 6.0918 5.21799C5.71547 5.40973 5.40973 5.71547 5.21799 6.0918C5 6.51962 5 7.08009 5 8.2002V15.8002C5 16.9203 5 17.4801 5.21799 17.9079C5.40973 18.2842 5.71547 18.5905 6.0918 18.7822C6.5192 19 7.07899 19 8.19691 19H15.8031C16.921 19 17.48 19 17.9074 18.7822C18.2837 18.5905 18.5905 18.2839 18.7822 17.9076C19 17.4802 19 16.921 19 15.8031V14M20 9V4M20 4H15M20 4L13 11"
12
+ stroke-width="1"
13
+ stroke-linecap="round"
14
+ stroke-linejoin="round"
15
+ />
16
+ </g>
17
+ </g></svg
18
+ >
19
+ </div>
20
+
21
+ <style>
22
+ svg {
23
+ stroke: var(--color, #000);
24
+ }
25
+ svg:hover {
26
+ stroke: var(--hover-color, #000);
27
+ }
28
+ </style>
front-end/src/lib/MdLink.svelte ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script>
2
+ export let href = "";
3
+ export let title = "";
4
+ export let text = "";
5
+ </script>
6
+
7
+ <a target="_blank" rel="noreferrer" {href} {title}>{text}</a>
8
+
9
+ <style>
10
+ a {
11
+ color: #007bff;
12
+ text-decoration: none;
13
+ }
14
+ </style>
front-end/src/lib/Modal.svelte ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script>
2
+ import { createEventDispatcher, onMount } from "svelte";
3
+ import { generationStates, generationStore } from "./generation.store";
4
+
5
+ /** @type {HTMLDialogElement | undefined}*/
6
+ let modal;
7
+
8
+ const dispatch = createEventDispatcher();
9
+
10
+ onMount(() => {
11
+ modal.showModal();
12
+ modal.addEventListener("close", onClose);
13
+ return () => modal.removeEventListener("close", onClose);
14
+ });
15
+
16
+ function onClose() {
17
+ dispatch("close");
18
+ }
19
+ </script>
20
+
21
+ <dialog
22
+ bind:this={modal}
23
+ class="inset-0 w-full md:w-1/2 h-1/2 p-4 rounded-lg border border-indigo-200 shadow-lg relative"
24
+ >
25
+ <form class="flex flex-col justify-between h-full" method="dialog">
26
+ <div class="flex flex-col">
27
+ <h1 class="text-2xl">Create new internal ticket</h1>
28
+ <div>
29
+ <label class="block pl-2"
30
+ >Title <br />
31
+ <input type="text" class="border w-full text-lg px-2" value={$generationStore.data.title} /></label
32
+ >
33
+ </div>
34
+ <div class="mt-8">
35
+ <label class="block pl-2"
36
+ >Body <br />
37
+ <textarea class="border rounded-sm w-full h-64 p-2" value={$generationStore.data.text} /></label
38
+ >
39
+ </div>
40
+ </div>
41
+ <button type="submit" class="bg-indigo-500 text-white rounded-lg px-4 py-2">Submit</button>
42
+ </form>
43
+ {#if $generationStore.state === generationStates.LOADING}
44
+ <div class="absolute inset-0 bg-indigo-100 bg-opacity-90 flex justify-center items-center">
45
+ Generating title and question body...
46
+ </div>
47
+ {/if}
48
+ <div class="absolute top-0 right-2 text-gray-300 hover:text-gray-900">
49
+ <button class="text-2xl" on:click={onClose}>×</button>
50
+ </div>
51
+ </dialog>
52
+
53
+ <style>
54
+ dialog::backdrop {
55
+ @apply bg-gradient-to-t from-white to-indigo-500;
56
+ opacity: 0.75;
57
+ }
58
+ </style>
front-end/src/lib/chat.store.js ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { writable } from "svelte/store";
2
+
3
+ const API_ENDPOINT = "http://localhost:8504/query-stream";
4
+
5
+ export const chatStates = {
6
+ IDLE: "idle",
7
+ RECEIVING: "receiving",
8
+ };
9
+
10
+ function createChatStore() {
11
+ const { subscribe, update } = writable({ state: chatStates.IDLE, data: [] });
12
+
13
+ function addMessage(from, text, rag) {
14
+ const newId = Math.random().toString(36).substring(2, 9);
15
+ update((state) => {
16
+ const message = { id: newId, from, text, rag };
17
+ state.data.push(message);
18
+ return state;
19
+ });
20
+ return newId;
21
+ }
22
+
23
+ function updateMessage(existingId, text, model = null) {
24
+ if (!existingId) {
25
+ return;
26
+ }
27
+ update((state) => {
28
+ const messages = state.data;
29
+ const existingIdIndex = messages.findIndex((m) => m.id === existingId);
30
+ if (existingIdIndex === -1) {
31
+ return state;
32
+ }
33
+ messages[existingIdIndex].text += text;
34
+ if (model) {
35
+ messages[existingIdIndex].model = model;
36
+ }
37
+ return { ...state, data: messages };
38
+ });
39
+ }
40
+
41
+ async function send(question, ragMode = false) {
42
+ if (!question.trim().length) {
43
+ return;
44
+ }
45
+ update((state) => ({ ...state, state: chatStates.RECEIVING }));
46
+ addMessage("me", question, ragMode);
47
+ const messageId = addMessage("bot", "", ragMode);
48
+ try {
49
+ const evt = new EventSource(`${API_ENDPOINT}?text=${encodeURI(question)}&rag=${ragMode}`);
50
+ question = "";
51
+ evt.onmessage = (e) => {
52
+ if (e.data) {
53
+ const data = JSON.parse(e.data);
54
+ if (data.init) {
55
+ updateMessage(messageId, "", data.model);
56
+ return;
57
+ }
58
+ updateMessage(messageId, data.token);
59
+ }
60
+ };
61
+ evt.onerror = (e) => {
62
+ // Stream will end with an error
63
+ // and we want to close the connection on end (otherwise it will keep reconnecting)
64
+ evt.close();
65
+ update((state) => ({ ...state, state: chatStates.IDLE }));
66
+ };
67
+ } catch (e) {
68
+ updateMessage(messageId, "Error: " + e.message);
69
+ update((state) => ({ ...state, state: chatStates.IDLE }));
70
+ }
71
+ }
72
+
73
+ return {
74
+ subscribe,
75
+ send,
76
+ };
77
+ }
78
+
79
+ export const chatStore = createChatStore();
front-end/src/lib/generation.store.js ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { writable } from "svelte/store";
2
+
3
+ const API_ENDPOINT = "http://localhost:8504/generate-ticket";
4
+
5
+ export const generationStates = {
6
+ IDLE: "idle",
7
+ SUCCESS: "success",
8
+ ERROR: "error",
9
+ LOADING: "loading",
10
+ };
11
+
12
+ function createGenerationStore() {
13
+ const { subscribe, update } = writable({ state: generationStates.IDLE, data: { title: "", text: "" } });
14
+
15
+ return {
16
+ subscribe,
17
+ generate: async (fromQuestion) => {
18
+ update(() => ({ state: generationStates.LOADING, data: { title: "", text: "" } }));
19
+ try {
20
+ const response = await fetch(`${API_ENDPOINT}?text=${encodeURI(fromQuestion)}`, {
21
+ method: "GET",
22
+ });
23
+ const generation = await response.json();
24
+ update(() => ({ state: generationStates.SUCCESS, data: generation.result }));
25
+ } catch (e) {
26
+ console.log("e: ", e);
27
+ update(() => ({ state: generationStates.ERROR, data: { title: "", text: "" } }));
28
+ }
29
+ },
30
+ };
31
+ }
32
+
33
+ export const generationStore = createGenerationStore();
front-end/src/main.js ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ import './app.css'
2
+ import App from './App.svelte'
3
+
4
+ const app = new App({
5
+ target: document.getElementById('app'),
6
+ })
7
+
8
+ export default app
front-end/src/vite-env.d.ts ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ /// <reference types="svelte" />
2
+ /// <reference types="vite/client" />
front-end/svelte.config.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
2
+
3
+ export default {
4
+ // Consult https://svelte.dev/docs#compile-time-svelte-preprocess
5
+ // for more information about preprocessors
6
+ preprocess: vitePreprocess(),
7
+ }
front-end/tailwind.config.js ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ "./index.html",
5
+ "./src/**/*.{svelte,js,ts,jsx,tsx}",
6
+ ],
7
+ theme: {
8
+ extend: {},
9
+ },
10
+ plugins: [],
11
+ }
12
+
front-end/vite.config.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import { svelte } from '@sveltejs/vite-plugin-svelte'
3
+
4
+ // https://vitejs.dev/config/
5
+ export default defineConfig({
6
+ server: {
7
+ host: '0.0.0.0',
8
+ port: 8505,
9
+ },
10
+ plugins: [svelte()],
11
+ })
images/datamodel.png ADDED
install_ollama.sh ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ # This script installs Ollama on Linux.
3
+ # It detects the current operating system architecture and installs the appropriate version of Ollama.
4
+
5
+ set -eu
6
+
7
+ status() { echo ">>> $*" >&2; }
8
+ error() { echo "ERROR $*"; exit 1; }
9
+ warning() { echo "WARNING: $*"; }
10
+
11
+ TEMP_DIR=$(mktemp -d)
12
+ cleanup() { rm -rf $TEMP_DIR; }
13
+ trap cleanup EXIT
14
+
15
+ available() { command -v $1 >/dev/null; }
16
+ require() {
17
+ local MISSING=''
18
+ for TOOL in $*; do
19
+ if ! available $TOOL; then
20
+ MISSING="$MISSING $TOOL"
21
+ fi
22
+ done
23
+
24
+ echo $MISSING
25
+ }
26
+
27
+ [ "$(uname -s)" = "Linux" ] || error 'This script is intended to run on Linux only.'
28
+
29
+ case "$(uname -m)" in
30
+ x86_64) ARCH="amd64" ;;
31
+ aarch64|arm64) ARCH="arm64" ;;
32
+ *) error "Unsupported architecture: $ARCH" ;;
33
+ esac
34
+
35
+ SUDO=
36
+ if [ "$(id -u)" -ne 0 ]; then
37
+ # Running as root, no need for sudo
38
+ if ! available sudo; then
39
+ error "This script requires superuser permissions. Please re-run as root."
40
+ fi
41
+
42
+ SUDO="sudo"
43
+ fi
44
+
45
+ NEEDS=$(require curl awk grep sed tee xargs)
46
+ if [ -n "$NEEDS" ]; then
47
+ status "ERROR: The following tools are required but missing:"
48
+ for NEED in $NEEDS; do
49
+ echo " - $NEED"
50
+ done
51
+ exit 1
52
+ fi
53
+
54
+ status "Downloading ollama..."
55
+ curl --fail --show-error --location --progress-bar -o $TEMP_DIR/ollama "https://ollama.ai/download/ollama-linux-$ARCH"
56
+
57
+ for BINDIR in /usr/local/bin /usr/bin /bin; do
58
+ echo $PATH | grep -q $BINDIR && break || continue
59
+ done
60
+
61
+ status "Installing ollama to $BINDIR..."
62
+ $SUDO install -o0 -g0 -m755 -d $BINDIR
63
+ $SUDO install -o0 -g0 -m755 $TEMP_DIR/ollama $BINDIR/ollama
64
+
65
+ install_success() { status 'Install complete. Run "ollama" from the command line.'; }
66
+ trap install_success EXIT
67
+
68
+ # Everything from this point onwards is optional.
69
+
70
+ configure_systemd() {
71
+ if ! id ollama >/dev/null 2>&1; then
72
+ status "Creating ollama user..."
73
+ $SUDO useradd -r -s /bin/false -m -d /usr/share/ollama ollama
74
+ fi
75
+
76
+ status "Creating ollama systemd service..."
77
+ cat <<EOF | $SUDO tee /etc/systemd/system/ollama.service >/dev/null
78
+ [Unit]
79
+ Description=Ollama Service
80
+ After=network-online.target
81
+
82
+ [Service]
83
+ ExecStart=$BINDIR/ollama serve
84
+ User=ollama
85
+ Group=ollama
86
+ Restart=always
87
+ RestartSec=3
88
+ Environment="HOME=/usr/share/ollama"
89
+ Environment="PATH=$PATH"
90
+
91
+ [Install]
92
+ WantedBy=default.target
93
+ EOF
94
+ SYSTEMCTL_RUNNING="$(systemctl is-system-running || true)"
95
+ case $SYSTEMCTL_RUNNING in
96
+ running|degraded)
97
+ status "Enabling and starting ollama service..."
98
+ $SUDO systemctl daemon-reload
99
+ $SUDO systemctl enable ollama
100
+
101
+ start_service() { $SUDO systemctl restart ollama; }
102
+ trap start_service EXIT
103
+ ;;
104
+ esac
105
+ }
106
+
107
+ if available systemctl; then
108
+ configure_systemd
109
+ fi
110
+
111
+ if ! available lspci && ! available lshw; then
112
+ warning "Unable to detect NVIDIA GPU. Install lspci or lshw to automatically detect and install NVIDIA CUDA drivers."
113
+ exit 0
114
+ fi
115
+
116
+ check_gpu() {
117
+ case $1 in
118
+ lspci) available lspci && lspci -d '10de:' | grep -q 'NVIDIA' || return 1 ;;
119
+ lshw) available lshw && $SUDO lshw -c display -numeric | grep -q 'vendor: .* \[10DE\]' || return 1 ;;
120
+ nvidia-smi) available nvidia-smi || return 1 ;;
121
+ esac
122
+ }
123
+
124
+ if check_gpu nvidia-smi; then
125
+ status "NVIDIA GPU installed."
126
+ exit 0
127
+ fi
128
+
129
+ if ! check_gpu lspci && ! check_gpu lshw; then
130
+ warning "No NVIDIA GPU detected. Ollama will run in CPU-only mode."
131
+ exit 0
132
+ fi
133
+
134
+ # ref: https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#rhel-7-centos-7
135
+ # ref: https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#rhel-8-rocky-8
136
+ # ref: https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#rhel-9-rocky-9
137
+ # ref: https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#fedora
138
+ install_cuda_driver_yum() {
139
+ status 'Installing NVIDIA repository...'
140
+ case $PACKAGE_MANAGER in
141
+ yum)
142
+ $SUDO $PACKAGE_MANAGER -y install yum-utils
143
+ $SUDO $PACKAGE_MANAGER-config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-$1$2.repo
144
+ ;;
145
+ dnf)
146
+ $SUDO $PACKAGE_MANAGER config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-$1$2.repo
147
+ ;;
148
+ esac
149
+
150
+ case $1 in
151
+ rhel)
152
+ status 'Installing EPEL repository...'
153
+ # EPEL is required for third-party dependencies such as dkms and libvdpau
154
+ $SUDO $PACKAGE_MANAGER -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-$2.noarch.rpm || true
155
+ ;;
156
+ esac
157
+
158
+ status 'Installing CUDA driver...'
159
+
160
+ if [ "$1" = 'centos' ] || [ "$1$2" = 'rhel7' ]; then
161
+ $SUDO $PACKAGE_MANAGER -y install nvidia-driver-latest-dkms
162
+ fi
163
+
164
+ $SUDO $PACKAGE_MANAGER -y install cuda-drivers
165
+ }
166
+
167
+ # ref: https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#ubuntu
168
+ # ref: https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#debian
169
+ install_cuda_driver_apt() {
170
+ status 'Installing NVIDIA repository...'
171
+ curl -fsSL -o $TEMP_DIR/cuda-keyring.deb https://developer.download.nvidia.com/compute/cuda/repos/$1$2/$(uname -m)/cuda-keyring_1.1-1_all.deb
172
+
173
+ case $1 in
174
+ debian)
175
+ status 'Enabling contrib sources...'
176
+ $SUDO sed 's/main/contrib/' < /etc/apt/sources.list | sudo tee /etc/apt/sources.list.d/contrib.list > /dev/null
177
+ ;;
178
+ esac
179
+
180
+ status 'Installing CUDA driver...'
181
+ $SUDO dpkg -i $TEMP_DIR/cuda-keyring.deb
182
+ $SUDO apt-get update
183
+
184
+ [ -n "$SUDO" ] && SUDO_E="$SUDO -E" || SUDO_E=
185
+ DEBIAN_FRONTEND=noninteractive $SUDO_E apt-get -y install cuda-drivers -q
186
+ }
187
+
188
+ if [ ! -f "/etc/os-release" ]; then
189
+ error "Unknown distribution. Skipping CUDA installation."
190
+ fi
191
+
192
+ . /etc/os-release
193
+
194
+ OS_NAME=$ID
195
+ OS_VERSION=$VERSION_ID
196
+
197
+ PACKAGE_MANAGER=
198
+ for PACKAGE_MANAGER in dnf yum apt-get; do
199
+ if available $PACKAGE_MANAGER; then
200
+ break
201
+ fi
202
+ done
203
+
204
+ if [ -z "$PACKAGE_MANAGER" ]; then
205
+ error "Unknown package manager. Skipping CUDA installation."
206
+ fi
207
+
208
+ if ! check_gpu nvidia-smi || [ -z "$(nvidia-smi | grep -o "CUDA Version: [0-9]*\.[0-9]*")" ]; then
209
+ case $OS_NAME in
210
+ centos|rhel) install_cuda_driver_yum 'rhel' $OS_VERSION ;;
211
+ rocky) install_cuda_driver_yum 'rhel' $(echo $OS_VERSION | cut -c1) ;;
212
+ fedora) install_cuda_driver_yum $OS_NAME $OS_VERSION ;;
213
+ amzn) install_cuda_driver_yum 'fedora' '35' ;;
214
+ debian) install_cuda_driver_apt $OS_NAME $OS_VERSION ;;
215
+ ubuntu) install_cuda_driver_apt $OS_NAME $(echo $OS_VERSION | sed 's/\.//') ;;
216
+ *) exit ;;
217
+ esac
218
+ fi
219
+
220
+ if ! lsmod | grep -q nvidia; then
221
+ KERNEL_RELEASE="$(uname -r)"
222
+ case $OS_NAME in
223
+ centos|rhel|rocky|amzn) $SUDO $PACKAGE_MANAGER -y install kernel-devel-$KERNEL_RELEASE kernel-headers-$KERNEL_RELEASE ;;
224
+ fedora) $SUDO $PACKAGE_MANAGER -y install kernel-devel-$KERNEL_RELEASE ;;
225
+ debian|ubuntu) $SUDO apt-get -y install linux-headers-$KERNEL_RELEASE ;;
226
+ *) exit ;;
227
+ esac
228
+
229
+ NVIDIA_CUDA_VERSION=$($SUDO dkms status | awk -F: '/added/ { print $1 }')
230
+ if [ -n "$NVIDIA_CUDA_VERSION" ]; then
231
+ $SUDO dkms install $NVIDIA_CUDA_VERSION
232
+ fi
233
+
234
+ if lsmod | grep -q nouveau; then
235
+ status 'Reboot to complete NVIDIA CUDA driver install.'
236
+ exit 0
237
+ fi
238
+
239
+ $SUDO modprobe nvidia
240
+ fi
241
+
242
+
243
+ status "NVIDIA CUDA drivers installed."
loader.Dockerfile ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM langchain/langchain
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y \
6
+ build-essential \
7
+ curl \
8
+ software-properties-common \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ COPY requirements.txt .
12
+
13
+ RUN pip install --upgrade -r requirements.txt
14
+
15
+ COPY loader.py .
16
+ COPY utils.py .
17
+ COPY chains.py .
18
+ COPY images ./images
19
+
20
+ EXPOSE 8502
21
+
22
+ HEALTHCHECK CMD curl --fail http://localhost:8502/_stcore/health
23
+
24
+ ENTRYPOINT ["streamlit", "run", "loader.py", "--server.port=8502", "--server.address=0.0.0.0"]
loader.py ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+ from dotenv import load_dotenv
4
+ from langchain_community.graphs import Neo4jGraph
5
+ import streamlit as st
6
+ from streamlit.logger import get_logger
7
+ from chains import load_embedding_model
8
+ from utils import create_constraints, create_vector_index
9
+ from PIL import Image
10
+
11
+ load_dotenv(".env")
12
+
13
+ url = os.getenv("NEO4J_URI")
14
+ username = os.getenv("NEO4J_USERNAME")
15
+ password = os.getenv("NEO4J_PASSWORD")
16
+ ollama_base_url = os.getenv("OLLAMA_BASE_URL")
17
+ embedding_model_name = os.getenv("EMBEDDING_MODEL")
18
+ # Remapping for Langchain Neo4j integration
19
+ os.environ["NEO4J_URL"] = url
20
+
21
+ logger = get_logger(__name__)
22
+
23
+ so_api_base_url = "https://api.stackexchange.com/2.3/search/advanced"
24
+
25
+ embeddings, dimension = load_embedding_model(
26
+ embedding_model_name, config={"ollama_base_url": ollama_base_url}, logger=logger
27
+ )
28
+
29
+ # if Neo4j is local, you can go to http://localhost:7474/ to browse the database
30
+ neo4j_graph = Neo4jGraph(
31
+ url=url, username=username, password=password, refresh_schema=False
32
+ )
33
+
34
+ create_constraints(neo4j_graph)
35
+ create_vector_index(neo4j_graph)
36
+
37
+
38
+ def load_so_data(tag: str = "neo4j", page: int = 1) -> None:
39
+ parameters = (
40
+ f"?pagesize=100&page={page}&order=desc&sort=creation&answers=1&tagged={tag}"
41
+ "&site=stackoverflow&filter=!*236eb_eL9rai)MOSNZ-6D3Q6ZKb0buI*IVotWaTb"
42
+ )
43
+ data = requests.get(so_api_base_url + parameters).json()
44
+ insert_so_data(data)
45
+
46
+
47
+ def load_high_score_so_data() -> None:
48
+ parameters = (
49
+ f"?fromdate=1664150400&order=desc&sort=votes&site=stackoverflow&"
50
+ "filter=!.DK56VBPooplF.)bWW5iOX32Fh1lcCkw1b_Y6Zkb7YD8.ZMhrR5.FRRsR6Z1uK8*Z5wPaONvyII"
51
+ )
52
+ data = requests.get(so_api_base_url + parameters).json()
53
+ insert_so_data(data)
54
+
55
+
56
+ def insert_so_data(data: dict) -> None:
57
+ # Calculate embedding values for questions and answers
58
+ for q in data["items"]:
59
+ question_text = q["title"] + "\n" + q["body_markdown"]
60
+ q["embedding"] = embeddings.embed_query(question_text)
61
+ for a in q["answers"]:
62
+ a["embedding"] = embeddings.embed_query(
63
+ question_text + "\n" + a["body_markdown"]
64
+ )
65
+
66
+ # Cypher, the query language of Neo4j, is used to import the data
67
+ # https://neo4j.com/docs/getting-started/cypher-intro/
68
+ # https://neo4j.com/docs/cypher-cheat-sheet/5/auradb-enterprise/
69
+ import_query = """
70
+ UNWIND $data AS q
71
+ MERGE (question:Question {id:q.question_id})
72
+ ON CREATE SET question.title = q.title, question.link = q.link, question.score = q.score,
73
+ question.favorite_count = q.favorite_count, question.creation_date = datetime({epochSeconds: q.creation_date}),
74
+ question.body = q.body_markdown, question.embedding = q.embedding
75
+ FOREACH (tagName IN q.tags |
76
+ MERGE (tag:Tag {name:tagName})
77
+ MERGE (question)-[:TAGGED]->(tag)
78
+ )
79
+ FOREACH (a IN q.answers |
80
+ MERGE (question)<-[:ANSWERS]-(answer:Answer {id:a.answer_id})
81
+ SET answer.is_accepted = a.is_accepted,
82
+ answer.score = a.score,
83
+ answer.creation_date = datetime({epochSeconds:a.creation_date}),
84
+ answer.body = a.body_markdown,
85
+ answer.embedding = a.embedding
86
+ MERGE (answerer:User {id:coalesce(a.owner.user_id, "deleted")})
87
+ ON CREATE SET answerer.display_name = a.owner.display_name,
88
+ answerer.reputation= a.owner.reputation
89
+ MERGE (answer)<-[:PROVIDED]-(answerer)
90
+ )
91
+ WITH * WHERE NOT q.owner.user_id IS NULL
92
+ MERGE (owner:User {id:q.owner.user_id})
93
+ ON CREATE SET owner.display_name = q.owner.display_name,
94
+ owner.reputation = q.owner.reputation
95
+ MERGE (owner)-[:ASKED]->(question)
96
+ """
97
+ neo4j_graph.query(import_query, {"data": data["items"]})
98
+
99
+
100
+ # Streamlit
101
+ def get_tag() -> str:
102
+ input_text = st.text_input(
103
+ "Which tag questions do you want to import?", value="neo4j"
104
+ )
105
+ return input_text
106
+
107
+
108
+ def get_pages():
109
+ col1, col2 = st.columns(2)
110
+ with col1:
111
+ num_pages = st.number_input(
112
+ "Number of pages (100 questions per page)", step=1, min_value=1
113
+ )
114
+ with col2:
115
+ start_page = st.number_input("Start page", step=1, min_value=1)
116
+ st.caption("Only questions with answers will be imported.")
117
+ return (int(num_pages), int(start_page))
118
+
119
+
120
+ def render_page():
121
+ datamodel_image = Image.open("./images/datamodel.png")
122
+ st.header("StackOverflow Loader")
123
+ st.subheader("Choose StackOverflow tags to load into Neo4j")
124
+ st.caption("Go to http://localhost:7474/ to explore the graph.")
125
+
126
+ user_input = get_tag()
127
+ num_pages, start_page = get_pages()
128
+
129
+ if st.button("Import", type="primary"):
130
+ with st.spinner("Loading... This might take a minute or two."):
131
+ try:
132
+ for page in range(1, num_pages + 1):
133
+ load_so_data(user_input, start_page + (page - 1))
134
+ st.success("Import successful", icon="✅")
135
+ st.caption("Data model")
136
+ st.image(datamodel_image)
137
+ st.caption("Go to http://localhost:7474/ to interact with the database")
138
+ except Exception as e:
139
+ st.error(f"Error: {e}", icon="🚨")
140
+ with st.expander("Highly ranked questions rather than tags?"):
141
+ if st.button("Import highly ranked questions"):
142
+ with st.spinner("Loading... This might take a minute or two."):
143
+ try:
144
+ load_high_score_so_data()
145
+ st.success("Import successful", icon="✅")
146
+ except Exception as e:
147
+ st.error(f"Error: {e}", icon="🚨")
148
+
149
+
150
+ render_page()
pdf_bot.Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM langchain/langchain
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y \
6
+ build-essential \
7
+ curl \
8
+ software-properties-common \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ COPY requirements.txt .
12
+
13
+ RUN pip install --upgrade -r requirements.txt
14
+
15
+ COPY pdf_bot.py .
16
+ COPY utils.py .
17
+ COPY chains.py .
18
+
19
+ EXPOSE 8503
20
+
21
+ HEALTHCHECK CMD curl --fail http://localhost:8503/_stcore/health
22
+
23
+ ENTRYPOINT ["streamlit", "run", "pdf_bot.py", "--server.port=8503", "--server.address=0.0.0.0"]
pdf_bot.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ import streamlit as st
4
+ from langchain.chains import RetrievalQA
5
+ from PyPDF2 import PdfReader
6
+ from langchain.callbacks.base import BaseCallbackHandler
7
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
8
+ from langchain_community.vectorstores import Neo4jVector
9
+ from streamlit.logger import get_logger
10
+ from chains import (
11
+ load_embedding_model,
12
+ load_llm,
13
+ )
14
+
15
+ # load api key lib
16
+ from dotenv import load_dotenv
17
+
18
+ load_dotenv(".env")
19
+
20
+
21
+ url = os.getenv("NEO4J_URI")
22
+ username = os.getenv("NEO4J_USERNAME")
23
+ password = os.getenv("NEO4J_PASSWORD")
24
+ ollama_base_url = os.getenv("OLLAMA_BASE_URL")
25
+ embedding_model_name = os.getenv("EMBEDDING_MODEL")
26
+ llm_name = os.getenv("LLM")
27
+ # Remapping for Langchain Neo4j integration
28
+ os.environ["NEO4J_URL"] = url
29
+
30
+ logger = get_logger(__name__)
31
+
32
+
33
+ embeddings, dimension = load_embedding_model(
34
+ embedding_model_name, config={"ollama_base_url": ollama_base_url}, logger=logger
35
+ )
36
+
37
+
38
+ class StreamHandler(BaseCallbackHandler):
39
+ def __init__(self, container, initial_text=""):
40
+ self.container = container
41
+ self.text = initial_text
42
+
43
+ def on_llm_new_token(self, token: str, **kwargs) -> None:
44
+ self.text += token
45
+ self.container.markdown(self.text)
46
+
47
+
48
+ llm = load_llm(llm_name, logger=logger, config={"ollama_base_url": ollama_base_url})
49
+
50
+
51
+ def main():
52
+ st.header("📄Chat with your pdf file")
53
+
54
+ # upload a your pdf file
55
+ pdf = st.file_uploader("Upload your PDF", type="pdf")
56
+
57
+ if pdf is not None:
58
+ pdf_reader = PdfReader(pdf)
59
+
60
+ text = ""
61
+ for page in pdf_reader.pages:
62
+ text += page.extract_text()
63
+
64
+ # langchain_textspliter
65
+ text_splitter = RecursiveCharacterTextSplitter(
66
+ chunk_size=1000, chunk_overlap=200, length_function=len
67
+ )
68
+
69
+ chunks = text_splitter.split_text(text=text)
70
+
71
+ # Store the chunks part in db (vector)
72
+ vectorstore = Neo4jVector.from_texts(
73
+ chunks,
74
+ url=url,
75
+ username=username,
76
+ password=password,
77
+ embedding=embeddings,
78
+ index_name="pdf_bot",
79
+ node_label="PdfBotChunk",
80
+ pre_delete_collection=True, # Delete existing PDF data
81
+ )
82
+ qa = RetrievalQA.from_chain_type(
83
+ llm=llm, chain_type="stuff", retriever=vectorstore.as_retriever()
84
+ )
85
+
86
+ # Accept user questions/query
87
+ query = st.text_input("Ask questions about your PDF file")
88
+
89
+ if query:
90
+ stream_handler = StreamHandler(st.empty())
91
+ qa.run(query, callbacks=[stream_handler])
92
+
93
+
94
+ if __name__ == "__main__":
95
+ main()
pull_model.Dockerfile ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #syntax = docker/dockerfile:1.4
2
+
3
+ FROM ollama/ollama:latest AS ollama
4
+ FROM babashka/babashka:latest
5
+
6
+ # just using as a client - never as a server
7
+ COPY --from=ollama /bin/ollama ./bin/ollama
8
+
9
+ COPY <<EOF pull_model.clj
10
+ (ns pull-model
11
+ (:require [babashka.process :as process]
12
+ [clojure.core.async :as async]))
13
+
14
+ (try
15
+ (let [llm (get (System/getenv) "LLM")
16
+ url (get (System/getenv) "OLLAMA_BASE_URL")]
17
+ (println (format "pulling ollama model %s using %s" llm url))
18
+ (if (and llm
19
+ url
20
+ (not (#{"gpt-4" "gpt-3.5" "claudev2" "gpt-4o" "gpt-4-turbo"} llm))
21
+ (not (some #(.startsWith llm %) ["ai21.jamba-instruct-v1:0"
22
+ "amazon.titan"
23
+ "anthropic.claude"
24
+ "cohere.command"
25
+ "meta.llama"
26
+ "mistral.mi"])))
27
+
28
+ ;; ----------------------------------------------------------------------
29
+ ;; just call `ollama pull` here - create OLLAMA_HOST from OLLAMA_BASE_URL
30
+ ;; ----------------------------------------------------------------------
31
+ ;; TODO - this still doesn't show progress properly when run from docker compose
32
+
33
+ (let [done (async/chan)]
34
+ (async/go-loop [n 0]
35
+ (let [[v _] (async/alts! [done (async/timeout 5000)])]
36
+ (if (= :stop v) :stopped (do (println (format "... pulling model (%ss) - will take several minutes" (* n 10))) (recur (inc n))))))
37
+ (process/shell {:env {"OLLAMA_HOST" url "HOME" (System/getProperty "user.home")} :out :inherit :err :inherit} (format "bash -c './bin/ollama show %s --modelfile > /dev/null || ./bin/ollama pull %s'" llm llm))
38
+ (async/>!! done :stop))
39
+
40
+ (println "OLLAMA model only pulled if both LLM and OLLAMA_BASE_URL are set and the LLM model is not gpt")))
41
+ (catch Throwable _ (System/exit 1)))
42
+ EOF
43
+
44
+ ENTRYPOINT ["bb", "-f", "pull_model.clj"]
45
+
requirements.txt ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ python-dotenv
2
+ wikipedia
3
+ tiktoken
4
+ neo4j
5
+ streamlit
6
+ Pillow
7
+ fastapi
8
+ PyPDF2
9
+ pydantic
10
+ uvicorn
11
+ sse-starlette
12
+ boto3
13
+ streamlit==1.32.1
14
+ # missing from the langchain base image?
15
+ langchain-openai==0.2.4
16
+ langchain-community==0.3.3
17
+ langchain-google-genai==2.0.3
18
+ langchain-ollama==0.2.0
19
+ langchain-huggingface==0.1.1
20
+ langchain-aws==0.2.4
running_on_wsl.md ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Run the stack on WSL
2
+
3
+ Note that for the stack to work on Windows, you should have running version on ollama installed somehow. Since Windows, is not yet supported, we can only use WSL.
4
+
5
+ I won't cover the activation of WSL procedure which you will find very easy on internet. Assuming that you have a version of WSL on your local windows machine and a running docker Desktop software installer and running as well, you can follow the following steps:
6
+
7
+ 1. enable docker-desktop to use WSL using the following tutorial:
8
+
9
+ >To connect WSL to Docker Desktop, you need to follow the instructions below:
10
+ >
11
+ >1. First, ensure that you have installed Docker Desktop for Windows on your machine. If you haven't, you can download it from ¹.
12
+ >2. Next, open Docker Desktop and navigate to **Settings**.
13
+ >3. From the **Settings** menu, select **Resources** and then click on **WSL Integration**.
14
+ >4. On the **WSL Integration** page, you will see a list of available WSL distributions. Select the distribution that you want to connect to Docker Desktop.
15
+ >5. Once you have selected the distribution, click on **Apply & Restart**.
16
+ >
17
+ >After following these steps, your WSL distribution should be connected to Docker Desktop.
18
+ >
19
+ >
20
+ >1. Docker Desktop WSL 2 backend on Windows | Docker Docs. https://docs.docker.com/desktop/wsl/.
21
+ >2. Get started with Docker containers on WSL | Microsoft Learn. https://learn.microsoft.com/en-us/windows/wsl/tutorials/wsl-containers.
22
+ >3. How to configure Docker Desktop to work with the WSL. https://tutorials.releaseworksacademy.com/learn/how-to-configure-docker-desktop-to-work-with-the-wsl.html.
23
+ >4. How can I access wsl2 which is used by Docker desktop?. https://stackoverflow.com/questions/70449927/how-can-i-access-wsl2-which-is-used-by-docker-desktop.
24
+
25
+ After the activation enter into the WSL with the command `wsl` and type `docker`.
26
+
27
+ 2. Install ollama on WSL using https://github.com/jmorganca/ollama (avec la commande `curl https://ollama.ai/install.sh | sh`)
28
+ - The script have been downloaded in `./install_ollama.sh` and you do the smae thing with `sh ./install_ollama.sh`
29
+ - To list the downloaded model: `ollama list`. This command could lead to:
30
+ ```sh
31
+ NAME ID SIZE MODIFIED
32
+ llama2:latest 7da22eda89ac 3.8 GB 22 minutes ago
33
+ ```
34
+ - (OPTIONAL) To remove model: `ollama rm llama2`
35
+ - To run the ollama on WSL: `ollama run llama2`
36
+
37
+ 3. clone the repo
38
+ 4. cd into the repo and enter wsl
39
+ 5. run `docker-compose up`
40
+
41
+ # Run the stack on MAC:
42
+
43
+ On MAC you can follow this tutorial: https://collabnix.com/getting-started-with-genai-stack-powered-with-docker-langchain-neo4j-and-ollama/
utils.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class BaseLogger:
2
+ def __init__(self) -> None:
3
+ self.info = print
4
+
5
+
6
+ def extract_title_and_question(input_string):
7
+ lines = input_string.strip().split("\n")
8
+
9
+ title = ""
10
+ question = ""
11
+ is_question = False # flag to know if we are inside a "Question" block
12
+
13
+ for line in lines:
14
+ if line.startswith("Title:"):
15
+ title = line.split("Title: ", 1)[1].strip()
16
+ elif line.startswith("Question:"):
17
+ question = line.split("Question: ", 1)[1].strip()
18
+ is_question = (
19
+ True # set the flag to True once we encounter a "Question:" line
20
+ )
21
+ elif is_question:
22
+ # if the line does not start with "Question:" but we are inside a "Question" block,
23
+ # then it is a continuation of the question
24
+ question += "\n" + line.strip()
25
+
26
+ return title, question
27
+
28
+
29
+ def create_vector_index(driver) -> None:
30
+ index_query = "CREATE VECTOR INDEX stackoverflow IF NOT EXISTS FOR (m:Question) ON m.embedding"
31
+ try:
32
+ driver.query(index_query)
33
+ except: # Already exists
34
+ pass
35
+ index_query = "CREATE VECTOR INDEX top_answers IF NOT EXISTS FOR (m:Answer) ON m.embedding"
36
+ try:
37
+ driver.query(index_query)
38
+ except: # Already exists
39
+ pass
40
+
41
+
42
+ def create_constraints(driver):
43
+ driver.query(
44
+ "CREATE CONSTRAINT question_id IF NOT EXISTS FOR (q:Question) REQUIRE (q.id) IS UNIQUE"
45
+ )
46
+ driver.query(
47
+ "CREATE CONSTRAINT answer_id IF NOT EXISTS FOR (a:Answer) REQUIRE (a.id) IS UNIQUE"
48
+ )
49
+ driver.query(
50
+ "CREATE CONSTRAINT user_id IF NOT EXISTS FOR (u:User) REQUIRE (u.id) IS UNIQUE"
51
+ )
52
+ driver.query(
53
+ "CREATE CONSTRAINT tag_name IF NOT EXISTS FOR (t:Tag) REQUIRE (t.name) IS UNIQUE"
54
+ )